@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.
Files changed (45) hide show
  1. package/build/generate-content-assets.ts +2 -2
  2. package/build/types.d.ts +1 -0
  3. package/build/utils/render.ts +5 -0
  4. package/build/utils/trace.ts +70 -22
  5. package/package.json +4 -3
  6. package/src/trace/index.ts +9 -1
  7. package/bin/validators.test.ts +0 -205
  8. package/build/build-manifest.test.ts +0 -369
  9. package/build/code.test.ts +0 -272
  10. package/build/colors.test.ts +0 -66
  11. package/build/globals.test.ts +0 -100
  12. package/build/json-schema.test.ts +0 -57
  13. package/build/log.test.ts +0 -117
  14. package/build/markdown-plugins.test.ts +0 -456
  15. package/build/pagefind.test.ts +0 -192
  16. package/build/pdf-text.test.ts +0 -52
  17. package/build/reachability.test.ts +0 -80
  18. package/build/serve.test.ts +0 -59
  19. package/build/toc-plugin.test.ts +0 -120
  20. package/build/utils/content-files.test.ts +0 -56
  21. package/build/utils/derive-theme.test.ts +0 -111
  22. package/build/utils/file-types.test.ts +0 -79
  23. package/build/utils/front-matter.test.ts +0 -77
  24. package/build/utils/include.test.ts +0 -117
  25. package/build/utils/paths.test.ts +0 -91
  26. package/build/utils/trace-layout.test.ts +0 -570
  27. package/build/utils/trace-svg.test.ts +0 -425
  28. package/build/utils/trace.test.ts +0 -169
  29. package/build/validate-config-links.test.ts +0 -239
  30. package/build/validate-internal-links-plugin.test.ts +0 -134
  31. package/build/watch-reachability-state.test.ts +0 -198
  32. package/src/anchor/index.test.ts +0 -64
  33. package/src/code/index.test.ts +0 -71
  34. package/src/header/index.test.ts +0 -89
  35. package/src/navigate/eligible.test.ts +0 -101
  36. package/src/navigate/index.test.ts +0 -82
  37. package/src/navigate/lifecycle.test.ts +0 -26
  38. package/src/print/index.test.ts +0 -71
  39. package/src/question/index.test.ts +0 -82
  40. package/src/search/index.test.ts +0 -203
  41. package/src/timezone/index.test.ts +0 -400
  42. package/src/toc/index.test.ts +0 -237
  43. package/src/top/index.test.ts +0 -46
  44. package/src/trace/index.test.ts +0 -528
  45. 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
- });