From f5dcffeb8ddb2a6bb0041292763afa70fbbeaa59 Mon Sep 17 00:00:00 2001 From: Amit Haridas Date: Wed, 1 Jul 2026 06:19:04 +0530 Subject: [PATCH] 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 - HTML export links the sidecar CSS which embeds the font Run with: node tests/smoke-e2e-monospace.js --- tests/smoke-e2e-monospace.js | 146 +++++++++++++++++++++++++++++++++++ 1 file changed, 146 insertions(+) create mode 100644 tests/smoke-e2e-monospace.js diff --git a/tests/smoke-e2e-monospace.js b/tests/smoke-e2e-monospace.js new file mode 100644 index 0000000..8e457a6 --- /dev/null +++ b/tests/smoke-e2e-monospace.js @@ -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 +// 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', / /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 reference rather than + // inlining the CSS; the @font-face lives in the sidecar CSS file. + const linksCss = / 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; +});