@transitrix/diagrams 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +47 -47
- package/dist/__tests__/edge-path.test.js +8 -0
- package/dist/activity-card/__tests__/example.test.js +20 -1
- package/dist/activity-card/__tests__/layout.test.js +102 -11
- package/dist/activity-card/__tests__/resolver.test.js +139 -6
- package/dist/activity-card/index.d.ts +1 -1
- package/dist/activity-card/layout.d.ts +1 -1
- package/dist/activity-card/layout.js +220 -103
- package/dist/activity-card/resolver.js +107 -26
- package/dist/activity-card/types.d.ts +150 -27
- package/dist/activity-card/validate.js +3 -0
- package/dist/actor/__tests__/validate.test.js +63 -31
- package/dist/actor/index.d.ts +1 -1
- package/dist/actor/index.js +1 -1
- package/dist/actor/types.d.ts +7 -3
- package/dist/actor/types.js +17 -3
- package/dist/actor/validate.js +55 -26
- package/dist/assertion/types.d.ts +3 -1
- package/dist/assertion/types.js +1 -1
- package/dist/change/__tests__/validate.test.js +40 -34
- package/dist/change/index.d.ts +1 -0
- package/dist/change/types.d.ts +1 -1
- package/dist/change/types.js +2 -2
- package/dist/change/validate.d.ts +6 -1
- package/dist/change/validate.js +68 -41
- package/dist/compliance/__tests__/classify.test.js +34 -0
- package/dist/compliance/__tests__/coverage-metric.test.js +146 -46
- package/dist/compliance/__tests__/html.test.js +2 -2
- package/dist/compliance/__tests__/impact.test.js +311 -6
- package/dist/compliance/classify.d.ts +6 -0
- package/dist/compliance/classify.js +9 -1
- package/dist/compliance/coverage-metric.d.ts +31 -11
- package/dist/compliance/coverage-metric.js +149 -39
- package/dist/compliance/html.js +77 -74
- package/dist/compliance/impact.d.ts +77 -16
- package/dist/compliance/impact.js +193 -25
- package/dist/compliance/index.d.ts +2 -2
- package/dist/compliance/index.js +1 -1
- package/dist/compliance/markdown.js +1 -0
- package/dist/compliance/types.d.ts +25 -6
- package/dist/edge-path.d.ts +8 -4
- package/dist/edge-path.js +11 -6
- package/dist/factor/__tests__/validate.test.js +74 -45
- package/dist/factor/index.d.ts +3 -2
- package/dist/factor/index.js +1 -1
- package/dist/factor/types.d.ts +4 -7
- package/dist/factor/types.js +10 -5
- package/dist/factor/validate.d.ts +6 -1
- package/dist/factor/validate.js +68 -50
- package/dist/fgca/__tests__/resolver.test.js +185 -0
- package/dist/fgca/index.d.ts +2 -0
- package/dist/fgca/index.js +1 -0
- package/dist/fgca/resolver.d.ts +49 -0
- package/dist/fgca/resolver.js +124 -0
- package/dist/goals/index.d.ts +1 -1
- package/dist/index.d.ts +5 -0
- package/dist/index.js +5 -0
- package/dist/process-blueprint/__tests__/layout.test.js +37 -0
- package/dist/process-blueprint/layout.js +5 -1
- package/dist/process-blueprint/types.d.ts +22 -1
- package/dist/stakeholder/__tests__/validate.test.js +49 -47
- package/dist/stakeholder/index.d.ts +1 -0
- package/dist/stakeholder/types.d.ts +3 -4
- package/dist/stakeholder/types.js +2 -3
- package/dist/stakeholder/validate.d.ts +6 -1
- package/dist/stakeholder/validate.js +71 -50
- package/dist/target-state/__tests__/validate.test.js +43 -27
- package/dist/target-state/index.d.ts +2 -0
- package/dist/target-state/index.js +1 -0
- package/dist/target-state/types.d.ts +6 -1
- package/dist/target-state/types.js +8 -7
- package/dist/target-state/validate.d.ts +6 -1
- package/dist/target-state/validate.js +69 -45
- package/dist/theme/themes.js +69 -69
- package/dist/webview/__tests__/entry.test.js +11 -11
- package/dist/webview/render-activities.d.ts +3 -1
- package/dist/webview/render-activities.js +2 -2
- package/dist/webview/render-activity-card.js +104 -77
- package/dist/webview/render-fgca.d.ts +4 -0
- package/dist/webview/render-fgca.js +2 -5
- package/dist/webview/render-goals.d.ts +1 -0
- package/dist/webview/render-goals.js +15 -15
- package/dist/webview/render-process-blueprint.js +10 -10
- package/package.json +40 -40
- package/src/__tests__/edge-path.test.ts +58 -46
- package/src/__tests__/scope.test.ts +27 -27
- package/src/__tests__/yaml-normalize.test.ts +158 -158
- package/src/activities/__tests__/cpm.test.ts +164 -164
- package/src/activities/__tests__/gantt.test.ts +245 -245
- package/src/activities/__tests__/layout.test.ts +112 -112
- package/src/activities/__tests__/validate.test.ts +409 -409
- package/src/activities/cpm.ts +95 -95
- package/src/activities/gantt.ts +276 -276
- package/src/activities/index.ts +28 -28
- package/src/activities/layout.ts +112 -112
- package/src/activities/types.ts +148 -148
- package/src/activities/validate.ts +357 -357
- package/src/activity-card/__tests__/example.test.ts +85 -66
- package/src/activity-card/__tests__/layout.test.ts +169 -63
- package/src/activity-card/__tests__/resolver.test.ts +307 -161
- package/src/activity-card/__tests__/validate.test.ts +131 -131
- package/src/activity-card/index.ts +38 -32
- package/src/activity-card/layout.ts +324 -194
- package/src/activity-card/resolver.ts +415 -337
- package/src/activity-card/types.ts +376 -237
- package/src/activity-card/validate.ts +162 -158
- package/src/actor/__tests__/validate.test.ts +70 -31
- package/src/actor/index.ts +1 -1
- package/src/actor/types.ts +21 -7
- package/src/actor/validate.ts +59 -27
- package/src/applications/__tests__/validate.test.ts +256 -256
- package/src/applications/index.ts +11 -11
- package/src/applications/types.ts +43 -43
- package/src/applications/validate.ts +131 -131
- package/src/assertion/__tests__/example.test.ts +37 -37
- package/src/assertion/__tests__/validate.test.ts +142 -142
- package/src/assertion/index.ts +12 -12
- package/src/assertion/types.ts +60 -58
- package/src/assertion/validate.ts +145 -145
- package/src/blocks/__tests__/layout.test.ts +169 -169
- package/src/blocks/__tests__/validate.test.ts +260 -260
- package/src/blocks/index.ts +17 -17
- package/src/blocks/layout.ts +186 -186
- package/src/blocks/types.ts +62 -62
- package/src/blocks/validate.ts +206 -206
- package/src/capability-map/__tests__/validate.test.ts +238 -238
- package/src/capability-map/index.ts +8 -8
- package/src/capability-map/types.ts +29 -29
- package/src/capability-map/validate.ts +127 -127
- package/src/change/__tests__/validate.test.ts +43 -34
- package/src/change/index.ts +1 -0
- package/src/change/types.ts +5 -3
- package/src/change/validate.ts +75 -43
- package/src/compliance/__tests__/classify.test.ts +64 -27
- package/src/compliance/__tests__/confidence.test.ts +98 -98
- package/src/compliance/__tests__/coverage-metric.test.ts +300 -188
- package/src/compliance/__tests__/demo-scenario.test.ts +273 -273
- package/src/compliance/__tests__/example.test.ts +57 -57
- package/src/compliance/__tests__/gap-report.test.ts +128 -128
- package/src/compliance/__tests__/html.test.ts +141 -141
- package/src/compliance/__tests__/impact.test.ts +840 -481
- package/src/compliance/__tests__/markdown.test.ts +94 -94
- package/src/compliance/__tests__/views.test.ts +64 -64
- package/src/compliance/classify.ts +100 -84
- package/src/compliance/confidence.ts +69 -69
- package/src/compliance/coverage-metric.ts +404 -263
- package/src/compliance/gap-report.ts +94 -94
- package/src/compliance/html.ts +298 -295
- package/src/compliance/impact.ts +861 -677
- package/src/compliance/index.ts +57 -56
- package/src/compliance/markdown.ts +136 -135
- package/src/compliance/reverse-index.ts +33 -33
- package/src/compliance/types.ts +136 -120
- package/src/compliance/views.ts +46 -46
- package/src/compliance-matrix/__tests__/build.test.ts +200 -200
- package/src/compliance-matrix/__tests__/example.test.ts +57 -57
- package/src/compliance-matrix/index.ts +11 -11
- package/src/compliance-matrix/types.ts +102 -102
- package/src/edge-path.ts +50 -44
- package/src/factor/__tests__/validate.test.ts +80 -45
- package/src/factor/index.ts +3 -7
- package/src/factor/types.ts +14 -18
- package/src/factor/validate.ts +77 -51
- package/src/fgca/__tests__/chain-table.test.ts +172 -172
- package/src/fgca/__tests__/layout.test.ts +179 -179
- package/src/fgca/__tests__/parse-canonical.test.ts +248 -248
- package/src/fgca/__tests__/preview-layout.test.ts +124 -124
- package/src/fgca/__tests__/resolver.test.ts +209 -0
- package/src/fgca/__tests__/validate.test.ts +142 -142
- package/src/fgca/chain-table.ts +225 -225
- package/src/fgca/index.ts +47 -45
- package/src/fgca/layout.ts +204 -204
- package/src/fgca/nodeTypes.ts +11 -11
- package/src/fgca/nodes/FGCAActivityNode.tsx +65 -65
- package/src/fgca/nodes/FGCAChangeNode.tsx +64 -64
- package/src/fgca/nodes/FGCAFactorNode.tsx +64 -64
- package/src/fgca/nodes/FGCAGoalNode.tsx +72 -72
- package/src/fgca/parse-canonical.ts +400 -400
- package/src/fgca/preview-layout.ts +217 -217
- package/src/fgca/resolver.ts +157 -0
- package/src/fgca/types.ts +50 -50
- package/src/fgca/validate.ts +126 -126
- package/src/geometry.ts +9 -9
- package/src/goals/__tests__/layout.test.ts +175 -175
- package/src/goals/__tests__/mutations.test.ts +111 -111
- package/src/goals/__tests__/parse-canonical.test.ts +145 -145
- package/src/goals/__tests__/validate.test.ts +202 -202
- package/src/goals/index.ts +20 -21
- package/src/goals/layout.ts +172 -172
- package/src/goals/mutations.ts +123 -123
- package/src/goals/parse-canonical.ts +286 -286
- package/src/goals/types.ts +85 -85
- package/src/goals/validate.ts +136 -136
- package/src/index.ts +28 -23
- package/src/process-blueprint/__tests__/compliance-lane.test.ts +320 -320
- package/src/process-blueprint/__tests__/layout.test.ts +240 -195
- package/src/process-blueprint/__tests__/validate.test.ts +310 -310
- package/src/process-blueprint/index.ts +31 -31
- package/src/process-blueprint/layout.ts +535 -531
- package/src/process-blueprint/types.ts +263 -242
- package/src/process-blueprint/validate.ts +181 -181
- package/src/process-map/__tests__/validate.test.ts +223 -223
- package/src/process-map/index.ts +10 -10
- package/src/process-map/types.ts +36 -36
- package/src/process-map/validate.ts +135 -135
- package/src/products/__tests__/validate.test.ts +239 -239
- package/src/products/index.ts +9 -9
- package/src/products/types.ts +31 -31
- package/src/products/validate.ts +112 -112
- package/src/repo-validate/__tests__/validate-repo.test.ts +149 -149
- package/src/repo-validate/index.ts +2 -2
- package/src/repo-validate/types.ts +50 -50
- package/src/repo-validate/validate-repo.ts +196 -196
- package/src/requirement/__tests__/example.test.ts +24 -24
- package/src/requirement/__tests__/validate.test.ts +96 -96
- package/src/requirement/index.ts +4 -4
- package/src/requirement/types.ts +35 -35
- package/src/requirement/validate.ts +92 -92
- package/src/scenarios/__tests__/validate.test.ts +209 -209
- package/src/scenarios/index.ts +15 -15
- package/src/scenarios/types.ts +37 -37
- package/src/scenarios/validate.ts +122 -122
- package/src/schema-version.ts +16 -16
- package/src/scope.ts +46 -46
- package/src/stakeholder/__tests__/validate.test.ts +51 -47
- package/src/stakeholder/index.ts +1 -0
- package/src/stakeholder/types.ts +7 -7
- package/src/stakeholder/validate.ts +79 -52
- package/src/target-state/__tests__/validate.test.ts +48 -27
- package/src/target-state/index.ts +2 -0
- package/src/target-state/types.ts +11 -7
- package/src/target-state/validate.ts +76 -46
- package/src/theme/index.ts +4 -4
- package/src/theme/themes.ts +237 -237
- package/src/theme/tokens.ts +133 -133
- package/src/typed-id.ts +56 -56
- package/src/validation-types.ts +36 -36
- package/src/webview/__tests__/entry-notations.test.ts +77 -77
- package/src/webview/__tests__/entry.test.ts +64 -64
- package/src/webview/__tests__/render-goals.test.ts +72 -72
- package/src/webview/entry.ts +292 -292
- package/src/webview/render-activities.ts +5 -3
- package/src/webview/render-activity-card.ts +266 -234
- package/src/webview/render-fgca.ts +6 -5
- package/src/webview/render-goals.ts +111 -110
- package/src/webview/render-process-blueprint.ts +207 -207
- package/src/webview/styles.css +322 -323
- package/src/yaml-normalize.ts +56 -56
- package/dist/element-envelope.d.ts +0 -15
- package/dist/element-envelope.js +0 -55
- package/dist/issues/__tests__/layout.test.js +0 -62
- package/dist/issues/__tests__/validate.test.d.ts +0 -1
- package/dist/issues/__tests__/validate.test.js +0 -82
- package/dist/issues/index.d.ts +0 -4
- package/dist/issues/index.js +0 -2
- package/dist/issues/layout.d.ts +0 -10
- package/dist/issues/layout.js +0 -82
- package/dist/issues/types.d.ts +0 -61
- package/dist/issues/types.js +0 -6
- package/dist/issues/validate.d.ts +0 -3
- package/dist/issues/validate.js +0 -143
- package/dist/webview/transitrix-render.css +0 -323
- package/dist/webview/transitrix-render.js +0 -6452
- package/dist/webview/transitrix-render.js.map +0 -7
- package/src/element-envelope.ts +0 -76
- /package/dist/{issues/__tests__/layout.test.d.ts → fgca/__tests__/resolver.test.d.ts} +0 -0
|
@@ -1,179 +1,179 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeAll } from "vitest";
|
|
2
|
-
|
|
3
|
-
// reactflow uses browser APIs — stub the minimum required before importing layout
|
|
4
|
-
beforeAll(() => {
|
|
5
|
-
vi.mock("reactflow", () => ({
|
|
6
|
-
Position: { Left: "left", Right: "right" },
|
|
7
|
-
MarkerType: { ArrowClosed: "arrowclosed" },
|
|
8
|
-
}));
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
import { buildFGCALayout } from "../layout";
|
|
12
|
-
import type { FactorItem, GoalItem, BdnChangeWithActivities, ActivityItem } from "../types";
|
|
13
|
-
import { ALL_FGCA_COLUMNS } from "../types";
|
|
14
|
-
|
|
15
|
-
const ALL_COLS = new Set(ALL_FGCA_COLUMNS);
|
|
16
|
-
|
|
17
|
-
const factors: FactorItem[] = [
|
|
18
|
-
{ id: 1, name: "Digital growth" },
|
|
19
|
-
{ id: 2, name: "Talent shortage" },
|
|
20
|
-
];
|
|
21
|
-
|
|
22
|
-
const goals: GoalItem[] = [
|
|
23
|
-
{ id: 10, name: "Grow revenue", level: 0, factor: [{ id: 1 }] },
|
|
24
|
-
{ id: 11, name: "Build talent pipeline", level: 1, factor: [{ id: 2 }] },
|
|
25
|
-
];
|
|
26
|
-
|
|
27
|
-
const changes: BdnChangeWithActivities[] = [
|
|
28
|
-
{ id: 100, name: "Launch digital channel", goal_id: 10, activity_ids: [200] },
|
|
29
|
-
{ id: 101, name: "Partner with bootcamps", goal_id: 11, activity_ids: [201] },
|
|
30
|
-
];
|
|
31
|
-
|
|
32
|
-
const activities: ActivityItem[] = [
|
|
33
|
-
{ id: 200, name: "Build e-commerce MVP", goal_id: 10, activity_type_id: 1 },
|
|
34
|
-
{ id: 201, name: "Run hiring sprint", goal_id: 11, activity_type_id: 1 },
|
|
35
|
-
];
|
|
36
|
-
|
|
37
|
-
describe("buildFGCALayout — all columns visible", () => {
|
|
38
|
-
const { nodes, edges } = buildFGCALayout({
|
|
39
|
-
factors,
|
|
40
|
-
goals,
|
|
41
|
-
changes,
|
|
42
|
-
activities,
|
|
43
|
-
visibleColumns: ALL_COLS,
|
|
44
|
-
activityTypeList: [{ id: 1, name: "Initiative" }],
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
it("produces the correct number of nodes", () => {
|
|
48
|
-
expect(nodes).toHaveLength(factors.length + goals.length + changes.length + activities.length);
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
it("assigns correct node types", () => {
|
|
52
|
-
const types = nodes.map((n) => n.type);
|
|
53
|
-
expect(types.filter((t) => t === "fgcaFactor")).toHaveLength(factors.length);
|
|
54
|
-
expect(types.filter((t) => t === "fgcaGoal")).toHaveLength(goals.length);
|
|
55
|
-
expect(types.filter((t) => t === "fgcaChange")).toHaveLength(changes.length);
|
|
56
|
-
expect(types.filter((t) => t === "fgcaActivity")).toHaveLength(activities.length);
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
it("creates Factor→Goal edges", () => {
|
|
60
|
-
const fgEdges = edges.filter((e) => e.source.startsWith("factor_") && e.target.startsWith("goal_"));
|
|
61
|
-
expect(fgEdges).toHaveLength(2);
|
|
62
|
-
expect(fgEdges.find((e) => e.source === "factor_1" && e.target === "goal_10")).toBeTruthy();
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
it("creates Goal→Change edges", () => {
|
|
66
|
-
const gcEdges = edges.filter((e) => e.source.startsWith("goal_") && e.target.startsWith("change_"));
|
|
67
|
-
expect(gcEdges).toHaveLength(2);
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
it("creates Change→Activity edges", () => {
|
|
71
|
-
const caEdges = edges.filter((e) => e.source.startsWith("change_") && e.target.startsWith("activity_"));
|
|
72
|
-
expect(caEdges).toHaveLength(2);
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
it("does NOT create direct Goal→Activity edges when Change column is visible and all activities are covered", () => {
|
|
76
|
-
const directEdges = edges.filter(
|
|
77
|
-
(e) => e.source.startsWith("goal_") && e.target.startsWith("activity_")
|
|
78
|
-
);
|
|
79
|
-
expect(directEdges).toHaveLength(0);
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
it("uses the activity type name in activity node data", () => {
|
|
83
|
-
const actNode = nodes.find((n) => n.id === "activity_200");
|
|
84
|
-
expect(actNode?.data.activityTypeName).toBe("Initiative");
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
it("encodes F-XXXX label format via id in node data", () => {
|
|
88
|
-
const factorNode = nodes.find((n) => n.id === "factor_1");
|
|
89
|
-
expect(String(factorNode?.data.id).padStart(4, "0")).toBe("0001");
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
it("encodes C-XXX label format via id in node data", () => {
|
|
93
|
-
const changeNode = nodes.find((n) => n.id === "change_100");
|
|
94
|
-
expect(String(changeNode?.data.id).padStart(3, "0")).toBe("100");
|
|
95
|
-
});
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
describe("buildFGCALayout — Factor column hidden", () => {
|
|
99
|
-
const cols = new Set(ALL_FGCA_COLUMNS.filter((c) => c !== "factor"));
|
|
100
|
-
const { nodes, edges } = buildFGCALayout({ factors, goals, changes, activities, visibleColumns: cols });
|
|
101
|
-
|
|
102
|
-
it("produces no factor nodes", () => {
|
|
103
|
-
expect(nodes.filter((n) => n.type === "fgcaFactor")).toHaveLength(0);
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
it("produces no Factor→Goal edges", () => {
|
|
107
|
-
expect(edges.filter((e) => e.source.startsWith("factor_"))).toHaveLength(0);
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
it("still produces Goal and downstream nodes", () => {
|
|
111
|
-
expect(nodes.filter((n) => n.type === "fgcaGoal")).toHaveLength(goals.length);
|
|
112
|
-
});
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
describe("buildFGCALayout — Change column hidden", () => {
|
|
116
|
-
const cols = new Set(ALL_FGCA_COLUMNS.filter((c) => c !== "change"));
|
|
117
|
-
const { nodes, edges } = buildFGCALayout({ factors, goals, changes, activities, visibleColumns: cols });
|
|
118
|
-
|
|
119
|
-
it("produces no change nodes", () => {
|
|
120
|
-
expect(nodes.filter((n) => n.type === "fgcaChange")).toHaveLength(0);
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
it("creates direct Goal→Activity edges when Change column is hidden", () => {
|
|
124
|
-
const directEdges = edges.filter(
|
|
125
|
-
(e) => e.source.startsWith("goal_") && e.target.startsWith("activity_")
|
|
126
|
-
);
|
|
127
|
-
expect(directEdges.length).toBeGreaterThan(0);
|
|
128
|
-
});
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
describe("buildFGCALayout — only one column visible", () => {
|
|
132
|
-
const { nodes, edges } = buildFGCALayout({
|
|
133
|
-
factors,
|
|
134
|
-
goals,
|
|
135
|
-
changes,
|
|
136
|
-
activities,
|
|
137
|
-
visibleColumns: new Set(["goal"] as const),
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
it("produces only goal nodes", () => {
|
|
141
|
-
expect(nodes.filter((n) => n.type !== "fgcaGoal")).toHaveLength(0);
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
it("produces no edges", () => {
|
|
145
|
-
expect(edges).toHaveLength(0);
|
|
146
|
-
});
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
describe("buildFGCALayout — columns are positioned left to right", () => {
|
|
150
|
-
const { nodes } = buildFGCALayout({ factors, goals, changes, activities, visibleColumns: ALL_COLS });
|
|
151
|
-
|
|
152
|
-
it("factor column x < goal column x", () => {
|
|
153
|
-
const f = nodes.find((n) => n.type === "fgcaFactor")!;
|
|
154
|
-
const g = nodes.find((n) => n.type === "fgcaGoal")!;
|
|
155
|
-
expect(f.position.x).toBeLessThan(g.position.x);
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
it("goal column x < change column x", () => {
|
|
159
|
-
const g = nodes.find((n) => n.type === "fgcaGoal")!;
|
|
160
|
-
const c = nodes.find((n) => n.type === "fgcaChange")!;
|
|
161
|
-
expect(g.position.x).toBeLessThan(c.position.x);
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
it("change column x < activity column x", () => {
|
|
165
|
-
const c = nodes.find((n) => n.type === "fgcaChange")!;
|
|
166
|
-
const a = nodes.find((n) => n.type === "fgcaActivity")!;
|
|
167
|
-
expect(c.position.x).toBeLessThan(a.position.x);
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
// Pre-release blocker regression (orchestrator review 2026-05-21).
|
|
171
|
-
it("[blocker] tolerates a change with no activity_ids (optional per validator)", () => {
|
|
172
|
-
const changesNoIds: BdnChangeWithActivities[] = [
|
|
173
|
-
{ id: 100, name: "No activities", goal_id: 10 } as BdnChangeWithActivities,
|
|
174
|
-
];
|
|
175
|
-
expect(() =>
|
|
176
|
-
buildFGCALayout({ factors, goals, changes: changesNoIds, activities, visibleColumns: ALL_COLS }),
|
|
177
|
-
).not.toThrow();
|
|
178
|
-
});
|
|
179
|
-
});
|
|
1
|
+
import { describe, it, expect, vi, beforeAll } from "vitest";
|
|
2
|
+
|
|
3
|
+
// reactflow uses browser APIs — stub the minimum required before importing layout
|
|
4
|
+
beforeAll(() => {
|
|
5
|
+
vi.mock("reactflow", () => ({
|
|
6
|
+
Position: { Left: "left", Right: "right" },
|
|
7
|
+
MarkerType: { ArrowClosed: "arrowclosed" },
|
|
8
|
+
}));
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
import { buildFGCALayout } from "../layout";
|
|
12
|
+
import type { FactorItem, GoalItem, BdnChangeWithActivities, ActivityItem } from "../types";
|
|
13
|
+
import { ALL_FGCA_COLUMNS } from "../types";
|
|
14
|
+
|
|
15
|
+
const ALL_COLS = new Set(ALL_FGCA_COLUMNS);
|
|
16
|
+
|
|
17
|
+
const factors: FactorItem[] = [
|
|
18
|
+
{ id: 1, name: "Digital growth" },
|
|
19
|
+
{ id: 2, name: "Talent shortage" },
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
const goals: GoalItem[] = [
|
|
23
|
+
{ id: 10, name: "Grow revenue", level: 0, factor: [{ id: 1 }] },
|
|
24
|
+
{ id: 11, name: "Build talent pipeline", level: 1, factor: [{ id: 2 }] },
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
const changes: BdnChangeWithActivities[] = [
|
|
28
|
+
{ id: 100, name: "Launch digital channel", goal_id: 10, activity_ids: [200] },
|
|
29
|
+
{ id: 101, name: "Partner with bootcamps", goal_id: 11, activity_ids: [201] },
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
const activities: ActivityItem[] = [
|
|
33
|
+
{ id: 200, name: "Build e-commerce MVP", goal_id: 10, activity_type_id: 1 },
|
|
34
|
+
{ id: 201, name: "Run hiring sprint", goal_id: 11, activity_type_id: 1 },
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
describe("buildFGCALayout — all columns visible", () => {
|
|
38
|
+
const { nodes, edges } = buildFGCALayout({
|
|
39
|
+
factors,
|
|
40
|
+
goals,
|
|
41
|
+
changes,
|
|
42
|
+
activities,
|
|
43
|
+
visibleColumns: ALL_COLS,
|
|
44
|
+
activityTypeList: [{ id: 1, name: "Initiative" }],
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("produces the correct number of nodes", () => {
|
|
48
|
+
expect(nodes).toHaveLength(factors.length + goals.length + changes.length + activities.length);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("assigns correct node types", () => {
|
|
52
|
+
const types = nodes.map((n) => n.type);
|
|
53
|
+
expect(types.filter((t) => t === "fgcaFactor")).toHaveLength(factors.length);
|
|
54
|
+
expect(types.filter((t) => t === "fgcaGoal")).toHaveLength(goals.length);
|
|
55
|
+
expect(types.filter((t) => t === "fgcaChange")).toHaveLength(changes.length);
|
|
56
|
+
expect(types.filter((t) => t === "fgcaActivity")).toHaveLength(activities.length);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("creates Factor→Goal edges", () => {
|
|
60
|
+
const fgEdges = edges.filter((e) => e.source.startsWith("factor_") && e.target.startsWith("goal_"));
|
|
61
|
+
expect(fgEdges).toHaveLength(2);
|
|
62
|
+
expect(fgEdges.find((e) => e.source === "factor_1" && e.target === "goal_10")).toBeTruthy();
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("creates Goal→Change edges", () => {
|
|
66
|
+
const gcEdges = edges.filter((e) => e.source.startsWith("goal_") && e.target.startsWith("change_"));
|
|
67
|
+
expect(gcEdges).toHaveLength(2);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("creates Change→Activity edges", () => {
|
|
71
|
+
const caEdges = edges.filter((e) => e.source.startsWith("change_") && e.target.startsWith("activity_"));
|
|
72
|
+
expect(caEdges).toHaveLength(2);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("does NOT create direct Goal→Activity edges when Change column is visible and all activities are covered", () => {
|
|
76
|
+
const directEdges = edges.filter(
|
|
77
|
+
(e) => e.source.startsWith("goal_") && e.target.startsWith("activity_")
|
|
78
|
+
);
|
|
79
|
+
expect(directEdges).toHaveLength(0);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("uses the activity type name in activity node data", () => {
|
|
83
|
+
const actNode = nodes.find((n) => n.id === "activity_200");
|
|
84
|
+
expect(actNode?.data.activityTypeName).toBe("Initiative");
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it("encodes F-XXXX label format via id in node data", () => {
|
|
88
|
+
const factorNode = nodes.find((n) => n.id === "factor_1");
|
|
89
|
+
expect(String(factorNode?.data.id).padStart(4, "0")).toBe("0001");
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it("encodes C-XXX label format via id in node data", () => {
|
|
93
|
+
const changeNode = nodes.find((n) => n.id === "change_100");
|
|
94
|
+
expect(String(changeNode?.data.id).padStart(3, "0")).toBe("100");
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
describe("buildFGCALayout — Factor column hidden", () => {
|
|
99
|
+
const cols = new Set(ALL_FGCA_COLUMNS.filter((c) => c !== "factor"));
|
|
100
|
+
const { nodes, edges } = buildFGCALayout({ factors, goals, changes, activities, visibleColumns: cols });
|
|
101
|
+
|
|
102
|
+
it("produces no factor nodes", () => {
|
|
103
|
+
expect(nodes.filter((n) => n.type === "fgcaFactor")).toHaveLength(0);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it("produces no Factor→Goal edges", () => {
|
|
107
|
+
expect(edges.filter((e) => e.source.startsWith("factor_"))).toHaveLength(0);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("still produces Goal and downstream nodes", () => {
|
|
111
|
+
expect(nodes.filter((n) => n.type === "fgcaGoal")).toHaveLength(goals.length);
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
describe("buildFGCALayout — Change column hidden", () => {
|
|
116
|
+
const cols = new Set(ALL_FGCA_COLUMNS.filter((c) => c !== "change"));
|
|
117
|
+
const { nodes, edges } = buildFGCALayout({ factors, goals, changes, activities, visibleColumns: cols });
|
|
118
|
+
|
|
119
|
+
it("produces no change nodes", () => {
|
|
120
|
+
expect(nodes.filter((n) => n.type === "fgcaChange")).toHaveLength(0);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it("creates direct Goal→Activity edges when Change column is hidden", () => {
|
|
124
|
+
const directEdges = edges.filter(
|
|
125
|
+
(e) => e.source.startsWith("goal_") && e.target.startsWith("activity_")
|
|
126
|
+
);
|
|
127
|
+
expect(directEdges.length).toBeGreaterThan(0);
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
describe("buildFGCALayout — only one column visible", () => {
|
|
132
|
+
const { nodes, edges } = buildFGCALayout({
|
|
133
|
+
factors,
|
|
134
|
+
goals,
|
|
135
|
+
changes,
|
|
136
|
+
activities,
|
|
137
|
+
visibleColumns: new Set(["goal"] as const),
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it("produces only goal nodes", () => {
|
|
141
|
+
expect(nodes.filter((n) => n.type !== "fgcaGoal")).toHaveLength(0);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it("produces no edges", () => {
|
|
145
|
+
expect(edges).toHaveLength(0);
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
describe("buildFGCALayout — columns are positioned left to right", () => {
|
|
150
|
+
const { nodes } = buildFGCALayout({ factors, goals, changes, activities, visibleColumns: ALL_COLS });
|
|
151
|
+
|
|
152
|
+
it("factor column x < goal column x", () => {
|
|
153
|
+
const f = nodes.find((n) => n.type === "fgcaFactor")!;
|
|
154
|
+
const g = nodes.find((n) => n.type === "fgcaGoal")!;
|
|
155
|
+
expect(f.position.x).toBeLessThan(g.position.x);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it("goal column x < change column x", () => {
|
|
159
|
+
const g = nodes.find((n) => n.type === "fgcaGoal")!;
|
|
160
|
+
const c = nodes.find((n) => n.type === "fgcaChange")!;
|
|
161
|
+
expect(g.position.x).toBeLessThan(c.position.x);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it("change column x < activity column x", () => {
|
|
165
|
+
const c = nodes.find((n) => n.type === "fgcaChange")!;
|
|
166
|
+
const a = nodes.find((n) => n.type === "fgcaActivity")!;
|
|
167
|
+
expect(c.position.x).toBeLessThan(a.position.x);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
// Pre-release blocker regression (orchestrator review 2026-05-21).
|
|
171
|
+
it("[blocker] tolerates a change with no activity_ids (optional per validator)", () => {
|
|
172
|
+
const changesNoIds: BdnChangeWithActivities[] = [
|
|
173
|
+
{ id: 100, name: "No activities", goal_id: 10 } as BdnChangeWithActivities,
|
|
174
|
+
];
|
|
175
|
+
expect(() =>
|
|
176
|
+
buildFGCALayout({ factors, goals, changes: changesNoIds, activities, visibleColumns: ALL_COLS }),
|
|
177
|
+
).not.toThrow();
|
|
178
|
+
});
|
|
179
|
+
});
|