@transitrix/diagrams 1.0.0 → 1.1.0
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/activities/cpm.js +11 -2
- package/dist/activities/gantt.js +6 -3
- package/dist/activities/types.d.ts +1 -0
- package/dist/activities/validate.js +3 -3
- 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 +161 -7
- 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 +115 -28
- 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 +509 -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 +78 -81
- package/dist/compliance/impact.d.ts +100 -16
- package/dist/compliance/impact.js +272 -28
- 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 +75 -46
- package/dist/factor/index.d.ts +3 -2
- package/dist/factor/index.js +1 -1
- package/dist/factor/types.d.ts +5 -8
- 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__/chain-table.test.js +2 -2
- package/dist/fgca/__tests__/layout.test.js +11 -11
- package/dist/fgca/__tests__/preview-layout.test.js +7 -7
- package/dist/fgca/__tests__/resolver.test.js +185 -0
- package/dist/fgca/chain-table.d.ts +1 -1
- package/dist/fgca/chain-table.js +4 -4
- package/dist/fgca/index.d.ts +4 -2
- package/dist/fgca/index.js +2 -1
- package/dist/fgca/layout.d.ts +2 -2
- package/dist/fgca/layout.js +9 -9
- package/dist/fgca/nodeTypes.d.ts +1 -1
- package/dist/fgca/nodeTypes.js +2 -2
- package/dist/fgca/nodes/FGCAFactorNode.d.ts +3 -3
- package/dist/fgca/nodes/FGCAFactorNode.js +5 -5
- package/dist/fgca/parse-canonical.js +15 -10
- package/dist/fgca/preview-layout.d.ts +1 -1
- package/dist/fgca/preview-layout.js +4 -4
- package/dist/fgca/resolver.d.ts +49 -0
- package/dist/fgca/resolver.js +126 -0
- package/dist/fgca/types.d.ts +11 -11
- package/dist/fgca/types.js +2 -2
- package/dist/fgca/validate.d.ts +2 -2
- package/dist/fgca/validate.js +17 -14
- 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/index.d.ts +1 -1
- package/dist/process-blueprint/layout.js +66 -35
- package/dist/process-blueprint/types.d.ts +36 -1
- package/dist/scenarios/index.d.ts +1 -1
- package/dist/scenarios/types.d.ts +4 -4
- 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 +71 -69
- package/dist/theme/tokens.d.ts +7 -1
- package/dist/theme/tokens.js +3 -1
- package/dist/webview/__tests__/entry-notations.test.js +2 -0
- package/dist/webview/__tests__/entry.test.js +11 -11
- package/dist/webview/__tests__/render-process.test.js +463 -0
- package/dist/webview/__tests__/render-util.test.d.ts +1 -0
- package/dist/webview/__tests__/render-util.test.js +54 -0
- package/dist/webview/entry.d.ts +1 -1
- package/dist/webview/entry.js +9 -5
- package/dist/webview/render-activities.d.ts +24 -2
- package/dist/webview/render-activities.js +92 -57
- package/dist/webview/render-activity-card.d.ts +11 -1
- package/dist/webview/render-activity-card.js +115 -79
- package/dist/webview/render-blocks.d.ts +24 -1
- package/dist/webview/render-blocks.js +58 -24
- package/dist/webview/render-fgca.d.ts +31 -0
- package/dist/webview/render-fgca.js +30 -19
- package/dist/webview/render-goals.d.ts +27 -1
- package/dist/webview/render-goals.js +42 -32
- package/dist/webview/render-process-blueprint.d.ts +11 -1
- package/dist/webview/render-process-blueprint.js +118 -53
- package/dist/webview/render-process.d.ts +95 -0
- package/dist/webview/render-process.js +392 -0
- package/dist/webview/render-scenarios.js +2 -2
- package/dist/webview/render-util.d.ts +19 -6
- package/dist/webview/render-util.js +23 -15
- package/package.json +54 -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 +107 -95
- package/src/activities/gantt.ts +277 -276
- package/src/activities/index.ts +28 -28
- package/src/activities/layout.ts +112 -112
- package/src/activities/types.ts +149 -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 +330 -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 +422 -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 +1059 -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 +291 -295
- package/src/compliance/impact.ts +979 -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 +81 -46
- package/src/factor/index.ts +3 -7
- package/src/factor/types.ts +15 -19
- 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 +405 -400
- package/src/fgca/preview-layout.ts +217 -217
- package/src/fgca/resolver.ts +159 -0
- package/src/fgca/types.ts +50 -50
- package/src/fgca/validate.ts +129 -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 +32 -31
- package/src/process-blueprint/layout.ts +564 -531
- package/src/process-blueprint/types.ts +278 -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 +239 -237
- package/src/theme/tokens.ts +135 -133
- package/src/typed-id.ts +56 -56
- package/src/validation-types.ts +36 -36
- package/src/webview/__tests__/entry-notations.test.ts +79 -77
- package/src/webview/__tests__/entry.test.ts +64 -64
- package/src/webview/__tests__/render-goals.test.ts +72 -72
- package/src/webview/__tests__/render-process.test.ts +538 -0
- package/src/webview/__tests__/render-util.test.ts +66 -0
- package/src/webview/entry.ts +298 -292
- package/src/webview/render-activities.ts +111 -69
- package/src/webview/render-activity-card.ts +275 -234
- package/src/webview/render-blocks.ts +78 -28
- package/src/webview/render-fgca.ts +53 -28
- package/src/webview/render-goals.ts +133 -110
- package/src/webview/render-process-blueprint.ts +281 -207
- package/src/webview/render-process.ts +507 -0
- package/src/webview/render-scenarios.ts +3 -3
- package/src/webview/render-util.ts +27 -14
- 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.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
- /package/dist/{issues/__tests__/validate.test.d.ts → webview/__tests__/render-process.test.d.ts} +0 -0
|
@@ -44,18 +44,37 @@ export interface IndexAssertion {
|
|
|
44
44
|
* stage-grouped matrix cells.
|
|
45
45
|
*/
|
|
46
46
|
realised_via?: string[];
|
|
47
|
+
/** ID or name of the party who must confirm this assertion (pending-owner state). */
|
|
48
|
+
owner_to_confirm?: string;
|
|
47
49
|
}
|
|
48
|
-
/** One
|
|
50
|
+
/** One stage (or task) element of a business object, used as a matrix sub-column. */
|
|
49
51
|
export interface ObjectDetailDef {
|
|
50
52
|
id: string;
|
|
51
53
|
name: string;
|
|
54
|
+
/**
|
|
55
|
+
* Ordered task-type flow steps within this stage.
|
|
56
|
+
* Present only when `grouping.columns: product-stage-task` grain is active.
|
|
57
|
+
* Each task produces a separate matrix column: (subject, stageId, taskId).
|
|
58
|
+
* When absent or empty, the stage itself is the column grain.
|
|
59
|
+
*/
|
|
60
|
+
tasks?: Array<{
|
|
61
|
+
id: string;
|
|
62
|
+
name: string;
|
|
63
|
+
}>;
|
|
52
64
|
}
|
|
53
65
|
/**
|
|
54
|
-
* Maps a business object to its ordered
|
|
55
|
-
* Passed into `buildImpactMatrix`
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
66
|
+
* Maps a business object to its ordered stages (and optionally their tasks).
|
|
67
|
+
* Passed into `buildImpactMatrix` to enable stage/task column grouping.
|
|
68
|
+
*
|
|
69
|
+
* - For `grouping.columns: product-stage`: `details` contains stages; each
|
|
70
|
+
* stage becomes one matrix column.
|
|
71
|
+
* - For `grouping.columns: product-stage-task`: each stage in `details` carries
|
|
72
|
+
* a `tasks` array; each task produces a `(subject, stage, task)` column.
|
|
73
|
+
* Stages with no tasks fall back to a stage-grain column.
|
|
74
|
+
*
|
|
75
|
+
* Typically built by combining `extractObjectDetails` (blueprint stages) and
|
|
76
|
+
* `extractProcessFlowTasks` (process flow tasks), then merging via
|
|
77
|
+
* `mergeStageTaskDetails`.
|
|
59
78
|
*/
|
|
60
79
|
export interface ObjectDetailInput {
|
|
61
80
|
objectId: string;
|
package/dist/edge-path.d.ts
CHANGED
|
@@ -10,9 +10,13 @@ export declare const DEFAULT_EDGE_CURVATURE = 1;
|
|
|
10
10
|
* Builds the SVG `d` for a horizontal-tangent cubic Bézier from (sx,sy) to
|
|
11
11
|
* (tx,ty).
|
|
12
12
|
*
|
|
13
|
-
* `curvature` scales the control-handle
|
|
14
|
-
*
|
|
13
|
+
* `curvature` scales the exit control-handle (departure from source).
|
|
14
|
+
* `entryCurvature` scales the entry control-handle (arrival at target);
|
|
15
|
+
* defaults to `curvature` when omitted so callers that pass one value get
|
|
16
|
+
* symmetric handles (the historical behaviour).
|
|
17
|
+
*
|
|
18
|
+
* - 0 → handle collapses onto its endpoint ⇒ straight at that end.
|
|
15
19
|
* - 1 → the historical curve (no visual change from before #76).
|
|
16
|
-
* - >1 → progressively stronger arc.
|
|
20
|
+
* - >1 → progressively stronger arc at that end.
|
|
17
21
|
*/
|
|
18
|
-
export declare function horizontalCubicEdgePath(sx: number, sy: number, tx: number, ty: number, curvature?: number): string;
|
|
22
|
+
export declare function horizontalCubicEdgePath(sx: number, sy: number, tx: number, ty: number, curvature?: number, entryCurvature?: number): string;
|
package/dist/edge-path.js
CHANGED
|
@@ -21,15 +21,20 @@ export const DEFAULT_EDGE_CURVATURE = 1;
|
|
|
21
21
|
* Builds the SVG `d` for a horizontal-tangent cubic Bézier from (sx,sy) to
|
|
22
22
|
* (tx,ty).
|
|
23
23
|
*
|
|
24
|
-
* `curvature` scales the control-handle
|
|
25
|
-
*
|
|
24
|
+
* `curvature` scales the exit control-handle (departure from source).
|
|
25
|
+
* `entryCurvature` scales the entry control-handle (arrival at target);
|
|
26
|
+
* defaults to `curvature` when omitted so callers that pass one value get
|
|
27
|
+
* symmetric handles (the historical behaviour).
|
|
28
|
+
*
|
|
29
|
+
* - 0 → handle collapses onto its endpoint ⇒ straight at that end.
|
|
26
30
|
* - 1 → the historical curve (no visual change from before #76).
|
|
27
|
-
* - >1 → progressively stronger arc.
|
|
31
|
+
* - >1 → progressively stronger arc at that end.
|
|
28
32
|
*/
|
|
29
|
-
export function horizontalCubicEdgePath(sx, sy, tx, ty, curvature = DEFAULT_EDGE_CURVATURE) {
|
|
33
|
+
export function horizontalCubicEdgePath(sx, sy, tx, ty, curvature = DEFAULT_EDGE_CURVATURE, entryCurvature) {
|
|
30
34
|
const dx = tx - sx;
|
|
31
35
|
const dy = ty - sy;
|
|
32
36
|
const baseHandle = Math.max(EDGE_MIN_HANDLE, Math.abs(dx) * DX_FACTOR, Math.abs(dy) * DY_FACTOR);
|
|
33
|
-
const
|
|
34
|
-
|
|
37
|
+
const exitHandle = baseHandle * curvature;
|
|
38
|
+
const entryHandle = baseHandle * (entryCurvature ?? curvature);
|
|
39
|
+
return `M${sx},${sy} C${sx + exitHandle},${sy} ${tx - entryHandle},${ty} ${tx},${ty}`;
|
|
35
40
|
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { describe, it, expect } from 'vitest';
|
|
2
2
|
import { validateFactor } from '../validate.js';
|
|
3
|
-
function
|
|
3
|
+
function validExternal() {
|
|
4
4
|
return {
|
|
5
|
-
notation: '
|
|
5
|
+
notation: 'driver',
|
|
6
6
|
id: 'FACTOR-EU-REG-1',
|
|
7
|
-
name: 'EU regulatory window
|
|
7
|
+
name: 'EU regulatory window',
|
|
8
8
|
type: 'external',
|
|
9
9
|
category: 'legal',
|
|
10
10
|
description: 'Standing external driver — the EU regulatory regime.',
|
|
@@ -17,67 +17,96 @@ function valid() {
|
|
|
17
17
|
valid_to: null,
|
|
18
18
|
};
|
|
19
19
|
}
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
function validInternal() {
|
|
21
|
+
return {
|
|
22
|
+
notation: 'driver',
|
|
23
|
+
id: 'FACTOR-COMP-1',
|
|
24
|
+
name: 'Support response time',
|
|
25
|
+
type: 'internal',
|
|
26
|
+
description: 'Standing internal driver.',
|
|
27
|
+
zone: 'canon',
|
|
28
|
+
admitted_at: '2026-05-29',
|
|
29
|
+
admitted_by: 'v.korobeinikov',
|
|
30
|
+
gate_checks: { uniqueness: 'pass', consistency: 'pass', completeness: 'pass' },
|
|
31
|
+
valid_from: '2026-05-26',
|
|
32
|
+
valid_to: null,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
const codes = (input, opts) => validateFactor(input, opts).errors.map(e => e.code);
|
|
22
36
|
describe('validateFactor — positive', () => {
|
|
23
|
-
it('accepts
|
|
24
|
-
const r = validateFactor(
|
|
37
|
+
it('accepts a well-formed external PESTLE factor', () => {
|
|
38
|
+
const r = validateFactor(validExternal());
|
|
25
39
|
expect(r.valid, JSON.stringify(r.errors)).toBe(true);
|
|
26
|
-
expect(r.errors).toHaveLength(0);
|
|
27
40
|
});
|
|
28
|
-
it('accepts a
|
|
29
|
-
|
|
30
|
-
delete r.type;
|
|
31
|
-
delete r.category;
|
|
32
|
-
delete r.references_constraint;
|
|
33
|
-
expect(validateFactor(r).valid).toBe(true);
|
|
41
|
+
it('accepts a well-formed internal factor (no PESTLE category)', () => {
|
|
42
|
+
expect(validateFactor(validInternal()).valid).toBe(true);
|
|
34
43
|
});
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
expect(
|
|
44
|
+
it('accepts a minimal factor with no optional fields', () => {
|
|
45
|
+
const f = validInternal();
|
|
46
|
+
delete f.type;
|
|
47
|
+
delete f.description;
|
|
48
|
+
expect(validateFactor(f).valid).toBe(true);
|
|
40
49
|
});
|
|
41
|
-
|
|
42
|
-
|
|
50
|
+
});
|
|
51
|
+
describe('validateFactor — FACTOR-001 (shape / id grammar)', () => {
|
|
52
|
+
it('flags a missing required envelope field', () => {
|
|
53
|
+
const f = validExternal();
|
|
54
|
+
delete f.admitted_by;
|
|
55
|
+
expect(codes(f)).toContain('FACTOR-001');
|
|
43
56
|
});
|
|
44
|
-
it('flags an id
|
|
45
|
-
expect(codes({ ...
|
|
57
|
+
it('flags an id that violates the grammar', () => {
|
|
58
|
+
expect(codes({ ...validExternal(), id: 'FACTOR-EU-REG-001' })).toContain('FACTOR-001');
|
|
59
|
+
expect(codes({ ...validExternal(), id: 'FAC-1' })).toContain('FACTOR-001');
|
|
60
|
+
expect(codes({ ...validExternal(), id: 'factor-1' })).toContain('FACTOR-001');
|
|
46
61
|
});
|
|
47
|
-
it('flags
|
|
48
|
-
expect(codes({ ...
|
|
62
|
+
it('flags a wrong notation tag and a non-canon zone', () => {
|
|
63
|
+
expect(codes({ ...validExternal(), notation: 'factor' })).toContain('FACTOR-001');
|
|
64
|
+
expect(codes({ ...validExternal(), zone: 'sandbox' })).toContain('FACTOR-001');
|
|
49
65
|
});
|
|
50
66
|
it('flags a missing valid_to key (distinct from a null value)', () => {
|
|
51
|
-
const
|
|
52
|
-
delete
|
|
53
|
-
expect(codes(
|
|
54
|
-
expect(validateFactor({ ...valid(), valid_to: null }).valid).toBe(true);
|
|
67
|
+
const f = validExternal();
|
|
68
|
+
delete f.valid_to;
|
|
69
|
+
expect(codes(f)).toContain('FACTOR-001');
|
|
55
70
|
});
|
|
56
|
-
it('
|
|
57
|
-
expect(codes(
|
|
58
|
-
expect(codes(
|
|
71
|
+
it('rejects a non-object', () => {
|
|
72
|
+
expect(codes(null)).toEqual(['FACTOR-001']);
|
|
73
|
+
expect(codes(['a'])).toEqual(['FACTOR-001']);
|
|
59
74
|
});
|
|
60
75
|
});
|
|
61
76
|
describe('validateFactor — FACTOR-002 (type enum)', () => {
|
|
62
|
-
it('flags a type outside external
|
|
63
|
-
expect(codes({ ...
|
|
64
|
-
});
|
|
65
|
-
it('accepts each permitted type', () => {
|
|
66
|
-
for (const t of ['external', 'internal']) {
|
|
67
|
-
expect(codes({ ...valid(), type: t, category: undefined })).not.toContain('FACTOR-002');
|
|
68
|
-
}
|
|
77
|
+
it('flags a type value outside {external, internal}', () => {
|
|
78
|
+
expect(codes({ ...validExternal(), type: 'driver' })).toContain('FACTOR-002');
|
|
69
79
|
});
|
|
70
80
|
});
|
|
71
81
|
describe('validateFactor — FACTOR-003 (PESTLE category)', () => {
|
|
72
|
-
it('flags a category outside
|
|
73
|
-
expect(codes({ ...
|
|
82
|
+
it('flags a category outside PESTLE', () => {
|
|
83
|
+
expect(codes({ ...validExternal(), category: 'cosmic' })).toContain('FACTOR-003');
|
|
74
84
|
});
|
|
75
|
-
it('
|
|
76
|
-
|
|
85
|
+
it('flags PESTLE category on an internal factor', () => {
|
|
86
|
+
const f = validInternal();
|
|
87
|
+
f.category = 'legal';
|
|
88
|
+
expect(codes(f)).toContain('FACTOR-003');
|
|
77
89
|
});
|
|
78
|
-
it('accepts each permitted PESTLE
|
|
79
|
-
for (const
|
|
80
|
-
|
|
90
|
+
it('accepts each permitted PESTLE category on an external factor', () => {
|
|
91
|
+
for (const cat of ['political', 'economic', 'social', 'technological', 'legal', 'environmental']) {
|
|
92
|
+
const f = validExternal();
|
|
93
|
+
f.category = cat;
|
|
94
|
+
expect(codes(f)).not.toContain('FACTOR-003');
|
|
81
95
|
}
|
|
82
96
|
});
|
|
83
97
|
});
|
|
98
|
+
describe('validateFactor — FACTOR-004 (references_constraint)', () => {
|
|
99
|
+
it('flags a malformed references_constraint entry', () => {
|
|
100
|
+
expect(codes({ ...validExternal(), references_constraint: ['not an id'] })).toContain('FACTOR-004');
|
|
101
|
+
});
|
|
102
|
+
it('flags a references_constraint entry of the wrong TYPE', () => {
|
|
103
|
+
expect(codes({ ...validExternal(), references_constraint: ['GOAL-X-1'] })).toContain('FACTOR-004');
|
|
104
|
+
});
|
|
105
|
+
it('flags an unresolved id when a catalog is supplied', () => {
|
|
106
|
+
const catalog = { typeOf: () => undefined };
|
|
107
|
+
expect(codes(validExternal(), { catalog })).toContain('FACTOR-004');
|
|
108
|
+
});
|
|
109
|
+
it('does not flag without a catalog for a well-formed CONSTRAINT id', () => {
|
|
110
|
+
expect(codes(validExternal())).not.toContain('FACTOR-004');
|
|
111
|
+
});
|
|
112
|
+
});
|
package/dist/factor/index.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
-
export type { Factor
|
|
2
|
-
export {
|
|
1
|
+
export type { Factor, FactorType, FactorPestleCategory } from './types.js';
|
|
2
|
+
export { FACTOR_PESTLE_CATEGORIES } from './types.js';
|
|
3
3
|
export { validateFactor } from './validate.js';
|
|
4
|
+
export type { FactorValidateOptions } from './validate.js';
|
package/dist/factor/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { FACTOR_PESTLE_CATEGORIES } from './types.js';
|
|
2
2
|
export { validateFactor } from './validate.js';
|
package/dist/factor/types.d.ts
CHANGED
|
@@ -1,17 +1,14 @@
|
|
|
1
1
|
import type { GateChecks } from '../requirement/types.js';
|
|
2
|
-
/** Optional subtype — internal/external organisation force. */
|
|
3
2
|
export type FactorType = 'external' | 'internal';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
export type FactorCategory = 'political' | 'economic' | 'social' | 'technological' | 'legal' | 'environmental';
|
|
8
|
-
export declare const FACTOR_CATEGORIES: readonly ["political", "economic", "social", "technological", "legal", "environmental"];
|
|
3
|
+
/** PESTLE sub-classification — external factors only (§7.1). */
|
|
4
|
+
export declare const FACTOR_PESTLE_CATEGORIES: readonly ["political", "economic", "social", "technological", "legal", "environmental"];
|
|
5
|
+
export type FactorPestleCategory = typeof FACTOR_PESTLE_CATEGORIES[number];
|
|
9
6
|
export interface Factor {
|
|
10
|
-
notation: '
|
|
7
|
+
notation: 'driver';
|
|
11
8
|
id: string;
|
|
12
9
|
name: string;
|
|
13
10
|
type?: FactorType;
|
|
14
|
-
category?:
|
|
11
|
+
category?: FactorPestleCategory;
|
|
15
12
|
description?: string;
|
|
16
13
|
references_constraint?: string[];
|
|
17
14
|
zone: 'canon';
|
package/dist/factor/types.js
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
|
-
// FACTOR — motivation-layer
|
|
2
|
-
// Schema: methodology notations/ELEMENT_PRIMITIVES.md §7.1
|
|
3
|
-
|
|
4
|
-
export const
|
|
5
|
-
'political',
|
|
1
|
+
// FACTOR — motivation-layer Driver (ArchiMate).
|
|
2
|
+
// Schema: methodology notations/ELEMENT_PRIMITIVES.md §7.1.
|
|
3
|
+
/** PESTLE sub-classification — external factors only (§7.1). */
|
|
4
|
+
export const FACTOR_PESTLE_CATEGORIES = [
|
|
5
|
+
'political',
|
|
6
|
+
'economic',
|
|
7
|
+
'social',
|
|
8
|
+
'technological',
|
|
9
|
+
'legal',
|
|
10
|
+
'environmental',
|
|
6
11
|
];
|
|
@@ -1,2 +1,7 @@
|
|
|
1
1
|
import type { ValidationResult } from '../validation-types.js';
|
|
2
|
-
|
|
2
|
+
import { type CanonCatalog } from '../typed-id.js';
|
|
3
|
+
export interface FactorValidateOptions {
|
|
4
|
+
/** When provided, enforces that each `references_constraint` id is admitted. */
|
|
5
|
+
catalog?: CanonCatalog;
|
|
6
|
+
}
|
|
7
|
+
export declare function validateFactor(input: unknown, options?: FactorValidateOptions): ValidationResult;
|
package/dist/factor/validate.js
CHANGED
|
@@ -1,66 +1,84 @@
|
|
|
1
1
|
// FACTOR validator — methodology notations/ELEMENT_PRIMITIVES.md §7.1.
|
|
2
2
|
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
3
|
+
// Codes:
|
|
4
|
+
// FACTOR-001 — shape / id grammar / required envelope fields.
|
|
5
|
+
// FACTOR-002 — `type` outside {external, internal}.
|
|
6
|
+
// FACTOR-003 — `category` outside PESTLE vocabulary, or PESTLE applied to a
|
|
7
|
+
// non-external factor (PESTLE applies only to external drivers).
|
|
8
|
+
// FACTOR-004 — `references_constraint` entry malformed or wrong TYPE.
|
|
9
|
+
//
|
|
10
|
+
// `references_constraint` existence (resolves to an admitted artefact) runs
|
|
11
|
+
// only when a `CanonCatalog` is supplied; the prefix-TYPE check runs either way.
|
|
12
|
+
import { typeOfId, isCanonicalIdOfType } from '../typed-id.js';
|
|
13
|
+
import { FACTOR_PESTLE_CATEGORIES } from './types.js';
|
|
14
|
+
const REQUIRED_STRING_FIELDS = [
|
|
15
|
+
'name', 'admitted_at', 'admitted_by', 'valid_from',
|
|
16
|
+
];
|
|
17
|
+
const PESTLE = FACTOR_PESTLE_CATEGORIES;
|
|
18
|
+
export function validateFactor(input, options = {}) {
|
|
10
19
|
const errors = [];
|
|
11
20
|
const warnings = [];
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
idType: 'FACTOR',
|
|
15
|
-
code: 'FACTOR-001',
|
|
16
|
-
});
|
|
17
|
-
errors.push(...envErrors);
|
|
18
|
-
if (!record)
|
|
21
|
+
if (input === null || typeof input !== 'object' || Array.isArray(input)) {
|
|
22
|
+
errors.push({ code: 'FACTOR-001', message: 'Factor must be a YAML mapping.' });
|
|
19
23
|
return { valid: false, errors, warnings };
|
|
20
|
-
// FACTOR-002 — type subtype enum (optional field).
|
|
21
|
-
if (record.type !== undefined && !FACTOR_TYPES.includes(record.type)) {
|
|
22
|
-
errors.push({
|
|
23
|
-
code: 'FACTOR-002',
|
|
24
|
-
message: `type "${String(record.type)}" must be one of ${FACTOR_TYPES.join(', ')}.`,
|
|
25
|
-
path: 'type',
|
|
26
|
-
});
|
|
27
24
|
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
25
|
+
const f = input;
|
|
26
|
+
// FACTOR-001 — id grammar + notation tag + envelope.
|
|
27
|
+
if (!isCanonicalIdOfType(f.id, 'FACTOR')) {
|
|
28
|
+
errors.push({ code: 'FACTOR-001', message: `id "${String(f.id)}" must match FACTOR-[<middle>-]<INTEGER>.`, path: 'id' });
|
|
29
|
+
}
|
|
30
|
+
if (f.notation !== 'driver') {
|
|
31
|
+
errors.push({ code: 'FACTOR-001', message: 'notation must be the fixed value "driver".', path: 'notation' });
|
|
32
|
+
}
|
|
33
|
+
if (f.zone !== undefined && f.zone !== 'canon') {
|
|
34
|
+
errors.push({ code: 'FACTOR-001', message: 'zone must be "canon" for a FACTOR.', path: 'zone' });
|
|
35
|
+
}
|
|
36
|
+
else if (f.zone === undefined) {
|
|
37
|
+
errors.push({ code: 'FACTOR-001', message: 'zone is required.', path: 'zone' });
|
|
38
|
+
}
|
|
39
|
+
for (const field of REQUIRED_STRING_FIELDS) {
|
|
40
|
+
const v = f[field];
|
|
41
|
+
if (typeof v !== 'string' || v.trim() === '') {
|
|
42
|
+
errors.push({ code: 'FACTOR-001', message: `${field} is required.`, path: field });
|
|
36
43
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
+
}
|
|
45
|
+
if (f.gate_checks === null || typeof f.gate_checks !== 'object') {
|
|
46
|
+
errors.push({ code: 'FACTOR-001', message: 'gate_checks is required and must be a mapping.', path: 'gate_checks' });
|
|
47
|
+
}
|
|
48
|
+
if (!('valid_to' in f) || !(typeof f.valid_to === 'string' || f.valid_to === null)) {
|
|
49
|
+
errors.push({ code: 'FACTOR-001', message: 'valid_to is required (an ISO date string or null).', path: 'valid_to' });
|
|
50
|
+
}
|
|
51
|
+
// FACTOR-002 — type enum (optional field; only check when present).
|
|
52
|
+
if (f.type !== undefined && f.type !== 'external' && f.type !== 'internal') {
|
|
53
|
+
errors.push({ code: 'FACTOR-002', message: `type "${String(f.type)}" must be one of external, internal.`, path: 'type' });
|
|
54
|
+
}
|
|
55
|
+
// FACTOR-003 — category PESTLE vocabulary + external-only constraint.
|
|
56
|
+
if (f.category !== undefined) {
|
|
57
|
+
if (typeof f.category !== 'string' || !PESTLE.includes(f.category)) {
|
|
58
|
+
errors.push({ code: 'FACTOR-003', message: `category "${String(f.category)}" must be one of ${PESTLE.join(', ')}.`, path: 'category' });
|
|
59
|
+
}
|
|
60
|
+
else if (f.type === 'internal') {
|
|
61
|
+
errors.push({ code: 'FACTOR-003', message: 'category is PESTLE — applies only to external factors; omit on internal factors.', path: 'category' });
|
|
44
62
|
}
|
|
45
63
|
}
|
|
46
|
-
// FACTOR-
|
|
47
|
-
if (
|
|
48
|
-
if (!Array.isArray(
|
|
49
|
-
errors.push({
|
|
50
|
-
code: 'FACTOR-001',
|
|
51
|
-
message: 'references_constraint must be a list of typed CONSTRAINT IDs.',
|
|
52
|
-
path: 'references_constraint',
|
|
53
|
-
});
|
|
64
|
+
// FACTOR-004 — references_constraint refs.
|
|
65
|
+
if (f.references_constraint !== undefined) {
|
|
66
|
+
if (!Array.isArray(f.references_constraint)) {
|
|
67
|
+
errors.push({ code: 'FACTOR-001', message: 'references_constraint must be a list of typed IDs.', path: 'references_constraint' });
|
|
54
68
|
}
|
|
55
69
|
else {
|
|
56
|
-
|
|
70
|
+
f.references_constraint.forEach((ref, i) => {
|
|
57
71
|
const type = typeOfId(ref);
|
|
72
|
+
if (!type) {
|
|
73
|
+
errors.push({ code: 'FACTOR-004', message: `references_constraint[${i}] "${String(ref)}" is not a resolvable typed ID.`, path: `references_constraint[${i}]` });
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
58
76
|
if (type !== 'CONSTRAINT') {
|
|
59
|
-
errors.push({
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
});
|
|
77
|
+
errors.push({ code: 'FACTOR-004', message: `references_constraint[${i}] TYPE "${type}" must be CONSTRAINT.`, path: `references_constraint[${i}]` });
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
if (options.catalog && options.catalog.typeOf(ref) === undefined) {
|
|
81
|
+
errors.push({ code: 'FACTOR-004', message: `references_constraint[${i}] "${String(ref)}" does not resolve to an admitted artefact.`, path: `references_constraint[${i}]` });
|
|
64
82
|
}
|
|
65
83
|
});
|
|
66
84
|
}
|
|
@@ -31,7 +31,7 @@ describe('buildChainTable — worked example (FGCA)', () => {
|
|
|
31
31
|
};
|
|
32
32
|
it('produces the spec grid with F1 spanning 3 and G2/C2 spanning 2', () => {
|
|
33
33
|
const t = buildChainTable(doc);
|
|
34
|
-
expect(t.columns).toEqual(['
|
|
34
|
+
expect(t.columns).toEqual(['driver', 'goal', 'change', 'activity']);
|
|
35
35
|
expect(asGrid(t)).toEqual([
|
|
36
36
|
['F1×3', 'G1', 'C1', 'A1'],
|
|
37
37
|
['·', 'G2×2', 'C2×2', 'A2'],
|
|
@@ -59,7 +59,7 @@ describe('buildChainTable — FGA (no Change column)', () => {
|
|
|
59
59
|
};
|
|
60
60
|
it('emits three columns and merges Factor/Goal across the two activities', () => {
|
|
61
61
|
const t = buildChainTable(doc, { hideChanges: true });
|
|
62
|
-
expect(t.columns).toEqual(['
|
|
62
|
+
expect(t.columns).toEqual(['driver', 'goal', 'activity']);
|
|
63
63
|
expect(asGrid(t)).toEqual([
|
|
64
64
|
['F1×2', 'G1×2', 'A1'],
|
|
65
65
|
['·', '·', 'A2'],
|
|
@@ -39,15 +39,15 @@ describe("buildFGCALayout — all columns visible", () => {
|
|
|
39
39
|
});
|
|
40
40
|
it("assigns correct node types", () => {
|
|
41
41
|
const types = nodes.map((n) => n.type);
|
|
42
|
-
expect(types.filter((t) => t === "
|
|
42
|
+
expect(types.filter((t) => t === "fgcaDriver")).toHaveLength(factors.length);
|
|
43
43
|
expect(types.filter((t) => t === "fgcaGoal")).toHaveLength(goals.length);
|
|
44
44
|
expect(types.filter((t) => t === "fgcaChange")).toHaveLength(changes.length);
|
|
45
45
|
expect(types.filter((t) => t === "fgcaActivity")).toHaveLength(activities.length);
|
|
46
46
|
});
|
|
47
47
|
it("creates Factor→Goal edges", () => {
|
|
48
|
-
const fgEdges = edges.filter((e) => e.source.startsWith("
|
|
48
|
+
const fgEdges = edges.filter((e) => e.source.startsWith("driver_") && e.target.startsWith("goal_"));
|
|
49
49
|
expect(fgEdges).toHaveLength(2);
|
|
50
|
-
expect(fgEdges.find((e) => e.source === "
|
|
50
|
+
expect(fgEdges.find((e) => e.source === "driver_1" && e.target === "goal_10")).toBeTruthy();
|
|
51
51
|
});
|
|
52
52
|
it("creates Goal→Change edges", () => {
|
|
53
53
|
const gcEdges = edges.filter((e) => e.source.startsWith("goal_") && e.target.startsWith("change_"));
|
|
@@ -66,7 +66,7 @@ describe("buildFGCALayout — all columns visible", () => {
|
|
|
66
66
|
expect(actNode?.data.activityTypeName).toBe("Initiative");
|
|
67
67
|
});
|
|
68
68
|
it("encodes F-XXXX label format via id in node data", () => {
|
|
69
|
-
const factorNode = nodes.find((n) => n.id === "
|
|
69
|
+
const factorNode = nodes.find((n) => n.id === "driver_1");
|
|
70
70
|
expect(String(factorNode?.data.id).padStart(4, "0")).toBe("0001");
|
|
71
71
|
});
|
|
72
72
|
it("encodes C-XXX label format via id in node data", () => {
|
|
@@ -74,14 +74,14 @@ describe("buildFGCALayout — all columns visible", () => {
|
|
|
74
74
|
expect(String(changeNode?.data.id).padStart(3, "0")).toBe("100");
|
|
75
75
|
});
|
|
76
76
|
});
|
|
77
|
-
describe("buildFGCALayout —
|
|
78
|
-
const cols = new Set(ALL_FGCA_COLUMNS.filter((c) => c !== "
|
|
77
|
+
describe("buildFGCALayout — Driver column hidden", () => {
|
|
78
|
+
const cols = new Set(ALL_FGCA_COLUMNS.filter((c) => c !== "driver"));
|
|
79
79
|
const { nodes, edges } = buildFGCALayout({ factors, goals, changes, activities, visibleColumns: cols });
|
|
80
|
-
it("produces no
|
|
81
|
-
expect(nodes.filter((n) => n.type === "
|
|
80
|
+
it("produces no driver nodes", () => {
|
|
81
|
+
expect(nodes.filter((n) => n.type === "fgcaDriver")).toHaveLength(0);
|
|
82
82
|
});
|
|
83
|
-
it("produces no
|
|
84
|
-
expect(edges.filter((e) => e.source.startsWith("
|
|
83
|
+
it("produces no Driver→Goal edges", () => {
|
|
84
|
+
expect(edges.filter((e) => e.source.startsWith("driver_"))).toHaveLength(0);
|
|
85
85
|
});
|
|
86
86
|
it("still produces Goal and downstream nodes", () => {
|
|
87
87
|
expect(nodes.filter((n) => n.type === "fgcaGoal")).toHaveLength(goals.length);
|
|
@@ -116,7 +116,7 @@ describe("buildFGCALayout — only one column visible", () => {
|
|
|
116
116
|
describe("buildFGCALayout — columns are positioned left to right", () => {
|
|
117
117
|
const { nodes } = buildFGCALayout({ factors, goals, changes, activities, visibleColumns: ALL_COLS });
|
|
118
118
|
it("factor column x < goal column x", () => {
|
|
119
|
-
const f = nodes.find((n) => n.type === "
|
|
119
|
+
const f = nodes.find((n) => n.type === "fgcaDriver");
|
|
120
120
|
const g = nodes.find((n) => n.type === "fgcaGoal");
|
|
121
121
|
expect(f.position.x).toBeLessThan(g.position.x);
|
|
122
122
|
});
|
|
@@ -18,7 +18,7 @@ const doc = {
|
|
|
18
18
|
describe('layoutFGCAPreview', () => {
|
|
19
19
|
it('lays out all four columns in order (FGCA)', () => {
|
|
20
20
|
const layout = layoutFGCAPreview(doc);
|
|
21
|
-
expect(layout.columns.map(c => c.col)).toEqual(['
|
|
21
|
+
expect(layout.columns.map(c => c.col)).toEqual(['driver', 'goal', 'change', 'activity']);
|
|
22
22
|
// 2 factors + 2 goals + 1 change + 2 activities
|
|
23
23
|
expect(layout.nodes).toHaveLength(7);
|
|
24
24
|
expect(layout.width).toBeGreaterThan(0);
|
|
@@ -31,7 +31,7 @@ describe('layoutFGCAPreview', () => {
|
|
|
31
31
|
});
|
|
32
32
|
it('hideChanges (FGA) drops the Changes column and links goals to activities', () => {
|
|
33
33
|
const layout = layoutFGCAPreview(doc, { hideChanges: true });
|
|
34
|
-
expect(layout.columns.map(c => c.col)).toEqual(['
|
|
34
|
+
expect(layout.columns.map(c => c.col)).toEqual(['driver', 'goal', 'activity']);
|
|
35
35
|
// 2 factors + 2 goals + 2 activities
|
|
36
36
|
expect(layout.nodes).toHaveLength(6);
|
|
37
37
|
// F1→G1, F2→G2 (2), G1→A1, G2→A2 (2) = 4
|
|
@@ -57,7 +57,7 @@ describe('layoutFGCAPreview', () => {
|
|
|
57
57
|
});
|
|
58
58
|
it('larger rowGap increases the gap between stacked nodes', () => {
|
|
59
59
|
const gapOf = (rowGap) => {
|
|
60
|
-
const factors = layoutFGCAPreview(doc, { rowGap }).nodes.filter(n => n.col === '
|
|
60
|
+
const factors = layoutFGCAPreview(doc, { rowGap }).nodes.filter(n => n.col === 'driver');
|
|
61
61
|
return factors[1].y - factors[0].y;
|
|
62
62
|
};
|
|
63
63
|
expect(gapOf(120)).toBeGreaterThan(gapOf(20));
|
|
@@ -71,14 +71,14 @@ describe('layoutFGCAPreview', () => {
|
|
|
71
71
|
// root 10 → factor 1 (referenced by G10), change 20 (goal_id 10),
|
|
72
72
|
// activity 30 (via change 20). G11/F2/A31 are dropped.
|
|
73
73
|
const layout = layoutFGCAPreview(doc, { scope: { mode: 'root', rootGoalId: '10' } });
|
|
74
|
-
expect(idsOf(layout)).toEqual(new Set(['
|
|
74
|
+
expect(idsOf(layout)).toEqual(new Set(['driver_1', 'goal_10', 'change_20', 'activity_30']));
|
|
75
75
|
});
|
|
76
76
|
it("mode 'root' filters factors and activities to those touching the visible goal", () => {
|
|
77
77
|
// root 11 → factor 2, activity 31 (direct goal link); no change touches G11.
|
|
78
78
|
const layout = layoutFGCAPreview(doc, { scope: { mode: 'root', rootGoalId: '11' } });
|
|
79
|
-
expect(idsOf(layout)).toEqual(new Set(['
|
|
79
|
+
expect(idsOf(layout)).toEqual(new Set(['driver_2', 'goal_11', 'activity_31']));
|
|
80
80
|
// F1, A30, C20 (which only touch the hidden G10) are excluded.
|
|
81
|
-
expect(layout.nodes.some(n => n.id === '
|
|
81
|
+
expect(layout.nodes.some(n => n.id === 'driver_1')).toBe(false);
|
|
82
82
|
expect(layout.nodes.some(n => n.id === 'activity_30')).toBe(false);
|
|
83
83
|
});
|
|
84
84
|
it("mode 'level' trims goals above the cap and their connections", () => {
|
|
@@ -95,7 +95,7 @@ describe('layoutFGCAPreview', () => {
|
|
|
95
95
|
],
|
|
96
96
|
};
|
|
97
97
|
const layout = layoutFGCAPreview(leveled, { scope: { mode: 'level', maxLevel: 0 } });
|
|
98
|
-
expect(idsOf(layout)).toEqual(new Set(['
|
|
98
|
+
expect(idsOf(layout)).toEqual(new Set(['driver_1', 'goal_10', 'activity_30']));
|
|
99
99
|
});
|
|
100
100
|
it("mode 'root' returns an empty layout when the root is absent", () => {
|
|
101
101
|
const layout = layoutFGCAPreview(doc, { scope: { mode: 'root', rootGoalId: '999' } });
|