@dxos/diagram 0.11.1 → 0.14.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/dist/lib/chunk-parse.mjs +9406 -0
- package/dist/lib/chunk-parse.mjs.map +1 -0
- package/dist/lib/extension.mjs +204 -0
- package/dist/lib/extension.mjs.map +1 -0
- package/dist/lib/index.mjs +497 -2846
- package/dist/lib/index.mjs.map +1 -1
- package/dist/types/src/aesthetics.d.ts +12 -0
- package/dist/types/src/aesthetics.d.ts.map +1 -0
- package/dist/types/src/aesthetics.test.d.ts +2 -0
- package/dist/types/src/aesthetics.test.d.ts.map +1 -0
- package/dist/types/src/appeal.d.ts +55 -0
- package/dist/types/src/appeal.d.ts.map +1 -0
- package/dist/types/src/appeal.test.d.ts +2 -0
- package/dist/types/src/appeal.test.d.ts.map +1 -0
- package/dist/types/src/architecture.d.ts +86 -0
- package/dist/types/src/architecture.d.ts.map +1 -0
- package/dist/types/src/architecture.test.d.ts +2 -0
- package/dist/types/src/architecture.test.d.ts.map +1 -0
- package/dist/types/src/compact.d.ts +39 -0
- package/dist/types/src/compact.d.ts.map +1 -0
- package/dist/types/src/compact.test.d.ts +2 -0
- package/dist/types/src/compact.test.d.ts.map +1 -0
- package/dist/types/src/diagnostics.d.ts +70 -3
- package/dist/types/src/diagnostics.d.ts.map +1 -1
- package/dist/types/src/dsl/cli.d.ts +2 -0
- package/dist/types/src/dsl/cli.d.ts.map +1 -0
- package/dist/types/src/dsl/compile.d.ts +12 -0
- package/dist/types/src/dsl/compile.d.ts.map +1 -0
- package/dist/types/src/dsl/convert.d.ts +45 -0
- package/dist/types/src/dsl/convert.d.ts.map +1 -0
- package/dist/types/src/dsl/convert.test.d.ts +2 -0
- package/dist/types/src/dsl/convert.test.d.ts.map +1 -0
- package/dist/types/src/dsl/dsl.test.d.ts +2 -0
- package/dist/types/src/dsl/dsl.test.d.ts.map +1 -0
- package/dist/types/src/dsl/gen/diagram.d.ts +3 -0
- package/dist/types/src/dsl/gen/diagram.d.ts.map +1 -0
- package/dist/types/src/dsl/index.d.ts +7 -0
- package/dist/types/src/dsl/index.d.ts.map +1 -0
- package/dist/types/src/dsl/parse.d.ts +49 -0
- package/dist/types/src/dsl/parse.d.ts.map +1 -0
- package/dist/types/src/dsl/print.d.ts +19 -0
- package/dist/types/src/dsl/print.d.ts.map +1 -0
- package/dist/types/src/dsl/semantic.test.d.ts +2 -0
- package/dist/types/src/dsl/semantic.test.d.ts.map +1 -0
- package/dist/types/src/dsl/vocabulary.d.ts +61 -0
- package/dist/types/src/dsl/vocabulary.d.ts.map +1 -0
- package/dist/types/src/dx-svg.d.ts +12 -0
- package/dist/types/src/dx-svg.d.ts.map +1 -0
- package/dist/types/src/dx-svg.test.d.ts +2 -0
- package/dist/types/src/dx-svg.test.d.ts.map +1 -0
- package/dist/types/src/extension/complete.d.ts +3 -0
- package/dist/types/src/extension/complete.d.ts.map +1 -0
- package/dist/types/src/extension/extension.test.d.ts +2 -0
- package/dist/types/src/extension/extension.test.d.ts.map +1 -0
- package/dist/types/src/extension/highlight.d.ts +14 -0
- package/dist/types/src/extension/highlight.d.ts.map +1 -0
- package/dist/types/src/extension/index.d.ts +6 -0
- package/dist/types/src/extension/index.d.ts.map +1 -0
- package/dist/types/src/extension/language.d.ts +13 -0
- package/dist/types/src/extension/language.d.ts.map +1 -0
- package/dist/types/src/extension/lint.d.ts +21 -0
- package/dist/types/src/extension/lint.d.ts.map +1 -0
- package/dist/types/src/extension/syntax.d.ts +3 -0
- package/dist/types/src/extension/syntax.d.ts.map +1 -0
- package/dist/types/src/index.d.ts +14 -0
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/mermaid-engine.d.ts +40 -1
- package/dist/types/src/mermaid-engine.d.ts.map +1 -1
- package/dist/types/src/mermaid.d.ts +3 -3
- package/dist/types/src/mermaid.d.ts.map +1 -1
- package/dist/types/src/nudge.d.ts +30 -0
- package/dist/types/src/nudge.d.ts.map +1 -0
- package/dist/types/src/nudge.test.d.ts +2 -0
- package/dist/types/src/nudge.test.d.ts.map +1 -0
- package/dist/types/src/objective.d.ts +4 -0
- package/dist/types/src/objective.d.ts.map +1 -1
- package/dist/types/src/ortho-router.d.ts +13 -1
- package/dist/types/src/ortho-router.d.ts.map +1 -1
- package/dist/types/src/ports.d.ts +40 -0
- package/dist/types/src/ports.d.ts.map +1 -0
- package/dist/types/src/ports.test.d.ts +2 -0
- package/dist/types/src/ports.test.d.ts.map +1 -0
- package/dist/types/src/relations.test.d.ts +2 -0
- package/dist/types/src/relations.test.d.ts.map +1 -0
- package/dist/types/src/rules.d.ts +30 -0
- package/dist/types/src/rules.d.ts.map +1 -0
- package/dist/types/src/scene.d.ts +108 -74
- package/dist/types/src/scene.d.ts.map +1 -1
- package/dist/types/src/score.d.ts +54 -0
- package/dist/types/src/score.d.ts.map +1 -0
- package/dist/types/src/score.test.d.ts +2 -0
- package/dist/types/src/score.test.d.ts.map +1 -0
- package/dist/types/src/semantic-engine.d.ts +41 -0
- package/dist/types/src/semantic-engine.d.ts.map +1 -0
- package/dist/types/src/semantic-engine.test.d.ts +2 -0
- package/dist/types/src/semantic-engine.test.d.ts.map +1 -0
- package/dist/types/src/semantic-place.d.ts +81 -0
- package/dist/types/src/semantic-place.d.ts.map +1 -0
- package/dist/types/src/semantic-route.d.ts +133 -0
- package/dist/types/src/semantic-route.d.ts.map +1 -0
- package/dist/types/src/semantic.d.ts +164 -0
- package/dist/types/src/semantic.d.ts.map +1 -0
- package/dist/types/src/uml-grid.d.ts.map +1 -1
- package/dist/types/src/uml.d.ts +3 -5
- package/dist/types/src/uml.d.ts.map +1 -1
- package/dist/types/src/view.d.ts +53 -0
- package/dist/types/src/view.d.ts.map +1 -0
- package/dist/types/src/view.test.d.ts +2 -0
- package/dist/types/src/view.test.d.ts.map +1 -0
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +23 -5
- package/rules/DIAGRAM.mdl +388 -0
- package/src/aesthetics.test.ts +89 -0
- package/src/aesthetics.ts +148 -0
- package/src/appeal.test.ts +144 -0
- package/src/appeal.ts +586 -0
- package/src/architecture.test.ts +94 -0
- package/src/architecture.ts +215 -0
- package/src/compact.test.ts +67 -0
- package/src/compact.ts +219 -0
- package/src/diagnostics.test.ts +207 -1
- package/src/diagnostics.ts +481 -37
- package/src/dsl/cli.ts +46 -0
- package/src/dsl/compile.ts +28 -0
- package/src/dsl/convert.test.ts +51 -0
- package/src/dsl/convert.ts +98 -0
- package/src/dsl/diagram.grammar +195 -0
- package/src/dsl/dsl.test.ts +286 -0
- package/src/dsl/gen/diagram.ts +22 -0
- package/src/dsl/index.ts +44 -0
- package/src/dsl/parse.ts +1054 -0
- package/src/dsl/print.ts +198 -0
- package/src/dsl/semantic.test.ts +216 -0
- package/src/dsl/vocabulary.ts +150 -0
- package/src/dx-svg.test.ts +44 -0
- package/src/dx-svg.ts +77 -0
- package/src/extension/complete.ts +105 -0
- package/src/extension/extension.test.ts +135 -0
- package/src/extension/highlight.ts +23 -0
- package/src/extension/index.ts +9 -0
- package/src/extension/language.ts +31 -0
- package/src/extension/lint.ts +60 -0
- package/src/extension/syntax.ts +62 -0
- package/src/index.ts +14 -0
- package/src/mermaid-engine.test.ts +6 -6
- package/src/mermaid-engine.ts +569 -119
- package/src/mermaid.test.ts +17 -1
- package/src/mermaid.ts +30 -18
- package/src/nudge.test.ts +186 -0
- package/src/nudge.ts +387 -0
- package/src/objective.test.ts +2 -0
- package/src/objective.ts +17 -1
- package/src/ortho-router.ts +240 -80
- package/src/ports.test.ts +157 -0
- package/src/ports.ts +186 -0
- package/src/relations.test.ts +98 -0
- package/src/rules.ts +117 -0
- package/src/scene.ts +66 -5
- package/src/score.test.ts +64 -0
- package/src/score.ts +130 -0
- package/src/semantic-engine.test.ts +635 -0
- package/src/semantic-engine.ts +891 -0
- package/src/semantic-place.ts +1508 -0
- package/src/semantic-route.ts +1136 -0
- package/src/semantic.ts +278 -0
- package/src/typings.d.ts +8 -0
- package/src/uml-grid.ts +6 -1
- package/src/uml.test.ts +5 -1
- package/src/uml.ts +24 -11
- package/src/view.test.ts +139 -0
- package/src/view.ts +376 -0
package/src/diagnostics.test.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import { describe, test } from 'vitest';
|
|
6
6
|
|
|
7
|
-
import { analyze, errors } from './diagnostics.ts';
|
|
7
|
+
import { analyze, arrowPoints, bindTargets, errors, layoutLabel, routes } from './diagnostics.ts';
|
|
8
8
|
import type * as Scene from './scene.ts';
|
|
9
9
|
import { CLASS_DIAGRAM } from './testing.ts';
|
|
10
10
|
import * as UmlEngine from './uml-engine.ts';
|
|
@@ -95,6 +95,40 @@ describe('diagnostics', () => {
|
|
|
95
95
|
expect(fanIn.metrics.crossings).toBe(0);
|
|
96
96
|
});
|
|
97
97
|
|
|
98
|
+
test('detects labels overlapping each other or a box, not a frame', ({ expect }) => {
|
|
99
|
+
const label = (id: string, x: number, y: number, text: string): Scene.Element => ({ kind: 'text', id, x, y, text });
|
|
100
|
+
const report = analyze([
|
|
101
|
+
box('a', 0, 0),
|
|
102
|
+
box('frame', -32, -32, 400, 400),
|
|
103
|
+
{ id: 'edges', elements: [label('one', 100, 100, 'spawn child'), label('two', 110, 110, 'per process')] },
|
|
104
|
+
{ id: 'more', elements: [label('three', 10, 10, 'on a box'), label('clear', 300, 300, 'x')] },
|
|
105
|
+
]);
|
|
106
|
+
expect(report.metrics.textOverlaps).toBe(2);
|
|
107
|
+
expect(report.diagnostics.filter(({ code }) => code === 'text-overlap').map(({ refs }) => refs)).toEqual([
|
|
108
|
+
['edges/one', 'edges/two'],
|
|
109
|
+
['more/three', 'a/frame'],
|
|
110
|
+
]);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test('detects connectors running along the same line, not ones that only cross or share a port', ({ expect }) => {
|
|
114
|
+
const report = analyze([
|
|
115
|
+
{
|
|
116
|
+
id: 'edges',
|
|
117
|
+
elements: [
|
|
118
|
+
arrow('trunk', { x: 0, y: 0 }, { x: 200, y: 0 }),
|
|
119
|
+
arrow('shares', { x: 100, y: 0 }, { x: 300, y: 0 }),
|
|
120
|
+
arrow('crosses', { x: 50, y: -50 }, { x: 50, y: 50 }),
|
|
121
|
+
arrow('port', { x: 0, y: 0 }, { x: 0, y: 100 }),
|
|
122
|
+
],
|
|
123
|
+
},
|
|
124
|
+
]);
|
|
125
|
+
expect(report.metrics.edgeOverlaps).toBe(1);
|
|
126
|
+
expect(report.diagnostics.find(({ code }) => code === 'edge-overlap')?.refs).toEqual([
|
|
127
|
+
'edges/trunk',
|
|
128
|
+
'edges/shares',
|
|
129
|
+
]);
|
|
130
|
+
});
|
|
131
|
+
|
|
98
132
|
test('rejoins a routed path with its arrow head and counts its bends', ({ expect }) => {
|
|
99
133
|
const report = analyze([
|
|
100
134
|
{
|
|
@@ -143,6 +177,178 @@ describe('diagnostics', () => {
|
|
|
143
177
|
});
|
|
144
178
|
});
|
|
145
179
|
|
|
180
|
+
describe('connector geometry', () => {
|
|
181
|
+
const line = (id: string, points: Scene.Point[]): Scene.Element => ({ kind: 'line', id, points });
|
|
182
|
+
|
|
183
|
+
test('a bound arrow is measured along the clipped segment the renderer draws', ({ expect }) => {
|
|
184
|
+
const bound: Scene.WorldObject = {
|
|
185
|
+
id: 'edges',
|
|
186
|
+
elements: [{ kind: 'arrow', id: 'a-b', from: 'a/frame', to: 'b/frame' }],
|
|
187
|
+
};
|
|
188
|
+
const report = analyze([box('a', 0, 0), box('blocker', 0, 128), box('b', 0, 320), bound]);
|
|
189
|
+
expect(report.metrics.connectors).toBe(1);
|
|
190
|
+
expect(report.metrics.length).toBe(256);
|
|
191
|
+
expect(report.diagnostics.map(({ code }) => code)).toEqual(['route-through-node']);
|
|
192
|
+
|
|
193
|
+
const objects = [box('a', 0, 0), box('b', 200, 200), bound];
|
|
194
|
+
expect(
|
|
195
|
+
arrowPoints(bound, { kind: 'arrow', id: 'a-b', from: 'a/frame', to: 'b/frame' }, bindTargets(objects)),
|
|
196
|
+
).toEqual([
|
|
197
|
+
{ x: 64, y: 64 },
|
|
198
|
+
{ x: 200, y: 200 },
|
|
199
|
+
]);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
test('a T where one connector ends on another at a port is not a crossing', ({ expect }) => {
|
|
203
|
+
const report = analyze([
|
|
204
|
+
box('port', 40, 50, 20, 20),
|
|
205
|
+
{
|
|
206
|
+
id: 'edges',
|
|
207
|
+
elements: [
|
|
208
|
+
arrow('along', { x: 0, y: 50 }, { x: 100, y: 50 }),
|
|
209
|
+
arrow('into', { x: 50, y: 100 }, { x: 50, y: 50 }),
|
|
210
|
+
],
|
|
211
|
+
},
|
|
212
|
+
]);
|
|
213
|
+
expect(report.metrics.crossings).toBe(0);
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
test('a bus trunk and its spokes are one connector, traced once per spoke', ({ expect }) => {
|
|
217
|
+
const objects: Scene.WorldObject[] = [
|
|
218
|
+
box('hub', 68, 0),
|
|
219
|
+
box('left', -12, 200),
|
|
220
|
+
box('middle', 28, 200),
|
|
221
|
+
box('other', 300, 100),
|
|
222
|
+
{
|
|
223
|
+
id: 'edges',
|
|
224
|
+
elements: [
|
|
225
|
+
line('hub-bus-0-trunk', [
|
|
226
|
+
{ x: 100, y: 64 },
|
|
227
|
+
{ x: 100, y: 120 },
|
|
228
|
+
]),
|
|
229
|
+
// Both spokes leave the junction along one run before they part.
|
|
230
|
+
line('hub-left-path', [
|
|
231
|
+
{ x: 100, y: 120 },
|
|
232
|
+
{ x: 60, y: 120 },
|
|
233
|
+
{ x: 20, y: 120 },
|
|
234
|
+
]),
|
|
235
|
+
arrow('hub-left', { x: 20, y: 120 }, { x: 20, y: 200 }),
|
|
236
|
+
line('hub-middle-path', [
|
|
237
|
+
{ x: 100, y: 120 },
|
|
238
|
+
{ x: 60, y: 120 },
|
|
239
|
+
]),
|
|
240
|
+
arrow('hub-middle', { x: 60, y: 120 }, { x: 60, y: 200 }),
|
|
241
|
+
// One unrelated connector crosses the trunk and both spokes' shared run.
|
|
242
|
+
line('stray-path', [
|
|
243
|
+
{ x: 332, y: 100 },
|
|
244
|
+
{ x: 332, y: 90 },
|
|
245
|
+
{ x: 80, y: 90 },
|
|
246
|
+
]),
|
|
247
|
+
arrow('stray', { x: 80, y: 90 }, { x: 80, y: 150 }),
|
|
248
|
+
],
|
|
249
|
+
},
|
|
250
|
+
];
|
|
251
|
+
const report = analyze(objects, { maxBends: 1 });
|
|
252
|
+
expect(report.metrics).toMatchObject({ connectors: 3, crossings: 1, edgeOverlaps: 0 });
|
|
253
|
+
// A spoke is traced from the hub; the turn at the junction is the bus's shape, its own corner a bend.
|
|
254
|
+
expect(report.diagnostics.filter(({ code }) => code === 'excessive-bends').map(({ refs }) => refs)).toEqual([
|
|
255
|
+
['edges/stray'],
|
|
256
|
+
]);
|
|
257
|
+
expect(routes(objects).find(({ ref }) => ref === 'edges/hub-left')).toMatchObject({ bends: 1 });
|
|
258
|
+
expect(routes(objects).find(({ ref }) => ref === 'edges/hub-left')?.points).toEqual([
|
|
259
|
+
{ x: 100, y: 64 },
|
|
260
|
+
{ x: 100, y: 120 },
|
|
261
|
+
{ x: 20, y: 120 },
|
|
262
|
+
{ x: 20, y: 200 },
|
|
263
|
+
]);
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
test('stubs meeting an inheritance bus mid-line do not cross it', ({ expect }) => {
|
|
267
|
+
const report = analyze([
|
|
268
|
+
box('base', 68, 0),
|
|
269
|
+
box('first', 0, 200),
|
|
270
|
+
box('second', 68, 200),
|
|
271
|
+
box('third', 136, 200),
|
|
272
|
+
{
|
|
273
|
+
id: 'edges',
|
|
274
|
+
elements: [
|
|
275
|
+
line('base-bus', [
|
|
276
|
+
{ x: 32, y: 132 },
|
|
277
|
+
{ x: 168, y: 132 },
|
|
278
|
+
]),
|
|
279
|
+
...[32, 100, 168].map((x, index) =>
|
|
280
|
+
line(`sub-${index}-base-stub-${index}`, [
|
|
281
|
+
{ x, y: 200 },
|
|
282
|
+
{ x, y: 132 },
|
|
283
|
+
]),
|
|
284
|
+
),
|
|
285
|
+
{
|
|
286
|
+
kind: 'arrow',
|
|
287
|
+
id: 'base-inherit',
|
|
288
|
+
start: { x: 100, y: 132 },
|
|
289
|
+
end: { x: 100, y: 64 },
|
|
290
|
+
relation: 'inheritance',
|
|
291
|
+
},
|
|
292
|
+
],
|
|
293
|
+
},
|
|
294
|
+
]);
|
|
295
|
+
expect(report.metrics).toMatchObject({ connectors: 3, crossings: 0, edgeOverlaps: 0 });
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
test('a self-loop is drawn off its box and measures without NaN or a crossing', ({ expect }) => {
|
|
299
|
+
const objects: Scene.WorldObject[] = [
|
|
300
|
+
box('a', 0, 0),
|
|
301
|
+
box('b', 0, 200),
|
|
302
|
+
{
|
|
303
|
+
id: 'edges',
|
|
304
|
+
elements: [
|
|
305
|
+
{ kind: 'arrow', id: 'a-a', from: 'a/frame', to: 'a/frame' },
|
|
306
|
+
{ kind: 'arrow', id: 'a-b', from: 'a/frame', to: 'b/frame' },
|
|
307
|
+
],
|
|
308
|
+
},
|
|
309
|
+
];
|
|
310
|
+
const { metrics, diagnostics } = analyze(objects);
|
|
311
|
+
expect(Object.values(metrics).every((value) => !Number.isNaN(value))).toBe(true);
|
|
312
|
+
expect(metrics).toMatchObject({ connectors: 2, crossings: 0, routesThroughNodes: 0, bends: 3 });
|
|
313
|
+
expect(diagnostics).toEqual([]);
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
test('a connector with an empty path is skipped rather than bused', ({ expect }) => {
|
|
317
|
+
const objects: Scene.WorldObject[] = [
|
|
318
|
+
box('a', 0, 0),
|
|
319
|
+
box('b', 0, 200),
|
|
320
|
+
{
|
|
321
|
+
id: 'edges',
|
|
322
|
+
elements: [{ kind: 'line', id: 'stray-path', points: [] }, arrow('a-b', { x: 32, y: 64 }, { x: 32, y: 200 })],
|
|
323
|
+
},
|
|
324
|
+
];
|
|
325
|
+
expect(analyze(objects).metrics).toMatchObject({ connectors: 1, crossings: 0 });
|
|
326
|
+
expect(routes(objects).map(({ ref }) => ref)).toEqual(['edges/a-b']);
|
|
327
|
+
});
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
describe('box labels', () => {
|
|
331
|
+
test('a long label wraps at camel case, and one too long to fit is ellipsized at a smaller size', ({ expect }) => {
|
|
332
|
+
const wrapped = layoutLabel('RemoteProcessHandleWithLongName', 'm', { w: 192, h: 160 });
|
|
333
|
+
expect(wrapped).toMatchObject({ lines: ['RemoteProcess', 'HandleWithLong', 'Name'], size: 18, overflow: false });
|
|
334
|
+
|
|
335
|
+
const squeezed = layoutLabel('RemoteProcessHandleWithLongName', 'm', { w: 192, h: 64 });
|
|
336
|
+
expect(squeezed.size).toBeLessThan(18);
|
|
337
|
+
expect(squeezed.overflow).toBe(false);
|
|
338
|
+
expect(squeezed.lines.join('')).toBe('RemoteProcessHandleWithLongName');
|
|
339
|
+
|
|
340
|
+
const truncated = layoutLabel('RemoteProcessHandleWithLongName and then some more words', 'm', { w: 192, h: 40 });
|
|
341
|
+
expect(truncated.overflow).toBe(true);
|
|
342
|
+
expect(truncated.lines).toHaveLength(1);
|
|
343
|
+
expect(truncated.lines[0].endsWith('…')).toBe(true);
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
test('label-overflow reports what the renderer cannot show, not text it wraps', ({ expect }) => {
|
|
347
|
+
const report = analyze([box('a', 0, 0, 192, 160, 'RemoteProcessHandleWithLongName')]);
|
|
348
|
+
expect(report.metrics.labelOverflows).toBe(0);
|
|
349
|
+
});
|
|
350
|
+
});
|
|
351
|
+
|
|
146
352
|
//
|
|
147
353
|
// Tier 1: every placement strategy must produce a scene free of hard defects for the shared
|
|
148
354
|
// fixture. Soft metrics are recorded in the snapshot so a layout change reads as a diff.
|