3 <title>Test for input event of text editor
</title>
4 <script src=
"/tests/SimpleTest/SimpleTest.js"></script>
5 <script src=
"/tests/SimpleTest/EventUtils.js"></script>
6 <link rel=
"stylesheet" type=
"text/css"
7 href=
"/tests/SimpleTest/test.css" />
11 <input type=
"text" id=
"input">
12 <textarea id=
"textarea"></textarea>
14 <div id=
"content" style=
"display: none">
20 <script class=
"testbody" type=
"application/javascript">
23 SimpleTest.waitForExplicitFinish();
24 SimpleTest.expectAssertions(
0,
1); // In a11y module
25 SimpleTest.waitForFocus(runTests, window);
28 const kWordSelectEatSpaceToNextWord = SpecialPowers.getBoolPref(
"layout.word_select.eat_space_to_next_word");
30 function doTests(aElement, aDescription, aIsTextarea) {
38 * @param aTestData Class like object to run a set of tests.
40 * Short explanation what it does.
41 * - cancelBeforeInput:
42 * true if preventDefault() of
"beforeinput" should be
44 * @param aFunc Function to run test.
45 * @param aExpected Object which has:
47 * Set string value if the test needs to check value of
49 * Set undefined if the test does not need to check it.
50 * - valueForCanceled [optional]:
51 * Set string value if canceling
"beforeinput" does not
52 * keep the value before calling aFunc.
53 * - beforeInputEvent [optional]:
54 * Set object which has `cancelable`, `inputType` and `data`
55 * if a
"beforeinput" event should be fired.
56 * Set null if
"beforeinput" event shouldn't be fired.
57 * - inputEvent [optional]:
58 * Set object which has `inputType` and `data` if an
"input" event
59 * should be fired if aTestData.cancelBeforeInput is not true.
60 * Set null if
"input" event shouldn't be fired.
61 * Note that if expected
"beforeinput" event is cancelable and
62 * aTestData.cancelBeforeInput is true, this is ignored.
64 function runTest(aTestData, aFunc, aExpected) {
65 let initializing = false;
66 let beforeInputEvent = null;
67 let inputEvent = null;
68 let beforeInputHandler = (aEvent) =
> {
73 `${aDescription}Multiple
"beforeinput" events are fired at ${aTestData.action} (inputType:
"${aEvent.inputType}", data: ${aEvent.data})`);
74 if (aTestData.cancelBeforeInput) {
75 aEvent.preventDefault();
78 `${aDescription}
"beforeinput" event at ${aTestData.action} must be trusted`);
79 is(aEvent.target, aElement,
80 `${aDescription}
"beforeinput" event at ${aTestData.action} is fired on unexpected element: ${aEvent.target.tagName}`);
81 ok(aEvent instanceof InputEvent,
82 `${aDescription}
"beforeinput" event at ${aTestData.action} should be dispatched with InputEvent interface`);
84 `${aDescription}
"beforeinput" event at ${aTestData.action} must be bubbles`);
85 beforeInputEvent = aEvent;
87 let inputHandler = (aEvent) =
> {
92 `${aDescription}Multiple
"input" events are fired at ${aTestData.action} (inputType:
"${aEvent.inputType}", data: ${aEvent.data})`);
94 `${aDescription}
"input" event at ${aTestData.action} must be trusted`);
95 is(aEvent.target, aElement, `
"input" event at ${aTestData.action} is fired on unexpected element: ${aEvent.target.tagName}`);
96 ok(aEvent instanceof InputEvent,
97 `${aDescription}
"input" event at ${aTestData.action} should be dispatched with InputEvent interface`);
98 ok(!aEvent.cancelable,
99 `${aDescription}
"input" event at ${aTestData.action} must not be cancelable`);
101 `${aDescription}
"input" event at ${aTestData.action} must be bubbles`);
102 let duration = Math.abs(window.performance.now() - aEvent.timeStamp);
103 ok(duration <
30 *
1000,
104 `${aDescription}perhaps, timestamp wasn't set correctly :${aEvent.timeStamp} (expected it to be within
30s of ` +
105 `the current time but it differed by ${duration}ms)`);
109 if (aTestData.cancelBeforeInput &&
110 (aExpected.beforeInputEvent === null || aExpected.beforeInputEvent === undefined)) {
112 `${aDescription}cancelBeforeInput must not be true for ${aTestData.action} because
"beforeinput" event is not expected`);
117 aElement.addEventListener(
"beforeinput", beforeInputHandler, true);
118 aElement.addEventListener(
"input", inputHandler, true);
120 let initialValue = aElement.value;
126 if (aExpected.value !== undefined) {
127 if (aTestData.cancelBeforeInput && aExpected.valueForCanceled === undefined) {
128 is(aElement.value, initialValue,
129 `${aDescription}the value should be
"${initialValue}" after ${aTestData.action}`);
132 aTestData.cancelBeforeInput ? aExpected.valueForCanceled : aExpected.value;
133 is(aElement.value, expectedValue,
134 `${aDescription}the value should be
"${expectedValue}" after ${aTestData.action}`);
137 if (aExpected.beforeInputEvent === null || aExpected.beforeInputEvent === undefined) {
138 ok(!beforeInputEvent,
139 `${aDescription}
"beforeinput" event shouldn't have been fired at ${aTestData.action}`);
142 `${aDescription}
"beforeinput" event should've been fired at ${aTestData.action}`);
143 is(beforeInputEvent.cancelable, aExpected.beforeInputEvent.cancelable,
144 `${aDescription}
"beforeinput" event by ${aTestData.action} should be ${
145 aExpected.beforeInputEvent.cancelable ?
"cancelable" :
"not cancelable"
147 is(beforeInputEvent.inputType, aExpected.beforeInputEvent.inputType,
148 `${aDescription}inputType of
"beforeinput" event by ${aTestData.action} should be
"${aExpected.beforeInputEvent.inputType}"`);
149 is(beforeInputEvent.data, aExpected.beforeInputEvent.data,
150 `${aDescription}data of
"beforeinput" event by ${aTestData.action} should be ${
151 aExpected.beforeInputEvent.data === null ?
"null" : `
"${aExpected.beforeInputEvent.data}"`
153 is(beforeInputEvent.dataTransfer, null,
154 `${aDescription}dataTransfer of
"beforeinput" event by ${aTestData.action} should be null`);
155 is(beforeInputEvent.getTargetRanges().length,
0,
156 `${aDescription}getTargetRanges() of
"beforeinput" event by ${aTestData.action} should return empty array`);
159 aTestData.cancelBeforeInput === true &&
160 aExpected.beforeInputEvent &&
161 aExpected.beforeInputEvent.cancelable
162 ) || aExpected.inputEvent === null || aExpected.inputEvent === undefined) {
164 `${aDescription}
"input" event shouldn't have been fired at ${aTestData.action}`);
167 `${aDescription}
"input" event should've been fired at ${aTestData.action}`);
168 is(inputEvent.cancelable, false,
169 `${aDescription}
"input" event by ${aTestData.action} should be not be cancelable`);
170 is(inputEvent.inputType, aExpected.inputEvent.inputType,
171 `${aDescription}inputType of
"input" event by ${aTestData.action} should be
"${aExpected.inputEvent.inputType}"`);
172 is(inputEvent.data, aExpected.inputEvent.data,
173 `${aDescription}data of
"input" event by ${aTestData.action} should be ${
174 aExpected.inputEvent.data === null ?
"null" : `
"${aExpected.inputEvent.data}"`
176 is(inputEvent.dataTransfer, null,
177 `${aDescription}dataTransfer of
"input" event by ${aTestData.action} should be null`);
178 is(inputEvent.getTargetRanges().length,
0,
179 `${aDescription}getTargetRanges() of
"input" event by ${aTestData.action} should return empty array`);
182 ok(false, `${aDescription}unexpected exception at verifying test result of
"${aTestData.action}": ${ex.toString()}`);
186 aElement.removeEventListener(
"beforeinput", beforeInputHandler, true);
187 aElement.removeEventListener(
"input", inputHandler, true);
191 function test_typing_a_in_empty_editor(aTestData) {
204 inputType:
"insertText",
208 inputType:
"insertText",
214 test_typing_a_in_empty_editor({
215 action: 'typing
"a" and canceling beforeinput',
216 cancelBeforeInput: true,
218 test_typing_a_in_empty_editor({
219 action: 'typing
"a"',
220 cancelBeforeInput: false,
223 function test_typing_backspace_to_delete_last_character(aTestData) {
224 aElement.value =
"a";
226 aElement.setSelectionStart =
"a".length;
231 synthesizeKey(
"KEY_Backspace");
237 inputType:
"deleteContentBackward",
241 inputType:
"deleteContentBackward",
247 test_typing_backspace_to_delete_last_character({
248 actin: 'typing
"Backspace" to delete
"a" and canceling
"beforeinput"',
249 cancelBeforeInput: true,
251 test_typing_backspace_to_delete_last_character({
252 actin: 'typing
"Backspace" to delete
"a"',
253 cancelBeforeInput: false,
256 function test_typing_enter_in_empty_editor(aTestData) {
263 synthesizeKey(
"KEY_Enter");
270 inputType:
"insertLineBreak",
274 inputType:
"insertLineBreak",
282 inputType:
"insertLineBreak",
288 test_typing_enter_in_empty_editor({
289 action: 'typing
"Enter" in empty editor and canceling
"beforeinput"',
290 cancelBeforeInput: true,
292 test_typing_enter_in_empty_editor({
293 action: 'typing
"Enter" in empty editor',
294 cancelBeforeInput: false,
297 (function test_setting_value(aTestData) {
304 aElement.value =
"foo-bar";
309 action:
"setting non-empty value",
312 (function test_setting_empty_value(aTestData) {
313 aElement.value =
"foo-bar";
324 action:
"setting empty value",
327 (function test_typing_space_in_empty_editor(aTestData) {
340 inputType:
"insertText",
344 inputType:
"insertText",
350 action:
"typing space",
353 (function test_typing_delete_at_end_of_editor(aTestData) {
354 aElement.value =
" ";
360 synthesizeKey(
"KEY_Delete");
366 inputType:
"deleteContentForward",
372 action: 'typing
"Delete" at end of editor',
375 (function test_typing_arrow_left_to_move_caret(aTestData) {
376 aElement.value =
" ";
378 aElement.selectionStart =
1;
383 synthesizeKey(
"KEY_ArrowLeft");
388 action: 'typing
"ArrowLeft" to move caret',
391 function test_typing_delete_to_delete_last_character(aTestData) {
392 aElement.value =
" ";
394 aElement.selectionStart =
0;
399 synthesizeKey(
"KEY_Delete");
405 inputType:
"deleteContentForward",
409 inputType:
"deleteContentForward",
415 test_typing_delete_to_delete_last_character({
416 action: 'typing
"Delete" to delete space and canceling
"beforeinput"',
417 cancelBeforeInput: true,
419 test_typing_delete_to_delete_last_character({
420 action: 'typing
"Delete" to delete space',
421 cancelBeforeInput: false,
424 function test_undoing_deleting_last_character(aTestData) {
425 aElement.value =
"a";
427 aElement.selectionStart =
0;
428 synthesizeKey(
"KEY_Delete");
433 synthesizeKey(
"z", {accelKey: true});
439 inputType:
"historyUndo",
443 inputType:
"historyUndo",
449 test_undoing_deleting_last_character({
450 action: 'undoing deleting last character and canceling
"beforeinput"',
451 cancelBeforeInput: true,
453 test_undoing_deleting_last_character({
454 action:
"undoing deleting last character",
455 cancelBeforeInput: false,
458 (function test_undoing_without_undoable_transaction(aTestData) {
459 aElement.value =
"a";
461 aElement.selectionStart =
0;
462 synthesizeKey(
"KEY_Delete");
463 synthesizeKey(
"z", {accelKey: true});
468 synthesizeKey(
"z", {accelKey: true});
473 action:
"trying to undo without undoable transaction"
476 function test_redoing_deleting_last_character(aTestData) {
477 aElement.value =
"a";
479 aElement.selectionStart =
0;
480 synthesizeKey(
"KEY_Delete");
481 synthesizeKey(
"z", {accelKey: true});
486 synthesizeKey(
"Z", {accelKey: true, shiftKey: true});
492 inputType:
"historyRedo",
496 inputType:
"historyRedo",
502 test_redoing_deleting_last_character({
503 action: 'redoing deleting last character and canceling
"beforeinput"',
504 cancelBeforeInput: true,
506 test_redoing_deleting_last_character({
507 action:
"redoing deleting last character",
508 cancelBeforeInput: false,
511 (function test_redoing_without_redoable_transaction(aTestData) {
512 aElement.value =
"a";
514 aElement.selectionStart =
0;
515 synthesizeKey(
"KEY_Delete");
516 synthesizeKey(
"z", {accelKey: true});
517 synthesizeKey(
"Z", {accelKey: true, shiftKey: true});
522 synthesizeKey(
"Z", {accelKey: true, shiftKey: true});
527 action:
"trying to redo without redoable transaction"
530 function test_typing_backspace_with_selecting_all_characters(aTestData) {
531 aElement.value =
"abc";
538 synthesizeKey(
"KEY_Backspace");
544 inputType:
"deleteContentBackward",
548 inputType:
"deleteContentBackward",
554 test_typing_backspace_with_selecting_all_characters({
555 action: 'typing
"Backspace" to delete all selected characters and canceling
"beforeinput"',
556 cancelBeforeInput: true,
558 test_typing_backspace_with_selecting_all_characters({
559 action: 'typing
"Backspace" to delete all selected characters',
560 cancelBeforeInput: false,
563 function test_typing_delete_with_selecting_all_characters(aTestData) {
564 aElement.value =
"abc";
571 synthesizeKey(
"KEY_Delete");
577 inputType:
"deleteContentForward",
581 inputType:
"deleteContentForward",
587 test_typing_delete_with_selecting_all_characters({
588 action: 'typing
"Delete" to delete all selected characters and canceling
"beforeinput"',
589 cancelBeforeInput: true,
591 test_typing_delete_with_selecting_all_characters({
592 action: 'typing
"Delete" to delete all selected characters and canceling
"beforeinput"',
593 cancelBeforeInput: false,
596 function test_deleting_word_backward_from_its_end(aTestData) {
597 aElement.value =
"abc def";
599 document.documentElement.scrollTop; // XXX Needs reflow here for working with nsFrameSelection, must be a bug.
600 aElement.setSelectionRange(
"abc def".length,
"abc def".length);
605 SpecialPowers.doCommand(window,
"cmd_deleteWordBackward");
611 inputType:
"deleteWordBackward",
615 inputType:
"deleteWordBackward",
621 test_deleting_word_backward_from_its_end({
622 action: 'deleting word backward from its end and canceling
"beforeinput"',
623 cancelBeforeInput: true,
625 test_deleting_word_backward_from_its_end({
626 action: 'deleting word backward from its end',
627 cancelBeforeInput: false,
630 function test_deleting_word_forward_from_its_start(aTestData) {
631 aElement.value =
"abc def";
633 document.documentElement.scrollTop; // XXX Needs reflow here for working with nsFrameSelection, must be a bug.
634 aElement.setSelectionRange(
0,
0);
639 SpecialPowers.doCommand(window,
"cmd_deleteWordForward");
642 value: kWordSelectEatSpaceToNextWord ?
"def" :
" def",
645 inputType:
"deleteWordForward",
649 inputType:
"deleteWordForward",
655 test_deleting_word_forward_from_its_start({
656 action: 'deleting word forward from its start and canceling
"beforeinput"',
657 cancelBeforeInput: true,
659 test_deleting_word_forward_from_its_start({
660 action:
"deleting word forward from its start",
661 cancelBeforeInput: false,
664 (function test_deleting_word_backward_from_middle_of_second_word(aTestData) {
665 aElement.value =
"abc def";
667 document.documentElement.scrollTop; // XXX Needs reflow here for working with nsFrameSelection, must be a bug.
668 aElement.setSelectionRange(
"abc d".length,
"abc de".length);
673 SpecialPowers.doCommand(window,
"cmd_deleteWordBackward");
679 inputType:
"deleteContentBackward",
683 inputType:
"deleteContentBackward",
689 action:
"removing characters backward from middle of second word",
692 (function test_deleting_word_forward_from_middle_of_first_word(aTestData) {
693 aElement.value =
"abc def";
695 document.documentElement.scrollTop; // XXX Needs reflow here for working with nsFrameSelection, must be a bug.
696 aElement.setSelectionRange(
"a".length,
"ab".length);
701 SpecialPowers.doCommand(window,
"cmd_deleteWordForward");
707 inputType:
"deleteContentForward",
711 inputType:
"deleteContentForward",
717 action:
"removing characters forward from middle of first word",
720 (function test_deleting_characters_backward_to_start_of_line(aTestData) {
721 aElement.value =
"abc def";
723 document.documentElement.scrollTop; // XXX Needs reflow here for working with nsFrameSelection, must be a bug.
724 aElement.setSelectionRange(
"abc d".length,
"abc d".length);
729 SpecialPowers.doCommand(window,
"cmd_deleteToBeginningOfLine");
735 inputType:
"deleteSoftLineBackward",
739 inputType:
"deleteSoftLineBackward",
745 action:
"removing characters backward to start of line"
748 (function test_deleting_characters_forward_to_end_of_line(aTestData) {
749 aElement.value =
"abc def";
751 document.documentElement.scrollTop; // XXX Needs reflow here for working with nsFrameSelection, must be a bug.
752 aElement.setSelectionRange(
"ab".length,
"ab".length);
757 SpecialPowers.doCommand(window,
"cmd_deleteToEndOfLine");
763 inputType:
"deleteSoftLineForward",
767 inputType:
"deleteSoftLineForward",
773 action:
"removing characters forward to end of line",
776 (function test_deleting_characters_backward_to_start_of_line_with_non_collapsed_selection(aTestData) {
777 aElement.value =
"abc def";
779 document.documentElement.scrollTop; // XXX Needs reflow here for working with nsFrameSelection, must be a bug.
780 aElement.setSelectionRange(
"abc d".length,
"abc_de".length);
785 SpecialPowers.doCommand(window,
"cmd_deleteToBeginningOfLine");
791 inputType:
"deleteContentBackward",
795 inputType:
"deleteContentBackward",
801 action:
"removing characters backward to start of line (with selection in second word)",
804 (function test_deleting_characters_forward_to_end_of_line_with_non_collapsed_selection(aTestData) {
805 aElement.value =
"abc def";
807 document.documentElement.scrollTop; // XXX Needs reflow here for working with nsFrameSelection, must be a bug.
808 aElement.setSelectionRange(
"a".length,
"ab".length);
813 SpecialPowers.doCommand(window,
"cmd_deleteToEndOfLine");
819 inputType:
"deleteContentForward",
823 inputType:
"deleteContentForward",
829 action:
"removing characters forward to end of line (with selection in second word)",
832 function test_switching_text_direction_from_default(aTestData) {
834 aElement.removeAttribute(
"dir");
835 aElement.scrollTop; // XXX Update the root frame
841 SpecialPowers.doCommand(window,
"cmd_switchTextDirection");
842 if (aTestData.cancelBeforeInput) {
843 is(aElement.getAttribute(
"dir"), null,
844 `${aDescription}dir attribute of the element shouldn't have been set by ${aTestData.action}`);
846 is(aElement.getAttribute(
"dir"),
"rtl",
847 `${aDescription}dir attribute of the element should've been set to
"rtl" by ${aTestData.action}`);
853 inputType:
"formatSetBlockTextDirection",
857 inputType:
"formatSetBlockTextDirection",
863 aElement.removeAttribute(
"dir");
864 aElement.scrollTop; // XXX Update the root frame
867 test_switching_text_direction_from_default({
868 action: 'switching text direction from default to
"rtl" and canceling
"beforeinput"',
869 cancelBeforeInput: true,
871 test_switching_text_direction_from_default({
872 action: 'switching text direction from default to
"rtl"',
873 cancelBeforeInput: false,
876 function test_switching_text_direction_from_rtl_to_ltr(aTestData) {
878 aElement.setAttribute(
"dir",
"rtl");
879 aElement.scrollTop; // XXX Update the root frame
885 SpecialPowers.doCommand(window,
"cmd_switchTextDirection");
886 let expectedDirValue = aTestData.cancelBeforeInput ?
"rtl" :
"ltr";
887 is(aElement.getAttribute(
"dir"), expectedDirValue,
888 `${aDescription}dir attribute of the element should be
"${expectedDirValue}" after ${aTestData.action}`);
893 inputType:
"formatSetBlockTextDirection",
897 inputType:
"formatSetBlockTextDirection",
903 aElement.removeAttribute(
"dir");
904 aElement.scrollTop; // XXX Update the root frame
907 test_switching_text_direction_from_rtl_to_ltr({
908 action: 'switching text direction from
"rtl" to
"ltr" and canceling
"beforeinput"',
909 cancelBeforeInput: true,
911 test_switching_text_direction_from_rtl_to_ltr({
912 action: 'switching text direction from
"rtl" to
"ltr" and canceling
"beforeinput"',
913 cancelBeforeInput: false,
917 doTests(document.getElementById(
"input"),
"<input type=\"text\
">", false);
918 doTests(document.getElementById(
"textarea"),
"<textarea>", true);