import { Node } from 'prosemirror-model'; import { describe, expect, it } from 'vitest'; import { docToHtml } from './html'; import { docToMarkdown, markdownToDoc } from './markdown'; import { extractWikilinkSlugs } from './wikilinks'; function firstTransclusion(doc: Node): Node | null { let found: Node | null = null; doc.descendants((node) => { if (!found && node.type.name === 'transclusion') found = node; }); return found; } describe('page embed / transclusion (issue #135)', () => { it('parses a lone ![[slug]] line to a transclusion block and round-trips', () => { const doc = markdownToDoc('Intro\n\n![[rennrad]]\n\nOutro'); const node = firstTransclusion(doc); expect(node).not.toBeNull(); expect(node!.attrs.targetSlug).toBe('rennrad'); expect(node!.attrs.displayText).toBeNull(); expect(docToMarkdown(doc)).toContain('![[rennrad]]'); }); it('supports optional display text and round-trips it', () => { const doc = markdownToDoc('![[rennrad|Mein Rad]]'); const node = firstTransclusion(doc); expect(node!.attrs.targetSlug).toBe('rennrad'); expect(node!.attrs.displayText).toBe('Mein Rad'); expect(docToMarkdown(doc).trim()).toBe('![[rennrad|Mein Rad]]'); }); it('renders a placeholder div carrying the slug', () => { const doc = markdownToDoc('![[rennrad]]'); const html = docToHtml(doc); expect(html).toContain('class="dt-transclusion"'); expect(html).toContain('data-transclusion="rennrad"'); }); it('registers the embed target as an outgoing link (backlinks/graph)', () => { const doc = markdownToDoc('![[rennrad]]\n\n[[dota]]'); expect(extractWikilinkSlugs(doc)).toEqual(['rennrad', 'dota']); }); it('only embeds a lone line — mid-paragraph ![[x]] is not a transclusion', () => { const doc = markdownToDoc('see ![[rennrad]] inline'); expect(firstTransclusion(doc)).toBeNull(); }); }); describe('bare page embed `$[[…]]` (issue #146)', () => { it('parses a lone $[[slug]] line to a bare transclusion and round-trips', () => { const doc = markdownToDoc('Intro\n\n$[[rennrad]]\n\nOutro'); const node = firstTransclusion(doc); expect(node).not.toBeNull(); expect(node!.attrs.targetSlug).toBe('rennrad'); expect(node!.attrs.bare).toBe(true); expect(docToMarkdown(doc)).toContain('$[[rennrad]]'); }); it('keeps `![[…]]` non-bare and round-trips both prefixes side by side', () => { const doc = markdownToDoc('![[dota]]\n\n$[[rennrad]]'); const markdown = docToMarkdown(doc); expect(markdown).toContain('![[dota]]'); expect(markdown).toContain('$[[rennrad]]'); let bares = 0; doc.descendants((node) => { if (node.type.name === 'transclusion' && node.attrs.bare) bares += 1; }); expect(bares).toBe(1); }); it('marks the placeholder div so the server expansion can drop the frame', () => { const html = docToHtml(markdownToDoc('$[[rennrad]]')); expect(html).toContain('data-transclusion="rennrad"'); expect(html).toContain('data-transclusion-bare="1"'); // The framed variant must NOT carry the marker. expect(docToHtml(markdownToDoc('![[rennrad]]'))).not.toContain('data-transclusion-bare'); }); it('registers the bare embed target as an outgoing link too', () => { const doc = markdownToDoc('$[[rennrad]]'); expect(extractWikilinkSlugs(doc)).toEqual(['rennrad']); }); it('does not treat mid-paragraph $[[x]] as a transclusion', () => { const doc = markdownToDoc('costs $[[rennrad]] inline'); expect(firstTransclusion(doc)).toBeNull(); }); });