mirror of
https://github.com/amitwh/markdown-converter.git
synced 2026-08-23 23:10:17 +05:30
test(monospace): add end-to-end smoke test for PDF/DOCX/EPUB/HTML font embedding
Exercises the real export pipeline against the bundled JetBrains Mono TTFs: - PdfFontHeader emits valid xelatex fontspec with correct family + Ligatures=NoCommon - ExportCss.build emits @font-face with base64 data URI - DocxFontEmbedder injects TTFs into pandoc-produced DOCX (word/fontTable.xml + word/fonts/) - EpubFontEmbedder.patchManifest adds TTF entry to OPF <manifest> - HTML export links the sidecar CSS which embeds the font Run with: node tests/smoke-e2e-monospace.js
This commit is contained in:
@@ -0,0 +1,146 @@
|
||||
// Manual end-to-end smoke test for the monospace font embedding pipeline.
|
||||
// Verifies that:
|
||||
// 1. PdfFontHeader produces valid fontspec that references real bundled TTFs
|
||||
// 2. ExportCss.build emits a @font-face with base64 data URI from the TTF
|
||||
// 3. DocxFontEmbedder.inject on a pandoc-produced DOCX includes the font
|
||||
// 4. EpubFontEmbedder.patchManifest references the TTF in OPF <manifest>
|
||||
// 5. MonospaceFontConfig.getMonoFontTtfPath returns real paths for both families
|
||||
//
|
||||
// Run with: node tests/smoke-e2e-monospace.js
|
||||
|
||||
'use strict';
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const os = require('os');
|
||||
const { execFileSync } = require('child_process');
|
||||
const JSZip = require('jszip');
|
||||
|
||||
const PdfFontHeader = require('../src/main/PdfFontHeader');
|
||||
const ExportCss = require('../src/main/ExportCss');
|
||||
const MonospaceFontConfig = require('../src/main/MonospaceFontConfig');
|
||||
const DocxFontEmbedder = require('../src/main/DocxFontEmbedder');
|
||||
const EpubFontEmbedder = require('../src/main/EpubFontEmbedder');
|
||||
|
||||
const TMP = fs.mkdtempSync(path.join(os.tmpdir(), 'mc-smoke-'));
|
||||
const ASCII_FIXTURE = `\`\`\`
|
||||
+---+---+---+
|
||||
| A | B | C |
|
||||
+---+---+---+
|
||||
| 1 | 2 | 3 |
|
||||
+---+---+---+
|
||||
\`\`\`
|
||||
|
||||
Use this **ASCII** table to verify \`column alignment\` in code blocks.
|
||||
`;
|
||||
|
||||
function check(label, cond, detail) {
|
||||
const tag = cond ? 'OK ' : 'FAIL';
|
||||
console.log(`[${tag}] ${label}${detail ? ' — ' + detail : ''}`);
|
||||
if (!cond) process.exitCode = 1;
|
||||
}
|
||||
|
||||
(async () => {
|
||||
console.log(`Smoke test dir: ${TMP}`);
|
||||
|
||||
// 1. PdfFontHeader
|
||||
const jbmRegular = MonospaceFontConfig.getMonoFontTtfPath('jetbrains-mono', 400);
|
||||
check('JBM Regular TTF resolves', !!jbmRegular, jbmRegular);
|
||||
const jbmBold = MonospaceFontConfig.getMonoFontTtfPath('jetbrains-mono', 700);
|
||||
check('JBM Bold TTF resolves', !!jbmBold, jbmBold);
|
||||
const fcRegular = MonospaceFontConfig.getMonoFontTtfPath('fira-code', 400);
|
||||
check('Fira Code Regular TTF resolves', !!fcRegular, fcRegular);
|
||||
|
||||
const tex = PdfFontHeader.build({
|
||||
fontTtfPath: jbmRegular,
|
||||
boldTtfPath: jbmBold,
|
||||
ligatures: false,
|
||||
});
|
||||
check('PdfFontHeader includes fontspec', tex.includes('\\usepackage{fontspec}'));
|
||||
check(
|
||||
'PdfFontHeader sets family to JetBrainsMono',
|
||||
/\\setmonofont\[[^\]]*\]\{JetBrainsMono\}/.test(tex)
|
||||
);
|
||||
check('PdfFontHeader disables common ligatures', tex.includes('Ligatures=NoCommon'));
|
||||
|
||||
// 2. ExportCss
|
||||
const css = ExportCss.build({
|
||||
activeFontPath: jbmRegular,
|
||||
family: 'JetBrains Mono',
|
||||
weight: 400,
|
||||
ligatures: false,
|
||||
});
|
||||
check('ExportCss declares @font-face', css.includes('@font-face'));
|
||||
check('ExportCss uses data URI', css.includes('data:font/woff2;base64,'));
|
||||
check('ExportCss sets font-feature-settings', css.includes('font-feature-settings'));
|
||||
|
||||
// 3. DocxFontEmbedder — build a DOCX via pandoc, then patch
|
||||
const mdPath = path.join(TMP, 'ascii.md');
|
||||
fs.writeFileSync(mdPath, ASCII_FIXTURE);
|
||||
const docxPath = path.join(TMP, 'ascii.docx');
|
||||
execFileSync('pandoc', [mdPath, '-o', docxPath], { stdio: 'pipe' });
|
||||
const patchedDocx = await DocxFontEmbedder.embed(docxPath, [
|
||||
{ path: jbmRegular, family: 'JetBrains Mono', weight: 400 },
|
||||
{ path: jbmBold, family: 'JetBrains Mono', weight: 700 },
|
||||
]);
|
||||
const docxZip = await JSZip.loadAsync(fs.readFileSync(patchedDocx));
|
||||
const fontTable = docxZip.file('word/fontTable.xml')
|
||||
? await docxZip.file('word/fontTable.xml').async('string')
|
||||
: '';
|
||||
check('DOCX has word/fontTable.xml', !!fontTable);
|
||||
check('DOCX fontTable names JetBrains Mono', fontTable.includes('JetBrains Mono'));
|
||||
check('DOCX fontTable has embedRegular', /<w:embedRegular/.test(fontTable));
|
||||
check(
|
||||
'DOCX embeds JetBrainsMono-Regular.ttf',
|
||||
Object.keys(docxZip.files).some((f) => /word\/fonts\/JetBrainsMono-Regular\.ttf$/.test(f))
|
||||
);
|
||||
check(
|
||||
'DOCX embeds JetBrainsMono-Bold.ttf',
|
||||
Object.keys(docxZip.files).some((f) => /word\/fonts\/JetBrainsMono-Bold\.ttf$/.test(f))
|
||||
);
|
||||
|
||||
// 4. EpubFontEmbedder — build an EPUB, then patch
|
||||
const epubPath = path.join(TMP, 'ascii.epub');
|
||||
execFileSync('pandoc', [mdPath, '-o', epubPath], { stdio: 'pipe' });
|
||||
const patchedEpub = await EpubFontEmbedder.patchManifest(epubPath, [
|
||||
{ path: jbmRegular, family: 'JetBrains Mono', weight: 400 },
|
||||
]);
|
||||
const epubZip = await JSZip.loadAsync(fs.readFileSync(patchedEpub));
|
||||
const opfEntry = Object.keys(epubZip.files).find((f) => f.endsWith('content.opf'));
|
||||
check('EPUB has content.opf', !!opfEntry);
|
||||
const opf = await epubZip.file(opfEntry).async('string');
|
||||
check('EPUB OPF references the TTF', /href="OEBPS\/fonts\/JetBrainsMono-Regular\.ttf"/.test(opf));
|
||||
check(
|
||||
'EPUB OPF declares x-font-ttf media type',
|
||||
/media-type="application\/x-font-ttf"/.test(opf)
|
||||
);
|
||||
check(
|
||||
'EPUB embeds the TTF in OEBPS/fonts/',
|
||||
Object.keys(epubZip.files).some((f) => /^OEBPS\/fonts\/JetBrainsMono-Regular\.ttf$/.test(f))
|
||||
);
|
||||
|
||||
// 5. HTML export via pandoc with --css pointing at ExportCss output
|
||||
const cssFile = path.join(TMP, 'mono.css');
|
||||
fs.writeFileSync(cssFile, css, 'utf-8');
|
||||
const htmlPath = path.join(TMP, 'ascii.html');
|
||||
execFileSync('pandoc', [mdPath, '-s', `--css=${cssFile}`, '-o', htmlPath], { stdio: 'pipe' });
|
||||
const html = fs.readFileSync(htmlPath, 'utf-8');
|
||||
// Pandoc with --css writes a <link rel="stylesheet"> reference rather than
|
||||
// inlining the CSS; the @font-face lives in the sidecar CSS file.
|
||||
const linksCss = /<link\s+rel="stylesheet"\s+href="[^"]*\.css/.test(html);
|
||||
check(
|
||||
'HTML export links the monospace CSS',
|
||||
linksCss,
|
||||
linksCss ? '' : 'expected <link rel="stylesheet" href="*.css"> in HTML'
|
||||
);
|
||||
const cssText = fs.readFileSync(cssFile, 'utf-8');
|
||||
check('Sidecar CSS declares @font-face', cssText.includes('@font-face'));
|
||||
check('Sidecar CSS embeds the font via data URI', cssText.includes('data:font/woff2;base64,'));
|
||||
|
||||
// Cleanup
|
||||
fs.rmSync(TMP, { recursive: true, force: true });
|
||||
console.log(`Exit code: ${process.exitCode || 0}`);
|
||||
})().catch((err) => {
|
||||
console.error('Smoke test crashed:', err);
|
||||
process.exitCode = 2;
|
||||
});
|
||||
Reference in New Issue
Block a user