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
56 changes: 56 additions & 0 deletions spec/Sort/Sort.server.test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import React from 'react';
import { renderToString } from 'react-dom/server';
import Sort from '../../src/components/Sort/Sort';
import CioPlp from '../../src/components/CioPlp';
import { DEMO_API_KEY } from '../../src/constants';
import '@testing-library/jest-dom';
import { transformSearchResponse } from '../../src/utils/transformers';
import mockSearchResponse from '../local_examples/apiSearchResponse.json';

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

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

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

it('Should throw error if used outside the CioPlp', () => {
expect(() => renderToString(<Sort searchOrBrowseResponse={searchResponse} />)).toThrow();
});

it('Should render sort options based on search or browse response', async () => {
const html = renderToString(
<CioPlp apiKey={DEMO_API_KEY}>
<Sort searchOrBrowseResponse={searchResponse} />
</CioPlp>,
);

responseSortOptions.forEach((option) => {
expect(html).toContain(option.displayName);
});
});

it('Should render correctly with render props', () => {
const mockChildren = jest.fn().mockReturnValue(<div>Custom Sort</div>);

const sortProps = {
searchOrBrowseResponse: searchResponse,
children: mockChildren,
};

const html = renderToString(
<CioPlp apiKey={DEMO_API_KEY}>
<Sort {...sortProps} />
</CioPlp>,
);
expect(mockChildren).toHaveBeenCalled();
expect(html).toContain('Custom Sort');
});
});
104 changes: 104 additions & 0 deletions spec/Sort/Sort.test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import React from 'react';
import { render, waitFor, fireEvent } from '@testing-library/react';
import Sort from '../../src/components/Sort/Sort';
import CioPlp from '../../src/components/CioPlp';
import { DEMO_API_KEY } from '../../src/constants';
import '@testing-library/jest-dom';
import { transformSearchResponse } from '../../src/utils/transformers';
import mockSearchResponse from '../local_examples/apiSearchResponse.json';

describe('Testing Component: Sort', () => {
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/';
});

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

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

it('Should throw error if used outside the CioPlp', () => {
expect(() => render(<Sort searchOrBrowseResponse={searchResponse} />)).toThrow();
});

it('Should render sort options based on search or browse response', async () => {
const { getByText } = render(
<CioPlp apiKey={DEMO_API_KEY}>
<Sort searchOrBrowseResponse={searchResponse} />
</CioPlp>,
);

await waitFor(() => {
responseSortOptions.forEach((option) => {
expect(getByText(option.displayName)).toBeInTheDocument();
});
});
});

it('Should change selected sort option in component correctly', async () => {
const { container } = render(
<CioPlp apiKey={DEMO_API_KEY}>
<Sort searchOrBrowseResponse={searchResponse} />
</CioPlp>,
);

const defaultSort = responseSortOptions.find((option) => option.status === 'selected');
expect(container.querySelector('input:checked')).toBeDefined();
expect(container.querySelector('input:checked').value).toBe(JSON.stringify(defaultSort));

const newSortOption = responseSortOptions.find((option) => option.status !== 'selected');
fireEvent.click(
container?.querySelector(`#${newSortOption.sortBy}-${newSortOption.sortOrder}`),
);

expect(container.querySelector('input:checked')).toBeDefined();
expect(container.querySelector('input:checked').value).toBe(JSON.stringify(newSortOption));
});

it('Should change selected sort option in url correctly', async () => {
const { container } = render(
<CioPlp apiKey={DEMO_API_KEY}>
<Sort searchOrBrowseResponse={searchResponse} />
</CioPlp>,
);

const newSortOption = responseSortOptions.find((option) => option.status !== 'selected');
fireEvent.click(
container?.querySelector(`#${newSortOption.sortBy}-${newSortOption.sortOrder}`),
);

const urlObject = new URL(window.location.href);
expect(urlObject.searchParams.get('sortBy')).toEqual(newSortOption.sortBy);
expect(urlObject.searchParams.get('sortOrder')).toEqual(newSortOption.sortOrder);
});

it('Should render correctly with render props', () => {
const mockChildren = jest.fn().mockReturnValue(<div>Custom Sort</div>);

const sortProps = {
searchOrBrowseResponse: searchResponse,
children: mockChildren,
};

const { getByText } = render(
<CioPlp apiKey={DEMO_API_KEY}>
<Sort {...sortProps} />
</CioPlp>,
);
expect(mockChildren).toHaveBeenCalled();
expect(getByText('Custom Sort')).toBeInTheDocument();
});
});
4 changes: 2 additions & 2 deletions spec/useSort.server.test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import '@testing-library/jest-dom';
import useSort from '../src/hooks/useSort';
import { transformSearchResponse, transformSortOptionsResponse } from '../src/utils/transformers';
import { transformSearchResponse } 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';
Expand All @@ -17,7 +17,7 @@ describe('Testing Hook on the server: useSort', () => {
});

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

it('Should throw an error if called outside of PlpContext', () => {
expect(() => renderHookServerSide(() => useSort(searchResponse))).toThrow();
Expand Down
4 changes: 2 additions & 2 deletions spec/useSort.test.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
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 { transformSearchResponse } from '../src/utils/transformers';
import mockSearchResponse from './local_examples/apiSearchResponse.json';
import { renderHookWithCioPlp } from './test-utils';

Expand All @@ -26,7 +26,7 @@ describe('Testing Hook: useSort', () => {
});

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

it('Should throw error if called outside of PlpContext', () => {
expect(() => renderHook(() => useSort())).toThrow();
Expand Down
82 changes: 82 additions & 0 deletions src/components/Sort/Sort.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import React, { useState } from 'react';
import useSort from '../../hooks/useSort';
import {
IncludeRenderProps,
PlpBrowseResponse,
PlpSearchResponse,
UseSortReturn,
} from '../../types';

type SortProps = {
/**
* Default open state of dropdown
*/
isOpen?: boolean;
/**
* Used to build and render sort options dynamically
*/
searchOrBrowseResponse: PlpBrowseResponse | PlpSearchResponse;
};
type SortWithRenderProps = IncludeRenderProps<SortProps, UseSortReturn>;

export default function Sort({
isOpen: defaultOpen = true,
searchOrBrowseResponse,
children,
}: SortWithRenderProps) {
const [isOpen, setIsOpen] = useState(defaultOpen);
const { sortOptions, selectedSort, changeSelectedSort } = useSort(searchOrBrowseResponse);

const toggleCollapsible = () => {
setIsOpen(!isOpen);
};

const handleOptionChange = (event: React.ChangeEvent<HTMLInputElement>) => {
changeSelectedSort(JSON.parse(event.target.value));
};

return (
<>
{typeof children === 'function' ? (
children({
sortOptions,
selectedSort,
changeSelectedSort,
})
) : (
<div className='cio-plp-sort'>
<button type='button' className='collapsible' onClick={toggleCollapsible}>
Sort
<i className={`arrow ${isOpen ? 'arrow-up' : 'arrow-down'}`} />
</button>
{isOpen && (
<div className='collapsible-content'>
{sortOptions.map((option) => (
<label
htmlFor={`${option.sortBy}-${option.sortOrder}`}
key={`${option.sortBy}-${option.sortOrder}`}>
<input
id={`${option.sortBy}-${option.sortOrder}`}
type='radio'
name={`${option.sortBy}-${option.sortOrder}`}
value={JSON.stringify(option)}
checked={
selectedSort?.sortBy === option.sortBy &&
selectedSort.sortOrder === option.sortOrder
}
onChange={handleOptionChange}
/>
<span>{option.displayName}</span>
</label>
))}
</div>
)}
</div>
)}
</>
);
}

Sort.defaultProps = {
isOpen: true,
};
84 changes: 84 additions & 0 deletions src/components/Sort/sort.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
.cio-plp-sort {
background-color: #FFFFFF;
width: 100%;
box-sizing: border-box;
padding: 10px;
}

.cio-plp-sort .collapsible {
background-color: #FFFFFF;
border: none;
border-bottom: 1px solid #0000001A;
cursor: pointer;
padding: 10px;
width: 100%;
min-width: 180px;
text-align: left;
outline: none;
font-size: 18px;
display: flex;
justify-content: space-between;
align-items: center;
}

.cio-plp-sort .collapsible-content {
padding: 10px;
background-color: #FFFFFF;
display: flex;
flex-wrap: wrap;
flex-direction: column;
}

.cio-plp-sort label {
cursor: pointer;
overflow: hidden;
width: 100%;
}

.cio-plp-sort input {
display: none;
}

.cio-plp-sort label span {
display: flex;
gap: 10px;
align-items: center;
padding: 10px;
transition: 0.25s ease;
}

/* Apply changes with a smooth and gradual transition */
.cio-plp-sort label span:before {
display: flex;
flex-shrink: 0;
content: "";
background-color: #FFFFFF;
width: 20px;
height: 20px;
border-radius: 50%;
transition: 0.25s ease;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The animation on these are so cool 🚀

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❤️

border: 1px solid #00000033;
}

.cio-plp-sort input:checked+span:before {
box-shadow: inset 0 0 0 6px #000000;
}

.cio-plp-sort .arrow {
border: 1px solid #00000099;
border-width: 0 2px 2px 0;
display: inline-block;
padding: 4px;
}

.cio-plp-sort .arrow-up {
transform: rotate(-135deg);
-webkit-transform: rotate(-135deg);
margin-bottom: -6px;
}

.cio-plp-sort .arrow-down {
transform: rotate(45deg);
-webkit-transform: rotate(45deg);
margin-top: -6px;
}
3 changes: 1 addition & 2 deletions src/hooks/useSort.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
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 => {
Expand All @@ -13,7 +12,7 @@ const useSort = (searchOrBrowseResponse: PlpBrowseResponse | PlpSearchResponse):

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

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