Files
cloudflare__vinext/tests/app-visited-response-cache.test.ts
T
James Anderson 5343a3ff32 fix(app-router): cache segment prefetches by rendered search params (#2477)
* fix(app-router): cache segment prefetches by rendered search params

* fix(app-router): protect rendered prefetch alias header

* fix(app-router): repair segment prefetch merge
2026-07-02 19:42:46 +00:00

148 lines
4.3 KiB
TypeScript

import { describe, expect, it } from "vite-plus/test";
import {
MAX_TRAVERSAL_CACHE_TTL,
VISITED_RESPONSE_CACHE_TTL,
createVisitedResponseCacheEntry,
isVisitedResponseCacheEntryFresh,
} from "../packages/vinext/src/server/app-visited-response-cache.js";
import type { CachedRscResponse } from "../packages/vinext/src/shims/navigation.js";
import type { AppElements } from "../packages/vinext/src/server/app-elements.js";
function createCachedResponse(overrides: Partial<CachedRscResponse> = {}): CachedRscResponse {
return {
buffer: new TextEncoder().encode("flight").buffer,
contentType: "text/x-component",
paramsHeader: null,
renderedPathAndSearch: null,
url: "/dynamic.rsc",
...overrides,
};
}
describe("visited response cache freshness", () => {
it("uses per-response dynamic stale time for regular navigations", () => {
// Ported from Next.js: test/e2e/app-dir/segment-cache/staleness/segment-cache-per-page-dynamic-stale-time.test.ts
const now = 1_000_000;
const entry = createVisitedResponseCacheEntry({
now,
mountedSlotsHeader: "slot:source",
params: {},
response: createCachedResponse({ dynamicStaleTimeSeconds: 10 }),
});
expect(entry.expiresAt).toBe(now + 10_000);
expect(entry.mountedSlotsHeader).toBe("slot:source");
expect(
isVisitedResponseCacheEntryFresh(entry, {
navigationKind: "navigate",
now: now + 9_999,
}),
).toBe(true);
expect(
isVisitedResponseCacheEntryFresh(entry, {
navigationKind: "navigate",
now: now + 10_000,
}),
).toBe(false);
});
it("falls back to the default visited response TTL without server metadata", () => {
const now = 1_000_000;
const entry = createVisitedResponseCacheEntry({
now,
params: {},
response: createCachedResponse(),
});
expect(entry.expiresAt).toBe(now + VISITED_RESPONSE_CACHE_TTL);
});
it("uses the configured dynamic fallback without server metadata", () => {
const now = 1_000_000;
const entry = createVisitedResponseCacheEntry({
fallbackTtlMs: 0,
now,
params: {},
response: createCachedResponse(),
});
expect(entry.expiresAt).toBe(now);
expect(isVisitedResponseCacheEntryFresh(entry, { navigationKind: "navigate", now })).toBe(
false,
);
});
it("inherits the expiry carried by a consumed prefetch snapshot", () => {
const now = 1_000_000;
const prefetchedExpiresAt = now + 30_000;
const entry = createVisitedResponseCacheEntry({
fallbackTtlMs: 0,
now,
params: {},
response: createCachedResponse({
dynamicStaleTimeSeconds: 0,
expiresAt: prefetchedExpiresAt,
}),
});
expect(entry.expiresAt).toBe(prefetchedExpiresAt);
expect(
isVisitedResponseCacheEntryFresh(entry, {
navigationKind: "navigate",
now: now + 29_999,
}),
).toBe(true);
});
it("retains decoded committed elements for partial Flight payload reuse", () => {
// Ported from Next.js: test/e2e/app-dir/app-client-cache/client-cache.original.test.ts
const elements = { "page:/dynamic": "cached page" } satisfies AppElements;
const entry = createVisitedResponseCacheEntry({
elements,
now: 1_000_000,
params: {},
response: createCachedResponse(),
});
expect(entry.elements).toBe(elements);
});
it("keeps traversal restores independent from dynamic stale expiry", () => {
const now = 1_000_000;
const entry = createVisitedResponseCacheEntry({
now,
params: {},
response: createCachedResponse({ dynamicStaleTimeSeconds: 10 }),
});
expect(
isVisitedResponseCacheEntryFresh(entry, {
navigationKind: "traverse",
now: now + 20_000,
}),
).toBe(true);
expect(
isVisitedResponseCacheEntryFresh(entry, {
navigationKind: "traverse",
now: now + MAX_TRAVERSAL_CACHE_TTL,
}),
).toBe(false);
});
it("never reuses visited responses for refresh navigations", () => {
const now = 1_000_000;
const entry = createVisitedResponseCacheEntry({
now,
params: {},
response: createCachedResponse({ dynamicStaleTimeSeconds: 60 }),
});
expect(
isVisitedResponseCacheEntryFresh(entry, {
navigationKind: "refresh",
now,
}),
).toBe(false);
});
});