| undefined
- const page = params.page
+ const { query, page } = params
+
await this.ensureEdgeFunction({ page, appPaths: params.appPaths })
middlewareInfo = this.getEdgeFunctionInfo({
page,
@@ -2048,21 +2044,20 @@ export default class NextNodeServer extends BaseServer {
}
// For middleware to "fetch" we must always provide an absolute URL
- const isDataReq = !!params.query.__nextDataReq
- const query = urlQueryToSearchParams(params.query).toString()
- const locale = params.query.__nextLocale
- // Use original pathname (without `/page`) instead of appPath for url
- let normalizedPathname = params.page
+ const locale = query.__nextLocale
+ const isDataReq = !!query.__nextDataReq
+ const queryString = urlQueryToSearchParams(query).toString()
if (isDataReq) {
params.req.headers['x-nextjs-data'] = '1'
}
+ let normalizedPathname = normalizeAppPath(page)
if (isDynamicRoute(normalizedPathname)) {
- const routeRegex = getNamedRouteRegex(params.page)
+ const routeRegex = getNamedRouteRegex(normalizedPathname)
normalizedPathname = interpolateDynamicPath(
- params.page,
- Object.assign({}, params.params, params.query),
+ normalizedPathname,
+ Object.assign({}, params.params, query),
routeRegex
)
}
@@ -2070,7 +2065,7 @@ export default class NextNodeServer extends BaseServer {
const url = `${getRequestMeta(params.req, '_protocol')}://${
this.hostname
}:${this.port}${locale ? `/${locale}` : ''}${normalizedPathname}${
- query ? `?${query}` : ''
+ queryString ? `?${queryString}` : ''
}`
if (!url.startsWith('http')) {
diff --git a/packages/next/shared/lib/constants.ts b/packages/next/shared/lib/constants.ts
index c314f8517b5b653..f17d05ba0f83924 100644
--- a/packages/next/shared/lib/constants.ts
+++ b/packages/next/shared/lib/constants.ts
@@ -26,6 +26,7 @@ export const APP_PATHS_MANIFEST = 'app-paths-manifest.json'
export const APP_PATH_ROUTES_MANIFEST = 'app-path-routes-manifest.json'
export const BUILD_MANIFEST = 'build-manifest.json'
export const APP_BUILD_MANIFEST = 'app-build-manifest.json'
+export const SUBRESOURCE_INTEGRITY_MANIFEST = 'subresource-integrity-manifest'
export const EXPORT_MARKER = 'export-marker.json'
export const EXPORT_DETAIL = 'export-detail.json'
export const PRERENDER_MANIFEST = 'prerender-manifest.json'
diff --git a/packages/react-dev-overlay/package.json b/packages/react-dev-overlay/package.json
index 11602ec757a0375..ecbf133e549a305 100644
--- a/packages/react-dev-overlay/package.json
+++ b/packages/react-dev-overlay/package.json
@@ -1,6 +1,6 @@
{
"name": "@next/react-dev-overlay",
- "version": "12.2.6-canary.12",
+ "version": "12.3.1-canary.0",
"description": "A development-only overlay for developing React applications.",
"repository": {
"url": "vercel/next.js",
diff --git a/packages/react-refresh-utils/package.json b/packages/react-refresh-utils/package.json
index eb8a3c1762febb8..d66abadd4b70189 100644
--- a/packages/react-refresh-utils/package.json
+++ b/packages/react-refresh-utils/package.json
@@ -1,6 +1,6 @@
{
"name": "@next/react-refresh-utils",
- "version": "12.2.6-canary.12",
+ "version": "12.3.1-canary.0",
"description": "An experimental package providing utilities for React Refresh.",
"repository": {
"url": "vercel/next.js",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index d204490f545aca6..5cbe4a27ab64d7e 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -40,6 +40,7 @@ importers:
'@types/http-proxy': 1.17.3
'@types/jest': 24.0.13
'@types/node': 13.11.0
+ '@types/node-fetch': 2.6.1
'@types/react': 16.9.17
'@types/react-dom': 16.9.4
'@types/relay-runtime': 13.0.0
@@ -139,8 +140,8 @@ importers:
react-17: npm:react@17.0.2
react-dom: 18.2.0
react-dom-17: npm:react-dom@17.0.2
- react-dom-exp: npm:react-dom@0.0.0-experimental-0de3ddf56-20220825
- react-exp: npm:react@0.0.0-experimental-0de3ddf56-20220825
+ react-dom-exp: npm:react-dom@0.0.0-experimental-7028ce745-20220907
+ react-exp: npm:react@0.0.0-experimental-7028ce745-20220907
react-ssr-prepass: 1.0.8
react-virtualized: 9.22.3
relay-compiler: 13.0.2
@@ -194,6 +195,7 @@ importers:
'@types/http-proxy': 1.17.3
'@types/jest': 24.0.13
'@types/node': 13.11.0
+ '@types/node-fetch': 2.6.1
'@types/react': 16.9.17
'@types/react-dom': 16.9.4
'@types/relay-runtime': 13.0.0
@@ -203,7 +205,7 @@ importers:
'@types/trusted-types': 2.0.2
'@typescript-eslint/eslint-plugin': 4.29.1_qxyn66xcaddhgaahwkbomftvi4
'@typescript-eslint/parser': 4.29.1_6x3mpmmsttbpxxsctsorxedanu
- '@vercel/fetch': 6.1.1_wbqoqouw2iimn65bqgaw3lwmza
+ '@vercel/fetch': 6.1.1_fii5qhbaymjqmfm7e2spxc5z4m
'@webassemblyjs/ast': 1.11.1
'@webassemblyjs/floating-point-hex-parser': 1.11.1
'@webassemblyjs/helper-api-error': 1.11.1
@@ -293,8 +295,8 @@ importers:
react-17: /react/17.0.2
react-dom: 18.2.0_react@18.2.0
react-dom-17: /react-dom/17.0.2_react@18.2.0
- react-dom-exp: /react-dom/0.0.0-experimental-0de3ddf56-20220825_react@18.2.0
- react-exp: /react/0.0.0-experimental-0de3ddf56-20220825
+ react-dom-exp: /react-dom/0.0.0-experimental-7028ce745-20220907_react@18.2.0
+ react-exp: /react/0.0.0-experimental-7028ce745-20220907
react-ssr-prepass: 1.0.8_qncsgtzehe3fgiqp6tr7lwq6fm
react-virtualized: 9.22.3_biqbaboplfbrettd7655fr4n2y
relay-compiler: 13.0.2
@@ -363,7 +365,7 @@ importers:
packages/eslint-config-next:
specifiers:
- '@next/eslint-plugin-next': 12.2.6-canary.12
+ '@next/eslint-plugin-next': 12.3.1-canary.0
'@rushstack/eslint-patch': ^1.1.3
'@typescript-eslint/parser': ^5.21.0
eslint-import-resolver-node: ^0.3.6
@@ -419,12 +421,12 @@ importers:
'@hapi/accept': 5.0.2
'@napi-rs/cli': 2.7.0
'@napi-rs/triples': 1.1.0
- '@next/env': 12.2.6-canary.12
- '@next/polyfill-module': 12.2.6-canary.12
- '@next/polyfill-nomodule': 12.2.6-canary.12
- '@next/react-dev-overlay': 12.2.6-canary.12
- '@next/react-refresh-utils': 12.2.6-canary.12
- '@next/swc': 12.2.6-canary.12
+ '@next/env': 12.3.1-canary.0
+ '@next/polyfill-module': 12.3.1-canary.0
+ '@next/polyfill-nomodule': 12.3.1-canary.0
+ '@next/react-dev-overlay': 12.3.1-canary.0
+ '@next/react-refresh-utils': 12.3.1-canary.0
+ '@next/swc': 12.3.1-canary.0
'@segment/ajv-human-errors': 2.1.2
'@swc/helpers': 0.4.11
'@taskr/clear': 1.1.0
@@ -546,7 +548,7 @@ importers:
raw-body: 2.4.1
react-is: 17.0.2
react-refresh: 0.12.0
- react-server-dom-webpack: 0.0.0-experimental-0de3ddf56-20220825
+ react-server-dom-webpack: 0.0.0-experimental-7028ce745-20220907
regenerator-runtime: 0.13.4
sass-loader: 12.4.0
schema-utils2: npm:schema-utils@2.7.1
@@ -735,7 +737,7 @@ importers:
raw-body: 2.4.1
react-is: 17.0.2
react-refresh: 0.12.0
- react-server-dom-webpack: 0.0.0-experimental-0de3ddf56-20220825_webpack@5.74.0
+ react-server-dom-webpack: 0.0.0-experimental-7028ce745-20220907_webpack@5.74.0
regenerator-runtime: 0.13.4
sass-loader: 12.4.0_webpack@5.74.0
schema-utils2: /schema-utils/2.7.1
@@ -7275,16 +7277,6 @@ packages:
form-data: 3.0.1
dev: true
- /@types/node-fetch/2.6.2:
- resolution:
- {
- integrity: sha512-DHqhlq5jeESLy19TYhLakJ07kNumXWjcDdxXsLUMJZ6ue8VZJj4kLPQVE/2mdHh3xZziNF1xppu5lwmS53HR+A==,
- }
- dependencies:
- '@types/node': 17.0.21
- form-data: 3.0.1
- dev: true
-
/@types/node/10.12.18:
resolution:
{
@@ -7890,7 +7882,7 @@ packages:
- supports-color
dev: true
- /@vercel/fetch/6.1.1_wbqoqouw2iimn65bqgaw3lwmza:
+ /@vercel/fetch/6.1.1_fii5qhbaymjqmfm7e2spxc5z4m:
resolution:
{
integrity: sha512-nddCkgpA0aVIqOlzh+qVlzDNcQq0cSnqefM+x6SciGI4GCvVZeaZ7WEowgX8I/HwBAq8Uj5Bdnd+r0+sYsJsig==,
@@ -7900,7 +7892,7 @@ packages:
node-fetch: '2'
dependencies:
'@types/async-retry': 1.2.1
- '@types/node-fetch': 2.6.2
+ '@types/node-fetch': 2.6.1
'@vercel/fetch-cached-dns': 2.0.2_node-fetch@2.6.7
'@vercel/fetch-retry': 5.0.3_node-fetch@2.6.7
agentkeepalive: 3.4.1
@@ -24206,17 +24198,17 @@ packages:
strip-json-comments: 2.0.1
dev: true
- /react-dom/0.0.0-experimental-0de3ddf56-20220825_react@18.2.0:
+ /react-dom/0.0.0-experimental-7028ce745-20220907_react@18.2.0:
resolution:
{
- integrity: sha512-d2pS60dObYaVw4cybVNlQ7JL+Mkpz6MVZo35KgsUzU4IUUywxrc09JJivOGiOyJTwTShITAgOvn/c/LZjlTpcg==,
+ integrity: sha512-vNt0UKStPB47hPjpk6I1JEnxjl3vb0VItCtpZolzcNtYlMyfDmZFInh6IGiMtfBpm6Fp1LCztfopAnuiYoEb3Q==,
}
peerDependencies:
- react: 0.0.0-experimental-0de3ddf56-20220825
+ react: 0.0.0-experimental-7028ce745-20220907
dependencies:
loose-envify: 1.4.0
react: 18.2.0
- scheduler: 0.0.0-experimental-0de3ddf56-20220825
+ scheduler: 0.0.0-experimental-7028ce745-20220907
dev: true
/react-dom/17.0.2_react@18.2.0:
@@ -24281,14 +24273,14 @@ packages:
engines: { node: '>=0.10.0' }
dev: true
- /react-server-dom-webpack/0.0.0-experimental-0de3ddf56-20220825_webpack@5.74.0:
+ /react-server-dom-webpack/0.0.0-experimental-7028ce745-20220907_webpack@5.74.0:
resolution:
{
- integrity: sha512-mowgFHsHjlGnL2YcedTXQMXmJaP3tKPjXjY3tyZVRVH7AaLRa+SMPEjx5GFCzz1nn6W0x/NQlCvqxV+F35nytg==,
+ integrity: sha512-DqVpIa9DdgQNre2urGq5brXRrgtd6jqCUO4Ax5yBAHvmWW5L2XkU3jI+YCMMZdxVwcP+NIJoB9mkIsFyIo8NAQ==,
}
engines: { node: '>=0.10.0' }
peerDependencies:
- react: 0.0.0-experimental-0de3ddf56-20220825
+ react: 0.0.0-experimental-7028ce745-20220907
webpack: ^5.59.0
dependencies:
acorn: 6.4.2
@@ -24330,10 +24322,10 @@ packages:
react-lifecycles-compat: 3.0.4
dev: true
- /react/0.0.0-experimental-0de3ddf56-20220825:
+ /react/0.0.0-experimental-7028ce745-20220907:
resolution:
{
- integrity: sha512-4KH+Ylv+P0SsKAjjpGALztqHBkqoh01GFh8hJkCAPJ7fAx6yLDY7i4XeoDutqo6okWnJncq7ixUoyru4hXFn4A==,
+ integrity: sha512-GJkc7NNWnUV7imij/n8YxJTQEY+OsE8qSmHB4esfFV/Gdi5zFOtzXr9LEB8CItAIoIpZ0eK1LXcOAO3cenDzLQ==,
}
engines: { node: '>=0.10.0' }
dependencies:
@@ -25663,10 +25655,10 @@ packages:
xmlchars: 2.2.0
dev: true
- /scheduler/0.0.0-experimental-0de3ddf56-20220825:
+ /scheduler/0.0.0-experimental-7028ce745-20220907:
resolution:
{
- integrity: sha512-cMaG6fmnYZvH2C5LuMGw//oHBskpicjdJ+GbLs7nAQLiuYl6sXzmqQeGgDLJvd76hcBz2aK2tEKZMHrmwuWVcA==,
+ integrity: sha512-vmk4QI8sXQYFk7rtB9omwlsyNOhdsai4BKvud+XE+CfZuI5iTV+Nzj2EQq19PcfS7cmf1UOQ83wgv4yGgsefUQ==,
}
dependencies:
loose-envify: 1.4.0
diff --git a/test/e2e/app-dir/app/next.config.js b/test/e2e/app-dir/app/next.config.js
index 087742808cea756..0e04741a08bf144 100644
--- a/test/e2e/app-dir/app/next.config.js
+++ b/test/e2e/app-dir/app/next.config.js
@@ -4,6 +4,9 @@ module.exports = {
serverComponents: true,
legacyBrowsers: false,
browsersListForSwc: true,
+ sri: {
+ algorithm: 'sha256',
+ },
},
// assetPrefix: '/assets',
rewrites: async () => {
diff --git a/test/e2e/app-dir/index.test.ts b/test/e2e/app-dir/index.test.ts
index 6ff89b27588809b..e92332aa0c8ca25 100644
--- a/test/e2e/app-dir/index.test.ts
+++ b/test/e2e/app-dir/index.test.ts
@@ -1,4 +1,5 @@
import { createNext, FileRef } from 'e2e-utils'
+import crypto from 'crypto'
import { NextInstance } from 'test/lib/next-modes/base'
import { check, fetchViaHTTP, renderViaHTTP, waitFor } from 'next-test-utils'
import path from 'path'
@@ -1194,6 +1195,128 @@ describe('app dir', () => {
})
})
})
+ ;(isDev ? describe.skip : describe)('Subresource Integrity', () => {
+ function fetchWithPolicy(policy: string | null) {
+ return fetchViaHTTP(next.url, '/dashboard', undefined, {
+ headers: policy
+ ? {
+ 'Content-Security-Policy': policy,
+ }
+ : {},
+ })
+ }
+
+ async function renderWithPolicy(policy: string | null) {
+ const res = await fetchWithPolicy(policy)
+
+ expect(res.ok).toBe(true)
+
+ const html = await res.text()
+
+ return cheerio.load(html)
+ }
+
+ it('does not include nonce when not enabled', async () => {
+ const policies = [
+ `script-src 'nonce-'`, // invalid nonce
+ 'style-src "nonce-cmFuZG9tCg=="', // no script or default src
+ '', // empty string
+ ]
+
+ for (const policy of policies) {
+ const $ = await renderWithPolicy(policy)
+
+ // Find all the script tags without src attributes and with nonce
+ // attributes.
+ const elements = $('script[nonce]:not([src])')
+
+ // Expect there to be none.
+ expect(elements.length).toBe(0)
+ }
+ })
+
+ it('includes a nonce value with inline scripts when Content-Security-Policy header is defined', async () => {
+ // A random nonce value, base64 encoded.
+ const nonce = 'cmFuZG9tCg=='
+
+ // Validate all the cases where we could parse the nonce.
+ const policies = [
+ `script-src 'nonce-${nonce}'`, // base case
+ ` script-src 'nonce-${nonce}' `, // extra space added around sources and directive
+ `style-src 'self'; script-src 'nonce-${nonce}'`, // extra directives
+ `script-src 'self' 'nonce-${nonce}' 'nonce-othernonce'`, // extra nonces
+ `default-src 'nonce-othernonce'; script-src 'nonce-${nonce}';`, // script and then fallback case
+ `default-src 'nonce-${nonce}'`, // fallback case
+ ]
+
+ for (const policy of policies) {
+ const $ = await renderWithPolicy(policy)
+
+ // Find all the script tags without src attributes.
+ const elements = $('script:not([src])')
+
+ // Expect there to be at least 1 script tag without a src attribute.
+ expect(elements.length).toBeGreaterThan(0)
+
+ // Expect all inline scripts to have the nonce value.
+ elements.each((i, el) => {
+ expect(el.attribs['nonce']).toBe(nonce)
+ })
+ }
+ })
+
+ it('includes an integrity attribute on scripts', async () => {
+ const html = await renderViaHTTP(next.url, '/dashboard')
+
+ const $ = cheerio.load(html)
+
+ // Find all the script tags with src attributes.
+ const elements = $('script[src]')
+
+ // Expect there to be at least 1 script tag with a src attribute.
+ expect(elements.length).toBeGreaterThan(0)
+
+ // Collect all the scripts with integrity hashes so we can verify them.
+ const files: [string, string][] = []
+
+ // For each of these attributes, ensure that there's an integrity
+ // attribute and starts with the correct integrity hash prefix.
+ elements.each((i, el) => {
+ const integrity = el.attribs['integrity']
+ expect(integrity).toBeDefined()
+ expect(integrity).toStartWith('sha256-')
+
+ const src = el.attribs['src']
+ expect(src).toBeDefined()
+
+ files.push([src, integrity])
+ })
+
+ // For each script tag, ensure that the integrity attribute is the
+ // correct hash of the script tag.
+ for (const [src, integrity] of files) {
+ const res = await fetchViaHTTP(next.url, src)
+ expect(res.status).toBe(200)
+ const content = await res.text()
+
+ const hash = crypto
+ .createHash('sha256')
+ .update(content)
+ .digest()
+ .toString('base64')
+
+ expect(integrity).toEndWith(hash)
+ }
+ })
+
+ it('throws when escape characters are included in nonce', async () => {
+ const res = await fetchWithPolicy(
+ `script-src 'nonce-">"'`
+ )
+
+ expect(res.status).toBe(500)
+ })
+ })
}
describe('without assetPrefix', () => {
diff --git a/test/e2e/app-dir/rsc-basic.test.ts b/test/e2e/app-dir/rsc-basic.test.ts
index c9996fe5ec0d286..0debb6818399675 100644
--- a/test/e2e/app-dir/rsc-basic.test.ts
+++ b/test/e2e/app-dir/rsc-basic.test.ts
@@ -340,6 +340,20 @@ describe('app dir - react server components', () => {
expect(head).toMatch(/{color:(\s*)blue;?}/)
})
+ it('should stick to the url without trailing /page suffix', async () => {
+ const browser = await webdriver(next.url, '/edge/dynamic')
+ const indexUrl = await browser.url()
+
+ await browser.loadPage(`${next.url}/edge/dynamic/123`, {
+ disableCache: false,
+ beforePageLoad: null,
+ })
+
+ const dynamicRouteUrl = await browser.url()
+ expect(indexUrl).toBe(`${next.url}/edge/dynamic`)
+ expect(dynamicRouteUrl).toBe(`${next.url}/edge/dynamic/123`)
+ })
+
it('should support streaming for flight response', async () => {
await fetchViaHTTP(next.url, '/?__flight__=1').then(async (response) => {
const result = await resolveStreamResponse(response)
diff --git a/test/e2e/app-dir/rsc-basic/app/edge/dynamic/[id]/page.server.js b/test/e2e/app-dir/rsc-basic/app/edge/dynamic/[id]/page.server.js
new file mode 100644
index 000000000000000..87bac2ff6be9ec8
--- /dev/null
+++ b/test/e2e/app-dir/rsc-basic/app/edge/dynamic/[id]/page.server.js
@@ -0,0 +1,7 @@
+export default function page() {
+ return 'dynamic route [id] page'
+}
+
+export const config = {
+ runtime: 'experimental-edge',
+}
diff --git a/test/e2e/app-dir/rsc-basic/app/edge/dynamic/page.server.js b/test/e2e/app-dir/rsc-basic/app/edge/dynamic/page.server.js
new file mode 100644
index 000000000000000..1b83f0120a6ab43
--- /dev/null
+++ b/test/e2e/app-dir/rsc-basic/app/edge/dynamic/page.server.js
@@ -0,0 +1,7 @@
+export default function page() {
+ return 'dynamic route index page'
+}
+
+export const config = {
+ runtime: 'experimental-edge',
+}
diff --git a/test/e2e/app-dir/rsc-basic/next.config.js b/test/e2e/app-dir/rsc-basic/next.config.js
index ca13fceaff38c09..eab44b6855ae317 100644
--- a/test/e2e/app-dir/rsc-basic/next.config.js
+++ b/test/e2e/app-dir/rsc-basic/next.config.js
@@ -7,4 +7,14 @@ module.exports = {
appDir: true,
serverComponents: true,
},
+ rewrites: async () => {
+ return {
+ afterFiles: [
+ {
+ source: '/rewritten-to-edge-dynamic',
+ destination: '/edge/dynamic',
+ },
+ ],
+ }
+ },
}
diff --git a/test/integration/image-future/default/pages/on-loading-complete.js b/test/integration/image-future/default/pages/on-loading-complete.js
index b89d655268e37c2..da60e069f31956e 100644
--- a/test/integration/image-future/default/pages/on-loading-complete.js
+++ b/test/integration/image-future/default/pages/on-loading-complete.js
@@ -105,13 +105,15 @@ function ImageWithMessage({ id, idToCount, setIdToCount, ...props }) {
{
+ onLoadingComplete={(img) => {
+ const { naturalWidth, naturalHeight, nodeName } = img
let count = idToCount[id] || 0
count++
idToCount[id] = count
setIdToCount(idToCount)
+ const name = nodeName.toLocaleLowerCase()
setMsg(
- `loaded ${count} img${id} with dimensions ${naturalWidth}x${naturalHeight}`
+ `loaded ${count} ${name}${id} with dimensions ${naturalWidth}x${naturalHeight}`
)
}}
{...props}
diff --git a/test/integration/image-optimizer/test/util.ts b/test/integration/image-optimizer/test/util.ts
index aa37a5aef7c78d4..0937682c742c607 100644
--- a/test/integration/image-optimizer/test/util.ts
+++ b/test/integration/image-optimizer/test/util.ts
@@ -15,6 +15,7 @@ import {
waitFor,
} from 'next-test-utils'
import isAnimated from 'next/dist/compiled/is-animated'
+import type { RequestInit } from 'node-fetch'
const largeSize = 1080 // defaults defined in server/config.ts
const sharpMissingText = `For production Image Optimization with Next.js, the optional 'sharp' package is strongly recommended`
@@ -115,10 +116,15 @@ async function expectAvifSmallerThanWebp(w, q, appPort) {
expect(avif).toBeLessThanOrEqual(webp)
}
-async function fetchWithDuration(...args) {
- console.warn('Fetching', args[1], args[2])
+async function fetchWithDuration(
+ appPort: string | number,
+ pathname: string,
+ query?: Record | string,
+ opts?: RequestInit
+) {
+ console.warn('Fetching', pathname, query)
const start = Date.now()
- const res = await fetchViaHTTP(...args)
+ const res = await fetchViaHTTP(appPort, pathname, query, opts)
const buffer = await res.buffer()
const duration = Date.now() - start
return { duration, buffer, res }
@@ -140,7 +146,10 @@ export function runTests(ctx) {
slowImageServer.port
}/slow.png?delay=${1}&status=308`
const query = { url, w: ctx.w, q: 39 }
- const opts = { headers: { accept: 'image/webp' }, redirect: 'manual' }
+ const opts: RequestInit = {
+ headers: { accept: 'image/webp' },
+ redirect: 'manual',
+ }
const res = await fetchViaHTTP(ctx.appPort, '/_next/image', query, opts)
expect(res.status).toBe(500)
diff --git a/test/lib/next-test-utils.js b/test/lib/next-test-utils.js
index d46441e156f38b4..6d07d22915e0827 100644
--- a/test/lib/next-test-utils.js
+++ b/test/lib/next-test-utils.js
@@ -83,6 +83,12 @@ export function initNextServerScript(
})
}
+/**
+ * @param {string | number} appPortOrUrl
+ * @param {string} [url]
+ * @param {string} [hostname]
+ * @returns
+ */
export function getFullUrl(appPortOrUrl, url, hostname) {
let fullUrl =
typeof appPortOrUrl === 'string' && appPortOrUrl.startsWith('http')
@@ -110,11 +116,24 @@ export function renderViaAPI(app, pathname, query) {
return app.renderToHTML({ url }, {}, pathname, query)
}
+/**
+ * @param {string | number} appPort
+ * @param {string} pathname
+ * @param {Record | string | undefined} [query]
+ * @param {import('node-fetch').RequestInit} [opts]
+ * @returns {Promise}
+ */
export function renderViaHTTP(appPort, pathname, query, opts) {
return fetchViaHTTP(appPort, pathname, query, opts).then((res) => res.text())
}
-/** @return {Promise} */
+/**
+ * @param {string | number} appPort
+ * @param {string} pathname
+ * @param {Record | string | undefined} [query]
+ * @param {import('node-fetch').RequestInit} [opts]
+ * @returns {Promise}
+ */
export function fetchViaHTTP(appPort, pathname, query, opts) {
const url = `${pathname}${
typeof query === 'string' ? query : query ? `?${qs.stringify(query)}` : ''
diff --git a/test/production/required-server-files-i18n.test.ts b/test/production/required-server-files-i18n.test.ts
index 17c5df1c80dff2a..8d290d779df5390 100644
--- a/test/production/required-server-files-i18n.test.ts
+++ b/test/production/required-server-files-i18n.test.ts
@@ -171,7 +171,7 @@ describe('should set-up next', () => {
await next.patchFile('standalone/data.txt', 'show')
const res = await fetchViaHTTP(appPort, '/gsp', undefined, {
- redirect: 'manual ',
+ redirect: 'manual',
})
expect(res.status).toBe(200)
expect(res.headers.get('cache-control')).toBe(
@@ -182,7 +182,7 @@ describe('should set-up next', () => {
await next.patchFile('standalone/data.txt', 'hide')
const res2 = await fetchViaHTTP(appPort, '/gsp', undefined, {
- redirect: 'manual ',
+ redirect: 'manual',
})
expect(res2.status).toBe(404)
expect(res2.headers.get('cache-control')).toBe(
@@ -194,7 +194,7 @@ describe('should set-up next', () => {
await next.patchFile('standalone/data.txt', 'show')
const res = await fetchViaHTTP(appPort, '/gssp', undefined, {
- redirect: 'manual ',
+ redirect: 'manual',
})
expect(res.status).toBe(200)
expect(res.headers.get('cache-control')).toBe(
@@ -204,7 +204,7 @@ describe('should set-up next', () => {
await next.patchFile('standalone/data.txt', 'hide')
const res2 = await fetchViaHTTP(appPort, '/gssp', undefined, {
- redirect: 'manual ',
+ redirect: 'manual',
})
await next.patchFile('standalone/data.txt', 'show')
diff --git a/test/production/required-server-files.test.ts b/test/production/required-server-files.test.ts
index 1d97b5bc5be3c35..de7d1bcc26c32de 100644
--- a/test/production/required-server-files.test.ts
+++ b/test/production/required-server-files.test.ts
@@ -422,7 +422,7 @@ describe('should set-up next', () => {
await next.patchFile('standalone/data.txt', 'show')
const res = await fetchViaHTTP(appPort, '/gsp', undefined, {
- redirect: 'manual ',
+ redirect: 'manual',
})
expect(res.status).toBe(200)
expect(res.headers.get('cache-control')).toBe(
@@ -433,7 +433,7 @@ describe('should set-up next', () => {
await next.patchFile('standalone/data.txt', 'hide')
const res2 = await fetchViaHTTP(appPort, '/gsp', undefined, {
- redirect: 'manual ',
+ redirect: 'manual',
})
expect(res2.status).toBe(404)
expect(res2.headers.get('cache-control')).toBe(
@@ -445,7 +445,7 @@ describe('should set-up next', () => {
await next.patchFile('standalone/data.txt', 'show')
const res = await fetchViaHTTP(appPort, '/gssp', undefined, {
- redirect: 'manual ',
+ redirect: 'manual',
})
expect(res.status).toBe(200)
expect(res.headers.get('cache-control')).toBe(
@@ -455,7 +455,7 @@ describe('should set-up next', () => {
await next.patchFile('standalone/data.txt', 'hide')
const res2 = await fetchViaHTTP(appPort, '/gssp', undefined, {
- redirect: 'manual ',
+ redirect: 'manual',
})
await next.patchFile('standalone/data.txt', 'show')