@transitrix/diagrams 1.8.6 → 1.8.8
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/dist/fgca/index.js +10 -10
- package/dist/fgca/layout.js +1 -1
- package/dist/fgca/nodeTypes.js +4 -4
- package/dist/index.js +33 -33
- package/package.json +8 -2
- package/dist/__tests__/edge-path.test.d.ts +0 -1
- package/dist/__tests__/edge-path.test.js +0 -55
- package/dist/__tests__/node-size-presets.test.d.ts +0 -1
- package/dist/__tests__/node-size-presets.test.js +0 -30
- package/dist/__tests__/scope.test.d.ts +0 -1
- package/dist/__tests__/scope.test.js +0 -23
- package/dist/__tests__/yaml-normalize.test.d.ts +0 -1
- package/dist/__tests__/yaml-normalize.test.js +0 -148
- package/dist/activities/__tests__/cpm.test.d.ts +0 -1
- package/dist/activities/__tests__/cpm.test.js +0 -146
- package/dist/activities/__tests__/gantt.test.d.ts +0 -1
- package/dist/activities/__tests__/gantt.test.js +0 -263
- package/dist/activities/__tests__/layout.test.d.ts +0 -1
- package/dist/activities/__tests__/layout.test.js +0 -98
- package/dist/activities/__tests__/validate.test.d.ts +0 -1
- package/dist/activities/__tests__/validate.test.js +0 -397
- package/dist/activity-card/__tests__/example.test.d.ts +0 -1
- package/dist/activity-card/__tests__/example.test.js +0 -73
- package/dist/activity-card/__tests__/layout.test.d.ts +0 -1
- package/dist/activity-card/__tests__/layout.test.js +0 -141
- package/dist/activity-card/__tests__/resolver.test.d.ts +0 -1
- package/dist/activity-card/__tests__/resolver.test.js +0 -325
- package/dist/activity-card/__tests__/validate.test.d.ts +0 -1
- package/dist/activity-card/__tests__/validate.test.js +0 -149
- package/dist/actor/__tests__/validate.test.d.ts +0 -1
- package/dist/actor/__tests__/validate.test.js +0 -95
- package/dist/applications/__tests__/validate.test.d.ts +0 -1
- package/dist/applications/__tests__/validate.test.js +0 -223
- package/dist/assertion/__tests__/example.test.d.ts +0 -1
- package/dist/assertion/__tests__/example.test.js +0 -32
- package/dist/assertion/__tests__/validate.test.d.ts +0 -1
- package/dist/assertion/__tests__/validate.test.js +0 -130
- package/dist/blocks/__tests__/layout.test.d.ts +0 -1
- package/dist/blocks/__tests__/layout.test.js +0 -153
- package/dist/blocks/__tests__/validate.test.d.ts +0 -1
- package/dist/blocks/__tests__/validate.test.js +0 -235
- package/dist/business-service/__tests__/validate.test.d.ts +0 -1
- package/dist/business-service/__tests__/validate.test.js +0 -135
- package/dist/capability-map/__tests__/CapabilityMapView.test.d.ts +0 -1
- package/dist/capability-map/__tests__/CapabilityMapView.test.js +0 -217
- package/dist/capability-map/__tests__/address.test.d.ts +0 -1
- package/dist/capability-map/__tests__/address.test.js +0 -49
- package/dist/capability-map/__tests__/dsm-layout.test.d.ts +0 -1
- package/dist/capability-map/__tests__/dsm-layout.test.js +0 -46
- package/dist/capability-map/__tests__/dsm-mutations.test.d.ts +0 -1
- package/dist/capability-map/__tests__/dsm-mutations.test.js +0 -125
- package/dist/capability-map/__tests__/dsm-validate.test.d.ts +0 -1
- package/dist/capability-map/__tests__/dsm-validate.test.js +0 -56
- package/dist/capability-map/__tests__/validate.test.d.ts +0 -1
- package/dist/capability-map/__tests__/validate.test.js +0 -212
- package/dist/change/__tests__/validate.test.d.ts +0 -1
- package/dist/change/__tests__/validate.test.js +0 -79
- package/dist/codex/__tests__/example.test.d.ts +0 -1
- package/dist/codex/__tests__/example.test.js +0 -33
- package/dist/codex/__tests__/validate.test.d.ts +0 -1
- package/dist/codex/__tests__/validate.test.js +0 -68
- package/dist/compliance/__tests__/canon-catalog.test.d.ts +0 -1
- package/dist/compliance/__tests__/canon-catalog.test.js +0 -60
- package/dist/compliance/__tests__/classify.test.d.ts +0 -1
- package/dist/compliance/__tests__/classify.test.js +0 -58
- package/dist/compliance/__tests__/confidence.test.d.ts +0 -1
- package/dist/compliance/__tests__/confidence.test.js +0 -78
- package/dist/compliance/__tests__/coverage-metric.test.d.ts +0 -1
- package/dist/compliance/__tests__/coverage-metric.test.js +0 -264
- package/dist/compliance/__tests__/demo-scenario.test.d.ts +0 -1
- package/dist/compliance/__tests__/demo-scenario.test.js +0 -226
- package/dist/compliance/__tests__/example.test.d.ts +0 -1
- package/dist/compliance/__tests__/example.test.js +0 -51
- package/dist/compliance/__tests__/gap-report.test.d.ts +0 -1
- package/dist/compliance/__tests__/gap-report.test.js +0 -113
- package/dist/compliance/__tests__/html.test.d.ts +0 -1
- package/dist/compliance/__tests__/html.test.js +0 -122
- package/dist/compliance/__tests__/impact.test.d.ts +0 -1
- package/dist/compliance/__tests__/impact.test.js +0 -913
- package/dist/compliance/__tests__/markdown.test.d.ts +0 -1
- package/dist/compliance/__tests__/markdown.test.js +0 -85
- package/dist/compliance/__tests__/trace.test.d.ts +0 -1
- package/dist/compliance/__tests__/trace.test.js +0 -144
- package/dist/compliance/__tests__/view-resolution.test.d.ts +0 -1
- package/dist/compliance/__tests__/view-resolution.test.js +0 -59
- package/dist/compliance/__tests__/views.test.d.ts +0 -1
- package/dist/compliance/__tests__/views.test.js +0 -59
- package/dist/compliance-matrix/__tests__/build.test.d.ts +0 -1
- package/dist/compliance-matrix/__tests__/build.test.js +0 -183
- package/dist/compliance-matrix/__tests__/example.test.d.ts +0 -1
- package/dist/compliance-matrix/__tests__/example.test.js +0 -49
- package/dist/confidence/__tests__/score.test.d.ts +0 -1
- package/dist/confidence/__tests__/score.test.js +0 -188
- package/dist/constraint/__tests__/example.test.d.ts +0 -1
- package/dist/constraint/__tests__/example.test.js +0 -14
- package/dist/constraint/__tests__/validate.test.d.ts +0 -1
- package/dist/constraint/__tests__/validate.test.js +0 -58
- package/dist/factor/__tests__/validate.test.d.ts +0 -1
- package/dist/factor/__tests__/validate.test.js +0 -112
- package/dist/fgca/__tests__/chain-table.test.d.ts +0 -1
- package/dist/fgca/__tests__/chain-table.test.js +0 -158
- package/dist/fgca/__tests__/layout.test.d.ts +0 -1
- package/dist/fgca/__tests__/layout.test.js +0 -140
- package/dist/fgca/__tests__/parse-canonical.test.d.ts +0 -1
- package/dist/fgca/__tests__/parse-canonical.test.js +0 -268
- package/dist/fgca/__tests__/preview-layout.test.d.ts +0 -1
- package/dist/fgca/__tests__/preview-layout.test.js +0 -106
- package/dist/fgca/__tests__/resolver.test.d.ts +0 -1
- package/dist/fgca/__tests__/resolver.test.js +0 -185
- package/dist/fgca/__tests__/validate.test.d.ts +0 -1
- package/dist/fgca/__tests__/validate.test.js +0 -125
- package/dist/goals/__tests__/GoalTreeView.test.d.ts +0 -1
- package/dist/goals/__tests__/GoalTreeView.test.js +0 -337
- package/dist/goals/__tests__/layout.test.d.ts +0 -1
- package/dist/goals/__tests__/layout.test.js +0 -153
- package/dist/goals/__tests__/mutations.test.d.ts +0 -1
- package/dist/goals/__tests__/mutations.test.js +0 -134
- package/dist/goals/__tests__/parse-canonical.test.d.ts +0 -1
- package/dist/goals/__tests__/parse-canonical.test.js +0 -131
- package/dist/goals/__tests__/validate.test.d.ts +0 -1
- package/dist/goals/__tests__/validate.test.js +0 -183
- package/dist/integration/__tests__/validate.test.d.ts +0 -1
- package/dist/integration/__tests__/validate.test.js +0 -169
- package/dist/location/__tests__/validate.test.d.ts +0 -1
- package/dist/location/__tests__/validate.test.js +0 -107
- package/dist/node/__tests__/validate.test.d.ts +0 -1
- package/dist/node/__tests__/validate.test.js +0 -100
- package/dist/process-blueprint/__tests__/compliance-lane.test.d.ts +0 -1
- package/dist/process-blueprint/__tests__/compliance-lane.test.js +0 -272
- package/dist/process-blueprint/__tests__/layout.test.d.ts +0 -1
- package/dist/process-blueprint/__tests__/layout.test.js +0 -196
- package/dist/process-blueprint/__tests__/validate.test.d.ts +0 -1
- package/dist/process-blueprint/__tests__/validate.test.js +0 -280
- package/dist/process-map/__tests__/validate.test.d.ts +0 -1
- package/dist/process-map/__tests__/validate.test.js +0 -199
- package/dist/products/__tests__/validate.test.d.ts +0 -1
- package/dist/products/__tests__/validate.test.js +0 -210
- package/dist/repo-validate/__tests__/validate-repo.test.d.ts +0 -1
- package/dist/repo-validate/__tests__/validate-repo.test.js +0 -576
- package/dist/requirement/__tests__/example.test.d.ts +0 -1
- package/dist/requirement/__tests__/example.test.js +0 -21
- package/dist/requirement/__tests__/validate.test.d.ts +0 -1
- package/dist/requirement/__tests__/validate.test.js +0 -80
- package/dist/scenarios/__tests__/validate.test.d.ts +0 -1
- package/dist/scenarios/__tests__/validate.test.js +0 -183
- package/dist/stakeholder/__tests__/validate.test.d.ts +0 -1
- package/dist/stakeholder/__tests__/validate.test.js +0 -88
- package/dist/target-state/__tests__/validate.test.d.ts +0 -1
- package/dist/target-state/__tests__/validate.test.js +0 -78
- package/dist/technology-service/__tests__/validate.test.d.ts +0 -1
- package/dist/technology-service/__tests__/validate.test.js +0 -121
- package/dist/theme/__tests__/tokens.test.d.ts +0 -1
- package/dist/theme/__tests__/tokens.test.js +0 -159
- package/dist/webview/__tests__/entity-text-layout.test.d.ts +0 -1
- package/dist/webview/__tests__/entity-text-layout.test.js +0 -155
- package/dist/webview/__tests__/entry-notations.test.d.ts +0 -1
- package/dist/webview/__tests__/entry-notations.test.js +0 -62
- package/dist/webview/__tests__/entry.test.d.ts +0 -1
- package/dist/webview/__tests__/entry.test.js +0 -56
- package/dist/webview/__tests__/render-activities.test.d.ts +0 -1
- package/dist/webview/__tests__/render-activities.test.js +0 -91
- package/dist/webview/__tests__/render-blocks.test.d.ts +0 -1
- package/dist/webview/__tests__/render-blocks.test.js +0 -175
- package/dist/webview/__tests__/render-goals.test.d.ts +0 -1
- package/dist/webview/__tests__/render-goals.test.js +0 -120
- package/dist/webview/__tests__/render-process.test.d.ts +0 -1
- package/dist/webview/__tests__/render-process.test.js +0 -463
- package/dist/webview/__tests__/render-util.test.d.ts +0 -1
- package/dist/webview/__tests__/render-util.test.js +0 -54
- package/src/__tests__/edge-path.test.ts +0 -67
- package/src/__tests__/node-size-presets.test.ts +0 -50
- package/src/__tests__/scope.test.ts +0 -27
- package/src/__tests__/yaml-normalize.test.ts +0 -158
- package/src/activities/__tests__/cpm.test.ts +0 -164
- package/src/activities/__tests__/gantt.test.ts +0 -290
- package/src/activities/__tests__/layout.test.ts +0 -112
- package/src/activities/__tests__/validate.test.ts +0 -446
- package/src/activity-card/__tests__/example.test.ts +0 -83
- package/src/activity-card/__tests__/layout.test.ts +0 -162
- package/src/activity-card/__tests__/resolver.test.ts +0 -357
- package/src/activity-card/__tests__/validate.test.ts +0 -167
- package/src/actor/__tests__/validate.test.ts +0 -114
- package/src/applications/__tests__/validate.test.ts +0 -256
- package/src/assertion/__tests__/example.test.ts +0 -37
- package/src/assertion/__tests__/validate.test.ts +0 -142
- package/src/blocks/__tests__/layout.test.ts +0 -169
- package/src/blocks/__tests__/validate.test.ts +0 -260
- package/src/business-service/__tests__/validate.test.ts +0 -160
- package/src/capability-map/__tests__/CapabilityMapView.test.tsx +0 -232
- package/src/capability-map/__tests__/address.test.ts +0 -59
- package/src/capability-map/__tests__/dsm-layout.test.ts +0 -52
- package/src/capability-map/__tests__/dsm-mutations.test.ts +0 -141
- package/src/capability-map/__tests__/dsm-validate.test.ts +0 -65
- package/src/capability-map/__tests__/validate.test.ts +0 -238
- package/src/change/__tests__/validate.test.ts +0 -97
- package/src/codex/__tests__/example.test.ts +0 -40
- package/src/codex/__tests__/validate.test.ts +0 -84
- package/src/compliance/__tests__/canon-catalog.test.ts +0 -68
- package/src/compliance/__tests__/classify.test.ts +0 -64
- package/src/compliance/__tests__/confidence.test.ts +0 -98
- package/src/compliance/__tests__/coverage-metric.test.ts +0 -300
- package/src/compliance/__tests__/demo-scenario.test.ts +0 -273
- package/src/compliance/__tests__/example.test.ts +0 -57
- package/src/compliance/__tests__/gap-report.test.ts +0 -129
- package/src/compliance/__tests__/html.test.ts +0 -142
- package/src/compliance/__tests__/impact.test.ts +0 -1060
- package/src/compliance/__tests__/markdown.test.ts +0 -95
- package/src/compliance/__tests__/trace.test.ts +0 -157
- package/src/compliance/__tests__/view-resolution.test.ts +0 -80
- package/src/compliance/__tests__/views.test.ts +0 -64
- package/src/compliance-matrix/__tests__/build.test.ts +0 -200
- package/src/compliance-matrix/__tests__/example.test.ts +0 -57
- package/src/confidence/__tests__/score.test.ts +0 -254
- package/src/constraint/__tests__/example.test.ts +0 -19
- package/src/constraint/__tests__/validate.test.ts +0 -68
- package/src/factor/__tests__/validate.test.ts +0 -133
- package/src/fgca/__tests__/chain-table.test.ts +0 -172
- package/src/fgca/__tests__/layout.test.ts +0 -179
- package/src/fgca/__tests__/parse-canonical.test.ts +0 -296
- package/src/fgca/__tests__/preview-layout.test.ts +0 -124
- package/src/fgca/__tests__/resolver.test.ts +0 -209
- package/src/fgca/__tests__/validate.test.ts +0 -142
- package/src/goals/__tests__/GoalTreeView.test.tsx +0 -361
- package/src/goals/__tests__/layout.test.ts +0 -175
- package/src/goals/__tests__/mutations.test.ts +0 -153
- package/src/goals/__tests__/parse-canonical.test.ts +0 -147
- package/src/goals/__tests__/validate.test.ts +0 -202
- package/src/integration/__tests__/validate.test.ts +0 -203
- package/src/location/__tests__/validate.test.ts +0 -127
- package/src/node/__tests__/validate.test.ts +0 -118
- package/src/process-blueprint/__tests__/compliance-lane.test.ts +0 -320
- package/src/process-blueprint/__tests__/layout.test.ts +0 -240
- package/src/process-blueprint/__tests__/validate.test.ts +0 -310
- package/src/process-map/__tests__/validate.test.ts +0 -223
- package/src/products/__tests__/validate.test.ts +0 -239
- package/src/repo-validate/__tests__/validate-repo.test.ts +0 -699
- package/src/requirement/__tests__/example.test.ts +0 -24
- package/src/requirement/__tests__/validate.test.ts +0 -96
- package/src/scenarios/__tests__/validate.test.ts +0 -209
- package/src/stakeholder/__tests__/validate.test.ts +0 -105
- package/src/target-state/__tests__/validate.test.ts +0 -95
- package/src/technology-service/__tests__/validate.test.ts +0 -143
- package/src/theme/__tests__/tokens.test.ts +0 -185
- package/src/webview/__tests__/entity-text-layout.test.ts +0 -182
- package/src/webview/__tests__/entry-notations.test.ts +0 -77
- package/src/webview/__tests__/entry.test.ts +0 -64
- package/src/webview/__tests__/render-activities.test.ts +0 -100
- package/src/webview/__tests__/render-blocks.test.ts +0 -206
- package/src/webview/__tests__/render-goals.test.ts +0 -128
- package/src/webview/__tests__/render-process.test.ts +0 -538
- package/src/webview/__tests__/render-util.test.ts +0 -66
|
@@ -1,179 +0,0 @@
|
|
|
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 { DriverItem, 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: DriverItem[] = [
|
|
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 === "fgcaDriver")).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("driver_") && e.target.startsWith("goal_"));
|
|
61
|
-
expect(fgEdges).toHaveLength(2);
|
|
62
|
-
expect(fgEdges.find((e) => e.source === "driver_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 === "driver_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 — Driver column hidden", () => {
|
|
99
|
-
const cols = new Set(ALL_FGCA_COLUMNS.filter((c) => c !== "driver"));
|
|
100
|
-
const { nodes, edges } = buildFGCALayout({ factors, goals, changes, activities, visibleColumns: cols });
|
|
101
|
-
|
|
102
|
-
it("produces no driver nodes", () => {
|
|
103
|
-
expect(nodes.filter((n) => n.type === "fgcaDriver")).toHaveLength(0);
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
it("produces no Driver→Goal edges", () => {
|
|
107
|
-
expect(edges.filter((e) => e.source.startsWith("driver_"))).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 === "fgcaDriver")!;
|
|
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,296 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import * as fs from 'node:fs';
|
|
3
|
-
import * as path from 'node:path';
|
|
4
|
-
import yaml from 'js-yaml';
|
|
5
|
-
import { parseCanonicalFGCA, parseCanonicalFGA } from '../parse-canonical.js';
|
|
6
|
-
|
|
7
|
-
const VALID = {
|
|
8
|
-
notation: 'dgca',
|
|
9
|
-
spec_version: '0.1',
|
|
10
|
-
id: 'FGCA-SAMPLE-1',
|
|
11
|
-
name: 'Sample FGCA chain',
|
|
12
|
-
factors: [
|
|
13
|
-
{ id: 'FACTOR-1', name: 'Driver one', type: 'external' },
|
|
14
|
-
],
|
|
15
|
-
goals: [
|
|
16
|
-
{ id: 'GOAL-1', name: 'Outcome one', factors: ['FACTOR-1'] },
|
|
17
|
-
],
|
|
18
|
-
changes: [
|
|
19
|
-
{ id: 'CHANGE-1', name: 'Transformation', goals: ['GOAL-1'] },
|
|
20
|
-
],
|
|
21
|
-
actions: [
|
|
22
|
-
{ id: 'ACTIVITY-1', name: 'Workstream', changes: ['CHANGE-1'] },
|
|
23
|
-
],
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
describe('parseCanonicalFGCA', () => {
|
|
27
|
-
it('accepts a valid canonical document', () => {
|
|
28
|
-
const r = parseCanonicalFGCA(VALID);
|
|
29
|
-
expect(r.valid).toBe(true);
|
|
30
|
-
expect(r.errors).toEqual([]);
|
|
31
|
-
expect(r.parsed).toBeDefined();
|
|
32
|
-
expect(r.parsed?.factors).toHaveLength(1);
|
|
33
|
-
expect(r.parsed?.goals).toHaveLength(1);
|
|
34
|
-
expect(r.parsed?.changes).toHaveLength(1);
|
|
35
|
-
expect(r.parsed?.activities).toHaveLength(1);
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
it('accepts ACTION-* ids (methodology 1.0) alongside legacy ACTIVITY-*', () => {
|
|
39
|
-
const r = parseCanonicalFGCA({
|
|
40
|
-
...VALID,
|
|
41
|
-
actions: [
|
|
42
|
-
{ id: 'ACTION-CRM-EU-1', name: 'CRM rollout', changes: ['CHANGE-1'] },
|
|
43
|
-
{ id: 'ACTIVITY-2', name: 'Legacy workstream', changes: ['CHANGE-1'] },
|
|
44
|
-
],
|
|
45
|
-
});
|
|
46
|
-
expect(r.valid, JSON.stringify(r.errors)).toBe(true);
|
|
47
|
-
expect(r.parsed?.activities.map((a) => a.id)).toEqual(['ACTION-CRM-EU-1', 'ACTIVITY-2']);
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
it('defaults changes to [] when view_config.layers.changes is off (DGA-in-dgca)', () => {
|
|
51
|
-
const r = parseCanonicalFGCA({
|
|
52
|
-
...VALID,
|
|
53
|
-
changes: undefined,
|
|
54
|
-
view_config: { layers: { changes: 'off' } },
|
|
55
|
-
actions: [
|
|
56
|
-
{ id: 'ACTION-DISCOVERY-1', name: 'Gap assessment', goals: ['GOAL-1'] },
|
|
57
|
-
],
|
|
58
|
-
});
|
|
59
|
-
expect(r.valid, JSON.stringify(r.errors)).toBe(true);
|
|
60
|
-
expect(r.parsed?.changes).toEqual([]);
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
it('FGCA-001: rejects non-object input', () => {
|
|
64
|
-
expect(parseCanonicalFGCA(null).errors[0].code).toBe('FGCA-001');
|
|
65
|
-
expect(parseCanonicalFGCA('string').errors[0].code).toBe('FGCA-001');
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
it('FGCA-001: rejects wrong notation', () => {
|
|
69
|
-
const r = parseCanonicalFGCA({ ...VALID, notation: 'fga' });
|
|
70
|
-
expect(r.valid).toBe(false);
|
|
71
|
-
expect(r.errors.some((e) => e.code === 'FGCA-001')).toBe(true);
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
it('FGCA-002: rejects malformed doc id', () => {
|
|
75
|
-
const r = parseCanonicalFGCA({ ...VALID, id: 'fgca-lowercase-1' });
|
|
76
|
-
expect(r.valid).toBe(false);
|
|
77
|
-
expect(r.errors.some((e) => e.code === 'FGCA-002')).toBe(true);
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
it('FGCA-003: rejects missing name', () => {
|
|
81
|
-
const { name: _, ...rest } = VALID;
|
|
82
|
-
const r = parseCanonicalFGCA(rest);
|
|
83
|
-
expect(r.valid).toBe(false);
|
|
84
|
-
expect(r.errors.some((e) => e.code === 'FGCA-003')).toBe(true);
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
it('FGCA-004: rejects missing factors array', () => {
|
|
88
|
-
const { factors: _, ...rest } = VALID;
|
|
89
|
-
const r = parseCanonicalFGCA(rest);
|
|
90
|
-
expect(r.valid).toBe(false);
|
|
91
|
-
expect(r.errors.some((e) => e.code === 'FGCA-004')).toBe(true);
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
it('FGCA-006: rejects duplicate IDs within a layer', () => {
|
|
95
|
-
const r = parseCanonicalFGCA({
|
|
96
|
-
...VALID,
|
|
97
|
-
factors: [
|
|
98
|
-
{ id: 'FACTOR-1', name: 'A' },
|
|
99
|
-
{ id: 'FACTOR-1', name: 'B' },
|
|
100
|
-
],
|
|
101
|
-
});
|
|
102
|
-
expect(r.valid).toBe(false);
|
|
103
|
-
expect(r.errors.some((e) => e.code === 'FGCA-006')).toBe(true);
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
it('FGCA-007: rejects malformed factor id', () => {
|
|
107
|
-
const r = parseCanonicalFGCA({
|
|
108
|
-
...VALID,
|
|
109
|
-
factors: [{ id: 'F-1', name: 'A' }],
|
|
110
|
-
});
|
|
111
|
-
expect(r.valid).toBe(false);
|
|
112
|
-
expect(r.errors.some((e) => e.code === 'FGCA-007')).toBe(true);
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
it('FGCA-008: rejects goal.factors[] referencing undefined factor', () => {
|
|
116
|
-
const r = parseCanonicalFGCA({
|
|
117
|
-
...VALID,
|
|
118
|
-
goals: [{ id: 'GOAL-1', name: 'A', factors: ['FACTOR-99'] }],
|
|
119
|
-
});
|
|
120
|
-
expect(r.valid).toBe(false);
|
|
121
|
-
expect(r.errors.some((e) => e.code === 'FGCA-008')).toBe(true);
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
it('FGCA-009: rejects change.goals[] referencing undefined goal', () => {
|
|
125
|
-
const r = parseCanonicalFGCA({
|
|
126
|
-
...VALID,
|
|
127
|
-
changes: [{ id: 'CHANGE-1', name: 'A', goals: ['GOAL-99'] }],
|
|
128
|
-
});
|
|
129
|
-
expect(r.valid).toBe(false);
|
|
130
|
-
expect(r.errors.some((e) => e.code === 'FGCA-009')).toBe(true);
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
it('FGCA-010: rejects activity.changes[] referencing undefined change', () => {
|
|
134
|
-
const r = parseCanonicalFGCA({
|
|
135
|
-
...VALID,
|
|
136
|
-
actions: [{ id: 'ACTIVITY-1', name: 'A', changes: ['CHANGE-99'] }],
|
|
137
|
-
});
|
|
138
|
-
expect(r.valid).toBe(false);
|
|
139
|
-
expect(r.errors.some((e) => e.code === 'FGCA-010')).toBe(true);
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
it('FGCA-015: rejects factor.references_constraint with malformed ID', () => {
|
|
143
|
-
const r = parseCanonicalFGCA({
|
|
144
|
-
...VALID,
|
|
145
|
-
factors: [{ id: 'FACTOR-1', name: 'A', references_constraint: ['BAD-1'] }],
|
|
146
|
-
});
|
|
147
|
-
expect(r.valid).toBe(false);
|
|
148
|
-
expect(r.errors.some((e) => e.code === 'FGCA-015')).toBe(true);
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
it('accepts factor.references_constraint with valid CONSTRAINT ID', () => {
|
|
152
|
-
const r = parseCanonicalFGCA({
|
|
153
|
-
...VALID,
|
|
154
|
-
factors: [{ id: 'FACTOR-1', name: 'A', references_constraint: ['CONSTRAINT-GDPR-1'] }],
|
|
155
|
-
});
|
|
156
|
-
expect(r.valid).toBe(true);
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
it('populates internal change.activity_ids from canonical activity.changes (reversed direction)', () => {
|
|
160
|
-
const r = parseCanonicalFGCA({
|
|
161
|
-
...VALID,
|
|
162
|
-
changes: [{ id: 'CHANGE-1', name: 'C', goals: ['GOAL-1'] }],
|
|
163
|
-
actions: [
|
|
164
|
-
{ id: 'ACTIVITY-1', name: 'A1', changes: ['CHANGE-1'] },
|
|
165
|
-
{ id: 'ACTIVITY-2', name: 'A2', changes: ['CHANGE-1'] },
|
|
166
|
-
],
|
|
167
|
-
});
|
|
168
|
-
expect(r.valid).toBe(true);
|
|
169
|
-
expect(r.parsed?.changes[0].activity_ids).toHaveLength(2);
|
|
170
|
-
});
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
// The "FGCA preview blank / FGA no edges" regression (vkgeorgia/strategy#65,
|
|
174
|
-
// transitrix/methodology#65) was a data defect: the canonical flat shape was
|
|
175
|
-
// not being mapped to the internal cross-ref fields the renderer turns into
|
|
176
|
-
// edges. These assert the edge-driving fields are populated, so the renderer
|
|
177
|
-
// has something to draw — locking the fix.
|
|
178
|
-
describe('parseCanonicalFGCA — edge-driving fields populated', () => {
|
|
179
|
-
it('maps goal.factors → goal.factor[], change.goals → change.goal_id, activity.changes → change.activity_ids', () => {
|
|
180
|
-
const r = parseCanonicalFGCA(VALID);
|
|
181
|
-
expect(r.valid).toBe(true);
|
|
182
|
-
const parsed = r.parsed!;
|
|
183
|
-
// factor → goal edge source
|
|
184
|
-
expect(parsed.goals[0].factor).toHaveLength(1);
|
|
185
|
-
// goal → change edge
|
|
186
|
-
expect(parsed.changes[0].goal_id).not.toBe(0);
|
|
187
|
-
expect(parsed.changes[0].goal_id).not.toBe('');
|
|
188
|
-
// change → activity edge
|
|
189
|
-
expect(parsed.changes[0].activity_ids).toHaveLength(1);
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
it('preserves canonical string IDs — not converted to sequential numbers', () => {
|
|
193
|
-
const r = parseCanonicalFGCA(VALID);
|
|
194
|
-
expect(r.valid).toBe(true);
|
|
195
|
-
const parsed = r.parsed!;
|
|
196
|
-
expect(parsed.factors[0].id).toBe('FACTOR-1');
|
|
197
|
-
expect(parsed.goals[0].id).toBe('GOAL-1');
|
|
198
|
-
expect(parsed.goals[0].factor![0].id).toBe('FACTOR-1');
|
|
199
|
-
expect(parsed.changes[0].id).toBe('CHANGE-1');
|
|
200
|
-
expect(parsed.changes[0].goal_id).toBe('GOAL-1');
|
|
201
|
-
expect(parsed.changes[0].activity_ids[0]).toBe('ACTIVITY-1');
|
|
202
|
-
expect(parsed.activities[0].id).toBe('ACTIVITY-1');
|
|
203
|
-
});
|
|
204
|
-
});
|
|
205
|
-
|
|
206
|
-
describe('parseCanonicalFGA', () => {
|
|
207
|
-
const VALID_FGA = {
|
|
208
|
-
notation: 'dga',
|
|
209
|
-
spec_version: '0.1',
|
|
210
|
-
id: 'FGA-SAMPLE-1',
|
|
211
|
-
name: 'Sample FGA chain',
|
|
212
|
-
factors: [{ id: 'FACTOR-1', name: 'Driver' }],
|
|
213
|
-
goals: [{ id: 'GOAL-1', name: 'Outcome', factors: ['FACTOR-1'] }],
|
|
214
|
-
actions: [{ id: 'ACTIVITY-1', name: 'Workstream', goals: ['GOAL-1'] }],
|
|
215
|
-
};
|
|
216
|
-
|
|
217
|
-
it('accepts a valid canonical FGA document (no changes layer)', () => {
|
|
218
|
-
const r = parseCanonicalFGA(VALID_FGA);
|
|
219
|
-
expect(r.valid).toBe(true);
|
|
220
|
-
expect(r.errors).toEqual([]);
|
|
221
|
-
expect(r.parsed?.factors).toHaveLength(1);
|
|
222
|
-
expect(r.parsed?.goals).toHaveLength(1);
|
|
223
|
-
expect(r.parsed?.activities).toHaveLength(1);
|
|
224
|
-
});
|
|
225
|
-
|
|
226
|
-
it('accepts ACTION-* ids in FGA mode', () => {
|
|
227
|
-
const r = parseCanonicalFGA({
|
|
228
|
-
...VALID_FGA,
|
|
229
|
-
actions: [{ id: 'ACTION-GDPR-DSR-WORKFLOW-1', name: 'DSR workflow', goals: ['GOAL-1'] }],
|
|
230
|
-
});
|
|
231
|
-
expect(r.valid, JSON.stringify(r.errors)).toBe(true);
|
|
232
|
-
expect(r.parsed?.activities[0].id).toBe('ACTION-GDPR-DSR-WORKFLOW-1');
|
|
233
|
-
});
|
|
234
|
-
|
|
235
|
-
it('populates activity.goal_id from activity.goals[] (the FGA edge-driving field)', () => {
|
|
236
|
-
// The "FGA nodes render, no edges" bug: activities had no resolvable
|
|
237
|
-
// goal_id, so the renderer drew no goal → activity edges.
|
|
238
|
-
const r = parseCanonicalFGA(VALID_FGA);
|
|
239
|
-
expect(r.valid).toBe(true);
|
|
240
|
-
expect(r.parsed?.activities[0].goal_id).not.toBeNull();
|
|
241
|
-
expect(r.parsed?.activities[0].goal_id).not.toBe(0);
|
|
242
|
-
});
|
|
243
|
-
|
|
244
|
-
it('FGA-001: rejects wrong notation', () => {
|
|
245
|
-
const r = parseCanonicalFGA({ ...VALID_FGA, notation: 'fgca' });
|
|
246
|
-
expect(r.valid).toBe(false);
|
|
247
|
-
expect(r.errors.some((e) => e.code === 'FGA-001')).toBe(true);
|
|
248
|
-
});
|
|
249
|
-
|
|
250
|
-
it('FGA-002: rejects malformed doc id', () => {
|
|
251
|
-
const r = parseCanonicalFGA({ ...VALID_FGA, id: 'fga-lower-1' });
|
|
252
|
-
expect(r.valid).toBe(false);
|
|
253
|
-
expect(r.errors.some((e) => e.code === 'FGA-002')).toBe(true);
|
|
254
|
-
});
|
|
255
|
-
|
|
256
|
-
it('remaps FGCA layer codes into the FGA registry (e.g. FGA-007 for a bad id)', () => {
|
|
257
|
-
const r = parseCanonicalFGA({ ...VALID_FGA, factors: [{ id: 'F-1', name: 'bad' }] });
|
|
258
|
-
expect(r.valid).toBe(false);
|
|
259
|
-
expect(r.errors.some((e) => e.code === 'FGA-007')).toBe(true);
|
|
260
|
-
// No raw FGCA-prefixed code should leak through the remap.
|
|
261
|
-
expect(r.errors.every((e) => !e.code.startsWith('FGCA-'))).toBe(true);
|
|
262
|
-
});
|
|
263
|
-
});
|
|
264
|
-
|
|
265
|
-
describe('parseCanonicalFGCA — example file regression', () => {
|
|
266
|
-
const EXAMPLES_DIR = path.resolve(process.cwd(), '..', '..', 'tests', 'fixtures', 'notation-corpus', 'dgca');
|
|
267
|
-
const files = fs.existsSync(EXAMPLES_DIR)
|
|
268
|
-
? fs.readdirSync(EXAMPLES_DIR).filter((f) => f.endsWith('.transitrix.yaml'))
|
|
269
|
-
: [];
|
|
270
|
-
for (const file of files) {
|
|
271
|
-
it(`accepts tests/fixtures/notation-corpus/dgca/${file}`, () => {
|
|
272
|
-
const text = fs.readFileSync(path.join(EXAMPLES_DIR, file), 'utf8');
|
|
273
|
-
const parsedYaml = yaml.load(text);
|
|
274
|
-
const r = parseCanonicalFGCA(parsedYaml);
|
|
275
|
-
expect(r.errors).toEqual([]);
|
|
276
|
-
expect(r.valid).toBe(true);
|
|
277
|
-
});
|
|
278
|
-
}
|
|
279
|
-
});
|
|
280
|
-
|
|
281
|
-
describe('parseCanonicalFGA — example file regression', () => {
|
|
282
|
-
const EXAMPLES_DIR = path.resolve(process.cwd(), '..', '..', 'tests', 'fixtures', 'notation-corpus', 'dga');
|
|
283
|
-
const files = fs.existsSync(EXAMPLES_DIR)
|
|
284
|
-
? fs.readdirSync(EXAMPLES_DIR).filter((f) => f.endsWith('.transitrix.yaml'))
|
|
285
|
-
: [];
|
|
286
|
-
for (const file of files) {
|
|
287
|
-
it(`accepts tests/fixtures/notation-corpus/dga/${file} and resolves every activity to a goal`, () => {
|
|
288
|
-
const text = fs.readFileSync(path.join(EXAMPLES_DIR, file), 'utf8');
|
|
289
|
-
const r = parseCanonicalFGA(yaml.load(text));
|
|
290
|
-
expect(r.errors).toEqual([]);
|
|
291
|
-
expect(r.valid).toBe(true);
|
|
292
|
-
// Every activity must carry a goal_id, or the FGA preview draws no edges.
|
|
293
|
-
expect(r.parsed!.activities.every((a) => a.goal_id != null && a.goal_id !== 0 && a.goal_id !== '')).toBe(true);
|
|
294
|
-
});
|
|
295
|
-
}
|
|
296
|
-
});
|
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import {
|
|
3
|
-
layoutFGCAPreview,
|
|
4
|
-
FGCA_PAD,
|
|
5
|
-
FGCA_NODE_W,
|
|
6
|
-
type FGCAPreviewDoc,
|
|
7
|
-
} from '../preview-layout.js';
|
|
8
|
-
|
|
9
|
-
const doc: FGCAPreviewDoc = {
|
|
10
|
-
factors: [
|
|
11
|
-
{ id: 1, name: 'F1' },
|
|
12
|
-
{ id: 2, name: 'F2' },
|
|
13
|
-
],
|
|
14
|
-
goals: [
|
|
15
|
-
{ id: 10, name: 'G1', factor: [{ id: 1 }] },
|
|
16
|
-
{ id: 11, name: 'G2', factor: [{ id: 2 }] },
|
|
17
|
-
],
|
|
18
|
-
changes: [{ id: 20, name: 'C1', goal_id: 10, activity_ids: [30] }],
|
|
19
|
-
activities: [
|
|
20
|
-
{ id: 30, name: 'A1', goal_id: 10 },
|
|
21
|
-
{ id: 31, name: 'A2', goal_id: 11 },
|
|
22
|
-
],
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
describe('layoutFGCAPreview', () => {
|
|
26
|
-
it('lays out all four columns in order (FGCA)', () => {
|
|
27
|
-
const layout = layoutFGCAPreview(doc);
|
|
28
|
-
expect(layout.columns.map(c => c.col)).toEqual(['driver', 'goal', 'change', 'activity']);
|
|
29
|
-
// 2 factors + 2 goals + 1 change + 2 activities
|
|
30
|
-
expect(layout.nodes).toHaveLength(7);
|
|
31
|
-
expect(layout.width).toBeGreaterThan(0);
|
|
32
|
-
expect(layout.height).toBeGreaterThan(0);
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
it('produces the expected FGCA edge set', () => {
|
|
36
|
-
// F1→G1, F2→G2 (2), G1→C1 (1), C1→A1 (1), G2→A2 direct (1) = 5
|
|
37
|
-
const layout = layoutFGCAPreview(doc);
|
|
38
|
-
expect(layout.edges).toHaveLength(5);
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
it('hideChanges (FGA) drops the Changes column and links goals to activities', () => {
|
|
42
|
-
const layout = layoutFGCAPreview(doc, { hideChanges: true });
|
|
43
|
-
expect(layout.columns.map(c => c.col)).toEqual(['driver', 'goal', 'activity']);
|
|
44
|
-
// 2 factors + 2 goals + 2 activities
|
|
45
|
-
expect(layout.nodes).toHaveLength(6);
|
|
46
|
-
// F1→G1, F2→G2 (2), G1→A1, G2→A2 (2) = 4
|
|
47
|
-
expect(layout.edges).toHaveLength(4);
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
it('first column sits at the pad origin', () => {
|
|
51
|
-
const layout = layoutFGCAPreview(doc);
|
|
52
|
-
expect(layout.columns[0].x).toBe(FGCA_PAD);
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
it('empty options reproduce the default layout', () => {
|
|
56
|
-
const a = layoutFGCAPreview(doc);
|
|
57
|
-
const b = layoutFGCAPreview(doc, {});
|
|
58
|
-
expect(b.nodes.map(n => `${n.id}:${n.x},${n.y}`)).toEqual(a.nodes.map(n => `${n.id}:${n.x},${n.y}`));
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
it('larger colGap widens the column step', () => {
|
|
62
|
-
const stepOf = (colGap: number) => {
|
|
63
|
-
const cols = layoutFGCAPreview(doc, { colGap }).columns;
|
|
64
|
-
return cols[1].x - cols[0].x;
|
|
65
|
-
};
|
|
66
|
-
expect(stepOf(80)).toBe(FGCA_NODE_W + 80);
|
|
67
|
-
expect(stepOf(240)).toBe(FGCA_NODE_W + 240);
|
|
68
|
-
expect(stepOf(240)).toBeGreaterThan(stepOf(80));
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
it('larger rowGap increases the gap between stacked nodes', () => {
|
|
72
|
-
const gapOf = (rowGap: number) => {
|
|
73
|
-
const factors = layoutFGCAPreview(doc, { rowGap }).nodes.filter(n => n.col === 'driver');
|
|
74
|
-
return factors[1].y - factors[0].y;
|
|
75
|
-
};
|
|
76
|
-
expect(gapOf(120)).toBeGreaterThan(gapOf(20));
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
// vkgeorgia/strategy#77 — scope filtering. FGCA goals are flat, so 'root'
|
|
80
|
-
// selects the single matching goal plus the factors/changes/activities that
|
|
81
|
-
// touch it.
|
|
82
|
-
describe('scope', () => {
|
|
83
|
-
const idsOf = (layout: ReturnType<typeof layoutFGCAPreview>) => new Set(layout.nodes.map(n => n.id));
|
|
84
|
-
|
|
85
|
-
it("mode 'root' keeps only the root goal and what connects to it", () => {
|
|
86
|
-
// root 10 → factor 1 (referenced by G10), change 20 (goal_id 10),
|
|
87
|
-
// activity 30 (via change 20). G11/F2/A31 are dropped.
|
|
88
|
-
const layout = layoutFGCAPreview(doc, { scope: { mode: 'root', rootGoalId: '10' } });
|
|
89
|
-
expect(idsOf(layout)).toEqual(new Set(['driver_1', 'goal_10', 'change_20', 'activity_30']));
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
it("mode 'root' filters factors and activities to those touching the visible goal", () => {
|
|
93
|
-
// root 11 → factor 2, activity 31 (direct goal link); no change touches G11.
|
|
94
|
-
const layout = layoutFGCAPreview(doc, { scope: { mode: 'root', rootGoalId: '11' } });
|
|
95
|
-
expect(idsOf(layout)).toEqual(new Set(['driver_2', 'goal_11', 'activity_31']));
|
|
96
|
-
// F1, A30, C20 (which only touch the hidden G10) are excluded.
|
|
97
|
-
expect(layout.nodes.some(n => n.id === 'driver_1')).toBe(false);
|
|
98
|
-
expect(layout.nodes.some(n => n.id === 'activity_30')).toBe(false);
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
it("mode 'level' trims goals above the cap and their connections", () => {
|
|
102
|
-
const leveled: FGCAPreviewDoc = {
|
|
103
|
-
factors: [{ id: 1, name: 'F1' }, { id: 2, name: 'F2' }],
|
|
104
|
-
goals: [
|
|
105
|
-
{ id: 10, name: 'G1', level: 0, factor: [{ id: 1 }] },
|
|
106
|
-
{ id: 11, name: 'G2', level: 1, factor: [{ id: 2 }] },
|
|
107
|
-
],
|
|
108
|
-
changes: [],
|
|
109
|
-
activities: [
|
|
110
|
-
{ id: 30, name: 'A1', goal_id: 10 },
|
|
111
|
-
{ id: 31, name: 'A2', goal_id: 11 },
|
|
112
|
-
],
|
|
113
|
-
};
|
|
114
|
-
const layout = layoutFGCAPreview(leveled, { scope: { mode: 'level', maxLevel: 0 } });
|
|
115
|
-
expect(idsOf(layout)).toEqual(new Set(['driver_1', 'goal_10', 'activity_30']));
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
it("mode 'root' returns an empty layout when the root is absent", () => {
|
|
119
|
-
const layout = layoutFGCAPreview(doc, { scope: { mode: 'root', rootGoalId: '999' } });
|
|
120
|
-
expect(layout.nodes).toHaveLength(0);
|
|
121
|
-
expect(layout.edges).toHaveLength(0);
|
|
122
|
-
});
|
|
123
|
-
});
|
|
124
|
-
});
|