storebound/tests/shots-bleed.test.ts

94 lines
5.5 KiB
TypeScript

// shots.layout 'bleed' (the frame fills the image, a corner caption with a text shadow, no device) and
// shots.orientation 'landscape', plus the Mac App Store size (apple.mac). Pure checks, no browser.
import { describe, expect, test } from 'bun:test';
import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { type ShipConfig, validateConfig } from '../cli/lib/config';
import { SHOTS_PAGES } from '../cli/lib/embedded';
import { checkFrameFolder } from '../cli/shots/check';
import { renderDims } from '../cli/shots/lib';
import { checkImage } from '../cli/shots/spec';
function png(w: number, h: number): Uint8Array {
const b = new Uint8Array(8 + 25 + 12);
b.set([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);
const dv = new DataView(b.buffer);
dv.setUint32(8, 13); b.set([0x49, 0x48, 0x44, 0x52], 12); dv.setUint32(16, w); dv.setUint32(20, h); b[24] = 8; b[25] = 2;
dv.setUint32(33, 0); b.set([0x49, 0x45, 0x4e, 0x44], 37);
return b;
}
const base = (shots: Partial<ShipConfig['shots']> = {}, apple: ShipConfig['apple'] = { bundleId: 'com.example.game', mac: true }): ShipConfig => ({
app: { name: 'Example Game' }, languages: [{ id: 'en', apple: 'en-US', play: 'en-US' }],
apple, play: { packageName: 'com.example.game', tablets: false },
shots: { copy: 'c.json', frames: 'frames', out: 'out', layout: 'bleed', orientation: 'landscape',
scenes: [{ id: 'a', palette: 'night', frames: [{ src: 'a.png' }], corner: 'bl' }], feature: { frame: 'f.png' }, ...shots },
} as ShipConfig);
describe('landscape sizes', () => {
const dims = (k: string, o?: 'landscape' | 'portrait') => { const d = renderDims(k, { orientation: o }); return { w: d.w, h: d.h }; };
test('portrait sizes swap, feature, mac and chromebook stay', () => {
expect(dims('ios69', 'landscape')).toEqual({ w: 2868, h: 1320 });
expect(dims('ipad13', 'landscape')).toEqual({ w: 2752, h: 2064 });
expect(dims('play', 'landscape')).toEqual({ w: 1920, h: 1080 });
expect(dims('ios69')).toEqual({ w: 1320, h: 2868 });
expect(dims('feature', 'landscape')).toEqual({ w: 1024, h: 500 });
expect(dims('mac', 'landscape')).toEqual({ w: 2880, h: 1800 });
expect(dims('chromebook', 'landscape')).toEqual({ w: 1920, h: 1080 });
});
test('stores accept the landscape renders', () => {
for (const [k, w, h] of [['ios69', 2868, 1320], ['ipad13', 2752, 2064], ['mac', 2880, 1800], ['play', 1920, 1080], ['tab7', 1920, 1200], ['tab10', 2560, 1600]] as const)
expect(checkImage({ format: 'png', w, h, alpha: false, palette: false, bytes: 1000, depth: 8 }, k)).toEqual([]);
});
test('orientation landscape needs the bleed layout', () => {
expect(validateConfig(base({ layout: 'device' })).join(' ')).toContain("needs shots.layout: 'bleed'");
expect(validateConfig(base()).join(' ')).not.toContain('orientation');
});
});
describe('bleed layout', () => {
test('config checks', () => {
const bad = base({ layout: 'full' as never, orientation: 'sideways' as never, scenes: [{ id: 'a', palette: 'night', frames: [{ src: 'a.png' }], corner: 'mid' as never, captionScale: 3 }] });
const e = validateConfig(bad).join('\n');
expect(e).toContain('shots.layout: device | bleed');
expect(e).toContain('shots.orientation: portrait | landscape');
expect(e).toContain('corner tl | tr | bl | br');
expect(e).toContain('captionScale 0.6-1.4');
});
test('the pages ship embedded and draw no band behind the caption', () => {
for (const p of ['template-bleed.html', 'feature-bleed.html']) {
const html = SHOTS_PAGES[p]!;
expect(html).toBeTruthy();
expect(html).toContain('text-shadow');
// owner rule: legibility from a text shadow and glow, never a dark band, scrim or box behind the words
const css = html.slice(html.indexOf('<style>'), html.indexOf('</style>'));
expect(css).not.toMatch(/\.(cap|name)\b[^}]*background/);
expect(css).not.toMatch(/linear-gradient|scrim|band/);
}
});
test('frames per size at native pixels', () => {
const dir = mkdtempSync(join(tmpdir(), 'sb-bleed-'));
try {
const c = base();
const put = (rel: string, w: number, h: number) => { mkdirSync(join(dir, rel, '..'), { recursive: true }); writeFileSync(join(dir, rel), png(w, h)); };
const missing = checkFrameFolder(c, dir, ['en']).map(i => i.file);
expect(missing).toContain('en/ios69/a.png');
expect(missing).toContain('en/mac/a.png');
expect(missing).toContain('en/feature/f.png');
put('en/ios69/a.png', 2868, 1320); put('en/ios65/a.png', 2778, 1284); put('en/mac/a.png', 2880, 1800);
put('en/play/a.png', 1920, 1080); put('en/feature/f.png', 2048, 1000);
expect(checkFrameFolder(c, dir, ['en'])).toEqual([]);
put('en/play/a.png', 1080, 1920);
expect(checkFrameFolder(c, dir, ['en'])[0]!.msg).toContain('aspect');
put('en/play/a.png', 960, 540);
expect(checkFrameFolder(c, dir, ['en'])[0]!.msg).toContain('native resolution');
} finally { rmSync(dir, { recursive: true, force: true }); }
});
test('render serves the bleed pages and names the size in the URL', () => {
const src = readFileSync(join(import.meta.dir, '..', 'cli', 'shots', 'render.ts'), 'utf8');
expect(src).toContain("bleedLayout ? 'template-bleed'");
expect(src).toContain('&size=${encodeURIComponent(size)}');
expect(SHOTS_PAGES['template-bleed.html']).toContain('/frames/${encodeURIComponent(LANG)}/${encodeURIComponent(SIZE)}/');
});
});