@@ -4,7 +4,7 @@ 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' ;
99
1010const Mathquill = require ( '@pie-framework/mathquill' ) ;
@@ -103,6 +103,64 @@ describe('Math-Inline Main', () => {
103103 } ) ;
104104 } ) ;
105105
106+ describe ( 'handleKeyDown' , ( ) => {
107+ let instance ;
108+ let textarea ;
109+
110+ beforeEach ( ( ) => {
111+ wrapper = component ( ) ;
112+ instance = wrapper . instance ( ) ;
113+
114+ textarea = document . createElement ( 'textarea' ) ;
115+ textarea . setAttribute ( 'aria-label' , 'Enter answer.' ) ;
116+ document . body . appendChild ( textarea ) ;
117+ textarea . focus ( ) ;
118+ } ) ;
119+
120+ afterEach ( ( ) => {
121+ document . body . innerHTML = '' ;
122+ } ) ;
123+
124+ it ( 'should have handleKeyDown method' , ( ) => {
125+ expect ( instance . handleKeyDown ) . toBeInstanceOf ( Function ) ;
126+ } ) ;
127+
128+ it ( 'should activate the keypad when ArrowDown is pressed' , ( ) => {
129+ const event = { key : 'ArrowDown' , target : document . activeElement } ;
130+ instance . handleKeyDown ( event , 'r1' ) ;
131+ expect ( wrapper . state ( 'activeAnswerBlock' ) ) . toEqual ( 'r1' ) ;
132+ } ) ;
133+
134+ it ( 'should activate the keypad on click or touch event' , ( ) => {
135+ const clickEvent = { type : 'click' , target : document . activeElement } ;
136+ instance . handleKeyDown ( clickEvent , 'r1' ) ;
137+ expect ( wrapper . state ( 'activeAnswerBlock' ) ) . toEqual ( 'r1' ) ;
138+
139+ wrapper . setState ( { activeAnswerBlock : '' } ) ;
140+ const touchEvent = { type : 'touchstart' , target : document . activeElement } ;
141+ instance . handleKeyDown ( touchEvent , 'r1' ) ;
142+ expect ( wrapper . state ( 'activeAnswerBlock' ) ) . toEqual ( 'r1' ) ;
143+ } ) ;
144+
145+ it ( 'should deactivate the keypad on Escape key press' , ( ) => {
146+ wrapper . setState ( { activeAnswerBlock : 'r1' } ) ;
147+ instance . handleKeyDown ( { key : 'Escape' , target : document . activeElement } , 'r1' ) ;
148+ expect ( wrapper . state ( 'activeAnswerBlock' ) ) . toEqual ( '' ) ;
149+ } ) ;
150+
151+ it ( 'should deactivate the keypad if the input is not focused and a click or touch event occurs' , ( ) => {
152+ textarea . blur ( ) ;
153+ wrapper . setState ( { activeAnswerBlock : 'r1' } ) ;
154+
155+ const differentElement = document . createElement ( 'div' ) ;
156+ document . body . appendChild ( differentElement ) ;
157+
158+ instance . handleKeyDown ( { type : 'click' , target : differentElement } , 'r2' ) ;
159+
160+ expect ( wrapper . state ( 'activeAnswerBlock' ) ) . toEqual ( '' ) ;
161+ } ) ;
162+ } ) ;
163+
106164 describe ( 'logic' , ( ) => {
107165 it ( 'prepares latex correctly and answer blocks and turns them into inputs' , ( ) => {
108166 expect ( wrapper . dive ( ) . find ( mq . Static ) . length ) . toEqual ( 1 ) ;
@@ -124,56 +182,6 @@ describe('Math-Inline Main', () => {
124182 expect ( wrapper . dive ( ) . find ( SimpleQuestionBlock ) . length ) . toEqual ( 1 ) ;
125183 } ) ;
126184
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-
177185 it ( 'correctly pre-populates answers from session' , ( ) => {
178186 wrapper = component ( {
179187 session : {
@@ -284,4 +292,171 @@ describe('Math-Inline Main', () => {
284292 } ) ;
285293 } ) ;
286294 } ) ;
295+
296+ describe ( 'Main component additional functions' , ( ) => {
297+ let instance ;
298+ let textarea ;
299+
300+ beforeEach ( ( ) => {
301+ wrapper = component ( ) ;
302+ instance = wrapper . instance ( ) ;
303+
304+ textarea = document . createElement ( 'textarea' ) ;
305+ textarea . setAttribute ( 'aria-label' , 'Enter answer.' ) ;
306+ document . body . appendChild ( textarea ) ;
307+ textarea . focus ( ) ;
308+ } ) ;
309+
310+ afterEach ( ( ) => {
311+ document . body . innerHTML = '' ;
312+ } ) ;
313+
314+ it ( 'should handle answer block DOM update correctly' , ( ) => {
315+ const mockRoot = document . createElement ( 'div' ) ;
316+ mockRoot . innerHTML = `
317+ <div id="r1"></div>
318+ <div id="r1Index"></div>
319+ ` ;
320+ instance . root = mockRoot ;
321+
322+ instance . handleAnswerBlockDomUpdate ( ) ;
323+
324+ expect ( mockRoot . querySelector ( '#r1' ) . textContent ) . toEqual ( '' ) ;
325+ } ) ;
326+
327+ it ( 'should count response occurrences correctly' , ( ) => {
328+ const expression = '{{response}} + {{response}} = {{response}}' ;
329+ const count = instance . countResponseOccurrences ( expression ) ;
330+ expect ( count ) . toEqual ( 3 ) ;
331+ } ) ;
332+
333+ it ( 'should update aria attributes correctly' , ( ) => {
334+ const mockRoot = document . createElement ( 'div' ) ;
335+ mockRoot . innerHTML = `
336+ <div class="mq-selectable"></div>
337+ <div class="mq-selectable"></div>
338+ <div class="mq-textarea">
339+ <textarea></textarea>
340+ </div>
341+ ` ;
342+ instance . root = mockRoot ;
343+
344+ instance . updateAria ( ) ;
345+
346+ const textareaElements = mockRoot . querySelectorAll ( 'textarea' ) ;
347+ textareaElements . forEach ( ( elem , index ) => {
348+ expect ( elem . getAttribute ( 'aria-label' ) ) . toEqual ( 'Enter answer.' ) ;
349+ expect ( elem . getAttribute ( 'aria-describedby' ) ) . not . toBeNull ( ) ;
350+ const describedById = elem . getAttribute ( 'aria-describedby' ) ;
351+ const describedByElement = mockRoot . querySelector ( `#${ describedById } ` ) ;
352+ expect ( describedByElement ) . not . toBeNull ( ) ;
353+ } ) ;
354+ } ) ;
355+
356+ it ( 'should focus first keypad element correctly' , ( ) => {
357+ jest . useFakeTimers ( ) ;
358+
359+ const mockRoot = document . createElement ( 'div' ) ;
360+ mockRoot . innerHTML = `
361+ <div data-keypad="true">
362+ <button>Button 1</button>
363+ <button>Button 2</button>
364+ </div>
365+ ` ;
366+
367+ document . body . appendChild ( mockRoot ) ;
368+
369+ instance . root = mockRoot ;
370+
371+ instance . focusFirstKeypadElement ( ) ;
372+
373+ jest . runAllTimers ( ) ;
374+
375+ const focusedElement = document . activeElement ;
376+ expect ( focusedElement . textContent ) . toEqual ( 'Button 1' ) ;
377+
378+ document . body . removeChild ( mockRoot ) ;
379+
380+ jest . useRealTimers ( ) ;
381+ } ) ;
382+
383+ it ( 'should handle blur correctly' , ( ) => {
384+ wrapper . setState ( { activeAnswerBlock : 'r1' } ) ;
385+
386+ const mockRelatedTarget = document . createElement ( 'div' ) ;
387+ const parentWithTooltip = document . createElement ( 'div' ) ;
388+ parentWithTooltip . setAttribute ( 'role' , 'tooltip' ) ;
389+
390+ const childWithKeypad = document . createElement ( 'div' ) ;
391+ childWithKeypad . setAttribute ( 'data-keypad' , 'true' ) ;
392+
393+ parentWithTooltip . appendChild ( childWithKeypad ) ;
394+
395+ Object . defineProperty ( mockRelatedTarget , 'offsetParent' , {
396+ get : function ( ) {
397+ return parentWithTooltip ;
398+ } ,
399+ } ) ;
400+
401+ const event = {
402+ relatedTarget : mockRelatedTarget ,
403+ currentTarget : document . createElement ( 'div' ) ,
404+ } ;
405+
406+ instance . onBlur ( event ) ;
407+
408+ expect ( wrapper . state ( 'activeAnswerBlock' ) ) . toEqual ( 'r1' ) ;
409+
410+ event . relatedTarget = null ;
411+ instance . onBlur ( event ) ;
412+
413+ expect ( wrapper . state ( 'activeAnswerBlock' ) ) . toEqual ( '' ) ;
414+ } ) ;
415+
416+ it ( 'should call onSessionChange correctly' , ( ) => {
417+ wrapper . setState ( {
418+ session : {
419+ answers : {
420+ r1 : { value : 'test' } ,
421+ } ,
422+ } ,
423+ } ) ;
424+
425+ instance . callOnSessionChange ( ) ;
426+
427+ expect ( defaultProps . onSessionChange ) . toHaveBeenCalledWith ( {
428+ answers : {
429+ r1 : { value : 'test' } ,
430+ } ,
431+ } ) ;
432+ } ) ;
433+
434+ it ( 'should toggle show correct state correctly' , ( ) => {
435+ instance . toggleShowCorrect ( true ) ;
436+ expect ( wrapper . state ( 'showCorrect' ) ) . toEqual ( true ) ;
437+
438+ instance . toggleShowCorrect ( false ) ;
439+ expect ( wrapper . state ( 'showCorrect' ) ) . toEqual ( false ) ;
440+ } ) ;
441+
442+ it ( 'should handle subFieldChanged correctly' , ( ) => {
443+ instance . subFieldChanged ( 'r1' , 'new value' ) ;
444+
445+ expect ( wrapper . state ( 'session' ) . answers . r1 . value ) . toEqual ( 'new value' ) ;
446+ } ) ;
447+
448+ it ( 'should get the correct field name' , ( ) => {
449+ const fields = { r1 : { id : 1 } , r2 : { id : 2 } } ;
450+ wrapper . setState ( {
451+ session : {
452+ answers : {
453+ r1 : { value : 'test' } ,
454+ } ,
455+ } ,
456+ } ) ;
457+
458+ const fieldName = instance . getFieldName ( { id : 1 } , fields ) ;
459+ expect ( fieldName ) . toEqual ( 'r1' ) ;
460+ } ) ;
461+ } ) ;
287462} ) ;
0 commit comments