@@ -24,6 +24,7 @@ describe('Exam', () => {
2424 stem : 'What is AWS Lambda?' ,
2525 choices : { A : 'Compute' , B : 'Storage' } ,
2626 answer : 'A' ,
27+ requiredAnswers : 1 ,
2728 conceptKey : 'lambda' ,
2829 domainKey : 'development' ,
2930 frExplanation : 'Explication' ,
@@ -38,6 +39,7 @@ describe('Exam', () => {
3839 stem : 'What is S3?' ,
3940 choices : { A : 'Compute' , B : 'Storage' } ,
4041 answer : 'B' ,
42+ requiredAnswers : 1 ,
4143 conceptKey : 's3' ,
4244 domainKey : 'security' ,
4345 frExplanation : 'Explication' ,
@@ -52,6 +54,7 @@ describe('Exam', () => {
5254 stem : 'What is EC2?' ,
5355 choices : { A : 'Compute' , B : 'Storage' } ,
5456 answer : 'A' ,
57+ requiredAnswers : 1 ,
5558 conceptKey : 'ec2' ,
5659 domainKey : 'deployment' ,
5760 frExplanation : 'Explication' ,
@@ -347,7 +350,7 @@ describe('Exam', () => {
347350 await fixture . whenStable ( ) ;
348351 } ) ;
349352
350- it ( 'should not allow answering same question twice ' , async ( ) => {
353+ it ( 'should allow changing answer ' , async ( ) => {
351354 component . startExam ( ) ;
352355 httpMock . expectOne ( `${ baseUrl } /api/exams/start` ) . flush ( {
353356 mode : 'exam' ,
@@ -357,7 +360,10 @@ describe('Exam', () => {
357360 } ) ;
358361 await fixture . whenStable ( ) ;
359362
363+ // Première réponse
360364 component . pick ( 'A' ) ;
365+ expect ( component . answers . get ( 'q1' ) ) . toBe ( 'A' ) ;
366+
361367 httpMock . expectOne ( `${ baseUrl } /api/attempts` ) . flush ( {
362368 ok : true ,
363369 attemptId : 'att1' ,
@@ -366,8 +372,9 @@ describe('Exam', () => {
366372 } ) ;
367373 await fixture . whenStable ( ) ;
368374
375+ // Changement de réponse
369376 component . pick ( 'B' ) ;
370- expect ( component . answers . get ( 'q1' ) ) . toBe ( 'A ' ) ;
377+ expect ( component . answers . get ( 'q1' ) ) . toBe ( 'B ' ) ;
371378 } ) ;
372379
373380 it ( 'should return chosen answer for question' , ( ) => {
@@ -618,4 +625,197 @@ describe('Exam', () => {
618625 it ( 'should not show negative time' , ( ) => {
619626 expect ( component . formatTime ( - 10 ) ) . toBe ( '00:00:00' ) ;
620627 } ) ;
628+
629+ // Tests pour les questions multi-réponses
630+ describe ( 'Multi-choice questions' , ( ) => {
631+ const multiQuestion : Question = {
632+ id : 'q-multi' ,
633+ exam : 'DVA-C02' ,
634+ topic : 1 ,
635+ questionNumber : 1 ,
636+ stem : 'Select two options:' ,
637+ choices : { A : 'Option A' , B : 'Option B' , C : 'Option C' , D : 'Option D' } ,
638+ answer : 'A,B' ,
639+ requiredAnswers : 2 ,
640+ conceptKey : 'multi' ,
641+ domainKey : 'development' ,
642+ frExplanation : 'Explication' ,
643+ sourceUrl : 'url' ,
644+ textHash : 'hash-multi' ,
645+ } ;
646+
647+ it ( 'should detect multi-choice question' , ( ) => {
648+ expect ( component . isMultiChoice ( multiQuestion ) ) . toBe ( true ) ;
649+ expect ( component . isMultiChoice ( mockQuestions [ 0 ] ) ) . toBe ( false ) ;
650+ } ) ;
651+
652+ it ( 'should allow selecting multiple answers up to requiredAnswers' , async ( ) => {
653+ component . startExam ( ) ;
654+ httpMock . expectOne ( `${ baseUrl } /api/exams/start` ) . flush ( {
655+ mode : 'exam' ,
656+ count : 65 ,
657+ durationMinutes : 130 ,
658+ items : [ multiQuestion ] ,
659+ } ) ;
660+ await fixture . whenStable ( ) ;
661+
662+ component . pick ( 'A' ) ;
663+ httpMock . expectOne ( `${ baseUrl } /api/attempts` ) . flush ( { ok : true , attemptId : 'att1' , isCorrect : false , correctAnswer : 'A,B' } ) ;
664+ await fixture . whenStable ( ) ;
665+
666+ component . pick ( 'B' ) ;
667+ httpMock . expectOne ( `${ baseUrl } /api/attempts` ) . flush ( { ok : true , attemptId : 'att2' , isCorrect : false , correctAnswer : 'A,B' } ) ;
668+ await fixture . whenStable ( ) ;
669+
670+ const answers = component . answers . get ( 'q-multi' ) as Set < string > ;
671+ expect ( answers . size ) . toBe ( 2 ) ;
672+ expect ( answers . has ( 'A' ) ) . toBe ( true ) ;
673+ expect ( answers . has ( 'B' ) ) . toBe ( true ) ;
674+ } ) ;
675+
676+ it ( 'should not allow more than requiredAnswers selections' , async ( ) => {
677+ component . startExam ( ) ;
678+ httpMock . expectOne ( `${ baseUrl } /api/exams/start` ) . flush ( {
679+ mode : 'exam' ,
680+ count : 65 ,
681+ durationMinutes : 130 ,
682+ items : [ multiQuestion ] ,
683+ } ) ;
684+ await fixture . whenStable ( ) ;
685+
686+ component . pick ( 'A' ) ;
687+ httpMock . expectOne ( `${ baseUrl } /api/attempts` ) . flush ( { ok : true , attemptId : 'att1' , isCorrect : false , correctAnswer : 'A,B' } ) ;
688+ await fixture . whenStable ( ) ;
689+
690+ component . pick ( 'B' ) ;
691+ httpMock . expectOne ( `${ baseUrl } /api/attempts` ) . flush ( { ok : true , attemptId : 'att2' , isCorrect : false , correctAnswer : 'A,B' } ) ;
692+ await fixture . whenStable ( ) ;
693+
694+ component . pick ( 'C' ) ; // Ne devrait pas être ajouté (déjà 2 réponses)
695+
696+ const answers = component . answers . get ( 'q-multi' ) as Set < string > ;
697+ expect ( answers . size ) . toBe ( 2 ) ;
698+ expect ( answers . has ( 'C' ) ) . toBe ( false ) ;
699+ } ) ;
700+
701+ it ( 'should allow deselecting answers' , async ( ) => {
702+ component . startExam ( ) ;
703+ httpMock . expectOne ( `${ baseUrl } /api/exams/start` ) . flush ( {
704+ mode : 'exam' ,
705+ count : 65 ,
706+ durationMinutes : 130 ,
707+ items : [ multiQuestion ] ,
708+ } ) ;
709+ await fixture . whenStable ( ) ;
710+
711+ component . pick ( 'A' ) ;
712+ httpMock . expectOne ( `${ baseUrl } /api/attempts` ) . flush ( { ok : true , attemptId : 'att1' , isCorrect : false , correctAnswer : 'A,B' } ) ;
713+ await fixture . whenStable ( ) ;
714+
715+ component . pick ( 'B' ) ;
716+ httpMock . expectOne ( `${ baseUrl } /api/attempts` ) . flush ( { ok : true , attemptId : 'att2' , isCorrect : false , correctAnswer : 'A,B' } ) ;
717+ await fixture . whenStable ( ) ;
718+
719+ component . pick ( 'A' ) ; // Désélectionner A
720+
721+ const answers = component . answers . get ( 'q-multi' ) as Set < string > ;
722+ expect ( answers . size ) . toBe ( 1 ) ;
723+ expect ( answers . has ( 'A' ) ) . toBe ( false ) ;
724+ expect ( answers . has ( 'B' ) ) . toBe ( true ) ;
725+ } ) ;
726+
727+ it ( 'should check if choice is selected for multi question' , async ( ) => {
728+ component . startExam ( ) ;
729+ httpMock . expectOne ( `${ baseUrl } /api/exams/start` ) . flush ( {
730+ mode : 'exam' ,
731+ count : 65 ,
732+ durationMinutes : 130 ,
733+ items : [ multiQuestion ] ,
734+ } ) ;
735+ await fixture . whenStable ( ) ;
736+
737+ expect ( component . isChosen ( multiQuestion , 'A' ) ) . toBe ( false ) ;
738+
739+ component . pick ( 'A' ) ;
740+ httpMock . expectOne ( `${ baseUrl } /api/attempts` ) . flush ( { ok : true , attemptId : 'att1' , isCorrect : false , correctAnswer : 'A,B' } ) ;
741+ await fixture . whenStable ( ) ;
742+
743+ expect ( component . isChosen ( multiQuestion , 'A' ) ) . toBe ( true ) ;
744+ expect ( component . isChosen ( multiQuestion , 'B' ) ) . toBe ( false ) ;
745+ } ) ;
746+
747+ it ( 'should return chosen set for multi question' , async ( ) => {
748+ component . startExam ( ) ;
749+ httpMock . expectOne ( `${ baseUrl } /api/exams/start` ) . flush ( {
750+ mode : 'exam' ,
751+ count : 65 ,
752+ durationMinutes : 130 ,
753+ items : [ multiQuestion ] ,
754+ } ) ;
755+ await fixture . whenStable ( ) ;
756+
757+ expect ( component . chosenSetFor ( multiQuestion ) ) . toBeNull ( ) ;
758+
759+ component . pick ( 'A' ) ;
760+ httpMock . expectOne ( `${ baseUrl } /api/attempts` ) . flush ( { ok : true , attemptId : 'att1' , isCorrect : false , correctAnswer : 'A,B' } ) ;
761+ await fixture . whenStable ( ) ;
762+
763+ const set = component . chosenSetFor ( multiQuestion ) ;
764+ expect ( set ) . not . toBeNull ( ) ;
765+ expect ( set ! . has ( 'A' ) ) . toBe ( true ) ;
766+ } ) ;
767+
768+ it ( 'should calculate correct score for multi answers' , async ( ) => {
769+ component . startExam ( ) ;
770+ httpMock . expectOne ( `${ baseUrl } /api/exams/start` ) . flush ( {
771+ mode : 'exam' ,
772+ count : 65 ,
773+ durationMinutes : 130 ,
774+ items : [ multiQuestion ] ,
775+ } ) ;
776+ await fixture . whenStable ( ) ;
777+
778+ // Réponses correctes
779+ component . answers . set ( 'q-multi' , new Set ( [ 'A' , 'B' ] ) ) ;
780+
781+ const score = component . score ( ) ;
782+ expect ( score . correct ) . toBe ( 1 ) ;
783+ expect ( score . total ) . toBe ( 1 ) ;
784+ expect ( score . percent ) . toBe ( 100 ) ;
785+ } ) ;
786+
787+ it ( 'should calculate incorrect score for partial multi answers' , async ( ) => {
788+ component . startExam ( ) ;
789+ httpMock . expectOne ( `${ baseUrl } /api/exams/start` ) . flush ( {
790+ mode : 'exam' ,
791+ count : 65 ,
792+ durationMinutes : 130 ,
793+ items : [ multiQuestion ] ,
794+ } ) ;
795+ await fixture . whenStable ( ) ;
796+
797+ // Réponses partielles (seulement A, pas B)
798+ component . answers . set ( 'q-multi' , new Set ( [ 'A' , 'C' ] ) ) ;
799+
800+ const score = component . score ( ) ;
801+ expect ( score . correct ) . toBe ( 0 ) ;
802+ expect ( score . total ) . toBe ( 1 ) ;
803+ expect ( score . percent ) . toBe ( 0 ) ;
804+ } ) ;
805+
806+ it ( 'should return joined string for multi chosenFor' , async ( ) => {
807+ component . startExam ( ) ;
808+ httpMock . expectOne ( `${ baseUrl } /api/exams/start` ) . flush ( {
809+ mode : 'exam' ,
810+ count : 65 ,
811+ durationMinutes : 130 ,
812+ items : [ multiQuestion ] ,
813+ } ) ;
814+ await fixture . whenStable ( ) ;
815+
816+ component . answers . set ( 'q-multi' , new Set ( [ 'B' , 'A' ] ) ) ;
817+
818+ expect ( component . chosenFor ( multiQuestion ) ) . toBe ( 'A,B' ) ;
819+ } ) ;
820+ } ) ;
621821} ) ;
0 commit comments