101 lines
6.7 KiB
TypeScript
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 }); }
|
|
});
|
|
});
|