From 97d9847aa5aaa58a961d0a14bf3173a249d72ff9 Mon Sep 17 00:00:00 2001 From: 4gray Date: Sun, 4 Oct 2026 11:37:48 +0200 Subject: [PATCH] test(e2e): sweep the EPG ribbon to see every programme Co-Authored-By: Claude Opus 5.5 --- .../src/xtream-epg.e2e.ts | 50 +++++++++++++++---- 1 file changed, 41 insertions(+), 9 deletions(-) diff --git a/apps/electron-backend-e2e/src/xtream-epg.e2e.ts b/apps/electron-backend-e2e/src/xtream-epg.e2e.ts index a5a901a97..f1741be35 100644 --- a/apps/electron-backend-e2e/src/xtream-epg.e2e.ts +++ b/apps/electron-backend-e2e/src/xtream-epg.e2e.ts @@ -285,7 +285,7 @@ for (const timeZone of ['UTC', 'Europe/Berlin'] as const) { // The timeline lays out the full multi-day window sorted by start // time (no per-day filtering — it scrolls) and renders the blocks // near the visible range: an ordered run around the current - // programme, and the last programme once scrolled to the end. + // programme, and every programme once the ribbon is swept. const allTitles = [...fixture.fullEpg] .sort((a, b) => a.startTimestamp - b.startTimestamp) .map((listing) => listing.title); @@ -298,14 +298,9 @@ for (const timeZone of ['UTC', 'Europe/Berlin'] as const) { ); }) .toBe(true); - await app.mainWindow - .locator('app-epg-timeline .epg-timeline__ribbon') - .evaluate((ribbon) => { - ribbon.scrollLeft = ribbon.scrollWidth; - }); - await expect - .poll(() => timelineBlockTitles(app.mainWindow)) - .toContain(allTitles[allTitles.length - 1]); + expect(await sweepTimelineTitles(app.mainWindow)).toEqual( + new Set(allTitles) + ); await app.mainWindow .locator('app-epg-timeline .epg-timeline__jump') .click(); @@ -886,6 +881,43 @@ async function timelineBlockTitles( .then((titles) => titles.map((title) => title.trim()).filter(Boolean)); } +/** + * Scroll the ribbon from its start to its end in half-viewport steps and + * collect every programme title rendered on the way. + */ +async function sweepTimelineTitles( + page: Parameters[0] +): Promise> { + const ribbon = page.locator('app-epg-timeline .epg-timeline__ribbon'); + const seen = new Set(); + let left = 0; + for (;;) { + const { scrollLeft, maxLeft, step } = await ribbon.evaluate( + (element, target) => { + element.scrollLeft = target; + return { + scrollLeft: element.scrollLeft, + maxLeft: element.scrollWidth - element.clientWidth, + step: Math.max(1, element.clientWidth / 2), + }; + }, + left + ); + // Two frames: the scroll's measure, then the re-rendered blocks. + await page.evaluate( + () => + new Promise((resolve) => + requestAnimationFrame(() => requestAnimationFrame(resolve)) + ) + ); + (await timelineBlockTitles(page)).forEach((title) => seen.add(title)); + if (scrollLeft >= maxLeft) { + return seen; + } + left = scrollLeft + step; + } +} + /** Whether `run` is a non-empty, in-order slice of `all`. */ function isContiguousRun(run: string[], all: string[]): boolean { if (run.length === 0) {