Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions spec/useSort.server.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import '@testing-library/jest-dom';
import useSort from '../src/hooks/useSort';
import { transformSearchResponse, transformSortOptionsResponse } from '../src/utils/transformers';
import mockSearchResponse from './local_examples/apiSearchResponse.json';
import { renderHookServerSide, renderHookServerSideWithCioPlp } from './test-utils.server';
import { DEMO_API_KEY } from '../src/constants';

describe('Testing Hook on the server: useSort', () => {
beforeEach(() => {
// Mock console error to de-clutter the console for expected errors
const spy = jest.spyOn(console, 'error');
spy.mockImplementation(() => {});
});

afterEach(() => {
jest.restoreAllMocks(); // This will reset all mocks after each test
});

const searchResponse = transformSearchResponse(mockSearchResponse);
const responseSortOptions = transformSortOptionsResponse(searchResponse.sortOptions);

it('Should throw an error if called outside of PlpContext', () => {
expect(() => renderHookServerSide(() => useSort(searchResponse))).toThrow();
});

it('Should not break if window is undefined', async () => {
expect(() =>
renderHookServerSideWithCioPlp(() => useSort(searchResponse), {
apiKey: DEMO_API_KEY,
}),
).not.toThrow();
});

it('Should return sortOptions array', async () => {
const {
result: { sortOptions },
} = renderHookServerSideWithCioPlp(() => useSort(searchResponse), {
apiKey: DEMO_API_KEY,
});

expect(sortOptions).toHaveLength(responseSortOptions.length);
expect(sortOptions).toEqual(responseSortOptions);
});

it('Should return null sort option', async () => {
const {
result: { selectedSort },
} = renderHookServerSideWithCioPlp(() => useSort(searchResponse), {
apiKey: DEMO_API_KEY,
});

expect(selectedSort).toBeNull();
});

it('Should return a function to change selected sort', async () => {
const {
result: { changeSelectedSort },
} = renderHookServerSideWithCioPlp(() => useSort(searchResponse), {
apiKey: DEMO_API_KEY,
});

expect(typeof changeSelectedSort).toBe('function');
});
});
97 changes: 97 additions & 0 deletions spec/useSort.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import '@testing-library/jest-dom';
import { renderHook, waitFor } from '@testing-library/react';
import useSort from '../src/hooks/useSort';
import { transformSearchResponse, transformSortOptionsResponse } from '../src/utils/transformers';
import mockSearchResponse from './local_examples/apiSearchResponse.json';
import { renderHookWithCioPlp } from './test-utils';

describe('Testing Hook: useSort', () => {
let location;
const mockLocation = new URL('https://example.com');

beforeEach(() => {
// Mock console error to de-clutter the console for expected errors
const spy = jest.spyOn(console, 'error');
spy.mockImplementation(() => {});

location = window.location;
delete window.location;
window.location = mockLocation;
mockLocation.href = 'https://example.com/';
});

afterEach(() => {
window.location = location;
jest.restoreAllMocks(); // This will reset all mocks after each test
});

const searchResponse = transformSearchResponse(mockSearchResponse);
const responseSortOptions = transformSortOptionsResponse(searchResponse.sortOptions);

it('Should throw error if called outside of PlpContext', () => {
expect(() => renderHook(() => useSort())).toThrow();
});

it('Should return sortOptions array', async () => {
const { result } = renderHookWithCioPlp(() => useSort(searchResponse));

await waitFor(() => {
const {
current: { sortOptions },
} = result;

expect(sortOptions).toHaveLength(responseSortOptions.length);
expect(sortOptions).toEqual(responseSortOptions);
});
});

it('Should return the default sort option if none is already selected in request configs', async () => {
const { result } = renderHookWithCioPlp(() => useSort(searchResponse));

await waitFor(() => {
const {
current: { selectedSort },
} = result;

const defaultSort = responseSortOptions.find((option) => option.status === 'selected');
expect(selectedSort).toEqual(defaultSort);
});
});

it('Should return pre-selected sort option', async () => {
const sortBy = 'item_name';
const sortOrder = 'ascending';
window.location.href = `https://www.example.com/group_id/test?sortBy=${sortBy}&sortOrder=${sortOrder}`;

const { result } = renderHookWithCioPlp(() => useSort(searchResponse));

await waitFor(() => {
const {
current: { selectedSort },
} = result;

expect(selectedSort.sortBy).toEqual(sortBy);
expect(selectedSort.sortOrder).toEqual(sortOrder);
});
});

it('Should change selected sort option correctly', async () => {
const { result } = renderHookWithCioPlp(() => useSort(searchResponse));

await waitFor(() => {
const {
current: { selectedSort, changeSelectedSort },
} = result;

changeSelectedSort({
sortBy: 'item_name',
sortOrder: 'descending',
displayName: 'Name Z-A',
});

expect(selectedSort.sortBy).toEqual('item_name');
expect(selectedSort.sortOrder).toEqual('descending');
expect(selectedSort.displayName).toEqual('Name Z-A');
});
});
});
49 changes: 49 additions & 0 deletions src/hooks/useSort.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { useEffect, useState } from 'react';
import { useCioPlpContext } from './useCioPlpContext';
import { PlpBrowseResponse, PlpSearchResponse, PlpSortOption, UseSortReturn } from '../types';
import { transformSortOptionsResponse } from '../utils/transformers';
import useRequestConfigs from './useRequestConfigs';

const useSort = (searchOrBrowseResponse: PlpBrowseResponse | PlpSearchResponse): UseSortReturn => {
const contextValue = useCioPlpContext();

if (!contextValue) {
throw new Error('useSort must be used within a component that is a child of <CioPlp />');
}

const [selectedSort, setSelectedSort] = useState<PlpSortOption | null>(null);

const sortOptions = transformSortOptionsResponse(searchOrBrowseResponse.sortOptions);
const {
requestConfigs: { sortBy, sortOrder },
setRequestConfigs,
} = useRequestConfigs();

// Read sort configs from url and set state
useEffect(() => {
const sortOption = sortOptions.find(
(option) => option.sortBy === sortBy && option.sortOrder === sortOrder,
);
if (sortOption) setSelectedSort(sortOption);
// Select default sort option
else if (sortOptions.length) {
const defaultSort = sortOptions.find((option) => option.status === 'selected');
if (defaultSort) setSelectedSort(defaultSort);
}

// eslint-disable-next-line react-hooks/exhaustive-deps
}, [sortBy, sortOrder]);

const changeSelectedSort = (sortOption: PlpSortOption) => {
setSelectedSort(sortOption);
setRequestConfigs({ sortBy: sortOption.sortBy, sortOrder: sortOption.sortOrder });
};

return {
sortOptions,
selectedSort,
changeSelectedSort,
};
};

export default useSort;
13 changes: 13 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,13 @@ export interface Item {
rawResponse?: ApiItem;
}

export type PlpSortOption = {
sortBy: string;
sortOrder: SortOrder;
displayName: string;
status: string;
};

export interface SwatchItem {
url?: string;
itemName?: string;
Expand Down Expand Up @@ -200,6 +207,12 @@ export interface CioPlpProviderProps {

export type CioPlpProps = CioPlpProviderProps;

export type UseSortReturn = {
sortOptions: PlpSortOption[];
selectedSort: PlpSortOption | null;
changeSelectedSort: (sortOption: PlpSortOption) => void;
};

/**
* Represents a function that handles pagination logic.
* @param searchResponse - The search response data.
Expand Down
14 changes: 14 additions & 0 deletions src/utils/transformers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@ import {
Redirect,
SearchResponse,
SearchResponseType,
SortOption,
} from '@constructor-io/constructorio-client-javascript/lib/types';
import {
PlpSearchResponse,
Item,
ApiItem,
PlpBrowseResponse,
PlpSearchRedirectResponse,
PlpSortOption,
Variation,
} from '../types';

Expand Down Expand Up @@ -125,3 +127,15 @@ export function transformBrowseResponse(res: GetBrowseResultsResponse) {
rawResponse: res,
} as PlpBrowseResponse;
}

export function transformSortOptionsResponse(options: SortOption[]): PlpSortOption[] {
return options.map(
(option) =>
({
sortBy: option.sort_by,
sortOrder: option.sort_order,
displayName: option.display_name,
status: option.status,
}) as PlpSortOption,
);
}