Merge origin/master into claude/server-storage-pwa

Resolves app-startup-status.component.ts: master replaced the progress
spinner with static markup (its import goes), the branch's router import
for the sign-in route stays.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
4grayandClaude Opus 5.5 committed 2026-10-14 14:36:01 +02:00
commit 614b0d3500
239 files changed
+17820 -7244

No files matched your search

+2 -6
View File
@@ -111,16 +111,12 @@ test('@a11y icon-only buttons are named in Sources and the playlist info dialog'
}) => {
await addM3uUrlPlaylist(page, M3U_TITLE);
// Sources list: refresh, details and remove on each row.
// Sources list: refresh, edit and the actions menu on each row.
await openSources(page);
const row = sourceRowByTitle(page, M3U_TITLE);
await expect(row).toBeVisible({ timeout: 15_000 });
await row.hover();
for (const name of [
'Refresh playlist',
'Show playlist details',
'Remove playlist',
]) {
for (const name of ['Refresh playlist', 'Edit…', 'Playlist actions']) {
await expect(row.getByRole('button', { name })).toBeAttached();
}
await expectNamedButtons(page, 3);
@@ -80,7 +80,7 @@ export async function importPlaylist(page: Page): Promise<string> {
.getByRole('button', { name: 'Add playlist', exact: true })
.click(),
]);
await expect(page.getByText('1 channels')).toBeVisible();
await expect(page.getByText('1 channel', { exact: false })).toBeVisible();
return page.url();
}
+1 -1
View File
@@ -259,7 +259,7 @@ test('@self-hosted M3U User-Agent reaches the provider on import and refresh', a
channel = 'Refreshed UA Channel';
const row = sourceRowByTitle(page, 'Protected M3U');
await row.hover();
await row.locator('.refresh-btn').click();
await row.locator('.source-row__refresh').click();
await expect(
page.locator('.mat-mdc-snack-bar-label').last()
).toContainText('updated');
+5 -2
View File
@@ -190,7 +190,10 @@ export async function openSources(page: Page): Promise<void> {
}
export function sourceRowByTitle(page: Page, title: string): Locator {
return page.locator('app-playlist-item').filter({ hasText: title }).first();
return page
.locator('app-workspace-sources app-source-row')
.filter({ hasText: title })
.first();
}
export async function openSourceEditor(
@@ -200,7 +203,7 @@ export async function openSourceEditor(
const row = sourceRowByTitle(page, title);
await expect(row).toBeVisible({ timeout: 15_000 });
await row.hover();
await row.locator('.edit-btn').click();
await row.locator('.source-row__edit').click();
const dialog = page.locator('mat-dialog-container').last();
await expect(dialog).toBeVisible();
return dialog;
+7 -4
View File
@@ -182,12 +182,15 @@ test('@stalker account info dialog shows subscription facts from the portal', as
}) => {
await addStalkerPortal(page);
// Open the header playlist switcher and its "Account info" entry for
// the active stalker portal.
await page.locator('.playlist-switcher-trigger').click();
// Open the header playlist switcher and the "Account info" entry in the
// active stalker portal's row menu.
await page.locator('[data-test-id="playlist-switcher-trigger"]').click();
await page
.locator('.context-action-item', { hasText: 'Account info' })
.locator(
'app-playlist-switcher-panel [role="option"][aria-current="true"] .source-row__more'
)
.click();
await page.getByRole('menuitem', { name: 'Account info' }).click();
const dialog = page.locator('mat-dialog-container');
await expect(dialog).toBeVisible();
+4 -7
View File
@@ -61,18 +61,15 @@ async function openPlaylistDetailsDialog(page: Page, title: string) {
await playlistSettingsButton.click();
} else {
await page
.locator('app-playlist-switcher .playlist-switcher-trigger')
.locator('[data-test-id="playlist-switcher-trigger"]')
.click();
const switcherMenu = page.getByRole('menu').filter({ hasText: title });
const sourceRow = switcherMenu
.locator('.playlist-item')
const sourceRow = page
.locator('app-playlist-switcher-panel [role="option"]')
.filter({ hasText: title })
.first();
await expect(sourceRow).toBeVisible();
await sourceRow
.getByRole('button', { name: 'Playlist actions' })
.click();
await sourceRow.locator('.source-row__more').click();
await page.getByRole('menuitem', { name: 'Playlist info' }).click();
}