mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Merge pull request #164 from gaearon/fix-lots-of-stuff
Fix reordering and Suspense logic
This commit is contained in:
+3
-3
@@ -130,10 +130,10 @@
|
||||
"opener": "^1.5.1",
|
||||
"prettier": "^1.16.4",
|
||||
"prop-types": "^15.6.2",
|
||||
"react": "^16.9.0-alpha.0",
|
||||
"react": "0.0.0-4221565e1",
|
||||
"react-color": "^2.11.7",
|
||||
"react-dom": "^16.9.0-alpha.0",
|
||||
"react-is": "^16.9.0-alpha.0",
|
||||
"react-dom": "0.0.0-4221565e1",
|
||||
"react-is": "0.0.0-4221565e1",
|
||||
"react-virtualized-auto-sizer": "^1.0.2",
|
||||
"react-window": "^1.8.0",
|
||||
"request-promise": "^4.2.4",
|
||||
|
||||
@@ -25,142 +25,6 @@ exports[`Store should filter DOM nodes from the store tree: 1: mount 1`] = `
|
||||
<Child>
|
||||
`;
|
||||
|
||||
exports[`Store should handle a stress test for Suspense 1`] = `
|
||||
[root]
|
||||
▾ <Root>
|
||||
<X>
|
||||
▾ <Suspense>
|
||||
<A key="a">
|
||||
<Y>
|
||||
`;
|
||||
|
||||
exports[`Store should handle a stress test for Suspense 2`] = `
|
||||
[root]
|
||||
▾ <Root>
|
||||
<X>
|
||||
▾ <Suspense>
|
||||
<A key="a">
|
||||
<Y>
|
||||
`;
|
||||
|
||||
exports[`Store should handle a stress test for Suspense 3`] = `
|
||||
[root]
|
||||
▾ <Root>
|
||||
<X>
|
||||
▾ <Suspense>
|
||||
<A key="a">
|
||||
<B key="b">
|
||||
<C key="c">
|
||||
<Y>
|
||||
`;
|
||||
|
||||
exports[`Store should handle a stress test for Suspense 4`] = `
|
||||
[root]
|
||||
▾ <Root>
|
||||
<X>
|
||||
▾ <Suspense>
|
||||
<C key="c">
|
||||
<B key="b">
|
||||
<A key="a">
|
||||
<Y>
|
||||
`;
|
||||
|
||||
exports[`Store should handle a stress test for Suspense 5`] = `
|
||||
[root]
|
||||
▾ <Root>
|
||||
<X>
|
||||
▾ <Suspense>
|
||||
<C key="c">
|
||||
<A key="a">
|
||||
<Y>
|
||||
`;
|
||||
|
||||
exports[`Store should handle a stress test for Suspense 6`] = `
|
||||
[root]
|
||||
▾ <Root>
|
||||
<X>
|
||||
▾ <Suspense>
|
||||
<C key="c">
|
||||
<A key="a">
|
||||
<Y>
|
||||
`;
|
||||
|
||||
exports[`Store should handle a stress test for Suspense 7`] = `
|
||||
[root]
|
||||
▾ <Root>
|
||||
<X>
|
||||
▾ <Suspense>
|
||||
<C key="c">
|
||||
<A key="a">
|
||||
<Y>
|
||||
`;
|
||||
|
||||
exports[`Store should handle a stress test for Suspense 8`] = `
|
||||
[root]
|
||||
▾ <Root>
|
||||
<X>
|
||||
▾ <Suspense>
|
||||
<A key="a">
|
||||
<B key="b">
|
||||
<Y>
|
||||
`;
|
||||
|
||||
exports[`Store should handle a stress test for Suspense 9`] = `
|
||||
[root]
|
||||
▾ <Root>
|
||||
<X>
|
||||
▾ <Suspense>
|
||||
<A key="a">
|
||||
<Y>
|
||||
`;
|
||||
|
||||
exports[`Store should handle a stress test for Suspense 10`] = `
|
||||
[root]
|
||||
▾ <Root>
|
||||
<X>
|
||||
<Suspense>
|
||||
<Y>
|
||||
`;
|
||||
|
||||
exports[`Store should handle a stress test for Suspense 11`] = `
|
||||
[root]
|
||||
▾ <Root>
|
||||
<X>
|
||||
▾ <Suspense>
|
||||
<B key="b">
|
||||
<Y>
|
||||
`;
|
||||
|
||||
exports[`Store should handle a stress test for Suspense 12`] = `
|
||||
[root]
|
||||
▾ <Root>
|
||||
<X>
|
||||
▾ <Suspense>
|
||||
<A key="a">
|
||||
<Y>
|
||||
`;
|
||||
|
||||
exports[`Store should handle a stress test with different tree operations: 1: abcde 1`] = `
|
||||
[root]
|
||||
▾ <Parent>
|
||||
<A key="a">
|
||||
<B key="b">
|
||||
<C key="c">
|
||||
<D key="d">
|
||||
<E key="e">
|
||||
`;
|
||||
|
||||
exports[`Store should handle a stress test with different tree operations: 2: abxde 1`] = `
|
||||
[root]
|
||||
▾ <Parent>
|
||||
<A key="a">
|
||||
<B key="b">
|
||||
▾ <C key="c">
|
||||
<X>
|
||||
<D key="d">
|
||||
<E key="e">
|
||||
`;
|
||||
|
||||
exports[`Store should support collapsing parts of the tree: 1: mount 1`] = `
|
||||
[root]
|
||||
▾ <Grandparent>
|
||||
|
||||
@@ -0,0 +1,239 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`StoreStress should handle a stress test for Suspense 1`] = `
|
||||
[root]
|
||||
▾ <Root>
|
||||
<X>
|
||||
▾ <Suspense>
|
||||
<A key="a">
|
||||
<Y>
|
||||
`;
|
||||
|
||||
exports[`StoreStress should handle a stress test for Suspense 2`] = `
|
||||
[root]
|
||||
▾ <Root>
|
||||
<X>
|
||||
▾ <Suspense>
|
||||
<A key="a">
|
||||
<Y>
|
||||
`;
|
||||
|
||||
exports[`StoreStress should handle a stress test for Suspense 3`] = `
|
||||
[root]
|
||||
▾ <Root>
|
||||
<X>
|
||||
▾ <Suspense>
|
||||
<A key="a">
|
||||
<B key="b">
|
||||
<C key="c">
|
||||
<Y>
|
||||
`;
|
||||
|
||||
exports[`StoreStress should handle a stress test for Suspense 4`] = `
|
||||
[root]
|
||||
▾ <Root>
|
||||
<X>
|
||||
▾ <Suspense>
|
||||
<C key="c">
|
||||
<B key="b">
|
||||
<A key="a">
|
||||
<Y>
|
||||
`;
|
||||
|
||||
exports[`StoreStress should handle a stress test for Suspense 5`] = `
|
||||
[root]
|
||||
▾ <Root>
|
||||
<X>
|
||||
▾ <Suspense>
|
||||
<C key="c">
|
||||
<A key="a">
|
||||
<Y>
|
||||
`;
|
||||
|
||||
exports[`StoreStress should handle a stress test for Suspense 6`] = `
|
||||
[root]
|
||||
▾ <Root>
|
||||
<X>
|
||||
▾ <Suspense>
|
||||
<C key="c">
|
||||
<A key="a">
|
||||
<Y>
|
||||
`;
|
||||
|
||||
exports[`StoreStress should handle a stress test for Suspense 7`] = `
|
||||
[root]
|
||||
▾ <Root>
|
||||
<X>
|
||||
▾ <Suspense>
|
||||
<C key="c">
|
||||
<A key="a">
|
||||
<Y>
|
||||
`;
|
||||
|
||||
exports[`StoreStress should handle a stress test for Suspense 8`] = `
|
||||
[root]
|
||||
▾ <Root>
|
||||
<X>
|
||||
▾ <Suspense>
|
||||
<A key="a">
|
||||
<B key="b">
|
||||
<Y>
|
||||
`;
|
||||
|
||||
exports[`StoreStress should handle a stress test for Suspense 9`] = `
|
||||
[root]
|
||||
▾ <Root>
|
||||
<X>
|
||||
▾ <Suspense>
|
||||
<A key="a">
|
||||
<Y>
|
||||
`;
|
||||
|
||||
exports[`StoreStress should handle a stress test for Suspense 10`] = `
|
||||
[root]
|
||||
▾ <Root>
|
||||
<X>
|
||||
<Suspense>
|
||||
<Y>
|
||||
`;
|
||||
|
||||
exports[`StoreStress should handle a stress test for Suspense 11`] = `
|
||||
[root]
|
||||
▾ <Root>
|
||||
<X>
|
||||
▾ <Suspense>
|
||||
<B key="b">
|
||||
<Y>
|
||||
`;
|
||||
|
||||
exports[`StoreStress should handle a stress test for Suspense 12`] = `
|
||||
[root]
|
||||
▾ <Root>
|
||||
<X>
|
||||
▾ <Suspense>
|
||||
<A key="a">
|
||||
<Y>
|
||||
`;
|
||||
|
||||
exports[`StoreStress should handle a stress test with different tree operations: 1: abcde 1`] = `
|
||||
[root]
|
||||
▾ <Parent>
|
||||
<A key="a">
|
||||
<B key="b">
|
||||
<C key="c">
|
||||
<D key="d">
|
||||
<E key="e">
|
||||
`;
|
||||
|
||||
exports[`StoreStress should handle a stress test with different tree operations: 2: abxde 1`] = `
|
||||
[root]
|
||||
▾ <Parent>
|
||||
<A key="a">
|
||||
<B key="b">
|
||||
▾ <C key="c">
|
||||
<X>
|
||||
<D key="d">
|
||||
<E key="e">
|
||||
`;
|
||||
|
||||
exports[`StoreStress should handle stress test with reordering 1`] = `
|
||||
[root]
|
||||
▾ <Root>
|
||||
<A key="a">
|
||||
`;
|
||||
|
||||
exports[`StoreStress should handle stress test with reordering 2`] = `
|
||||
[root]
|
||||
▾ <Root>
|
||||
<B key="b">
|
||||
`;
|
||||
|
||||
exports[`StoreStress should handle stress test with reordering 3`] = `
|
||||
[root]
|
||||
▾ <Root>
|
||||
<C key="c">
|
||||
`;
|
||||
|
||||
exports[`StoreStress should handle stress test with reordering 4`] = `
|
||||
[root]
|
||||
▾ <Root>
|
||||
<D key="d">
|
||||
`;
|
||||
|
||||
exports[`StoreStress should handle stress test with reordering 5`] = `
|
||||
[root]
|
||||
▾ <Root>
|
||||
<E key="e">
|
||||
`;
|
||||
|
||||
exports[`StoreStress should handle stress test with reordering 6`] = `
|
||||
[root]
|
||||
▾ <Root>
|
||||
<A key="a">
|
||||
`;
|
||||
|
||||
exports[`StoreStress should handle stress test with reordering 7`] = `
|
||||
[root]
|
||||
▾ <Root>
|
||||
<B key="b">
|
||||
`;
|
||||
|
||||
exports[`StoreStress should handle stress test with reordering 8`] = `
|
||||
[root]
|
||||
▾ <Root>
|
||||
<C key="c">
|
||||
`;
|
||||
|
||||
exports[`StoreStress should handle stress test with reordering 9`] = `
|
||||
[root]
|
||||
▾ <Root>
|
||||
<D key="d">
|
||||
`;
|
||||
|
||||
exports[`StoreStress should handle stress test with reordering 10`] = `
|
||||
[root]
|
||||
▾ <Root>
|
||||
<E key="e">
|
||||
`;
|
||||
|
||||
exports[`StoreStress should handle stress test with reordering 11`] = `
|
||||
[root]
|
||||
▾ <Root>
|
||||
<A key="a">
|
||||
<B key="b">
|
||||
`;
|
||||
|
||||
exports[`StoreStress should handle stress test with reordering 12`] = `
|
||||
[root]
|
||||
▾ <Root>
|
||||
<B key="b">
|
||||
<A key="a">
|
||||
`;
|
||||
|
||||
exports[`StoreStress should handle stress test with reordering 13`] = `
|
||||
[root]
|
||||
▾ <Root>
|
||||
<B key="b">
|
||||
<C key="c">
|
||||
`;
|
||||
|
||||
exports[`StoreStress should handle stress test with reordering 14`] = `
|
||||
[root]
|
||||
▾ <Root>
|
||||
<C key="c">
|
||||
<B key="b">
|
||||
`;
|
||||
|
||||
exports[`StoreStress should handle stress test with reordering 15`] = `
|
||||
[root]
|
||||
▾ <Root>
|
||||
<A key="a">
|
||||
<C key="c">
|
||||
`;
|
||||
|
||||
exports[`StoreStress should handle stress test with reordering 16`] = `
|
||||
[root]
|
||||
▾ <Root>
|
||||
<C key="c">
|
||||
<A key="a">
|
||||
`;
|
||||
+2
-519
@@ -4,9 +4,7 @@ describe('Store', () => {
|
||||
let React;
|
||||
let ReactDOM;
|
||||
let TestUtils;
|
||||
// let bridge;
|
||||
let store;
|
||||
let print;
|
||||
|
||||
const act = (callback: Function) => {
|
||||
TestUtils.act(() => {
|
||||
@@ -16,14 +14,11 @@ describe('Store', () => {
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
// bridge = global.bridge;
|
||||
store = global.store;
|
||||
|
||||
React = require('react');
|
||||
ReactDOM = require('react-dom');
|
||||
TestUtils = require('react-dom/test-utils');
|
||||
|
||||
print = require('./storeSerializer').print;
|
||||
});
|
||||
|
||||
it('should support mount and update operations', () => {
|
||||
@@ -98,8 +93,7 @@ describe('Store', () => {
|
||||
expect(store).toMatchSnapshot('1: mount');
|
||||
});
|
||||
|
||||
// TODO We should write more complex Suspense tests than just this
|
||||
it('should display Suspense nodes properly in various states', async done => {
|
||||
it('should display Suspense nodes properly in various states', () => {
|
||||
const Loading = () => <div>Loading...</div>;
|
||||
const SuspendingComponent = () => {
|
||||
throw new Promise(() => {});
|
||||
@@ -124,12 +118,10 @@ describe('Store', () => {
|
||||
act(() => ReactDOM.render(<Wrapper shouldSuspense={true} />, container));
|
||||
expect(store).toMatchSnapshot('1: loading');
|
||||
|
||||
act(async () => {
|
||||
act(() => {
|
||||
ReactDOM.render(<Wrapper shouldSuspense={false} />, container);
|
||||
});
|
||||
expect(store).toMatchSnapshot('2: resolved');
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
it('should support collapsing parts of the tree', () => {
|
||||
@@ -167,513 +159,4 @@ describe('Store', () => {
|
||||
act(() => store.toggleIsCollapsed(grandparentID, false));
|
||||
expect(store).toMatchSnapshot('6: expand Grandparent');
|
||||
});
|
||||
|
||||
// This is a stress test for the tree mount/update/unmount traversal.
|
||||
// It renders different trees that should produce the same output.
|
||||
it('should handle a stress test with different tree operations', () => {
|
||||
let setShowX;
|
||||
const A = () => 'a';
|
||||
const B = () => 'b';
|
||||
const C = () => {
|
||||
// We'll be manually flipping this component back and forth in the test.
|
||||
// We only do this for a single node in order to verify that DevTools
|
||||
// can handle a subtree switching alternates while other subtrees are memoized.
|
||||
let [showX, _setShowX] = React.useState(false);
|
||||
setShowX = _setShowX;
|
||||
return showX ? <X /> : 'c';
|
||||
};
|
||||
const D = () => 'd';
|
||||
const E = () => 'e';
|
||||
const X = () => 'x';
|
||||
const a = <A key="a" />;
|
||||
const b = <B key="b" />;
|
||||
const c = <C key="c" />;
|
||||
const d = <D key="d" />;
|
||||
const e = <E key="e" />;
|
||||
|
||||
function Parent({ children }) {
|
||||
return children;
|
||||
}
|
||||
|
||||
// 1. Render a normal version of [a, b, c, d, e].
|
||||
let container = document.createElement('div');
|
||||
act(() => ReactDOM.render(<Parent>{[a, b, c, d, e]}</Parent>, container));
|
||||
expect(store).toMatchSnapshot('1: abcde');
|
||||
expect(container.textContent).toMatch('abcde');
|
||||
const snapshotForABCDE = print(store);
|
||||
|
||||
// 2. Render a version where <C /> renders an <X /> child instead of 'c'.
|
||||
// This is how we'll test an update to a single component.
|
||||
act(() => {
|
||||
setShowX(true);
|
||||
});
|
||||
expect(store).toMatchSnapshot('2: abxde');
|
||||
expect(container.textContent).toMatch('abxde');
|
||||
const snapshotForABXDE = print(store);
|
||||
|
||||
// 3. Verify flipping it back produces the original result.
|
||||
act(() => {
|
||||
setShowX(false);
|
||||
});
|
||||
expect(container.textContent).toMatch('abcde');
|
||||
expect(print(store)).toBe(snapshotForABCDE);
|
||||
|
||||
// 4. Clean up.
|
||||
act(() => ReactDOM.unmountComponentAtNode(container));
|
||||
expect(print(store)).toBe('');
|
||||
|
||||
// Now comes the interesting part.
|
||||
// All of these cases are equivalent to [a, b, c, d, e] in output.
|
||||
// We'll verify that DevTools produces the same snapshots for them.
|
||||
// These cases are picked so that rendering them sequentially in the same
|
||||
// container results in a combination of mounts, updates, unmounts, and reorders.
|
||||
// prettier-ignore
|
||||
let cases = [
|
||||
[a, b, c, d, e],
|
||||
[[a], b, c, d, e],
|
||||
[[a, b], c, d, e],
|
||||
[[a, b], c, [d, e]],
|
||||
[[a, b], c, [d, '', e]],
|
||||
[[a], b, c, d, [e]],
|
||||
[a, b, [[c]], d, e],
|
||||
[[a, ''], [b], [c], [d], [e]],
|
||||
[a, b, [c, [d, ['', e]]]],
|
||||
[a, b, c, d, e],
|
||||
[<div key="0">{a}</div>, b, c, d, e],
|
||||
[<div key="0">{a}{b}</div>, c, d, e],
|
||||
[<div key="0">{a}{b}</div>, c, <div key="1">{d}{e}</div>],
|
||||
[<div key="1">{a}{b}</div>, c, <div key="0">{d}{e}</div>],
|
||||
[<div key="0">{a}{b}</div>, c, <div key="1">{d}{e}</div>],
|
||||
[<div key="2">{a}{b}</div>, c, <div key="3">{d}{e}</div>],
|
||||
[<span key="0">{a}</span>, b, c, d, [e]],
|
||||
[a, b, <span key="0"><span>{c}</span></span>, d, e],
|
||||
[<div key="0">{a}</div>, [b], <span key="1">{c}</span>, [d], <div key="2">{e}</div>],
|
||||
[a, b, [c, <div key="0">{d}<span>{e}</span></div>], ''],
|
||||
[a, [[]], b, c, [d, [[]], e]],
|
||||
[[[a, b, c, d], e]],
|
||||
[a, b, c, d, e]
|
||||
];
|
||||
|
||||
// 5. Test fresh mount for each case.
|
||||
for (let i = 0; i < cases.length; i++) {
|
||||
// Ensure fresh mount.
|
||||
container = document.createElement('div');
|
||||
|
||||
// Verify mounting 'abcde'.
|
||||
act(() => ReactDOM.render(<Parent>{cases[i]}</Parent>, container));
|
||||
expect(container.textContent).toMatch('abcde');
|
||||
expect(print(store)).toEqual(snapshotForABCDE);
|
||||
|
||||
// Verify switching to 'abxde'.
|
||||
act(() => {
|
||||
setShowX(true);
|
||||
});
|
||||
expect(container.textContent).toMatch('abxde');
|
||||
expect(print(store)).toBe(snapshotForABXDE);
|
||||
|
||||
// Verify switching back to 'abcde'.
|
||||
act(() => {
|
||||
setShowX(false);
|
||||
});
|
||||
expect(container.textContent).toMatch('abcde');
|
||||
expect(print(store)).toBe(snapshotForABCDE);
|
||||
|
||||
// Clean up.
|
||||
act(() => ReactDOM.unmountComponentAtNode(container));
|
||||
expect(print(store)).toBe('');
|
||||
}
|
||||
|
||||
// 6. Verify *updates* by reusing the container between iterations.
|
||||
// There'll be no unmounting until the very end.
|
||||
container = document.createElement('div');
|
||||
for (let i = 0; i < cases.length; i++) {
|
||||
// Verify mounting 'abcde'.
|
||||
act(() => ReactDOM.render(<Parent>{cases[i]}</Parent>, container));
|
||||
expect(container.textContent).toMatch('abcde');
|
||||
expect(print(store)).toEqual(snapshotForABCDE);
|
||||
|
||||
// Verify switching to 'abxde'.
|
||||
act(() => {
|
||||
setShowX(true);
|
||||
});
|
||||
expect(container.textContent).toMatch('abxde');
|
||||
expect(print(store)).toBe(snapshotForABXDE);
|
||||
|
||||
// Verify switching back to 'abcde'.
|
||||
act(() => {
|
||||
setShowX(false);
|
||||
});
|
||||
expect(container.textContent).toMatch('abcde');
|
||||
expect(print(store)).toBe(snapshotForABCDE);
|
||||
// Don't unmount. Reuse the container between iterations.
|
||||
}
|
||||
act(() => ReactDOM.unmountComponentAtNode(container));
|
||||
expect(print(store)).toBe('');
|
||||
|
||||
// 7. Same as the previous step, but for Concurrent Mode.
|
||||
container = document.createElement('div');
|
||||
// $FlowFixMe
|
||||
let root = ReactDOM.unstable_createRoot(container);
|
||||
for (let i = 0; i < cases.length; i++) {
|
||||
// Verify mounting 'abcde'.
|
||||
act(() => root.render(<Parent>{cases[i]}</Parent>));
|
||||
expect(container.textContent).toMatch('abcde');
|
||||
expect(print(store)).toEqual(snapshotForABCDE);
|
||||
|
||||
// Verify switching to 'abxde'.
|
||||
act(() => {
|
||||
setShowX(true);
|
||||
});
|
||||
expect(container.textContent).toMatch('abxde');
|
||||
expect(print(store)).toBe(snapshotForABXDE);
|
||||
|
||||
// Verify switching back to 'abcde'.
|
||||
act(() => {
|
||||
setShowX(false);
|
||||
});
|
||||
expect(container.textContent).toMatch('abcde');
|
||||
expect(print(store)).toBe(snapshotForABCDE);
|
||||
// Don't unmount. Reuse the container between iterations.
|
||||
}
|
||||
act(() => root.unmount());
|
||||
expect(print(store)).toBe('');
|
||||
});
|
||||
|
||||
it('should handle a stress test for Suspense', async () => {
|
||||
const A = () => 'a';
|
||||
const B = () => 'b';
|
||||
const C = () => 'c';
|
||||
const X = () => 'x';
|
||||
const Y = () => 'y';
|
||||
const Z = () => 'z';
|
||||
const a = <A key="a" />;
|
||||
const b = <B key="b" />;
|
||||
const c = <C key="c" />;
|
||||
// const x = <X key="x" />;
|
||||
// const y = <Y key="y" />;
|
||||
const z = <Z key="z" />;
|
||||
|
||||
// prettier-ignore
|
||||
const steps = [
|
||||
a,
|
||||
[a],
|
||||
[a, b, c],
|
||||
[c, b, a],
|
||||
[c, null, a],
|
||||
<React.Fragment>{c}{a}</React.Fragment>,
|
||||
<div>{c}{a}</div>,
|
||||
<div><span>{a}</span>{b}</div>,
|
||||
[[a]],
|
||||
null,
|
||||
b,
|
||||
a
|
||||
];
|
||||
|
||||
const Never = () => {
|
||||
throw new Promise(() => {});
|
||||
};
|
||||
|
||||
const Root = ({ children }) => {
|
||||
return children;
|
||||
};
|
||||
|
||||
// 1. For each step, check Suspense can render them as initial primary content.
|
||||
// This is the only step where we use Jest snapshots.
|
||||
let snapshots = [];
|
||||
let container = document.createElement('div');
|
||||
for (let i = 0; i < steps.length; i++) {
|
||||
act(() =>
|
||||
ReactDOM.render(
|
||||
<Root>
|
||||
<X />
|
||||
<React.Suspense fallback={z}>{steps[i]}</React.Suspense>
|
||||
<Y />
|
||||
</Root>,
|
||||
container
|
||||
)
|
||||
);
|
||||
// We snapshot each step once so it doesn't regress.
|
||||
expect(store).toMatchSnapshot();
|
||||
snapshots.push(print(store));
|
||||
act(() => ReactDOM.unmountComponentAtNode(container));
|
||||
expect(print(store)).toBe('');
|
||||
}
|
||||
|
||||
// 2. Verify check Suspense can render same steps as initial fallback content.
|
||||
for (let i = 0; i < steps.length; i++) {
|
||||
act(() =>
|
||||
ReactDOM.render(
|
||||
<Root>
|
||||
<X />
|
||||
<React.Suspense fallback={steps[i]}>
|
||||
<Z />
|
||||
<Never />
|
||||
<Z />
|
||||
</React.Suspense>
|
||||
<Y />
|
||||
</Root>,
|
||||
container
|
||||
)
|
||||
);
|
||||
expect(print(store)).toEqual(snapshots[i]);
|
||||
act(() => ReactDOM.unmountComponentAtNode(container));
|
||||
expect(print(store)).toBe('');
|
||||
}
|
||||
|
||||
// 3. Verify we can update from each step to each step in primary mode.
|
||||
for (let i = 0; i < steps.length; i++) {
|
||||
for (let j = 0; j < steps.length; j++) {
|
||||
// Always start with a fresh container and steps[i].
|
||||
container = document.createElement('div');
|
||||
act(() =>
|
||||
ReactDOM.render(
|
||||
<Root>
|
||||
<X />
|
||||
<React.Suspense fallback={z}>{steps[i]}</React.Suspense>
|
||||
<Y />
|
||||
</Root>,
|
||||
container
|
||||
)
|
||||
);
|
||||
expect(print(store)).toEqual(snapshots[i]);
|
||||
// Re-render with steps[j].
|
||||
act(() =>
|
||||
ReactDOM.render(
|
||||
<Root>
|
||||
<X />
|
||||
<React.Suspense fallback={z}>{steps[j]}</React.Suspense>
|
||||
<Y />
|
||||
</Root>,
|
||||
container
|
||||
)
|
||||
);
|
||||
// Verify the successful transition to steps[j].
|
||||
expect(print(store)).toEqual(snapshots[j]);
|
||||
// Clean up after every iteration.
|
||||
act(() => ReactDOM.unmountComponentAtNode(container));
|
||||
expect(print(store)).toBe('');
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: fix the bugs
|
||||
// // 4. Verify we can update from each step to each step in fallback mode.
|
||||
// for (let i = 0; i < steps.length; i++) {
|
||||
// for (let j = 0; j < steps.length; j++) {
|
||||
// // Always start with a fresh container and steps[i].
|
||||
// container = document.createElement('div');
|
||||
// act(() => ReactDOM.render(
|
||||
// <Root>
|
||||
// <X />
|
||||
// <React.Suspense fallback={steps[i]}>
|
||||
// <Z />
|
||||
// <Never />
|
||||
// <Z />
|
||||
// </React.Suspense>
|
||||
// <Y />
|
||||
// </Root>,
|
||||
// container
|
||||
// ));
|
||||
// expect(print(store)).toEqual(snapshots[i]);
|
||||
// // Re-render with steps[j].
|
||||
// act(() => ReactDOM.render(
|
||||
// <Root>
|
||||
// <X />
|
||||
// <React.Suspense fallback={steps[j]}>
|
||||
// <Z />
|
||||
// <Never />
|
||||
// <Z />
|
||||
// </React.Suspense>
|
||||
// <Y />
|
||||
// </Root>,
|
||||
// container
|
||||
// ));
|
||||
// // Verify the successful transition to steps[j].
|
||||
// expect(print(store)).toEqual(snapshots[j]);
|
||||
// // Clean up after every iteration.
|
||||
// act(() => ReactDOM.unmountComponentAtNode(container));
|
||||
// expect(print(store)).toBe('');
|
||||
// }
|
||||
// }
|
||||
|
||||
// 5. Verify we can update from each step to each step when moving primary -> fallback.
|
||||
for (let i = 0; i < steps.length; i++) {
|
||||
for (let j = 0; j < steps.length; j++) {
|
||||
// Always start with a fresh container and steps[i].
|
||||
container = document.createElement('div');
|
||||
act(() =>
|
||||
ReactDOM.render(
|
||||
<Root>
|
||||
<X />
|
||||
<React.Suspense fallback={z}>{steps[i]}</React.Suspense>
|
||||
<Y />
|
||||
</Root>,
|
||||
container
|
||||
)
|
||||
);
|
||||
expect(print(store)).toEqual(snapshots[i]);
|
||||
// Re-render with steps[j].
|
||||
act(() =>
|
||||
ReactDOM.render(
|
||||
<Root>
|
||||
<X />
|
||||
<React.Suspense fallback={steps[j]}>
|
||||
<Z />
|
||||
<Never />
|
||||
<Z />
|
||||
</React.Suspense>
|
||||
<Y />
|
||||
</Root>,
|
||||
container
|
||||
)
|
||||
);
|
||||
// Verify the successful transition to steps[j].
|
||||
expect(print(store)).toEqual(snapshots[j]);
|
||||
// Clean up after every iteration.
|
||||
act(() => ReactDOM.unmountComponentAtNode(container));
|
||||
expect(print(store)).toBe('');
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: fix the bugs
|
||||
// 6. Verify we can update from each step to each step when moving fallback -> primary.
|
||||
// for (let i = 0; i < steps.length; i++) {
|
||||
// for (let j = 0; j < steps.length; j++) {
|
||||
// // Always start with a fresh container and steps[i].
|
||||
// container = document.createElement('div');
|
||||
// act(() => ReactDOM.render(
|
||||
// <Root>
|
||||
// <X />
|
||||
// <React.Suspense fallback={steps[i]}>
|
||||
// <Z />
|
||||
// <Never />
|
||||
// <Z />
|
||||
// </React.Suspense>
|
||||
// <Y />
|
||||
// </Root>,
|
||||
// container
|
||||
// ));
|
||||
// expect(print(store)).toEqual(snapshots[i]);
|
||||
// // Re-render with steps[j].
|
||||
// act(() => ReactDOM.render(
|
||||
// <Root>
|
||||
// <X />
|
||||
// <React.Suspense fallback={z}>
|
||||
// {steps[j]}
|
||||
// </React.Suspense>
|
||||
// <Y />
|
||||
// </Root>,
|
||||
// container
|
||||
// ));
|
||||
// // Verify the successful transition to steps[j].
|
||||
// expect(print(store)).toEqual(snapshots[j]);
|
||||
// // Clean up after every iteration.
|
||||
// act(() => ReactDOM.unmountComponentAtNode(container));
|
||||
// expect(print(store)).toBe('');
|
||||
// }
|
||||
// }
|
||||
|
||||
// TODO: fix the bugs
|
||||
// 7. Verify we can update from each step to each step when toggling Suspense.
|
||||
// for (let i = 0; i < steps.length; i++) {
|
||||
// for (let j = 0; j < steps.length; j++) {
|
||||
// // Always start with a fresh container and steps[i].
|
||||
// container = document.createElement('div');
|
||||
// act(() => ReactDOM.render(
|
||||
// <Root>
|
||||
// <X />
|
||||
// <React.Suspense fallback={steps[j]}>
|
||||
// {steps[i]}
|
||||
// </React.Suspense>
|
||||
// <Y />
|
||||
// </Root>,
|
||||
// container
|
||||
// ));
|
||||
|
||||
// // We get ID from the index in the tree above:
|
||||
// // Root, X, Suspense, ...
|
||||
// // ^ (index is 2)
|
||||
// const suspenseID = store.getElementIDAtIndex(2);
|
||||
|
||||
// // Force fallback.
|
||||
// expect(print(store)).toEqual(snapshots[i]);
|
||||
// act(() => {
|
||||
// const suspenseID = store.getElementIDAtIndex(2);
|
||||
// bridge.send('overrideSuspense', {
|
||||
// id: suspenseID,
|
||||
// rendererID: store.getRendererIDForElement(suspenseID),
|
||||
// forceFallback: true
|
||||
// });
|
||||
// })
|
||||
// expect(print(store)).toEqual(snapshots[j]);
|
||||
|
||||
// // Stop forcing fallback.
|
||||
// act(() => {
|
||||
// bridge.send('overrideSuspense', {
|
||||
// id: suspenseID,
|
||||
// rendererID: store.getRendererIDForElement(suspenseID),
|
||||
// forceFallback: false
|
||||
// });
|
||||
// })
|
||||
// expect(print(store)).toEqual(snapshots[i]);
|
||||
|
||||
// // Trigger actual fallback.
|
||||
// act(() => ReactDOM.render(
|
||||
// <Root>
|
||||
// <X />
|
||||
// <React.Suspense fallback={steps[j]}>
|
||||
// <Z />
|
||||
// <Never />
|
||||
// <Z />
|
||||
// </React.Suspense>
|
||||
// <Y />
|
||||
// </Root>,
|
||||
// container
|
||||
// ));
|
||||
// expect(print(store)).toEqual(snapshots[j]);
|
||||
|
||||
// // Force fallback while we're in fallback mode.
|
||||
// act(() => {
|
||||
// bridge.send('overrideSuspense', {
|
||||
// id: suspenseID,
|
||||
// rendererID: store.getRendererIDForElement(suspenseID),
|
||||
// forceFallback: true
|
||||
// });
|
||||
// })
|
||||
// // Keep seeing fallback content.
|
||||
// expect(print(store)).toEqual(snapshots[j]);
|
||||
|
||||
// // Switch to primary mode.
|
||||
// act(() => ReactDOM.render(
|
||||
// <Root>
|
||||
// <X />
|
||||
// <React.Suspense fallback={steps[j]}>
|
||||
// {steps[i]}
|
||||
// </React.Suspense>
|
||||
// <Y />
|
||||
// </Root>,
|
||||
// container
|
||||
// ));
|
||||
// // Fallback is still forced though.
|
||||
// expect(print(store)).toEqual(snapshots[j]);
|
||||
|
||||
// // Stop forcing fallback. This reverts to primary content.
|
||||
// act(() => {
|
||||
// bridge.send('overrideSuspense', {
|
||||
// id: suspenseID,
|
||||
// rendererID: store.getRendererIDForElement(suspenseID),
|
||||
// forceFallback: false
|
||||
// });
|
||||
// })
|
||||
// // Now we see primary content.
|
||||
// expect(print(store)).toEqual(snapshots[i]);
|
||||
|
||||
// // Clean up after every iteration.
|
||||
// act(() => ReactDOM.unmountComponentAtNode(container));
|
||||
// expect(print(store)).toBe('');
|
||||
// }
|
||||
// }
|
||||
|
||||
// TODO:
|
||||
// Test Concurrent Mode
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,694 @@
|
||||
// @flow
|
||||
|
||||
describe('StoreStress', () => {
|
||||
let React;
|
||||
let ReactDOM;
|
||||
let TestUtils;
|
||||
let bridge;
|
||||
let store;
|
||||
let print;
|
||||
|
||||
const act = (callback: Function) => {
|
||||
TestUtils.act(() => {
|
||||
callback();
|
||||
});
|
||||
jest.runAllTimers(); // Flush Bridge operations
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
bridge = global.bridge;
|
||||
store = global.store;
|
||||
|
||||
React = require('react');
|
||||
ReactDOM = require('react-dom');
|
||||
TestUtils = require('react-dom/test-utils');
|
||||
|
||||
print = require('./storeSerializer').print;
|
||||
});
|
||||
|
||||
// This is a stress test for the tree mount/update/unmount traversal.
|
||||
// It renders different trees that should produce the same output.
|
||||
it('should handle a stress test with different tree operations', () => {
|
||||
let setShowX;
|
||||
const A = () => 'a';
|
||||
const B = () => 'b';
|
||||
const C = () => {
|
||||
// We'll be manually flipping this component back and forth in the test.
|
||||
// We only do this for a single node in order to verify that DevTools
|
||||
// can handle a subtree switching alternates while other subtrees are memoized.
|
||||
let [showX, _setShowX] = React.useState(false);
|
||||
setShowX = _setShowX;
|
||||
return showX ? <X /> : 'c';
|
||||
};
|
||||
const D = () => 'd';
|
||||
const E = () => 'e';
|
||||
const X = () => 'x';
|
||||
const a = <A key="a" />;
|
||||
const b = <B key="b" />;
|
||||
const c = <C key="c" />;
|
||||
const d = <D key="d" />;
|
||||
const e = <E key="e" />;
|
||||
|
||||
function Parent({ children }) {
|
||||
return children;
|
||||
}
|
||||
|
||||
// 1. Render a normal version of [a, b, c, d, e].
|
||||
let container = document.createElement('div');
|
||||
act(() => ReactDOM.render(<Parent>{[a, b, c, d, e]}</Parent>, container));
|
||||
expect(store).toMatchSnapshot('1: abcde');
|
||||
expect(container.textContent).toMatch('abcde');
|
||||
const snapshotForABCDE = print(store);
|
||||
|
||||
// 2. Render a version where <C /> renders an <X /> child instead of 'c'.
|
||||
// This is how we'll test an update to a single component.
|
||||
act(() => {
|
||||
setShowX(true);
|
||||
});
|
||||
expect(store).toMatchSnapshot('2: abxde');
|
||||
expect(container.textContent).toMatch('abxde');
|
||||
const snapshotForABXDE = print(store);
|
||||
|
||||
// 3. Verify flipping it back produces the original result.
|
||||
act(() => {
|
||||
setShowX(false);
|
||||
});
|
||||
expect(container.textContent).toMatch('abcde');
|
||||
expect(print(store)).toBe(snapshotForABCDE);
|
||||
|
||||
// 4. Clean up.
|
||||
act(() => ReactDOM.unmountComponentAtNode(container));
|
||||
expect(print(store)).toBe('');
|
||||
|
||||
// Now comes the interesting part.
|
||||
// All of these cases are equivalent to [a, b, c, d, e] in output.
|
||||
// We'll verify that DevTools produces the same snapshots for them.
|
||||
// These cases are picked so that rendering them sequentially in the same
|
||||
// container results in a combination of mounts, updates, unmounts, and reorders.
|
||||
// prettier-ignore
|
||||
let cases = [
|
||||
[a, b, c, d, e],
|
||||
[[a], b, c, d, e],
|
||||
[[a, b], c, d, e],
|
||||
[[a, b], c, [d, e]],
|
||||
[[a, b], c, [d, '', e]],
|
||||
[[a], b, c, d, [e]],
|
||||
[a, b, [[c]], d, e],
|
||||
[[a, ''], [b], [c], [d], [e]],
|
||||
[a, b, [c, [d, ['', e]]]],
|
||||
[a, b, c, d, e],
|
||||
[<div key="0">{a}</div>, b, c, d, e],
|
||||
[<div key="0">{a}{b}</div>, c, d, e],
|
||||
[<div key="0">{a}{b}</div>, c, <div key="1">{d}{e}</div>],
|
||||
[<div key="1">{a}{b}</div>, c, <div key="0">{d}{e}</div>],
|
||||
[<div key="0">{a}{b}</div>, c, <div key="1">{d}{e}</div>],
|
||||
[<div key="2">{a}{b}</div>, c, <div key="3">{d}{e}</div>],
|
||||
[<span key="0">{a}</span>, b, c, d, [e]],
|
||||
[a, b, <span key="0"><span>{c}</span></span>, d, e],
|
||||
[<div key="0">{a}</div>, [b], <span key="1">{c}</span>, [d], <div key="2">{e}</div>],
|
||||
[a, b, [c, <div key="0">{d}<span>{e}</span></div>], ''],
|
||||
[a, [[]], b, c, [d, [[]], e]],
|
||||
[[[a, b, c, d], e]],
|
||||
[a, b, c, d, e]
|
||||
];
|
||||
|
||||
// 5. Test fresh mount for each case.
|
||||
for (let i = 0; i < cases.length; i++) {
|
||||
// Ensure fresh mount.
|
||||
container = document.createElement('div');
|
||||
|
||||
// Verify mounting 'abcde'.
|
||||
act(() => ReactDOM.render(<Parent>{cases[i]}</Parent>, container));
|
||||
expect(container.textContent).toMatch('abcde');
|
||||
expect(print(store)).toEqual(snapshotForABCDE);
|
||||
|
||||
// Verify switching to 'abxde'.
|
||||
act(() => {
|
||||
setShowX(true);
|
||||
});
|
||||
expect(container.textContent).toMatch('abxde');
|
||||
expect(print(store)).toBe(snapshotForABXDE);
|
||||
|
||||
// Verify switching back to 'abcde'.
|
||||
act(() => {
|
||||
setShowX(false);
|
||||
});
|
||||
expect(container.textContent).toMatch('abcde');
|
||||
expect(print(store)).toBe(snapshotForABCDE);
|
||||
|
||||
// Clean up.
|
||||
act(() => ReactDOM.unmountComponentAtNode(container));
|
||||
expect(print(store)).toBe('');
|
||||
}
|
||||
|
||||
// 6. Verify *updates* by reusing the container between iterations.
|
||||
// There'll be no unmounting until the very end.
|
||||
container = document.createElement('div');
|
||||
for (let i = 0; i < cases.length; i++) {
|
||||
// Verify mounting 'abcde'.
|
||||
act(() => ReactDOM.render(<Parent>{cases[i]}</Parent>, container));
|
||||
expect(container.textContent).toMatch('abcde');
|
||||
expect(print(store)).toEqual(snapshotForABCDE);
|
||||
|
||||
// Verify switching to 'abxde'.
|
||||
act(() => {
|
||||
setShowX(true);
|
||||
});
|
||||
expect(container.textContent).toMatch('abxde');
|
||||
expect(print(store)).toBe(snapshotForABXDE);
|
||||
|
||||
// Verify switching back to 'abcde'.
|
||||
act(() => {
|
||||
setShowX(false);
|
||||
});
|
||||
expect(container.textContent).toMatch('abcde');
|
||||
expect(print(store)).toBe(snapshotForABCDE);
|
||||
// Don't unmount. Reuse the container between iterations.
|
||||
}
|
||||
act(() => ReactDOM.unmountComponentAtNode(container));
|
||||
expect(print(store)).toBe('');
|
||||
|
||||
// 7. Same as the previous step, but for Concurrent Mode.
|
||||
container = document.createElement('div');
|
||||
// $FlowFixMe
|
||||
let root = ReactDOM.unstable_createRoot(container);
|
||||
for (let i = 0; i < cases.length; i++) {
|
||||
// Verify mounting 'abcde'.
|
||||
act(() => root.render(<Parent>{cases[i]}</Parent>));
|
||||
expect(container.textContent).toMatch('abcde');
|
||||
expect(print(store)).toEqual(snapshotForABCDE);
|
||||
|
||||
// Verify switching to 'abxde'.
|
||||
act(() => {
|
||||
setShowX(true);
|
||||
});
|
||||
expect(container.textContent).toMatch('abxde');
|
||||
expect(print(store)).toBe(snapshotForABXDE);
|
||||
|
||||
// Verify switching back to 'abcde'.
|
||||
act(() => {
|
||||
setShowX(false);
|
||||
});
|
||||
expect(container.textContent).toMatch('abcde');
|
||||
expect(print(store)).toBe(snapshotForABCDE);
|
||||
// Don't unmount. Reuse the container between iterations.
|
||||
}
|
||||
act(() => root.unmount());
|
||||
expect(print(store)).toBe('');
|
||||
});
|
||||
|
||||
it('should handle stress test with reordering', () => {
|
||||
const A = () => 'a';
|
||||
const B = () => 'b';
|
||||
const C = () => 'c';
|
||||
const D = () => 'd';
|
||||
const E = () => 'e';
|
||||
const a = <A key="a" />;
|
||||
const b = <B key="b" />;
|
||||
const c = <C key="c" />;
|
||||
const d = <D key="d" />;
|
||||
const e = <E key="e" />;
|
||||
|
||||
// prettier-ignore
|
||||
let steps = [
|
||||
a,
|
||||
b,
|
||||
c,
|
||||
d,
|
||||
e,
|
||||
[a],
|
||||
[b],
|
||||
[c],
|
||||
[d],
|
||||
[e],
|
||||
[a, b],
|
||||
[b, a],
|
||||
[b, c],
|
||||
[c, b],
|
||||
[a, c],
|
||||
[c, a],
|
||||
];
|
||||
|
||||
const Root = ({ children }) => {
|
||||
return children;
|
||||
};
|
||||
|
||||
// 1. Capture the expected render result.
|
||||
let snapshots = [];
|
||||
let container = document.createElement('div');
|
||||
for (let i = 0; i < steps.length; i++) {
|
||||
act(() => ReactDOM.render(<Root>{steps[i]}</Root>, container));
|
||||
// We snapshot each step once so it doesn't regress.
|
||||
expect(store).toMatchSnapshot();
|
||||
snapshots.push(print(store));
|
||||
act(() => ReactDOM.unmountComponentAtNode(container));
|
||||
expect(print(store)).toBe('');
|
||||
}
|
||||
|
||||
// 2. Verify that we can update from every step to every other step and back.
|
||||
for (let i = 0; i < steps.length; i++) {
|
||||
for (let j = 0; j < steps.length; j++) {
|
||||
let container = document.createElement('div');
|
||||
act(() => ReactDOM.render(<Root>{steps[i]}</Root>, container));
|
||||
expect(print(store)).toMatch(snapshots[i]);
|
||||
act(() => ReactDOM.render(<Root>{steps[j]}</Root>, container));
|
||||
expect(print(store)).toMatch(snapshots[j]);
|
||||
act(() => ReactDOM.render(<Root>{steps[i]}</Root>, container));
|
||||
expect(print(store)).toMatch(snapshots[i]);
|
||||
act(() => ReactDOM.unmountComponentAtNode(container));
|
||||
expect(print(store)).toBe('');
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Same test as above, but this time we wrap children in a host component.
|
||||
for (let i = 0; i < steps.length; i++) {
|
||||
for (let j = 0; j < steps.length; j++) {
|
||||
let container = document.createElement('div');
|
||||
act(() =>
|
||||
ReactDOM.render(
|
||||
<Root>
|
||||
<div>{steps[i]}</div>
|
||||
</Root>,
|
||||
container
|
||||
)
|
||||
);
|
||||
expect(print(store)).toMatch(snapshots[i]);
|
||||
act(() =>
|
||||
ReactDOM.render(
|
||||
<Root>
|
||||
<div>{steps[j]}</div>
|
||||
</Root>,
|
||||
container
|
||||
)
|
||||
);
|
||||
expect(print(store)).toMatch(snapshots[j]);
|
||||
act(() =>
|
||||
ReactDOM.render(
|
||||
<Root>
|
||||
<div>{steps[i]}</div>
|
||||
</Root>,
|
||||
container
|
||||
)
|
||||
);
|
||||
expect(print(store)).toMatch(snapshots[i]);
|
||||
act(() => ReactDOM.unmountComponentAtNode(container));
|
||||
expect(print(store)).toBe('');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('should handle a stress test for Suspense', async () => {
|
||||
const A = () => 'a';
|
||||
const B = () => 'b';
|
||||
const C = () => 'c';
|
||||
const X = () => 'x';
|
||||
const Y = () => 'y';
|
||||
const Z = () => 'z';
|
||||
const a = <A key="a" />;
|
||||
const b = <B key="b" />;
|
||||
const c = <C key="c" />;
|
||||
const z = <Z key="z" />;
|
||||
|
||||
// prettier-ignore
|
||||
const steps = [
|
||||
a,
|
||||
[a],
|
||||
[a, b, c],
|
||||
[c, b, a],
|
||||
[c, null, a],
|
||||
<React.Fragment>{c}{a}</React.Fragment>,
|
||||
<div>{c}{a}</div>,
|
||||
<div><span>{a}</span>{b}</div>,
|
||||
[[a]],
|
||||
null,
|
||||
b,
|
||||
a
|
||||
];
|
||||
|
||||
const Never = () => {
|
||||
throw new Promise(() => {});
|
||||
};
|
||||
|
||||
const Root = ({ children }) => {
|
||||
return children;
|
||||
};
|
||||
|
||||
// 1. For each step, check Suspense can render them as initial primary content.
|
||||
// This is the only step where we use Jest snapshots.
|
||||
let snapshots = [];
|
||||
let container = document.createElement('div');
|
||||
for (let i = 0; i < steps.length; i++) {
|
||||
act(() =>
|
||||
ReactDOM.render(
|
||||
<Root>
|
||||
<X />
|
||||
<React.Suspense fallback={z}>{steps[i]}</React.Suspense>
|
||||
<Y />
|
||||
</Root>,
|
||||
container
|
||||
)
|
||||
);
|
||||
// We snapshot each step once so it doesn't regress.
|
||||
expect(store).toMatchSnapshot();
|
||||
snapshots.push(print(store));
|
||||
act(() => ReactDOM.unmountComponentAtNode(container));
|
||||
expect(print(store)).toBe('');
|
||||
}
|
||||
|
||||
// 2. Verify check Suspense can render same steps as initial fallback content.
|
||||
for (let i = 0; i < steps.length; i++) {
|
||||
act(() =>
|
||||
ReactDOM.render(
|
||||
<Root>
|
||||
<X />
|
||||
<React.Suspense fallback={steps[i]}>
|
||||
<Z />
|
||||
<Never />
|
||||
<Z />
|
||||
</React.Suspense>
|
||||
<Y />
|
||||
</Root>,
|
||||
container
|
||||
)
|
||||
);
|
||||
expect(print(store)).toEqual(snapshots[i]);
|
||||
act(() => ReactDOM.unmountComponentAtNode(container));
|
||||
expect(print(store)).toBe('');
|
||||
}
|
||||
|
||||
// 3. Verify we can update from each step to each step in primary mode.
|
||||
for (let i = 0; i < steps.length; i++) {
|
||||
for (let j = 0; j < steps.length; j++) {
|
||||
// Always start with a fresh container and steps[i].
|
||||
container = document.createElement('div');
|
||||
act(() =>
|
||||
ReactDOM.render(
|
||||
<Root>
|
||||
<X />
|
||||
<React.Suspense fallback={z}>{steps[i]}</React.Suspense>
|
||||
<Y />
|
||||
</Root>,
|
||||
container
|
||||
)
|
||||
);
|
||||
expect(print(store)).toEqual(snapshots[i]);
|
||||
// Re-render with steps[j].
|
||||
act(() =>
|
||||
ReactDOM.render(
|
||||
<Root>
|
||||
<X />
|
||||
<React.Suspense fallback={z}>{steps[j]}</React.Suspense>
|
||||
<Y />
|
||||
</Root>,
|
||||
container
|
||||
)
|
||||
);
|
||||
// Verify the successful transition to steps[j].
|
||||
expect(print(store)).toEqual(snapshots[j]);
|
||||
// Check that we can transition back again.
|
||||
act(() =>
|
||||
ReactDOM.render(
|
||||
<Root>
|
||||
<X />
|
||||
<React.Suspense fallback={z}>{steps[i]}</React.Suspense>
|
||||
<Y />
|
||||
</Root>,
|
||||
container
|
||||
)
|
||||
);
|
||||
expect(print(store)).toEqual(snapshots[i]);
|
||||
// Clean up after every iteration.
|
||||
act(() => ReactDOM.unmountComponentAtNode(container));
|
||||
expect(print(store)).toBe('');
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Verify we can update from each step to each step in fallback mode.
|
||||
for (let i = 0; i < steps.length; i++) {
|
||||
for (let j = 0; j < steps.length; j++) {
|
||||
// Always start with a fresh container and steps[i].
|
||||
container = document.createElement('div');
|
||||
act(() =>
|
||||
ReactDOM.render(
|
||||
<Root>
|
||||
<X />
|
||||
<React.Suspense fallback={steps[i]}>
|
||||
<Z />
|
||||
<Never />
|
||||
<Z />
|
||||
</React.Suspense>
|
||||
<Y />
|
||||
</Root>,
|
||||
container
|
||||
)
|
||||
);
|
||||
expect(print(store)).toEqual(snapshots[i]);
|
||||
// Re-render with steps[j].
|
||||
act(() =>
|
||||
ReactDOM.render(
|
||||
<Root>
|
||||
<X />
|
||||
<React.Suspense fallback={steps[j]}>
|
||||
<Z />
|
||||
<Never />
|
||||
<Z />
|
||||
</React.Suspense>
|
||||
<Y />
|
||||
</Root>,
|
||||
container
|
||||
)
|
||||
);
|
||||
// Verify the successful transition to steps[j].
|
||||
expect(print(store)).toEqual(snapshots[j]);
|
||||
// Check that we can transition back again.
|
||||
act(() =>
|
||||
ReactDOM.render(
|
||||
<Root>
|
||||
<X />
|
||||
<React.Suspense fallback={steps[i]}>
|
||||
<Z />
|
||||
<Never />
|
||||
<Z />
|
||||
</React.Suspense>
|
||||
<Y />
|
||||
</Root>,
|
||||
container
|
||||
)
|
||||
);
|
||||
expect(print(store)).toEqual(snapshots[i]);
|
||||
// Clean up after every iteration.
|
||||
act(() => ReactDOM.unmountComponentAtNode(container));
|
||||
expect(print(store)).toBe('');
|
||||
}
|
||||
}
|
||||
|
||||
// 5. Verify we can update from each step to each step when moving primary -> fallback.
|
||||
for (let i = 0; i < steps.length; i++) {
|
||||
for (let j = 0; j < steps.length; j++) {
|
||||
// Always start with a fresh container and steps[i].
|
||||
container = document.createElement('div');
|
||||
act(() =>
|
||||
ReactDOM.render(
|
||||
<Root>
|
||||
<X />
|
||||
<React.Suspense fallback={z}>{steps[i]}</React.Suspense>
|
||||
<Y />
|
||||
</Root>,
|
||||
container
|
||||
)
|
||||
);
|
||||
expect(print(store)).toEqual(snapshots[i]);
|
||||
// Re-render with steps[j].
|
||||
act(() =>
|
||||
ReactDOM.render(
|
||||
<Root>
|
||||
<X />
|
||||
<React.Suspense fallback={steps[j]}>
|
||||
<Z />
|
||||
<Never />
|
||||
<Z />
|
||||
</React.Suspense>
|
||||
<Y />
|
||||
</Root>,
|
||||
container
|
||||
)
|
||||
);
|
||||
// Verify the successful transition to steps[j].
|
||||
expect(print(store)).toEqual(snapshots[j]);
|
||||
// Check that we can transition back again.
|
||||
act(() =>
|
||||
ReactDOM.render(
|
||||
<Root>
|
||||
<X />
|
||||
<React.Suspense fallback={z}>{steps[i]}</React.Suspense>
|
||||
<Y />
|
||||
</Root>,
|
||||
container
|
||||
)
|
||||
);
|
||||
expect(print(store)).toEqual(snapshots[i]);
|
||||
// Clean up after every iteration.
|
||||
act(() => ReactDOM.unmountComponentAtNode(container));
|
||||
expect(print(store)).toBe('');
|
||||
}
|
||||
}
|
||||
|
||||
// 6. Verify we can update from each step to each step when moving fallback -> primary.
|
||||
for (let i = 0; i < steps.length; i++) {
|
||||
for (let j = 0; j < steps.length; j++) {
|
||||
// Always start with a fresh container and steps[i].
|
||||
container = document.createElement('div');
|
||||
act(() =>
|
||||
ReactDOM.render(
|
||||
<Root>
|
||||
<X />
|
||||
<React.Suspense fallback={steps[i]}>
|
||||
<Z />
|
||||
<Never />
|
||||
<Z />
|
||||
</React.Suspense>
|
||||
<Y />
|
||||
</Root>,
|
||||
container
|
||||
)
|
||||
);
|
||||
expect(print(store)).toEqual(snapshots[i]);
|
||||
// Re-render with steps[j].
|
||||
act(() =>
|
||||
ReactDOM.render(
|
||||
<Root>
|
||||
<X />
|
||||
<React.Suspense fallback={z}>{steps[j]}</React.Suspense>
|
||||
<Y />
|
||||
</Root>,
|
||||
container
|
||||
)
|
||||
);
|
||||
// Verify the successful transition to steps[j].
|
||||
expect(print(store)).toEqual(snapshots[j]);
|
||||
// Check that we can transition back again.
|
||||
act(() =>
|
||||
ReactDOM.render(
|
||||
<Root>
|
||||
<X />
|
||||
<React.Suspense fallback={steps[i]}>
|
||||
<Z />
|
||||
<Never />
|
||||
<Z />
|
||||
</React.Suspense>
|
||||
<Y />
|
||||
</Root>,
|
||||
container
|
||||
)
|
||||
);
|
||||
expect(print(store)).toEqual(snapshots[i]);
|
||||
// Clean up after every iteration.
|
||||
act(() => ReactDOM.unmountComponentAtNode(container));
|
||||
expect(print(store)).toBe('');
|
||||
}
|
||||
}
|
||||
|
||||
// 7. Verify we can update from each step to each step when toggling Suspense.
|
||||
for (let i = 0; i < steps.length; i++) {
|
||||
for (let j = 0; j < steps.length; j++) {
|
||||
// Always start with a fresh container and steps[i].
|
||||
container = document.createElement('div');
|
||||
act(() =>
|
||||
ReactDOM.render(
|
||||
<Root>
|
||||
<X />
|
||||
<React.Suspense fallback={steps[j]}>{steps[i]}</React.Suspense>
|
||||
<Y />
|
||||
</Root>,
|
||||
container
|
||||
)
|
||||
);
|
||||
|
||||
// We get ID from the index in the tree above:
|
||||
// Root, X, Suspense, ...
|
||||
// ^ (index is 2)
|
||||
const suspenseID = store.getElementIDAtIndex(2);
|
||||
|
||||
// Force fallback.
|
||||
expect(print(store)).toEqual(snapshots[i]);
|
||||
act(() => {
|
||||
const suspenseID = store.getElementIDAtIndex(2);
|
||||
bridge.send('overrideSuspense', {
|
||||
id: suspenseID,
|
||||
rendererID: store.getRendererIDForElement(suspenseID),
|
||||
forceFallback: true,
|
||||
});
|
||||
});
|
||||
expect(print(store)).toEqual(snapshots[j]);
|
||||
|
||||
// Stop forcing fallback.
|
||||
act(() => {
|
||||
bridge.send('overrideSuspense', {
|
||||
id: suspenseID,
|
||||
rendererID: store.getRendererIDForElement(suspenseID),
|
||||
forceFallback: false,
|
||||
});
|
||||
});
|
||||
expect(print(store)).toEqual(snapshots[i]);
|
||||
|
||||
// Trigger actual fallback.
|
||||
act(() =>
|
||||
ReactDOM.render(
|
||||
<Root>
|
||||
<X />
|
||||
<React.Suspense fallback={steps[j]}>
|
||||
<Z />
|
||||
<Never />
|
||||
<Z />
|
||||
</React.Suspense>
|
||||
<Y />
|
||||
</Root>,
|
||||
container
|
||||
)
|
||||
);
|
||||
expect(print(store)).toEqual(snapshots[j]);
|
||||
|
||||
// Force fallback while we're in fallback mode.
|
||||
act(() => {
|
||||
bridge.send('overrideSuspense', {
|
||||
id: suspenseID,
|
||||
rendererID: store.getRendererIDForElement(suspenseID),
|
||||
forceFallback: true,
|
||||
});
|
||||
});
|
||||
// Keep seeing fallback content.
|
||||
expect(print(store)).toEqual(snapshots[j]);
|
||||
|
||||
// Switch to primary mode.
|
||||
act(() =>
|
||||
ReactDOM.render(
|
||||
<Root>
|
||||
<X />
|
||||
<React.Suspense fallback={steps[j]}>{steps[i]}</React.Suspense>
|
||||
<Y />
|
||||
</Root>,
|
||||
container
|
||||
)
|
||||
);
|
||||
// Fallback is still forced though.
|
||||
expect(print(store)).toEqual(snapshots[j]);
|
||||
|
||||
// Stop forcing fallback. This reverts to primary content.
|
||||
act(() => {
|
||||
bridge.send('overrideSuspense', {
|
||||
id: suspenseID,
|
||||
rendererID: store.getRendererIDForElement(suspenseID),
|
||||
forceFallback: false,
|
||||
});
|
||||
});
|
||||
// Now we see primary content.
|
||||
expect(print(store)).toEqual(snapshots[i]);
|
||||
|
||||
// Clean up after every iteration.
|
||||
act(() => ReactDOM.unmountComponentAtNode(container));
|
||||
expect(print(store)).toBe('');
|
||||
}
|
||||
}
|
||||
// TODO: Test Concurrent Mode
|
||||
});
|
||||
});
|
||||
+167
-149
@@ -828,69 +828,62 @@ export function attach(
|
||||
}
|
||||
}
|
||||
|
||||
function maybeRecordUpdate(fiber: Fiber, hasChildOrderChanged: boolean) {
|
||||
if (__DEBUG__) {
|
||||
debug('maybeRecordUpdate()', fiber);
|
||||
}
|
||||
function recordTreeDuration(fiber: Fiber) {
|
||||
const id = getFiberID(getPrimaryFiber(fiber));
|
||||
const { actualDuration, treeBaseDuration } = fiber;
|
||||
|
||||
const isProfilingSupported = fiber.hasOwnProperty('treeBaseDuration');
|
||||
if (isProfilingSupported) {
|
||||
const id = getFiberID(getPrimaryFiber(fiber));
|
||||
const { actualDuration, treeBaseDuration } = fiber;
|
||||
idToTreeBaseDurationMap.set(id, fiber.treeBaseDuration);
|
||||
|
||||
idToTreeBaseDurationMap.set(id, fiber.treeBaseDuration);
|
||||
if (isProfiling) {
|
||||
if (treeBaseDuration !== fiber.alternate.treeBaseDuration) {
|
||||
// Tree base duration updates are included in the operations typed array.
|
||||
// So we have to convert them from milliseconds to microseconds so we can send them as ints.
|
||||
const treeBaseDuration = Math.floor(fiber.treeBaseDuration * 1000);
|
||||
|
||||
if (isProfiling) {
|
||||
if (treeBaseDuration !== fiber.alternate.treeBaseDuration) {
|
||||
// Tree base duration updates are included in the operations typed array.
|
||||
// So we have to convert them from milliseconds to microseconds so we can send them as ints.
|
||||
const treeBaseDuration = Math.floor(fiber.treeBaseDuration * 1000);
|
||||
beginNextOperation(3);
|
||||
nextOperation[0] = TREE_OPERATION_UPDATE_TREE_BASE_DURATION;
|
||||
nextOperation[1] = getFiberID(getPrimaryFiber(fiber));
|
||||
nextOperation[2] = treeBaseDuration;
|
||||
endNextOperation(false);
|
||||
}
|
||||
|
||||
beginNextOperation(3);
|
||||
nextOperation[0] = TREE_OPERATION_UPDATE_TREE_BASE_DURATION;
|
||||
nextOperation[1] = getFiberID(getPrimaryFiber(fiber));
|
||||
nextOperation[2] = treeBaseDuration;
|
||||
endNextOperation(false);
|
||||
}
|
||||
|
||||
if (haveProfilerTimesChanged(fiber.alternate, fiber)) {
|
||||
if (actualDuration > 0) {
|
||||
// If profiling is active, store durations for elements that were rendered during the commit.
|
||||
const metadata = ((currentCommitProfilingMetadata: any): CommitProfilingData);
|
||||
metadata.actualDurations.push(id, actualDuration);
|
||||
metadata.maxActualDuration = Math.max(
|
||||
metadata.maxActualDuration,
|
||||
actualDuration
|
||||
);
|
||||
}
|
||||
if (haveProfilerTimesChanged(fiber.alternate, fiber)) {
|
||||
if (actualDuration > 0) {
|
||||
// If profiling is active, store durations for elements that were rendered during the commit.
|
||||
const metadata = ((currentCommitProfilingMetadata: any): CommitProfilingData);
|
||||
metadata.actualDurations.push(id, actualDuration);
|
||||
metadata.maxActualDuration = Math.max(
|
||||
metadata.maxActualDuration,
|
||||
actualDuration
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function recordResetChildren(fiber: Fiber, childSet: Fiber) {
|
||||
// The frontend only really cares about the displayName, key, and children.
|
||||
// The first two don't really change, so we are only concerned with the order of children here.
|
||||
// This is trickier than a simple comparison though, since certain types of fibers are filtered.
|
||||
if (hasChildOrderChanged) {
|
||||
const nextChildren: Array<number> = [];
|
||||
const nextChildren: Array<number> = [];
|
||||
|
||||
// This is a naive implimentation that shallowly recurses children.
|
||||
// We might want to revisit this if it proves to be too inefficient.
|
||||
let child = fiber.child;
|
||||
while (child !== null) {
|
||||
findReorderedChildrenRecursively(child, nextChildren);
|
||||
child = child.sibling;
|
||||
}
|
||||
|
||||
const numChildren = nextChildren.length;
|
||||
beginNextOperation(3 + numChildren);
|
||||
nextOperation[0] = TREE_OPERATION_RESET_CHILDREN;
|
||||
nextOperation[1] = getFiberID(getPrimaryFiber(fiber));
|
||||
nextOperation[2] = numChildren;
|
||||
for (let i = 0; i < nextChildren.length; i++) {
|
||||
nextOperation[3 + i] = nextChildren[i];
|
||||
}
|
||||
endNextOperation(false);
|
||||
// This is a naive implimentation that shallowly recurses children.
|
||||
// We might want to revisit this if it proves to be too inefficient.
|
||||
let child = childSet;
|
||||
while (child !== null) {
|
||||
findReorderedChildrenRecursively(child, nextChildren);
|
||||
child = child.sibling;
|
||||
}
|
||||
|
||||
const numChildren = nextChildren.length;
|
||||
beginNextOperation(3 + numChildren);
|
||||
nextOperation[0] = TREE_OPERATION_RESET_CHILDREN;
|
||||
nextOperation[1] = getFiberID(getPrimaryFiber(fiber));
|
||||
nextOperation[2] = numChildren;
|
||||
for (let i = 0; i < nextChildren.length; i++) {
|
||||
nextOperation[3 + i] = nextChildren[i];
|
||||
}
|
||||
endNextOperation(false);
|
||||
}
|
||||
|
||||
function findReorderedChildrenRecursively(
|
||||
@@ -908,15 +901,18 @@ export function attach(
|
||||
}
|
||||
}
|
||||
|
||||
// Returns whether closest unfiltered fiber parent needs to reset its child list.
|
||||
function updateFiberRecursively(
|
||||
nextFiber: Fiber,
|
||||
prevFiber: Fiber,
|
||||
parentFiber: Fiber | null
|
||||
) {
|
||||
): boolean {
|
||||
if (__DEBUG__) {
|
||||
debug('updateFiberRecursively()', nextFiber, parentFiber);
|
||||
}
|
||||
|
||||
const shouldIncludeInTree = !shouldFilterFiber(nextFiber);
|
||||
const isSuspense = nextFiber.tag === SuspenseComponent;
|
||||
let shouldResetChildren = false;
|
||||
// The behavior of timed-out Suspense trees is unique.
|
||||
// Rather than unmount the timed out content (and possibly lose important state),
|
||||
// React re-parents this content within a hidden Fragment while the fallback is showing.
|
||||
@@ -924,108 +920,130 @@ export function attach(
|
||||
// It might even result in a bad user experience for e.g. node selection in the Elements panel.
|
||||
// The easiest fix is to strip out the intermediate Fragment fibers,
|
||||
// so the Elements panel and Profiler don't need to special case them.
|
||||
if (nextFiber.tag === SuspenseComponent) {
|
||||
// Suspense components only have a non-null memoizedState if they're timed-out.
|
||||
const prevDidTimeout = prevFiber.memoizedState !== null;
|
||||
const nextDidTimeOut = nextFiber.memoizedState !== null;
|
||||
|
||||
// The logic below is inspired by the codepaths in updateSuspenseComponent()
|
||||
// inside ReactFiberBeginWork in the React source code.
|
||||
if (prevDidTimeout) {
|
||||
if (nextDidTimeOut) {
|
||||
// Fallback -> Fallback:
|
||||
// 1. Reconcile fallback set.
|
||||
const nextFallbackChildSet = nextFiber.child.sibling;
|
||||
// Note: We can't use nextFiber.child.sibling.alternate
|
||||
// because the set is special and alternate may not exist.
|
||||
const prevFallbackChildSet = prevFiber.child.sibling;
|
||||
updateFiberRecursively(
|
||||
nextFallbackChildSet,
|
||||
prevFallbackChildSet,
|
||||
nextFiber
|
||||
);
|
||||
return;
|
||||
} else {
|
||||
// Fallback -> Primary:
|
||||
// 1. Unmount fallback set
|
||||
// Note: don't emulate fallback unmount because React actually did it.
|
||||
// 2. Mount primary set
|
||||
const nextPrimaryChildSet = nextFiber.child;
|
||||
mountFiberRecursively(nextPrimaryChildSet, nextFiber, true);
|
||||
return;
|
||||
// Suspense components only have a non-null memoizedState if they're timed-out.
|
||||
const prevDidTimeout = isSuspense && prevFiber.memoizedState !== null;
|
||||
const nextDidTimeOut = isSuspense && nextFiber.memoizedState !== null;
|
||||
// The logic below is inspired by the codepaths in updateSuspenseComponent()
|
||||
// inside ReactFiberBeginWork in the React source code.
|
||||
if (prevDidTimeout && nextDidTimeOut) {
|
||||
// Fallback -> Fallback:
|
||||
// 1. Reconcile fallback set.
|
||||
const nextFallbackChildSet = nextFiber.child.sibling;
|
||||
// Note: We can't use nextFiber.child.sibling.alternate
|
||||
// because the set is special and alternate may not exist.
|
||||
const prevFallbackChildSet = prevFiber.child.sibling;
|
||||
if (
|
||||
updateFiberRecursively(
|
||||
nextFallbackChildSet,
|
||||
prevFallbackChildSet,
|
||||
nextFiber
|
||||
)
|
||||
) {
|
||||
shouldResetChildren = true;
|
||||
}
|
||||
} else if (prevDidTimeout && !nextDidTimeOut) {
|
||||
// Fallback -> Primary:
|
||||
// 1. Unmount fallback set
|
||||
// Note: don't emulate fallback unmount because React actually did it.
|
||||
// 2. Mount primary set
|
||||
const nextPrimaryChildSet = nextFiber.child;
|
||||
if (nextPrimaryChildSet !== null) {
|
||||
mountFiberRecursively(nextPrimaryChildSet, nextFiber, true);
|
||||
}
|
||||
shouldResetChildren = true;
|
||||
} else if (!prevDidTimeout && nextDidTimeOut) {
|
||||
// Primary -> Fallback:
|
||||
// 1. Hide primary set
|
||||
// This is not a real unmount, so it won't get reported by React.
|
||||
// By this point it's *too late* to find the previous primary child set
|
||||
// so we'll just tell the store to "forget" about those children.
|
||||
// They might "resurface" later when we switch to primary content,
|
||||
// but from the store's point of view they will be a new tree.
|
||||
recordRecursiveRemoveChildren(nextFiber);
|
||||
// 2. Mount fallback set
|
||||
const nextFallbackChildSet = nextFiber.child.sibling;
|
||||
mountFiberRecursively(nextFallbackChildSet, nextFiber, true);
|
||||
shouldResetChildren = true;
|
||||
} else {
|
||||
// Common case: Primary -> Primary.
|
||||
// This is the same codepath as for non-Suspense fibers.
|
||||
if (nextFiber.child !== prevFiber.child) {
|
||||
// If the first child is different, we need to traverse them.
|
||||
// Each next child will be either a new child (mount) or an alternate (update).
|
||||
let nextChild = nextFiber.child;
|
||||
let prevChildAtSameIndex = prevFiber.child;
|
||||
while (nextChild) {
|
||||
// We already know children will be referentially different because
|
||||
// they are either new mounts or alternates of previous children.
|
||||
// Schedule updates and mounts depending on whether alternates exist.
|
||||
// We don't track deletions here because they are reported separately.
|
||||
if (nextChild.alternate) {
|
||||
const prevChild = nextChild.alternate;
|
||||
if (
|
||||
updateFiberRecursively(
|
||||
nextChild,
|
||||
prevChild,
|
||||
shouldIncludeInTree ? nextFiber : parentFiber
|
||||
)
|
||||
) {
|
||||
// If a nested tree child order changed but it can't handle its own
|
||||
// child order invalidation (e.g. because it's filtered out like host nodes),
|
||||
// propagate the need to reset child order upwards to this Fiber.
|
||||
shouldResetChildren = true;
|
||||
}
|
||||
// However we also keep track if the order of the children matches
|
||||
// the previous order. They are always different referentially, but
|
||||
// if the instances line up conceptually we'll want to know that.
|
||||
if (prevChild !== prevChildAtSameIndex) {
|
||||
shouldResetChildren = true;
|
||||
}
|
||||
} else {
|
||||
mountFiberRecursively(
|
||||
nextChild,
|
||||
shouldIncludeInTree ? nextFiber : parentFiber
|
||||
);
|
||||
shouldResetChildren = true;
|
||||
}
|
||||
// Try the next child.
|
||||
nextChild = nextChild.sibling;
|
||||
// Advance the pointer in the previous list so that we can
|
||||
// keep comparing if they line up.
|
||||
if (!shouldResetChildren && prevChildAtSameIndex !== null) {
|
||||
prevChildAtSameIndex = prevChildAtSameIndex.sibling;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (nextDidTimeOut) {
|
||||
// Primary -> Fallback:
|
||||
// 1. Hide primary set
|
||||
// This is not a real unmount, so it won't get reported by React.
|
||||
// By this point it's *too late* to find the previous primary child set
|
||||
// so we'll just tell the store to "forget" about those children.
|
||||
// They might "resurface" later when we switch to primary content,
|
||||
// but from the store's point of view they will be a new tree.
|
||||
recordRecursiveRemoveChildren(nextFiber);
|
||||
// 2. Mount fallback set
|
||||
const nextFallbackChildSet = nextFiber.child.sibling;
|
||||
mountFiberRecursively(nextFallbackChildSet, nextFiber, true);
|
||||
return;
|
||||
} else {
|
||||
// Primary -> Primary:
|
||||
// 1. Reconcile primary set.
|
||||
// Note: no return so we can passthrough to the logic below.
|
||||
// If we have no more children, but used to, they don't line up.
|
||||
if (prevChildAtSameIndex !== null) {
|
||||
shouldResetChildren = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const shouldIncludeInTree = !shouldFilterFiber(nextFiber);
|
||||
let hasChildOrderChanged = false;
|
||||
if (nextFiber.child !== prevFiber.child) {
|
||||
// If the first child is different, we need to traverse them.
|
||||
// Each next child will be either a new child (mount) or an alternate (update).
|
||||
let nextChild = nextFiber.child;
|
||||
let prevChildAtSameIndex = prevFiber.child;
|
||||
while (nextChild) {
|
||||
// We already know children will be referentially different because
|
||||
// they are either new mounts or alternates of previous children.
|
||||
// Schedule updates and mounts depending on whether alternates exist.
|
||||
// We don't track deletions here because they are reported separately.
|
||||
if (nextChild.alternate) {
|
||||
const prevChild = nextChild.alternate;
|
||||
updateFiberRecursively(
|
||||
nextChild,
|
||||
prevChild,
|
||||
shouldIncludeInTree ? nextFiber : parentFiber
|
||||
);
|
||||
// However we also keep track if the order of the children matches
|
||||
// the previous order. They are always different referentially, but
|
||||
// if the instances line up conceptually we'll want to know that.
|
||||
if (!hasChildOrderChanged && prevChild !== prevChildAtSameIndex) {
|
||||
hasChildOrderChanged = true;
|
||||
}
|
||||
} else {
|
||||
mountFiberRecursively(
|
||||
nextChild,
|
||||
shouldIncludeInTree ? nextFiber : parentFiber
|
||||
);
|
||||
if (!hasChildOrderChanged) {
|
||||
hasChildOrderChanged = true;
|
||||
}
|
||||
}
|
||||
// Try the next child.
|
||||
nextChild = nextChild.sibling;
|
||||
// Advance the pointer in the previous list so that we can
|
||||
// keep comparing if they line up.
|
||||
if (!hasChildOrderChanged && prevChildAtSameIndex != null) {
|
||||
prevChildAtSameIndex = prevChildAtSameIndex.sibling;
|
||||
}
|
||||
}
|
||||
// If we have no more children, but used to, they don't line up.
|
||||
if (!hasChildOrderChanged && prevChildAtSameIndex != null) {
|
||||
hasChildOrderChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldIncludeInTree) {
|
||||
maybeRecordUpdate(nextFiber, hasChildOrderChanged);
|
||||
const isProfilingSupported = nextFiber.hasOwnProperty('treeBaseDuration');
|
||||
if (isProfilingSupported) {
|
||||
recordTreeDuration(nextFiber);
|
||||
}
|
||||
}
|
||||
if (shouldResetChildren) {
|
||||
// We need to crawl the subtree for closest non-filtered Fibers
|
||||
// so that we can display them in a flat children set.
|
||||
if (shouldIncludeInTree) {
|
||||
// Normally, search for children from the rendered child.
|
||||
let nextChildSet = nextFiber.child;
|
||||
if (nextDidTimeOut) {
|
||||
// Special case: timed-out Suspense renders the fallback set.
|
||||
nextChildSet = nextFiber.child.sibling;
|
||||
}
|
||||
recordResetChildren(nextFiber, nextChildSet);
|
||||
// We've handled the child order change for this Fiber.
|
||||
// Since it's included, there's no need to invalidate parent child order.
|
||||
return false;
|
||||
} else {
|
||||
// Let the closest unfiltered parent Fiber reset its child order instead.
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9180,15 +9180,20 @@ react-color@^2.11.7:
|
||||
reactcss "^1.2.0"
|
||||
tinycolor2 "^1.4.1"
|
||||
|
||||
react-dom@^16.9.0-alpha.0:
|
||||
version "16.9.0-alpha.0"
|
||||
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.9.0-alpha.0.tgz#9dfaec18ac1a500fa72cab7b70f2ae29d0cd7716"
|
||||
integrity sha512-BQ5gN42yIPuTnBvE6K9vSjNfDRpSNcYCs2sUx9XR5VaWKwlHTt3G6qIWK6zdXy8TYKb1+IxpsAI0RtbRdXQZ2A==
|
||||
react-dom@0.0.0-4221565e1:
|
||||
version "0.0.0-4221565e1"
|
||||
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-0.0.0-4221565e1.tgz#d9044312a3294a8828867624a9fea7fd9aa76570"
|
||||
integrity sha512-uoBFBMDt5OOAZQNWl72hqCuvVxE1YpAILqIHSwv2ktiLcHPmyByftiuFdqrjXVCJe3LR9sAQWrB8xWgC2bCi+w==
|
||||
dependencies:
|
||||
loose-envify "^1.1.0"
|
||||
object-assign "^4.1.1"
|
||||
prop-types "^15.6.2"
|
||||
scheduler "^0.14.0-alpha.0"
|
||||
scheduler "0.0.0-4221565e1"
|
||||
|
||||
react-is@0.0.0-4221565e1:
|
||||
version "0.0.0-4221565e1"
|
||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-0.0.0-4221565e1.tgz#b9516ac872939c5d805f970e7aff4b9d3b3cc99e"
|
||||
integrity sha512-Ko7j3yyyQi5aB8tw3TmpmHrjeZbrzKm2FCKNpXJyXFAeummKLzeWH+M/gCh9w6EA6sZcX2W/YAX4U/KmeWjsWw==
|
||||
|
||||
react-is@^16.8.1:
|
||||
version "16.8.3"
|
||||
@@ -9200,11 +9205,6 @@ react-is@^16.8.4:
|
||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.8.4.tgz#90f336a68c3a29a096a3d648ab80e87ec61482a2"
|
||||
integrity sha512-PVadd+WaUDOAciICm/J1waJaSvgq+4rHE/K70j0PFqKhkTBsPv/82UGQJNXAngz1fOQLLxI6z1sEDmJDQhCTAA==
|
||||
|
||||
react-is@^16.9.0-alpha.0:
|
||||
version "16.9.0-alpha.0"
|
||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.9.0-alpha.0.tgz#af943642da49460f9fe98630182b0a62db3e9e6a"
|
||||
integrity sha512-psl0ePLTFliYfwcbwvimLgTNN156ZdeWB4zvP7dV/6lTAqWMHFfidg/mSZ2fFgE1LMNN8ZJOLl2DfZ8yg+3ETA==
|
||||
|
||||
react-lifecycles-compat@^3.0.4:
|
||||
version "3.0.4"
|
||||
resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
|
||||
@@ -9223,15 +9223,15 @@ react-window@^1.8.0:
|
||||
"@babel/runtime" "^7.0.0"
|
||||
memoize-one ">=3.1.1 <6"
|
||||
|
||||
react@^16.9.0-alpha.0:
|
||||
version "16.9.0-alpha.0"
|
||||
resolved "https://registry.yarnpkg.com/react/-/react-16.9.0-alpha.0.tgz#e350f3d8af36e3251079cbc90d304620e2f78ccb"
|
||||
integrity sha512-y4bu7rJvtnPPsIwOj7sp5Y2SqlOb0jFupfkdjWxxn8ZeqzUARgpR9wJBUVwW1/QosVdOblmApjo/j6iiAXnebA==
|
||||
react@0.0.0-4221565e1:
|
||||
version "0.0.0-4221565e1"
|
||||
resolved "https://registry.yarnpkg.com/react/-/react-0.0.0-4221565e1.tgz#bc7f3e67f2c2c53693cda2e89e6fa71bfc89ebcd"
|
||||
integrity sha512-iVAljelOxza6S9QcFMkxJCcj2Y+QDiv6XwGz9A8c5jtKejGOEKK/Xumg7uTDmGwy8vjGVDI7qGDW8Rta80xH9g==
|
||||
dependencies:
|
||||
loose-envify "^1.1.0"
|
||||
object-assign "^4.1.1"
|
||||
prop-types "^15.6.2"
|
||||
scheduler "^0.14.0-alpha.0"
|
||||
scheduler "0.0.0-4221565e1"
|
||||
|
||||
reactcss@^1.2.0:
|
||||
version "1.2.3"
|
||||
@@ -9882,6 +9882,14 @@ sax@>=0.6.0, sax@^1.2.4:
|
||||
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
|
||||
integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==
|
||||
|
||||
scheduler@0.0.0-4221565e1:
|
||||
version "0.0.0-4221565e1"
|
||||
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.0.0-4221565e1.tgz#70c99271bd7bc4fbf4e7c69f31c003c4c61cd285"
|
||||
integrity sha512-RhQ6QhkRJDXAcs6woR6p9UYDyjRguRsYXpWDFbCft45lWs4sSfszxPRr5TJVVLjIwPu0Wy0Mkd75smQ8TMponA==
|
||||
dependencies:
|
||||
loose-envify "^1.1.0"
|
||||
object-assign "^4.1.1"
|
||||
|
||||
scheduler@^0.14.0-alpha.0:
|
||||
version "0.14.0-alpha.0"
|
||||
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.14.0-alpha.0.tgz#6d301d8fd10373487e0e47e837ad24e863ce807d"
|
||||
|
||||
Reference in New Issue
Block a user