Skip to content

Commit

Permalink
Add Modern Event System fork
Browse files Browse the repository at this point in the history
  • Loading branch information
trueadm committed Feb 19, 2020
1 parent b6c94d6 commit 1dcdf45
Show file tree
Hide file tree
Showing 13 changed files with 1,013 additions and 177 deletions.
2 changes: 1 addition & 1 deletion packages/legacy-events/ReactSyntheticEventType.js
Expand Up @@ -31,4 +31,4 @@ export type ReactSyntheticEvent = {|
nativeEventTarget: EventTarget,
) => ReactSyntheticEvent,
isPersistent: () => boolean,
|} & SyntheticEvent<>;
|};
Expand Up @@ -17,6 +17,7 @@ let ReactDOMComponentTree;
let listenToEvent;
let ReactDOMEventListener;
let ReactTestUtils;
let ReactFeatureFlags;

let idCallOrder;
const recordID = function(id) {
Expand Down Expand Up @@ -60,13 +61,20 @@ describe('ReactBrowserEventEmitter', () => {
jest.resetModules();
LISTENER.mockClear();

ReactFeatureFlags = require('shared/ReactFeatureFlags');
EventPluginGetListener = require('legacy-events/getListener').default;
EventPluginRegistry = require('legacy-events/EventPluginRegistry');
React = require('react');
ReactDOM = require('react-dom');
ReactDOMComponentTree = require('../client/ReactDOMComponentTree');
listenToEvent = require('../events/DOMLegacyEventPluginSystem')
.legacyListenToEvent;
if (ReactFeatureFlags.enableModernEventSystem) {
listenToEvent = require('../events/DOMModernPluginEventSystem')
.listenToEvent;
} else {
listenToEvent = require('../events/DOMLegacyEventPluginSystem')
.legacyListenToEvent;
}

ReactDOMEventListener = require('../events/ReactDOMEventListener');
ReactTestUtils = require('react-dom/test-utils');

Expand Down
91 changes: 60 additions & 31 deletions packages/react-dom/src/__tests__/ReactDOMEventListener-test.js
Expand Up @@ -12,36 +12,41 @@
describe('ReactDOMEventListener', () => {
let React;
let ReactDOM;
let ReactFeatureFlags = require('shared/ReactFeatureFlags');

beforeEach(() => {
jest.resetModules();
React = require('react');
ReactDOM = require('react-dom');
});

it('should dispatch events from outside React tree', () => {
const mock = jest.fn();
// We attached events to roots with the modern system,
// so this test is no longer valid.
if (!ReactFeatureFlags.enableModernEventSystem) {
it('should dispatch events from outside React tree', () => {
const mock = jest.fn();

const container = document.createElement('div');
const node = ReactDOM.render(<div onMouseEnter={mock} />, container);
const otherNode = document.createElement('h1');
document.body.appendChild(container);
document.body.appendChild(otherNode);
const container = document.createElement('div');
const node = ReactDOM.render(<div onMouseEnter={mock} />, container);
const otherNode = document.createElement('h1');
document.body.appendChild(container);
document.body.appendChild(otherNode);

try {
otherNode.dispatchEvent(
new MouseEvent('mouseout', {
bubbles: true,
cancelable: true,
relatedTarget: node,
}),
);
expect(mock).toBeCalled();
} finally {
document.body.removeChild(container);
document.body.removeChild(otherNode);
}
});
try {
otherNode.dispatchEvent(
new MouseEvent('mouseout', {
bubbles: true,
cancelable: true,
relatedTarget: node,
}),
);
expect(mock).toBeCalled();
} finally {
document.body.removeChild(container);
document.body.removeChild(otherNode);
}
});
}

describe('Propagation', () => {
it('should propagate events one level down', () => {
Expand Down Expand Up @@ -189,9 +194,21 @@ describe('ReactDOMEventListener', () => {
// The first call schedules a render of '1' into the 'Child'.
// However, we're batching so it isn't flushed yet.
expect(mock.mock.calls[0][0]).toBe('Child');
// The first call schedules a render of '2' into the 'Child'.
// We're still batching so it isn't flushed yet either.
expect(mock.mock.calls[1][0]).toBe('Child');
if (ReactFeatureFlags.enableModernEventSystem) {
// As we have two roots, it means we have two event listeners.
// This also means we enter the event batching phase twice,
// flushing the child to be 1.
// TODO: can we improve on this? microtasks seem to problematic
// (I tried that). We don't have any good way of knowing if
// another event will occur because another event handler
// might invoke stopPropagation() along the way. This means
// we over-flush.
expect(mock.mock.calls[1][0]).toBe('1');
} else {
// The first call schedules a render of '2' into the 'Child'.
// We're still batching so it isn't flushed yet either.
expect(mock.mock.calls[1][0]).toBe('Child');
}
// By the time we leave the handler, the second update is flushed.
expect(childNode.textContent).toBe('2');
} finally {
Expand Down Expand Up @@ -362,13 +379,25 @@ describe('ReactDOMEventListener', () => {
bubbles: false,
}),
);
// Historically, we happened to not support onLoadStart
// on <img>, and this test documents that lack of support.
// If we decide to support it in the future, we should change
// this line to expect 1 call. Note that fixing this would
// be simple but would require attaching a handler to each
// <img>. So far nobody asked us for it.
expect(handleImgLoadStart).toHaveBeenCalledTimes(0);
if (ReactFeatureFlags.enableModernEventSystem) {
// As of the modern event system refactor, we now support
// this on <img>. The reason for this, is because we now
// attach all media events to the "root" or "portal" in the
// capture phase, rather than the bubble phase. This allows
// us to assign less event listeners to individual elements,
// which also nicely allows us to support more without needing
// to add more individual code paths to support various
// events that do not bubble.
expect(handleImgLoadStart).toHaveBeenCalledTimes(1);
} else {
// Historically, we happened to not support onLoadStart
// on <img>, and this test documents that lack of support.
// If we decide to support it in the future, we should change
// this line to expect 1 call. Note that fixing this would
// be simple but would require attaching a handler to each
// <img>. So far nobody asked us for it.
expect(handleImgLoadStart).toHaveBeenCalledTimes(0);
}

videoRef.current.dispatchEvent(
new ProgressEvent('loadstart', {
Expand Down
119 changes: 84 additions & 35 deletions packages/react-dom/src/__tests__/ReactTreeTraversal-test.js
Expand Up @@ -11,6 +11,7 @@

let React;
let ReactDOM;
let ReactFeatureFlags = require('shared/ReactFeatureFlags');

const ChildComponent = ({id, eventHandler}) => (
<div
Expand Down Expand Up @@ -203,41 +204,89 @@ describe('ReactTreeTraversal', () => {
expect(mockFn.mock.calls).toEqual(expectedCalls);
});

it('should enter from the window', () => {
const enterNode = document.getElementById('P_P1_C1__DIV');

const expectedCalls = [
['P', 'mouseenter'],
['P_P1', 'mouseenter'],
['P_P1_C1__DIV', 'mouseenter'],
];

outerNode1.dispatchEvent(
new MouseEvent('mouseout', {
bubbles: true,
cancelable: true,
relatedTarget: enterNode,
}),
);

expect(mockFn.mock.calls).toEqual(expectedCalls);
});

it('should enter from the window to the shallowest', () => {
const enterNode = document.getElementById('P');

const expectedCalls = [['P', 'mouseenter']];

outerNode1.dispatchEvent(
new MouseEvent('mouseout', {
bubbles: true,
cancelable: true,
relatedTarget: enterNode,
}),
);

expect(mockFn.mock.calls).toEqual(expectedCalls);
});
// This will not work with the modern event system that
// attaches event listeners to roots as the event below
// is being triggered on a node that React does not listen
// to any more. Instead we should fire mouseover.
if (ReactFeatureFlags.enableModernEventSystem) {
it('should enter from the window', () => {
const enterNode = document.getElementById('P_P1_C1__DIV');

const expectedCalls = [
['P', 'mouseenter'],
['P_P1', 'mouseenter'],
['P_P1_C1__DIV', 'mouseenter'],
];

enterNode.dispatchEvent(
new MouseEvent('mouseover', {
bubbles: true,
cancelable: true,
relatedTarget: outerNode1,
}),
);

expect(mockFn.mock.calls).toEqual(expectedCalls);
});
} else {
it('should enter from the window', () => {
const enterNode = document.getElementById('P_P1_C1__DIV');

const expectedCalls = [
['P', 'mouseenter'],
['P_P1', 'mouseenter'],
['P_P1_C1__DIV', 'mouseenter'],
];

outerNode1.dispatchEvent(
new MouseEvent('mouseout', {
bubbles: true,
cancelable: true,
relatedTarget: enterNode,
}),
);

expect(mockFn.mock.calls).toEqual(expectedCalls);
});
}

// This will not work with the modern event system that
// attaches event listeners to roots as the event below
// is being triggered on a node that React does not listen
// to any more. Instead we should fire mouseover.
if (ReactFeatureFlags.enableModernEventSystem) {
it('should enter from the window to the shallowest', () => {
const enterNode = document.getElementById('P');

const expectedCalls = [['P', 'mouseenter']];

enterNode.dispatchEvent(
new MouseEvent('mouseover', {
bubbles: true,
cancelable: true,
relatedTarget: outerNode1,
}),
);

expect(mockFn.mock.calls).toEqual(expectedCalls);
});
} else {
it('should enter from the window to the shallowest', () => {
const enterNode = document.getElementById('P');

const expectedCalls = [['P', 'mouseenter']];

outerNode1.dispatchEvent(
new MouseEvent('mouseout', {
bubbles: true,
cancelable: true,
relatedTarget: enterNode,
}),
);

expect(mockFn.mock.calls).toEqual(expectedCalls);
});
}

it('should leave to the window', () => {
const leaveNode = document.getElementById('P_P1_C1__DIV');
Expand Down

0 comments on commit 1dcdf45

Please sign in to comment.