import assert from 'node:assert/strict' import { createMarkdownParser } from '../src/renderer/src/workbuddy/markdown/parser' import { parseCodeBlockMeta, resolveCodeBlockFenceInfo } from '../src/renderer/src/workbuddy/markdown/code-fence' import { ensurePrismLanguage, getMdReadyVersion, getMdSyntaxVersion, getPrismLanguage, isPrismLanguageLoaded, onPrismLanguageLoaded, subscribeMdReady } from '../src/renderer/src/workbuddy/markdown/languages' const md = createMarkdownParser() const fileMeta = parseCodeBlockMeta('ts:4:8:src/app.ts') assert.deepEqual(fileMeta, { startLine: 4, endLine: 8, filePath: 'src/app.ts', fileName: 'app.ts', metaLanguage: 'ts' }) assert.deepEqual(resolveCodeBlockFenceInfo('4:8:README'), { language: '', meta: '4:8:README', fileMeta: { startLine: 4, endLine: 8, filePath: 'README', fileName: 'README', metaLanguage: undefined } }) assert.equal(resolveCodeBlockFenceInfo('').language, '') const codeTokens = md.parse('```ts:4:8:src/app.ts\nconst answer = 42\n```', {}) const fence = codeTokens.find((token) => token.type === 'fence') assert.ok(fence) assert.equal(fence.info, 'ts') assert.equal(fence.attrGet('data-meta'), 'ts:4:8:src/app.ts') assert.equal(fence.attrGet('data-id'), `code-${codeTokens.indexOf(fence)}`) assert.equal(fence.attrGet('data-content'), 'const answer = 42\n') assert.match(md.render('```js\nconst answer = 42\n```'), /class="token keyword"|class="token keyword keyword"/) assert.doesNotMatch(md.render('reply answer'), /class="loading-dot"/) const indentedCode = md.parse(' plain code\n', {}) const convertedCode = indentedCode.find((token) => token.type === 'fence') assert.ok(convertedCode) assert.equal(convertedCode.info, 'plaintext') assert.equal(convertedCode.tag, 'code') const equalsHeading = md.parse('Recovered heading\n===\n', {}) assert.ok(equalsHeading.some((token) => token.type === 'heading_open' && token.tag === 'h1')) const dashHeading = md.parse('Not a recovered equals heading\n---\n', {}) assert.ok(!dashHeading.some((token) => token.type === 'heading_open' && token.tag === 'h2')) const taskTokens = md.parse('- [x] completed\n- [ ] pending\n', {}) assert.equal(taskTokens.filter((token) => token.type === 'list_item_open' && token.attrGet('class') === 'cr-task-item').length, 2) assert.equal(taskTokens.find((token) => token.type === 'bullet_list_open')?.attrGet('class'), 'cr-task-list cr-task-list') const checkboxes = taskTokens.flatMap((token) => token.children ?? []).filter((token) => token.type === 'html_inline') assert.deepEqual(checkboxes.map((token) => token.content), [ ' ', ' ' ]) const tableSource = '| a | b |\n| - | - |\n| c | d |\n\ntrailing paragraph' const tableTokens = md.parse(tableSource, {}) const table = tableTokens.find((token) => token.type === 'table_open') assert.ok(table) assert.equal(table.attrGet('data-id'), `table-${tableTokens.indexOf(table)}`) assert.equal(table.attrGet('data-content'), '| a | b |\n| - | - |\n| c | d |') const html = md.render('left
middle under right') assert.match(html, /left
/) assert.match(html, /under<\/u>/) assert.doesNotMatch(html, /script|ignored\(\)/) assert.match(html, /right/) assert.doesNotMatch(md.render('visible text.length - pos }] }).render('javascript:alert(1)'), /href="javascript:/) const unicodeLink = md.parse('https://example.com日本', {}) const unicodeInline = unicodeLink.find((token) => token.type === 'inline') const linkOpen = unicodeInline?.children?.find((token) => token.type === 'link_open' && token.markup === 'linkify') assert.ok(linkOpen) assert.equal(linkOpen.attrGet('href'), 'https://example.com') assert.ok(unicodeInline?.children?.some((token) => token.type === 'text' && token.content === '日本')) const loadingParser = createMarkdownParser({ loading: true }) const loadingTokens = loadingParser.parse('reply answer', {}) const loadingInline = loadingTokens.find((token) => token.type === 'inline') const loadingDots = loadingInline?.children?.filter((token) => token.type === 'loadingDot') ?? [] assert.deepEqual(loadingDots.map((token) => token.attrGet('content')), ['a', 'n', 's', 'w', 'e', 'r']) assert.deepEqual(loadingDots.map((token) => token.attrGet('level')), ['6', '5', '4', '3', '2', '1']) assert.match(loadingParser.render('reply answer'), /class="loading-dot"/) const surrogateTail = loadingParser.parse('prefixabcdef😀', {}) const surrogateInline = surrogateTail.find((token) => token.type === 'inline') const surrogateDots = surrogateInline?.children?.filter((token) => token.type === 'loadingDot') ?? [] assert.equal(surrogateDots.length, 5) assert.equal(surrogateDots.at(-1)?.attrGet('content'), '😀') const nestedLoading = loadingParser.parse('prefix **123456**', {}) const nestedInline = nestedLoading.find((token) => token.type === 'inline') const nestedDots = nestedInline?.children?.filter((token) => token.type === 'loadingDot') ?? [] assert.deepEqual(nestedDots.map((token) => token.attrGet('content')), ['1', '2', '3', '4', '5', '6']) assert.equal(nestedInline?.children?.[0]?.content, 'prefix ') const completedCodeTokens = loadingParser.parse('finished answer\n\n```js\nconst x = 1\n```', {}) assert.ok(!completedCodeTokens.some((token) => token.children?.some((child) => child.type === 'loadingDot'))) assert.equal(isPrismLanguageLoaded('js'), true) assert.equal(getMdReadyVersion(), 0) assert.equal(getMdSyntaxVersion(), 0) let readinessNotifications = 0 const unsubscribeReady = subscribeMdReady(() => { readinessNotifications++ }) async function waitForPrismLanguage(language: string): Promise { if (isPrismLanguageLoaded(language)) return await new Promise((resolve, reject) => { let unsubscribe = (): void => {} const timeout = setTimeout(() => { unsubscribe() reject(new Error(`Prism language did not load: ${language}`)) }, 4000) unsubscribe = onPrismLanguageLoaded(() => { if (!isPrismLanguageLoaded(language)) return clearTimeout(timeout) unsubscribe() resolve() }) ensurePrismLanguage(language) }) } async function verifyPrismReadiness(): Promise { for (const language of ['cpp', 'php', 'tsx', 'scala']) { const readyBefore = getMdReadyVersion() await waitForPrismLanguage(language) assert.ok(getPrismLanguage(language)) assert.equal(getMdReadyVersion(), readyBefore + 1) } unsubscribeReady() assert.equal(readinessNotifications, 4) assert.equal(getPrismLanguage('ts'), getPrismLanguage('typescript')) console.log('Recovered markdown parser regression checks passed') } void verifyPrismReadiness().catch((error) => { console.error(error); process.exitCode = 1 })