@@ -36,6 +36,7 @@ describe('ChallengeInfoComponent', () => {
3636 let modalService : NgbModal
3737 let mockActiveIdSubject : Subject < ChallengeTab >
3838 let mockChallengeCompletedSubject : Subject < string >
39+ let mockSolutionSentSubject : Subject < boolean >
3940
4041 beforeEach ( async ( ) => {
4142 // Create new subjects for each test
@@ -87,6 +88,12 @@ describe('ChallengeInfoComponent', () => {
8788 Object . defineProperty ( component [ 'solutionService' ] , 'challengeCompleted$' , {
8889 value : mockChallengeCompletedSubject . asObservable ( )
8990 } )
91+
92+ // Provide solutionSent subject to trigger solution-sent logic
93+ mockSolutionSentSubject = new Subject < boolean > ( )
94+ Object . defineProperty ( component [ 'solutionService' ] , 'solutionSent$' , {
95+ value : mockSolutionSentSubject . asObservable ( )
96+ } )
9097
9198 fixture . detectChanges ( )
9299 } )
@@ -384,6 +391,49 @@ describe('ChallengeInfoComponent', () => {
384391 expect ( component . isEditorChallengeVisible ) . toBe ( false ) ;
385392 } ) ;
386393
394+ it ( 'should react to solutionSent$ emission and load solutions and user data' , ( ) => {
395+ // Spy on methods called when solution is sent
396+ const loadSolutionsSpy = jest . spyOn ( component , 'loadSolutions' ) . mockImplementation ( )
397+ const loadUserSolutionSpy = jest . spyOn ( component as any , 'loadUserSolutionData' )
398+
399+ // set necessary props
400+ component . idChallenge = 'abc'
401+ component . languages = [ { id_language : 'lang1' , language_name : 'JS' } ]
402+
403+ // Emit true to simulate solution sent
404+ mockSolutionSentSubject . next ( true )
405+
406+ expect ( loadSolutionsSpy ) . toHaveBeenCalledWith ( 'abc' , 'lang1' )
407+ expect ( loadUserSolutionSpy ) . toHaveBeenCalled ( )
408+ } )
409+
410+ it ( 'should not call fetchUserSolution when userId is empty in loadUserSolutionData' , async ( ) => {
411+ jest . spyOn ( ( component as any ) . authService , 'getUserId' ) . mockReturnValue ( of ( '' ) )
412+ const fetchSpy = jest . spyOn ( component [ 'solutionService' ] , 'fetchUserSolution' )
413+ component . idChallenge = 'c-empty'
414+ component . languages = [ { id_language : 'lang1' , language_name : 'JS' } ]
415+
416+ await ( component as any ) . loadUserSolutionData ( )
417+
418+ expect ( fetchSpy ) . not . toHaveBeenCalled ( )
419+ } )
420+
421+ it ( 'should set userSolution and solutionText when loadUserSolutionData finds a match' , async ( ) => {
422+ jest . spyOn ( ( component as any ) . authService , 'getUserId' ) . mockReturnValue ( of ( 'user-1' ) )
423+ const submissions = [
424+ { uuid_user : 'user-1' , uuid_challenge : 'c-1' , uuid_language : 'lang1' , solution_text : 'found-text' }
425+ ] as any [ ]
426+ jest . spyOn ( component [ 'solutionService' ] , 'fetchUserSolution' ) . mockReturnValue ( of ( submissions ) )
427+
428+ component . idChallenge = 'c-1'
429+ component . languages = [ { id_language : 'lang1' , language_name : 'JS' } ]
430+
431+ await ( component as any ) . loadUserSolutionData ( )
432+
433+ expect ( component . userSolution ) . toEqual ( { solution_text : 'found-text' } )
434+ expect ( component . solutionText ) . toBe ( 'found-text' )
435+ } )
436+
387437 it ( 'should not load solutions when activeId input changes to SOLUTIONS and user is not admin' , ( ) => {
388438 component . isAdmin = false ;
389439 component . languages = [ { id_language : 'test-language-id' , language_name : 'JavaScript' } ] ;
@@ -419,13 +469,102 @@ describe('ChallengeInfoComponent', () => {
419469 expect ( component . isDropdownOpen ) . toBe ( false ) ;
420470 } ) ;
421471
422- it ( 'should load solutions from the service' , ( ) => {
423- const mockSolutions = { results : [ { solution_text : 'test solution' } ] } as any ;
424- const solutionServiceSpy = jest . spyOn ( component [ 'solutionService' ] , 'getAllChallengeSolutions' ) . mockReturnValue ( of ( mockSolutions ) ) ;
425- component . loadSolutions ( 'test-challenge-id' , 'test-language-id' ) ;
426- expect ( solutionServiceSpy ) . toHaveBeenCalledWith ( 'test-challenge-id' , 'test-language-id' ) ;
427- expect ( component . challengeSolutions ) . toEqual ( mockSolutions . results ) ;
428- } ) ;
472+ it ( 'should load solutions from user submissions' , fakeAsync ( ( ) => {
473+ const mockSubmissions = [
474+ {
475+ uuid_challenge : 'test-challenge-id' ,
476+ uuid_language : 'test-language-id' ,
477+ solution_text : 'test solution' ,
478+ uuid_submission : 'sub-1'
479+ }
480+ ] as any [ ] ;
481+
482+ const fetchSpy = jest
483+ . spyOn ( component [ 'solutionService' ] , 'fetchUserSolution' )
484+ . mockReturnValue ( of ( mockSubmissions ) ) ;
485+
486+ component . loadSolutions ( 'test-challenge-id' , 'test-language-id' ) ;
487+ tick ( ) ;
488+
489+ expect ( fetchSpy ) . toHaveBeenCalled ( ) ;
490+ expect ( component . challengeSolutions ) . toEqual ( [
491+ {
492+ id_solution : 'sub-1' ,
493+ uuid_language : 'test-language-id' ,
494+ uuid_challenge : 'test-challenge-id' ,
495+ solution_text : 'test solution'
496+ }
497+ ] ) ;
498+ } ) ) ;
499+
500+ it ( 'should load solutions using id_solution and solution_text when present' , fakeAsync ( ( ) => {
501+ const mockSubmissions = [
502+ {
503+ uuid_challenge : 'test-challenge-id' ,
504+ uuid_language : 'test-language-id' ,
505+ id_solution : 'sol-123' ,
506+ solution_text : 'text from solution_text'
507+ }
508+ ] as any [ ] ;
509+
510+ const fetchSpy = jest
511+ . spyOn ( component [ 'solutionService' ] , 'fetchUserSolution' )
512+ . mockReturnValue ( of ( mockSubmissions ) ) ;
513+
514+ const detectSpy = jest
515+ . spyOn ( ( component as any ) . cdr , 'detectChanges' )
516+ . mockImplementation ( ) ;
517+
518+ component . loadSolutions ( 'test-challenge-id' , 'test-language-id' ) ;
519+ tick ( ) ;
520+
521+ expect ( fetchSpy ) . toHaveBeenCalled ( ) ;
522+ expect ( component . challengeSolutions ) . toEqual ( [
523+ {
524+ id_solution : 'sol-123' ,
525+ uuid_language : 'test-language-id' ,
526+ uuid_challenge : 'test-challenge-id' ,
527+ solution_text : 'text from solution_text'
528+ }
529+ ] ) ;
530+ expect ( detectSpy ) . toHaveBeenCalled ( ) ;
531+ } ) ) ;
532+
533+ it ( 'should filter non-matching submissions and use fallbacks for mapped fields' , fakeAsync ( ( ) => {
534+ const mockSubmissions = [
535+
536+ {
537+ uuid_challenge : 'other-challenge' ,
538+ uuid_language : 'test-language-id' ,
539+ id_solution : 'should-be-filtered'
540+ } ,
541+
542+ {
543+ uuid_challenge : 'test-challenge-id' ,
544+ uuid_language : 'test-language-id' ,
545+ submission_text : 'text from submission_text'
546+ }
547+ ] as any [ ] ;
548+
549+ jest
550+ . spyOn ( component [ 'solutionService' ] , 'fetchUserSolution' )
551+ . mockReturnValue ( of ( mockSubmissions ) ) ;
552+
553+ component . loadSolutions ( 'test-challenge-id' , 'test-language-id' ) ;
554+ tick ( ) ;
555+
556+ expect ( component . challengeSolutions ) . toEqual ( [
557+ {
558+ id_solution : '' ,
559+ uuid_language : 'test-language-id' ,
560+ uuid_challenge : 'test-challenge-id' ,
561+ solution_text : 'text from submission_text'
562+ }
563+ ] ) ;
564+ } ) ) ;
565+
566+
567+
429568
430569 describe ( 'Dropdown functionality' , ( ) => {
431570 it ( 'should toggle dropdown' , ( ) => {
@@ -608,3 +747,103 @@ describe('loadRelatedChallenges', () => {
608747 consoleSpy . mockRestore ( ) ;
609748 } ) ;
610749} ) ;
750+
751+ describe ( 'localStorage challenge started' , ( ) => {
752+ let component : ChallengeInfoComponent ;
753+ let fixture : ComponentFixture < ChallengeInfoComponent > ;
754+ let mockSolutionSentSubject : Subject < boolean > ;
755+
756+ beforeEach ( async ( ) => {
757+ mockSolutionSentSubject = new Subject < boolean > ( ) ;
758+
759+ await TestBed . configureTestingModule ( {
760+ declarations : [
761+ ChallengeInfoComponent ,
762+ ResourceCardComponent ,
763+ ChallengeCardComponent ,
764+ SolutionComponent ,
765+ MockEditorChallengeComponent
766+ ] ,
767+ imports : [
768+ RouterTestingModule ,
769+ I18nModule ,
770+ FormsModule ,
771+ NgbNavModule ,
772+ DynamicTranslatePipe
773+ ] ,
774+ providers : [
775+ provideHttpClient ( withInterceptorsFromDi ( ) ) ,
776+ provideHttpClientTesting ( ) ,
777+ {
778+ provide : NgbModal ,
779+ useValue : {
780+ open : jest . fn ( )
781+ }
782+ }
783+ ]
784+ } ) . compileComponents ( ) ;
785+
786+ fixture = TestBed . createComponent ( ChallengeInfoComponent ) ;
787+ component = fixture . componentInstance ;
788+
789+ Object . defineProperty ( component [ 'solutionService' ] , 'solutionSent$' , {
790+ value : mockSolutionSentSubject . asObservable ( )
791+ } ) ;
792+
793+ Object . defineProperty ( component [ 'solutionService' ] , 'activeIdSubject' , {
794+ value : new Subject < ChallengeTab > ( )
795+ } ) ;
796+
797+ Object . defineProperty ( component [ 'solutionService' ] , 'activeId$' , {
798+ value : new Subject < ChallengeTab > ( ) . asObservable ( )
799+ } ) ;
800+
801+ Object . defineProperty ( component [ 'solutionService' ] , 'challengeCompleted$' , {
802+ value : new Subject < string > ( ) . asObservable ( )
803+ } ) ;
804+ } ) ;
805+
806+ afterEach ( ( ) => {
807+ localStorage . clear ( ) ;
808+ } ) ;
809+
810+ it ( 'should restore challenge started state from localStorage when challenge matches' , ( ) => {
811+ component . idChallenge = 'test-challenge-123' ;
812+ localStorage . setItem ( 'challengeStarted' , JSON . stringify ( { id : 'test-challenge-123' , started : true } ) ) ;
813+
814+ component . languages = [ { id_language : 'lang1' , language_name : 'JS' } ] ;
815+ component . solutions = [ ] ;
816+
817+ component . ngOnInit ( ) ;
818+
819+ expect ( component . challengeStarted ) . toBe ( true ) ;
820+ expect ( component . isEditorChallengeVisible ) . toBe ( true ) ;
821+ expect ( component . isChallengeStatementVisible ) . toBe ( false ) ;
822+ } ) ;
823+
824+ it ( 'should NOT restore challenge started state from localStorage when challenge ID does not match' , ( ) => {
825+ component . idChallenge = 'test-challenge-different' ;
826+ localStorage . setItem ( 'challengeStarted' , JSON . stringify ( { id : 'test-challenge-123' , started : true } ) ) ;
827+
828+ component . languages = [ { id_language : 'lang1' , language_name : 'JS' } ] ;
829+ component . solutions = [ ] ;
830+
831+ component . ngOnInit ( ) ;
832+
833+ expect ( component . challengeStarted ) . toBe ( false ) ;
834+ expect ( component . isEditorChallengeVisible ) . toBe ( false ) ;
835+ expect ( component . isChallengeStatementVisible ) . toBe ( true ) ;
836+ } ) ;
837+
838+ it ( 'should NOT restore challenge started state when localStorage has no data' , ( ) => {
839+ component . idChallenge = 'test-challenge-123' ;
840+ localStorage . clear ( ) ;
841+
842+ component . languages = [ { id_language : 'lang1' , language_name : 'JS' } ] ;
843+ component . solutions = [ ] ;
844+
845+ component . ngOnInit ( ) ;
846+
847+ expect ( component . challengeStarted ) . toBe ( false ) ;
848+ } ) ;
849+ } ) ;
0 commit comments