mirror of
https://github.com/cloudflare/vinext.git
synced 2026-09-14 19:04:59 +08:00
263 lines
9.3 KiB
TypeScript
263 lines
9.3 KiB
TypeScript
/**
|
|
* Tests for startup-time caching of expensive filesystem operations.
|
|
*
|
|
* Task 1a: hasMdxFiles() should cache its result per (root, appDir, pagesDir) combination.
|
|
* Task 1b: resolvePostcssStringPlugins() should cache its result per project root.
|
|
*/
|
|
import { afterEach, describe, it, expect, beforeAll, beforeEach, vi } from "vite-plus/test";
|
|
import fs from "node:fs";
|
|
import path from "node:path";
|
|
import os from "node:os";
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Task 1a: hasMdxFiles caching
|
|
// ---------------------------------------------------------------------------
|
|
|
|
describe("hasMdxFiles caching", () => {
|
|
let hasMdxFiles: (typeof import("../packages/vinext/src/utils/mdx-scan.js"))["hasMdxFiles"];
|
|
let mdxScanCache: (typeof import("../packages/vinext/src/utils/mdx-scan.js"))["mdxScanCache"];
|
|
|
|
beforeAll(async () => {
|
|
const mod = await import("../packages/vinext/src/utils/mdx-scan.js");
|
|
hasMdxFiles = mod.hasMdxFiles;
|
|
mdxScanCache = mod.mdxScanCache;
|
|
});
|
|
|
|
beforeEach(() => {
|
|
// Clear the cache between tests to ensure isolation
|
|
mdxScanCache.clear();
|
|
});
|
|
|
|
afterEach(() => {
|
|
vi.restoreAllMocks();
|
|
});
|
|
|
|
it("returns true when .mdx files exist in appDir", async () => {
|
|
const fsp = await import("node:fs/promises");
|
|
const tmpDir = await fsp.mkdtemp(path.join(os.tmpdir(), "vinext-mdx-"));
|
|
const appDir = path.join(tmpDir, "app");
|
|
await fsp.mkdir(appDir, { recursive: true });
|
|
await fsp.writeFile(path.join(appDir, "page.mdx"), "# Hello");
|
|
|
|
try {
|
|
const result = hasMdxFiles(tmpDir, appDir, null);
|
|
expect(result).toBe(true);
|
|
} finally {
|
|
await fsp.rm(tmpDir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
it("returns false when no .mdx files exist", async () => {
|
|
const fsp = await import("node:fs/promises");
|
|
const tmpDir = await fsp.mkdtemp(path.join(os.tmpdir(), "vinext-mdx-"));
|
|
const appDir = path.join(tmpDir, "app");
|
|
await fsp.mkdir(appDir, { recursive: true });
|
|
await fsp.writeFile(path.join(appDir, "page.tsx"), "export default () => null");
|
|
|
|
try {
|
|
const result = hasMdxFiles(tmpDir, appDir, null);
|
|
expect(result).toBe(false);
|
|
} finally {
|
|
await fsp.rm(tmpDir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
it("does not rescan the filesystem on second call with same root", async () => {
|
|
const fsp = await import("node:fs/promises");
|
|
const tmpDir = await fsp.mkdtemp(path.join(os.tmpdir(), "vinext-mdx-cache-"));
|
|
const appDir = path.join(tmpDir, "app");
|
|
await fsp.mkdir(appDir, { recursive: true });
|
|
await fsp.writeFile(path.join(appDir, "page.mdx"), "# Hello");
|
|
|
|
try {
|
|
// First call — populates cache
|
|
const result1 = hasMdxFiles(tmpDir, appDir, null);
|
|
expect(result1).toBe(true);
|
|
|
|
// Spy on readdirSync after first call
|
|
const readdirSpy = vi.spyOn(fs, "readdirSync");
|
|
|
|
// Second call — should use cache, no filesystem reads
|
|
const result2 = hasMdxFiles(tmpDir, appDir, null);
|
|
expect(result2).toBe(true);
|
|
expect(readdirSpy).not.toHaveBeenCalled();
|
|
} finally {
|
|
await fsp.rm(tmpDir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
it("caches false results too (no redundant scans for missing mdx)", async () => {
|
|
const fsp = await import("node:fs/promises");
|
|
const tmpDir = await fsp.mkdtemp(path.join(os.tmpdir(), "vinext-mdx-nomdx-"));
|
|
const appDir = path.join(tmpDir, "app");
|
|
await fsp.mkdir(appDir, { recursive: true });
|
|
await fsp.writeFile(path.join(appDir, "page.tsx"), "export default () => null");
|
|
|
|
try {
|
|
// First call
|
|
const result1 = hasMdxFiles(tmpDir, appDir, null);
|
|
expect(result1).toBe(false);
|
|
|
|
const readdirSpy = vi.spyOn(fs, "readdirSync");
|
|
|
|
// Second call — should hit cache
|
|
const result2 = hasMdxFiles(tmpDir, appDir, null);
|
|
expect(result2).toBe(false);
|
|
expect(readdirSpy).not.toHaveBeenCalled();
|
|
} finally {
|
|
await fsp.rm(tmpDir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
it("caches independently per root directory", async () => {
|
|
const fsp = await import("node:fs/promises");
|
|
const tmpDir1 = await fsp.mkdtemp(path.join(os.tmpdir(), "vinext-mdx-root1-"));
|
|
const tmpDir2 = await fsp.mkdtemp(path.join(os.tmpdir(), "vinext-mdx-root2-"));
|
|
const appDir1 = path.join(tmpDir1, "app");
|
|
const appDir2 = path.join(tmpDir2, "app");
|
|
await fsp.mkdir(appDir1, { recursive: true });
|
|
await fsp.mkdir(appDir2, { recursive: true });
|
|
await fsp.writeFile(path.join(appDir1, "page.mdx"), "# Hello");
|
|
await fsp.writeFile(path.join(appDir2, "page.tsx"), "export default () => null");
|
|
|
|
try {
|
|
expect(hasMdxFiles(tmpDir1, appDir1, null)).toBe(true);
|
|
expect(hasMdxFiles(tmpDir2, appDir2, null)).toBe(false);
|
|
} finally {
|
|
await fsp.rm(tmpDir1, { recursive: true, force: true });
|
|
await fsp.rm(tmpDir2, { recursive: true, force: true });
|
|
}
|
|
});
|
|
});
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Task 1b: resolvePostcssStringPlugins caching
|
|
// ---------------------------------------------------------------------------
|
|
|
|
describe("resolvePostcssStringPlugins caching", () => {
|
|
let resolvePostcssStringPlugins: (typeof import("../packages/vinext/src/plugins/postcss.js"))["resolvePostcssStringPlugins"];
|
|
let postcssCache: (typeof import("../packages/vinext/src/plugins/postcss.js"))["postcssCache"];
|
|
|
|
beforeAll(async () => {
|
|
const mod = await import("../packages/vinext/src/plugins/postcss.js");
|
|
resolvePostcssStringPlugins = mod.resolvePostcssStringPlugins;
|
|
postcssCache = mod.postcssCache;
|
|
});
|
|
|
|
beforeEach(() => {
|
|
postcssCache.clear();
|
|
});
|
|
|
|
afterEach(() => {
|
|
vi.restoreAllMocks();
|
|
});
|
|
|
|
async function createTmpProject(configFileName: string, configContent: string): Promise<string> {
|
|
const fsp = await import("node:fs/promises");
|
|
const dir = await fsp.mkdtemp(path.join(os.tmpdir(), "vinext-postcss-cache-"));
|
|
|
|
// Create a mock PostCSS plugin
|
|
const pluginDir = path.join(dir, "node_modules", "mock-postcss-plugin");
|
|
await fsp.mkdir(pluginDir, { recursive: true });
|
|
await fsp.writeFile(
|
|
path.join(pluginDir, "index.js"),
|
|
`module.exports = function mockPlugin(opts) {
|
|
return { postcssPlugin: "mock-postcss-plugin", Once(root) {} };
|
|
};
|
|
module.exports.postcss = true;`,
|
|
);
|
|
await fsp.writeFile(
|
|
path.join(pluginDir, "package.json"),
|
|
JSON.stringify({ name: "mock-postcss-plugin", version: "1.0.0", main: "index.js" }),
|
|
);
|
|
|
|
await fsp.writeFile(path.join(dir, configFileName), configContent);
|
|
return dir;
|
|
}
|
|
|
|
async function cleanupDir(dir: string) {
|
|
const fsp = await import("node:fs/promises");
|
|
await fsp.rm(dir, { recursive: true, force: true }).catch(() => {});
|
|
}
|
|
|
|
it("does not re-scan filesystem on second call with same projectRoot", async () => {
|
|
const dir = await createTmpProject(
|
|
"postcss.config.cjs",
|
|
`module.exports = { plugins: ["mock-postcss-plugin"] };`,
|
|
);
|
|
|
|
try {
|
|
// First call — populates cache
|
|
const result1 = await resolvePostcssStringPlugins(dir);
|
|
expect(result1).toBeDefined();
|
|
expect(result1!.plugins).toHaveLength(1);
|
|
|
|
// Spy on existsSync after first call
|
|
const existsSpy = vi.spyOn(fs, "existsSync");
|
|
|
|
// Second call — should use cache
|
|
const result2 = await resolvePostcssStringPlugins(dir);
|
|
expect(result2).toBeDefined();
|
|
expect(result2!.plugins).toHaveLength(1);
|
|
expect(existsSpy).not.toHaveBeenCalled();
|
|
} finally {
|
|
await cleanupDir(dir);
|
|
}
|
|
});
|
|
|
|
it("caches undefined results (no config file)", async () => {
|
|
const fsp = await import("node:fs/promises");
|
|
const dir = await fsp.mkdtemp(path.join(os.tmpdir(), "vinext-postcss-noconf-"));
|
|
|
|
try {
|
|
// First call — no config, returns undefined
|
|
const result1 = await resolvePostcssStringPlugins(dir);
|
|
expect(result1).toBeUndefined();
|
|
|
|
const existsSpy = vi.spyOn(fs, "existsSync");
|
|
|
|
// Second call — should use cache
|
|
const result2 = await resolvePostcssStringPlugins(dir);
|
|
expect(result2).toBeUndefined();
|
|
expect(existsSpy).not.toHaveBeenCalled();
|
|
} finally {
|
|
await cleanupDir(dir);
|
|
}
|
|
});
|
|
|
|
it("returns the exact same object reference from cache", async () => {
|
|
const dir = await createTmpProject(
|
|
"postcss.config.cjs",
|
|
`module.exports = { plugins: ["mock-postcss-plugin"] };`,
|
|
);
|
|
|
|
try {
|
|
const result1 = await resolvePostcssStringPlugins(dir);
|
|
const result2 = await resolvePostcssStringPlugins(dir);
|
|
// Same reference — not re-computed
|
|
expect(result1).toBe(result2);
|
|
} finally {
|
|
await cleanupDir(dir);
|
|
}
|
|
});
|
|
|
|
it("caches independently per project root", async () => {
|
|
const dir1 = await createTmpProject(
|
|
"postcss.config.cjs",
|
|
`module.exports = { plugins: ["mock-postcss-plugin"] };`,
|
|
);
|
|
const fsp = await import("node:fs/promises");
|
|
const dir2 = await fsp.mkdtemp(path.join(os.tmpdir(), "vinext-postcss-noconf2-"));
|
|
|
|
try {
|
|
const result1 = await resolvePostcssStringPlugins(dir1);
|
|
const result2 = await resolvePostcssStringPlugins(dir2);
|
|
expect(result1).toBeDefined();
|
|
expect(result2).toBeUndefined();
|
|
} finally {
|
|
await cleanupDir(dir1);
|
|
await cleanupDir(dir2);
|
|
}
|
|
});
|
|
});
|