storebound/tests/shots-gaps.test.ts

91 lines
5.2 KiB
TypeScript

// Real-use gaps in `shots`: feature rotate, check/render frame fallback per language, layout-aware capture selectors.
import { describe, expect, test } from 'bun:test';
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { DEFAULT_PHONE, featureLayout, frameLangs, langHasFrames } from '../cli/shots/lib';
import { applySelectors, candidates, missMessage, probe, validateSelector, type SelPage } from '../cli/shots/selectors';
import { validateCapture } from '../cli/shots/devices';
describe('feature rotate', () => {
test('adds to the template tilt and still fits', () => {
const a = featureLayout('game', DEFAULT_PHONE, {}), b = featureLayout('game', DEFAULT_PHONE, { rotate: -90 });
expect(a.angle).toBe(7); expect(b.angle).toBe(-83);
expect(b.box.w).toBeGreaterThan(b.box.h);
expect(b.box.x).toBeGreaterThanOrEqual(23.99); expect(b.box.x + b.box.w).toBeLessThanOrEqual(1024 - 23.99);
expect(b.box.y).toBeGreaterThanOrEqual(23.99); expect(b.box.y + b.box.h).toBeLessThanOrEqual(500 - 23.99);
});
});
describe('frame languages', () => {
const has = (set: string[]) => (l: string) => set.includes(l);
test('a language without frames borrows the default language', () => {
expect(frameLangs(['en', 'tr', 'de'], has(['en', 'de']), 'en')).toEqual({ map: { en: 'en', tr: 'en', de: 'de' }, borrowed: ['tr'] });
});
test('perLanguage requires its own', () => {
expect(frameLangs(['en', 'tr'], has(['en']), 'en', true)).toEqual({ map: { en: 'en', tr: 'tr' }, borrowed: [] });
});
test('no frames anywhere: nothing borrowed, the missing error names the language', () => {
expect(frameLangs(['en', 'tr'], has([]), 'en').borrowed).toEqual([]);
});
test('langHasFrames reads the folder', () => {
const d = mkdtempSync(join(tmpdir(), 'sb-fl-'));
try {
mkdirSync(join(d, 'en', 'phone'), { recursive: true }); writeFileSync(join(d, 'en', 'phone', 'a.png'), 'x');
mkdirSync(join(d, 'tr'), { recursive: true });
expect(langHasFrames(d, 'en')).toBe(true);
expect(langHasFrames(d, 'tr')).toBe(false);
expect(langHasFrames(d, 'de')).toBe(false);
} finally { rmSync(d, { recursive: true, force: true }); }
});
});
/** Fake page: a DOM of selector -> count; text= selectors are just keys. */
function fake(dom: Record<string, number>, clicks: string[] = []): SelPage {
return {
locator: (s: string) => ({ count: async () => { if (s.startsWith('!')) throw new Error('bad'); return dom[s] ?? 0; }, first: () => ({ click: async () => { clicks.push(s); } }) }),
waitForTimeout: async () => {},
};
}
describe('capture selectors', () => {
test('per-device list first, then shared, no repeats', () => {
const s = { phone: ['[data-shot="row"]'], tablet: ['[data-shot="tile"]', 'text=Shop'], any: ['text=Shop'] };
expect(candidates(s, 'tablet')).toEqual(['[data-shot="tile"]', 'text=Shop']);
expect(candidates(s, 'phone')).toEqual(['[data-shot="row"]', 'text=Shop']);
expect(candidates('a', 'phone')).toEqual(['a']);
expect(candidates(undefined, 'phone')).toEqual([]);
});
test('validation', () => {
expect(validateSelector('x', 'a')).toEqual([]);
expect(validateSelector('x', { tablet: ['a'] })).toEqual([]);
expect(validateSelector('x', { desktop: 'a' })[0]).toContain('unknown key desktop');
expect(validateSelector('x', '')).not.toEqual([]);
expect(validateSelector('x', {})).not.toEqual([]);
});
test('the click lands on the first candidate that matches', async () => {
const clicks: string[] = [];
await applySelectors(fake({ 'text=Shop': 1 }, clicks), { waitSelector: ['[data-shot="row"]', 'text=Shop'], click: { tablet: ['[data-shot="tile"]', 'text=Shop'] } }, { device: 'tablet', w: 1180, h: 820 }, 50);
expect(clicks).toEqual(['text=Shop']);
});
test('several clicks in order', async () => {
const clicks: string[] = [];
await applySelectors(fake({ a: 1, b: 1 }, clicks), { click: [{ any: 'a' }, 'b'] }, { device: 'phone', w: 440, h: 902 }, 50);
expect(clicks).toEqual(['a', 'b']);
});
test('a miss names the selector, the size and every match count', async () => {
const err = await applySelectors(fake({ '[data-shot="row"]': 3 }), { click: { tablet: ['[data-shot="tile"]', 'text=Shop'] } }, { device: 'tablet', w: 1180, h: 820 }, 30).then(() => '', e => (e as Error).message);
expect(err).toContain('click selector missed on tablet 1180x820');
expect(err).toContain('[data-shot="tile"] matched 0');
expect(err).toContain('text=Shop matched 0');
expect(missMessage('click', { device: 'phone', w: 1, h: 2 }, [{ selector: '!x', matches: -1 }])).toContain('is not a valid selector');
});
test('probe counts every candidate', async () => {
expect(await probe(fake({ a: 2 }), ['a', 'b'])).toEqual([{ selector: 'a', matches: 2 }, { selector: 'b', matches: 0 }]);
});
test('validateCapture accepts and rejects', () => {
const cap = (shot: object) => ({ source: 'web', baseUrl: 'http://127.0.0.1:1', shots: { shop: { path: '/', ...shot } } }) as never;
expect(validateCapture(cap({ waitSelector: { tablet: 'a' }, click: ['a', 'b'] }))).toEqual([]);
expect(validateCapture(cap({ click: { desktop: 'a' } }))[0]).toContain('unknown key desktop');
});
});