diff --git a/src/app/components/item-card/item-card.component.ts b/src/app/components/item-card/item-card.component.ts index 3f5f811..fd1c54a 100644 --- a/src/app/components/item-card/item-card.component.ts +++ b/src/app/components/item-card/item-card.component.ts @@ -83,20 +83,20 @@ export class ItemCardComponent { constructor() { this.destroyRef.onDestroy(() => { this.destroyed = true; - const url = this.posterUrl(); - if (url) URL.revokeObjectURL(url); }); - effect(() => void this.loadPoster(this.item().posterId)); + + effect(() => { + const version = this.imageStorage.version(); + void this.loadPoster(this.item().posterId, version); + }); } - private async loadPoster(id: string | undefined): Promise { + private async loadPoster(id: string | undefined, loadedVersion: number): Promise { const url = await this.imageStorage.getUrl(id); if (this.destroyed || id !== this.item().posterId) { - if (url) URL.revokeObjectURL(url); return; } - const previous = this.posterUrl(); - if (previous) URL.revokeObjectURL(previous); + if (loadedVersion !== this.imageStorage.version()) return; this.posterUrl.set(url); } } diff --git a/src/app/components/item-form/item-form.component.spec.ts b/src/app/components/item-form/item-form.component.spec.ts index 0c05865..e24023d 100644 --- a/src/app/components/item-form/item-form.component.spec.ts +++ b/src/app/components/item-form/item-form.component.spec.ts @@ -1,3 +1,4 @@ +import { signal } from '@angular/core'; import { TestBed } from '@angular/core/testing'; import { Group } from '../../models/group.model'; import { TmdbSuggestionService } from '../../services/tmdb-suggestion.service'; @@ -31,6 +32,7 @@ describe('ItemFormComponent', () => { storeUrl: vi.fn(() => Promise.resolve('image-1')), storeFile: vi.fn(() => Promise.resolve('image-1')), delete: vi.fn(() => Promise.resolve()), + version: signal(0).asReadonly(), }, }, ], diff --git a/src/app/components/item-view/item-view.component.spec.ts b/src/app/components/item-view/item-view.component.spec.ts index 337762d..0b270f3 100644 --- a/src/app/components/item-view/item-view.component.spec.ts +++ b/src/app/components/item-view/item-view.component.spec.ts @@ -52,7 +52,10 @@ describe('ItemViewComponent', () => { provide: GroupService, useValue: { groups: signal([{ id: 'group-1', name: 'Favourites', order: 0 }]) }, }, - { provide: ImageStorageService, useValue: { getUrl: vi.fn(async () => null) } }, + { + provide: ImageStorageService, + useValue: { getUrl: vi.fn(async () => null), version: signal(0).asReadonly() }, + }, ], }); } @@ -100,15 +103,38 @@ describe('ItemViewComponent', () => { const service = TestBed.inject(WatchListService); fixture.componentInstance.runAction('watched'); - fixture.componentInstance.runAction('completed'); fixture.componentInstance.runAction('started'); fixture.componentInstance.runAction('paused'); fixture.componentInstance.runAction('dropped'); expect(service.markWatched).toHaveBeenCalledWith(item.id); - expect(service.markCompleted).toHaveBeenCalledWith(item.id); expect(service.markStarted).toHaveBeenCalledWith(item.id); expect(service.markPaused).toHaveBeenCalledWith(item.id); expect(service.markDropped).toHaveBeenCalledWith(item.id); }); + + it('shows only status-appropriate quick actions', async () => { + configure([{ ...item, type: 'series', status: 'dropped' }]); + const fixture = TestBed.createComponent(ItemViewComponent); + fixture.detectChanges(); + await fixture.whenStable(); + + const buttons = [...(fixture.nativeElement as HTMLElement).querySelectorAll('button')]; + const labels = buttons.map((button) => button.textContent?.trim()); + + expect(labels).toEqual(['Start']); + }); + + it('does not show ineffective quick actions for a new item', async () => { + configure([{ ...item, type: 'movie', status: 'not-started', watchHistory: [] }]); + const fixture = TestBed.createComponent(ItemViewComponent); + fixture.detectChanges(); + await fixture.whenStable(); + + const labels = [ + ...(fixture.nativeElement as HTMLElement).querySelectorAll('section button'), + ].map((button) => button.textContent?.trim()); + + expect(labels).toEqual(['Mark Watched', 'Start', 'Drop']); + }); }); diff --git a/src/app/components/item-view/item-view.component.ts b/src/app/components/item-view/item-view.component.ts index 0c8f41a..f82e671 100644 --- a/src/app/components/item-view/item-view.component.ts +++ b/src/app/components/item-view/item-view.component.ts @@ -5,12 +5,12 @@ import { ActivatedRoute, RouterLink } from '@angular/router'; import { WatchListService } from '../../services/watch-list.service'; import { GroupService } from '../../services/group.service'; import { ImageStorageService } from '../../services/image-storage.service'; -import { Item } from '../../models/item.model'; +import { Item, ItemStatus } from '../../models/item.model'; import { TimeAgoComponent } from '../time-ago/time-ago.component'; import { getMostRecentWatchDate } from '../../utils/progress.utils'; import { getPlaceholderUrl } from '../../utils/tmdb-image.utils'; -type QuickAction = 'watched' | 'completed' | 'started' | 'paused' | 'dropped'; +type QuickAction = 'watched' | 'started' | 'paused' | 'dropped'; @Component({ selector: 'app-item-view', @@ -83,7 +83,7 @@ type QuickAction = 'watched' | 'completed' | 'started' | 'paused' | 'dropped';

Quick Actions

- @for (action of quickActions; track action.label) { + @for (action of quickActions(); track action.label) {