@@ -6,14 +6,17 @@ import { type Monaco } from '@monaco-editor/react';
66import { toast } from 'sonner' ;
77import { getCandidateAssessment , submitCandidateAssessment } from '@/lib/api/candidate-assessment' ;
88import { useAssessmentTimer } from '@/lib/hooks/useAssessmentTimer' ;
9+ import useTestRunner from '@/lib/hooks/useTestRunner' ;
910import type {
1011 AssessmentPhase ,
1112 AssessmentQuestion ,
1213 CandidateAssessment ,
1314 OutroReason ,
1415 SectionState ,
16+ TestCaseResultStatus ,
1517} from '@/lib/types/candidate-assessment.types' ;
1618import { createToken } from '@/lib/api/token' ;
19+ import { type ProgrammingLanguage } from '@/generated/prisma' ;
1720
1821function buildInitialSections ( questions : AssessmentQuestion [ ] ) : SectionState [ ] {
1922 return questions . map ( ( q , i ) => {
@@ -52,6 +55,71 @@ export default function useAssessment(assessmentId: string) {
5255 currentSectionIndexRef . current = currentSectionIndex ;
5356 } , [ currentSectionIndex ] ) ;
5457
58+ const {
59+ error : testError ,
60+ loading : testLoading ,
61+ output : testOutput ,
62+ runAssessmentTests,
63+ reset : resetTests ,
64+ } = useTestRunner ( ) ;
65+
66+ useEffect ( ( ) => {
67+ if ( testLoading ) {
68+ setSections ( ( prev ) =>
69+ prev . map ( ( section ) => ( {
70+ ...section ,
71+ testCaseResults : section . testCaseResults . map ( ( test ) => ( {
72+ ...test ,
73+ status : 'loading' ,
74+ } ) ) ,
75+ } ) )
76+ ) ;
77+ } else if ( testError ) {
78+ setSections ( ( prev ) =>
79+ prev . map ( ( section ) => ( {
80+ ...section ,
81+ testCaseResults : section . testCaseResults . map ( ( test ) => ( {
82+ ...test ,
83+ status : 'runtime_error' ,
84+ actualOutput : 'An error occurred while running tests.' ,
85+ } ) ) ,
86+ } ) )
87+ ) ;
88+ } else if ( testOutput ) {
89+ setSections ( ( prev ) =>
90+ prev . map ( ( section ) => {
91+ return {
92+ ...section ,
93+ testCaseResults : section . testCaseResults . map ( ( test , i ) => ( {
94+ ...test ,
95+ status : resolveStatusId ( testOutput [ i ] ?. statusId ?? 0 ) ,
96+ actualOutput : testOutput [ i ] ?. stdout ?? testOutput [ i ] ?. stderr ?? '' ,
97+ } ) ) ,
98+ } ;
99+ } )
100+ ) ;
101+ }
102+ } , [ testLoading , testError , testOutput , currentSectionIndex ] ) ;
103+
104+ function resolveStatusId ( statusId : number ) : TestCaseResultStatus {
105+ switch ( statusId ) {
106+ case 3 :
107+ return 'passed' ;
108+ case 5 :
109+ case 7 :
110+ case 8 :
111+ case 9 :
112+ case 10 :
113+ case 11 :
114+ case 12 :
115+ return 'runtime_error' ;
116+ case 4 :
117+ return 'failed' ;
118+ default :
119+ return 'failed' ;
120+ }
121+ }
122+
55123 // the timer is in seconds however our model is in minutes
56124 const totalEstimatedMinutes = sections . reduce (
57125 ( sum , s ) => sum + ( s . taskTemplate . estimatedTime ?? 0 ) ,
@@ -122,6 +190,7 @@ export default function useAssessment(assessmentId: string) {
122190 handleSubmitAssessment ( 'submitted' ) ;
123191 } else {
124192 setIsTransitioning ( true ) ;
193+ resetTests ( ) ;
125194 setTimeout ( ( ) => {
126195 setSections ( ( prev ) =>
127196 prev . map ( ( s , i ) => {
@@ -137,10 +206,19 @@ export default function useAssessment(assessmentId: string) {
137206 }
138207 }
139208
140- function updateCode ( code : string ) {
141- setSections ( ( prev ) =>
142- prev . map ( ( s , i ) => ( i === currentSectionIndexRef . current ? { ...s , code } : s ) )
143- ) ;
209+ function updateCode ( ) {
210+ const code = editorRef . current ?. getValue ( ) ?? '' ;
211+ setSections ( ( prev ) => prev . map ( ( s , i ) => ( i === currentSectionIndex ? { ...s , code } : s ) ) ) ;
212+
213+ return code ;
214+ }
215+
216+ function runTests ( ) {
217+ const code = updateCode ( ) || '' ;
218+ const section = sections [ currentSectionIndex ] ;
219+ if ( ! section ) return ;
220+
221+ runAssessmentTests ( section . taskTemplateId , code , section . language as ProgrammingLanguage ) ;
144222 }
145223
146224 function changeLanguage ( language : string ) {
@@ -162,58 +240,12 @@ export default function useAssessment(assessmentId: string) {
162240 }
163241 }
164242
165- // TODO: Replace with Judge0 execution
166- function runTests ( ) {
167- const section = sections [ currentSectionIndex ] ;
168- if ( ! section ) return ;
169- if ( section . testCaseResults . some ( ( r ) => r . status === 'loading' ) ) return ;
170-
171- const MOCK_STATUSES = [ 'passed' , 'failed' , 'runtime_error' ] as const ;
172-
173- setSections ( ( prev ) =>
174- prev . map ( ( s , i ) =>
175- i === currentSectionIndex
176- ? {
177- ...s ,
178- testCaseResults : s . testCaseResults . map ( ( ) => ( {
179- status : 'loading' as const ,
180- } ) ) ,
181- }
182- : s
183- )
184- ) ;
185-
186- setTimeout ( ( ) => {
187- setSections ( ( prev ) =>
188- prev . map ( ( s , i ) => {
189- if ( i !== currentSectionIndex ) return s ;
190- return {
191- ...s ,
192- testCaseResults : s . testCaseResults . map ( ( _ , idx ) => {
193- const status =
194- MOCK_STATUSES [ Math . floor ( Math . random ( ) * MOCK_STATUSES . length ) ] ;
195- const tc = section . taskTemplate . publicTestCases [ idx ] ;
196- if ( status === 'passed' )
197- return { status, actualOutput : tc ?. output ?? '' } ;
198- if ( status === 'runtime_error' )
199- return {
200- status,
201- actualOutput : 'Timeout Error: Execution Time Exceeded' ,
202- } ;
203- return { status, actualOutput : 'wrong_answer' } ;
204- } ) ,
205- } ;
206- } )
207- ) ;
208- } , 1500 ) ;
209- }
210-
211243 function handleEditorMount ( editorInstance : editor . IStandaloneCodeEditor , monaco : Monaco ) {
212244 editorRef . current = editorInstance ;
213245 monacoRef . current = monaco ;
214246
215247 editorInstance . onDidChangeModelContent ( ( ) => {
216- updateCode ( editorInstance . getValue ( ) ) ;
248+ updateCode ( ) ;
217249 } ) ;
218250 }
219251
@@ -234,6 +266,7 @@ export default function useAssessment(assessmentId: string) {
234266 isSubmitting,
235267 isTransitioning,
236268 error,
269+ testError,
237270 startAssessment,
238271 submitAndContinue,
239272 updateCode,
0 commit comments