main
js 186 lines 4.95 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 import {AsyncLocalStorage} from 'node:async_hooks';
13
14 let act;
15 let React;
16 let ReactNoopServer;
17
18 function normalizeCodeLocInfo(str) {
19 return (
20 str &&
21 str.replace(/^ +(?:at|in) ([\S]+)[^\n]*/gm, function (m, name) {
22 const dot = name.lastIndexOf('.');
23 if (dot !== -1) {
24 name = name.slice(dot + 1);
25 }
26 return ' in ' + name + (/\d/.test(m) ? ' (at **)' : '');
27 })
28 );
29 }
30
31 /**
32 * Removes all stackframes not pointing into this file
33 */
34 function ignoreListStack(str) {
35 if (!str) {
36 return str;
37 }
38
39 let ignoreListedStack = '';
40 const lines = str.split('\n');
41
42 // eslint-disable-next-line no-for-of-loops/no-for-of-loops
43 for (const line of lines) {
44 if (line.indexOf(__filename) === -1) {
45 } else {
46 ignoreListedStack += '\n' + line.replace(__dirname, '.');
47 }
48 }
49
50 return ignoreListedStack;
51 }
52
53 const currentTask = new AsyncLocalStorage({defaultValue: null});
54
55 describe('ReactServer', () => {
56 beforeEach(() => {
57 jest.resetModules();
58
59 console.createTask = jest.fn(taskName => {
60 return {
61 run: taskFn => {
62 const parentTask = currentTask.getStore() || '';
63 return currentTask.run(parentTask + '\n' + taskName, taskFn);
64 },
65 };
66 });
67
68 act = require('internal-test-utils').act;
69 React = require('react');
70 ReactNoopServer = require('react-noop-renderer/server');
71 });
72
73 function div(...children) {
74 children = children.map(c =>
75 typeof c === 'string' ? {text: c, hidden: false} : c,
76 );
77 return {type: 'div', children, prop: undefined, hidden: false};
78 }
79
80 it('can call render', () => {
81 const result = ReactNoopServer.render(<div>hello world</div>);
82 expect(result.root).toEqual(div('hello world'));
83 });
84
85 it('has Owner Stacks in DEV when aborted', async () => {
86 const Context = React.createContext(null);
87
88 function Component({p1, p2, p3}) {
89 const context = React.use(Context);
90 if (context === null) {
91 throw new Error('Missing context');
92 }
93 React.use(p1);
94 React.use(p2);
95 React.use(p3);
96 return <div>Hello, Dave!</div>;
97 }
98 function Indirection({p1, p2, p3}) {
99 return (
100 <div>
101 <Component p1={p1} p2={p2} p3={p3} />
102 </div>
103 );
104 }
105 function App({p1, p2, p3}) {
106 return (
107 <section>
108 <div>
109 <Indirection p1={p1} p2={p2} p3={p3} />
110 </div>
111 </section>
112 );
113 }
114
115 let caughtError;
116 let componentStack;
117 let ownerStack;
118 let task;
119 const resolvedPromise = Promise.resolve('one');
120 resolvedPromise.status = 'fulfilled';
121 resolvedPromise.value = 'one';
122 let resolvePendingPromise;
123 const pendingPromise = new Promise(resolve => {
124 resolvePendingPromise = value => {
125 pendingPromise.status = 'fulfilled';
126 pendingPromise.value = value;
127 resolve(value);
128 };
129 });
130 const hangingPromise = new Promise(() => {});
131 const result = ReactNoopServer.render(
132 <Context value="provided">
133 <App p1={resolvedPromise} p2={pendingPromise} p3={hangingPromise} />
134 </Context>,
135 {
136 onError: (error, errorInfo) => {
137 caughtError = error;
138 componentStack = errorInfo.componentStack;
139 ownerStack = __DEV__ ? React.captureOwnerStack() : null;
140 task = currentTask.getStore();
141 },
142 },
143 );
144
145 await act(async () => {
146 resolvePendingPromise('two');
147 result.abort();
148 });
149 expect(caughtError).toEqual(
150 expect.objectContaining({
151 message: 'The render was aborted by the server without a reason.',
152 }),
153 );
154 expect(normalizeCodeLocInfo(componentStack)).toEqual(
155 '\n in Component (at **)' +
156 '\n in div' +
157 '\n in Indirection (at **)' +
158 '\n in div' +
159 '\n in section' +
160 '\n in App (at **)',
161 );
162 if (__DEV__) {
163 // The concrete location may change as this test is updated.
164 // Just make sure they still point at the same code
165 if (gate(flags => flags.enableAsyncDebugInfo)) {
166 expect(ignoreListStack(ownerStack)).toEqual(
167 '' +
168 // Pointing at React.use(p2)
169 '\n at Component (./ReactServer-test.js:94:13)' +
170 '\n at Indirection (./ReactServer-test.js:101:44)' +
171 '\n at App (./ReactServer-test.js:109:46)',
172 );
173 } else {
174 expect(ignoreListStack(ownerStack)).toEqual(
175 '' +
176 '\n at Indirection (./ReactServer-test.js:101:44)' +
177 '\n at App (./ReactServer-test.js:109:46)',
178 );
179 }
180 expect(task).toEqual('\n<Component>');
181 } else {
182 expect(ownerStack).toBeNull();
183 expect(task).toEqual(undefined);
184 }
185 });
186 });