Skip to content

Commit d37d1b3

Browse files
authored
Merge pull request #165 from ImperialCollegeLondon/task/notifications
task: Notification on failed app checks
2 parents f9d0ce7 + 122d29f commit d37d1b3

File tree

23 files changed

+163
-56
lines changed

23 files changed

+163
-56
lines changed

app/DataVisualization.m

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@
4848
ResultsManager mag.app.manage.Manager {mustBeScalarOrEmpty}
4949
ExportManager mag.app.manage.ExportManager {mustBeScalarOrEmpty}
5050
VisualizationManager mag.app.manage.VisualizationManager {mustBeScalarOrEmpty}
51-
AppNotificationHandler mag.app.internal.AppNotificationHandler {mustBeScalarOrEmpty}
51+
NotificationHandler mag.app.internal.NotificationHandler {mustBeScalarOrEmpty}
5252
end
5353

5454
properties (SetObservable, SetAccess = private)
@@ -74,7 +74,7 @@
7474
app.ToolbarManager = mag.app.manage.ToolbarManager(app, pathToAppIcons);
7575
app.ToolbarManager.instantiate(app.UIFigure);
7676

77-
app.AppNotificationHandler = mag.app.internal.AppNotificationHandler(app.UIFigure, app.ToolbarManager);
77+
app.NotificationHandler = mag.app.internal.NotificationHandler(app.UIFigure, app.ToolbarManager);
7878

7979
% Initialize app based on mission.
8080
try
@@ -131,7 +131,7 @@ function selectMission(app, mission)
131131
app.SelectMissionDialog.delete();
132132
end
133133

134-
closeProgressBar = app.AppNotificationHandler.overlayProgressBar("Initializing mission..."); %#ok<NASGU>
134+
closeProgressBar = app.NotificationHandler.overlayProgressBar("Initializing mission..."); %#ok<NASGU>
135135
end
136136

137137
app.Mission = mission;
@@ -188,7 +188,7 @@ function modelChangedCallback(app, model, ~)
188188
hasData = hasModel && (model.Analysis.Results.HasScience || model.Analysis.Results.HasHK);
189189

190190
if hasModel && ~hasData
191-
app.AppNotificationHandler.displayAlert("No HK or science data detected.", "No Data", "warning");
191+
app.NotificationHandler.displayAlert("No HK or science data detected.", "No Data", "warning");
192192
end
193193

194194
status = matlab.lang.OnOffSwitchState(hasData);
@@ -205,13 +205,13 @@ function figuresChanged(app, varargin)
205205

206206
function processDataButtonPushed(app)
207207

208-
closeProgressBar = app.AppNotificationHandler.overlayProgressBar("Processing data..."); %#ok<NASGU>
208+
closeProgressBar = app.NotificationHandler.overlayProgressBar("Processing data..."); %#ok<NASGU>
209209
restoreWarningState = app.disableWarningStackTrace(); %#ok<NASGU>
210210

211211
try
212212
app.Model.analyze(app.AnalysisManager.getAnalysisOptions());
213213
catch exception
214-
app.AppNotificationHandler.displayAlert(exception);
214+
app.NotificationHandler.displayAlert(exception);
215215
end
216216
end
217217

@@ -229,7 +229,7 @@ function resetButtonPushed(app)
229229

230230
function exportButtonPushed(app)
231231

232-
closeProgressBar = app.AppNotificationHandler.overlayProgressBar("Exporting..."); %#ok<NASGU>
232+
closeProgressBar = app.NotificationHandler.overlayProgressBar("Exporting..."); %#ok<NASGU>
233233
restoreWarningState = app.disableWarningStackTrace(); %#ok<NASGU>
234234

235235
format = app.ExportFormatDropDown.Value;
@@ -267,34 +267,34 @@ case cellstr(app.ExportManager.SupportedFormats)
267267
try
268268
app.Model.export(app.ExportManager.getExportOptions(format, app.ResultsLocation));
269269
catch exception
270-
app.AppNotificationHandler.displayAlert(exception);
270+
app.NotificationHandler.displayAlert(exception);
271271
end
272272
otherwise
273-
app.AppNotificationHandler.displayAlert(compose("Unrecognized export format option ""%s"".", format));
273+
app.NotificationHandler.displayAlert(compose("Unrecognized export format option ""%s"".", format));
274274
end
275275
end
276276

277277
function showFiguresButtonPushed(app)
278278

279-
closeProgressBar = app.AppNotificationHandler.overlayProgressBar("Plotting data..."); %#ok<NASGU>
279+
closeProgressBar = app.NotificationHandler.overlayProgressBar("Plotting data..."); %#ok<NASGU>
280280
restoreWarningState = app.disableWarningStackTrace(); %#ok<NASGU>
281281

282282
try
283283
app.Figures = [app.Figures, app.VisualizationManager.visualize(app.Model.Analysis)];
284284
catch exception
285-
app.AppNotificationHandler.displayAlert(exception);
285+
app.NotificationHandler.displayAlert(exception);
286286
end
287287
end
288288

289289
function saveFiguresButtonPushed(app)
290290

291-
closeProgressBar = app.AppNotificationHandler.overlayProgressBar("Saving figures..."); %#ok<NASGU>
291+
closeProgressBar = app.NotificationHandler.overlayProgressBar("Saving figures..."); %#ok<NASGU>
292292
restoreWarningState = app.disableWarningStackTrace(); %#ok<NASGU>
293293

294294
try
295295
mag.graphics.savePlots(app.Figures, app.ResultsLocation);
296296
catch exception
297-
app.AppNotificationHandler.displayAlert(exception);
297+
app.NotificationHandler.displayAlert(exception);
298298
end
299299
end
300300

@@ -304,7 +304,7 @@ function closeFiguresButtonPushed(app)
304304

305305
if ~isempty(app.Figures) && any(isValidFigures)
306306

307-
closeProgressBar = app.AppNotificationHandler.overlayProgressBar("Closing figures..."); %#ok<NASGU>
307+
closeProgressBar = app.NotificationHandler.overlayProgressBar("Closing figures..."); %#ok<NASGU>
308308
restoreWarningState = app.disableWarningStackTrace(); %#ok<NASGU>
309309

310310
close(app.Figures(isValidFigures));

app/core/+mag/+app/+internal/AppNotificationHandler.m renamed to app/core/+mag/+app/+internal/NotificationHandler.m

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
classdef AppNotificationHandler < handle
2-
% APPNOTIFICATIONHANDLER Handle notifications for app components.
1+
classdef NotificationHandler < handle
2+
% NOTIFICATIONHANDLER Handle notifications for app components.
33

44
properties (Access = private)
55
UIFigure matlab.ui.Figure {mustBeScalarOrEmpty}
@@ -8,7 +8,7 @@
88

99
methods
1010

11-
function this = AppNotificationHandler(uiFigure, toolbarManager)
11+
function this = NotificationHandler(uiFigure, toolbarManager)
1212

1313
this.UIFigure = uiFigure;
1414
this.ToolbarManager = toolbarManager;

app/core/+mag/+app/+manage/ToolbarManager.m

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -119,17 +119,17 @@ function missionPushToolClicked(this)
119119

120120
function importPushToolClicked(this)
121121

122-
closeProgressBar = this.App.AppNotificationHandler.overlayProgressBar("Importing..."); %#ok<NASGU>
122+
closeProgressBar = this.App.NotificationHandler.overlayProgressBar("Importing..."); %#ok<NASGU>
123123
[file, folder] = uigetfile("*.mat", "Import Analysis");
124124

125125
if ~isequal(file, 0) && ~isequal(folder, 0)
126126

127127
try
128128

129129
this.App.Model.load(fullfile(folder, file));
130-
this.App.AppNotificationHandler.displayAlert("Analysis successfully imported.", "Import Complete", "success");
130+
this.App.NotificationHandler.displayAlert("Analysis successfully imported.", "Import Complete", "success");
131131
catch exception
132-
this.App.AppNotificationHandler.displayAlert(exception);
132+
this.App.NotificationHandler.displayAlert(exception);
133133
end
134134
end
135135
end
@@ -141,7 +141,7 @@ function debugToggleToolOn(this)
141141
if isempty(this.PreviousError)
142142

143143
this.DebugToggleTool.State = "off";
144-
this.App.AppNotificationHandler.displayAlert("No error found.", "No Errors", "warning");
144+
this.App.NotificationHandler.displayAlert("No error found.", "No Errors", "warning");
145145
return;
146146
end
147147

@@ -170,7 +170,7 @@ function helpPushToolClicked(this)
170170

171171
web("https://github.com/ImperialCollegeLondon/MAG-Data-Visualization-Toolbox/issues/new/choose");
172172

173-
this.App.AppNotificationHandler.displayAlert("Create issue on GitHub to share feedback, report issues and ask questions.", ...
173+
this.App.NotificationHandler.displayAlert("Create issue on GitHub to share feedback, report issues and ask questions.", ...
174174
"Create GitHub Issue", "info");
175175
end
176176
end

app/mission/imap/+mag/+app/+imap/+control/CPT.m

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -93,13 +93,15 @@ function instantiate(this, parent)
9393

9494
methods (Access = private)
9595

96-
function validatePattern(~, changingData)
96+
function validatePattern(~, changedData)
9797

98-
value = changingData.Value;
98+
value = changedData.Value;
9999
pattern = asManyOfPattern(digitsPattern() + optionalPattern(characterListPattern(",") + whitespacePattern()));
100100

101101
if ~matches(value, pattern)
102-
error("mag:app:InvalidPattern", "Value must match the pattern ""1, 2, 3"".");
102+
103+
[~, figure] = gcbo();
104+
uialert(figure, "Value must match the pattern ""1, 2, 3"".", "Invalid Pattern");
103105
end
104106
end
105107
end

resources/release-notes.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@
22

33
- (All) Add shortcut to open app from toolbar (quick access)
44
- (All) Fix errors when closing app during mission selection
5+
- (IMAP) Replace `error` with `uialert` for CPT view when mode or range patterns are incorrect
56

67
## Software
78

89
- (All) Add `mag.mixin.TimeSeriesOperationSupport` for simplified operations support
910
- (All) Fix issues with colors and legends for multiple stackedplots
11+
- (IMAP) Fix cropping of events when no events are loaded
1012
- (IMAP) Check there is data before enabling auto-compression event in `mag.imap.view.Field`
1113

1214
## Package

src/mission/imap/+mag/+imap/@Analysis/loadEventsData.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -292,7 +292,7 @@ function loadEventsData(this)
292292
%% Amend Time Range
293293

294294
% Concentrate on recorded timerange.
295-
if ~isempty(this.Results.Metadata) && ~ismissing(this.Results.Metadata.Timestamp)
295+
if ~isempty(events) && ~isempty(this.Results.Metadata) && ~ismissing(this.Results.Metadata.Timestamp)
296296
events = events([events.Timestamp] > this.Results.Metadata.Timestamp);
297297
end
298298

tests/system/analyze/AnalysisTestCase.m

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,10 @@
1-
classdef (Abstract) AnalysisTestCase < matlab.unittest.TestCase
1+
classdef (Abstract) AnalysisTestCase < mag.test.case.UITestCase
22
% ANALYSISTESTCASE Base class for all MAG analysis tests.
33

44
properties (Access = protected)
55
WorkingDirectory (1, 1) matlab.unittest.fixtures.WorkingFolderFixture
66
end
77

8-
methods (TestClassSetup)
9-
10-
function useMATLABR2024bOrAbove(testCase)
11-
testCase.assumeFalse(isMATLABReleaseOlderThan("R2024b"), "Only MATLAB R2024b or later is supported for this test.");
12-
end
13-
end
14-
158
methods (TestMethodSetup)
169

1710
function setUpWorkingDirectory(testCase)

tests/system/app/AppTestCase.m

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,6 @@
1-
classdef (Abstract) AppTestCase < matlab.uitest.TestCase & mag.test.GraphicsTestCase
1+
classdef (Abstract) AppTestCase < mag.test.case.UITestCase & mag.test.case.GraphicsTestCase
22
% APPTESTCASE Base class for all MAG app tests.
33

4-
methods (TestClassSetup)
5-
6-
function useMATLABR2024bOrAbove(testCase)
7-
testCase.assumeFalse(isMATLABReleaseOlderThan("R2024b"), "Only MATLAB R2024b or later is supported for this test.");
8-
end
9-
end
10-
114
methods (Access = protected)
125

136
function copyDataToWorkingDirectory(testCase, workingDirectory, testFolder)
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
classdef (Abstract) UITestCase < matlab.uitest.TestCase
2+
% UITESTCASE Base class for all MAG UI tests.
3+
4+
methods (TestClassSetup)
5+
6+
% Do not run tests in R2024a and older as not all functionality is
7+
% supported.
8+
function useMATLABR2024bOrAbove(testCase)
9+
testCase.assumeFalse(isMATLABReleaseOlderThan("R2024b"), "Only MATLAB R2024b or later is supported for this test.");
10+
end
11+
end
12+
end

tests/tool/+mag/+test/ViewControllerTestCase.m renamed to tests/tool/+mag/+test/+case/ViewControllerTestCase.m

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
classdef (Abstract) ViewControllerTestCase < mag.test.GraphicsTestCase
1+
classdef (Abstract) ViewControllerTestCase < mag.test.case.GraphicsTestCase
22
% VIEWCONTROLLERTESTCASE Base class for all view-controller tests.
33

44
properties (Constant, Access = protected)
@@ -7,10 +7,15 @@
77

88
methods (Access = protected)
99

10-
function panel = createTestPanel(testCase)
10+
function panel = createTestPanel(testCase, options)
1111
% CREATETESTPANEL Create "uipanel" to add controls to.
1212

13-
f = uifigure();
13+
arguments
14+
testCase
15+
options.VisibleOverride (1, 1) matlab.lang.OnOffSwitchState = "off"
16+
end
17+
18+
f = uifigure(Visible = options.VisibleOverride);
1419
panel = uipanel(f, Position = [1, 1, f.InnerPosition(3:4)]);
1520

1621
testCase.addTeardown(@() close(f));

tests/unit/app/imap/tCPT.m

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
classdef tCPT < mag.test.case.UITestCase & mag.test.case.ViewControllerTestCase
2+
% TCPT Unit tests for "mag.app.imap.control.CPT" class.
3+
4+
properties (TestParameter)
5+
PatternField = {"PrimaryModePatternField", "SecondaryModePatternField", "RangePatternField"}
6+
end
7+
8+
methods (Test)
9+
10+
% Test that "instantiate" creates expected elements.
11+
function instantiate(testCase)
12+
13+
% Set up.
14+
panel = testCase.createTestPanel();
15+
cpt = mag.app.imap.control.CPT();
16+
17+
% Exercise.
18+
cpt.instantiate(panel);
19+
20+
% Verify.
21+
testCase.assertNotEmpty(cpt.StartFilterSpinner, "Start filter spinner should not be empty.");
22+
testCase.assertNotEmpty(cpt.PrimaryModePatternField, "Primary mode pattern field should not be empty.");
23+
testCase.assertNotEmpty(cpt.SecondaryModePatternField, "Secondary mode pattern field should not be empty.");
24+
testCase.assertNotEmpty(cpt.RangePatternField, "Range pattern field should not be empty.");
25+
26+
testCase.verifyEqual(cpt.StartFilterSpinner.Layout, matlab.ui.layout.GridLayoutOptions(Row = 1, Column = [2, 3]), ...
27+
"Start filter spinner layout should match expectation.");
28+
29+
testCase.verifyEqual(cpt.PrimaryModePatternField.Value, '2, 64, 2, 4, 64, 4, 4, 128', "Primary mode pattern field value should match expectation.");
30+
testCase.verifyEqual(cpt.PrimaryModePatternField.Layout, matlab.ui.layout.GridLayoutOptions(Row = 2, Column = [2, 3]), ...
31+
"Primary mode pattern field layout should match expectation.");
32+
33+
testCase.verifyEqual(cpt.SecondaryModePatternField.Value, '2, 8, 2, 1, 64, 1, 4, 128', "Secondary mode pattern field value should match expectation.");
34+
testCase.verifyEqual(cpt.SecondaryModePatternField.Layout, matlab.ui.layout.GridLayoutOptions(Row = 3, Column = [2, 3]), ...
35+
"Secondary mode pattern field layout should match expectation.");
36+
37+
testCase.verifyEqual(cpt.RangePatternField.Value, '3, 2, 1, 0', "Range pattern field value should match expectation.");
38+
testCase.verifyEqual(cpt.RangePatternField.Layout, matlab.ui.layout.GridLayoutOptions(Row = 4, Column = [2, 3]), ...
39+
"Range pattern field layout should match expectation.");
40+
end
41+
42+
% Test that "getVisualizeCommand" returns expected command.
43+
function getVisualizeCommand(testCase)
44+
45+
% Set up.
46+
panel = testCase.createTestPanel();
47+
48+
cpt = mag.app.imap.control.CPT();
49+
cpt.instantiate(panel);
50+
51+
results = mag.imap.Analysis();
52+
53+
% Exercise.
54+
command = cpt.getVisualizeCommand(results);
55+
56+
% Verify.
57+
testCase.verifyEqual(command.PositionalArguments, {results}, "Visualize command positional arguments should match expectation.");
58+
59+
for f = ["Filter", "PrimaryModePattern", "SecondaryModePattern", "RangePattern"]
60+
testCase.assertThat(command.NamedArguments, mag.test.constraint.IsField(f), compose("""%s"" should be a named argument.", f));
61+
end
62+
63+
testCase.verifyEqual(command.NamedArguments.Filter, minutes(1), """Filter"" should match expectation.");
64+
testCase.verifyEqual(command.NamedArguments.PrimaryModePattern, [2, 64, 2, 4, 64, 4, 4, 128], """PrimaryModePattern"" should match expectation.");
65+
testCase.verifyEqual(command.NamedArguments.SecondaryModePattern, [2, 8, 2, 1, 64, 1, 4, 128], """SecondaryModePattern"" should match expectation.");
66+
testCase.verifyEqual(command.NamedArguments.RangePattern, [3, 2, 1, 0], """RangePattern"" should match expectation.");
67+
end
68+
69+
% Test that error is thrown if patterns do not match expected
70+
% format.
71+
function pattern_validFormat(testCase, PatternField)
72+
73+
% Set up.
74+
panel = testCase.createTestPanel(VisibleOverride = "on");
75+
76+
cpt = mag.app.imap.control.CPT();
77+
cpt.instantiate(panel);
78+
79+
% Exercise and verify.
80+
testCase.type(cpt.(PatternField), "1, 2, 3");
81+
end
82+
83+
% Test that alert is shown if patterns do not match expected
84+
% format.
85+
function pattern_invalidFormat(testCase, PatternField)
86+
87+
% Set up.
88+
panel = testCase.createTestPanel(VisibleOverride = "on");
89+
90+
cpt = mag.app.imap.control.CPT();
91+
cpt.instantiate(panel);
92+
93+
% Exercise.
94+
testCase.type(cpt.(PatternField), "123 invalid");
95+
96+
% Verify.
97+
testCase.dismissDialog("uialert", panel.Parent);
98+
end
99+
end
100+
end

tests/unit/app/imap/tIMAPField.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
classdef tIMAPField < mag.test.ViewControllerTestCase
1+
classdef tIMAPField < mag.test.case.ViewControllerTestCase
22
% TIMAPFIELD Unit tests for "mag.app.imap.control.Field" class.
33

44
methods (Test)

tests/unit/app/tField.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
classdef tField < mag.test.ViewControllerTestCase
1+
classdef tField < mag.test.case.ViewControllerTestCase
22
% TFIELD Unit tests for "mag.app.control.Field" class.
33

44
methods (Test)

tests/unit/app/tModel.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
classdef tModel < mag.test.ViewControllerTestCase
1+
classdef tModel < mag.test.case.ViewControllerTestCase
22
% TMODEL Unit tests for "mag.app.Model" classes.
33

44
properties (Access = private)

0 commit comments

Comments
 (0)