diff --git a/tests/routing.test.ts b/tests/routing.test.ts index 8a27b1b2b2..7831c173e2 100644 --- a/tests/routing.test.ts +++ b/tests/routing.test.ts @@ -2111,15 +2111,32 @@ describe("pagesRouter - hyphenated param names", () => { }); describe("pagesRouter - dotted/colon param names (Next.js parity)", () => { - it("discovers dynamic segments with dots and colons", async () => { + it("discovers dynamic segments with dots", async () => { await withTempDir("vinext-pages-dotted-param-", async (tmpDir) => { const pagesDir = path.join(tmpDir, "pages"); await mkdir(path.join(pagesDir, "products"), { recursive: true }); - await mkdir(path.join(pagesDir, "repos"), { recursive: true }); await writeFile( path.join(pagesDir, "products", "[variant.id].tsx"), "export default function Page() { return null; }", ); + + invalidateRouteCache(pagesDir); + const routes = await pagesRouter(pagesDir); + const patterns = routes.map((r) => r.pattern); + + expect(patterns).toContain("/products/:variant.id"); + invalidateRouteCache(pagesDir); + }); + }); + + // `:` is a reserved character in Windows filenames — it delimits an NTFS + // alternate data stream, so writing `[repo:name].tsx` there silently produces + // a `[repo` file instead. The scenario is unreachable on Windows, so gate it + // to POSIX and keep the assertion unconditional. + it.runIf(process.platform !== "win32")("discovers dynamic segments with colons", async () => { + await withTempDir("vinext-pages-colon-param-", async (tmpDir) => { + const pagesDir = path.join(tmpDir, "pages"); + await mkdir(path.join(pagesDir, "repos"), { recursive: true }); await writeFile( path.join(pagesDir, "repos", "[repo:name].tsx"), "export default function Page() { return null; }", @@ -2129,7 +2146,6 @@ describe("pagesRouter - dotted/colon param names (Next.js parity)", () => { const routes = await pagesRouter(pagesDir); const patterns = routes.map((r) => r.pattern); - expect(patterns).toContain("/products/:variant.id"); expect(patterns).toContain("/repos/:repo:name"); invalidateRouteCache(pagesDir); }); @@ -2155,7 +2171,10 @@ describe("pagesRouter - dotted/colon param names (Next.js parity)", () => { }); }); - it("skips routes whose param names end in + or * (would collide with internal modifiers)", async () => { + // The + and * cases are split because `*` is a reserved character in Windows + // filenames — a literal `[id*].tsx` file can't exist there, so that scenario + // is gated to POSIX (where it's reachable). + it("skips routes whose param names end in + (would collide with internal modifiers)", async () => { await withTempDir("vinext-pages-skip-plus-", async (tmpDir) => { const pagesDir = path.join(tmpDir, "pages"); await mkdir(pagesDir, { recursive: true }); @@ -2163,10 +2182,6 @@ describe("pagesRouter - dotted/colon param names (Next.js parity)", () => { path.join(pagesDir, "[id+].tsx"), "export default function Page() { return null; }", ); - await writeFile( - path.join(pagesDir, "[id*].tsx"), - "export default function Page() { return null; }", - ); invalidateRouteCache(pagesDir); const routes = await pagesRouter(pagesDir); @@ -2176,4 +2191,25 @@ describe("pagesRouter - dotted/colon param names (Next.js parity)", () => { invalidateRouteCache(pagesDir); }); }); + + it.runIf(process.platform !== "win32")( + "skips routes whose param names end in * (would collide with internal modifiers)", + async () => { + await withTempDir("vinext-pages-skip-star-", async (tmpDir) => { + const pagesDir = path.join(tmpDir, "pages"); + await mkdir(pagesDir, { recursive: true }); + await writeFile( + path.join(pagesDir, "[id*].tsx"), + "export default function Page() { return null; }", + ); + + invalidateRouteCache(pagesDir); + const routes = await pagesRouter(pagesDir); + + // Skipped entirely to avoid ambiguity with internal :name+ / :name* modifiers + expect(routes).toHaveLength(0); + invalidateRouteCache(pagesDir); + }); + }, + ); });