From deea6ad06d54e5c9a82a2366a0784e17a4f19de4 Mon Sep 17 00:00:00 2001 From: Logan Date: Fri, 21 Aug 2026 10:44:53 -0400 Subject: [PATCH] test(player): pin the desktop timer gate, drop a leaked queue MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- e2e/specs/phone-progress-line.spec.ts | 14 ++++++++ .../test/components/progress-line.test.ts | 34 +++++++++++++++++-- 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/e2e/specs/phone-progress-line.spec.ts b/e2e/specs/phone-progress-line.spec.ts index 2de3b3a..006e79f 100644 --- a/e2e/specs/phone-progress-line.spec.ts +++ b/e2e/specs/phone-progress-line.spec.ts @@ -79,6 +79,20 @@ test.describe('the progress line sits on the border between the bars', () => { 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, }) => { diff --git a/frontend/test/components/progress-line.test.ts b/frontend/test/components/progress-line.test.ts index 6b41cf6..5409ce3 100644 --- a/frontend/test/components/progress-line.test.ts +++ b/frontend/test/components/progress-line.test.ts @@ -7,8 +7,8 @@ * backend reported rather than a count of its own, and that it is * neither announced nor touchable. It cannot see where it sits — that * is the shell's grid, and it is asserted in - * `e2e/specs/phone-transport.spec.ts` where there is a real bar with a - * real tab bar under it. + * `e2e/specs/phone-progress-line.spec.ts` where there is a real bar + * with a real tab bar under it. */ import { describe, expect, it, beforeEach, afterEach, vi } from 'vitest'; @@ -168,6 +168,36 @@ describe('', () => { expect(scale(el)).toBeCloseTo(41 / 90, 3); }); + /* + * The reason this component asks `matchMedia` instead of letting a + * stylesheet hide it: a media query cannot stop a 1 Hz interval + * running for the life of every desktop session. That claim is + * load-bearing in CLAUDE.md, so it is asserted rather than + * described — the timer count, because a desktop render is empty + * either way and so cannot tell the two apart. + */ + it('runs no interpolation timer above the breakpoint', async () => { + pretendPhone(false); + vi.useFakeTimers(); + + const el = await fixture('player-progress-line'); + + emit(Events.TrackChanged, { ...TRACK, trackChangeId: 9 }); + emit(Events.PlaybackStateChanged, { state: 'playing' }); + emit(Events.PlaybackPositionChanged, { + positionSeconds: 3, + trackLength: 90, + trackChangeId: 9, + seq: 9, + playing: true, + }); + await vi.advanceTimersByTimeAsync(5000); + await el.updateComplete; + + expect(scale(el)).toBeNull(); + expect(vi.getTimerCount()).toBe(0); + }); + it('ignores a report about a track that is no longer loaded', async () => { pretendPhone(true);