Skip to content

Commit

Permalink
Improve new useOrder hook
Browse files Browse the repository at this point in the history
  • Loading branch information
abailly-akamai committed Dec 10, 2024
1 parent c41c336 commit 20a009a
Show file tree
Hide file tree
Showing 6 changed files with 144 additions and 231 deletions.
3 changes: 1 addition & 2 deletions packages/manager/src/components/OrderBy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import {
sortByUTFDate,
} from 'src/utilities/sort-by';

import type { AllParams, AnyRoute } from '@tanstack/react-router';
import type { Order } from 'src/hooks/useOrder';
import type { ManagerPreferences } from 'src/types/ManagerPreferences';

Expand Down Expand Up @@ -60,7 +59,7 @@ export type CombinedProps<T> = Props<T>;
export const getInitialValuesFromUserPreferences = (
preferenceKey: string,
preferences: ManagerPreferences,
params: AllParams<AnyRoute> | Record<string, string>, // account for both TanStack and React Router params
params: Record<string, string>,
defaultOrderBy?: string,
defaultOrder?: Order,
prefix?: string
Expand Down
4 changes: 2 additions & 2 deletions packages/manager/src/features/Volumes/VolumesLanding.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,11 +69,11 @@ export const VolumesLanding = () => {

const { handleOrderChange, order, orderBy } = useOrderV2({
initialRoute: {
from: '/volumes',
search: {
defaultOrder: {
order: VOLUME_TABLE_DEFAULT_ORDER,
orderBy: VOLUME_TABLE_DEFAULT_ORDER_BY,
},
from: '/volumes',
},
preferenceKey: VOLUME_TABLE_PREFERENCE_KEY,
});
Expand Down
271 changes: 84 additions & 187 deletions packages/manager/src/hooks/useOrderV2.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,257 +2,154 @@ import { act, renderHook, waitFor } from '@testing-library/react';

import { HttpResponse, http, server } from 'src/mocks/testServer';
import { queryClientFactory } from 'src/queries/base';
import { usePreferences } from 'src/queries/profile/preferences';
import { wrapWithThemeAndRouter } from 'src/utilities/testHelpers';

import { useOrderV2 } from './useOrderV2';

import type { OrderSet } from 'src/types/ManagerPreferences';

// Default for Sorting
const defaultOrder: OrderSet = {
order: 'asc',
orderBy: 'status',
};

// Expected order for preference test
const queryOrder: OrderSet = {
order: 'desc',
orderBy: 'when',
};

// Expected order for preference test
const preferenceOrder: OrderSet = {
order: 'asc',
orderBy: 'type',
};

// Expected order for calling handleOrderChange
const handleOrderChangeOrder: OrderSet = {
order: 'desc',
orderBy: 'type',
};
import type { UseOrderV2Props } from './useOrderV2';

const mockNavigate = vi.fn();
const mockUseSearch = vi.fn(() => ({}));

// Used to mock query params
vi.mock('@tanstack/react-router', async () => {
const actual = await vi.importActual('@tanstack/react-router');
return {
...actual,
useNavigate: vi.fn(() => mockNavigate),
useParams: vi.fn(() => ({
volumeId: '123',
})),
useSearch: vi.fn(() => mockUseSearch()),
};
});

const queryClient = queryClientFactory();
const defaultProps: UseOrderV2Props = {
initialRoute: {
defaultOrder: {
order: 'asc',
orderBy: 'label',
},
from: '/',
},
preferenceKey: 'volumes',
};

const currentRoute = '/';

describe('useOrderV2 hook', () => {
describe('useOrderV2', () => {
beforeEach(() => {
vi.clearAllMocks();
queryClient.clear();
});

it('should use default sort options when there are no query params or preference', async () => {
const { result } = renderHook(
() =>
useOrderV2({
initialRoute: {
from: currentRoute,
search: {
order: defaultOrder.order,
orderBy: defaultOrder.orderBy,
},
},
}),
{
wrapper: (ui) =>
wrapWithThemeAndRouter(ui.children, {
queryClient,
}),
}
);

await waitFor(() => {
expect(result.current.order).toBe(defaultOrder.order);
expect(result.current.orderBy).toBe(defaultOrder.orderBy);
it('should use URL params with prefix', async () => {
mockUseSearch.mockReturnValue({
'test-order': 'desc',
'test-orderBy': 'status',
});
});

it('query parameters with sort data should take precedence over defaults', async () => {
const { result } = renderHook(
() =>
useOrderV2({
initialRoute: {
from: currentRoute,
search: {
order: queryOrder.order,
orderBy: queryOrder.orderBy,
},
},
}),
() => useOrderV2({ ...defaultProps, prefix: 'test' }),
{
wrapper: (ui) =>
wrapWithThemeAndRouter(ui.children, {
queryClient,
}),
wrapper: (ui) => wrapWithThemeAndRouter(ui.children, { queryClient }),
}
);

await waitFor(() => {
expect(result.current.order).toBe(queryOrder.order);
expect(result.current.orderBy).toBe(queryOrder.orderBy);
expect(result.current.order).toBe('desc');
expect(result.current.orderBy).toBe('status');
});
});

it('use preferences are used when there are no query params', async () => {
const queryClient = queryClientFactory();
it('should use preferences when present and no URL params are provided', async () => {
mockUseSearch.mockReturnValue({});

server.use(
http.get('*/profile/preferences', () => {
return HttpResponse.json({
sortKeys: {
'account-maintenance-order': preferenceOrder,
volumes: {
order: 'desc',
orderBy: 'size',
},
},
});
})
);

const { result: preferencesResult } = renderHook(() => usePreferences(), {
const { result } = renderHook(() => useOrderV2(defaultProps), {
wrapper: (ui) => wrapWithThemeAndRouter(ui.children, { queryClient }),
});

// This is kind of a bug. useOrder currently requires preferences to be cached
// before it works properly.
await waitFor(() => {
expect(preferencesResult.current.data).toBeDefined();
});

const { result } = renderHook(
() =>
useOrderV2({
initialRoute: {
from: currentRoute,
search: undefined,
},
preferenceKey: 'account-maintenance-order',
}),
{
wrapper: (ui) => wrapWithThemeAndRouter(ui.children, { queryClient }),
}
);

await waitFor(() => {
expect(result.current.order).toBe(preferenceOrder.order);
expect(result.current.orderBy).toBe(preferenceOrder.orderBy);
expect(result.current.order).toBe('desc');
expect(result.current.orderBy).toBe('size');
});
});

it('should change order when handleOrderChange is called with new values', async () => {
const { result } = renderHook(
() =>
useOrderV2({
initialRoute: {
from: currentRoute,
search: undefined,
},
}),
{
wrapper: (ui) => wrapWithThemeAndRouter(ui.children, { queryClient }),
}
);
it('should use default values as last priority', async () => {
mockUseSearch.mockReturnValue({});

act(() =>
result.current.handleOrderChange(
handleOrderChangeOrder.orderBy,
handleOrderChangeOrder.order
)
server.use(
http.get('*/profile/preferences', () => {
return HttpResponse.json({ sortKeys: {} });
})
);

const { result } = renderHook(() => useOrderV2(defaultProps), {
wrapper: (ui) => wrapWithThemeAndRouter(ui.children, { queryClient }),
});

await waitFor(() => {
expect(result.current.order).toBe(handleOrderChangeOrder.order);
expect(result.current.orderBy).toBe(handleOrderChangeOrder.orderBy);
expect(result.current.order).toBe(
defaultProps.initialRoute.defaultOrder.order
);
expect(result.current.orderBy).toBe(
defaultProps.initialRoute.defaultOrder.orderBy
);
});
});

it('should update query params when handleOrderChange is called', async () => {
const { result } = renderHook(
() =>
useOrderV2({
initialRoute: {
from: currentRoute,
search: undefined,
},
preferenceKey: 'account-maintenance-order',
}),
{
wrapper: (ui) => wrapWithThemeAndRouter(ui.children, { queryClient }),
}
);

act(() =>
result.current.handleOrderChange(
handleOrderChangeOrder.orderBy,
handleOrderChangeOrder.order
)
it.only('should update URL and preferences when handleOrderChange is called', async () => {
const mutatePreferencesMock = vi.fn();
server.use(
http.put('*/profile/preferences', async ({ request }) => {
const body = await request.json();
mutatePreferencesMock(body);
return HttpResponse.json(body);
})
);

expect(mockNavigate).toHaveBeenCalled();

// Get the actual function that was passed for futu
const searchFn = mockNavigate.mock.calls[0][0].search;
mockUseSearch.mockReturnValue({});

// Test that the search function returns the expected object
expect(searchFn({})).toEqual({
order: handleOrderChangeOrder.order,
orderBy: handleOrderChangeOrder.orderBy,
volumeId: '123',
const { result } = renderHook(() => useOrderV2(defaultProps), {
wrapper: (ui) => wrapWithThemeAndRouter(ui.children, { queryClient }),
});

await waitFor(() => {
expect(mockNavigate.mock.calls[0][0].to).toBe(currentRoute);
expect(result.current.order).toBe(handleOrderChangeOrder.order);
expect(result.current.orderBy).toBe(handleOrderChangeOrder.orderBy);
act(() => {
result.current.handleOrderChange('size', 'desc');
});
});

it('should update query params when handleOrderChange is called (with prefix)', async () => {
const prefix = 'volume';
const { result } = renderHook(
() =>
useOrderV2({
initialRoute: {
from: currentRoute,
search: undefined,
},
prefix,
}),
{
wrapper: (ui) => wrapWithThemeAndRouter(ui.children, { queryClient }),
}
);

act(() =>
result.current.handleOrderChange(
handleOrderChangeOrder.orderBy,
handleOrderChangeOrder.order
)
);

expect(mockNavigate).toHaveBeenCalled();

const searchFn = mockNavigate.mock.calls[0][0].search;
mockUseSearch.mockReturnValue({
order: 'desc',
orderBy: 'size',
});

await waitFor(() => {
expect(searchFn({})).toEqual({
[`${prefix}-order`]: handleOrderChangeOrder.order,
[`${prefix}-orderBy`]: handleOrderChangeOrder.orderBy,
volumeId: '123',
});
expect(result.current.order).toBe(handleOrderChangeOrder.order);
expect(result.current.orderBy).toBe(handleOrderChangeOrder.orderBy);
expect(mockNavigate).toHaveBeenCalledWith(
expect.objectContaining({
search: expect.any(Function),
to: '/',
})
);
expect(mutatePreferencesMock).toHaveBeenCalledWith(
expect.objectContaining({
sortKeys: expect.objectContaining({
volumes: {
order: 'desc',
orderBy: 'size',
},
}),
})
);
expect(result.current.order).toBe('desc');
expect(result.current.orderBy).toBe('size');
});
});
});
Loading

0 comments on commit 20a009a

Please sign in to comment.