diff --git a/README.md b/README.md
index 141bfa1..659dc69 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,8 @@
# MATLAB®/SimBiology® DevOps Workflow Example
This workshop provides hands-on experience using some of MATLAB's powerful software testing and automation features.
-It is based on the workshop [Generating Tests for Your MATLAB® Code](https://github.com/mathworks/Generating-Tests-for-Your-MATLAB-Code-Workshop).
+It showcases the development of a Web App to simulate a SimBiology model. The App is developed using the [model-view-controller (MVC)](https://www.mathworks.com/company/technical-articles/developing-matlab-apps-using-the-model-view-controller-pattern.html) software architecture pattern and is
+based on the workshop [Generating Tests for Your MATLAB® Code](https://github.com/mathworks/Generating-Tests-for-Your-MATLAB-Code-Workshop) developed by [Adam Sifounakis](https://github.com/asifouna).
## About the workshop
@@ -27,4 +28,4 @@ Step-by-step workshop instructions can be found in:
* [WorkshopGuide.m](WorkshopGuide.m)
-Copyright 2025 The MathWorks, Inc.
+Copyright 2026 The MathWorks, Inc.
diff --git a/WorkshopGuide.md b/WorkshopGuide.md
index 30b1c3b..2292787 100644
--- a/WorkshopGuide.md
+++ b/WorkshopGuide.md
@@ -19,19 +19,19 @@ In this workshop, you will:
## Table of Contents
[Workshop Requirements](#H_34C2FB57)
- [Part 1: Getting the workshop files and configuring GitHub for automated testing and results publishing](#TMP_877c)
+ [Part 1: Getting the workshop files and configuring GitHub for automated testing and results publishing](#TMP_3984)
- [Part 2: Generating your first tests](#TMP_020d)
+ [Part 2: Generating your first tests](#TMP_95f4)
- [Part 3: Finding existing tests and measuring coverage](#TMP_4b61)
+ [Part 3: Finding existing tests and measuring coverage](#TMP_8279)
- [Part 4: Updating badges, committing our changes, and pushing to GitHub](#TMP_74db)
+ [Part 4: Updating badges, committing our changes, and pushing to GitHub](#TMP_1d39)
- [Part 5: Create a pull request, watch GitHub Actions automatically test your changes and publish results](#TMP_651a)
+ [Part 5: Create a pull request, watch GitHub Actions automatically test your changes and publish results](#TMP_2a59)
- [Part 6: Compile the App in the CI workflow and download the artifact](#TMP_85dd)
+ [Part 6: Compile the App in the CI workflow and download the artifact](#TMP_2c41)
- [Workshop wrap\-up and additional information](#TMP_230e)
+ [Workshop wrap\-up and additional information](#TMP_86ec)
@@ -56,7 +56,7 @@ The following steps cover all of the things you will need to successfully comple
- The workshop leverages the free repository and CI capabilities offered by GitHub and GitHub Actions
- Go to: [**https://github.com/signup**](https://github.com/signup)
-
+
# Part 1: Getting the workshop files and configuring GitHub for automated testing and results publishing
@@ -308,7 +308,7 @@ Click on the 'Run App' shortcut to start the app in MATLAB:

-
+
# Part 2: Generating your first tests
@@ -529,7 +529,7 @@ Congratulations! You just created multiple tests for your MATLAB code!
It was easier than you thought, right?
-
+
# Part 3: Finding existing tests and measuring coverage
@@ -868,7 +868,7 @@ It looks like we've achieved full statement coverage for [`generateSimFun`](./co

-
+
# Part 4: Updating badges, committing our changes, and pushing to GitHub
@@ -1088,7 +1088,7 @@ At this point, all of your changes will be pushed to GitHub.

-
+
# Part 5: Create a pull request, watch GitHub Actions automatically test your changes and publish results
@@ -1249,7 +1249,7 @@ The code coverage report looks like this:
Now anyone that visits your repository can immediately see the quality of your code, explore your test and code coverage results, and will have more confidence in the code you are writing!
-
+
# Part 6: Compile the App in the CI workflow and download the artifact
@@ -1353,7 +1353,7 @@ You can now download the CTF file and upload it to your Web App Server using you
[https://vdi\-wd1ah2\-348.dhcp.mathworks.com:9999/webapps/home/login.html](https://vdi-wd1ah2-348.dhcp.mathworks.com:9999/webapps/home/login.html)
-
+
# Workshop wrap\-up and additional information
diff --git a/code/ConcTimecourseView.m b/code/ConcTimecourseView.m
index 57ca81c..48da3b3 100644
--- a/code/ConcTimecourseView.m
+++ b/code/ConcTimecourseView.m
@@ -1,8 +1,6 @@
classdef ConcTimecourseView < handle
- properties ( Access = private )
- Model
-
+ properties ( Access = private )
ConcColors = [0.30,0.75,0.93;...
0.86,0.55,0.41;...
0.91,0.73,0.42]; % colors to plot concentrations
@@ -60,7 +58,6 @@
obj.DataListener = dataListener;
% save objects
- obj.Model = model;
obj.Axes = ax;
end % constructor
@@ -70,8 +67,8 @@
methods ( Access = private )
- function update(obj,~,~)
- t = obj.Model.SimDataTable;
+ function update(obj,srcModel,~)
+ t = srcModel.SimDataTable;
set(obj.lhDrug,'XData',t.Time, 'YData',t.Drug);
set(obj.lhReceptor,'XData',t.Time, 'YData',t.Receptor);
diff --git a/code/LampView.m b/code/LampView.m
index 5bb823b..9e2d1b0 100644
--- a/code/LampView.m
+++ b/code/LampView.m
@@ -15,10 +15,6 @@
LampObj
end
- properties ( Access=private )
- Model
- end
-
properties( Access = private )
DataListener % listener
end
@@ -42,7 +38,6 @@
obj.DataListener = dataListener;
obj.LampObj = lampObj;
- obj.Model = model;
end % constructor
@@ -68,8 +63,8 @@
methods ( Access = private )
- function update(obj,~,~)
- obj.IsOn = obj.Model.ROIsBetweenThresholds;
+ function update(obj,srcModel,~)
+ obj.IsOn = srcModel.ROIsBetweenThresholds;
end % update
end % private method
diff --git a/code/NCAView.m b/code/NCAView.m
index db36c3e..1e658d5 100644
--- a/code/NCAView.m
+++ b/code/NCAView.m
@@ -1,7 +1,6 @@
classdef NCAView < handle
- properties ( Access = private )
- Model
+ properties ( Access = private )
NCApanel
GridLayout
BackgroundColor = [1,1,1]
@@ -67,7 +66,6 @@
obj.DataListener = dataListener;
% save objects
- obj.Model = model;
obj.NCAoptions = opt;
obj.NCApanel = ncapanel;
obj.GridLayout = gl;
@@ -89,10 +87,10 @@
methods ( Access = private )
- function update(obj,~,~)
+ function update(obj,srcModel,~)
% compute NCA parameters and display them in table
- ncaParameters = sbionca(obj.Model.SimDataTable, obj.NCAoptions);
+ ncaParameters = sbionca(srcModel.SimDataTable, obj.NCAoptions);
obj.NCAtable.ColumnName = ncaParameters.Properties.VariableNames(2:end);
obj.NCAtable.Data = ncaParameters(:,2:end);
diff --git a/code/ROTimecourseView.m b/code/ROTimecourseView.m
index cea3fbb..eeda351 100644
--- a/code/ROTimecourseView.m
+++ b/code/ROTimecourseView.m
@@ -1,8 +1,6 @@
classdef ROTimecourseView < handle
- properties ( Access = private )
- Model
-
+ properties ( Access = private )
ThresholdStyle = {'Color','r','Linewidth',2,'LineStyle','--',...
'FontWeight','bold','LabelVerticalAlignment','middle'}; % style for threshold lines
@@ -60,7 +58,6 @@
obj.DataListener = dataListener;
% save objects
- obj.Model = model;
obj.Axes = ax;
end % constructor
@@ -70,8 +67,8 @@
methods ( Access = private )
- function update(obj,~,~)
- t = obj.Model.SimDataTable;
+ function update(obj,srcModel,~)
+ t = srcModel.SimDataTable;
set(obj.lhRO,'XData',t.Time, 'YData',100*t.RO);
diff --git a/resources/project/Project.xml b/resources/project/Project.xml
index b4141a8..dfe1ea3 100644
--- a/resources/project/Project.xml
+++ b/resources/project/Project.xml
@@ -138,6 +138,13 @@
+
+
+
+
+
+
+
diff --git a/tests/tLampView.m b/tests/tLampView.m
new file mode 100644
index 0000000..2e85a94
--- /dev/null
+++ b/tests/tLampView.m
@@ -0,0 +1,72 @@
+% This test file was generated by Copilot. Validate generated output before use.
+classdef tLampView < matlab.unittest.TestCase
+ properties
+ Model
+ LampViewObj
+ Parent
+ end
+
+ methods(TestMethodSetup)
+ function createLampView(testCase)
+ testCase.Parent = figure('Visible', 'off'); % Create a hidden figure for the lamp
+ testCase.Model = SimulationModel(); % Assuming SimulationModel is defined elsewhere
+ testCase.LampViewObj = LampView(testCase.Parent, testCase.Model);
+ end
+ end
+
+ methods(Test)
+ function testIsOnTrue(testCase)
+ simulate(testCase.Model, [0.5234,0.0485,0.0934,119,150,24]);
+
+ testCase.verifyTrue(testCase.LampViewObj.IsOn);
+ end
+
+ function testIsOnFalse(testCase)
+ simulate(testCase.Model, [0.5234,0.0485,0.0934,119,200,24]);
+
+ testCase.verifyFalse(testCase.LampViewObj.IsOn);
+ end
+
+ function testLampColorSuccess(testCase)
+ testCase.LampViewObj.IsOn = true;
+ expectedColor = testCase.LampViewObj.LampColorSucess;
+
+ actualColor = testCase.LampViewObj.LampObj.Color;
+
+ testCase.verifyEqual(actualColor, expectedColor, 'Lamp color should be success color when IsOn is true.');
+ end
+
+ function testLampColorFailure(testCase)
+ testCase.LampViewObj.IsOn = false;
+ expectedColor = testCase.LampViewObj.LampColorFailure;
+
+ actualColor = testCase.LampViewObj.LampObj.Color;
+
+ testCase.verifyEqual(actualColor, expectedColor, 'Lamp color should be failure color when IsOn is false.');
+ end
+
+ function testTooltipSuccess(testCase)
+ testCase.LampViewObj.IsOn = true;
+ expectedTooltip = char(compose("Target occupancy remains\n between thresholds"));
+
+ actualTooltip = testCase.LampViewObj.LampObj.Tooltip;
+
+ testCase.verifyEqual(actualTooltip, expectedTooltip, 'Tooltip should indicate success when IsOn is true.');
+ end
+
+ function testTooltipFailure(testCase)
+ testCase.LampViewObj.IsOn = false;
+ expectedTooltip = char(compose("Target occupancy does not remain\n between thresholds"));
+
+ actualTooltip = testCase.LampViewObj.LampObj.Tooltip;
+
+ testCase.verifyEqual(actualTooltip, expectedTooltip, 'Tooltip should indicate failure when IsOn is false.');
+ end
+ end
+
+ methods(TestMethodTeardown)
+ function closeFigure(testCase)
+ close(testCase.Parent);
+ end
+ end
+end
\ No newline at end of file