UNPKG

@embeddable.com/sdk-core

Version:

Core Embeddable SDK module responsible for web-components bundling and publishing.

274 lines (244 loc) 8.79 kB
// buildGlobalHooks.unit.test.ts import { describe, it, expect, beforeEach, vi, Mock } from "vitest"; import * as fs from "node:fs/promises"; import * as fsSync from "node:fs"; import * as vite from "vite"; import { findFiles, getContentHash, getGlobalHooksMeta, getComponentLibraryConfig, } from "@embeddable.com/sdk-utils"; import buildGlobalHooks from "../src/buildGlobalHooks"; import { ResolvedEmbeddableConfig } from "./defineConfig"; import path from "node:path"; // Mock implementations vi.mock("node:fs/promises"); vi.mock("node:fs"); vi.mock("vite"); vi.mock("@embeddable.com/sdk-utils", async () => { const actual = await vi.importActual< typeof import("@embeddable.com/sdk-utils") >("@embeddable.com/sdk-utils"); return { ...actual, findFiles: vi.fn(), getContentHash: vi.fn(), getGlobalHooksMeta: vi.fn(), getComponentLibraryConfig: vi.fn(), }; }); const lifecyclePath = path.resolve( process.cwd(), "fake", "root", "embeddable.lifecycle.ts", ); const themePath = path.resolve( process.cwd(), "fake", "root", "embeddable.theme.ts", ); describe("buildGlobalHooks (Unit Tests)", () => { beforeEach(() => { vi.clearAllMocks(); }); it("should skip lifecycle building if file doesn't exist", async () => { vi.spyOn(fsSync, "existsSync").mockImplementation((p: any) => { // We want the code to see that /fake/root/embeddable.lifecycle.ts does NOT exist if (p === lifecyclePath) { return false; } // Otherwise, default to false return false; }); // Possibly not used, but let's mock anyway (findFiles as Mock).mockResolvedValue([]); // The aggregator template might be read if there's some library with a theme (fs.readFile as Mock).mockResolvedValue("some template content"); (getContentHash as Mock).mockReturnValue("abc123"); const ctx: ResolvedEmbeddableConfig = { client: { srcDir: path.resolve(process.cwd(), "fake", "src"), buildDir: path.resolve(process.cwd(), "fake", "build"), rootDir: path.resolve(process.cwd(), "fake", "root"), lifecycleHooksFile: lifecyclePath, componentLibraries: [], customizationFile: themePath, }, core: { templatesDir: "/fake/templates", }, } as any; await buildGlobalHooks(ctx); // Because we skip building the repo lifecycle, no call to vite.build with that entry expect(vite.build).not.toHaveBeenCalledWith( expect.objectContaining({ build: expect.objectContaining({ lib: expect.objectContaining({ entry: lifecyclePath, }), }), }), ); }); it("should build lifecycle file if it exists", async () => { // Now we say "repo lifecycle does exist" vi.spyOn(fsSync, "existsSync").mockImplementation((p: any) => { // If path is exactly the lifecycle file => true return p === lifecyclePath; }); // Just in case, but not strictly used here (findFiles as Mock).mockResolvedValue([]); // The aggregator template might or might not be read (fs.readFile as Mock).mockResolvedValue("some file content"); (getContentHash as Mock).mockReturnValue("abc123"); (fs.rename as Mock).mockResolvedValue(undefined); (vite.build as Mock).mockResolvedValue(undefined); const ctx: ResolvedEmbeddableConfig = { client: { srcDir: path.resolve(process.cwd(), "fake", "src"), buildDir: path.resolve(process.cwd(), "fake", "build"), rootDir: path.resolve(process.cwd(), "fake", "root"), lifecycleHooksFile: lifecyclePath, customizationFile: themePath, componentLibraries: [], }, core: { templatesDir: "/fake/templates", }, } as any; await buildGlobalHooks(ctx); // We expect a call to build the lifecycle expect(vite.build).toHaveBeenCalledWith( expect.objectContaining({ build: expect.objectContaining({ lib: expect.objectContaining({ entry: lifecyclePath, fileName: "embeddable-lifecycle", }), }), }), ); }); it("should build theme aggregator if libraries exist with themeProvider references", async () => { // aggregator template read is guaranteed (fs.readFile as Mock).mockResolvedValue("template content"); (getContentHash as Mock).mockReturnValue("xyz777"); (fs.rename as Mock).mockResolvedValue(undefined); (vite.build as Mock).mockResolvedValue(undefined); // Suppose we skip the lifecycle, but aggregator is still built vi.spyOn(fsSync, "existsSync").mockImplementation((p: any) => { // lifecycle => false if (p === lifecyclePath) return false; // local theme => true if (p === themePath) return true; return false; }); (getComponentLibraryConfig as Mock).mockImplementation((cfg: any) => ({ libraryName: cfg.name, })); // Each library call to getGlobalHooksMeta occurs twice: // 1) buildThemeHook -> aggregator // 2) buildLifecycleHooks -> but we skip it if lifecycle doesn't exist // The code still calls getGlobalHooksMeta for each library in buildLifecycleHooks // So we must provide 4 total mock results for 2 libraries => aggregator + lifecycle each. (getGlobalHooksMeta as Mock) // aggregator call #1: libA .mockResolvedValueOnce({ themeProvider: "libA-theme.js", lifecycleHooks: [], }) // aggregator call #2: libB .mockResolvedValueOnce({ themeProvider: "libB-theme.js", lifecycleHooks: [], }) // lifecycle call #1: libA .mockResolvedValueOnce({ themeProvider: "libA-theme.js", lifecycleHooks: [], }) // lifecycle call #2: libB .mockResolvedValueOnce({ themeProvider: "libB-theme.js", lifecycleHooks: [], }); const ctx: ResolvedEmbeddableConfig = { client: { srcDir: path.resolve(process.cwd(), "fake", "src"), buildDir: path.resolve(process.cwd(), "fake", "build"), rootDir: path.resolve(process.cwd(), "fake", "root"), lifecycleHooksFile: lifecyclePath, customizationFile: themePath, componentLibraries: [{ name: "libA" }, { name: "libB" }], }, core: { templatesDir: "/fake/templates", }, } as any; await buildGlobalHooks(ctx); // aggregator => build with entry = /fake/build/embeddableThemeHook.js expect(vite.build).toHaveBeenCalledWith( expect.objectContaining({ build: expect.objectContaining({ lib: expect.objectContaining({ entry: expect.stringContaining("embeddableThemeHook.js"), fileName: `embeddable-theme-xyz777`, // from getContentHash }), }), }), ); }); it("should skip aggregator if no library has themeProvider and no local theme", async () => { (fs.readFile as Mock).mockResolvedValue("template content"); (getContentHash as Mock).mockReturnValue("someHash"); (fs.rename as Mock).mockResolvedValue(undefined); (vite.build as Mock).mockResolvedValue(undefined); vi.spyOn(fsSync, "existsSync").mockImplementation((p: any) => { // Suppose no local theme => false if (p === themePath) return false; if (p === lifecyclePath) return false; return false; }); // Each library is missing a themeProvider (getComponentLibraryConfig as Mock).mockImplementation((cfg: any) => ({ libraryName: cfg.name, })); // no theme, so aggregator is skipped (getGlobalHooksMeta as Mock).mockResolvedValue({ themeProvider: null, lifecycleHooks: [], }); const ctx: ResolvedEmbeddableConfig = { client: { srcDir: path.resolve(process.cwd(), "fake", "src"), buildDir: path.resolve(process.cwd(), "fake", "build"), rootDir: path.resolve(process.cwd(), "fake", "root"), lifecycleHooksFile: lifecyclePath, customizationFile: themePath, componentLibraries: [ { name: "libA" }, // no theme ], }, core: { templatesDir: "/fake/templates", }, } as any; await buildGlobalHooks(ctx); // aggregator not built // We do an exact check: "not toHaveBeenCalledWith" // But the code might still do a build for the lifecycle if it existed. // We said the lifecycle is false => so no build for aggregator expect(vite.build).not.toHaveBeenCalledWith( expect.objectContaining({ build: expect.objectContaining({ lib: expect.objectContaining({ entry: expect.stringContaining("embeddableThemeHook.js"), }), }), }), ); }); });