Skip to content

Commit

Permalink
make RSCClientRoot tests compatible with React 19
Browse files Browse the repository at this point in the history
  • Loading branch information
AbanoubGhadban committed Feb 20, 2025
1 parent ff7fbfa commit dc0c0c3
Show file tree
Hide file tree
Showing 8 changed files with 64 additions and 70 deletions.
6 changes: 1 addition & 5 deletions .github/workflows/package-js-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.versions == 'oldest' && '18' || '20' }}
node-version: ${{ matrix.versions == 'oldest' && '16' || '20' }}
- name: Print system information
run: |
echo "Linux release: "; cat /etc/issue
Expand All @@ -43,7 +43,3 @@ jobs:
sudo yarn global add yalc
- name: Run JS unit tests for Renderer package
run: yarn test
# TODO: Remove this once we made these tests compatible with React 19
- name: Run JS unit tests for Renderer package with React 18 (for tests not compatible with React 19)
if: matrix.versions == 'newest'
run: yarn test:react-18
16 changes: 7 additions & 9 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,16 @@
const nodeVersion = parseInt(process.version.slice(1), 10);

module.exports = {
preset: 'ts-jest/presets/js-with-ts',
testEnvironment: 'jsdom',
setupFiles: ['<rootDir>/node_package/tests/jest.setup.js'],
// TODO: Remove this once we made RSCClientRoot compatible with React 19
moduleNameMapper: process.env.USE_REACT_18
// React Server Components tests are not compatible with Experimental React 18 and React 19
// That only run with node version 18 and above
moduleNameMapper: nodeVersion < 18
? {
'^react$': '<rootDir>/node_modules/react-18',
'^react/(.*)$': '<rootDir>/node_modules/react-18/$1',
'^react-dom$': '<rootDir>/node_modules/react-dom-18',
'^react-dom/(.*)$': '<rootDir>/node_modules/react-dom-18/$1',
}
: {
'react-server-dom-webpack/client': '<rootDir>/node_package/tests/emptyForTesting.js',
'^@testing-library/dom$': '<rootDir>/node_package/tests/emptyForTesting.js',
'^@testing-library/react$': '<rootDir>/node_package/tests/emptyForTesting.js',
},
}
: {},
};
2 changes: 0 additions & 2 deletions knip.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,6 @@ const config: KnipConfig = {
'react-server-dom-webpack',
'cross-fetch',
'jsdom',
'react-18',
'react-dom-18',
],
},
'spec/dummy': {
Expand Down
69 changes: 45 additions & 24 deletions node_package/tests/RSCClientRoot.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ window.__webpack_chunk_load__ = jest.fn();

import * as React from 'react';
import { enableFetchMocks } from 'jest-fetch-mock';
import { render, waitFor, screen } from '@testing-library/react';
import { render, screen, act } from '@testing-library/react';
import '@testing-library/jest-dom';
import path from 'path';
import fs from 'fs';
Expand All @@ -20,8 +20,11 @@ import RSCClientRoot, { resetRenderCache } from '../src/RSCClientRoot';

enableFetchMocks();

// TODO: Remove this once we made these tests compatible with React 19
(process.env.USE_REACT_18 ? describe : describe.skip)('RSCClientRoot', () => {
const nodeVersion = parseInt(process.version.slice(1), 10);

// React Server Components tests are not compatible with Experimental React 18 and React 19
// That only run with node version 18 and above
(nodeVersion >= 18 ? describe : describe.skip)('RSCClientRoot', () => {
beforeEach(() => {
jest.clearAllMocks();

Expand All @@ -41,7 +44,7 @@ enableFetchMocks();
}).toThrow('React.use is not defined');
});

const mockRSCRequest = (rscPayloadGenerationUrlPath = 'rsc-render') => {
const mockRSCRequest = async (rscPayloadGenerationUrlPath = 'rsc-render') => {
const chunksDirectory = path.join(
__dirname,
'fixtures',
Expand All @@ -59,7 +62,7 @@ enableFetchMocks();
rscPayloadGenerationUrlPath,
};

const { rerender } = render(<RSCClientRoot {...props} />);
const { rerender } = await act(async () => render(<RSCClientRoot {...props} />));

return {
rerender: () => rerender(<RSCClientRoot {...props} />),
Expand All @@ -71,20 +74,24 @@ enableFetchMocks();
};

it('fetches and caches component data', async () => {
const { rerender, pushFirstChunk, pushSecondChunk, endStream } = mockRSCRequest();
const { rerender, pushFirstChunk, pushSecondChunk, endStream } = await mockRSCRequest();

expect(window.fetch).toHaveBeenCalledWith('/rsc-render/TestComponent');
expect(window.fetch).toHaveBeenCalledTimes(1);
expect(screen.queryByText('StaticServerComponent')).not.toBeInTheDocument();

pushFirstChunk();
await waitFor(() => expect(screen.getByText('StaticServerComponent')).toBeInTheDocument());
await act(async () => {
pushFirstChunk();
});
expect(screen.getByText('StaticServerComponent')).toBeInTheDocument();
expect(screen.getByText('Loading AsyncComponent...')).toBeInTheDocument();
expect(screen.queryByText('AsyncComponent')).not.toBeInTheDocument();

pushSecondChunk();
endStream();
await waitFor(() => expect(screen.getByText('AsyncComponent')).toBeInTheDocument());
await act(async () => {
pushSecondChunk();
endStream();
});
expect(screen.getByText('AsyncComponent')).toBeInTheDocument();
expect(screen.queryByText('Loading AsyncComponent...')).not.toBeInTheDocument();

// Second render - should use cache
Expand All @@ -96,15 +103,27 @@ enableFetchMocks();

it('replays console logs', async () => {
const consoleSpy = jest.spyOn(console, 'log');
const { rerender, pushFirstChunk, pushSecondChunk, endStream } = mockRSCRequest();

pushFirstChunk();
await waitFor(() => expect(consoleSpy).toHaveBeenCalledWith('[SERVER] Console log at first chunk'));
const { rerender, pushFirstChunk, pushSecondChunk, endStream } = await mockRSCRequest();

await act(async () => {
pushFirstChunk();
});
expect(consoleSpy).toHaveBeenCalledWith(
expect.stringContaining('Console log at first chunk'),
expect.anything(), expect.anything(), expect.anything()
);
expect(consoleSpy).toHaveBeenCalledTimes(1);

pushSecondChunk();
await waitFor(() => expect(consoleSpy).toHaveBeenCalledWith('[SERVER] Console log at second chunk'));
endStream();
await act(async () => {
pushSecondChunk();
});
expect(consoleSpy).toHaveBeenCalledWith(
expect.stringContaining('Console log at second chunk'),
expect.anything(), expect.anything(), expect.anything()
);
await act(async () => {
endStream();
});
expect(consoleSpy).toHaveBeenCalledTimes(2);

// On rerender, console logs should not be replayed again
Expand All @@ -113,15 +132,17 @@ enableFetchMocks();
});

it('strips leading and trailing slashes from rscPayloadGenerationUrlPath', async () => {
const { pushFirstChunk, pushSecondChunk, endStream } = mockRSCRequest('/rsc-render/');
const { pushFirstChunk, pushSecondChunk, endStream } = await mockRSCRequest('/rsc-render/');

pushFirstChunk();
pushSecondChunk();
endStream();
await act(async () => {
pushFirstChunk();
pushSecondChunk();
endStream();
});

await waitFor(() => expect(window.fetch).toHaveBeenCalledWith('/rsc-render/TestComponent'));
expect(window.fetch).toHaveBeenCalledWith('/rsc-render/TestComponent');
expect(window.fetch).toHaveBeenCalledTimes(1);

await waitFor(() => expect(screen.getByText('StaticServerComponent')).toBeInTheDocument());
expect(screen.getByText('StaticServerComponent')).toBeInTheDocument();
});
});
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"html": "1:\"$Sreact.suspense\"\n0:D{\"name\":\"StaticServerComponent\",\"env\":\"Server\"}\n2:D{\"name\":\"AsyncComponent\",\"env\":\"Server\"}\n0:[\"$\",\"div\",null,{\"children\":[[\"$\",\"h1\",null,{\"children\":\"StaticServerComponent\"}],[\"$\",\"p\",null,{\"children\":\"This is a static server component\"}],[\"$\",\"$1\",null,{\"fallback\":[\"$\",\"div\",null,{\"children\":\"Loading AsyncComponent...\"}],\"children\":\"$L2\"}]]}]\n",
"consoleReplayScript": "\n\u003cscript id=\"consoleReplayLog\"\u003e\nconsole.log.apply(console, [\"[SERVER] Console log at first chunk\"]);\n\u003c/script\u003e",
"html": "3:\"$Sreact.suspense\"\n1:{\"name\":\"SimpleComponentForTesting\",\"env\":\"Server\",\"key\":null,\"owner\":null,\"props\":{\"helloWorldData\":{\"name\":\"Mr. Server Side Rendering\",\"\u003cscript\u003ewindow.alert('xss1');\u003c/script\u003e\":\"\u003cscript\u003ewindow.alert(\\\"xss2\\\");\u003c/script\u003e\"}}}\n0:D\"$1\"\n2:W[\"log\",[[\"SimpleComponentForTesting\",\"webpack://react_on_rails_pro_dummy/./client/app/ror-auto-load-components/SimpleComponentForTesting.jsx?\",35,11]],\"$1\",\"Server\",\"Console log at first chunk\"]\n5:{\"name\":\"AsyncComponent\",\"env\":\"Server\",\"key\":null,\"owner\":\"$1\",\"props\":{}}\n4:D\"$5\"\n0:[\"$\",\"div\",null,{\"children\":[[\"$\",\"h1\",null,{\"children\":\"StaticServerComponent\"},\"$1\"],[\"$\",\"p\",null,{\"children\":\"This is a static server component\"},\"$1\"],[\"$\",\"$3\",null,{\"fallback\":[\"$\",\"div\",null,{\"children\":\"Loading AsyncComponent...\"},\"$1\"],\"children\":\"$L4\"},\"$1\"]]},\"$1\"]\n",
"consoleReplayScript": "",
"hasErrors": false,
"isShellReady": true
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"html": "2:[\"$\",\"div\",null,{\"children\":\"AsyncComponent\"}]\n",
"consoleReplayScript": "\n\u003cscript id=\"consoleReplayLog\"\u003e\nconsole.log.apply(console, [\"[SERVER] Console log at second chunk\"]);\n\u003c/script\u003e",
"html": "6:W[\"log\",[[\"AsyncComponent\",\"webpack://react_on_rails_pro_dummy/./client/app/ror-auto-load-components/SimpleComponentForTesting.jsx?\",24,11]],\"$5\",\"Server\",\"Console log at second chunk\"]\n4:[\"$\",\"div\",null,{\"children\":\"AsyncComponent\"},\"$5\"]\n",
"consoleReplayScript": "",
"hasErrors": false,
"isShellReady": true
}
5 changes: 1 addition & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,8 @@
"prettier": "^2.8.8",
"prop-types": "^15.8.1",
"react": "^19.0.0",
"react-18": "npm:[email protected]",
"react-dom": "^19.0.0",
"react-dom-18": "npm:[email protected]",
"react-server-dom-webpack": "18.3.0-canary-670811593-20240322",
"react-server-dom-webpack": "19.0.0",
"redux": "^4.2.1",
"ts-jest": "^29.2.5",
"typescript": "^5.6.2",
Expand All @@ -66,7 +64,6 @@
],
"scripts": {
"test": "jest node_package/tests",
"test:react-18": "USE_REACT_18=true jest node_package/tests/RSCClientRoot.test.jsx",
"clean": "rm -rf node_package/lib",
"start": "nps",
"prepack": "nps build.prepack",
Expand Down
28 changes: 6 additions & 22 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -5815,18 +5815,6 @@ randombytes@^2.1.0:
dependencies:
safe-buffer "^5.1.0"

"react-18@npm:[email protected]":
version "18.3.0-canary-670811593-20240322"
resolved "https://registry.yarnpkg.com/react/-/react-18.3.0-canary-670811593-20240322.tgz#3735250b45468d313ed36121324452bb5a732e9b"
integrity sha512-EI6+q3tOT+0z4OkB2sz842Ra/n/yz7b3jOJhSK1HQwi4Ng29VJzLGngWmSuxQ94YfdE3EBhpUKDfgNgzoKM9Vg==

"react-dom-18@npm:[email protected]":
version "18.3.0-canary-670811593-20240322"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.3.0-canary-670811593-20240322.tgz#ac677b164fd83050272bf985e740ed4ca65337be"
integrity sha512-AHxCnyDzZueXIHY4WA2Uba1yaL7/vbjhO3D3TWPQeruKD5MwgD0/xExZi0T104gBr6Thv6MEsLSxFjBAHhHKKg==
dependencies:
scheduler "0.24.0-canary-670811593-20240322"

react-dom@^19.0.0:
version "19.0.0"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-19.0.0.tgz#43446f1f01c65a4cd7f7588083e686a6726cfb57"
Expand All @@ -5849,13 +5837,14 @@ react-is@^18.0.0:
resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b"
integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==

react-server-dom-webpack@18.3.0-canary-670811593-20240322:
version "18.3.0-canary-670811593-20240322"
resolved "https://registry.yarnpkg.com/react-server-dom-webpack/-/react-server-dom-webpack-18.3.0-canary-670811593-20240322.tgz#e9b99b1f0179357e5acbf2fbacaee88dd1e8bf3b"
integrity sha512-YaCk3AvvOXcOo0FL7SlAY2GVBeuZKFQ/5FfAtE48IjpI6MvXTwMBu3QVnT/Ukk9Y4M9GzpIbLtuc8hPjfFAOaw==
react-server-dom-webpack@19.0.0:
version "19.0.0"
resolved "https://registry.yarnpkg.com/react-server-dom-webpack/-/react-server-dom-webpack-19.0.0.tgz#c60819b6cb54e317e675ddc0c5959ff915b789d0"
integrity sha512-hLug9KEXLc8vnU9lDNe2b2rKKDaqrp5gNiES4uyu2Up3FZfZJZmdwLFXlWzdA9gTB/6/cWduSB2K1Lfag2pSvw==
dependencies:
acorn-loose "^8.3.0"
neo-async "^2.6.1"
webpack-sources "^3.2.0"

react@^19.0.0:
version "19.0.0"
Expand Down Expand Up @@ -6110,11 +6099,6 @@ saxes@^6.0.0:
dependencies:
xmlchars "^2.2.0"

[email protected]:
version "0.24.0-canary-670811593-20240322"
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.24.0-canary-670811593-20240322.tgz#45c5c45f18a127ab4e3c805dd466bc231b20adf3"
integrity sha512-IGX6Fq969h1L0X7jV0sJ/EdI4fr+mRetbBNJl55nn+/RsCuQSVwgKnZG6Q3NByixDNbkRI8nRmWuhOm8NQowGQ==

scheduler@^0.25.0:
version "0.25.0"
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.25.0.tgz#336cd9768e8cceebf52d3c80e3dcf5de23e7e015"
Expand Down Expand Up @@ -6870,7 +6854,7 @@ webidl-conversions@^7.0.0:
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-7.0.0.tgz#256b4e1882be7debbf01d05f0aa2039778ea080a"
integrity sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==

webpack-sources@^3.2.3:
webpack-sources@^3.2.0, webpack-sources@^3.2.3:
version "3.2.3"
resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde"
integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==
Expand Down

0 comments on commit dc0c0c3

Please sign in to comment.