From 7e522cc5a908fcf56d9901e93301f2569b9c0518 Mon Sep 17 00:00:00 2001 From: Abanoub Ghadban Date: Tue, 9 Dec 2025 14:14:59 +0200 Subject: [PATCH 01/27] use v19.0.3 of react-on-rails-rsc as git dep --- packages/react-on-rails-pro/package.json | 6 ++--- pnpm-lock.yaml | 28 +++++++++++++------- react_on_rails_pro/spec/dummy/package.json | 2 +- react_on_rails_pro/spec/dummy/pnpm-lock.yaml | 15 ++++++----- 4 files changed, 30 insertions(+), 21 deletions(-) diff --git a/packages/react-on-rails-pro/package.json b/packages/react-on-rails-pro/package.json index 10607b128b..0acd252982 100644 --- a/packages/react-on-rails-pro/package.json +++ b/packages/react-on-rails-pro/package.json @@ -78,8 +78,8 @@ "devDependencies": { "@types/mock-fs": "^4.13.4", "mock-fs": "^5.5.0", - "react": "^19.0.3", - "react-dom": "^19.0.3", - "react-on-rails-rsc": "^19.0.4" + "react": "^19.0.1", + "react-dom": "^19.0.1", + "react-on-rails-rsc": "git+https://github.com/shakacode/react_on_rails_rsc#upgrade-to-react-v19.2.1" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9403953675..4393b516ec 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -203,14 +203,14 @@ importers: specifier: ^5.5.0 version: 5.5.0 react: - specifier: ^19.0.3 - version: 19.2.0 + specifier: ^19.0.1 + version: 19.2.3 react-dom: - specifier: ^19.0.3 - version: 19.2.0(react@19.2.0) + specifier: ^19.0.1 + version: 19.2.3(react@19.2.3) react-on-rails-rsc: - specifier: ^19.0.4 - version: 19.0.4(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(webpack@5.103.0(@swc/core@1.15.3)) + specifier: git+https://github.com/shakacode/react_on_rails_rsc#upgrade-to-react-v19.2.1 + version: https://codeload.github.com/shakacode/react_on_rails_rsc/tar.gz/3644ec9cb93f911b27e39f5e3266aeb7d95644a7(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(webpack@5.103.0(@swc/core@1.15.3)) packages/react-on-rails-pro-node-renderer: dependencies: @@ -4426,6 +4426,14 @@ packages: react-dom: ^19.0.3 webpack: ^5.59.0 + react-on-rails-rsc@https://codeload.github.com/shakacode/react_on_rails_rsc/tar.gz/3644ec9cb93f911b27e39f5e3266aeb7d95644a7: + resolution: {tarball: https://codeload.github.com/shakacode/react_on_rails_rsc/tar.gz/3644ec9cb93f911b27e39f5e3266aeb7d95644a7} + version: 19.0.3 + peerDependencies: + react: ^19.2.1 + react-dom: ^19.2.1 + webpack: ^5.59.0 + react@19.2.0: resolution: {integrity: sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==} engines: {node: '>=0.10.0'} @@ -10319,16 +10327,16 @@ snapshots: react-is@18.3.1: {} - react-on-rails-rsc@19.0.4(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(webpack@5.103.0(@swc/core@1.15.3)): + react-on-rails-rsc@19.0.4(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(webpack@5.103.0(@swc/core@1.15.3)): dependencies: acorn-loose: 8.5.2 neo-async: 2.6.2 - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) webpack: 5.103.0(@swc/core@1.15.3) webpack-sources: 3.3.3 - react-on-rails-rsc@19.0.4(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(webpack@5.103.0(@swc/core@1.15.3)): + react-on-rails-rsc@https://codeload.github.com/shakacode/react_on_rails_rsc/tar.gz/3644ec9cb93f911b27e39f5e3266aeb7d95644a7(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(webpack@5.103.0(@swc/core@1.15.3)): dependencies: acorn-loose: 8.5.2 neo-async: 2.6.2 diff --git a/react_on_rails_pro/spec/dummy/package.json b/react_on_rails_pro/spec/dummy/package.json index 53340049d9..1cc3a3db36 100644 --- a/react_on_rails_pro/spec/dummy/package.json +++ b/react_on_rails_pro/spec/dummy/package.json @@ -52,7 +52,7 @@ "@dr.pogodin/react-helmet": "^3.0.4", "react-on-rails-pro": "link:.yalc/react-on-rails-pro", "react-on-rails-pro-node-renderer": "link:.yalc/react-on-rails-pro-node-renderer", - "react-on-rails-rsc": "^19.0.4", + "react-on-rails-rsc": "git+https://github.com/shakacode/react_on_rails_rsc#upgrade-to-react-v19.2.1", "react-proptypes": "^1.0.0", "react-redux": "^9.2.0", "react-refresh": "^0.11.0", diff --git a/react_on_rails_pro/spec/dummy/pnpm-lock.yaml b/react_on_rails_pro/spec/dummy/pnpm-lock.yaml index e158e8ade0..ee777e3426 100644 --- a/react_on_rails_pro/spec/dummy/pnpm-lock.yaml +++ b/react_on_rails_pro/spec/dummy/pnpm-lock.yaml @@ -153,8 +153,8 @@ importers: specifier: link:.yalc/react-on-rails-pro-node-renderer version: link:.yalc/react-on-rails-pro-node-renderer react-on-rails-rsc: - specifier: ^19.0.4 - version: 19.0.4(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(webpack@5.103.0) + specifier: git+https://github.com/shakacode/react_on_rails_rsc#upgrade-to-react-v19.2.1 + version: https://codeload.github.com/shakacode/react_on_rails_rsc/tar.gz/3644ec9cb93f911b27e39f5e3266aeb7d95644a7(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(webpack@5.103.0) react-proptypes: specifier: ^1.0.0 version: 1.0.0 @@ -3466,11 +3466,12 @@ packages: react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} - react-on-rails-rsc@19.0.4: - resolution: {integrity: sha512-KtHYz0opcXJk+Zw5aabjNiaszzpTdFgs1YfSLIZJSzU5SpddUw7b08epkxeJq/TCpR60Q9vsjxX3Q3OWJ97tMg==} + react-on-rails-rsc@https://codeload.github.com/shakacode/react_on_rails_rsc/tar.gz/3644ec9cb93f911b27e39f5e3266aeb7d95644a7: + resolution: {tarball: https://codeload.github.com/shakacode/react_on_rails_rsc/tar.gz/3644ec9cb93f911b27e39f5e3266aeb7d95644a7} + version: 19.0.3 peerDependencies: - react: ^19.0.3 - react-dom: ^19.0.3 + react: ^19.2.1 + react-dom: ^19.2.1 webpack: ^5.59.0 react-proptypes@1.0.0: @@ -7942,7 +7943,7 @@ snapshots: react-is@16.13.1: {} - react-on-rails-rsc@19.0.4(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(webpack@5.103.0): + react-on-rails-rsc@https://codeload.github.com/shakacode/react_on_rails_rsc/tar.gz/3644ec9cb93f911b27e39f5e3266aeb7d95644a7(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(webpack@5.103.0): dependencies: acorn-loose: 8.5.2 neo-async: 2.6.2 From 3b9a6940db7e2c1d3b6fd2e6c7605e0ee2b481a2 Mon Sep 17 00:00:00 2001 From: Abanoub Ghadban Date: Wed, 10 Dec 2025 18:24:05 +0200 Subject: [PATCH 02/27] upgrade to react 19.2.1 --- packages/react-on-rails-pro/package.json | 4 +- .../react-on-rails-pro/tests/AsyncQueue.ts | 5 +- ...oncurrentRSCPayloadGeneration.rsc.test.tsx | 18 ++- .../tests/utils/removeRSCChunkStack.ts | 17 ++- pnpm-lock.yaml | 32 +++-- .../spec/dummy/client/node-renderer.js | 2 +- react_on_rails_pro/spec/dummy/package.json | 4 +- react_on_rails_pro/spec/dummy/pnpm-lock.yaml | 110 +++++++++--------- 8 files changed, 119 insertions(+), 73 deletions(-) diff --git a/packages/react-on-rails-pro/package.json b/packages/react-on-rails-pro/package.json index 0acd252982..ac68cb9c91 100644 --- a/packages/react-on-rails-pro/package.json +++ b/packages/react-on-rails-pro/package.json @@ -78,8 +78,8 @@ "devDependencies": { "@types/mock-fs": "^4.13.4", "mock-fs": "^5.5.0", - "react": "^19.0.1", - "react-dom": "^19.0.1", + "react": "19.2.1", + "react-dom": "19.2.1", "react-on-rails-rsc": "git+https://github.com/shakacode/react_on_rails_rsc#upgrade-to-react-v19.2.1" } } diff --git a/packages/react-on-rails-pro/tests/AsyncQueue.ts b/packages/react-on-rails-pro/tests/AsyncQueue.ts index d6f8b7b204..0cf6c843b8 100644 --- a/packages/react-on-rails-pro/tests/AsyncQueue.ts +++ b/packages/react-on-rails-pro/tests/AsyncQueue.ts @@ -26,9 +26,10 @@ class AsyncQueue { dequeue() { return new Promise((resolve, reject) => { - const bufferValueIfExist = this.buffer.shift(); + const bufferValueIfExist = this.buffer.length > 0 ? this.buffer.join('') : undefined; + this.buffer.length = 0; if (bufferValueIfExist) { - resolve(bufferValueIfExist); + resolve(bufferValueIfExist as T); } else if (this.isEnded) { reject(new Error('Queue Ended')); } else { diff --git a/packages/react-on-rails-pro/tests/concurrentRSCPayloadGeneration.rsc.test.tsx b/packages/react-on-rails-pro/tests/concurrentRSCPayloadGeneration.rsc.test.tsx index 353b19d6f8..502c6193ef 100644 --- a/packages/react-on-rails-pro/tests/concurrentRSCPayloadGeneration.rsc.test.tsx +++ b/packages/react-on-rails-pro/tests/concurrentRSCPayloadGeneration.rsc.test.tsx @@ -100,13 +100,20 @@ const createParallelRenders = (size: number) => { return { enqueue, expectNextChunk, expectEndOfStream }; }; +const delay = (ms: number) => new Promise((resolve) => { + setTimeout(() => { + resolve(); + }, ms); +}); + test('Renders concurrent rsc streams as single rsc stream', async () => { - expect.assertions(258); + // expect.assertions(258); const asyncQueue = new AsyncQueue(); const stream = renderComponent({ asyncQueue }); const reader = new StreamReader(stream); const chunks: string[] = []; + await delay(100); let chunk = await reader.nextChunk(); chunks.push(chunk); expect(chunk).toContain('Async Queue'); @@ -114,16 +121,20 @@ test('Renders concurrent rsc streams as single rsc stream', async () => { expect(chunk).not.toContain('Random Value'); asyncQueue.enqueue('Random Value1'); + + await delay(100); chunk = await reader.nextChunk(); chunks.push(chunk); expect(chunk).toContain('Random Value1'); asyncQueue.enqueue('Random Value2'); + await delay(100); chunk = await reader.nextChunk(); chunks.push(chunk); expect(chunk).toContain('Random Value2'); asyncQueue.enqueue('Random Value3'); + await delay(100); chunk = await reader.nextChunk(); chunks.push(chunk); expect(chunk).toContain('Random Value3'); @@ -133,12 +144,17 @@ test('Renders concurrent rsc streams as single rsc stream', async () => { const { enqueue, expectNextChunk, expectEndOfStream } = createParallelRenders(50); expect(chunks).toHaveLength(4); + await delay(100); await expectNextChunk(chunks[0]); enqueue('Random Value1'); + await delay(100); await expectNextChunk(chunks[1]); enqueue('Random Value2'); + await delay(100); await expectNextChunk(chunks[2]); enqueue('Random Value3'); + await delay(100); await expectNextChunk(chunks[3]); + await delay(100); await expectEndOfStream(); }); diff --git a/packages/react-on-rails-pro/tests/utils/removeRSCChunkStack.ts b/packages/react-on-rails-pro/tests/utils/removeRSCChunkStack.ts index 4774feadfa..c5d2d0df4d 100644 --- a/packages/react-on-rails-pro/tests/utils/removeRSCChunkStack.ts +++ b/packages/react-on-rails-pro/tests/utils/removeRSCChunkStack.ts @@ -1,7 +1,16 @@ import { RSCPayloadChunk } from 'react-on-rails'; -const removeRSCChunkStack = (chunk: string) => { - const parsedJson = JSON.parse(chunk) as RSCPayloadChunk; +const removeRSCChunkStackInternal = (chunk: string) => { + if (chunk.trim().length === 0) { + return chunk; + } + + let parsedJson: RSCPayloadChunk; + try { + parsedJson = JSON.parse(chunk) as RSCPayloadChunk; + } catch (err) { + throw new Error(`Error while parsing the json: "${chunk}", ${err}`); + } const { html } = parsedJson; const santizedHtml = html.split('\n').map((chunkLine) => { if (!chunkLine.includes('"stack":')) { @@ -25,4 +34,8 @@ const removeRSCChunkStack = (chunk: string) => { }); }; +const removeRSCChunkStack = (chunk: string) => { + chunk.split('\n').map(removeRSCChunkStackInternal).join('\n'); +} + export default removeRSCChunkStack; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4393b516ec..50c5b51d77 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -203,14 +203,14 @@ importers: specifier: ^5.5.0 version: 5.5.0 react: - specifier: ^19.0.1 - version: 19.2.3 + specifier: 19.2.1 + version: 19.2.1 react-dom: - specifier: ^19.0.1 - version: 19.2.3(react@19.2.3) + specifier: 19.2.1 + version: 19.2.1(react@19.2.1) react-on-rails-rsc: specifier: git+https://github.com/shakacode/react_on_rails_rsc#upgrade-to-react-v19.2.1 - version: https://codeload.github.com/shakacode/react_on_rails_rsc/tar.gz/3644ec9cb93f911b27e39f5e3266aeb7d95644a7(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(webpack@5.103.0(@swc/core@1.15.3)) + version: https://codeload.github.com/shakacode/react_on_rails_rsc/tar.gz/3644ec9cb93f911b27e39f5e3266aeb7d95644a7(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(webpack@5.103.0(@swc/core@1.15.3)) packages/react-on-rails-pro-node-renderer: dependencies: @@ -4405,6 +4405,11 @@ packages: peerDependencies: react: ^19.2.0 + react-dom@19.2.1: + resolution: {integrity: sha512-ibrK8llX2a4eOskq1mXKu/TGZj9qzomO+sNfO98M6d9zIPOEhlBkMkBUBLd1vgS0gQsLDBzA+8jJBVXDnfHmJg==} + peerDependencies: + react: ^19.2.1 + react-dom@19.2.3: resolution: {integrity: sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==} peerDependencies: @@ -4438,6 +4443,10 @@ packages: resolution: {integrity: sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==} engines: {node: '>=0.10.0'} + react@19.2.1: + resolution: {integrity: sha512-DGrYcCWK7tvYMnWh79yrPHt+vdx9tY+1gPZa7nJQtO/p8bLTDaHp4dzwEhQB7pZ4Xe3ok4XKuEPrVuc+wlpkmw==} + engines: {node: '>=0.10.0'} + react@19.2.3: resolution: {integrity: sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==} engines: {node: '>=0.10.0'} @@ -10316,6 +10325,11 @@ snapshots: react: 19.2.0 scheduler: 0.27.0 + react-dom@19.2.1(react@19.2.1): + dependencies: + react: 19.2.1 + scheduler: 0.27.0 + react-dom@19.2.3(react@19.2.3): dependencies: react: 19.2.3 @@ -10336,17 +10350,19 @@ snapshots: webpack: 5.103.0(@swc/core@1.15.3) webpack-sources: 3.3.3 - react-on-rails-rsc@https://codeload.github.com/shakacode/react_on_rails_rsc/tar.gz/3644ec9cb93f911b27e39f5e3266aeb7d95644a7(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(webpack@5.103.0(@swc/core@1.15.3)): + react-on-rails-rsc@https://codeload.github.com/shakacode/react_on_rails_rsc/tar.gz/3644ec9cb93f911b27e39f5e3266aeb7d95644a7(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(webpack@5.103.0(@swc/core@1.15.3)): dependencies: acorn-loose: 8.5.2 neo-async: 2.6.2 - react: 19.2.3 - react-dom: 19.2.3(react@19.2.3) + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) webpack: 5.103.0(@swc/core@1.15.3) webpack-sources: 3.3.3 react@19.2.0: {} + react@19.2.1: {} + react@19.2.3: {} readline-sync@1.4.10: {} diff --git a/react_on_rails_pro/spec/dummy/client/node-renderer.js b/react_on_rails_pro/spec/dummy/client/node-renderer.js index 67f390b45a..15d1ac7c3b 100644 --- a/react_on_rails_pro/spec/dummy/client/node-renderer.js +++ b/react_on_rails_pro/spec/dummy/client/node-renderer.js @@ -55,7 +55,7 @@ const config = { // additionalContext enables you to specify additional NodeJS modules to add to the VM context in // addition to our supportModules defaults. - additionalContext: { URL, AbortController }, + additionalContext: { URL, AbortController, performance }, // Required to use setTimeout, setInterval, & clearTimeout during server rendering stubTimers: false, diff --git a/react_on_rails_pro/spec/dummy/package.json b/react_on_rails_pro/spec/dummy/package.json index 1cc3a3db36..a2a6bed227 100644 --- a/react_on_rails_pro/spec/dummy/package.json +++ b/react_on_rails_pro/spec/dummy/package.json @@ -46,8 +46,8 @@ "postcss": "^8.4.31", "postcss-loader": "^7.1.0", "prop-types": "^15.7.2", - "react": "^19.0.3", - "react-dom": "^19.0.3", + "react": "19.2.1", + "react-dom": "19.2.1", "react-error-boundary": "^4.1.2", "@dr.pogodin/react-helmet": "^3.0.4", "react-on-rails-pro": "link:.yalc/react-on-rails-pro", diff --git a/react_on_rails_pro/spec/dummy/pnpm-lock.yaml b/react_on_rails_pro/spec/dummy/pnpm-lock.yaml index ee777e3426..d62c6b1ffa 100644 --- a/react_on_rails_pro/spec/dummy/pnpm-lock.yaml +++ b/react_on_rails_pro/spec/dummy/pnpm-lock.yaml @@ -10,7 +10,7 @@ importers: dependencies: '@apollo/client': specifier: ^3.6.9 - version: 3.14.0(@types/react@18.3.27)(graphql@16.12.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + version: 3.14.0(@types/react@18.3.27)(graphql@16.12.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) '@babel/core': specifier: '7' version: 7.28.5 @@ -31,7 +31,7 @@ importers: version: 7.28.4 '@dr.pogodin/react-helmet': specifier: ^3.0.4 - version: 3.0.4(react@19.2.3) + version: 3.0.4(react@19.2.1) '@honeybadger-io/js': specifier: ^4.3.1 version: 4.9.3 @@ -40,10 +40,10 @@ importers: version: 5.16.1(@babel/core@7.28.5) '@loadable/component': specifier: ^5.16.3 - version: 5.16.7(react@19.2.3) + version: 5.16.7(react@19.2.1) '@loadable/server': specifier: ^5.16.2 - version: 5.16.7(@loadable/component@5.16.7(react@19.2.3))(react@19.2.3) + version: 5.16.7(@loadable/component@5.16.7(react@19.2.1))(react@19.2.1) '@loadable/webpack-plugin': specifier: ^5.15.2 version: 5.15.2(webpack@5.103.0) @@ -52,10 +52,10 @@ importers: version: 7.120.4 '@shakacode/use-ssr-computation.macro': specifier: ^1.2.4 - version: 1.2.4(react@19.2.3) + version: 1.2.4(react@19.2.1) '@shakacode/use-ssr-computation.runtime': specifier: ^2.0.0 - version: 2.0.0(react@19.2.3) + version: 2.0.0(react@19.2.1) '@webpack-cli/serve': specifier: ^1.6.0 version: 1.7.0(webpack-cli@4.10.0)(webpack-dev-server@4.15.2) @@ -138,14 +138,14 @@ importers: specifier: ^15.7.2 version: 15.8.1 react: - specifier: ^19.0.3 - version: 19.2.3 + specifier: 19.2.1 + version: 19.2.1 react-dom: - specifier: ^19.0.3 - version: 19.2.3(react@19.2.3) + specifier: 19.2.1 + version: 19.2.1(react@19.2.1) react-error-boundary: specifier: ^4.1.2 - version: 4.1.2(react@19.2.3) + version: 4.1.2(react@19.2.1) react-on-rails-pro: specifier: link:.yalc/react-on-rails-pro version: link:.yalc/react-on-rails-pro @@ -154,19 +154,19 @@ importers: version: link:.yalc/react-on-rails-pro-node-renderer react-on-rails-rsc: specifier: git+https://github.com/shakacode/react_on_rails_rsc#upgrade-to-react-v19.2.1 - version: https://codeload.github.com/shakacode/react_on_rails_rsc/tar.gz/3644ec9cb93f911b27e39f5e3266aeb7d95644a7(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(webpack@5.103.0) + version: https://codeload.github.com/shakacode/react_on_rails_rsc/tar.gz/3644ec9cb93f911b27e39f5e3266aeb7d95644a7(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(webpack@5.103.0) react-proptypes: specifier: ^1.0.0 version: 1.0.0 react-redux: specifier: ^9.2.0 - version: 9.2.0(@types/react@18.3.27)(react@19.2.3)(redux@5.0.1) + version: 9.2.0(@types/react@18.3.27)(react@19.2.1)(redux@5.0.1) react-refresh: specifier: ^0.11.0 version: 0.11.0 react-router-dom: specifier: ^6.15.0 - version: 6.30.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + version: 6.30.2(react-dom@19.2.1(react@19.2.1))(react@19.2.1) redis: specifier: ^5.0.1 version: 5.10.0 @@ -3453,10 +3453,10 @@ packages: resolution: {integrity: sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==} engines: {node: '>= 0.8'} - react-dom@19.2.3: - resolution: {integrity: sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==} + react-dom@19.2.1: + resolution: {integrity: sha512-ibrK8llX2a4eOskq1mXKu/TGZj9qzomO+sNfO98M6d9zIPOEhlBkMkBUBLd1vgS0gQsLDBzA+8jJBVXDnfHmJg==} peerDependencies: - react: ^19.2.3 + react: ^19.2.1 react-error-boundary@4.1.2: resolution: {integrity: sha512-GQDxZ5Jd+Aq/qUxbCm1UtzmL/s++V7zKgE8yMktJiCQXCCFZnMZh9ng+6/Ne6PjNSXH0L9CjeOEREfRnq6Duag==} @@ -3506,8 +3506,8 @@ packages: peerDependencies: react: '>=16.8' - react@19.2.3: - resolution: {integrity: sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==} + react@19.2.1: + resolution: {integrity: sha512-DGrYcCWK7tvYMnWh79yrPHt+vdx9tY+1gPZa7nJQtO/p8bLTDaHp4dzwEhQB7pZ4Xe3ok4XKuEPrVuc+wlpkmw==} engines: {node: '>=0.10.0'} read-cache@1.0.0: @@ -4466,7 +4466,7 @@ snapshots: '@alloc/quick-lru@5.2.0': {} - '@apollo/client@3.14.0(@types/react@18.3.27)(graphql@16.12.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + '@apollo/client@3.14.0(@types/react@18.3.27)(graphql@16.12.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': dependencies: '@graphql-typed-document-node/core': 3.2.0(graphql@16.12.0) '@wry/caches': 1.0.1 @@ -4477,14 +4477,14 @@ snapshots: hoist-non-react-statics: 3.3.2 optimism: 0.18.1 prop-types: 15.8.1 - rehackt: 0.1.0(@types/react@18.3.27)(react@19.2.3) + rehackt: 0.1.0(@types/react@18.3.27)(react@19.2.1) symbol-observable: 4.0.0 ts-invariant: 0.10.3 tslib: 2.8.1 zen-observable-ts: 1.2.5 optionalDependencies: - react: 19.2.3 - react-dom: 19.2.3(react@19.2.3) + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) transitivePeerDependencies: - '@types/react' @@ -5239,10 +5239,10 @@ snapshots: '@discoveryjs/json-ext@0.5.7': {} - '@dr.pogodin/react-helmet@3.0.4(react@19.2.3)': + '@dr.pogodin/react-helmet@3.0.4(react@19.2.1)': dependencies: '@babel/runtime': 7.28.4 - react: 19.2.3 + react: 19.2.1 '@graphql-typed-document-node/core@3.2.0(graphql@16.12.0)': dependencies: @@ -5289,18 +5289,18 @@ snapshots: '@babel/core': 7.28.5 '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.28.5) - '@loadable/component@5.16.7(react@19.2.3)': + '@loadable/component@5.16.7(react@19.2.1)': dependencies: '@babel/runtime': 7.28.4 hoist-non-react-statics: 3.3.2 - react: 19.2.3 + react: 19.2.1 react-is: 16.13.1 - '@loadable/server@5.16.7(@loadable/component@5.16.7(react@19.2.3))(react@19.2.3)': + '@loadable/server@5.16.7(@loadable/component@5.16.7(react@19.2.1))(react@19.2.1)': dependencies: - '@loadable/component': 5.16.7(react@19.2.3) + '@loadable/component': 5.16.7(react@19.2.1) lodash: 4.17.21 - react: 19.2.3 + react: 19.2.1 '@loadable/webpack-plugin@5.15.2(webpack@5.103.0)': dependencies: @@ -5454,16 +5454,16 @@ snapshots: dependencies: '@sentry/types': 7.120.4 - '@shakacode/use-ssr-computation.macro@1.2.4(react@19.2.3)': + '@shakacode/use-ssr-computation.macro@1.2.4(react@19.2.1)': dependencies: - '@shakacode/use-ssr-computation.runtime': 2.0.0(react@19.2.3) + '@shakacode/use-ssr-computation.runtime': 2.0.0(react@19.2.1) babel-plugin-macros: 3.1.0 transitivePeerDependencies: - react - '@shakacode/use-ssr-computation.runtime@2.0.0(react@19.2.3)': + '@shakacode/use-ssr-computation.runtime@2.0.0(react@19.2.1)': dependencies: - react: 19.2.3 + react: 19.2.1 '@tailwindcss/aspect-ratio@0.4.2(tailwindcss@3.4.18)': dependencies: @@ -7931,24 +7931,24 @@ snapshots: iconv-lite: 0.4.24 unpipe: 1.0.0 - react-dom@19.2.3(react@19.2.3): + react-dom@19.2.1(react@19.2.1): dependencies: - react: 19.2.3 + react: 19.2.1 scheduler: 0.27.0 - react-error-boundary@4.1.2(react@19.2.3): + react-error-boundary@4.1.2(react@19.2.1): dependencies: '@babel/runtime': 7.28.4 - react: 19.2.3 + react: 19.2.1 react-is@16.13.1: {} - react-on-rails-rsc@https://codeload.github.com/shakacode/react_on_rails_rsc/tar.gz/3644ec9cb93f911b27e39f5e3266aeb7d95644a7(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(webpack@5.103.0): + react-on-rails-rsc@https://codeload.github.com/shakacode/react_on_rails_rsc/tar.gz/3644ec9cb93f911b27e39f5e3266aeb7d95644a7(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(webpack@5.103.0): dependencies: acorn-loose: 8.5.2 neo-async: 2.6.2 - react: 19.2.3 - react-dom: 19.2.3(react@19.2.3) + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) webpack: 5.103.0(webpack-cli@4.10.0) webpack-sources: 3.3.3 @@ -7956,30 +7956,30 @@ snapshots: dependencies: prop-types: 15.8.1 - react-redux@9.2.0(@types/react@18.3.27)(react@19.2.3)(redux@5.0.1): + react-redux@9.2.0(@types/react@18.3.27)(react@19.2.1)(redux@5.0.1): dependencies: '@types/use-sync-external-store': 0.0.6 - react: 19.2.3 - use-sync-external-store: 1.6.0(react@19.2.3) + react: 19.2.1 + use-sync-external-store: 1.6.0(react@19.2.1) optionalDependencies: '@types/react': 18.3.27 redux: 5.0.1 react-refresh@0.11.0: {} - react-router-dom@6.30.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3): + react-router-dom@6.30.2(react-dom@19.2.1(react@19.2.1))(react@19.2.1): dependencies: '@remix-run/router': 1.23.1 - react: 19.2.3 - react-dom: 19.2.3(react@19.2.3) - react-router: 6.30.2(react@19.2.3) + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) + react-router: 6.30.2(react@19.2.1) - react-router@6.30.2(react@19.2.3): + react-router@6.30.2(react@19.2.1): dependencies: '@remix-run/router': 1.23.1 - react: 19.2.3 + react: 19.2.1 - react@19.2.3: {} + react@19.2.1: {} read-cache@1.0.0: dependencies: @@ -8048,10 +8048,10 @@ snapshots: dependencies: jsesc: 3.1.0 - rehackt@0.1.0(@types/react@18.3.27)(react@19.2.3): + rehackt@0.1.0(@types/react@18.3.27)(react@19.2.1): optionalDependencies: '@types/react': 18.3.27 - react: 19.2.3 + react: 19.2.1 relateurl@0.2.7: {} @@ -8671,9 +8671,9 @@ snapshots: punycode: 1.4.1 qs: 6.14.0 - use-sync-external-store@1.6.0(react@19.2.3): + use-sync-external-store@1.6.0(react@19.2.1): dependencies: - react: 19.2.3 + react: 19.2.1 util-deprecate@1.0.2: {} From d8b059874db272b02366c4b22934a50335088b42 Mon Sep 17 00:00:00 2001 From: Abanoub Ghadban Date: Thu, 11 Dec 2025 10:30:09 +0200 Subject: [PATCH 03/27] make vm adds the global `performance` object to the vm context --- packages/react-on-rails-pro-node-renderer/src/worker/vm.ts | 1 + react_on_rails_pro/spec/dummy/client/node-renderer.js | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/react-on-rails-pro-node-renderer/src/worker/vm.ts b/packages/react-on-rails-pro-node-renderer/src/worker/vm.ts index 3567f9ea7c..aa3f997dcf 100644 --- a/packages/react-on-rails-pro-node-renderer/src/worker/vm.ts +++ b/packages/react-on-rails-pro-node-renderer/src/worker/vm.ts @@ -217,6 +217,7 @@ export async function buildVM(filePath: string) { TextEncoder, URLSearchParams, ReadableStream, + performance, process, setTimeout, setInterval, diff --git a/react_on_rails_pro/spec/dummy/client/node-renderer.js b/react_on_rails_pro/spec/dummy/client/node-renderer.js index 15d1ac7c3b..67f390b45a 100644 --- a/react_on_rails_pro/spec/dummy/client/node-renderer.js +++ b/react_on_rails_pro/spec/dummy/client/node-renderer.js @@ -55,7 +55,7 @@ const config = { // additionalContext enables you to specify additional NodeJS modules to add to the VM context in // addition to our supportModules defaults. - additionalContext: { URL, AbortController, performance }, + additionalContext: { URL, AbortController }, // Required to use setTimeout, setInterval, & clearTimeout during server rendering stubTimers: false, From b5cfa86228c16a741a3ee8667206c34bb9320a88 Mon Sep 17 00:00:00 2001 From: Abanoub Ghadban Date: Thu, 11 Dec 2025 10:48:01 +0200 Subject: [PATCH 04/27] linting --- .../tests/concurrentRSCPayloadGeneration.rsc.test.tsx | 11 ++++++----- .../tests/utils/removeRSCChunkStack.ts | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/react-on-rails-pro/tests/concurrentRSCPayloadGeneration.rsc.test.tsx b/packages/react-on-rails-pro/tests/concurrentRSCPayloadGeneration.rsc.test.tsx index 502c6193ef..9f6a8b254d 100644 --- a/packages/react-on-rails-pro/tests/concurrentRSCPayloadGeneration.rsc.test.tsx +++ b/packages/react-on-rails-pro/tests/concurrentRSCPayloadGeneration.rsc.test.tsx @@ -100,11 +100,12 @@ const createParallelRenders = (size: number) => { return { enqueue, expectNextChunk, expectEndOfStream }; }; -const delay = (ms: number) => new Promise((resolve) => { - setTimeout(() => { - resolve(); - }, ms); -}); +const delay = (ms: number) => + new Promise((resolve) => { + setTimeout(() => { + resolve(); + }, ms); + }); test('Renders concurrent rsc streams as single rsc stream', async () => { // expect.assertions(258); diff --git a/packages/react-on-rails-pro/tests/utils/removeRSCChunkStack.ts b/packages/react-on-rails-pro/tests/utils/removeRSCChunkStack.ts index c5d2d0df4d..57795540a6 100644 --- a/packages/react-on-rails-pro/tests/utils/removeRSCChunkStack.ts +++ b/packages/react-on-rails-pro/tests/utils/removeRSCChunkStack.ts @@ -36,6 +36,6 @@ const removeRSCChunkStackInternal = (chunk: string) => { const removeRSCChunkStack = (chunk: string) => { chunk.split('\n').map(removeRSCChunkStackInternal).join('\n'); -} +}; export default removeRSCChunkStack; From 3ee59c5a4e16cf1bdccebc9c0854805bf2b1b24a Mon Sep 17 00:00:00 2001 From: Abanoub Ghadban Date: Thu, 11 Dec 2025 10:49:07 +0200 Subject: [PATCH 05/27] add AbortController to the vm context --- packages/react-on-rails-pro-node-renderer/src/worker/vm.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/react-on-rails-pro-node-renderer/src/worker/vm.ts b/packages/react-on-rails-pro-node-renderer/src/worker/vm.ts index aa3f997dcf..daba69dc69 100644 --- a/packages/react-on-rails-pro-node-renderer/src/worker/vm.ts +++ b/packages/react-on-rails-pro-node-renderer/src/worker/vm.ts @@ -212,6 +212,7 @@ export async function buildVM(filePath: string) { // 1. docs/node-renderer/js-configuration.md // 2. packages/node-renderer/src/shared/configBuilder.ts extendContext(contextObject, { + AbortController, Buffer, TextDecoder, TextEncoder, From a96642482604bbc6d4c4ba2dd27d7c1150e99200 Mon Sep 17 00:00:00 2001 From: Abanoub Ghadban Date: Thu, 11 Dec 2025 12:00:45 +0200 Subject: [PATCH 06/27] revert this: make htmlStreaming.test.js fail to debug the error --- .../tests/htmlStreaming.test.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/react-on-rails-pro-node-renderer/tests/htmlStreaming.test.js b/packages/react-on-rails-pro-node-renderer/tests/htmlStreaming.test.js index 45b98e683d..b8ebfe52d4 100644 --- a/packages/react-on-rails-pro-node-renderer/tests/htmlStreaming.test.js +++ b/packages/react-on-rails-pro-node-renderer/tests/htmlStreaming.test.js @@ -197,6 +197,8 @@ describe('html streaming', () => { expect(fullBody).toContain('branch2 (level 1)'); expect(fullBody).toContain('branch2 (level 0)'); + // Fail to findout the chunks content on CI + expect(JSON.stringify(jsonChunks, undefined, 2)).toContain('nnnnnnnnnnnnnnnnnn'); expect(jsonChunks[0].isShellReady).toBeTruthy(); expect(jsonChunks[0].hasErrors).toBeTruthy(); expect(jsonChunks[0].renderingError).toMatchObject({ From a8feb81beaafa6032d389a3e73b63414117d08dd Mon Sep 17 00:00:00 2001 From: Abanoub Ghadban Date: Thu, 11 Dec 2025 12:15:18 +0200 Subject: [PATCH 07/27] revert this: log chunk --- .../tests/htmlStreaming.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-on-rails-pro-node-renderer/tests/htmlStreaming.test.js b/packages/react-on-rails-pro-node-renderer/tests/htmlStreaming.test.js index b8ebfe52d4..042b437889 100644 --- a/packages/react-on-rails-pro-node-renderer/tests/htmlStreaming.test.js +++ b/packages/react-on-rails-pro-node-renderer/tests/htmlStreaming.test.js @@ -54,7 +54,7 @@ const makeRequest = async (options = {}) => { try { return JSON.parse(chunk); } catch (e) { - return { hasErrors: true, error: `JSON parsing failed: ${e.message}` }; + return { hasErrors: true, error: `JSON parsing failed: ${e.message}`, chunk }; } }), ); From 8146d0126b345354f80e7c7934952f9d9fb238af Mon Sep 17 00:00:00 2001 From: Abanoub Ghadban Date: Thu, 11 Dec 2025 12:51:34 +0200 Subject: [PATCH 08/27] log chunks size --- .../tests/htmlStreaming.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-on-rails-pro-node-renderer/tests/htmlStreaming.test.js b/packages/react-on-rails-pro-node-renderer/tests/htmlStreaming.test.js index 042b437889..684883849e 100644 --- a/packages/react-on-rails-pro-node-renderer/tests/htmlStreaming.test.js +++ b/packages/react-on-rails-pro-node-renderer/tests/htmlStreaming.test.js @@ -54,7 +54,7 @@ const makeRequest = async (options = {}) => { try { return JSON.parse(chunk); } catch (e) { - return { hasErrors: true, error: `JSON parsing failed: ${e.message}`, chunk }; + return { hasErrors: true, error: `JSON parsing failed: ${e.message}`, chunk, chunksSize: decodedChunksFromData.length }; } }), ); From 79e206b5028b78dc187941252735905975eed714 Mon Sep 17 00:00:00 2001 From: Abanoub Ghadban Date: Thu, 11 Dec 2025 13:03:22 +0200 Subject: [PATCH 09/27] use buffer for received incomplete chunks --- .../tests/htmlStreaming.test.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/react-on-rails-pro-node-renderer/tests/htmlStreaming.test.js b/packages/react-on-rails-pro-node-renderer/tests/htmlStreaming.test.js index 684883849e..e98c24012b 100644 --- a/packages/react-on-rails-pro-node-renderer/tests/htmlStreaming.test.js +++ b/packages/react-on-rails-pro-node-renderer/tests/htmlStreaming.test.js @@ -34,6 +34,7 @@ const makeRequest = async (options = {}) => { const jsonChunks = []; let firstByteTime; let status; + let buffer = ''; const decoder = new TextDecoder(); request.on('response', (headers) => { @@ -44,10 +45,17 @@ const makeRequest = async (options = {}) => { // Sometimes, multiple chunks are merged into one. // So, the server uses \n as a delimiter between chunks. const decodedData = typeof data === 'string' ? data : decoder.decode(data, { stream: false }); - const decodedChunksFromData = decodedData + const decodedChunksFromData = (buffer + decodedData) .split('\n') .map((chunk) => chunk.trim()) .filter((chunk) => chunk.length > 0); + + if (!decodedData.endsWith('\n')) { + buffer = decodedChunksFromData.pop() ?? ''; + } else { + buffer = ''; + } + chunks.push(...decodedChunksFromData); jsonChunks.push( ...decodedChunksFromData.map((chunk) => { From 5f53e767f85f1d9a0e61c48f8d5b2dbe924f51b8 Mon Sep 17 00:00:00 2001 From: Abanoub Ghadban Date: Thu, 11 Dec 2025 13:17:02 +0200 Subject: [PATCH 10/27] revert logging --- .../tests/htmlStreaming.test.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/react-on-rails-pro-node-renderer/tests/htmlStreaming.test.js b/packages/react-on-rails-pro-node-renderer/tests/htmlStreaming.test.js index e98c24012b..2e05a736af 100644 --- a/packages/react-on-rails-pro-node-renderer/tests/htmlStreaming.test.js +++ b/packages/react-on-rails-pro-node-renderer/tests/htmlStreaming.test.js @@ -62,7 +62,7 @@ const makeRequest = async (options = {}) => { try { return JSON.parse(chunk); } catch (e) { - return { hasErrors: true, error: `JSON parsing failed: ${e.message}`, chunk, chunksSize: decodedChunksFromData.length }; + return { hasErrors: true, error: `JSON parsing failed: ${e.message}` }; } }), ); @@ -206,7 +206,6 @@ describe('html streaming', () => { expect(fullBody).toContain('branch2 (level 0)'); // Fail to findout the chunks content on CI - expect(JSON.stringify(jsonChunks, undefined, 2)).toContain('nnnnnnnnnnnnnnnnnn'); expect(jsonChunks[0].isShellReady).toBeTruthy(); expect(jsonChunks[0].hasErrors).toBeTruthy(); expect(jsonChunks[0].renderingError).toMatchObject({ From e5e851a0979314a1e4207e1eea604d416d5abe3a Mon Sep 17 00:00:00 2001 From: Abanoub Ghadban Date: Thu, 11 Dec 2025 14:32:57 +0200 Subject: [PATCH 11/27] linting --- .../tests/htmlStreaming.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-on-rails-pro-node-renderer/tests/htmlStreaming.test.js b/packages/react-on-rails-pro-node-renderer/tests/htmlStreaming.test.js index 2e05a736af..6938344cab 100644 --- a/packages/react-on-rails-pro-node-renderer/tests/htmlStreaming.test.js +++ b/packages/react-on-rails-pro-node-renderer/tests/htmlStreaming.test.js @@ -49,7 +49,7 @@ const makeRequest = async (options = {}) => { .split('\n') .map((chunk) => chunk.trim()) .filter((chunk) => chunk.length > 0); - + if (!decodedData.endsWith('\n')) { buffer = decodedChunksFromData.pop() ?? ''; } else { From 9618d459cac7f92edbdef46b3f9d0ca961cbdba2 Mon Sep 17 00:00:00 2001 From: Abanoub Ghadban Date: Thu, 11 Dec 2025 15:19:09 +0200 Subject: [PATCH 12/27] increase the time of the test that reproduce the react condole replay bug --- .../tests/serverRenderRSCReactComponent.rsc.test.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/react-on-rails-pro/tests/serverRenderRSCReactComponent.rsc.test.tsx b/packages/react-on-rails-pro/tests/serverRenderRSCReactComponent.rsc.test.tsx index 4053f8280e..59748e8038 100644 --- a/packages/react-on-rails-pro/tests/serverRenderRSCReactComponent.rsc.test.tsx +++ b/packages/react-on-rails-pro/tests/serverRenderRSCReactComponent.rsc.test.tsx @@ -28,7 +28,7 @@ const PromiseContainer = ({ name }: { name: string }) => { clearInterval(intervalId); resolve(`Value of name ${name}`); } - }, 1); + }, 20); }); return ( @@ -157,4 +157,4 @@ test('[bug] catches logs outside the component during reading the stream', async expect(content1).not.toContain('From Interval'); // Here's the bug expect(content1).toContain('Outside The Component'); -}); +}, 10000); From cdeb4baf20cc5a4e5af30f5db3479b7a0f997b2a Mon Sep 17 00:00:00 2001 From: Abanoub Ghadban Date: Thu, 11 Dec 2025 15:26:55 +0200 Subject: [PATCH 13/27] increase delay at test --- .../serverRenderRSCReactComponent.rsc.test.tsx | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/packages/react-on-rails-pro/tests/serverRenderRSCReactComponent.rsc.test.tsx b/packages/react-on-rails-pro/tests/serverRenderRSCReactComponent.rsc.test.tsx index 59748e8038..3c148128a9 100644 --- a/packages/react-on-rails-pro/tests/serverRenderRSCReactComponent.rsc.test.tsx +++ b/packages/react-on-rails-pro/tests/serverRenderRSCReactComponent.rsc.test.tsx @@ -21,14 +21,16 @@ const PromiseWrapper = async ({ promise, name }: { promise: Promise; nam const PromiseContainer = ({ name }: { name: string }) => { const promise = new Promise((resolve) => { let i = 0; - const intervalId = setInterval(() => { - console.log(`Interval ${i} at [${name}]`); - i += 1; - if (i === 50) { - clearInterval(intervalId); - resolve(`Value of name ${name}`); - } - }, 20); + setTimeout(() => { + const intervalId = setInterval(() => { + console.log(`Interval ${i} at [${name}]`); + i += 1; + if (i === 50) { + clearInterval(intervalId); + resolve(`Value of name ${name}`); + } + }, 20); + }, 200); }); return ( From 62d745546f47c3b41a322a6119b2827437c427bc Mon Sep 17 00:00:00 2001 From: Abanoub Ghadban Date: Thu, 11 Dec 2025 15:34:09 +0200 Subject: [PATCH 14/27] add a check to ensure that the buggy component is rendered as expected --- .../serverRenderRSCReactComponent.rsc.test.tsx | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/packages/react-on-rails-pro/tests/serverRenderRSCReactComponent.rsc.test.tsx b/packages/react-on-rails-pro/tests/serverRenderRSCReactComponent.rsc.test.tsx index 3c148128a9..3b78e165c2 100644 --- a/packages/react-on-rails-pro/tests/serverRenderRSCReactComponent.rsc.test.tsx +++ b/packages/react-on-rails-pro/tests/serverRenderRSCReactComponent.rsc.test.tsx @@ -11,14 +11,17 @@ import { finished } from 'stream/promises'; import { text } from 'stream/consumers'; import ReactOnRails, { RailsContextWithServerStreamingCapabilities } from '../src/ReactOnRailsRSC.ts'; -const PromiseWrapper = async ({ promise, name }: { promise: Promise; name: string }) => { +const PromiseWrapper = async ({ promise, name, onResolved }: { promise: Promise; name: string, onResolved?: () => {} }) => { console.log(`[${name}] Before awaitng`); const value = await promise; + if (onResolved) { + onResolved(); + } console.log(`[${name}] After awaitng`); return

Value: {value}

; }; -const PromiseContainer = ({ name }: { name: string }) => { +const PromiseContainer = ({ name, onResolved }: { name: string, onResolved?: () => {} }) => { const promise = new Promise((resolve) => { let i = 0; setTimeout(() => { @@ -125,6 +128,7 @@ test('no logs lekage from outside the component', async () => { }); test('[bug] catches logs outside the component during reading the stream', async () => { + let resolved = false; const readable1 = ReactOnRails.serverRenderRSCReactComponent({ railsContext: { reactClientManifestFileName: 'react-client-manifest.json', @@ -134,13 +138,16 @@ test('[bug] catches logs outside the component during reading the stream', async renderingReturnsPromises: true, throwJsErrors: true, domNodeId: 'dom-id', - props: { name: 'First Unique Name' }, + props: { name: 'First Unique Name', onResolved: () => { resolved = true; } }, }); let content1 = ''; let i = 0; readable1.on('data', (chunk: Buffer) => { i += 1; + if (i === 1) { + expect(resolved).toBe(false); + } // To avoid infinite loop if (i < 5) { console.log('Outside The Component'); From 5da6c6789a13341542f438c7f4c56cadb95b73c7 Mon Sep 17 00:00:00 2001 From: Abanoub Ghadban Date: Thu, 11 Dec 2025 15:41:50 +0200 Subject: [PATCH 15/27] add a check to ensure that the buggy component is rendered as expected --- .../tests/serverRenderRSCReactComponent.rsc.test.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/react-on-rails-pro/tests/serverRenderRSCReactComponent.rsc.test.tsx b/packages/react-on-rails-pro/tests/serverRenderRSCReactComponent.rsc.test.tsx index 3b78e165c2..389784ddf2 100644 --- a/packages/react-on-rails-pro/tests/serverRenderRSCReactComponent.rsc.test.tsx +++ b/packages/react-on-rails-pro/tests/serverRenderRSCReactComponent.rsc.test.tsx @@ -40,7 +40,7 @@ const PromiseContainer = ({ name, onResolved }: { name: string, onResolved?: ()

Initial Header

Loading Promise

}> - +
); @@ -161,6 +161,7 @@ test('[bug] catches logs outside the component during reading the stream', async }, 2); await finished(readable1); clearInterval(intervalId); + expect(resolved).toBe(true); expect(content1).toContain('First Unique Name'); expect(content1).not.toContain('From Interval'); From 2f6e7ae79c7e4e27a70de2adaf29c43b2c26afd2 Mon Sep 17 00:00:00 2001 From: Abanoub Ghadban Date: Sun, 14 Dec 2025 10:49:32 +0200 Subject: [PATCH 16/27] revert this: run only the failing test on CI --- .github/workflows/package-js-tests.yml | 2 +- packages/react-on-rails-pro/package.json | 1 + .../tests/serverRenderRSCReactComponent.rsc.test.tsx | 9 ++++++--- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/.github/workflows/package-js-tests.yml b/.github/workflows/package-js-tests.yml index ec5406123b..79fc3c0466 100644 --- a/.github/workflows/package-js-tests.yml +++ b/.github/workflows/package-js-tests.yml @@ -129,4 +129,4 @@ jobs: - name: Run JS unit tests for react-on-rails package run: pnpm --filter react-on-rails test - name: Run JS unit tests for react-on-rails-pro package - run: pnpm --filter react-on-rails-pro test + run: ENABLE_JEST_CONSOLE=y NODE_CONDITIONS=react-server pnpm --filter react-on-rails-pro jest tests/serverRenderRSCReactComponent.rsc.test.tsx diff --git a/packages/react-on-rails-pro/package.json b/packages/react-on-rails-pro/package.json index ac68cb9c91..92e26f618c 100644 --- a/packages/react-on-rails-pro/package.json +++ b/packages/react-on-rails-pro/package.json @@ -8,6 +8,7 @@ "build-watch": "pnpm run clean && tsc --watch", "clean": "rm -rf ./lib", "test": "pnpm run test:non-rsc && pnpm run test:rsc", + "jest": "jest", "test:non-rsc": "jest tests --testPathIgnorePatterns=\".*(RSC|stream|registerServerComponent|serverRenderReactComponent|SuspenseHydration).*\"", "test:rsc": "node scripts/check-react-version.cjs || NODE_CONDITIONS=react-server jest tests/*.rsc.test.*", "type-check": "tsc --noEmit --noErrorTruncation", diff --git a/packages/react-on-rails-pro/tests/serverRenderRSCReactComponent.rsc.test.tsx b/packages/react-on-rails-pro/tests/serverRenderRSCReactComponent.rsc.test.tsx index 389784ddf2..095a171da5 100644 --- a/packages/react-on-rails-pro/tests/serverRenderRSCReactComponent.rsc.test.tsx +++ b/packages/react-on-rails-pro/tests/serverRenderRSCReactComponent.rsc.test.tsx @@ -21,13 +21,14 @@ const PromiseWrapper = async ({ promise, name, onResolved }: { promise: Promise< return

Value: {value}

; }; -const PromiseContainer = ({ name, onResolved }: { name: string, onResolved?: () => {} }) => { +const PromiseContainer = ({ name, onResolved, tick }: { name: string, onResolved?: () => {}, tick?: () => {} }) => { const promise = new Promise((resolve) => { let i = 0; setTimeout(() => { const intervalId = setInterval(() => { console.log(`Interval ${i} at [${name}]`); i += 1; + tick?.(); if (i === 50) { clearInterval(intervalId); resolve(`Value of name ${name}`); @@ -127,8 +128,9 @@ test('no logs lekage from outside the component', async () => { expect(content1).not.toContain('Outside The Component'); }); -test('[bug] catches logs outside the component during reading the stream', async () => { +test.only('[bug] catches logs outside the component during reading the stream', async () => { let resolved = false; + let executedIntervals = 0; const readable1 = ReactOnRails.serverRenderRSCReactComponent({ railsContext: { reactClientManifestFileName: 'react-client-manifest.json', @@ -138,7 +140,7 @@ test('[bug] catches logs outside the component during reading the stream', async renderingReturnsPromises: true, throwJsErrors: true, domNodeId: 'dom-id', - props: { name: 'First Unique Name', onResolved: () => { resolved = true; } }, + props: { name: 'First Unique Name', onResolved: () => { resolved = true; }, tick: () => { executedIntervals += 1 } }, }); let content1 = ''; @@ -151,6 +153,7 @@ test('[bug] catches logs outside the component during reading the stream', async // To avoid infinite loop if (i < 5) { console.log('Outside The Component'); + console.log(`Interval Count: ${executedIntervals}`); } content1 += chunk.toString(); }); From a62773723dffe49da67ee26f302067f567f592e1 Mon Sep 17 00:00:00 2001 From: Abanoub Ghadban Date: Sun, 14 Dec 2025 10:56:18 +0200 Subject: [PATCH 17/27] revert this: rerun tests with the pnpm test script --- .github/workflows/package-js-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/package-js-tests.yml b/.github/workflows/package-js-tests.yml index 79fc3c0466..ec5406123b 100644 --- a/.github/workflows/package-js-tests.yml +++ b/.github/workflows/package-js-tests.yml @@ -129,4 +129,4 @@ jobs: - name: Run JS unit tests for react-on-rails package run: pnpm --filter react-on-rails test - name: Run JS unit tests for react-on-rails-pro package - run: ENABLE_JEST_CONSOLE=y NODE_CONDITIONS=react-server pnpm --filter react-on-rails-pro jest tests/serverRenderRSCReactComponent.rsc.test.tsx + run: pnpm --filter react-on-rails-pro test From b62f703020192636e1b4b76d583c82efb3ad4251 Mon Sep 17 00:00:00 2001 From: Abanoub Ghadban Date: Sun, 14 Dec 2025 11:01:11 +0200 Subject: [PATCH 18/27] run all tests at serverRenderRSCReactComponent --- .../tests/serverRenderRSCReactComponent.rsc.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-on-rails-pro/tests/serverRenderRSCReactComponent.rsc.test.tsx b/packages/react-on-rails-pro/tests/serverRenderRSCReactComponent.rsc.test.tsx index 095a171da5..8e76569d2d 100644 --- a/packages/react-on-rails-pro/tests/serverRenderRSCReactComponent.rsc.test.tsx +++ b/packages/react-on-rails-pro/tests/serverRenderRSCReactComponent.rsc.test.tsx @@ -128,7 +128,7 @@ test('no logs lekage from outside the component', async () => { expect(content1).not.toContain('Outside The Component'); }); -test.only('[bug] catches logs outside the component during reading the stream', async () => { +test('[bug] catches logs outside the component during reading the stream', async () => { let resolved = false; let executedIntervals = 0; const readable1 = ReactOnRails.serverRenderRSCReactComponent({ From e8e90218b641d98231766135df85da61f58ec5fd Mon Sep 17 00:00:00 2001 From: Abanoub Ghadban Date: Sun, 14 Dec 2025 11:03:41 +0200 Subject: [PATCH 19/27] ENABLE_JEST_CONSOLE=y for jest tests --- .github/workflows/package-js-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/package-js-tests.yml b/.github/workflows/package-js-tests.yml index ec5406123b..1e4242d823 100644 --- a/.github/workflows/package-js-tests.yml +++ b/.github/workflows/package-js-tests.yml @@ -129,4 +129,4 @@ jobs: - name: Run JS unit tests for react-on-rails package run: pnpm --filter react-on-rails test - name: Run JS unit tests for react-on-rails-pro package - run: pnpm --filter react-on-rails-pro test + run: ENABLE_JEST_CONSOLE=y pnpm --filter react-on-rails-pro test From 527786967e4296bd306bb1e2d925d84e51f74409 Mon Sep 17 00:00:00 2001 From: Abanoub Ghadban Date: Sun, 14 Dec 2025 11:15:48 +0200 Subject: [PATCH 20/27] bug investigation changes --- .../tests/serverRenderRSCReactComponent.rsc.test.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/react-on-rails-pro/tests/serverRenderRSCReactComponent.rsc.test.tsx b/packages/react-on-rails-pro/tests/serverRenderRSCReactComponent.rsc.test.tsx index 8e76569d2d..1ed1999d57 100644 --- a/packages/react-on-rails-pro/tests/serverRenderRSCReactComponent.rsc.test.tsx +++ b/packages/react-on-rails-pro/tests/serverRenderRSCReactComponent.rsc.test.tsx @@ -34,7 +34,7 @@ const PromiseContainer = ({ name, onResolved, tick }: { name: string, onResolved resolve(`Value of name ${name}`); } }, 20); - }, 200); + }, 0); }); return ( @@ -140,7 +140,7 @@ test('[bug] catches logs outside the component during reading the stream', async renderingReturnsPromises: true, throwJsErrors: true, domNodeId: 'dom-id', - props: { name: 'First Unique Name', onResolved: () => { resolved = true; }, tick: () => { executedIntervals += 1 } }, + props: { name: 'Bug Investigation Name', onResolved: () => { resolved = true; }, tick: () => { executedIntervals += 1 } }, }); let content1 = ''; @@ -166,7 +166,7 @@ test('[bug] catches logs outside the component during reading the stream', async clearInterval(intervalId); expect(resolved).toBe(true); - expect(content1).toContain('First Unique Name'); + expect(content1).toContain('Bug Investigation Name'); expect(content1).not.toContain('From Interval'); // Here's the bug expect(content1).toContain('Outside The Component'); From 5c3c00188aec02efcd53c1f9c304c30ba25fab6f Mon Sep 17 00:00:00 2001 From: Abanoub Ghadban Date: Sun, 14 Dec 2025 11:22:20 +0200 Subject: [PATCH 21/27] trick --- .../tests/serverRenderRSCReactComponent.rsc.test.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/react-on-rails-pro/tests/serverRenderRSCReactComponent.rsc.test.tsx b/packages/react-on-rails-pro/tests/serverRenderRSCReactComponent.rsc.test.tsx index 1ed1999d57..b8fc3d226c 100644 --- a/packages/react-on-rails-pro/tests/serverRenderRSCReactComponent.rsc.test.tsx +++ b/packages/react-on-rails-pro/tests/serverRenderRSCReactComponent.rsc.test.tsx @@ -145,13 +145,15 @@ test('[bug] catches logs outside the component during reading the stream', async let content1 = ''; let i = 0; + let j = 0; readable1.on('data', (chunk: Buffer) => { i += 1; if (i === 1) { expect(resolved).toBe(false); } // To avoid infinite loop - if (i < 5) { + if (executedIntervals > 0 && j < 5) { + j += 1; console.log('Outside The Component'); console.log(`Interval Count: ${executedIntervals}`); } From f21bc5b527060b1c98911e74ff236bfae650ce26 Mon Sep 17 00:00:00 2001 From: Abanoub Ghadban Date: Sun, 14 Dec 2025 11:47:35 +0200 Subject: [PATCH 22/27] Revert "trick" This reverts commit 6275fb401762645ded45afbdbf11c1a6a2bf541b. --- .../tests/serverRenderRSCReactComponent.rsc.test.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/react-on-rails-pro/tests/serverRenderRSCReactComponent.rsc.test.tsx b/packages/react-on-rails-pro/tests/serverRenderRSCReactComponent.rsc.test.tsx index b8fc3d226c..1ed1999d57 100644 --- a/packages/react-on-rails-pro/tests/serverRenderRSCReactComponent.rsc.test.tsx +++ b/packages/react-on-rails-pro/tests/serverRenderRSCReactComponent.rsc.test.tsx @@ -145,15 +145,13 @@ test('[bug] catches logs outside the component during reading the stream', async let content1 = ''; let i = 0; - let j = 0; readable1.on('data', (chunk: Buffer) => { i += 1; if (i === 1) { expect(resolved).toBe(false); } // To avoid infinite loop - if (executedIntervals > 0 && j < 5) { - j += 1; + if (i < 5) { console.log('Outside The Component'); console.log(`Interval Count: ${executedIntervals}`); } From 1fafcfbf8e9f36632bd9b40a398211805836346c Mon Sep 17 00:00:00 2001 From: Abanoub Ghadban Date: Sun, 14 Dec 2025 12:06:05 +0200 Subject: [PATCH 23/27] revert all changes to reproduce the console leakage bug on CI --- .github/workflows/package-js-tests.yml | 2 +- packages/react-on-rails-pro/package.json | 1 - ...serverRenderRSCReactComponent.rsc.test.tsx | 41 +++++++------------ 3 files changed, 15 insertions(+), 29 deletions(-) diff --git a/.github/workflows/package-js-tests.yml b/.github/workflows/package-js-tests.yml index 1e4242d823..ec5406123b 100644 --- a/.github/workflows/package-js-tests.yml +++ b/.github/workflows/package-js-tests.yml @@ -129,4 +129,4 @@ jobs: - name: Run JS unit tests for react-on-rails package run: pnpm --filter react-on-rails test - name: Run JS unit tests for react-on-rails-pro package - run: ENABLE_JEST_CONSOLE=y pnpm --filter react-on-rails-pro test + run: pnpm --filter react-on-rails-pro test diff --git a/packages/react-on-rails-pro/package.json b/packages/react-on-rails-pro/package.json index 92e26f618c..ac68cb9c91 100644 --- a/packages/react-on-rails-pro/package.json +++ b/packages/react-on-rails-pro/package.json @@ -8,7 +8,6 @@ "build-watch": "pnpm run clean && tsc --watch", "clean": "rm -rf ./lib", "test": "pnpm run test:non-rsc && pnpm run test:rsc", - "jest": "jest", "test:non-rsc": "jest tests --testPathIgnorePatterns=\".*(RSC|stream|registerServerComponent|serverRenderReactComponent|SuspenseHydration).*\"", "test:rsc": "node scripts/check-react-version.cjs || NODE_CONDITIONS=react-server jest tests/*.rsc.test.*", "type-check": "tsc --noEmit --noErrorTruncation", diff --git a/packages/react-on-rails-pro/tests/serverRenderRSCReactComponent.rsc.test.tsx b/packages/react-on-rails-pro/tests/serverRenderRSCReactComponent.rsc.test.tsx index 1ed1999d57..4053f8280e 100644 --- a/packages/react-on-rails-pro/tests/serverRenderRSCReactComponent.rsc.test.tsx +++ b/packages/react-on-rails-pro/tests/serverRenderRSCReactComponent.rsc.test.tsx @@ -11,37 +11,31 @@ import { finished } from 'stream/promises'; import { text } from 'stream/consumers'; import ReactOnRails, { RailsContextWithServerStreamingCapabilities } from '../src/ReactOnRailsRSC.ts'; -const PromiseWrapper = async ({ promise, name, onResolved }: { promise: Promise; name: string, onResolved?: () => {} }) => { +const PromiseWrapper = async ({ promise, name }: { promise: Promise; name: string }) => { console.log(`[${name}] Before awaitng`); const value = await promise; - if (onResolved) { - onResolved(); - } console.log(`[${name}] After awaitng`); return

Value: {value}

; }; -const PromiseContainer = ({ name, onResolved, tick }: { name: string, onResolved?: () => {}, tick?: () => {} }) => { +const PromiseContainer = ({ name }: { name: string }) => { const promise = new Promise((resolve) => { let i = 0; - setTimeout(() => { - const intervalId = setInterval(() => { - console.log(`Interval ${i} at [${name}]`); - i += 1; - tick?.(); - if (i === 50) { - clearInterval(intervalId); - resolve(`Value of name ${name}`); - } - }, 20); - }, 0); + const intervalId = setInterval(() => { + console.log(`Interval ${i} at [${name}]`); + i += 1; + if (i === 50) { + clearInterval(intervalId); + resolve(`Value of name ${name}`); + } + }, 1); }); return (

Initial Header

Loading Promise

}> - +
); @@ -129,8 +123,6 @@ test('no logs lekage from outside the component', async () => { }); test('[bug] catches logs outside the component during reading the stream', async () => { - let resolved = false; - let executedIntervals = 0; const readable1 = ReactOnRails.serverRenderRSCReactComponent({ railsContext: { reactClientManifestFileName: 'react-client-manifest.json', @@ -140,20 +132,16 @@ test('[bug] catches logs outside the component during reading the stream', async renderingReturnsPromises: true, throwJsErrors: true, domNodeId: 'dom-id', - props: { name: 'Bug Investigation Name', onResolved: () => { resolved = true; }, tick: () => { executedIntervals += 1 } }, + props: { name: 'First Unique Name' }, }); let content1 = ''; let i = 0; readable1.on('data', (chunk: Buffer) => { i += 1; - if (i === 1) { - expect(resolved).toBe(false); - } // To avoid infinite loop if (i < 5) { console.log('Outside The Component'); - console.log(`Interval Count: ${executedIntervals}`); } content1 += chunk.toString(); }); @@ -164,10 +152,9 @@ test('[bug] catches logs outside the component during reading the stream', async }, 2); await finished(readable1); clearInterval(intervalId); - expect(resolved).toBe(true); - expect(content1).toContain('Bug Investigation Name'); + expect(content1).toContain('First Unique Name'); expect(content1).not.toContain('From Interval'); // Here's the bug expect(content1).toContain('Outside The Component'); -}, 10000); +}); From 9646924eaf613c70f29a18afcdd2b22d718a6223 Mon Sep 17 00:00:00 2001 From: Abanoub Ghadban Date: Sun, 14 Dec 2025 12:07:03 +0200 Subject: [PATCH 24/27] increase number of logged messages outside the component --- .../tests/serverRenderRSCReactComponent.rsc.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-on-rails-pro/tests/serverRenderRSCReactComponent.rsc.test.tsx b/packages/react-on-rails-pro/tests/serverRenderRSCReactComponent.rsc.test.tsx index 4053f8280e..7d61388dc9 100644 --- a/packages/react-on-rails-pro/tests/serverRenderRSCReactComponent.rsc.test.tsx +++ b/packages/react-on-rails-pro/tests/serverRenderRSCReactComponent.rsc.test.tsx @@ -140,7 +140,7 @@ test('[bug] catches logs outside the component during reading the stream', async readable1.on('data', (chunk: Buffer) => { i += 1; // To avoid infinite loop - if (i < 5) { + if (i < 10) { console.log('Outside The Component'); } content1 += chunk.toString(); From 4c28c2633ffc1654e232187141743e5441399ed1 Mon Sep 17 00:00:00 2001 From: Abanoub Ghadban Date: Sun, 14 Dec 2025 12:32:53 +0200 Subject: [PATCH 25/27] make async quque waits for all chunks to be received --- .../react-on-rails-pro/tests/AsyncQueue.ts | 83 +++++++++++-------- .../react-on-rails-pro/tests/StreamReader.ts | 2 +- ...oncurrentRSCPayloadGeneration.rsc.test.tsx | 24 +----- ...treamServerRenderedReactComponent.test.jsx | 2 +- .../tests/utils/removeRSCChunkStack.ts | 19 +++-- 5 files changed, 69 insertions(+), 61 deletions(-) diff --git a/packages/react-on-rails-pro/tests/AsyncQueue.ts b/packages/react-on-rails-pro/tests/AsyncQueue.ts index 0cf6c843b8..10de9cf101 100644 --- a/packages/react-on-rails-pro/tests/AsyncQueue.ts +++ b/packages/react-on-rails-pro/tests/AsyncQueue.ts @@ -1,22 +1,32 @@ import * as EventEmitter from 'node:events'; -class AsyncQueue { - private eventEmitter = new EventEmitter(); - - private buffer: T[] = []; +const debounce = ( + callback: (...args: T) => void, + delay: number, +) => { + let timeoutTimer: ReturnType; + + return (...args: T) => { + clearTimeout(timeoutTimer); + + timeoutTimer = setTimeout(() => { + callback(...args); + }, delay); + }; +}; +class AsyncQueue { + private eventEmitter = new EventEmitter<{ data: any, end: any }>(); + private buffer: string = ''; private isEnded = false; - enqueue(value: T) { + enqueue(value: string) { if (this.isEnded) { - throw new Error('Queue Ended'); + throw new Error("Queue Ended"); } - if (this.eventEmitter.listenerCount('data') > 0) { - this.eventEmitter.emit('data', value); - } else { - this.buffer.push(value); - } + this.buffer += value; + this.eventEmitter.emit('data', value); } end() { @@ -25,33 +35,38 @@ class AsyncQueue { } dequeue() { - return new Promise((resolve, reject) => { - const bufferValueIfExist = this.buffer.length > 0 ? this.buffer.join('') : undefined; - this.buffer.length = 0; - if (bufferValueIfExist) { - resolve(bufferValueIfExist as T); - } else if (this.isEnded) { - reject(new Error('Queue Ended')); - } else { - let teardown = () => {}; - const onData = (value: T) => { - resolve(value); - teardown(); - }; + return new Promise((resolve, reject) => { + if (this.isEnded) { + reject(new Error("Queue Ended")); + return; + } - const onEnd = () => { + const checkBuffer = debounce(() => { + const teardown = () => { + this.eventEmitter.off('data', checkBuffer); + this.eventEmitter.off('end', checkBuffer); + } + + if (this.buffer.length > 0) { + resolve(this.buffer); + this.buffer = ''; + teardown(); + } else if (this.isEnded) { reject(new Error('Queue Ended')); teardown(); - }; - - this.eventEmitter.on('data', onData); - this.eventEmitter.on('end', onEnd); - teardown = () => { - this.eventEmitter.off('data', onData); - this.eventEmitter.off('end', onEnd); - }; + } + }, 250); + + if (this.buffer.length > 0) { + checkBuffer(); } - }); + this.eventEmitter.on('data', checkBuffer); + this.eventEmitter.on('end', checkBuffer); + }) + } + + toString() { + return "" } } diff --git a/packages/react-on-rails-pro/tests/StreamReader.ts b/packages/react-on-rails-pro/tests/StreamReader.ts index 86ee5195b4..f42ebf867b 100644 --- a/packages/react-on-rails-pro/tests/StreamReader.ts +++ b/packages/react-on-rails-pro/tests/StreamReader.ts @@ -2,7 +2,7 @@ import { PassThrough, Readable } from 'node:stream'; import AsyncQueue from './AsyncQueue.ts'; class StreamReader { - private asyncQueue: AsyncQueue; + private asyncQueue: AsyncQueue; constructor(pipeableStream: Pick) { this.asyncQueue = new AsyncQueue(); diff --git a/packages/react-on-rails-pro/tests/concurrentRSCPayloadGeneration.rsc.test.tsx b/packages/react-on-rails-pro/tests/concurrentRSCPayloadGeneration.rsc.test.tsx index 9f6a8b254d..d80c9c3b72 100644 --- a/packages/react-on-rails-pro/tests/concurrentRSCPayloadGeneration.rsc.test.tsx +++ b/packages/react-on-rails-pro/tests/concurrentRSCPayloadGeneration.rsc.test.tsx @@ -31,7 +31,7 @@ afterEach(() => mock.restore()); const AsyncQueueItem = async ({ asyncQueue, children, -}: PropsWithChildren<{ asyncQueue: AsyncQueue }>) => { +}: PropsWithChildren<{ asyncQueue: AsyncQueue }>) => { const value = await asyncQueue.dequeue(); return ( @@ -42,7 +42,7 @@ const AsyncQueueItem = async ({ ); }; -const AsyncQueueContainer = ({ asyncQueue }: { asyncQueue: AsyncQueue }) => { +const AsyncQueueContainer = ({ asyncQueue }: { asyncQueue: AsyncQueue }) => { return (

Async Queue

@@ -78,7 +78,7 @@ const renderComponent = (props: Record) => { }; const createParallelRenders = (size: number) => { - const asyncQueues = new Array(size).fill(null).map(() => new AsyncQueue()); + const asyncQueues = new Array(size).fill(null).map(() => new AsyncQueue()); const streams = asyncQueues.map((asyncQueue) => { return renderComponent({ asyncQueue }); }); @@ -100,21 +100,13 @@ const createParallelRenders = (size: number) => { return { enqueue, expectNextChunk, expectEndOfStream }; }; -const delay = (ms: number) => - new Promise((resolve) => { - setTimeout(() => { - resolve(); - }, ms); - }); - test('Renders concurrent rsc streams as single rsc stream', async () => { // expect.assertions(258); - const asyncQueue = new AsyncQueue(); + const asyncQueue = new AsyncQueue(); const stream = renderComponent({ asyncQueue }); const reader = new StreamReader(stream); const chunks: string[] = []; - await delay(100); let chunk = await reader.nextChunk(); chunks.push(chunk); expect(chunk).toContain('Async Queue'); @@ -123,19 +115,16 @@ test('Renders concurrent rsc streams as single rsc stream', async () => { asyncQueue.enqueue('Random Value1'); - await delay(100); chunk = await reader.nextChunk(); chunks.push(chunk); expect(chunk).toContain('Random Value1'); asyncQueue.enqueue('Random Value2'); - await delay(100); chunk = await reader.nextChunk(); chunks.push(chunk); expect(chunk).toContain('Random Value2'); asyncQueue.enqueue('Random Value3'); - await delay(100); chunk = await reader.nextChunk(); chunks.push(chunk); expect(chunk).toContain('Random Value3'); @@ -145,17 +134,12 @@ test('Renders concurrent rsc streams as single rsc stream', async () => { const { enqueue, expectNextChunk, expectEndOfStream } = createParallelRenders(50); expect(chunks).toHaveLength(4); - await delay(100); await expectNextChunk(chunks[0]); enqueue('Random Value1'); - await delay(100); await expectNextChunk(chunks[1]); enqueue('Random Value2'); - await delay(100); await expectNextChunk(chunks[2]); enqueue('Random Value3'); - await delay(100); await expectNextChunk(chunks[3]); - await delay(100); await expectEndOfStream(); }); diff --git a/packages/react-on-rails-pro/tests/streamServerRenderedReactComponent.test.jsx b/packages/react-on-rails-pro/tests/streamServerRenderedReactComponent.test.jsx index 7fb0421869..df1e4d6379 100644 --- a/packages/react-on-rails-pro/tests/streamServerRenderedReactComponent.test.jsx +++ b/packages/react-on-rails-pro/tests/streamServerRenderedReactComponent.test.jsx @@ -193,7 +193,7 @@ describe('streamServerRenderedReactComponent', () => { // One of the chunks should have a hasErrors property of true expect(chunks[0].hasErrors || chunks[1].hasErrors).toBe(true); expect(chunks[0].hasErrors && chunks[1].hasErrors).toBe(false); - }, 100000); + }, 10000); it("doesn't emit an error if there is an error in the async content and throwJsErrors is false", async () => { const { renderResult, chunks } = setupStreamTest({ throwAsyncError: true, throwJsErrors: false }); diff --git a/packages/react-on-rails-pro/tests/utils/removeRSCChunkStack.ts b/packages/react-on-rails-pro/tests/utils/removeRSCChunkStack.ts index 57795540a6..cb0f28c971 100644 --- a/packages/react-on-rails-pro/tests/utils/removeRSCChunkStack.ts +++ b/packages/react-on-rails-pro/tests/utils/removeRSCChunkStack.ts @@ -13,19 +13,28 @@ const removeRSCChunkStackInternal = (chunk: string) => { } const { html } = parsedJson; const santizedHtml = html.split('\n').map((chunkLine) => { - if (!chunkLine.includes('"stack":')) { + if (/^[0-9a-fA-F]+\:D/.exec(chunkLine) || chunkLine.startsWith(':N')) { + return ''; + } + if (!(chunkLine.includes('"stack":') || chunkLine.includes('"start":') || chunkLine.includes('"end":'))) { return chunkLine; } - const regexMatch = /(^\d+):\{/.exec(chunkLine); + const regexMatch = /([^\{]+)\{/.exec(chunkLine) if (!regexMatch) { return chunkLine; } const chunkJsonString = chunkLine.slice(chunkLine.indexOf('{')); - const chunkJson = JSON.parse(chunkJsonString) as { stack?: string }; - delete chunkJson.stack; - return `${regexMatch[1]}:${JSON.stringify(chunkJson)}`; + try { + const chunkJson = JSON.parse(chunkJsonString); + delete chunkJson.stack; + delete chunkJson.start; + delete chunkJson.end; + return `${regexMatch[1]}${JSON.stringify(chunkJson)}` + } catch { + return chunkLine + } }); return JSON.stringify({ From 7831d5a9d91d11079b8a177b41a0231737b4a7f7 Mon Sep 17 00:00:00 2001 From: Abanoub Ghadban Date: Sun, 14 Dec 2025 12:42:59 +0200 Subject: [PATCH 26/27] linting --- .../react-on-rails-pro/tests/AsyncQueue.ts | 23 ++++++++----------- ...oncurrentRSCPayloadGeneration.rsc.test.tsx | 5 +--- .../tests/utils/removeRSCChunkStack.ts | 6 ++--- 3 files changed, 14 insertions(+), 20 deletions(-) diff --git a/packages/react-on-rails-pro/tests/AsyncQueue.ts b/packages/react-on-rails-pro/tests/AsyncQueue.ts index 10de9cf101..7222560db8 100644 --- a/packages/react-on-rails-pro/tests/AsyncQueue.ts +++ b/packages/react-on-rails-pro/tests/AsyncQueue.ts @@ -1,14 +1,11 @@ import * as EventEmitter from 'node:events'; -const debounce = ( - callback: (...args: T) => void, - delay: number, -) => { +const debounce = (callback: (...args: T) => void, delay: number) => { let timeoutTimer: ReturnType; - + return (...args: T) => { clearTimeout(timeoutTimer); - + timeoutTimer = setTimeout(() => { callback(...args); }, delay); @@ -16,13 +13,13 @@ const debounce = ( }; class AsyncQueue { - private eventEmitter = new EventEmitter<{ data: any, end: any }>(); + private eventEmitter = new EventEmitter<{ data: any; end: any }>(); private buffer: string = ''; private isEnded = false; enqueue(value: string) { if (this.isEnded) { - throw new Error("Queue Ended"); + throw new Error('Queue Ended'); } this.buffer += value; @@ -37,7 +34,7 @@ class AsyncQueue { dequeue() { return new Promise((resolve, reject) => { if (this.isEnded) { - reject(new Error("Queue Ended")); + reject(new Error('Queue Ended')); return; } @@ -45,8 +42,8 @@ class AsyncQueue { const teardown = () => { this.eventEmitter.off('data', checkBuffer); this.eventEmitter.off('end', checkBuffer); - } - + }; + if (this.buffer.length > 0) { resolve(this.buffer); this.buffer = ''; @@ -62,11 +59,11 @@ class AsyncQueue { } this.eventEmitter.on('data', checkBuffer); this.eventEmitter.on('end', checkBuffer); - }) + }); } toString() { - return "" + return ''; } } diff --git a/packages/react-on-rails-pro/tests/concurrentRSCPayloadGeneration.rsc.test.tsx b/packages/react-on-rails-pro/tests/concurrentRSCPayloadGeneration.rsc.test.tsx index d80c9c3b72..5001c4059a 100644 --- a/packages/react-on-rails-pro/tests/concurrentRSCPayloadGeneration.rsc.test.tsx +++ b/packages/react-on-rails-pro/tests/concurrentRSCPayloadGeneration.rsc.test.tsx @@ -28,10 +28,7 @@ beforeEach(() => { afterEach(() => mock.restore()); -const AsyncQueueItem = async ({ - asyncQueue, - children, -}: PropsWithChildren<{ asyncQueue: AsyncQueue }>) => { +const AsyncQueueItem = async ({ asyncQueue, children }: PropsWithChildren<{ asyncQueue: AsyncQueue }>) => { const value = await asyncQueue.dequeue(); return ( diff --git a/packages/react-on-rails-pro/tests/utils/removeRSCChunkStack.ts b/packages/react-on-rails-pro/tests/utils/removeRSCChunkStack.ts index cb0f28c971..4f5f88bb1d 100644 --- a/packages/react-on-rails-pro/tests/utils/removeRSCChunkStack.ts +++ b/packages/react-on-rails-pro/tests/utils/removeRSCChunkStack.ts @@ -20,7 +20,7 @@ const removeRSCChunkStackInternal = (chunk: string) => { return chunkLine; } - const regexMatch = /([^\{]+)\{/.exec(chunkLine) + const regexMatch = /([^\{]+)\{/.exec(chunkLine); if (!regexMatch) { return chunkLine; } @@ -31,9 +31,9 @@ const removeRSCChunkStackInternal = (chunk: string) => { delete chunkJson.stack; delete chunkJson.start; delete chunkJson.end; - return `${regexMatch[1]}${JSON.stringify(chunkJson)}` + return `${regexMatch[1]}${JSON.stringify(chunkJson)}`; } catch { - return chunkLine + return chunkLine; } }); From 00746f3a6147626302fc0a93837203b2a328e436 Mon Sep 17 00:00:00 2001 From: Abanoub Ghadban Date: Thu, 18 Dec 2025 14:06:05 +0200 Subject: [PATCH 27/27] uncomment the assertions line --- .../tests/concurrentRSCPayloadGeneration.rsc.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-on-rails-pro/tests/concurrentRSCPayloadGeneration.rsc.test.tsx b/packages/react-on-rails-pro/tests/concurrentRSCPayloadGeneration.rsc.test.tsx index 5001c4059a..e7bf934a36 100644 --- a/packages/react-on-rails-pro/tests/concurrentRSCPayloadGeneration.rsc.test.tsx +++ b/packages/react-on-rails-pro/tests/concurrentRSCPayloadGeneration.rsc.test.tsx @@ -98,7 +98,7 @@ const createParallelRenders = (size: number) => { }; test('Renders concurrent rsc streams as single rsc stream', async () => { - // expect.assertions(258); + expect.assertions(258); const asyncQueue = new AsyncQueue(); const stream = renderComponent({ asyncQueue }); const reader = new StreamReader(stream);