@bendyline/squisq 0.1.2 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{themeLibrary-DySHPcgj.d.ts → Doc-1hmT1LZl.d.ts} +1 -42
- package/dist/__tests__/animationUtils.test.d.ts +2 -0
- package/dist/__tests__/animationUtils.test.d.ts.map +1 -0
- package/dist/__tests__/animationUtils.test.js +60 -0
- package/dist/__tests__/animationUtils.test.js.map +1 -0
- package/dist/__tests__/contentExtractor.test.d.ts +2 -0
- package/dist/__tests__/contentExtractor.test.d.ts.map +1 -0
- package/dist/__tests__/contentExtractor.test.js +151 -0
- package/dist/__tests__/contentExtractor.test.js.map +1 -0
- package/dist/__tests__/geohash.test.d.ts +2 -0
- package/dist/__tests__/geohash.test.d.ts.map +1 -0
- package/dist/__tests__/geohash.test.js +87 -0
- package/dist/__tests__/geohash.test.js.map +1 -0
- package/dist/__tests__/getLayers.test.d.ts +2 -0
- package/dist/__tests__/getLayers.test.d.ts.map +1 -0
- package/dist/__tests__/getLayers.test.js +167 -0
- package/dist/__tests__/getLayers.test.js.map +1 -0
- package/dist/__tests__/haversine.test.d.ts +2 -0
- package/dist/__tests__/haversine.test.d.ts.map +1 -0
- package/dist/__tests__/haversine.test.js +56 -0
- package/dist/__tests__/haversine.test.js.map +1 -0
- package/dist/__tests__/localForageAdapter.test.d.ts +8 -0
- package/dist/__tests__/localForageAdapter.test.d.ts.map +1 -0
- package/dist/__tests__/localForageAdapter.test.js +130 -0
- package/dist/__tests__/localForageAdapter.test.js.map +1 -0
- package/dist/__tests__/markdown.test.d.ts +2 -0
- package/dist/__tests__/markdown.test.d.ts.map +1 -0
- package/dist/__tests__/markdown.test.js +654 -0
- package/dist/__tests__/markdown.test.js.map +1 -0
- package/dist/__tests__/markdownToDoc.test.d.ts +2 -0
- package/dist/__tests__/markdownToDoc.test.d.ts.map +1 -0
- package/dist/__tests__/markdownToDoc.test.js +313 -0
- package/dist/__tests__/markdownToDoc.test.js.map +1 -0
- package/dist/__tests__/seededRandom.test.d.ts +2 -0
- package/dist/__tests__/seededRandom.test.d.ts.map +1 -0
- package/dist/__tests__/seededRandom.test.js +125 -0
- package/dist/__tests__/seededRandom.test.js.map +1 -0
- package/dist/__tests__/slideshowGenerator.test.d.ts +2 -0
- package/dist/__tests__/slideshowGenerator.test.d.ts.map +1 -0
- package/dist/__tests__/slideshowGenerator.test.js +114 -0
- package/dist/__tests__/slideshowGenerator.test.js.map +1 -0
- package/dist/__tests__/storage.test.d.ts +2 -0
- package/dist/__tests__/storage.test.d.ts.map +1 -0
- package/dist/__tests__/storage.test.js +51 -0
- package/dist/__tests__/storage.test.js.map +1 -0
- package/dist/__tests__/templates.test.d.ts +2 -0
- package/dist/__tests__/templates.test.d.ts.map +1 -0
- package/dist/__tests__/templates.test.js +217 -0
- package/dist/__tests__/templates.test.js.map +1 -0
- package/dist/__tests__/timing.test.d.ts +2 -0
- package/dist/__tests__/timing.test.d.ts.map +1 -0
- package/dist/__tests__/timing.test.js +131 -0
- package/dist/__tests__/timing.test.js.map +1 -0
- package/dist/chunk-3E5F2XMR.js +129 -0
- package/dist/chunk-3E5F2XMR.js.map +1 -0
- package/dist/chunk-6YJPI6VI.js +35 -0
- package/dist/chunk-6YJPI6VI.js.map +1 -0
- package/dist/chunk-FVTQSNR7.js +100 -0
- package/dist/chunk-FVTQSNR7.js.map +1 -0
- package/dist/chunk-V543AI2F.js +1 -0
- package/dist/chunk-V543AI2F.js.map +1 -0
- package/dist/chunk-VP5SZTZ2.js +792 -0
- package/dist/chunk-VP5SZTZ2.js.map +1 -0
- package/dist/doc/docToMarkdown.d.ts +41 -0
- package/dist/doc/docToMarkdown.d.ts.map +1 -0
- package/dist/doc/docToMarkdown.js +88 -0
- package/dist/doc/docToMarkdown.js.map +1 -0
- package/dist/doc/getLayers.d.ts +65 -0
- package/dist/doc/getLayers.d.ts.map +1 -0
- package/dist/doc/getLayers.js +103 -0
- package/dist/doc/getLayers.js.map +1 -0
- package/dist/doc/index.d.ts +9 -3
- package/dist/doc/index.d.ts.map +1 -0
- package/dist/doc/index.js +6 -125
- package/dist/doc/index.js.map +1 -1
- package/dist/doc/markdownToDoc.d.ts +68 -0
- package/dist/doc/markdownToDoc.d.ts.map +1 -0
- package/dist/doc/markdownToDoc.js +225 -0
- package/dist/doc/markdownToDoc.js.map +1 -0
- package/dist/doc/templates/accentImage.d.ts +59 -0
- package/dist/doc/templates/accentImage.d.ts.map +1 -0
- package/dist/doc/templates/accentImage.js +275 -0
- package/dist/doc/templates/accentImage.js.map +1 -0
- package/dist/doc/templates/captionUtils.d.ts +20 -0
- package/dist/doc/templates/captionUtils.d.ts.map +1 -0
- package/dist/doc/templates/captionUtils.js +29 -0
- package/dist/doc/templates/captionUtils.js.map +1 -0
- package/dist/doc/templates/comparisonBar.d.ts +13 -0
- package/dist/doc/templates/comparisonBar.d.ts.map +1 -0
- package/dist/doc/templates/comparisonBar.js +127 -0
- package/dist/doc/templates/comparisonBar.js.map +1 -0
- package/dist/doc/templates/coverBlock.d.ts +51 -0
- package/dist/doc/templates/coverBlock.d.ts.map +1 -0
- package/dist/doc/templates/coverBlock.js +125 -0
- package/dist/doc/templates/coverBlock.js.map +1 -0
- package/dist/doc/templates/dateEvent.d.ts +15 -0
- package/dist/doc/templates/dateEvent.d.ts.map +1 -0
- package/dist/doc/templates/dateEvent.js +110 -0
- package/dist/doc/templates/dateEvent.js.map +1 -0
- package/dist/doc/templates/definitionCard.d.ts +13 -0
- package/dist/doc/templates/definitionCard.d.ts.map +1 -0
- package/dist/doc/templates/definitionCard.js +114 -0
- package/dist/doc/templates/definitionCard.js.map +1 -0
- package/dist/doc/templates/factCard.d.ts +15 -0
- package/dist/doc/templates/factCard.d.ts.map +1 -0
- package/dist/doc/templates/factCard.js +100 -0
- package/dist/doc/templates/factCard.js.map +1 -0
- package/dist/doc/templates/fullBleedQuote.d.ts +13 -0
- package/dist/doc/templates/fullBleedQuote.d.ts.map +1 -0
- package/dist/doc/templates/fullBleedQuote.js +54 -0
- package/dist/doc/templates/fullBleedQuote.js.map +1 -0
- package/dist/doc/templates/imageWithCaption.d.ts +13 -0
- package/dist/doc/templates/imageWithCaption.d.ts.map +1 -0
- package/dist/doc/templates/imageWithCaption.js +142 -0
- package/dist/doc/templates/imageWithCaption.js.map +1 -0
- package/dist/doc/templates/index.d.ts +104 -0
- package/dist/doc/templates/index.d.ts.map +1 -0
- package/dist/doc/templates/index.js +442 -0
- package/dist/doc/templates/index.js.map +1 -0
- package/dist/doc/templates/listBlock.d.ts +13 -0
- package/dist/doc/templates/listBlock.d.ts.map +1 -0
- package/dist/doc/templates/listBlock.js +89 -0
- package/dist/doc/templates/listBlock.js.map +1 -0
- package/dist/doc/templates/mapBlock.d.ts +17 -0
- package/dist/doc/templates/mapBlock.d.ts.map +1 -0
- package/dist/doc/templates/mapBlock.js +112 -0
- package/dist/doc/templates/mapBlock.js.map +1 -0
- package/dist/doc/templates/persistentLayers.d.ts +39 -0
- package/dist/doc/templates/persistentLayers.d.ts.map +1 -0
- package/dist/doc/templates/persistentLayers.js +447 -0
- package/dist/doc/templates/persistentLayers.js.map +1 -0
- package/dist/doc/templates/photoGrid.d.ts +20 -0
- package/dist/doc/templates/photoGrid.d.ts.map +1 -0
- package/dist/doc/templates/photoGrid.js +172 -0
- package/dist/doc/templates/photoGrid.js.map +1 -0
- package/dist/doc/templates/pullQuote.d.ts +13 -0
- package/dist/doc/templates/pullQuote.d.ts.map +1 -0
- package/dist/doc/templates/pullQuote.js +109 -0
- package/dist/doc/templates/pullQuote.js.map +1 -0
- package/dist/doc/templates/quoteBlock.d.ts +15 -0
- package/dist/doc/templates/quoteBlock.d.ts.map +1 -0
- package/dist/doc/templates/quoteBlock.js +97 -0
- package/dist/doc/templates/quoteBlock.js.map +1 -0
- package/dist/doc/templates/sectionHeader.d.ts +14 -0
- package/dist/doc/templates/sectionHeader.d.ts.map +1 -0
- package/dist/doc/templates/sectionHeader.js +96 -0
- package/dist/doc/templates/sectionHeader.js.map +1 -0
- package/dist/doc/templates/statHighlight.d.ts +15 -0
- package/dist/doc/templates/statHighlight.d.ts.map +1 -0
- package/dist/doc/templates/statHighlight.js +99 -0
- package/dist/doc/templates/statHighlight.js.map +1 -0
- package/dist/doc/templates/titleBlock.d.ts +13 -0
- package/dist/doc/templates/titleBlock.d.ts.map +1 -0
- package/dist/doc/templates/titleBlock.js +92 -0
- package/dist/doc/templates/titleBlock.js.map +1 -0
- package/dist/doc/templates/twoColumn.d.ts +15 -0
- package/dist/doc/templates/twoColumn.d.ts.map +1 -0
- package/dist/doc/templates/twoColumn.js +219 -0
- package/dist/doc/templates/twoColumn.js.map +1 -0
- package/dist/doc/templates/videoPullQuote.d.ts +20 -0
- package/dist/doc/templates/videoPullQuote.d.ts.map +1 -0
- package/dist/doc/templates/videoPullQuote.js +118 -0
- package/dist/doc/templates/videoPullQuote.js.map +1 -0
- package/dist/doc/templates/videoWithCaption.d.ts +20 -0
- package/dist/doc/templates/videoWithCaption.d.ts.map +1 -0
- package/dist/doc/templates/videoWithCaption.js +104 -0
- package/dist/doc/templates/videoWithCaption.js.map +1 -0
- package/dist/doc/utils/animationUtils.d.ts +34 -0
- package/dist/doc/utils/animationUtils.d.ts.map +1 -0
- package/dist/doc/utils/animationUtils.js +110 -0
- package/dist/doc/utils/animationUtils.js.map +1 -0
- package/dist/doc/utils/themeUtils.d.ts +55 -0
- package/dist/doc/utils/themeUtils.d.ts.map +1 -0
- package/dist/doc/utils/themeUtils.js +109 -0
- package/dist/doc/utils/themeUtils.js.map +1 -0
- package/dist/generate/contentExtractor.d.ts +112 -0
- package/dist/generate/contentExtractor.d.ts.map +1 -0
- package/dist/generate/contentExtractor.js +638 -0
- package/dist/generate/contentExtractor.js.map +1 -0
- package/dist/generate/index.d.ts +7 -0
- package/dist/generate/index.d.ts.map +1 -0
- package/dist/generate/index.js +4 -0
- package/dist/generate/index.js.map +1 -0
- package/dist/generate/slideshowGenerator.d.ts +72 -0
- package/dist/generate/slideshowGenerator.d.ts.map +1 -0
- package/dist/generate/slideshowGenerator.js +159 -0
- package/dist/generate/slideshowGenerator.js.map +1 -0
- package/dist/generate/templateMapper.d.ts +23 -0
- package/dist/generate/templateMapper.d.ts.map +1 -0
- package/dist/generate/templateMapper.js +114 -0
- package/dist/generate/templateMapper.js.map +1 -0
- package/dist/index.d.ts +9 -8
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -216
- package/dist/index.js.map +1 -1
- package/dist/markdown/convert.d.ts +71 -0
- package/dist/markdown/convert.d.ts.map +1 -0
- package/dist/markdown/convert.js +746 -0
- package/dist/markdown/convert.js.map +1 -0
- package/dist/markdown/htmlParse.d.ts +26 -0
- package/dist/markdown/htmlParse.d.ts.map +1 -0
- package/dist/markdown/htmlParse.js +195 -0
- package/dist/markdown/htmlParse.js.map +1 -0
- package/dist/markdown/index.d.ts +19 -225
- package/dist/markdown/index.d.ts.map +1 -0
- package/dist/markdown/index.js +33 -32
- package/dist/markdown/index.js.map +1 -1
- package/dist/markdown/parse.d.ts +27 -0
- package/dist/markdown/parse.d.ts.map +1 -0
- package/dist/markdown/parse.js +90 -0
- package/dist/markdown/parse.js.map +1 -0
- package/dist/markdown/stringify.d.ts +32 -0
- package/dist/markdown/stringify.d.ts.map +1 -0
- package/dist/markdown/stringify.js +108 -0
- package/dist/markdown/stringify.js.map +1 -0
- package/dist/markdown/types.d.ts +496 -0
- package/dist/markdown/types.d.ts.map +1 -0
- package/dist/markdown/types.js +17 -0
- package/dist/markdown/types.js.map +1 -0
- package/dist/markdown/utils.d.ts +78 -0
- package/dist/markdown/utils.d.ts.map +1 -0
- package/dist/markdown/utils.js +149 -0
- package/dist/markdown/utils.js.map +1 -0
- package/dist/random/SeededRandom.d.ts +50 -0
- package/dist/random/SeededRandom.d.ts.map +1 -0
- package/dist/random/SeededRandom.js +115 -0
- package/dist/random/SeededRandom.js.map +1 -0
- package/dist/random/index.d.ts +2 -0
- package/dist/random/index.d.ts.map +1 -0
- package/dist/random/index.js +2 -0
- package/dist/random/index.js.map +1 -0
- package/dist/schemas/BlockTemplates.d.ts +583 -0
- package/dist/schemas/BlockTemplates.d.ts.map +1 -0
- package/dist/schemas/BlockTemplates.js +72 -0
- package/dist/schemas/BlockTemplates.js.map +1 -0
- package/dist/schemas/Doc.d.ts +411 -0
- package/dist/schemas/Doc.d.ts.map +1 -0
- package/dist/schemas/Doc.js +90 -0
- package/dist/schemas/Doc.js.map +1 -0
- package/dist/schemas/LayoutStrategy.d.ts +84 -0
- package/dist/schemas/LayoutStrategy.d.ts.map +1 -0
- package/dist/schemas/LayoutStrategy.js +123 -0
- package/dist/schemas/LayoutStrategy.js.map +1 -0
- package/dist/schemas/MediaProvider.d.ts +65 -0
- package/dist/schemas/MediaProvider.d.ts.map +1 -0
- package/dist/schemas/MediaProvider.js +13 -0
- package/dist/schemas/MediaProvider.js.map +1 -0
- package/dist/schemas/Theme.d.ts +163 -0
- package/dist/schemas/Theme.d.ts.map +1 -0
- package/dist/schemas/Theme.js +62 -0
- package/dist/schemas/Theme.js.map +1 -0
- package/dist/schemas/Types.d.ts +14 -0
- package/dist/schemas/Types.d.ts.map +1 -0
- package/dist/schemas/Types.js +5 -0
- package/dist/schemas/Types.js.map +1 -0
- package/dist/schemas/Viewport.d.ts +74 -0
- package/dist/schemas/Viewport.d.ts.map +1 -0
- package/dist/schemas/Viewport.js +63 -0
- package/dist/schemas/Viewport.js.map +1 -0
- package/dist/schemas/index.d.ts +10 -70
- package/dist/schemas/index.d.ts.map +1 -0
- package/dist/schemas/index.js +7 -60
- package/dist/schemas/index.js.map +1 -1
- package/dist/schemas/themeLibrary.d.ts +41 -0
- package/dist/schemas/themeLibrary.d.ts.map +1 -0
- package/dist/schemas/themeLibrary.js +466 -0
- package/dist/schemas/themeLibrary.js.map +1 -0
- package/dist/spatial/Geohash.d.ts +85 -0
- package/dist/spatial/Geohash.d.ts.map +1 -0
- package/dist/spatial/Geohash.js +142 -0
- package/dist/spatial/Geohash.js.map +1 -0
- package/dist/spatial/Haversine.d.ts +24 -0
- package/dist/spatial/Haversine.d.ts.map +1 -0
- package/dist/spatial/Haversine.js +48 -0
- package/dist/spatial/Haversine.js.map +1 -0
- package/dist/spatial/index.d.ts +3 -111
- package/dist/spatial/index.d.ts.map +1 -0
- package/dist/spatial/index.js +2 -24
- package/dist/spatial/index.js.map +1 -1
- package/dist/storage/LocalForageAdapter.d.ts +36 -0
- package/dist/storage/LocalForageAdapter.d.ts.map +1 -0
- package/dist/storage/LocalForageAdapter.js +82 -0
- package/dist/storage/LocalForageAdapter.js.map +1 -0
- package/dist/storage/LocalStorageAdapter.d.ts +16 -0
- package/dist/storage/LocalStorageAdapter.d.ts.map +1 -0
- package/dist/storage/LocalStorageAdapter.js +50 -0
- package/dist/storage/LocalStorageAdapter.js.map +1 -0
- package/dist/storage/MemoryStorageAdapter.d.ts +15 -0
- package/dist/storage/MemoryStorageAdapter.d.ts.map +1 -0
- package/dist/storage/MemoryStorageAdapter.js +34 -0
- package/dist/storage/MemoryStorageAdapter.js.map +1 -0
- package/dist/storage/Storage.d.ts +23 -0
- package/dist/storage/Storage.d.ts.map +1 -0
- package/dist/storage/Storage.js +9 -0
- package/dist/storage/Storage.js.map +1 -0
- package/dist/storage/index.d.ts +6 -91
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +3 -10
- package/dist/storage/index.js.map +1 -1
- package/dist/story/index.d.ts +3 -2
- package/dist/themeLibrary-DR1VRo-8.d.ts +44 -0
- package/dist/timing/index.d.ts +4 -0
- package/dist/timing/index.d.ts.map +1 -0
- package/dist/timing/index.js +3 -0
- package/dist/timing/index.js.map +1 -0
- package/dist/timing/narrationTiming.d.ts +54 -0
- package/dist/timing/narrationTiming.d.ts.map +1 -0
- package/dist/timing/narrationTiming.js +190 -0
- package/dist/timing/narrationTiming.js.map +1 -0
- package/dist/timing/readingTime.d.ts +54 -0
- package/dist/timing/readingTime.d.ts.map +1 -0
- package/dist/timing/readingTime.js +49 -0
- package/dist/timing/readingTime.js.map +1 -0
- package/package.json +17 -3
- package/src/__tests__/contentExtractor.test.ts +183 -0
- package/src/__tests__/seededRandom.test.ts +142 -0
- package/src/__tests__/slideshowGenerator.test.ts +128 -0
- package/src/__tests__/timing.test.ts +167 -0
- package/src/generate/contentExtractor.ts +808 -0
- package/src/generate/index.ts +21 -0
- package/src/generate/slideshowGenerator.ts +273 -0
- package/src/generate/templateMapper.ts +170 -0
- package/src/index.ts +3 -0
- package/src/random/SeededRandom.ts +127 -0
- package/src/random/index.ts +1 -0
- package/src/timing/index.ts +17 -0
- package/src/timing/narrationTiming.ts +220 -0
- package/src/timing/readingTime.ts +94 -0
|
@@ -0,0 +1,654 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { parseMarkdown, stringifyMarkdown, parseHtmlToNodes, stringifyHtmlNodes, getChildren, walkMarkdownTree, findNodesByType, extractPlainText, countNodes, createDocument, parseFrontmatter, } from '../markdown/index';
|
|
3
|
+
// ============================================
|
|
4
|
+
// Helper: strip positions for cleaner assertions
|
|
5
|
+
// ============================================
|
|
6
|
+
function stripPositions(obj) {
|
|
7
|
+
if (obj === null || obj === undefined)
|
|
8
|
+
return obj;
|
|
9
|
+
if (typeof obj !== 'object')
|
|
10
|
+
return obj;
|
|
11
|
+
if (Array.isArray(obj))
|
|
12
|
+
return obj.map(stripPositions);
|
|
13
|
+
const result = {};
|
|
14
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
15
|
+
if (key === 'position')
|
|
16
|
+
continue;
|
|
17
|
+
result[key] = stripPositions(value);
|
|
18
|
+
}
|
|
19
|
+
return result;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Parse, strip positions, and return the tree.
|
|
23
|
+
*/
|
|
24
|
+
function parse(md) {
|
|
25
|
+
return stripPositions(parseMarkdown(md));
|
|
26
|
+
}
|
|
27
|
+
// ============================================
|
|
28
|
+
// CommonMark Basics
|
|
29
|
+
// ============================================
|
|
30
|
+
describe('parseMarkdown / stringifyMarkdown', () => {
|
|
31
|
+
describe('CommonMark basics', () => {
|
|
32
|
+
it('parses headings', () => {
|
|
33
|
+
const doc = parse('# H1\n\n## H2\n\n### H3');
|
|
34
|
+
expect(doc.children).toHaveLength(3);
|
|
35
|
+
const h1 = doc.children[0];
|
|
36
|
+
expect(h1.type).toBe('heading');
|
|
37
|
+
expect(h1.depth).toBe(1);
|
|
38
|
+
expect(h1.children[0]).toEqual({ type: 'text', value: 'H1' });
|
|
39
|
+
const h2 = doc.children[1];
|
|
40
|
+
expect(h2.depth).toBe(2);
|
|
41
|
+
const h3 = doc.children[2];
|
|
42
|
+
expect(h3.depth).toBe(3);
|
|
43
|
+
});
|
|
44
|
+
it('parses paragraphs with inline formatting', () => {
|
|
45
|
+
const doc = parse('Hello **bold** and *italic* text');
|
|
46
|
+
const p = doc.children[0];
|
|
47
|
+
expect(p.type).toBe('paragraph');
|
|
48
|
+
expect(p.children).toHaveLength(5);
|
|
49
|
+
expect(p.children[0]).toEqual({ type: 'text', value: 'Hello ' });
|
|
50
|
+
expect(p.children[1]).toEqual({
|
|
51
|
+
type: 'strong',
|
|
52
|
+
children: [{ type: 'text', value: 'bold' }],
|
|
53
|
+
});
|
|
54
|
+
expect(p.children[2]).toEqual({ type: 'text', value: ' and ' });
|
|
55
|
+
expect(p.children[3]).toEqual({
|
|
56
|
+
type: 'emphasis',
|
|
57
|
+
children: [{ type: 'text', value: 'italic' }],
|
|
58
|
+
});
|
|
59
|
+
expect(p.children[4]).toEqual({ type: 'text', value: ' text' });
|
|
60
|
+
});
|
|
61
|
+
it('parses unordered lists', () => {
|
|
62
|
+
const doc = parse('- Item 1\n- Item 2\n- Item 3');
|
|
63
|
+
const list = doc.children[0];
|
|
64
|
+
expect(list.type).toBe('list');
|
|
65
|
+
expect(list.ordered).toBe(false);
|
|
66
|
+
expect(list.children).toHaveLength(3);
|
|
67
|
+
const item = list.children[0];
|
|
68
|
+
expect(item.type).toBe('listItem');
|
|
69
|
+
const itemP = item.children[0];
|
|
70
|
+
expect(itemP.children[0]).toEqual({ type: 'text', value: 'Item 1' });
|
|
71
|
+
});
|
|
72
|
+
it('parses ordered lists', () => {
|
|
73
|
+
const doc = parse('1. First\n2. Second\n3. Third');
|
|
74
|
+
const list = doc.children[0];
|
|
75
|
+
expect(list.ordered).toBe(true);
|
|
76
|
+
expect(list.start).toBe(1);
|
|
77
|
+
expect(list.children).toHaveLength(3);
|
|
78
|
+
});
|
|
79
|
+
it('parses code blocks', () => {
|
|
80
|
+
const doc = parse('```typescript\nconst x = 1;\n```');
|
|
81
|
+
const code = doc.children[0];
|
|
82
|
+
expect(code.type).toBe('code');
|
|
83
|
+
expect(code.lang).toBe('typescript');
|
|
84
|
+
expect(code.value).toBe('const x = 1;');
|
|
85
|
+
});
|
|
86
|
+
it('parses blockquotes', () => {
|
|
87
|
+
const doc = parse('> This is a quote\n>\n> With two paragraphs');
|
|
88
|
+
const bq = doc.children[0];
|
|
89
|
+
expect(bq.type).toBe('blockquote');
|
|
90
|
+
expect(bq.children).toHaveLength(2);
|
|
91
|
+
});
|
|
92
|
+
it('parses inline code', () => {
|
|
93
|
+
const doc = parse('Use `console.log()` for debugging');
|
|
94
|
+
const p = doc.children[0];
|
|
95
|
+
expect(p.children[1]).toEqual({ type: 'inlineCode', value: 'console.log()' });
|
|
96
|
+
});
|
|
97
|
+
it('parses links', () => {
|
|
98
|
+
const doc = parse('[Click here](https://example.com "Title")');
|
|
99
|
+
const p = doc.children[0];
|
|
100
|
+
const link = p.children[0];
|
|
101
|
+
expect(link.type).toBe('link');
|
|
102
|
+
expect(link.url).toBe('https://example.com');
|
|
103
|
+
expect(link.title).toBe('Title');
|
|
104
|
+
expect(link.children[0]).toEqual({ type: 'text', value: 'Click here' });
|
|
105
|
+
});
|
|
106
|
+
it('parses images', () => {
|
|
107
|
+
const doc = parse('');
|
|
108
|
+
const p = doc.children[0];
|
|
109
|
+
const img = p.children[0];
|
|
110
|
+
expect(img.type).toBe('image');
|
|
111
|
+
expect(img.url).toBe('image.png');
|
|
112
|
+
expect(img.alt).toBe('Alt text');
|
|
113
|
+
expect(img.title).toBe('Image title');
|
|
114
|
+
});
|
|
115
|
+
it('parses thematic breaks', () => {
|
|
116
|
+
const doc = parse('Before\n\n---\n\nAfter');
|
|
117
|
+
expect(doc.children).toHaveLength(3);
|
|
118
|
+
expect(doc.children[1].type).toBe('thematicBreak');
|
|
119
|
+
});
|
|
120
|
+
it('parses hard line breaks', () => {
|
|
121
|
+
const doc = parse('Line one \nLine two');
|
|
122
|
+
const p = doc.children[0];
|
|
123
|
+
expect(p.children).toHaveLength(3);
|
|
124
|
+
expect(p.children[1].type).toBe('break');
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
// ============================================
|
|
128
|
+
// GFM Extensions
|
|
129
|
+
// ============================================
|
|
130
|
+
describe('GFM extensions', () => {
|
|
131
|
+
it('parses tables', () => {
|
|
132
|
+
const md = '| Name | Age |\n| --- | --- |\n| Alice | 30 |\n| Bob | 25 |';
|
|
133
|
+
const doc = parse(md);
|
|
134
|
+
const table = doc.children[0];
|
|
135
|
+
expect(table.type).toBe('table');
|
|
136
|
+
expect(table.children).toHaveLength(3); // header + 2 data rows
|
|
137
|
+
// Header row
|
|
138
|
+
const headerRow = table.children[0];
|
|
139
|
+
expect(headerRow.children[0].isHeader).toBe(true);
|
|
140
|
+
expect(headerRow.children[0].children[0]).toEqual({ type: 'text', value: 'Name' });
|
|
141
|
+
// Data row
|
|
142
|
+
const dataRow = table.children[1];
|
|
143
|
+
expect(dataRow.children[0].isHeader).toBeUndefined();
|
|
144
|
+
});
|
|
145
|
+
it('parses strikethrough', () => {
|
|
146
|
+
const doc = parse('This is ~~deleted~~ text');
|
|
147
|
+
const p = doc.children[0];
|
|
148
|
+
const del = p.children[1];
|
|
149
|
+
expect(del.type).toBe('delete');
|
|
150
|
+
expect(del.children[0]).toEqual({ type: 'text', value: 'deleted' });
|
|
151
|
+
});
|
|
152
|
+
it('parses task lists', () => {
|
|
153
|
+
const doc = parse('- [x] Done\n- [ ] Todo\n- Regular');
|
|
154
|
+
const list = doc.children[0];
|
|
155
|
+
expect(list.children[0].checked).toBe(true);
|
|
156
|
+
expect(list.children[1].checked).toBe(false);
|
|
157
|
+
// Regular item has no checked property
|
|
158
|
+
expect(list.children[2].checked).toBeUndefined();
|
|
159
|
+
});
|
|
160
|
+
it('parses footnotes', () => {
|
|
161
|
+
const md = 'Text with a footnote[^1].\n\n[^1]: This is the footnote content.';
|
|
162
|
+
const doc = parse(md);
|
|
163
|
+
// Find footnote reference in the paragraph
|
|
164
|
+
const p = doc.children[0];
|
|
165
|
+
const ref = p.children.find((c) => c.type === 'footnoteReference');
|
|
166
|
+
expect(ref).toBeDefined();
|
|
167
|
+
expect(ref.identifier).toBe('1');
|
|
168
|
+
// Find footnote definition
|
|
169
|
+
const fnDef = doc.children.find((c) => c.type === 'footnoteDefinition');
|
|
170
|
+
expect(fnDef).toBeDefined();
|
|
171
|
+
expect(fnDef.identifier).toBe('1');
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
// ============================================
|
|
175
|
+
// Math Extensions
|
|
176
|
+
// ============================================
|
|
177
|
+
describe('Math extensions', () => {
|
|
178
|
+
it('parses inline math', () => {
|
|
179
|
+
const doc = parse('The equation $E = mc^2$ is famous');
|
|
180
|
+
const p = doc.children[0];
|
|
181
|
+
const math = p.children.find((c) => c.type === 'inlineMath');
|
|
182
|
+
expect(math).toBeDefined();
|
|
183
|
+
expect(math.value).toBe('E = mc^2');
|
|
184
|
+
});
|
|
185
|
+
it('parses display math blocks', () => {
|
|
186
|
+
const doc = parse('$$\nx = \\frac{-b \\pm \\sqrt{b^2 - 4ac}}{2a}\n$$');
|
|
187
|
+
const math = doc.children[0];
|
|
188
|
+
expect(math.type).toBe('math');
|
|
189
|
+
expect(math.value).toContain('\\frac{-b');
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
// ============================================
|
|
193
|
+
// Link/Image References
|
|
194
|
+
// ============================================
|
|
195
|
+
describe('Reference links', () => {
|
|
196
|
+
it('parses link reference definitions', () => {
|
|
197
|
+
const md = '[example]: https://example.com "Example"\n\nSee [example].';
|
|
198
|
+
const doc = parse(md);
|
|
199
|
+
const def = doc.children.find((c) => c.type === 'definition');
|
|
200
|
+
expect(def).toBeDefined();
|
|
201
|
+
expect(def.identifier).toBe('example');
|
|
202
|
+
expect(def.url).toBe('https://example.com');
|
|
203
|
+
expect(def.title).toBe('Example');
|
|
204
|
+
});
|
|
205
|
+
it('parses link references', () => {
|
|
206
|
+
const md = '[Click here][ref]\n\n[ref]: https://example.com';
|
|
207
|
+
const doc = parse(md);
|
|
208
|
+
const p = doc.children[0];
|
|
209
|
+
const ref = p.children[0];
|
|
210
|
+
expect(ref.type).toBe('linkReference');
|
|
211
|
+
expect(ref.identifier).toBe('ref');
|
|
212
|
+
expect(ref.referenceType).toBe('full');
|
|
213
|
+
});
|
|
214
|
+
it('parses image references', () => {
|
|
215
|
+
const md = '![Alt text][img]\n\n[img]: image.png';
|
|
216
|
+
const doc = parse(md);
|
|
217
|
+
const p = doc.children[0];
|
|
218
|
+
const ref = p.children[0];
|
|
219
|
+
expect(ref.type).toBe('imageReference');
|
|
220
|
+
expect(ref.identifier).toBe('img');
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
// ============================================
|
|
224
|
+
// HTML Handling
|
|
225
|
+
// ============================================
|
|
226
|
+
describe('HTML handling', () => {
|
|
227
|
+
it('parses block-level HTML', () => {
|
|
228
|
+
const doc = parse('<div class="container">\n <p>Hello</p>\n</div>');
|
|
229
|
+
const html = doc.children[0];
|
|
230
|
+
expect(html.type).toBe('htmlBlock');
|
|
231
|
+
expect(html.rawHtml).toContain('<div');
|
|
232
|
+
expect(html.htmlChildren.length).toBeGreaterThan(0);
|
|
233
|
+
});
|
|
234
|
+
it('parses inline HTML', () => {
|
|
235
|
+
const doc = parse('Text with <em>emphasis</em> inline');
|
|
236
|
+
const p = doc.children[0];
|
|
237
|
+
const htmlNodes = p.children.filter((c) => c.type === 'htmlInline');
|
|
238
|
+
expect(htmlNodes.length).toBeGreaterThan(0);
|
|
239
|
+
});
|
|
240
|
+
it('preserves rawHtml for round-tripping', () => {
|
|
241
|
+
const input = '<div class="test">content</div>';
|
|
242
|
+
const doc = parse(input);
|
|
243
|
+
const html = doc.children[0];
|
|
244
|
+
expect(html.rawHtml).toBe(input);
|
|
245
|
+
});
|
|
246
|
+
});
|
|
247
|
+
// ============================================
|
|
248
|
+
// Nested Structures
|
|
249
|
+
// ============================================
|
|
250
|
+
describe('Nested structures', () => {
|
|
251
|
+
it('handles blockquote containing list', () => {
|
|
252
|
+
const md = '> - Item 1\n> - Item 2';
|
|
253
|
+
const doc = parse(md);
|
|
254
|
+
const bq = doc.children[0];
|
|
255
|
+
expect(bq.type).toBe('blockquote');
|
|
256
|
+
const list = bq.children[0];
|
|
257
|
+
expect(list.type).toBe('list');
|
|
258
|
+
expect(list.children).toHaveLength(2);
|
|
259
|
+
});
|
|
260
|
+
it('handles nested lists', () => {
|
|
261
|
+
const md = '- Outer\n - Inner 1\n - Inner 2';
|
|
262
|
+
const doc = parse(md);
|
|
263
|
+
const outerList = doc.children[0];
|
|
264
|
+
const firstItem = outerList.children[0];
|
|
265
|
+
// First item should have a paragraph and a nested list
|
|
266
|
+
expect(firstItem.children.length).toBeGreaterThanOrEqual(2);
|
|
267
|
+
const nestedList = firstItem.children.find((c) => c.type === 'list');
|
|
268
|
+
expect(nestedList).toBeDefined();
|
|
269
|
+
expect(nestedList.children).toHaveLength(2);
|
|
270
|
+
});
|
|
271
|
+
it('handles emphasis inside links', () => {
|
|
272
|
+
const md = '[**Bold link**](https://example.com)';
|
|
273
|
+
const doc = parse(md);
|
|
274
|
+
const p = doc.children[0];
|
|
275
|
+
const link = p.children[0];
|
|
276
|
+
expect(link.type).toBe('link');
|
|
277
|
+
expect(link.children[0].type).toBe('strong');
|
|
278
|
+
});
|
|
279
|
+
});
|
|
280
|
+
// ============================================
|
|
281
|
+
// Round-Trip Tests
|
|
282
|
+
// ============================================
|
|
283
|
+
describe('Round-trip (parse → stringify → parse)', () => {
|
|
284
|
+
function roundTrip(md) {
|
|
285
|
+
const doc1 = parseMarkdown(md);
|
|
286
|
+
const output = stringifyMarkdown(doc1);
|
|
287
|
+
const doc2 = parseMarkdown(output);
|
|
288
|
+
// Strip positions before comparing (positions will differ between parse runs)
|
|
289
|
+
expect(stripPositions(doc2)).toEqual(stripPositions(doc1));
|
|
290
|
+
}
|
|
291
|
+
it('round-trips headings', () => {
|
|
292
|
+
roundTrip('# Heading 1\n\n## Heading 2\n\n### Heading 3\n');
|
|
293
|
+
});
|
|
294
|
+
it('round-trips paragraphs with inline formatting', () => {
|
|
295
|
+
roundTrip('Hello **bold** and *italic* text\n');
|
|
296
|
+
});
|
|
297
|
+
it('round-trips unordered lists', () => {
|
|
298
|
+
roundTrip('- Item 1\n- Item 2\n- Item 3\n');
|
|
299
|
+
});
|
|
300
|
+
it('round-trips ordered lists', () => {
|
|
301
|
+
roundTrip('1. First\n2. Second\n3. Third\n');
|
|
302
|
+
});
|
|
303
|
+
it('round-trips code blocks', () => {
|
|
304
|
+
roundTrip('```js\nconst x = 1;\n```\n');
|
|
305
|
+
});
|
|
306
|
+
it('round-trips blockquotes', () => {
|
|
307
|
+
roundTrip('> A quote\n>\n> Second paragraph\n');
|
|
308
|
+
});
|
|
309
|
+
it('round-trips links and images', () => {
|
|
310
|
+
roundTrip('[Link](https://example.com)\n');
|
|
311
|
+
roundTrip('\n');
|
|
312
|
+
});
|
|
313
|
+
it('round-trips thematic breaks', () => {
|
|
314
|
+
roundTrip('---\n');
|
|
315
|
+
});
|
|
316
|
+
it('round-trips tables', () => {
|
|
317
|
+
roundTrip('| A | B |\n| - | - |\n| 1 | 2 |\n');
|
|
318
|
+
});
|
|
319
|
+
it('round-trips task lists', () => {
|
|
320
|
+
roundTrip('- [x] Done\n- [ ] Todo\n');
|
|
321
|
+
});
|
|
322
|
+
it('round-trips inline math', () => {
|
|
323
|
+
roundTrip('The formula $E = mc^2$ is famous.\n');
|
|
324
|
+
});
|
|
325
|
+
it('round-trips display math', () => {
|
|
326
|
+
roundTrip('$$\nx^2 + y^2 = z^2\n$$\n');
|
|
327
|
+
});
|
|
328
|
+
it('round-trips strikethrough', () => {
|
|
329
|
+
roundTrip('This is ~~deleted~~ text.\n');
|
|
330
|
+
});
|
|
331
|
+
});
|
|
332
|
+
// ============================================
|
|
333
|
+
// Edge Cases
|
|
334
|
+
// ============================================
|
|
335
|
+
describe('Edge cases', () => {
|
|
336
|
+
it('handles empty document', () => {
|
|
337
|
+
const doc = parse('');
|
|
338
|
+
expect(doc.type).toBe('document');
|
|
339
|
+
expect(doc.children).toHaveLength(0);
|
|
340
|
+
});
|
|
341
|
+
it('handles single text paragraph', () => {
|
|
342
|
+
const doc = parse('Hello');
|
|
343
|
+
expect(doc.children).toHaveLength(1);
|
|
344
|
+
const p = doc.children[0];
|
|
345
|
+
expect(p.type).toBe('paragraph');
|
|
346
|
+
expect(p.children[0]).toEqual({ type: 'text', value: 'Hello' });
|
|
347
|
+
});
|
|
348
|
+
it('handles deeply nested blockquotes', () => {
|
|
349
|
+
const doc = parse('> > > Deep');
|
|
350
|
+
expect(doc.children[0].type).toBe('blockquote');
|
|
351
|
+
const bq1 = doc.children[0];
|
|
352
|
+
const bq2 = bq1.children[0];
|
|
353
|
+
const bq3 = bq2.children[0];
|
|
354
|
+
expect(bq3.type).toBe('blockquote');
|
|
355
|
+
});
|
|
356
|
+
it('preserves source positions when parsing', () => {
|
|
357
|
+
const doc = parseMarkdown('# Hello\n\nWorld');
|
|
358
|
+
expect(doc.position).toBeDefined();
|
|
359
|
+
expect(doc.children[0].position).toBeDefined();
|
|
360
|
+
expect(doc.children[0].position.start.line).toBe(1);
|
|
361
|
+
expect(doc.children[1].position.start.line).toBe(3);
|
|
362
|
+
});
|
|
363
|
+
it('handles parseHtml: false option', () => {
|
|
364
|
+
const doc = parseMarkdown('<div>content</div>', { parseHtml: false });
|
|
365
|
+
const html = doc.children[0];
|
|
366
|
+
expect(html.rawHtml).toBe('<div>content</div>');
|
|
367
|
+
expect(html.htmlChildren).toEqual([]);
|
|
368
|
+
});
|
|
369
|
+
});
|
|
370
|
+
});
|
|
371
|
+
// ============================================
|
|
372
|
+
// HTML Sub-DOM Tests
|
|
373
|
+
// ============================================
|
|
374
|
+
describe('HTML sub-DOM (parseHtmlToNodes / stringifyHtmlNodes)', () => {
|
|
375
|
+
it('parses a simple element', () => {
|
|
376
|
+
const nodes = parseHtmlToNodes('<b>bold</b>');
|
|
377
|
+
expect(nodes).toHaveLength(1);
|
|
378
|
+
expect(nodes[0].type).toBe('htmlElement');
|
|
379
|
+
const el = nodes[0];
|
|
380
|
+
expect(el.tagName).toBe('b');
|
|
381
|
+
expect(el.children).toHaveLength(1);
|
|
382
|
+
expect(el.children[0]).toEqual({ type: 'htmlText', value: 'bold' });
|
|
383
|
+
});
|
|
384
|
+
it('parses attributes', () => {
|
|
385
|
+
const nodes = parseHtmlToNodes('<div class="foo" id="bar">content</div>');
|
|
386
|
+
const el = nodes[0];
|
|
387
|
+
expect(el.attributes.class).toBe('foo');
|
|
388
|
+
expect(el.attributes.id).toBe('bar');
|
|
389
|
+
});
|
|
390
|
+
it('handles self-closing elements', () => {
|
|
391
|
+
const nodes = parseHtmlToNodes('<br>');
|
|
392
|
+
const el = nodes[0];
|
|
393
|
+
expect(el.tagName).toBe('br');
|
|
394
|
+
expect(el.selfClosing).toBe(true);
|
|
395
|
+
});
|
|
396
|
+
it('handles nested elements', () => {
|
|
397
|
+
const nodes = parseHtmlToNodes('<div><span>inner</span></div>');
|
|
398
|
+
const outer = nodes[0];
|
|
399
|
+
expect(outer.tagName).toBe('div');
|
|
400
|
+
const inner = outer.children[0];
|
|
401
|
+
expect(inner.tagName).toBe('span');
|
|
402
|
+
expect(inner.children[0]).toEqual({ type: 'htmlText', value: 'inner' });
|
|
403
|
+
});
|
|
404
|
+
it('handles empty input', () => {
|
|
405
|
+
expect(parseHtmlToNodes('')).toEqual([]);
|
|
406
|
+
expect(parseHtmlToNodes(' ')).toEqual([]);
|
|
407
|
+
});
|
|
408
|
+
it('stringifies nodes back to HTML', () => {
|
|
409
|
+
const nodes = parseHtmlToNodes('<b>bold</b>');
|
|
410
|
+
const html = stringifyHtmlNodes(nodes);
|
|
411
|
+
expect(html).toBe('<b>bold</b>');
|
|
412
|
+
});
|
|
413
|
+
it('stringifies void elements', () => {
|
|
414
|
+
const nodes = parseHtmlToNodes('<br>');
|
|
415
|
+
const html = stringifyHtmlNodes(nodes);
|
|
416
|
+
expect(html).toBe('<br>');
|
|
417
|
+
});
|
|
418
|
+
});
|
|
419
|
+
// ============================================
|
|
420
|
+
// Tree Utility Tests
|
|
421
|
+
// ============================================
|
|
422
|
+
describe('Tree utilities', () => {
|
|
423
|
+
const doc = {
|
|
424
|
+
type: 'document',
|
|
425
|
+
children: [
|
|
426
|
+
{
|
|
427
|
+
type: 'heading',
|
|
428
|
+
depth: 1,
|
|
429
|
+
children: [{ type: 'text', value: 'Title' }],
|
|
430
|
+
},
|
|
431
|
+
{
|
|
432
|
+
type: 'paragraph',
|
|
433
|
+
children: [
|
|
434
|
+
{ type: 'text', value: 'Hello ' },
|
|
435
|
+
{
|
|
436
|
+
type: 'link',
|
|
437
|
+
url: 'https://example.com',
|
|
438
|
+
children: [{ type: 'text', value: 'world' }],
|
|
439
|
+
},
|
|
440
|
+
],
|
|
441
|
+
},
|
|
442
|
+
],
|
|
443
|
+
};
|
|
444
|
+
it('getChildren returns children for parent nodes', () => {
|
|
445
|
+
expect(getChildren(doc)).toEqual(doc.children);
|
|
446
|
+
});
|
|
447
|
+
it('getChildren returns empty array for leaf nodes', () => {
|
|
448
|
+
expect(getChildren({ type: 'text', value: 'hi' })).toEqual([]);
|
|
449
|
+
});
|
|
450
|
+
it('walkMarkdownTree visits all nodes', () => {
|
|
451
|
+
const types = [];
|
|
452
|
+
walkMarkdownTree(doc, (node) => {
|
|
453
|
+
types.push(node.type);
|
|
454
|
+
});
|
|
455
|
+
expect(types).toEqual(['document', 'heading', 'text', 'paragraph', 'text', 'link', 'text']);
|
|
456
|
+
});
|
|
457
|
+
it('walkMarkdownTree supports pruning', () => {
|
|
458
|
+
const types = [];
|
|
459
|
+
walkMarkdownTree(doc, (node) => {
|
|
460
|
+
types.push(node.type);
|
|
461
|
+
if (node.type === 'paragraph')
|
|
462
|
+
return true; // skip children
|
|
463
|
+
});
|
|
464
|
+
expect(types).toEqual(['document', 'heading', 'text', 'paragraph']);
|
|
465
|
+
});
|
|
466
|
+
it('findNodesByType finds matching nodes', () => {
|
|
467
|
+
const headings = findNodesByType(doc, 'heading');
|
|
468
|
+
expect(headings).toHaveLength(1);
|
|
469
|
+
expect(headings[0].depth).toBe(1);
|
|
470
|
+
const texts = findNodesByType(doc, 'text');
|
|
471
|
+
expect(texts).toHaveLength(3);
|
|
472
|
+
});
|
|
473
|
+
it('extractPlainText extracts all text', () => {
|
|
474
|
+
expect(extractPlainText(doc)).toBe('TitleHello world');
|
|
475
|
+
});
|
|
476
|
+
it('countNodes counts all nodes', () => {
|
|
477
|
+
expect(countNodes(doc)).toBe(7); // document + heading + text + paragraph + text + link + text
|
|
478
|
+
});
|
|
479
|
+
it('createDocument creates a minimal document', () => {
|
|
480
|
+
const d = createDocument({ type: 'paragraph', children: [{ type: 'text', value: 'Hello' }] });
|
|
481
|
+
expect(d.type).toBe('document');
|
|
482
|
+
expect(d.children).toHaveLength(1);
|
|
483
|
+
});
|
|
484
|
+
});
|
|
485
|
+
// ============================================
|
|
486
|
+
// AST Stability Test
|
|
487
|
+
// ============================================
|
|
488
|
+
describe('AST stability', () => {
|
|
489
|
+
it('parse(stringify(parse(input))) === parse(input)', () => {
|
|
490
|
+
const input = `# Hello World
|
|
491
|
+
|
|
492
|
+
This is a **bold** and *italic* paragraph with \`code\`.
|
|
493
|
+
|
|
494
|
+
- Item 1
|
|
495
|
+
- Item 2
|
|
496
|
+
- Nested
|
|
497
|
+
|
|
498
|
+
> A blockquote
|
|
499
|
+
|
|
500
|
+
\`\`\`js
|
|
501
|
+
const x = 1;
|
|
502
|
+
\`\`\`
|
|
503
|
+
|
|
504
|
+
| A | B |
|
|
505
|
+
| - | - |
|
|
506
|
+
| 1 | 2 |
|
|
507
|
+
|
|
508
|
+
---
|
|
509
|
+
|
|
510
|
+
[Link](https://example.com)
|
|
511
|
+
|
|
512
|
+

|
|
513
|
+
`;
|
|
514
|
+
const doc1 = parseMarkdown(input);
|
|
515
|
+
const output = stringifyMarkdown(doc1);
|
|
516
|
+
const doc2 = parseMarkdown(output);
|
|
517
|
+
const output2 = stringifyMarkdown(doc2);
|
|
518
|
+
const doc3 = parseMarkdown(output2);
|
|
519
|
+
// After one round-trip, the AST should be stable
|
|
520
|
+
expect(stripPositions(doc3)).toEqual(stripPositions(doc2));
|
|
521
|
+
});
|
|
522
|
+
});
|
|
523
|
+
// ============================================
|
|
524
|
+
// Template Annotation on Headings
|
|
525
|
+
// ============================================
|
|
526
|
+
describe('heading template annotation', () => {
|
|
527
|
+
it('parses {[templateName]} from heading text', () => {
|
|
528
|
+
const doc = parseMarkdown('### Report Data {[chart]}');
|
|
529
|
+
const heading = doc.children[0];
|
|
530
|
+
expect(heading.type).toBe('heading');
|
|
531
|
+
expect(heading.depth).toBe(3);
|
|
532
|
+
expect(heading.templateAnnotation).toEqual({ template: 'chart' });
|
|
533
|
+
// The annotation text should be stripped from children
|
|
534
|
+
expect(heading.children).toHaveLength(1);
|
|
535
|
+
expect(heading.children[0].type).toBe('text');
|
|
536
|
+
expect(heading.children[0].value).toBe('Report Data');
|
|
537
|
+
});
|
|
538
|
+
it('parses template name with key=value params', () => {
|
|
539
|
+
const doc = parseMarkdown('## Data {[statHighlight colorScheme=blue size=large]}');
|
|
540
|
+
const heading = doc.children[0];
|
|
541
|
+
expect(heading.templateAnnotation).toEqual({
|
|
542
|
+
template: 'statHighlight',
|
|
543
|
+
params: { colorScheme: 'blue', size: 'large' },
|
|
544
|
+
});
|
|
545
|
+
expect(heading.children[0].value).toBe('Data');
|
|
546
|
+
});
|
|
547
|
+
it('returns no annotation for plain headings', () => {
|
|
548
|
+
const doc = parseMarkdown('# Just a title');
|
|
549
|
+
const heading = doc.children[0];
|
|
550
|
+
expect(heading.templateAnnotation).toBeUndefined();
|
|
551
|
+
expect(heading.children[0].value).toBe('Just a title');
|
|
552
|
+
});
|
|
553
|
+
it('does not match incomplete bracket syntax', () => {
|
|
554
|
+
const doc = parseMarkdown('## Title {chart}');
|
|
555
|
+
const heading = doc.children[0];
|
|
556
|
+
// Regular curly braces should not trigger annotation parsing
|
|
557
|
+
expect(heading.templateAnnotation).toBeUndefined();
|
|
558
|
+
});
|
|
559
|
+
it('does not match {[...]} in the middle of heading text', () => {
|
|
560
|
+
// The annotation must be trailing
|
|
561
|
+
const doc = parseMarkdown('## The {[chart]} section');
|
|
562
|
+
const heading = doc.children[0];
|
|
563
|
+
// remark may split this into multiple text nodes or keep as one;
|
|
564
|
+
// either way, no trailing annotation should be extracted
|
|
565
|
+
expect(heading.templateAnnotation).toBeUndefined();
|
|
566
|
+
});
|
|
567
|
+
it('round-trips annotation through parse → stringify → parse', () => {
|
|
568
|
+
const input = '### My Section {[factCard colorScheme=warm]}';
|
|
569
|
+
const doc1 = parseMarkdown(input);
|
|
570
|
+
const output = stringifyMarkdown(doc1);
|
|
571
|
+
const doc2 = parseMarkdown(output);
|
|
572
|
+
const h1 = doc1.children[0];
|
|
573
|
+
const h2 = doc2.children[0];
|
|
574
|
+
expect(h2.templateAnnotation).toEqual(h1.templateAnnotation);
|
|
575
|
+
expect(stripPositions(h2.children)).toEqual(stripPositions(h1.children));
|
|
576
|
+
});
|
|
577
|
+
it('handles heading with only annotation (no display text)', () => {
|
|
578
|
+
const doc = parseMarkdown('## {[chart]}');
|
|
579
|
+
const heading = doc.children[0];
|
|
580
|
+
expect(heading.templateAnnotation).toEqual({ template: 'chart' });
|
|
581
|
+
// Children should be empty after stripping the annotation
|
|
582
|
+
expect(heading.children).toHaveLength(0);
|
|
583
|
+
});
|
|
584
|
+
it('handles heading with bold text before annotation', () => {
|
|
585
|
+
const doc = parseMarkdown('## **Bold Title** {[quoteBlock]}');
|
|
586
|
+
const heading = doc.children[0];
|
|
587
|
+
expect(heading.templateAnnotation).toEqual({ template: 'quoteBlock' });
|
|
588
|
+
// The bold node should remain, trailing text stripped
|
|
589
|
+
const types = heading.children.map((c) => c.type);
|
|
590
|
+
expect(types).toContain('strong');
|
|
591
|
+
});
|
|
592
|
+
it('preserves annotation through stringify', () => {
|
|
593
|
+
const input = '## Section {[chart]}';
|
|
594
|
+
const doc = parseMarkdown(input);
|
|
595
|
+
const output = stringifyMarkdown(doc);
|
|
596
|
+
// Output should contain the annotation
|
|
597
|
+
expect(output).toContain('{[chart]}');
|
|
598
|
+
// The heading text should be there too
|
|
599
|
+
expect(output).toContain('Section');
|
|
600
|
+
});
|
|
601
|
+
});
|
|
602
|
+
// ============================================
|
|
603
|
+
// YAML Frontmatter
|
|
604
|
+
// ============================================
|
|
605
|
+
describe('parseFrontmatter', () => {
|
|
606
|
+
it('parses simple key-value pairs', () => {
|
|
607
|
+
const result = parseFrontmatter('title: My Doc\nauthor: Jane');
|
|
608
|
+
expect(result).toEqual({ title: 'My Doc', author: 'Jane' });
|
|
609
|
+
});
|
|
610
|
+
it('parses booleans and numbers', () => {
|
|
611
|
+
const result = parseFrontmatter('draft: true\ncount: 42\nratio: 3.14');
|
|
612
|
+
expect(result).toEqual({ draft: true, count: 42, ratio: 3.14 });
|
|
613
|
+
});
|
|
614
|
+
it('strips surrounding quotes', () => {
|
|
615
|
+
const result = parseFrontmatter('title: "Hello World"\nname: \'Jane\'');
|
|
616
|
+
expect(result).toEqual({ title: 'Hello World', name: 'Jane' });
|
|
617
|
+
});
|
|
618
|
+
it('skips comment lines and blank lines', () => {
|
|
619
|
+
const result = parseFrontmatter('# comment\n\ntitle: Test');
|
|
620
|
+
expect(result).toEqual({ title: 'Test' });
|
|
621
|
+
});
|
|
622
|
+
it('returns null for empty input', () => {
|
|
623
|
+
expect(parseFrontmatter('')).toBeNull();
|
|
624
|
+
expect(parseFrontmatter(' \n ')).toBeNull();
|
|
625
|
+
});
|
|
626
|
+
});
|
|
627
|
+
describe('frontmatter in parseMarkdown', () => {
|
|
628
|
+
it('extracts frontmatter from markdown with YAML header', () => {
|
|
629
|
+
const md = '---\ndocument-render-as: landscape\ntitle: Test\n---\n\n# Hello';
|
|
630
|
+
const doc = parseMarkdown(md);
|
|
631
|
+
expect(doc.frontmatter).toEqual({ 'document-render-as': 'landscape', title: 'Test' });
|
|
632
|
+
// The heading should still be there
|
|
633
|
+
expect(doc.children.length).toBeGreaterThanOrEqual(1);
|
|
634
|
+
expect(doc.children[0].type).toBe('heading');
|
|
635
|
+
});
|
|
636
|
+
it('has no frontmatter when YAML header is absent', () => {
|
|
637
|
+
const doc = parseMarkdown('# Just a heading');
|
|
638
|
+
expect(doc.frontmatter).toBeUndefined();
|
|
639
|
+
});
|
|
640
|
+
it('round-trips frontmatter through stringify', () => {
|
|
641
|
+
const md = '---\ndocument-render-as: portrait\n---\n\n# Test';
|
|
642
|
+
const doc = parseMarkdown(md);
|
|
643
|
+
const output = stringifyMarkdown(doc);
|
|
644
|
+
expect(output).toContain('---');
|
|
645
|
+
expect(output).toContain('document-render-as: portrait');
|
|
646
|
+
expect(output).toContain('# Test');
|
|
647
|
+
});
|
|
648
|
+
it('can disable frontmatter parsing', () => {
|
|
649
|
+
const md = '---\ntitle: Disabled\n---\n\n# Hello';
|
|
650
|
+
const doc = parseMarkdown(md, { frontmatter: false });
|
|
651
|
+
expect(doc.frontmatter).toBeUndefined();
|
|
652
|
+
});
|
|
653
|
+
});
|
|
654
|
+
//# sourceMappingURL=markdown.test.js.map
|