main
js 238 lines 6.32 KB
Raw
1 /**
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 *
7 * @emails react-core
8 * @jest-environment node
9 */
10
11 'use strict';
12
13 let React;
14 let ReactNoopPersistent;
15
16 let act;
17 let waitForAll;
18
19 describe('ReactPersistent', () => {
20 beforeEach(() => {
21 jest.resetModules();
22
23 React = require('react');
24 ReactNoopPersistent = require('react-noop-renderer/persistent');
25 ({act, waitForAll} = require('internal-test-utils'));
26 });
27
28 // Inlined from shared folder so we can run this test on a bundle.
29 function createPortal(children, containerInfo, implementation, key) {
30 return {
31 $$typeof: Symbol.for('react.portal'),
32 key: key == null ? null : String(key),
33 children,
34 containerInfo,
35 implementation,
36 };
37 }
38
39 function render(element) {
40 ReactNoopPersistent.render(element);
41 }
42
43 function div(...children) {
44 children = children.map(c =>
45 typeof c === 'string' ? {text: c, hidden: false} : c,
46 );
47 return {type: 'div', children, prop: undefined, hidden: false};
48 }
49
50 function span(prop) {
51 return {type: 'span', children: [], prop, hidden: false};
52 }
53
54 // For persistent renderers we have to mix deep equality and reference equality checks
55 // for which we need the actual children.
56 // None of the tests are gated and the underlying implementation is rarely touch
57 // so it's unlikely we deal with failing `toEqual` checks which cause bad performance.
58 function dangerouslyGetChildren() {
59 return ReactNoopPersistent.dangerouslyGetChildren();
60 }
61
62 it('can update child nodes of a host instance', async () => {
63 function Bar(props) {
64 return <span>{props.text}</span>;
65 }
66
67 function Foo(props) {
68 return (
69 <div>
70 <Bar text={props.text} />
71 {props.text === 'World' ? <Bar text={props.text} /> : null}
72 </div>
73 );
74 }
75
76 render(<Foo text="Hello" />);
77 await waitForAll([]);
78 const originalChildren = dangerouslyGetChildren();
79 expect(originalChildren).toEqual([div(span())]);
80
81 render(<Foo text="World" />);
82 await waitForAll([]);
83 const newChildren = dangerouslyGetChildren();
84 expect(newChildren).toEqual([div(span(), span())]);
85
86 expect(originalChildren).toEqual([div(span())]);
87 });
88
89 it('can reuse child nodes between updates', async () => {
90 function Baz(props) {
91 return <span prop={props.text} />;
92 }
93 class Bar extends React.Component {
94 shouldComponentUpdate(newProps) {
95 return false;
96 }
97 render() {
98 return <Baz text={this.props.text} />;
99 }
100 }
101 function Foo(props) {
102 return (
103 <div>
104 <Bar text={props.text} />
105 {props.text === 'World' ? <Bar text={props.text} /> : null}
106 </div>
107 );
108 }
109
110 render(<Foo text="Hello" />);
111 await waitForAll([]);
112 const originalChildren = dangerouslyGetChildren();
113 expect(originalChildren).toEqual([div(span('Hello'))]);
114
115 render(<Foo text="World" />);
116 await waitForAll([]);
117 const newChildren = dangerouslyGetChildren();
118 expect(newChildren).toEqual([div(span('Hello'), span('World'))]);
119
120 expect(originalChildren).toEqual([div(span('Hello'))]);
121
122 // Reused node should have reference equality
123 expect(newChildren[0].children[0]).toBe(originalChildren[0].children[0]);
124 });
125
126 it('can update child text nodes', async () => {
127 function Foo(props) {
128 return (
129 <div>
130 {props.text}
131 <span />
132 </div>
133 );
134 }
135
136 render(<Foo text="Hello" />);
137 await waitForAll([]);
138 const originalChildren = dangerouslyGetChildren();
139 expect(originalChildren).toEqual([div('Hello', span())]);
140
141 render(<Foo text="World" />);
142 await waitForAll([]);
143 const newChildren = dangerouslyGetChildren();
144 expect(newChildren).toEqual([div('World', span())]);
145
146 expect(originalChildren).toEqual([div('Hello', span())]);
147 });
148
149 it('supports portals', async () => {
150 function Parent(props) {
151 return <div>{props.children}</div>;
152 }
153
154 function BailoutSpan() {
155 return <span />;
156 }
157
158 class BailoutTest extends React.Component {
159 shouldComponentUpdate() {
160 return false;
161 }
162 render() {
163 return <BailoutSpan />;
164 }
165 }
166
167 function Child(props) {
168 return (
169 <div>
170 <BailoutTest />
171 {props.children}
172 </div>
173 );
174 }
175 const portalContainer = {rootID: 'persistent-portal-test', children: []};
176 const emptyPortalChildSet = portalContainer.children;
177 render(<Parent>{createPortal(<Child />, portalContainer, null)}</Parent>);
178 await waitForAll([]);
179
180 expect(emptyPortalChildSet).toEqual([]);
181
182 const originalChildren = dangerouslyGetChildren();
183 expect(originalChildren).toEqual([div()]);
184 const originalPortalChildren = portalContainer.children;
185 expect(originalPortalChildren).toEqual([div(span())]);
186
187 render(
188 <Parent>
189 {createPortal(<Child>Hello {'World'}</Child>, portalContainer, null)}
190 </Parent>,
191 );
192 await waitForAll([]);
193
194 const newChildren = dangerouslyGetChildren();
195 expect(newChildren).toEqual([div()]);
196 const newPortalChildren = portalContainer.children;
197 expect(newPortalChildren).toEqual([div(span(), 'Hello ', 'World')]);
198
199 expect(originalChildren).toEqual([div()]);
200 expect(originalPortalChildren).toEqual([div(span())]);
201
202 // Reused portal children should have reference equality
203 expect(newPortalChildren[0].children[0]).toBe(
204 originalPortalChildren[0].children[0],
205 );
206
207 // Deleting the Portal, should clear its children
208 render(<Parent />);
209 await waitForAll([]);
210
211 const clearedPortalChildren = portalContainer.children;
212 expect(clearedPortalChildren).toEqual([]);
213
214 // The original is unchanged.
215 expect(newPortalChildren).toEqual([div(span(), 'Hello ', 'World')]);
216 });
217
218 it('remove children', async () => {
219 function Wrapper({children}) {
220 return children;
221 }
222
223 const root = ReactNoopPersistent.createRoot();
224 await act(() => {
225 root.render(
226 <Wrapper>
227 <inner />
228 </Wrapper>,
229 );
230 });
231 expect(root.getChildrenAsJSX()).toEqual(<inner />);
232
233 await act(() => {
234 root.render(<Wrapper />);
235 });
236 expect(root.getChildrenAsJSX()).toEqual(null);
237 });
238 });