@@ -7,6 +7,10 @@ import {
77 filterPassedTestsHelper ,
88 formatDurationFromTimesHelper ,
99 formatDurationHelper ,
10+ formatTestMessageHelper ,
11+ formatTestMessagePreCodeHelper ,
12+ getCtrfEmojiHelper ,
13+ limitFailedTestsHelper ,
1014 sortTestsByFailRateHelper ,
1115 sortTestsByFlakyRateHelper ,
1216} from "../../src/helpers/ctrf" ;
@@ -174,6 +178,128 @@ describe("CTRF Helpers", () => {
174178 } ) ;
175179 } ) ;
176180
181+ describe ( "limitFailedTestsHelper" , ( ) => {
182+ it ( "should filter failed tests and limit to specified number" , ( ) => {
183+ const result = limitFailedTestsHelper . fn ( mockTests , 1 ) ;
184+
185+ if ( Array . isArray ( result ) ) {
186+ expect ( result ) . toHaveLength ( 1 ) ;
187+ expect ( result [ 0 ] . status ) . toBe ( "failed" ) ;
188+ expect ( result [ 0 ] . name ) . toBe ( "Failing Test 1" ) ;
189+ }
190+ } ) ;
191+
192+ it ( "should return all failed tests when limit is greater than failed count" , ( ) => {
193+ const result = limitFailedTestsHelper . fn ( mockTests , 10 ) ;
194+
195+ if ( Array . isArray ( result ) ) {
196+ expect ( result ) . toHaveLength ( 2 ) ; // Only 2 failed tests in mockTests
197+ expect ( result . every ( ( test : Test ) => test . status === "failed" ) ) . toBe ( true ) ;
198+ expect ( result [ 0 ] . name ) . toBe ( "Failing Test 1" ) ;
199+ expect ( result [ 1 ] . name ) . toBe ( "Failing Test 2" ) ;
200+ }
201+ } ) ;
202+
203+ it ( "should handle zero limit" , ( ) => {
204+ const result = limitFailedTestsHelper . fn ( mockTests , 0 ) ;
205+ expect ( result ) . toHaveLength ( 0 ) ;
206+ } ) ;
207+
208+ it ( "should handle negative limit by defaulting to 10" , ( ) => {
209+ const result = limitFailedTestsHelper . fn ( mockTests , - 5 ) ;
210+
211+ if ( Array . isArray ( result ) ) {
212+ expect ( result ) . toHaveLength ( 2 ) ; // All 2 failed tests, since -5 defaults to 10
213+ expect ( result . every ( ( test : Test ) => test . status === "failed" ) ) . toBe ( true ) ;
214+ }
215+ } ) ;
216+
217+ it ( "should handle string limit by parsing to number" , ( ) => {
218+ const result = limitFailedTestsHelper . fn ( mockTests , "1" ) ;
219+
220+ if ( Array . isArray ( result ) ) {
221+ expect ( result ) . toHaveLength ( 1 ) ;
222+ expect ( result [ 0 ] . status ) . toBe ( "failed" ) ;
223+ expect ( result [ 0 ] . name ) . toBe ( "Failing Test 1" ) ;
224+ }
225+ } ) ;
226+
227+ it ( "should handle invalid string limit by defaulting to 10" , ( ) => {
228+ const result = limitFailedTestsHelper . fn ( mockTests , "invalid" ) ;
229+
230+ if ( Array . isArray ( result ) ) {
231+ expect ( result ) . toHaveLength ( 2 ) ; // All 2 failed tests, since "invalid" defaults to 10
232+ expect ( result . every ( ( test : Test ) => test . status === "failed" ) ) . toBe ( true ) ;
233+ }
234+ } ) ;
235+
236+ it ( "should handle undefined limit by defaulting to 10" , ( ) => {
237+ const result = limitFailedTestsHelper . fn ( mockTests , undefined ) ;
238+
239+ if ( Array . isArray ( result ) ) {
240+ expect ( result ) . toHaveLength ( 2 ) ; // All 2 failed tests, since undefined defaults to 10
241+ expect ( result . every ( ( test : Test ) => test . status === "failed" ) ) . toBe ( true ) ;
242+ }
243+ } ) ;
244+
245+ it ( "should return empty array when no failed tests" , ( ) => {
246+ const passedOnlyTests : Test [ ] = [
247+ { name : "Passed Test" , status : "passed" , duration : 1000 } ,
248+ { name : "Skipped Test" , status : "skipped" , duration : 0 } ,
249+ ] ;
250+
251+ const result = limitFailedTestsHelper . fn ( passedOnlyTests , 5 ) ;
252+ expect ( result ) . toHaveLength ( 0 ) ;
253+ } ) ;
254+
255+ it ( "should return empty array for non-array input" , ( ) => {
256+ expect ( limitFailedTestsHelper . fn ( null , 5 ) ) . toEqual ( [ ] ) ;
257+ expect ( limitFailedTestsHelper . fn ( undefined , 5 ) ) . toEqual ( [ ] ) ;
258+ expect ( limitFailedTestsHelper . fn ( "not-an-array" , 5 ) ) . toEqual ( [ ] ) ;
259+ expect ( limitFailedTestsHelper . fn ( { } , 5 ) ) . toEqual ( [ ] ) ;
260+ } ) ;
261+
262+ it ( "should handle edge case with many failed tests" , ( ) => {
263+ const manyFailedTests : Test [ ] = Array . from ( { length : 20 } , ( _ , i ) => ( {
264+ name : `Failed Test ${ i + 1 } ` ,
265+ status : "failed" as const ,
266+ duration : 1000 ,
267+ } ) ) ;
268+
269+ const result = limitFailedTestsHelper . fn ( manyFailedTests , 3 ) ;
270+
271+ if ( Array . isArray ( result ) ) {
272+ expect ( result ) . toHaveLength ( 3 ) ;
273+ expect ( result [ 0 ] . name ) . toBe ( "Failed Test 1" ) ;
274+ expect ( result [ 1 ] . name ) . toBe ( "Failed Test 2" ) ;
275+ expect ( result [ 2 ] . name ) . toBe ( "Failed Test 3" ) ;
276+ expect ( result . every ( ( test : Test ) => test . status === "failed" ) ) . toBe ( true ) ;
277+ }
278+ } ) ;
279+
280+ it ( "should preserve order of failed tests" , ( ) => {
281+ const orderedTests : Test [ ] = [
282+ { name : "First Failed" , status : "failed" , duration : 100 } ,
283+ { name : "Passed Test" , status : "passed" , duration : 200 } ,
284+ { name : "Second Failed" , status : "failed" , duration : 300 } ,
285+ { name : "Third Failed" , status : "failed" , duration : 400 } ,
286+ ] ;
287+
288+ const result = limitFailedTestsHelper . fn ( orderedTests , 2 ) ;
289+
290+ if ( Array . isArray ( result ) ) {
291+ expect ( result ) . toHaveLength ( 2 ) ;
292+ expect ( result [ 0 ] . name ) . toBe ( "First Failed" ) ;
293+ expect ( result [ 1 ] . name ) . toBe ( "Second Failed" ) ;
294+ }
295+ } ) ;
296+
297+ it ( "should be categorized as CTRF helper" , ( ) => {
298+ expect ( limitFailedTestsHelper . category ) . toBe ( "CTRF" ) ;
299+ expect ( limitFailedTestsHelper . name ) . toBe ( "limitFailedTests" ) ;
300+ } ) ;
301+ } ) ;
302+
177303 describe ( "filterOtherTestsHelper" , ( ) => {
178304 it ( "should filter skipped, pending, and other status tests" , ( ) => {
179305 const result = filterOtherTestsHelper . fn ( mockTests ) ;
@@ -333,7 +459,7 @@ describe("CTRF Helpers", () => {
333459
334460 it ( "should handle negative duration (stop before start)" , ( ) => {
335461 const result = formatDurationFromTimesHelper . fn ( 2000 , 1000 ) ;
336- expect ( result ) . toBe ( "not captured " ) ;
462+ expect ( result ) . toBe ( "1ms " ) ;
337463 } ) ;
338464 } ) ;
339465
@@ -371,6 +497,140 @@ describe("CTRF Helpers", () => {
371497 } ) ;
372498 } ) ;
373499
500+ describe ( "formatTestMessageHelper (formatTestMessage)" , ( ) => {
501+ it ( "should convert ANSI codes to HTML and newlines to <br> for test messages" , ( ) => {
502+ expect ( formatTestMessageHelper . fn ( "Line1\nLine2" ) ) . toBe ( "Line1<br>Line2" ) ;
503+ expect ( formatTestMessageHelper . fn ( "Test \u001b[31mFAILED\u001b[0m\nNext line" ) ) . toBe ( "Test <span style=\"color:#A00\">FAILED</span><br>Next line" ) ;
504+ } ) ;
505+
506+ it ( "should handle multiple newlines in test output" , ( ) => {
507+ expect ( formatTestMessageHelper . fn ( "Error\n\nStack trace" ) ) . toBe ( "Error<br><br>Stack trace" ) ;
508+ expect ( formatTestMessageHelper . fn ( "Line1\n\n\nLine4" ) ) . toBe ( "Line1<br><br>Line4" ) ;
509+ } ) ;
510+
511+ it ( "should handle complex ANSI sequences in test failures" , ( ) => {
512+ const input = "\u001b[32mExpected\u001b[0m\n\u001b[31mActual\u001b[0m\n\u001b[1mDiff\u001b[0m" ;
513+ const expected = "<span style=\"color:#0A0\">Expected</span><br><span style=\"color:#A00\">Actual</span><br><b>Diff</b>" ;
514+ expect ( formatTestMessageHelper . fn ( input ) ) . toBe ( expected ) ;
515+ } ) ;
516+
517+ it ( "should handle empty and null test messages" , ( ) => {
518+ expect ( formatTestMessageHelper . fn ( "" ) ) . toBe ( "No message available" ) ;
519+ expect ( formatTestMessageHelper . fn ( null as unknown ) ) . toBe ( "" ) ;
520+ expect ( formatTestMessageHelper . fn ( undefined as unknown ) ) . toBe ( "" ) ;
521+ } ) ;
522+
523+ it ( "should handle non-string inputs gracefully" , ( ) => {
524+ expect ( formatTestMessageHelper . fn ( 123 as unknown ) ) . toBe ( "" ) ;
525+ expect ( formatTestMessageHelper . fn ( { } as unknown ) ) . toBe ( "" ) ;
526+ expect ( formatTestMessageHelper . fn ( [ ] as unknown ) ) . toBe ( "" ) ;
527+ } ) ;
528+
529+ it ( "should handle real-world test failure scenarios" , ( ) => {
530+ const assertionError = "AssertionError: expected 'false' to be 'true'\n\u001b[31m- false\u001b[0m\n\u001b[32m+ true\u001b[0m" ;
531+ const expected = "AssertionError: expected 'false' to be 'true'<br><span style=\"color:#A00\">- false</span><br><span style=\"color:#0A0\">+ true</span>" ;
532+ expect ( formatTestMessageHelper . fn ( assertionError ) ) . toBe ( expected ) ;
533+ } ) ;
534+
535+ it ( "should handle Jest/Vitest style test output" , ( ) => {
536+ const jestOutput = "\u001b[1m\u001b[32m✓\u001b[0m Test passed\n\u001b[1m\u001b[31m✗\u001b[0m Test failed\nStack trace" ;
537+ const result = formatTestMessageHelper . fn ( jestOutput ) ;
538+ expect ( result ) . toContain ( "<b><span style=\"color:#0A0\">✓</span></b>" ) ;
539+ expect ( result ) . toContain ( "<b><span style=\"color:#A00\">✗</span></b>" ) ;
540+ expect ( result ) . toContain ( "<br>" ) ;
541+ } ) ;
542+
543+ it ( "should be categorized as CTRF helper" , ( ) => {
544+ expect ( formatTestMessageHelper . category ) . toBe ( "CTRF" ) ;
545+ expect ( formatTestMessageHelper . name ) . toBe ( "formatTestMessage" ) ;
546+ } ) ;
547+ } ) ;
548+
549+ describe ( "formatTestMessagePreCodeHelper" , ( ) => {
550+ it ( "should convert ANSI codes to HTML but preserve newlines for code formatting" , ( ) => {
551+ expect ( formatTestMessagePreCodeHelper . fn ( "Line1\nLine2" ) ) . toBe ( "Line1\nLine2" ) ;
552+ expect ( formatTestMessagePreCodeHelper . fn ( "Code \u001b[31mError\u001b[0m\nNext line" ) ) . toBe ( "Code <span style=\"color:#A00\">Error</span>\nNext line" ) ;
553+ } ) ;
554+
555+ it ( "should minimize consecutive newlines but preserve single ones" , ( ) => {
556+ expect ( formatTestMessagePreCodeHelper . fn ( "Line1\n\nLine3" ) ) . toBe ( "Line1\nLine3" ) ;
557+ expect ( formatTestMessagePreCodeHelper . fn ( "Line1\n\n\nLine4" ) ) . toBe ( "Line1\nLine4" ) ;
558+ expect ( formatTestMessagePreCodeHelper . fn ( "Line1\n\n\n\nLine5" ) ) . toBe ( "Line1\nLine5" ) ;
559+ } ) ;
560+
561+ it ( "should handle stack traces with ANSI codes" , ( ) => {
562+ const stackTrace = "Error: Test failed\n at test.js:10:5\n\u001b[31m at runner.js:45:12\u001b[0m\n at main.js:20:3" ;
563+ const expected = "Error: Test failed\n at test.js:10:5\n<span style=\"color:#A00\"> at runner.js:45:12</span>\n at main.js:20:3" ;
564+ expect ( formatTestMessagePreCodeHelper . fn ( stackTrace ) ) . toBe ( expected ) ;
565+ } ) ;
566+
567+ it ( "should handle code blocks with syntax highlighting" , ( ) => {
568+ const codeBlock = "function test() {\n \u001b[32mconsole.log\u001b[0m('hello');\n \u001b[31mthrow new Error\u001b[0m('test');\n}" ;
569+ const expected = "function test() {\n <span style=\"color:#0A0\">console.log</span>('hello');\n <span style=\"color:#A00\">throw new Error</span>('test');\n}" ;
570+ expect ( formatTestMessagePreCodeHelper . fn ( codeBlock ) ) . toBe ( expected ) ;
571+ } ) ;
572+
573+ it ( "should handle empty and null inputs" , ( ) => {
574+ expect ( formatTestMessagePreCodeHelper . fn ( "" ) ) . toBe ( "No message available" ) ;
575+ expect ( formatTestMessagePreCodeHelper . fn ( null as unknown ) ) . toBe ( "" ) ;
576+ expect ( formatTestMessagePreCodeHelper . fn ( undefined as unknown ) ) . toBe ( "" ) ;
577+ } ) ;
578+
579+ it ( "should handle non-string inputs gracefully" , ( ) => {
580+ expect ( formatTestMessagePreCodeHelper . fn ( 123 as unknown ) ) . toBe ( "" ) ;
581+ expect ( formatTestMessagePreCodeHelper . fn ( { } as unknown ) ) . toBe ( "" ) ;
582+ expect ( formatTestMessagePreCodeHelper . fn ( [ ] as unknown ) ) . toBe ( "" ) ;
583+ } ) ;
584+
585+ it ( "should be ideal for pre-formatted content" , ( ) => {
586+ const preFormattedContent = "Code:\n function add(a, b) {\n \u001b[33mreturn a + b;\u001b[0m\n }\n\n\nOutput:\n \u001b[32m5\u001b[0m" ;
587+ const expected = "Code:\n function add(a, b) {\n <span style=\"color:#A50\">return a + b;</span>\n }\nOutput:\n <span style=\"color:#0A0\">5</span>" ;
588+ expect ( formatTestMessagePreCodeHelper . fn ( preFormattedContent ) ) . toBe ( expected ) ;
589+ } ) ;
590+
591+ it ( "should be categorized as CTRF helper" , ( ) => {
592+ expect ( formatTestMessagePreCodeHelper . category ) . toBe ( "CTRF" ) ;
593+ expect ( formatTestMessagePreCodeHelper . name ) . toBe ( "formatTestMessagePreCode" ) ;
594+ } ) ;
595+ } ) ;
596+
597+ describe ( "getCtrfEmojiHelper" , ( ) => {
598+ it ( "should return correct emojis for test statuses" , ( ) => {
599+ expect ( getCtrfEmojiHelper . fn ( "passed" ) ) . toBe ( "✅" ) ;
600+ expect ( getCtrfEmojiHelper . fn ( "failed" ) ) . toBe ( "❌" ) ;
601+ expect ( getCtrfEmojiHelper . fn ( "skipped" ) ) . toBe ( "⏭️" ) ;
602+ expect ( getCtrfEmojiHelper . fn ( "pending" ) ) . toBe ( "⏳" ) ;
603+ expect ( getCtrfEmojiHelper . fn ( "other" ) ) . toBe ( "❓" ) ;
604+ } ) ;
605+
606+ it ( "should return correct emojis for categories" , ( ) => {
607+ expect ( getCtrfEmojiHelper . fn ( "build" ) ) . toBe ( "🏗️" ) ;
608+ expect ( getCtrfEmojiHelper . fn ( "duration" ) ) . toBe ( "⏱️" ) ;
609+ expect ( getCtrfEmojiHelper . fn ( "flaky" ) ) . toBe ( "🍂" ) ;
610+ expect ( getCtrfEmojiHelper . fn ( "tests" ) ) . toBe ( "📝" ) ;
611+ expect ( getCtrfEmojiHelper . fn ( "result" ) ) . toBe ( "🧪" ) ;
612+ expect ( getCtrfEmojiHelper . fn ( "warning" ) ) . toBe ( "⚠️" ) ;
613+ } ) ;
614+
615+ it ( "should return default emoji for unknown status" , ( ) => {
616+ expect ( getCtrfEmojiHelper . fn ( "unknown" ) ) . toBe ( "❓" ) ;
617+ expect ( getCtrfEmojiHelper . fn ( "invalid" ) ) . toBe ( "❓" ) ;
618+ } ) ;
619+
620+ it ( "should return default emoji for non-string input" , ( ) => {
621+ expect ( getCtrfEmojiHelper . fn ( null ) ) . toBe ( "❓" ) ;
622+ expect ( getCtrfEmojiHelper . fn ( undefined ) ) . toBe ( "❓" ) ;
623+ expect ( getCtrfEmojiHelper . fn ( 123 ) ) . toBe ( "❓" ) ;
624+ expect ( getCtrfEmojiHelper . fn ( { } ) ) . toBe ( "❓" ) ;
625+ expect ( getCtrfEmojiHelper . fn ( [ ] ) ) . toBe ( "❓" ) ;
626+ } ) ;
627+
628+ it ( "should be categorized as CTRF helper" , ( ) => {
629+ expect ( getCtrfEmojiHelper . category ) . toBe ( "CTRF" ) ;
630+ expect ( getCtrfEmojiHelper . name ) . toBe ( "getCtrfEmoji" ) ;
631+ } ) ;
632+ } ) ;
633+
374634 describe ( "Helper metadata" , ( ) => {
375635 it ( "should have correct helper names" , ( ) => {
376636 expect ( sortTestsByFlakyRateHelper . name ) . toBe ( "sortTestsByFlakyRate" ) ;
@@ -384,6 +644,10 @@ describe("CTRF Helpers", () => {
384644 "formatDurationFromTimes" ,
385645 ) ;
386646 expect ( formatDurationHelper . name ) . toBe ( "formatDuration" ) ;
647+ expect ( formatTestMessageHelper . name ) . toBe ( "formatTestMessage" ) ;
648+ expect ( formatTestMessagePreCodeHelper . name ) . toBe ( "formatTestMessagePreCode" ) ;
649+ expect ( getCtrfEmojiHelper . name ) . toBe ( "getCtrfEmoji" ) ;
650+ expect ( limitFailedTestsHelper . name ) . toBe ( "limitFailedTests" ) ;
387651 } ) ;
388652
389653 it ( "should have correct helper categories" , ( ) => {
@@ -392,11 +656,15 @@ describe("CTRF Helpers", () => {
392656 filterPassedTestsHelper ,
393657 filterFailedTestsHelper ,
394658 filterOtherTestsHelper ,
659+ limitFailedTestsHelper ,
395660 countFlakyTestsHelper ,
396661 anyFlakyTestsHelper ,
397662 sortTestsByFailRateHelper ,
398663 formatDurationFromTimesHelper ,
399664 formatDurationHelper ,
665+ formatTestMessageHelper ,
666+ formatTestMessagePreCodeHelper ,
667+ getCtrfEmojiHelper ,
400668 ] ;
401669
402670 helpers . forEach ( ( helper ) => {
0 commit comments