Files
cloudflare__vinext/tests/dev-route-files.test.ts
Yunfei He 2b61f3fd29 perf(metadata): cache scanMetadataFiles to skip the redundant 2nd per-build app-tree walk (#2394)
* perf(metadata): cache the per-build app metadata-file scan

* fix(metadata): invalidate cache on opengraph/twitter alt-text sidecars

scanMetadataFiles folds `*.alt.txt` sidecars into a static social image
route's altFilePath via an existsSync probe, but isMetadataRouteFile did
not treat them as route files. With the new per-appDir scan cache, adding
or removing an alt sidecar in dev no longer triggered invalidation, so
the cached scan kept a stale altFilePath. Match the sidecars in the dev
invalidation predicate so the metadata cache clears and re-scans.

---------

Co-authored-by: James Anderson <james@eli.cx>
2026-06-28 19:30:38 +00:00

55 lines
2.6 KiB
TypeScript

/**
* Unit tests for the dev-server route-file invalidation predicate.
*
* `shouldInvalidateAppRouteFile` decides whether a watched file change must
* clear the cached app-route graph and metadata scan. It must return true for
* every file whose add/remove changes the output of those scans — including the
* `*.alt.txt` sidecars that `scanMetadataFiles` folds into a static social
* image route's `altFilePath`.
*/
import { describe, it, expect } from "vite-plus/test";
import path from "node:path";
import { shouldInvalidateAppRouteFile } from "../packages/vinext/src/server/dev-route-files.js";
import { createValidFileMatcher } from "../packages/vinext/src/routing/file-matcher.js";
const appDir = path.resolve("/app");
const matcher = createValidFileMatcher();
const inApp = (...segments: string[]) => path.join(appDir, ...segments);
describe("shouldInvalidateAppRouteFile", () => {
it("invalidates on app-router structure files", () => {
expect(shouldInvalidateAppRouteFile(appDir, inApp("page.tsx"), matcher)).toBe(true);
expect(shouldInvalidateAppRouteFile(appDir, inApp("blog", "layout.tsx"), matcher)).toBe(true);
});
it("invalidates on primary metadata files (static and dynamic)", () => {
expect(shouldInvalidateAppRouteFile(appDir, inApp("favicon.ico"), matcher)).toBe(true);
expect(shouldInvalidateAppRouteFile(appDir, inApp("opengraph-image.png"), matcher)).toBe(true);
expect(shouldInvalidateAppRouteFile(appDir, inApp("opengraph-image.tsx"), matcher)).toBe(true);
expect(shouldInvalidateAppRouteFile(appDir, inApp("blog", "twitter-image.jpg"), matcher)).toBe(
true,
);
});
it("invalidates on opengraph/twitter alt-text sidecars", () => {
// These are not standalone routes, but scanMetadataFiles resolves them into
// the image route's `altFilePath`, so add/remove must invalidate the cache.
expect(shouldInvalidateAppRouteFile(appDir, inApp("opengraph-image.alt.txt"), matcher)).toBe(
true,
);
expect(
shouldInvalidateAppRouteFile(appDir, inApp("blog", "twitter-image.alt.txt"), matcher),
).toBe(true);
// Numbered image variant sidecar (opengraph-image1.png -> opengraph-image1.alt.txt).
expect(shouldInvalidateAppRouteFile(appDir, inApp("opengraph-image1.alt.txt"), matcher)).toBe(
true,
);
});
it("ignores unrelated .alt.txt and plain text files", () => {
expect(shouldInvalidateAppRouteFile(appDir, inApp("notes.alt.txt"), matcher)).toBe(false);
expect(shouldInvalidateAppRouteFile(appDir, inApp("readme.txt"), matcher)).toBe(false);
expect(shouldInvalidateAppRouteFile(appDir, inApp("styles.css"), matcher)).toBe(false);
});
});