diff --git a/scripts/fiber/tests-passing.txt b/scripts/fiber/tests-passing.txt index a8e34c1158f..2d514484d82 100644 --- a/scripts/fiber/tests-passing.txt +++ b/scripts/fiber/tests-passing.txt @@ -1366,6 +1366,13 @@ src/renderers/dom/shared/eventPlugins/__tests__/ChangeEventPlugin-test.js * should deduplicate input value change events * should listen for both change and input events when supported * should only fire events when the value changes for range inputs +* should only fire change once on Chrome +* should only fire change once on Chrome under 53 +* should only fire change once on Firefox +* should only fire change once on IE9 +* should only fire change once on IE10 +* should only fire change once on IE11 +* should only fire change once on Edge src/renderers/dom/shared/eventPlugins/__tests__/EnterLeaveEventPlugin-test.js * should set relatedTarget properly in iframe diff --git a/src/renderers/dom/shared/eventPlugins/ChangeEventPlugin.js b/src/renderers/dom/shared/eventPlugins/ChangeEventPlugin.js index fb56d1408f5..7a9682aadc4 100644 --- a/src/renderers/dom/shared/eventPlugins/ChangeEventPlugin.js +++ b/src/renderers/dom/shared/eventPlugins/ChangeEventPlugin.js @@ -39,6 +39,9 @@ var eventTypes = { 'topKeyDown', 'topKeyUp', 'topSelectionChange', + 'topCompositionStart', + 'topCompositionUpdate', + 'topCompositionEnd', ], }, }; @@ -62,6 +65,11 @@ function createAndAccumulateChangeEvent(inst, nativeEvent, target) { var activeElement = null; var activeElementInst = null; +/** + * For composition events + */ +var lastTopLevelType = null; + /** * SECTION: handle `change` event */ @@ -217,7 +225,18 @@ function getTargetInstForClickEvent(topLevelType, targetInst) { } function getTargetInstForInputOrChangeEvent(topLevelType, targetInst) { - if (topLevelType === 'topInput' || topLevelType === 'topChange') { + if (inComposition(topLevelType)) { + return; + } else if ( + topLevelType === 'topInput' && lastTopLevelType === 'topCompositionEnd' + ) { + return getInstIfValueChanged(targetInst); + } else if ( + topLevelType === 'topKeyUp' && lastTopLevelType === 'topCompositionEnd' + ) { + // Chrome fires 'compositionEnd' event after 'input' event. + return getInstIfValueChanged(targetInst); + } else if (topLevelType === 'topInput' || topLevelType === 'topChange') { return getInstIfValueChanged(targetInst); } } @@ -242,6 +261,16 @@ function handleControlledInputBlur(inst, node) { } } +var isComposing = false; +function inComposition(topLevelType) { + if (topLevelType === 'topCompositionStart') { + isComposing = true; + } else if (topLevelType === 'topCompositionEnd') { + isComposing = false; + } + return isComposing; +} + /** * This plugin creates an `onChange` event that normalizes change events * across form elements. This event fires at a time when it's possible to @@ -283,6 +312,7 @@ var ChangeEventPlugin = { if (getTargetInstFunc) { var inst = getTargetInstFunc(topLevelType, targetInst); + lastTopLevelType = topLevelType; if (inst) { var event = createAndAccumulateChangeEvent( inst, diff --git a/src/renderers/dom/shared/eventPlugins/__tests__/ChangeEventPlugin-test.js b/src/renderers/dom/shared/eventPlugins/__tests__/ChangeEventPlugin-test.js index f1978fad3aa..1c046244ae4 100644 --- a/src/renderers/dom/shared/eventPlugins/__tests__/ChangeEventPlugin-test.js +++ b/src/renderers/dom/shared/eventPlugins/__tests__/ChangeEventPlugin-test.js @@ -220,4 +220,179 @@ describe('ChangeEventPlugin', () => { ReactTestUtils.SimulateNative.change(input); expect(called).toBe(2); }); + + describe('composition events', () => { + function simulateEvent(inst, event) { + ReactTestUtils.SimulateNative[event](inst); + } + + function TestCompositionEvent(Scenario) { + var called = 0; + var value = null; + + function cb(e) { + called += 1; + value = e.target.value; + } + + var input = ReactTestUtils.renderIntoDocument( + , + ); + + Scenario.forEach(el => { + el.run.apply(null, [input].concat(el.args)); + }); + + expect(called).toBe(1); + expect(value).toBe('你'); + } + + var Scenario = { + ChromeUnder53: [ + {run: setUntrackedValue, args: ['n']}, + {run: simulateEvent, args: ['keyDown']}, + {run: simulateEvent, args: ['compositionStart']}, + {run: simulateEvent, args: ['compositionUpdate']}, + {run: simulateEvent, args: ['input']}, + {run: simulateEvent, args: ['keyUp']}, + {run: setUntrackedValue, args: ['ni']}, + {run: simulateEvent, args: ['keyDown']}, + {run: simulateEvent, args: ['compositionUpdate']}, + {run: simulateEvent, args: ['input']}, + {run: simulateEvent, args: ['keyUp']}, + {run: setUntrackedValue, args: ['你']}, + {run: simulateEvent, args: ['keyDown']}, + {run: simulateEvent, args: ['compositionEnd']}, + {run: simulateEvent, args: ['textInput']}, + {run: simulateEvent, args: ['input']}, + {run: simulateEvent, args: ['keyUp']}, + ], + Chrome: [ + {run: setUntrackedValue, args: ['n']}, + {run: simulateEvent, args: ['keyDown']}, + {run: simulateEvent, args: ['compositionStart']}, + {run: simulateEvent, args: ['compositionUpdate']}, + {run: simulateEvent, args: ['input']}, + {run: simulateEvent, args: ['keyUp']}, + {run: setUntrackedValue, args: ['ni']}, + {run: simulateEvent, args: ['keyDown']}, + {run: simulateEvent, args: ['compositionUpdate']}, + {run: simulateEvent, args: ['input']}, + {run: simulateEvent, args: ['keyUp']}, + {run: setUntrackedValue, args: ['你']}, + {run: simulateEvent, args: ['keyDown']}, + {run: simulateEvent, args: ['compositionUpdate']}, + {run: simulateEvent, args: ['textInput']}, + {run: simulateEvent, args: ['input']}, + {run: simulateEvent, args: ['compositionEnd']}, + {run: simulateEvent, args: ['keyUp']}, + ], + Firefox: [ + {run: setUntrackedValue, args: ['n']}, + {run: simulateEvent, args: ['keyDown']}, + {run: simulateEvent, args: ['compositionStart']}, + {run: simulateEvent, args: ['compositionUpdate']}, + {run: simulateEvent, args: ['input']}, + {run: setUntrackedValue, args: ['ni']}, + {run: simulateEvent, args: ['compositionUpdate']}, + {run: simulateEvent, args: ['input']}, + {run: setUntrackedValue, args: ['你']}, + {run: simulateEvent, args: ['compositionUpdate']}, + {run: simulateEvent, args: ['compositionEnd']}, + {run: simulateEvent, args: ['input']}, + {run: simulateEvent, args: ['keyUp']}, + ], + IE9: [ + {run: setUntrackedValue, args: ['n']}, + {run: simulateEvent, args: ['keyDown']}, + {run: simulateEvent, args: ['compositionStart']}, + {run: simulateEvent, args: ['compositionUpdate']}, + {run: simulateEvent, args: ['keyUp']}, + {run: setUntrackedValue, args: ['ni']}, + {run: simulateEvent, args: ['keyDown']}, + {run: simulateEvent, args: ['compositionUpdate']}, + {run: simulateEvent, args: ['keyUp']}, + {run: setUntrackedValue, args: ['你']}, + {run: simulateEvent, args: ['keyDown']}, + {run: simulateEvent, args: ['input']}, + {run: simulateEvent, args: ['compositionUpdate']}, + {run: simulateEvent, args: ['compositionEnd']}, + {run: simulateEvent, args: ['keyUp']}, + ], + IE10: [ + {run: setUntrackedValue, args: ['n']}, + {run: simulateEvent, args: ['keyDown']}, + {run: simulateEvent, args: ['compositionStart']}, + {run: simulateEvent, args: ['keyUp']}, + {run: setUntrackedValue, args: ['ni']}, + {run: simulateEvent, args: ['keyDown']}, + {run: simulateEvent, args: ['keyUp']}, + {run: setUntrackedValue, args: ['你']}, + {run: simulateEvent, args: ['keyDown']}, + {run: simulateEvent, args: ['keyUp']}, + {run: simulateEvent, args: ['keyDown']}, + {run: simulateEvent, args: ['compositionEnd']}, + {run: simulateEvent, args: ['input']}, + {run: simulateEvent, args: ['keyUp']}, + ], + IE11: [ + {run: setUntrackedValue, args: ['n']}, + {run: simulateEvent, args: ['keyDown']}, + {run: simulateEvent, args: ['compositionStart']}, + {run: simulateEvent, args: ['compositionUpdate']}, + {run: simulateEvent, args: ['input']}, + {run: simulateEvent, args: ['keyUp']}, + {run: setUntrackedValue, args: ['ni']}, + {run: simulateEvent, args: ['keyDown']}, + {run: simulateEvent, args: ['input']}, + {run: simulateEvent, args: ['keyUp']}, + {run: setUntrackedValue, args: ['你']}, + {run: simulateEvent, args: ['keyDown']}, + {run: simulateEvent, args: ['compositionEnd']}, + {run: simulateEvent, args: ['input']}, + {run: simulateEvent, args: ['keyUp']}, + ], + Edge: [ + {run: setUntrackedValue, args: ['n']}, + {run: simulateEvent, args: ['keyDown']}, + {run: simulateEvent, args: ['compositionStart']}, + {run: simulateEvent, args: ['keyUp']}, + {run: setUntrackedValue, args: ['ni']}, + {run: simulateEvent, args: ['keyDown']}, + {run: simulateEvent, args: ['keyUp']}, + {run: setUntrackedValue, args: ['你']}, + {run: simulateEvent, args: ['keyDown']}, + {run: simulateEvent, args: ['compositionEnd']}, + {run: simulateEvent, args: ['input']}, + ], + }; + + it('should only fire change once on Chrome', () => { + TestCompositionEvent(Scenario.Chrome); + }); + + it('should only fire change once on Chrome under 53', () => { + TestCompositionEvent(Scenario.ChromeUnder53); + }); + + it('should only fire change once on Firefox', () => { + TestCompositionEvent(Scenario.Firefox); + }); + + it('should only fire change once on IE9', () => { + TestCompositionEvent(Scenario.IE9); + }); + + it('should only fire change once on IE10', () => { + TestCompositionEvent(Scenario.IE10); + }); + + it('should only fire change once on IE11', () => { + TestCompositionEvent(Scenario.IE11); + }); + + it('should only fire change once on Edge', () => { + TestCompositionEvent(Scenario.Edge); + }); + }); });