// Behavioral browser checks for the accepted publication surface. const { chromium } = require('playwright'); const { readFileSync } = require('fs'); const assert = require('node:assert/strict'); (async () => { const origin = process.env.ODYSSEUS_TEST_STATIC_ORIGIN; const browser = await chromium.launch({ headless: true }); try { const page = await browser.newPage(); const mediaRequests = []; const errors = []; page.on('request', req => { if (/\.(webm|mp4)(?:$|\?)/.test(req.url())) mediaRequests.push(req.url()); }); page.on('pageerror', e => errors.push(e.message)); await page.goto(origin + '/website/index.html'); assert.equal(await page.locator('video, .preview-panel, .sec-bg-tint').count(), 0); assert.equal(await page.locator('.feature-description').count(), 8); for (const description of await page.locator('.feature-description .desc').allTextContents()) assert(description.trim()); for (const width of [1280, 390]) { await page.setViewportSize({ width, height: 900 }); assert(await page.locator('.feature-description').first().isVisible()); assert(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)); } assert.deepEqual(mediaRequests, []); assert.deepEqual(errors, []); const manifest = await (await page.request.get(origin + '/static/manifest.json')).json(); assert.equal(manifest.display, 'standalone'); assert(!manifest.icons); for (const filename of ['index.html', 'login.html']) { const html = await (await page.request.get(origin + '/static/' + filename)).text(); assert(!/]+rel=["']apple-touch-icon/.test(html)); } // A small host DOM runs the actual export function and markdown renderer. await page.route('**/publication-harness', route => route.fulfill({ contentType: 'text/html', body: '
' })); await page.route('**/api/**', route => route.fulfill({ contentType: 'application/json', body: JSON.stringify({ fonts: {}, value: null }) })); await page.goto(origin + '/publication-harness'); const source = readFileSync('static/js/document.js', 'utf8'); const fn = source.match(/\n async function exportAsPdf\(\) \{(.*?)\n \}\n/s)[0]; const result = await page.evaluate(async ({ origin, fn }) => { const module = await import(origin + '/static/js/markdown.js'); const markdownModule = module.default || module; const _isRichTextLang = lang => lang === 'html'; const _isDocxLang = () => false; const _richTextExportCss = () => 'p { color: black; }'; const _getExportBaseName = () => 'Print test'; const activeDocId = 'test'; const errors = []; const uiModule = { showError: message => errors.push(message) }; const originalRender = markdownModule.renderMath; const prints = []; markdownModule.renderMath = async container => { await originalRender(container); const frame = document.getElementById('doc-browser-print-frame'); frame.contentWindow.print = () => { prints.push({ body: frame.contentDocument.body.innerHTML, title: frame.contentDocument.title, links: [...frame.contentDocument.querySelectorAll('link')].map(l => l.href) }); frame.contentWindow.dispatchEvent(new Event('afterprint')); }; }; const run = eval('(' + fn.trim() + ')'); const textarea = document.getElementById('doc-editor-textarea'); textarea.value = 'Formula $E = mc^2$ here.'; await run(); document.getElementById('doc-language-select').value = 'text'; textarea.value = '& literal'; await run(); document.getElementById('doc-language-select').value = 'html'; textarea.value = '

Rich content

'; await run(); const oldPrint = window.print; window.print = undefined; await run(); window.print = oldPrint; return { prints, errors, unsafe: !!window.unsafe, frameRemaining: !!document.getElementById('doc-browser-print-frame') }; }, { origin, fn }); assert.equal(result.prints.length, 3); assert(result.prints[0].body.includes('class="katex"')); assert(!result.prints[0].body.includes('ody-math-pending')); assert(result.prints[0].links.some(url => url.includes('/katex/'))); assert.equal(result.prints[0].title, 'Print test'); assert(result.prints[1].body.includes('<script>')); assert(result.prints[2].body.includes('Rich content')); assert.equal(result.unsafe, false); assert.equal(result.frameRemaining, false); assert.deepEqual(result.errors, ['Browser printing is unavailable.']); // Actual theme module: default, legacy local/server selections and custom theme preferences. const fonts = await page.evaluate(async origin => { localStorage.setItem('odysseus-theme', JSON.stringify({ name: 'dark', colors: {}, font: 'gohu' })); localStorage.setItem('odysseus-custom-themes', JSON.stringify({ old: { font: 'GohuFont' } })); const theme = await import(origin + '/static/js/theme.js'); theme.applyFontDensity(null, null); const fallback = document.documentElement.style.getPropertyValue('--font-family'); theme.applyFontDensity('gohu', null); const legacy = document.documentElement.style.getPropertyValue('--font-family'); theme.initThemeUI(); const selected = document.getElementById('theme-font-select').value; const saved = theme.getSaved().font; const custom = theme.getCustomThemes().old.font; theme.applyFontDensity('sans', null); const sans = document.documentElement.style.getPropertyValue('--font-family'); return { fallback, legacy, selected, saved, custom, sans }; }, origin); assert.equal(fonts.fallback, "'Fira Code', monospace"); assert.equal(fonts.legacy, fonts.fallback); assert.equal(fonts.selected, 'mono'); assert.equal(fonts.saved, 'mono'); assert.equal(fonts.custom, 'mono'); assert(fonts.sans.includes('system-ui')); // Verify the real early bootstrap handles persisted legacy preferences. const appHtml = readFileSync('static/index.html', 'utf8'); const bootstrap = [...appHtml.matchAll(/]*)?>([\s\S]*?)<\/script>/g)].map(m => m[1]).find(code => code.includes('Apply font early')); assert(bootstrap); const early = await page.evaluate(async code => { const theme = await import('/static/js/theme.js'); localStorage.setItem('odysseus-theme', JSON.stringify({ name: 'dark', colors: theme.THEMES.dark, font: 'gohu' })); eval(code); return document.documentElement.style.getPropertyValue('--font-family'); }, bootstrap); assert.equal(early, fonts.fallback); console.log(JSON.stringify({ website: true, fonts: true, print: true, pwa: true })); } finally { await browser.close(); } })().catch(e => { console.error(e); process.exit(1); });