diff --git a/tests/e2e/edge-middleware.test.ts b/tests/e2e/edge-middleware.test.ts index c40eabc931..ef66c47b0c 100644 --- a/tests/e2e/edge-middleware.test.ts +++ b/tests/e2e/edge-middleware.test.ts @@ -3,6 +3,10 @@ import { nextVersionSatisfies } from '../utils/next-version-helpers.mjs' import { test } from '../utils/playwright-helpers.js' import { getImageSize } from 'next/dist/server/image-optimizer.js' +type ExtendedWindow = Window & { + didReload?: boolean +} + test('Runs edge middleware', async ({ page, middleware }) => { await page.goto(`${middleware.url}/test/redirect`) @@ -53,21 +57,139 @@ test('it should render OpenGraph image meta tag correctly', async ({ page, middl expect([size.width, size.height]).toEqual([1200, 630]) }) -test('json data rewrite works', async ({ middlewarePages }) => { - const response = await fetch(`${middlewarePages.url}/_next/data/build-id/sha.json`, { - headers: { - 'x-nextjs-data': '1', +test.describe('json data', () => { + const testConfigs = [ + { + describeLabel: 'NextResponse.next() -> getServerSideProps page', + selector: 'NextResponse.next()#getServerSideProps', + jsonPathMatcher: '/link/next-getserversideprops.json', + }, + { + describeLabel: 'NextResponse.next() -> getStaticProps page', + selector: 'NextResponse.next()#getStaticProps', + jsonPathMatcher: '/link/next-getstaticprops.json', + }, + { + describeLabel: 'NextResponse.next() -> fully static page', + selector: 'NextResponse.next()#fullyStatic', + jsonPathMatcher: '/link/next-fullystatic.json', + }, + { + describeLabel: 'NextResponse.rewrite() -> getServerSideProps page', + selector: 'NextResponse.rewrite()#getServerSideProps', + jsonPathMatcher: '/link/rewrite-me-getserversideprops.json', }, + { + describeLabel: 'NextResponse.rewrite() -> getStaticProps page', + selector: 'NextResponse.rewrite()#getStaticProps', + jsonPathMatcher: '/link/rewrite-me-getstaticprops.json', + }, + { + describeLabel: 'NextResponse.rewrite() -> fully static page', + selector: 'NextResponse.rewrite()#fullyStatic', + jsonPathMatcher: '/link/rewrite-me-fullystatic.json', + }, + ] + test.describe('no 18n', () => { + for (const testConfig of testConfigs) { + test.describe(testConfig.describeLabel, () => { + test('json data fetch', async ({ middlewarePages, page }) => { + const dataFetchPromise = new Promise((resolve) => { + page.on('response', (response) => { + if (response.url().includes(testConfig.jsonPathMatcher)) { + resolve(response) + } + }) + }) + + await page.goto(`${middlewarePages.url}/link`) + + await page.hover(`[data-link="${testConfig.selector}"]`) + + const dataResponse = await dataFetchPromise + + expect(dataResponse.ok()).toBe(true) + }) + + test('navigation', async ({ middlewarePages, page }) => { + await page.goto(`${middlewarePages.url}/link`) + + await page.evaluate(() => { + // set some value to window to check later if browser did reload and lost this state + ;(window as ExtendedWindow).didReload = false + }) + + await page.click(`[data-link="${testConfig.selector}"]`) + + // wait for page to be rendered + await page.waitForSelector(`[data-page="${testConfig.selector}"]`) + + // check if browser navigation worked by checking if state was preserved + const browserNavigationWorked = + (await page.evaluate(() => { + return (window as ExtendedWindow).didReload + })) === false + + // we expect client navigation to work without browser reload + expect(browserNavigationWorked).toBe(true) + }) + }) + } + }) + test.describe('with 18n', () => { + for (const testConfig of testConfigs) { + test.describe(testConfig.describeLabel, () => { + for (const { localeLabel, pageWithLinksPathname } of [ + { localeLabel: 'implicit default locale', pageWithLinksPathname: '/link' }, + { localeLabel: 'explicit default locale', pageWithLinksPathname: '/en/link' }, + { localeLabel: 'explicit non-default locale', pageWithLinksPathname: '/fr/link' }, + ]) { + test.describe(localeLabel, () => { + test('json data fetch', async ({ middlewareI18n, page }) => { + const dataFetchPromise = new Promise((resolve) => { + page.on('response', (response) => { + if (response.url().includes(testConfig.jsonPathMatcher)) { + resolve(response) + } + }) + }) + + await page.goto(`${middlewareI18n.url}${pageWithLinksPathname}`) + + await page.hover(`[data-link="${testConfig.selector}"]`) + + const dataResponse = await dataFetchPromise + + expect(dataResponse.ok()).toBe(true) + }) + + test('navigation', async ({ middlewareI18n, page }) => { + await page.goto(`${middlewareI18n.url}${pageWithLinksPathname}`) + + await page.evaluate(() => { + // set some value to window to check later if browser did reload and lost this state + ;(window as ExtendedWindow).didReload = false + }) + + await page.click(`[data-link="${testConfig.selector}"]`) + + // wait for page to be rendered + await page.waitForSelector(`[data-page="${testConfig.selector}"]`) + + // check if browser navigation worked by checking if state was preserved + const browserNavigationWorked = + (await page.evaluate(() => { + return (window as ExtendedWindow).didReload + })) === false + + // we expect client navigation to work without browser reload + expect(browserNavigationWorked).toBe(true) + }) + }) + } + }) + } }) - - expect(response.ok).toBe(true) - const body = await response.text() - - expect(body).toMatch(/^{"pageProps":/) - - const data = JSON.parse(body) - - expect(data.pageProps.message).toBeDefined() }) // those tests use `fetch` instead of `page.goto` intentionally to avoid potential client rendering diff --git a/tests/fixtures/middleware-i18n/middleware.js b/tests/fixtures/middleware-i18n/middleware.js index 0057764f47..3462214f1d 100644 --- a/tests/fixtures/middleware-i18n/middleware.js +++ b/tests/fixtures/middleware-i18n/middleware.js @@ -8,6 +8,26 @@ export async function middleware(request) { return NextResponse.next() } + if (url.pathname.startsWith('/link/next')) { + return NextResponse.next({ + headers: { + 'x-middleware-test': 'link-next', + }, + }) + } + + if (url.pathname.startsWith('/link/rewrite-me')) { + const rewriteUrl = new URL( + url.pathname.replace('/link/rewrite-me', '/link/rewrite-target'), + url, + ) + return NextResponse.rewrite(rewriteUrl, { + headers: { + 'x-middleware-test': 'link-rewrite', + }, + }) + } + if (url.pathname === '/old-home') { if (url.searchParams.get('override') === 'external') { return Response.redirect('https://example.vercel.sh') diff --git a/tests/fixtures/middleware-i18n/pages/link/index.js b/tests/fixtures/middleware-i18n/pages/link/index.js new file mode 100644 index 0000000000..73699d73a1 --- /dev/null +++ b/tests/fixtures/middleware-i18n/pages/link/index.js @@ -0,0 +1,67 @@ +import Link from 'next/link' + +export default function Page() { + return ( +
+

Page with Links

+
    +
  • + NextResponse.next() +
      +
    • + + getServerSideProps + +
    • + +
    • + + getStaticProps + +
    • + +
    • + + fullyStatic + +
    • +
    +
  • +
  • + NextResponse.rewrite() +
      +
    • + + getServerSideProps + +
    • + +
    • + + getStaticProps + +
    • + +
    • + + fullyStatic + +
    • +
    +
  • +
+
+ ) +} diff --git a/tests/fixtures/middleware-i18n/pages/link/next-fullystatic.js b/tests/fixtures/middleware-i18n/pages/link/next-fullystatic.js new file mode 100644 index 0000000000..f8461a24ef --- /dev/null +++ b/tests/fixtures/middleware-i18n/pages/link/next-fullystatic.js @@ -0,0 +1,7 @@ +export default function Page() { + return ( +
+

fully static page

+
+ ) +} diff --git a/tests/fixtures/middleware-i18n/pages/link/next-getserversideprops.js b/tests/fixtures/middleware-i18n/pages/link/next-getserversideprops.js new file mode 100644 index 0000000000..f177bf770b --- /dev/null +++ b/tests/fixtures/middleware-i18n/pages/link/next-getserversideprops.js @@ -0,0 +1,15 @@ +export default function Page() { + return ( +
+

+ getServerSideProps page +

+
+ ) +} + +export function getServerSideProps() { + return { + props: {}, + } +} diff --git a/tests/fixtures/middleware-i18n/pages/link/next-getstaticprops.js b/tests/fixtures/middleware-i18n/pages/link/next-getstaticprops.js new file mode 100644 index 0000000000..847a6f626b --- /dev/null +++ b/tests/fixtures/middleware-i18n/pages/link/next-getstaticprops.js @@ -0,0 +1,15 @@ +export default function Page() { + return ( +
+

+ getStaticProps page +

+
+ ) +} + +export function getStaticProps() { + return { + props: {}, + } +} diff --git a/tests/fixtures/middleware-i18n/pages/link/rewrite-target-fullystatic.js b/tests/fixtures/middleware-i18n/pages/link/rewrite-target-fullystatic.js new file mode 100644 index 0000000000..8de98fef67 --- /dev/null +++ b/tests/fixtures/middleware-i18n/pages/link/rewrite-target-fullystatic.js @@ -0,0 +1,7 @@ +export default function Page() { + return ( +
+

fully static page

+
+ ) +} diff --git a/tests/fixtures/middleware-i18n/pages/link/rewrite-target-getserversideprops.js b/tests/fixtures/middleware-i18n/pages/link/rewrite-target-getserversideprops.js new file mode 100644 index 0000000000..b621a86d93 --- /dev/null +++ b/tests/fixtures/middleware-i18n/pages/link/rewrite-target-getserversideprops.js @@ -0,0 +1,15 @@ +export default function Page() { + return ( +
+

+ getServerSideProps page +

+
+ ) +} + +export function getServerSideProps() { + return { + props: {}, + } +} diff --git a/tests/fixtures/middleware-i18n/pages/link/rewrite-target-getstaticprops.js b/tests/fixtures/middleware-i18n/pages/link/rewrite-target-getstaticprops.js new file mode 100644 index 0000000000..60ede7e031 --- /dev/null +++ b/tests/fixtures/middleware-i18n/pages/link/rewrite-target-getstaticprops.js @@ -0,0 +1,15 @@ +export default function Page() { + return ( +
+

+ getStaticProps page +

+
+ ) +} + +export function getStaticProps() { + return { + props: {}, + } +} diff --git a/tests/fixtures/middleware-pages/middleware.js b/tests/fixtures/middleware-pages/middleware.js index 1dbf1eaf26..a89a491a8c 100644 --- a/tests/fixtures/middleware-pages/middleware.js +++ b/tests/fixtures/middleware-pages/middleware.js @@ -8,6 +8,26 @@ export async function middleware(request) { return NextResponse.next() } + if (url.pathname.startsWith('/link/next')) { + return NextResponse.next({ + headers: { + 'x-middleware-test': 'link-next', + }, + }) + } + + if (url.pathname.startsWith('/link/rewrite-me')) { + const rewriteUrl = new URL( + url.pathname.replace('/link/rewrite-me', '/link/rewrite-target'), + url, + ) + return NextResponse.rewrite(rewriteUrl, { + headers: { + 'x-middleware-test': 'link-rewrite', + }, + }) + } + if (request.headers.get('x-prerender-revalidate')) { return NextResponse.next({ headers: { 'x-middleware': 'hi' }, diff --git a/tests/fixtures/middleware-pages/pages/link/index.js b/tests/fixtures/middleware-pages/pages/link/index.js new file mode 100644 index 0000000000..73699d73a1 --- /dev/null +++ b/tests/fixtures/middleware-pages/pages/link/index.js @@ -0,0 +1,67 @@ +import Link from 'next/link' + +export default function Page() { + return ( +
+

Page with Links

+
    +
  • + NextResponse.next() +
      +
    • + + getServerSideProps + +
    • + +
    • + + getStaticProps + +
    • + +
    • + + fullyStatic + +
    • +
    +
  • +
  • + NextResponse.rewrite() +
      +
    • + + getServerSideProps + +
    • + +
    • + + getStaticProps + +
    • + +
    • + + fullyStatic + +
    • +
    +
  • +
+
+ ) +} diff --git a/tests/fixtures/middleware-pages/pages/link/next-fullystatic.js b/tests/fixtures/middleware-pages/pages/link/next-fullystatic.js new file mode 100644 index 0000000000..f8461a24ef --- /dev/null +++ b/tests/fixtures/middleware-pages/pages/link/next-fullystatic.js @@ -0,0 +1,7 @@ +export default function Page() { + return ( +
+

fully static page

+
+ ) +} diff --git a/tests/fixtures/middleware-pages/pages/link/next-getserversideprops.js b/tests/fixtures/middleware-pages/pages/link/next-getserversideprops.js new file mode 100644 index 0000000000..f177bf770b --- /dev/null +++ b/tests/fixtures/middleware-pages/pages/link/next-getserversideprops.js @@ -0,0 +1,15 @@ +export default function Page() { + return ( +
+

+ getServerSideProps page +

+
+ ) +} + +export function getServerSideProps() { + return { + props: {}, + } +} diff --git a/tests/fixtures/middleware-pages/pages/link/next-getstaticprops.js b/tests/fixtures/middleware-pages/pages/link/next-getstaticprops.js new file mode 100644 index 0000000000..847a6f626b --- /dev/null +++ b/tests/fixtures/middleware-pages/pages/link/next-getstaticprops.js @@ -0,0 +1,15 @@ +export default function Page() { + return ( +
+

+ getStaticProps page +

+
+ ) +} + +export function getStaticProps() { + return { + props: {}, + } +} diff --git a/tests/fixtures/middleware-pages/pages/link/rewrite-target-fullystatic.js b/tests/fixtures/middleware-pages/pages/link/rewrite-target-fullystatic.js new file mode 100644 index 0000000000..8de98fef67 --- /dev/null +++ b/tests/fixtures/middleware-pages/pages/link/rewrite-target-fullystatic.js @@ -0,0 +1,7 @@ +export default function Page() { + return ( +
+

fully static page

+
+ ) +} diff --git a/tests/fixtures/middleware-pages/pages/link/rewrite-target-getserversideprops.js b/tests/fixtures/middleware-pages/pages/link/rewrite-target-getserversideprops.js new file mode 100644 index 0000000000..b621a86d93 --- /dev/null +++ b/tests/fixtures/middleware-pages/pages/link/rewrite-target-getserversideprops.js @@ -0,0 +1,15 @@ +export default function Page() { + return ( +
+

+ getServerSideProps page +

+
+ ) +} + +export function getServerSideProps() { + return { + props: {}, + } +} diff --git a/tests/fixtures/middleware-pages/pages/link/rewrite-target-getstaticprops.js b/tests/fixtures/middleware-pages/pages/link/rewrite-target-getstaticprops.js new file mode 100644 index 0000000000..60ede7e031 --- /dev/null +++ b/tests/fixtures/middleware-pages/pages/link/rewrite-target-getstaticprops.js @@ -0,0 +1,15 @@ +export default function Page() { + return ( +
+

+ getStaticProps page +

+
+ ) +} + +export function getStaticProps() { + return { + props: {}, + } +} diff --git a/tests/utils/create-e2e-fixture.ts b/tests/utils/create-e2e-fixture.ts index 5d17341700..d7af065d21 100644 --- a/tests/utils/create-e2e-fixture.ts +++ b/tests/utils/create-e2e-fixture.ts @@ -333,6 +333,7 @@ export const fixtureFactories = { pnpm: () => createE2EFixture('pnpm', { packageManger: 'pnpm' }), bun: () => createE2EFixture('simple', { packageManger: 'bun' }), middleware: () => createE2EFixture('middleware'), + middlewareI18n: () => createE2EFixture('middleware-i18n'), middlewareI18nExcludedPaths: () => createE2EFixture('middleware-i18n-excluded-paths'), middlewareOg: () => createE2EFixture('middleware-og'), middlewarePages: () => createE2EFixture('middleware-pages'),