fix(downloads): block ambiguous episode actions

This commit is contained in:
4gray committed 2026-08-02 19:10:58 +02:00
1 parent ee7041e3c2
commit 339dbde3c4
8 files changed
+136 -54

No files matched your search

@@ -615,6 +615,29 @@ describe('SeasonContainerComponent', () => {
);
});
it('blocks episode and season actions when managed rows claim an ambiguous identity', () => {
const episode = createEpisode();
downloadsServiceStub.downloads.set([
createDownload(episode, { id: 71, xtreamId: 9001 }),
createDownload(episode, { id: 72, xtreamId: 9002 }),
]);
enableDownloads();
setRequiredInputs({ '1': [episode] });
fixture.detectChanges();
const action = episodeAction(episode.id);
const seasonButton = fixture.nativeElement.querySelector(
'[data-test-id="download-season"]'
) as HTMLButtonElement;
expect(action.disabled).toBe(true);
expect(action.querySelector('mat-icon')?.textContent).toContain(
'block'
);
expect(seasonButton.disabled).toBe(true);
expect(downloadsServiceStub.startDownload).not.toHaveBeenCalled();
});
it('gives the grid and list view radios localized accessible names', () => {
TestBed.inject(TranslateService).setTranslation(
'en',
@@ -872,14 +895,14 @@ describe('SeasonContainerComponent', () => {
]);
const candidateSpy = jest.spyOn(downloadAdapter, 'createCandidate');
const coordinator = TestBed.inject(SeasonDownloadCoordinator);
const findDownloadSpy = jest.spyOn(coordinator, 'findDownload');
const resolveDownloadSpy = jest.spyOn(coordinator, 'resolveDownload');
enableDownloads();
setRequiredInputs({ '1': [paused, local] });
fixture.detectChanges();
fixture.detectChanges();
expect(candidateSpy).toHaveBeenCalledTimes(2);
expect(findDownloadSpy).toHaveBeenCalledTimes(2);
expect(resolveDownloadSpy).toHaveBeenCalledTimes(2);
episodeAction(101).click();
episodeAction(102).click();
@@ -887,7 +910,7 @@ describe('SeasonContainerComponent', () => {
fixture.detectChanges();
expect(candidateSpy).toHaveBeenCalledTimes(2);
expect(findDownloadSpy).toHaveBeenCalledTimes(2);
expect(resolveDownloadSpy).toHaveBeenCalledTimes(2);
});
it('resumes by managed row id and plays the coordinate-matched local file', async () => {
@@ -131,9 +131,12 @@ export class SeasonDownloadPresenter {
const pending = candidate
? this.coordinator.isPending(candidate.identity)
: false;
const download = candidate
? this.coordinator.findDownload(candidate.identity)
: undefined;
const resolution = candidate
? this.coordinator.resolveDownload(candidate.identity)
: { kind: 'missing' as const };
const download =
resolution.kind === 'match' ? resolution.download : undefined;
const identityConflict = resolution.kind === 'conflict';
return {
candidate,
download,
@@ -142,14 +145,15 @@ export class SeasonDownloadPresenter {
downloadsAvailable &&
downloadsReady &&
!pending &&
isEpisodeDownloadEligible(download)
isEpisodeDownloadEligible(resolution)
),
episode,
presentation: this.createPresentation(
candidate,
download,
pending,
downloadsReady
downloadsReady,
identityConflict
),
};
});
@@ -281,9 +285,10 @@ export class SeasonDownloadPresenter {
candidate: EpisodeDownloadCandidate | null,
download: DownloadItem | undefined,
pending: boolean,
downloadsReady: boolean
downloadsReady: boolean,
identityConflict: boolean
): EpisodeDownloadPresentation {
if (!candidate || !downloadsReady) {
if (!candidate || !downloadsReady || identityConflict) {
return EPISODE_DOWNLOAD_STATES.blocked;
}
if (