diff --git a/packages/react-dom/src/events/__tests__/SyntheticKeyboardEvent-test.internal.js b/packages/react-dom/src/events/__tests__/SyntheticKeyboardEvent-test.internal.js deleted file mode 100644 index aa8751c17ac5b..0000000000000 --- a/packages/react-dom/src/events/__tests__/SyntheticKeyboardEvent-test.internal.js +++ /dev/null @@ -1,122 +0,0 @@ -/** - * Copyright (c) 2016-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'; - -var SyntheticKeyboardEvent; -var getEventCharCode; - -describe('SyntheticKeyboardEvent', () => { - var createEvent; - - beforeEach(() => { - // Mock getEventCharCode for proper unit testing - jest.mock('../getEventCharCode'); - getEventCharCode = require('../getEventCharCode').default; - - // TODO: can we express this test with only public API? - SyntheticKeyboardEvent = require('../SyntheticKeyboardEvent').default; - createEvent = function(nativeEvent) { - var target = require('../getEventTarget').default(nativeEvent); - return SyntheticKeyboardEvent.getPooled({}, '', nativeEvent, target); - }; - }); - - describe('KeyboardEvent interface', () => { - describe('charCode', () => { - describe('when event is `keypress`', () => { - it('returns whatever getEventCharCode returns', () => { - getEventCharCode.mockReturnValue(100500); - var keyboardEvent = createEvent({type: 'keypress', charCode: 50}); - - expect(keyboardEvent.charCode).toBe(100500); - }); - }); - - describe('when event is not `keypress`', () => { - it('returns 0', () => { - var keyboardEvent = createEvent({type: 'keyup', charCode: 50}); - expect(keyboardEvent.charCode).toBe(0); - }); - }); - }); - - describe('keyCode', () => { - describe('when event is `keydown` or `keyup`', () => { - it('returns a passed keyCode', () => { - var keyboardEvent = createEvent({type: 'keyup', keyCode: 40}); - expect(keyboardEvent.keyCode).toBe(40); - }); - }); - - describe('when event is `keypress`', () => { - it('returns 0', () => { - var keyboardEvent = createEvent({type: 'keypress', charCode: 40}); - expect(keyboardEvent.keyCode).toBe(0); - }); - }); - }); - - describe('which', () => { - describe('when event is `keypress`', () => { - it('returns whatever getEventCharCode returns', () => { - getEventCharCode.mockReturnValue(9001); - var keyboardEvent = createEvent({type: 'keypress', charCode: 50}); - - expect(keyboardEvent.which).toBe(9001); - }); - }); - - describe('when event is `keydown` or `keyup`', () => { - it('returns a passed keyCode', () => { - var keyboardEvent = createEvent({type: 'keyup', keyCode: 40}); - expect(keyboardEvent.which).toBe(40); - }); - }); - - describe('when event type is unknown', () => { - it('returns 0', () => { - var keyboardEvent = createEvent({type: 'keysmack', keyCode: 40}); - expect(keyboardEvent.which).toBe(0); - }); - }); - }); - }); - - describe('EventInterface', () => { - it('normalizes properties from the Event interface', () => { - var target = document.createElement('div'); - var syntheticEvent = createEvent({srcElement: target}); - - expect(syntheticEvent.target).toBe(target); - expect(syntheticEvent.type).toBe(undefined); - }); - - it('is able to `preventDefault` and `stopPropagation`', () => { - var nativeEvent = {}; - var syntheticEvent = createEvent(nativeEvent); - - expect(syntheticEvent.isDefaultPrevented()).toBe(false); - syntheticEvent.preventDefault(); - expect(syntheticEvent.isDefaultPrevented()).toBe(true); - - expect(syntheticEvent.isPropagationStopped()).toBe(false); - syntheticEvent.stopPropagation(); - expect(syntheticEvent.isPropagationStopped()).toBe(true); - }); - - it('is able to `persist`', () => { - var syntheticEvent = createEvent({}); - - expect(syntheticEvent.isPersistent()).toBe(false); - syntheticEvent.persist(); - expect(syntheticEvent.isPersistent()).toBe(true); - }); - }); -}); diff --git a/packages/react-dom/src/events/__tests__/SyntheticKeyboardEvent-test.js b/packages/react-dom/src/events/__tests__/SyntheticKeyboardEvent-test.js new file mode 100644 index 0000000000000..c31912c3993c0 --- /dev/null +++ b/packages/react-dom/src/events/__tests__/SyntheticKeyboardEvent-test.js @@ -0,0 +1,577 @@ +/** + * Copyright (c) 2016-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('SyntheticKeyboardEvent', () => { + let container; + + beforeEach(() => { + React = require('react'); + ReactDOM = require('react-dom'); + // The container has to be attached for events to fire. + container = document.createElement('div'); + document.body.appendChild(container); + }); + + afterEach(() => { + document.body.removeChild(container); + container = null; + }); + + describe('KeyboardEvent interface', () => { + describe('charCode', () => { + describe('when event is `keypress`', () => { + describe('when charCode is present in nativeEvent', () => { + it('when charCode is 0 and keyCode is 13, returns 13', () => { + let charCode = null; + const node = ReactDOM.render( + { + charCode = e.charCode; + }} + />, + container, + ); + node.dispatchEvent( + new KeyboardEvent('keypress', { + charCode: 0, + keyCode: 13, + bubbles: true, + cancelable: true, + }), + ); + expect(charCode).toBe(13); + }); + + it('when charCode is 32 or bigger and keyCode is missing, returns charCode', () => { + let charCode = null; + const node = ReactDOM.render( + { + charCode = e.charCode; + }} + />, + container, + ); + node.dispatchEvent( + new KeyboardEvent('keypress', { + charCode: 32, + bubbles: true, + cancelable: true, + }), + ); + expect(charCode).toBe(32); + }); + + it('when charCode is 13 and keyCode is missing, returns charCode', () => { + let charCode = null; + const node = ReactDOM.render( + { + charCode = e.charCode; + }} + />, + container, + ); + node.dispatchEvent( + new KeyboardEvent('keypress', { + charCode: 13, + bubbles: true, + cancelable: true, + }), + ); + expect(charCode).toBe(13); + }); + + // Firefox creates a keypress event for function keys too. This removes + // the unwanted keypress events. Enter is however both printable and + // non-printable. One would expect Tab to be as well (but it isn't). + it('when charCode is smaller than 32 but is not 13, and keyCode is missing, ignores keypress', () => { + let called = false; + const node = ReactDOM.render( + { + called = true; + }} + />, + container, + ); + node.dispatchEvent( + new KeyboardEvent('keypress', { + charCode: 31, + bubbles: true, + cancelable: true, + }), + ); + expect(called).toBe(false); + }); + }); + + // TODO: this seems IE8 specific. + // We can probably remove this normalization. + describe('when charCode is not present in nativeEvent', () => { + let charCodeDescriptor; + + beforeEach(() => { + charCodeDescriptor = Object.getOwnPropertyDescriptor( + KeyboardEvent.prototype, + 'charCode', + ); + delete KeyboardEvent.prototype.charCode; + }); + + afterEach(() => { + // Don't forget to restore for other tests. + Object.defineProperty( + KeyboardEvent.prototype, + 'charCode', + charCodeDescriptor, + ); + charCodeDescriptor = null; + }); + + it('when keyCode is 32 or bigger, returns keyCode', () => { + let charCode = null; + const node = ReactDOM.render( + { + charCode = e.charCode; + }} + />, + container, + ); + node.dispatchEvent( + new KeyboardEvent('keypress', { + keyCode: 32, + bubbles: true, + cancelable: true, + }), + ); + expect(charCode).toBe(32); + }); + + it('when keyCode is 13, returns 13', () => { + let charCode = null; + const node = ReactDOM.render( + { + charCode = e.charCode; + }} + />, + container, + ); + node.dispatchEvent( + new KeyboardEvent('keypress', { + keyCode: 13, + bubbles: true, + cancelable: true, + }), + ); + expect(charCode).toBe(13); + }); + + it('when keyCode is smaller than 32 and is not 13, ignores keypress', () => { + let called = false; + const node = ReactDOM.render( + { + called = true; + }} + />, + container, + ); + node.dispatchEvent( + new KeyboardEvent('keypress', { + keyCode: 31, + bubbles: true, + cancelable: true, + }), + ); + expect(called).toBe(false); + }); + }); + }); + + describe('when event is not `keypress`', () => { + it('returns 0', () => { + let charCodeDown = null; + let charCodeUp = null; + const node = ReactDOM.render( + { + charCodeDown = e.charCode; + }} + onKeyUp={e => { + charCodeUp = e.charCode; + }} + />, + container, + ); + node.dispatchEvent( + new KeyboardEvent('keydown', { + key: 'Del', + bubbles: true, + cancelable: true, + }), + ); + node.dispatchEvent( + new KeyboardEvent('keyup', { + key: 'Del', + bubbles: true, + cancelable: true, + }), + ); + expect(charCodeDown).toBe(0); + expect(charCodeUp).toBe(0); + }); + }); + + it('when charCode is smaller than 32 but is not 13, and keyCode is missing, charCode is 0', () => { + let charCode = null; + const node = ReactDOM.render( + { + charCode = e.charCode; + }} + />, + container, + ); + node.dispatchEvent( + new KeyboardEvent('keydown', { + charCode: 31, + bubbles: true, + cancelable: true, + }), + ); + expect(charCode).toBe(0); + }); + }); + + describe('keyCode', () => { + describe('when event is `keydown` or `keyup`', () => { + it('returns a passed keyCode', () => { + let keyCodeDown = null; + let keyCodeUp = null; + const node = ReactDOM.render( + { + keyCodeDown = e.keyCode; + }} + onKeyUp={e => { + keyCodeUp = e.keyCode; + }} + />, + container, + ); + node.dispatchEvent( + new KeyboardEvent('keydown', { + keyCode: 40, + bubbles: true, + cancelable: true, + }), + ); + node.dispatchEvent( + new KeyboardEvent('keyup', { + keyCode: 40, + bubbles: true, + cancelable: true, + }), + ); + expect(keyCodeDown).toBe(40); + expect(keyCodeUp).toBe(40); + }); + }); + + describe('when event is `keypress`', () => { + it('returns 0', () => { + let keyCode = null; + const node = ReactDOM.render( + { + keyCode = e.keyCode; + }} + />, + container, + ); + node.dispatchEvent( + new KeyboardEvent('keypress', { + charCode: 65, + bubbles: true, + cancelable: true, + }), + ); + expect(keyCode).toBe(0); + }); + }); + }); + + describe('which', () => { + describe('when event is `keypress`', () => { + it('is consistent with `charCode`', () => { + let calls = 0; + const node = ReactDOM.render( + { + expect(e.which).toBe(e.charCode); + calls++; + }} + />, + container, + ); + // Try different combinations from other tests. + node.dispatchEvent( + new KeyboardEvent('keypress', { + charCode: 0, + keyCode: 13, + bubbles: true, + cancelable: true, + }), + ); + node.dispatchEvent( + new KeyboardEvent('keypress', { + charCode: 32, + bubbles: true, + cancelable: true, + }), + ); + node.dispatchEvent( + new KeyboardEvent('keypress', { + charCode: 13, + bubbles: true, + cancelable: true, + }), + ); + expect(calls).toBe(3); + }); + }); + + describe('when event is `keydown` or `keyup`', () => { + it('is consistent with `keyCode`', () => { + let calls = 0; + const node = ReactDOM.render( + { + expect(e.which).toBe(e.keyCode); + calls++; + }} + onKeyUp={e => { + expect(e.which).toBe(e.keyCode); + calls++; + }} + />, + container, + ); + node.dispatchEvent( + new KeyboardEvent('keydown', { + key: 'Del', + bubbles: true, + cancelable: true, + }), + ); + node.dispatchEvent( + new KeyboardEvent('keydown', { + charCode: 31, + bubbles: true, + cancelable: true, + }), + ); + node.dispatchEvent( + new KeyboardEvent('keydown', { + keyCode: 40, + bubbles: true, + cancelable: true, + }), + ); + node.dispatchEvent( + new KeyboardEvent('keyup', { + key: 'Del', + bubbles: true, + cancelable: true, + }), + ); + node.dispatchEvent( + new KeyboardEvent('keyup', { + keyCode: 40, + bubbles: true, + cancelable: true, + }), + ); + expect(calls).toBe(5); + }); + }); + }); + }); + + describe('EventInterface', () => { + it('normalizes properties from the Event interface', () => { + let expectedCount = 0; + let div; + const eventHandler = type => event => { + expect(event.target).toBe(div); + expect(event.type).toBe(type); + expectedCount++; + }; + div = ReactDOM.render( +
, + container, + ); + + let event; + event = new KeyboardEvent('keydown', { + keyCode: 40, + bubbles: true, + cancelable: true, + }); + // Emulate IE8 + Object.defineProperty(event, 'target', { + get() {}, + }); + Object.defineProperty(event, 'srcElement', { + get() { + return div; + }, + }); + div.dispatchEvent(event); + + event = new KeyboardEvent('keyup', { + keyCode: 40, + bubbles: true, + cancelable: true, + }); + // Emulate IE8 + Object.defineProperty(event, 'target', { + get() {}, + }); + Object.defineProperty(event, 'srcElement', { + get() { + return div; + }, + }); + div.dispatchEvent(event); + + event = new KeyboardEvent('keypress', { + charCode: 40, + keyCode: 40, + bubbles: true, + cancelable: true, + }); + // Emulate IE8 + Object.defineProperty(event, 'target', { + get() {}, + }); + Object.defineProperty(event, 'srcElement', { + get() { + return div; + }, + }); + div.dispatchEvent(event); + + expect(expectedCount).toBe(3); + }); + + it('is able to `preventDefault` and `stopPropagation`', () => { + let expectedCount = 0; + const eventHandler = event => { + expect(event.isDefaultPrevented()).toBe(false); + event.preventDefault(); + expect(event.isDefaultPrevented()).toBe(true); + + expect(event.isPropagationStopped()).toBe(false); + event.stopPropagation(); + expect(event.isPropagationStopped()).toBe(true); + expectedCount++; + }; + let div = ReactDOM.render( +
, + container, + ); + + div.dispatchEvent( + new KeyboardEvent('keydown', { + keyCode: 40, + bubbles: true, + cancelable: true, + }), + ); + div.dispatchEvent( + new KeyboardEvent('keyup', { + keyCode: 40, + bubbles: true, + cancelable: true, + }), + ); + div.dispatchEvent( + new KeyboardEvent('keypress', { + charCode: 40, + keyCode: 40, + bubbles: true, + cancelable: true, + }), + ); + expect(expectedCount).toBe(3); + }); + + it('is able to `persist`', () => { + const persistentEvents = []; + const eventHandler = event => { + expect(event.isPersistent()).toBe(false); + event.persist(); + expect(event.isPersistent()).toBe(true); + persistentEvents.push(event); + }; + let div = ReactDOM.render( +
, + container, + ); + + div.dispatchEvent( + new KeyboardEvent('keydown', { + keyCode: 40, + bubbles: true, + cancelable: true, + }), + ); + div.dispatchEvent( + new KeyboardEvent('keyup', { + keyCode: 40, + bubbles: true, + cancelable: true, + }), + ); + div.dispatchEvent( + new KeyboardEvent('keypress', { + charCode: 40, + keyCode: 40, + bubbles: true, + cancelable: true, + }), + ); + expect(persistentEvents.length).toBe(3); + expect(persistentEvents[0].type).toBe('keydown'); + expect(persistentEvents[1].type).toBe('keyup'); + expect(persistentEvents[2].type).toBe('keypress'); + }); + }); +}); diff --git a/packages/react-dom/src/events/__tests__/getEventCharCode-test.js b/packages/react-dom/src/events/__tests__/getEventCharCode-test.js deleted file mode 100644 index 552286b8d0a17..0000000000000 --- a/packages/react-dom/src/events/__tests__/getEventCharCode-test.js +++ /dev/null @@ -1,89 +0,0 @@ -/** - * Copyright (c) 2016-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'; - -// TODO: can we express this test with only public API? -var getEventCharCode = require('../getEventCharCode').default; - -describe('getEventCharCode', () => { - describe('when charCode is present in nativeEvent', () => { - describe('when charCode is 0 and keyCode is 13', () => { - it('returns 13', () => { - var nativeEvent = new KeyboardEvent('keypress', { - charCode: 0, - keyCode: 13, - }); - - expect(getEventCharCode(nativeEvent)).toBe(13); - }); - }); - - describe('when charCode is not 0 and/or keyCode is not 13', () => { - describe('when charCode is 32 or bigger', () => { - it('returns charCode', () => { - var nativeEvent = new KeyboardEvent('keypress', {charCode: 32}); - - expect(getEventCharCode(nativeEvent)).toBe(32); - }); - }); - - describe('when charCode is smaller than 32', () => { - describe('when charCode is 13', () => { - it('returns 13', () => { - var nativeEvent = new KeyboardEvent('keypress', {charCode: 13}); - - expect(getEventCharCode(nativeEvent)).toBe(13); - }); - }); - - describe('when charCode is not 13', () => { - it('returns 0', () => { - var nativeEvent = new KeyboardEvent('keypress', {charCode: 31}); - - expect(getEventCharCode(nativeEvent)).toBe(0); - }); - }); - }); - }); - }); - - /** - nativeEvent is represented as a plain object here to ease testing, because - KeyboardEvent's 'charCode' event key cannot be deleted to simulate a missing - charCode key. - */ - describe('when charCode is not present in nativeEvent', () => { - describe('when keyCode is 32 or bigger', () => { - it('returns keyCode', () => { - var nativeEvent = {keyCode: 32}; - - expect(getEventCharCode(nativeEvent)).toBe(32); - }); - }); - - describe('when keyCode is smaller than 32', () => { - describe('when keyCode is 13', () => { - it('returns 13', () => { - var nativeEvent = {keyCode: 13}; - - expect(getEventCharCode(nativeEvent)).toBe(13); - }); - }); - - describe('when keyCode is not 13', () => { - it('returns 0', () => { - var nativeEvent = {keyCode: 31}; - - expect(getEventCharCode(nativeEvent)).toBe(0); - }); - }); - }); - }); -});