@@ -7,6 +7,13 @@ import TipTapEditor from 'shared/views/TipTapEditor/TipTapEditor/TipTapEditor.vu
77
88jest . mock ( 'shared/views/TipTapEditor/TipTapEditor/TipTapEditor.vue' ) ;
99
10+ jest . mock ( 'kolibri-design-system/lib/composables/useKResponsiveWindow' , ( ) => {
11+ return function useKResponsiveWindow ( ) {
12+ const { ref } = require ( 'vue' ) ;
13+ return { windowIsSmall : ref ( false ) } ;
14+ } ;
15+ } ) ;
16+
1017const clickNewAnswerBtn = async wrapper => {
1118 await wrapper . findComponent ( '[data-test="newAnswerBtn"]' ) . trigger ( 'click' ) ;
1219} ;
@@ -16,7 +23,7 @@ const rendersNewAnswerBtn = wrapper => {
1623} ;
1724
1825const clickAnswer = async ( wrapper , answerIdx ) => {
19- await wrapper . findAllComponents ( '[data-test="answer"]' ) . at ( answerIdx ) . trigger ( 'click' ) ;
26+ await wrapper . findAll ( '[data-test="answer"]' ) . at ( answerIdx ) . trigger ( 'click' ) ;
2027} ;
2128
2229const clickMoveAnswerUp = async ( wrapper , answerIdx ) => {
@@ -59,6 +66,24 @@ describe('AnswersEditor', () => {
5966 expect ( wrapper . html ( ) ) . toContain ( 'Question has no answer options' ) ;
6067 } ) ;
6168
69+ describe ( 'answers label' , ( ) => {
70+ it . each ( [
71+ [ AssessmentItemTypes . SINGLE_SELECTION , AnswersEditor . $trs . answersLabelSingleChoice ] ,
72+ [ AssessmentItemTypes . TRUE_FALSE , AnswersEditor . $trs . answersLabelSingleChoice ] ,
73+ [ AssessmentItemTypes . MULTIPLE_SELECTION , AnswersEditor . $trs . answersLabelMultipleChoice ] ,
74+ [ AssessmentItemTypes . INPUT_QUESTION , AnswersEditor . $trs . answersLabelNumeric ] ,
75+ ] ) ( 'renders the correct label for %s questions' , ( questionKind , expectedLabel ) => {
76+ wrapper = shallowMount ( AnswersEditor , {
77+ propsData : {
78+ questionKind,
79+ answers : [ ] ,
80+ } ,
81+ } ) ;
82+
83+ expect ( wrapper . text ( ) ) . toContain ( expectedLabel ) ;
84+ } ) ;
85+ } ) ;
86+
6287 describe ( 'for a single selection question' , ( ) => {
6388 beforeEach ( ( ) => {
6489 wrapper = mount ( AnswersEditor , {
@@ -72,7 +97,7 @@ describe('AnswersEditor', () => {
7297 } ) ;
7398 } ) ;
7499
75- it ( 'renders answers as radio selects ' , ( ) => {
100+ it ( 'renders answers as radio controls ' , ( ) => {
76101 const inputs = wrapper . findAll ( 'input' ) ;
77102
78103 expect ( inputs . length ) . toBe ( 2 ) ;
@@ -88,8 +113,38 @@ describe('AnswersEditor', () => {
88113 expect ( inputs . at ( 1 ) . element . checked ) . toBe ( false ) ;
89114 } ) ;
90115
116+ it ( 'marks correct answer rows with the selected visual state' , ( ) => {
117+ const answerRows = wrapper . findAll ( '[data-test="answer"]' ) ;
118+
119+ // Correct row has both border-color and background-color applied
120+ expect ( answerRows . at ( 0 ) . attributes ( 'style' ) ) . toContain ( 'border-color' ) ;
121+ expect ( answerRows . at ( 0 ) . attributes ( 'style' ) ) . toContain ( 'background-color' ) ;
122+ // Incorrect row has border-color but no inline background-color (null omits it)
123+ expect ( answerRows . at ( 1 ) . attributes ( 'style' ) ) . toContain ( 'border-color' ) ;
124+ expect ( answerRows . at ( 1 ) . attributes ( 'style' ) ) . not . toContain ( 'background-color' ) ;
125+ } ) ;
126+
127+ it ( 'renders all possible answers' , ( ) => {
128+ // First answer is open by default (openAnswerIdx=0) — edit mode TipTapEditor
129+ // Second answer is closed — view mode TipTapEditor
130+ const editors = wrapper . findAllComponents ( TipTapEditor ) ;
131+
132+ // Closed answer uses view mode to safely render rich text
133+ const viewEditor = editors . filter ( e => e . props ( 'mode' ) === 'view' ) . at ( 0 ) ;
134+ expect ( viewEditor . exists ( ) ) . toBe ( true ) ;
135+ expect ( viewEditor . props ( 'value' ) ) . toBe ( 'Peanut butter' ) ;
136+
137+ // Open answer uses edit mode
138+ const editEditor = editors . filter ( e => e . props ( 'mode' ) === 'edit' ) . at ( 0 ) ;
139+ expect ( editEditor . exists ( ) ) . toBe ( true ) ;
140+ expect ( editEditor . props ( 'value' ) ) . toBe ( 'Mayonnaise (I mean you can, but...)' ) ;
141+ } ) ;
142+
91143 it ( 'renders new answer button' , ( ) => {
92144 expect ( rendersNewAnswerBtn ( wrapper ) ) . toBe ( true ) ;
145+ expect ( wrapper . findComponent ( '[data-test="newAnswerBtn"]' ) . text ( ) ) . toContain (
146+ AnswersEditor . $trs . addOptionBtnLabel ,
147+ ) ;
93148 } ) ;
94149
95150 describe ( 'on new answer button click' , ( ) => {
@@ -140,8 +195,26 @@ describe('AnswersEditor', () => {
140195 expect ( inputs . at ( 2 ) . element . checked ) . toBe ( true ) ;
141196 } ) ;
142197
198+ it ( 'renders all possible answers' , ( ) => {
199+ // First answer is open by default (openAnswerIdx=0) — edit mode TipTapEditor
200+ // Remaining answers are closed — each gets a view mode TipTapEditor
201+ const editors = wrapper . findAllComponents ( TipTapEditor ) ;
202+
203+ const viewEditors = editors . filter ( e => e . props ( 'mode' ) === 'view' ) ;
204+ expect ( viewEditors . length ) . toBe ( 2 ) ;
205+ expect ( viewEditors . at ( 0 ) . props ( 'value' ) ) . toBe ( 'Peanut butter' ) ;
206+ expect ( viewEditors . at ( 1 ) . props ( 'value' ) ) . toBe ( 'Jelly' ) ;
207+
208+ const editEditor = editors . filter ( e => e . props ( 'mode' ) === 'edit' ) . at ( 0 ) ;
209+ expect ( editEditor . exists ( ) ) . toBe ( true ) ;
210+ expect ( editEditor . props ( 'value' ) ) . toBe ( 'Mayonnaise (I mean you can, but...)' ) ;
211+ } ) ;
212+
143213 it ( 'renders new answer button' , ( ) => {
144214 expect ( rendersNewAnswerBtn ( wrapper ) ) . toBe ( true ) ;
215+ expect ( wrapper . findComponent ( '[data-test="newAnswerBtn"]' ) . text ( ) ) . toContain (
216+ AnswersEditor . $trs . addOptionBtnLabel ,
217+ ) ;
145218 } ) ;
146219
147220 describe ( 'on new answer button click' , ( ) => {
@@ -175,7 +248,7 @@ describe('AnswersEditor', () => {
175248 } ) ;
176249 } ) ;
177250
178- it ( 'renders answers as radio selects ' , ( ) => {
251+ it ( 'renders answers as radio controls ' , ( ) => {
179252 const inputs = wrapper . findAll ( 'input' ) ;
180253
181254 expect ( inputs . length ) . toBe ( 2 ) ;
@@ -202,31 +275,24 @@ describe('AnswersEditor', () => {
202275 propsData : {
203276 questionKind : AssessmentItemTypes . INPUT_QUESTION ,
204277 answers : [
205- { answer : 'Mayonnaise (I mean you can, but...) ' , correct : true , order : 1 } ,
206- { answer : 'Peanut butter ' , correct : true , order : 2 } ,
278+ { answer : '1.5 ' , correct : true , order : 1 } ,
279+ { answer : '2 ' , correct : true , order : 2 } ,
207280 ] ,
208281 } ,
209282 } ) ;
210283 } ) ;
211284
212- it ( 'renders all possible answers' , ( ) => {
213- // The answer text won't render when `mount()` is used so we override
214- // and use shallowMount here
215- wrapper = shallowMount ( AnswersEditor , {
216- propsData : {
217- questionKind : AssessmentItemTypes . INPUT_QUESTION ,
218- answers : [
219- { answer : 'Mayonnaise (I mean you can, but...)' , correct : true , order : 1 } ,
220- { answer : 'Peanut butter' , correct : true , order : 2 } ,
221- ] ,
222- } ,
223- } ) ;
224- expect ( wrapper . html ( ) ) . toContain ( 'Mayonnaise (I mean you can, but...)' ) ;
225- expect ( wrapper . html ( ) ) . toContain ( 'Peanut butter' ) ;
285+ it ( 'renders open answer as a number input and closed answer as plain text' , ( ) => {
286+ expect ( wrapper . find ( 'input[type="number"]' ) . element . value ) . toBe ( '1.5' ) ;
287+
288+ expect ( wrapper . html ( ) ) . toContain ( '2' ) ;
226289 } ) ;
227290
228291 it ( 'renders new answer button' , ( ) => {
229292 expect ( rendersNewAnswerBtn ( wrapper ) ) . toBe ( true ) ;
293+ expect ( wrapper . findComponent ( '[data-test="newAnswerBtn"]' ) . text ( ) ) . toContain (
294+ AnswersEditor . $trs . newAnswerBtnLabel ,
295+ ) ;
230296 } ) ;
231297
232298 describe ( 'on new answer button click' , ( ) => {
@@ -238,8 +304,8 @@ describe('AnswersEditor', () => {
238304 expect ( wrapper . emitted ( ) . update ) . toBeTruthy ( ) ;
239305 expect ( wrapper . emitted ( ) . update . length ) . toBe ( 1 ) ;
240306 expect ( wrapper . emitted ( ) . update [ 0 ] [ 0 ] ) . toEqual ( [
241- { answer : 'Mayonnaise (I mean you can, but...) ' , correct : true , order : 1 } ,
242- { answer : 'Peanut butter ' , correct : true , order : 2 } ,
307+ { answer : '1.5 ' , correct : true , order : 1 } ,
308+ { answer : '2 ' , correct : true , order : 2 } ,
243309 { answer : '' , correct : true , order : 3 } ,
244310 ] ) ;
245311 } ) ;
@@ -260,14 +326,12 @@ describe('AnswersEditor', () => {
260326 } ) ;
261327 } ) ;
262328
263- it ( 'passes autofocus=true to the open answer editor' , ( ) => {
264- const editors = wrapper . findAllComponents ( TipTapEditor ) ;
265- expect ( editors . at ( 1 ) . props ( 'autofocus' ) ) . toBe ( true ) ;
266- } ) ;
267-
268- it ( 'passes autofocus=false to closed answer editors' , ( ) => {
329+ it ( 'passes autofocus=true to the open (edit-mode) answer editor' , ( ) => {
330+ // A single TipTapEditor per answer switches mode reactively.
331+ // The editor for openAnswerIdx has mode='edit' and autofocus=true.
269332 const editors = wrapper . findAllComponents ( TipTapEditor ) ;
270- expect ( editors . at ( 0 ) . props ( 'autofocus' ) ) . toBe ( false ) ;
333+ const editModeEditor = editors . filter ( e => e . props ( 'mode' ) === 'edit' ) . at ( 0 ) ;
334+ expect ( editModeEditor . props ( 'autofocus' ) ) . toBe ( true ) ;
271335 } ) ;
272336 } ) ;
273337
@@ -373,7 +437,7 @@ describe('AnswersEditor', () => {
373437 } ) ;
374438
375439 await wrapper . vm . $nextTick ( ) ;
376- await wrapper . findAll ( 'input[type="radio"]' ) . at ( 1 ) . setChecked ( ) ;
440+ await wrapper . findAll ( '.answer-selection input[type="radio"]' ) . at ( 1 ) . trigger ( 'click' ) ;
377441 } ) ;
378442
379443 it ( 'emits update event with a payload containing updated answers' , ( ) => {
0 commit comments