mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Merge pull request #186 from gaearon/more-suspensey-stuff
Fix more Suspense issues
This commit is contained in:
+8
-4
@@ -69,16 +69,20 @@ For example, adding a function component `<Foo>` with an id 2:
|
||||
|
||||
#### Removing a node
|
||||
|
||||
Removing a fiber from the tree (a root or a leaf) only requires sending 2 numbers:
|
||||
Removing a fiber from the tree (a root or a leaf) requires sending:
|
||||
|
||||
1. remove operation constant (`2`)
|
||||
1. fiber id
|
||||
1. how many items were removed
|
||||
1. number of children
|
||||
* (followed by a children-first list of removed fiber ids)
|
||||
|
||||
For example, removing a root fiber with an id of 1:
|
||||
For example, removing fibers with ids of 35 and 21:
|
||||
```js
|
||||
[
|
||||
2, // remove operation
|
||||
1, // fiber id
|
||||
1, // number of removed fibers
|
||||
35, // first removed id
|
||||
21, // second removed id
|
||||
]
|
||||
```
|
||||
|
||||
|
||||
@@ -115,6 +115,260 @@ exports[`StoreStress (Sync Mode) should handle a stress test for Suspense (Sync
|
||||
<Y>
|
||||
`;
|
||||
|
||||
exports[`StoreStress (Sync Mode) should handle a stress test for Suspense without type change (Sync Mode) 1`] = `
|
||||
[root]
|
||||
▾ <Root>
|
||||
<X>
|
||||
▾ <Suspense>
|
||||
▾ <MaybeSuspend>
|
||||
<A key="a">
|
||||
<Z>
|
||||
<Y>
|
||||
`;
|
||||
|
||||
exports[`StoreStress (Sync Mode) should handle a stress test for Suspense without type change (Sync Mode) 2`] = `
|
||||
[root]
|
||||
▾ <Root>
|
||||
<X>
|
||||
▾ <Suspense>
|
||||
▾ <MaybeSuspend>
|
||||
<A key="a">
|
||||
<Z>
|
||||
<Y>
|
||||
`;
|
||||
|
||||
exports[`StoreStress (Sync Mode) should handle a stress test for Suspense without type change (Sync Mode) 3`] = `
|
||||
[root]
|
||||
▾ <Root>
|
||||
<X>
|
||||
▾ <Suspense>
|
||||
▾ <MaybeSuspend>
|
||||
<A key="a">
|
||||
<B key="b">
|
||||
<C key="c">
|
||||
<Z>
|
||||
<Y>
|
||||
`;
|
||||
|
||||
exports[`StoreStress (Sync Mode) should handle a stress test for Suspense without type change (Sync Mode) 4`] = `
|
||||
[root]
|
||||
▾ <Root>
|
||||
<X>
|
||||
▾ <Suspense>
|
||||
▾ <MaybeSuspend>
|
||||
<C key="c">
|
||||
<B key="b">
|
||||
<A key="a">
|
||||
<Z>
|
||||
<Y>
|
||||
`;
|
||||
|
||||
exports[`StoreStress (Sync Mode) should handle a stress test for Suspense without type change (Sync Mode) 5`] = `
|
||||
[root]
|
||||
▾ <Root>
|
||||
<X>
|
||||
▾ <Suspense>
|
||||
▾ <MaybeSuspend>
|
||||
<C key="c">
|
||||
<A key="a">
|
||||
<Z>
|
||||
<Y>
|
||||
`;
|
||||
|
||||
exports[`StoreStress (Sync Mode) should handle a stress test for Suspense without type change (Sync Mode) 6`] = `
|
||||
[root]
|
||||
▾ <Root>
|
||||
<X>
|
||||
▾ <Suspense>
|
||||
▾ <MaybeSuspend>
|
||||
<C key="c">
|
||||
<A key="a">
|
||||
<Z>
|
||||
<Y>
|
||||
`;
|
||||
|
||||
exports[`StoreStress (Sync Mode) should handle a stress test for Suspense without type change (Sync Mode) 7`] = `
|
||||
[root]
|
||||
▾ <Root>
|
||||
<X>
|
||||
▾ <Suspense>
|
||||
▾ <MaybeSuspend>
|
||||
<C key="c">
|
||||
<A key="a">
|
||||
<Z>
|
||||
<Y>
|
||||
`;
|
||||
|
||||
exports[`StoreStress (Sync Mode) should handle a stress test for Suspense without type change (Sync Mode) 8`] = `
|
||||
[root]
|
||||
▾ <Root>
|
||||
<X>
|
||||
▾ <Suspense>
|
||||
▾ <MaybeSuspend>
|
||||
<A key="a">
|
||||
<B key="b">
|
||||
<Z>
|
||||
<Y>
|
||||
`;
|
||||
|
||||
exports[`StoreStress (Sync Mode) should handle a stress test for Suspense without type change (Sync Mode) 9`] = `
|
||||
[root]
|
||||
▾ <Root>
|
||||
<X>
|
||||
▾ <Suspense>
|
||||
▾ <MaybeSuspend>
|
||||
<A key="a">
|
||||
<Z>
|
||||
<Y>
|
||||
`;
|
||||
|
||||
exports[`StoreStress (Sync Mode) should handle a stress test for Suspense without type change (Sync Mode) 10`] = `
|
||||
[root]
|
||||
▾ <Root>
|
||||
<X>
|
||||
▾ <Suspense>
|
||||
▾ <MaybeSuspend>
|
||||
<Z>
|
||||
<Y>
|
||||
`;
|
||||
|
||||
exports[`StoreStress (Sync Mode) should handle a stress test for Suspense without type change (Sync Mode) 11`] = `
|
||||
[root]
|
||||
▾ <Root>
|
||||
<X>
|
||||
▾ <Suspense>
|
||||
▾ <MaybeSuspend>
|
||||
<B key="b">
|
||||
<Z>
|
||||
<Y>
|
||||
`;
|
||||
|
||||
exports[`StoreStress (Sync Mode) should handle a stress test for Suspense without type change (Sync Mode) 12`] = `
|
||||
[root]
|
||||
▾ <Root>
|
||||
<X>
|
||||
▾ <Suspense>
|
||||
▾ <MaybeSuspend>
|
||||
<A key="a">
|
||||
<Z>
|
||||
<Y>
|
||||
`;
|
||||
|
||||
exports[`StoreStress (Sync Mode) should handle a stress test for Suspense without type change (Sync Mode) 13`] = `
|
||||
[root]
|
||||
▾ <Root>
|
||||
<X>
|
||||
▾ <Suspense>
|
||||
<A key="a">
|
||||
<Y>
|
||||
`;
|
||||
|
||||
exports[`StoreStress (Sync Mode) should handle a stress test for Suspense without type change (Sync Mode) 14`] = `
|
||||
[root]
|
||||
▾ <Root>
|
||||
<X>
|
||||
▾ <Suspense>
|
||||
<A key="a">
|
||||
<Y>
|
||||
`;
|
||||
|
||||
exports[`StoreStress (Sync Mode) should handle a stress test for Suspense without type change (Sync Mode) 15`] = `
|
||||
[root]
|
||||
▾ <Root>
|
||||
<X>
|
||||
▾ <Suspense>
|
||||
<A key="a">
|
||||
<B key="b">
|
||||
<C key="c">
|
||||
<Y>
|
||||
`;
|
||||
|
||||
exports[`StoreStress (Sync Mode) should handle a stress test for Suspense without type change (Sync Mode) 16`] = `
|
||||
[root]
|
||||
▾ <Root>
|
||||
<X>
|
||||
▾ <Suspense>
|
||||
<C key="c">
|
||||
<B key="b">
|
||||
<A key="a">
|
||||
<Y>
|
||||
`;
|
||||
|
||||
exports[`StoreStress (Sync Mode) should handle a stress test for Suspense without type change (Sync Mode) 17`] = `
|
||||
[root]
|
||||
▾ <Root>
|
||||
<X>
|
||||
▾ <Suspense>
|
||||
<C key="c">
|
||||
<A key="a">
|
||||
<Y>
|
||||
`;
|
||||
|
||||
exports[`StoreStress (Sync Mode) should handle a stress test for Suspense without type change (Sync Mode) 18`] = `
|
||||
[root]
|
||||
▾ <Root>
|
||||
<X>
|
||||
▾ <Suspense>
|
||||
<C key="c">
|
||||
<A key="a">
|
||||
<Y>
|
||||
`;
|
||||
|
||||
exports[`StoreStress (Sync Mode) should handle a stress test for Suspense without type change (Sync Mode) 19`] = `
|
||||
[root]
|
||||
▾ <Root>
|
||||
<X>
|
||||
▾ <Suspense>
|
||||
<C key="c">
|
||||
<A key="a">
|
||||
<Y>
|
||||
`;
|
||||
|
||||
exports[`StoreStress (Sync Mode) should handle a stress test for Suspense without type change (Sync Mode) 20`] = `
|
||||
[root]
|
||||
▾ <Root>
|
||||
<X>
|
||||
▾ <Suspense>
|
||||
<A key="a">
|
||||
<B key="b">
|
||||
<Y>
|
||||
`;
|
||||
|
||||
exports[`StoreStress (Sync Mode) should handle a stress test for Suspense without type change (Sync Mode) 21`] = `
|
||||
[root]
|
||||
▾ <Root>
|
||||
<X>
|
||||
▾ <Suspense>
|
||||
<A key="a">
|
||||
<Y>
|
||||
`;
|
||||
|
||||
exports[`StoreStress (Sync Mode) should handle a stress test for Suspense without type change (Sync Mode) 22`] = `
|
||||
[root]
|
||||
▾ <Root>
|
||||
<X>
|
||||
<Suspense>
|
||||
<Y>
|
||||
`;
|
||||
|
||||
exports[`StoreStress (Sync Mode) should handle a stress test for Suspense without type change (Sync Mode) 23`] = `
|
||||
[root]
|
||||
▾ <Root>
|
||||
<X>
|
||||
▾ <Suspense>
|
||||
<B key="b">
|
||||
<Y>
|
||||
`;
|
||||
|
||||
exports[`StoreStress (Sync Mode) should handle a stress test for Suspense without type change (Sync Mode) 24`] = `
|
||||
[root]
|
||||
▾ <Root>
|
||||
<X>
|
||||
▾ <Suspense>
|
||||
<A key="a">
|
||||
<Y>
|
||||
`;
|
||||
|
||||
exports[`StoreStress (Sync Mode) should handle a stress test with different tree operations (Sync Mode): 1: abcde 1`] = `
|
||||
[root]
|
||||
▾ <Parent>
|
||||
|
||||
@@ -115,6 +115,260 @@ exports[`StoreStressConcurrent should handle a stress test for Suspense (Concurr
|
||||
<Y>
|
||||
`;
|
||||
|
||||
exports[`StoreStressConcurrent should handle a stress test for Suspense without type change (Concurrent Mode) 1`] = `
|
||||
[root]
|
||||
▾ <Root>
|
||||
<X>
|
||||
▾ <Suspense>
|
||||
▾ <MaybeSuspend>
|
||||
<A key="a">
|
||||
<Z>
|
||||
<Y>
|
||||
`;
|
||||
|
||||
exports[`StoreStressConcurrent should handle a stress test for Suspense without type change (Concurrent Mode) 2`] = `
|
||||
[root]
|
||||
▾ <Root>
|
||||
<X>
|
||||
▾ <Suspense>
|
||||
▾ <MaybeSuspend>
|
||||
<A key="a">
|
||||
<Z>
|
||||
<Y>
|
||||
`;
|
||||
|
||||
exports[`StoreStressConcurrent should handle a stress test for Suspense without type change (Concurrent Mode) 3`] = `
|
||||
[root]
|
||||
▾ <Root>
|
||||
<X>
|
||||
▾ <Suspense>
|
||||
▾ <MaybeSuspend>
|
||||
<A key="a">
|
||||
<B key="b">
|
||||
<C key="c">
|
||||
<Z>
|
||||
<Y>
|
||||
`;
|
||||
|
||||
exports[`StoreStressConcurrent should handle a stress test for Suspense without type change (Concurrent Mode) 4`] = `
|
||||
[root]
|
||||
▾ <Root>
|
||||
<X>
|
||||
▾ <Suspense>
|
||||
▾ <MaybeSuspend>
|
||||
<C key="c">
|
||||
<B key="b">
|
||||
<A key="a">
|
||||
<Z>
|
||||
<Y>
|
||||
`;
|
||||
|
||||
exports[`StoreStressConcurrent should handle a stress test for Suspense without type change (Concurrent Mode) 5`] = `
|
||||
[root]
|
||||
▾ <Root>
|
||||
<X>
|
||||
▾ <Suspense>
|
||||
▾ <MaybeSuspend>
|
||||
<C key="c">
|
||||
<A key="a">
|
||||
<Z>
|
||||
<Y>
|
||||
`;
|
||||
|
||||
exports[`StoreStressConcurrent should handle a stress test for Suspense without type change (Concurrent Mode) 6`] = `
|
||||
[root]
|
||||
▾ <Root>
|
||||
<X>
|
||||
▾ <Suspense>
|
||||
▾ <MaybeSuspend>
|
||||
<C key="c">
|
||||
<A key="a">
|
||||
<Z>
|
||||
<Y>
|
||||
`;
|
||||
|
||||
exports[`StoreStressConcurrent should handle a stress test for Suspense without type change (Concurrent Mode) 7`] = `
|
||||
[root]
|
||||
▾ <Root>
|
||||
<X>
|
||||
▾ <Suspense>
|
||||
▾ <MaybeSuspend>
|
||||
<C key="c">
|
||||
<A key="a">
|
||||
<Z>
|
||||
<Y>
|
||||
`;
|
||||
|
||||
exports[`StoreStressConcurrent should handle a stress test for Suspense without type change (Concurrent Mode) 8`] = `
|
||||
[root]
|
||||
▾ <Root>
|
||||
<X>
|
||||
▾ <Suspense>
|
||||
▾ <MaybeSuspend>
|
||||
<A key="a">
|
||||
<B key="b">
|
||||
<Z>
|
||||
<Y>
|
||||
`;
|
||||
|
||||
exports[`StoreStressConcurrent should handle a stress test for Suspense without type change (Concurrent Mode) 9`] = `
|
||||
[root]
|
||||
▾ <Root>
|
||||
<X>
|
||||
▾ <Suspense>
|
||||
▾ <MaybeSuspend>
|
||||
<A key="a">
|
||||
<Z>
|
||||
<Y>
|
||||
`;
|
||||
|
||||
exports[`StoreStressConcurrent should handle a stress test for Suspense without type change (Concurrent Mode) 10`] = `
|
||||
[root]
|
||||
▾ <Root>
|
||||
<X>
|
||||
▾ <Suspense>
|
||||
▾ <MaybeSuspend>
|
||||
<Z>
|
||||
<Y>
|
||||
`;
|
||||
|
||||
exports[`StoreStressConcurrent should handle a stress test for Suspense without type change (Concurrent Mode) 11`] = `
|
||||
[root]
|
||||
▾ <Root>
|
||||
<X>
|
||||
▾ <Suspense>
|
||||
▾ <MaybeSuspend>
|
||||
<B key="b">
|
||||
<Z>
|
||||
<Y>
|
||||
`;
|
||||
|
||||
exports[`StoreStressConcurrent should handle a stress test for Suspense without type change (Concurrent Mode) 12`] = `
|
||||
[root]
|
||||
▾ <Root>
|
||||
<X>
|
||||
▾ <Suspense>
|
||||
▾ <MaybeSuspend>
|
||||
<A key="a">
|
||||
<Z>
|
||||
<Y>
|
||||
`;
|
||||
|
||||
exports[`StoreStressConcurrent should handle a stress test for Suspense without type change (Concurrent Mode) 13`] = `
|
||||
[root]
|
||||
▾ <Root>
|
||||
<X>
|
||||
▾ <Suspense>
|
||||
<A key="a">
|
||||
<Y>
|
||||
`;
|
||||
|
||||
exports[`StoreStressConcurrent should handle a stress test for Suspense without type change (Concurrent Mode) 14`] = `
|
||||
[root]
|
||||
▾ <Root>
|
||||
<X>
|
||||
▾ <Suspense>
|
||||
<A key="a">
|
||||
<Y>
|
||||
`;
|
||||
|
||||
exports[`StoreStressConcurrent should handle a stress test for Suspense without type change (Concurrent Mode) 15`] = `
|
||||
[root]
|
||||
▾ <Root>
|
||||
<X>
|
||||
▾ <Suspense>
|
||||
<A key="a">
|
||||
<B key="b">
|
||||
<C key="c">
|
||||
<Y>
|
||||
`;
|
||||
|
||||
exports[`StoreStressConcurrent should handle a stress test for Suspense without type change (Concurrent Mode) 16`] = `
|
||||
[root]
|
||||
▾ <Root>
|
||||
<X>
|
||||
▾ <Suspense>
|
||||
<C key="c">
|
||||
<B key="b">
|
||||
<A key="a">
|
||||
<Y>
|
||||
`;
|
||||
|
||||
exports[`StoreStressConcurrent should handle a stress test for Suspense without type change (Concurrent Mode) 17`] = `
|
||||
[root]
|
||||
▾ <Root>
|
||||
<X>
|
||||
▾ <Suspense>
|
||||
<C key="c">
|
||||
<A key="a">
|
||||
<Y>
|
||||
`;
|
||||
|
||||
exports[`StoreStressConcurrent should handle a stress test for Suspense without type change (Concurrent Mode) 18`] = `
|
||||
[root]
|
||||
▾ <Root>
|
||||
<X>
|
||||
▾ <Suspense>
|
||||
<C key="c">
|
||||
<A key="a">
|
||||
<Y>
|
||||
`;
|
||||
|
||||
exports[`StoreStressConcurrent should handle a stress test for Suspense without type change (Concurrent Mode) 19`] = `
|
||||
[root]
|
||||
▾ <Root>
|
||||
<X>
|
||||
▾ <Suspense>
|
||||
<C key="c">
|
||||
<A key="a">
|
||||
<Y>
|
||||
`;
|
||||
|
||||
exports[`StoreStressConcurrent should handle a stress test for Suspense without type change (Concurrent Mode) 20`] = `
|
||||
[root]
|
||||
▾ <Root>
|
||||
<X>
|
||||
▾ <Suspense>
|
||||
<A key="a">
|
||||
<B key="b">
|
||||
<Y>
|
||||
`;
|
||||
|
||||
exports[`StoreStressConcurrent should handle a stress test for Suspense without type change (Concurrent Mode) 21`] = `
|
||||
[root]
|
||||
▾ <Root>
|
||||
<X>
|
||||
▾ <Suspense>
|
||||
<A key="a">
|
||||
<Y>
|
||||
`;
|
||||
|
||||
exports[`StoreStressConcurrent should handle a stress test for Suspense without type change (Concurrent Mode) 22`] = `
|
||||
[root]
|
||||
▾ <Root>
|
||||
<X>
|
||||
<Suspense>
|
||||
<Y>
|
||||
`;
|
||||
|
||||
exports[`StoreStressConcurrent should handle a stress test for Suspense without type change (Concurrent Mode) 23`] = `
|
||||
[root]
|
||||
▾ <Root>
|
||||
<X>
|
||||
▾ <Suspense>
|
||||
<B key="b">
|
||||
<Y>
|
||||
`;
|
||||
|
||||
exports[`StoreStressConcurrent should handle a stress test for Suspense without type change (Concurrent Mode) 24`] = `
|
||||
[root]
|
||||
▾ <Root>
|
||||
<X>
|
||||
▾ <Suspense>
|
||||
<A key="a">
|
||||
<Y>
|
||||
`;
|
||||
|
||||
exports[`StoreStressConcurrent should handle a stress test with different tree operations (Concurrent Mode): 1: abcde 1`] = `
|
||||
[root]
|
||||
▾ <Parent>
|
||||
|
||||
@@ -663,4 +663,440 @@ describe('StoreStress (Sync Mode)', () => {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('should handle a stress test for Suspense without type change (Sync Mode)', () => {
|
||||
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 MaybeSuspend = ({ children, suspend }) => {
|
||||
if (suspend) {
|
||||
return (
|
||||
<div>
|
||||
{children}
|
||||
<Never />
|
||||
<X />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
{children}
|
||||
<Z />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
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}>
|
||||
<MaybeSuspend suspend={false}>{steps[i]}</MaybeSuspend>
|
||||
</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.
|
||||
// We don't actually assert here because the tree includes <MaybeSuspend>
|
||||
// which is different from the snapshots above. So we take more snapshots.
|
||||
let fallbackSnapshots = [];
|
||||
for (let i = 0; i < steps.length; i++) {
|
||||
act(() =>
|
||||
ReactDOM.render(
|
||||
<Root>
|
||||
<X />
|
||||
<React.Suspense fallback={steps[i]}>
|
||||
<Z />
|
||||
<MaybeSuspend suspend={true}>{steps[i]}</MaybeSuspend>
|
||||
<Z />
|
||||
</React.Suspense>
|
||||
<Y />
|
||||
</Root>,
|
||||
container
|
||||
)
|
||||
);
|
||||
// We snapshot each step once so it doesn't regress.
|
||||
expect(store).toMatchSnapshot();
|
||||
fallbackSnapshots.push(print(store));
|
||||
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}>
|
||||
<MaybeSuspend suspend={false}>{steps[i]}</MaybeSuspend>
|
||||
</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}>
|
||||
<MaybeSuspend suspend={false}>{steps[j]}</MaybeSuspend>
|
||||
</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}>
|
||||
<MaybeSuspend suspend={false}>{steps[i]}</MaybeSuspend>
|
||||
</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 />
|
||||
<MaybeSuspend suspend={true}>
|
||||
<X />
|
||||
<Y />
|
||||
</MaybeSuspend>
|
||||
<Z />
|
||||
</React.Suspense>
|
||||
<Y />
|
||||
</Root>,
|
||||
container
|
||||
)
|
||||
);
|
||||
expect(print(store)).toEqual(fallbackSnapshots[i]);
|
||||
// Re-render with steps[j].
|
||||
act(() =>
|
||||
ReactDOM.render(
|
||||
<Root>
|
||||
<X />
|
||||
<React.Suspense fallback={steps[j]}>
|
||||
<Z />
|
||||
<MaybeSuspend suspend={true}>
|
||||
<Y />
|
||||
<X />
|
||||
</MaybeSuspend>
|
||||
<Z />
|
||||
</React.Suspense>
|
||||
<Y />
|
||||
</Root>,
|
||||
container
|
||||
)
|
||||
);
|
||||
// Verify the successful transition to steps[j].
|
||||
expect(print(store)).toEqual(fallbackSnapshots[j]);
|
||||
// Check that we can transition back again.
|
||||
act(() =>
|
||||
ReactDOM.render(
|
||||
<Root>
|
||||
<X />
|
||||
<React.Suspense fallback={steps[i]}>
|
||||
<Z />
|
||||
<MaybeSuspend suspend={true}>
|
||||
<X />
|
||||
<Y />
|
||||
</MaybeSuspend>
|
||||
<Z />
|
||||
</React.Suspense>
|
||||
<Y />
|
||||
</Root>,
|
||||
container
|
||||
)
|
||||
);
|
||||
expect(print(store)).toEqual(fallbackSnapshots[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}>
|
||||
<MaybeSuspend suspend={false}>{steps[i]}</MaybeSuspend>
|
||||
</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]}>
|
||||
<MaybeSuspend suspend={true}>{steps[i]}</MaybeSuspend>
|
||||
</React.Suspense>
|
||||
<Y />
|
||||
</Root>,
|
||||
container
|
||||
)
|
||||
);
|
||||
// Verify the successful transition to steps[j].
|
||||
expect(print(store)).toEqual(fallbackSnapshots[j]);
|
||||
// Check that we can transition back again.
|
||||
act(() =>
|
||||
ReactDOM.render(
|
||||
<Root>
|
||||
<X />
|
||||
<React.Suspense fallback={z}>
|
||||
<MaybeSuspend suspend={false}>{steps[i]}</MaybeSuspend>
|
||||
</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]}>
|
||||
<MaybeSuspend suspend={true}>{steps[j]}</MaybeSuspend>
|
||||
</React.Suspense>
|
||||
<Y />
|
||||
</Root>,
|
||||
container
|
||||
)
|
||||
);
|
||||
expect(print(store)).toEqual(fallbackSnapshots[i]);
|
||||
// Re-render with steps[j].
|
||||
act(() =>
|
||||
ReactDOM.render(
|
||||
<Root>
|
||||
<X />
|
||||
<React.Suspense fallback={steps[i]}>
|
||||
<MaybeSuspend suspend={false}>{steps[j]}</MaybeSuspend>
|
||||
</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]}>
|
||||
<MaybeSuspend suspend={true}>{steps[j]}</MaybeSuspend>
|
||||
</React.Suspense>
|
||||
<Y />
|
||||
</Root>,
|
||||
container
|
||||
)
|
||||
);
|
||||
expect(print(store)).toEqual(fallbackSnapshots[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]}>
|
||||
<MaybeSuspend suspend={false}>{steps[i]}</MaybeSuspend>
|
||||
</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(fallbackSnapshots[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]}>
|
||||
<MaybeSuspend suspend={true}>{steps[i]}</MaybeSuspend>
|
||||
</React.Suspense>
|
||||
<Y />
|
||||
</Root>,
|
||||
container
|
||||
)
|
||||
);
|
||||
expect(print(store)).toEqual(fallbackSnapshots[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(fallbackSnapshots[j]);
|
||||
|
||||
// Switch to primary mode.
|
||||
act(() =>
|
||||
ReactDOM.render(
|
||||
<Root>
|
||||
<X />
|
||||
<React.Suspense fallback={steps[j]}>
|
||||
<MaybeSuspend suspend={false}>{steps[i]}</MaybeSuspend>
|
||||
</React.Suspense>
|
||||
<Y />
|
||||
</Root>,
|
||||
container
|
||||
)
|
||||
);
|
||||
// Fallback is still forced though.
|
||||
expect(print(store)).toEqual(fallbackSnapshots[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('');
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -668,4 +668,435 @@ describe('StoreStressConcurrent', () => {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('should handle a stress test for Suspense without type change (Concurrent Mode)', () => {
|
||||
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 MaybeSuspend = ({ children, suspend }) => {
|
||||
if (suspend) {
|
||||
return (
|
||||
<div>
|
||||
{children}
|
||||
<Never />
|
||||
<X />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
{children}
|
||||
<Z />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
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');
|
||||
// $FlowFixMe
|
||||
let root = ReactDOM.unstable_createRoot(container);
|
||||
for (let i = 0; i < steps.length; i++) {
|
||||
act(() =>
|
||||
root.render(
|
||||
<Root>
|
||||
<X />
|
||||
<React.Suspense fallback={z}>
|
||||
<MaybeSuspend suspend={false}>{steps[i]}</MaybeSuspend>
|
||||
</React.Suspense>
|
||||
<Y />
|
||||
</Root>
|
||||
)
|
||||
);
|
||||
// We snapshot each step once so it doesn't regress.
|
||||
expect(store).toMatchSnapshot();
|
||||
snapshots.push(print(store));
|
||||
act(() => root.unmount());
|
||||
expect(print(store)).toBe('');
|
||||
}
|
||||
|
||||
// 2. Verify check Suspense can render same steps as initial fallback content.
|
||||
// We don't actually assert here because the tree includes <MaybeSuspend>
|
||||
// which is different from the snapshots above. So we take more snapshots.
|
||||
let fallbackSnapshots = [];
|
||||
for (let i = 0; i < steps.length; i++) {
|
||||
act(() =>
|
||||
root.render(
|
||||
<Root>
|
||||
<X />
|
||||
<React.Suspense fallback={steps[i]}>
|
||||
<Z />
|
||||
<MaybeSuspend suspend={true}>{steps[i]}</MaybeSuspend>
|
||||
<Z />
|
||||
</React.Suspense>
|
||||
<Y />
|
||||
</Root>
|
||||
)
|
||||
);
|
||||
// We snapshot each step once so it doesn't regress.
|
||||
expect(store).toMatchSnapshot();
|
||||
fallbackSnapshots.push(print(store));
|
||||
act(() => root.unmount());
|
||||
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');
|
||||
// $FlowFixMe
|
||||
root = ReactDOM.unstable_createRoot(container);
|
||||
act(() =>
|
||||
root.render(
|
||||
<Root>
|
||||
<X />
|
||||
<React.Suspense fallback={z}>
|
||||
<MaybeSuspend suspend={false}>{steps[i]}</MaybeSuspend>
|
||||
</React.Suspense>
|
||||
<Y />
|
||||
</Root>
|
||||
)
|
||||
);
|
||||
expect(print(store)).toEqual(snapshots[i]);
|
||||
// Re-render with steps[j].
|
||||
act(() =>
|
||||
root.render(
|
||||
<Root>
|
||||
<X />
|
||||
<React.Suspense fallback={z}>
|
||||
<MaybeSuspend suspend={false}>{steps[j]}</MaybeSuspend>
|
||||
</React.Suspense>
|
||||
<Y />
|
||||
</Root>
|
||||
)
|
||||
);
|
||||
// Verify the successful transition to steps[j].
|
||||
expect(print(store)).toEqual(snapshots[j]);
|
||||
// Check that we can transition back again.
|
||||
act(() =>
|
||||
root.render(
|
||||
<Root>
|
||||
<X />
|
||||
<React.Suspense fallback={z}>
|
||||
<MaybeSuspend suspend={false}>{steps[i]}</MaybeSuspend>
|
||||
</React.Suspense>
|
||||
<Y />
|
||||
</Root>
|
||||
)
|
||||
);
|
||||
expect(print(store)).toEqual(snapshots[i]);
|
||||
// Clean up after every iteration.
|
||||
act(() => root.unmount());
|
||||
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');
|
||||
// $FlowFixMe
|
||||
root = ReactDOM.unstable_createRoot(container);
|
||||
act(() =>
|
||||
root.render(
|
||||
<Root>
|
||||
<X />
|
||||
<React.Suspense fallback={steps[i]}>
|
||||
<Z />
|
||||
<MaybeSuspend suspend={true}>
|
||||
<X />
|
||||
<Y />
|
||||
</MaybeSuspend>
|
||||
<Z />
|
||||
</React.Suspense>
|
||||
<Y />
|
||||
</Root>
|
||||
)
|
||||
);
|
||||
expect(print(store)).toEqual(fallbackSnapshots[i]);
|
||||
// Re-render with steps[j].
|
||||
act(() =>
|
||||
root.render(
|
||||
<Root>
|
||||
<X />
|
||||
<React.Suspense fallback={steps[j]}>
|
||||
<Z />
|
||||
<MaybeSuspend suspend={true}>
|
||||
<Y />
|
||||
<X />
|
||||
</MaybeSuspend>
|
||||
<Z />
|
||||
</React.Suspense>
|
||||
<Y />
|
||||
</Root>
|
||||
)
|
||||
);
|
||||
// Verify the successful transition to steps[j].
|
||||
expect(print(store)).toEqual(fallbackSnapshots[j]);
|
||||
// Check that we can transition back again.
|
||||
act(() =>
|
||||
root.render(
|
||||
<Root>
|
||||
<X />
|
||||
<React.Suspense fallback={steps[i]}>
|
||||
<Z />
|
||||
<MaybeSuspend suspend={true}>
|
||||
<X />
|
||||
<Y />
|
||||
</MaybeSuspend>
|
||||
<Z />
|
||||
</React.Suspense>
|
||||
<Y />
|
||||
</Root>
|
||||
)
|
||||
);
|
||||
expect(print(store)).toEqual(fallbackSnapshots[i]);
|
||||
// Clean up after every iteration.
|
||||
act(() => root.unmount());
|
||||
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');
|
||||
// $FlowFixMe
|
||||
root = ReactDOM.unstable_createRoot(container);
|
||||
act(() =>
|
||||
root.render(
|
||||
<Root>
|
||||
<X />
|
||||
<React.Suspense fallback={z}>
|
||||
<MaybeSuspend suspend={false}>{steps[i]}</MaybeSuspend>
|
||||
</React.Suspense>
|
||||
<Y />
|
||||
</Root>
|
||||
)
|
||||
);
|
||||
expect(print(store)).toEqual(snapshots[i]);
|
||||
// Re-render with steps[j].
|
||||
act(() =>
|
||||
root.render(
|
||||
<Root>
|
||||
<X />
|
||||
<React.Suspense fallback={steps[j]}>
|
||||
<MaybeSuspend suspend={true}>{steps[i]}</MaybeSuspend>
|
||||
</React.Suspense>
|
||||
<Y />
|
||||
</Root>
|
||||
)
|
||||
);
|
||||
// Verify the successful transition to steps[j].
|
||||
expect(print(store)).toEqual(fallbackSnapshots[j]);
|
||||
// Check that we can transition back again.
|
||||
act(() =>
|
||||
root.render(
|
||||
<Root>
|
||||
<X />
|
||||
<React.Suspense fallback={z}>
|
||||
<MaybeSuspend suspend={false}>{steps[i]}</MaybeSuspend>
|
||||
</React.Suspense>
|
||||
<Y />
|
||||
</Root>
|
||||
)
|
||||
);
|
||||
expect(print(store)).toEqual(snapshots[i]);
|
||||
// Clean up after every iteration.
|
||||
act(() => root.unmount());
|
||||
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');
|
||||
// $FlowFixMe
|
||||
root = ReactDOM.unstable_createRoot(container);
|
||||
act(() =>
|
||||
root.render(
|
||||
<Root>
|
||||
<X />
|
||||
<React.Suspense fallback={steps[i]}>
|
||||
<MaybeSuspend suspend={true}>{steps[j]}</MaybeSuspend>
|
||||
</React.Suspense>
|
||||
<Y />
|
||||
</Root>
|
||||
)
|
||||
);
|
||||
expect(print(store)).toEqual(fallbackSnapshots[i]);
|
||||
// Re-render with steps[j].
|
||||
act(() =>
|
||||
root.render(
|
||||
<Root>
|
||||
<X />
|
||||
<React.Suspense fallback={steps[i]}>
|
||||
<MaybeSuspend suspend={false}>{steps[j]}</MaybeSuspend>
|
||||
</React.Suspense>
|
||||
<Y />
|
||||
</Root>
|
||||
)
|
||||
);
|
||||
// Verify the successful transition to steps[j].
|
||||
expect(print(store)).toEqual(snapshots[j]);
|
||||
// Check that we can transition back again.
|
||||
act(() =>
|
||||
root.render(
|
||||
<Root>
|
||||
<X />
|
||||
<React.Suspense fallback={steps[i]}>
|
||||
<MaybeSuspend suspend={true}>{steps[j]}</MaybeSuspend>
|
||||
</React.Suspense>
|
||||
<Y />
|
||||
</Root>
|
||||
)
|
||||
);
|
||||
expect(print(store)).toEqual(fallbackSnapshots[i]);
|
||||
// Clean up after every iteration.
|
||||
act(() => root.unmount());
|
||||
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');
|
||||
// $FlowFixMe
|
||||
root = ReactDOM.unstable_createRoot(container);
|
||||
act(() =>
|
||||
root.render(
|
||||
<Root>
|
||||
<X />
|
||||
<React.Suspense fallback={steps[j]}>
|
||||
<MaybeSuspend suspend={false}>{steps[i]}</MaybeSuspend>
|
||||
</React.Suspense>
|
||||
<Y />
|
||||
</Root>
|
||||
)
|
||||
);
|
||||
|
||||
// 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(fallbackSnapshots[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(() =>
|
||||
root.render(
|
||||
<Root>
|
||||
<X />
|
||||
<React.Suspense fallback={steps[j]}>
|
||||
<MaybeSuspend suspend={true}>{steps[i]}</MaybeSuspend>
|
||||
</React.Suspense>
|
||||
<Y />
|
||||
</Root>
|
||||
)
|
||||
);
|
||||
expect(print(store)).toEqual(fallbackSnapshots[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(fallbackSnapshots[j]);
|
||||
|
||||
// Switch to primary mode.
|
||||
act(() =>
|
||||
root.render(
|
||||
<Root>
|
||||
<X />
|
||||
<React.Suspense fallback={steps[j]}>
|
||||
<MaybeSuspend suspend={false}>{steps[i]}</MaybeSuspend>
|
||||
</React.Suspense>
|
||||
<Y />
|
||||
</Root>
|
||||
)
|
||||
);
|
||||
// Fallback is still forced though.
|
||||
expect(print(store)).toEqual(fallbackSnapshots[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(() => root.unmount());
|
||||
expect(print(store)).toBe('');
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
+107
-42
@@ -574,13 +574,21 @@ export function attach(
|
||||
}
|
||||
|
||||
let pendingOperations: Array<number> = [];
|
||||
let pendingOperationsQueue: Array<Array<number>> | null = [];
|
||||
let pendingRealUnmountedIDs: Array<number> = [];
|
||||
let pendingSimulatedUnmountedIDs: Array<number> = [];
|
||||
let pendingOperationsQueue: Array<Uint32Array> | null = [];
|
||||
|
||||
// We keep track of which Fibers have been reported as unmounted by React
|
||||
// during this commit phase so that we don't try to "hide" them or their
|
||||
// children when Suspense flips to fallback. These Fibers won't have IDs.
|
||||
let fibersUnmountedInThisCommitPhase: WeakSet<Fiber> = new WeakSet();
|
||||
|
||||
// TODO: we could make this layer DEV-only and write directly to pendingOperations.
|
||||
let nextOperation: Array<number> = [];
|
||||
function beginNextOperation(size: number): void {
|
||||
nextOperation.length = size;
|
||||
}
|
||||
function endNextOperation(addToStartOfQueue: boolean): void {
|
||||
function endNextOperation(): void {
|
||||
if (__DEV__) {
|
||||
for (let i = 0; i < nextOperation.length; i++) {
|
||||
if (!Number.isInteger(nextOperation[i])) {
|
||||
@@ -591,20 +599,16 @@ export function attach(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (addToStartOfQueue) {
|
||||
pendingOperations.splice.apply(
|
||||
pendingOperations,
|
||||
[0, 0].concat(nextOperation)
|
||||
);
|
||||
} else {
|
||||
pendingOperations.push.apply(pendingOperations, nextOperation);
|
||||
}
|
||||
pendingOperations.push.apply(pendingOperations, nextOperation);
|
||||
nextOperation.length = 0;
|
||||
}
|
||||
|
||||
function flushPendingEvents(root: Object): void {
|
||||
if (pendingOperations.length === 0) {
|
||||
if (
|
||||
pendingOperations.length === 0 &&
|
||||
pendingRealUnmountedIDs.length === 0 &&
|
||||
pendingSimulatedUnmountedIDs.length === 0
|
||||
) {
|
||||
// If we're currently profiling, send an "operations" method even if there are no mutations to the tree.
|
||||
// The frontend needs this no-op info to know how to reconstruct the tree for each commit,
|
||||
// even if a particular commit didn't change the shape of the tree.
|
||||
@@ -613,13 +617,44 @@ export function attach(
|
||||
}
|
||||
}
|
||||
|
||||
const ops = new Uint32Array(
|
||||
// Identify which renderer this update is coming from.
|
||||
2 + // [rendererID, rootFiberID]
|
||||
// All unmounts are batched in a single message.
|
||||
2 + // [TREE_OPERATION_REMOVE, removedIDLength]
|
||||
pendingRealUnmountedIDs.length +
|
||||
pendingSimulatedUnmountedIDs.length +
|
||||
// Regular operations
|
||||
pendingOperations.length
|
||||
);
|
||||
|
||||
// Identify which renderer this update is coming from.
|
||||
// This enables roots to be mapped to renderers,
|
||||
// Which in turn enables fiber props, states, and hooks to be inspected.
|
||||
beginNextOperation(2);
|
||||
nextOperation[0] = rendererID;
|
||||
nextOperation[1] = getFiberID(getPrimaryFiber(root.current));
|
||||
endNextOperation(true);
|
||||
let i = 0;
|
||||
ops[i++] = rendererID;
|
||||
ops[i++] = getFiberID(getPrimaryFiber(root.current));
|
||||
|
||||
// All unmounts except roots are batched in a single message.
|
||||
ops[i++] = TREE_OPERATION_REMOVE;
|
||||
// The first number is how many unmounted IDs we're gonna send.
|
||||
ops[i++] =
|
||||
pendingRealUnmountedIDs.length + pendingSimulatedUnmountedIDs.length;
|
||||
// Fill in the real unmounts in the reverse order.
|
||||
// They were inserted parents-first by React, but we want children-first.
|
||||
// So we traverse our array backwards.
|
||||
for (let j = pendingRealUnmountedIDs.length - 1; j >= 0; j--) {
|
||||
ops[i++] = pendingRealUnmountedIDs[j];
|
||||
}
|
||||
// Fill in the simulated unmounts (hidden Suspense subtrees) in their order.
|
||||
// (We want children to go before parents.)
|
||||
// They go *after* the real unmounts because we know for sure they won't be
|
||||
// children of already pushed "real" IDs. If they were, we wouldn't be able
|
||||
// to discover them during the traversal, as they would have been deleted.
|
||||
ops.set(pendingSimulatedUnmountedIDs, i);
|
||||
i += pendingSimulatedUnmountedIDs.length;
|
||||
// Fill in the rest of the operations.
|
||||
ops.set(pendingOperations, i);
|
||||
|
||||
// Let the frontend know about tree operations.
|
||||
// The first value in this array will identify which root it corresponds to,
|
||||
@@ -628,13 +663,16 @@ export function attach(
|
||||
// Until the frontend has been connected, store the tree operations.
|
||||
// This will let us avoid walking the tree later when the frontend connects,
|
||||
// and it enables the Profiler's reload-and-profile functionality to work as well.
|
||||
pendingOperationsQueue.push(pendingOperations);
|
||||
pendingOperationsQueue.push(ops);
|
||||
} else {
|
||||
// If we've already connected to the frontend, just pass the operations through.
|
||||
hook.emit('operations', Uint32Array.from(pendingOperations));
|
||||
hook.emit('operations', ops);
|
||||
}
|
||||
|
||||
pendingOperations = [];
|
||||
pendingOperations.length = 0;
|
||||
pendingRealUnmountedIDs.length = 0;
|
||||
pendingSimulatedUnmountedIDs.length = 0;
|
||||
fibersUnmountedInThisCommitPhase = new WeakSet();
|
||||
}
|
||||
|
||||
function recordMount(fiber: Fiber, parentFiber: Fiber | null) {
|
||||
@@ -656,7 +694,7 @@ export function attach(
|
||||
nextOperation[2] = ElementTypeRoot;
|
||||
nextOperation[3] = isProfilingSupported ? 1 : 0;
|
||||
nextOperation[4] = hasOwnerMetadata ? 1 : 0;
|
||||
endNextOperation(false);
|
||||
endNextOperation();
|
||||
} else {
|
||||
const { displayName, key, type } = getDataForFiber(fiber);
|
||||
const { _debugOwner } = fiber;
|
||||
@@ -701,7 +739,7 @@ export function attach(
|
||||
nextOperation[6 + encodedDisplayNameSize + 1 + i] = encodedKey[i];
|
||||
}
|
||||
}
|
||||
endNextOperation(false);
|
||||
endNextOperation();
|
||||
}
|
||||
|
||||
if (isProfiling) {
|
||||
@@ -713,7 +751,7 @@ export function attach(
|
||||
nextOperation[0] = TREE_OPERATION_UPDATE_TREE_BASE_DURATION;
|
||||
nextOperation[1] = id;
|
||||
nextOperation[2] = treeBaseDuration;
|
||||
endNextOperation(false);
|
||||
endNextOperation();
|
||||
|
||||
const { actualDuration } = fiber;
|
||||
if (actualDuration > 0) {
|
||||
@@ -728,7 +766,7 @@ export function attach(
|
||||
}
|
||||
}
|
||||
|
||||
function recordUnmount(fiber: Fiber) {
|
||||
function recordUnmount(fiber: Fiber, isSimulated: boolean) {
|
||||
const isRoot = fiber.tag === HostRoot;
|
||||
const primaryFiber = getPrimaryFiber(fiber);
|
||||
if (!fiberToIDMap.has(primaryFiber)) {
|
||||
@@ -743,19 +781,22 @@ export function attach(
|
||||
}
|
||||
const id = getFiberID(primaryFiber);
|
||||
if (isRoot) {
|
||||
beginNextOperation(2);
|
||||
// Removing a root needs to happen at the end
|
||||
// so we don't batch it with other unmounts.
|
||||
beginNextOperation(3);
|
||||
nextOperation[0] = TREE_OPERATION_REMOVE;
|
||||
nextOperation[1] = id;
|
||||
endNextOperation(false);
|
||||
nextOperation[1] = 1; // Remove one item
|
||||
nextOperation[2] = id;
|
||||
endNextOperation();
|
||||
} else if (!shouldFilterFiber(fiber)) {
|
||||
beginNextOperation(2);
|
||||
nextOperation[0] = TREE_OPERATION_REMOVE;
|
||||
nextOperation[1] = id;
|
||||
// Non-root fibers are deleted during the commit phase.
|
||||
// They are deleted in the parent-first order. However
|
||||
// DevTools currently expects deletions to be child-first.
|
||||
// This is why we prepend the delete operation to the queue.
|
||||
endNextOperation(true);
|
||||
// To maintain child-first ordering,
|
||||
// we'll push it into one of these queues,
|
||||
// and later arrange them in the correct order.
|
||||
if (isSimulated) {
|
||||
pendingSimulatedUnmountedIDs.push(id);
|
||||
} else {
|
||||
pendingRealUnmountedIDs.push(id);
|
||||
}
|
||||
}
|
||||
fiberToIDMap.delete(primaryFiber);
|
||||
idToFiberMap.delete(id);
|
||||
@@ -815,14 +856,35 @@ export function attach(
|
||||
}
|
||||
}
|
||||
|
||||
// We use this to simulate unmounting for Suspense trees
|
||||
// when we switch from primary to fallback.
|
||||
function unmountFiberChildrenRecursively(fiber: Fiber) {
|
||||
if (__DEBUG__) {
|
||||
debug('unmountFiberChildrenRecursively()', fiber);
|
||||
}
|
||||
|
||||
// We might meet a nested Suspense on our way.
|
||||
const isTimedOutSuspense =
|
||||
fiber.tag === ReactTypeOfWork.SuspenseComponent &&
|
||||
fiber.memoizedState !== null;
|
||||
|
||||
let child = fiber.child;
|
||||
if (isTimedOutSuspense) {
|
||||
// If it's showing fallback tree, let's traverse it instead.
|
||||
const primaryChildFragment = fiber.child;
|
||||
const fallbackChildFragment = primaryChildFragment.sibling;
|
||||
// Skip over to the real Fiber child.
|
||||
child = fallbackChildFragment.child;
|
||||
}
|
||||
|
||||
while (child !== null) {
|
||||
recordUnmount(child);
|
||||
unmountFiberChildrenRecursively(child);
|
||||
// Record simulated unmounts children-first.
|
||||
// We might find real committed unmounts along the way--skip them.
|
||||
// Otherwise we would send duplicated messages for the same IDs.
|
||||
if (!fibersUnmountedInThisCommitPhase.has(child)) {
|
||||
unmountFiberChildrenRecursively(child);
|
||||
recordUnmount(child, true);
|
||||
}
|
||||
child = child.sibling;
|
||||
}
|
||||
}
|
||||
@@ -843,7 +905,7 @@ export function attach(
|
||||
nextOperation[0] = TREE_OPERATION_UPDATE_TREE_BASE_DURATION;
|
||||
nextOperation[1] = getFiberID(getPrimaryFiber(fiber));
|
||||
nextOperation[2] = treeBaseDuration;
|
||||
endNextOperation(false);
|
||||
endNextOperation();
|
||||
}
|
||||
|
||||
if (haveProfilerTimesChanged(fiber.alternate, fiber)) {
|
||||
@@ -882,7 +944,7 @@ export function attach(
|
||||
for (let i = 0; i < nextChildren.length; i++) {
|
||||
nextOperation[3 + i] = nextChildren[i];
|
||||
}
|
||||
endNextOperation(false);
|
||||
endNextOperation();
|
||||
}
|
||||
|
||||
function findReorderedChildrenRecursively(
|
||||
@@ -1058,8 +1120,8 @@ export function attach(
|
||||
) {
|
||||
// We may have already queued up some operations before the frontend connected
|
||||
// If so, let the frontend know about them.
|
||||
localPendingOperationsQueue.forEach(pendingOperations => {
|
||||
hook.emit('operations', Uint32Array.from(pendingOperations));
|
||||
localPendingOperationsQueue.forEach(ops => {
|
||||
hook.emit('operations', ops);
|
||||
});
|
||||
} else {
|
||||
// If we have not been profiling, then we can just walk the tree and build up its current state as-is.
|
||||
@@ -1090,10 +1152,13 @@ export function attach(
|
||||
}
|
||||
|
||||
function handleCommitFiberUnmount(fiber) {
|
||||
// Remeber this is a real deletion so we don't
|
||||
// go down this tree when hiding Suspense nodes.
|
||||
fibersUnmountedInThisCommitPhase.add(fiber);
|
||||
// This is not recursive.
|
||||
// We can't traverse fibers after unmounting so instead
|
||||
// we rely on React telling us about each unmount.
|
||||
recordUnmount(fiber);
|
||||
recordUnmount(fiber, false);
|
||||
}
|
||||
|
||||
function handleCommitFiberRoot(root) {
|
||||
@@ -1133,7 +1198,7 @@ export function attach(
|
||||
updateFiberRecursively(current, alternate, null);
|
||||
} else if (wasMounted && !isMounted) {
|
||||
// Unmount an existing root.
|
||||
recordUnmount(current);
|
||||
recordUnmount(current, false);
|
||||
}
|
||||
} else {
|
||||
// Mount a new root.
|
||||
|
||||
+115
-124
@@ -543,6 +543,33 @@ export default class Store extends EventEmitter {
|
||||
}
|
||||
};
|
||||
|
||||
_adjustParentTreeWeight = (
|
||||
parentElement: Element | null,
|
||||
weightDelta: number
|
||||
) => {
|
||||
let isInsideCollapsedSubTree = false;
|
||||
|
||||
while (parentElement != null) {
|
||||
parentElement.weight += weightDelta;
|
||||
|
||||
// Additions and deletions within a collapsed subtree should not bubble beyond the collapsed parent.
|
||||
// Their weight will bubble up when the parent is expanded.
|
||||
if (parentElement.isCollapsed) {
|
||||
isInsideCollapsedSubTree = true;
|
||||
break;
|
||||
}
|
||||
|
||||
parentElement = ((this._idToElement.get(
|
||||
parentElement.parentID
|
||||
): any): Element);
|
||||
}
|
||||
|
||||
// Additions and deletions within a collapsed subtree should not affect the overall number of elements.
|
||||
if (!isInsideCollapsedSubTree) {
|
||||
this._weightAcrossRoots += weightDelta;
|
||||
}
|
||||
};
|
||||
|
||||
onBridgeOperations = (operations: Uint32Array) => {
|
||||
if (!(operations instanceof Uint32Array)) {
|
||||
// $FlowFixMe TODO HACK Temporary workaround for the fact that Chrome is not transferring the typed array.
|
||||
@@ -574,25 +601,16 @@ export default class Store extends EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
let addedElementIDs: Uint32Array = new Uint32Array(0);
|
||||
let removedElementIDs: Uint32Array = new Uint32Array(0);
|
||||
const addedElementIDs: Array<number> = [];
|
||||
const removedElementIDs: Array<number> = [];
|
||||
|
||||
let i = 2;
|
||||
while (i < operations.length) {
|
||||
let id: number = ((null: any): number);
|
||||
let element: Element = ((null: any): Element);
|
||||
let ownerID: number = 0;
|
||||
let parentID: number = ((null: any): number);
|
||||
let parentElement: Element = ((null: any): Element);
|
||||
let type: ElementType = ((null: any): ElementType);
|
||||
let weightDelta: number = 0;
|
||||
|
||||
const operation = operations[i];
|
||||
|
||||
switch (operation) {
|
||||
case TREE_OPERATION_ADD:
|
||||
id = ((operations[i + 1]: any): number);
|
||||
type = ((operations[i + 2]: any): ElementType);
|
||||
case TREE_OPERATION_ADD: {
|
||||
const id = ((operations[i + 1]: any): number);
|
||||
const type = ((operations[i + 2]: any): ElementType);
|
||||
|
||||
i = i + 3;
|
||||
|
||||
@@ -602,6 +620,8 @@ export default class Store extends EventEmitter {
|
||||
);
|
||||
}
|
||||
|
||||
let ownerID: number = 0;
|
||||
let parentID: number = ((null: any): number);
|
||||
if (type === ElementTypeRoot) {
|
||||
if (__DEBUG__) {
|
||||
debug('Add', `new root node ${id}`);
|
||||
@@ -672,7 +692,9 @@ export default class Store extends EventEmitter {
|
||||
);
|
||||
}
|
||||
|
||||
parentElement = ((this._idToElement.get(parentID): any): Element);
|
||||
const parentElement = ((this._idToElement.get(
|
||||
parentID
|
||||
): any): Element);
|
||||
parentElement.children = parentElement.children.concat(id);
|
||||
|
||||
const element: Element = {
|
||||
@@ -689,70 +711,67 @@ export default class Store extends EventEmitter {
|
||||
};
|
||||
|
||||
this._idToElement.set(id, element);
|
||||
|
||||
const oldAddedElementIDs = addedElementIDs;
|
||||
addedElementIDs = new Uint32Array(addedElementIDs.length + 1);
|
||||
addedElementIDs.set(oldAddedElementIDs);
|
||||
addedElementIDs[oldAddedElementIDs.length] = id;
|
||||
|
||||
weightDelta = 1;
|
||||
addedElementIDs.push(id);
|
||||
this._adjustParentTreeWeight(parentElement, 1);
|
||||
}
|
||||
break;
|
||||
case TREE_OPERATION_REMOVE: {
|
||||
id = ((operations[i + 1]: any): number);
|
||||
|
||||
if (!this._idToElement.has(id)) {
|
||||
throw Error(
|
||||
`Cannot remove node ${id} because no matching node was found in the Store.`
|
||||
);
|
||||
}
|
||||
|
||||
i = i + 2;
|
||||
|
||||
element = ((this._idToElement.get(id): any): Element);
|
||||
parentID = element.parentID;
|
||||
weightDelta = -element.weight;
|
||||
|
||||
if (element.children.length > 0) {
|
||||
throw new Error(`Node ${id} was removed before its children.`);
|
||||
}
|
||||
|
||||
this._idToElement.delete(id);
|
||||
|
||||
if (parentID === 0) {
|
||||
if (__DEBUG__) {
|
||||
debug('Remove', `node ${id} root`);
|
||||
}
|
||||
|
||||
this._roots = this._roots.filter(rootID => rootID !== id);
|
||||
this._rootIDToRendererID.delete(id);
|
||||
this._rootIDToCapabilities.delete(id);
|
||||
|
||||
haveRootsChanged = true;
|
||||
} else {
|
||||
if (__DEBUG__) {
|
||||
debug('Remove', `node ${id} from parent ${parentID}`);
|
||||
}
|
||||
parentElement = ((this._idToElement.get(parentID): any): Element);
|
||||
if (parentElement === undefined) {
|
||||
throw Error(
|
||||
`Cannot remove node ${id} from parent ${parentID} because no matching node was found in the Store.`
|
||||
);
|
||||
}
|
||||
parentElement.children = parentElement.children.filter(
|
||||
childID => childID !== id
|
||||
);
|
||||
}
|
||||
|
||||
// Track removed items so search results can be updated
|
||||
const oldRemovedElementIDs = removedElementIDs;
|
||||
removedElementIDs = new Uint32Array(removedElementIDs.length + 1);
|
||||
removedElementIDs.set(oldRemovedElementIDs);
|
||||
removedElementIDs[oldRemovedElementIDs.length] = id;
|
||||
break;
|
||||
}
|
||||
case TREE_OPERATION_REORDER_CHILDREN:
|
||||
id = ((operations[i + 1]: any): number);
|
||||
case TREE_OPERATION_REMOVE: {
|
||||
const removeLength = ((operations[i + 1]: any): number);
|
||||
i = i + 2;
|
||||
|
||||
for (let removeIndex = 0; removeIndex < removeLength; removeIndex++) {
|
||||
const id = ((operations[i]: any): number);
|
||||
|
||||
if (!this._idToElement.has(id)) {
|
||||
throw Error(
|
||||
`Cannot remove node ${id} because no matching node was found in the Store.`
|
||||
);
|
||||
}
|
||||
|
||||
i = i + 1;
|
||||
|
||||
const element = ((this._idToElement.get(id): any): Element);
|
||||
if (element.children.length > 0) {
|
||||
throw new Error(`Node ${id} was removed before its children.`);
|
||||
}
|
||||
|
||||
this._idToElement.delete(id);
|
||||
|
||||
const parentID = element.parentID;
|
||||
let parentElement = null;
|
||||
if (parentID === 0) {
|
||||
if (__DEBUG__) {
|
||||
debug('Remove', `node ${id} root`);
|
||||
}
|
||||
|
||||
this._roots = this._roots.filter(rootID => rootID !== id);
|
||||
this._rootIDToRendererID.delete(id);
|
||||
this._rootIDToCapabilities.delete(id);
|
||||
|
||||
haveRootsChanged = true;
|
||||
} else {
|
||||
if (__DEBUG__) {
|
||||
debug('Remove', `node ${id} from parent ${parentID}`);
|
||||
}
|
||||
parentElement = ((this._idToElement.get(parentID): any): Element);
|
||||
if (parentElement === undefined) {
|
||||
throw Error(
|
||||
`Cannot remove node ${id} from parent ${parentID} because no matching node was found in the Store.`
|
||||
);
|
||||
}
|
||||
parentElement.children = parentElement.children.filter(
|
||||
childID => childID !== id
|
||||
);
|
||||
}
|
||||
|
||||
this._adjustParentTreeWeight(parentElement, -element.weight);
|
||||
removedElementIDs.push(id);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case TREE_OPERATION_REORDER_CHILDREN: {
|
||||
const id = ((operations[i + 1]: any): number);
|
||||
const numChildren = ((operations[i + 2]: any): number);
|
||||
const nextChildren = ((operations.slice(
|
||||
i + 3,
|
||||
@@ -771,38 +790,29 @@ export default class Store extends EventEmitter {
|
||||
);
|
||||
}
|
||||
|
||||
element = ((this._idToElement.get(id): any): Element);
|
||||
|
||||
const element = ((this._idToElement.get(id): any): Element);
|
||||
const prevChildren = element.children;
|
||||
if (
|
||||
nextChildren.length !== prevChildren.length ||
|
||||
nextChildren.find(childID => {
|
||||
const childElement = this._idToElement.get(childID);
|
||||
return childElement == null || childElement.parentID !== id;
|
||||
}) != null
|
||||
) {
|
||||
if (nextChildren.length !== prevChildren.length) {
|
||||
throw Error(
|
||||
`Children cannot be added or removed during a reorder operation.`
|
||||
);
|
||||
}
|
||||
|
||||
element.children = Array.from(nextChildren);
|
||||
|
||||
if (!element.isCollapsed) {
|
||||
const prevWeight = element.weight;
|
||||
|
||||
let nextWeight = element.type === ElementTypeRoot ? 0 : 1;
|
||||
|
||||
nextChildren.forEach(childID => {
|
||||
const child = ((this._idToElement.get(childID): any): Element);
|
||||
nextWeight += child.isCollapsed ? 1 : child.weight;
|
||||
});
|
||||
|
||||
element.weight = nextWeight;
|
||||
|
||||
weightDelta = nextWeight - prevWeight;
|
||||
// This check is more expensive so it's gated
|
||||
if (__DEV__) {
|
||||
if (
|
||||
nextChildren.find(childID => {
|
||||
const childElement = this._idToElement.get(childID);
|
||||
return childElement == null || childElement.parentID !== id;
|
||||
}) != null
|
||||
) {
|
||||
console.error(
|
||||
`Children cannot be added or removed during a reorder operation.`
|
||||
);
|
||||
}
|
||||
}
|
||||
element.children = Array.from(nextChildren);
|
||||
break;
|
||||
}
|
||||
case TREE_OPERATION_UPDATE_TREE_BASE_DURATION:
|
||||
// Base duration updates are only sent while profiling is in progress.
|
||||
// We can ignore them at this point.
|
||||
@@ -812,28 +822,6 @@ export default class Store extends EventEmitter {
|
||||
default:
|
||||
throw Error(`Unsupported Bridge operation ${operation}`);
|
||||
}
|
||||
|
||||
let isInsideCollapsedSubTree = false;
|
||||
|
||||
while (parentElement != null) {
|
||||
parentElement.weight += weightDelta;
|
||||
|
||||
// Additions and deletions within a collapsed subtree should not bubble beyond the collapsed parent.
|
||||
// Their weight will bubble up when the parent is expanded.
|
||||
if (parentElement.isCollapsed) {
|
||||
isInsideCollapsedSubTree = true;
|
||||
break;
|
||||
}
|
||||
|
||||
parentElement = ((this._idToElement.get(
|
||||
parentElement.parentID
|
||||
): any): Element);
|
||||
}
|
||||
|
||||
// Additions and deletions within a collapsed subtree should not affect the overall number of elements.
|
||||
if (!isInsideCollapsedSubTree) {
|
||||
this._weightAcrossRoots += weightDelta;
|
||||
}
|
||||
}
|
||||
|
||||
this._revision++;
|
||||
@@ -860,7 +848,10 @@ export default class Store extends EventEmitter {
|
||||
console.groupEnd();
|
||||
}
|
||||
|
||||
this.emit('mutated', [addedElementIDs, removedElementIDs]);
|
||||
this.emit('mutated', [
|
||||
new Uint32Array(addedElementIDs),
|
||||
new Uint32Array(removedElementIDs),
|
||||
]);
|
||||
};
|
||||
|
||||
onProfilingStatus = (isProfiling: boolean) => {
|
||||
|
||||
@@ -168,18 +168,12 @@ function updateTree(
|
||||
|
||||
let i = 2;
|
||||
while (i < operations.length) {
|
||||
let id: number = ((null: any): number);
|
||||
let node: Node = ((null: any): Node);
|
||||
let parentID: number = ((null: any): number);
|
||||
let parentNode: Node = ((null: any): Node);
|
||||
let type: ElementType = ((null: any): ElementType);
|
||||
|
||||
const operation = operations[i];
|
||||
|
||||
switch (operation) {
|
||||
case TREE_OPERATION_ADD:
|
||||
id = ((operations[i + 1]: any): number);
|
||||
type = ((operations[i + 2]: any): ElementType);
|
||||
const id = ((operations[i + 1]: any): number);
|
||||
const type = ((operations[i + 2]: any): ElementType);
|
||||
|
||||
i = i + 3;
|
||||
|
||||
@@ -210,7 +204,7 @@ function updateTree(
|
||||
|
||||
nodes.set(id, node);
|
||||
} else {
|
||||
parentID = ((operations[i]: any): number);
|
||||
const parentID = ((operations[i]: any): number);
|
||||
i++;
|
||||
|
||||
i++; // ownerID
|
||||
@@ -240,7 +234,7 @@ function updateTree(
|
||||
);
|
||||
}
|
||||
|
||||
parentNode = getClonedNode(parentID);
|
||||
const parentNode = getClonedNode(parentID);
|
||||
parentNode.children = parentNode.children.concat(id);
|
||||
|
||||
const node: Node = {
|
||||
@@ -254,40 +248,46 @@ function updateTree(
|
||||
|
||||
nodes.set(id, node);
|
||||
}
|
||||
break;
|
||||
case TREE_OPERATION_REMOVE:
|
||||
id = ((operations[i + 1]: any): number);
|
||||
|
||||
break;
|
||||
case TREE_OPERATION_REMOVE: {
|
||||
const removeLength = ((operations[i + 1]: any): number);
|
||||
i = i + 2;
|
||||
|
||||
if (!nodes.has(id)) {
|
||||
throw new Error(
|
||||
'Commit tree does not contain fiber ' +
|
||||
id +
|
||||
'. This is a bug in React DevTools.'
|
||||
);
|
||||
}
|
||||
for (let removeIndex = 0; removeIndex < removeLength; removeIndex++) {
|
||||
const id = ((operations[i]: any): number);
|
||||
i = i + 1;
|
||||
|
||||
node = getClonedNode(id);
|
||||
parentID = node.parentID;
|
||||
|
||||
nodes.delete(id);
|
||||
|
||||
parentNode = getClonedNode(parentID);
|
||||
if (parentNode == null) {
|
||||
// No-op
|
||||
} else {
|
||||
if (__DEBUG__) {
|
||||
debug('Remove', `fiber ${id} from parent ${parentID}`);
|
||||
if (!nodes.has(id)) {
|
||||
throw new Error(
|
||||
'Commit tree does not contain fiber ' +
|
||||
id +
|
||||
'. This is a bug in React DevTools.'
|
||||
);
|
||||
}
|
||||
|
||||
parentNode.children = parentNode.children.filter(
|
||||
childID => childID !== id
|
||||
);
|
||||
const node = getClonedNode(id);
|
||||
const parentID = node.parentID;
|
||||
|
||||
nodes.delete(id);
|
||||
|
||||
const parentNode = getClonedNode(parentID);
|
||||
if (parentNode == null) {
|
||||
// No-op
|
||||
} else {
|
||||
if (__DEBUG__) {
|
||||
debug('Remove', `fiber ${id} from parent ${parentID}`);
|
||||
}
|
||||
|
||||
parentNode.children = parentNode.children.filter(
|
||||
childID => childID !== id
|
||||
);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case TREE_OPERATION_REORDER_CHILDREN:
|
||||
id = ((operations[i + 1]: any): number);
|
||||
}
|
||||
case TREE_OPERATION_REORDER_CHILDREN: {
|
||||
const id = ((operations[i + 1]: any): number);
|
||||
const numChildren = ((operations[i + 2]: any): number);
|
||||
const children = ((operations.slice(
|
||||
i + 3,
|
||||
@@ -300,14 +300,15 @@ function updateTree(
|
||||
debug('Re-order', `fiber ${id} children ${children.join(',')}`);
|
||||
}
|
||||
|
||||
node = getClonedNode(id);
|
||||
const node = getClonedNode(id);
|
||||
node.children = Array.from(children);
|
||||
|
||||
break;
|
||||
case TREE_OPERATION_UPDATE_TREE_BASE_DURATION:
|
||||
id = operations[i + 1];
|
||||
}
|
||||
case TREE_OPERATION_UPDATE_TREE_BASE_DURATION: {
|
||||
const id = operations[i + 1];
|
||||
|
||||
node = getClonedNode(id);
|
||||
const node = getClonedNode(id);
|
||||
node.treeBaseDuration = operations[i + 2] / 1000; // Convert microseconds back to milliseconds;
|
||||
|
||||
if (__DEBUG__) {
|
||||
@@ -319,6 +320,7 @@ function updateTree(
|
||||
|
||||
i = i + 3;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw Error(`Unsupported Bridge operation ${operation}`);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user