mirror of
https://github.com/cloudflare/vinext.git
synced 2026-09-14 19:04:59 +08:00
6b7e7557a9
* fix: support revalidate = false for App route segment config (#1041) - Build-time: extractExportConstNumber now matches false and returns Infinity - Classifiers: classifyLayoutSegmentConfig and classifyAppRoute correctly classify revalidate = false as static (indefinite caching) - Runtime: getAppRouteHandlerRevalidateSeconds returns Infinity for false, enabling ISR cache gates that emit STATIC cache-control instead of SWR - Template: RSC entry resolves revalidate = false from page exports as Infinity - Cache-Control: buildRouteHandlerCacheControl emits STATIC cache-control when revalidateSeconds is Infinity, matching the page-path behavior Ported from Next.js segment config validation: https://github.com/vercel/next.js/blob/canary/packages/next/src/build/segment-config/app/app-segment-config.ts * fix: exclude Infinity from route handler ISR read/write gates * Update packages/vinext/src/build/report.ts Co-authored-by: ask-bonk[bot] <249159057+ask-bonk[bot]@users.noreply.github.com> --------- Co-authored-by: ask-bonk[bot] <249159057+ask-bonk[bot]@users.noreply.github.com>
219 lines
5.8 KiB
TypeScript
219 lines
5.8 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import {
|
|
resolveAppPageFetchCacheMode,
|
|
resolveAppPageSegmentConfig,
|
|
} from "../packages/vinext/src/server/app-segment-config.js";
|
|
|
|
describe("resolveAppPageSegmentConfig", () => {
|
|
it("returns defaults when no segment config is present", () => {
|
|
expect(resolveAppPageSegmentConfig({})).toEqual({
|
|
revalidateSeconds: null,
|
|
});
|
|
});
|
|
|
|
it("merges route segment config from layouts and the page", () => {
|
|
expect(
|
|
resolveAppPageSegmentConfig({
|
|
layouts: [
|
|
{ revalidate: 120, dynamicParams: false, fetchCache: "default-cache" },
|
|
{ dynamic: "error", revalidate: 60 },
|
|
],
|
|
page: { dynamic: "force-static", revalidate: 300 },
|
|
}),
|
|
).toEqual({
|
|
dynamicConfig: "force-static",
|
|
dynamicParamsConfig: false,
|
|
fetchCache: "default-cache",
|
|
revalidateSeconds: 60,
|
|
});
|
|
});
|
|
|
|
it("treats force-dynamic from any effective segment as revalidate zero", () => {
|
|
expect(
|
|
resolveAppPageSegmentConfig({
|
|
layouts: [{ revalidate: 60 }],
|
|
page: { dynamic: "force-dynamic" },
|
|
}),
|
|
).toEqual({
|
|
dynamicConfig: "force-dynamic",
|
|
fetchCache: "force-no-store",
|
|
revalidateSeconds: 0,
|
|
});
|
|
});
|
|
|
|
it("derives fetchCache from static-only dynamic modes", () => {
|
|
expect(
|
|
resolveAppPageSegmentConfig({
|
|
layouts: [{ dynamic: "error" }],
|
|
page: {},
|
|
}),
|
|
).toEqual({
|
|
dynamicConfig: "error",
|
|
dynamicParamsConfig: false,
|
|
fetchCache: "only-cache",
|
|
revalidateSeconds: null,
|
|
});
|
|
});
|
|
|
|
it("lets explicit fetchCache override the dynamic mode default", () => {
|
|
expect(
|
|
resolveAppPageSegmentConfig({
|
|
layouts: [{ dynamic: "error" }],
|
|
page: { fetchCache: "default-cache" },
|
|
}),
|
|
).toEqual({
|
|
dynamicConfig: "error",
|
|
dynamicParamsConfig: false,
|
|
fetchCache: "default-cache",
|
|
revalidateSeconds: null,
|
|
});
|
|
});
|
|
|
|
it("resolves fetchCache force modes with route-level precedence", () => {
|
|
expect(
|
|
resolveAppPageSegmentConfig({
|
|
layouts: [{ fetchCache: "only-cache" }],
|
|
page: { fetchCache: "force-cache" },
|
|
}).fetchCache,
|
|
).toBe("force-cache");
|
|
|
|
expect(
|
|
resolveAppPageSegmentConfig({
|
|
layouts: [{ fetchCache: "force-no-store" }],
|
|
page: { fetchCache: "only-no-store" },
|
|
}).fetchCache,
|
|
).toBe("force-no-store");
|
|
});
|
|
|
|
it("rejects incompatible cross-segment fetchCache modes", () => {
|
|
expect(() =>
|
|
resolveAppPageSegmentConfig({
|
|
layouts: [{ fetchCache: "only-cache" }],
|
|
page: { fetchCache: "only-no-store" },
|
|
}),
|
|
).toThrow(/incompatible fetchCache/);
|
|
|
|
expect(() =>
|
|
resolveAppPageSegmentConfig({
|
|
layouts: [{ fetchCache: "force-cache" }],
|
|
page: { fetchCache: "force-no-store" },
|
|
}),
|
|
).toThrow(/incompatible fetchCache/);
|
|
|
|
expect(() =>
|
|
resolveAppPageSegmentConfig({
|
|
layouts: [{ fetchCache: "default-no-store" }],
|
|
page: { fetchCache: "auto" },
|
|
}),
|
|
).toThrow(/incompatible fetchCache/);
|
|
});
|
|
|
|
it("ignores unknown dynamic values", () => {
|
|
expect(
|
|
resolveAppPageSegmentConfig({
|
|
layouts: [{ dynamic: "sometimes" }],
|
|
page: { dynamic: "force-static" },
|
|
}),
|
|
).toEqual({
|
|
dynamicConfig: "force-static",
|
|
dynamicParamsConfig: false,
|
|
revalidateSeconds: null,
|
|
});
|
|
});
|
|
|
|
it("defaults dynamicParams to false for static-only dynamic modes", () => {
|
|
expect(
|
|
resolveAppPageSegmentConfig({
|
|
layouts: [{ dynamic: "error" }],
|
|
page: {},
|
|
}),
|
|
).toEqual({
|
|
dynamicConfig: "error",
|
|
dynamicParamsConfig: false,
|
|
fetchCache: "only-cache",
|
|
revalidateSeconds: null,
|
|
});
|
|
|
|
expect(
|
|
resolveAppPageSegmentConfig({
|
|
layouts: [{ dynamic: "force-static" }],
|
|
page: {},
|
|
}),
|
|
).toEqual({
|
|
dynamicConfig: "force-static",
|
|
dynamicParamsConfig: false,
|
|
revalidateSeconds: null,
|
|
});
|
|
});
|
|
|
|
it("lets explicit dynamicParams override static-only dynamic defaults", () => {
|
|
expect(
|
|
resolveAppPageSegmentConfig({
|
|
layouts: [{ dynamic: "error" }],
|
|
page: { dynamicParams: true },
|
|
}),
|
|
).toEqual({
|
|
dynamicConfig: "error",
|
|
dynamicParamsConfig: true,
|
|
fetchCache: "only-cache",
|
|
revalidateSeconds: null,
|
|
});
|
|
});
|
|
|
|
it("keeps explicit dynamicParams false sticky across child segments", () => {
|
|
expect(
|
|
resolveAppPageSegmentConfig({
|
|
layouts: [{ dynamicParams: false }],
|
|
page: { dynamicParams: true },
|
|
}),
|
|
).toEqual({
|
|
dynamicParamsConfig: false,
|
|
revalidateSeconds: null,
|
|
});
|
|
});
|
|
|
|
it("resolves revalidate = false as Infinity (cache indefinitely)", () => {
|
|
expect(
|
|
resolveAppPageSegmentConfig({
|
|
page: { revalidate: false },
|
|
}),
|
|
).toEqual({
|
|
revalidateSeconds: Infinity,
|
|
});
|
|
});
|
|
|
|
it("resolves shortest-wins: finite revalidate beats false (Infinity)", () => {
|
|
expect(
|
|
resolveAppPageSegmentConfig({
|
|
layouts: [{ revalidate: 60 }],
|
|
page: { revalidate: false },
|
|
}).revalidateSeconds,
|
|
).toBe(60);
|
|
});
|
|
|
|
it("resolves shortest-wins: false (Infinity) loses to any finite value", () => {
|
|
expect(
|
|
resolveAppPageSegmentConfig({
|
|
layouts: [{ revalidate: false }],
|
|
page: { revalidate: 60 },
|
|
}).revalidateSeconds,
|
|
).toBe(60);
|
|
});
|
|
|
|
it("resolves just the fetchCache mode for route-specific render scopes", () => {
|
|
expect(
|
|
resolveAppPageFetchCacheMode({
|
|
layouts: [{ fetchCache: "only-cache" }],
|
|
page: {},
|
|
}),
|
|
).toBe("only-cache");
|
|
|
|
expect(
|
|
resolveAppPageFetchCacheMode({
|
|
layouts: [{ revalidate: 60 }],
|
|
page: {},
|
|
}),
|
|
).toBeNull();
|
|
});
|
|
});
|