Files
wehub-resource-sync 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
chore: import upstream snapshot with attribution
2026-07-13 13:08:55 +08:00

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.');
});
});