Skip to content

Commit

Permalink
Rewrite SyntheticEvent tests using public APIs only (#11525)
Browse files Browse the repository at this point in the history
* generate synthetics events using public API

* rewritten createEvent to use public APIs
* removed all references SyntheticEvent.release

In order to test under realistic circumstances I had to move
the expectations into a callback in mosts tests to overcome
the effects of event pooling.

* run prettier

* remove empty line

* don't use ReactTestUtils

* run prettier and fix linter issues

* remove duplicate test

* remove invalid calls to expect

The removed `expect` calls verified the correct behaviour based on
missing `preventDefault` and `stopPropagation` methods.
The was correct as we used plain objects to simulate events.
Since we switched to the public API we're using native events which
do have these methods.

* set event.defaultPrevented to undefined

This was missed when the test was first migrated.
When emulating IE8 not only has returnValue to be false.
In addition defaultPrevented must not be defined.

* run all tests and format code

* rename instance variable to node

* remove backtick

* only simulate IE in normalisation test

* include assignment in definition

* add missing `persist` test

* use method instead of field to prevent default

* expect properties to be unchanged on persisted event

* optimise tests that deal with event persitence

* declare and assign `event` on the same line if not reassigned later
  • Loading branch information
timjacobi authored and gaearon committed Nov 20, 2017
1 parent bd9dbd5 commit 669a70d
Showing 1 changed file with 199 additions and 53 deletions.
252 changes: 199 additions & 53 deletions packages/react-dom/src/events/__tests__/SyntheticEvent-test.js
Expand Up @@ -9,79 +9,170 @@

'use strict';

var SyntheticEvent;
var React;
var ReactDOM;
var ReactTestUtils;

describe('SyntheticEvent', () => {
var createEvent;
var container;

beforeEach(() => {
// TODO: can we express this test with only public API?
SyntheticEvent = require('events/SyntheticEvent').default;
React = require('react');
ReactDOM = require('react-dom');
ReactTestUtils = require('react-dom/test-utils');

createEvent = function(nativeEvent) {
var target = require('../getEventTarget').default(nativeEvent);
return SyntheticEvent.getPooled({}, '', nativeEvent, target);
};
container = document.createElement('div');
document.body.appendChild(container);
});

afterEach(() => {
document.body.removeChild(container);
container = null;
});

it('should normalize `target` from the nativeEvent', () => {
var target = document.createElement('div');
var syntheticEvent = createEvent({srcElement: target});
var node;
var expectedCount = 0;

var eventHandler = syntheticEvent => {
expect(syntheticEvent.target).toBe(node);

expectedCount++;
};
node = ReactDOM.render(<div onClick={eventHandler} />, container);

var event = document.createEvent('Event');
event.initEvent('click', true, true);
// Emulate IE8
Object.defineProperty(event, 'target', {
get() {},
});
Object.defineProperty(event, 'srcElement', {
get() {
return node;
},
});
node.dispatchEvent(event);

expect(syntheticEvent.target).toBe(target);
expect(syntheticEvent.type).toBe(undefined);
expect(expectedCount).toBe(1);
});

it('should be able to `preventDefault`', () => {
var nativeEvent = {};
var syntheticEvent = createEvent(nativeEvent);
var node;
var expectedCount = 0;

expect(syntheticEvent.isDefaultPrevented()).toBe(false);
syntheticEvent.preventDefault();
expect(syntheticEvent.isDefaultPrevented()).toBe(true);
var eventHandler = syntheticEvent => {
expect(syntheticEvent.isDefaultPrevented()).toBe(false);
syntheticEvent.preventDefault();
expect(syntheticEvent.isDefaultPrevented()).toBe(true);
expect(syntheticEvent.defaultPrevented).toBe(true);

expectedCount++;
};
node = ReactDOM.render(<div onClick={eventHandler} />, container);

expect(syntheticEvent.defaultPrevented).toBe(true);
var event = document.createEvent('Event');
event.initEvent('click', true, true);
node.dispatchEvent(event);

expect(nativeEvent.returnValue).toBe(false);
expect(expectedCount).toBe(1);
});

it('should be prevented if nativeEvent is prevented', () => {
expect(createEvent({defaultPrevented: true}).isDefaultPrevented()).toBe(
true,
);
expect(createEvent({returnValue: false}).isDefaultPrevented()).toBe(true);
var node;
var expectedCount = 0;

var eventHandler = syntheticEvent => {
expect(syntheticEvent.isDefaultPrevented()).toBe(true);

expectedCount++;
};
node = ReactDOM.render(<div onClick={eventHandler} />, container);

var event;
event = document.createEvent('Event');
event.initEvent('click', true, true);
event.preventDefault();
node.dispatchEvent(event);

event = document.createEvent('Event');
event.initEvent('click', true, true);
// Emulate IE8
Object.defineProperty(event, 'defaultPrevented', {
get() {},
});
Object.defineProperty(event, 'returnValue', {
get() {
return false;
},
});
node.dispatchEvent(event);

expect(expectedCount).toBe(2);
});

it('should be able to `stopPropagation`', () => {
var nativeEvent = {};
var syntheticEvent = createEvent(nativeEvent);
var node;
var expectedCount = 0;

expect(syntheticEvent.isPropagationStopped()).toBe(false);
syntheticEvent.stopPropagation();
expect(syntheticEvent.isPropagationStopped()).toBe(true);
var eventHandler = syntheticEvent => {
expect(syntheticEvent.isPropagationStopped()).toBe(false);
syntheticEvent.stopPropagation();
expect(syntheticEvent.isPropagationStopped()).toBe(true);

expect(nativeEvent.cancelBubble).toBe(true);
expectedCount++;
};
node = ReactDOM.render(<div onClick={eventHandler} />, container);

var event = document.createEvent('Event');
event.initEvent('click', true, true);
node.dispatchEvent(event);

expect(expectedCount).toBe(1);
});

it('should be able to `persist`', () => {
var syntheticEvent = createEvent({});
var node;
var expectedCount = 0;
var syntheticEvent;

var eventHandler = e => {
expect(e.isPersistent()).toBe(false);
e.persist();
syntheticEvent = e;
expect(e.isPersistent()).toBe(true);

expectedCount++;
};
node = ReactDOM.render(<div onClick={eventHandler} />, container);

expect(syntheticEvent.isPersistent()).toBe(false);
syntheticEvent.persist();
expect(syntheticEvent.isPersistent()).toBe(true);
var event = document.createEvent('Event');
event.initEvent('click', true, true);
node.dispatchEvent(event);

expect(syntheticEvent.type).toBe('click');
expect(syntheticEvent.bubbles).toBe(true);
expect(syntheticEvent.cancelable).toBe(true);
expect(expectedCount).toBe(1);
});

it('should be nullified if the synthetic event has called destructor and log warnings', () => {
it('should be nullified and log warnings if the synthetic event has not been persisted', () => {
spyOn(console, 'error');
var target = document.createElement('div');
var syntheticEvent = createEvent({srcElement: target});
syntheticEvent.destructor();
var node;
var expectedCount = 0;
var syntheticEvent;

var eventHandler = e => {
syntheticEvent = e;

expectedCount++;
};
node = ReactDOM.render(<div onClick={eventHandler} />, container);

var event = document.createEvent('Event');
event.initEvent('click', true, true);
node.dispatchEvent(event);

expect(syntheticEvent.type).toBe(null);
expect(syntheticEvent.nativeEvent).toBe(null);
expect(syntheticEvent.target).toBe(null);
Expand All @@ -95,14 +186,27 @@ describe('SyntheticEvent', () => {
'keep the original synthetic event around, use event.persist(). ' +
'See https://fb.me/react-event-pooling for more information.',
);
expect(expectedCount).toBe(1);
});

it('should warn when setting properties of a destructored synthetic event', () => {
it('should warn when setting properties of a synthetic event that has not been persisted', () => {
spyOn(console, 'error');
var target = document.createElement('div');
var syntheticEvent = createEvent({srcElement: target});
syntheticEvent.destructor();
expect((syntheticEvent.type = 'MouseEvent')).toBe('MouseEvent');
var node;
var expectedCount = 0;
var syntheticEvent;

var eventHandler = e => {
syntheticEvent = e;

expectedCount++;
};
node = ReactDOM.render(<div onClick={eventHandler} />, container);

var event = document.createEvent('Event');
event.initEvent('click', true, true);
node.dispatchEvent(event);

syntheticEvent.type = 'MouseEvent';
expectDev(console.error.calls.count()).toBe(1);
expectDev(console.error.calls.argsFor(0)[0]).toBe(
'Warning: This synthetic event is reused for performance reasons. If ' +
Expand All @@ -111,12 +215,25 @@ describe('SyntheticEvent', () => {
'keep the original synthetic event around, use event.persist(). ' +
'See https://fb.me/react-event-pooling for more information.',
);
expect(expectedCount).toBe(1);
});

it('should warn if the synthetic event has been released when calling `preventDefault`', () => {
it('should warn when calling `preventDefault` if the synthetic event has not been persisted', () => {
spyOn(console, 'error');
var syntheticEvent = createEvent({});
SyntheticEvent.release(syntheticEvent);
var node;
var expectedCount = 0;
var syntheticEvent;

var eventHandler = e => {
syntheticEvent = e;
expectedCount++;
};
node = ReactDOM.render(<div onClick={eventHandler} />, container);

var event = document.createEvent('Event');
event.initEvent('click', true, true);
node.dispatchEvent(event);

syntheticEvent.preventDefault();
expectDev(console.error.calls.count()).toBe(1);
expectDev(console.error.calls.argsFor(0)[0]).toBe(
Expand All @@ -126,12 +243,26 @@ describe('SyntheticEvent', () => {
'keep the original synthetic event around, use event.persist(). ' +
'See https://fb.me/react-event-pooling for more information.',
);
expect(expectedCount).toBe(1);
});

it('should warn if the synthetic event has been released when calling `stopPropagation`', () => {
it('should warn when calling `stopPropagation` if the synthetic event has not been persisted', () => {
spyOn(console, 'error');
var syntheticEvent = createEvent({});
SyntheticEvent.release(syntheticEvent);
var node;
var expectedCount = 0;
var syntheticEvent;

var eventHandler = e => {
syntheticEvent = e;
expectedCount++;
};
node = ReactDOM.render(<div onClick={eventHandler} />, container);

var event = document.createEvent('Event');
event.initEvent('click', true, true);

node.dispatchEvent(event);

syntheticEvent.stopPropagation();
expectDev(console.error.calls.count()).toBe(1);
expectDev(console.error.calls.argsFor(0)[0]).toBe(
Expand All @@ -141,6 +272,7 @@ describe('SyntheticEvent', () => {
'keep the original synthetic event around, use event.persist(). ' +
'See https://fb.me/react-event-pooling for more information.',
);
expect(expectedCount).toBe(1);
});

// TODO: reenable this test. We are currently silencing these warnings when
Expand All @@ -153,8 +285,8 @@ describe('SyntheticEvent', () => {
function assignEvent(e) {
event = e;
}
var instance = ReactDOM.render(<div onClick={assignEvent} />, element);
ReactTestUtils.Simulate.click(ReactDOM.findDOMNode(instance));
var node = ReactDOM.render(<div onClick={assignEvent} />, element);
ReactTestUtils.Simulate.click(ReactDOM.findDOMNode(node));
expectDev(console.error.calls.count()).toBe(0);

// access a property to cause the warning
Expand All @@ -172,9 +304,22 @@ describe('SyntheticEvent', () => {

it('should warn if Proxy is supported and the synthetic event is added a property', () => {
spyOn(console, 'error');
var syntheticEvent = createEvent({});
syntheticEvent.foo = 'bar';
SyntheticEvent.release(syntheticEvent);
var node;
var expectedCount = 0;
var syntheticEvent;

var eventHandler = e => {
e.foo = 'bar';
syntheticEvent = e;
expectedCount++;
};
node = ReactDOM.render(<div onClick={eventHandler} />, container);

var event = document.createEvent('Event');
event.initEvent('click', true, true);

node.dispatchEvent(event);

expect(syntheticEvent.foo).toBe('bar');
if (typeof Proxy === 'function') {
expectDev(console.error.calls.count()).toBe(1);
Expand All @@ -187,5 +332,6 @@ describe('SyntheticEvent', () => {
} else {
expectDev(console.error.calls.count()).toBe(0);
}
expect(expectedCount).toBe(1);
});
});

0 comments on commit 669a70d

Please sign in to comment.