import { describe, expect, it } from 'vitest'; import { scanMarkdown } from './markdown.js'; describe('scanMarkdown', () => { it('names a block from its role= as role.type.lang', () => { const md = [ '# Title', '', '```yaml role=part.cargo', 'id: wood', '```', '', 'text after', ].join('\n'); const { files, fences } = scanMarkdown(md, 'harbor/harbor.md'); expect(fences).toHaveLength(1); expect(fences[0]).toMatchObject({ info: 'yaml role=part.cargo', content: 'id: wood', startLine: 3, endLine: 5, }); expect(files).toHaveLength(1); expect(files[0]).toMatchObject({ name: 'harbor/part.cargo.yaml', kind: 'yaml', text: 'id: wood', source: 'harbor/harbor.md:3-5', role: { role: 'part', type: 'cargo' }, }); }); it('names a package block package.yaml', () => { const md = '```yaml role=package\nid: harbor\n```'; const { files } = scanMarkdown(md, 'harbor/harbor.md'); expect(files[0]).toMatchObject({ name: 'harbor/package.yaml', role: { role: 'package' } }); }); it('names a surface block with type and id', () => { const md = '```yaml role=surface.game#main\n```'; const { files } = scanMarkdown(md, 'poker/poker.md'); expect(files[0]).toMatchObject({ name: 'poker/surface.game.yaml', role: { role: 'surface', type: 'game', id: 'main' }, }); }); it('uses file= to override the role.type name', () => { const md = '```yaml file=parts/cargo.yaml role=part.cargo\n```'; const { files } = scanMarkdown(md, 'harbor/harbor.md'); expect(files[0]).toMatchObject({ name: 'harbor/parts/cargo.yaml', role: { role: 'part', type: 'cargo' }, }); }); it('ignores a block without role= or file=', () => { const md = '```yaml\nrole: part\n```'; const { files } = scanMarkdown(md, 'harbor/harbor.md'); expect(files).toHaveLength(0); }); it('names a csv block with file= as csv', () => { const md = '```csv file=parts/seats.csv\nseat,x\nstring,number\n0,40\n```'; const { files } = scanMarkdown(md, 'harbor/harbor.md'); expect(files[0]).toMatchObject({ name: 'harbor/parts/seats.csv', kind: 'csv' }); }); it('throws on an unknown role=', () => { const md = '```yaml role=widget\n```'; expect(() => scanMarkdown(md, 'poker/poker.md')).toThrow(/Invalid role/); }); it('throws on role=package with a type', () => { const md = '```yaml role=package.foo\n```'; expect(() => scanMarkdown(md, 'poker/poker.md')).toThrow(/Package role takes no type/); }); it('throws on a role without a type', () => { const md = '```yaml role=part\n```'; expect(() => scanMarkdown(md, 'poker/poker.md')).toThrow(/requires a type/); }); it('ignores non-definition languages', () => { const md = '```js\nconst x = 1;\n```'; const { files, fences } = scanMarkdown(md, 'harbor/harbor.md'); expect(files).toHaveLength(0); expect(fences).toHaveLength(1); }); it('ignores indented code blocks', () => { const md = ' role: part\n'; const { files } = scanMarkdown(md, 'harbor/harbor.md'); expect(files).toHaveLength(0); }); it('tracks line numbers across multiple blocks', () => { const md = [ '```yaml role=part.a', '```', '', '```yaml role=part.b', '```', ].join('\n'); const { fences } = scanMarkdown(md, 'harbor/harbor.md'); expect(fences.map((f) => f.startLine)).toEqual([1, 4]); }); });