mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-15 05:36:17 -08:00
fix(stalker): enhance pagination handling and add search phrase management
Entire-Checkpoint: c6e522b4276c
This commit is contained in:
1 parent
efa0385fc8
commit
cd111b6c36
5 files changed
+442
-14
No files matched your search
+86
@@ -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');
|
||||
});
|
||||
});
|
||||
+37
@@ -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,
|
||||
|
||||
+19
@@ -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',
|
||||
|
||||
+12
@@ -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) {
|
||||
|
||||
Reference in new issue
Block a user