Skip to content

Commit 8475baf

Browse files
committed
Add drift-guard test for VertexSymbol visual field coverage
Fails when a field is added to VertexVisualStyle but not consumed by the preview or explicitly excluded with justification. Compile-time exhaustiveness via a satisfies check on a Record, runtime via test.
1 parent 71ec9a5 commit 8475baf

2 files changed

Lines changed: 92 additions & 0 deletions

File tree

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { describe, expect, it } from "vitest";
2+
3+
import {
4+
ALL_VISUAL_FIELDS,
5+
CONSUMED_VISUAL_FIELDS,
6+
EXCLUDED_VISUAL_FIELDS,
7+
} from "./visualFields";
8+
9+
/**
10+
* Drift guard — fails when a visual field is added to VertexVisualStyle but
11+
* neither consumed by VertexSymbol nor explicitly excluded with justification.
12+
*
13+
* The source of truth for "all visual fields" is ALL_VISUAL_FIELDS, which is
14+
* compile-time checked against `keyof VertexVisualStyle` via `satisfies`.
15+
*/
16+
describe("VertexSymbol visual field coverage", () => {
17+
const allVisualFields = new Set<string>(ALL_VISUAL_FIELDS);
18+
19+
it("CONSUMED ∪ EXCLUDED exhausts all visual fields", () => {
20+
const covered = new Set<string>([
21+
...CONSUMED_VISUAL_FIELDS,
22+
...EXCLUDED_VISUAL_FIELDS,
23+
]);
24+
const uncovered = [...allVisualFields].filter(f => !covered.has(f));
25+
expect(uncovered).toEqual([]);
26+
});
27+
28+
it("CONSUMED and EXCLUDED do not overlap", () => {
29+
const overlap = [...CONSUMED_VISUAL_FIELDS].filter(f =>
30+
EXCLUDED_VISUAL_FIELDS.has(f),
31+
);
32+
expect(overlap).toHaveLength(0);
33+
});
34+
35+
it("no phantom entries that aren't real visual fields", () => {
36+
const phantoms = [
37+
...[...CONSUMED_VISUAL_FIELDS].filter(f => !allVisualFields.has(f)),
38+
...[...EXCLUDED_VISUAL_FIELDS].filter(f => !allVisualFields.has(f)),
39+
];
40+
expect(phantoms).toHaveLength(0);
41+
});
42+
});
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import type { VertexVisualStyle } from "@/core/StateProvider/graphStyles";
2+
3+
/**
4+
* Compile-time proof that every key of `VertexVisualStyle` is listed. If a key
5+
* is added to the type but missing here, TypeScript reports it on the
6+
* `satisfies` line.
7+
*/
8+
type ExhaustiveKeys = Record<keyof VertexVisualStyle, true>;
9+
const ALL_VISUAL_FIELDS_RECORD = {
10+
color: true,
11+
iconUrl: true,
12+
iconImageType: true,
13+
shape: true,
14+
backgroundOpacity: true,
15+
borderWidth: true,
16+
borderColor: true,
17+
borderStyle: true,
18+
} as const satisfies ExhaustiveKeys;
19+
20+
export const ALL_VISUAL_FIELDS = Object.keys(
21+
ALL_VISUAL_FIELDS_RECORD,
22+
) as (keyof VertexVisualStyle)[];
23+
24+
/**
25+
* Visual fields that VertexSymbol renders. Typed as `keyof VertexVisualStyle`
26+
* so adding a field to the type without listing it here is a compile error in
27+
* the drift-guard test (which asserts this set + the excluded set exhausts
28+
* the type's keys).
29+
*/
30+
export const CONSUMED_VISUAL_FIELDS: ReadonlySet<keyof VertexVisualStyle> =
31+
new Set([
32+
"color",
33+
"iconUrl",
34+
"iconImageType",
35+
"shape",
36+
"backgroundOpacity",
37+
"borderWidth",
38+
"borderColor",
39+
"borderStyle",
40+
]);
41+
42+
/**
43+
* Visual fields intentionally not rendered by the preview. Each entry must have
44+
* a justification comment here — the drift-guard test asserts
45+
* `CONSUMED ∪ EXCLUDED = all keys of VertexVisualStyle`.
46+
*/
47+
export const EXCLUDED_VISUAL_FIELDS: ReadonlySet<keyof VertexVisualStyle> =
48+
new Set([
49+
// None currently excluded — all visual fields are rendered.
50+
]);

0 commit comments

Comments
 (0)