diff --git a/scripts/export.checks.test.ts b/scripts/export.checks.test.ts index cc64200d..ff6e7d8a 100644 --- a/scripts/export.checks.test.ts +++ b/scripts/export.checks.test.ts @@ -130,7 +130,7 @@ describe('exported content width', () => { test('the export cap loses to the print block, so the PDF route is unchanged', () => { // `@media print` in styles.css already forces `.markdown-body` to - // `max-width: 100% !important` with its own .75in padding, and that block + // `max-width: 100% !important` with its own 1cm padding, and that block // travels into the export with the rest of the sheet. Printing an exported // file therefore never saw the 900px and must not start seeing the setting // either — the two routes stay separate concerns. @@ -138,7 +138,7 @@ describe('exported content width', () => { const printed = declarationsFor(css, 'max-width', 'print'); assert.ok(printed.length >= 1, 'the print block must still cap .markdown-body'); assert.deepEqual(printed.at(-1), { value: '100%', important: true }); - assert.deepEqual(declarationsFor(css, 'padding', 'print').at(-1), { value: '0.75in', important: true }); + assert.deepEqual(declarationsFor(css, 'padding', 'print').at(-1), { value: '1cm', important: true }); }); test('the app stylesheet copied into an export does not out-rank the cap', () => { @@ -801,11 +801,12 @@ describe('oversized media in print', () => { const printBlock = extractPrintBlock(styles); // Paper geometry the cap has to satisfy. `@page { margin: 0 }` makes the page - // box the whole sheet, and `.markdown-body` supplies 0.75in of padding top and - // bottom, so the printable column is the sheet height minus 1.5in. - const LETTER_PRINTABLE_IN = 11 - 1.5; + // box the whole sheet, and `.markdown-body` supplies 1cm of padding top and + // bottom, so the printable column is the sheet height minus 2cm. + const MARGINS_IN = 2 / 2.54; + const LETTER_PRINTABLE_IN = 11 - MARGINS_IN; - const A4_PRINTABLE_IN = 11.69 - 1.5; + const A4_PRINTABLE_IN = 11.69 - MARGINS_IN; /** Print rules whose body sets `property` (never a vendor-prefixed variant). */ function printRulesSetting(property: string): { selector: string; body: string }[] { diff --git a/scripts/export.spec.ts b/scripts/export.spec.ts new file mode 100644 index 00000000..b7b9d26c --- /dev/null +++ b/scripts/export.spec.ts @@ -0,0 +1,68 @@ +import assert from 'node:assert/strict'; +import { describe, test } from 'vitest'; + +import { buildExportDocument } from '../src/lib/utils/export.ts'; +import { plainAppearance } from './exportFixtures.ts'; +import { readSource } from './sourceTree.js'; + +const appStyles = readSource('src/styles.css'); + +/** + * Loads `html` into the jsdom document as it would come out on `media`. jsdom + * applies no media type itself, so each `@media` block that names `media` (or + * `all`) is replaced in place by its own rules, and every other `@media` block + * is dropped. + */ +function render(html: string, media: 'print' | 'screen'): Document { + document.documentElement.innerHTML = html.replace(/^\s*]*>/, '').replace(/<\/html>\s*$/, ''); + for (const sheet of Array.from(document.styleSheets)) { + for (let index = sheet.cssRules.length - 1; index >= 0; index -= 1) { + const rule = sheet.cssRules[index]; + if (!(rule instanceof CSSMediaRule)) continue; + const applies = /\ball\b/.test(rule.media.mediaText) || rule.media.mediaText.includes(media); + const inner = applies ? Array.from(rule.cssRules, (r) => r.cssText) : []; + sheet.deleteRule(index); + inner.forEach((text, offset) => sheet.insertRule(text, index + offset)); + } + } + return document; +} + +function article(doc: Document): CSSStyleDeclaration { + const element = doc.querySelector('article.markdown-body'); + assert.ok(element, 'the page has no article'); + return getComputedStyle(element); +} + +const exported = buildExportDocument({ + theme: 'light', + title: 'Notes', + styles: appStyles, + articleHtml: '
body
', + contentWidth: null, + appearance: plainAppearance, +}); + +describe('printing an exported HTML file', () => { + test('prints the article instead of a blank page', () => { + // The copied print sheet hides everything outside #app, which an exported + // file does not have, so the article itself was hidden on paper. + assert.notEqual(article(render(exported, 'print')).display, 'none'); + }); + + test('gets the same paper margins as the app’s own PDF', () => { + // The PDF route prints #print-root inside #app; the export used to keep its + // screen padding on paper instead. + const pdf = render( + `