Files
yellowjacket/e2e/specs/phone-progress-line.spec.ts
T
logan deea6ad06d
CI / check (push) Skipped
CI / e2e (push) Skipped
CI / check (pull_request) Successful in 2m32s
CI / e2e (pull_request) Successful in 9m14s
test(player): pin the desktop timer gate, drop a leaked queue
Two gaps a review found. The this.phone gate on the interpolation interval is what CLAUDE.md says earns the matchMedia call, and every test passed without it — so it is asserted on the timer count now, since a desktop render is empty either way and cannot tell the two apart. Watched failing with the gate removed.

The e2e spec left LONG_TRACK playing in a workers: 1 suite against one long-lived app, immediately before four other phone-* specs. Nine specs clear the queue in afterEach for that reason and phone-transport.spec.ts records the flake it caused.
2026-08-21 10:45:15 -04:00

137 lines
4.4 KiB
TypeScript

import {
test,
expect,
callBinding,
resetEvents,
waitForEvent,
LONG_TRACK,
NO_QUEUE_SOURCE,
} from '../support/fixtures.js';
import type { Page } from '@playwright/test';
/**
* The phone's progress line (#58).
*
* The component tier already pins what the line *says* — that it
* renders the backend's reported position and never a count of its own.
* What only a real shell can answer is **where it is**: the issue asks
* for a line on the border between the mini player and the tab bar, and
* "on the border" is two adjacencies in a grid that no component-level
* render has around it.
*
* It also asserts the line is not there on a desktop, which is the
* other half of the same fact: above 600px there is no tab bar for it
* to sit on the border of, and the bar carries a real seek bar.
*/
type Rect = { x: number; y: number; width: number; height: number };
/** The reference device's real viewport. */
const DEVICE = { width: 424, height: 439 };
const DESKTOP = { width: 1280, height: 800 };
async function rectOf(app: Page, selector: string): Promise<Rect | null> {
return app.evaluate((sel) => {
const el = document.querySelector(sel);
if (!el) return null;
const r = el.getBoundingClientRect();
return { x: r.x, y: r.y, width: r.width, height: r.height };
}, selector);
}
/**
* Put the 90-second fixture on and wait for the first position report.
*
* The long track rather than any track: every other fixture is 2-6
* seconds, which is shorter than the time this spec takes to measure
* three rectangles.
*/
async function play(app: Page): Promise<void> {
const tracks = await callBinding<{ FilePath: string; TrackName: string }[]>(
app,
'library.Library.GetTracks',
[0],
);
// `TrackName`, not `Title`: that is what the library model calls it.
const long = tracks.find((t) => t.TrackName === LONG_TRACK);
expect(long, `no fixture track named ${LONG_TRACK}`).toBeTruthy();
await callBinding(app, 'queue.Queue.Clear');
await resetEvents(app);
await callBinding(app, 'queue.Queue.SetQueue', [
[long!.FilePath],
0,
false,
NO_QUEUE_SOURCE,
]);
await waitForEvent(app, 'QueueChanged');
await callBinding(app, 'queue.Queue.Play');
await waitForEvent(app, 'PlaybackPositionChanged', { timeoutMs: 15_000 });
}
test.describe('the progress line sits on the border between the bars', () => {
test.beforeEach(async ({ app }) => {
await app.setViewportSize(DEVICE);
await play(app);
});
/*
* Every test here starts a LONG_TRACK and the suite is workers: 1,
* fullyParallel: false against one long-lived app — so without this
* the four phone-* specs that follow alphabetically inherit a playing
* queue. phone-transport.spec.ts records where that lesson came from:
* the fault first showed up as a flake in a spec about something else.
*/
test.afterEach(async ({ app }) => {
await callBinding(app, 'queue.Queue.Clear').catch(() => {
/* already empty */
});
await app.setViewportSize(DESKTOP);
});
test('spans the width, between the mini player and the tab bar', async ({
app,
}) => {
const line = await rectOf(app, 'player-progress-line');
const bar = await rectOf(app, '.bottom-bar');
const nav = await rectOf(app, 'bottom-nav');
expect(line, 'no progress line on the phone').not.toBeNull();
expect(bar).not.toBeNull();
expect(nav).not.toBeNull();
// A border, not a band: 2px, the full width, and touching both.
expect(line!.height).toBeCloseTo(2, 0);
expect(line!.width).toBeCloseTo(bar!.width, 0);
expect(line!.y).toBeCloseTo(bar!.y + bar!.height, 0);
expect(nav!.y).toBeCloseTo(line!.y + line!.height, 0);
});
/**
* It is 2px on the top edge of the tab bar, which is exactly where a
* thumb aiming at a tab lands. A line that sometimes seeks is worse
* than one that never does, so it must take no part in hit testing
* at all.
*/
test('takes no taps', async ({ app }) => {
const line = await rectOf(app, 'player-progress-line');
const hit = await app.evaluate(
({ x, y }) => document.elementFromPoint(x, y)?.tagName ?? '',
{ x: line!.x + line!.width / 2, y: line!.y + 1 },
);
expect(hit).not.toBe('PLAYER-PROGRESS-LINE');
});
test('is not there on a desktop', async ({ app }) => {
await app.setViewportSize(DESKTOP);
await expect(app.locator('player-progress-line')).toBeHidden();
});
});