import { generateHTML } from '@tiptap/core' import { getTipTapDefaultExtensions } from 'tldraw' import { describe, expect, it } from 'vitest' import { createMentionExtension } from './mention-extension' // The exact render path shape text uses: tldraw's default rich-text extensions plus the mention // node, run through TipTap's `generateHTML` (what `renderHtmlFromRichText` does under the hood). const render = (body: any, resolveName?: (id: string) => string | undefined) => generateHTML(body, [...getTipTapDefaultExtensions(), createMentionExtension({ resolveName })]) const doc = (...content: any[]) => ({ type: 'doc', content }) as any const para = (...content: any[]) => ({ type: 'paragraph', content }) const text = (value: string) => ({ type: 'text', text: value }) const mention = (id: string, label?: string) => ({ type: 'mention', attrs: { id, label } }) describe('createMentionExtension', () => { it('renders a mention node as a pill carrying its member id', () => { const html = render(doc(para(text('hi '), mention('ada', 'Ada')))) expect(html).toContain('tlui-cmt-mention') expect(html).toContain('data-mention-id="ada"') }) it('resolves the id to the current name, not the label frozen at insert time', () => { const html = render(doc(para(mention('ada', 'Ada'))), (id) => id === 'ada' ? 'Ada Lovelace' : undefined ) expect(html).toContain('@Ada Lovelace') expect(html).not.toContain('@Ada<') }) it('falls back to the stored label when the id can not be resolved', () => { const html = render(doc(para(mention('ghost', 'Ada'))), () => undefined) expect(html).toContain('@Ada') }) it('escapes a resolved name so it can never inject markup', () => { const html = render(doc(para(mention('ada'))), () => '') expect(html).not.toContain('