/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
describe('Store (legacy)', () => {
let React;
let ReactDOM;
let store;
const act = (callback: Function) => {
callback();
jest.runAllTimers(); // Flush Bridge operations
};
beforeEach(() => {
store = global.store;
// Redirect all React/ReactDOM requires to the v15 UMD.
// We use the UMD because Jest doesn't enable us to mock deep imports (e.g. "react/lib/Something").
jest.mock('react', () => jest.requireActual('react-15/dist/react.js'));
jest.mock('react-dom', () =>
jest.requireActual('react-dom-15/dist/react-dom.js'),
);
React = require('react');
ReactDOM = require('react-dom');
});
it('should not allow a root node to be collapsed', () => {
const Component = () => <div>Hi</div>;
act(() =>
ReactDOM.render(<Component count={4} />, document.createElement('div')),
);
expect(store).toMatchInlineSnapshot(`
[root]
▾ <Component>
<div>
`);
expect(store.roots).toHaveLength(1);
const rootID = store.roots[0];
expect(() => store.toggleIsCollapsed(rootID, true)).toThrow(
'Root nodes cannot be collapsed',
);
});
describe('collapseNodesByDefault:false', () => {
beforeEach(() => {
store.collapseNodesByDefault = false;
});
it('should support mount and update operations', () => {
const Grandparent = ({count}) => (
<div>
<Parent count={count} />
<Parent count={count} />
</div>
);
const Parent = ({count}) => (
<div>
{new Array(count).fill(true).map((_, index) => (
<Child key={index} />
))}
</div>
);
const Child = () => <div>Hi!</div>;
const container = document.createElement('div');
act(() => ReactDOM.render(<Grandparent count={4} />, container));
expect(store).toMatchInlineSnapshot(`
[root]
▾ <Grandparent>
▾ <div>
▾ <Parent>
▾ <div>
▾ <Child key="0">
<div>
▾ <Child key="1">
<div>
▾ <Child key="2">
<div>
▾ <Child key="3">
<div>
▾ <Parent>
▾ <div>
▾ <Child key="0">
<div>
▾ <Child key="1">
<div>
▾ <Child key="2">
<div>
▾ <Child key="3">
<div>
`);
act(() => ReactDOM.render(<Grandparent count={2} />, container));
expect(store).toMatchInlineSnapshot(`
[root]
▾ <Grandparent>
▾ <div>
▾ <Parent>
▾ <div>
▾ <Child key="0">
<div>
▾ <Child key="1">
<div>
▾ <Parent>
▾ <div>
▾ <Child key="0">
<div>
▾ <Child key="1">
<div>
`);
act(() => ReactDOM.unmountComponentAtNode(container));
expect(store).toMatchInlineSnapshot(``);
});
it('should support mount and update operations for multiple roots', () => {
const Parent = ({count}) => (
<div>
{new Array(count).fill(true).map((_, index) => (
<Child key={index} />
))}
</div>
);
const Child = () => <div>Hi!</div>;
const containerA = document.createElement('div');
const containerB = document.createElement('div');
act(() => {
ReactDOM.render(<Parent key="A" count={3} />, containerA);
ReactDOM.render(<Parent key="B" count={2} />, containerB);
});
expect(store).toMatchInlineSnapshot(`
[root]
▾ <Parent key="A">
▾ <div>
▾ <Child key="0">
<div>
▾ <Child key="1">
<div>
▾ <Child key="2">
<div>
[root]
▾ <Parent key="B">
▾ <div>
▾ <Child key="0">
<div>
▾ <Child key="1">
<div>
`);
act(() => {
ReactDOM.render(<Parent key="A" count={4} />, containerA);
ReactDOM.render(<Parent key="B" count={1} />, containerB);
});
expect(store).toMatchInlineSnapshot(`
[root]
▾ <Parent key="A">
▾ <div>
▾ <Child key="0">
<div>
▾ <Child key="1">
<div>
▾ <Child key="2">
<div>
▾ <Child key="3">
<div>
[root]
▾ <Parent key="B">
▾ <div>
▾ <Child key="0">
<div>
`);
act(() => ReactDOM.unmountComponentAtNode(containerB));
expect(store).toMatchInlineSnapshot(`
[root]
▾ <Parent key="A">
▾ <div>
▾ <Child key="0">
<div>
▾ <Child key="1">
<div>
▾ <Child key="2">
<div>
▾ <Child key="3">
<div>
`);
act(() => ReactDOM.unmountComponentAtNode(containerA));
expect(store).toMatchInlineSnapshot(``);
});
it('should not filter DOM nodes from the store tree', () => {
const Grandparent = ({flip}) => (
<div>
<div>
<Parent flip={flip} />
</div>
<Parent flip={flip} />
<Nothing />
</div>
);
const Parent = ({flip}) => (
<div>
{flip ? 'foo' : null}
<Child />
{flip && [null, 'hello', 42]}
{flip ? 'bar' : 'baz'}
</div>
);
const Child = () => <div>Hi!</div>;
const Nothing = () => null;
const container = document.createElement('div');
act(() =>
ReactDOM.render(<Grandparent count={4} flip={false} />, container),
);
expect(store).toMatchInlineSnapshot(`
[root]
▾ <Grandparent>
▾ <div>
▾ <div>
▾ <Parent>
▾ <div>
▾ <Child>
<div>
▾ <Parent>
▾ <div>
▾ <Child>
<div>
<Nothing>
`);
act(() =>
ReactDOM.render(<Grandparent count={4} flip={true} />, container),
);
expect(store).toMatchInlineSnapshot(`
[root]
▾ <Grandparent>
▾ <div>
▾ <div>
▾ <Parent>
▾ <div>
▾ <Child>
<div>
▾ <Parent>
▾ <div>
▾ <Child>
<div>
<Nothing>
`);
act(() => ReactDOM.unmountComponentAtNode(container));
expect(store).toMatchInlineSnapshot(``);
});
it('should support collapsing parts of the tree', () => {
const Grandparent = ({count}) => (
<div>
<Parent count={count} />
<Parent count={count} />
</div>
);
const Parent = ({count}) => (
<div>
{new Array(count).fill(true).map((_, index) => (
<Child key={index} />
))}
</div>
);
const Child = () => <div>Hi!</div>;
act(() =>
ReactDOM.render(
<Grandparent count={2} />,
document.createElement('div'),
),
);
expect(store).toMatchInlineSnapshot(`
[root]
▾ <Grandparent>
▾ <div>
▾ <Parent>
▾ <div>
▾ <Child key="0">
<div>
▾ <Child key="1">
<div>
▾ <Parent>
▾ <div>
▾ <Child key="0">
<div>
▾ <Child key="1">
<div>
`);
const grandparentID = store.getElementIDAtIndex(0);
const parentOneID = store.getElementIDAtIndex(2);
const parentTwoID = store.getElementIDAtIndex(8);
act(() => store.toggleIsCollapsed(parentOneID, true));
expect(store).toMatchInlineSnapshot(`
[root]
▾ <Grandparent>
▾ <div>
▸ <Parent>
▾ <Parent>
▾ <div>
▾ <Child key="0">
<div>
▾ <Child key="1">
<div>
`);
act(() => store.toggleIsCollapsed(parentTwoID, true));
expect(store).toMatchInlineSnapshot(`
[root]
▾ <Grandparent>
▾ <div>
▸ <Parent>
▸ <Parent>
`);
act(() => store.toggleIsCollapsed(parentOneID, false));
expect(store).toMatchInlineSnapshot(`
[root]
▾ <Grandparent>
▾ <div>
▾ <Parent>
▾ <div>
▾ <Child key="0">
<div>
▾ <Child key="1">
<div>
▸ <Parent>
`);
act(() => store.toggleIsCollapsed(grandparentID, true));
expect(store).toMatchInlineSnapshot(`
[root]
▸ <Grandparent>
`);
act(() => store.toggleIsCollapsed(grandparentID, false));
expect(store).toMatchInlineSnapshot(`
[root]
▾ <Grandparent>
▾ <div>
▾ <Parent>
▾ <div>
▾ <Child key="0">
<div>
▾ <Child key="1">
<div>
▸ <Parent>
`);
});
it('should support adding and removing children', () => {
const Root = ({children}) => <div>{children}</div>;
const Component = () => <div />;
const container = document.createElement('div');
act(() =>
ReactDOM.render(
<Root>
<Component key="a" />
</Root>,
container,
),
);
expect(store).toMatchInlineSnapshot(`
[root]
▾ <Root>
▾ <div>
▾ <Component key="a">
<div>
`);
act(() =>
ReactDOM.render(
<Root>
<Component key="a" />
<Component key="b" />
</Root>,
container,
),
);
expect(store).toMatchInlineSnapshot(`
[root]
▾ <Root>
▾ <div>
▾ <Component key="a">
<div>
▾ <Component key="b">
<div>
`);
act(() =>
ReactDOM.render(
<Root>
<Component key="b" />
</Root>,
container,
),
);
expect(store).toMatchInlineSnapshot(`
[root]
▾ <Root>
▾ <div>
▾ <Component key="b">
<div>
`);
});
it('should support reordering of children', () => {
const Root = ({children}) => <div>{children}</div>;
const Component = () => <div />;
const Foo = () => <div>{[<Component key="0" />]}</div>;
const Bar = () => (
<div>{[<Component key="0" />, <Component key="1" />]}</div>
);
const foo = <Foo key="foo" />;
const bar = <Bar key="bar" />;
const container = document.createElement('div');
act(() => ReactDOM.render(<Root>{[foo, bar]}</Root>, container));
expect(store).toMatchInlineSnapshot(`
[root]
▾ <Root>
▾ <div>
▾ <Foo key="foo">
▾ <div>
▾ <Component key="0">
<div>
▾ <Bar key="bar">
▾ <div>
▾ <Component key="0">
<div>
▾ <Component key="1">
<div>
`);
act(() => ReactDOM.render(<Root>{[bar, foo]}</Root>, container));
expect(store).toMatchInlineSnapshot(`
[root]
▾ <Root>
▾ <div>
▾ <Bar key="bar">
▾ <div>
▾ <Component key="0">
<div>
▾ <Component key="1">
<div>
▾ <Foo key="foo">
▾ <div>
▾ <Component key="0">
<div>
`);
act(() => store.toggleIsCollapsed(store.getElementIDAtIndex(0), true));
expect(store).toMatchInlineSnapshot(`
[root]
▸ <Root>
`);
act(() => store.toggleIsCollapsed(store.getElementIDAtIndex(0), false));
expect(store).toMatchInlineSnapshot(`
[root]
▾ <Root>
▾ <div>
▾ <Bar key="bar">
▾ <div>
▾ <Component key="0">
<div>
▾ <Component key="1">
<div>
▾ <Foo key="foo">
▾ <div>
▾ <Component key="0">
<div>
`);
});
});
describe('collapseNodesByDefault:true', () => {
beforeEach(() => {
store.collapseNodesByDefault = true;
});
it('should support mount and update operations', () => {
const Parent = ({count}) => (
<div>
{new Array(count).fill(true).map((_, index) => (
<Child key={index} />
))}
</div>
);
const Child = () => <div>Hi!</div>;
const container = document.createElement('div');
act(() =>
ReactDOM.render(
<div>
<Parent count={1} />
<Parent count={3} />
</div>,
container,
),
);
expect(store).toMatchInlineSnapshot(`
[root]
▸ <div>
`);
act(() =>
ReactDOM.render(
<div>
<Parent count={2} />
<Parent count={1} />
</div>,
container,
),
);
expect(store).toMatchInlineSnapshot(`
[root]
▸ <div>
`);
act(() => ReactDOM.unmountComponentAtNode(container));
expect(store).toMatchInlineSnapshot(``);
});
it('should support mount and update operations for multiple roots', () => {
const Parent = ({count}) => (
<div>
{new Array(count).fill(true).map((_, index) => (
<Child key={index} />
))}
</div>
);
const Child = () => <div>Hi!</div>;
const containerA = document.createElement('div');
const containerB = document.createElement('div');
act(() => {
ReactDOM.render(<Parent key="A" count={3} />, containerA);
ReactDOM.render(<Parent key="B" count={2} />, containerB);
});
expect(store).toMatchInlineSnapshot(`
[root]
▸ <Parent key="A">
[root]
▸ <Parent key="B">
`);
act(() => {
ReactDOM.render(<Parent key="A" count={4} />, containerA);
ReactDOM.render(<Parent key="B" count={1} />, containerB);
});
expect(store).toMatchInlineSnapshot(`
[root]
▸ <Parent key="A">
[root]
▸ <Parent key="B">
`);
act(() => ReactDOM.unmountComponentAtNode(containerB));
expect(store).toMatchInlineSnapshot(`
[root]
▸ <Parent key="A">
`);
act(() => ReactDOM.unmountComponentAtNode(containerA));
expect(store).toMatchInlineSnapshot(``);
});
it('should not filter DOM nodes from the store tree', () => {
const Grandparent = ({flip}) => (
<div>
<div>
<Parent flip={flip} />
</div>
<Parent flip={flip} />
<Nothing />
</div>
);
const Parent = ({flip}) => (
<div>
{flip ? 'foo' : null}
<Child />
{flip && [null, 'hello', 42]}
{flip ? 'bar' : 'baz'}
</div>
);
const Child = () => <div>Hi!</div>;
const Nothing = () => null;
const container = document.createElement('div');
act(() =>
ReactDOM.render(<Grandparent count={4} flip={false} />, container),
);
expect(store).toMatchInlineSnapshot(`
[root]
▸ <Grandparent>
`);
act(() => store.toggleIsCollapsed(store.getElementIDAtIndex(0), false));
expect(store).toMatchInlineSnapshot(`
[root]
▾ <Grandparent>
▸ <div>
`);
act(() => store.toggleIsCollapsed(store.getElementIDAtIndex(1), false));
expect(store).toMatchInlineSnapshot(`
[root]
▾ <Grandparent>
▾ <div>
▸ <div>
▸ <Parent>
<Nothing>
`);
act(() =>
ReactDOM.render(<Grandparent count={4} flip={true} />, container),
);
expect(store).toMatchInlineSnapshot(`
[root]
▾ <Grandparent>
▾ <div>
▸ <div>
▸ <Parent>
<Nothing>
`);
act(() => ReactDOM.unmountComponentAtNode(container));
expect(store).toMatchInlineSnapshot(``);
});
it('should support expanding parts of the tree', () => {
const Grandparent = ({count}) => (
<div>
<Parent count={count} />
<Parent count={count} />
</div>
);
const Parent = ({count}) => (
<div>
{new Array(count).fill(true).map((_, index) => (
<Child key={index} />
))}
</div>
);
const Child = () => <div>Hi!</div>;
act(() =>
ReactDOM.render(
<Grandparent count={2} />,
document.createElement('div'),
),
);
expect(store).toMatchInlineSnapshot(`
[root]
▸ <Grandparent>
`);
const grandparentID = store.getElementIDAtIndex(0);
act(() => store.toggleIsCollapsed(grandparentID, false));
expect(store).toMatchInlineSnapshot(`
[root]
▾ <Grandparent>
▸ <div>
`);
const parentDivID = store.getElementIDAtIndex(1);
act(() => store.toggleIsCollapsed(parentDivID, false));
expect(store).toMatchInlineSnapshot(`
[root]
▾ <Grandparent>
▾ <div>
▸ <Parent>
▸ <Parent>
`);
const parentOneID = store.getElementIDAtIndex(2);
const parentTwoID = store.getElementIDAtIndex(3);
act(() => store.toggleIsCollapsed(parentOneID, false));
expect(store).toMatchInlineSnapshot(`
[root]
▾ <Grandparent>
▾ <div>
▾ <Parent>
▸ <div>
▸ <Parent>
`);
act(() => store.toggleIsCollapsed(parentTwoID, false));
expect(store).toMatchInlineSnapshot(`
[root]
▾ <Grandparent>
▾ <div>
▾ <Parent>
▸ <div>
▾ <Parent>
▸ <div>
`);
act(() => store.toggleIsCollapsed(parentOneID, true));
expect(store).toMatchInlineSnapshot(`
[root]
▾ <Grandparent>
▾ <div>
▸ <Parent>
▾ <Parent>
▸ <div>
`);
act(() => store.toggleIsCollapsed(parentTwoID, true));
expect(store).toMatchInlineSnapshot(`
[root]
▾ <Grandparent>
▾ <div>
▸ <Parent>
▸ <Parent>
`);
act(() => store.toggleIsCollapsed(grandparentID, true));
expect(store).toMatchInlineSnapshot(`
[root]
▸ <Grandparent>
`);
});
it('should support expanding deep parts of the tree', () => {
const Wrapper = ({forwardedRef}) => (
<Nested depth={3} forwardedRef={forwardedRef} />
);
const Nested = ({depth, forwardedRef}) =>
depth > 0 ? (
<Nested depth={depth - 1} forwardedRef={forwardedRef} />
) : (
<div ref={forwardedRef} />
);
let ref = null;
const refSetter = value => {
ref = value;
};
act(() =>
ReactDOM.render(
<Wrapper forwardedRef={refSetter} />,
document.createElement('div'),
),
);
expect(store).toMatchInlineSnapshot(`
[root]
▸ <Wrapper>
`);
const deepestedNodeID = global.agent.getIDForNode(ref);
act(() => store.toggleIsCollapsed(deepestedNodeID, false));
expect(store).toMatchInlineSnapshot(`
[root]
▾ <Wrapper>
▾ <Nested>
▾ <Nested>
▾ <Nested>
▾ <Nested>
<div>
`);
const rootID = store.getElementIDAtIndex(0);
act(() => store.toggleIsCollapsed(rootID, true));
expect(store).toMatchInlineSnapshot(`
[root]
▸ <Wrapper>
`);
act(() => store.toggleIsCollapsed(rootID, false));
expect(store).toMatchInlineSnapshot(`
[root]
▾ <Wrapper>
▾ <Nested>
▾ <Nested>
▾ <Nested>
▾ <Nested>
<div>
`);
const id = store.getElementIDAtIndex(1);
act(() => store.toggleIsCollapsed(id, true));
expect(store).toMatchInlineSnapshot(`
[root]
▾ <Wrapper>
▸ <Nested>
`);
act(() => store.toggleIsCollapsed(id, false));
expect(store).toMatchInlineSnapshot(`
[root]
▾ <Wrapper>
▾ <Nested>
▾ <Nested>
▾ <Nested>
▾ <Nested>
<div>
`);
});
it('should support reordering of children', () => {
const Root = ({children}) => <div>{children}</div>;
const Component = () => <div />;
const Foo = () => <div>{[<Component key="0" />]}</div>;
const Bar = () => (
<div>{[<Component key="0" />, <Component key="1" />]}</div>
);
const foo = <Foo key="foo" />;
const bar = <Bar key="bar" />;
const container = document.createElement('div');
act(() => ReactDOM.render(<Root>{[foo, bar]}</Root>, container));
expect(store).toMatchInlineSnapshot(`
[root]
▸ <Root>
`);
act(() => ReactDOM.render(<Root>{[bar, foo]}</Root>, container));
expect(store).toMatchInlineSnapshot(`
[root]
▸ <Root>
`);
act(() => store.toggleIsCollapsed(store.getElementIDAtIndex(0), false));
expect(store).toMatchInlineSnapshot(`
[root]
▾ <Root>
▸ <div>
`);
act(() => store.toggleIsCollapsed(store.getElementIDAtIndex(1), false));
expect(store).toMatchInlineSnapshot(`
[root]
▾ <Root>
▾ <div>
▸ <Bar key="bar">
▸ <Foo key="foo">
`);
act(() => {
store.toggleIsCollapsed(store.getElementIDAtIndex(3), false);
store.toggleIsCollapsed(store.getElementIDAtIndex(2), false);
});
expect(store).toMatchInlineSnapshot(`
[root]
▾ <Root>
▾ <div>
▾ <Bar key="bar">
▸ <div>
▾ <Foo key="foo">
▸ <div>
`);
act(() => store.toggleIsCollapsed(store.getElementIDAtIndex(0), true));
expect(store).toMatchInlineSnapshot(`
[root]
▸ <Root>
`);
});
});
describe('StrictMode compliance', () => {
it('should mark all elements as strict mode compliant', () => {
const App = () => null;
const container = document.createElement('div');
act(() => ReactDOM.render(<App />, container));
expect(store.getElementAtIndex(0).isStrictModeNonCompliant).toBe(false);
});
});
});