test(downloads): cover Stalker adapter reactivity

This commit is contained in:
4gray committed 2026-08-02 12:15:38 +02:00
1 parent 2500b9015b
commit e6cc9e5ff9
1 file changed
+90 -85
@@ -99,6 +99,11 @@ describe('StalkerSeriesViewComponent', () => {
macAddress: '00:1A:79:12:34:56',
});
async function stabilize(): Promise<void> {
fixture.detectChanges();
await fixture.whenStable();
}
beforeEach(async () => {
selectedContentType.set('series');
selectedItem.set({
@@ -285,8 +290,7 @@ describe('StalkerSeriesViewComponent', () => {
});
it('renders quick start for regular series and starts the first episode', async () => {
fixture.detectChanges();
await fixture.whenStable();
await stabilize();
fixture.detectChanges();
const quickStartButton: HTMLButtonElement | null =
@@ -322,8 +326,7 @@ describe('StalkerSeriesViewComponent', () => {
it('keeps provider episodes playable while hiding download presentation', async () => {
fixture.componentRef.setInput('providerOnly', true);
fixture.detectChanges();
await fixture.whenStable();
await stabilize();
const seasonContainer = fixture.debugElement.query(
By.directive(StubSeasonContainerComponent)
@@ -347,21 +350,16 @@ describe('StalkerSeriesViewComponent', () => {
id: '50001',
is_series: true,
category_id: '18',
info: {
name: 'Signal House',
description: 'Parent series plot',
movie_image:
'https://images.example.test/posters/signal-house.jpg',
},
info: { name: 'Signal House' },
});
fixture.detectChanges();
await fixture.whenStable();
await stabilize();
const seasonContainer = fixture.debugElement.query(
By.directive(StubSeasonContainerComponent)
).componentInstance as StubSeasonContainerComponent;
const candidate = seasonContainer.downloadAdapter()?.createCandidate(
const initialAdapter = seasonContainer.downloadAdapter();
const candidate = initialAdapter?.createCandidate(
{
id: '61001',
episode_num: 3,
@@ -387,24 +385,60 @@ describe('StalkerSeriesViewComponent', () => {
expect(request).toEqual(
expect.objectContaining({
playlistId: 'stalker-1',
playlistType: 'stalker',
seriesXtreamId: 50001,
xtreamId: 61001,
seasonNumber: 2,
episodeNumber: 3,
title: 'Signal House - S02E03 - The Call',
url: 'https://cdn.example.test/episode.mpg',
playlistName: 'Living Room Portal',
})
);
currentPlaylist.set({
_id: 'stalker-2',
title: 'Bedroom Portal',
portalUrl: 'https://bedroom.example.test',
macAddress: '00:1A:79:65:43:21',
});
selectedItem.set({
id: '40002:season-slice',
category_id: '22',
info: { name: 'Second Signal' },
});
await stabilize();
const updatedAdapter = seasonContainer.downloadAdapter();
expect(updatedAdapter).not.toBe(initialAdapter);
const updatedCandidate = updatedAdapter?.createCandidate(
{
id: '62001',
episode_num: 1,
title: 'Pilot',
custom_sid: 'regular-series',
season: 4,
originalCmd: '/media/file_888.mpg',
} as never,
'4'
);
if (!updatedCandidate) {
throw new Error('expected a refreshed Stalker download adapter');
}
const updatedRequest = await updatedCandidate.prepare();
expect(fetchLinkToPlay).toHaveBeenLastCalledWith(
'https://bedroom.example.test',
'00:1A:79:65:43:21',
'/media/file_888.mpg',
1
);
expect(updatedRequest).toEqual(
expect.objectContaining({
playlistId: 'stalker-2',
seriesXtreamId: 40002,
xtreamId: 62001,
title: 'Second Signal - S04E01 - Pilot',
portalUrl: 'https://bedroom.example.test',
macAddress: '00:1A:79:65:43:21',
metadataSnapshot: expect.objectContaining({
language: 'en',
mediaKind: 'series',
title: 'Signal House',
plot: 'Parent series plot',
providerCategoryId: '18',
episode: expect.objectContaining({
seasonNumber: 2,
episodeNumber: 3,
}),
title: 'Second Signal',
providerCategoryId: '22',
}),
})
);
@@ -424,8 +458,7 @@ describe('StalkerSeriesViewComponent', () => {
serialSeasonsResource.set([]);
vodSeriesSeasonsResource.set([]);
fixture.detectChanges();
await fixture.whenStable();
await stabilize();
fixture.componentInstance.vodSeriesSeasons.set([
{
id: 'season-1',
@@ -505,8 +538,7 @@ describe('StalkerSeriesViewComponent', () => {
},
]);
fixture.detectChanges();
await fixture.whenStable();
await stabilize();
fixture.detectChanges();
const quickStartButton: HTMLButtonElement | null =
@@ -577,8 +609,7 @@ describe('StalkerSeriesViewComponent', () => {
},
]);
fixture.detectChanges();
await fixture.whenStable();
await stabilize();
fixture.componentInstance.vodSeriesSeasons.set([
{
@@ -677,8 +708,7 @@ describe('StalkerSeriesViewComponent', () => {
]);
isEmbeddedPlayer.mockReturnValue(true);
fixture.detectChanges();
await fixture.whenStable();
await stabilize();
fixture.componentInstance.vodSeriesSeasons.set([
{
@@ -820,8 +850,7 @@ describe('StalkerSeriesViewComponent', () => {
]);
isEmbeddedPlayer.mockReturnValue(true);
fixture.detectChanges();
await fixture.whenStable();
await stabilize();
// Season 1 loaded, season 2 still empty — the state a user is in
// right after opening the series and starting the first episode.
@@ -855,8 +884,7 @@ describe('StalkerSeriesViewComponent', () => {
const firstEpisode = fixture.componentInstance.mappedSeasons()['1'][0];
fixture.componentInstance.onEpisodeClicked(firstEpisode);
await fixture.whenStable();
fixture.detectChanges();
await fixture.whenStable();
await stabilize();
fixture.detectChanges();
expect(fetchVodSeriesEpisodes).toHaveBeenCalledWith(
@@ -904,8 +932,7 @@ describe('StalkerSeriesViewComponent', () => {
]);
isEmbeddedPlayer.mockReturnValue(true);
fixture.detectChanges();
await fixture.whenStable();
await stabilize();
fixture.componentInstance.vodSeriesSeasons.set([
{
@@ -938,8 +965,7 @@ describe('StalkerSeriesViewComponent', () => {
const seasonOne = fixture.componentInstance.mappedSeasons()['1'];
fixture.componentInstance.onEpisodeClicked(seasonOne[0]);
await fixture.whenStable();
fixture.detectChanges();
await fixture.whenStable();
await stabilize();
fixture.detectChanges();
expect(fetchVodSeriesEpisodes).toHaveBeenCalledTimes(1);
@@ -947,8 +973,7 @@ describe('StalkerSeriesViewComponent', () => {
// Further playback activity in the same season must not retrigger it.
fixture.componentInstance.onEpisodeClicked(seasonOne[1]);
await fixture.whenStable();
fixture.detectChanges();
await fixture.whenStable();
await stabilize();
fixture.detectChanges();
expect(fetchVodSeriesEpisodes).toHaveBeenCalledTimes(1);
@@ -982,8 +1007,7 @@ describe('StalkerSeriesViewComponent', () => {
]);
isEmbeddedPlayer.mockReturnValue(true);
fixture.detectChanges();
await fixture.whenStable();
await stabilize();
fixture.componentInstance.vodSeriesSeasons.set([
{
@@ -1014,8 +1038,7 @@ describe('StalkerSeriesViewComponent', () => {
const seasonOne = fixture.componentInstance.mappedSeasons()['1'];
fixture.componentInstance.onEpisodeClicked(seasonOne[0]);
await fixture.whenStable();
fixture.detectChanges();
await fixture.whenStable();
await stabilize();
fixture.detectChanges();
// The failure must not loop while the same episode keeps playing.
@@ -1027,8 +1050,7 @@ describe('StalkerSeriesViewComponent', () => {
]);
fixture.componentInstance.onEpisodeClicked(seasonOne[1]);
await fixture.whenStable();
fixture.detectChanges();
await fixture.whenStable();
await stabilize();
fixture.detectChanges();
expect(fetchVodSeriesEpisodes).toHaveBeenCalledTimes(2);
@@ -1079,8 +1101,7 @@ describe('StalkerSeriesViewComponent', () => {
},
]);
fixture.detectChanges();
await fixture.whenStable();
await stabilize();
fixture.componentInstance.vodSeriesSeasons.set([
{
@@ -1151,8 +1172,7 @@ describe('StalkerSeriesViewComponent', () => {
});
it('fetches the TMDB season once the show-level match arrives after auto-select', async () => {
fixture.detectChanges();
await fixture.whenStable();
await stabilize();
fixture.detectChanges();
// Season tabs auto-select immediately — usually before the async
@@ -1161,8 +1181,7 @@ describe('StalkerSeriesViewComponent', () => {
By.directive(StubSeasonContainerComponent)
).componentInstance as StubSeasonContainerComponent;
seasonContainer.seasonSelected.emit('1');
fixture.detectChanges();
await fixture.whenStable();
await stabilize();
expect(tmdbGetSeason).not.toHaveBeenCalled();
// The TMDB match lands afterwards — the fetch must run now.
@@ -1176,8 +1195,7 @@ describe('StalkerSeriesViewComponent', () => {
tmdb_id: 777,
},
} as never);
fixture.detectChanges();
await fixture.whenStable();
await stabilize();
expect(tmdbGetSeason).toHaveBeenCalledWith(777, 1);
});
@@ -1194,12 +1212,10 @@ describe('StalkerSeriesViewComponent', () => {
tmdb_id: 777,
},
} as never);
fixture.detectChanges();
await fixture.whenStable();
await stabilize();
fixture.componentInstance.onSeasonSelected('1');
fixture.detectChanges();
await fixture.whenStable();
await stabilize();
// Season resource still loading — fetching now would pass a zero
// season count, suppress the title-marker override and cache the
@@ -1214,8 +1230,7 @@ describe('StalkerSeriesViewComponent', () => {
series: [1, 2],
},
]);
fixture.detectChanges();
await fixture.whenStable();
await stabilize();
// Single-season slice whose provider season is renumbered to 1:
// the title marker names the real TMDB season.
@@ -1223,12 +1238,10 @@ describe('StalkerSeriesViewComponent', () => {
});
it('gates the fetch on the reloading season resource during detail-to-detail navigation', async () => {
fixture.detectChanges();
await fixture.whenStable();
await stabilize();
fixture.componentInstance.onSeasonSelected('1');
fixture.detectChanges();
await fixture.whenStable();
await stabilize();
// No show-level TMDB match yet — nothing fetched for the first item
expect(tmdbGetSeason).not.toHaveBeenCalled();
@@ -1246,8 +1259,7 @@ describe('StalkerSeriesViewComponent', () => {
tmdb_id: 888,
},
} as never);
fixture.detectChanges();
await fixture.whenStable();
await stabilize();
// The new tmdb_id must NOT pair with the previous series' season
// context while the resource reloads.
@@ -1266,17 +1278,14 @@ describe('StalkerSeriesViewComponent', () => {
},
]);
isSerialSeasonsLoading.set(false);
fixture.detectChanges();
await fixture.whenStable();
await stabilize();
expect(tmdbGetSeason).toHaveBeenCalledWith(888, 2);
});
it('enriches after equal-id navigation once the season resource settles', async () => {
fixture.detectChanges();
await fixture.whenStable();
await stabilize();
fixture.componentInstance.onSeasonSelected('1');
fixture.detectChanges();
await fixture.whenStable();
await stabilize();
// Distinct items can reuse a provider id; the loading gate (not an
// id comparison) keeps the stale map from being used.
@@ -1291,13 +1300,11 @@ describe('StalkerSeriesViewComponent', () => {
tmdb_id: 999,
},
} as never);
fixture.detectChanges();
await fixture.whenStable();
await stabilize();
expect(tmdbGetSeason).not.toHaveBeenCalled();
isSerialSeasonsLoading.set(false);
fixture.detectChanges();
await fixture.whenStable();
await stabilize();
expect(tmdbGetSeason).toHaveBeenCalledWith(999, 3);
});
@@ -1313,12 +1320,10 @@ describe('StalkerSeriesViewComponent', () => {
tmdb_id: 777,
},
} as never);
fixture.detectChanges();
await fixture.whenStable();
await stabilize();
fixture.componentInstance.onSeasonSelected('1');
fixture.detectChanges();
await fixture.whenStable();
await stabilize();
expect(tmdbGetSeason).toHaveBeenCalledWith(777, 2);
});