Skip to content

Commit c1b3d92

Browse files
committed
[test] Cleanup stack assertions in tests mixing React Server and Client
1 parent 3016ff8 commit c1b3d92

File tree

6 files changed

+58
-41
lines changed

6 files changed

+58
-41
lines changed

packages/react-client/src/ReactFlightReplyClient.js

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -474,8 +474,22 @@ export function processReply(
474474
}
475475
}
476476

477+
const existingReference = writtenObjects.get(value);
478+
477479
// $FlowFixMe[method-unbinding]
478480
if (typeof value.then === 'function') {
481+
if (existingReference !== undefined) {
482+
if (modelRoot === value) {
483+
// This is the ID we're currently emitting so we need to write it
484+
// once but if we discover it again, we refer to it by id.
485+
modelRoot = null;
486+
} else {
487+
// We've already emitted this as an outlined object, so we can
488+
// just refer to that by its existing ID.
489+
return existingReference;
490+
}
491+
}
492+
479493
// We assume that any object with a .then property is a "Thenable" type,
480494
// or a Promise type. Either of which can be represented by a Promise.
481495
if (formData === null) {
@@ -484,11 +498,19 @@ export function processReply(
484498
}
485499
pendingParts++;
486500
const promiseId = nextPartId++;
501+
const promiseReference = serializePromiseID(promiseId);
502+
writtenObjects.set(value, promiseReference);
487503
const thenable: Thenable<any> = (value: any);
488504
thenable.then(
489505
partValue => {
490506
try {
491-
const partJSON = serializeModel(partValue, promiseId);
507+
const previousReference = writtenObjects.get(partValue);
508+
let partJSON;
509+
if (previousReference !== undefined) {
510+
partJSON = JSON.stringify(previousReference);
511+
} else {
512+
partJSON = serializeModel(partValue, promiseId);
513+
}
492514
// $FlowFixMe[incompatible-type] We know it's not null because we assigned it above.
493515
const data: FormData = formData;
494516
data.append(formFieldPrefix + promiseId, partJSON);
@@ -504,10 +526,9 @@ export function processReply(
504526
// that throws on the server instead.
505527
reject,
506528
);
507-
return serializePromiseID(promiseId);
529+
return promiseReference;
508530
}
509531

510-
const existingReference = writtenObjects.get(value);
511532
if (existingReference !== undefined) {
512533
if (modelRoot === value) {
513534
// This is the ID we're currently emitting so we need to write it

packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMBrowser-test.js

Lines changed: 9 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2820,15 +2820,16 @@ describe('ReactFlightDOMBrowser', () => {
28202820
},
28212821
});
28222822

2823+
const app = ReactServer.createElement(
2824+
ReactServer.Fragment,
2825+
null,
2826+
ReactServer.createElement(Paragraph, null, 'foo'),
2827+
ReactServer.createElement(Paragraph, null, 'bar'),
2828+
);
28232829
const stream = await serverAct(() =>
28242830
ReactServerDOMServer.renderToReadableStream(
28252831
{
2826-
root: ReactServer.createElement(
2827-
ReactServer.Fragment,
2828-
null,
2829-
ReactServer.createElement(Paragraph, null, 'foo'),
2830-
ReactServer.createElement(Paragraph, null, 'bar'),
2831-
),
2832+
root: app,
28322833
},
28332834
webpackMap,
28342835
{
@@ -2900,27 +2901,11 @@ describe('ReactFlightDOMBrowser', () => {
29002901
"name": "Paragraph",
29012902
"props": {},
29022903
"stack": [
2903-
[
2904-
"",
2905-
"/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMBrowser-test.js",
2906-
2829,
2907-
27,
2908-
2823,
2909-
34,
2910-
],
2911-
[
2912-
"serverAct",
2913-
"/packages/internal-test-utils/internalAct.js",
2914-
270,
2915-
19,
2916-
231,
2917-
1,
2918-
],
29192904
[
29202905
"Object.<anonymous>",
29212906
"/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMBrowser-test.js",
2922-
2823,
2923-
18,
2907+
2826,
2908+
19,
29242909
2810,
29252910
89,
29262911
],

packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMNode-test.js

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1216,11 +1216,7 @@ describe('ReactFlightDOMNode', () => {
12161216
const data1 = await getDynamicData1();
12171217
const data2 = await getDynamicData2();
12181218

1219-
return (
1220-
<p>
1221-
{data1} {data2}
1222-
</p>
1223-
);
1219+
return ReactServer.createElement('p', null, data1, ' ', data2);
12241220
}
12251221

12261222
function App() {
@@ -1350,8 +1346,8 @@ describe('ReactFlightDOMNode', () => {
13501346
: '\n') +
13511347
' in body\n' +
13521348
' in html\n' +
1353-
' in App (file://ReactFlightDOMNode-test.js:1233:25)\n' +
1354-
' in ClientRoot (ReactFlightDOMNode-test.js:1308:16)',
1349+
' in App (file://ReactFlightDOMNode-test.js:1229:25)\n' +
1350+
' in ClientRoot (ReactFlightDOMNode-test.js:1304:16)',
13551351
);
13561352
} else {
13571353
expect(
@@ -1371,15 +1367,15 @@ describe('ReactFlightDOMNode', () => {
13711367
).toBe(
13721368
'\n' +
13731369
' in Dynamic (file://ReactFlightDOMNode-test.js:1216:27)\n' +
1374-
' in App (file://ReactFlightDOMNode-test.js:1233:25)',
1370+
' in App (file://ReactFlightDOMNode-test.js:1229:25)',
13751371
);
13761372
} else {
13771373
expect(
13781374
normalizeCodeLocInfo(ownerStack, {preserveLocation: true}),
13791375
).toBe(
13801376
'' +
13811377
'\n' +
1382-
' in App (file://ReactFlightDOMNode-test.js:1233:25)',
1378+
' in App (file://ReactFlightDOMNode-test.js:1229:25)',
13831379
);
13841380
}
13851381
} else {

packages/react-server/src/ReactFlightReplyServer.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1006,6 +1006,11 @@ function parseTypedArray<T: $ArrayBufferView | ArrayBuffer>(
10061006
const id = parseInt(reference.slice(2), 16);
10071007
const prefix = response._prefix;
10081008
const key = prefix + id;
1009+
const chunks = response._chunks;
1010+
if (chunks.has(id)) {
1011+
throw new Error('Cannot repeat reference typed arrays.');
1012+
}
1013+
10091014
// We should have this backingEntry in the store already because we emitted
10101015
// it before referencing it. It should be a Blob.
10111016
// TODO: Use getOutlinedModel to allow us to emit the Blob later. We should be able to do that now.
@@ -1116,6 +1121,10 @@ function parseReadableStream<T>(
11161121
parentKey: string,
11171122
): ReadableStream {
11181123
const id = parseInt(reference.slice(2), 16);
1124+
const chunks = response._chunks;
1125+
if (chunks.has(id)) {
1126+
throw new Error('Cannot self reference streams.');
1127+
}
11191128

11201129
let controller: ReadableStreamController = (null: any);
11211130
const stream = new ReadableStream({
@@ -1218,6 +1227,10 @@ function parseAsyncIterable<T>(
12181227
parentKey: string,
12191228
): $AsyncIterable<T, T, void> | $AsyncIterator<T, T, void> {
12201229
const id = parseInt(reference.slice(2), 16);
1230+
const chunks = response._chunks;
1231+
if (chunks.has(id)) {
1232+
throw new Error('Cannot self reference streams.');
1233+
}
12211234

12221235
const buffer: Array<SomeChunk<IteratorResult<T, T>>> = [];
12231236
let closed = false;

packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3155,7 +3155,7 @@ describe('ReactFlightAsyncDebugInfo', () => {
31553155
}
31563156

31573157
const stream = ReactServerDOMServer.renderToPipeableStream(
3158-
<Component />,
3158+
ReactServer.createElement(Component),
31593159
{},
31603160
);
31613161

@@ -3192,7 +3192,7 @@ describe('ReactFlightAsyncDebugInfo', () => {
31923192
"Object.<anonymous>",
31933193
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
31943194
3158,
3195-
40,
3195+
19,
31963196
3146,
31973197
36,
31983198
],
@@ -3216,7 +3216,7 @@ describe('ReactFlightAsyncDebugInfo', () => {
32163216
"Object.<anonymous>",
32173217
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
32183218
3158,
3219-
40,
3219+
19,
32203220
3146,
32213221
36,
32223222
],
@@ -3248,7 +3248,7 @@ describe('ReactFlightAsyncDebugInfo', () => {
32483248
"Object.<anonymous>",
32493249
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
32503250
3158,
3251-
40,
3251+
19,
32523252
3146,
32533253
36,
32543254
],

scripts/error-codes/codes.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -551,5 +551,7 @@
551551
"563": "This render completed successfully. All cacheSignals are now aborted to allow clean up of any unused resources.",
552552
"564": "Unknown command. The debugChannel was not wired up properly.",
553553
"565": "resolveDebugMessage/closeDebugChannel should not be called for a Request that wasn't kept alive. This is a bug in React.",
554-
"566": "FragmentInstance.scrollIntoView() does not support scrollIntoViewOptions. Use the alignToTop boolean instead."
554+
"566": "FragmentInstance.scrollIntoView() does not support scrollIntoViewOptions. Use the alignToTop boolean instead.",
555+
"567": "Cannot self reference streams.",
556+
"568": "Cannot repeat reference typed arrays."
555557
}

0 commit comments

Comments
 (0)