diff --git a/libs/portal/downloads/feature/src/lib/downloads.component.html b/libs/portal/downloads/feature/src/lib/downloads.component.html index 1f1293e75..cc6620199 100644 --- a/libs/portal/downloads/feature/src/lib/downloads.component.html +++ b/libs/portal/downloads/feature/src/lib/downloads.component.html @@ -235,7 +235,7 @@ [availablePlaylistIds]="availablePlaylistIds()" [pendingIds]="pendingIds()" (itemAction)="runAction($event)" - (seriesOpened)="openInLibrary($event)" + (openRequested)="openInLibrary($event)" (episodesOpened)="openDownloadedSeries($event)" /> } diff --git a/libs/portal/downloads/feature/src/lib/downloads.component.scss b/libs/portal/downloads/feature/src/lib/downloads.component.scss index 89f93feb6..6595374c3 100644 --- a/libs/portal/downloads/feature/src/lib/downloads.component.scss +++ b/libs/portal/downloads/feature/src/lib/downloads.component.scss @@ -365,8 +365,11 @@ .downloads__skeleton-cards { display: grid; - grid-template-columns: repeat(auto-fill, minmax(min(100%, 150px), 1fr)); - gap: clamp(12px, 1.5vw, 20px); + grid-template-columns: repeat( + auto-fill, + minmax(min(100%, var(--cover-grid-min-width, 148px)), 1fr) + ); + gap: var(--cover-gap, 16px); } .downloads__skeleton-card { diff --git a/libs/portal/downloads/feature/src/lib/downloads.component.spec.ts b/libs/portal/downloads/feature/src/lib/downloads.component.spec.ts index 14a0904ec..55cd8aa32 100644 --- a/libs/portal/downloads/feature/src/lib/downloads.component.spec.ts +++ b/libs/portal/downloads/feature/src/lib/downloads.component.spec.ts @@ -1,3 +1,5 @@ +import { readFileSync } from 'node:fs'; +import { join } from 'node:path'; import { computed, signal } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { MatDialog } from '@angular/material/dialog'; @@ -288,6 +290,7 @@ describe('DownloadsComponent', () => { URL_COPIED: 'URL copied', URL_COPY_FAILED: 'URL copy failed', REMOVE_FROM_MANAGER: 'Remove from manager', + PLAY: 'Play', }, CHANNELS: { LOADING: 'Loading' }, WORKSPACE: { @@ -358,6 +361,22 @@ describe('DownloadsComponent', () => { ).not.toBeNull(); }); + it('uses the shared cover-grid sizing contract for skeleton cards', () => { + const styles = readFileSync( + join(__dirname, 'downloads.component.scss'), + 'utf8' + ); + const skeletonCardsRule = + styles.match(/\.downloads__skeleton-cards\s*\{([^}]*)\}/)?.[1] ?? + ''; + + expect(skeletonCardsRule).toContain( + 'var(--cover-grid-min-width, 148px)' + ); + expect(skeletonCardsRule).toContain('var(--cover-gap, 16px)'); + expect(skeletonCardsRule).not.toContain('150px'); + }); + it('derives route scope without mutating the global service signal', () => { const globalItems = [ download(1, { status: 'downloading' }), @@ -608,6 +627,40 @@ describe('DownloadsComponent', () => { ); }); + it('opens completed movie artwork in details while explicit Play uses the file', async () => { + const item = download(14, { + title: 'Completed movie', + filePath: '/downloads/completed-movie.mp4', + }); + downloads.set([item]); + await fixture.whenStable(); + + const artworkButton = fixture.nativeElement.querySelector( + '.download-library__artwork-button' + ) as HTMLButtonElement; + expect(artworkButton).toBeTruthy(); + + artworkButton.click(); + await fixture.whenStable(); + + expect(navigation.open).toHaveBeenCalledWith(item); + expect(downloadsService.playDownload).not.toHaveBeenCalled(); + + jest.clearAllMocks(); + const playButton = fixture.nativeElement.querySelector( + '.download-library__actions button[aria-label="Play: Completed movie"]' + ) as HTMLButtonElement; + expect(playButton).toBeTruthy(); + + playButton.click(); + await fixture.whenStable(); + + expect(downloadsService.playDownload).toHaveBeenCalledWith( + item.filePath + ); + expect(navigation.open).not.toHaveBeenCalled(); + }); + it('does not navigate an item whose source playlist is missing', async () => { const item = download(13, { playlistId: 'removed-playlist' }); navigation.canOpen.mockReturnValueOnce(false);