/** * Regression tests for the v2.0.3 journal fixes. * * These run the real functions out of the shipping HTML build, so they fail * if the demo and shipping files drift apart or if the encoding changes. */ import { describe, it, expect } from 'vitest'; import { readFileSync, existsSync } from 'node:fs'; import { execSync } from 'node:child_process'; const DEMO = 'public/demo-app/index.html'; const SHIP = 'release/The_Eternal_Journal.html'; if (!existsSync(SHIP)) execSync('node scripts/build-journal-shipping.mjs'); const demo = readFileSync(DEMO, 'utf8'); const ship = readFileSync(SHIP, 'utf8'); /** Pulls a named function's source out of the HTML and evaluates it. */ function extract(names: string[]) { const parts = names.map((name) => { const start = ship.indexOf(`function ${name}(`); if (start === -1) throw new Error(`function ${name} not found`); let depth = 0; let i = ship.indexOf('{', start); const open = i; for (; i < ship.length; i++) { if (ship[i] === '{') depth++; else if (ship[i] === '}') { depth--; if (depth === 0) break; } } return ship.slice(start, i + 1) + `\nglobalThis.${name} = ${name};`; }); // eslint-disable-next-line no-new-func new Function('BASE64_CHUNK_SIZE', 'MIN_PASSPHRASE_LENGTH', parts.join('\n'))(8192, 12); } const g = globalThis as any; g.window = g.window || {}; extract(['bytesToBase64', 'base64ToBytes', 'getMinimumPassphraseLength', 'describePassphraseStrength']); const bytesToBase64: (bytes: Uint8Array) => string = g.bytesToBase64; const base64ToBytes: (value: string) => Uint8Array = g.base64ToBytes; const describePassphraseStrength: (value: string) => string = g.describePassphraseStrength; const legacyEncode = (bytes: Uint8Array) => btoa(String.fromCharCode(...Array.from(bytes))); describe('parity between demo and shipping builds', () => { it('differs only by the caret CSS and the demo bootstrap', () => { const stripped = demo .replace('\n caret-color:var(--tx);-webkit-text-fill-color:var(--tx);}', '}') .replace(/