Skip to content

Commit 891e780

Browse files
document bug behavior in React
1 parent add0cd8 commit 891e780

File tree

4 files changed

+107
-31
lines changed

4 files changed

+107
-31
lines changed

packages/react-on-rails-pro/tests/concurrentRSCPayloadGeneration.rsc.test.tsx

Lines changed: 2 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import * as mock from 'mock-fs';
1414
import ReactOnRails, { RailsContextWithServerStreamingCapabilities } from '../src/ReactOnRailsRSC';
1515
import AsyncQueue from './AsyncQueue';
1616
import StreamReader from './StreamReader';
17+
import removeRSCChunkStack from './utils/removeRSCChunkStack';
1718

1819
const manifestFileDirectory = path.resolve(__dirname, '../src')
1920
const clientManifestPath = path.join(manifestFileDirectory, 'react-client-manifest.json');
@@ -82,35 +83,10 @@ const createParallelRenders = (size: number) => {
8283

8384
const enqueue = (value: string) => asyncQueues.forEach(asyncQueues => asyncQueues.enqueue(value));
8485

85-
const removeComponentJsonData = (chunk: string) => {
86-
const parsedJson = JSON.parse(chunk);
87-
const html = parsedJson.html as string;
88-
const santizedHtml = html.split('\n').map(chunkLine => {
89-
if (!chunkLine.includes('"stack":')) {
90-
return chunkLine;
91-
}
92-
93-
const regexMatch = /(^\d+):\{/.exec(chunkLine)
94-
if (!regexMatch) {
95-
return;
96-
}
97-
98-
const chunkJsonString = chunkLine.slice(chunkLine.indexOf('{'));
99-
const chunkJson = JSON.parse(chunkJsonString);
100-
delete chunkJson.stack;
101-
return `${regexMatch[1]}:${JSON.stringify(chunkJson)}`
102-
});
103-
104-
return JSON.stringify({
105-
...parsedJson,
106-
html: santizedHtml,
107-
});
108-
}
109-
11086
const expectNextChunk = (nextChunk: string) => Promise.all(
11187
readers.map(async (reader) => {
11288
const chunk = await reader.nextChunk();
113-
expect(removeComponentJsonData(chunk)).toEqual(removeComponentJsonData(nextChunk));
89+
expect(removeRSCChunkStack(chunk)).toEqual(removeRSCChunkStack(nextChunk));
11490
})
11591
);
11692

packages/react-on-rails-pro/tests/serverRenderRSCReactComponent.rsc.test.tsx

Lines changed: 70 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { text } from 'stream/consumers';
1212
import { buildServerRenderer } from 'react-on-rails-rsc/server.node';
1313
import createReactOutput from 'react-on-rails/createReactOutput';
1414
import ReactOnRails, { RailsContextWithServerStreamingCapabilities } from '../src/ReactOnRailsRSC.ts';
15+
import removeRSCStackFromAllChunks from './utils/removeRSCStackFromAllChunks.ts'
1516

1617
const PromiseWrapper = async ({ promise, name }: { promise: Promise<string>, name: string }) => {
1718
console.log(`[${name}] Before awaitng`);
@@ -59,7 +60,7 @@ mock({
5960

6061
afterAll(() => {
6162
mock.restore();
62-
})
63+
});
6364

6465
test('no logs leakage between concurrent rendering components', async () => {
6566
const readable1 = ReactOnRails.serverRenderRSCReactComponent({
@@ -89,10 +90,74 @@ test('no logs leakage between concurrent rendering components', async () => {
8990

9091
expect(content1).toContain("First Unique Name");
9192
expect(content2).toContain("Second Unique Name");
92-
// expect(content1.match(/First Unique Name/g)).toHaveLength(55)
93-
// expect(content2.match(/Second Unique Name/g)).toHaveLength(55)
9493
expect(content1).not.toContain("Second Unique Name");
9594
expect(content2).not.toContain("First Unique Name");
95+
});
96+
97+
test('no logs lekage from outside the component', async () => {
98+
const readable1 = ReactOnRails.serverRenderRSCReactComponent({
99+
railsContext: {
100+
reactClientManifestFileName: 'react-client-manifest.json',
101+
reactServerClientManifestFileName: 'react-server-client-manifest.json',
102+
} as unknown as RailsContextWithServerStreamingCapabilities,
103+
name: 'PromiseContainer',
104+
renderingReturnsPromises: true,
105+
throwJsErrors: true,
106+
domNodeId: 'dom-id',
107+
props: { name: "First Unique Name" }
108+
});
96109

97-
// expect(content1.replace(new RegExp("First Unique Name", 'g'), "Second Unique Name")).toEqual(content2);
98-
})
110+
const promise = new Promise<void>((resolve) => {
111+
let i = 0;
112+
const intervalId = setInterval(() => {
113+
console.log(`Interval ${i} at [Outside The Component]`);
114+
i += 1;
115+
if (i === 50) {
116+
clearInterval(intervalId);
117+
resolve();
118+
}
119+
}, 1);
120+
});
121+
122+
const [content1] = await Promise.all([text(readable1), promise]);
123+
124+
expect(content1).toContain("First Unique Name");
125+
expect(content1).not.toContain("Outside The Component");
126+
});
127+
128+
test('[bug] catches logs outside the component during reading the stream', async () => {
129+
const readable1 = ReactOnRails.serverRenderRSCReactComponent({
130+
railsContext: {
131+
reactClientManifestFileName: 'react-client-manifest.json',
132+
reactServerClientManifestFileName: 'react-server-client-manifest.json',
133+
} as unknown as RailsContextWithServerStreamingCapabilities,
134+
name: 'PromiseContainer',
135+
renderingReturnsPromises: true,
136+
throwJsErrors: true,
137+
domNodeId: 'dom-id',
138+
props: { name: "First Unique Name" }
139+
});
140+
141+
let content1 = "";
142+
let i = 0;
143+
readable1.on('data', (chunk) => {
144+
i += 1;
145+
// To avoid infinite loop
146+
if (i < 5) {
147+
console.log("Outside The Component");
148+
}
149+
content1 += chunk.toString();
150+
});
151+
152+
// However, any logs from outside the stream 'data' event callback is not catched
153+
const intervalId = setInterval(() => {
154+
console.log("From Interval")
155+
}, 2);
156+
await finished(readable1);
157+
clearInterval(intervalId);
158+
159+
expect(content1).toContain("First Unique Name");
160+
expect(content1).not.toContain("From Interval");
161+
// Here's the bug
162+
expect(content1).toContain("Outside The Component");
163+
});
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
const removeRSCChunkStack = (chunk: string) => {
2+
const parsedJson = JSON.parse(chunk);
3+
const html = parsedJson.html as string;
4+
const santizedHtml = html.split('\n').map(chunkLine => {
5+
if (!chunkLine.includes('"stack":')) {
6+
return chunkLine;
7+
}
8+
9+
const regexMatch = /(^\d+):\{/.exec(chunkLine)
10+
if (!regexMatch) {
11+
return;
12+
}
13+
14+
const chunkJsonString = chunkLine.slice(chunkLine.indexOf('{'));
15+
const chunkJson = JSON.parse(chunkJsonString);
16+
delete chunkJson.stack;
17+
return `${regexMatch[1]}:${JSON.stringify(chunkJson)}`
18+
});
19+
20+
return JSON.stringify({
21+
...parsedJson,
22+
html: santizedHtml,
23+
});
24+
}
25+
26+
export default removeRSCChunkStack;
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import removeRSCChunkStack from "./removeRSCChunkStack.ts";
2+
3+
const removeRSCStackFromAllChunks = (allChunks: string) => {
4+
return allChunks.split('\n')
5+
.map((chunk) => chunk.trim().length > 0 ? removeRSCChunkStack(chunk) : chunk)
6+
.join('\n');
7+
}
8+
9+
export default removeRSCStackFromAllChunks;

0 commit comments

Comments
 (0)