/** * Tests for services/pdf.js * * Currently covers the replaceKatexWithLatex helper: the substitution step * that swaps rendered KaTeX elements for their raw LaTeX source before the * PDF walker extracts textContent. Regression-protects the fix for * top-level `$$\n...\n$$` display math being silently dropped from PDFs. */ import '@js/services/pdf.js'; const { replaceKatexWithLatex } = window.pdfService; // Build a DOM with KaTeX-shaped markup. We construct it by hand (rather than // driving marked + KaTeX) so the test does not depend on KaTeX's exact // internal layout — only on the structural contract: `.katex-display` for // display math, `.katex` for inline, both containing an `` with // the original LaTeX. That matches what marked-katex-extension always emits. function el(tag, attrs = {}, ...children) { const node = document.createElement(tag); for (const [k, v] of Object.entries(attrs)) node.setAttribute(k, v); for (const c of children) node.appendChild(typeof c === 'string' ? document.createTextNode(c) : c); return node; } function makeKatexInline(latex) { return el('span', { class: 'katex' }, el('span', { class: 'katex-mathml' }, el('math', {}, el('semantics', {}, el('mrow'), el('annotation', { encoding: 'application/x-tex' }, latex) ) ) ), el('span', { class: 'katex-html', 'aria-hidden': 'true' }, 'rendered') ); } function makeKatexDisplay(latex) { return el('span', { class: 'katex-display' }, makeKatexInline(latex)); } describe('replaceKatexWithLatex', () => { let root; beforeEach(() => { root = document.createElement('div'); document.body.appendChild(root); }); afterEach(() => { root.remove(); }); it('replaces inline .katex with `$LATEX$` text node inside its parent', () => { const p = document.createElement('p'); p.append('Energy is ', makeKatexInline('E=mc^2'), ' famous.'); root.appendChild(p); replaceKatexWithLatex(root); // The parent

still exists and now reads cleanly. expect(root.children.length).toBe(1); expect(root.children[0].tagName).toBe('P'); expect(root.children[0].textContent).toBe('Energy is $E=mc^2$ famous.'); expect(root.querySelector('.katex')).toBeNull(); }); it('wraps top-level .katex-display in a

so the PDF walker sees it', () => { // Regression: previously, display math at top level (the canonical // multi-line `$$\n...\n$$` form) was replaced with a bare text node. // The PDF walker iterates contentDiv.children (Elements only — text // nodes are skipped), so display math was silently dropped. root.appendChild(document.createElement('p')).textContent = 'Before'; root.appendChild(makeKatexDisplay('\\sum_{i=1}^n i')); root.appendChild(document.createElement('p')).textContent = 'After'; replaceKatexWithLatex(root); const kids = Array.from(root.children); expect(kids.map((k) => k.tagName)).toEqual(['P', 'P', 'P']); expect(kids[0].textContent).toBe('Before'); expect(kids[1].textContent).toBe('$$\\sum_{i=1}^n i$$'); expect(kids[2].textContent).toBe('After'); }); it('handles back-to-back display math blocks', () => { root.appendChild(makeKatexDisplay('a')); root.appendChild(makeKatexDisplay('b')); replaceKatexWithLatex(root); const kids = Array.from(root.children); expect(kids.length).toBe(2); expect(kids[0].textContent).toBe('$$a$$'); expect(kids[1].textContent).toBe('$$b$$'); }); it('handles display math nested inside another element', () => { const li = document.createElement('li'); li.appendChild(makeKatexDisplay('x=1')); const ul = document.createElement('ul'); ul.appendChild(li); root.appendChild(ul); replaceKatexWithLatex(root); // The wrap

sits inside the

  • ; the walker iterates the