storeStressSync Test
storeStressSync Test
beforeEach(() => {
bridge = global.bridge;
store = global.store;
store.collapseNodesByDefault = false;
React = require('react');
ReactDOM = require('react-dom');
print = require('./__serializers__/storeSerializer').print;
});
// 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).toMatchInlineSnapshot(`
[root]
▾ <Parent>
<A key="a">
<B key="b">
▾ <C key="c">
<X>
<D key="d">
<E key="e">
`);
expect(container.textContent).toMatch('abxde');
const snapshotForABXDE = print(store);
// 4. Clean up.
act(() => ReactDOM.unmountComponentAtNode(container));
expect(print(store)).toBe('');
// Clean up.
act(() => ReactDOM.unmountComponentAtNode(container));
expect(print(store)).toBe('');
}
// prettier-ignore
const steps = [
a,
b,
c,
d,
e,
[a],
[b],
[c],
[d],
[e],
[a, b],
[b, a],
[b, c],
[c, b],
[a, c],
[c, a],
];
const stepsSnapshot = [
`
[root]
▾ <Root>
<A key="a">
`,
`
[root]
▾ <Root>
<B key="b">
`,
`
[root]
▾ <Root>
<C key="c">
`,
`
[root]
▾ <Root>
<D key="d">
`,
`
[root]
▾ <Root>
<E key="e">
`,
`
[root]
▾ <Root>
<A key="a">
`,
`
[root]
▾ <Root>
<B key="b">
`,
`
[root]
▾ <Root>
<C key="c">
`,
`
[root]
▾ <Root>
<D key="d">
`,
`
[root]
▾ <Root>
<E key="e">
`,
`
[root]
▾ <Root>
<A key="a">
<B key="b">
`,
`
[root]
▾ <Root>
<B key="b">
<A key="a">
`,
`
[root]
▾ <Root>
<B key="b">
<C key="c">
`,
`
[root]
▾ <Root>
<C key="c">
<B key="b">
`,
`
[root]
▾ <Root>
<A key="a">
<C key="c">
`,
`
[root]
▾ <Root>
<C key="c">
<A key="a">
`,
];
// 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++) {
container = document.createElement('div');
act(() => legacyRender(<Root>{steps[i]}</Root>, container));
expect(print(store)).toMatch(snapshots[i]);
act(() => legacyRender(<Root>{steps[j]}</Root>, container));
expect(print(store)).toMatch(snapshots[j]);
act(() => legacyRender(<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++) {
container = document.createElement('div');
act(() =>
legacyRender(
<Root>
<div>{steps[i]}</div>
</Root>,
container,
),
);
expect(print(store)).toMatch(snapshots[i]);
act(() =>
legacyRender(
<Root>
<div>{steps[j]}</div>
</Root>,
container,
),
);
expect(print(store)).toMatch(snapshots[j]);
act(() =>
legacyRender(
<Root>
<div>{steps[i]}</div>
</Root>,
container,
),
);
expect(print(store)).toMatch(snapshots[i]);
act(() => ReactDOM.unmountComponentAtNode(container));
expect(print(store)).toBe('');
}
}
});
// 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 stepsSnapshot = [
`
[root]
▾ <Root>
<X>
▾ <Suspense>
<A key="a">
<Y>
`,
`
[root]
▾ <Root>
<X>
▾ <Suspense>
<A key="a">
<Y>
`,
`
[root]
▾ <Root>
<X>
▾ <Suspense>
<A key="a">
<B key="b">
<C key="c">
<Y>
`,
`
[root]
▾ <Root>
<X>
▾ <Suspense>
<C key="c">
<B key="b">
<A key="a">
<Y>
`,
`
[root]
▾ <Root>
<X>
▾ <Suspense>
<C key="c">
<A key="a">
<Y>
`,
`
[root]
▾ <Root>
<X>
▾ <Suspense>
<C key="c">
<A key="a">
<Y>
`,
`
[root]
▾ <Root>
<X>
▾ <Suspense>
<C key="c">
<A key="a">
<Y>
`,
`
[root]
▾ <Root>
<X>
▾ <Suspense>
<A key="a">
<B key="b">
<Y>
`,
`
[root]
▾ <Root>
<X>
▾ <Suspense>
<A key="a">
<Y>
`,
`
[root]
▾ <Root>
<X>
<Suspense>
<Y>
`,
`
[root]
▾ <Root>
<X>
▾ <Suspense>
<B key="b">
<Y>
`,
`
[root]
▾ <Root>
<X>
▾ <Suspense>
<A key="a">
<Y>
`,
];
// 2. Verify check Suspense can render same steps as initial fallback content.
for (let i = 0; i < steps.length; i++) {
act(() =>
legacyRender(
<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(() =>
legacyRender(
<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(() =>
legacyRender(
<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(() =>
legacyRender(
<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(() =>
legacyRender(
<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(() =>
legacyRender(
<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(() =>
legacyRender(
<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(() =>
legacyRender(
<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(() =>
legacyRender(
<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(() =>
legacyRender(
<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(() =>
legacyRender(
<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(() =>
legacyRender(
<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(() =>
legacyRender(
<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(() =>
legacyRender(
<Root>
<X />
<React.Suspense fallback={steps[j]}>{steps[i]}</React.Suspense>
<Y />
</Root>,
container,
),
);
// Force fallback.
expect(print(store)).toEqual(snapshots[i]);
act(() => {
bridge.send('overrideSuspense', {
id: suspenseID,
rendererID: store.getRendererIDForElement(suspenseID),
forceFallback: true,
});
});
expect(print(store)).toEqual(snapshots[j]);
// 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 stepsSnapshot = [
`
[root]
▾ <Root>
<X>
▾ <Suspense>
▾ <MaybeSuspend>
<A key="a">
<Z>
<Y>
`,
`
[root]
▾ <Root>
<X>
▾ <Suspense>
▾ <MaybeSuspend>
<A key="a">
<Z>
<Y>
`,
`
[root]
▾ <Root>
<X>
▾ <Suspense>
▾ <MaybeSuspend>
<A key="a">
<B key="b">
<C key="c">
<Z>
<Y>
`,
`
[root]
▾ <Root>
<X>
▾ <Suspense>
▾ <MaybeSuspend>
<C key="c">
<B key="b">
<A key="a">
<Z>
<Y>
`,
`
[root]
▾ <Root>
<X>
▾ <Suspense>
▾ <MaybeSuspend>
<C key="c">
<A key="a">
<Z>
<Y>
`,
`
[root]
▾ <Root>
<X>
▾ <Suspense>
▾ <MaybeSuspend>
<C key="c">
<A key="a">
<Z>
<Y>
`,
`
[root]
▾ <Root>
<X>
▾ <Suspense>
▾ <MaybeSuspend>
<C key="c">
<A key="a">
<Z>
<Y>
`,
`
[root]
▾ <Root>
<X>
▾ <Suspense>
▾ <MaybeSuspend>
<A key="a">
<B key="b">
<Z>
<Y>
`,
`
[root]
▾ <Root>
<X>
▾ <Suspense>
▾ <MaybeSuspend>
<A key="a">
<Z>
<Y>
`,
`
[root]
▾ <Root>
<X>
▾ <Suspense>
▾ <MaybeSuspend>
<Z>
<Y>
`,
`
[root]
▾ <Root>
<X>
▾ <Suspense>
▾ <MaybeSuspend>
<B key="b">
<Z>
<Y>
`,
`
[root]
▾ <Root>
<X>
▾ <Suspense>
▾ <MaybeSuspend>
<A key="a">
<Z>
<Y>
`,
];
const stepsSnapshotTwo = [
`
[root]
▾ <Root>
<X>
▾ <Suspense>
<A key="a">
<Y>
`,
`
[root]
▾ <Root>
<X>
▾ <Suspense>
<A key="a">
<Y>
`,
`
[root]
▾ <Root>
<X>
▾ <Suspense>
<A key="a">
<B key="b">
<C key="c">
<Y>
`,
`
[root]
▾ <Root>
<X>
▾ <Suspense>
<C key="c">
<B key="b">
<A key="a">
<Y>
`,
`
[root]
▾ <Root>
<X>
▾ <Suspense>
<C key="c">
<A key="a">
<Y>
`,
`
[root]
▾ <Root>
<X>
▾ <Suspense>
<C key="c">
<A key="a">
<Y>
`,
`
[root]
▾ <Root>
<X>
▾ <Suspense>
<C key="c">
<A key="a">
<Y>
`,
`
[root]
▾ <Root>
<X>
▾ <Suspense>
<A key="a">
<B key="b">
<Y>
`,
`
[root]
▾ <Root>
<X>
▾ <Suspense>
<A key="a">
<Y>
`,
`
[root]
▾ <Root>
<X>
<Suspense>
<Y>
`,
`
[root]
▾ <Root>
<X>
▾ <Suspense>
<B key="b">
<Y>
`,
`
[root]
▾ <Root>
<X>
▾ <Suspense>
<A key="a">
<Y>
`,
];
// 1. For each step, check Suspense can render them as initial primary content.
// This is the only step where we use Jest snapshots.
const snapshots = [];
let container = document.createElement('div');
for (let i = 0; i < steps.length; i++) {
act(() =>
legacyRender(
<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).toMatchInlineSnapshot(stepsSnapshot[i]);
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.
const fallbackSnapshots = [];
for (let i = 0; i < steps.length; i++) {
act(() =>
legacyRender(
<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).toMatchInlineSnapshot(stepsSnapshotTwo[i]);
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(() =>
legacyRender(
<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(() =>
legacyRender(
<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(() =>
legacyRender(
<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(() =>
legacyRender(
<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(() =>
legacyRender(
<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(() =>
legacyRender(
<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(() =>
legacyRender(
<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(() =>
legacyRender(
<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(() =>
legacyRender(
<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(() =>
legacyRender(
<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(() =>
legacyRender(
<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(() =>
legacyRender(
<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(() =>
legacyRender(
<Root>
<X />
<React.Suspense fallback={steps[j]}>
<MaybeSuspend suspend={false}>{steps[i]}</MaybeSuspend>
</React.Suspense>
<Y />
</Root>,
container,
),
);
// Force fallback.
expect(print(store)).toEqual(snapshots[i]);
act(() => {
bridge.send('overrideSuspense', {
id: suspenseID,
rendererID: store.getRendererIDForElement(suspenseID),
forceFallback: true,
});
});
expect(print(store)).toEqual(fallbackSnapshots[j]);