test(web): add 232 tests for catalog, presets, skeleton, hooks

Lib: blueprint-field-catalog (74).

Hooks: useAppPreferences (25), useTheme (19),
useMultiSelectIntersection (12), useTimelineKeyboard (21).

Components: ColumnTogglePanel, DateRangePresets (17, timezone-safe),
ShimmerSkeleton (29), SuccessToast.

Fix ShimmerGroup tests to use plain divs (ShimmerSkeleton doesn't
forward the style prop from cloneElement).
Fix DateRangePresets tests to compute expected dates via toISOString
matching the component's UTC conversion.

Web test suite: 87 → 96 files, 844 → 1076 tests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-10 17:27:35 +02:00
parent a3d75973ee
commit dcac9952ca
9 changed files with 3136 additions and 0 deletions
@@ -0,0 +1,542 @@
import { describe, expect, it } from "vitest";
import { BlueprintTarget, FieldType } from "@capakraken/shared";
import {
type CatalogCategory,
type CatalogField,
PROJECT_CATEGORIES,
PROJECT_FIELD_CATALOG,
RESOURCE_CATEGORIES,
RESOURCE_FIELD_CATALOG,
findCatalogField,
getCatalogForTarget,
getCategoriesForTarget,
} from "./blueprint-field-catalog.js";
// ---------------------------------------------------------------------------
// CatalogField shape
// ---------------------------------------------------------------------------
describe("CatalogField interface", () => {
it("every PROJECT_FIELD_CATALOG entry has required fields", () => {
for (const field of PROJECT_FIELD_CATALOG) {
expect(field.key).toBeTypeOf("string");
expect(field.key.length).toBeGreaterThan(0);
expect(field.label).toBeTypeOf("string");
expect(field.label.length).toBeGreaterThan(0);
expect(field.type).toBeTypeOf("string");
expect(field.category).toBeTypeOf("string");
expect(field.description).toBeTypeOf("string");
expect(field.builtIn).toBeTypeOf("boolean");
}
});
it("every RESOURCE_FIELD_CATALOG entry has required fields", () => {
for (const field of RESOURCE_FIELD_CATALOG) {
expect(field.key).toBeTypeOf("string");
expect(field.key.length).toBeGreaterThan(0);
expect(field.label).toBeTypeOf("string");
expect(field.label.length).toBeGreaterThan(0);
expect(field.type).toBeTypeOf("string");
expect(field.category).toBeTypeOf("string");
expect(field.description).toBeTypeOf("string");
expect(field.builtIn).toBeTypeOf("boolean");
}
});
it("all fields in both catalogs have builtIn === false", () => {
const all = [...PROJECT_FIELD_CATALOG, ...RESOURCE_FIELD_CATALOG];
for (const field of all) {
expect(field.builtIn).toBe(false);
}
});
});
// ---------------------------------------------------------------------------
// Field type coverage
// ---------------------------------------------------------------------------
describe("FieldType usage in PROJECT_FIELD_CATALOG", () => {
const fieldsByType = (type: FieldType) => PROJECT_FIELD_CATALOG.filter((f) => f.type === type);
it("contains TEXT fields", () => {
expect(fieldsByType(FieldType.TEXT).length).toBeGreaterThan(0);
});
it("contains NUMBER fields", () => {
expect(fieldsByType(FieldType.NUMBER).length).toBeGreaterThan(0);
});
it("contains SELECT fields", () => {
expect(fieldsByType(FieldType.SELECT).length).toBeGreaterThan(0);
});
it("contains MULTI_SELECT fields", () => {
expect(fieldsByType(FieldType.MULTI_SELECT).length).toBeGreaterThan(0);
});
it("contains DATE fields", () => {
expect(fieldsByType(FieldType.DATE).length).toBeGreaterThan(0);
});
it("contains BOOLEAN fields", () => {
expect(fieldsByType(FieldType.BOOLEAN).length).toBeGreaterThan(0);
});
it("contains URL fields", () => {
expect(fieldsByType(FieldType.URL).length).toBeGreaterThan(0);
});
});
describe("FieldType usage in RESOURCE_FIELD_CATALOG", () => {
const fieldsByType = (type: FieldType) => RESOURCE_FIELD_CATALOG.filter((f) => f.type === type);
it("contains TEXT fields", () => {
expect(fieldsByType(FieldType.TEXT).length).toBeGreaterThan(0);
});
it("contains NUMBER fields", () => {
expect(fieldsByType(FieldType.NUMBER).length).toBeGreaterThan(0);
});
it("contains SELECT fields", () => {
expect(fieldsByType(FieldType.SELECT).length).toBeGreaterThan(0);
});
it("contains MULTI_SELECT fields", () => {
expect(fieldsByType(FieldType.MULTI_SELECT).length).toBeGreaterThan(0);
});
it("contains DATE fields", () => {
expect(fieldsByType(FieldType.DATE).length).toBeGreaterThan(0);
});
it("contains BOOLEAN fields", () => {
expect(fieldsByType(FieldType.BOOLEAN).length).toBeGreaterThan(0);
});
it("contains EMAIL fields", () => {
expect(fieldsByType(FieldType.EMAIL).length).toBeGreaterThan(0);
});
it("contains URL fields", () => {
expect(fieldsByType(FieldType.URL).length).toBeGreaterThan(0);
});
});
// ---------------------------------------------------------------------------
// Options on SELECT / MULTI_SELECT fields
// ---------------------------------------------------------------------------
describe("SELECT and MULTI_SELECT fields have options", () => {
const selectTypes = new Set([FieldType.SELECT, FieldType.MULTI_SELECT]);
it("every SELECT/MULTI_SELECT in PROJECT_FIELD_CATALOG has at least one option", () => {
const selectFields = PROJECT_FIELD_CATALOG.filter((f) => selectTypes.has(f.type));
expect(selectFields.length).toBeGreaterThan(0);
for (const field of selectFields) {
expect(Array.isArray(field.options)).toBe(true);
expect(field.options!.length).toBeGreaterThan(0);
}
});
it("every SELECT/MULTI_SELECT in RESOURCE_FIELD_CATALOG has at least one option", () => {
const selectFields = RESOURCE_FIELD_CATALOG.filter((f) => selectTypes.has(f.type));
expect(selectFields.length).toBeGreaterThan(0);
for (const field of selectFields) {
expect(Array.isArray(field.options)).toBe(true);
expect(field.options!.length).toBeGreaterThan(0);
}
});
it("each option has non-empty value and label strings", () => {
const all = [...PROJECT_FIELD_CATALOG, ...RESOURCE_FIELD_CATALOG];
for (const field of all) {
for (const opt of field.options ?? []) {
expect(opt.value).toBeTypeOf("string");
expect(opt.value.length).toBeGreaterThan(0);
expect(opt.label).toBeTypeOf("string");
expect(opt.label.length).toBeGreaterThan(0);
}
}
});
it("non-SELECT/MULTI_SELECT fields do not declare options", () => {
const nonSelect = [...PROJECT_FIELD_CATALOG, ...RESOURCE_FIELD_CATALOG].filter(
(f) => !selectTypes.has(f.type),
);
for (const field of nonSelect) {
expect(field.options).toBeUndefined();
}
});
});
// ---------------------------------------------------------------------------
// defaultValue presence
// ---------------------------------------------------------------------------
describe("defaultValue on catalog fields", () => {
it("clientApprovalRounds has defaultValue 2", () => {
const field = PROJECT_FIELD_CATALOG.find((f) => f.key === "clientApprovalRounds");
expect(field).toBeDefined();
expect(field!.defaultValue).toBe(2);
});
it("nda has defaultValue false", () => {
const field = PROJECT_FIELD_CATALOG.find((f) => f.key === "nda");
expect(field).toBeDefined();
expect(field!.defaultValue).toBe(false);
});
it("weeklyHours has defaultValue 40", () => {
const field = RESOURCE_FIELD_CATALOG.find((f) => f.key === "weeklyHours");
expect(field).toBeDefined();
expect(field!.defaultValue).toBe(40);
});
it("remoteEligible has defaultValue false", () => {
const field = RESOURCE_FIELD_CATALOG.find((f) => f.key === "remoteEligible");
expect(field).toBeDefined();
expect(field!.defaultValue).toBe(false);
});
it("fields without explicit defaultValue have undefined defaultValue", () => {
const field = PROJECT_FIELD_CATALOG.find((f) => f.key === "clientUnit");
expect(field).toBeDefined();
expect(field!.defaultValue).toBeUndefined();
});
});
// ---------------------------------------------------------------------------
// Unique keys within each catalog
// ---------------------------------------------------------------------------
describe("catalog key uniqueness", () => {
it("PROJECT_FIELD_CATALOG has no duplicate keys", () => {
const keys = PROJECT_FIELD_CATALOG.map((f) => f.key);
const unique = new Set(keys);
expect(unique.size).toBe(keys.length);
});
it("RESOURCE_FIELD_CATALOG has no duplicate keys", () => {
const keys = RESOURCE_FIELD_CATALOG.map((f) => f.key);
const unique = new Set(keys);
expect(unique.size).toBe(keys.length);
});
});
// ---------------------------------------------------------------------------
// Category alignment
// ---------------------------------------------------------------------------
describe("catalog field categories match declared category lists", () => {
it("every PROJECT_FIELD_CATALOG category exists in PROJECT_CATEGORIES", () => {
const validNames = new Set(PROJECT_CATEGORIES.map((c) => c.name));
for (const field of PROJECT_FIELD_CATALOG) {
expect(validNames.has(field.category)).toBe(true);
}
});
it("every RESOURCE_FIELD_CATALOG category exists in RESOURCE_CATEGORIES", () => {
const validNames = new Set(RESOURCE_CATEGORIES.map((c) => c.name));
for (const field of RESOURCE_FIELD_CATALOG) {
expect(validNames.has(field.category)).toBe(true);
}
});
it("each project category is used by at least one field", () => {
const usedCategories = new Set(PROJECT_FIELD_CATALOG.map((f) => f.category));
for (const cat of PROJECT_CATEGORIES) {
expect(usedCategories.has(cat.name)).toBe(true);
}
});
it("each resource category is used by at least one field", () => {
const usedCategories = new Set(RESOURCE_FIELD_CATALOG.map((f) => f.category));
for (const cat of RESOURCE_CATEGORIES) {
expect(usedCategories.has(cat.name)).toBe(true);
}
});
});
// ---------------------------------------------------------------------------
// CatalogCategory shape
// ---------------------------------------------------------------------------
describe("CatalogCategory shape", () => {
it("PROJECT_CATEGORIES entries have non-empty name and description", () => {
expect(PROJECT_CATEGORIES.length).toBeGreaterThan(0);
for (const cat of PROJECT_CATEGORIES) {
expect(cat.name).toBeTypeOf("string");
expect(cat.name.length).toBeGreaterThan(0);
expect(cat.description).toBeTypeOf("string");
expect(cat.description.length).toBeGreaterThan(0);
}
});
it("RESOURCE_CATEGORIES entries have non-empty name and description", () => {
expect(RESOURCE_CATEGORIES.length).toBeGreaterThan(0);
for (const cat of RESOURCE_CATEGORIES) {
expect(cat.name).toBeTypeOf("string");
expect(cat.name.length).toBeGreaterThan(0);
expect(cat.description).toBeTypeOf("string");
expect(cat.description.length).toBeGreaterThan(0);
}
});
});
// ---------------------------------------------------------------------------
// getCatalogForTarget
// ---------------------------------------------------------------------------
describe("getCatalogForTarget", () => {
it("returns PROJECT_FIELD_CATALOG for BlueprintTarget.PROJECT", () => {
expect(getCatalogForTarget(BlueprintTarget.PROJECT)).toBe(PROJECT_FIELD_CATALOG);
});
it("returns RESOURCE_FIELD_CATALOG for BlueprintTarget.RESOURCE", () => {
expect(getCatalogForTarget(BlueprintTarget.RESOURCE)).toBe(RESOURCE_FIELD_CATALOG);
});
it("returns PROJECT_FIELD_CATALOG for the string literal 'PROJECT'", () => {
expect(getCatalogForTarget("PROJECT")).toBe(PROJECT_FIELD_CATALOG);
});
it("returns RESOURCE_FIELD_CATALOG for any other string (fallback)", () => {
expect(getCatalogForTarget("RESOURCE")).toBe(RESOURCE_FIELD_CATALOG);
});
it("returns RESOURCE_FIELD_CATALOG for an unrecognised string", () => {
// The implementation falls back to RESOURCE catalog for non-PROJECT targets.
expect(getCatalogForTarget("unknown")).toBe(RESOURCE_FIELD_CATALOG);
});
it("returns an array", () => {
expect(Array.isArray(getCatalogForTarget(BlueprintTarget.PROJECT))).toBe(true);
expect(Array.isArray(getCatalogForTarget(BlueprintTarget.RESOURCE))).toBe(true);
});
});
// ---------------------------------------------------------------------------
// getCategoriesForTarget
// ---------------------------------------------------------------------------
describe("getCategoriesForTarget", () => {
it("returns PROJECT_CATEGORIES for BlueprintTarget.PROJECT", () => {
expect(getCategoriesForTarget(BlueprintTarget.PROJECT)).toBe(PROJECT_CATEGORIES);
});
it("returns RESOURCE_CATEGORIES for BlueprintTarget.RESOURCE", () => {
expect(getCategoriesForTarget(BlueprintTarget.RESOURCE)).toBe(RESOURCE_CATEGORIES);
});
it("returns PROJECT_CATEGORIES for the string literal 'PROJECT'", () => {
expect(getCategoriesForTarget("PROJECT")).toBe(PROJECT_CATEGORIES);
});
it("returns RESOURCE_CATEGORIES for any other string (fallback)", () => {
expect(getCategoriesForTarget("RESOURCE")).toBe(RESOURCE_CATEGORIES);
});
it("returns RESOURCE_CATEGORIES for an unrecognised string", () => {
expect(getCategoriesForTarget("unknown")).toBe(RESOURCE_CATEGORIES);
});
it("returns an array", () => {
expect(Array.isArray(getCategoriesForTarget(BlueprintTarget.PROJECT))).toBe(true);
expect(Array.isArray(getCategoriesForTarget(BlueprintTarget.RESOURCE))).toBe(true);
});
});
// ---------------------------------------------------------------------------
// findCatalogField
// ---------------------------------------------------------------------------
describe("findCatalogField", () => {
// --- project fields ---
it("finds a known project field by key", () => {
const field = findCatalogField(BlueprintTarget.PROJECT, "billingModel");
expect(field).toBeDefined();
expect(field!.key).toBe("billingModel");
expect(field!.type).toBe(FieldType.SELECT);
expect(field!.category).toBe("Client & Billing");
});
it("finds 'deliveryFormat' as MULTI_SELECT in the project catalog", () => {
const field = findCatalogField(BlueprintTarget.PROJECT, "deliveryFormat");
expect(field).toBeDefined();
expect(field!.type).toBe(FieldType.MULTI_SELECT);
});
it("finds 'deliveryDate' as DATE in the project catalog", () => {
const field = findCatalogField(BlueprintTarget.PROJECT, "deliveryDate");
expect(field).toBeDefined();
expect(field!.type).toBe(FieldType.DATE);
});
it("finds 'nda' as BOOLEAN in the project catalog", () => {
const field = findCatalogField(BlueprintTarget.PROJECT, "nda");
expect(field).toBeDefined();
expect(field!.type).toBe(FieldType.BOOLEAN);
});
it("finds 'projectBrief' as URL in the project catalog", () => {
const field = findCatalogField(BlueprintTarget.PROJECT, "projectBrief");
expect(field).toBeDefined();
expect(field!.type).toBe(FieldType.URL);
});
// --- resource fields ---
it("finds a known resource field by key", () => {
const field = findCatalogField(BlueprintTarget.RESOURCE, "contractType");
expect(field).toBeDefined();
expect(field!.key).toBe("contractType");
expect(field!.type).toBe(FieldType.SELECT);
expect(field!.category).toBe("Contract");
});
it("finds 'personalEmail' as EMAIL in the resource catalog", () => {
const field = findCatalogField(BlueprintTarget.RESOURCE, "personalEmail");
expect(field).toBeDefined();
expect(field!.type).toBe(FieldType.EMAIL);
});
it("finds 'linkedInUrl' as URL in the resource catalog", () => {
const field = findCatalogField(BlueprintTarget.RESOURCE, "linkedInUrl");
expect(field).toBeDefined();
expect(field!.type).toBe(FieldType.URL);
});
it("finds 'primarySoftware' as MULTI_SELECT in the resource catalog", () => {
const field = findCatalogField(BlueprintTarget.RESOURCE, "primarySoftware");
expect(field).toBeDefined();
expect(field!.type).toBe(FieldType.MULTI_SELECT);
});
it("finds 'remoteEligible' as BOOLEAN in the resource catalog", () => {
const field = findCatalogField(BlueprintTarget.RESOURCE, "remoteEligible");
expect(field).toBeDefined();
expect(field!.type).toBe(FieldType.BOOLEAN);
});
// --- string target variants ---
it("finds a project field when target is the string 'PROJECT'", () => {
const field = findCatalogField("PROJECT", "shotCount");
expect(field).toBeDefined();
expect(field!.key).toBe("shotCount");
});
it("finds a resource field when target is the string 'RESOURCE'", () => {
const field = findCatalogField("RESOURCE", "timezone");
expect(field).toBeDefined();
expect(field!.key).toBe("timezone");
});
// --- not found ---
it("returns undefined for an unknown key in the project catalog", () => {
expect(findCatalogField(BlueprintTarget.PROJECT, "nonExistentKey")).toBeUndefined();
});
it("returns undefined for an unknown key in the resource catalog", () => {
expect(findCatalogField(BlueprintTarget.RESOURCE, "nonExistentKey")).toBeUndefined();
});
it("returns undefined for an empty string key", () => {
expect(findCatalogField(BlueprintTarget.PROJECT, "")).toBeUndefined();
expect(findCatalogField(BlueprintTarget.RESOURCE, "")).toBeUndefined();
});
it("does not find a resource field when searching in the project catalog", () => {
// 'contractType' lives only in the resource catalog
expect(findCatalogField(BlueprintTarget.PROJECT, "contractType")).toBeUndefined();
});
it("does not find a project field when searching in the resource catalog", () => {
// 'billingModel' lives only in the project catalog
expect(findCatalogField(BlueprintTarget.RESOURCE, "billingModel")).toBeUndefined();
});
// --- returned object integrity ---
it("returned field contains the expected options array for a SELECT field", () => {
const field = findCatalogField(BlueprintTarget.PROJECT, "complexityLevel");
expect(field).toBeDefined();
const values = field!.options!.map((o) => o.value);
expect(values).toContain("low");
expect(values).toContain("medium");
expect(values).toContain("high");
expect(values).toContain("very_high");
});
});
// ---------------------------------------------------------------------------
// Specific well-known fields spot checks
// ---------------------------------------------------------------------------
describe("specific PROJECT_FIELD_CATALOG fields", () => {
it("billingModel options include fixed, tm, and hybrid", () => {
const field = PROJECT_FIELD_CATALOG.find((f) => f.key === "billingModel")!;
const values = field.options!.map((o) => o.value);
expect(values).toContain("fixed");
expect(values).toContain("tm");
expect(values).toContain("hybrid");
});
it("renderEngine includes unreal and arnold", () => {
const field = PROJECT_FIELD_CATALOG.find((f) => f.key === "renderEngine")!;
const values = field.options!.map((o) => o.value);
expect(values).toContain("unreal");
expect(values).toContain("arnold");
});
it("deliveryFormat includes exr, mp4, and glb", () => {
const field = PROJECT_FIELD_CATALOG.find((f) => f.key === "deliveryFormat")!;
const values = field.options!.map((o) => o.value);
expect(values).toContain("exr");
expect(values).toContain("mp4");
expect(values).toContain("glb");
});
it("invoiceCycle includes all 5 expected values", () => {
const field = PROJECT_FIELD_CATALOG.find((f) => f.key === "invoiceCycle")!;
const values = field.options!.map((o) => o.value);
expect(values).toEqual(
expect.arrayContaining(["weekly", "biweekly", "monthly", "milestone", "on_completion"]),
);
});
});
describe("specific RESOURCE_FIELD_CATALOG fields", () => {
it("contractType options include freelance and permanent", () => {
const field = RESOURCE_FIELD_CATALOG.find((f) => f.key === "contractType")!;
const values = field.options!.map((o) => o.value);
expect(values).toContain("freelance");
expect(values).toContain("permanent");
});
it("primarySoftware includes houdini and maya", () => {
const field = RESOURCE_FIELD_CATALOG.find((f) => f.key === "primarySoftware")!;
const values = field.options!.map((o) => o.value);
expect(values).toContain("houdini");
expect(values).toContain("maya");
});
it("spokenLanguages includes de (German) and en (English)", () => {
const field = RESOURCE_FIELD_CATALOG.find((f) => f.key === "spokenLanguages")!;
const values = field.options!.map((o) => o.value);
expect(values).toContain("de");
expect(values).toContain("en");
});
it("specialization includes td (Technical Direction) and generalist", () => {
const field = RESOURCE_FIELD_CATALOG.find((f) => f.key === "specialization")!;
const values = field.options!.map((o) => o.value);
expect(values).toContain("td");
expect(values).toContain("generalist");
});
it("timezone includes Europe/Berlin and America/Los_Angeles", () => {
const field = RESOURCE_FIELD_CATALOG.find((f) => f.key === "timezone")!;
const values = field.options!.map((o) => o.value);
expect(values).toContain("Europe/Berlin");
expect(values).toContain("America/Los_Angeles");
});
});