@@ -5,7 +5,7 @@ import * as path from "path";
55import * as os from "os" ;
66import * as nodeFs from "fs" ;
77import { JSDOM } from "jsdom" ;
8- import type { TestResultsData , MatlabTestFile , TestStatistics } from "./testResultsSummary.js" ;
8+ import type { TestResultsData , MatlabTestFile , TestStatistics , TestSession } from "./testResultsSummary.js" ;
99
1010// Mock @actions /core
1111jest . unstable_mockModule ( "@actions/core" , ( ) => ( {
@@ -22,6 +22,7 @@ jest.unstable_mockModule("fs", () => ({
2222 existsSync : nodeFs . existsSync ,
2323 writeFileSync : nodeFs . writeFileSync ,
2424 copyFileSync : nodeFs . copyFileSync ,
25+ readdirSync : nodeFs . readdirSync ,
2526 unlinkSync : mockUnlinkSync ,
2627} ) ) ;
2728
@@ -34,8 +35,8 @@ const { MatlabTestStatus } = testResultsSummary;
3435describe ( "Artifact Processing Tests" , ( ) => {
3536 // Shared test data
3637 let testResultsData : TestResultsData | null ;
37- let testResults : MatlabTestFile [ ] [ ] ;
38- let stats : TestStatistics ;
38+ let testSessions : TestSession [ ] ;
39+ let overallStats : TestStatistics ;
3940
4041 beforeAll ( ( ) => {
4142 const runnerTemp = path . join ( import . meta. dirname , ".." ) ;
@@ -48,8 +49,8 @@ describe("Artifact Processing Tests", () => {
4849
4950 testResultsData = testResultsSummary . getTestResults ( runnerTemp , runId , workspace ) ;
5051 if ( testResultsData ) {
51- testResults = testResultsData . TestResults ;
52- stats = testResultsData . Stats ;
52+ testSessions = testResultsData . TestSessions ;
53+ overallStats = testResultsData . OverallStats ;
5354 }
5455 } ) ;
5556
@@ -78,7 +79,7 @@ describe("Artifact Processing Tests", () => {
7879 osName ,
7980 "matlabTestResults.json" ,
8081 ) ;
81- const destinationFilePath = path . join ( runnerTemp , "matlabTestResults" + runId + " .json") ;
82+ const destinationFilePath = path . join ( runnerTemp , "matlabTestResults_20250101_120000_000 .json" ) ;
8283
8384 try {
8485 fs . copyFileSync ( sourceFilePath , destinationFilePath ) ;
@@ -87,26 +88,48 @@ describe("Artifact Processing Tests", () => {
8788 }
8889 }
8990
91+ it ( "should return correct test results data structure" , ( ) => {
92+ expect ( testResultsData ) . toBeDefined ( ) ;
93+ expect ( testSessions ) . toBeDefined ( ) ;
94+ expect ( overallStats ) . toBeDefined ( ) ;
95+ expect ( testSessions . length ) . toBe ( 1 ) ;
96+ expect ( testSessions [ 0 ] . FileName ) . toBe ( "matlabTestResults_20250101_120000_000.json" ) ;
97+ expect ( testSessions [ 0 ] . TestResults . length ) . toBe ( 2 ) ;
98+ expect ( testSessions [ 0 ] . TestResults [ 0 ] . length ) . toBe ( 1 ) ;
99+ expect ( testSessions [ 0 ] . TestResults [ 1 ] . length ) . toBe ( 1 ) ;
100+ } ) ;
101+
90102 it ( "should return correct test results data for valid JSON" , ( ) => {
103+ const testResults = testSessions [ 0 ] . TestResults ;
91104 expect ( testResults ) . toBeDefined ( ) ;
92- expect ( stats ) . toBeDefined ( ) ;
93105 expect ( testResults . length ) . toBe ( 2 ) ;
94106 expect ( testResults [ 0 ] . length ) . toBe ( 1 ) ;
95107 expect ( testResults [ 1 ] . length ) . toBe ( 1 ) ;
96108 expect ( testResults [ 0 ] [ 0 ] . TestCases . length ) . toBe ( 9 ) ;
97109 expect ( testResults [ 1 ] [ 0 ] . TestCases . length ) . toBe ( 1 ) ;
98110 } ) ;
99111
100- it ( "should return correct test stats for valid JSON" , ( ) => {
101- expect ( stats . Total ) . toBe ( 10 ) ;
102- expect ( stats . Passed ) . toBe ( 4 ) ;
103- expect ( stats . Failed ) . toBe ( 3 ) ;
104- expect ( stats . Incomplete ) . toBe ( 2 ) ;
105- expect ( stats . NotRun ) . toBe ( 1 ) ;
106- expect ( stats . Duration ) . toBeCloseTo ( 1.83 ) ;
112+ it ( "should return correct overall stats for valid JSON" , ( ) => {
113+ expect ( overallStats . Total ) . toBe ( 10 ) ;
114+ expect ( overallStats . Passed ) . toBe ( 4 ) ;
115+ expect ( overallStats . Failed ) . toBe ( 3 ) ;
116+ expect ( overallStats . Incomplete ) . toBe ( 2 ) ;
117+ expect ( overallStats . NotRun ) . toBe ( 1 ) ;
118+ expect ( overallStats . Duration ) . toBeCloseTo ( 1.83 ) ;
119+ } ) ;
120+
121+ it ( "should return correct session stats for valid JSON" , ( ) => {
122+ const sessionStats = testSessions [ 0 ] . Stats ;
123+ expect ( sessionStats . Total ) . toBe ( 10 ) ;
124+ expect ( sessionStats . Passed ) . toBe ( 4 ) ;
125+ expect ( sessionStats . Failed ) . toBe ( 3 ) ;
126+ expect ( sessionStats . Incomplete ) . toBe ( 2 ) ;
127+ expect ( sessionStats . NotRun ) . toBe ( 1 ) ;
128+ expect ( sessionStats . Duration ) . toBeCloseTo ( 1.83 ) ;
107129 } ) ;
108130
109131 it ( "should return correct test files data for valid JSON" , ( ) => {
132+ const testResults = testSessions [ 0 ] . TestResults ;
110133 expect ( testResults [ 0 ] [ 0 ] . Path ) . toBe ( path . join ( "visualization" , "tests" , "TestExamples1" ) ) ;
111134 expect ( testResults [ 1 ] [ 0 ] . Path ) . toBe (
112135 path . join ( "visualization" , "duplicate_tests" , "TestExamples2" ) ,
@@ -120,6 +143,7 @@ describe("Artifact Processing Tests", () => {
120143 } ) ;
121144
122145 it ( "should return correct test cases data for valid JSON" , ( ) => {
146+ const testResults = testSessions [ 0 ] . TestResults ;
123147 expect ( testResults [ 0 ] [ 0 ] . TestCases [ 0 ] . Name ) . toBe ( "testNonLeapYear" ) ;
124148 expect ( testResults [ 0 ] [ 0 ] . TestCases [ 4 ] . Name ) . toBe ( "testLeapYear" ) ;
125149 expect ( testResults [ 0 ] [ 0 ] . TestCases [ 7 ] . Name ) . toBe ( "testValidDateFormat" ) ;
@@ -148,12 +172,100 @@ describe("Artifact Processing Tests", () => {
148172 ) ;
149173 } ) ;
150174
175+ it ( "should handle test results with undefined Details property" , ( ) => {
176+ const runnerTemp = path . join ( import . meta. dirname , ".." ) ;
177+ const testFilePath = path . join ( runnerTemp , "matlabTestResults_undefined_details.json" ) ;
178+
179+ // Create test data with undefined Details
180+ const testData = [ [
181+ {
182+ BaseFolder : "/workspace/tests" ,
183+ TestResult : {
184+ Name : "TestFile/testCase1" ,
185+ Duration : 0.5 ,
186+ Failed : false ,
187+ Incomplete : false ,
188+ Passed : true ,
189+ // Details property is intentionally omitted
190+ }
191+ }
192+ ] ] ;
193+
194+ try {
195+ fs . writeFileSync ( testFilePath , JSON . stringify ( testData ) ) ;
196+
197+ const result = testResultsSummary . getTestResults ( runnerTemp , "123" , "/workspace" ) ;
198+
199+ expect ( result ) . not . toBeNull ( ) ;
200+ if ( result ) {
201+ expect ( result . TestSessions . length ) . toBeGreaterThan ( 0 ) ;
202+ const session = result . TestSessions . find ( s => s . FileName === "matlabTestResults_undefined_details.json" ) ;
203+ expect ( session ) . toBeDefined ( ) ;
204+ if ( session ) {
205+ expect ( session . TestResults . length ) . toBeGreaterThan ( 0 ) ;
206+ const testFile = session . TestResults [ 0 ] [ 0 ] ;
207+ expect ( testFile . TestCases . length ) . toBe ( 1 ) ;
208+ expect ( testFile . TestCases [ 0 ] . Diagnostics ) . toEqual ( [ ] ) ;
209+ }
210+ }
211+ } finally {
212+ if ( nodeFs . existsSync ( testFilePath ) ) {
213+ nodeFs . unlinkSync ( testFilePath ) ;
214+ }
215+ }
216+ } ) ;
217+
218+ it ( "should handle test results with Details but no DiagnosticRecord" , ( ) => {
219+ const runnerTemp = path . join ( import . meta. dirname , ".." ) ;
220+ const testFilePath = path . join ( runnerTemp , "matlabTestResults_no_diagnostics.json" ) ;
221+
222+ // Create test data with Details but no DiagnosticRecord
223+ const testData = [ [
224+ {
225+ BaseFolder : "/workspace/tests" ,
226+ TestResult : {
227+ Name : "TestFile/testCase1" ,
228+ Duration : 0.5 ,
229+ Failed : false ,
230+ Incomplete : false ,
231+ Passed : true ,
232+ Details : { }
233+ }
234+ }
235+ ] ] ;
236+
237+ try {
238+ fs . writeFileSync ( testFilePath , JSON . stringify ( testData ) ) ;
239+
240+ const result = testResultsSummary . getTestResults ( runnerTemp , "123" , "/workspace" ) ;
241+
242+ expect ( result ) . not . toBeNull ( ) ;
243+ if ( result ) {
244+ expect ( result . TestSessions . length ) . toBeGreaterThan ( 0 ) ;
245+ const session = result . TestSessions . find ( s => s . FileName === "matlabTestResults_no_diagnostics.json" ) ;
246+ expect ( session ) . toBeDefined ( ) ;
247+ if ( session ) {
248+ expect ( session . TestResults . length ) . toBeGreaterThan ( 0 ) ;
249+ const testFile = session . TestResults [ 0 ] [ 0 ] ;
250+ expect ( testFile . TestCases . length ) . toBe ( 1 ) ;
251+ expect ( testFile . TestCases [ 0 ] . Diagnostics ) . toEqual ( [ ] ) ;
252+ }
253+ }
254+ } finally {
255+ if ( nodeFs . existsSync ( testFilePath ) ) {
256+ nodeFs . unlinkSync ( testFilePath ) ;
257+ }
258+ }
259+ } ) ;
260+
151261 it ( "should write test results data to the GitHub job summary" , ( ) => {
152262 if ( testResultsData ) {
153- const actionName = process . env . GITHUB_ACTION || "" ;
154263 testResultsSummary . addSummary ( testResultsData , null ) ;
155264
265+ // Should have: 1 main heading + 1 session heading = 2 total
156266 expect ( core . summary . addHeading ) . toHaveBeenCalledTimes ( 2 ) ;
267+
268+ // First heading: overall results
157269 expect ( core . summary . addHeading ) . toHaveBeenNthCalledWith (
158270 1 ,
159271 expect . stringContaining ( "MATLAB Test Results " ) ,
@@ -172,9 +284,47 @@ describe("Artifact Processing Tests", () => {
172284 1 ,
173285 expect . stringContaining ( "ℹ️</a>" ) ,
174286 ) ;
175- expect ( core . summary . addHeading ) . toHaveBeenNthCalledWith ( 2 , "All tests" , 3 ) ;
287+
288+ // Second heading: session (no session number for single session)
289+ expect ( core . summary . addHeading ) . toHaveBeenNthCalledWith ( 2 , "Test Session" , 3 ) ;
176290
177- expect ( core . summary . addRaw ) . toHaveBeenCalledTimes ( 2 ) ;
291+ // Should have: 1 overall header + 1 session header + 1 detailed results = 3 total
292+ expect ( core . summary . addRaw ) . toHaveBeenCalledTimes ( 3 ) ;
293+ }
294+ } ) ;
295+
296+ it ( "should show session numbers when multiple sessions exist" , ( ) => {
297+ if ( testResultsData ) {
298+ // Create a mock with multiple sessions
299+ const multiSessionData : TestResultsData = {
300+ TestSessions : [
301+ testResultsData . TestSessions [ 0 ] ,
302+ {
303+ FileName : "matlabTestResults_20250101_120001_000.json" ,
304+ TestResults : testResultsData . TestSessions [ 0 ] . TestResults ,
305+ Stats : testResultsData . TestSessions [ 0 ] . Stats ,
306+ } ,
307+ ] ,
308+ OverallStats : {
309+ Total : testResultsData . OverallStats . Total * 2 ,
310+ Passed : testResultsData . OverallStats . Passed * 2 ,
311+ Failed : testResultsData . OverallStats . Failed * 2 ,
312+ Incomplete : testResultsData . OverallStats . Incomplete * 2 ,
313+ NotRun : testResultsData . OverallStats . NotRun * 2 ,
314+ Duration : testResultsData . OverallStats . Duration * 2 ,
315+ } ,
316+ } ;
317+
318+ // Clear previous mock calls
319+ ( core . summary . addHeading as jest . Mock ) . mockClear ( ) ;
320+ ( core . summary . addRaw as jest . Mock ) . mockClear ( ) ;
321+
322+ testResultsSummary . addSummary ( multiSessionData , null ) ;
323+
324+ // Should have: 1 main heading + 2 session headings = 3 total
325+ expect ( core . summary . addHeading ) . toHaveBeenCalledTimes ( 3 ) ;
326+ expect ( core . summary . addHeading ) . toHaveBeenNthCalledWith ( 2 , "Test Session (Session 1)" , 3 ) ;
327+ expect ( core . summary . addHeading ) . toHaveBeenNthCalledWith ( 3 , "Test Session (Session 2)" , 3 ) ;
178328 }
179329 } ) ;
180330} ) ;
@@ -347,10 +497,14 @@ describe("Error Handling Tests", () => {
347497 NotRun : 0 ,
348498 Duration : 0.5 ,
349499 } ;
350- const mockTestResults : MatlabTestFile [ ] [ ] = [ ] ;
500+ const mockTestResults : MatlabTestFile [ ] = [ ] ;
351501 const mockTestResultsData : TestResultsData = {
352- TestResults : mockTestResults ,
353- Stats : mockStats ,
502+ TestSessions : [ {
503+ FileName : "test.json" ,
504+ TestResults : [ mockTestResults ] ,
505+ Stats : mockStats ,
506+ } ] ,
507+ OverallStats : mockStats ,
354508 } ;
355509
356510 // This should not throw, but should log the error
@@ -376,7 +530,7 @@ describe("Error Handling Tests", () => {
376530 process . env . GITHUB_ACTION = "run-tests" ;
377531
378532 // Create a file with invalid JSON
379- const invalidJsonPath = path . join ( process . env . RUNNER_TEMP , "matlabTestResults123 .json" ) ;
533+ const invalidJsonPath = path . join ( process . env . RUNNER_TEMP , "matlabTestResults_invalid .json" ) ;
380534 fs . writeFileSync ( invalidJsonPath , "{ invalid json content" ) ;
381535
382536 try {
@@ -385,7 +539,12 @@ describe("Error Handling Tests", () => {
385539 process . env . GITHUB_RUN_ID ,
386540 "" ,
387541 ) ;
388- expect ( result ) . toBeNull ( ) ;
542+
543+ // Should return data but skip the invalid file
544+ if ( result ) {
545+ // The invalid file should be skipped, so we might have 0 sessions
546+ expect ( result . TestSessions ) . toBeDefined ( ) ;
547+ }
389548
390549 // Verify error was logged
391550 expect ( consoleSpy ) . toHaveBeenCalledWith (
@@ -417,8 +576,8 @@ describe("Error Handling Tests", () => {
417576 process . env . GITHUB_ACTION = "run-tests" ;
418577
419578 // Create a valid JSON file
420- const validJsonPath = path . join ( process . env . RUNNER_TEMP , "matlabTestResults123 .json" ) ;
421- fs . writeFileSync ( validJsonPath , "[]" ) ; // Empty array - valid JSON
579+ const validJsonPath = path . join ( process . env . RUNNER_TEMP , "matlabTestResults_delete_test .json" ) ;
580+ fs . writeFileSync ( validJsonPath , "[[] ]" ) ; // Empty array - valid JSON
422581
423582 try {
424583 const result = testResultsSummary . getTestResults (
@@ -427,10 +586,10 @@ describe("Error Handling Tests", () => {
427586 "" ,
428587 ) ;
429588
589+ // Should still return results even if deletion fails
590+ expect ( result ) . toBeDefined ( ) ;
430591 if ( result ) {
431- // Should still return results even if deletion fails
432- expect ( result ) . toBeDefined ( ) ;
433- expect ( result . TestResults ) . toEqual ( [ ] ) ;
592+ expect ( result . TestSessions . length ) . toBeGreaterThanOrEqual ( 0 ) ;
434593 }
435594
436595 // Verify deletion error was logged
@@ -456,4 +615,25 @@ describe("Error Handling Tests", () => {
456615 }
457616 }
458617 } ) ;
618+
619+ it ( "should handle directory read errors gracefully" , ( ) => {
620+ const consoleSpy = jest . spyOn ( console , "error" ) . mockImplementation ( ( ) => { } ) ;
621+
622+ // Use a non-existent directory
623+ const nonExistentDir = path . join ( import . meta. dirname , "non_existent_directory_12345" ) ;
624+
625+ const result = testResultsSummary . getTestResults (
626+ nonExistentDir ,
627+ "123" ,
628+ "" ,
629+ ) ;
630+
631+ expect ( result ) . toBeNull ( ) ;
632+ expect ( consoleSpy ) . toHaveBeenCalledWith (
633+ expect . stringContaining ( `An error occurred while reading directory ${ nonExistentDir } ` ) ,
634+ expect . any ( Error ) ,
635+ ) ;
636+
637+ consoleSpy . mockRestore ( ) ;
638+ } ) ;
459639} ) ;
0 commit comments