@abreen/tada 1.5.1 → 1.5.4

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 (54) hide show
  1. package/build/globals.ts +1 -1
  2. package/package.json +3 -2
  3. package/src/code/index.ts +5 -4
  4. package/src/index.ts +1 -1
  5. package/src/navigate/index.ts +27 -21
  6. package/src/navigate/lifecycle.ts +1 -1
  7. package/src/navigate/style.scss +2 -2
  8. package/src/search/index.ts +19 -16
  9. package/src/toc/index.ts +1 -1
  10. package/src/top/index.ts +3 -3
  11. package/src/util.ts +7 -6
  12. package/templates/_nav.html +1 -0
  13. package/bin/validators.test.ts +0 -205
  14. package/build/build-manifest.test.ts +0 -369
  15. package/build/code.test.ts +0 -272
  16. package/build/colors.test.ts +0 -66
  17. package/build/globals.test.ts +0 -100
  18. package/build/json-schema.test.ts +0 -57
  19. package/build/log.test.ts +0 -117
  20. package/build/markdown-plugins.test.ts +0 -456
  21. package/build/pagefind.test.ts +0 -192
  22. package/build/pdf-text.test.ts +0 -52
  23. package/build/reachability.test.ts +0 -80
  24. package/build/serve.test.ts +0 -59
  25. package/build/toc-plugin.test.ts +0 -120
  26. package/build/utils/content-files.test.ts +0 -56
  27. package/build/utils/derive-theme.test.ts +0 -111
  28. package/build/utils/file-types.test.ts +0 -79
  29. package/build/utils/front-matter.test.ts +0 -77
  30. package/build/utils/include.test.ts +0 -117
  31. package/build/utils/paths.test.ts +0 -91
  32. package/build/utils/trace-layout.test.ts +0 -570
  33. package/build/utils/trace-svg.test.ts +0 -425
  34. package/build/utils/trace.test.ts +0 -169
  35. package/build/validate-config-links.test.ts +0 -239
  36. package/build/validate-internal-links-plugin.test.ts +0 -134
  37. package/build/watch-reachability-state.test.ts +0 -198
  38. package/fonts/google-sans-code/ttf/GoogleSansCodeVariable-Italic.ttf +0 -0
  39. package/fonts/google-sans-code/ttf/GoogleSansCodeVariable.ttf +0 -0
  40. package/fonts/inter/ttf/InterVariable-Italic.ttf +0 -0
  41. package/src/anchor/index.test.ts +0 -64
  42. package/src/code/index.test.ts +0 -71
  43. package/src/header/index.test.ts +0 -89
  44. package/src/navigate/eligible.test.ts +0 -101
  45. package/src/navigate/index.test.ts +0 -82
  46. package/src/navigate/lifecycle.test.ts +0 -26
  47. package/src/print/index.test.ts +0 -71
  48. package/src/question/index.test.ts +0 -82
  49. package/src/search/index.test.ts +0 -203
  50. package/src/timezone/index.test.ts +0 -400
  51. package/src/toc/index.test.ts +0 -237
  52. package/src/top/index.test.ts +0 -46
  53. package/src/trace/index.test.ts +0 -554
  54. package/src/util.test.ts +0 -135
@@ -1,425 +0,0 @@
1
- import { describe, test, expect } from 'bun:test';
2
- import {
3
- generateStepSvg,
4
- formatValue,
5
- escapeXml,
6
- filterStep,
7
- } from './trace-svg';
8
- import { computeLayout } from './trace-layout';
9
- import type { TraceStep, TraceHeapObject, TraceStackFrame } from '../types';
10
-
11
- function makeStep(
12
- line: number,
13
- stack: TraceStackFrame[],
14
- heap: Record<string, TraceHeapObject>,
15
- ): TraceStep {
16
- return { line, file: 'Test.java', stack, heap, stdout: '' };
17
- }
18
-
19
- describe('generateStepSvg', () => {
20
- test('generates valid SVG with stack frames for a simple int variable', () => {
21
- const step = makeStep(
22
- 5,
23
- [
24
- {
25
- method: 'main',
26
- class: 'Test',
27
- line: 5,
28
- locals: { x: { type: 'int', value: 42 } },
29
- },
30
- ],
31
- {},
32
- );
33
- const layout = computeLayout([step]);
34
- const svg = generateStepSvg(step, layout);
35
-
36
- expect(svg).toStartWith('<svg class="trace-memory"');
37
- expect(svg).toEndWith('</svg>');
38
- expect(svg).toContain('viewBox="0 0');
39
- expect(svg).toContain('xmlns="http://www.w3.org/2000/svg"');
40
- // Should contain frame title with method:line
41
- expect(svg).toContain('main:5');
42
- expect(svg).toContain('class="trace-frame-title"');
43
- // Should contain variable name
44
- expect(svg).toContain('>x</text>');
45
- // Should contain value
46
- expect(svg).toContain('>42</text>');
47
- // Should contain a var box rect
48
- expect(svg).toContain('class="trace-var-box"');
49
- // Should have the arrowhead defs
50
- expect(svg).toContain('<defs>');
51
- expect(svg).toContain('id="trace-arrowhead"');
52
- // Should have stack separator line
53
- expect(svg).toContain('class="trace-separator"');
54
- });
55
-
56
- test('generates heap objects with arrows for ref variable', () => {
57
- const step = makeStep(
58
- 10,
59
- [
60
- {
61
- method: 'main',
62
- class: 'Test',
63
- line: 10,
64
- locals: { node: { type: 'ref', id: '1' } },
65
- },
66
- ],
67
- {
68
- '1': {
69
- type: 'Node',
70
- fields: { val: { type: 'int', value: 7 }, next: { type: 'null' } },
71
- },
72
- },
73
- );
74
- const layout = computeLayout([step]);
75
- const svg = generateStepSvg(step, layout);
76
-
77
- // Should have a heap object
78
- expect(svg).toContain('class="trace-obj"');
79
- expect(svg).toContain('data-id="1"');
80
- // Should have object border
81
- expect(svg).toContain('class="trace-obj-border"');
82
- // Should have field names
83
- expect(svg).toContain('>val</text>');
84
- expect(svg).toContain('>next</text>');
85
- // Should have field values
86
- expect(svg).toContain('>7</text>');
87
- expect(svg).toContain('>null</text>');
88
- // Should have at least one arrow (stack -> heap)
89
- expect(svg).toContain('class="trace-arrow"');
90
- expect(svg).toContain('marker-end="url(#trace-arrowhead)"');
91
- });
92
-
93
- test('generates BST with all three nodes and arrows', () => {
94
- const step = makeStep(
95
- 15,
96
- [
97
- {
98
- method: 'main',
99
- class: 'Test',
100
- line: 15,
101
- locals: { root: { type: 'ref', id: '1' } },
102
- },
103
- ],
104
- {
105
- '1': {
106
- type: 'Node',
107
- fields: {
108
- val: { type: 'int', value: 10 },
109
- left: { type: 'ref', id: '2' },
110
- right: { type: 'ref', id: '3' },
111
- },
112
- },
113
- '2': {
114
- type: 'Node',
115
- fields: {
116
- val: { type: 'int', value: 5 },
117
- left: { type: 'null' },
118
- right: { type: 'null' },
119
- },
120
- },
121
- '3': {
122
- type: 'Node',
123
- fields: {
124
- val: { type: 'int', value: 15 },
125
- left: { type: 'null' },
126
- right: { type: 'null' },
127
- },
128
- },
129
- },
130
- );
131
- const layout = computeLayout([step]);
132
- const svg = generateStepSvg(step, layout);
133
-
134
- // All three nodes should be present
135
- expect(svg).toContain('data-id="1"');
136
- expect(svg).toContain('data-id="2"');
137
- expect(svg).toContain('data-id="3"');
138
- // At least 3 arrows: root->1 (stack, fg), 1->2 and 1->3 (heap)
139
- const allArrows = (svg.match(/class="trace-arrow(?:-heap|-dim)?"/g) ?? [])
140
- .length;
141
- expect(allArrows).toBeGreaterThanOrEqual(3);
142
- });
143
-
144
- test('BST node renders child-slot row with left/right labels', () => {
145
- const step = makeStep(
146
- 15,
147
- [
148
- {
149
- method: 'main',
150
- class: 'Test',
151
- line: 15,
152
- locals: { root: { type: 'ref', id: '1' } },
153
- },
154
- ],
155
- {
156
- '1': {
157
- type: 'Node',
158
- fields: {
159
- val: { type: 'int', value: 10 },
160
- left: { type: 'ref', id: '2' },
161
- right: { type: 'ref', id: '3' },
162
- },
163
- },
164
- '2': {
165
- type: 'Node',
166
- fields: {
167
- val: { type: 'int', value: 5 },
168
- left: { type: 'null' },
169
- right: { type: 'null' },
170
- },
171
- },
172
- '3': {
173
- type: 'Node',
174
- fields: {
175
- val: { type: 'int', value: 15 },
176
- left: { type: 'null' },
177
- right: { type: 'null' },
178
- },
179
- },
180
- },
181
- );
182
- const layout = computeLayout([step]);
183
- const svg = generateStepSvg(step, layout);
184
-
185
- // The root node should have child-slot labels (left, right) as field names
186
- // "val" should be rendered as a data field row
187
- expect(svg).toContain('>val</text>');
188
- expect(svg).toContain('>left</text>');
189
- expect(svg).toContain('>right</text>');
190
- });
191
-
192
- test('handles empty step gracefully', () => {
193
- const step = makeStep(
194
- 1,
195
- [{ method: 'main', class: 'Test', locals: {} }],
196
- {},
197
- );
198
- const layout = computeLayout([step]);
199
- const svg = generateStepSvg(step, layout);
200
-
201
- // Should still be valid SVG
202
- expect(svg).toStartWith('<svg class="trace-memory"');
203
- expect(svg).toEndWith('</svg>');
204
- // Should have viewBox 0 0 0 0 for empty diagram
205
- expect(svg).toContain('viewBox="0 0 0 0"');
206
- });
207
-
208
- test('handles string heap objects', () => {
209
- const step = makeStep(
210
- 5,
211
- [
212
- {
213
- method: 'main',
214
- class: 'Test',
215
- locals: { s: { type: 'ref', id: '1' } },
216
- },
217
- ],
218
- { '1': { type: 'String', value: 'hello' } },
219
- );
220
- const layout = computeLayout([step]);
221
- const svg = generateStepSvg(step, layout);
222
-
223
- // String object should show quoted text
224
- expect(svg).toContain('&quot;hello&quot;');
225
- // Should have an arrow to the string
226
- expect(svg).toContain('class="trace-arrow"');
227
- });
228
-
229
- test('handles array heap objects', () => {
230
- const step = makeStep(
231
- 5,
232
- [
233
- {
234
- method: 'main',
235
- class: 'Test',
236
- locals: { arr: { type: 'ref', id: '1' } },
237
- },
238
- ],
239
- {
240
- '1': {
241
- type: 'int[]',
242
- elements: [
243
- { type: 'int', value: 1 },
244
- { type: 'int', value: 2 },
245
- { type: 'int', value: 3 },
246
- ],
247
- },
248
- },
249
- );
250
- const layout = computeLayout([step]);
251
- const svg = generateStepSvg(step, layout);
252
-
253
- // Should show array values
254
- expect(svg).toContain('>1</text>');
255
- expect(svg).toContain('>2</text>');
256
- expect(svg).toContain('>3</text>');
257
- // Should have cell separators
258
- expect(svg).toContain('class="trace-separator"');
259
- });
260
-
261
- test('multiple stack frames are rendered in reverse order', () => {
262
- // In JDI traces, stack[0] is the bottom (main), stack[1] is the newest call.
263
- // The renderer reverses the stack so the newest frame appears at the top
264
- // visually and main appears at the bottom.
265
- const step = makeStep(
266
- 20,
267
- [
268
- {
269
- method: 'main',
270
- class: 'Test',
271
- line: 10,
272
- locals: { x: { type: 'int', value: 1 } },
273
- },
274
- {
275
- method: 'foo',
276
- class: 'Test',
277
- line: 20,
278
- locals: { y: { type: 'int', value: 2 } },
279
- },
280
- ],
281
- {},
282
- );
283
- const layout = computeLayout([step]);
284
- const svg = generateStepSvg(step, layout);
285
-
286
- // Both frames should be present
287
- expect(svg).toContain('main:10');
288
- expect(svg).toContain('foo:20');
289
- // foo (newest frame) should appear first in SVG output (top of diagram)
290
- const mainPos = svg.indexOf('main:10');
291
- const fooPos = svg.indexOf('foo:20');
292
- expect(fooPos).toBeLessThan(mainPos);
293
- });
294
-
295
- test('non-tree field object has no child-slot row', () => {
296
- const step = makeStep(
297
- 5,
298
- [
299
- {
300
- method: 'main',
301
- class: 'Test',
302
- locals: { p: { type: 'ref', id: '1' } },
303
- },
304
- ],
305
- {
306
- '1': {
307
- type: 'Person',
308
- fields: {
309
- name: { type: 'String', value: 'Alice' },
310
- age: { type: 'int', value: 30 },
311
- },
312
- },
313
- },
314
- );
315
- const layout = computeLayout([step]);
316
- const svg = generateStepSvg(step, layout);
317
-
318
- // Should have field names as regular rows
319
- expect(svg).toContain('>name</text>');
320
- expect(svg).toContain('>age</text>');
321
- // Should have values
322
- expect(svg).toContain('>&quot;Alice&quot;</text>');
323
- expect(svg).toContain('>30</text>');
324
- });
325
- });
326
-
327
- describe('filterStep', () => {
328
- test('removes args from main locals', () => {
329
- const step = makeStep(
330
- 1,
331
- [
332
- {
333
- method: 'main',
334
- class: 'Test',
335
- locals: {
336
- args: { type: 'ref', id: 'args_1' },
337
- x: { type: 'int', value: 5 },
338
- },
339
- },
340
- ],
341
- { args_1: { type: 'String[]', elements: [] } },
342
- );
343
- const { stack, heap } = filterStep(step);
344
- expect(stack[0].locals).not.toHaveProperty('args');
345
- expect(stack[0].locals).toHaveProperty('x');
346
- // args_1 should be filtered out since it's only reachable via args
347
- expect(heap).not.toHaveProperty('args_1');
348
- });
349
-
350
- test('keeps objects reachable from non-args variables', () => {
351
- const step = makeStep(
352
- 1,
353
- [
354
- {
355
- method: 'main',
356
- class: 'Test',
357
- locals: {
358
- args: { type: 'ref', id: 'args_1' },
359
- node: { type: 'ref', id: '2' },
360
- },
361
- },
362
- ],
363
- {
364
- args_1: { type: 'String[]', elements: [] },
365
- '2': { type: 'Node', fields: { val: { type: 'int', value: 10 } } },
366
- },
367
- );
368
- const { heap } = filterStep(step);
369
- expect(heap).toHaveProperty('2');
370
- expect(heap).not.toHaveProperty('args_1');
371
- });
372
- });
373
-
374
- describe('formatValue', () => {
375
- test('null shows "null"', () => {
376
- expect(formatValue({ type: 'null' })).toBe('null');
377
- });
378
-
379
- test('boolean shows true/false', () => {
380
- expect(formatValue({ type: 'boolean', value: true })).toBe('true');
381
- expect(formatValue({ type: 'boolean', value: false })).toBe('false');
382
- });
383
-
384
- test('char shows quoted character', () => {
385
- expect(formatValue({ type: 'char', value: 'A' })).toBe("'A'");
386
- });
387
-
388
- test('String shows double-quoted', () => {
389
- expect(formatValue({ type: 'String', value: 'hi' })).toBe('"hi"');
390
- });
391
-
392
- test('int shows number', () => {
393
- expect(formatValue({ type: 'int', value: 42 })).toBe('42');
394
- });
395
-
396
- test('ref shows bullet', () => {
397
- expect(formatValue({ type: 'ref', id: '1' })).toBe('\u2022');
398
- });
399
-
400
- test('unknown shows ?', () => {
401
- expect(formatValue({ type: 'unknown' })).toBe('?');
402
- });
403
-
404
- test('uninitialized shows empty', () => {
405
- expect(formatValue({ type: 'uninitialized' })).toBe('');
406
- });
407
-
408
- test('truncated shows ellipsis with count', () => {
409
- expect(formatValue({ type: 'truncated', remaining: 5 })).toBe('\u2026(5)');
410
- });
411
- });
412
-
413
- describe('escapeXml', () => {
414
- test('escapes &, <, >, "', () => {
415
- expect(escapeXml('a & b')).toBe('a &amp; b');
416
- expect(escapeXml('<tag>')).toBe('&lt;tag&gt;');
417
- expect(escapeXml('say "hi"')).toBe('say &quot;hi&quot;');
418
- });
419
-
420
- test('handles combined special chars', () => {
421
- expect(escapeXml('a & b < c > d "e"')).toBe(
422
- 'a &amp; b &lt; c &gt; d &quot;e&quot;',
423
- );
424
- });
425
- });
@@ -1,169 +0,0 @@
1
- import { describe, test, expect, beforeEach, afterEach } from 'bun:test';
2
- import fs from 'fs';
3
- import os from 'os';
4
- import path from 'path';
5
- import { chunkTraceOutput, parseIgnoreFields } from './trace';
6
- import type { TraceManifest } from '../types';
7
-
8
- describe('chunkTraceOutput', () => {
9
- let tempDir: string;
10
-
11
- beforeEach(() => {
12
- tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'tada-trace-test-'));
13
- });
14
-
15
- afterEach(() => {
16
- fs.rmSync(tempDir, { recursive: true, force: true });
17
- });
18
-
19
- test('chunks JSONL output into files and writes manifest', () => {
20
- const lines = [];
21
- for (let i = 0; i < 5; i++) {
22
- lines.push(
23
- JSON.stringify({
24
- line: i + 1,
25
- file: 'Test.java',
26
- stack: [{ method: 'main', class: 'Test', locals: {} }],
27
- heap: {},
28
- stdout: '',
29
- }),
30
- );
31
- }
32
- const output = lines.join('\n') + '\n';
33
- const outputDir = path.join(tempDir, '_traces', 'Test');
34
-
35
- const manifest = chunkTraceOutput(
36
- output,
37
- outputDir,
38
- 'Test.java',
39
- 'public class Test {}',
40
- 3,
41
- );
42
-
43
- // Should create 2 chunks (3 + 2)
44
- const chunk0 = JSON.parse(
45
- fs.readFileSync(path.join(outputDir, 'chunk-0.json'), 'utf-8'),
46
- );
47
- const chunk1 = JSON.parse(
48
- fs.readFileSync(path.join(outputDir, 'chunk-1.json'), 'utf-8'),
49
- );
50
- expect(chunk0).toHaveLength(3);
51
- expect(chunk1).toHaveLength(2);
52
-
53
- // Chunks now contain TraceChunkEntry objects with precomputed SVG
54
- expect(chunk0[0].svg).toContain('<svg');
55
- expect(chunk0[0]).toHaveProperty('line');
56
- expect(chunk0[0]).toHaveProperty('stdout');
57
-
58
- // Manifest (returned value and file on disk should both be correct)
59
- const manifestFile = JSON.parse(
60
- fs.readFileSync(path.join(outputDir, 'manifest.json'), 'utf-8'),
61
- ) as TraceManifest;
62
- expect(manifest.totalSteps).toBe(5);
63
- expect(manifest.chunkSize).toBe(3);
64
- expect(manifestFile.sourceFile).toBe('Test.java');
65
- expect(manifestFile.source).toBe('public class Test {}');
66
- expect(manifest.lineToSteps[1]).toEqual([0]);
67
- expect(manifest.lineToSteps[5]).toEqual([4]);
68
- });
69
-
70
- test('handles single chunk', () => {
71
- const line = JSON.stringify({
72
- line: 1,
73
- file: 'Test.java',
74
- stack: [],
75
- heap: {},
76
- stdout: 'hello\n',
77
- });
78
- const outputDir = path.join(tempDir, '_traces', 'Test');
79
-
80
- const manifest = chunkTraceOutput(
81
- line,
82
- outputDir,
83
- 'Test.java',
84
- 'class Test {}',
85
- 50,
86
- );
87
-
88
- expect(manifest.totalSteps).toBe(1);
89
- const chunk0 = JSON.parse(
90
- fs.readFileSync(path.join(outputDir, 'chunk-0.json'), 'utf-8'),
91
- );
92
- expect(chunk0).toHaveLength(1);
93
- expect(chunk0[0].stdout).toBe('hello\n');
94
- });
95
-
96
- test('passes ignoreFields from source to layout', () => {
97
- const source = `class Node {
98
- int value;
99
- Node left;
100
- Node right;
101
- Node parent; // @trace-ignore
102
- }
103
- public class Test {
104
- public static void main(String[] args) {}
105
- }`;
106
- const fields = parseIgnoreFields(source);
107
- expect(fields).toEqual({ Node: ['parent'] });
108
- });
109
-
110
- test('parseIgnoreFields handles multiple classes', () => {
111
- const source = `class A {
112
- A prev; // @trace-ignore
113
- A next;
114
- }
115
- class B {
116
- B up; // @trace-ignore
117
- B down; // @trace-ignore
118
- }`;
119
- const fields = parseIgnoreFields(source);
120
- expect(fields).toEqual({ A: ['prev'], B: ['up', 'down'] });
121
- });
122
-
123
- test('parseIgnoreFields handles inner classes with $ separator', () => {
124
- const source = `public class SearchTreeDemo {
125
- static class Node {
126
- String data;
127
- Node left;
128
- Node right;
129
- Node parent; // @trace-ignore
130
- }
131
-
132
- public static void main(String[] args) {}
133
- }`;
134
- const fields = parseIgnoreFields(source);
135
- expect(fields).toEqual({ SearchTreeDemo$Node: ['parent'] });
136
- });
137
-
138
- test('parseIgnoreFields returns empty for no annotations', () => {
139
- const source = `class Node { int x; }`;
140
- expect(parseIgnoreFields(source)).toEqual({});
141
- });
142
-
143
- test('maps repeated lines to multiple step indices', () => {
144
- const lines = [];
145
- // Simulate a loop: line 3 executes at steps 0, 1, 2
146
- for (let i = 0; i < 3; i++) {
147
- lines.push(
148
- JSON.stringify({
149
- line: 3,
150
- file: 'Test.java',
151
- stack: [],
152
- heap: {},
153
- stdout: '',
154
- }),
155
- );
156
- }
157
- const outputDir = path.join(tempDir, '_traces', 'Test');
158
-
159
- const manifest = chunkTraceOutput(
160
- lines.join('\n'),
161
- outputDir,
162
- 'Test.java',
163
- '',
164
- 50,
165
- );
166
-
167
- expect(manifest.lineToSteps[3]).toEqual([0, 1, 2]);
168
- });
169
- });