fix(ui): keep detail back navigation available while scrolling (#1576)

This commit is contained in:
4gray authored and GitHub committed 2026-09-10 21:34:10 +02:00
1 parent e76447975b
commit fff022afe4
28 files changed
+679 -138

No files matched your search

@@ -590,9 +590,12 @@ async function firstVisibleGridTitle(page: Page): Promise<string> {
}
async function goBackFromDetail(page: Page): Promise<void> {
// Return to the list: browse uses the sticky Back, watch uses the
// now-playing bar's direct Back (the sticky watch action is Close player).
const backButton = page
.locator('app-content-hero .hero__back-button')
.first();
.locator('app-portal-detail-shell')
.first()
.getByRole('button', { name: 'Back', exact: true });
await expect(backButton).toBeVisible({ timeout: 20000 });
try {
@@ -97,7 +97,10 @@ test.describe('Dashboard Activation', () => {
await app.mainWindow
.getByTestId('dashboard-favorite-vod-rail-manage-all')
.click();
await expectPathname(app.mainWindow, /\/workspace\/global-favorites$/);
await expectPathname(
app.mainWindow,
/\/workspace\/global-favorites$/
);
await expectVisibleContentCardTitle(app.mainWindow, movieTitle);
await app.mainWindow.goBack();
@@ -223,7 +226,10 @@ const xtreamCredentials = {
password: defaultXtreamPassword,
};
async function expectDashboardRail(page: Page, railTestId: string): Promise<void> {
async function expectDashboardRail(
page: Page,
railTestId: string
): Promise<void> {
await expect(page.locator(`[data-test-id="${railTestId}"]`)).toBeVisible({
timeout: 20000,
});
@@ -234,15 +240,21 @@ function dashboardRailCardByTitle(
railTestId: string,
title: string
) {
return page.locator(`[data-test-id="${railTestId}-card"]`).filter({
hasText: title,
}).first();
return page
.locator(`[data-test-id="${railTestId}-card"]`)
.filter({
hasText: title,
})
.first();
}
async function goBackFromDetail(page: Page): Promise<void> {
// Return to the list: browse uses the sticky Back, watch uses the
// now-playing bar's direct Back (the sticky watch action is Close player).
const backButton = page
.locator('app-content-hero .hero__back-button')
.first();
.locator('app-portal-detail-shell')
.first()
.getByRole('button', { name: 'Back', exact: true });
await expect(backButton).toBeVisible({ timeout: 20000 });
try {
@@ -282,7 +294,7 @@ async function expectInlineCollectionDetail(
await expect(page.locator('app-workspace-context-panel')).toHaveCount(0);
await expect(page.locator('app-content-hero')).toContainText(params.title);
await expect(
page.locator('app-content-hero .hero__back-button').first()
page.locator('app-portal-detail-shell .shell__back-button').first()
).toBeVisible({ timeout: 20000 });
}
+22 -7
View File
@@ -351,7 +351,10 @@ test.describe('Electron Favorites', () => {
});
await goBackFromDetail(app.mainWindow);
await expectPathname(app.mainWindow, /\/workspace\/global-favorites$/);
await expectPathname(
app.mainWindow,
/\/workspace\/global-favorites$/
);
await expectVisibleContentCardTitle(app.mainWindow, movieTitle);
await switchUnifiedCollectionContent(app.mainWindow, 'Series');
@@ -366,7 +369,10 @@ test.describe('Electron Favorites', () => {
});
await goBackFromDetail(app.mainWindow);
await expectPathname(app.mainWindow, /\/workspace\/global-favorites$/);
await expectPathname(
app.mainWindow,
/\/workspace\/global-favorites$/
);
await switchUnifiedCollectionContent(app.mainWindow, 'Series');
await expectVisibleContentCardTitle(app.mainWindow, seriesTitle);
} finally {
@@ -545,7 +551,10 @@ test.describe('Electron Favorites', () => {
await expectInlinePlayerWithoutDialog(app.mainWindow);
await goBackFromDetail(app.mainWindow);
await expectPathname(app.mainWindow, /\/workspace\/global-favorites$/);
await expectPathname(
app.mainWindow,
/\/workspace\/global-favorites$/
);
await expectVisibleContentCardTitle(app.mainWindow, movieTitle);
await switchUnifiedCollectionContent(app.mainWindow, 'Series');
@@ -562,7 +571,10 @@ test.describe('Electron Favorites', () => {
await expectInlinePlayerWithoutDialog(app.mainWindow);
await goBackFromDetail(app.mainWindow);
await expectPathname(app.mainWindow, /\/workspace\/global-favorites$/);
await expectPathname(
app.mainWindow,
/\/workspace\/global-favorites$/
);
await switchUnifiedCollectionContent(app.mainWindow, 'Series');
await expectVisibleContentCardTitle(app.mainWindow, seriesTitle);
} finally {
@@ -594,9 +606,12 @@ async function addCurrentDetailToFavorites(page: Page): Promise<void> {
}
async function goBackFromDetail(page: Page): Promise<void> {
// Return to the list: browse uses the sticky Back, watch uses the
// now-playing bar's direct Back (the sticky watch action is Close player).
const backButton = page
.locator('app-content-hero .hero__back-button')
.first();
.locator('app-portal-detail-shell')
.first()
.getByRole('button', { name: 'Back', exact: true });
await expect(backButton).toBeVisible({ timeout: 20000 });
try {
@@ -623,7 +638,7 @@ async function expectInlineCollectionDetail(
await expect(page.locator('app-workspace-context-panel')).toHaveCount(0);
await expect(page.locator('app-content-hero')).toContainText(params.title);
await expect(
page.locator('app-content-hero .hero__back-button').first()
page.locator('app-portal-detail-shell .shell__back-button').first()
).toBeVisible({ timeout: 20000 });
}
+13 -5
View File
@@ -74,7 +74,9 @@ test.describe('Electron Recently Viewed', () => {
'Stable Recent Channel'
).first();
const livePlayer = app.mainWindow
.locator('app-unified-live-tab .content-container .video-player')
.locator(
'app-unified-live-tab .content-container .video-player'
)
.first();
await item.click();
@@ -129,7 +131,10 @@ test.describe('Electron Recently Viewed', () => {
.poll(() => visibleLiveTitles(app.mainWindow))
.toEqual(['Recent Channel Two', 'Recent Channel One']);
await toggleFavoriteForChannel(app.mainWindow, 'Recent Channel Two');
await toggleFavoriteForChannel(
app.mainWindow,
'Recent Channel Two'
);
await expect
.poll(() => visibleLiveTitles(app.mainWindow))
.toEqual(['Recent Channel Two', 'Recent Channel One']);
@@ -648,9 +653,12 @@ async function expectUnifiedLiveDetailOpen(
}
async function goBackFromDetail(page: Page): Promise<void> {
// Return to the list: browse uses the sticky Back, watch uses the
// now-playing bar's direct Back (the sticky watch action is Close player).
const backButton = page
.locator('app-content-hero .hero__back-button')
.first();
.locator('app-portal-detail-shell')
.first()
.getByRole('button', { name: 'Back', exact: true });
await expect(backButton).toBeVisible({ timeout: 20000 });
try {
@@ -677,7 +685,7 @@ async function expectInlineCollectionDetail(
await expect(page.locator('app-workspace-context-panel')).toHaveCount(0);
await expect(page.locator('app-content-hero')).toContainText(params.title);
await expect(
page.locator('app-content-hero .hero__back-button').first()
page.locator('app-portal-detail-shell .shell__back-button').first()
).toBeVisible({ timeout: 20000 });
}
+5 -2
View File
@@ -1540,9 +1540,12 @@ async function addCurrentDetailToFavorites(page: Page): Promise<void> {
}
async function goBackFromDetail(page: Page): Promise<void> {
// Return to the list: browse uses the sticky Back, watch uses the
// now-playing bar's direct Back (the sticky watch action is Close player).
const backButton = page
.locator('app-content-hero .hero__back-button')
.first();
.locator('app-portal-detail-shell')
.first()
.getByRole('button', { name: 'Back', exact: true });
await expect(backButton).toBeVisible({ timeout: 20000 });
await backButton.click();
@@ -135,6 +135,46 @@ test.describe('Xtream VOD Details', () => {
});
for (const theme of ['light', 'dark']) {
test(`detail action tooltips preserve one-press Escape (${theme})`, async ({
dataDir,
request,
}) => {
await resetMockServers(request, ['xtream']);
const app = await launchElectronApp(dataDir);
try {
const page = app.mainWindow;
await addXtreamPortal(page);
await waitForXtreamWorkspaceReady(page);
await page
.getByRole('link', { name: 'Movies', exact: true })
.click();
await page.evaluate(
(dark) => document.body.classList.toggle('dark-theme', dark),
theme === 'dark'
);
for (const action of [
'vod-favorite-toggle',
'vod-download-start',
]) {
await page.locator('app-grid-list mat-card').first().click();
const shell = page.locator('app-portal-detail-shell');
await expect(
shell.getByRole('heading', { level: 1 })
).toBeVisible();
const button = shell.locator(`[data-testid="${action}"]`);
await button.focus();
await button.hover();
await expect(
page.locator('.mat-mdc-tooltip-show')
).toBeVisible();
await page.keyboard.press('Escape');
await expect(shell).toHaveCount(0);
}
} finally {
await closeElectronApp(app);
}
});
test(`supports keyboard and mouse scrolling in portal details (${theme})`, async ({
dataDir,
request,
@@ -188,7 +228,9 @@ for (const theme of ['light', 'dark']) {
.poll(() => shell.evaluate((el) => el.scrollTop))
.toBeGreaterThan(0);
await page.keyboard.press('Tab');
await expect(shell.locator('.hero__back-button')).toBeFocused();
await expect(
shell.locator('.shell__back-button')
).toBeFocused();
await page.keyboard.press('Enter');
await expect(shell).toHaveCount(0);
}