mirror of
https://github.com/cloudflare/vinext.git
synced 2026-09-14 19:04:59 +08:00
5343a3ff32
* 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
148 lines
4.3 KiB
TypeScript
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);
|
|
});
|
|
});
|