mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-12 03:56:15 -08:00
Merge remote-tracking branch 'origin/master' into claude/iptvnator-vod-hero-redesign-2c8950
# Conflicts: # apps/electron-backend-e2e/src/detail-back-lane.e2e.ts # apps/web-e2e/src/m3u-movie-details.e2e.ts # docs/architecture/iptvnator-ui-guidelines.md # libs/ui/components/src/lib/content-hero/content-hero.component.scss # libs/ui/components/src/lib/portal-detail-shell/portal-detail-shell.component.scss
This commit is contained in:
commit
87d7e1d46c
122 files changed
+2080
-1040
No files matched your search
@@ -345,17 +345,15 @@ test('@web @m3u @tmdb recognized movies open the VOD detail view', async ({
|
||||
).toEqual(['app-html-video-player']);
|
||||
|
||||
// Metadata patches the mounted view asynchronously. The shell stamps the
|
||||
// host templates into BOTH the hero and the watch-state About block, so
|
||||
// every metadata string legitimately resolves twice.
|
||||
// host templates into BOTH the hero and the watch-state About block, but
|
||||
// the collapsed hero turns `visibility: hidden` 300ms after mount,
|
||||
// sometimes before TMDB answers. Assert the copy a watching user sees.
|
||||
const about = detail(page).locator('app-content-about');
|
||||
await expect(
|
||||
detail(page).getByText('Paul Atreides arrives on Arrakis.').first()
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
detail(page).getByText('Denis Villeneuve').first()
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
detail(page).getByText('Science Fiction').first()
|
||||
about.getByText('Paul Atreides arrives on Arrakis.')
|
||||
).toBeVisible();
|
||||
await expect(about.getByText('Denis Villeneuve')).toBeVisible();
|
||||
await expect(about.getByText('Science Fiction')).toBeVisible();
|
||||
|
||||
// A live channel keeps the classic layout.
|
||||
await sidebarEntry(page, 'Live One').click();
|
||||
@@ -406,19 +404,19 @@ test('@web @m3u @tmdb browse and watch keep the adjusted volume', async ({
|
||||
)
|
||||
)
|
||||
.toBe(0.25);
|
||||
// M3U has no browse Back target, so the shell shows no arrow in either
|
||||
// M3U has no browse Back target, so the header shows no arrow in either
|
||||
// state; the now-playing bar's own Close button returns to browse.
|
||||
const shell = detail(page).locator('app-portal-detail-shell');
|
||||
await expect(shell.locator('.shell__back-button')).toHaveCount(0);
|
||||
const headerBack = page.locator('[data-test-id="workspace-header-back"]');
|
||||
await expect(headerBack).toHaveCount(0);
|
||||
await shell
|
||||
.locator('app-portal-inline-player')
|
||||
.getByRole('button', { name: 'Close player', exact: true })
|
||||
.click();
|
||||
await expect(inlineVideo(page)).toHaveCount(0);
|
||||
await expect(shell.locator('.shell__back-button')).toHaveCount(0);
|
||||
// Without the arrow there is no lane to reserve: the hero keeps its own
|
||||
// inset (32px, or 20px in a pane narrower than 760px) instead of the
|
||||
// 72px column portal details give the Back control.
|
||||
await expect(headerBack).toHaveCount(0);
|
||||
// The hero keeps its own inset (32px, or 20px in a pane narrower than
|
||||
// 760px) in both states.
|
||||
expect(
|
||||
await shell
|
||||
.locator('.hero__content')
|
||||
|
||||
@@ -90,8 +90,7 @@ async function openUnsavedDialog(page: Page, locale: Locale): Promise<Locator> {
|
||||
page.locator('[data-test-id="settings-unsaved-bar"]')
|
||||
).toBeVisible();
|
||||
|
||||
// The rail's Dashboard link stays visible in the phone top bar, unlike
|
||||
// the brand link.
|
||||
// The rail's Dashboard link stays visible in the phone top bar.
|
||||
await page
|
||||
.getByRole('navigation')
|
||||
.locator('a[href$="/workspace/dashboard"]')
|
||||
|
||||
@@ -1156,7 +1156,11 @@ for (const theme of ['light', 'dark']) {
|
||||
.click();
|
||||
await page.locator('app-grid-list mat-card').first().click();
|
||||
const shell = page.locator('app-portal-detail-shell');
|
||||
await expect(shell).toBeVisible();
|
||||
// The loading skeleton is a scrollable shell of its own; a key
|
||||
// sent to it is lost when the loaded shell replaces it.
|
||||
await expect(
|
||||
shell.getByRole('heading', { level: 1 })
|
||||
).toBeVisible();
|
||||
await page.evaluate(
|
||||
(dark) => document.body.classList.toggle('dark-theme', dark),
|
||||
theme === 'dark'
|
||||
@@ -1178,7 +1182,7 @@ for (const theme of ['light', 'dark']) {
|
||||
.toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test(`@xtream sticky detail Back ${section} (${theme})`, async ({
|
||||
test(`@xtream header detail Back ${section} (${theme})`, async ({
|
||||
page,
|
||||
}, testInfo) => {
|
||||
await page.setViewportSize({ width: 1200, height: 540 });
|
||||
@@ -1196,30 +1200,28 @@ for (const theme of ['light', 'dark']) {
|
||||
(dark) => document.body.classList.toggle('dark-theme', dark),
|
||||
theme === 'dark'
|
||||
);
|
||||
const back = shell.getByRole('button', {
|
||||
name: 'Back',
|
||||
exact: true,
|
||||
});
|
||||
// Back is the header's leading control, not a page overlay.
|
||||
const back = page.locator(
|
||||
'app-workspace-shell-header [data-test-id="workspace-header-back"]'
|
||||
);
|
||||
await expect(back).toBeVisible();
|
||||
const backOffset = () =>
|
||||
back.evaluate((el) => {
|
||||
const owner = el.closest('app-portal-detail-shell');
|
||||
return owner
|
||||
? el.getBoundingClientRect().top -
|
||||
owner.getBoundingClientRect().top
|
||||
: NaN;
|
||||
});
|
||||
await expect.poll(backOffset).toBeCloseTo(16, 0);
|
||||
await expect(back).toHaveAccessibleName('Back');
|
||||
await expect(
|
||||
shell.getByRole('button', { name: 'Back', exact: true })
|
||||
).toHaveCount(0);
|
||||
const backTop = () =>
|
||||
back.evaluate((el) => el.getBoundingClientRect().top);
|
||||
const initialTop = await backTop();
|
||||
await page.keyboard.press('End');
|
||||
await expect
|
||||
.poll(() => shell.evaluate((el) => el.scrollTop))
|
||||
.toBeGreaterThan(0);
|
||||
await waitForScrollIdle(shell);
|
||||
await expect.poll(backOffset).toBeCloseTo(16, 0);
|
||||
expect(await backTop()).toBe(initialTop);
|
||||
await expect(back).toBeInViewport();
|
||||
await shell.screenshot({
|
||||
await page.screenshot({
|
||||
path: testInfo.outputPath(
|
||||
`sticky-back-${section}-${theme}.png`
|
||||
`header-back-${section}-${theme}.png`
|
||||
),
|
||||
});
|
||||
await back.click();
|
||||
@@ -1230,7 +1232,8 @@ for (const theme of ['light', 'dark']) {
|
||||
).toBeVisible();
|
||||
await shell.focus();
|
||||
await page.keyboard.press('End');
|
||||
// Hover/focus must not make a tooltip consume the advertised Esc.
|
||||
// Hover/focus must not make a tooltip consume the advertised Esc,
|
||||
// even with focus on the header rather than inside the page.
|
||||
await page.clock.install();
|
||||
await back.focus();
|
||||
await back.hover();
|
||||
|
||||
Reference in new issue
Block a user