UNPKG

@embeddable.com/sdk-core

Version:

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

250 lines (212 loc) 6.7 kB
import * as fs from "node:fs/promises"; import * as path from "node:path"; import { describe, it, expect, vi, beforeEach } from "vitest"; import buildTypes, { EMB_TYPE_FILE_REGEX } from "./buildTypes"; import { build } from "vite"; import { ResolvedEmbeddableConfig } from "./defineConfig"; import { findFiles, getContentHash, getComponentLibraryConfig, } from "@embeddable.com/sdk-utils"; import fg from "fast-glob"; // The same config you already have const config = { client: { srcDir: "src", rootDir: "root", buildDir: "build", componentLibraries: [], }, outputOptions: { typesEntryPointFilename: "typesEntryPointFilename", }, }; const startMock = { succeed: vi.fn(), fail: vi.fn(), }; vi.mock("ora", () => ({ default: () => ({ start: vi.fn().mockImplementation(() => startMock), info: vi.fn(), fail: vi.fn(), }), })); vi.mock("@embeddable.com/sdk-utils", () => ({ findFiles: vi.fn(), getContentHash: vi.fn(), getComponentLibraryConfig: vi.fn(), })); vi.mock("node:path", async () => { const actualPath = await vi.importActual<typeof import("node:path")>("node:path"); return { ...actualPath, resolve: vi.fn(), relative: vi.fn(), join: vi.fn(), dirname: vi.fn(), }; }); vi.mock("node:fs/promises", () => ({ writeFile: vi.fn(), rm: vi.fn(), readFile: vi.fn(), rename: vi.fn(), })); vi.mock("vite", () => ({ build: vi.fn(), })); vi.mock("fast-glob", () => ({ default: { sync: vi.fn(), }, })); describe("buildTypes", () => { beforeEach(() => { vi.clearAllMocks(); vi.mocked(findFiles).mockResolvedValue([["fileName", "filePath"]]); vi.mocked(path.relative).mockReturnValue("relativePath"); vi.mocked(path.resolve).mockReturnValue("resolvedPath"); vi.mocked(fs.readFile).mockResolvedValue("fileContent"); vi.mocked(getContentHash).mockReturnValue("somehash"); // By default, no libraries => no fast-glob usage vi.mocked(fg.sync).mockReturnValue([]); // We also default to returning an empty config from getComponentLibraryConfig vi.mocked(getComponentLibraryConfig).mockReturnValue({ libraryName: "", include: [], exclude: [], }); }); it("should build types", async () => { await buildTypes(config as unknown as ResolvedEmbeddableConfig); expect(findFiles).toHaveBeenCalledWith("src", EMB_TYPE_FILE_REGEX); expect(build).toHaveBeenCalledWith({ build: { emptyOutDir: false, sourcemap: true, minify: true, rollupOptions: undefined, lib: { entry: "resolvedPath", fileName: "embeddable-types", formats: ["es"], }, outDir: "build", }, logLevel: "error", }); expect(fs.readFile).toHaveBeenCalledWith("resolvedPath", "utf8"); expect(getContentHash).toHaveBeenCalledWith("fileContent"); expect(startMock.succeed).toHaveBeenCalledWith("Types built completed"); expect(fs.writeFile).toHaveBeenCalledWith( "resolvedPath", `import '../relativePath'; import '../relativePath';`, ); expect(fs.rm).toHaveBeenCalledWith("resolvedPath"); }); it("should not add hash to the file name when watch is enabled", async () => { await buildTypes({ ...config, dev: { watch: true, logger: undefined, sys: undefined }, } as unknown as ResolvedEmbeddableConfig); expect(getContentHash).not.toHaveBeenCalled(); expect(fs.rename).not.toHaveBeenCalled(); }); it("should use dev optimizations when watch mode is enabled", async () => { await buildTypes({ ...config, dev: { watch: true, logger: undefined, sys: undefined }, } as unknown as ResolvedEmbeddableConfig); expect(build).toHaveBeenCalledWith({ build: { emptyOutDir: false, sourcemap: false, minify: false, rollupOptions: { treeshake: false }, lib: { entry: "resolvedPath", fileName: "embeddable-types", formats: ["es"], }, outDir: "build", }, logLevel: "error", }); }); it("should rename the built file with hash when not in watch mode", async () => { // Reset the mock to control it better vi.mocked(path.resolve).mockReset(); // Mock the calls in order they happen vi.mocked(path.resolve) .mockReturnValueOnce("resolvedPath") // typesFilePath in build() .mockReturnValueOnce("resolvedPath") // typesFilePath for readFile .mockReturnValueOnce("build/embeddable-types.js") // source file for rename .mockReturnValueOnce("build/embeddable-types-somehash.js"); // target file for rename await buildTypes(config as unknown as ResolvedEmbeddableConfig); expect(fs.rename).toHaveBeenCalledWith( "build/embeddable-types.js", "build/embeddable-types-somehash.js" ); }); it("should import types from installed libraries if present", async () => { const configWithLibrary = { ...config, client: { ...config.client, componentLibraries: [ { /* any library config object */ }, ], }, } as unknown as ResolvedEmbeddableConfig; // Mock getComponentLibraryConfig => returns libraryName: "my-lib" vi.mocked(getComponentLibraryConfig).mockReturnValue({ libraryName: "my-lib", include: [], exclude: [], }); vi.mocked(fg.sync).mockReturnValue([ "/fake/path/node_modules/my-lib/dist/embeddable-types-abc123.js", "/fake/path/node_modules/my-lib/dist/embeddable-types-xyz987.js", ]); // Now run await buildTypes(configWithLibrary); const written = vi.mocked(fs.writeFile).mock.calls[0][1]; expect(written).toContain( `import 'my-lib/dist/embeddable-types-abc123.js';`, ); expect(written).toContain( `import 'my-lib/dist/embeddable-types-xyz987.js';`, ); expect(written).toContain(`import '../relativePath';`); }); it("should throw if an error occurs when loading a library", async () => { const configWithLibrary = { ...config, client: { ...config.client, componentLibraries: [ { /* any library config object */ }, ], }, } as unknown as ResolvedEmbeddableConfig; vi.mocked(getComponentLibraryConfig).mockReturnValue({ libraryName: "my-broken-lib", include: [], exclude: [], }); vi.mocked(fg.sync).mockImplementation(() => { throw new Error("some library error"); }); await expect(buildTypes(configWithLibrary)).rejects.toThrow( "some library error", ); }); });