Files
HiveGrid-Wireframes/hivegrid-cloud/apps/web/app/application/ui/uiInventory.test.ts
T

183 lines
7.1 KiB
TypeScript

import fs from "node:fs";
import path from "node:path";
import { describe, expect, it } from "vitest";
import { uiAdapterCategories } from "@toolctrl/ui";
import { layoutAdapters } from "@toolctrl/ui/layout";
import { navigationAdapters } from "@toolctrl/ui/navigation";
import { UI_ACTION_IDS, UI_NAV_IDS, UI_SURFACE_IDS } from "~/application/ui/uiRegistry";
import { readRepoDir, repoPathExists } from "~/utils/testing/repoPaths";
type UiInventoryComponent = {
stableName: string;
path: string;
category: string;
library: string;
status: string;
};
type UiInventory = {
schemaVersion: number;
notes: string[];
components: UiInventoryComponent[];
};
type UiSurfaceMapEntry = {
id: string;
kind: "surface" | "action" | "nav";
label: string;
sourceFiles: string[];
renderer: string;
notes: string[];
};
type UiSurfaceMap = {
schemaVersion: number;
surfaces: UiSurfaceMapEntry[];
};
const inventoryPath = path.join(process.cwd(), "docs/ui-inventory/components.json");
const schemaPath = path.join(process.cwd(), "docs/ui-inventory/schema.json");
const surfacesPath = path.join(process.cwd(), "docs/ui-inventory/surfaces.json");
const surfacesSchemaPath = path.join(process.cwd(), "docs/ui-inventory/surfaces.schema.json");
const inventory = JSON.parse(fs.readFileSync(inventoryPath, "utf8")) as UiInventory;
const schema = JSON.parse(fs.readFileSync(schemaPath, "utf8")) as {
$defs: { component: { properties: { category: { enum: string[] }; status: { enum: string[] } } } };
};
const surfaceMap = JSON.parse(fs.readFileSync(surfacesPath, "utf8")) as UiSurfaceMap;
const surfaceSchema = JSON.parse(fs.readFileSync(surfacesSchemaPath, "utf8")) as {
$defs: { surface: { properties: { kind: { enum: string[] } } } };
};
const allowedCategories = schema.$defs.component.properties.category.enum;
const allowedStatuses = schema.$defs.component.properties.status.enum;
const allowedSurfaceKinds = surfaceSchema.$defs.surface.properties.kind.enum;
function physicalWebPath(sourcePath: string) {
if (sourcePath.startsWith("apps/web/app/")) {
return sourcePath;
}
return sourcePath.startsWith("app/") ? `apps/web/${sourcePath}` : sourcePath;
}
function expectStringArray(value: unknown) {
expect(Array.isArray(value)).toBe(true);
expect((value as unknown[]).every((item) => typeof item === "string")).toBe(true);
}
describe("UI inventory", () => {
it("matches the declared schema shape", () => {
expect(inventory.schemaVersion).toBe(1);
expect(Array.isArray(inventory.notes)).toBe(true);
expect(inventory.components.length).toBeGreaterThan(0);
inventory.components.forEach((component) => {
expect(component.stableName).toEqual(expect.any(String));
expect(component.path).toEqual(expect.any(String));
expect(component.library).toEqual(expect.any(String));
expect(allowedCategories).toContain(component.category);
expect(allowedStatuses).toContain(component.status);
});
});
it("uses the same category names as packages/ui", () => {
expect(new Set(allowedCategories)).toEqual(new Set(uiAdapterCategories));
});
it("contains inventory entries for every available shell/navigation adapter", () => {
const availableAdapterNames = [...layoutAdapters, ...navigationAdapters].map((adapter) => adapter.name);
const inventoryStableNames = new Set(inventory.components.map((component) => component.stableName));
availableAdapterNames.forEach((adapterName) => {
expect(inventoryStableNames.has(adapterName), adapterName).toBe(true);
});
});
it("points every inventory entry at an existing file or directory", () => {
inventory.components.forEach((component) => {
expect(repoPathExists(component.path), component.path).toBe(true);
});
});
it("covers root shadcn/custom UI primitive files", () => {
const inventoryPaths = new Set(inventory.components.map((component) => component.path));
const rootPrimitivePaths = readRepoDir("apps/web/app/components/ui")
.filter((file) => /^[a-z0-9-]+\.tsx$/.test(file))
.map((file) => `apps/web/app/components/ui/${file}`);
rootPrimitivePaths.forEach((primitivePath) => {
expect(inventoryPaths.has(primitivePath), primitivePath).toBe(true);
});
});
it("references every shell/navigation adapter source in the inventory or surface map", () => {
const inventoryPaths = new Set(inventory.components.map((component) => component.path));
const surfaceSourceFiles = new Set(surfaceMap.surfaces.flatMap((surface) => surface.sourceFiles));
const adapterSources = [...layoutAdapters, ...navigationAdapters].map((adapter) => physicalWebPath(adapter.source));
adapterSources.forEach((source) => {
expect(inventoryPaths.has(source) || surfaceSourceFiles.has(source), source).toBe(true);
});
});
});
describe("UI surface map", () => {
it("provides the UI agent playbooks and human-readable surface map", () => {
const requiredDocs = [
"docs/agent-playbooks/ui-changes.md",
"docs/agent-playbooks/ui-adapter-migration.md",
"docs/ui-inventory/surface-map.md",
"docs/ui-inventory/surfaces.json",
"docs/ui-inventory/surfaces.schema.json",
];
requiredDocs.forEach((docPath) => {
expect(fs.existsSync(path.join(process.cwd(), docPath)), docPath).toBe(true);
});
const uiPlaybook = fs.readFileSync(path.join(process.cwd(), "docs/agent-playbooks/ui-changes.md"), "utf8");
const adapterPlaybook = fs.readFileSync(path.join(process.cwd(), "docs/agent-playbooks/ui-adapter-migration.md"), "utf8");
expect(uiPlaybook).toContain("Identify the target by `data-ui-id`");
expect(adapterPlaybook).toContain("@toolctrl/ui/buttons");
});
it("matches the declared surface schema shape", () => {
expect(surfaceMap.schemaVersion).toBe(1);
expect(surfaceMap.surfaces.length).toBeGreaterThan(0);
surfaceMap.surfaces.forEach((surface) => {
expect(surface.id).toMatch(/^ui\.(surface|action|nav)\./);
expect(allowedSurfaceKinds).toContain(surface.kind);
expect(surface.label).toEqual(expect.any(String));
expect(surface.renderer).toEqual(expect.any(String));
expectStringArray(surface.sourceFiles);
expectStringArray(surface.notes);
});
});
it("points every surface map source file at an existing file", () => {
surfaceMap.surfaces.forEach((surface) => {
surface.sourceFiles.forEach((sourceFile) => {
expect(repoPathExists(sourceFile), `${surface.id} -> ${sourceFile}`).toBe(true);
});
});
});
it("maps all stable registry surface, action and nav IDs", () => {
const mappedIds = new Set(surfaceMap.surfaces.map((surface) => surface.id));
const registryIds = [...Object.values(UI_SURFACE_IDS), ...Object.values(UI_ACTION_IDS), ...Object.values(UI_NAV_IDS)];
registryIds.forEach((id) => {
expect(mappedIds.has(id), id).toBe(true);
});
});
it("keeps mapped IDs unique and kind-aligned", () => {
const ids = surfaceMap.surfaces.map((surface) => surface.id);
expect(new Set(ids).size).toBe(ids.length);
surfaceMap.surfaces.forEach((surface) => {
expect(surface.id.startsWith(`ui.${surface.kind}.`), surface.id).toBe(true);
});
});
});