Skip to content

Commit 063a441

Browse files
Merge pull request #2108 from pie-framework/develop
Merge
2 parents 37cbcaa + 9d14615 commit 063a441

4 files changed

Lines changed: 1095 additions & 850 deletions

File tree

packages/math-inline/src/__tests__/main.test.js

Lines changed: 232 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@ import Main from '../main';
44
import { mq, HorizontalKeypad } from '@pie-lib/pie-toolbox/math-input';
55
import { shallowChild } from '@pie-lib/pie-toolbox/test-utils';
66
import { 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';
88
import SimpleQuestionBlock from '../simple-question-block';
9+
import { mount } from 'enzyme';
910

1011
const 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
});

packages/math-inline/src/__tests__/simple-question-block.test.js

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ describe('SimpleQuestionBlock', () => {
5050
response: 'sessionResponse',
5151
},
5252
onSimpleResponseChange: jest.fn(),
53+
onSubFieldFocus: jest.fn(),
54+
showKeypad: true,
5355
};
5456

5557
let wrapper;
@@ -79,25 +81,29 @@ describe('SimpleQuestionBlock', () => {
7981
component = wrapper();
8082

8183
component.instance().onFocus();
82-
expect(component.state().showKeypad).toEqual(true);
84+
expect(defaultProps.onSubFieldFocus).toHaveBeenCalledWith(component.instance().mathToolBarId);
85+
expect(component.instance().props.showKeypad).toEqual(true);
8386

84-
// hardcoded
8587
component.instance().mathToolBarContainsTarget = () => true;
8688
component.instance().handleClick();
8789

88-
expect(component.state().showKeypad).toEqual(true);
90+
expect(component.instance().props.showKeypad).toEqual(true);
8991
});
9092

9193
it('correctly hides the keypad', () => {
9294
component = wrapper();
9395

9496
component.instance().onFocus();
95-
expect(component.state().showKeypad).toEqual(true);
97+
expect(defaultProps.onSubFieldFocus).toHaveBeenCalledWith(component.instance().mathToolBarId);
98+
expect(component.instance().props.showKeypad).toEqual(true);
9699

97-
// hardcoded
100+
// Simulate clicking outside the toolbar (to hide the keypad)
98101
component.instance().mathToolBarContainsTarget = () => false;
99102
component.instance().handleClick();
100103

101-
expect(component.state().showKeypad).toEqual(false);
104+
// Simulate the parent component reacting to the event by setting showKeypad to false
105+
component.setProps({ showKeypad: false });
106+
107+
expect(component.instance().props.showKeypad).toEqual(false);
102108
});
103109
});

0 commit comments

Comments
 (0)