main
js 180 lines 4.54 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 */
9
10 'use strict';
11
12 let React;
13 let ReactDOM;
14 let ReactDOMClient;
15 let TestComponent;
16 let act;
17 let theInnerDivRef;
18 let theInnerClassComponentRef;
19
20 describe('refs-destruction', () => {
21 beforeEach(() => {
22 jest.resetModules();
23
24 React = require('react');
25 ReactDOM = require('react-dom');
26 ReactDOMClient = require('react-dom/client');
27 act = require('internal-test-utils').act;
28
29 class ClassComponent extends React.Component {
30 render() {
31 return null;
32 }
33 }
34
35 TestComponent = class extends React.Component {
36 constructor(props) {
37 super(props);
38 theInnerDivRef = React.createRef();
39 theInnerClassComponentRef = React.createRef();
40 }
41
42 render() {
43 if (this.props.destroy) {
44 return <div />;
45 } else if (this.props.removeRef) {
46 return (
47 <div>
48 <div />
49 <ClassComponent />
50 </div>
51 );
52 } else {
53 return (
54 <div>
55 <div ref={theInnerDivRef} />
56 <ClassComponent ref={theInnerClassComponentRef} />
57 </div>
58 );
59 }
60 }
61 };
62 });
63
64 afterEach(() => {
65 theInnerClassComponentRef = null;
66 theInnerDivRef = null;
67 });
68
69 it('should remove refs when destroying the parent', async () => {
70 const container = document.createElement('div');
71 const root = ReactDOMClient.createRoot(container);
72 await act(async () => {
73 root.render(<TestComponent />);
74 });
75
76 expect(theInnerDivRef.current).toBeInstanceOf(Element);
77 expect(theInnerClassComponentRef.current).toBeTruthy();
78
79 root.unmount();
80
81 expect(theInnerDivRef.current).toBe(null);
82 expect(theInnerClassComponentRef.current).toBe(null);
83 });
84
85 it('should remove refs when destroying the child', async () => {
86 const container = document.createElement('div');
87 const root = ReactDOMClient.createRoot(container);
88 await act(async () => {
89 root.render(<TestComponent />);
90 });
91
92 expect(theInnerDivRef.current).toBeInstanceOf(Element);
93 expect(theInnerClassComponentRef.current).toBeTruthy();
94
95 await act(async () => {
96 root.render(<TestComponent destroy={true} />);
97 });
98
99 expect(theInnerDivRef.current).toBe(null);
100 expect(theInnerClassComponentRef.current).toBe(null);
101 });
102
103 it('should remove refs when removing the child ref attribute', async () => {
104 const container = document.createElement('div');
105 const root = ReactDOMClient.createRoot(container);
106 await act(async () => {
107 root.render(<TestComponent />);
108 });
109
110 expect(theInnerDivRef.current).toBeInstanceOf(Element);
111 expect(theInnerClassComponentRef.current).toBeTruthy();
112
113 await act(async () => {
114 root.render(<TestComponent removeRef={true} />);
115 });
116
117 expect(theInnerDivRef.current).toBe(null);
118 expect(theInnerClassComponentRef.current).toBe(null);
119 });
120
121 it('should not error when destroying child with ref asynchronously', async () => {
122 let nestedRoot;
123 class Modal extends React.Component {
124 componentDidMount() {
125 this.div = document.createElement('div');
126 nestedRoot = ReactDOMClient.createRoot(this.div);
127 document.body.appendChild(this.div);
128 this.componentDidUpdate();
129 }
130
131 componentDidUpdate() {
132 setTimeout(() => {
133 ReactDOM.flushSync(() => {
134 nestedRoot.render(<div>{this.props.children}</div>);
135 });
136 }, 0);
137 }
138
139 componentWillUnmount() {
140 const self = this;
141 // some async animation
142 setTimeout(function () {
143 expect(function () {
144 nestedRoot.unmount();
145 }).not.toThrow();
146 document.body.removeChild(self.div);
147 }, 0);
148 }
149
150 render() {
151 return null;
152 }
153 }
154
155 class AppModal extends React.Component {
156 render() {
157 return (
158 <Modal>
159 <a ref={React.createRef()} />
160 </Modal>
161 );
162 }
163 }
164
165 class App extends React.Component {
166 render() {
167 return this.props.hidden ? null : <AppModal onClose={this.close} />;
168 }
169 }
170
171 const container = document.createElement('div');
172 const root = ReactDOMClient.createRoot(container);
173 await act(async () => {
174 root.render(<App />);
175 });
176 await act(async () => {
177 root.render(<App hidden={true} />);
178 });
179 });
180 });