193 lines · 6.1 KB · TypeScript
RawDownload
import { describe, it, expect, beforeAll, afterAll } from "@effect/vitest";import * as fs from "node:fs";import * as path from "node:path"; import type { AnyPlugin } from "@executor-js/sdk";import { loadPluginsFromJsonc } from "./load-plugins"; // Fixtures live under packages/core/config/__test-fixtures__/, which sits// directly inside the package directory so Node's `require.resolve` walks// up from a tmp jsonc and lands on// `__test-fixtures__/node_modules/@loader-fixture/...`. The hand-managed// node_modules entries declare a `./server` subpath whose default export// matches the loader's expectation: a function `(options) => Plugin`.const FIXTURES_ROOT = path.resolve(__dirname, "..", "__test-fixtures__");const TMP_ROOT = path.join(FIXTURES_ROOT, "tmp"); // Single typed boundary for the loader's runtime-erased `AnyPlugin[]`:// fixture plugins are hand-rolled JS objects whose extra fields (id,// packageName, __optionsReceived) only matter to these tests, not to the// loader. Centralising the bridge here keeps every assertion in the// suite working off a precise type instead of repeating cast noise.interface FixturePlugin {  readonly id: string;  readonly packageName: string;  readonly __optionsReceived: Record<string, unknown> | null;} const asFixturePlugins = (  plugins: readonly AnyPlugin[] | null,): readonly FixturePlugin[] => {  // Fixture servers return shapes wider than AnyPlugin (they carry  // __optionsReceived for test assertions); narrow once here.  // oxlint-disable-next-line executor/no-double-cast  return plugins as unknown as readonly FixturePlugin[];}; const writeJsonc = (name: string, body: string): string => {  const dir = fs.mkdtempSync(path.join(TMP_ROOT, `${name}-`));  const file = path.join(dir, "executor.jsonc");  fs.writeFileSync(file, body);  return file;}; beforeAll(() => {  fs.mkdirSync(TMP_ROOT, { recursive: true });}); afterAll(() => {  fs.rmSync(TMP_ROOT, { recursive: true, force: true });}); describe("loadPluginsFromJsonc", () => {  it("returns null when the jsonc file does not exist", async () => {    const plugins = await loadPluginsFromJsonc({      path: path.join(TMP_ROOT, "nonexistent-config.jsonc"),    });    expect(plugins).toBeNull();  });   it("returns null when the file has no `plugins` key", async () => {    const file = writeJsonc("no-plugins-key", `{ "name": "demo" }`);    const plugins = await loadPluginsFromJsonc({ path: file });    expect(plugins).toBeNull();  });   it("returns null for an empty plugins array", async () => {    const file = writeJsonc("empty-plugins", `{ "plugins": [] }`);    const plugins = await loadPluginsFromJsonc({ path: file });    expect(plugins).toBeNull();  });   it("throws on JSONC parse errors", async () => {    const file = writeJsonc("parse-error", `{ "plugins": [`);    await expect(loadPluginsFromJsonc({ path: file })).rejects.toThrow(      /failed to parse/i,    );  });   it("throws with a helpful message when the package can't be resolved", async () => {    const file = writeJsonc(      "unresolved",      `{        "plugins": [          { "package": "@loader-fixture/does-not-exist" }        ]      }`,    );    await expect(loadPluginsFromJsonc({ path: file })).rejects.toThrow(      /cannot resolve "@loader-fixture\/does-not-exist\/server"/,    );  });   it("throws when the resolved module has no callable factory", async () => {    const file = writeJsonc(      "no-factory",      `{        "plugins": [          { "package": "@loader-fixture/plugin-bad" }        ]      }`,    );    await expect(loadPluginsFromJsonc({ path: file })).rejects.toThrow(      /did not export a default[\s\S]*definePlugin/,    );  });   it("loads a plugin and exposes the package's id + packageName", async () => {    const file = writeJsonc(      "happy-path",      `{        "plugins": [          { "package": "@loader-fixture/plugin-alpha" }        ]      }`,    );    const plugins = asFixturePlugins(await loadPluginsFromJsonc({ path: file }));    expect(plugins).toHaveLength(1);    expect(plugins[0]!.id).toBe("alpha");    expect(plugins[0]!.packageName).toBe("@loader-fixture/plugin-alpha");  });   it("forwards merged deps + options to the factory; entry options win on conflict", async () => {    const file = writeJsonc(      "merge-options",      `{        "plugins": [          {            "package": "@loader-fixture/plugin-alpha",            "options": { "from": "options", "shared": "options-wins" }          }        ]      }`,    );    const plugins = asFixturePlugins(      await loadPluginsFromJsonc({        path: file,        deps: { from: "deps", shared: "deps-loses", onlyDep: 42 },      }),    );    expect(plugins[0]!.__optionsReceived).toEqual({      from: "options",      shared: "options-wins",      onlyDep: 42,    });  });   it("calls the factory with deps only when no options are declared", async () => {    const file = writeJsonc(      "deps-only",      `{        "plugins": [          { "package": "@loader-fixture/plugin-alpha" }        ]      }`,    );    const plugins = asFixturePlugins(      await loadPluginsFromJsonc({        path: file,        deps: { configFile: "stub-sink" },      }),    );    expect(plugins[0]!.__optionsReceived).toEqual({ configFile: "stub-sink" });  });   it("loads multiple plugins in declared order", async () => {    const file = writeJsonc(      "ordered",      `{        "plugins": [          { "package": "@loader-fixture/plugin-beta" },          { "package": "@loader-fixture/plugin-alpha" }        ]      }`,    );    const plugins = asFixturePlugins(await loadPluginsFromJsonc({ path: file }));    expect(plugins.map((p) => p.id)).toEqual(["beta", "alpha"]);  });   it("accepts line and block comments (jsonc semantics)", async () => {    const file = writeJsonc(      "jsonc-syntax",      `{        // pick the alpha fixture        "plugins": [          /* one entry */          { "package": "@loader-fixture/plugin-alpha" }        ]      }`,    );    const plugins = await loadPluginsFromJsonc({ path: file });    expect(plugins).toHaveLength(1);  });});

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t