fix(stalker): enhance pagination handling and add search phrase management

Entire-Checkpoint: c6e522b4276c
This commit is contained in:
4gray committed 2026-04-25 14:09:59 +02:00
1 parent efa0385fc8
commit cd111b6c36
5 files changed
+442 -14

No files matched your search

@@ -45,10 +45,39 @@ const TestContentStore = signalStore(
setSelectedCategory(id: string | null | undefined) {
patchState(store, { selectedCategoryId: id });
},
setPage(page: number) {
patchState(store, { page });
},
})),
withStalkerContent()
);
function createDeferred<T>() {
let resolve!: (value: T) => void;
let reject!: (reason?: unknown) => void;
const promise = new Promise<T>((res, rej) => {
resolve = res;
reject = rej;
});
return { promise, resolve, reject };
}
function createContentResponse(title: string) {
return {
js: {
data: [
{
id: title,
name: title,
category_id: '5',
},
],
total_items: 28,
},
};
}
async function flushResources(): Promise<void> {
TestBed.flushEffects();
await Promise.resolve();
@@ -194,4 +223,61 @@ describe('withStalkerContent failure states', () => {
'get_ordered_list failed'
);
});
it('ignores stale content responses after the selected page changes', async () => {
const pendingByPage = new Map<
number,
ReturnType<typeof createDeferred<unknown>>[]
>();
dataService.sendIpcEvent.mockImplementation(
(_event: unknown, payload: { params?: { p?: number } }) => {
const page = Number(payload.params?.p);
const deferred = createDeferred<unknown>();
const pending = pendingByPage.get(page) ?? [];
pending.push(deferred);
pendingByPage.set(page, pending);
return deferred.promise;
}
);
store.setSelectedContentType('vod');
store.setCategories('vod', [
{
category_id: '5',
category_name: 'Movies',
},
]);
store.setSelectedCategory('5');
store.setCurrentPlaylist(PLAYLIST);
void store.isPaginatedContentLoading();
await waitForCondition(() => pendingByPage.get(1)?.length === 1);
pendingByPage
.get(1)?.[0]
.resolve(createContentResponse('Page 1 initial'));
await waitForCondition(
() => store.getPaginatedContent()[0]?.name === 'Page 1 initial'
);
store.setPage(1);
await waitForCondition(() => pendingByPage.get(2)?.length === 1);
store.setPage(0);
await waitForCondition(() => pendingByPage.get(1)?.length === 2);
pendingByPage
.get(1)?.[1]
.resolve(createContentResponse('Page 1 restored'));
await waitForCondition(
() => store.getPaginatedContent()[0]?.name === 'Page 1 restored'
);
pendingByPage
.get(2)?.[0]
.resolve(createContentResponse('Page 2 stale'));
await flushResources();
expect(store.page()).toBe(0);
expect(store.getPaginatedContent()[0]?.name).toBe('Page 1 restored');
});
});
@@ -323,6 +323,30 @@ export function withStalkerContent() {
}
const categoryParam = params.category || '*';
const paramsPlaylistKey =
params.currentPlaylist?._id ??
params.currentPlaylist?.portalUrl ??
null;
const isCurrentRequest = (): boolean => {
const currentPlaylist =
storeContext.currentPlaylist();
const currentPlaylistKey =
currentPlaylist?._id ??
currentPlaylist?.portalUrl ??
null;
return (
params.contentType ===
storeContext.selectedContentType() &&
params.category ===
storeContext.selectedCategoryId() &&
params.search ===
storeContext.searchPhrase() &&
params.pageIndex ===
storeContext.page() + 1 &&
paramsPlaylistKey === currentPlaylistKey
);
};
const queryParams: Record<string, string | number> =
{
action: StalkerContentTypes[
@@ -347,6 +371,11 @@ export function withStalkerContent() {
}
try {
patchState(store, {
paginatedContent: [],
contentError: null,
});
const response =
await executeStalkerRequest<StalkerOrderedListResponse>(
requestDeps,
@@ -354,6 +383,10 @@ export function withStalkerContent() {
queryParams
);
if (!isCurrentRequest()) {
return [];
}
if (!Array.isArray(response?.js?.data)) {
const invalidResponseError = new Error(
'Invalid response structure'
@@ -411,6 +444,10 @@ export function withStalkerContent() {
return newItems;
} catch (error) {
if (!isCurrentRequest()) {
return [];
}
logger.warn('Error loading content', {
contentType: params.contentType,
category: params.category,
@@ -23,6 +23,25 @@ describe('withStalkerSelection', () => {
expect(store.page()).toBe(0);
});
it('keeps paging when the search phrase is unchanged', () => {
store.setSearchPhrase('matrix');
store.setPage(2);
store.setSearchPhrase('matrix');
expect(store.searchPhrase()).toBe('matrix');
expect(store.page()).toBe(2);
});
it('resets paging when the search phrase changes', () => {
store.setPage(2);
store.setSearchPhrase('matrix');
expect(store.searchPhrase()).toBe('matrix');
expect(store.page()).toBe(0);
});
it('synchronizes entity ids when the selected item changes', () => {
store.setSelectedItem({
id: '55',
@@ -62,12 +62,24 @@ export function withStalkerSelection() {
patchState(store, { selectedItvId: id });
},
setLimit(limit: number) {
if (store.limit() === limit) {
return;
}
patchState(store, { limit });
},
setPage(page: number) {
if (store.page() === page) {
return;
}
patchState(store, { page });
},
setSearchPhrase(phrase: string) {
if (store.searchPhrase() === phrase) {
return;
}
patchState(store, { searchPhrase: phrase, page: 0 });
},
setSelectedItem(selectedItem: StalkerVodSource | null | undefined) {