Skip to content

Commit cc9866b

Browse files
committed
Add support for special keys in userEvent.type()
- Parses commands {enter}, {esc}, {backspace}, {shift}, {ctrl}, {alt}, {meta} - Create setupTests.js to add a custom matcher for testing event properties
1 parent 38e3c22 commit cc9866b

File tree

6 files changed

+921
-77
lines changed

6 files changed

+921
-77
lines changed

__tests__/react/type.js

Lines changed: 349 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,4 +208,353 @@ describe("userEvent.type", () => {
208208
expect(onKeyUp).not.toHaveBeenCalled();
209209
}
210210
);
211+
212+
describe("special characters", () => {
213+
afterEach(jest.clearAllMocks);
214+
215+
const onChange = jest.fn().mockImplementation(e => e.persist());
216+
const onKeyDown = jest.fn().mockImplementation(e => e.persist());
217+
const onKeyPress = jest.fn().mockImplementation(e => e.persist());
218+
const onKeyUp = jest.fn().mockImplementation(e => e.persist());
219+
220+
it.each(["a{bc", "a{bc}", "a{backspacee}c"])(
221+
"properly parses %s",
222+
async text => {
223+
const { getByTestId } = render(
224+
React.createElement("input", {
225+
"data-testid": "input"
226+
})
227+
);
228+
229+
const inputEl = getByTestId("input");
230+
231+
await userEvent.type(inputEl, text);
232+
233+
expect(inputEl).toHaveProperty("value", text);
234+
}
235+
);
236+
237+
describe("{enter}", () => {
238+
describe("input", () => {
239+
it("should record key up/down/press events from {enter}", async () => {
240+
const { getByTestId } = render(
241+
React.createElement("input", {
242+
"data-testid": "input",
243+
onChange,
244+
onKeyDown,
245+
onKeyPress,
246+
onKeyUp
247+
})
248+
);
249+
250+
const text = "abc{enter}";
251+
252+
const inputEl = getByTestId("input");
253+
254+
await userEvent.type(inputEl, text);
255+
256+
const expectedText = "abc";
257+
258+
expect(inputEl).toHaveProperty("value", expectedText);
259+
expect(onChange).toHaveBeenCalledTimes(3);
260+
expect(onKeyPress).toHaveBeenCalledTimes(4);
261+
expect(onKeyDown).toHaveBeenCalledTimes(4);
262+
expect(onKeyUp).toHaveBeenCalledTimes(4);
263+
});
264+
});
265+
266+
describe("textarea", () => {
267+
it("should be able to type newlines with {enter}", async () => {
268+
const { getByTestId } = render(
269+
React.createElement("textarea", {
270+
"data-testid": "input",
271+
onChange,
272+
onKeyDown,
273+
onKeyPress,
274+
onKeyUp
275+
})
276+
);
277+
278+
const text = "a{enter}{enter}b{enter}";
279+
280+
const inputEl = getByTestId("input");
281+
282+
await userEvent.type(inputEl, text);
283+
284+
const expectedText = "a\n\nb\n";
285+
286+
expect(inputEl).toHaveProperty("value", expectedText);
287+
expect(onChange).toHaveBeenCalledTimes(5);
288+
expect(onKeyPress).toHaveBeenCalledTimes(5);
289+
expect(onKeyDown).toHaveBeenCalledTimes(5);
290+
expect(onKeyUp).toHaveBeenCalledTimes(5);
291+
});
292+
});
293+
});
294+
295+
describe("{esc}", () => {
296+
describe("input", () => {
297+
it("should record key up/down/press events from {esc}", async () => {
298+
const { getByTestId } = render(
299+
React.createElement("input", {
300+
"data-testid": "input",
301+
onChange,
302+
onKeyDown,
303+
onKeyPress,
304+
onKeyUp
305+
})
306+
);
307+
308+
const text = "a{esc}";
309+
310+
const inputEl = getByTestId("input");
311+
312+
await userEvent.type(inputEl, text);
313+
314+
const expectedText = "a";
315+
316+
expect(inputEl).toHaveProperty("value", expectedText);
317+
expect(onChange).toHaveBeenCalledTimes(1);
318+
expect(onKeyPress).toHaveBeenCalledTimes(1);
319+
expect(onKeyDown).toHaveBeenCalledTimes(2);
320+
expect(onKeyUp).toHaveBeenCalledTimes(2);
321+
});
322+
});
323+
324+
describe("textarea", () => {
325+
it("should be able to type newlines with {esc}", async () => {
326+
const { getByTestId } = render(
327+
React.createElement("textarea", {
328+
"data-testid": "input",
329+
onChange,
330+
onKeyDown,
331+
onKeyPress,
332+
onKeyUp
333+
})
334+
);
335+
336+
const text = "a{esc}";
337+
338+
const inputEl = getByTestId("input");
339+
340+
await userEvent.type(inputEl, text);
341+
342+
const expectedText = "a";
343+
344+
expect(inputEl).toHaveProperty("value", expectedText);
345+
expect(onChange).toHaveBeenCalledTimes(1);
346+
expect(onKeyPress).toHaveBeenCalledTimes(1);
347+
expect(onKeyDown).toHaveBeenCalledTimes(2);
348+
expect(onKeyUp).toHaveBeenCalledTimes(2);
349+
});
350+
});
351+
});
352+
353+
describe("{backspace}", () => {
354+
describe.each(["input", "textarea"])("%s", elementType => {
355+
it.each([
356+
[
357+
"ab{backspace}c",
358+
"ac",
359+
{ keyDown: 4, keyUp: 4, keyPress: 3, change: 4 }
360+
],
361+
[
362+
"a{backspace}{backspace}bc",
363+
"bc",
364+
{ keyDown: 5, keyUp: 5, keyPress: 3, change: 4 }
365+
],
366+
[
367+
"a{{backspace}}",
368+
"a}",
369+
{ keyDown: 4, keyUp: 4, keyPress: 3, change: 4 }
370+
]
371+
])(
372+
"input `%s` should output `%s` and have the correct number of fired events",
373+
async (
374+
typeText,
375+
expectedText,
376+
{
377+
keyDown: numKeyDownEvents,
378+
keyUp: numKeyUpEvents,
379+
keyPress: numKeyPressEvents,
380+
change: numOnChangeEvents
381+
}
382+
) => {
383+
const { getByTestId } = render(
384+
React.createElement(elementType, {
385+
"data-testid": "input",
386+
onChange,
387+
onKeyDown,
388+
onKeyPress,
389+
onKeyUp
390+
})
391+
);
392+
393+
const inputEl = getByTestId("input");
394+
395+
await userEvent.type(inputEl, typeText);
396+
397+
expect(inputEl).toHaveProperty("value", expectedText);
398+
expect(onChange).toHaveBeenCalledTimes(numOnChangeEvents);
399+
expect(onKeyDown).toHaveBeenCalledTimes(numKeyDownEvents);
400+
expect(onKeyUp).toHaveBeenCalledTimes(numKeyUpEvents);
401+
expect(onKeyPress).toHaveBeenCalledTimes(numKeyPressEvents);
402+
}
403+
);
404+
});
405+
});
406+
407+
describe("modifiers", () => {
408+
describe.each([
409+
["shift", "Shift", "shiftKey"],
410+
["ctrl", "Control", "ctrlKey"],
411+
["alt", "Alt", "altKey"],
412+
["meta", "OS", "metaKey"]
413+
])("%s", (modifierText, modifierKey, modifierProperty) => {
414+
describe.each(["input", "textarea"])("%s", elementType => {
415+
it("only adds modifier to following keystroke", async () => {
416+
const handler = jest.fn().mockImplementation(e => e.persist());
417+
418+
const { getByTestId } = render(
419+
React.createElement(elementType, {
420+
"data-testid": "input",
421+
onKeyDown: handler,
422+
onKeyPress: handler,
423+
onKeyUp: handler
424+
})
425+
);
426+
427+
const inputEl = getByTestId("input");
428+
429+
await userEvent.type(inputEl, `{${modifierText}}ab`);
430+
431+
expect(inputEl).toHaveProperty("value", "ab");
432+
433+
expect(handler).toHaveBeenCalledWithEventAtIndex(0, {
434+
type: "keydown",
435+
key: modifierKey,
436+
[modifierProperty]: false
437+
});
438+
expect(handler).toHaveBeenCalledWithEventAtIndex(1, {
439+
type: "keydown",
440+
key: "a",
441+
[modifierProperty]: true
442+
});
443+
expect(handler).toHaveBeenCalledWithEventAtIndex(2, {
444+
type: "keypress",
445+
key: "a",
446+
[modifierProperty]: true
447+
});
448+
expect(handler).toHaveBeenCalledWithEventAtIndex(3, {
449+
type: "keyup",
450+
key: "a",
451+
[modifierProperty]: true
452+
});
453+
expect(handler).toHaveBeenCalledWithEventAtIndex(4, {
454+
type: "keyup",
455+
key: modifierKey,
456+
[modifierProperty]: false
457+
});
458+
expect(handler).toHaveBeenCalledWithEventAtIndex(5, {
459+
type: "keydown",
460+
key: "b",
461+
[modifierProperty]: false
462+
});
463+
expect(handler).toHaveBeenCalledWithEventAtIndex(6, {
464+
type: "keypress",
465+
key: "b",
466+
[modifierProperty]: false
467+
});
468+
expect(handler).toHaveBeenCalledWithEventAtIndex(7, {
469+
type: "keyup",
470+
key: "b",
471+
[modifierProperty]: false
472+
});
473+
});
474+
});
475+
});
476+
477+
it("can handle multiple held modifiers", async () => {
478+
const handler = jest.fn().mockImplementation(e => e.persist());
479+
480+
const { getByTestId } = render(
481+
React.createElement("input", {
482+
"data-testid": "input",
483+
onKeyDown: handler,
484+
onKeyPress: handler,
485+
onKeyUp: handler
486+
})
487+
);
488+
489+
const inputEl = getByTestId("input");
490+
491+
await userEvent.type(inputEl, "{ctrl}{shift}ab");
492+
493+
expect(inputEl).toHaveProperty("value", "ab");
494+
495+
expect(handler).toHaveBeenCalledTimes(10);
496+
497+
expect(handler).toHaveBeenCalledWithEventAtIndex(0, {
498+
type: "keydown",
499+
key: "Control",
500+
ctrlKey: false,
501+
shiftKey: false
502+
});
503+
expect(handler).toHaveBeenCalledWithEventAtIndex(1, {
504+
type: "keydown",
505+
key: "Shift",
506+
ctrlKey: true,
507+
shiftKey: false
508+
});
509+
expect(handler).toHaveBeenCalledWithEventAtIndex(2, {
510+
type: "keydown",
511+
key: "a",
512+
ctrlKey: true,
513+
shiftKey: true
514+
});
515+
expect(handler).toHaveBeenCalledWithEventAtIndex(3, {
516+
type: "keypress",
517+
key: "a",
518+
ctrlKey: true,
519+
shiftKey: true
520+
});
521+
expect(handler).toHaveBeenCalledWithEventAtIndex(4, {
522+
type: "keyup",
523+
key: "a",
524+
ctrlKey: true,
525+
shiftKey: true
526+
});
527+
expect(handler).toHaveBeenCalledWithEventAtIndex(5, {
528+
type: "keyup",
529+
key: "Control",
530+
ctrlKey: false,
531+
shiftKey: true
532+
});
533+
expect(handler).toHaveBeenCalledWithEventAtIndex(6, {
534+
type: "keyup",
535+
key: "Shift",
536+
ctrlKey: false,
537+
shiftKey: false
538+
});
539+
expect(handler).toHaveBeenCalledWithEventAtIndex(7, {
540+
type: "keydown",
541+
key: "b",
542+
ctrlKey: false,
543+
shiftKey: false
544+
});
545+
expect(handler).toHaveBeenCalledWithEventAtIndex(8, {
546+
type: "keypress",
547+
key: "b",
548+
ctrlKey: false,
549+
shiftKey: false
550+
});
551+
expect(handler).toHaveBeenCalledWithEventAtIndex(9, {
552+
type: "keyup",
553+
key: "b",
554+
ctrlKey: false,
555+
shiftKey: false
556+
});
557+
});
558+
});
559+
});
211560
});

0 commit comments

Comments
 (0)