@@ -55,7 +55,7 @@ const mockProblem = {
5555 ] ,
5656} ;
5757
58- const mockProblems = [
58+ const _mockProblems = [
5959 {
6060 id : 'prev-problem' ,
6161 title : 'Previous Problem' ,
@@ -199,7 +199,7 @@ jest.mock('@/lib/test-runner', () => ({
199199} ) ) ;
200200
201201// Track editor instances
202- const editorInstanceCount = 0 ;
202+ const _editorInstanceCount = 0 ;
203203
204204// Mock CodeEditor component
205205jest . mock ( '@/components/CodeEditor' , ( ) => {
@@ -279,7 +279,7 @@ jest.mock('@/components/TestResults', () => {
279279 < div data-testid = "results-count" > { results . length } </ div >
280280 { error && < div data-testid = "error-message" > { error } </ div > }
281281 { results . map ( ( r , i ) => (
282- < div key = { i } data-testid = { `result-${ i } ` } >
282+ < div key = { `test-result- ${ i } - ${ r . passed ? 'pass' : 'fail' } ` } data-testid = { `result-${ i } ` } >
283283 { r . passed ? 'passed' : 'failed' }
284284 </ div >
285285 ) ) }
@@ -613,7 +613,7 @@ describe('Problem Detail Page', () => {
613613 expect ( screen . getByRole ( 'button' , { name : / R u n n i n g .../ i } ) ) . toBeDisabled ( ) ;
614614
615615 await act ( async ( ) => {
616- resolveTests ! ( { allPassed : false , results : [ ] } ) ;
616+ resolveTests ?. ( { allPassed : false , results : [ ] } ) ;
617617 } ) ;
618618 } ) ;
619619
@@ -917,7 +917,7 @@ describe('Problem Detail Page', () => {
917917 expect ( screen . getByTestId ( 'is-running' ) ) . toHaveTextContent ( 'true' ) ;
918918
919919 await act ( async ( ) => {
920- resolveTests ! ( { allPassed : false , results : [ ] } ) ;
920+ resolveTests ?. ( { allPassed : false , results : [ ] } ) ;
921921 } ) ;
922922
923923 expect ( screen . getByTestId ( 'is-running' ) ) . toHaveTextContent ( 'false' ) ;
@@ -1002,7 +1002,7 @@ describe('Problem Page Edge Cases', () => {
10021002 // Click the solution button multiple times to exercise both branches of handleToggleSolution
10031003 // First click - shows solution (else branch)
10041004 await act ( async ( ) => {
1005- fireEvent . click ( solutionButton ! ) ;
1005+ if ( solutionButton ) fireEvent . click ( solutionButton ) ;
10061006 } ) ;
10071007
10081008 // Wait a tick for React to process
@@ -1012,15 +1012,161 @@ describe('Problem Page Edge Cases', () => {
10121012
10131013 // Second click - should attempt to hide solution (if branch - line 122)
10141014 await act ( async ( ) => {
1015- fireEvent . click ( solutionButton ! ) ;
1015+ if ( solutionButton ) fireEvent . click ( solutionButton ) ;
10161016 } ) ;
10171017
10181018 // Third click - just to make sure component is stable
10191019 await act ( async ( ) => {
1020- fireEvent . click ( solutionButton ! ) ;
1020+ if ( solutionButton ) fireEvent . click ( solutionButton ) ;
10211021 } ) ;
10221022
10231023 // Verify the component is still functional
10241024 expect ( screen . getByTestId ( 'code-editor' ) ) . toBeInTheDocument ( ) ;
10251025 } ) ;
1026+
1027+ it ( 'should handle non-Error error types in catch block (string rejection)' , async ( ) => {
1028+ const user = userEvent . setup ( ) ;
1029+ // Simulate a promise rejection with a string (non-Error type)
1030+ // This tests the .catch() handler's handling of err?.toString()
1031+ mockRunTests . mockRejectedValue ( 'String error message' ) ;
1032+
1033+ renderWithProgress ( < ProblemPage /> ) ;
1034+
1035+ const runButton = screen . getByRole ( 'button' , { name : / R u n T e s t s / i } ) ;
1036+ await user . click ( runButton ) ;
1037+
1038+ await waitFor ( ( ) => {
1039+ // The inner .catch() handler formats this as "Error running tests: ..."
1040+ expect ( screen . getByTestId ( 'error-message' ) ) . toHaveTextContent (
1041+ 'Error running tests: String error message'
1042+ ) ;
1043+ } ) ;
1044+ } ) ;
1045+
1046+ it ( 'should handle unknown error types in catch block (null rejection)' , async ( ) => {
1047+ const user = userEvent . setup ( ) ;
1048+ // Simulate a promise rejection with null (no message or toString)
1049+ mockRunTests . mockRejectedValue ( null ) ;
1050+
1051+ renderWithProgress ( < ProblemPage /> ) ;
1052+
1053+ const runButton = screen . getByRole ( 'button' , { name : / R u n T e s t s / i } ) ;
1054+ await user . click ( runButton ) ;
1055+
1056+ await waitFor ( ( ) => {
1057+ // The inner .catch() handler falls back to 'Unknown error'
1058+ expect ( screen . getByTestId ( 'error-message' ) ) . toHaveTextContent (
1059+ 'Error running tests: Unknown error'
1060+ ) ;
1061+ } ) ;
1062+ } ) ;
1063+
1064+ it ( 'should validate and reject whitespace-only code in handleRunTests' , async ( ) => {
1065+ const user = userEvent . setup ( ) ;
1066+ // Test with test-problem but simulate whitespace-only code being submitted
1067+ // by verifying the validation error is shown when runTests returns the error
1068+ mockParams . id = 'test-problem' ;
1069+ mockRunTests . mockClear ( ) ;
1070+
1071+ // Mock returns validation error (simulating what happens with whitespace code)
1072+ mockRunTests . mockResolvedValue ( {
1073+ allPassed : false ,
1074+ results : [ ] ,
1075+ error : 'No code to test. Please write your solution first.' ,
1076+ } ) ;
1077+
1078+ renderWithProgress ( < ProblemPage /> ) ;
1079+
1080+ const runButton = screen . getByRole ( 'button' , { name : / R u n T e s t s / i } ) ;
1081+ await user . click ( runButton ) ;
1082+
1083+ await waitFor ( ( ) => {
1084+ expect ( screen . getByTestId ( 'error-message' ) ) . toHaveTextContent (
1085+ 'No code to test. Please write your solution first.'
1086+ ) ;
1087+ } ) ;
1088+ } ) ;
1089+
1090+ it ( 'should use codeRef to prevent stale closure when toggling solution' , async ( ) => {
1091+ mockParams . id = 'test-problem' ;
1092+ mockRunTests . mockClear ( ) ;
1093+
1094+ renderWithProgress ( < ProblemPage /> ) ;
1095+
1096+ // Type some code in the editor
1097+ const editor = screen . getByTestId ( 'user-editor' ) as HTMLTextAreaElement ;
1098+ await act ( async ( ) => {
1099+ fireEvent . change ( editor , { target : { value : 'const myNewCode = 42;' } } ) ;
1100+ } ) ;
1101+
1102+ // Find the solution button and click to show solution
1103+ const buttons = screen . getAllByRole ( 'button' ) ;
1104+ const solutionButton = buttons . find ( ( btn ) => btn . textContent ?. includes ( 'Solution' ) ) ;
1105+ expect ( solutionButton ) . toBeDefined ( ) ;
1106+
1107+ // Click to show solution - this should save the current code via codeRef
1108+ await act ( async ( ) => {
1109+ if ( solutionButton ) fireEvent . click ( solutionButton ) ;
1110+ } ) ;
1111+
1112+ // Now set up the mock to return success when we run tests
1113+ mockRunTests . mockResolvedValue ( {
1114+ allPassed : true ,
1115+ results : [ { passed : true } ] ,
1116+ } ) ;
1117+
1118+ // Run tests - this should use the saved userCode (from codeRef), not the solution
1119+ const runButton = screen . getByRole ( 'button' , { name : / R u n T e s t s / i } ) ;
1120+ await act ( async ( ) => {
1121+ fireEvent . click ( runButton ) ;
1122+ } ) ;
1123+
1124+ await waitFor ( ( ) => {
1125+ expect ( mockRunTests ) . toHaveBeenCalled ( ) ;
1126+ } ) ;
1127+
1128+ // Verify the test was run with user code, not empty or solution code
1129+ // The mock was called, meaning validation passed (code was not empty)
1130+ expect ( mockRunTests ) . toHaveBeenCalledTimes ( 1 ) ;
1131+ } ) ;
1132+
1133+ it ( 'should preserve user code via codeRef when rapidly changing code and toggling solution' , async ( ) => {
1134+ mockParams . id = 'test-problem' ;
1135+ mockRunTests . mockClear ( ) ;
1136+
1137+ renderWithProgress ( < ProblemPage /> ) ;
1138+
1139+ // Simulate rapid code changes
1140+ const editor = screen . getByTestId ( 'user-editor' ) as HTMLTextAreaElement ;
1141+ await act ( async ( ) => {
1142+ fireEvent . change ( editor , { target : { value : 'step1' } } ) ;
1143+ fireEvent . change ( editor , { target : { value : 'step2' } } ) ;
1144+ fireEvent . change ( editor , { target : { value : 'const finalCode = "preserved";' } } ) ;
1145+ } ) ;
1146+
1147+ // Find and click solution button
1148+ const buttons = screen . getAllByRole ( 'button' ) ;
1149+ const solutionButton = buttons . find ( ( btn ) => btn . textContent ?. includes ( 'Solution' ) ) ;
1150+
1151+ await act ( async ( ) => {
1152+ if ( solutionButton ) fireEvent . click ( solutionButton ) ;
1153+ } ) ;
1154+
1155+ // The userCode should have been saved with the latest value from codeRef
1156+ // Verify by running tests (which uses userCode when solution is shown)
1157+ mockRunTests . mockResolvedValue ( {
1158+ allPassed : true ,
1159+ results : [ { passed : true } ] ,
1160+ } ) ;
1161+
1162+ const runButton = screen . getByRole ( 'button' , { name : / R u n T e s t s / i } ) ;
1163+ await act ( async ( ) => {
1164+ fireEvent . click ( runButton ) ;
1165+ } ) ;
1166+
1167+ await waitFor ( ( ) => {
1168+ // Tests should run successfully with the preserved user code
1169+ expect ( mockRunTests ) . toHaveBeenCalled ( ) ;
1170+ } ) ;
1171+ } ) ;
10261172} ) ;
0 commit comments