@bendyline/squisq 1.5.2 → 2.0.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/README.md +63 -10
- package/dist/{Doc-BBVGq1_9.d.ts → Doc-ylqWKDc_.d.ts} +248 -37
- package/dist/{ImageEditDoc-FuyGtt6o.d.ts → ImageEditDoc-D828IXJ1.d.ts} +1 -1
- package/dist/annotationCoercion-Dr_DY9Hx.d.ts +86 -0
- package/dist/{chunk-VWUFZ6ZG.js → chunk-3E5F2XMR.js} +2 -29
- package/dist/chunk-3E5F2XMR.js.map +1 -0
- package/dist/chunk-4X3JQXNM.js +35 -0
- package/dist/chunk-4X3JQXNM.js.map +1 -0
- package/dist/{chunk-P3K7NLHF.js → chunk-5DKBFXPY.js} +31 -121
- package/dist/chunk-5DKBFXPY.js.map +1 -0
- package/dist/{chunk-DLXZMS5K.js → chunk-5J4HKRJF.js} +51 -3
- package/dist/chunk-5J4HKRJF.js.map +1 -0
- package/dist/chunk-5UYI3RJB.js +2503 -0
- package/dist/chunk-5UYI3RJB.js.map +1 -0
- package/dist/{chunk-BEQODJWV.js → chunk-66QO7XFB.js} +11 -291
- package/dist/chunk-66QO7XFB.js.map +1 -0
- package/dist/{chunk-KKNUBQ6Y.js → chunk-6PJRB2CF.js} +156 -91
- package/dist/chunk-6PJRB2CF.js.map +1 -0
- package/dist/{chunk-HWVFJAAH.js → chunk-AMIR72JP.js} +28 -10
- package/dist/chunk-AMIR72JP.js.map +1 -0
- package/dist/chunk-FVJUUTEM.js +74 -0
- package/dist/chunk-FVJUUTEM.js.map +1 -0
- package/dist/{chunk-5TYS5V3G.js → chunk-GF3BVNLU.js} +4127 -3538
- package/dist/chunk-GF3BVNLU.js.map +1 -0
- package/dist/{chunk-Q3ROPT5H.js → chunk-GZY66SDN.js} +1 -1
- package/dist/{chunk-Q3ROPT5H.js.map → chunk-GZY66SDN.js.map} +1 -1
- package/dist/chunk-JOQYPRDZ.js +127 -0
- package/dist/chunk-JOQYPRDZ.js.map +1 -0
- package/dist/{chunk-A4O7GIWE.js → chunk-LYNJEOAC.js} +10 -73
- package/dist/chunk-LYNJEOAC.js.map +1 -0
- package/dist/{chunk-LDQ2HJIX.js → chunk-PXLVXTLW.js} +252 -54
- package/dist/chunk-PXLVXTLW.js.map +1 -0
- package/dist/{chunk-TQWLI6S2.js → chunk-Q7LROV2H.js} +4 -2
- package/dist/{chunk-TQWLI6S2.js.map → chunk-Q7LROV2H.js.map} +1 -1
- package/dist/{chunk-T5UK6YOB.js → chunk-QTSJOY54.js} +60 -17
- package/dist/chunk-QTSJOY54.js.map +1 -0
- package/dist/chunk-QVANWDPH.js +2358 -0
- package/dist/chunk-QVANWDPH.js.map +1 -0
- package/dist/{chunk-GPLTCSXO.js → chunk-R2GC6RLD.js} +7 -7
- package/dist/chunk-R2GC6RLD.js.map +1 -0
- package/dist/chunk-R2YZTCLO.js +292 -0
- package/dist/chunk-R2YZTCLO.js.map +1 -0
- package/dist/chunk-S7A5D7ZJ.js +174 -0
- package/dist/chunk-S7A5D7ZJ.js.map +1 -0
- package/dist/chunk-T3TI7GSZ.js +884 -0
- package/dist/chunk-T3TI7GSZ.js.map +1 -0
- package/dist/{chunk-LTN6I7QW.js → chunk-TUWMK62B.js} +55 -4
- package/dist/chunk-TUWMK62B.js.map +1 -0
- package/dist/{chunk-IMSCRLLV.js → chunk-VR7UPJ7C.js} +35 -25
- package/dist/chunk-VR7UPJ7C.js.map +1 -0
- package/dist/doc/index.d.ts +2334 -5
- package/dist/doc/index.js +127 -33
- package/dist/generate/index.d.ts +7 -2
- package/dist/generate/index.js +1 -1
- package/dist/imageEdit/index.d.ts +9 -6
- package/dist/imageEdit/index.js +3 -3
- package/dist/index.d.ts +12 -10
- package/dist/index.js +246 -80
- package/dist/jsonForm/index.d.ts +7 -3
- package/dist/jsonForm/index.js +9 -4
- package/dist/markdown/index.d.ts +6 -70
- package/dist/markdown/index.js +13 -9
- package/dist/narration/index.d.ts +618 -0
- package/dist/narration/index.js +82 -0
- package/dist/recommend/index.d.ts +15 -1
- package/dist/recommend/index.js +2 -2
- package/dist/schemas/index.d.ts +28 -6
- package/dist/schemas/index.js +15 -13
- package/dist/spatial/index.d.ts +0 -1
- package/dist/spatial/index.js +1 -1
- package/dist/storage/index.js +1 -1
- package/dist/{themeLibrary-CehcJhzz.d.ts → themeLibrary-RjVgLpUi.d.ts} +10 -11
- package/dist/timing/index.js +5 -3
- package/dist/transform/index.d.ts +35 -24
- package/dist/transform/index.js +9 -10
- package/dist/{types-DlAZ7MW4.d.ts → types-BwMPl2CB.d.ts} +1 -1
- package/dist/{types-D98li1gV.d.ts → types-C4kiRn_k.d.ts} +3 -1
- package/dist/versions/index.d.ts +2 -1
- package/dist/versions/index.js +2 -2
- package/package.json +6 -6
- package/src/__tests__/applyNarrationTiming.test.ts +190 -0
- package/src/__tests__/applyRenderStyle.test.ts +3 -3
- package/src/__tests__/asciiDiagramBattle.test.ts +246 -0
- package/src/__tests__/asciiDiagramDetect.test.ts +162 -0
- package/src/__tests__/asciiDiagramMapping.test.ts +38 -0
- package/src/__tests__/asciiDiagramParse.test.ts +233 -0
- package/src/__tests__/asciiDiagramPipeline.test.ts +273 -0
- package/src/__tests__/asciiDiagramRender.test.ts +162 -0
- package/src/__tests__/asciiDiagramRepair.test.ts +84 -0
- package/src/__tests__/asciiTimelineDetect.test.ts +157 -0
- package/src/__tests__/asciiTimelineParse.test.ts +205 -0
- package/src/__tests__/asciiTimelinePipeline.test.ts +349 -0
- package/src/__tests__/asciiTimelineRender.test.ts +376 -0
- package/src/__tests__/attrTokens.test.ts +5 -0
- package/src/__tests__/contentContainer.test.ts +43 -0
- package/src/__tests__/customTemplatesFrontmatter.test.ts +0 -15
- package/src/__tests__/dataTable.test.ts +25 -15
- package/src/__tests__/diagramBlock.test.ts +3 -3
- package/src/__tests__/diagramText.test.ts +26 -0
- package/src/__tests__/docToMarkdownMedia.test.ts +83 -0
- package/src/__tests__/drawingBlock.test.ts +3 -3
- package/src/__tests__/fixtures/asciiDiagramBattle.ts +512 -0
- package/src/__tests__/fixtures/asciiDiagrams.ts +287 -0
- package/src/__tests__/fixtures/asciiTimelines.ts +35 -0
- package/src/__tests__/fixtures/treeviewFixtures.ts +243 -0
- package/src/__tests__/getLayers.test.ts +32 -18
- package/src/__tests__/getLayersFallback.test.ts +8 -8
- package/src/__tests__/imageEditVersions.test.ts +2 -0
- package/src/__tests__/jsonForm.pointer.test.ts +47 -1
- package/src/__tests__/layoutBlock.test.ts +3 -3
- package/src/__tests__/majorApiTypes.test.ts +18 -0
- package/src/__tests__/markdown.test.ts +41 -0
- package/src/__tests__/matchFontFamily.test.ts +114 -0
- package/src/__tests__/materializeBlockLayers.test.ts +135 -0
- package/src/__tests__/materializeTestUtils.ts +17 -0
- package/src/__tests__/narrationAlign.test.ts +150 -0
- package/src/__tests__/narrationFeatures.test.ts +71 -0
- package/src/__tests__/narrationNuclei.test.ts +45 -0
- package/src/__tests__/narrationPacing.test.ts +189 -0
- package/src/__tests__/narrationScript.test.ts +149 -0
- package/src/__tests__/narrationSessionRepro.test.ts +62 -0
- package/src/__tests__/narrationSidecar.test.ts +88 -0
- package/src/__tests__/narrationSyllables.test.ts +65 -0
- package/src/__tests__/narrationTestSignals.ts +167 -0
- package/src/__tests__/narrationVad.test.ts +66 -0
- package/src/__tests__/resolveDocTheme.test.ts +16 -2
- package/src/__tests__/rootExports.test.ts +18 -0
- package/src/__tests__/scopedContentContainer.test.ts +20 -0
- package/src/__tests__/shapeGeometry.test.ts +12 -0
- package/src/__tests__/templateAnnotationParse.test.ts +10 -0
- package/src/__tests__/templateInputs.test.ts +69 -0
- package/src/__tests__/templateMetadata.test.ts +2 -1
- package/src/__tests__/templates.test.ts +143 -17
- package/src/__tests__/themeCompile.test.ts +49 -22
- package/src/__tests__/transformProvenance.test.ts +160 -0
- package/src/__tests__/transformV2.test.ts +40 -14
- package/src/__tests__/treeviewBattle.test.ts +76 -0
- package/src/__tests__/treeviewDetect.test.ts +168 -0
- package/src/__tests__/treeviewParse.test.ts +118 -0
- package/src/__tests__/treeviewPipeline.test.ts +183 -0
- package/src/__tests__/treeviewRender.test.ts +91 -0
- package/src/__tests__/versions.test.ts +44 -0
- package/src/doc/applyNarrationTiming.ts +244 -0
- package/src/doc/asciiDiagram/chars.ts +210 -0
- package/src/doc/asciiDiagram/detect.ts +175 -0
- package/src/doc/asciiDiagram/index.ts +38 -0
- package/src/doc/asciiDiagram/mapping.ts +225 -0
- package/src/doc/asciiDiagram/parse.ts +1095 -0
- package/src/doc/asciiDiagram/render.ts +963 -0
- package/src/doc/asciiDiagram/repair.ts +400 -0
- package/src/doc/asciiDiagram/types.ts +65 -0
- package/src/doc/asciiTimeline/detect.ts +138 -0
- package/src/doc/asciiTimeline/index.ts +26 -0
- package/src/doc/asciiTimeline/mapping.ts +43 -0
- package/src/doc/asciiTimeline/parse.ts +870 -0
- package/src/doc/asciiTimeline/render.ts +427 -0
- package/src/doc/asciiTimeline/types.ts +73 -0
- package/src/doc/asciiTimeline/wrappedFlow.ts +427 -0
- package/src/doc/audioMapping.ts +60 -46
- package/src/doc/customTemplatesFrontmatter.ts +0 -41
- package/src/doc/docToMarkdown.ts +70 -2
- package/src/doc/index.ts +76 -4
- package/src/doc/markdownToDoc.ts +135 -1
- package/src/doc/materializeBlockLayers.ts +304 -0
- package/src/doc/mediaAnnotations.ts +20 -0
- package/src/doc/resolveDocTheme.ts +8 -7
- package/src/doc/templateInputs.ts +185 -2
- package/src/doc/templates/__tests__/customTemplateRegistry.test.ts +22 -8
- package/src/doc/templates/diagramBlock.ts +237 -38
- package/src/doc/templates/fallbackBlock.ts +1 -1
- package/src/doc/templates/index.ts +134 -279
- package/src/doc/templates/inputDescriptors.ts +20 -1
- package/src/doc/templates/metadata.ts +10 -0
- package/src/doc/templates/persistentLayers.ts +12 -126
- package/src/doc/templates/registry.ts +88 -0
- package/src/doc/templates/templateNames.ts +22 -0
- package/src/doc/templates/timelineBlock.ts +613 -0
- package/src/doc/templates/treeBlock.ts +131 -0
- package/src/doc/treeview/chars.ts +72 -0
- package/src/doc/treeview/detect.ts +145 -0
- package/src/doc/treeview/index.ts +30 -0
- package/src/doc/treeview/mapping.ts +95 -0
- package/src/doc/treeview/parse.ts +201 -0
- package/src/doc/treeview/render.ts +41 -0
- package/src/doc/treeview/types.ts +43 -0
- package/src/doc/utils/applyRenderStyle.ts +2 -9
- package/src/doc/utils/diagramText.ts +78 -0
- package/src/doc/utils/shapeGeometry.ts +67 -5
- package/src/doc/validate.ts +1 -1
- package/src/generate/templateMapper.ts +7 -0
- package/src/imageEdit/export.ts +6 -5
- package/src/imageEdit/versionPaths.ts +13 -26
- package/src/imageEdit/versions.ts +21 -100
- package/src/index.ts +1 -0
- package/src/internal/immutable.ts +58 -0
- package/src/jsonForm/index.ts +9 -1
- package/src/jsonForm/pointer.ts +68 -23
- package/src/markdown/attrTokens.ts +7 -5
- package/src/markdown/index.ts +1 -0
- package/src/markdown/stringify.ts +4 -9
- package/src/markdown/utils.ts +36 -14
- package/src/narration/align.ts +514 -0
- package/src/narration/features.ts +231 -0
- package/src/narration/index.ts +86 -0
- package/src/narration/nuclei.ts +118 -0
- package/src/narration/pacing.ts +211 -0
- package/src/narration/script.ts +239 -0
- package/src/narration/session.ts +100 -0
- package/src/narration/sidecar.ts +187 -0
- package/src/narration/syllables.ts +85 -0
- package/src/narration/trace.ts +50 -0
- package/src/narration/types.ts +299 -0
- package/src/narration/vad.ts +131 -0
- package/src/recommend/numberHighlight.ts +25 -0
- package/src/recommend/templates.ts +108 -12
- package/src/schemas/BlockTemplates.ts +156 -7
- package/src/schemas/Doc.ts +72 -9
- package/src/schemas/Media.ts +12 -2
- package/src/schemas/Theme.ts +45 -25
- package/src/schemas/Viewport.ts +2 -2
- package/src/schemas/fontStacks.ts +79 -0
- package/src/schemas/themeCompile.ts +15 -0
- package/src/schemas/themeConstants.ts +2 -0
- package/src/schemas/themeLibrary.ts +25 -19
- package/src/schemas/themeValidator.ts +1 -1
- package/src/spatial/Geohash.ts +0 -3
- package/src/storage/ContentContainer.ts +17 -9
- package/src/storage/ScopedContentContainer.ts +45 -15
- package/src/transform/applyTransform.ts +5 -5
- package/src/transform/index.ts +3 -1
- package/src/transform/registry.ts +169 -49
- package/src/transform/templateSelector.ts +21 -2
- package/src/transform/timingAllocator.ts +152 -21
- package/src/transform/types.ts +14 -1
- package/src/versions/gitignore.ts +34 -0
- package/src/versions/index.ts +2 -0
- package/src/versions/operations.ts +18 -104
- package/src/versions/paths.ts +13 -28
- package/src/versions/snapshotStore.ts +202 -0
- package/src/versions/types.ts +3 -1
- package/dist/chunk-5TYS5V3G.js.map +0 -1
- package/dist/chunk-A4O7GIWE.js.map +0 -1
- package/dist/chunk-BEQODJWV.js.map +0 -1
- package/dist/chunk-DLXZMS5K.js.map +0 -1
- package/dist/chunk-GPLTCSXO.js.map +0 -1
- package/dist/chunk-HWVFJAAH.js.map +0 -1
- package/dist/chunk-IMSCRLLV.js.map +0 -1
- package/dist/chunk-JELIRVDP.js +0 -24
- package/dist/chunk-JELIRVDP.js.map +0 -1
- package/dist/chunk-KKNUBQ6Y.js.map +0 -1
- package/dist/chunk-LDQ2HJIX.js.map +0 -1
- package/dist/chunk-LTN6I7QW.js.map +0 -1
- package/dist/chunk-P3K7NLHF.js.map +0 -1
- package/dist/chunk-T5UK6YOB.js.map +0 -1
- package/dist/chunk-UY7KGQ5R.js +0 -192
- package/dist/chunk-UY7KGQ5R.js.map +0 -1
- package/dist/chunk-VLJJHDAC.js +0 -218
- package/dist/chunk-VLJJHDAC.js.map +0 -1
- package/dist/chunk-VWUFZ6ZG.js.map +0 -1
- package/dist/story/index.d.ts +0 -1801
- package/dist/story/index.js +0 -279
- package/src/doc/getLayers.ts +0 -195
- /package/dist/{story → narration}/index.js.map +0 -0
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
asciiTimelineToTemplateData,
|
|
4
|
+
detectAsciiTimeline,
|
|
5
|
+
parseAsciiTimeline,
|
|
6
|
+
renderAsciiTimeline,
|
|
7
|
+
type AsciiTimeline,
|
|
8
|
+
} from '../doc/asciiTimeline/index.js';
|
|
9
|
+
import {
|
|
10
|
+
TICK_INTERPOLATION_TIMELINE,
|
|
11
|
+
WRAPPED_CLIENT_KERNEL_TIMELINE,
|
|
12
|
+
} from './fixtures/asciiTimelines.js';
|
|
13
|
+
|
|
14
|
+
function renderFixpoint(timeline: AsciiTimeline, style?: 'unicode' | 'ascii'): string {
|
|
15
|
+
const first = renderAsciiTimeline(timeline, style ? { style } : {});
|
|
16
|
+
const second = renderAsciiTimeline(parseAsciiTimeline(first));
|
|
17
|
+
const third = renderAsciiTimeline(parseAsciiTimeline(second));
|
|
18
|
+
expect(second).toBe(first);
|
|
19
|
+
expect(third).toBe(first);
|
|
20
|
+
return first;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
describe('renderAsciiTimeline', () => {
|
|
24
|
+
const timeline: AsciiTimeline = {
|
|
25
|
+
tracks: [
|
|
26
|
+
{
|
|
27
|
+
id: 'kernel',
|
|
28
|
+
label: 'kernel ticks (30 Hz)',
|
|
29
|
+
row: 4,
|
|
30
|
+
startColumn: 20,
|
|
31
|
+
endColumn: 65,
|
|
32
|
+
events: [
|
|
33
|
+
{
|
|
34
|
+
id: 'tick-28',
|
|
35
|
+
label: 'T28',
|
|
36
|
+
description: 'delta-28',
|
|
37
|
+
column: 22,
|
|
38
|
+
side: 'above',
|
|
39
|
+
marker: 'filled',
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
id: 'tick-29',
|
|
43
|
+
label: 'T29',
|
|
44
|
+
column: 38,
|
|
45
|
+
side: 'below',
|
|
46
|
+
marker: 'hollow',
|
|
47
|
+
},
|
|
48
|
+
],
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
id: 'branch',
|
|
52
|
+
label: 'client frames',
|
|
53
|
+
row: 9,
|
|
54
|
+
startColumn: 30,
|
|
55
|
+
endColumn: 55,
|
|
56
|
+
events: [
|
|
57
|
+
{
|
|
58
|
+
id: 'render-point',
|
|
59
|
+
label: 'render position',
|
|
60
|
+
description: 'interpolate T28 to T29',
|
|
61
|
+
column: 42,
|
|
62
|
+
marker: 'diamond',
|
|
63
|
+
},
|
|
64
|
+
],
|
|
65
|
+
},
|
|
66
|
+
],
|
|
67
|
+
links: [{ source: 'tick-28', target: 'render-point', label: 'feeds renderer' }],
|
|
68
|
+
width: 80,
|
|
69
|
+
height: 12,
|
|
70
|
+
style: 'unicode',
|
|
71
|
+
warnings: [],
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
it('emits a continuous rail with pointer callouts and branch declarations', () => {
|
|
75
|
+
const output = renderFixpoint(timeline);
|
|
76
|
+
const lines = output.split('\n');
|
|
77
|
+
const kernelRail = lines.find((line) => line.includes('kernel ticks (30 Hz)'));
|
|
78
|
+
|
|
79
|
+
expect(kernelRail).toMatch(/kernel ticks \(30 Hz\) \{#kernel start=20 end=65\}: ─*●─*○─*►/u);
|
|
80
|
+
expect(output).toContain('▲ T28 :: delta-28 {#tick-28 column=22}');
|
|
81
|
+
expect(output).toContain('▼ T29 {#tick-29 column=38}');
|
|
82
|
+
expect(lines[lines.length - 1]).toBe('branch: tick-28 → render-point : feeds renderer');
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('uses parser-recognizable ASCII markers and rails', () => {
|
|
86
|
+
const output = renderFixpoint(timeline, 'ascii');
|
|
87
|
+
|
|
88
|
+
expect(output).toMatch(/^kernel ticks \(30 Hz\) \{#kernel start=20 end=65\}: -*\*-*o-*>/m);
|
|
89
|
+
expect(output).toContain('^ T28 :: delta-28 {#tick-28 column=22}');
|
|
90
|
+
expect(output).toContain('v T29 {#tick-29 column=38}');
|
|
91
|
+
expect(output).toContain('branch: tick-28 -> render-point : feeds renderer');
|
|
92
|
+
expect(output).not.toMatch(/[─●○◆►→]/u);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('sorts tracks/events and drops unresolved links before the first render', () => {
|
|
96
|
+
const shuffled: AsciiTimeline = {
|
|
97
|
+
...timeline,
|
|
98
|
+
tracks: [...timeline.tracks].reverse().map((track) => ({
|
|
99
|
+
...track,
|
|
100
|
+
events: [...track.events].reverse(),
|
|
101
|
+
})),
|
|
102
|
+
links: [...timeline.links, { source: 'missing', target: 'tick-28' }],
|
|
103
|
+
};
|
|
104
|
+
const output = renderFixpoint(shuffled);
|
|
105
|
+
|
|
106
|
+
expect(output.indexOf('kernel ticks')).toBeLessThan(output.indexOf('client frames'));
|
|
107
|
+
expect(output.indexOf('T28')).toBeLessThan(output.indexOf('T29'));
|
|
108
|
+
expect(output).not.toContain('missing');
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('normalizes colliding ids and prose-heavy Unicode without fixpoint churn', () => {
|
|
112
|
+
const adversarial: AsciiTimeline = {
|
|
113
|
+
tracks: [
|
|
114
|
+
{
|
|
115
|
+
id: 'one',
|
|
116
|
+
row: 0,
|
|
117
|
+
startColumn: 0,
|
|
118
|
+
endColumn: 20,
|
|
119
|
+
events: [
|
|
120
|
+
{ id: 'same', label: 'oooooooooooo', column: 0 },
|
|
121
|
+
{ id: 'same', label: 'second', column: 10 },
|
|
122
|
+
{ id: 'same_2', label: 'third', column: 20 },
|
|
123
|
+
],
|
|
124
|
+
},
|
|
125
|
+
],
|
|
126
|
+
links: [],
|
|
127
|
+
width: 21,
|
|
128
|
+
height: 1,
|
|
129
|
+
style: 'unicode',
|
|
130
|
+
warnings: [],
|
|
131
|
+
};
|
|
132
|
+
const output = renderFixpoint(adversarial);
|
|
133
|
+
|
|
134
|
+
expect(output).toContain('{#same column=0}');
|
|
135
|
+
expect(output).toContain('{#same_2 column=10}');
|
|
136
|
+
expect(output).toContain('{#same_2_2 column=20}');
|
|
137
|
+
expect(parseAsciiTimeline(output).style).toBe('unicode');
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('preserves shared geometry when track labels have different lengths', () => {
|
|
141
|
+
const geometry: AsciiTimeline = {
|
|
142
|
+
tracks: [
|
|
143
|
+
{
|
|
144
|
+
id: 'short',
|
|
145
|
+
label: 'A',
|
|
146
|
+
row: 0,
|
|
147
|
+
startColumn: 10,
|
|
148
|
+
endColumn: 110,
|
|
149
|
+
events: [
|
|
150
|
+
{ id: 'a20', label: '20', column: 20 },
|
|
151
|
+
{ id: 'a90', label: '90', column: 90 },
|
|
152
|
+
],
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
id: 'long',
|
|
156
|
+
label: 'A much longer parallel track label',
|
|
157
|
+
row: 3,
|
|
158
|
+
startColumn: 10,
|
|
159
|
+
endColumn: 110,
|
|
160
|
+
events: [
|
|
161
|
+
{ id: 'b20', label: '20', column: 20 },
|
|
162
|
+
{ id: 'b90', label: '90', column: 90 },
|
|
163
|
+
],
|
|
164
|
+
},
|
|
165
|
+
],
|
|
166
|
+
links: [],
|
|
167
|
+
width: 111,
|
|
168
|
+
height: 4,
|
|
169
|
+
style: 'unicode',
|
|
170
|
+
warnings: [],
|
|
171
|
+
};
|
|
172
|
+
const before = asciiTimelineToTemplateData(geometry).tracks.map((track) =>
|
|
173
|
+
track.events.map((event) => event.position),
|
|
174
|
+
);
|
|
175
|
+
const rendered = renderFixpoint(geometry);
|
|
176
|
+
const parsed = parseAsciiTimeline(rendered);
|
|
177
|
+
const after = asciiTimelineToTemplateData(parsed).tracks.map((track) =>
|
|
178
|
+
track.events.map((event) => event.position),
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
expect(parsed.tracks.map((track) => [track.startColumn, track.endColumn])).toEqual([
|
|
182
|
+
[10, 110],
|
|
183
|
+
[10, 110],
|
|
184
|
+
]);
|
|
185
|
+
expect(after).toEqual(before);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it('preserves internal arrows and double hyphens in event prose', () => {
|
|
189
|
+
const prose: AsciiTimeline = {
|
|
190
|
+
tracks: [
|
|
191
|
+
{
|
|
192
|
+
id: 'flow',
|
|
193
|
+
label: 'Flow',
|
|
194
|
+
row: 0,
|
|
195
|
+
startColumn: 0,
|
|
196
|
+
endColumn: 20,
|
|
197
|
+
events: [
|
|
198
|
+
{
|
|
199
|
+
id: 'compare',
|
|
200
|
+
label: 'Compare',
|
|
201
|
+
description: 'A > B; release--candidate',
|
|
202
|
+
column: 10,
|
|
203
|
+
},
|
|
204
|
+
],
|
|
205
|
+
},
|
|
206
|
+
],
|
|
207
|
+
links: [],
|
|
208
|
+
width: 21,
|
|
209
|
+
height: 1,
|
|
210
|
+
style: 'unicode',
|
|
211
|
+
warnings: [],
|
|
212
|
+
};
|
|
213
|
+
const parsed = parseAsciiTimeline(renderFixpoint(prose));
|
|
214
|
+
|
|
215
|
+
expect(parsed.tracks[0].events[0].description).toBe('A > B; release--candidate');
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it('canonicalizes the exact sample without losing its cadence or callout semantics', () => {
|
|
219
|
+
const parsed = parseAsciiTimeline(TICK_INTERPOLATION_TIMELINE);
|
|
220
|
+
const before = asciiTimelineToTemplateData(parsed).tracks.map((track) =>
|
|
221
|
+
track.events.map((event) => event.position),
|
|
222
|
+
);
|
|
223
|
+
const output = renderFixpoint(parsed);
|
|
224
|
+
expect(detectAsciiTimeline(output).isTimeline).toBe(true);
|
|
225
|
+
expect(detectAsciiTimeline(output, { explicit: true }).isTimeline).toBe(true);
|
|
226
|
+
const reparsed = parseAsciiTimeline(output);
|
|
227
|
+
const [kernel, client] = reparsed.tracks;
|
|
228
|
+
|
|
229
|
+
expect(reparsed.tracks).toHaveLength(2);
|
|
230
|
+
expect(kernel.endLabel).toBe('sim time');
|
|
231
|
+
expect(kernel.events[0]).toMatchObject({
|
|
232
|
+
label: 'T28',
|
|
233
|
+
description: 'deltas sent: Δ28',
|
|
234
|
+
side: 'above',
|
|
235
|
+
descriptionSide: 'below',
|
|
236
|
+
});
|
|
237
|
+
expect(client.events.filter((event) => event.callout === false)).toHaveLength(16);
|
|
238
|
+
expect(client.events.map((event) => event.label)).toEqual(
|
|
239
|
+
expect.arrayContaining(['estimated kernel clock', 'render position']),
|
|
240
|
+
);
|
|
241
|
+
expect(
|
|
242
|
+
asciiTimelineToTemplateData(reparsed).tracks.map((track) =>
|
|
243
|
+
track.events.map((event) => event.position),
|
|
244
|
+
),
|
|
245
|
+
).toEqual(before);
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
it('canonicalizes wrapped flow art into a byte-stable point timeline', () => {
|
|
249
|
+
const wrapped = parseAsciiTimeline(WRAPPED_CLIENT_KERNEL_TIMELINE);
|
|
250
|
+
const output = renderFixpoint(wrapped);
|
|
251
|
+
const reparsed = parseAsciiTimeline(output);
|
|
252
|
+
|
|
253
|
+
expect(output).toContain('CLIENT (main thread)');
|
|
254
|
+
expect(output).toContain('KERNEL (Web Worker / Node)');
|
|
255
|
+
expect(output).toContain('branch: command-rejected-event → error-surfaced-to-user-agent');
|
|
256
|
+
expect(reparsed.tracks.map((track) => track.events.map((event) => event.id))).toEqual(
|
|
257
|
+
wrapped.tracks.map((track) => track.events.map((event) => event.id)),
|
|
258
|
+
);
|
|
259
|
+
expect(reparsed.links).toEqual(wrapped.links);
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
it('round-trips a hyphenated label ending in an ASCII-marker letter', () => {
|
|
263
|
+
const hyphenated: AsciiTimeline = {
|
|
264
|
+
tracks: [
|
|
265
|
+
{
|
|
266
|
+
id: 'flow',
|
|
267
|
+
row: 0,
|
|
268
|
+
startColumn: 0,
|
|
269
|
+
endColumn: 20,
|
|
270
|
+
events: [
|
|
271
|
+
{ id: 'go', label: 'Go-live', column: 0 },
|
|
272
|
+
{ id: 'done', label: 'Done', column: 20 },
|
|
273
|
+
],
|
|
274
|
+
},
|
|
275
|
+
],
|
|
276
|
+
links: [],
|
|
277
|
+
width: 21,
|
|
278
|
+
height: 1,
|
|
279
|
+
style: 'unicode',
|
|
280
|
+
warnings: [],
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
const parsed = parseAsciiTimeline(renderFixpoint(hyphenated));
|
|
284
|
+
expect(parsed.tracks[0].events.map((event) => [event.id, event.label])).toEqual([
|
|
285
|
+
['go', 'Go-live'],
|
|
286
|
+
['done', 'Done'],
|
|
287
|
+
]);
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
it('does not treat marker letters beside double hyphens as ASCII events', () => {
|
|
291
|
+
const doubleHyphen: AsciiTimeline = {
|
|
292
|
+
tracks: [
|
|
293
|
+
{
|
|
294
|
+
id: 'ascii-flow',
|
|
295
|
+
row: 0,
|
|
296
|
+
startColumn: 0,
|
|
297
|
+
endColumn: 10,
|
|
298
|
+
events: [{ id: 'foo', label: 'foo--bar', column: 5 }],
|
|
299
|
+
},
|
|
300
|
+
],
|
|
301
|
+
links: [],
|
|
302
|
+
width: 11,
|
|
303
|
+
height: 1,
|
|
304
|
+
style: 'ascii',
|
|
305
|
+
warnings: [],
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
const parsed = parseAsciiTimeline(renderFixpoint(doubleHyphen, 'ascii'));
|
|
309
|
+
expect(parsed.tracks[0].events).toMatchObject([{ id: 'foo', label: 'foo--bar' }]);
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
it('round-trips brace-delimited prose in a track end label', () => {
|
|
313
|
+
const braceSuffix: AsciiTimeline = {
|
|
314
|
+
tracks: [
|
|
315
|
+
{
|
|
316
|
+
id: 'release',
|
|
317
|
+
row: 0,
|
|
318
|
+
startColumn: 0,
|
|
319
|
+
endColumn: 10,
|
|
320
|
+
endLabel: 'status {beta}',
|
|
321
|
+
events: [
|
|
322
|
+
{ id: 'a', label: 'A', column: 0 },
|
|
323
|
+
{ id: 'b', label: 'B', column: 10 },
|
|
324
|
+
],
|
|
325
|
+
},
|
|
326
|
+
],
|
|
327
|
+
links: [],
|
|
328
|
+
width: 11,
|
|
329
|
+
height: 1,
|
|
330
|
+
style: 'unicode',
|
|
331
|
+
warnings: [],
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
const parsed = parseAsciiTimeline(renderFixpoint(braceSuffix));
|
|
335
|
+
expect(parsed.tracks[0].endLabel).toBe('status {beta}');
|
|
336
|
+
expect(parsed.tracks[0].events.map((event) => [event.id, event.label])).toEqual([
|
|
337
|
+
['a', 'A'],
|
|
338
|
+
['b', 'B'],
|
|
339
|
+
]);
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
it.each(['unicode', 'ascii'] as const)(
|
|
343
|
+
'normalizes leading rail prose without %s fixpoint churn',
|
|
344
|
+
(style) => {
|
|
345
|
+
const leadingRails: AsciiTimeline = {
|
|
346
|
+
tracks: [
|
|
347
|
+
{
|
|
348
|
+
id: 'flags',
|
|
349
|
+
endLabel: '─-done',
|
|
350
|
+
row: 0,
|
|
351
|
+
startColumn: 0,
|
|
352
|
+
endColumn: 20,
|
|
353
|
+
events: [
|
|
354
|
+
{ id: 'watch', label: '--watch', column: 0 },
|
|
355
|
+
{ id: 'negative', label: '-1.5', column: 10 },
|
|
356
|
+
{ id: 'phase', label: '─phase', column: 20 },
|
|
357
|
+
],
|
|
358
|
+
},
|
|
359
|
+
],
|
|
360
|
+
links: [],
|
|
361
|
+
width: 21,
|
|
362
|
+
height: 1,
|
|
363
|
+
style,
|
|
364
|
+
warnings: [],
|
|
365
|
+
};
|
|
366
|
+
const parsed = parseAsciiTimeline(renderFixpoint(leadingRails, style));
|
|
367
|
+
|
|
368
|
+
expect(parsed.tracks[0].events.map((event) => event.label)).toEqual([
|
|
369
|
+
'−−watch',
|
|
370
|
+
'−1.5',
|
|
371
|
+
'–phase',
|
|
372
|
+
]);
|
|
373
|
+
expect(parsed.tracks[0].endLabel).toBe('–−done');
|
|
374
|
+
},
|
|
375
|
+
);
|
|
376
|
+
});
|
|
@@ -120,6 +120,11 @@ describe('needsQuoting / quoteAttrValue', () => {
|
|
|
120
120
|
});
|
|
121
121
|
|
|
122
122
|
describe('matchTrailingTemplateAnnotation', () => {
|
|
123
|
+
it('matches an empty trailing annotation', () => {
|
|
124
|
+
const m = matchTrailingTemplateAnnotation('Heading {[]}');
|
|
125
|
+
expect(m?.inner).toBe('');
|
|
126
|
+
});
|
|
127
|
+
|
|
123
128
|
it('matches a simple trailing annotation', () => {
|
|
124
129
|
const m = matchTrailingTemplateAnnotation('Getting Started {[comparisonBar]}');
|
|
125
130
|
expect(m?.inner).toBe('comparisonBar');
|
|
@@ -34,6 +34,38 @@ describe('MemoryContentContainer', () => {
|
|
|
34
34
|
expect(new Uint8Array(result!)).toEqual(new Uint8Array([1, 2, 3, 4]));
|
|
35
35
|
});
|
|
36
36
|
|
|
37
|
+
it('accepts ArrayBuffers created in another browser realm', async () => {
|
|
38
|
+
const iframe = document.createElement('iframe');
|
|
39
|
+
document.body.appendChild(iframe);
|
|
40
|
+
try {
|
|
41
|
+
const foreignWindow = iframe.contentWindow as unknown as {
|
|
42
|
+
ArrayBuffer: ArrayBufferConstructor;
|
|
43
|
+
Uint8Array: Uint8ArrayConstructor;
|
|
44
|
+
};
|
|
45
|
+
const data = new foreignWindow.ArrayBuffer(3);
|
|
46
|
+
new foreignWindow.Uint8Array(data).set([7, 8, 9]);
|
|
47
|
+
await container.writeFile('foreign.bin', data);
|
|
48
|
+
expect(new Uint8Array((await container.readFile('foreign.bin'))!)).toEqual(
|
|
49
|
+
new Uint8Array([7, 8, 9]),
|
|
50
|
+
);
|
|
51
|
+
} finally {
|
|
52
|
+
iframe.remove();
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('owns copies on both write and read', async () => {
|
|
57
|
+
const input = new ArrayBuffer(2);
|
|
58
|
+
new Uint8Array(input).set([1, 2]);
|
|
59
|
+
await container.writeFile('owned.bin', input);
|
|
60
|
+
|
|
61
|
+
new Uint8Array(input)[0] = 9;
|
|
62
|
+
const firstRead = (await container.readFile('owned.bin'))!;
|
|
63
|
+
expect(Array.from(new Uint8Array(firstRead))).toEqual([1, 2]);
|
|
64
|
+
|
|
65
|
+
new Uint8Array(firstRead)[1] = 9;
|
|
66
|
+
expect(Array.from(new Uint8Array((await container.readFile('owned.bin'))!))).toEqual([1, 2]);
|
|
67
|
+
});
|
|
68
|
+
|
|
37
69
|
it('writeFile overwrites existing file', async () => {
|
|
38
70
|
await container.writeFile('f.txt', new TextEncoder().encode('first'));
|
|
39
71
|
await container.writeFile('f.txt', new TextEncoder().encode('second'));
|
|
@@ -120,6 +152,12 @@ describe('getDocumentPath', () => {
|
|
|
120
152
|
expect(await container.getDocumentPath()).toBe('index.md');
|
|
121
153
|
});
|
|
122
154
|
|
|
155
|
+
it('preserves the actual case of a priority filename', async () => {
|
|
156
|
+
await container.writeFile('Index.MD', new TextEncoder().encode('# Hello'));
|
|
157
|
+
expect(await container.getDocumentPath()).toBe('Index.MD');
|
|
158
|
+
expect(await container.readDocument()).toBe('# Hello');
|
|
159
|
+
});
|
|
160
|
+
|
|
123
161
|
it('prefers index.md over doc.md', async () => {
|
|
124
162
|
await container.writeFile('doc.md', new TextEncoder().encode('doc'));
|
|
125
163
|
await container.writeFile('index.md', new TextEncoder().encode('index'));
|
|
@@ -196,6 +234,11 @@ describe('findDocumentPath', () => {
|
|
|
196
234
|
expect(findDocumentPath(entries)).toBe('index.md');
|
|
197
235
|
});
|
|
198
236
|
|
|
237
|
+
it('returns the matched entry path rather than a lower-cased synthetic path', () => {
|
|
238
|
+
const entries: ContentEntry[] = [{ path: 'INDEX.md', mimeType: 'text/markdown', size: 50 }];
|
|
239
|
+
expect(findDocumentPath(entries)).toBe('INDEX.md');
|
|
240
|
+
});
|
|
241
|
+
|
|
199
242
|
it('skips subdirectory .md files', () => {
|
|
200
243
|
const entries: ContentEntry[] = [
|
|
201
244
|
{ path: 'docs/guide.md', mimeType: 'text/markdown', size: 50 },
|
|
@@ -10,8 +10,6 @@ import { describe, it, expect, vi } from 'vitest';
|
|
|
10
10
|
import { markdownToDoc } from '../doc/markdownToDoc';
|
|
11
11
|
import { docToMarkdown } from '../doc/docToMarkdown';
|
|
12
12
|
import {
|
|
13
|
-
encodeLayersForFrontmatter,
|
|
14
|
-
decodeLayersFromFrontmatter,
|
|
15
13
|
readCustomTemplatesFromFrontmatter,
|
|
16
14
|
writeCustomTemplatesToFrontmatter,
|
|
17
15
|
FRONTMATTER_CUSTOM_TEMPLATES_KEY,
|
|
@@ -43,19 +41,6 @@ const heroDef: CustomTemplateDefinition = {
|
|
|
43
41
|
layers: sampleLayers,
|
|
44
42
|
};
|
|
45
43
|
|
|
46
|
-
describe('encodeLayersForFrontmatter ↔ decodeLayersFromFrontmatter', () => {
|
|
47
|
-
it('round-trips a single Layer array verbatim', () => {
|
|
48
|
-
const encoded = encodeLayersForFrontmatter(sampleLayers);
|
|
49
|
-
const decoded = decodeLayersFromFrontmatter(encoded);
|
|
50
|
-
expect(decoded).toEqual(sampleLayers);
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
it("returns [] when the base64 isn't valid JSON", () => {
|
|
54
|
-
const garbage = encodeLayersForFrontmatter(sampleLayers).slice(0, 4);
|
|
55
|
-
expect(decodeLayersFromFrontmatter(garbage)).toEqual([]);
|
|
56
|
-
});
|
|
57
|
-
});
|
|
58
|
-
|
|
59
44
|
describe('writeCustomTemplatesToFrontmatter → readCustomTemplatesFromFrontmatter', () => {
|
|
60
45
|
it('round-trips a single definition through the compact JSON wire format', () => {
|
|
61
46
|
const payload = writeCustomTemplatesToFrontmatter([heroDef]);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import {
|
|
2
|
+
import type { MaterializeBlockLayersOptions } from '../doc/materializeBlockLayers.js';
|
|
3
|
+
import { materializeLayers } from './materializeTestUtils.js';
|
|
3
4
|
import { DEFAULT_THEME } from '../schemas/themeLibrary.js';
|
|
4
5
|
import { VIEWPORT_PRESETS } from '../schemas/Viewport.js';
|
|
5
6
|
import type { TemplateBlock } from '../schemas/BlockTemplates.js';
|
|
@@ -24,7 +25,7 @@ function makeDataTableBlock(overrides: Record<string, unknown> = {}): TemplateBl
|
|
|
24
25
|
} as TemplateBlock;
|
|
25
26
|
}
|
|
26
27
|
|
|
27
|
-
const defaultContext:
|
|
28
|
+
const defaultContext: MaterializeBlockLayersOptions = {
|
|
28
29
|
theme: DEFAULT_THEME,
|
|
29
30
|
viewport: VIEWPORT_PRESETS.landscape,
|
|
30
31
|
blockIndex: 0,
|
|
@@ -37,7 +38,7 @@ const defaultContext: RenderContext = {
|
|
|
37
38
|
|
|
38
39
|
describe('dataTable template', () => {
|
|
39
40
|
it('produces layers including a table layer', () => {
|
|
40
|
-
const layers =
|
|
41
|
+
const layers = materializeLayers(makeDataTableBlock(), defaultContext);
|
|
41
42
|
expect(layers.length).toBeGreaterThan(0);
|
|
42
43
|
|
|
43
44
|
const tableLayer = layers.find((l) => l.type === 'table');
|
|
@@ -45,14 +46,14 @@ describe('dataTable template', () => {
|
|
|
45
46
|
});
|
|
46
47
|
|
|
47
48
|
it('table layer contains the correct headers', () => {
|
|
48
|
-
const layers =
|
|
49
|
+
const layers = materializeLayers(makeDataTableBlock(), defaultContext);
|
|
49
50
|
const tableLayer = layers.find((l) => l.type === 'table') as TableLayer;
|
|
50
51
|
|
|
51
52
|
expect(tableLayer.content.headers).toEqual(['Name', 'Value']);
|
|
52
53
|
});
|
|
53
54
|
|
|
54
55
|
it('table layer contains the correct rows', () => {
|
|
55
|
-
const layers =
|
|
56
|
+
const layers = materializeLayers(makeDataTableBlock(), defaultContext);
|
|
56
57
|
const tableLayer = layers.find((l) => l.type === 'table') as TableLayer;
|
|
57
58
|
|
|
58
59
|
expect(tableLayer.content.rows).toEqual([
|
|
@@ -62,7 +63,7 @@ describe('dataTable template', () => {
|
|
|
62
63
|
});
|
|
63
64
|
|
|
64
65
|
it('includes a title text layer when title is provided', () => {
|
|
65
|
-
const layers =
|
|
66
|
+
const layers = materializeLayers(makeDataTableBlock({ title: 'My Table' }), defaultContext);
|
|
66
67
|
const textLayers = layers.filter((l) => l.type === 'text');
|
|
67
68
|
|
|
68
69
|
expect(textLayers.length).toBeGreaterThanOrEqual(1);
|
|
@@ -71,7 +72,7 @@ describe('dataTable template', () => {
|
|
|
71
72
|
});
|
|
72
73
|
|
|
73
74
|
it('omits title layer when no title is given', () => {
|
|
74
|
-
const layers =
|
|
75
|
+
const layers = materializeLayers(makeDataTableBlock({ title: undefined }), defaultContext);
|
|
75
76
|
const textLayers = layers.filter((l) => l.type === 'text');
|
|
76
77
|
|
|
77
78
|
// Should have no text layers (only bg + table)
|
|
@@ -79,20 +80,23 @@ describe('dataTable template', () => {
|
|
|
79
80
|
});
|
|
80
81
|
|
|
81
82
|
it('includes a background layer', () => {
|
|
82
|
-
const layers =
|
|
83
|
+
const layers = materializeLayers(makeDataTableBlock(), defaultContext);
|
|
83
84
|
// Background is the first layer (shape with gradient fill)
|
|
84
85
|
expect(['shape', 'image']).toContain(layers[0].type);
|
|
85
86
|
});
|
|
86
87
|
|
|
87
88
|
it('passes column alignment to the table layer', () => {
|
|
88
|
-
const layers =
|
|
89
|
+
const layers = materializeLayers(
|
|
90
|
+
makeDataTableBlock({ align: ['left', 'right'] }),
|
|
91
|
+
defaultContext,
|
|
92
|
+
);
|
|
89
93
|
const tableLayer = layers.find((l) => l.type === 'table') as TableLayer;
|
|
90
94
|
|
|
91
95
|
expect(tableLayer.content.align).toEqual(['left', 'right']);
|
|
92
96
|
});
|
|
93
97
|
|
|
94
98
|
it('applies theme colors to table styling', () => {
|
|
95
|
-
const layers =
|
|
99
|
+
const layers = materializeLayers(makeDataTableBlock(), defaultContext);
|
|
96
100
|
const tableLayer = layers.find((l) => l.type === 'table') as TableLayer;
|
|
97
101
|
const style = tableLayer.content.style;
|
|
98
102
|
|
|
@@ -106,8 +110,11 @@ describe('dataTable template', () => {
|
|
|
106
110
|
});
|
|
107
111
|
|
|
108
112
|
it('adjusts table position when title is present vs absent', () => {
|
|
109
|
-
const withTitle =
|
|
110
|
-
const withoutTitle =
|
|
113
|
+
const withTitle = materializeLayers(makeDataTableBlock({ title: 'Title' }), defaultContext);
|
|
114
|
+
const withoutTitle = materializeLayers(
|
|
115
|
+
makeDataTableBlock({ title: undefined }),
|
|
116
|
+
defaultContext,
|
|
117
|
+
);
|
|
111
118
|
|
|
112
119
|
const tableWithTitle = withTitle.find((l) => l.type === 'table') as TableLayer;
|
|
113
120
|
const tableWithoutTitle = withoutTitle.find((l) => l.type === 'table') as TableLayer;
|
|
@@ -117,18 +124,21 @@ describe('dataTable template', () => {
|
|
|
117
124
|
});
|
|
118
125
|
|
|
119
126
|
it('works with portrait viewport', () => {
|
|
120
|
-
const portraitContext:
|
|
127
|
+
const portraitContext: MaterializeBlockLayersOptions = {
|
|
121
128
|
...defaultContext,
|
|
122
129
|
viewport: VIEWPORT_PRESETS.portrait,
|
|
123
130
|
};
|
|
124
|
-
const layers =
|
|
131
|
+
const layers = materializeLayers(makeDataTableBlock(), portraitContext);
|
|
125
132
|
expect(layers.length).toBeGreaterThan(0);
|
|
126
133
|
const tableLayer = layers.find((l) => l.type === 'table');
|
|
127
134
|
expect(tableLayer).toBeDefined();
|
|
128
135
|
});
|
|
129
136
|
|
|
130
137
|
it('handles empty rows gracefully', () => {
|
|
131
|
-
const layers =
|
|
138
|
+
const layers = materializeLayers(
|
|
139
|
+
makeDataTableBlock({ headers: ['A', 'B'], rows: [] }),
|
|
140
|
+
defaultContext,
|
|
141
|
+
);
|
|
132
142
|
const tableLayer = layers.find((l) => l.type === 'table') as TableLayer;
|
|
133
143
|
expect(tableLayer.content.rows).toEqual([]);
|
|
134
144
|
});
|
|
@@ -98,9 +98,9 @@ describe('diagramBlock template', () => {
|
|
|
98
98
|
});
|
|
99
99
|
});
|
|
100
100
|
|
|
101
|
-
describe('diagramBlock integration with
|
|
101
|
+
describe('diagramBlock integration with materializeBlockLayers', () => {
|
|
102
102
|
it('renders through the full template registry', async () => {
|
|
103
|
-
const {
|
|
103
|
+
const { materializeBlockLayers } = await import('../doc/materializeBlockLayers.js');
|
|
104
104
|
const parentBlock: Block = {
|
|
105
105
|
id: 'family',
|
|
106
106
|
startTime: 0,
|
|
@@ -113,7 +113,7 @@ describe('diagramBlock integration with getLayers', () => {
|
|
|
113
113
|
makeBlock({ id: 'b', title: 'B', x: 400, y: 100, connectsTo: [{ target: 'a' }] }),
|
|
114
114
|
],
|
|
115
115
|
};
|
|
116
|
-
const layers =
|
|
116
|
+
const layers = materializeBlockLayers(parentBlock, { persistentLayers: false }).layers;
|
|
117
117
|
// Should contain at least one shape (node card) and one path (edge).
|
|
118
118
|
const types = new Set(layers.map((l: Layer) => l.type));
|
|
119
119
|
expect(types.has('shape')).toBe(true);
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { DIAGRAM_LABEL_LINE_HEIGHT, fitDiagramLabel } from '../doc/utils/diagramText';
|
|
3
|
+
|
|
4
|
+
describe('fitDiagramLabel', () => {
|
|
5
|
+
it('keeps a short single-line label at its preferred size', () => {
|
|
6
|
+
expect(fitDiagramLabel('Web1', 180, 64, 22)).toMatchObject({
|
|
7
|
+
fontSize: 22,
|
|
8
|
+
lineCount: 1,
|
|
9
|
+
firstLineOffset: 0,
|
|
10
|
+
});
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it('shrinks and centers a dense multi-line package label inside its card', () => {
|
|
14
|
+
const fit = fitDiagramLabel(
|
|
15
|
+
'@bendyline/molen-kernel\nheadless sim — no DOM, no thread\nWorker + Node',
|
|
16
|
+
252,
|
|
17
|
+
112,
|
|
18
|
+
38,
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
expect(fit.fontSize).toBeLessThan(38);
|
|
22
|
+
expect(fit.lineCount).toBeGreaterThanOrEqual(3);
|
|
23
|
+
expect(fit.lineCount * fit.fontSize * DIAGRAM_LABEL_LINE_HEIGHT).toBeLessThanOrEqual(96);
|
|
24
|
+
expect(fit.firstLineOffset).toBeLessThan(0);
|
|
25
|
+
});
|
|
26
|
+
});
|