mirror of
https://github.com/cloudflare/vinext.git
synced 2026-09-14 19:04:59 +08:00
4688fa9c16
* fix(app-router): prerender cacheComponents root-param fallback shells PR 1 of 4: core model + build integration. Model: app-ppr-fallback-shell, pregenerated-concrete-paths, prerender-manifest Build: prerender/run-prerender fallback shell artifact generation Tests: createAppPprFallbackShells, pregenerated-concrete-paths core * test(prerender-route-params): add unit tests for fallbackParamNames and validation Adds focused unit tests for the new prerender-route-params logic introduced in this PR: - encodePrerenderRouteParams round-trips fallbackParamNames and omits them when empty. - prerenderRouteParamsPayloadMatchesRoute rejects payloads whose fallbackParamNames contain unknown params or duplicates. - prerenderRouteParamsPayloadMatchesRoute returns false for valid fallback-shell matches (only exact matches are accepted today). Also adds a comment in app-ppr-fallback-shell.ts noting that placeholder brackets in shell pathnames are percent-encoded by new URL() at fetch time, so the render path must supply params via the prerender-params header rather than URL matching. * docs(prerender): clarify BUILD_ID reuse and fallback-shell param-resolution seams Address review feedback on PR #1702: - Explain why run-prerender reuses the built BUILD_ID instead of re-resolving one (config.buildId feeds prerendered-output identity). - Note that the fallback-shell prerender-params header path described in app-ppr-fallback-shell.ts is honored by the #1715 follow-up; this generation-only PR still resolves params from the URL placeholder. --------- Co-authored-by: James <james@eli.cx>
130 lines
4.1 KiB
TypeScript
130 lines
4.1 KiB
TypeScript
import { describe, expect, it } from "vite-plus/test";
|
|
import {
|
|
createAppPprFallbackShell,
|
|
createAppPprFallbackShells,
|
|
rewriteAppPprFallbackShellHtmlNavigation,
|
|
} from "../packages/vinext/src/server/app-ppr-fallback-shell.js";
|
|
|
|
describe("createAppPprFallbackShell", () => {
|
|
it("builds a cacheComponents fallback shell from known root params and missing child params", () => {
|
|
const shell = createAppPprFallbackShell(
|
|
{
|
|
params: ["locale", "slug"],
|
|
pattern: "/:locale/blog/:slug",
|
|
rootParamNames: ["locale"],
|
|
},
|
|
{ locale: "en", slug: "new-post" },
|
|
);
|
|
|
|
expect(shell).toEqual({
|
|
fallbackParamNames: ["slug"],
|
|
pathname: "/en/blog/[slug]",
|
|
params: { locale: "en", slug: "[slug]" },
|
|
});
|
|
});
|
|
|
|
it("preserves catch-all placeholder shape in the shell path and params", () => {
|
|
const shell = createAppPprFallbackShell(
|
|
{
|
|
params: ["locale", "slug"],
|
|
pattern: "/:locale/docs/:slug+",
|
|
rootParamNames: ["locale"],
|
|
},
|
|
{ locale: "fr", slug: ["guides", "intro"] },
|
|
);
|
|
|
|
expect(shell).toEqual({
|
|
fallbackParamNames: ["slug"],
|
|
pathname: "/fr/docs/[...slug]",
|
|
params: { locale: "fr", slug: ["[...slug]"] },
|
|
});
|
|
});
|
|
|
|
it("preserves optional catch-all placeholder shape in the shell path and params", () => {
|
|
const shell = createAppPprFallbackShell(
|
|
{
|
|
params: ["locale", "slug"],
|
|
pattern: "/:locale/docs/:slug*",
|
|
rootParamNames: ["locale"],
|
|
},
|
|
{ locale: "fr", slug: ["guides", "intro"] },
|
|
);
|
|
|
|
expect(shell).toEqual({
|
|
fallbackParamNames: ["slug"],
|
|
pathname: "/fr/docs/[[...slug]]",
|
|
params: { locale: "fr", slug: ["[[...slug]]"] },
|
|
});
|
|
});
|
|
|
|
it("orders nested fallback shells from most specific child prefix to root-only", () => {
|
|
const shells = createAppPprFallbackShells(
|
|
{
|
|
params: ["locale", "category", "slug"],
|
|
pattern: "/:locale/category/:category/post/:slug",
|
|
rootParamNames: ["locale"],
|
|
},
|
|
{ locale: "en", category: "news", slug: "launch" },
|
|
);
|
|
|
|
expect(shells).toEqual([
|
|
{
|
|
fallbackParamNames: ["slug"],
|
|
pathname: "/en/category/news/post/[slug]",
|
|
params: { locale: "en", category: "news", slug: "[slug]" },
|
|
},
|
|
{
|
|
fallbackParamNames: ["category", "slug"],
|
|
pathname: "/en/category/[category]/post/[slug]",
|
|
params: { locale: "en", category: "[category]", slug: "[slug]" },
|
|
},
|
|
]);
|
|
});
|
|
|
|
it("does not create a fallback shell without a known root-param boundary", () => {
|
|
expect(
|
|
createAppPprFallbackShell(
|
|
{
|
|
params: ["locale", "slug"],
|
|
pattern: "/:locale/blog/:slug",
|
|
rootParamNames: [],
|
|
},
|
|
{ locale: "en", slug: "new-post" },
|
|
),
|
|
).toBeNull();
|
|
});
|
|
|
|
it("does not create a fallback shell when the matched request lacks a root param", () => {
|
|
expect(
|
|
createAppPprFallbackShell(
|
|
{
|
|
params: ["locale", "slug"],
|
|
pattern: "/:locale/blog/:slug",
|
|
rootParamNames: ["locale"],
|
|
},
|
|
{ slug: "new-post" },
|
|
),
|
|
).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe("rewriteAppPprFallbackShellHtmlNavigation", () => {
|
|
it("patches cached fallback-shell HTML with the actual request navigation metadata", () => {
|
|
const html = rewriteAppPprFallbackShellHtmlNavigation({
|
|
html: "<html><head><title>x</title></head><body>shell</body></html>",
|
|
params: { locale: "en", slug: "new-post" },
|
|
pathname: "/en/blog/new-post",
|
|
searchParams: new URLSearchParams([["preview", "1"]]),
|
|
});
|
|
|
|
expect(html).toContain('params:{"locale":"en","slug":"new-post"}');
|
|
expect(html).toContain('"pathname":"/en/blog/new-post"');
|
|
expect(html).toContain('"searchParams":[["preview","1"]]');
|
|
const paramsIndex = html.indexOf('params:{"locale":"en","slug":"new-post"}');
|
|
const headCloseIndex = html.indexOf("</head>");
|
|
expect(paramsIndex).toBeGreaterThanOrEqual(0);
|
|
expect(headCloseIndex).toBeGreaterThanOrEqual(0);
|
|
expect(paramsIndex).toBeLessThan(headCloseIndex);
|
|
});
|
|
});
|