@abreen/tada 1.5.0 → 1.5.2
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/build/generate-content-assets.ts +2 -2
- package/build/types.d.ts +1 -0
- package/build/utils/render.ts +5 -0
- package/build/utils/trace.ts +70 -22
- package/package.json +4 -3
- package/src/trace/index.ts +9 -1
- package/bin/validators.test.ts +0 -205
- package/build/build-manifest.test.ts +0 -369
- package/build/code.test.ts +0 -272
- package/build/colors.test.ts +0 -66
- package/build/globals.test.ts +0 -100
- package/build/json-schema.test.ts +0 -57
- package/build/log.test.ts +0 -117
- package/build/markdown-plugins.test.ts +0 -456
- package/build/pagefind.test.ts +0 -192
- package/build/pdf-text.test.ts +0 -52
- package/build/reachability.test.ts +0 -80
- package/build/serve.test.ts +0 -59
- package/build/toc-plugin.test.ts +0 -120
- package/build/utils/content-files.test.ts +0 -56
- package/build/utils/derive-theme.test.ts +0 -111
- package/build/utils/file-types.test.ts +0 -79
- package/build/utils/front-matter.test.ts +0 -77
- package/build/utils/include.test.ts +0 -117
- package/build/utils/paths.test.ts +0 -91
- package/build/utils/trace-layout.test.ts +0 -570
- package/build/utils/trace-svg.test.ts +0 -425
- package/build/utils/trace.test.ts +0 -169
- package/build/validate-config-links.test.ts +0 -239
- package/build/validate-internal-links-plugin.test.ts +0 -134
- package/build/watch-reachability-state.test.ts +0 -198
- package/src/anchor/index.test.ts +0 -64
- package/src/code/index.test.ts +0 -71
- package/src/header/index.test.ts +0 -89
- package/src/navigate/eligible.test.ts +0 -101
- package/src/navigate/index.test.ts +0 -82
- package/src/navigate/lifecycle.test.ts +0 -26
- package/src/print/index.test.ts +0 -71
- package/src/question/index.test.ts +0 -82
- package/src/search/index.test.ts +0 -203
- package/src/timezone/index.test.ts +0 -400
- package/src/toc/index.test.ts +0 -237
- package/src/top/index.test.ts +0 -46
- package/src/trace/index.test.ts +0 -528
- package/src/util.test.ts +0 -135
|
@@ -1,570 +0,0 @@
|
|
|
1
|
-
import { describe, test, expect } from 'bun:test';
|
|
2
|
-
import {
|
|
3
|
-
scanSteps,
|
|
4
|
-
buildSupergraph,
|
|
5
|
-
getObjectSize,
|
|
6
|
-
computeLayout,
|
|
7
|
-
} from './trace-layout';
|
|
8
|
-
import type { TraceStep, TraceHeapObject, TraceStackFrame } from '../types';
|
|
9
|
-
|
|
10
|
-
function makeStep(
|
|
11
|
-
line: number,
|
|
12
|
-
stack: TraceStackFrame[],
|
|
13
|
-
heap: Record<string, TraceHeapObject>,
|
|
14
|
-
): TraceStep {
|
|
15
|
-
return { line, file: 'Test.java', stack, heap, stdout: '' };
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
const mainStack: TraceStackFrame[] = [
|
|
19
|
-
{ method: 'main', class: 'Test', locals: {} },
|
|
20
|
-
];
|
|
21
|
-
|
|
22
|
-
describe('scanSteps', () => {
|
|
23
|
-
test('collects object lifetimes from multi-step traces', () => {
|
|
24
|
-
const steps: TraceStep[] = [
|
|
25
|
-
makeStep(1, mainStack, {
|
|
26
|
-
'1': { type: 'Node', fields: { val: { type: 'int', value: 10 } } },
|
|
27
|
-
}),
|
|
28
|
-
makeStep(2, mainStack, {
|
|
29
|
-
'1': { type: 'Node', fields: { val: { type: 'int', value: 10 } } },
|
|
30
|
-
'2': { type: 'Node', fields: { val: { type: 'int', value: 20 } } },
|
|
31
|
-
}),
|
|
32
|
-
makeStep(3, mainStack, {
|
|
33
|
-
'2': { type: 'Node', fields: { val: { type: 'int', value: 20 } } },
|
|
34
|
-
}),
|
|
35
|
-
];
|
|
36
|
-
|
|
37
|
-
const objects = scanSteps(steps);
|
|
38
|
-
expect(objects.size).toBe(2);
|
|
39
|
-
|
|
40
|
-
const obj1 = objects.get('1')!;
|
|
41
|
-
expect(obj1.firstStep).toBe(0);
|
|
42
|
-
expect(obj1.lastStep).toBe(1);
|
|
43
|
-
expect(obj1.type).toBe('Node');
|
|
44
|
-
|
|
45
|
-
const obj2 = objects.get('2')!;
|
|
46
|
-
expect(obj2.firstStep).toBe(1);
|
|
47
|
-
expect(obj2.lastStep).toBe(2);
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
test('collects references from field objects', () => {
|
|
51
|
-
const steps: TraceStep[] = [
|
|
52
|
-
makeStep(1, mainStack, {
|
|
53
|
-
'1': {
|
|
54
|
-
type: 'Node',
|
|
55
|
-
fields: { left: { type: 'ref', id: '2' }, right: { type: 'null' } },
|
|
56
|
-
},
|
|
57
|
-
'2': {
|
|
58
|
-
type: 'Node',
|
|
59
|
-
fields: { left: { type: 'null' }, right: { type: 'null' } },
|
|
60
|
-
},
|
|
61
|
-
}),
|
|
62
|
-
makeStep(2, mainStack, {
|
|
63
|
-
'1': {
|
|
64
|
-
type: 'Node',
|
|
65
|
-
fields: {
|
|
66
|
-
left: { type: 'ref', id: '2' },
|
|
67
|
-
right: { type: 'ref', id: '3' },
|
|
68
|
-
},
|
|
69
|
-
},
|
|
70
|
-
'2': {
|
|
71
|
-
type: 'Node',
|
|
72
|
-
fields: { left: { type: 'null' }, right: { type: 'null' } },
|
|
73
|
-
},
|
|
74
|
-
'3': {
|
|
75
|
-
type: 'Node',
|
|
76
|
-
fields: { left: { type: 'null' }, right: { type: 'null' } },
|
|
77
|
-
},
|
|
78
|
-
}),
|
|
79
|
-
];
|
|
80
|
-
|
|
81
|
-
const objects = scanSteps(steps);
|
|
82
|
-
const obj1 = objects.get('1')!;
|
|
83
|
-
expect(obj1.references.has('2')).toBe(true);
|
|
84
|
-
expect(obj1.references.has('3')).toBe(true);
|
|
85
|
-
expect(obj1.fieldNames.has('left')).toBe(true);
|
|
86
|
-
expect(obj1.fieldNames.has('right')).toBe(true);
|
|
87
|
-
expect(obj1.maxFields).toBe(2);
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
test('detects arrays and tracks max elements', () => {
|
|
91
|
-
const steps: TraceStep[] = [
|
|
92
|
-
makeStep(1, mainStack, {
|
|
93
|
-
'1': {
|
|
94
|
-
type: 'int[]',
|
|
95
|
-
elements: [
|
|
96
|
-
{ type: 'int', value: 1 },
|
|
97
|
-
{ type: 'int', value: 2 },
|
|
98
|
-
],
|
|
99
|
-
},
|
|
100
|
-
}),
|
|
101
|
-
makeStep(2, mainStack, {
|
|
102
|
-
'1': {
|
|
103
|
-
type: 'int[]',
|
|
104
|
-
elements: [
|
|
105
|
-
{ type: 'int', value: 1 },
|
|
106
|
-
{ type: 'int', value: 2 },
|
|
107
|
-
{ type: 'int', value: 3 },
|
|
108
|
-
],
|
|
109
|
-
},
|
|
110
|
-
}),
|
|
111
|
-
];
|
|
112
|
-
|
|
113
|
-
const objects = scanSteps(steps);
|
|
114
|
-
const obj = objects.get('1')!;
|
|
115
|
-
expect(obj.isArray).toBe(true);
|
|
116
|
-
expect(obj.maxElements).toBe(3);
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
test('detects string objects', () => {
|
|
120
|
-
const steps: TraceStep[] = [
|
|
121
|
-
makeStep(1, mainStack, { '1': { type: 'String', value: 'hello' } }),
|
|
122
|
-
];
|
|
123
|
-
|
|
124
|
-
const objects = scanSteps(steps);
|
|
125
|
-
const obj = objects.get('1')!;
|
|
126
|
-
expect(obj.isString).toBe(true);
|
|
127
|
-
expect(obj.value).toBe('hello');
|
|
128
|
-
});
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
describe('buildSupergraph', () => {
|
|
132
|
-
test('finds structural children from field refs', () => {
|
|
133
|
-
const steps: TraceStep[] = [
|
|
134
|
-
makeStep(1, mainStack, {
|
|
135
|
-
'1': {
|
|
136
|
-
type: 'Node',
|
|
137
|
-
fields: {
|
|
138
|
-
left: { type: 'ref', id: '2' },
|
|
139
|
-
right: { type: 'ref', id: '3' },
|
|
140
|
-
},
|
|
141
|
-
},
|
|
142
|
-
'2': {
|
|
143
|
-
type: 'Node',
|
|
144
|
-
fields: { left: { type: 'null' }, right: { type: 'null' } },
|
|
145
|
-
},
|
|
146
|
-
'3': {
|
|
147
|
-
type: 'Node',
|
|
148
|
-
fields: { left: { type: 'null' }, right: { type: 'null' } },
|
|
149
|
-
},
|
|
150
|
-
}),
|
|
151
|
-
];
|
|
152
|
-
|
|
153
|
-
const objects = scanSteps(steps);
|
|
154
|
-
const { graph, referenced } = buildSupergraph(objects);
|
|
155
|
-
|
|
156
|
-
expect(graph.get('1')).toEqual(expect.arrayContaining(['2', '3']));
|
|
157
|
-
expect(graph.get('1')!.length).toBe(2);
|
|
158
|
-
expect(referenced.has('2')).toBe(true);
|
|
159
|
-
expect(referenced.has('3')).toBe(true);
|
|
160
|
-
expect(referenced.has('1')).toBe(false);
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
test('returns fieldToChild mapping', () => {
|
|
164
|
-
const steps: TraceStep[] = [
|
|
165
|
-
makeStep(1, mainStack, {
|
|
166
|
-
'1': {
|
|
167
|
-
type: 'Node',
|
|
168
|
-
fields: {
|
|
169
|
-
left: { type: 'ref', id: '2' },
|
|
170
|
-
right: { type: 'ref', id: '3' },
|
|
171
|
-
},
|
|
172
|
-
},
|
|
173
|
-
'2': {
|
|
174
|
-
type: 'Node',
|
|
175
|
-
fields: { left: { type: 'null' }, right: { type: 'null' } },
|
|
176
|
-
},
|
|
177
|
-
'3': {
|
|
178
|
-
type: 'Node',
|
|
179
|
-
fields: { left: { type: 'null' }, right: { type: 'null' } },
|
|
180
|
-
},
|
|
181
|
-
}),
|
|
182
|
-
];
|
|
183
|
-
|
|
184
|
-
const objects = scanSteps(steps);
|
|
185
|
-
const { fieldToChild } = buildSupergraph(objects);
|
|
186
|
-
|
|
187
|
-
const fMap = fieldToChild.get('1')!;
|
|
188
|
-
expect(fMap).toBeDefined();
|
|
189
|
-
expect(fMap.get('left')).toBe('2');
|
|
190
|
-
expect(fMap.get('right')).toBe('3');
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
test('respects ignoreFields', () => {
|
|
194
|
-
const steps: TraceStep[] = [
|
|
195
|
-
makeStep(1, mainStack, {
|
|
196
|
-
'1': {
|
|
197
|
-
type: 'Node',
|
|
198
|
-
fields: {
|
|
199
|
-
left: { type: 'ref', id: '2' },
|
|
200
|
-
parent: { type: 'ref', id: '3' },
|
|
201
|
-
},
|
|
202
|
-
},
|
|
203
|
-
'2': { type: 'Node', fields: {} },
|
|
204
|
-
'3': { type: 'Node', fields: {} },
|
|
205
|
-
}),
|
|
206
|
-
];
|
|
207
|
-
|
|
208
|
-
const objects = scanSteps(steps);
|
|
209
|
-
const { graph, referenced } = buildSupergraph(objects, {
|
|
210
|
-
Node: ['parent'],
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
expect(graph.get('1')).toEqual(['2']);
|
|
214
|
-
expect(referenced.has('2')).toBe(true);
|
|
215
|
-
expect(referenced.has('3')).toBe(false);
|
|
216
|
-
});
|
|
217
|
-
|
|
218
|
-
test('follows array element refs', () => {
|
|
219
|
-
const steps: TraceStep[] = [
|
|
220
|
-
makeStep(1, mainStack, {
|
|
221
|
-
'1': {
|
|
222
|
-
type: 'Object[]',
|
|
223
|
-
elements: [
|
|
224
|
-
{ type: 'ref', id: '2' },
|
|
225
|
-
{ type: 'ref', id: '3' },
|
|
226
|
-
],
|
|
227
|
-
},
|
|
228
|
-
'2': { type: 'String', value: 'a' },
|
|
229
|
-
'3': { type: 'String', value: 'b' },
|
|
230
|
-
}),
|
|
231
|
-
];
|
|
232
|
-
|
|
233
|
-
const objects = scanSteps(steps);
|
|
234
|
-
const { graph, referenced } = buildSupergraph(objects);
|
|
235
|
-
|
|
236
|
-
expect(graph.get('1')).toEqual(expect.arrayContaining(['2', '3']));
|
|
237
|
-
expect(referenced.has('2')).toBe(true);
|
|
238
|
-
expect(referenced.has('3')).toBe(true);
|
|
239
|
-
});
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
describe('getObjectSize', () => {
|
|
243
|
-
test('sizes a string object', () => {
|
|
244
|
-
const steps: TraceStep[] = [
|
|
245
|
-
makeStep(1, mainStack, { '1': { type: 'String', value: 'hello' } }),
|
|
246
|
-
];
|
|
247
|
-
const objects = scanSteps(steps);
|
|
248
|
-
const size = getObjectSize(objects.get('1')!);
|
|
249
|
-
expect(size.width).toBeGreaterThan(0);
|
|
250
|
-
expect(size.height).toBeGreaterThan(0);
|
|
251
|
-
expect(size.cellWidth).toBeUndefined();
|
|
252
|
-
});
|
|
253
|
-
|
|
254
|
-
test('sizes an array with cellWidth', () => {
|
|
255
|
-
const steps: TraceStep[] = [
|
|
256
|
-
makeStep(1, mainStack, {
|
|
257
|
-
'1': {
|
|
258
|
-
type: 'int[]',
|
|
259
|
-
elements: [
|
|
260
|
-
{ type: 'int', value: 1 },
|
|
261
|
-
{ type: 'int', value: 2 },
|
|
262
|
-
{ type: 'int', value: 3 },
|
|
263
|
-
],
|
|
264
|
-
},
|
|
265
|
-
}),
|
|
266
|
-
];
|
|
267
|
-
const objects = scanSteps(steps);
|
|
268
|
-
const size = getObjectSize(objects.get('1')!);
|
|
269
|
-
expect(size.cellWidth).toBeDefined();
|
|
270
|
-
expect(size.cellWidth).toBeGreaterThan(0);
|
|
271
|
-
expect(size.valueX).toBeUndefined();
|
|
272
|
-
});
|
|
273
|
-
|
|
274
|
-
test('sizes a field object without child fields: no extra header row', () => {
|
|
275
|
-
const steps: TraceStep[] = [
|
|
276
|
-
makeStep(1, mainStack, {
|
|
277
|
-
'1': {
|
|
278
|
-
type: 'Person',
|
|
279
|
-
fields: {
|
|
280
|
-
name: { type: 'String', value: 'Alice' },
|
|
281
|
-
age: { type: 'int', value: 30 },
|
|
282
|
-
},
|
|
283
|
-
},
|
|
284
|
-
}),
|
|
285
|
-
];
|
|
286
|
-
const objects = scanSteps(steps);
|
|
287
|
-
const size = getObjectSize(objects.get('1')!);
|
|
288
|
-
expect(size.valueX).toBeDefined();
|
|
289
|
-
expect(size.valueX).toBeGreaterThan(0);
|
|
290
|
-
// 2 data fields, no child slots = 2 rows
|
|
291
|
-
expect(size.height).toBe(15 * 2 * 2);
|
|
292
|
-
});
|
|
293
|
-
|
|
294
|
-
test('sizes a tree node with child fields: data rows + 1 child-slot row', () => {
|
|
295
|
-
const steps: TraceStep[] = [
|
|
296
|
-
makeStep(1, mainStack, {
|
|
297
|
-
'1': {
|
|
298
|
-
type: 'Node',
|
|
299
|
-
fields: {
|
|
300
|
-
val: { type: 'int', value: 42 },
|
|
301
|
-
left: { type: 'null' },
|
|
302
|
-
right: { type: 'null' },
|
|
303
|
-
},
|
|
304
|
-
},
|
|
305
|
-
}),
|
|
306
|
-
];
|
|
307
|
-
const objects = scanSteps(steps);
|
|
308
|
-
// Pass left and right as child fields
|
|
309
|
-
const childFields = new Set(['left', 'right']);
|
|
310
|
-
const size = getObjectSize(objects.get('1')!, 15, childFields);
|
|
311
|
-
expect(size.valueX).toBeDefined();
|
|
312
|
-
expect(size.valueX).toBeGreaterThan(0);
|
|
313
|
-
// 1 data field (val) + 2 child-slot rows (label + box) = 3 rows
|
|
314
|
-
expect(size.height).toBe(15 * 2 * 3);
|
|
315
|
-
});
|
|
316
|
-
});
|
|
317
|
-
|
|
318
|
-
describe('computeLayout', () => {
|
|
319
|
-
test('returns empty layout for empty steps', () => {
|
|
320
|
-
const layout = computeLayout([]);
|
|
321
|
-
expect(Object.keys(layout.objects)).toHaveLength(0);
|
|
322
|
-
});
|
|
323
|
-
|
|
324
|
-
test('returns empty layout for steps with no heap objects', () => {
|
|
325
|
-
const layout = computeLayout([makeStep(1, mainStack, {})]);
|
|
326
|
-
expect(Object.keys(layout.objects)).toHaveLength(0);
|
|
327
|
-
});
|
|
328
|
-
|
|
329
|
-
test('BST: root above children, left child left of right child, same y', () => {
|
|
330
|
-
// Build a BST: root(1) -> left(2), right(3)
|
|
331
|
-
const steps: TraceStep[] = [
|
|
332
|
-
makeStep(1, mainStack, {
|
|
333
|
-
'1': {
|
|
334
|
-
type: 'Node',
|
|
335
|
-
fields: {
|
|
336
|
-
val: { type: 'int', value: 10 },
|
|
337
|
-
left: { type: 'ref', id: '2' },
|
|
338
|
-
right: { type: 'ref', id: '3' },
|
|
339
|
-
},
|
|
340
|
-
},
|
|
341
|
-
'2': {
|
|
342
|
-
type: 'Node',
|
|
343
|
-
fields: {
|
|
344
|
-
val: { type: 'int', value: 5 },
|
|
345
|
-
left: { type: 'null' },
|
|
346
|
-
right: { type: 'null' },
|
|
347
|
-
},
|
|
348
|
-
},
|
|
349
|
-
'3': {
|
|
350
|
-
type: 'Node',
|
|
351
|
-
fields: {
|
|
352
|
-
val: { type: 'int', value: 15 },
|
|
353
|
-
left: { type: 'null' },
|
|
354
|
-
right: { type: 'null' },
|
|
355
|
-
},
|
|
356
|
-
},
|
|
357
|
-
}),
|
|
358
|
-
];
|
|
359
|
-
|
|
360
|
-
const layout = computeLayout(steps);
|
|
361
|
-
const pos = layout.objects;
|
|
362
|
-
|
|
363
|
-
expect(pos['1']).toBeDefined();
|
|
364
|
-
expect(pos['2']).toBeDefined();
|
|
365
|
-
expect(pos['3']).toBeDefined();
|
|
366
|
-
|
|
367
|
-
// Root should be above children
|
|
368
|
-
expect(pos['1'].y).toBeLessThan(pos['2'].y);
|
|
369
|
-
expect(pos['1'].y).toBeLessThan(pos['3'].y);
|
|
370
|
-
|
|
371
|
-
// Children at the same depth
|
|
372
|
-
expect(pos['2'].y).toBe(pos['3'].y);
|
|
373
|
-
|
|
374
|
-
// Left child should be to the left of right child
|
|
375
|
-
expect(pos['2'].x).toBeLessThan(pos['3'].x);
|
|
376
|
-
|
|
377
|
-
// All coordinates should be non-negative
|
|
378
|
-
expect(pos['1'].x).toBeGreaterThanOrEqual(0);
|
|
379
|
-
expect(pos['1'].y).toBeGreaterThanOrEqual(0);
|
|
380
|
-
expect(pos['2'].x).toBeGreaterThanOrEqual(0);
|
|
381
|
-
expect(pos['2'].y).toBeGreaterThanOrEqual(0);
|
|
382
|
-
expect(pos['3'].x).toBeGreaterThanOrEqual(0);
|
|
383
|
-
expect(pos['3'].y).toBeGreaterThanOrEqual(0);
|
|
384
|
-
|
|
385
|
-
// Root should have childFields set
|
|
386
|
-
expect(pos['1'].childFields).toBeDefined();
|
|
387
|
-
expect(pos['1'].childFields).toContain('left');
|
|
388
|
-
expect(pos['1'].childFields).toContain('right');
|
|
389
|
-
});
|
|
390
|
-
|
|
391
|
-
test('linked list: each node to the right of the previous', () => {
|
|
392
|
-
// A -> B -> C (chain / linked list)
|
|
393
|
-
const steps: TraceStep[] = [
|
|
394
|
-
makeStep(1, mainStack, {
|
|
395
|
-
'1': {
|
|
396
|
-
type: 'Node',
|
|
397
|
-
fields: {
|
|
398
|
-
val: { type: 'int', value: 1 },
|
|
399
|
-
next: { type: 'ref', id: '2' },
|
|
400
|
-
},
|
|
401
|
-
},
|
|
402
|
-
'2': {
|
|
403
|
-
type: 'Node',
|
|
404
|
-
fields: {
|
|
405
|
-
val: { type: 'int', value: 2 },
|
|
406
|
-
next: { type: 'ref', id: '3' },
|
|
407
|
-
},
|
|
408
|
-
},
|
|
409
|
-
'3': {
|
|
410
|
-
type: 'Node',
|
|
411
|
-
fields: { val: { type: 'int', value: 3 }, next: { type: 'null' } },
|
|
412
|
-
},
|
|
413
|
-
}),
|
|
414
|
-
];
|
|
415
|
-
|
|
416
|
-
const layout = computeLayout(steps);
|
|
417
|
-
const pos = layout.objects;
|
|
418
|
-
|
|
419
|
-
// Chain type (1 structural child field): laid out left to right
|
|
420
|
-
expect(pos['1'].y).toBe(pos['2'].y);
|
|
421
|
-
expect(pos['2'].y).toBe(pos['3'].y);
|
|
422
|
-
expect(pos['1'].x).toBeLessThan(pos['2'].x);
|
|
423
|
-
expect(pos['2'].x).toBeLessThan(pos['3'].x);
|
|
424
|
-
});
|
|
425
|
-
|
|
426
|
-
test('layout stability: supergraph union produces same positions regardless of step order', () => {
|
|
427
|
-
// Object '3' appears only in step 2, but the supergraph should still
|
|
428
|
-
// place it consistently because we scan ALL steps.
|
|
429
|
-
const stepsEarly: TraceStep[] = [
|
|
430
|
-
makeStep(1, mainStack, {
|
|
431
|
-
'1': {
|
|
432
|
-
type: 'Node',
|
|
433
|
-
fields: {
|
|
434
|
-
left: { type: 'ref', id: '2' },
|
|
435
|
-
right: { type: 'ref', id: '3' },
|
|
436
|
-
},
|
|
437
|
-
},
|
|
438
|
-
'2': {
|
|
439
|
-
type: 'Node',
|
|
440
|
-
fields: { left: { type: 'null' }, right: { type: 'null' } },
|
|
441
|
-
},
|
|
442
|
-
'3': {
|
|
443
|
-
type: 'Node',
|
|
444
|
-
fields: { left: { type: 'null' }, right: { type: 'null' } },
|
|
445
|
-
},
|
|
446
|
-
}),
|
|
447
|
-
];
|
|
448
|
-
|
|
449
|
-
// Same final state but objects appear across two steps
|
|
450
|
-
const stepsLate: TraceStep[] = [
|
|
451
|
-
makeStep(1, mainStack, {
|
|
452
|
-
'1': {
|
|
453
|
-
type: 'Node',
|
|
454
|
-
fields: { left: { type: 'ref', id: '2' }, right: { type: 'null' } },
|
|
455
|
-
},
|
|
456
|
-
'2': {
|
|
457
|
-
type: 'Node',
|
|
458
|
-
fields: { left: { type: 'null' }, right: { type: 'null' } },
|
|
459
|
-
},
|
|
460
|
-
}),
|
|
461
|
-
makeStep(2, mainStack, {
|
|
462
|
-
'1': {
|
|
463
|
-
type: 'Node',
|
|
464
|
-
fields: {
|
|
465
|
-
left: { type: 'ref', id: '2' },
|
|
466
|
-
right: { type: 'ref', id: '3' },
|
|
467
|
-
},
|
|
468
|
-
},
|
|
469
|
-
'2': {
|
|
470
|
-
type: 'Node',
|
|
471
|
-
fields: { left: { type: 'null' }, right: { type: 'null' } },
|
|
472
|
-
},
|
|
473
|
-
'3': {
|
|
474
|
-
type: 'Node',
|
|
475
|
-
fields: { left: { type: 'null' }, right: { type: 'null' } },
|
|
476
|
-
},
|
|
477
|
-
}),
|
|
478
|
-
];
|
|
479
|
-
|
|
480
|
-
const layoutEarly = computeLayout(stepsEarly);
|
|
481
|
-
const layoutLate = computeLayout(stepsLate);
|
|
482
|
-
|
|
483
|
-
// Both should produce the same positions because the supergraph
|
|
484
|
-
// (union of all references across all steps) is the same
|
|
485
|
-
for (const id of ['1', '2', '3']) {
|
|
486
|
-
expect(layoutLate.objects[id].x).toBe(layoutEarly.objects[id].x);
|
|
487
|
-
expect(layoutLate.objects[id].y).toBe(layoutEarly.objects[id].y);
|
|
488
|
-
expect(layoutLate.objects[id].width).toBe(layoutEarly.objects[id].width);
|
|
489
|
-
expect(layoutLate.objects[id].height).toBe(
|
|
490
|
-
layoutEarly.objects[id].height,
|
|
491
|
-
);
|
|
492
|
-
}
|
|
493
|
-
});
|
|
494
|
-
|
|
495
|
-
test('multiple disconnected roots are stacked vertically', () => {
|
|
496
|
-
const steps: TraceStep[] = [
|
|
497
|
-
makeStep(1, mainStack, {
|
|
498
|
-
'1': { type: 'Node', fields: { val: { type: 'int', value: 1 } } },
|
|
499
|
-
'2': { type: 'Node', fields: { val: { type: 'int', value: 2 } } },
|
|
500
|
-
}),
|
|
501
|
-
];
|
|
502
|
-
|
|
503
|
-
const layout = computeLayout(steps);
|
|
504
|
-
const pos = layout.objects;
|
|
505
|
-
|
|
506
|
-
// Both are roots (neither references the other)
|
|
507
|
-
// They should be stacked: second root below the first
|
|
508
|
-
expect(pos['1'].y).toBeLessThan(pos['2'].y);
|
|
509
|
-
});
|
|
510
|
-
|
|
511
|
-
test('passes through ignoreFields', () => {
|
|
512
|
-
const steps: TraceStep[] = [
|
|
513
|
-
makeStep(1, mainStack, {
|
|
514
|
-
'1': {
|
|
515
|
-
type: 'Node',
|
|
516
|
-
fields: {
|
|
517
|
-
left: { type: 'ref', id: '2' },
|
|
518
|
-
parent: { type: 'ref', id: '3' },
|
|
519
|
-
},
|
|
520
|
-
},
|
|
521
|
-
'2': { type: 'Node', fields: {} },
|
|
522
|
-
'3': { type: 'Node', fields: {} },
|
|
523
|
-
}),
|
|
524
|
-
];
|
|
525
|
-
|
|
526
|
-
const layout = computeLayout(steps, { Node: ['parent'] });
|
|
527
|
-
|
|
528
|
-
// With 'parent' ignored, only 'left' is structural (1 field = chain).
|
|
529
|
-
// '2' should be to the right of '1', '3' is a separate root below.
|
|
530
|
-
expect(layout.objects['1'].x).toBeLessThan(layout.objects['2'].x);
|
|
531
|
-
expect(layout.objects['1'].y).toBe(layout.objects['2'].y);
|
|
532
|
-
|
|
533
|
-
expect(layout.ignoreFields).toEqual({ Node: ['parent'] });
|
|
534
|
-
});
|
|
535
|
-
|
|
536
|
-
test('handles cycles gracefully', () => {
|
|
537
|
-
// Create a cycle: 1 -> 2 -> 1
|
|
538
|
-
const steps: TraceStep[] = [
|
|
539
|
-
makeStep(1, mainStack, {
|
|
540
|
-
'1': { type: 'Node', fields: { next: { type: 'ref', id: '2' } } },
|
|
541
|
-
'2': { type: 'Node', fields: { next: { type: 'ref', id: '1' } } },
|
|
542
|
-
}),
|
|
543
|
-
];
|
|
544
|
-
|
|
545
|
-
// Should not infinite-loop; both objects should get positions
|
|
546
|
-
const layout = computeLayout(steps);
|
|
547
|
-
expect(layout.objects['1']).toBeDefined();
|
|
548
|
-
expect(layout.objects['2']).toBeDefined();
|
|
549
|
-
});
|
|
550
|
-
|
|
551
|
-
test('non-tree references (different types) do not get childFields', () => {
|
|
552
|
-
// A Person referencing a String -- not a tree structure
|
|
553
|
-
const steps: TraceStep[] = [
|
|
554
|
-
makeStep(1, mainStack, {
|
|
555
|
-
'1': {
|
|
556
|
-
type: 'Person',
|
|
557
|
-
fields: {
|
|
558
|
-
name: { type: 'ref', id: '2' },
|
|
559
|
-
age: { type: 'int', value: 30 },
|
|
560
|
-
},
|
|
561
|
-
},
|
|
562
|
-
'2': { type: 'String', value: 'Alice' },
|
|
563
|
-
}),
|
|
564
|
-
];
|
|
565
|
-
|
|
566
|
-
const layout = computeLayout(steps);
|
|
567
|
-
// Person should NOT have childFields since String is a different type
|
|
568
|
-
expect(layout.objects['1'].childFields).toBeUndefined();
|
|
569
|
-
});
|
|
570
|
-
});
|