mirror of
https://github.com/oven-sh/bun
synced 2026-02-02 15:08:46 +00:00
136 lines
5.2 KiB
TypeScript
136 lines
5.2 KiB
TypeScript
import { frameworkRouterInternals } from "bun:internal-for-testing";
|
|
import { describe, expect, test } from "bun:test";
|
|
import { tempDirWithFiles } from "harness";
|
|
import path from "path";
|
|
|
|
const { parseRoutePattern, FrameworkRouter } = frameworkRouterInternals;
|
|
|
|
const testRoutePattern = (style: string) => {
|
|
// The 'expected' is a one-off string serialization that is only used for testing.
|
|
// Params are serialized as ":param", catch all as ":*param", and optional catch all as ":*?param".
|
|
const fn = (pattern: string, expected: string, kind: "page" | "layout" | "extra" = "page") => {
|
|
test(`[${style}] pass: ${JSON.stringify(pattern)}`, () => {
|
|
const result = parseRoutePattern(style, pattern);
|
|
if (result === null) {
|
|
throw new Error("Parser said this file is not a route");
|
|
}
|
|
expect(result.kind, "expected route kind to match").toBe(kind);
|
|
expect(result.pattern, "expected route pattern to match").toBe(expected);
|
|
});
|
|
};
|
|
fn.fails = (pattern: string, msg: string) => {
|
|
test(`[${style}] error: ${JSON.stringify(pattern)}`, () => {
|
|
expect(() => parseRoutePattern(style, pattern)).toThrow(msg);
|
|
});
|
|
};
|
|
fn.isNull = (pattern: string) => {
|
|
test(`[${style}] ignore: ${JSON.stringify(pattern)}`, () => {
|
|
expect(parseRoutePattern(style, pattern)).toBeNull();
|
|
});
|
|
};
|
|
return fn;
|
|
};
|
|
|
|
describe("pattern parse", () => {
|
|
const testPages = testRoutePattern("nextjs-pages");
|
|
testPages("/index.tsx", "", "page");
|
|
testPages("/_layout.tsx", "", "layout");
|
|
testPages("/subdir/index.tsx", "/subdir", "page");
|
|
testPages("/subdir/_layout.tsx", "/subdir", "layout");
|
|
testPages("/subdir/[page].tsx", "/subdir/:page", "page");
|
|
testPages("/[user]/posts.tsx", "/:user/posts", "page");
|
|
testPages("/[user]/_layout.tsx", "/:user", "layout");
|
|
testPages("/subdir/[page]/[other].tsx", "/subdir/:page/:other", "page");
|
|
testPages("/[page]/[other]/index.js", "/:page/:other", "page");
|
|
testPages("/[...data].js", "/:*data", "page");
|
|
testPages("/[[...data]].js", "/:*?data", "page");
|
|
testPages("/[...data]/index.tsx", "/:*data", "page");
|
|
testPages("/[[...data]]/index.jsx", "/:*?data", "page");
|
|
testPages("/hello/[...data]/index.tsx", "/hello/:*data", "page");
|
|
testPages("/hello/[[...data]]/index.jsx", "/hello/:*?data", "page");
|
|
testPages("/[...data]/_layout.tsx", "/:*data", "layout");
|
|
testPages("/[[...data]]/_layout.jsx", "/:*?data", "layout");
|
|
testPages("/hello/[...data]/_layout.tsx", "/hello/:*data", "layout");
|
|
testPages("/hello/[[...data]]/_layout.jsx", "/hello/:*?data", "layout");
|
|
// Parenthesis is the error location (column:length)
|
|
testPages.fails("/subdir/[", 'Missing "]" to match this route parameter (8:1)');
|
|
testPages.fails("/subdir/[a", 'Missing "]" to match this route parameter (8:2)');
|
|
testPages.fails("/subdir/[page.tsx", 'Missing "]" to match this route parameter (8:9)');
|
|
testPages.fails("/subdir/[]/hello", "Parameter needs a name (8:2)");
|
|
testPages.fails("/subdir/[.hello]-hello.tsx", 'Parameter name cannot start with "." (use "..." for catch-all) (8:8)');
|
|
testPages.fails(
|
|
"/subdir/[..hello]-hello.tsx",
|
|
'Parameter name cannot start with "." (use "..." for catch-all) (8:9)',
|
|
);
|
|
testPages.fails("/subdir/[...hello]-hello.tsx", "Parameters must take up the entire file name (8:10)");
|
|
testPages.fails("/subdir/[...hello]/bar.tsx", "Catch-all parameter must be at the end of a route (8:10)");
|
|
testPages.fails(
|
|
"/hello/[[optional_param]]/_layout.tsx",
|
|
'Optional parameters can only be catch-all (change to "[[...optional_param]]" or remove extra brackets) (7:18)',
|
|
);
|
|
|
|
const testApp = testRoutePattern("nextjs-app-ui");
|
|
testApp("/page.tsx", "", "page");
|
|
testApp("/layout.tsx", "", "layout");
|
|
testApp("/route/[param]/page.tsx", "/route/:param", "page");
|
|
testApp("/route/(group)/page.tsx", "/route/(group)", "page");
|
|
testApp("/route/[param]/not-found.tsx", "/route/:param", "extra");
|
|
testApp.isNull("/route/_layout.tsx");
|
|
});
|
|
|
|
test("discovers from filesystem paths", () => {
|
|
const dir = tempDirWithFiles("fsr", {
|
|
"hello.tsx": "1",
|
|
"meow/_layout.tsx": "1",
|
|
"meow/bark/[param]/hello.tsx": "1",
|
|
"[world].tsx": "1",
|
|
});
|
|
const router = new FrameworkRouter({ root: dir, style: "nextjs-pages" });
|
|
expect(router.toJSON()).toEqual({
|
|
part: "/",
|
|
page: null,
|
|
layout: null,
|
|
children: [
|
|
{
|
|
part: "/:world",
|
|
page: path.join(dir, "[world].tsx"),
|
|
layout: null,
|
|
children: [],
|
|
},
|
|
{
|
|
part: "/meow",
|
|
page: null,
|
|
layout: path.join(dir, "meow/_layout.tsx"),
|
|
children: [
|
|
{
|
|
part: "/bark",
|
|
page: null,
|
|
layout: null,
|
|
children: [
|
|
{
|
|
part: "/:param",
|
|
page: null,
|
|
layout: null,
|
|
children: [
|
|
{
|
|
part: "/hello",
|
|
page: path.join(dir, "meow/bark/[param]/hello.tsx"),
|
|
layout: null,
|
|
children: [],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
part: "/hello",
|
|
page: path.join(dir, "hello.tsx"),
|
|
layout: null,
|
|
children: [],
|
|
},
|
|
],
|
|
});
|
|
});
|