diff --git a/.github/workflows/package-js-tests.yml b/.github/workflows/package-js-tests.yml index 2fe0c9274..81d4e4ce4 100644 --- a/.github/workflows/package-js-tests.yml +++ b/.github/workflows/package-js-tests.yml @@ -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 @@ -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 diff --git a/jest.config.js b/jest.config.js index 0777f708b..8276f03bf 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,18 +1,16 @@ +const nodeVersion = parseInt(process.version.slice(1), 10); + module.exports = { preset: 'ts-jest/presets/js-with-ts', testEnvironment: 'jsdom', setupFiles: ['/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$': '/node_modules/react-18', - '^react/(.*)$': '/node_modules/react-18/$1', - '^react-dom$': '/node_modules/react-dom-18', - '^react-dom/(.*)$': '/node_modules/react-dom-18/$1', - } - : { 'react-server-dom-webpack/client': '/node_package/tests/emptyForTesting.js', '^@testing-library/dom$': '/node_package/tests/emptyForTesting.js', '^@testing-library/react$': '/node_package/tests/emptyForTesting.js', - }, + } + : {}, }; diff --git a/knip.ts b/knip.ts index 741d91a0a..781d7acda 100644 --- a/knip.ts +++ b/knip.ts @@ -34,8 +34,6 @@ const config: KnipConfig = { 'react-server-dom-webpack', 'cross-fetch', 'jsdom', - 'react-18', - 'react-dom-18', ], }, 'spec/dummy': { diff --git a/node_package/tests/RSCClientRoot.test.jsx b/node_package/tests/RSCClientRoot.test.jsx index 4bef9ed45..02c938530 100644 --- a/node_package/tests/RSCClientRoot.test.jsx +++ b/node_package/tests/RSCClientRoot.test.jsx @@ -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'; @@ -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(); @@ -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', @@ -59,7 +62,7 @@ enableFetchMocks(); rscPayloadGenerationUrlPath, }; - const { rerender } = render(); + const { rerender } = await act(async () => render()); return { rerender: () => rerender(), @@ -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 @@ -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 @@ -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(); }); }); diff --git a/node_package/tests/fixtures/rsc-payloads/simple-shell-with-async-component/chunk1.json b/node_package/tests/fixtures/rsc-payloads/simple-shell-with-async-component/chunk1.json index 5ea689406..c062ada49 100644 --- a/node_package/tests/fixtures/rsc-payloads/simple-shell-with-async-component/chunk1.json +++ b/node_package/tests/fixtures/rsc-payloads/simple-shell-with-async-component/chunk1.json @@ -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 } diff --git a/node_package/tests/fixtures/rsc-payloads/simple-shell-with-async-component/chunk2.json b/node_package/tests/fixtures/rsc-payloads/simple-shell-with-async-component/chunk2.json index 1737a3859..5274f1ab1 100644 --- a/node_package/tests/fixtures/rsc-payloads/simple-shell-with-async-component/chunk2.json +++ b/node_package/tests/fixtures/rsc-payloads/simple-shell-with-async-component/chunk2.json @@ -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 } diff --git a/package.json b/package.json index 5588f96a9..4a2778523 100644 --- a/package.json +++ b/package.json @@ -46,10 +46,8 @@ "prettier": "^2.8.8", "prop-types": "^15.8.1", "react": "^19.0.0", - "react-18": "npm:react@18.3.0-canary-670811593-20240322", "react-dom": "^19.0.0", - "react-dom-18": "npm:react-dom@18.3.0-canary-670811593-20240322", - "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", @@ -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", diff --git a/yarn.lock b/yarn.lock index 454dd9e53..075c3111a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5815,18 +5815,6 @@ randombytes@^2.1.0: dependencies: safe-buffer "^5.1.0" -"react-18@npm:react@18.3.0-canary-670811593-20240322": - 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:react-dom@18.3.0-canary-670811593-20240322": - 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" @@ -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" @@ -6110,11 +6099,6 @@ saxes@^6.0.0: dependencies: xmlchars "^2.2.0" -scheduler@0.24.0-canary-670811593-20240322: - 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" @@ -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==