test(portals): cover page tokens across recreated series pages

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
4grayandClaude Fable 5.1 committed 2026-10-03 15:22:55 +02:00
1 parent 1bb03d883c
commit 5787a6643a
2 files changed
+83 -3

No files matched your search

@@ -0,0 +1,80 @@
import { signal } from '@angular/core';
import { TestBed } from '@angular/core/testing';
import { ActivatedRoute } from '@angular/router';
import { PlaybackHistoryGate } from '@iptvnator/playback/data-access';
import {
PORTAL_EXTERNAL_PLAYBACK,
PORTAL_PLAYBACK_POSITIONS,
PORTAL_PLAYER,
} from '@iptvnator/portal/shared/util';
import { XtreamStore } from '@iptvnator/portal/xtream/data-access';
import { PlaybackPositionRuntimeBridgeService } from '@iptvnator/services';
import {
SerialDetailsPlaybackService,
type XtreamSerieDetailsView,
} from './serial-details-playback.service';
import { XTREAM_SERIES_RESUME_TARGET } from './serial-details-resume-target.token';
import { SerialDetailsSeasonWatchService } from './serial-details-season-watch.service';
const series = {
series_id: 103,
info: {},
episodes: {},
} as unknown as XtreamSerieDetailsView;
/** A fresh page: the component provides the service, so each visit gets its own instance. */
function createPage(): SerialDetailsPlaybackService {
TestBed.resetTestingModule();
TestBed.configureTestingModule({
providers: [
SerialDetailsPlaybackService,
{ provide: ActivatedRoute, useValue: {} },
{
provide: XtreamStore,
useValue: { currentPlaylist: signal({ id: 'xtream-1' }) },
},
{ provide: PORTAL_PLAYBACK_POSITIONS, useValue: {} },
{ provide: PORTAL_PLAYER, useValue: {} },
{
provide: PORTAL_EXTERNAL_PLAYBACK,
useValue: { activeSession: signal(null) },
},
{
provide: PlaybackPositionRuntimeBridgeService,
useValue: { onPlaybackPositionUpdate: () => undefined },
},
{ provide: PlaybackHistoryGate, useValue: {} },
{ provide: XTREAM_SERIES_RESUME_TARGET, useValue: signal(null) },
{
provide: SerialDetailsSeasonWatchService,
useValue: { batchRunning: signal(false) },
},
],
});
const service = TestBed.inject(SerialDetailsPlaybackService);
service.bind({ selectedItem: signal(series) });
return service;
}
describe('SerialDetailsPlaybackService page token', () => {
it('gives a recreated page of the same series a token of its own', () => {
const first = createPage();
const firstOwner = first.launchOwner();
const firstToken = first.pageToken();
const reopened = createPage();
expect(reopened.launchOwner()).toBe(firstOwner);
expect(reopened.pageToken()).not.toBe(firstToken);
});
it('changes the token when the page moves on to another series', () => {
const page = createPage();
const token = page.pageToken();
page.resetForNewSeries();
expect(page.launchOwner()).toBe('xtream-1:103');
expect(page.pageToken()).not.toBe(token);
});
});
@@ -61,14 +61,14 @@ interface SerialDetailsPlaybackBindings {
readonly selectedItem: Signal<XtreamSerieDetailsView | null>;
}
/** Page instances created so far: a recreated page must not reuse a token. */
let pageInstances = 0;
/**
* Component-provided service that owns the episode playback concern of the
* serial details view: inline playback state, per-episode playback
* positions, external-player session tracking, and playback orchestration.
*/
/** Page instances created so far: a recreated page must not reuse a token. */
let pageInstances = 0;
@Injectable()
export class SerialDetailsPlaybackService {
private readonly route = inject(ActivatedRoute);