import { describe, it, expect, beforeEach, vi, afterEach } from "vitest"; import { writeAssets, inlineCritical, tree, BuildResult, CompilationDetails, } from "../builder.js"; // Mock esbuild vi.mock("esbuild", () => ({ build: vi.fn(), })); // Mock fs and fs/promises vi.mock("node:fs", () => ({ readdirSync: vi.fn(), })); vi.mock("node:fs/promises", () => ({ readFile: vi.fn(), writeFile: vi.fn(), })); // Mock utils vi.mock("../utils.js", () => ({ formatBytes: vi.fn((bytes: number) => `${bytes} bytes`), GtbConfig: vi.fn(), logToFile: vi.fn(), })); // Mock constants vi.mock("../constants.js", () => ({ EXTERNAL_ASSETS: [ "*.png", "*.jpg", "*.jpeg", "*.svg", "*.gif", "*.woff", "*.woff2", "*.ttf", "*.otf", ], })); // Mock lightningcss and browserslist vi.mock("lightningcss", () => ({ transform: vi.fn(() => ({ code: Buffer.from(""), map: undefined })), Features: { Nesting: 1, CustomProperties: 2, ContainerQueries: 4, MediaQueries: 8, Colors: 16, }, browserslistToTargets: vi.fn((x) => x), })); vi.mock("browserslist", () => ({ default: vi.fn(() => ["chrome >= 90", "firefox >= 88", "safari >= 14"]), })); import * as esbuild from "esbuild"; import { readFile, writeFile } from "node:fs/promises"; import { readdirSync } from "node:fs"; function makeMetafile( outputs: Record< string, { bytes: number; entryPoint?: string; inputs?: Record; } >, inputs?: Record, ) { return { metafile: { inputs: inputs || {}, outputs, }, }; } describe("writeAssets", () => { beforeEach(() => { vi.clearAllMocks(); tree.clear(); }); afterEach(() => { vi.restoreAllMocks(); }); it("should build assets successfully", async () => { const mockEsbuild = vi.mocked(esbuild); mockEsbuild.build.mockResolvedValue( makeMetafile( { "assets/built/js/index.js": { bytes: 512, entryPoint: "src/js/index.ts", inputs: { "src/js/index.ts": { bytesInOutput: 512 } }, }, }, { "src/js/index.ts": { bytes: 1024 } }, ) as any, ); const result = await writeAssets(["src/js/index.ts"], 3000, false); expect(result.results).toHaveLength(1); expect(result.results[0]).toEqual({ file: "assets/built/js/index.js", value: "512 bytes", }); expect(result.time).toBeGreaterThan(0); }); it("should handle critical assets and call inlineCritical", async () => { const mockEsbuild = vi.mocked(esbuild); const mockReadFile = vi.mocked(readFile); const mockWriteFile = vi.mocked(writeFile); const mockReaddirSync = vi.mocked(readdirSync); // writeAssets build mockEsbuild.build.mockResolvedValue( makeMetafile( { "assets/built/js/critical/index.js": { bytes: 512, entryPoint: "src/js/critical/index.ts", inputs: { "src/js/critical/index.ts": { bytesInOutput: 512 }, }, }, }, { "src/js/critical/index.ts": { bytes: 1024 } }, ) as any, ); const CSS_TAG = ''; // inlineCritical reads: default.hbs, then critical css (may fail), then getCriticalDirs mockReadFile .mockResolvedValueOnce(`${CSS_TAG}` as any) .mockResolvedValueOnce("/* critical css */" as any); mockReaddirSync .mockReturnValueOnce([] as any) .mockReturnValueOnce([] as any); mockWriteFile.mockResolvedValue(); vi.spyOn(console, "log").mockImplementation(() => {}); await writeAssets(["src/js/critical/index.ts"], 3000, false); expect(mockWriteFile).toHaveBeenCalledWith( "default.hbs", expect.stringContaining(""), ); }); it("should not inline critical if tags not found in template", async () => { const mockEsbuild = vi.mocked(esbuild); const mockReadFile = vi.mocked(readFile); const mockWriteFile = vi.mocked(writeFile); mockEsbuild.build.mockResolvedValue( makeMetafile( { "assets/built/js/critical/index.js": { bytes: 512, entryPoint: "src/js/critical/index.ts", inputs: { "src/js/critical/index.ts": { bytesInOutput: 512 }, }, }, }, { "src/js/critical/index.ts": { bytes: 1024 } }, ) as any, ); // Template without the expected tags mockReadFile.mockResolvedValue("plain template content" as any); mockWriteFile.mockResolvedValue(); await writeAssets(["src/js/critical/index.ts"], 3000, false); expect(mockWriteFile).toHaveBeenCalledWith( "default.hbs", expect.not.stringContaining("