| <!DOCTYPE html> |
| <meta charset="utf-8"> |
| <title>Input Event typing tests</title> |
| <script src="/resources/testharness.js"></script> |
| <script src="/resources/testharnessreport.js"></script> |
| <script src="/resources/testdriver.js"></script> |
| <script src="/resources/testdriver-vendor.js"></script> |
| <script src="/resources/testdriver-actions.js"></script> |
| <div id="rich" contenteditable></div> |
| <textarea id="plain"></textarea> |
| <script> |
| let inputEventsLog = []; |
| const rich = document.getElementById('rich'); |
| const plain = document.getElementById('plain'); |
| |
| function log(event) { |
| const clone = new event.constructor(event.type, event); |
| clone.state = rich.innerHTML; |
| inputEventsLog.push(clone); |
| } |
| |
| function resetRich() { |
| inputEventsLog = []; |
| rich.innerHTML = ''; |
| } |
| |
| rich.addEventListener('beforeinput', log); |
| rich.addEventListener('input', log); |
| |
| promise_test(async function() { |
| this.add_cleanup(resetRich); |
| rich.focus(); |
| const message = 'Hello'; |
| await test_driver.send_keys(rich, message); |
| // 10 events (5 beforeinput + 5 input events) |
| assert_equals(inputEventsLog.length, 10); |
| for (let i = 0; i < inputEventsLog.length; i += 2) { |
| const beforeInputEvent = inputEventsLog[i]; |
| const inputEvent = inputEventsLog[i + 1]; |
| assert_equals(beforeInputEvent.type, 'beforeinput'); |
| assert_equals(inputEvent.type, 'input'); |
| assert_equals(beforeInputEvent.inputType, 'insertText'); |
| assert_equals(inputEvent.inputType, 'insertText'); |
| assert_equals(beforeInputEvent.data, inputEvent.data); |
| assert_equals(inputEvent.data, message[i / 2]); |
| assert_equals(beforeInputEvent.state + message[i / 2], inputEvent.state); |
| } |
| }, 'It triggers beforeinput and input events on text typing'); |
| |
| promise_test(async function() { |
| this.add_cleanup(resetRich); |
| rich.focus(); |
| await test_driver.send_keys(rich, "\uE006"); // Return |
| |
| assert_equals(inputEventsLog.length, 2); |
| const beforeInputEvent = inputEventsLog[0]; |
| const inputEvent = inputEventsLog[1]; |
| assert_equals(beforeInputEvent.type, 'beforeinput'); |
| assert_equals(inputEvent.type, 'input'); |
| assert_equals(beforeInputEvent.inputType, 'insertParagraph'); |
| assert_equals(inputEvent.inputType, 'insertParagraph'); |
| assert_equals(beforeInputEvent.data, inputEvent.data); |
| }, 'It triggers beforeinput and input events on typing RETURN'); |
| |
| promise_test(async function() { |
| this.add_cleanup(resetRich); |
| rich.focus(); |
| await new test_driver.Actions() |
| .keyDown('\uE008') // Shift |
| .keyDown('\uE006') // Return |
| .keyUp('\uE006') |
| .keyUp('\uE008') |
| .send(); |
| |
| assert_equals(inputEventsLog.length, 2); |
| const [beforeInputEvent, inputEvent] = inputEventsLog; |
| assert_equals(beforeInputEvent.type, 'beforeinput'); |
| assert_equals(inputEvent.type, 'input'); |
| assert_equals(beforeInputEvent.inputType, 'insertLineBreak'); |
| assert_equals(inputEvent.inputType, 'insertLineBreak'); |
| assert_equals(beforeInputEvent.data, inputEvent.data); |
| }, 'It triggers beforeinput and input events on typing Shift+RETURN'); |
| |
| promise_test(async function() { |
| this.add_cleanup(resetRich); |
| rich.innerHTML = '<p>Preexisting <i id="caret">c</i>ontent</p>'; |
| const caret = document.querySelector('#caret'); |
| await test_driver.click(caret); |
| await test_driver.send_keys(caret, "\uE017"); // Delete |
| |
| assert_equals(inputEventsLog.length, 2); |
| const [beforeInputEvent, inputEvent] = inputEventsLog; |
| assert_equals(beforeInputEvent.type, 'beforeinput'); |
| assert_equals(inputEvent.type, 'input'); |
| assert_equals(beforeInputEvent.inputType, 'deleteContentForward'); |
| assert_equals(inputEvent.inputType, 'deleteContentForward'); |
| assert_equals(beforeInputEvent.data, inputEvent.data); |
| }, 'It triggers beforeinput and input events on typing DELETE with pre-existing content'); |
| |
| promise_test(async function() { |
| this.add_cleanup(resetRich); |
| rich.focus(); |
| await test_driver.send_keys(rich, "\uE017"); // Delete |
| assert_equals(inputEventsLog.length, 2); |
| const [beforeInputEvent, inputEvent] = inputEventsLog; |
| assert_equals(beforeInputEvent.type, 'beforeinput'); |
| assert_equals(inputEvent.type, 'input'); |
| assert_equals(beforeInputEvent.inputType, 'deleteContentForward'); |
| assert_equals(inputEvent.inputType, 'deleteContentForward'); |
| assert_equals(beforeInputEvent.data, inputEvent.data); |
| }, 'It triggers beforeinput and input events on typing DELETE with no pre-existing content'); |
| |
| promise_test(async function() { |
| this.add_cleanup(resetRich); |
| rich.innerHTML = '<p>Preexisting <i id="caret">c</i>ontent</p>'; |
| |
| await test_driver.click(document.querySelector('#caret')); |
| await test_driver.send_keys(rich, "\uE003"); // Back Space |
| |
| assert_equals(inputEventsLog.length, 2); |
| const [beforeInputEvent, inputEvent] = inputEventsLog; |
| assert_equals(beforeInputEvent.type, 'beforeinput'); |
| assert_equals(inputEvent.type, 'input'); |
| assert_equals(beforeInputEvent.inputType, 'deleteContentBackward'); |
| assert_equals(inputEvent.inputType, 'deleteContentBackward'); |
| assert_equals(beforeInputEvent.data, inputEvent.data); |
| }, 'It triggers beforeinput and input events on typing BACK_SPACE with pre-existing content'); |
| |
| promise_test(async function() { |
| this.add_cleanup(resetRich); |
| rich.focus(); |
| await test_driver.send_keys(rich, "\uE003"); // Back Space |
| |
| assert_equals(inputEventsLog.length, 2); |
| const [beforeInputEvent, inputEvent] = inputEventsLog; |
| assert_equals(beforeInputEvent.type, 'beforeinput'); |
| assert_equals(inputEvent.type, 'input'); |
| assert_equals(beforeInputEvent.inputType, 'deleteContentBackward'); |
| assert_equals(inputEvent.inputType, 'deleteContentBackward'); |
| assert_equals(beforeInputEvent.data, inputEvent.data); |
| }, 'It triggers beforeinput and input events on typing BACK_SPACE with no pre-existing content'); |
| |
| promise_test(async function() { |
| this.add_cleanup(resetRich); |
| rich.focus(); |
| await test_driver.send_keys(rich, "hello"); |
| |
| // Decide whether to use Key.COMMAND (mac) or Key.CONTROL (everything else) |
| const modifierKey = navigator.platform === "MacIntel" ? '\u2318' : '\uE009'; |
| |
| // Undo |
| await new test_driver.Actions() |
| .keyDown(modifierKey) |
| .keyDown('z') |
| .keyUp('z') |
| .keyUp(modifierKey) |
| .send(); |
| // Redo |
| await new test_driver.Actions() |
| .keyDown(modifierKey) |
| .keyDown('\uE008') // Shift |
| .keyDown('z') |
| .keyUp('z') |
| .keyUp('\uE008') |
| .keyUp(modifierKey) |
| .send(); |
| |
| // Ignore the initial typing of 'hello' |
| const historyInputEventsLog = inputEventsLog.slice(10); |
| |
| assert_equals(historyInputEventsLog.length, 4); |
| const inputTypes = ['historyUndo', 'historyRedo']; |
| for (let i = 0; i < historyInputEventsLog.length; i += 2) { |
| // We are increaisng i by 2 as there should always be matching beforeinput and input events. |
| const beforeInputEvent = historyInputEventsLog[i]; |
| const inputEvent = historyInputEventsLog[i + 1]; |
| assert_equals(beforeInputEvent.type, 'beforeinput'); |
| assert_equals(inputEvent.type, 'input'); |
| assert_equals(beforeInputEvent.inputType, inputTypes[i / 2]); |
| assert_equals(inputEvent.inputType, inputTypes[i / 2]); |
| assert_equals(beforeInputEvent.data, inputEvent.data); |
| } |
| }, 'It triggers beforeinput and input events on typing Undo and Redo key combinations with an existing history'); |
| |
| promise_test(async function() { |
| this.add_cleanup(resetRich); |
| rich.focus(); |
| // Decide whether to use Key.COMMAND (mac) or Key.CONTROL (everything else) |
| const modifierKey = navigator.platform === "MacIntel" ? '\u2318' : '\uE009'; |
| |
| // Undo |
| await new test_driver.Actions() |
| .keyDown(modifierKey) |
| .keyDown('z') |
| .keyUp('z') |
| .keyUp(modifierKey) |
| .send(); |
| // Redo |
| await new test_driver.Actions() |
| .keyDown(modifierKey) |
| .keyDown('\uE008') // Shift |
| .keyDown('z') |
| .keyUp('z') |
| .keyUp('\uE008') |
| .keyUp(modifierKey) |
| .send(); |
| |
| assert_equals(inputEventsLog.length, 4); |
| const inputTypes = ['historyUndo', 'historyRedo']; |
| for (let i = 0; i < inputEventsLog.length; i += 2) { |
| const beforeInputEvent = inputEventsLog[i]; |
| const inputEvent = inputEventsLog[i + 1]; |
| assert_equals(beforeInputEvent.type, 'beforeinput'); |
| assert_equals(inputEvent.type, 'input'); |
| assert_equals(beforeInputEvent.inputType, inputTypes[i / 2]); |
| assert_equals(inputEvent.inputType, inputTypes[i / 2]); |
| assert_equals(beforeInputEvent.data, inputEvent.data); |
| } |
| }, 'It triggers beforeinput and input events on typing Undo and Redo key combinations without an existing history'); |
| |
| promise_test(async function() { |
| this.add_cleanup(resetRich); |
| const expectedResult = [ |
| // Pressing 'a'. |
| 'plain-keydown-a', |
| 'plain-keypress-a', |
| 'plain-beforeinput-a-null', |
| 'plain-input-a-null', |
| 'plain-keyup-a', |
| // Pressing Shift-'b'. |
| 'plain-keydown-B', |
| 'plain-keypress-B', |
| 'plain-beforeinput-B-null', |
| 'plain-input-B-null', |
| 'plain-keyup-B', |
| // Pressing 'c'. |
| 'rich-keydown-c', |
| 'rich-keypress-c', |
| 'rich-beforeinput-c-null', |
| 'rich-input-c-null', |
| 'rich-keyup-c', |
| // Pressing Shift-'d'. |
| 'rich-keydown-D', |
| 'rich-keypress-D', |
| 'rich-beforeinput-D-null', |
| 'rich-input-D-null', |
| 'rich-keyup-D', |
| ]; |
| const result = []; |
| |
| for (const eventType of ['beforeinput', 'input', 'keydown', 'keypress', 'keyup']) { |
| const listener = event => { |
| if (event.key === 'Shift') return; |
| const eventInfo = [event.target.id, event.type, event.data || event.key]; |
| if (event instanceof InputEvent) eventInfo.push(String(event.dataTransfer)); |
| result.push(eventInfo.join('-')); |
| } |
| rich.addEventListener(eventType, listener); |
| plain.addEventListener(eventType, listener); |
| } |
| |
| plain.focus(); |
| await new test_driver.Actions() |
| .keyDown('a') |
| .keyUp('a') |
| .keyDown('\uE008') // Shift |
| .keyDown('b') |
| .keyUp('b') |
| .keyUp('\uE008') |
| .send(); |
| |
| rich.focus(); |
| await new test_driver.Actions() |
| .keyDown('c') |
| .keyUp('c') |
| .keyDown('\uE008') // Shift |
| .keyDown('d') |
| .keyUp('d') |
| .keyUp('\uE008') |
| .send(); |
| |
| assert_equals(expectedResult.length, result.length); |
| expectedResult.forEach((er, index) => assert_equals(er, result[index])); |
| }, 'InputEvents have correct data/order when typing on textarea and contenteditable'); |
| </script> |