diff --git a/tests/helpers/load-plugin-modules.mjs b/tests/helpers/load-plugin-modules.mjs index 4b9a9ae..80e1fd4 100644 --- a/tests/helpers/load-plugin-modules.mjs +++ b/tests/helpers/load-plugin-modules.mjs @@ -43,6 +43,7 @@ let textToolbarControllerModulePromise; let insertControllerModulePromise; let arrangeControllerModulePromise; let slideExtensionPreserveModulePromise; +let renderedPdfExportModulePromise; globalThis.DOMParser ??= DOMParser; globalThis.XMLSerializer ??= XMLSerializer; @@ -379,6 +380,16 @@ export function loadLoggerModule() { return loggerModulePromise; } +export function loadRenderedPdfExportModule() { + renderedPdfExportModulePromise ??= bundleSource( + "src/renderedPdfExport.ts", + "rendered-pdf-export.cjs", + ["html2canvas"], + [stubObsidianPlugin], + ).then((outfile) => require(outfile)); + return renderedPdfExportModulePromise; +} + export function loadNativePowerPointViewModule() { viewModulePromise ??= bundleSource( "src/NativePowerPointView.ts", diff --git a/tests/rendered-pdf-export.test.mjs b/tests/rendered-pdf-export.test.mjs index b6bce43..543bb2b 100644 --- a/tests/rendered-pdf-export.test.mjs +++ b/tests/rendered-pdf-export.test.mjs @@ -3,6 +3,7 @@ import { readFile } from 'node:fs/promises'; import path from 'node:path'; import test from 'node:test'; import { fileURLToPath } from 'node:url'; +import { loadRenderedPdfExportModule } from './helpers/load-plugin-modules.mjs'; const projectRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'); @@ -21,3 +22,39 @@ test('rendered PDF export uses detached canvases and prefers the SVG renderer', 'SVG rendering should be attempted before html2canvas', ); }); + +test('dataUrlToBytes decodes standard data URLs, raw base64, empty payloads, and binary byte ranges', async () => { + const { dataUrlToBytes } = await loadRenderedPdfExportModule(); + + // Standard data URL with image/jpeg header + const helloWorldBase64 = Buffer.from('Hello World').toString('base64'); + const jpegDataUrl = `data:image/jpeg;base64,${helloWorldBase64}`; + const bytes1 = dataUrlToBytes(jpegDataUrl); + assert.deepEqual(bytes1, new Uint8Array(Buffer.from('Hello World'))); + + // Data URL with custom MIME type and parameters + const textDataUrl = `data:text/plain;charset=utf-8;base64,${helloWorldBase64}`; + const bytes2 = dataUrlToBytes(textDataUrl); + assert.deepEqual(bytes2, new Uint8Array(Buffer.from('Hello World'))); + + // Raw base64 string without comma prefix + const bytes3 = dataUrlToBytes(helloWorldBase64); + assert.deepEqual(bytes3, new Uint8Array(Buffer.from('Hello World'))); + + // Empty payloads + const emptyStringBytes = dataUrlToBytes(''); + assert.deepEqual(emptyStringBytes, new Uint8Array(0)); + + const emptyHeaderBytes = dataUrlToBytes('data:image/png;base64,'); + assert.deepEqual(emptyHeaderBytes, new Uint8Array(0)); + + // Full 8-bit byte range round-trip (0x00 .. 0xFF) + const allBytes = new Uint8Array(256); + for (let i = 0; i < 256; i++) { + allBytes[i] = i; + } + const binaryBase64 = Buffer.from(allBytes).toString('base64'); + const binaryDataUrl = `data:application/octet-stream;base64,${binaryBase64}`; + const bytes4 = dataUrlToBytes(binaryDataUrl); + assert.deepEqual(bytes4, allBytes); +});