diff --git a/src/app/components/home/home.component.spec.ts b/src/app/components/home/home.component.spec.ts new file mode 100644 index 0000000..7e59dc5 --- /dev/null +++ b/src/app/components/home/home.component.spec.ts @@ -0,0 +1,96 @@ +import { signal } from '@angular/core'; +import { TestBed } from '@angular/core/testing'; +import { provideRouter } from '@angular/router'; +import { vi } from 'vitest'; +import { Item } from '../../models/item.model'; +import { RoundRobinService } from '../../services/round-robin.service'; +import { WatchListService } from '../../services/watch-list.service'; +import { HomeComponent } from './home.component'; + +describe('HomeComponent', () => { + const items: Item[] = [ + { + id: 'paused-movie', + title: 'Paused Movie', + type: 'movie', + groupId: 'ungrouped', + status: 'paused', + watchHistory: [], + createdAt: '2026-05-02T10:00:00.000Z', + }, + { + id: 'backlog-item', + title: 'Backlog Series', + type: 'series', + groupId: 'ungrouped', + status: 'not-started', + watchHistory: [], + createdAt: '2026-05-01T10:00:00.000Z', + }, + ]; + + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [ + provideRouter([]), + { + provide: RoundRobinService, + useValue: { nextSeries: signal(null), nextMovie: signal(null) }, + }, + { + provide: WatchListService, + useValue: { + items: signal(items), + inProgressSeries: signal([]), + markStarted: vi.fn(), + markDropped: vi.fn(), + }, + }, + ], + }); + }); + + it('shows paused movies separately from backlog', async () => { + const fixture = TestBed.createComponent(HomeComponent); + fixture.detectChanges(); + await fixture.whenStable(); + fixture.detectChanges(); + + const sections = Array.from( + fixture.nativeElement.querySelectorAll('h2'), + ) as HTMLHeadingElement[]; + const pausedSection = sections.find((section) => section.textContent?.trim() === 'Paused'); + const pausedContainer = pausedSection?.parentElement; + + expect(pausedContainer?.textContent).toContain('Paused Movie'); + expect(pausedContainer?.textContent).not.toContain('Backlog Series'); + }); + + it('resumes and drops paused items', () => { + const fixture = TestBed.createComponent(HomeComponent); + const watchListService = TestBed.inject(WatchListService); + + fixture.componentInstance.resumePausedItem('paused-movie'); + fixture.componentInstance.dropPausedItem('paused-movie'); + + expect(watchListService.markStarted).toHaveBeenCalledWith('paused-movie'); + expect(watchListService.markDropped).toHaveBeenCalledWith('paused-movie'); + }); + + it('shows an empty state when no items are paused', async () => { + TestBed.overrideProvider(WatchListService, { + useValue: { + items: signal([]), + inProgressSeries: signal([]), + markStarted: vi.fn(), + markDropped: vi.fn(), + }, + }); + const fixture = TestBed.createComponent(HomeComponent); + fixture.detectChanges(); + await fixture.whenStable(); + fixture.detectChanges(); + + expect(fixture.nativeElement.textContent).toContain('No items paused'); + }); +}); diff --git a/src/app/components/home/home.component.ts b/src/app/components/home/home.component.ts index ba8cf5e..f94279f 100644 --- a/src/app/components/home/home.component.ts +++ b/src/app/components/home/home.component.ts @@ -89,7 +89,7 @@ import { Item, ItemType } from '../../models/item.model'; (click)="markItem(nextMovie, 'paused')" class="px-3 py-1.5 border border-light-border dark:border-dark-border rounded bg-light-bg-secondary dark:bg-dark-bg-secondary text-light-font dark:text-dark-font cursor-pointer text-sm hover:bg-light-bg-tertiary dark:hover:bg-dark-bg-tertiary" > - Move to backlog + Pause + + + + } + + } @else { +

+ No items paused +

+ } + `, }) @@ -177,6 +224,13 @@ export class HomeComponent { .sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()), ); + pausedItems = computed(() => + this.watchListService + .items() + .filter((item) => item.status === 'paused') + .sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()), + ); + protected hasSeries = computed(() => this.hasItemType('series')); protected hasMovies = computed(() => this.hasItemType('movie')); protected hasInProgressSeries = computed( @@ -203,6 +257,14 @@ export class HomeComponent { this.watchListService.markDropped(itemId); } + resumePausedItem(itemId: string): void { + this.watchListService.markStarted(itemId); + } + + dropPausedItem(itemId: string): void { + this.watchListService.markDropped(itemId); + } + private hasItemType(type: ItemType): boolean { return this.watchListService.items().some((item) => item.type === type); } 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 e24023d..d1d0dad 100644 --- a/src/app/components/item-form/item-form.component.spec.ts +++ b/src/app/components/item-form/item-form.component.spec.ts @@ -186,6 +186,34 @@ describe('ItemFormComponent', () => { expect(fixture.componentInstance.formValue().seasons[0].firstEpisodeAirDate).toBe('2026-05-01'); }); + it('allows a movie to remain paused', async () => { + const fixture = TestBed.createComponent(ItemFormComponent); + fixture.componentRef.setInput('groups', groups); + fixture.componentRef.setInput('initialValue', { + title: 'Paused Movie', + type: 'series', + groupId: 'ungrouped', + status: 'paused', + season: 2, + episode: 3, + seasons: [{ seasonNumber: 2, totalEpisodes: 10 }], + startImmediately: false, + }); + + fixture.detectChanges(); + await fixture.whenStable(); + + fixture.componentInstance.setType('movie'); + await fixture.whenStable(); + + expect(fixture.componentInstance.formValue()).toMatchObject({ + title: 'Paused Movie', + type: 'movie', + status: 'paused', + }); + expect(fixture.componentInstance.itemStatuses).toContain('paused'); + }); + it('applies an autofill patch once when the patch id changes', async () => { const fixture = TestBed.createComponent(ItemFormComponent); fixture.componentRef.setInput('groups', groups); diff --git a/src/app/components/item-form/item-form.component.ts b/src/app/components/item-form/item-form.component.ts index 4cfbfe3..96338dc 100644 --- a/src/app/components/item-form/item-form.component.ts +++ b/src/app/components/item-form/item-form.component.ts @@ -11,7 +11,7 @@ import { } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { Group } from '../../models/group.model'; -import { SeasonInfo } from '../../models/item.model'; +import { ItemStatus, SeasonInfo } from '../../models/item.model'; import { TmdbSuggestion } from '../../models/tmdb-suggestion.model'; import { createDefaultItemFormValue, @@ -156,7 +156,7 @@ export interface ItemFormAutofillPatch {
Status:
- @for (status of itemStatuses(); track status) { + @for (status of itemStatuses; track status) {