@arcote.tech/arc-map 0.7.28 → 0.7.29
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/package.json +4 -6
- package/src/app/nodes.tsx +0 -247
- package/src/app/theme.ts +1 -0
- package/src/app/usecase-view.tsx +64 -144
- package/src/catalog.ts +0 -0
- package/src/index.ts +7 -2
- package/src/mapper.ts +78 -0
- package/src/module-index.ts +1 -1
- package/src/scan.ts +1 -1
- package/src/server.ts +48 -1
- package/src/types.ts +39 -0
- package/tests/catalog.test.ts +67 -0
- package/src/app/arc.tsx +0 -92
- package/src/app/contexts.tsx +0 -72
- package/src/app/mdx-view.tsx +0 -74
- package/src/app/ref-context.tsx +0 -49
- package/src/app/structure.tsx +0 -95
- package/src/app/subgraph.ts +0 -127
- package/src/app/usecase-layout.ts +0 -249
- package/src/app/usecase-model.ts +0 -346
- package/src/refs.ts +0 -121
- package/tests/refs.test.ts +0 -56
- package/tests/subgraph.test.ts +0 -54
- package/tests/usecase-model.test.ts +0 -197
|
@@ -1,197 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from "bun:test";
|
|
2
|
-
import { buildUseCaseModel, type OccurrenceRef } from "../src/app/usecase-model";
|
|
3
|
-
import { resolveRef } from "../src/refs";
|
|
4
|
-
import type { MapEdge, MapJson } from "../src/types";
|
|
5
|
-
|
|
6
|
-
function el(id: string, kind: any, extra: any = {}) {
|
|
7
|
-
return { id, name: id, kind, spaceId: "s", ...extra };
|
|
8
|
-
}
|
|
9
|
-
function edge(source: string, target: string, kind: MapEdge["kind"]): MapEdge {
|
|
10
|
-
return { id: `${source}->${target}:${kind}`, source, target, kind, crossContext: false };
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
// cmd reads v, writes e; listener l also writes e; v,x are views; e event.
|
|
14
|
-
const map: MapJson = {
|
|
15
|
-
spaces: [{ id: "s", name: "s" }],
|
|
16
|
-
elements: [
|
|
17
|
-
el("cmd", "command", { queryDeps: ["v"], mutationDeps: ["e"] }),
|
|
18
|
-
el("l", "listener", { mutationDeps: ["e"] }),
|
|
19
|
-
el("v", "view"),
|
|
20
|
-
el("e", "event"),
|
|
21
|
-
el("x", "view"),
|
|
22
|
-
],
|
|
23
|
-
edges: [edge("cmd", "v", "queries"), edge("cmd", "e", "mutates"), edge("l", "e", "mutates")],
|
|
24
|
-
components: [],
|
|
25
|
-
componentEdges: [],
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
function occ(occId: string, props: any, label = occId): OccurrenceRef {
|
|
29
|
-
return { occId, ref: resolveRef(props), label };
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
describe("buildUseCaseModel", () => {
|
|
33
|
-
test("one step per occurrence (duplicates kept)", () => {
|
|
34
|
-
const m = buildUseCaseModel({
|
|
35
|
-
map,
|
|
36
|
-
occurrences: [occ("o1", { command: "cmd" }), occ("o2", { command: "cmd" })],
|
|
37
|
-
});
|
|
38
|
-
expect(m.steps.map((s) => s.occId)).toEqual(["o1", "o2"]);
|
|
39
|
-
expect(m.steps.map((s) => s.seq)).toEqual([1, 2]);
|
|
40
|
-
expect(m.steps.every((s) => s.exists)).toBe(true);
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
test("missing reference → step with exists=false", () => {
|
|
44
|
-
const m = buildUseCaseModel({ map, occurrences: [occ("o1", { command: "ghost" })] });
|
|
45
|
-
expect(m.steps[0].exists).toBe(false);
|
|
46
|
-
expect(m.steps[0].element).toBeUndefined();
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
test("forward badges exclude visible; query dep becomes a badge", () => {
|
|
50
|
-
const m = buildUseCaseModel({
|
|
51
|
-
map,
|
|
52
|
-
occurrences: [occ("o1", { command: "cmd" }), occ("o2", { event: "e" })],
|
|
53
|
-
});
|
|
54
|
-
const cmd = m.steps.find((s) => s.occId === "o1")!;
|
|
55
|
-
// cmd queries v (not visible → badge); mutates e (visible → excluded).
|
|
56
|
-
expect(cmd.badges.queries).toEqual(["v"]);
|
|
57
|
-
expect(cmd.badges.mutates).toBeUndefined();
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
test("reverse mutatedBy badge (e is written by cmd[visible] and l[hidden])", () => {
|
|
61
|
-
const m = buildUseCaseModel({
|
|
62
|
-
map,
|
|
63
|
-
occurrences: [occ("o1", { command: "cmd" }), occ("o2", { event: "e" })],
|
|
64
|
-
});
|
|
65
|
-
const e = m.steps.find((s) => s.occId === "o2")!;
|
|
66
|
-
// mutatedBy = {cmd (visible→excluded), l (hidden→badge)}.
|
|
67
|
-
expect(e.badges.mutatedBy).toEqual(["l"]);
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
test("connector sits on the DIRECTED flow path between two narrated steps", () => {
|
|
71
|
-
// cmd emits e, listener l handles e → directed flow cmd → e → l, so e is
|
|
72
|
-
// genuinely BETWEEN them and surfaces as a connector.
|
|
73
|
-
const chainMap: MapJson = {
|
|
74
|
-
spaces: [{ id: "s", name: "s" }],
|
|
75
|
-
elements: [el("cmd", "command", { mutationDeps: ["e"] }), el("e", "event"), el("l", "listener")],
|
|
76
|
-
edges: [edge("cmd", "e", "emits"), edge("l", "e", "handles")],
|
|
77
|
-
components: [],
|
|
78
|
-
componentEdges: [],
|
|
79
|
-
};
|
|
80
|
-
const m = buildUseCaseModel({
|
|
81
|
-
map: chainMap,
|
|
82
|
-
occurrences: [occ("o1", { command: "cmd" }), occ("o2", { listener: "l" })],
|
|
83
|
-
});
|
|
84
|
-
expect(m.connectors.map((c) => c.id)).toEqual(["e"]);
|
|
85
|
-
expect(m.connectors[0].between).toEqual([1, 2]); // [upstream cmd, downstream l]
|
|
86
|
-
expect(m.connectors[0].order).toBe(1);
|
|
87
|
-
expect(m.connectors[0].count).toBe(1);
|
|
88
|
-
expect(m.visible.has("e")).toBe(true);
|
|
89
|
-
// e is a connector (visible) → cmd no longer badges it as mutates.
|
|
90
|
-
expect(m.steps.find((s) => s.occId === "o1")!.badges.mutates).toBeUndefined();
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
test("shared downstream is NOT a connector (no directed between-path)", () => {
|
|
94
|
-
// In `map`, cmd AND l both write e → e is downstream of both, not between
|
|
95
|
-
// them. Directed flow finds no cmd→l / l→cmd path, so no connector.
|
|
96
|
-
const m = buildUseCaseModel({
|
|
97
|
-
map,
|
|
98
|
-
occurrences: [occ("o1", { command: "cmd" }), occ("o2", { listener: "l" })],
|
|
99
|
-
});
|
|
100
|
-
expect(m.connectors.length).toBe(0);
|
|
101
|
-
expect(m.visible.has("e")).toBe(false);
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
test("usedByUI badge from componentEdges + component refs resolve", () => {
|
|
105
|
-
const uiMap: MapJson = {
|
|
106
|
-
spaces: [{ id: "s", name: "s" }],
|
|
107
|
-
elements: [el("acc", "aggregate")],
|
|
108
|
-
edges: [],
|
|
109
|
-
components: [
|
|
110
|
-
{ id: "pkg/login.tsx", file: "pkg/login.tsx", name: "login" },
|
|
111
|
-
{ id: "pkg/other.tsx", file: "pkg/other.tsx", name: "other" },
|
|
112
|
-
],
|
|
113
|
-
componentEdges: [
|
|
114
|
-
{ id: "e1", source: "pkg/login.tsx", target: "acc", usage: "query", methods: [] },
|
|
115
|
-
],
|
|
116
|
-
};
|
|
117
|
-
// narrate the aggregate; login.tsx uses it → usedByUI badge.
|
|
118
|
-
const m = buildUseCaseModel({ map: uiMap, occurrences: [occ("o1", { aggregate: "acc" })] });
|
|
119
|
-
expect(m.steps[0].badges.usedByUI).toEqual(["pkg/login.tsx"]);
|
|
120
|
-
expect(m.uiById.has("pkg/login.tsx")).toBe(true);
|
|
121
|
-
|
|
122
|
-
// `<Arc component="...">` resolves against the component (UI) layer.
|
|
123
|
-
const m2 = buildUseCaseModel({ map: uiMap, occurrences: [occ("o1", { component: "pkg/login.tsx" })] });
|
|
124
|
-
expect(m2.steps[0].exists).toBe(true);
|
|
125
|
-
expect(m2.steps[0].element?.kind).toBe("page");
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
test("depth/lane: plain sequence increments depth, lane 0", () => {
|
|
129
|
-
const m = buildUseCaseModel({ map, occurrences: [occ("o1", { command: "cmd" }), occ("o2", { event: "e" })] });
|
|
130
|
-
expect(m.steps.map((s) => [s.depth, s.lane])).toEqual([[0, 0], [1, 0]]);
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
test("depth/lane: <Parallel> members share depth on separate lanes; next plain advances", () => {
|
|
134
|
-
const p = (occId: string, props: any, lane: number) => ({
|
|
135
|
-
occId, ref: resolveRef(props), label: occId,
|
|
136
|
-
path: [{ type: "parallel" as const, id: "p1", laneIndex: lane }],
|
|
137
|
-
});
|
|
138
|
-
const m = buildUseCaseModel({
|
|
139
|
-
map,
|
|
140
|
-
occurrences: [
|
|
141
|
-
p("a", { command: "cmd" }, 0),
|
|
142
|
-
p("b", { view: "v" }, 1),
|
|
143
|
-
occ("c", { event: "e" }),
|
|
144
|
-
],
|
|
145
|
-
});
|
|
146
|
-
const by = new Map(m.steps.map((s) => [s.occId, s]));
|
|
147
|
-
expect([by.get("a")!.depth, by.get("a")!.lane]).toEqual([0, 0]);
|
|
148
|
-
expect([by.get("b")!.depth, by.get("b")!.lane]).toEqual([0, 1]); // same depth, lane 1
|
|
149
|
-
expect([by.get("c")!.depth, by.get("c")!.lane]).toEqual([1, 0]); // block occupies 1 slot
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
test("depth/lane: <Branch>/<Lane> marks alt and sets lanes", () => {
|
|
153
|
-
const b = (occId: string, props: any, lane: number, title: string) => ({
|
|
154
|
-
occId, ref: resolveRef(props), label: occId,
|
|
155
|
-
path: [
|
|
156
|
-
{ type: "branch" as const, id: "br1", laneIndex: lane },
|
|
157
|
-
{ type: "lane" as const, id: "l" + lane, title, alt: true },
|
|
158
|
-
],
|
|
159
|
-
});
|
|
160
|
-
const m = buildUseCaseModel({
|
|
161
|
-
map,
|
|
162
|
-
occurrences: [b("ok", { view: "v" }, 0, "Sukces"), b("err", { event: "e" }, 1, "Błąd")],
|
|
163
|
-
});
|
|
164
|
-
const by = new Map(m.steps.map((s) => [s.occId, s]));
|
|
165
|
-
expect(by.get("ok")!.alt).toBe(true);
|
|
166
|
-
expect([by.get("ok")!.depth, by.get("ok")!.lane]).toEqual([0, 0]);
|
|
167
|
-
expect([by.get("err")!.depth, by.get("err")!.lane]).toEqual([0, 1]);
|
|
168
|
-
expect(by.get("ok")!.laneTitle).toBe("Sukces");
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
test("aggregate mutationMethod uses per-method deps", () => {
|
|
172
|
-
const aggMap: MapJson = {
|
|
173
|
-
spaces: [{ id: "s", name: "s" }],
|
|
174
|
-
elements: [
|
|
175
|
-
el("acc", "aggregate", {
|
|
176
|
-
methods: [
|
|
177
|
-
{ name: "check", kind: "mutate", queryDeps: ["dirA"], mutationDeps: ["dirB"] },
|
|
178
|
-
{ name: "other", kind: "mutate", queryDeps: ["zzz"], mutationDeps: [] },
|
|
179
|
-
],
|
|
180
|
-
}),
|
|
181
|
-
el("dirA", "view"),
|
|
182
|
-
el("dirB", "event"),
|
|
183
|
-
el("zzz", "view"),
|
|
184
|
-
],
|
|
185
|
-
edges: [],
|
|
186
|
-
components: [],
|
|
187
|
-
componentEdges: [],
|
|
188
|
-
};
|
|
189
|
-
const m = buildUseCaseModel({
|
|
190
|
-
map: aggMap,
|
|
191
|
-
occurrences: [occ("o1", { aggregate: "acc", mutationMethod: "check" })],
|
|
192
|
-
});
|
|
193
|
-
const step = m.steps[0];
|
|
194
|
-
expect(step.badges.queries).toEqual(["dirA"]); // check's deps, NOT other's "zzz"
|
|
195
|
-
expect(step.badges.mutates).toEqual(["dirB"]);
|
|
196
|
-
});
|
|
197
|
-
});
|