coaching-fence-render.test.ts3.5 KBView on GitHub /**
* Does a ```scorecard / ```moment fence actually become its node when real document markdown is
* parsed by the editor?
*
* Registering an extension is not the same as it firing. StarterKit's generic fenced-code rule
* will happily claim ```scorecard and render a grey code block instead of the card, which looks
* to a reader exactly like "the scorecard didn't render". This drives the real markdown from a
* generated coaching doc through the real extension list.
*/
import { Editor } from '@tiptap/core';
import { Markdown } from '@tiptap/markdown';
import StarterKit from '@tiptap/starter-kit';
import { Table } from '@tiptap/extension-table';
import { TableRow } from '@tiptap/extension-table-row';
import { TableCell } from '@tiptap/extension-table-cell';
import { TableHeader } from '@tiptap/extension-table-header';
import TaskList from '@tiptap/extension-task-list';
import TaskItem from '@tiptap/extension-task-item';
import { ScorecardFenceNode } from '@/modules/documents/coaching/ScorecardFenceNode';
import { MomentFenceNode } from '@/modules/documents/coaching/MomentFenceNode';
const extensions = [
StarterKit.configure({ heading: { levels: [1, 2, 3] } }),
Markdown,
Table.configure({ resizable: false }),
TableRow,
TableHeader,
TableCell,
TaskList,
TaskItem.configure({ nested: true }),
ScorecardFenceNode,
MomentFenceNode,
];
const DOC = [
'# Marmon Mok',
'',
'## Discovery',
'',
'```scorecard',
'| Discovery | Score | Why |',
'| --- | --- | --- |',
'| D1 Knew the must-asks | 1 | Only asked where projects are stored. |',
'| D2 Genuinely curious | 1 | Budget objection dropped. |',
'```',
'',
'## Checklist',
'',
'- [x] Built rapport before starting discovery',
'- [ ] Asked "what piqued your interest"',
'',
'## Moments',
'',
'```moment',
'deal: Marmon Mok',
'meeting: Pirros | Marmon Mok',
'criterion: D2',
'verdict: bad',
'coaching: Ask what changed since the pilot stalled.',
'',
'## moment',
'**Ben Zavala** (02:16) We are just revisiting whether we can afford it.',
'',
'## reaction',
'**Xander** (02:36) Got it. And remind me what you have set up.',
'```',
].join('\n');
function types(json: unknown, acc: Set<string> = new Set()): Set<string> {
if (!json || typeof json !== 'object') return acc;
const n = json as { type?: string; content?: unknown[] };
if (n.type) acc.add(n.type);
for (const c of n.content ?? []) types(c, acc);
return acc;
}
describe('coaching fences render from real document markdown', () => {
let editor: Editor;
beforeAll(() => {
editor = new Editor({ extensions, content: DOC, contentType: 'markdown' as never });
});
afterAll(() => editor?.destroy());
it('produces a scorecardBlock, not a generic code block', () => {
const t = types(editor.getJSON());
expect(t.has('scorecardBlock')).toBe(true);
expect(t.has('codeBlock')).toBe(false);
});
it('produces a momentBlock', () => {
expect(types(editor.getJSON()).has('momentBlock')).toBe(true);
});
it('still renders the checklist as task items alongside the fences', () => {
expect(types(editor.getJSON()).has('taskItem')).toBe(true);
});
it('keeps the fence body verbatim on the node so it round-trips', () => {
const md = (editor as unknown as { getMarkdown: () => string }).getMarkdown();
expect(md).toContain('```scorecard');
expect(md).toContain('| D1 Knew the must-asks | 1 |');
expect(md).toContain('```moment');
expect(md).toContain('coaching: Ask what changed since the pilot stalled.');
});
});