Files
agentic-dev-template/turbo/generators/config.test.ts
Danijel Martinek 47627f1a54 feat(generators): core-ui-component generator + action helpers + tests
Adds the setGenerator("core-ui-component") block, coreUiComponentActions
helper (2 guards + 4 add + 1 modify + 1 print = 8 actions), and
printCoreUiComponentNextSteps to config.ts; covers all three paths with
3 new unit tests in config.test.ts (registration shape, action shape per
tier, PascalCase validator). Generator test count: 17 → 20.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 09:20:38 +02:00

151 lines
6.5 KiB
TypeScript

import { describe, it, expect } from "vitest";
import type { PlopTypes } from "@turbo/gen";
import generator from "./config.js";
describe("core-package generator", () => {
it("is registered with realtime and events in choices list", () => {
const captured: Array<{ name: string; def: PlopTypes.PlopGeneratorConfig }> = [];
const plopMock = {
setHelper: () => {},
setGenerator: (name: string, def: PlopTypes.PlopGeneratorConfig) =>
captured.push({ name, def }),
} as unknown as PlopTypes.NodePlopAPI;
generator(plopMock);
const corePkg = captured.find((c) => c.name === "core-package");
expect(corePkg).toBeDefined();
const prompts = corePkg!.def.prompts as Array<{ name: string; choices: unknown[] }>;
expect(prompts[0]!.name).toBe("name");
expect(prompts[0]!.choices).toContain("realtime");
expect(prompts[0]!.choices).toContain("events");
expect(prompts[0]!.choices).toContain("trpc");
expect(prompts[0]!.choices).toContain("ui");
});
});
describe("core-package realtime", () => {
it("emits actions covering package files, transpilePackages, and ESLint rules", () => {
// Capture the actions returned by the realtime entry.
const captured: Array<{ name: string; def: PlopTypes.PlopGeneratorConfig }> = [];
const plop = {
setHelper: () => {},
setGenerator: (n: string, d: unknown) => captured.push({ name: n, def: d as PlopTypes.PlopGeneratorConfig }),
} as unknown as PlopTypes.NodePlopAPI;
generator(plop);
const corePkg = captured.find((c) => c.name === "core-package")!.def;
const actions = (corePkg.actions as (a: { name: string }) => PlopTypes.ActionType[])(
{ name: "realtime" },
);
// Expectations: at least one assertNotPresent guard, multiple `add` actions, and a transpilePackages action.
expect(actions.length).toBeGreaterThan(20); // 28 files + extras
});
});
describe("core-package events", () => {
it("emits actions covering package files, transpilePackages, and ESLint rule splice", () => {
const captured: Array<{ name: string; def: PlopTypes.PlopGeneratorConfig }> = [];
const plop = {
setHelper: () => {},
setGenerator: (n: string, d: unknown) => captured.push({ name: n, def: d as PlopTypes.PlopGeneratorConfig }),
} as unknown as PlopTypes.NodePlopAPI;
generator(plop);
const corePkg = captured.find((c) => c.name === "core-package")!.def;
const actions = (corePkg.actions as (a: { name: string }) => PlopTypes.ActionType[])(
{ name: "events" },
);
// 1 guard + 15 template files + transpilePackages + ESLint splice + printNextSteps = 19
expect(actions.length).toBeGreaterThanOrEqual(18);
});
});
describe("core-ui-component generator", () => {
it("is registered with tier and name prompts", () => {
const captured: Array<{ name: string; def: PlopTypes.PlopGeneratorConfig }> = [];
const plopMock = {
setHelper: () => {},
setGenerator: (name: string, def: PlopTypes.PlopGeneratorConfig) =>
captured.push({ name, def }),
} as unknown as PlopTypes.NodePlopAPI;
generator(plopMock);
const entry = captured.find((c) => c.name === "core-ui-component");
expect(entry).toBeDefined();
const prompts = entry!.def.prompts as Array<{ name: string; choices?: unknown[] }>;
expect(prompts.map((p) => p.name)).toEqual(["tier", "name"]);
expect(prompts[0]!.choices).toEqual(["atom", "molecule", "organism"]);
});
it("for each tier, emits 4 add actions, 1 modify, plus guards and print", () => {
const captured: Array<{ name: string; def: PlopTypes.PlopGeneratorConfig }> = [];
const plopMock = {
setHelper: () => {},
setGenerator: (name: string, def: PlopTypes.PlopGeneratorConfig) =>
captured.push({ name, def }),
} as unknown as PlopTypes.NodePlopAPI;
generator(plopMock);
const corePkg = captured.find((c) => c.name === "core-ui-component")!.def;
for (const tier of ["atom", "molecule", "organism"] as const) {
const actions = (corePkg.actions as (a: { tier: string; name: string }) => PlopTypes.ActionType[])(
{ tier, name: "Spinner" },
);
const tierPlural = `${tier}s`;
// 4 `add` actions, one per emitted file
const adds = actions.filter(
(a): a is PlopTypes.AddActionConfig =>
typeof a === "object" && "type" in a && (a as { type: string }).type === "add",
);
expect(adds).toHaveLength(4);
const addPaths = adds.map((a) => a.path);
expect(addPaths).toContain(
`packages/core-ui/src/${tierPlural}/{{kebabCase name}}/{{kebabCase name}}.tsx`,
);
expect(addPaths).toContain(
`packages/core-ui/src/${tierPlural}/{{kebabCase name}}/{{kebabCase name}}.stories.tsx`,
);
expect(addPaths).toContain(
`packages/core-ui/src/${tierPlural}/{{kebabCase name}}/{{kebabCase name}}.test.tsx`,
);
expect(addPaths).toContain(
`packages/core-ui/src/${tierPlural}/{{kebabCase name}}/index.ts`,
);
// 1 `modify` action targeting the tier barrel
const modifies = actions.filter(
(a): a is PlopTypes.ModifyActionConfig =>
typeof a === "object" && "type" in a && (a as { type: string }).type === "modify",
);
expect(modifies).toHaveLength(1);
expect(modifies[0]!.path).toBe(`packages/core-ui/src/${tierPlural}/index.ts`);
expect(String(modifies[0]!.pattern)).toContain(`<gen:${tierPlural}>`);
// 3 function actions (2 guards + 1 print)
const fns = actions.filter((a) => typeof a === "function");
expect(fns).toHaveLength(3);
}
});
it("PascalCase validator rejects bad names", () => {
const captured: Array<{ name: string; def: PlopTypes.PlopGeneratorConfig }> = [];
const plopMock = {
setHelper: () => {},
setGenerator: (name: string, def: PlopTypes.PlopGeneratorConfig) =>
captured.push({ name, def }),
} as unknown as PlopTypes.NodePlopAPI;
generator(plopMock);
const corePkg = captured.find((c) => c.name === "core-ui-component")!.def;
const nameValidate = (corePkg.prompts as Array<{ name: string; validate?: (i: string) => string | true }>)
.find((p) => p.name === "name")!.validate!;
expect(nameValidate("")).toBe("Required");
expect(nameValidate("spinner")).toContain("PascalCase");
expect(nameValidate("123Foo")).toContain("PascalCase");
expect(nameValidate("Foo-Bar")).toContain("PascalCase");
expect(nameValidate("Spinner")).toBe(true);
expect(nameValidate("IconButton")).toBe(true);
});
});