diff --git a/.changes/detail-back-button-lane.md b/.changes/detail-back-button-lane.md index 401cf29f8..21a1e6da9 100644 --- a/.changes/detail-back-button-lane.md +++ b/.changes/detail-back-button-lane.md @@ -3,4 +3,4 @@ type: fix area: detail --- -On movie and series pages, the Back button no longer covers the start of the "Seasons and Episodes" heading or the corner of the player while you scroll, and the heading stays on one line in narrow windows. +On movie and series pages, the Back button no longer covers the start of the "Seasons and Episodes" heading or the corner of the player while you scroll, and in narrow windows the heading no longer gets squeezed onto several lines beside its buttons. diff --git a/apps/electron-backend-e2e/src/detail-back-lane.e2e.ts b/apps/electron-backend-e2e/src/detail-back-lane.e2e.ts index 0c0ceed2b..607303622 100644 --- a/apps/electron-backend-e2e/src/detail-back-lane.e2e.ts +++ b/apps/electron-backend-e2e/src/detail-back-lane.e2e.ts @@ -1,11 +1,15 @@ import type { Locator, Page } from '@playwright/test'; +import { readFileSync } from 'node:fs'; +import { join } from 'node:path'; import { addXtreamPortal, clickFirstGridListCard, closeElectronApp, expect, launchElectronApp, + openSettings, resetMockServers, + saveSettings, test, waitForXtreamWorkspaceReady, } from './electron-test-fixtures'; @@ -28,6 +32,16 @@ import { const widths = [1280, 780]; const compactWidths = [700, 375]; const playerCorner = 56; +/** The widest translation of the heading; it must fit wherever English does. */ +const widestLocale = 'nl'; +const widestHeading = ( + JSON.parse( + readFileSync( + join(__dirname, `../../web/src/assets/i18n/${widestLocale}.json`), + 'utf8' + ) + ) as { PORTALS: { SEASONS_AND_EPISODES: string } } +).PORTALS.SEASONS_AND_EPISODES; type Sweep = { overlaps: string[]; @@ -232,6 +246,35 @@ async function expectBackClearOfContent( } } +/** + * Re-checks the heading in the widest translation at the lane and bar widths. + * Below them (a ~220px header beside the category panel) a translation wider + * than the pane itself wraps by design rather than losing words to an + * ellipsis. + */ +async function expectWidestHeadingOnOneLine( + page: Page, + detailUrl: string +): Promise { + await page.setViewportSize({ width: widths[0], height: 800 }); + await openSettings(page); + await page.getByTestId('select-language').click(); + await page.getByTestId(widestLocale).click(); + await saveSettings(page); + await page.goBack(); + await page.waitForURL(detailUrl); + await expect(page.locator('.section-title')).toHaveText(widestHeading, { + timeout: 20_000, + }); + for (const width of [...widths, ...compactWidths]) { + await page.setViewportSize({ width, height: 800 }); + expect( + await headingLineCount(page), + `${widestLocale} at ${width}px` + ).toBe(1); + } +} + /** * The actions move onto their own row before the heading wraps, at every * pane width that can hold the heading at all — including the widths where @@ -266,6 +309,7 @@ test.describe('Portal detail Back lane', () => { /\/workspace\/xtreams\/[^/]+\/series\/[^/]+\/[^/]+$/ ); + const detailUrl = page.url(); const shell = page.locator('app-portal-detail-shell'); const episodes = page.locator('.episode-card'); await expect(page.locator('.section-title')).toBeVisible({ @@ -284,6 +328,7 @@ test.describe('Portal detail Back lane', () => { ).toBeVisible({ timeout: 20_000 }); await expectBackClearOfContent(page, 'watch'); + await expectWidestHeadingOnOneLine(page, detailUrl); } finally { await closeElectronApp(app); } diff --git a/docs/architecture/iptvnator-ui-guidelines.md b/docs/architecture/iptvnator-ui-guidelines.md index a84402d4f..360fec444 100644 --- a/docs/architecture/iptvnator-ui-guidelines.md +++ b/docs/architecture/iptvnator-ui-guidelines.md @@ -630,7 +630,9 @@ partial so Xtream and Stalker share the same behavior. The season header's actions wrap onto their own row, starting under the "Seasons and Episodes" heading, before the heading itself would wrap. The detail pane is narrower than the window (context panel, the sticky Back lane), -so the header's own width decides, not a viewport breakpoint. +so the header's own width decides, not a viewport breakpoint. A translation +wider than the pane itself wraps rather than ellipsizing: unlike a fixed-height +panel title, a content heading has room to wrap and should not lose words. Browser regression coverage measures the composited neutral edges and selected toggle fill, in addition to capturing light/dark grid and list screenshots.