storebound/tests/capture-landscape.test.ts

101 lines
6.7 KiB
TypeScript

// `shots capture --device landscape`: desktop frames for Mac App Store / Chromebook screenshots from a web build.
// Dry runs and pure planning only: no browser, no simulator.
import { afterEach, describe, expect, test } from 'bun:test';
import { rmSync } from 'node:fs';
import type { ShipConfig } from '../cli/lib/config';
import { planWeb } from '../cli/shots/capture-web';
import { run as captureRun } from '../cli/shots/capture';
import { shotsFor, validateCapture } from '../cli/shots/devices';
import { frameDims, landscapeDims, rendersLandscape } from '../cli/shots/lib';
import { BASE, capture, project, restore } from './helpers/store-mock';
afterEach(restore);
const cap = { source: 'web', baseUrl: 'http://127.0.0.1:5173', shots: {
hero: { path: '/?scene=hero' }, wide: { path: '/?scene=wide', device: 'landscape' }, pad: { path: '/pad', device: 'tablet' }, both: { path: '/both', device: 'both' }, ph: { path: '/ph', device: 'phone' },
} } as const;
const cfg = (over: Record<string, unknown> = {}, shots: Record<string, unknown> = {}): ShipConfig =>
({ ...BASE, ...over, shots: { frames: 'frames', copy: 'copy.json', scenes: [], capture: cap, ...shots } }) as unknown as ShipConfig;
const names = (c: Parameters<typeof shotsFor>[0], d: 'phone' | 'tablet' | 'landscape', all = false) => shotsFor(c, d, [], all).map(([n]) => n);
describe('landscape device selection', () => {
test('phone / tablet unchanged; landscape only for explicit shots unless the project renders landscape', () => {
expect(names(cap, 'phone')).toEqual(['hero', 'both', 'ph']);
expect(names(cap, 'tablet')).toEqual(['pad', 'both']);
expect(names(cap, 'landscape')).toEqual(['wide']);
expect(names(cap, 'landscape', true)).toEqual(['hero', 'wide', 'both']); // not the explicit phone / tablet ones
});
test('validateCapture accepts landscape and names it in the error', () => {
expect(validateCapture({ ...cap, shots: { a: { path: '/', device: 'landscape' } } })).toEqual([]);
expect(validateCapture({ ...cap, shots: { a: { path: '/', device: 'desktop' as 'both' } } }).join()).toContain('phone | tablet | both | landscape');
});
test('rendersLandscape follows the render rules (mac, chromebook, playTablets)', () => {
expect(rendersLandscape(cfg())).toBe(false);
expect(rendersLandscape(cfg({ apple: { ...BASE.apple!, mac: true } }))).toBe(true);
expect(rendersLandscape(cfg({ apple: undefined, play: { ...BASE.play!, chromebook: true } }))).toBe(true);
expect(rendersLandscape(cfg({}, { landscape: { playTablets: true } }))).toBe(true);
});
test('landscapeDims: 1440 x 900 @2x default, shots.landscape.size overrides', () => {
expect(landscapeDims(cfg())).toEqual({ w: 1440, h: 900, dpr: 2, bar: 0 });
expect(landscapeDims(cfg({}, { landscape: { size: { w: 1280, h: 800 } } }))).toEqual({ w: 1280, h: 800, dpr: 2, bar: 0 });
expect(landscapeDims(cfg({}, { landscape: { size: { w: 1920, h: 1080, dpr: 1 } } }))).toEqual({ w: 1920, h: 1080, dpr: 1, bar: 0 });
});
});
describe('planWeb with landscape', () => {
test('jobs target <lang>/landscape/<name>.png and the hash tracks the landscape viewport', () => {
const c = cfg();
const base = { langs: ['en'], devices: ['landscape' as const], only: [], framesDir: '/f', build: 'b', allLandscape: true };
const a = planWeb(cap, { ...base, dims: { ...frameDims(c), landscape: landscapeDims(c) } });
expect(a.map(j => j.frame)).toEqual(['en/landscape/hero.png', 'en/landscape/wide.png', 'en/landscape/both.png']);
const b = planWeb(cap, { ...base, dims: { ...frameDims(c), landscape: { w: 1280, h: 800, dpr: 2, bar: 0 } } });
expect(b[0]!.hash).not.toBe(a[0]!.hash);
});
});
describe('capture --dry', () => {
const dry = async (config: ShipConfig, argv: string[]) => {
const p = project(config);
try { return await capture(() => captureRun(p.ctx(['--dry', ...argv], ['dry']))); } finally { rmSync(p.root, { recursive: true, force: true }); }
};
test('--device landscape lists landscape targets, no phone / tablet', async () => {
const r = await dry(cfg(), ['--device', 'landscape', '--lang', 'en']);
expect(r.out).toContain('en/landscape/wide.png <- http://127.0.0.1:5173/?scene=wide');
expect(r.out).toContain('en/landscape/hero.png'); // --device landscape captures every shot without a phone / tablet device
expect(r.out).not.toContain('en/phone/');
expect(r.out).not.toContain('en/tablet/');
expect(r.out).toContain('capture planned (dry run): 3 frames from web');
});
test('default devices add landscape only when the project renders a landscape size', async () => {
const plain = await dry(cfg(), ['--lang', 'en']);
expect(plain.out).not.toContain('en/landscape/hero.png');
expect(plain.out).toContain('en/phone/hero.png');
const mac = await dry(cfg({ apple: { ...BASE.apple!, mac: true } }), ['--lang', 'en']);
expect(mac.out).toContain('en/phone/hero.png');
expect(mac.out).toContain('en/tablet/pad.png');
for (const n of ['hero', 'wide', 'both']) expect(mac.out).toContain(`en/landscape/${n}.png`);
expect(mac.out).not.toContain('en/landscape/pad.png');
expect(mac.out).not.toContain('en/landscape/ph.png');
});
test('shot selectors accept a landscape key and print the landscape viewport', async () => {
const c = cfg({ apple: { ...BASE.apple!, mac: true } }, { capture: { ...cap, shots: { hero: { path: '/', waitSelector: { phone: '#m', landscape: '#desk' } } } } });
const r = await dry(c, ['--lang', 'en']);
expect(r.out).toContain('waitSelector on landscape 1440x900: #desk');
expect(r.out).toContain('waitSelector on phone 440x902: #m');
});
test('ios-sim / android-emu skip landscape with a warning', async () => {
const mac = { apple: { ...BASE.apple!, mac: true } };
const sim = await dry(cfg(mac, { capture: { source: 'ios-sim', ios: { bundleId: 'com.example.app', device: 'iPhone 17 Pro Max' }, shots: { hero: { path: '/', device: 'landscape' } } } }), ['--device', 'landscape', '--lang', 'en']);
expect(sim.err + sim.out).toContain('landscape frames (Mac, Chromebook) are not supported from ios-sim');
expect(sim.out).toContain('0 frames');
const and = await dry(cfg(mac, { capture: { source: 'android-emu', android: { package: 'com.example.app' }, shots: { hero: { path: '/' } } } }), ['--lang', 'en']);
expect(and.err + and.out).toContain('not supported from android-emu');
expect(and.out).not.toContain('landscape/hero');
});
test('an unknown device is refused', async () => {
const p = project(cfg());
try {
await expect(captureRun(p.ctx(['--dry', '--device', 'desktop'], ['dry']))).rejects.toThrow('--device: phone | tablet | landscape');
} finally { rmSync(p.root, { recursive: true, force: true }); }
});
});