fix(stalker): lazy load live tv channels

Entire-Checkpoint: c6e522b4276c
This commit is contained in:
4gray committed 2026-04-27 00:25:56 +02:00
1 parent 16fede1180
commit 10139955e7
3 files changed
+111 -9

No files matched your search

@@ -224,6 +224,79 @@ describe('withStalkerContent failure states', () => {
);
});
it('loads live TV channels page by page and appends later pages', async () => {
dataService.sendIpcEvent.mockImplementation(
(_event: unknown, payload: { params?: { p?: number } }) => {
const page = Number(payload.params?.p ?? 1);
return Promise.resolve({
js: {
data: [
{
id: `channel-${page}`,
name: `Channel page ${page}`,
category_id: '5',
},
],
total_items: 2,
},
});
}
);
store.setSelectedContentType('itv');
store.setCategories('itv', [
{
category_id: '5',
category_name: 'News',
},
]);
store.setSelectedCategory('5');
store.setCurrentPlaylist(PLAYLIST);
void store.isPaginatedContentLoading();
await waitForCondition(() => store.itvChannels().length === 1);
expect(dataService.sendIpcEvent).toHaveBeenLastCalledWith(
expect.anything(),
expect.objectContaining({
params: expect.objectContaining({
action: StalkerPortalActions.GetOrderedList,
type: 'itv',
category: '5',
genre: '5',
p: 1,
}),
})
);
expect(store.itvChannels().map((channel) => channel.name)).toEqual([
'Channel page 1',
]);
expect(store.hasMoreChannels()).toBe(true);
store.setPage(1);
await waitForCondition(() => store.itvChannels().length === 2);
expect(dataService.sendIpcEvent).toHaveBeenLastCalledWith(
expect.anything(),
expect.objectContaining({
params: expect.objectContaining({
action: StalkerPortalActions.GetOrderedList,
type: 'itv',
category: '5',
genre: '5',
p: 2,
}),
})
);
expect(store.itvChannels().map((channel) => channel.name)).toEqual([
'Channel page 1',
'Channel page 2',
]);
expect(store.hasMoreChannels()).toBe(false);
});
it('ignores stale content responses after the selected page changes', async () => {
const pendingByPage = new Map<
number,
@@ -116,12 +116,14 @@ describe('StalkerLiveStreamLayoutComponent', () => {
const bulkItvEpgPlaylistId = signal<string | null>(null);
const bulkItvEpgPeriodHours = signal<number | null>(null);
const isLoadingBulkItvEpg = signal(false);
const hasMoreChannels = signal(false);
const page = signal(0);
const stalkerStore = {
getSelectedCategoryName: signal('News'),
itvChannels,
searchPhrase,
hasMoreChannels: signal(false),
hasMoreChannels,
selectedItvId,
currentPlaylist: playlist,
selectedItvEpgPrograms,
@@ -133,7 +135,7 @@ describe('StalkerLiveStreamLayoutComponent', () => {
selectedCategoryId,
selectedItem,
selectedContentType: signal<'itv' | 'vod' | 'series'>('itv'),
page: signal(0),
page,
setItvChannels: jest.fn(),
setPage: jest.fn(),
setSelectedItem: jest.fn((item) => {
@@ -180,6 +182,8 @@ describe('StalkerLiveStreamLayoutComponent', () => {
bulkItvEpgPlaylistId.set(null);
bulkItvEpgPeriodHours.set(null);
isLoadingBulkItvEpg.set(false);
hasMoreChannels.set(false);
page.set(0);
resolveItvPlayback.mockReset();
resolveItvPlayback.mockResolvedValue({
@@ -202,7 +206,14 @@ describe('StalkerLiveStreamLayoutComponent', () => {
);
});
stalkerStore.setItvChannels.mockClear();
stalkerStore.setPage.mockClear();
stalkerStore.setPage.mockReset();
stalkerStore.setPage.mockImplementation((nextPage: number) => {
if (page() === nextPage) {
return;
}
page.set(nextPage);
});
stalkerStore.setSelectedItem.mockClear();
stalkerStore.clearBulkItvEpgCache.mockClear();
@@ -272,6 +283,21 @@ describe('StalkerLiveStreamLayoutComponent', () => {
).toBeNull();
});
it('does not reset live channels when loading the next lazy page', async () => {
hasMoreChannels.set(true);
fixture.detectChanges();
await fixture.whenStable();
stalkerStore.setItvChannels.mockClear();
stalkerStore.setPage.mockClear();
component.loadMore();
await fixture.whenStable();
expect(page()).toBe(1);
expect(stalkerStore.setPage).toHaveBeenCalledWith(1);
expect(stalkerStore.setItvChannels).not.toHaveBeenCalled();
});
it('keeps row previews empty before bulk epg is loaded', async () => {
fixture.detectChanges();
await fixture.whenStable();
@@ -8,6 +8,7 @@ import {
inject,
OnDestroy,
signal,
untracked,
viewChild,
} from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
@@ -183,12 +184,14 @@ export class StalkerLiveStreamLayoutComponent implements OnDestroy {
// Reset channels/page on category change
effect(() => {
this.stalkerStore.selectedCategoryId();
this.stalkerStore.setItvChannels([]);
this.stalkerStore.setPage(0);
this.clearEpgPreviewMaps();
this.epgLoadRequestId += 1;
this.fallbackEpgPrograms.set([]);
this.isLoadingFallbackEpg.set(false);
untracked(() => {
this.stalkerStore.setItvChannels([]);
this.stalkerStore.setPage(0);
this.clearEpgPreviewMaps();
this.epgLoadRequestId += 1;
this.fallbackEpgPrograms.set([]);
this.isLoadingFallbackEpg.set(false);
});
});
// Reset loading state when channels load and keep preview data in sync with bulk EPG.