Files
community-rule/tests/unit/methodFacets.test.ts
T
2026-05-22 14:32:15 -06:00

93 lines
3.7 KiB
TypeScript

import { describe, expect, it } from "vitest";
import { readFileSync } from "node:fs";
import path from "node:path";
import { catalogMethodSlugsForSection } from "../../lib/server/governanceCatalog";
import {
FACET_GROUP_IDS,
FACET_VALUE_IDS_BY_GROUP,
SECTION_IDS,
type SectionId,
facetGroupsFileSchema,
sectionFacetsSchema,
} from "../../lib/server/validation/methodFacetsSchemas";
const REPO_ROOT = path.resolve(__dirname, "..", "..");
const SECTION_TO_MESSAGES_FILE: Record<SectionId, string> = {
communication: "messages/en/create/customRule/communication.json",
membership: "messages/en/create/customRule/membership.json",
decisionApproaches: "messages/en/create/customRule/decisionApproaches.json",
conflictManagement: "messages/en/create/customRule/conflictManagement.json",
};
function readJson<T>(rel: string): T {
return JSON.parse(readFileSync(path.join(REPO_ROOT, rel), "utf8")) as T;
}
describe("data/create/customRule parity (CR-88)", () => {
for (const section of SECTION_IDS) {
const messagesPath = SECTION_TO_MESSAGES_FILE[section];
const dataPath = `data/create/customRule/${section}.json`;
it(`${section}: facet file matches messages methods one-to-one`, () => {
const messages = readJson<{ methods: { id: string }[] }>(messagesPath);
const dataParsed = sectionFacetsSchema.safeParse(readJson(dataPath));
expect(dataParsed.success).toBe(true);
if (!dataParsed.success) return;
const messageSlugs = new Set(messages.methods.map((m) => m.id));
const dataSlugs = new Set(Object.keys(dataParsed.data));
const onlyInMessages = [...messageSlugs].filter((s) => !dataSlugs.has(s));
const onlyInData = [...dataSlugs].filter((s) => !messageSlugs.has(s));
expect(onlyInMessages, `${section} slugs missing from data/`).toEqual([]);
expect(onlyInData, `${section} slugs missing from messages/`).toEqual([]);
const catalogSlugs = catalogMethodSlugsForSection(section);
expect(catalogSlugs).toEqual([...messageSlugs]);
});
}
});
describe("data/create/customRule/_facetGroups.json (CR-88)", () => {
const groups = readJson<unknown>("data/create/customRule/_facetGroups.json");
it("matches the facetGroupsFileSchema", () => {
const parsed = facetGroupsFileSchema.safeParse(groups);
expect(parsed.success).toBe(true);
});
it("every chipId resolves to a real position in the referenced messages file", () => {
const parsed = facetGroupsFileSchema.parse(groups);
for (const group of FACET_GROUP_IDS) {
const block = parsed[group];
// source is "messages/en/.../foo.json#/<arrayKey>"; resolve relative to repo root.
const [filePath, jsonPointer] = block.source.split("#");
const file = readJson<Record<string, { label: string }[]>>(filePath);
const arrayKey = jsonPointer.replace(/^\//, "");
const arr = file[arrayKey];
expect(Array.isArray(arr), `${group}: ${block.source} → array`).toBe(true);
const positions = new Set(arr.map((_, i) => String(i + 1)));
for (const [valueId, { chipId }] of Object.entries(block.values)) {
expect(
positions.has(chipId),
`${group}.${valueId} chipId ${chipId} should be a position in ${block.source} (have ${[...positions].join(",")})`,
).toBe(true);
}
}
});
it("uses the canonical 19 value ids across the four groups", () => {
const parsed = facetGroupsFileSchema.parse(groups);
let total = 0;
for (const group of FACET_GROUP_IDS) {
const expected = new Set(FACET_VALUE_IDS_BY_GROUP[group]);
const actual = new Set(Object.keys(parsed[group].values));
expect(actual).toEqual(expected);
total += actual.size;
}
expect(total).toBe(19);
});
});