7a0da7932b
OSV-Scanner (Scheduled) / scan-scheduled (push) Failing after 0s
Create Release / test-gate (push) Has been cancelled
Create Release / release-gate (push) Has been cancelled
Create Release / ci-gate (push) Has been cancelled
Create Release / version-check (push) Has been cancelled
Create Release / e2e-test-gate (push) Has been cancelled
Create Release / responsive-test-gate (push) Has been cancelled
Create Release / compat-test-gate (push) Has been cancelled
Create Release / compose-integration-gate (push) Has been cancelled
Create Release / vulture-gate (push) Has been cancelled
Create Release / build (push) Has been cancelled
Create Release / provenance (push) Has been cancelled
Create Release / prerelease-docker (push) Has been cancelled
Create Release / publish-docker (push) Has been cancelled
Create Release / create-release (push) Has been cancelled
Create Release / cleanup-changelog (push) Has been cancelled
Create Release / trigger-pypi (push) Has been cancelled
Create Release / monitor-pypi (push) Has been cancelled
Create Release / Clean up orphan prerelease tags and signatures (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-form] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-metrics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-workflow] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-core] (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [history-news] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [library] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [link-analytics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-core] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-lifecycle] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [error-benchmark] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) (push) Has been cancelled
Docker Tests (Consolidated) / Accessibility Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Unit Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Example Tests (push) Has been cancelled
Docker Tests (Consolidated) / Production Image Smoke Test (push) Has been cancelled
Docker Tests (Consolidated) / Infrastructure Tests (push) Has been cancelled
OSSF Scorecard / OSSF Security Scorecard Analysis (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [mobile] (push) Has been cancelled
Backwards Compatibility / Verify Encryption Constants (push) Has been cancelled
Backwards Compatibility / PyPI Version Compatibility (push) Has been cancelled
Backwards Compatibility / Database Migration Tests (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Docker Tests (Consolidated) / detect-changes (push) Has been cancelled
Docker Tests (Consolidated) / Build Test Image (push) Has been cancelled
Docker Tests (Consolidated) / All Pytest Tests + Coverage (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [accessibility] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [api-crud] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-login] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-register] (push) Has been cancelled
141 lines
5.2 KiB
JavaScript
141 lines
5.2 KiB
JavaScript
/**
|
|
* 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 `<annotation>` 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 <p> 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 <p> 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 <p> sits inside the <li>; the walker iterates the <ul>
|
|
// and the <li> textContent recovers the LaTeX.
|
|
expect(root.querySelector('.katex-display')).toBeNull();
|
|
expect(root.querySelector('li').textContent).toBe('$$x=1$$');
|
|
});
|
|
|
|
it('leaves elements without an annotation untouched', () => {
|
|
// Defensive: if DOMPurify ever stripped <annotation>, the substitution
|
|
// should skip rather than silently produce $$$$ or $$.
|
|
const broken = el('span', { class: 'katex-display' },
|
|
el('span', { class: 'katex-html' }, 'no annotation here')
|
|
);
|
|
root.appendChild(broken);
|
|
|
|
replaceKatexWithLatex(root);
|
|
|
|
// Unchanged.
|
|
expect(root.querySelector('.katex-display')).not.toBeNull();
|
|
expect(root.querySelector('.katex-display').textContent).toBe('no annotation here');
|
|
});
|
|
|
|
it('is a no-op on a container with no KaTeX', () => {
|
|
const p = document.createElement('p');
|
|
p.textContent = 'Plain text only.';
|
|
root.appendChild(p);
|
|
|
|
replaceKatexWithLatex(root);
|
|
|
|
expect(root.children.length).toBe(1);
|
|
expect(root.children[0].textContent).toBe('Plain text only.');
|
|
});
|
|
});
|