diff --git a/packages/react-dom/src/events/__tests__/BeforeInputEventPlugin-test.internal.js b/packages/react-dom/src/events/__tests__/BeforeInputEventPlugin-test.internal.js deleted file mode 100644 index e7aaf65073c9f..0000000000000 --- a/packages/react-dom/src/events/__tests__/BeforeInputEventPlugin-test.internal.js +++ /dev/null @@ -1,226 +0,0 @@ -/** - * Copyright (c) 2013-present, Facebook, Inc. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @emails react-core - */ - -'use strict'; - -const React = require('react'); -const ReactTestUtils = require('react-dom/test-utils'); - -const EventMapping = { - compositionstart: 'topCompositionStart', - compositionend: 'topCompositionEnd', - keyup: 'topKeyUp', - keydown: 'topKeyDown', - keypress: 'topKeyPress', - textInput: 'topTextInput', - textinput: null, // Not defined now -}; - -describe('BeforeInputEventPlugin', function() { - let ModuleCache; - - function simulateIE11() { - document.documentMode = 11; - window.CompositionEvent = {}; - delete window.TextEvent; - } - - function simulateWebkit() { - delete document.documentMode; - window.CompositionEvent = {}; - window.TextEvent = {}; - } - - function initialize(simulator) { - // Need to delete cached modules before executing simulator - jest.resetModules(); - - // Initialize variables in the scope of BeforeInputEventPlugin - simulator(); - - // Modules which have dependency on BeforeInputEventPlugin are stored - // in ModuleCache so that we can use these modules ouside test functions. - this.ReactDOM = require('react-dom'); - - // TODO: can we express this test with only public API? - this.ReactDOMComponentTree = require('../../client/ReactDOMComponentTree'); - this.SyntheticCompositionEvent = require('../SyntheticCompositionEvent').default; - this.SyntheticInputEvent = require('../SyntheticInputEvent').default; - this.BeforeInputEventPlugin = require('../BeforeInputEventPlugin').default; - } - - function extract(node, eventType, optionalData) { - let evt = document.createEvent('HTMLEvents'); - evt.initEvent(eventType, true, true); - evt = Object.assign(evt, optionalData); - return ModuleCache.BeforeInputEventPlugin.extractEvents( - EventMapping[eventType], - ModuleCache.ReactDOMComponentTree.getInstanceFromNode(node), - evt, - node, - ); - } - - function setElementText(node) { - return args => (node.innerHTML = args); - } - - function accumulateEvents(node, events) { - // We don't use accumulateInto module to apply partial application. - return function() { - const newArgs = [node].concat(Array.prototype.slice.call(arguments)); - const newEvents = extract.apply(this, newArgs); - - if (Array.isArray(newEvents)) { - Array.prototype.push.apply(events, newEvents); - } else if (newEvents) { - events.push(newEvents); - } - }; - } - - function EventMismatchError(idx, message) { - this.name = 'EventMismatchError'; - this.message = '[' + idx + '] ' + message; - } - EventMismatchError.prototype = Object.create(Error.prototype); - - function verifyEvents(actualEvents, expectedEvents) { - expect(actualEvents.length).toBe(expectedEvents.length); - expectedEvents.forEach(function(expected, idx) { - const actual = actualEvents[idx]; - expect(function() { - if (actual === null && expected.type === null) { - // Both are null. Expected. - } else if (actual === null) { - throw new EventMismatchError(idx, 'Expected not to be null'); - } else if ( - expected.type === null || - !(actual instanceof expected.type) - ) { - throw new EventMismatchError(idx, 'Unexpected type: ' + actual); - } else { - // Type match. - Object.keys(expected.data).forEach(function(expectedKey) { - if (!(expectedKey in actual)) { - throw new EventMismatchError(idx, 'KeyNotFound: ' + expectedKey); - } else if (actual[expectedKey] !== expected.data[expectedKey]) { - throw new EventMismatchError( - idx, - 'ValueMismatch: ' + actual[expectedKey], - ); - } - }); - } - }).not.toThrow(); - }); - } - - // IE fires an event named `textinput` with all lowercase characters, - // instead of a standard name `textInput`. As of now, React does not have - // a corresponding topEvent to IE's textinput, but both events are added to - // this scenario data for future use. - const Test_Scenario = [ - // Composition test - {run: accumulateEvents, arg: ['compositionstart', {data: ''}]}, - {run: accumulateEvents, arg: ['textInput', {data: 'A'}]}, - {run: accumulateEvents, arg: ['textinput', {data: 'A'}]}, - {run: accumulateEvents, arg: ['keyup', {keyCode: 65}]}, - {run: setElementText, arg: ['ABC']}, - {run: accumulateEvents, arg: ['textInput', {data: 'abc'}]}, - {run: accumulateEvents, arg: ['textinput', {data: 'abc'}]}, - {run: accumulateEvents, arg: ['keyup', {keyCode: 32}]}, - {run: setElementText, arg: ['XYZ']}, - {run: accumulateEvents, arg: ['textInput', {data: 'xyz'}]}, - {run: accumulateEvents, arg: ['textinput', {data: 'xyz'}]}, - {run: accumulateEvents, arg: ['keyup', {keyCode: 32}]}, - {run: accumulateEvents, arg: ['compositionend', {data: 'Hello'}]}, - - // Emoji test - { - run: accumulateEvents, - arg: ['keypress', {char: '\uD83D\uDE0A', which: 65}], - }, - {run: accumulateEvents, arg: ['textInput', {data: '\uD83D\uDE0A'}]}, - ]; - - /* Defined expected results as a factory of result data because we need - lazy evaluation for event modules. - Event modules are reloaded to simulate a different platform per testcase. - If we define expected results as a simple dictionary here, the comparison - of 'instanceof' fails after module cache is reset. */ - - // Webkit behavior is simple. We expect SyntheticInputEvent at each - // textInput, SyntheticCompositionEvent at composition, and nothing from - // keyUp. - const Expected_Webkit = () => [ - {type: ModuleCache.SyntheticCompositionEvent, data: {}}, - {type: ModuleCache.SyntheticInputEvent, data: {data: 'A'}}, - // textinput of A - // keyUp of 65 - {type: ModuleCache.SyntheticInputEvent, data: {data: 'abc'}}, - // textinput of abc - // keyUp of 32 - {type: ModuleCache.SyntheticInputEvent, data: {data: 'xyz'}}, - // textinput of xyz - // keyUp of 32 - {type: ModuleCache.SyntheticCompositionEvent, data: {data: 'Hello'}}, - // Emoji test - {type: ModuleCache.SyntheticInputEvent, data: {data: '\uD83D\uDE0A'}}, - ]; - - // For IE11, we use fallback data instead of IE's textinput events. - // We expect no SyntheticInputEvent from textinput. Fallback beforeInput is - // expected to be triggered at compositionend with a text of the target - // element, not event data. - const Expected_IE11 = () => [ - {type: ModuleCache.SyntheticCompositionEvent, data: {}}, - // textInput of A - // textinput of A - // keyUp of 65 - // textInput of abc - // textinput of abc - // fallbackData should NOT be set at keyUp with any of END_KEYCODES - // keyUp of 32 - // textInput of xyz - // textinput of xyz - // keyUp of 32 - // fallbackData is retrieved from the element, which is XYZ, - // at a time of compositionend - {type: ModuleCache.SyntheticCompositionEvent, data: {}}, - {type: ModuleCache.SyntheticInputEvent, data: {data: 'XYZ'}}, - // Emoji test - {type: ModuleCache.SyntheticInputEvent, data: {data: '\uD83D\uDE0A'}}, - ]; - - function TestEditableReactComponent(Emulator, Scenario, ExpectedResult) { - ModuleCache = new initialize(Emulator); - - class EditableDiv extends React.Component { - render() { - return
; - } - } - const rendered = ReactTestUtils.renderIntoDocument(); - - const node = ModuleCache.ReactDOM.findDOMNode(rendered); - const events = []; - - Scenario.forEach(el => el.run.call(this, node, events).apply(this, el.arg)); - verifyEvents(events, ExpectedResult()); - } - - it('extract onBeforeInput from native textinput events', function() { - TestEditableReactComponent(simulateWebkit, Test_Scenario, Expected_Webkit); - }); - - it('extract onBeforeInput from fallback objects', function() { - TestEditableReactComponent(simulateIE11, Test_Scenario, Expected_IE11); - }); -}); diff --git a/packages/react-dom/src/events/__tests__/BeforeInputEventPlugin-test.js b/packages/react-dom/src/events/__tests__/BeforeInputEventPlugin-test.js new file mode 100644 index 0000000000000..542b3ebe69f9e --- /dev/null +++ b/packages/react-dom/src/events/__tests__/BeforeInputEventPlugin-test.js @@ -0,0 +1,830 @@ +/** + * Copyright (c) 2013-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @emails react-core + */ + +'use strict'; + +let React; +let ReactDOM; + +describe('BeforeInputEventPlugin', () => { + let container; + + function loadReactDOM(envSimulator) { + jest.resetModules(); + if (envSimulator) { + envSimulator(); + } + return require('react-dom'); + } + + function simulateIE11() { + document.documentMode = 11; + window.CompositionEvent = {}; + } + + function simulateWebkit() { + window.CompositionEvent = {}; + window.TextEvent = {}; + } + + function simulateComposition() { + window.CompositionEvent = {}; + } + + function simulateNoComposition() { + // no composition event in Window - will use fallback + } + + function simulateEvent(elem, type, data) { + const event = new Event(type, {bubbles: true}); + Object.keys(data).forEach(key => { + event[key] = data[key]; + }); + elem.dispatchEvent(event); + } + + function simulateKeyboardEvent(elem, type, data) { + const {char, value, ...rest} = data; + const event = new KeyboardEvent(type, { + bubbles: true, + ...rest, + }); + if (char) { + event.char = char; + } + if (value) { + elem.value = value; + } + elem.dispatchEvent(event); + } + + function simulatePaste(elem) { + const pasteEvent = new Event('paste', { + bubbles: true, + }); + pasteEvent.clipboardData = { + dropEffect: null, + effectAllowed: null, + files: null, + items: null, + types: null, + }; + elem.dispatchEvent(pasteEvent); + } + + beforeEach(() => { + React = require('react'); + container = document.createElement('div'); + document.body.appendChild(container); + }); + + afterEach(() => { + delete document.documentMode; + delete window.CompositionEvent; + delete window.TextEvent; + delete window.opera; + document.body.removeChild(container); + container = null; + }); + + function keyCode(char) { + return char.charCodeAt(0); + } + + const scenarios = [ + { + eventSimulator: simulateEvent, + eventSimulatorArgs: [ + 'compositionstart', + {detail: {data: 'test'}, data: 'test'}, + ], + }, + { + eventSimulator: simulateEvent, + eventSimulatorArgs: [ + 'compositionupdate', + {detail: {data: 'test string'}, data: 'test string'}, + ], + }, + { + eventSimulator: simulateEvent, + eventSimulatorArgs: [ + 'compositionend', + {detail: {data: 'test string 3'}, data: 'test string 3'}, + ], + }, + { + eventSimulator: simulateEvent, + eventSimulatorArgs: ['textInput', {data: 'abcß'}], + }, + { + eventSimulator: simulateKeyboardEvent, + eventSimulatorArgs: ['keypress', {which: keyCode('a')}], + }, + { + eventSimulator: simulateKeyboardEvent, + eventSimulatorArgs: ['keypress', {which: keyCode(' ')}, ' '], + }, + { + eventSimulator: simulateEvent, + eventSimulatorArgs: ['textInput', {data: ' '}], + }, + { + eventSimulator: simulateKeyboardEvent, + eventSimulatorArgs: ['keypress', {which: keyCode('a'), ctrlKey: true}], + }, + { + eventSimulator: simulateKeyboardEvent, + eventSimulatorArgs: ['keypress', {which: keyCode('b'), altKey: true}], + }, + { + eventSimulator: simulateKeyboardEvent, + eventSimulatorArgs: [ + 'keypress', + {which: keyCode('c'), altKey: true, ctrlKey: true}, + ], + }, + { + eventSimulator: simulateKeyboardEvent, + eventSimulatorArgs: [ + 'keypress', + {which: keyCode('X'), char: '\uD83D\uDE0A'}, + ], + }, + { + eventSimulator: simulateEvent, + eventSimulatorArgs: ['textInput', {data: '\uD83D\uDE0A'}], + }, + { + eventSimulator: simulateKeyboardEvent, + eventSimulatorArgs: ['keydown', {keyCode: 229, value: 'foo'}], + }, + { + eventSimulator: simulateKeyboardEvent, + eventSimulatorArgs: ['keydown', {keyCode: 9, value: 'foobar'}], + }, + { + eventSimulator: simulateKeyboardEvent, + eventSimulatorArgs: ['keydown', {keyCode: 229, value: 'foofoo'}], + }, + { + eventSimulator: simulateKeyboardEvent, + eventSimulatorArgs: ['keyup', {keyCode: 9, value: 'fooBARfoo'}], + }, + { + eventSimulator: simulateKeyboardEvent, + eventSimulatorArgs: ['keydown', {keyCode: 229, value: 'foofoo'}], + }, + { + eventSimulator: simulateKeyboardEvent, + eventSimulatorArgs: ['keypress', {keyCode: 60, value: 'Barfoofoo'}], + }, + { + eventSimulator: simulatePaste, + eventSimulatorArgs: [], + }, + ]; + + const environments = [ + { + emulator: simulateWebkit, + assertions: [ + { + run: ({ + beforeInputEvent, + compositionStartEvent, + spyOnBeforeInput, + spyOnCompositionStart, + }) => { + expect(spyOnBeforeInput.mock.calls.length).toBe(0); + expect(beforeInputEvent).toBeNull(); + expect(spyOnCompositionStart.mock.calls.length).toBe(1); + expect(compositionStartEvent.type).toBe('compositionstart'); + expect(compositionStartEvent.data).toBe('test'); + }, + }, + { + run: ({ + beforeInputEvent, + compositionUpdateEvent, + spyOnBeforeInput, + spyOnCompositionUpdate, + }) => { + expect(spyOnBeforeInput.mock.calls.length).toBe(0); + expect(beforeInputEvent).toBeNull(); + expect(spyOnCompositionUpdate.mock.calls.length).toBe(1); + expect(compositionUpdateEvent.type).toBe('compositionupdate'); + expect(compositionUpdateEvent.data).toBe('test string'); + }, + }, + { + run: ({beforeInputEvent, spyOnBeforeInput}) => { + expect(spyOnBeforeInput.mock.calls.length).toBe(1); + expect(beforeInputEvent.type).toBe('compositionend'); + expect(beforeInputEvent.data).toBe('test string 3'); + }, + }, + { + run: ({beforeInputEvent, spyOnBeforeInput}) => { + expect(spyOnBeforeInput.mock.calls.length).toBe(1); + expect(beforeInputEvent.type).toBe('textInput'); + expect(beforeInputEvent.data).toBe('abcß'); + }, + }, + { + run: ({beforeInputEvent, spyOnBeforeInput}) => { + expect(spyOnBeforeInput.mock.calls.length).toBe(0); + expect(beforeInputEvent).toBeNull(); + }, + }, + { + run: ({beforeInputEvent, spyOnBeforeInput}) => { + expect(spyOnBeforeInput.mock.calls.length).toBe(1); + expect(beforeInputEvent.type).toBe('keypress'); + expect(beforeInputEvent.data).toBe(' '); + }, + }, + { + run: ({beforeInputEvent, spyOnBeforeInput}) => { + expect(spyOnBeforeInput.mock.calls.length).toBe(0); + expect(beforeInputEvent).toBeNull(); + }, + }, + { + run: ({beforeInputEvent, spyOnBeforeInput}) => { + expect(spyOnBeforeInput.mock.calls.length).toBe(0); + expect(beforeInputEvent).toBeNull(); + }, + }, + { + run: ({beforeInputEvent, spyOnBeforeInput}) => { + expect(spyOnBeforeInput.mock.calls.length).toBe(0); + expect(beforeInputEvent).toBeNull(); + }, + }, + { + run: ({beforeInputEvent, spyOnBeforeInput}) => { + expect(spyOnBeforeInput.mock.calls.length).toBe(0); + expect(beforeInputEvent).toBeNull(); + }, + }, + { + run: ({beforeInputEvent, spyOnBeforeInput}) => { + expect(spyOnBeforeInput.mock.calls.length).toBe(0); + expect(beforeInputEvent).toBeNull(); + }, + }, + { + run: ({beforeInputEvent, spyOnBeforeInput}) => { + expect(spyOnBeforeInput.mock.calls.length).toBe(1); + expect(beforeInputEvent.type).toBe('textInput'); + expect(beforeInputEvent.data).toBe('\uD83D\uDE0A'); + }, + }, + { + run: ({beforeInputEvent, spyOnBeforeInput}) => { + expect(spyOnBeforeInput.mock.calls.length).toBe(0); + expect(beforeInputEvent).toBeNull(); + }, + }, + { + run: ({beforeInputEvent, spyOnBeforeInput}) => { + expect(spyOnBeforeInput.mock.calls.length).toBe(0); + expect(beforeInputEvent).toBeNull(); + }, + }, + { + run: ({beforeInputEvent, spyOnBeforeInput}) => { + expect(spyOnBeforeInput.mock.calls.length).toBe(0); + expect(beforeInputEvent).toBeNull(); + }, + }, + { + run: ({beforeInputEvent, spyOnBeforeInput}) => { + expect(spyOnBeforeInput.mock.calls.length).toBe(0); + expect(beforeInputEvent).toBeNull(); + }, + }, + { + run: ({beforeInputEvent, spyOnBeforeInput}) => { + expect(spyOnBeforeInput.mock.calls.length).toBe(0); + expect(beforeInputEvent).toBeNull(); + }, + }, + { + run: ({beforeInputEvent, spyOnBeforeInput}) => { + expect(spyOnBeforeInput.mock.calls.length).toBe(0); + expect(beforeInputEvent).toBeNull(); + }, + }, + { + run: ({beforeInputEvent, spyOnBeforeInput}) => { + expect(spyOnBeforeInput.mock.calls.length).toBe(0); + expect(beforeInputEvent).toBeNull(); + }, + }, + ], + }, + { + emulator: simulateIE11, + assertions: [ + { + run: ({beforeInputEvent, spyOnBeforeInput}) => { + expect(spyOnBeforeInput.mock.calls.length).toBe(0); + expect(beforeInputEvent).toBeNull(); + }, + }, + { + run: ({beforeInputEvent, spyOnBeforeInput}) => { + expect(spyOnBeforeInput.mock.calls.length).toBe(0); + expect(beforeInputEvent).toBeNull(); + }, + }, + { + run: ({beforeInputEvent, spyOnBeforeInput}) => { + expect(spyOnBeforeInput.mock.calls.length).toBe(0); + expect(beforeInputEvent).toBeNull(); + }, + }, + { + run: ({beforeInputEvent, spyOnBeforeInput}) => { + expect(spyOnBeforeInput.mock.calls.length).toBe(0); + expect(beforeInputEvent).toBeNull(); + }, + }, + { + run: ({beforeInputEvent, spyOnBeforeInput}) => { + expect(spyOnBeforeInput.mock.calls.length).toBe(1); + expect(beforeInputEvent.type).toBe('keypress'); + expect(beforeInputEvent.data).toBe('a'); + }, + }, + { + run: ({beforeInputEvent, spyOnBeforeInput}) => { + expect(spyOnBeforeInput.mock.calls.length).toBe(1); + expect(beforeInputEvent.type).toBe('keypress'); + expect(beforeInputEvent.data).toBe(' '); + }, + }, + { + run: ({beforeInputEvent, spyOnBeforeInput}) => { + expect(spyOnBeforeInput.mock.calls.length).toBe(0); + expect(beforeInputEvent).toBeNull(); + }, + }, + { + run: ({beforeInputEvent, spyOnBeforeInput}) => { + expect(spyOnBeforeInput.mock.calls.length).toBe(0); + expect(beforeInputEvent).toBeNull(); + }, + }, + { + run: ({beforeInputEvent, spyOnBeforeInput}) => { + expect(spyOnBeforeInput.mock.calls.length).toBe(0); + expect(beforeInputEvent).toBeNull(); + }, + }, + { + run: ({beforeInputEvent, spyOnBeforeInput}) => { + expect(spyOnBeforeInput.mock.calls.length).toBe(1); + expect(beforeInputEvent.type).toBe('keypress'); + expect(beforeInputEvent.data).toBe('c'); + }, + }, + { + run: ({beforeInputEvent, spyOnBeforeInput}) => { + expect(spyOnBeforeInput.mock.calls.length).toBe(1); + expect(beforeInputEvent.type).toBe('keypress'); + expect(beforeInputEvent.data).toBe('\uD83D\uDE0A'); + }, + }, + { + run: ({beforeInputEvent, spyOnBeforeInput}) => { + expect(spyOnBeforeInput.mock.calls.length).toBe(0); + expect(beforeInputEvent).toBeNull(); + }, + }, + { + run: ({beforeInputEvent, spyOnBeforeInput}) => { + expect(spyOnBeforeInput.mock.calls.length).toBe(0); + expect(beforeInputEvent).toBeNull(); + }, + }, + { + run: ({beforeInputEvent, spyOnBeforeInput}) => { + expect(spyOnBeforeInput.mock.calls.length).toBe(0); + expect(beforeInputEvent).toBeNull(); + }, + }, + { + run: ({beforeInputEvent, spyOnBeforeInput}) => { + expect(spyOnBeforeInput.mock.calls.length).toBe(0); + expect(beforeInputEvent).toBeNull(); + }, + }, + { + run: ({beforeInputEvent, spyOnBeforeInput}) => { + expect(spyOnBeforeInput.mock.calls.length).toBe(0); + expect(beforeInputEvent).toBeNull(); + }, + }, + { + run: ({beforeInputEvent, spyOnBeforeInput}) => { + expect(spyOnBeforeInput.mock.calls.length).toBe(0); + expect(beforeInputEvent).toBeNull(); + }, + }, + { + run: ({beforeInputEvent, spyOnBeforeInput}) => { + expect(spyOnBeforeInput.mock.calls.length).toBe(0); + expect(beforeInputEvent).toBeNull(); + }, + }, + { + run: ({beforeInputEvent, spyOnBeforeInput}) => { + expect(spyOnBeforeInput.mock.calls.length).toBe(0); + expect(beforeInputEvent).toBeNull(); + }, + }, + ], + }, + { + emulator: simulateNoComposition, + assertions: [ + { + run: ({beforeInputEvent, spyOnBeforeInput}) => { + expect(spyOnBeforeInput.mock.calls.length).toBe(0); + expect(beforeInputEvent).toBeNull(); + }, + }, + { + run: ({beforeInputEvent, spyOnBeforeInput}) => { + expect(spyOnBeforeInput.mock.calls.length).toBe(0); + expect(beforeInputEvent).toBeNull(); + }, + }, + { + run: ({beforeInputEvent, spyOnBeforeInput}) => { + expect(spyOnBeforeInput.mock.calls.length).toBe(0); + expect(beforeInputEvent).toBeNull(); + }, + }, + { + run: ({beforeInputEvent, spyOnBeforeInput}) => { + expect(spyOnBeforeInput.mock.calls.length).toBe(0); + expect(beforeInputEvent).toBeNull(); + }, + }, + { + run: ({beforeInputEvent, spyOnBeforeInput}) => { + expect(spyOnBeforeInput.mock.calls.length).toBe(1); + expect(beforeInputEvent.type).toBe('keypress'); + expect(beforeInputEvent.data).toBe('a'); + }, + }, + { + run: ({beforeInputEvent, spyOnBeforeInput}) => { + expect(spyOnBeforeInput.mock.calls.length).toBe(1); + expect(beforeInputEvent.type).toBe('keypress'); + expect(beforeInputEvent.data).toBe(' '); + }, + }, + { + run: ({beforeInputEvent, spyOnBeforeInput}) => { + expect(spyOnBeforeInput.mock.calls.length).toBe(0); + expect(beforeInputEvent).toBeNull(); + }, + }, + { + run: ({beforeInputEvent, spyOnBeforeInput}) => { + expect(spyOnBeforeInput.mock.calls.length).toBe(0); + expect(beforeInputEvent).toBeNull(); + }, + }, + { + run: ({beforeInputEvent, spyOnBeforeInput}) => { + expect(spyOnBeforeInput.mock.calls.length).toBe(0); + expect(beforeInputEvent).toBeNull(); + }, + }, + { + run: ({beforeInputEvent, spyOnBeforeInput}) => { + expect(spyOnBeforeInput.mock.calls.length).toBe(1); + expect(beforeInputEvent.type).toBe('keypress'); + expect(beforeInputEvent.data).toBe('c'); + }, + }, + { + run: ({beforeInputEvent, spyOnBeforeInput}) => { + expect(spyOnBeforeInput.mock.calls.length).toBe(1); + expect(beforeInputEvent.type).toBe('keypress'); + expect(beforeInputEvent.data).toBe('\uD83D\uDE0A'); + }, + }, + { + run: ({beforeInputEvent, spyOnBeforeInput}) => { + expect(spyOnBeforeInput.mock.calls.length).toBe(0); + expect(beforeInputEvent).toBeNull(); + }, + }, + { + run: ({beforeInputEvent, spyOnBeforeInput}) => { + expect(spyOnBeforeInput.mock.calls.length).toBe(0); + expect(beforeInputEvent).toBeNull(); + }, + }, + { + run: ({beforeInputEvent, spyOnBeforeInput}) => { + expect(spyOnBeforeInput.mock.calls.length).toBe(1); + expect(beforeInputEvent.type).toBe('keydown'); + expect(beforeInputEvent.data).toBe('bar'); + }, + }, + { + run: ({beforeInputEvent, spyOnBeforeInput}) => { + expect(spyOnBeforeInput.mock.calls.length).toBe(0); + expect(beforeInputEvent).toBeNull(); + }, + }, + { + run: ({beforeInputEvent, spyOnBeforeInput}) => { + expect(spyOnBeforeInput.mock.calls.length).toBe(1); + expect(beforeInputEvent.type).toBe('keyup'); + expect(beforeInputEvent.data).toBe('BAR'); + }, + }, + { + run: ({beforeInputEvent, spyOnBeforeInput}) => { + expect(spyOnBeforeInput.mock.calls.length).toBe(0); + expect(beforeInputEvent).toBeNull(); + }, + }, + { + run: ({beforeInputEvent, spyOnBeforeInput}) => { + expect(spyOnBeforeInput.mock.calls.length).toBe(1); + expect(beforeInputEvent.type).toBe('keypress'); + expect(beforeInputEvent.data).toBe('Bar'); + }, + }, + { + run: ({beforeInputEvent, spyOnBeforeInput}) => { + expect(spyOnBeforeInput.mock.calls.length).toBe(0); + expect(beforeInputEvent).toBeNull(); + }, + }, + ], + }, + { + emulator: simulateComposition, + assertions: [ + { + run: ({beforeInputEvent, spyOnBeforeInput}) => { + expect(spyOnBeforeInput.mock.calls.length).toBe(0); + expect(beforeInputEvent).toBeNull(); + }, + }, + { + run: ({beforeInputEvent, spyOnBeforeInput}) => { + expect(spyOnBeforeInput.mock.calls.length).toBe(0); + expect(beforeInputEvent).toBeNull(); + }, + }, + { + run: ({beforeInputEvent, spyOnBeforeInput}) => { + expect(spyOnBeforeInput.mock.calls.length).toBe(1); + expect(beforeInputEvent.type).toBe('compositionend'); + expect(beforeInputEvent.data).toBe('test string 3'); + }, + }, + { + run: ({beforeInputEvent, spyOnBeforeInput}) => { + expect(spyOnBeforeInput.mock.calls.length).toBe(0); + expect(beforeInputEvent).toBeNull(); + }, + }, + { + run: ({beforeInputEvent, spyOnBeforeInput}) => { + expect(spyOnBeforeInput.mock.calls.length).toBe(1); + expect(beforeInputEvent.type).toBe('keypress'); + expect(beforeInputEvent.data).toBe('a'); + }, + }, + { + run: ({beforeInputEvent, spyOnBeforeInput}) => { + expect(spyOnBeforeInput.mock.calls.length).toBe(1); + expect(beforeInputEvent.type).toBe('keypress'); + expect(beforeInputEvent.data).toBe(' '); + }, + }, + { + run: ({beforeInputEvent, spyOnBeforeInput}) => { + expect(spyOnBeforeInput.mock.calls.length).toBe(0); + expect(beforeInputEvent).toBeNull(); + }, + }, + { + run: ({beforeInputEvent, spyOnBeforeInput}) => { + expect(spyOnBeforeInput.mock.calls.length).toBe(0); + expect(beforeInputEvent).toBeNull(); + }, + }, + { + run: ({beforeInputEvent, spyOnBeforeInput}) => { + expect(spyOnBeforeInput.mock.calls.length).toBe(0); + expect(beforeInputEvent).toBeNull(); + }, + }, + { + run: ({beforeInputEvent, spyOnBeforeInput}) => { + expect(spyOnBeforeInput.mock.calls.length).toBe(1); + expect(beforeInputEvent.type).toBe('keypress'); + expect(beforeInputEvent.data).toBe('c'); + }, + }, + { + run: ({beforeInputEvent, spyOnBeforeInput}) => { + expect(spyOnBeforeInput.mock.calls.length).toBe(1); + expect(beforeInputEvent.type).toBe('keypress'); + expect(beforeInputEvent.data).toBe('\uD83D\uDE0A'); + }, + }, + { + run: ({beforeInputEvent, spyOnBeforeInput}) => { + expect(spyOnBeforeInput.mock.calls.length).toBe(0); + expect(beforeInputEvent).toBeNull(); + }, + }, + { + run: ({beforeInputEvent, spyOnBeforeInput}) => { + expect(spyOnBeforeInput.mock.calls.length).toBe(0); + expect(beforeInputEvent).toBeNull(); + }, + }, + { + run: ({beforeInputEvent, spyOnBeforeInput}) => { + expect(spyOnBeforeInput.mock.calls.length).toBe(0); + expect(beforeInputEvent).toBeNull(); + }, + }, + { + run: ({beforeInputEvent, spyOnBeforeInput}) => { + expect(spyOnBeforeInput.mock.calls.length).toBe(0); + expect(beforeInputEvent).toBeNull(); + }, + }, + { + run: ({beforeInputEvent, spyOnBeforeInput}) => { + expect(spyOnBeforeInput.mock.calls.length).toBe(0); + expect(beforeInputEvent).toBeNull(); + }, + }, + { + run: ({beforeInputEvent, spyOnBeforeInput}) => { + expect(spyOnBeforeInput.mock.calls.length).toBe(0); + expect(beforeInputEvent).toBeNull(); + }, + }, + { + run: ({beforeInputEvent, spyOnBeforeInput}) => { + expect(spyOnBeforeInput.mock.calls.length).toBe(0); + expect(beforeInputEvent).toBeNull(); + }, + }, + { + run: ({beforeInputEvent, spyOnBeforeInput}) => { + expect(spyOnBeforeInput.mock.calls.length).toBe(0); + expect(beforeInputEvent).toBeNull(); + }, + }, + ], + }, + ]; + + const testInputComponent = (env, scenes) => { + let beforeInputEvent; + let compositionStartEvent; + let compositionUpdateEvent; + let spyOnBeforeInput; + let spyOnCompositionStart; + let spyOnCompositionUpdate; + ReactDOM = loadReactDOM(env.emulator); + const node = ReactDOM.render( + { + spyOnBeforeInput(); + beforeInputEvent = {type, data}; + }} + onCompositionStart={({type, data}) => { + spyOnCompositionStart(); + compositionStartEvent = {type, data}; + }} + onCompositionUpdate={({type, data}) => { + spyOnCompositionUpdate(); + compositionUpdateEvent = {type, data}; + }} + />, + container, + ); + + scenes.forEach((s, id) => { + beforeInputEvent = null; + compositionStartEvent = null; + compositionUpdateEvent = null; + spyOnBeforeInput = jest.fn(); + spyOnCompositionStart = jest.fn(); + spyOnCompositionUpdate = jest.fn(); + s.eventSimulator.apply(null, [node, ...s.eventSimulatorArgs]); + env.assertions[id].run({ + beforeInputEvent, + compositionStartEvent, + compositionUpdateEvent, + spyOnBeforeInput, + spyOnCompositionStart, + spyOnCompositionUpdate, + }); + }); + }; + + const testContentEditableComponent = (env, scenes) => { + let beforeInputEvent; + let compositionStartEvent; + let compositionUpdateEvent; + let spyOnBeforeInput; + let spyOnCompositionStart; + let spyOnCompositionUpdate; + ReactDOM = loadReactDOM(env.emulator); + const node = ReactDOM.render( +
{ + spyOnBeforeInput(); + beforeInputEvent = {type, data}; + }} + onCompositionStart={({type, data}) => { + spyOnCompositionStart(); + compositionStartEvent = {type, data}; + }} + onCompositionUpdate={({type, data}) => { + spyOnCompositionUpdate(); + compositionUpdateEvent = {type, data}; + }} + />, + container, + ); + + scenes.forEach((s, id) => { + beforeInputEvent = null; + compositionStartEvent = null; + compositionUpdateEvent = null; + spyOnBeforeInput = jest.fn(); + spyOnCompositionStart = jest.fn(); + spyOnCompositionUpdate = jest.fn(); + s.eventSimulator.apply(null, [node, ...s.eventSimulatorArgs]); + env.assertions[id].run({ + beforeInputEvent, + compositionStartEvent, + compositionUpdateEvent, + spyOnBeforeInput, + spyOnCompositionStart, + spyOnCompositionUpdate, + }); + }); + }; + + it('should extract onBeforeInput when simulating in Webkit on input[type=text]', () => { + testInputComponent(environments[0], scenarios); + }); + it('should extract onBeforeInput when simulating in Webkit on contenteditable', () => { + testContentEditableComponent(environments[0], scenarios); + }); + + it('should extract onBeforeInput when simulating in IE11 on input[type=text]', () => { + testInputComponent(environments[1], scenarios); + }); + it('should extract onBeforeInput when simulating in IE11 on contenteditable', () => { + testContentEditableComponent(environments[1], scenarios); + }); + + it('should extract onBeforeInput when simulating in env with no CompositionEvent on input[type=text]', () => { + testInputComponent(environments[2], scenarios); + }); + + // in an environment using composition fallback onBeforeInput will not work + // as expected on a contenteditable as keydown and keyup events are translated + // to keypress events + + it('should extract onBeforeInput when simulating in env with only CompositionEvent on input[type=text]', () => { + testInputComponent(environments[3], scenarios); + }); + + it('should extract onBeforeInput when simulating in env with only CompositionEvent on contenteditable', () => { + testContentEditableComponent(environments[3], scenarios); + }); +}); diff --git a/packages/react-dom/src/events/__tests__/FallbackCompositionState-test.js b/packages/react-dom/src/events/__tests__/FallbackCompositionState-test.js deleted file mode 100644 index 61f2d86607e11..0000000000000 --- a/packages/react-dom/src/events/__tests__/FallbackCompositionState-test.js +++ /dev/null @@ -1,91 +0,0 @@ -/** - * Copyright (c) 2013-present, Facebook, Inc. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @emails react-core - */ - -'use strict'; - -describe('FallbackCompositionState', () => { - let FallbackCompositionState; - - const TEXT = 'Hello world'; - - beforeEach(() => { - // TODO: can we express this test with only public API? - FallbackCompositionState = require('../FallbackCompositionState'); - }); - - function getInput() { - const input = document.createElement('input'); - input.value = TEXT; - return input; - } - - function getTextarea() { - const textarea = document.createElement('textarea'); - textarea.value = TEXT; - return textarea; - } - - function getContentEditable() { - const editable = document.createElement('div'); - const inner = document.createElement('span'); - inner.appendChild(document.createTextNode(TEXT)); - editable.appendChild(inner); - return editable; - } - - function assertExtractedData(modifiedValue, expectedData) { - const input = getInput(); - FallbackCompositionState.initialize(input); - input.value = modifiedValue; - expect(FallbackCompositionState.getData()).toBe(expectedData); - FallbackCompositionState.reset(); - } - - it('extracts value via `getText()`', () => { - FallbackCompositionState.initialize(getInput()); - expect(FallbackCompositionState.getText()).toBe(TEXT); - FallbackCompositionState.reset(); - - FallbackCompositionState.initialize(getTextarea()); - expect(FallbackCompositionState.getText()).toBe(TEXT); - FallbackCompositionState.reset(); - - FallbackCompositionState.initialize(getContentEditable()); - expect(FallbackCompositionState.getText()).toBe(TEXT); - FallbackCompositionState.reset(); - }); - - describe('Extract fallback data inserted at collapsed cursor', () => { - it('extracts when inserted at start of text', () => { - assertExtractedData('XXXHello world', 'XXX'); - }); - - it('extracts when inserted within text', () => { - assertExtractedData('Hello XXXworld', 'XXX'); - }); - - it('extracts when inserted at end of text', () => { - assertExtractedData('Hello worldXXX', 'XXX'); - }); - }); - - describe('Extract fallback data for non-collapsed range', () => { - it('extracts when inserted at start of text', () => { - assertExtractedData('XXX world', 'XXX'); - }); - - it('extracts when inserted within text', () => { - assertExtractedData('HelXXXrld', 'XXX'); - }); - - it('extracts when inserted at end of text', () => { - assertExtractedData('Hello XXX', 'XXX'); - }); - }); -});