76f525fd38
- Migrate `EnvironmentList` from `GroupSortTable` to `SortableList`, removing ~1,700 lines of duplicated component code - Move health sort ranking to the backend (`sort.go`), adding `Health` and `Id` sort keys - Delete `GroupSortTable`, `GroupSortTableGroupRow`, `useGroupSortTableState`, and `store` — functionality absorbed by `SortableList` - Add `useHomeViewState` hook to centralise home view URL state (`groupBy`, `groupFilter`, `order`, `page`, `search`) - Update `useTableStateFromUrl` to support `groupBy` and `groupFilter` URL params with a `buildExtra` callback - Rename URL param `filter` → `groupFilter` for clarity; add `search` and `order` to `/home` route definition - Simplify `EnvironmentList` props — remove `headerFilter` / `onHeaderFilterChange`, leaving only `onClickBrowse` - Add `computeSortDesc` pure utility to `SortableList` and cover all toggle/reset cases with unit tests - Update `SortableListHeader` to use `activeKey` prop (renamed from `sortBy`); fix all callsites and stories - Fix `SortableList` sort-key normalisation to be case-insensitive; update tests to reflect no-match behaviour
133 lines
3.8 KiB
TypeScript
133 lines
3.8 KiB
TypeScript
import { renderHook, act } from '@testing-library/react-hooks';
|
|
import { vi, describe, it, expect, beforeEach } from 'vitest';
|
|
|
|
import { useTableStateFromUrl } from './useTableStateFromUrl';
|
|
|
|
const mockSetUrlState = vi.fn();
|
|
const mockSetStoredPageSize = vi.fn();
|
|
let mockUrlParams: Record<string, string | undefined> = {};
|
|
let mockStoredPageSize = 10;
|
|
|
|
vi.mock('@/react/hooks/useParamState', () => ({
|
|
useParamsState: (
|
|
parseParams: (params: Record<string, string | undefined>) => unknown
|
|
) => [parseParams(mockUrlParams), mockSetUrlState] as const,
|
|
}));
|
|
|
|
vi.mock('@/react/hooks/useLocalStorage', () => ({
|
|
useLocalStorage: () => [mockStoredPageSize, mockSetStoredPageSize],
|
|
}));
|
|
|
|
describe('useTableStateFromUrl', () => {
|
|
beforeEach(() => {
|
|
mockUrlParams = {};
|
|
mockStoredPageSize = 10;
|
|
mockSetUrlState.mockReset();
|
|
mockSetStoredPageSize.mockReset();
|
|
});
|
|
|
|
it('returns defaults when URL params are absent', () => {
|
|
const { result } = renderHook(() =>
|
|
useTableStateFromUrl({ localStorageKey: 'test', defaultSort: 'name' })
|
|
);
|
|
|
|
expect(result.current.search).toBe('');
|
|
expect(result.current.sortBy).toEqual({ id: 'name', desc: false });
|
|
expect(result.current.page).toBe(0);
|
|
expect(result.current.pageSize).toBe(10);
|
|
});
|
|
|
|
it('setSearch updates search and resets page to 0', () => {
|
|
const { result } = renderHook(() =>
|
|
useTableStateFromUrl({ localStorageKey: 'test', defaultSort: 'name' })
|
|
);
|
|
|
|
act(() => {
|
|
result.current.setSearch('hello');
|
|
});
|
|
|
|
expect(mockSetUrlState).toHaveBeenCalledWith({ search: 'hello', page: 0 });
|
|
});
|
|
|
|
it('setSortBy updates sort and order and resets page to 0', () => {
|
|
const { result } = renderHook(() =>
|
|
useTableStateFromUrl({ localStorageKey: 'test', defaultSort: 'name' })
|
|
);
|
|
|
|
act(() => {
|
|
result.current.setSortBy('age', true);
|
|
});
|
|
|
|
expect(mockSetUrlState).toHaveBeenCalledWith({
|
|
sort: 'age',
|
|
order: 'desc',
|
|
page: 0,
|
|
groupBy: null,
|
|
groupFilter: null,
|
|
});
|
|
});
|
|
|
|
it('setPage updates page only', () => {
|
|
const { result } = renderHook(() =>
|
|
useTableStateFromUrl({ localStorageKey: 'test', defaultSort: 'name' })
|
|
);
|
|
|
|
act(() => {
|
|
result.current.setPage(3);
|
|
});
|
|
|
|
expect(mockSetUrlState).toHaveBeenCalledWith({ page: 3 });
|
|
});
|
|
|
|
it('setPageSize updates localStorage and URL pageSize, resets page to 0', () => {
|
|
const { result } = renderHook(() =>
|
|
useTableStateFromUrl({ localStorageKey: 'test', defaultSort: 'name' })
|
|
);
|
|
|
|
act(() => {
|
|
result.current.setPageSize(25);
|
|
});
|
|
|
|
expect(mockSetStoredPageSize).toHaveBeenCalledWith(25);
|
|
expect(mockSetUrlState).toHaveBeenCalledWith({ pageSize: 25, page: 0 });
|
|
});
|
|
|
|
it('falls back to 0 for invalid page URL param', () => {
|
|
mockUrlParams = { page: 'abc' };
|
|
|
|
const { result } = renderHook(() =>
|
|
useTableStateFromUrl({ localStorageKey: 'test', defaultSort: 'name' })
|
|
);
|
|
|
|
expect(result.current.page).toBe(0);
|
|
});
|
|
|
|
it('falls back to localStorage pageSize for invalid pageSize URL param', () => {
|
|
mockUrlParams = { pageSize: 'abc' };
|
|
mockStoredPageSize = 20;
|
|
|
|
const { result } = renderHook(() =>
|
|
useTableStateFromUrl({ localStorageKey: 'test', defaultSort: 'name' })
|
|
);
|
|
|
|
expect(result.current.pageSize).toBe(20);
|
|
});
|
|
|
|
it('sanitizes out-of-range URL params to safe defaults', () => {
|
|
mockUrlParams = { page: '-3', pageSize: '0', order: 'sideways' };
|
|
mockStoredPageSize = 15;
|
|
|
|
const defaultSort = 'sorting';
|
|
|
|
const { result } = renderHook(() =>
|
|
useTableStateFromUrl({ localStorageKey: 'test', defaultSort })
|
|
);
|
|
|
|
expect(result.current.page).toBe(0);
|
|
expect(result.current.pageSize).toBe(15);
|
|
expect(result.current.sortBy?.desc).toBe(false);
|
|
expect(result.current.search).toBe('');
|
|
expect(result.current.sortBy?.id).toBe(defaultSort);
|
|
});
|
|
});
|