storebound/tests/browser/palette-bg.browser.ts

65 lines
4.3 KiB
TypeScript

// Regression: an app-style palette with a string bg ('#15171C' or 'linear-gradient(...)') rendered a WHITE background
// (the template indexed bg[0..2] of a string), so white ink headlines vanished. Now the bg goes through palette.ts
// bgCss, and `shots render` samples the pixels behind the title (titleContrast) for `shots check`.
import { afterAll, describe, expect, test } from 'bun:test';
import { readFileSync } from 'node:fs';
import { join } from 'node:path';
import { DEFAULT_PHONE, DEFAULT_TABLET } from '../../cli/shots/lib';
import { bgCss, contrastIssues, photoCss } from '../../cli/shots/palette';
import { titleContrast } from '../../cli/shots/render';
import { AFTER_ALL_MS, launchTestBrowser, scaled } from '../helpers/browser';
const shared = await launchTestBrowser('playwright-core');
const { browser } = shared;
if (!browser) console.warn(`palette bg render test skipped: no Chromium installed (${shared.skipWhy}).`);
afterAll(() => shared.close(), AFTER_ALL_MS);
const PNG = Buffer.from('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==', 'base64');
const html = readFileSync(join(import.meta.dir, '..', '..', 'cli', 'shots', 'template-app.html'), 'utf8');
async function render(bg: unknown, ink: string, photo = false): Promise<{ contrast: Awaited<ReturnType<typeof titleContrast>>; bgImage: string; bgColor: string }> {
const page = await browser.newPage({ viewport: { width: 540, height: 960 } });
try {
const pal = { bg, glow: '#93c5fd', ink, shadow: 'rgba(0,0,0,.4)' };
const css = photo ? photoCss('/bgimg/0', 0.6) + ', ' + bgCss(bg as never) : bgCss(bg as never);
const job = { style: 'app', phone: DEFAULT_PHONE, tablet: DEFAULT_TABLET, landscape: { w: 1440, h: 900, bar: 0 }, side: 'left', logo: false, appName: 'Demo',
fonts: { title: 'X', body: 'Y' }, number: 1, scene: { id: 'a', frames: [{ src: 'a.png' }] },
palette: { ...pal, bgCss: css, bgTone: photo ? '#000000' : undefined, ...(photo ? { photo: { overlay: 0.6 } } : {}) },
copy: { eyebrow: 'Notes', title: ['Calm notes', 'for busy days'], sub: 'Synced everywhere.' } };
await page.route('http://t.test/**', (r: { request(): { url(): string }; fulfill(o: unknown): Promise<void> }) => {
const u = new URL(r.request().url());
if (u.pathname.endsWith('.html')) return r.fulfill({ contentType: 'text/html', body: html });
if (u.pathname === '/job.json') return r.fulfill({ contentType: 'application/json', body: JSON.stringify(job) });
if (u.pathname.startsWith('/frames/') || u.pathname.startsWith('/bgimg/')) return r.fulfill({ contentType: 'image/png', body: PNG });
return r.fulfill({ contentType: 'text/css', body: '' });
});
await page.goto('http://t.test/template-app.html?s=a&w=540&h=960&lang=en');
await page.waitForFunction("document.body.dataset.ready === '1'", null, { timeout: 15000 });
const st = JSON.parse(await page.evaluate("JSON.stringify((s => ({ i: s.backgroundImage, c: s.backgroundColor }))(getComputedStyle(document.getElementById('c'))))")) as { i: string; c: string };
return { contrast: await titleContrast(page, 540, 960), bgImage: st.i, bgColor: st.c };
} finally { await page.close(); }
}
describe.skipIf(!browser)('app template: palette bg forms and the contrast guard', () => {
test('a flat colour string renders that colour, white ink passes', async () => {
const r = await render('#15171C', '#ffffff');
expect(r.bgColor).toBe('rgb(21, 23, 28)');
expect(r.contrast.length).toBeGreaterThan(0);
expect(contrastIssues(r.contrast)).toEqual([]);
}, scaled(30_000));
test('a gradient string passes through', async () => {
const r = await render('linear-gradient(160deg, #0b1736, #1d4ed8)', '#ffffff');
expect(r.bgImage).toContain('linear-gradient');
expect(contrastIssues(r.contrast)).toEqual([]);
}, scaled(30_000));
test('white ink on a white bg is flagged', async () => {
const r = await render(['#ffffff', '#fafafa', '#ffffff'], '#ffffff');
expect(contrastIssues(r.contrast).map(i => i.code)).toContain('contrast');
}, scaled(30_000));
test('a photo under the overlay keeps white ink legible', async () => {
const r = await render('#ffffff', '#ffffff', true);
expect(r.bgImage).toContain('/bgimg/0');
expect(contrastIssues(r.contrast)).toEqual([]);
}, scaled(30_000));
});