diff --git a/.github/workflows/build_test_deploy.yml b/.github/workflows/build_test_deploy.yml index fd346920c8d6..6f308ce4fa89 100644 --- a/.github/workflows/build_test_deploy.yml +++ b/.github/workflows/build_test_deploy.yml @@ -1222,7 +1222,8 @@ jobs: - run: TEST_WASM=true xvfb-run node run-tests.js test/integration/production/test/index.test.js if: ${{needs.build.outputs.docsChange == 'nope'}} - - run: TEST_WASM=true xvfb-run node run-tests.js test/production/react-18-streaming-ssr/index.test.ts + # test wasm parsing for runtime in page config + - run: TEST_WASM=true xvfb-run node run-tests.js test/e2e/streaming-ssr/index.test.ts if: ${{needs.build.outputs.docsChange == 'nope'}} # Build binaries for publishing diff --git a/test/e2e/edge-vs.-non-edge-api-route-priority/index.test.ts b/test/e2e/edge-vs.-non-edge-api-route-priority/index.test.ts deleted file mode 100644 index 48fe0a3e92ba..000000000000 --- a/test/e2e/edge-vs.-non-edge-api-route-priority/index.test.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { createNext } from 'e2e-utils' -import { NextInstance } from 'test/lib/next-modes/base' -import { fetchViaHTTP } from 'next-test-utils' - -describe('Edge vs. non-Edge API route priority', () => { - let next: NextInstance - - beforeAll(async () => { - next = await createNext({ - files: { - 'pages/api/user/login.js': ` - export default async function handler(_, res) { - res.send('from login.js') - } - `, - 'pages/api/user/[id].js': ` - export const config = { - runtime: 'experimental-edge', - } - export default async function handler() { - return new Response('from [id].js') - }`, - }, - dependencies: {}, - }) - }) - afterAll(() => next.destroy()) - - it('more specific route should match', async () => { - const res = await fetchViaHTTP(next.url, '/api/user/login') - expect(await res.text()).toBe('from login.js') - }) -}) diff --git a/test/production/react-18-streaming-ssr/custom-server/next.config.js b/test/e2e/streaming-ssr/custom-server/next.config.js similarity index 100% rename from test/production/react-18-streaming-ssr/custom-server/next.config.js rename to test/e2e/streaming-ssr/custom-server/next.config.js diff --git a/test/production/react-18-streaming-ssr/custom-server/pages/index.js b/test/e2e/streaming-ssr/custom-server/pages/index.js similarity index 100% rename from test/production/react-18-streaming-ssr/custom-server/pages/index.js rename to test/e2e/streaming-ssr/custom-server/pages/index.js diff --git a/test/production/react-18-streaming-ssr/custom-server/server.js b/test/e2e/streaming-ssr/custom-server/server.js similarity index 100% rename from test/production/react-18-streaming-ssr/custom-server/server.js rename to test/e2e/streaming-ssr/custom-server/server.js diff --git a/test/e2e/streaming-ssr/index.test.ts b/test/e2e/streaming-ssr/index.test.ts new file mode 100644 index 000000000000..482346e66ae3 --- /dev/null +++ b/test/e2e/streaming-ssr/index.test.ts @@ -0,0 +1,180 @@ +import { join } from 'path' +import { createNext, FileRef } from 'e2e-utils' +import { NextInstance } from 'test/lib/next-modes/base' +import { + fetchViaHTTP, + findPort, + initNextServerScript, + killApp, + renderViaHTTP, +} from 'next-test-utils' + +const react18Deps = { + react: '^18.0.0', + 'react-dom': '^18.0.0', +} + +const isNextProd = !(global as any).isNextDev && !(global as any).isNextDeploy + +describe('react 18 streaming SSR with custom next configs', () => { + let next: NextInstance + + beforeAll(async () => { + next = await createNext({ + files: { + pages: new FileRef(join(__dirname, 'streaming-ssr/pages')), + }, + nextConfig: require(join(__dirname, 'streaming-ssr/next.config.js')), + dependencies: react18Deps, + installCommand: 'npm install', + }) + }) + afterAll(() => next.destroy()) + + it('should match more specific route along with dynamic routes', async () => { + const res1 = await fetchViaHTTP(next.url, '/api/user/login') + const res2 = await fetchViaHTTP(next.url, '/api/user/any') + expect(await res1.text()).toBe('login') + expect(await res2.text()).toBe('[id]') + }) + + it('should render styled-jsx styles in streaming', async () => { + const html = await renderViaHTTP(next.url, '/') + expect(html).toContain('color:blue') + }) + + it('should redirect paths without trailing-slash and render when slash is appended', async () => { + const page = '/hello' + const redirectRes = await fetchViaHTTP( + next.url, + page, + {}, + { redirect: 'manual' } + ) + const res = await fetchViaHTTP(next.url, page + '/') + const html = await res.text() + + expect(redirectRes.status).toBe(308) + expect(res.status).toBe(200) + expect(html).toContain('hello nextjs') + expect(html).toContain('home') + }) + + it('should render multi-byte characters correctly in streaming', async () => { + const html = await renderViaHTTP(next.url, '/multi-byte') + expect(html).toContain('マルチバイト'.repeat(28)) + }) +}) + +if (isNextProd) { + describe('react 18 streaming SSR with custom server', () => { + let next + let server + let appPort + beforeAll(async () => { + next = await createNext({ + files: { + pages: new FileRef(join(__dirname, 'custom-server/pages')), + 'server.js': new FileRef(join(__dirname, 'custom-server/server.js')), + }, + nextConfig: require(join(__dirname, 'custom-server/next.config.js')), + dependencies: react18Deps, + }) + await next.stop() + + const testServer = join(next.testDir, 'server.js') + appPort = await findPort() + server = await initNextServerScript( + testServer, + /Listening/, + { + ...process.env, + PORT: appPort, + }, + undefined, + { + cwd: next.testDir, + } + ) + }) + afterAll(async () => { + await next.destroy() + if (server) await killApp(server) + }) + it('should render page correctly under custom server', async () => { + const html = await renderViaHTTP(appPort, '/') + expect(html).toContain('streaming') + }) + }) + + describe('react 18 streaming SSR in minimal mode with node runtime', () => { + let next: NextInstance + + beforeAll(async () => { + if (isNextProd) { + process.env.NEXT_PRIVATE_MINIMAL_MODE = '1' + } + + next = await createNext({ + files: { + 'pages/index.js': ` + export default function Page() { + return

streaming

+ } + export async function getServerSideProps() { + return { props: {} } + }`, + }, + nextConfig: { + experimental: { + runtime: 'nodejs', + }, + webpack(config, { nextRuntime }) { + const path = require('path') + const fs = require('fs') + + const runtimeFilePath = path.join(__dirname, 'runtimes.txt') + let runtimeContent = '' + + try { + runtimeContent = fs.readFileSync(runtimeFilePath, 'utf8') + runtimeContent += '\n' + } catch (_) {} + + runtimeContent += nextRuntime || 'client' + + fs.writeFileSync(runtimeFilePath, runtimeContent) + return config + }, + }, + dependencies: react18Deps, + }) + }) + afterAll(() => { + if (isNextProd) { + delete process.env.NEXT_PRIVATE_MINIMAL_MODE + } + next.destroy() + }) + + it('should pass correct nextRuntime values', async () => { + const content = await next.readFile('runtimes.txt') + expect(content.split('\n').sort()).toEqual(['client', 'edge', 'nodejs']) + }) + + it('should generate html response by streaming correctly', async () => { + const html = await renderViaHTTP(next.url, '/') + expect(html).toContain('streaming') + }) + + if (isNextProd) { + it('should have generated a static 404 page', async () => { + expect(await next.readFile('.next/server/pages/404.html')).toBeTruthy() + + const res = await fetchViaHTTP(next.url, '/non-existent') + expect(res.status).toBe(404) + expect(await res.text()).toContain('This page could not be found') + }) + } + }) +} diff --git a/test/e2e/streaming-ssr/streaming-ssr/next.config.js b/test/e2e/streaming-ssr/streaming-ssr/next.config.js new file mode 100644 index 000000000000..ce3f975d0eac --- /dev/null +++ b/test/e2e/streaming-ssr/streaming-ssr/next.config.js @@ -0,0 +1,3 @@ +module.exports = { + trailingSlash: true, +} diff --git a/test/e2e/streaming-ssr/streaming-ssr/pages/api/user/[id].js b/test/e2e/streaming-ssr/streaming-ssr/pages/api/user/[id].js new file mode 100644 index 000000000000..c5649d2074e4 --- /dev/null +++ b/test/e2e/streaming-ssr/streaming-ssr/pages/api/user/[id].js @@ -0,0 +1,7 @@ +export default async function handler() { + return new Response('[id]') +} + +export const config = { + runtime: 'experimental-edge', +} diff --git a/test/e2e/streaming-ssr/streaming-ssr/pages/api/user/login.js b/test/e2e/streaming-ssr/streaming-ssr/pages/api/user/login.js new file mode 100644 index 000000000000..31493ce1ef6a --- /dev/null +++ b/test/e2e/streaming-ssr/streaming-ssr/pages/api/user/login.js @@ -0,0 +1,3 @@ +export default async function handler(_, res) { + res.send('login') +} diff --git a/test/production/react-18-streaming-ssr/streaming-ssr/pages/hello.js b/test/e2e/streaming-ssr/streaming-ssr/pages/hello.js similarity index 100% rename from test/production/react-18-streaming-ssr/streaming-ssr/pages/hello.js rename to test/e2e/streaming-ssr/streaming-ssr/pages/hello.js diff --git a/test/production/react-18-streaming-ssr/streaming-ssr/pages/index.js b/test/e2e/streaming-ssr/streaming-ssr/pages/index.js similarity index 100% rename from test/production/react-18-streaming-ssr/streaming-ssr/pages/index.js rename to test/e2e/streaming-ssr/streaming-ssr/pages/index.js diff --git a/test/production/react-18-streaming-ssr/streaming-ssr/pages/multi-byte.js b/test/e2e/streaming-ssr/streaming-ssr/pages/multi-byte.js similarity index 100% rename from test/production/react-18-streaming-ssr/streaming-ssr/pages/multi-byte.js rename to test/e2e/streaming-ssr/streaming-ssr/pages/multi-byte.js diff --git a/test/e2e/switchable-runtime/index.test.ts b/test/e2e/switchable-runtime/index.test.ts index 7ec464455d62..b7d1ab0efb96 100644 --- a/test/e2e/switchable-runtime/index.test.ts +++ b/test/e2e/switchable-runtime/index.test.ts @@ -286,6 +286,10 @@ describe('Switchable runtime', () => { isStatic: false, isEdge: true, }) + await testRoute(context.appPort, '/rewrite/edge', { + isStatic: false, + isEdge: true, + }) }) // TODO: edge rsc in app dir @@ -305,6 +309,11 @@ describe('Switchable runtime', () => { text = await response.text() expect(text).toMatch(/Returned by Edge API Route .+\/api\/edge/) + // Rewrite should also work + response = await fetchViaHTTP(context.appPort, 'rewrite/api/edge') + text = await response.text() + expect(text).toMatch(/Returned by Edge API Route .+\/api\/edge/) + if (!(global as any).isNextDeploy) { const manifest = await readJson( join(context.appDir, '.next/server/middleware-manifest.json') diff --git a/test/e2e/switchable-runtime/next.config.js b/test/e2e/switchable-runtime/next.config.js index 31dd1d92233e..a055cc5ad2bc 100644 --- a/test/e2e/switchable-runtime/next.config.js +++ b/test/e2e/switchable-runtime/next.config.js @@ -1,7 +1,22 @@ +/** @type {import('next').NextConfig} */ module.exports = { reactStrictMode: true, experimental: { appDir: true, serverComponents: true, }, + async rewrites() { + return { + afterFiles: [ + { + source: '/rewrite/edge', + destination: '/edge', + }, + { + source: '/rewrite/api/edge', + destination: '/api/edge', + }, + ], + } + }, } diff --git a/test/integration/getserversideprops-preview/test/index.test.js b/test/integration/getserversideprops-preview/test/index.test.js index 00a636a1881e..b3ec85122f95 100644 --- a/test/integration/getserversideprops-preview/test/index.test.js +++ b/test/integration/getserversideprops-preview/test/index.test.js @@ -239,7 +239,7 @@ describe('ServerSide Props Preview Mode', () => { expect(cookies.length).toBe(2) }) - /** @type import('next-webdriver').Chain */ + /** @type {import('next-webdriver').Chain} */ let browser it('should start the client-side browser', async () => { browser = await webdriver( diff --git a/test/integration/prerender-preview/test/index.test.js b/test/integration/prerender-preview/test/index.test.js index ce9d77087f93..885cdb19d149 100644 --- a/test/integration/prerender-preview/test/index.test.js +++ b/test/integration/prerender-preview/test/index.test.js @@ -300,7 +300,7 @@ describe('Prerender Preview Mode', () => { expect(cookies.length).toBe(2) }) - /** @type import('next-webdriver').Chain */ + /** @type {import('next-webdriver').Chain} */ let browser it('should start the client-side browser', async () => { browser = await webdriver( diff --git a/test/production/react-18-streaming-ssr/index.test.ts b/test/production/react-18-streaming-ssr/index.test.ts deleted file mode 100644 index bc4fb8df0c54..000000000000 --- a/test/production/react-18-streaming-ssr/index.test.ts +++ /dev/null @@ -1,164 +0,0 @@ -import { join } from 'path' -import { createNext, FileRef } from 'e2e-utils' -import { NextInstance } from 'test/lib/next-modes/base' -import { - fetchViaHTTP, - findPort, - initNextServerScript, - killApp, - renderViaHTTP, -} from 'next-test-utils' - -const react18Deps = { - react: '^18.0.0', - 'react-dom': '^18.0.0', -} - -describe('react 18 streaming SSR in minimal mode', () => { - let next: NextInstance - - beforeAll(async () => { - process.env.NEXT_PRIVATE_MINIMAL_MODE = '1' - - next = await createNext({ - files: { - 'pages/index.js': ` - export default function Page() { - return

streaming

- } - export async function getServerSideProps() { - return { props: {} } - } - `, - }, - nextConfig: { - experimental: { - runtime: 'nodejs', - }, - webpack(config, { nextRuntime }) { - const path = require('path') - const fs = require('fs') - - const runtimeFilePath = path.join(__dirname, 'runtimes.txt') - let runtimeContent = '' - - try { - runtimeContent = fs.readFileSync(runtimeFilePath, 'utf8') - runtimeContent += '\n' - } catch (_) {} - - runtimeContent += nextRuntime || 'client' - - fs.writeFileSync(runtimeFilePath, runtimeContent) - return config - }, - }, - dependencies: react18Deps, - }) - }) - afterAll(() => { - delete process.env.NEXT_PRIVATE_MINIMAL_MODE - next.destroy() - }) - - it('should pass correct nextRuntime values', async () => { - const content = await next.readFile('runtimes.txt') - expect(content.split('\n').sort()).toEqual(['client', 'edge', 'nodejs']) - }) - - it('should generate html response by streaming correctly', async () => { - const html = await renderViaHTTP(next.url, '/') - expect(html).toContain('streaming') - }) - - it('should have generated a static 404 page', async () => { - expect(await next.readFile('.next/server/pages/404.html')).toBeTruthy() - - const res = await fetchViaHTTP(next.url, '/non-existent') - expect(res.status).toBe(404) - expect(await res.text()).toContain('This page could not be found') - }) -}) - -describe('react 18 streaming SSR with custom next configs', () => { - let next: NextInstance - - beforeAll(async () => { - next = await createNext({ - files: { - pages: new FileRef(join(__dirname, 'streaming-ssr/pages')), - }, - nextConfig: require(join(__dirname, 'streaming-ssr/next.config.js')), - dependencies: react18Deps, - installCommand: 'npm install', - }) - }) - afterAll(() => next.destroy()) - - it('should render styled-jsx styles in streaming', async () => { - const html = await renderViaHTTP(next.url, '/') - expect(html).toContain('color:blue') - }) - - it('should redirect paths without trailing-slash and render when slash is appended', async () => { - const page = '/hello' - const redirectRes = await fetchViaHTTP( - next.url, - page, - {}, - { redirect: 'manual' } - ) - const res = await fetchViaHTTP(next.url, page + '/') - const html = await res.text() - - expect(redirectRes.status).toBe(308) - expect(res.status).toBe(200) - expect(html).toContain('hello nextjs') - expect(html).toContain('home') - }) - - it('should render multi-byte characters correctly in streaming', async () => { - const html = await renderViaHTTP(next.url, '/multi-byte') - expect(html).toContain('マルチバイト'.repeat(28)) - }) -}) - -describe('react 18 streaming SSR with custom server', () => { - let next - let server - let appPort - beforeAll(async () => { - next = await createNext({ - files: { - pages: new FileRef(join(__dirname, 'custom-server/pages')), - 'server.js': new FileRef(join(__dirname, 'custom-server/server.js')), - }, - nextConfig: require(join(__dirname, 'custom-server/next.config.js')), - dependencies: react18Deps, - }) - await next.stop() - - const testServer = join(next.testDir, 'server.js') - appPort = await findPort() - server = await initNextServerScript( - testServer, - /Listening/, - { - ...process.env, - PORT: appPort, - }, - undefined, - { - cwd: next.testDir, - } - ) - }) - afterAll(async () => { - await next.destroy() - if (server) await killApp(server) - }) - it('should render page correctly under custom server', async () => { - const html = await renderViaHTTP(appPort, '/') - expect(html).toContain('streaming') - }) -}) diff --git a/test/production/react-18-streaming-ssr/streaming-ssr/next.config.js b/test/production/react-18-streaming-ssr/streaming-ssr/next.config.js deleted file mode 100644 index 04abd7efb81a..000000000000 --- a/test/production/react-18-streaming-ssr/streaming-ssr/next.config.js +++ /dev/null @@ -1,6 +0,0 @@ -module.exports = { - trailingSlash: true, - experimental: { - runtime: 'experimental-edge', - }, -}