▾
`);
await act(() => {
render(
e2
e1
,
);
});
expect(store).toMatchInlineSnapshot(`
[root]
▾
▾
▾
`);
});
describe('StrictMode compliance', () => {
it('should mark strict root elements as strict', async () => {
const App = () => ;
const Component = () => null;
const container = document.createElement('div');
const root = ReactDOMClient.createRoot(container, {
unstable_strictMode: true,
});
await act(() => {
root.render();
});
expect(store.getElementAtIndex(0).isStrictModeNonCompliant).toBe(false);
expect(store.getElementAtIndex(1).isStrictModeNonCompliant).toBe(false);
});
// @reactVersion >= 18.0
it('should mark non strict root elements as not strict', async () => {
const App = () => ;
const Component = () => null;
const container = document.createElement('div');
const root = ReactDOMClient.createRoot(container);
await act(() => {
root.render();
});
expect(store.getElementAtIndex(0).isStrictModeNonCompliant).toBe(true);
expect(store.getElementAtIndex(1).isStrictModeNonCompliant).toBe(true);
});
it('should mark StrictMode subtree elements as strict', async () => {
const App = () => (
);
const Component = () => null;
const container = document.createElement('div');
const root = ReactDOMClient.createRoot(container);
await act(() => {
root.render();
});
expect(store.getElementAtIndex(0).isStrictModeNonCompliant).toBe(true);
expect(store.getElementAtIndex(1).isStrictModeNonCompliant).toBe(false);
});
});
describe('Activity hidden state', () => {
// @reactVersion >= 19
it('should mark Activity subtree elements as hidden when mode is hidden', async () => {
const Activity = React.Activity || React.unstable_Activity;
function Child() {
return
child
;
}
function App({hidden}) {
return (
);
}
await actAsync(() => {
render();
});
// Activity element should be marked as hidden and collapsed
const activityElement = store.getElementAtIndex(1);
expect(activityElement.displayName).toBe('Activity');
expect(activityElement.isActivityHidden).toBe(true);
expect(activityElement.isInsideHiddenActivity).toBe(false);
expect(activityElement.isCollapsed).toBe(true);
// Expand to access children
store.toggleIsCollapsed(activityElement.id, false);
// Children should still be in the tree but marked as inside hidden Activity
const childElement = store.getElementAtIndex(2);
expect(childElement.displayName).toBe('Child');
expect(childElement.isInsideHiddenActivity).toBe(true);
});
// @reactVersion >= 19
it('should not mark Activity subtree as hidden when mode is visible', async () => {
const Activity = React.Activity || React.unstable_Activity;
function Child() {
return child
;
}
function App() {
return (
);
}
await actAsync(() => {
render();
});
const activityElement = store.getElementAtIndex(1);
expect(activityElement.displayName).toBe('Activity');
expect(activityElement.isActivityHidden).toBe(false);
expect(activityElement.isInsideHiddenActivity).toBe(false);
expect(activityElement.isCollapsed).toBe(false);
const childElement = store.getElementAtIndex(2);
expect(childElement.displayName).toBe('Child');
expect(childElement.isInsideHiddenActivity).toBe(false);
});
// @reactVersion >= 19
it('should update hidden state when Activity mode toggles', async () => {
const Activity = React.Activity || React.unstable_Activity;
function Child() {
return child
;
}
function App({hidden}) {
return (
);
}
// Start visible
await actAsync(() => {
render();
});
let activityElement = store.getElementAtIndex(1);
expect(activityElement.isActivityHidden).toBe(false);
expect(activityElement.isCollapsed).toBe(false);
let childElement = store.getElementAtIndex(2);
expect(childElement.isInsideHiddenActivity).toBe(false);
// Toggle to hidden — children remain but subtree collapses
await actAsync(() => {
render();
});
activityElement = store.getElementAtIndex(1);
expect(activityElement.isActivityHidden).toBe(true);
expect(activityElement.isCollapsed).toBe(true);
// Expand to verify children are still marked
store.toggleIsCollapsed(activityElement.id, false);
childElement = store.getElementAtIndex(2);
expect(childElement.displayName).toBe('Child');
expect(childElement.isInsideHiddenActivity).toBe(true);
// Toggle back to visible — subtree expands automatically
await actAsync(() => {
render();
});
activityElement = store.getElementAtIndex(1);
expect(activityElement.isActivityHidden).toBe(false);
expect(activityElement.isCollapsed).toBe(false);
childElement = store.getElementAtIndex(2);
expect(childElement.isInsideHiddenActivity).toBe(false);
});
// @reactVersion >= 19
it('should propagate hidden state to deeply nested children', async () => {
const Activity = React.Activity || React.unstable_Activity;
function GrandChild() {
return grandchild
;
}
function Child() {
return ;
}
function App({hidden}) {
return (
);
}
await actAsync(() => {
render();
});
const activityElement = store.getElementAtIndex(1);
expect(activityElement.displayName).toBe('Activity');
expect(activityElement.isActivityHidden).toBe(true);
expect(activityElement.isCollapsed).toBe(true);
// Expand to access children
store.toggleIsCollapsed(activityElement.id, false);
const childElement = store.getElementAtIndex(2);
expect(childElement.displayName).toBe('Child');
expect(childElement.isInsideHiddenActivity).toBe(true);
const grandChildElement = store.getElementAtIndex(3);
expect(grandChildElement.displayName).toBe('GrandChild');
expect(grandChildElement.isInsideHiddenActivity).toBe(true);
});
// @reactVersion >= 19
it('should collapse hidden Activity subtree by default', async () => {
const Activity = React.Activity || React.unstable_Activity;
function Child() {
return child
;
}
function App({hidden}) {
return (
);
}
// Hidden Activity should be collapsed
await actAsync(() => {
render();
});
expect(store).toMatchInlineSnapshot(`
[root]
▾
▸
`);
// Toggle to visible — should expand
await actAsync(() => {
render();
});
expect(store).toMatchInlineSnapshot(`
[root]
▾
▾
`);
// Toggle back to hidden — should collapse again
await actAsync(() => {
render();
});
expect(store).toMatchInlineSnapshot(`
[root]
▾
▸
`);
});
// @reactVersion >= 19
it('should dim nested visible Activity inside a hidden Activity', async () => {
const Activity = React.Activity || React.unstable_Activity;
function Leaf() {
return leaf
;
}
function App() {
return (
);
}
await actAsync(() => {
render();
});
// Outer Activity: hidden, collapsed, not dimmed itself
const outerActivity = store.getElementAtIndex(1);
expect(outerActivity.displayName).toBe('Activity');
expect(outerActivity.nameProp).toBe('outer');
expect(outerActivity.isActivityHidden).toBe(true);
expect(outerActivity.isInsideHiddenActivity).toBe(false);
expect(outerActivity.isCollapsed).toBe(true);
// Expand to access inner elements
store.toggleIsCollapsed(outerActivity.id, false);
// Inner Activity: visible, but inside hidden outer so still dimmed
const innerActivity = store.getElementAtIndex(2);
expect(innerActivity.displayName).toBe('Activity');
expect(innerActivity.nameProp).toBe('inner');
expect(innerActivity.isActivityHidden).toBe(false);
expect(innerActivity.isInsideHiddenActivity).toBe(true);
// Leaf: inside both, dimmed
const leaf = store.getElementAtIndex(3);
expect(leaf.displayName).toBe('Leaf');
expect(leaf.isInsideHiddenActivity).toBe(true);
});
});
describe('collapseNodesByDefault:false', () => {
beforeEach(() => {
store.collapseNodesByDefault = false;
});
// @reactVersion >= 18.0
it('should support mount and update operations', async () => {
const Grandparent = ({count}) => (
);
const Parent = ({count}) =>
new Array(count).fill(true).map((_, index) => );
const Child = () => Hi!
;
await act(() => render());
expect(store).toMatchInlineSnapshot(`
[root]
▾
▾
▾
`);
await act(() => render());
expect(store).toMatchInlineSnapshot(`
[root]
▾
▾
▾
`);
await act(() => unmount());
expect(store).toMatchInlineSnapshot(``);
});
// @reactVersion >= 18.0
// @reactVersion < 19
// @gate !disableLegacyMode
it('should support mount and update operations for multiple roots (legacy render)', async () => {
const Parent = ({count}) =>
new Array(count).fill(true).map((_, index) => );
const Child = () => Hi!
;
const containerA = document.createElement('div');
const containerB = document.createElement('div');
await act(() => {
legacyRender(, containerA);
legacyRender(, containerB);
});
expect(store).toMatchInlineSnapshot(`
[root]
▾
[root]
▾
`);
await act(() => {
legacyRender(, containerA);
legacyRender(, containerB);
});
expect(store).toMatchInlineSnapshot(`
[root]
▾
[root]
▾
`);
await act(() => ReactDOM.unmountComponentAtNode(containerB));
expect(store).toMatchInlineSnapshot(`
[root]
▾
`);
await act(() => ReactDOM.unmountComponentAtNode(containerA));
expect(store).toMatchInlineSnapshot(``);
});
// @reactVersion >= 18.0
it('should support mount and update operations for multiple roots (createRoot)', async () => {
const Parent = ({count}) =>
new Array(count).fill(true).map((_, index) => );
const Child = () => Hi!
;
const containerA = document.createElement('div');
const containerB = document.createElement('div');
const rootA = ReactDOMClient.createRoot(containerA);
const rootB = ReactDOMClient.createRoot(containerB);
await act(() => {
rootA.render();
rootB.render();
});
expect(store).toMatchInlineSnapshot(`
[root]
▾
[root]
▾
`);
await act(() => {
rootA.render();
rootB.render();
});
expect(store).toMatchInlineSnapshot(`
[root]
▾
[root]
▾
`);
await act(() => rootB.unmount());
expect(store).toMatchInlineSnapshot(`
[root]
▾
`);
await act(() => rootA.unmount());
expect(store).toMatchInlineSnapshot(``);
});
// @reactVersion >= 18.0
it('should filter DOM nodes from the store tree', async () => {
const Grandparent = () => (
);
const Parent = () => (
);
const Child = () => Hi!
;
await act(() => render());
expect(store).toMatchInlineSnapshot(`
[root]
▾
▾
▾
`);
});
// @reactVersion >= 18.0
it('should display Suspense nodes properly in various states', async () => {
const Loading = () => Loading...
;
const never = new Promise(() => {});
const SuspendingComponent = () => {
readValue(never);
};
const Component = () => {
return Hello
;
};
const Wrapper = ({shouldSuspense}) => (
}>
{shouldSuspense ? (
) : (
)}
);
await act(() => render());
expect(store).toMatchInlineSnapshot(`
[root]
▾
▾
[suspense-root] rects={[{x:1,y:2,width:5,height:1}, {x:1,y:2,width:10,height:1}]}
`);
await act(() => {
render();
});
expect(store).toMatchInlineSnapshot(`
[root]
▾
▾
[suspense-root] rects={[{x:1,y:2,width:5,height:1}, {x:1,y:2,width:5,height:1}]}
`);
});
// @reactVersion >= 18.0
it('should support nested Suspense nodes', async () => {
const Component = () => null;
const Loading = () => Loading...
;
const never = new Promise(() => {});
const Never = () => {
readValue(never);
};
const Wrapper = ({
suspendFirst = false,
suspendSecond = false,
suspendParent = false,
}) => (
}>
}>
{suspendFirst ? (
) : (
)}
}>
{suspendSecond ? (
) : (
)}
}>
{suspendParent && }
);
await actAsync(() =>
render(
,
),
);
expect(store).toMatchInlineSnapshot(`
[root]
▾
▾
▾
▾
▾
[suspense-root] rects={[{x:1,y:2,width:10,height:1}]}
`);
await act(() =>
render(
,
),
);
expect(store).toMatchInlineSnapshot(`
[root]
▾
▾
▾
▾
▾
[suspense-root] rects={[{x:1,y:2,width:10,height:1}, {x:1,y:2,width:10,height:1}]}
`);
await act(() =>
render(
,
),
);
expect(store).toMatchInlineSnapshot(`
[root]
▾
▾
▾
▾
▾
[suspense-root] rects={[{x:1,y:2,width:10,height:1}, {x:1,y:2,width:10,height:1}]}
`);
await act(() =>
render(
,
),
);
expect(store).toMatchInlineSnapshot(`
[root]
▾
▾
▾
▾
▾
[suspense-root] rects={[{x:1,y:2,width:10,height:1}, {x:1,y:2,width:10,height:1}]}
`);
await act(() =>
render(
,
),
);
expect(store).toMatchInlineSnapshot(`
[root]
▾
▾
[suspense-root] rects={[{x:1,y:2,width:10,height:1}, {x:1,y:2,width:10,height:1}, {x:1,y:2,width:10,height:1}]}
`);
await act(() =>
render(
,
),
);
expect(store).toMatchInlineSnapshot(`
[root]
▾
▾
▾
▾
▾
[suspense-root] rects={[{x:1,y:2,width:10,height:1}, {x:1,y:2,width:10,height:1}, {x:1,y:2,width:10,height:1}]}
`);
await act(() =>
render(
,
),
);
expect(store).toMatchInlineSnapshot(`
[root]
▾
▾
▾
▾
▾
[suspense-root] rects={[{x:1,y:2,width:10,height:1}]}
`);
const rendererID = getRendererID();
await act(() =>
agent.overrideSuspense({
id: store.getElementIDAtIndex(4),
rendererID,
forceFallback: true,
}),
);
expect(store).toMatchInlineSnapshot(`
[root]
▾
▾
▾
▾
▾
[suspense-root] rects={[{x:1,y:2,width:10,height:1}, {x:1,y:2,width:10,height:1}]}
`);
await act(() =>
agent.overrideSuspense({
id: store.getElementIDAtIndex(2),
rendererID,
forceFallback: true,
}),
);
expect(store).toMatchInlineSnapshot(`
[root]
▾
▾
[suspense-root] rects={[{x:1,y:2,width:10,height:1}, {x:1,y:2,width:10,height:1}, {x:1,y:2,width:10,height:1}]}
`);
await act(() =>
render(
,
),
);
expect(store).toMatchInlineSnapshot(`
[root]
▾
▾
[suspense-root] rects={[{x:1,y:2,width:10,height:1}, {x:1,y:2,width:10,height:1}, {x:1,y:2,width:10,height:1}]}
`);
await actAsync(() =>
agent.overrideSuspense({
id: store.getElementIDAtIndex(2),
rendererID,
forceFallback: false,
}),
);
expect(store).toMatchInlineSnapshot(`
[root]
▾
▾
▾
▾
▾
[suspense-root] rects={[{x:1,y:2,width:10,height:1}, {x:1,y:2,width:10,height:1}, {x:1,y:2,width:10,height:1}]}
`);
await act(() =>
agent.overrideSuspense({
id: store.getElementIDAtIndex(4),
rendererID,
forceFallback: false,
}),
);
expect(store).toMatchInlineSnapshot(`
[root]
▾
▾
▾
▾
▾
[suspense-root] rects={[{x:1,y:2,width:10,height:1}, {x:1,y:2,width:10,height:1}, {x:1,y:2,width:10,height:1}]}