Files
portainer/app/react/components/datatables/useTableStateFromUrl.test.ts
T
bernard-portainer 76f525fd38 refactor(home): refactor Environment List to use SortableList component [C9S-131] (#2522)
- 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
2026-05-08 16:55:40 +12:00

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);
});
});