mirror of
https://github.com/cloudflare/vinext.git
synced 2026-09-14 19:04:59 +08:00
a3580bc79b
* refactor(metadata): share route pattern helpers Metadata route handling repeated the same dynamic segment normalization and pattern matching in several places. That made the file-convention metadata path harder to review and kept generic routing behavior inside metadata-specific modules. Move route pattern parsing, matching, and dynamic segment filling into a normal routing module. Reuse it from metadata route build data, file-based metadata, metadata route response matching, and app RSC intercept matching, then consolidate duplicated icon descriptor normalization in the metadata server and shim code. Add focused route-pattern tests and keep the existing metadata and shim tests covering consumer behavior. * fix(routing): preserve prototype-named route params The shared route pattern matcher used a normal object for matched params, unlike the previous metadata and App RSC matchers. A route parameter named __proto__ could mutate the params object's prototype instead of being stored as an own value. Restore a null-prototype params map and add regression coverage for single and catch-all prototype-named route params. * fix(routing): preserve literal intercept target segments
70 lines
2.4 KiB
TypeScript
70 lines
2.4 KiB
TypeScript
import { describe, expect, it } from "vite-plus/test";
|
|
import {
|
|
fillRoutePatternSegments,
|
|
matchRoutePattern,
|
|
routePattern,
|
|
routePatternParts,
|
|
} from "../packages/vinext/src/routing/route-pattern.js";
|
|
|
|
describe("route pattern helpers", () => {
|
|
it("normalizes app route segments into vinext pattern parts", () => {
|
|
expect(routePatternParts("/docs/[section]/[[...slug]]/icon")).toEqual([
|
|
"docs",
|
|
":section",
|
|
":slug*",
|
|
"icon",
|
|
]);
|
|
expect(routePattern("/shop/[...slug]/opengraph-image")).toBe("/shop/:slug+/opengraph-image");
|
|
expect(routePattern("/")).toBe("");
|
|
});
|
|
|
|
it("fills dynamic route segments from params and rejects incomplete paths", () => {
|
|
expect(
|
|
fillRoutePatternSegments("/docs/[section]/[[...slug]]/icon", {
|
|
section: "api",
|
|
}),
|
|
).toBe("/docs/api/icon");
|
|
expect(
|
|
fillRoutePatternSegments("/docs/[section]/[[...slug]]/icon", {
|
|
section: "api",
|
|
slug: ["routing", "metadata"],
|
|
}),
|
|
).toBe("/docs/api/routing/metadata/icon");
|
|
expect(fillRoutePatternSegments("/docs/[...slug]/icon", {})).toBeNull();
|
|
expect(fillRoutePatternSegments("/docs/[section]/icon", { section: ["a", "b"] })).toBeNull();
|
|
});
|
|
|
|
it("matches dynamic pattern parts with catch-all segments before suffixes", () => {
|
|
expect(
|
|
matchRoutePattern(
|
|
["metadata-multi-catchall", "a", "b", "icon"],
|
|
["metadata-multi-catchall", ":slug+", "icon"],
|
|
),
|
|
).toEqual({ slug: ["a", "b"] });
|
|
expect(matchRoutePattern(["shop"], ["shop", ":slug*"])).toEqual({});
|
|
expect(
|
|
matchRoutePattern(
|
|
["metadata-multi-catchall", "icon"],
|
|
["metadata-multi-catchall", ":slug+", "icon"],
|
|
),
|
|
).toBeNull();
|
|
});
|
|
|
|
it("stores prototype-named params as own values", () => {
|
|
const singleParam = matchRoutePattern(["first"], [":__proto__"]);
|
|
|
|
expect(Object.hasOwn(singleParam ?? {}, "__proto__")).toBe(true);
|
|
expect(singleParam?.__proto__).toBe("first");
|
|
|
|
const catchAllParam = matchRoutePattern(["first", "second", "icon"], [":__proto__+", "icon"]);
|
|
|
|
expect(Object.hasOwn(catchAllParam ?? {}, "__proto__")).toBe(true);
|
|
expect(catchAllParam?.__proto__).toEqual(["first", "second"]);
|
|
});
|
|
|
|
it("treats literal route segments ending in pattern markers as literals", () => {
|
|
expect(matchRoutePattern(["docs+", "icon"], ["docs+", "icon"])).toEqual({});
|
|
expect(matchRoutePattern(["docs", "icon"], ["docs+", "icon"])).toBeNull();
|
|
});
|
|
});
|