@@ -4,8 +4,9 @@ import Main from '../main';
44import { mq , HorizontalKeypad } from '@pie-lib/pie-toolbox/math-input' ;
55import { shallowChild } from '@pie-lib/pie-toolbox/test-utils' ;
66import { Feedback } from '@pie-lib/pie-toolbox/render-ui' ;
7- import { CorrectAnswerToggle } from '@pie-lib/pie-toolbox/correct-answer-toggle' ;
7+ import { CorrectAnswerToggle } from '@pie-lib/pie-toolbox/correct-answer-toggle' ;
88import SimpleQuestionBlock from '../simple-question-block' ;
9+ import { mount } from 'enzyme' ;
910
1011const Mathquill = require ( '@pie-framework/mathquill' ) ;
1112
@@ -97,12 +98,71 @@ describe('Math-Inline Main', () => {
9798 } ,
9899 } ,
99100 showCorrect : false ,
101+ tooltipContainerRef : expect . any ( Object ) ,
100102 } ) ;
101103
102104 expect ( Mathquill . getInterface ( ) . registerEmbed ) . toHaveBeenCalled ( ) ;
103105 } ) ;
104106 } ) ;
105107
108+ describe ( 'handleKeyDown' , ( ) => {
109+ let instance ;
110+ let textarea ;
111+
112+ beforeEach ( ( ) => {
113+ wrapper = component ( ) ;
114+ instance = wrapper . instance ( ) ;
115+
116+ textarea = document . createElement ( 'textarea' ) ;
117+ textarea . setAttribute ( 'aria-label' , 'Enter answer.' ) ;
118+ document . body . appendChild ( textarea ) ;
119+ textarea . focus ( ) ;
120+ } ) ;
121+
122+ afterEach ( ( ) => {
123+ document . body . innerHTML = '' ;
124+ } ) ;
125+
126+ it ( 'should have handleKeyDown method' , ( ) => {
127+ expect ( instance . handleKeyDown ) . toBeInstanceOf ( Function ) ;
128+ } ) ;
129+
130+ it ( 'should activate the keypad when ArrowDown is pressed' , ( ) => {
131+ const event = { key : 'ArrowDown' , target : document . activeElement } ;
132+ instance . handleKeyDown ( event , 'r1' ) ;
133+ expect ( wrapper . state ( 'activeAnswerBlock' ) ) . toEqual ( 'r1' ) ;
134+ } ) ;
135+
136+ it ( 'should activate the keypad on click or touch event' , ( ) => {
137+ const clickEvent = { type : 'click' , target : document . activeElement } ;
138+ instance . handleKeyDown ( clickEvent , 'r1' ) ;
139+ expect ( wrapper . state ( 'activeAnswerBlock' ) ) . toEqual ( 'r1' ) ;
140+
141+ wrapper . setState ( { activeAnswerBlock : '' } ) ;
142+ const touchEvent = { type : 'touchstart' , target : document . activeElement } ;
143+ instance . handleKeyDown ( touchEvent , 'r1' ) ;
144+ expect ( wrapper . state ( 'activeAnswerBlock' ) ) . toEqual ( 'r1' ) ;
145+ } ) ;
146+
147+ it ( 'should deactivate the keypad on Escape key press' , ( ) => {
148+ wrapper . setState ( { activeAnswerBlock : 'r1' } ) ;
149+ instance . handleKeyDown ( { key : 'Escape' , target : document . activeElement } , 'r1' ) ;
150+ expect ( wrapper . state ( 'activeAnswerBlock' ) ) . toEqual ( '' ) ;
151+ } ) ;
152+
153+ it ( 'should deactivate the keypad if the input is not focused and a click or touch event occurs' , ( ) => {
154+ textarea . blur ( ) ;
155+ wrapper . setState ( { activeAnswerBlock : 'r1' } ) ;
156+
157+ const differentElement = document . createElement ( 'div' ) ;
158+ document . body . appendChild ( differentElement ) ;
159+
160+ instance . handleKeyDown ( { type : 'click' , target : differentElement } , 'r2' ) ;
161+
162+ expect ( wrapper . state ( 'activeAnswerBlock' ) ) . toEqual ( '' ) ;
163+ } ) ;
164+ } ) ;
165+
106166 describe ( 'logic' , ( ) => {
107167 it ( 'prepares latex correctly and answer blocks and turns them into inputs' , ( ) => {
108168 expect ( wrapper . dive ( ) . find ( mq . Static ) . length ) . toEqual ( 1 ) ;
@@ -124,56 +184,6 @@ describe('Math-Inline Main', () => {
124184 expect ( wrapper . dive ( ) . find ( SimpleQuestionBlock ) . length ) . toEqual ( 1 ) ;
125185 } ) ;
126186
127- it ( 'correctly shows the keypad' , ( ) => {
128- wrapper = component ( ) ;
129-
130- expect ( wrapper . find ( HorizontalKeypad ) . length ) . toEqual ( 0 ) ;
131- wrapper . instance ( ) . onSubFieldFocus ( 'r1' ) ;
132- expect ( wrapper . state ( ) ) . toEqual ( {
133- activeAnswerBlock : 'r1' ,
134- session : {
135- answers : {
136- r1 : {
137- value : '' ,
138- } ,
139- r2 : {
140- value : '' ,
141- } ,
142- r3 : {
143- value : '' ,
144- } ,
145- r4 : {
146- value : '' ,
147- } ,
148- } ,
149- } ,
150- showCorrect : false ,
151- } ) ;
152- } ) ;
153-
154- it ( 'correctly keeps the keypad open' , ( ) => {
155- wrapper = component ( ) ;
156-
157- expect ( wrapper . find ( HorizontalKeypad ) . length ) . toEqual ( 0 ) ;
158- wrapper . instance ( ) . onSubFieldFocus ( 'r1' ) ;
159- wrapper . instance ( ) . onBlur ( {
160- relatedTarget : { offsetParent : { getAttribute : ( ) => 'tooltip' , children : [ { attributes : { 'data-keypad' : true } } ] } } ,
161- currentTarget : { offsetParent : 'editor1' } ,
162- } ) ;
163- expect ( wrapper . state ( ) . activeAnswerBlock ) . toEqual ( 'r1' ) ;
164- } ) ;
165-
166- it ( 'correctly hides the keypad' , ( ) => {
167- wrapper = component ( ) ;
168-
169- expect ( wrapper . find ( HorizontalKeypad ) . length ) . toEqual ( 0 ) ;
170- wrapper . instance ( ) . onBlur ( {
171- relatedTarget : { offsetParent : { getAttribute : ( ) => 'tooltip' , children : [ { attributes : { 'data-keypad' : false } } ] } } ,
172- currentTarget : { offsetParent : 'editor2' } ,
173- } ) ;
174- expect ( wrapper . state ( ) . activeAnswerBlock ) . toEqual ( '' ) ;
175- } ) ;
176-
177187 it ( 'correctly pre-populates answers from session' , ( ) => {
178188 wrapper = component ( {
179189 session : {
@@ -203,6 +213,7 @@ describe('Math-Inline Main', () => {
203213 } ,
204214 } ,
205215 showCorrect : false ,
216+ tooltipContainerRef : expect . any ( Object ) ,
206217 } ) ;
207218 } ) ;
208219
@@ -229,6 +240,7 @@ describe('Math-Inline Main', () => {
229240 } ,
230241 } ,
231242 showCorrect : false ,
243+ tooltipContainerRef : expect . any ( Object ) ,
232244 } ) ;
233245 } ) ;
234246
@@ -254,6 +266,7 @@ describe('Math-Inline Main', () => {
254266 } ,
255267 } ,
256268 showCorrect : false ,
269+ tooltipContainerRef : expect . any ( Object ) ,
257270 } ) ;
258271
259272 const newProps = { ...defaultProps } ;
@@ -281,7 +294,175 @@ describe('Math-Inline Main', () => {
281294 } ,
282295 } ,
283296 showCorrect : false ,
297+ tooltipContainerRef : expect . any ( Object ) ,
298+ } ) ;
299+ } ) ;
300+ } ) ;
301+
302+ describe ( 'Main component additional functions' , ( ) => {
303+ let instance ;
304+ let textarea ;
305+
306+ beforeEach ( ( ) => {
307+ wrapper = component ( ) ;
308+ instance = wrapper . instance ( ) ;
309+
310+ textarea = document . createElement ( 'textarea' ) ;
311+ textarea . setAttribute ( 'aria-label' , 'Enter answer.' ) ;
312+ document . body . appendChild ( textarea ) ;
313+ textarea . focus ( ) ;
314+ } ) ;
315+
316+ afterEach ( ( ) => {
317+ document . body . innerHTML = '' ;
318+ } ) ;
319+
320+ it ( 'should handle answer block DOM update correctly' , ( ) => {
321+ const mockRoot = document . createElement ( 'div' ) ;
322+ mockRoot . innerHTML = `
323+ <div id="r1"></div>
324+ <div id="r1Index"></div>
325+ ` ;
326+ instance . root = mockRoot ;
327+
328+ instance . handleAnswerBlockDomUpdate ( ) ;
329+
330+ expect ( mockRoot . querySelector ( '#r1' ) . textContent ) . toEqual ( '' ) ;
331+ } ) ;
332+
333+ it ( 'should count response occurrences correctly' , ( ) => {
334+ const expression = '{{response}} + {{response}} = {{response}}' ;
335+ const count = instance . countResponseOccurrences ( expression ) ;
336+ expect ( count ) . toEqual ( 3 ) ;
337+ } ) ;
338+
339+ it ( 'should update aria attributes correctly' , ( ) => {
340+ const mockRoot = document . createElement ( 'div' ) ;
341+ mockRoot . innerHTML = `
342+ <div class="mq-selectable"></div>
343+ <div class="mq-selectable"></div>
344+ <div class="mq-textarea">
345+ <textarea></textarea>
346+ </div>
347+ ` ;
348+ instance . root = mockRoot ;
349+
350+ instance . updateAria ( ) ;
351+
352+ const textareaElements = mockRoot . querySelectorAll ( 'textarea' ) ;
353+ textareaElements . forEach ( ( elem , index ) => {
354+ expect ( elem . getAttribute ( 'aria-label' ) ) . toEqual ( 'Enter answer.' ) ;
355+ expect ( elem . getAttribute ( 'aria-describedby' ) ) . not . toBeNull ( ) ;
356+ const describedById = elem . getAttribute ( 'aria-describedby' ) ;
357+ const describedByElement = mockRoot . querySelector ( `#${ describedById } ` ) ;
358+ expect ( describedByElement ) . not . toBeNull ( ) ;
359+ } ) ;
360+ } ) ;
361+
362+ it ( 'should focus first keypad element correctly' , ( ) => {
363+ jest . useFakeTimers ( ) ;
364+
365+ const mockRoot = document . createElement ( 'div' ) ;
366+ mockRoot . innerHTML = `
367+ <div data-keypad="true">
368+ <button>Button 1</button>
369+ <button>Button 2</button>
370+ </div>
371+ ` ;
372+
373+ document . body . appendChild ( mockRoot ) ;
374+
375+ instance . root = mockRoot ;
376+
377+ instance . focusFirstKeypadElement ( ) ;
378+
379+ jest . runAllTimers ( ) ;
380+
381+ const focusedElement = document . activeElement ;
382+ expect ( focusedElement . textContent ) . toEqual ( 'Button 1' ) ;
383+
384+ document . body . removeChild ( mockRoot ) ;
385+
386+ jest . useRealTimers ( ) ;
387+ } ) ;
388+
389+ it ( 'should handle blur correctly' , ( ) => {
390+ wrapper . setState ( { activeAnswerBlock : 'r1' } ) ;
391+
392+ const mockRelatedTarget = document . createElement ( 'div' ) ;
393+ const parentWithTooltip = document . createElement ( 'div' ) ;
394+ parentWithTooltip . setAttribute ( 'role' , 'tooltip' ) ;
395+
396+ const childWithKeypad = document . createElement ( 'div' ) ;
397+ childWithKeypad . setAttribute ( 'data-keypad' , 'true' ) ;
398+
399+ parentWithTooltip . appendChild ( childWithKeypad ) ;
400+
401+ Object . defineProperty ( mockRelatedTarget , 'offsetParent' , {
402+ get : function ( ) {
403+ return parentWithTooltip ;
404+ } ,
284405 } ) ;
406+
407+ const event = {
408+ relatedTarget : mockRelatedTarget ,
409+ currentTarget : document . createElement ( 'div' ) ,
410+ } ;
411+
412+ instance . onBlur ( event ) ;
413+
414+ expect ( wrapper . state ( 'activeAnswerBlock' ) ) . toEqual ( 'r1' ) ;
415+
416+ event . relatedTarget = null ;
417+ instance . onBlur ( event ) ;
418+
419+ expect ( wrapper . state ( 'activeAnswerBlock' ) ) . toEqual ( '' ) ;
420+ } ) ;
421+
422+ it ( 'should call onSessionChange correctly' , ( ) => {
423+ wrapper . setState ( {
424+ session : {
425+ answers : {
426+ r1 : { value : 'test' } ,
427+ } ,
428+ } ,
429+ } ) ;
430+
431+ instance . callOnSessionChange ( ) ;
432+
433+ expect ( defaultProps . onSessionChange ) . toHaveBeenCalledWith ( {
434+ answers : {
435+ r1 : { value : 'test' } ,
436+ } ,
437+ } ) ;
438+ } ) ;
439+
440+ it ( 'should toggle show correct state correctly' , ( ) => {
441+ instance . toggleShowCorrect ( true ) ;
442+ expect ( wrapper . state ( 'showCorrect' ) ) . toEqual ( true ) ;
443+
444+ instance . toggleShowCorrect ( false ) ;
445+ expect ( wrapper . state ( 'showCorrect' ) ) . toEqual ( false ) ;
446+ } ) ;
447+
448+ it ( 'should handle subFieldChanged correctly' , ( ) => {
449+ instance . subFieldChanged ( 'r1' , 'new value' ) ;
450+
451+ expect ( wrapper . state ( 'session' ) . answers . r1 . value ) . toEqual ( 'new value' ) ;
452+ } ) ;
453+
454+ it ( 'should get the correct field name' , ( ) => {
455+ const fields = { r1 : { id : 1 } , r2 : { id : 2 } } ;
456+ wrapper . setState ( {
457+ session : {
458+ answers : {
459+ r1 : { value : 'test' } ,
460+ } ,
461+ } ,
462+ } ) ;
463+
464+ const fieldName = instance . getFieldName ( { id : 1 } , fields ) ;
465+ expect ( fieldName ) . toEqual ( 'r1' ) ;
285466 } ) ;
286467 } ) ;
287468} ) ;
0 commit comments