Curated, product-agnostic snapshot of the post-story-04 tree: demo content deleted, auth-only reference feature, web-next shell, all gates green. Product-specific docs, ADRs 027-029, PRDs/epics/archive, editor library traces, and product naming are curated out; generic template repairs (coverage provider devDeps, root test:coverage script, live lint fixes, root-only release-please) are kept. See TEMPLATE.md for provenance, curation list, and usage. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016j8z4VHjedXDTjEDNg7qHK
190 lines
6.7 KiB
TypeScript
190 lines
6.7 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);
|
|
});
|
|
});
|