From 40a14d30df9f45204d6c4dcead78f440197b5e9c Mon Sep 17 00:00:00 2001 From: PVDecker1 Date: Thu, 19 Mar 2026 23:00:50 -0700 Subject: [PATCH 1/5] Updated for increased and more accurate coverage Increased coverage on the two main files, while restricting coverage to those files. No need to collect coverage on the tests themselves or related artifacts. --- .github/workflows/matlab-tests.yml | 5 +- .../TCsOjEr3rMe6CtDnuQLgASe-kAEd.xml | 6 + .../TCsOjEr3rMe6CtDnuQLgASe-kAEp.xml | 2 + runTests.m | 29 +++++ tests/MockComponent.m | 2 +- tests/tConsoleErrorRerouter.m | 114 ++++++++++++++++++ tests/tUIHTMLDevTools.m | 93 ++++++++++++++ toolbox/ConsoleErrorRerouter.m | 13 +- toolbox/UIHTMLDevTools.m | 11 +- 9 files changed, 256 insertions(+), 19 deletions(-) create mode 100644 resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/TCsOjEr3rMe6CtDnuQLgASe-kAEd.xml create mode 100644 resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/TCsOjEr3rMe6CtDnuQLgASe-kAEp.xml create mode 100644 runTests.m diff --git a/.github/workflows/matlab-tests.yml b/.github/workflows/matlab-tests.yml index 135b641..368c6ba 100644 --- a/.github/workflows/matlab-tests.yml +++ b/.github/workflows/matlab-tests.yml @@ -26,10 +26,9 @@ jobs: uses: matlab-actions/setup-matlab@v2 - name: Run tests - uses: matlab-actions/run-tests@v2 + uses: matlab-actions/run-command@v2 with: - source-folder: toolbox - code-coverage-cobertura: code-coverage/coverage.xml + command: runTests - name: Upload coverage to Codecov uses: codecov/codecov-action@v4 diff --git a/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/TCsOjEr3rMe6CtDnuQLgASe-kAEd.xml b/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/TCsOjEr3rMe6CtDnuQLgASe-kAEd.xml new file mode 100644 index 0000000..99772b4 --- /dev/null +++ b/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/TCsOjEr3rMe6CtDnuQLgASe-kAEd.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/TCsOjEr3rMe6CtDnuQLgASe-kAEp.xml b/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/TCsOjEr3rMe6CtDnuQLgASe-kAEp.xml new file mode 100644 index 0000000..323751e --- /dev/null +++ b/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/TCsOjEr3rMe6CtDnuQLgASe-kAEp.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/runTests.m b/runTests.m new file mode 100644 index 0000000..8224f65 --- /dev/null +++ b/runTests.m @@ -0,0 +1,29 @@ +import matlab.unittest.TestRunner; +import matlab.unittest.TestSuite; +import matlab.unittest.plugins.CodeCoveragePlugin; +import matlab.unittest.plugins.codecoverage.CoberturaFormat; + +% Create suite +suite = TestSuite.fromFolder('tests', 'IncludingSubfolders', true); + +% Create runner +runner = TestRunner.withTextOutput; + +% Configure coverage +sourceFiles = { ... + fullfile(pwd, 'toolbox', 'ConsoleErrorRerouter.m'), ... + fullfile(pwd, 'toolbox', 'UIHTMLDevTools.m') ... +}; + +mkdir('code-coverage'); +coverageFile = fullfile(pwd, 'code-coverage', 'coverage.xml'); +plugin = CodeCoveragePlugin.forFile(sourceFiles, ... + 'Producing', CoberturaFormat(coverageFile)); +runner.addPlugin(plugin); + +% Run tests +results = runner.run(suite); + +% Output results for CI +display(results); +assertSuccess(results); diff --git a/tests/MockComponent.m b/tests/MockComponent.m index d57807b..f72ffaf 100644 --- a/tests/MockComponent.m +++ b/tests/MockComponent.m @@ -1,4 +1,4 @@ -classdef MockComponent < handle +classdef MockComponent < handle & dynamicprops properties HTMLSource end diff --git a/tests/tConsoleErrorRerouter.m b/tests/tConsoleErrorRerouter.m index 500a8c2..e08aff5 100644 --- a/tests/tConsoleErrorRerouter.m +++ b/tests/tConsoleErrorRerouter.m @@ -60,6 +60,13 @@ function testConstructorInvalidComponent(testCase) "ConsoleErrorRerouter:InvalidComponent"); end % function testConstructorInvalidComponent + function testConstructorNoSource(testCase) + testCase.Component.HTMLSource = ""; + rerouter = ConsoleErrorRerouter(testCase.Component); + testCase.addTeardown(@() delete(rerouter)); + testCase.verifyEqual(string(testCase.Component.HTMLSource), ""); + end % function testConstructorNoSource + function testUrlSourceMock(testCase) mockComp = MockComponent(); mockComp.HTMLSource = "http://example.com"; @@ -216,8 +223,115 @@ function testPayloadEdgeCases(testCase) 'HTMLEventData', struct('level', 'error', 'message', 'No stack')); rerouter.onHTMLEventReceived([], goodData); testCase.verifySubstring(rerouter.LastMessage, "No stack"); + + % Object payload (instead of struct) + mockPayload = struct('level', 'error', 'message', 'Object message'); + objData = struct('HTMLEventName', "ConsoleError", ... + 'HTMLEventData', mockPayload); + rerouter.onHTMLEventReceived([], objData); + testCase.verifySubstring(rerouter.LastMessage, "Object message"); + + % Missing level + badData = struct('HTMLEventName', "ConsoleError", ... + 'HTMLEventData', struct('message', 'No level')); + % We can't set LastMessage, but we know it should NOT be "No level" + % It currently is "Object message" from previous step + rerouter.onHTMLEventReceived([], badData); + testCase.verifyEqual(rerouter.LastMessage, "Object message"); + + % Other event name + otherData = struct('HTMLEventName', "OtherEvent", ... + 'HTMLEventData', struct('level', 'error', 'message', 'Ignored')); + rerouter.onHTMLEventReceived([], otherData); + testCase.verifyEqual(rerouter.LastMessage, "Object message"); end % function testPayloadEdgeCases + + function testListenerSetupFailure(testCase) + tempDir = tempname; + mkdir(tempDir); + testCase.addTeardown(@() rmdir(tempDir, 's')); + htmlFile = fullfile(tempDir, "test.html"); + writelines("", htmlFile); + + % Create a struct that doesn't support listeners but has valid HTMLSource + mock = struct('HTMLSource', htmlFile); + rerouter = ConsoleErrorRerouter(mock); + testCase.addTeardown(@() delete(rerouter)); + testCase.verifyClass(rerouter, "ConsoleErrorRerouter"); + end % function testListenerSetupFailure + + function testDeleteInvalidComponent(testCase) + rerouter = ConsoleErrorRerouter(testCase.Component); + testCase.Rerouter = rerouter; + delete(testCase.Figure); % Deletes component too + delete(rerouter); % Should not error in removeShim + testCase.Rerouter = []; + end % function testDeleteInvalidComponent + + function testOriginalSourceDeleted(testCase) + tempDir = tempname; + mkdir(tempDir); + testCase.addTeardown(@() rmdir(tempDir, 's')); + htmlFile = fullfile(tempDir, "temp.html"); + writelines("", htmlFile); + + mockComp = MockComponent(); + mockComp.HTMLSource = htmlFile; + rerouter = ConsoleErrorRerouter(mockComp); + + % Delete original source before rerouter is deleted + delete(htmlFile); + + % removeShim should handle this gracefully + delete(rerouter); + testCase.verifyEqual(string(mockComp.HTMLSource), string(htmlFile)); + end % function testOriginalSourceDeleted + function testCleanupWarnings(testCase) + if isunix + tempDir = tempname; + mkdir(tempDir); + testCase.addTeardown(@() rmdir(tempDir, 's')); + htmlFile = fullfile(tempDir, "test.html"); + writelines("", htmlFile); + + mockComp = MockComponent(); + mockComp.HTMLSource = htmlFile; + rerouter = ConsoleErrorRerouter(mockComp); + + % Make the temp file read-only so delete(rerouter) fails to delete it + tempFile = string(mockComp.HTMLSource); + fileattrib(tempFile, '-w'); + testCase.addTeardown(@() fileattrib(tempFile, '+w')); + + testCase.verifyWarning(@() delete(rerouter), "ConsoleErrorRerouter:FailedCleanup"); + end + end % function testCleanupWarnings + + function testTempWriteFailure(testCase) + if isunix + % On Unix, we can easily make a directory read-only + tempDir = tempname; + mkdir(tempDir); + testCase.addTeardown(@() rmdir(tempDir, 's')); + htmlFile = fullfile(tempDir, "test.html"); + writelines("", htmlFile); + + % Make dir read-only + fileattrib(tempDir, '-w'); + testCase.addTeardown(@() fileattrib(tempDir, '+w')); + + mockComp = MockComponent(); + mockComp.HTMLSource = htmlFile; + testCase.verifyError(@() ConsoleErrorRerouter(mockComp), ... + "ConsoleErrorRerouter:TempWriteFailure"); + else + % On Windows, it's harder to reliably trigger this via attributes, + % so we might skip or use a different trick if needed. + % For now, we'll assume unix coverage in CI is enough or skip. + end + end % function testTempWriteFailure + function testEmptyTargetDir(testCase) origDir = pwd; tempDir = tempname; diff --git a/tests/tUIHTMLDevTools.m b/tests/tUIHTMLDevTools.m index 6fba5ac..6702d60 100644 --- a/tests/tUIHTMLDevTools.m +++ b/tests/tUIHTMLDevTools.m @@ -148,6 +148,31 @@ function testEmptyTargetDir(testCase) testCase.verifySubstring(string(mockComp.HTMLSource), "_devtools_"); end % function testEmptyTargetDir + function testCleanupWarnings(testCase) + if isunix + tempDir = tempname; + mkdir(tempDir); + testCase.addTeardown(@() rmdir(tempDir, 's')); + htmlFile = fullfile(tempDir, "test.html"); + writelines("", htmlFile); + + mockComp = MockComponent(); + mockComp.HTMLSource = htmlFile; + devTools = UIHTMLDevTools(mockComp); + + % Make the temp file and eruda.js read-only + tempFile = string(mockComp.HTMLSource); + pErudaDest = fullfile(tempDir, "eruda.js"); + + fileattrib(tempFile, '-w'); + testCase.addTeardown(@() fileattrib(tempFile, '+w')); + fileattrib(pErudaDest, '-w'); + testCase.addTeardown(@() fileattrib(pErudaDest, '+w')); + + testCase.verifyWarning(@() delete(devTools), "uihtmlDevTools:FailedCleanup"); + end + end % function testCleanupWarnings + function testDeleteWithMissingFiles(testCase) devTools = UIHTMLDevTools(testCase.Component); @@ -165,6 +190,74 @@ function testDeleteWithMissingFiles(testCase) delete(devTools); end % function testDeleteWithMissingFiles + function testDeleteInvalidComponent(testCase) + devTools = UIHTMLDevTools(testCase.Component); + testCase.DevTools = devTools; + delete(testCase.Figure); % Deletes component too + delete(devTools); % Should not error in removeEruda + testCase.DevTools = []; + end % function testDeleteInvalidComponent + + function testOriginalSourceDeleted(testCase) + tempDir = tempname; + mkdir(tempDir); + testCase.addTeardown(@() rmdir(tempDir, 's')); + htmlFile = fullfile(tempDir, "temp.html"); + writelines("", htmlFile); + + mockComp = MockComponent(); + mockComp.HTMLSource = htmlFile; + devTools = UIHTMLDevTools(mockComp); + + % Delete original source before devTools is deleted + delete(htmlFile); + + % removeEruda should handle this gracefully + delete(devTools); + testCase.verifyEqual(string(mockComp.HTMLSource), string(htmlFile)); + end % function testOriginalSourceDeleted + + function testTempWriteFailure(testCase) + if isunix + tempDir = tempname; + mkdir(tempDir); + testCase.addTeardown(@() rmdir(tempDir, 's')); + htmlFile = fullfile(tempDir, "test.html"); + writelines("", htmlFile); + + % Make dir read-only + fileattrib(tempDir, '-w'); + testCase.addTeardown(@() fileattrib(tempDir, '+w')); + + mockComp = MockComponent(); + mockComp.HTMLSource = htmlFile; + testCase.verifyError(@() UIHTMLDevTools(mockComp), ... + "uihtmlDevTools:TempWriteFailure"); + end + end % function testTempWriteFailure + + function testErudaCopyFailure(testCase) + if isunix + tempDir = tempname; + mkdir(tempDir); + testCase.addTeardown(@() rmdir(tempDir, 's')); + htmlFile = fullfile(tempDir, "test.html"); + writelines("", htmlFile); + + % Pre-create eruda.js and make it read-only or make dir read-only + % Actually copyfile might fail if dest is read-only. + pErudaDest = fullfile(tempDir, "eruda.js"); + writelines("locked", pErudaDest); + fileattrib(pErudaDest, '-w'); + testCase.addTeardown(@() fileattrib(pErudaDest, '+w')); + + mockComp = MockComponent(); + mockComp.HTMLSource = htmlFile; + testCase.verifyError(@() UIHTMLDevTools(mockComp), ... + "uihtmlDevTools:ErudaCopyFailure"); + end + end % function testErudaCopyFailure + function testUrlSourceMock(testCase) mockComp = MockComponent(); mockComp.HTMLSource = "http://example.com"; diff --git a/toolbox/ConsoleErrorRerouter.m b/toolbox/ConsoleErrorRerouter.m index 85266bf..5ce9e21 100644 --- a/toolbox/ConsoleErrorRerouter.m +++ b/toolbox/ConsoleErrorRerouter.m @@ -61,7 +61,7 @@ end % Handle shim delivery if HTMLSource is provided - if ~isempty(string(uihtmlComp.HTMLSource)) + if strlength(string(uihtmlComp.HTMLSource)) > 0 obj.injectShim(); end end % Constructor @@ -136,13 +136,10 @@ function removeShim(obj) % removeShim Restores the original HTML and cleans up the temporary file. if isa(obj.HtmlComponent, "handle") && isvalid(obj.HtmlComponent) && ... strlength(obj.OriginalHTMLSource) > 0 - % Check if OriginalHTMLSource still exists (it might be a temp file of another tool) - if isfile(obj.OriginalHTMLSource) || startsWith(obj.OriginalHTMLSource, "http") - try - obj.HtmlComponent.HTMLSource = obj.OriginalHTMLSource; - catch - % Ignore restoration errors if the file was already deleted by another tool - end + try + obj.HtmlComponent.HTMLSource = obj.OriginalHTMLSource; + catch + % Ignore restoration errors end end diff --git a/toolbox/UIHTMLDevTools.m b/toolbox/UIHTMLDevTools.m index 6a78109..2ff6931 100644 --- a/toolbox/UIHTMLDevTools.m +++ b/toolbox/UIHTMLDevTools.m @@ -134,13 +134,10 @@ function removeEruda(obj) % removeEruda Restores the original HTML and cleans up the temporary files. if isa(obj.HtmlComponent, "handle") && isvalid(obj.HtmlComponent) && ... strlength(obj.OriginalHTMLSource) > 0 - % Check if OriginalHTMLSource still exists (it might be a temp file of another tool) - if isfile(obj.OriginalHTMLSource) || startsWith(obj.OriginalHTMLSource, "http") - try - obj.HtmlComponent.HTMLSource = obj.OriginalHTMLSource; - catch - % Ignore restoration errors - end + try + obj.HtmlComponent.HTMLSource = obj.OriginalHTMLSource; + catch + % Ignore restoration errors end end From e673b0472c6a85a4188c2084e72093744f4921ee Mon Sep 17 00:00:00 2001 From: PVDecker1 Date: Thu, 19 Mar 2026 23:10:58 -0700 Subject: [PATCH 2/5] Fixing bug with CI, opening project before running tests --- .github/workflows/matlab-tests.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/matlab-tests.yml b/.github/workflows/matlab-tests.yml index 368c6ba..fe50634 100644 --- a/.github/workflows/matlab-tests.yml +++ b/.github/workflows/matlab-tests.yml @@ -28,7 +28,9 @@ jobs: - name: Run tests uses: matlab-actions/run-command@v2 with: - command: runTests + command: | + openProject("uihtml-debugger.prj"); + runTests; - name: Upload coverage to Codecov uses: codecov/codecov-action@v4 From 85ed3779ea412002442ec78a1e61347ebe7eaa0d Mon Sep 17 00:00:00 2001 From: PVDecker1 Date: Thu, 19 Mar 2026 23:33:58 -0700 Subject: [PATCH 3/5] Working on multi-OS compatibility --- README.md | 9 +++++- .../2rAGxcYfPv6icDMAq1Qlbz9L_I0d.xml | 2 ++ .../2rAGxcYfPv6icDMAq1Qlbz9L_I0p.xml | 2 ++ .../SLdGz8tUgmpJ67GHyRyb0tvRNuUd.xml | 2 ++ .../SLdGz8tUgmpJ67GHyRyb0tvRNuUp.xml | 2 ++ .../gY9EjcEXQx_03t0CxCXVb3wLivEd.xml | 2 ++ .../gY9EjcEXQx_03t0CxCXVb3wLivEp.xml | 2 ++ .../0qs9rsEL3yBSqUmUWZjEFIhMQPod.xml | 2 ++ .../0qs9rsEL3yBSqUmUWZjEFIhMQPop.xml | 2 ++ .../root/amwE3LmoG--0pRWluRol6WgE4ZYd.xml | 2 +- .../root/xRfC_Iq2vKr7PkzRJo-T0mI2Drkd.xml | 2 ++ .../root/xRfC_Iq2vKr7PkzRJo-T0mI2Drkp.xml | 2 ++ .../yBqJXaEP8Uc9sR9kNtkek_ebn80d.xml | 2 ++ .../yBqJXaEP8Uc9sR9kNtkek_ebn80p.xml | 2 ++ tests/tConsoleErrorRerouter.m | 6 ++-- tests/tUIHTMLDevTools.m | 30 ++++++++----------- toolbox/ConsoleErrorRerouter.m | 5 ++-- toolbox/UIHTMLDevTools.m | 10 +++---- 18 files changed, 55 insertions(+), 31 deletions(-) create mode 100644 resources/project/0qs9rsEL3yBSqUmUWZjEFIhMQPo/2rAGxcYfPv6icDMAq1Qlbz9L_I0d.xml create mode 100644 resources/project/0qs9rsEL3yBSqUmUWZjEFIhMQPo/2rAGxcYfPv6icDMAq1Qlbz9L_I0p.xml create mode 100644 resources/project/0qs9rsEL3yBSqUmUWZjEFIhMQPo/SLdGz8tUgmpJ67GHyRyb0tvRNuUd.xml create mode 100644 resources/project/0qs9rsEL3yBSqUmUWZjEFIhMQPo/SLdGz8tUgmpJ67GHyRyb0tvRNuUp.xml create mode 100644 resources/project/0qs9rsEL3yBSqUmUWZjEFIhMQPo/gY9EjcEXQx_03t0CxCXVb3wLivEd.xml create mode 100644 resources/project/0qs9rsEL3yBSqUmUWZjEFIhMQPo/gY9EjcEXQx_03t0CxCXVb3wLivEp.xml create mode 100644 resources/project/fjRQtWiSIy7hIlj-Kmk87M7s21k/0qs9rsEL3yBSqUmUWZjEFIhMQPod.xml create mode 100644 resources/project/fjRQtWiSIy7hIlj-Kmk87M7s21k/0qs9rsEL3yBSqUmUWZjEFIhMQPop.xml create mode 100644 resources/project/root/xRfC_Iq2vKr7PkzRJo-T0mI2Drkd.xml create mode 100644 resources/project/root/xRfC_Iq2vKr7PkzRJo-T0mI2Drkp.xml create mode 100644 resources/project/xRfC_Iq2vKr7PkzRJo-T0mI2Drk/yBqJXaEP8Uc9sR9kNtkek_ebn80d.xml create mode 100644 resources/project/xRfC_Iq2vKr7PkzRJo-T0mI2Drk/yBqJXaEP8Uc9sR9kNtkek_ebn80p.xml diff --git a/README.md b/README.md index 375a836..d73cfba 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,14 @@ The UIHTML Debugger is a non-intrusive toolkit designed to bridge the gap between MATLAB and JavaScript development. It provides two essential tools to streamline web-based UI development within MATLAB: 1. **Console Error Rerouter**: Forwards console.log, warn, and error messages directly to the MATLAB Command Window. -2. **UIHTML DevTools**: Injects Eruda—a full-featured mobile-style console—directly into your UI for deep inspection without leaving the MATLAB environment. +1. **UIHTML DevTools**: Injects Eruda—a full-featured mobile-style console—directly into your UI for deep inspection without leaving the MATLAB environment. + +--- + +## Requirements + +* **MATLAB R2023a** or later. +* **Base MATLAB** (no additional toolboxes required). --- diff --git a/resources/project/0qs9rsEL3yBSqUmUWZjEFIhMQPo/2rAGxcYfPv6icDMAq1Qlbz9L_I0d.xml b/resources/project/0qs9rsEL3yBSqUmUWZjEFIhMQPo/2rAGxcYfPv6icDMAq1Qlbz9L_I0d.xml new file mode 100644 index 0000000..8f602f1 --- /dev/null +++ b/resources/project/0qs9rsEL3yBSqUmUWZjEFIhMQPo/2rAGxcYfPv6icDMAq1Qlbz9L_I0d.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/0qs9rsEL3yBSqUmUWZjEFIhMQPo/2rAGxcYfPv6icDMAq1Qlbz9L_I0p.xml b/resources/project/0qs9rsEL3yBSqUmUWZjEFIhMQPo/2rAGxcYfPv6icDMAq1Qlbz9L_I0p.xml new file mode 100644 index 0000000..e55acbe --- /dev/null +++ b/resources/project/0qs9rsEL3yBSqUmUWZjEFIhMQPo/2rAGxcYfPv6icDMAq1Qlbz9L_I0p.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/0qs9rsEL3yBSqUmUWZjEFIhMQPo/SLdGz8tUgmpJ67GHyRyb0tvRNuUd.xml b/resources/project/0qs9rsEL3yBSqUmUWZjEFIhMQPo/SLdGz8tUgmpJ67GHyRyb0tvRNuUd.xml new file mode 100644 index 0000000..7688e41 --- /dev/null +++ b/resources/project/0qs9rsEL3yBSqUmUWZjEFIhMQPo/SLdGz8tUgmpJ67GHyRyb0tvRNuUd.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/0qs9rsEL3yBSqUmUWZjEFIhMQPo/SLdGz8tUgmpJ67GHyRyb0tvRNuUp.xml b/resources/project/0qs9rsEL3yBSqUmUWZjEFIhMQPo/SLdGz8tUgmpJ67GHyRyb0tvRNuUp.xml new file mode 100644 index 0000000..baafa8b --- /dev/null +++ b/resources/project/0qs9rsEL3yBSqUmUWZjEFIhMQPo/SLdGz8tUgmpJ67GHyRyb0tvRNuUp.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/0qs9rsEL3yBSqUmUWZjEFIhMQPo/gY9EjcEXQx_03t0CxCXVb3wLivEd.xml b/resources/project/0qs9rsEL3yBSqUmUWZjEFIhMQPo/gY9EjcEXQx_03t0CxCXVb3wLivEd.xml new file mode 100644 index 0000000..79316c3 --- /dev/null +++ b/resources/project/0qs9rsEL3yBSqUmUWZjEFIhMQPo/gY9EjcEXQx_03t0CxCXVb3wLivEd.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/0qs9rsEL3yBSqUmUWZjEFIhMQPo/gY9EjcEXQx_03t0CxCXVb3wLivEp.xml b/resources/project/0qs9rsEL3yBSqUmUWZjEFIhMQPo/gY9EjcEXQx_03t0CxCXVb3wLivEp.xml new file mode 100644 index 0000000..b9195e0 --- /dev/null +++ b/resources/project/0qs9rsEL3yBSqUmUWZjEFIhMQPo/gY9EjcEXQx_03t0CxCXVb3wLivEp.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/fjRQtWiSIy7hIlj-Kmk87M7s21k/0qs9rsEL3yBSqUmUWZjEFIhMQPod.xml b/resources/project/fjRQtWiSIy7hIlj-Kmk87M7s21k/0qs9rsEL3yBSqUmUWZjEFIhMQPod.xml new file mode 100644 index 0000000..fbd21c0 --- /dev/null +++ b/resources/project/fjRQtWiSIy7hIlj-Kmk87M7s21k/0qs9rsEL3yBSqUmUWZjEFIhMQPod.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/fjRQtWiSIy7hIlj-Kmk87M7s21k/0qs9rsEL3yBSqUmUWZjEFIhMQPop.xml b/resources/project/fjRQtWiSIy7hIlj-Kmk87M7s21k/0qs9rsEL3yBSqUmUWZjEFIhMQPop.xml new file mode 100644 index 0000000..274d91f --- /dev/null +++ b/resources/project/fjRQtWiSIy7hIlj-Kmk87M7s21k/0qs9rsEL3yBSqUmUWZjEFIhMQPop.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/root/amwE3LmoG--0pRWluRol6WgE4ZYd.xml b/resources/project/root/amwE3LmoG--0pRWluRol6WgE4ZYd.xml index cb17be0..7192f7b 100644 --- a/resources/project/root/amwE3LmoG--0pRWluRol6WgE4ZYd.xml +++ b/resources/project/root/amwE3LmoG--0pRWluRol6WgE4ZYd.xml @@ -1,2 +1,2 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/resources/project/root/xRfC_Iq2vKr7PkzRJo-T0mI2Drkd.xml b/resources/project/root/xRfC_Iq2vKr7PkzRJo-T0mI2Drkd.xml new file mode 100644 index 0000000..d31bdab --- /dev/null +++ b/resources/project/root/xRfC_Iq2vKr7PkzRJo-T0mI2Drkd.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/root/xRfC_Iq2vKr7PkzRJo-T0mI2Drkp.xml b/resources/project/root/xRfC_Iq2vKr7PkzRJo-T0mI2Drkp.xml new file mode 100644 index 0000000..20870d6 --- /dev/null +++ b/resources/project/root/xRfC_Iq2vKr7PkzRJo-T0mI2Drkp.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/xRfC_Iq2vKr7PkzRJo-T0mI2Drk/yBqJXaEP8Uc9sR9kNtkek_ebn80d.xml b/resources/project/xRfC_Iq2vKr7PkzRJo-T0mI2Drk/yBqJXaEP8Uc9sR9kNtkek_ebn80d.xml new file mode 100644 index 0000000..4356a6a --- /dev/null +++ b/resources/project/xRfC_Iq2vKr7PkzRJo-T0mI2Drk/yBqJXaEP8Uc9sR9kNtkek_ebn80d.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/xRfC_Iq2vKr7PkzRJo-T0mI2Drk/yBqJXaEP8Uc9sR9kNtkek_ebn80p.xml b/resources/project/xRfC_Iq2vKr7PkzRJo-T0mI2Drk/yBqJXaEP8Uc9sR9kNtkek_ebn80p.xml new file mode 100644 index 0000000..01cb34e --- /dev/null +++ b/resources/project/xRfC_Iq2vKr7PkzRJo-T0mI2Drk/yBqJXaEP8Uc9sR9kNtkek_ebn80p.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/tests/tConsoleErrorRerouter.m b/tests/tConsoleErrorRerouter.m index e08aff5..9455aa1 100644 --- a/tests/tConsoleErrorRerouter.m +++ b/tests/tConsoleErrorRerouter.m @@ -299,10 +299,10 @@ function testCleanupWarnings(testCase) mockComp.HTMLSource = htmlFile; rerouter = ConsoleErrorRerouter(mockComp); - % Make the temp file read-only so delete(rerouter) fails to delete it + % On Linux, to make delete fail, we make the directory read-only tempFile = string(mockComp.HTMLSource); - fileattrib(tempFile, '-w'); - testCase.addTeardown(@() fileattrib(tempFile, '+w')); + fileattrib(tempDir, '-w'); + testCase.addTeardown(@() fileattrib(tempDir, '+w')); testCase.verifyWarning(@() delete(rerouter), "ConsoleErrorRerouter:FailedCleanup"); end diff --git a/tests/tUIHTMLDevTools.m b/tests/tUIHTMLDevTools.m index 6702d60..734c029 100644 --- a/tests/tUIHTMLDevTools.m +++ b/tests/tUIHTMLDevTools.m @@ -160,14 +160,9 @@ function testCleanupWarnings(testCase) mockComp.HTMLSource = htmlFile; devTools = UIHTMLDevTools(mockComp); - % Make the temp file and eruda.js read-only - tempFile = string(mockComp.HTMLSource); - pErudaDest = fullfile(tempDir, "eruda.js"); - - fileattrib(tempFile, '-w'); - testCase.addTeardown(@() fileattrib(tempFile, '+w')); - fileattrib(pErudaDest, '-w'); - testCase.addTeardown(@() fileattrib(pErudaDest, '+w')); + % On Linux, to make delete fail, we make the directory read-only + fileattrib(tempDir, '-w'); + testCase.addTeardown(@() fileattrib(tempDir, '+w')); testCase.verifyWarning(@() delete(devTools), "uihtmlDevTools:FailedCleanup"); end @@ -178,7 +173,9 @@ function testDeleteWithMissingFiles(testCase) % Delete files manually before object delete tempFile = string(testCase.Component.HTMLSource); - delete(tempFile); + if isfile(tempFile) + delete(tempFile); + end targetDir = fileparts(testCase.FixtureHtml); pErudaDest = fullfile(targetDir, "eruda.js"); @@ -225,14 +222,16 @@ function testTempWriteFailure(testCase) htmlFile = fullfile(tempDir, "test.html"); writelines("", htmlFile); - % Make dir read-only + % Make dir read-only. This will cause either ErudaCopyFailure (first) + % or TempWriteFailure to occur. fileattrib(tempDir, '-w'); testCase.addTeardown(@() fileattrib(tempDir, '+w')); mockComp = MockComponent(); mockComp.HTMLSource = htmlFile; + % On Linux, copyfile hits the read-only dir first. testCase.verifyError(@() UIHTMLDevTools(mockComp), ... - "uihtmlDevTools:TempWriteFailure"); + "(uihtmlDevTools:TempWriteFailure|uihtmlDevTools:ErudaCopyFailure)"); end end % function testTempWriteFailure @@ -244,12 +243,9 @@ function testErudaCopyFailure(testCase) htmlFile = fullfile(tempDir, "test.html"); writelines("", htmlFile); - % Pre-create eruda.js and make it read-only or make dir read-only - % Actually copyfile might fail if dest is read-only. - pErudaDest = fullfile(tempDir, "eruda.js"); - writelines("locked", pErudaDest); - fileattrib(pErudaDest, '-w'); - testCase.addTeardown(@() fileattrib(pErudaDest, '+w')); + % On Linux, to make copyfile/delete fail, we make the directory read-only + fileattrib(tempDir, '-w'); + testCase.addTeardown(@() fileattrib(tempDir, '+w')); mockComp = MockComponent(); mockComp.HTMLSource = htmlFile; diff --git a/toolbox/ConsoleErrorRerouter.m b/toolbox/ConsoleErrorRerouter.m index 5ce9e21..9d4ed27 100644 --- a/toolbox/ConsoleErrorRerouter.m +++ b/toolbox/ConsoleErrorRerouter.m @@ -145,9 +145,8 @@ function removeShim(obj) % Delete temporary HTML file if ~isempty(obj.TempHTMLPath) && isfile(obj.TempHTMLPath) - try - delete(obj.TempHTMLPath); - catch + delete(obj.TempHTMLPath); + if isfile(obj.TempHTMLPath) warning("ConsoleErrorRerouter:FailedCleanup", ... "Failed to delete %s. Please check your file system", ... obj.TempHTMLPath) diff --git a/toolbox/UIHTMLDevTools.m b/toolbox/UIHTMLDevTools.m index 2ff6931..99f0eff 100644 --- a/toolbox/UIHTMLDevTools.m +++ b/toolbox/UIHTMLDevTools.m @@ -143,9 +143,8 @@ function removeEruda(obj) % Delete temporary HTML file if strlength(obj.TempHTMLPath) > 0 && isfile(obj.TempHTMLPath) - try - delete(obj.TempHTMLPath); - catch + delete(obj.TempHTMLPath); + if isfile(obj.TempHTMLPath) warning("uihtmlDevTools:FailedCleanup", ... "Failed to delete %s. Please check your file system", ... obj.TempHTMLPath) @@ -160,9 +159,8 @@ function removeEruda(obj) end pErudaDest = fullfile(targetDir, "eruda.js"); if isfile(pErudaDest) - try - delete(pErudaDest); - catch + delete(pErudaDest); + if isfile(pErudaDest) warning("uihtmlDevTools:FailedCleanup", ... "Failed to delete %s. Please check your file system", ... pErudaDest) From 85e2b70b96697ff5e3c12fda2bb2a5264f5c1859 Mon Sep 17 00:00:00 2001 From: PVDecker1 Date: Fri, 20 Mar 2026 00:03:43 -0700 Subject: [PATCH 4/5] Another attempt at fixing CI test failures --- .../lJpff9HwDURjOood0D_4QIQyyZod.xml | 2 ++ .../lJpff9HwDURjOood0D_4QIQyyZop.xml | 2 ++ .../M2j0tNMQnrX0MAixgunqSH6O1cUd.xml | 2 ++ .../M2j0tNMQnrX0MAixgunqSH6O1cUp.xml | 2 ++ .../O-y4x_Fk_elHFgVfW5CS2Gg4bVEd.xml | 2 ++ .../O-y4x_Fk_elHFgVfW5CS2Gg4bVEp.xml | 2 ++ .../PBlyKlej6TlbKRuFfxG9YYP9NA8d.xml | 2 ++ .../PBlyKlej6TlbKRuFfxG9YYP9NA8p.xml | 2 ++ resources/project/root/xRfC_Iq2vKr7PkzRJo-T0mI2Drkd.xml | 2 +- tests/tUIHTMLDevTools.m | 3 ++- toolbox/ConsoleErrorRerouter.m | 2 +- 11 files changed, 20 insertions(+), 3 deletions(-) create mode 100644 resources/project/fjRQtWiSIy7hIlj-Kmk87M7s21k/lJpff9HwDURjOood0D_4QIQyyZod.xml create mode 100644 resources/project/fjRQtWiSIy7hIlj-Kmk87M7s21k/lJpff9HwDURjOood0D_4QIQyyZop.xml create mode 100644 resources/project/lJpff9HwDURjOood0D_4QIQyyZo/M2j0tNMQnrX0MAixgunqSH6O1cUd.xml create mode 100644 resources/project/lJpff9HwDURjOood0D_4QIQyyZo/M2j0tNMQnrX0MAixgunqSH6O1cUp.xml create mode 100644 resources/project/lJpff9HwDURjOood0D_4QIQyyZo/O-y4x_Fk_elHFgVfW5CS2Gg4bVEd.xml create mode 100644 resources/project/lJpff9HwDURjOood0D_4QIQyyZo/O-y4x_Fk_elHFgVfW5CS2Gg4bVEp.xml create mode 100644 resources/project/lJpff9HwDURjOood0D_4QIQyyZo/PBlyKlej6TlbKRuFfxG9YYP9NA8d.xml create mode 100644 resources/project/lJpff9HwDURjOood0D_4QIQyyZo/PBlyKlej6TlbKRuFfxG9YYP9NA8p.xml diff --git a/resources/project/fjRQtWiSIy7hIlj-Kmk87M7s21k/lJpff9HwDURjOood0D_4QIQyyZod.xml b/resources/project/fjRQtWiSIy7hIlj-Kmk87M7s21k/lJpff9HwDURjOood0D_4QIQyyZod.xml new file mode 100644 index 0000000..b706d30 --- /dev/null +++ b/resources/project/fjRQtWiSIy7hIlj-Kmk87M7s21k/lJpff9HwDURjOood0D_4QIQyyZod.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/fjRQtWiSIy7hIlj-Kmk87M7s21k/lJpff9HwDURjOood0D_4QIQyyZop.xml b/resources/project/fjRQtWiSIy7hIlj-Kmk87M7s21k/lJpff9HwDURjOood0D_4QIQyyZop.xml new file mode 100644 index 0000000..731c128 --- /dev/null +++ b/resources/project/fjRQtWiSIy7hIlj-Kmk87M7s21k/lJpff9HwDURjOood0D_4QIQyyZop.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/lJpff9HwDURjOood0D_4QIQyyZo/M2j0tNMQnrX0MAixgunqSH6O1cUd.xml b/resources/project/lJpff9HwDURjOood0D_4QIQyyZo/M2j0tNMQnrX0MAixgunqSH6O1cUd.xml new file mode 100644 index 0000000..79316c3 --- /dev/null +++ b/resources/project/lJpff9HwDURjOood0D_4QIQyyZo/M2j0tNMQnrX0MAixgunqSH6O1cUd.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/lJpff9HwDURjOood0D_4QIQyyZo/M2j0tNMQnrX0MAixgunqSH6O1cUp.xml b/resources/project/lJpff9HwDURjOood0D_4QIQyyZo/M2j0tNMQnrX0MAixgunqSH6O1cUp.xml new file mode 100644 index 0000000..7397d9b --- /dev/null +++ b/resources/project/lJpff9HwDURjOood0D_4QIQyyZo/M2j0tNMQnrX0MAixgunqSH6O1cUp.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/lJpff9HwDURjOood0D_4QIQyyZo/O-y4x_Fk_elHFgVfW5CS2Gg4bVEd.xml b/resources/project/lJpff9HwDURjOood0D_4QIQyyZo/O-y4x_Fk_elHFgVfW5CS2Gg4bVEd.xml new file mode 100644 index 0000000..7688e41 --- /dev/null +++ b/resources/project/lJpff9HwDURjOood0D_4QIQyyZo/O-y4x_Fk_elHFgVfW5CS2Gg4bVEd.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/lJpff9HwDURjOood0D_4QIQyyZo/O-y4x_Fk_elHFgVfW5CS2Gg4bVEp.xml b/resources/project/lJpff9HwDURjOood0D_4QIQyyZo/O-y4x_Fk_elHFgVfW5CS2Gg4bVEp.xml new file mode 100644 index 0000000..d0c8018 --- /dev/null +++ b/resources/project/lJpff9HwDURjOood0D_4QIQyyZo/O-y4x_Fk_elHFgVfW5CS2Gg4bVEp.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/lJpff9HwDURjOood0D_4QIQyyZo/PBlyKlej6TlbKRuFfxG9YYP9NA8d.xml b/resources/project/lJpff9HwDURjOood0D_4QIQyyZo/PBlyKlej6TlbKRuFfxG9YYP9NA8d.xml new file mode 100644 index 0000000..8f602f1 --- /dev/null +++ b/resources/project/lJpff9HwDURjOood0D_4QIQyyZo/PBlyKlej6TlbKRuFfxG9YYP9NA8d.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/lJpff9HwDURjOood0D_4QIQyyZo/PBlyKlej6TlbKRuFfxG9YYP9NA8p.xml b/resources/project/lJpff9HwDURjOood0D_4QIQyyZo/PBlyKlej6TlbKRuFfxG9YYP9NA8p.xml new file mode 100644 index 0000000..673600c --- /dev/null +++ b/resources/project/lJpff9HwDURjOood0D_4QIQyyZo/PBlyKlej6TlbKRuFfxG9YYP9NA8p.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/root/xRfC_Iq2vKr7PkzRJo-T0mI2Drkd.xml b/resources/project/root/xRfC_Iq2vKr7PkzRJo-T0mI2Drkd.xml index d31bdab..4ce2746 100644 --- a/resources/project/root/xRfC_Iq2vKr7PkzRJo-T0mI2Drkd.xml +++ b/resources/project/root/xRfC_Iq2vKr7PkzRJo-T0mI2Drkd.xml @@ -1,2 +1,2 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/tests/tUIHTMLDevTools.m b/tests/tUIHTMLDevTools.m index 734c029..8831ae9 100644 --- a/tests/tUIHTMLDevTools.m +++ b/tests/tUIHTMLDevTools.m @@ -231,7 +231,8 @@ function testTempWriteFailure(testCase) mockComp.HTMLSource = htmlFile; % On Linux, copyfile hits the read-only dir first. testCase.verifyError(@() UIHTMLDevTools(mockComp), ... - "(uihtmlDevTools:TempWriteFailure|uihtmlDevTools:ErudaCopyFailure)"); + matlab.unittest.constraints.HasIdentifier("uihtmlDevTools:TempWriteFailure") | ... + matlab.unittest.constraints.HasIdentifier("uihtmlDevTools:ErudaCopyFailure")); end end % function testTempWriteFailure diff --git a/toolbox/ConsoleErrorRerouter.m b/toolbox/ConsoleErrorRerouter.m index 9d4ed27..5310666 100644 --- a/toolbox/ConsoleErrorRerouter.m +++ b/toolbox/ConsoleErrorRerouter.m @@ -124,7 +124,7 @@ function injectShim(obj) writelines(newHtml,obj.TempHTMLPath); catch error("ConsoleErrorRerouter:TempWriteFailure", ... - "Filed to write temporary html file to:\n%s",obj.TempHTMLPath); + "Failed to write temporary html file to:\n%s",obj.TempHTMLPath); end % Update the component's HTMLSource with the temporary file path. From 765d2f630e29f9cd277fa30b34235dc597419b7f Mon Sep 17 00:00:00 2001 From: PVDecker1 Date: Fri, 20 Mar 2026 12:17:08 -0700 Subject: [PATCH 5/5] Debugging ci test errors --- tests/tUIHTMLDevTools.m | 63 ++++++++++++++--------------------------- 1 file changed, 21 insertions(+), 42 deletions(-) diff --git a/tests/tUIHTMLDevTools.m b/tests/tUIHTMLDevTools.m index 8831ae9..639aaae 100644 --- a/tests/tUIHTMLDevTools.m +++ b/tests/tUIHTMLDevTools.m @@ -74,7 +74,7 @@ function testEnabledToggle(testCase) testCase.verifyNotEqual(tempFile, string(origHtml)); testCase.verifyTrue(isfile(tempFile)); - % Set Enabled to same value + % Set Enabled to same value - should be a no-op devTools.Enabled = true; % Disable @@ -131,19 +131,19 @@ function testEmptyTargetDir(testCase) mkdir(tempDir); testCase.addTeardown(@() rmdir(tempDir, 's')); testCase.addTeardown(@() cd(origDir)); - + testFile = which(mfilename); testDir = fileparts(testFile); origHtml = fullfile(testDir, "html", "test_page.html"); copyfile(origHtml, fullfile(tempDir, "test.html")); - + cd(tempDir); mockComp = MockComponent(); mockComp.HTMLSource = "test.html"; - + devTools = UIHTMLDevTools(mockComp); testCase.addTeardown(@() delete(devTools)); - + testCase.verifyTrue(isfile("eruda.js")); testCase.verifySubstring(string(mockComp.HTMLSource), "_devtools_"); end % function testEmptyTargetDir @@ -155,35 +155,35 @@ function testCleanupWarnings(testCase) testCase.addTeardown(@() rmdir(tempDir, 's')); htmlFile = fullfile(tempDir, "test.html"); writelines("", htmlFile); - + mockComp = MockComponent(); mockComp.HTMLSource = htmlFile; devTools = UIHTMLDevTools(mockComp); - - % On Linux, to make delete fail, we make the directory read-only + + % On Linux, make the directory read-only to force delete failure fileattrib(tempDir, '-w'); testCase.addTeardown(@() fileattrib(tempDir, '+w')); - + testCase.verifyWarning(@() delete(devTools), "uihtmlDevTools:FailedCleanup"); end end % function testCleanupWarnings function testDeleteWithMissingFiles(testCase) devTools = UIHTMLDevTools(testCase.Component); - + % Delete files manually before object delete tempFile = string(testCase.Component.HTMLSource); if isfile(tempFile) delete(tempFile); end - + targetDir = fileparts(testCase.FixtureHtml); pErudaDest = fullfile(targetDir, "eruda.js"); if isfile(pErudaDest) delete(pErudaDest); end - - % Should not error/warning if they don't exist + + % Should not error or warn if files don't exist delete(devTools); end % function testDeleteWithMissingFiles @@ -201,41 +201,19 @@ function testOriginalSourceDeleted(testCase) testCase.addTeardown(@() rmdir(tempDir, 's')); htmlFile = fullfile(tempDir, "temp.html"); writelines("", htmlFile); - + mockComp = MockComponent(); mockComp.HTMLSource = htmlFile; devTools = UIHTMLDevTools(mockComp); - + % Delete original source before devTools is deleted delete(htmlFile); - + % removeEruda should handle this gracefully delete(devTools); testCase.verifyEqual(string(mockComp.HTMLSource), string(htmlFile)); end % function testOriginalSourceDeleted - function testTempWriteFailure(testCase) - if isunix - tempDir = tempname; - mkdir(tempDir); - testCase.addTeardown(@() rmdir(tempDir, 's')); - htmlFile = fullfile(tempDir, "test.html"); - writelines("", htmlFile); - - % Make dir read-only. This will cause either ErudaCopyFailure (first) - % or TempWriteFailure to occur. - fileattrib(tempDir, '-w'); - testCase.addTeardown(@() fileattrib(tempDir, '+w')); - - mockComp = MockComponent(); - mockComp.HTMLSource = htmlFile; - % On Linux, copyfile hits the read-only dir first. - testCase.verifyError(@() UIHTMLDevTools(mockComp), ... - matlab.unittest.constraints.HasIdentifier("uihtmlDevTools:TempWriteFailure") | ... - matlab.unittest.constraints.HasIdentifier("uihtmlDevTools:ErudaCopyFailure")); - end - end % function testTempWriteFailure - function testErudaCopyFailure(testCase) if isunix tempDir = tempname; @@ -243,11 +221,12 @@ function testErudaCopyFailure(testCase) testCase.addTeardown(@() rmdir(tempDir, 's')); htmlFile = fullfile(tempDir, "test.html"); writelines("", htmlFile); - - % On Linux, to make copyfile/delete fail, we make the directory read-only + + % Make directory read-only -- copyfile runs before writelines + % so ErudaCopyFailure is always the first error thrown fileattrib(tempDir, '-w'); testCase.addTeardown(@() fileattrib(tempDir, '+w')); - + mockComp = MockComponent(); mockComp.HTMLSource = htmlFile; testCase.verifyError(@() UIHTMLDevTools(mockComp), ... @@ -269,4 +248,4 @@ function testInvalidFileSourceMock(testCase) "uihtmlDevTools:InvalidHTMLSource"); end % function testInvalidFileSourceMock end % methods (Test) -end % classdef tUIHTMLDevTools +end % classdef tUIHTMLDevTools \ No newline at end of file