mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
036ae3c6e2
* Use native event dispatching instead of Simulate or SimulateNative In #12629 @gaearon suggested that it would be better to drop usage of `ReactTestUtils.Simulate` and `ReactTestUtils.SimulateNative`. In this PR I’m attempting at removing it from a lot of places with only a few leftovers. Those leftovers can be categorized into three groups: 1. Anything that tests that `SimulateNative` throws. This is a property that native event dispatching doesn’t have so I can’t convert that easily. Affected test suites: `EventPluginHub-test`, `ReactBrowserEventEmitter-test`. 2. Anything that tests `ReactTestUtils` directly. Affected test suites: `ReactBrowserEventEmitter-test` (this file has one test that reads "should have mouse enter simulated by test utils"), `ReactTestUtils-test`. 3. Anything that dispatches a `change` event. The reason here goes a bit deeper and is rooted in the way we shim onChange. Usually when using native event dispatching, you would set the node’s `.value` and then dispatch the event. However inside [`inputValueTracking.js`][] we install a setter on the node’s `.value` that will ignore the next `change` event (I found [this][near-perfect-oninput-shim] article from Sophie that explains that this is to avoid onChange when updating the value via JavaScript). All remaining usages of `Simulate` or `SimulateNative` can be avoided by mounting the containers inside the `document` and dispatching native events. Here some remarks: 1. I’m using `Element#click()` instead of `dispatchEvent`. In the jsdom changelog I read that `click()` now properly sets the correct values (you can also verify it does the same thing by looking at the [source][jsdom-source]). 2. I had to update jsdom in order to get `TouchEvent` constructors working (and while doing so also updated jest). There was one unexpected surprise: `ReactScheduler-test` was relying on not having `window.performance` available. I’ve recreated the previous environment by deleting this property from the global object. 3. I was a bit confused that `ReactTestUtils.renderIntoDocument()` does not render into the document 🤷 [`inputValueTracking.js`]: https://github.com/facebook/react/blob/392530104c00c25074ce38e1f7e1dd363018c7ce/packages/react-dom/src/client/inputValueTracking.js#L79 [near-perfect-oninput-shim]: https://sophiebits.com/2013/06/18/a-near-perfect-oninput-shim-for-ie-8-and-9.html [jsdom-source]: https://github.com/jsdom/jsdom/blob/45b77f5d21cef74cad278d089937d8462c29acce/lib/jsdom/living/nodes/HTMLElement-impl.js#L43-L76 * Make sure contains are unlinked from the document even if the test fails * Remove unnecessary findDOMNode calls
288 lines
6.4 KiB
JavaScript
288 lines
6.4 KiB
JavaScript
/**
|
|
* 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;
|
|
let ReactDOMServer;
|
|
|
|
// In standard React, TextComponent keeps track of different Text templates
|
|
// using comments. However, in React Fiber, those comments are not outputted due
|
|
// to the way Fiber keeps track of the templates.
|
|
// This function "Normalizes" childNodes lists to avoid the presence of comments
|
|
// and make the child list identical in standard React and Fiber
|
|
function filterOutComments(nodeList) {
|
|
return [].slice.call(nodeList).filter(node => !(node instanceof Comment));
|
|
}
|
|
|
|
describe('ReactDOMTextComponent', () => {
|
|
beforeEach(() => {
|
|
React = require('react');
|
|
ReactDOM = require('react-dom');
|
|
ReactDOMServer = require('react-dom/server');
|
|
});
|
|
|
|
it('updates a mounted text component in place', () => {
|
|
const el = document.createElement('div');
|
|
let inst = ReactDOM.render(
|
|
<div>
|
|
<span />
|
|
{'foo'}
|
|
{'bar'}
|
|
</div>,
|
|
el,
|
|
);
|
|
let nodes = filterOutComments(inst.childNodes);
|
|
|
|
let foo = nodes[1];
|
|
let bar = nodes[2];
|
|
expect(foo.data).toBe('foo');
|
|
expect(bar.data).toBe('bar');
|
|
|
|
inst = ReactDOM.render(
|
|
<div>
|
|
<span />
|
|
{'baz'}
|
|
{'qux'}
|
|
</div>,
|
|
el,
|
|
);
|
|
// After the update, the text nodes should have stayed in place (as opposed
|
|
// to getting unmounted and remounted)
|
|
nodes = filterOutComments(inst.childNodes);
|
|
expect(nodes[1]).toBe(foo);
|
|
expect(nodes[2]).toBe(bar);
|
|
expect(foo.data).toBe('baz');
|
|
expect(bar.data).toBe('qux');
|
|
});
|
|
|
|
it('can be toggled in and out of the markup', () => {
|
|
const el = document.createElement('div');
|
|
let inst = ReactDOM.render(
|
|
<div>
|
|
{'foo'}
|
|
<div />
|
|
{'bar'}
|
|
</div>,
|
|
el,
|
|
);
|
|
|
|
let childNodes = filterOutComments(inst.childNodes);
|
|
let childDiv = childNodes[1];
|
|
|
|
inst = ReactDOM.render(
|
|
<div>
|
|
{null}
|
|
<div />
|
|
{null}
|
|
</div>,
|
|
el,
|
|
);
|
|
childNodes = filterOutComments(inst.childNodes);
|
|
expect(childNodes.length).toBe(1);
|
|
expect(childNodes[0]).toBe(childDiv);
|
|
|
|
inst = ReactDOM.render(
|
|
<div>
|
|
{'foo'}
|
|
<div />
|
|
{'bar'}
|
|
</div>,
|
|
el,
|
|
);
|
|
childNodes = filterOutComments(inst.childNodes);
|
|
expect(childNodes.length).toBe(3);
|
|
expect(childNodes[0].data).toBe('foo');
|
|
expect(childNodes[1]).toBe(childDiv);
|
|
expect(childNodes[2].data).toBe('bar');
|
|
});
|
|
|
|
/**
|
|
* The following Node.normalize() tests are intentionally failing.
|
|
* See #9836 tracking whether we'll need to fix this or if it's unnecessary.
|
|
*/
|
|
|
|
xit('can reconcile text merged by Node.normalize() alongside other elements', () => {
|
|
const el = document.createElement('div');
|
|
let inst = ReactDOM.render(
|
|
<div>
|
|
{'foo'}
|
|
{'bar'}
|
|
{'baz'}
|
|
<span />
|
|
{'qux'}
|
|
</div>,
|
|
el,
|
|
);
|
|
|
|
inst.normalize();
|
|
|
|
inst = ReactDOM.render(
|
|
<div>
|
|
{'bar'}
|
|
{'baz'}
|
|
{'qux'}
|
|
<span />
|
|
{'foo'}
|
|
</div>,
|
|
el,
|
|
);
|
|
expect(inst.textContent).toBe('barbazquxfoo');
|
|
});
|
|
|
|
xit('can reconcile text merged by Node.normalize()', () => {
|
|
const el = document.createElement('div');
|
|
let inst = ReactDOM.render(
|
|
<div>
|
|
{'foo'}
|
|
{'bar'}
|
|
{'baz'}
|
|
</div>,
|
|
el,
|
|
);
|
|
|
|
inst.normalize();
|
|
|
|
inst = ReactDOM.render(
|
|
<div>
|
|
{'bar'}
|
|
{'baz'}
|
|
{'qux'}
|
|
</div>,
|
|
el,
|
|
);
|
|
expect(inst.textContent).toBe('barbazqux');
|
|
});
|
|
|
|
it('can reconcile text from pre-rendered markup', () => {
|
|
const el = document.createElement('div');
|
|
let reactEl = (
|
|
<div>
|
|
{'foo'}
|
|
{'bar'}
|
|
{'baz'}
|
|
</div>
|
|
);
|
|
el.innerHTML = ReactDOMServer.renderToString(reactEl);
|
|
|
|
ReactDOM.hydrate(reactEl, el);
|
|
expect(el.textContent).toBe('foobarbaz');
|
|
|
|
ReactDOM.unmountComponentAtNode(el);
|
|
|
|
reactEl = (
|
|
<div>
|
|
{''}
|
|
{''}
|
|
{''}
|
|
</div>
|
|
);
|
|
el.innerHTML = ReactDOMServer.renderToString(reactEl);
|
|
|
|
ReactDOM.hydrate(reactEl, el);
|
|
expect(el.textContent).toBe('');
|
|
});
|
|
|
|
xit('can reconcile text arbitrarily split into multiple nodes', () => {
|
|
const el = document.createElement('div');
|
|
let inst = ReactDOM.render(
|
|
<div>
|
|
<span />
|
|
{'foobarbaz'}
|
|
</div>,
|
|
el,
|
|
);
|
|
|
|
let childNodes = filterOutComments(inst.childNodes);
|
|
let textNode = childNodes[1];
|
|
textNode.textContent = 'foo';
|
|
inst.insertBefore(
|
|
document.createTextNode('bar'),
|
|
childNodes[1].nextSibling,
|
|
);
|
|
inst.insertBefore(
|
|
document.createTextNode('baz'),
|
|
childNodes[1].nextSibling,
|
|
);
|
|
|
|
inst = ReactDOM.render(
|
|
<div>
|
|
<span />
|
|
{'barbazqux'}
|
|
</div>,
|
|
el,
|
|
);
|
|
expect(inst.textContent).toBe('barbazqux');
|
|
});
|
|
|
|
xit('can reconcile text arbitrarily split into multiple nodes on some substitutions only', () => {
|
|
const el = document.createElement('div');
|
|
let inst = ReactDOM.render(
|
|
<div>
|
|
<span />
|
|
{'bar'}
|
|
<span />
|
|
{'foobarbaz'}
|
|
{'foo'}
|
|
{'barfoo'}
|
|
<span />
|
|
</div>,
|
|
el,
|
|
);
|
|
|
|
let childNodes = filterOutComments(inst.childNodes);
|
|
let textNode = childNodes[3];
|
|
textNode.textContent = 'foo';
|
|
inst.insertBefore(
|
|
document.createTextNode('bar'),
|
|
childNodes[3].nextSibling,
|
|
);
|
|
inst.insertBefore(
|
|
document.createTextNode('baz'),
|
|
childNodes[3].nextSibling,
|
|
);
|
|
let secondTextNode = childNodes[5];
|
|
secondTextNode.textContent = 'bar';
|
|
inst.insertBefore(
|
|
document.createTextNode('foo'),
|
|
childNodes[5].nextSibling,
|
|
);
|
|
|
|
inst = ReactDOM.render(
|
|
<div>
|
|
<span />
|
|
{'baz'}
|
|
<span />
|
|
{'barbazqux'}
|
|
{'bar'}
|
|
{'bazbar'}
|
|
<span />
|
|
</div>,
|
|
el,
|
|
);
|
|
expect(inst.textContent).toBe('bazbarbazquxbarbazbar');
|
|
});
|
|
|
|
xit('can unmount normalized text nodes', () => {
|
|
const el = document.createElement('div');
|
|
ReactDOM.render(
|
|
<div>
|
|
{''}
|
|
{'foo'}
|
|
{'bar'}
|
|
</div>,
|
|
el,
|
|
);
|
|
el.normalize();
|
|
ReactDOM.render(<div />, el);
|
|
expect(el.innerHTML).toBe('<div></div>');
|
|
});
|
|
});
|