This commit is contained in:
170
tests/hooks/usePaginatedResource.test.tsx
Normal file
170
tests/hooks/usePaginatedResource.test.tsx
Normal file
@@ -0,0 +1,170 @@
|
||||
import { act } from 'react';
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { usePaginatedResource } from '../../src/hooks/usePaginatedResource';
|
||||
import { renderHook } from '../helpers/renderHook';
|
||||
|
||||
type Item = {
|
||||
id: string;
|
||||
};
|
||||
|
||||
async function flushDebounce(delay = 250) {
|
||||
await act(async () => {
|
||||
vi.advanceTimersByTime(delay);
|
||||
await Promise.resolve();
|
||||
});
|
||||
}
|
||||
|
||||
describe('usePaginatedResource', () => {
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
it('loads first page after debounce and stores response fields', async () => {
|
||||
const loadMock = vi.fn(async () => ({
|
||||
items: [{ id: '1' }] as Item[],
|
||||
page: 1,
|
||||
pageSize: 10,
|
||||
total: 25,
|
||||
totalPages: 3,
|
||||
}));
|
||||
|
||||
const { result } = renderHook(() =>
|
||||
usePaginatedResource<Item>({
|
||||
load: loadMock,
|
||||
sort: '-createdAt',
|
||||
}),
|
||||
);
|
||||
|
||||
await flushDebounce();
|
||||
|
||||
expect(loadMock).toHaveBeenCalledWith({
|
||||
q: '',
|
||||
page: 1,
|
||||
pageSize: 10,
|
||||
sort: '-createdAt',
|
||||
});
|
||||
expect(result.current.items).toEqual([{ id: '1' }]);
|
||||
expect(result.current.total).toBe(25);
|
||||
expect(result.current.totalPages).toBe(3);
|
||||
expect(result.current.isLoading).toBe(false);
|
||||
expect(result.current.error).toBeNull();
|
||||
});
|
||||
|
||||
it('resets page to 1 when query or page size changes', async () => {
|
||||
const loadMock = vi.fn(
|
||||
async ({ q, page, pageSize }: { q: string; page: number; pageSize: number }) => ({
|
||||
items: [{ id: `${q}:${page}:${pageSize}` }] as Item[],
|
||||
page,
|
||||
pageSize,
|
||||
total: 1,
|
||||
totalPages: 1,
|
||||
}),
|
||||
);
|
||||
|
||||
const { result } = renderHook(() =>
|
||||
usePaginatedResource<Item>({
|
||||
load: loadMock,
|
||||
}),
|
||||
);
|
||||
|
||||
await flushDebounce();
|
||||
|
||||
act(() => {
|
||||
result.current.setPage(2);
|
||||
});
|
||||
await flushDebounce();
|
||||
expect(loadMock).toHaveBeenLastCalledWith({
|
||||
q: '',
|
||||
page: 2,
|
||||
pageSize: 10,
|
||||
sort: undefined,
|
||||
});
|
||||
|
||||
act(() => {
|
||||
result.current.setQuery('search');
|
||||
});
|
||||
await flushDebounce();
|
||||
expect(loadMock).toHaveBeenLastCalledWith({
|
||||
q: 'search',
|
||||
page: 1,
|
||||
pageSize: 10,
|
||||
sort: undefined,
|
||||
});
|
||||
|
||||
act(() => {
|
||||
result.current.setPage(3);
|
||||
});
|
||||
await flushDebounce();
|
||||
|
||||
act(() => {
|
||||
result.current.setPageSize(20);
|
||||
});
|
||||
await flushDebounce();
|
||||
expect(loadMock).toHaveBeenLastCalledWith({
|
||||
q: 'search',
|
||||
page: 1,
|
||||
pageSize: 20,
|
||||
sort: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it('cancels stale debounce timers when inputs change quickly', async () => {
|
||||
const loadMock = vi.fn(async () => ({
|
||||
items: [] as Item[],
|
||||
page: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
totalPages: 0,
|
||||
}));
|
||||
|
||||
const { result } = renderHook(() =>
|
||||
usePaginatedResource<Item>({
|
||||
load: loadMock,
|
||||
debounceMs: 100,
|
||||
}),
|
||||
);
|
||||
|
||||
act(() => {
|
||||
result.current.setQuery('latest');
|
||||
});
|
||||
|
||||
await flushDebounce(100);
|
||||
|
||||
expect(loadMock).toHaveBeenCalledTimes(1);
|
||||
expect(loadMock).toHaveBeenCalledWith({
|
||||
q: 'latest',
|
||||
page: 1,
|
||||
pageSize: 10,
|
||||
sort: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it('maps thrown errors into string error state', async () => {
|
||||
const loadMock = vi
|
||||
.fn()
|
||||
.mockRejectedValueOnce(new Error('Load failed'))
|
||||
.mockRejectedValueOnce('unknown');
|
||||
|
||||
const { result } = renderHook(() =>
|
||||
usePaginatedResource<Item>({
|
||||
load: loadMock,
|
||||
}),
|
||||
);
|
||||
|
||||
await flushDebounce();
|
||||
expect(result.current.error).toBe('Load failed');
|
||||
expect(result.current.isLoading).toBe(false);
|
||||
|
||||
act(() => {
|
||||
result.current.setPage(2);
|
||||
});
|
||||
await flushDebounce();
|
||||
|
||||
expect(result.current.error).toBe('Request failed. Please try again.');
|
||||
expect(result.current.isLoading).toBe(false);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user