Files
cloudflare__vinext/tests/app-prerender-static-params.test.ts
James Anderson 8759b65c86 fix(build): validate CDN warm discovery and deploy inputs (#3057)
* fix(build): validate CDN warm path params

* fix(build): exclude handlers from CDN warm paths

* fix(build): resolve all CDN warm path owners

* fix(build): preserve exact CDN warm path identity

* fix(build): preserve encoded Pages warm paths

* test(cloudflare): preserve encoded warm request URLs

* fix(build): reject unsafe Pages warm paths

* fix(router): match localized warm paths case-insensitively

* fix(cache): honor build identity adapter capability

* fix(build): reject normalized CDN warm paths

* fix(build): discover dynamic MDX warm paths

* fix(build): skip routes without static param generators

* fix(cloudflare): validate warmup promotion delay
2026-08-24 23:03:15 +01:00

96 lines
4.1 KiB
TypeScript

import { describe, expect, it, vi } from "vite-plus/test";
import { createAppPrerenderStaticParamsResolver } from "../packages/vinext/src/server/app-prerender-static-params.js";
describe("createAppPrerenderStaticParamsResolver", () => {
it("returns null when there are no sources at all", () => {
expect(createAppPrerenderStaticParamsResolver([])).toBeNull();
// An eager source that is not a function (e.g. `mod?.generateStaticParams`
// where the module has none) and no lazy sources → still null.
expect(createAppPrerenderStaticParamsResolver([undefined])).toBeNull();
});
it("resolves an eager generateStaticParams source", async () => {
const fn = () => [{ id: "a" }, { id: "b" }];
const resolver = createAppPrerenderStaticParamsResolver([fn]);
expect(resolver).not.toBeNull();
await expect(resolver!({ params: {} })).resolves.toEqual([{ id: "a" }, { id: "b" }]);
});
it("loads a lazy page source on demand and reads its generateStaticParams", async () => {
const load = vi.fn(async () => ({
generateStaticParams: () => [{ slug: "x" }, { slug: "y" }],
}));
const resolver = createAppPrerenderStaticParamsResolver([{ load }]);
expect(resolver).not.toBeNull();
// Not loaded until the resolver is actually invoked (prerender time).
expect(load).not.toHaveBeenCalled();
await expect(resolver!({ params: {} })).resolves.toEqual([{ slug: "x" }, { slug: "y" }]);
expect(load).toHaveBeenCalledTimes(1);
// Memoized: a second call does not re-import.
await resolver!({ params: {} });
expect(load).toHaveBeenCalledTimes(1);
});
it("returns the null sentinel when a lazy page has no generateStaticParams", async () => {
// A lazy page module with no generateStaticParams export. The resolver is
// non-null (there is a source) but must yield null so the prerender driver
// treats the route as having no static params (skip / output:export error).
const resolver = createAppPrerenderStaticParamsResolver([
{ load: async () => ({ default: () => null }) },
]);
expect(resolver).not.toBeNull();
await expect(resolver!({ params: {} })).resolves.toBeNull();
});
it("composes an eager layout source with a lazy page source", async () => {
const layout = () => [{ lang: "en" }, { lang: "fr" }];
const load = async () => ({ generateStaticParams: () => [{ slug: "post" }] });
const resolver = createAppPrerenderStaticParamsResolver([layout, { load }]);
await expect(resolver!({ params: {} })).resolves.toEqual([
{ lang: "en", slug: "post" },
{ lang: "fr", slug: "post" },
]);
});
it("composes sources in declared order regardless of eager/lazy kind", async () => {
// Lazy source first, eager second: composition order must follow `sources`
// order, not be reordered to eager-then-lazy.
const resolver = createAppPrerenderStaticParamsResolver([
{ load: async () => ({ generateStaticParams: () => [{ a: "1" }, { a: "2" }] }) },
() => [{ b: "x" }],
]);
await expect(resolver!({ params: {} })).resolves.toEqual([
{ a: "1", b: "x" },
{ a: "2", b: "x" },
]);
});
it("surfaces malformed results during CDN warm path discovery", async () => {
const previous = process.env.__VINEXT_PRERENDER_PATH_DISCOVERY;
process.env.__VINEXT_PRERENDER_PATH_DISCOVERY = "1";
try {
const nonArray = createAppPrerenderStaticParamsResolver([() => null]);
await expect(nonArray!({ params: {} })).rejects.toThrow(
"generateStaticParams must return an array",
);
const nonObjectEntry = createAppPrerenderStaticParamsResolver([() => ["slug"]]);
await expect(nonObjectEntry!({ params: {} })).rejects.toThrow(
"generateStaticParams must return an array of objects",
);
const nonPlainEntry = createAppPrerenderStaticParamsResolver([() => [new Date(0)]]);
await expect(nonPlainEntry!({ params: {} })).rejects.toThrow(
"generateStaticParams must return an array of objects",
);
} finally {
if (previous === undefined) delete process.env.__VINEXT_PRERENDER_PATH_DISCOVERY;
else process.env.__VINEXT_PRERENDER_PATH_DISCOVERY = previous;
}
});
});