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
45 changes: 45 additions & 0 deletions spec/components/MobileModal/MobileModal.test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import React, { useState } from 'react';
import '@testing-library/jest-dom';
import { render, fireEvent, waitFor } from '@testing-library/react';
import MobileModal from '../../../src/components/MobileModal';

function Wrapper() {
const [isOpen, setIsOpen] = useState(false);

return (
<div>
<button type='button' onClick={() => setIsOpen(true)}>
Open
</button>
<MobileModal isOpen={isOpen} setIsOpen={setIsOpen}>
Content
</MobileModal>
</div>
);
}

describe('MobileModal Component', () => {
it('renders the modal when open', () => {
const { getByText, queryByText } = render(<Wrapper />);

expect(queryByText('Content')).not.toBeInTheDocument();

fireEvent.click(getByText('Open'));

expect(getByText('Content')).toBeInTheDocument();
});

it('should dismiss modal when clicking in backdrop', () => {
const { getByText, queryByText, getByRole } = render(<Wrapper />);

fireEvent.click(getByText('Open'));

expect(getByText('Content')).toBeInTheDocument();

fireEvent.click(getByRole('presentation'));

waitFor(() => {
expect(queryByText('Content')).not.toBeInTheDocument();
});
});
});
20 changes: 20 additions & 0 deletions spec/components/Pagination/Pagination.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import { DEMO_API_KEY } from '../../../src/constants';
const originalWindowLocation = window.location;

beforeEach(() => {
window.innerWidth = 1024;
fireEvent(window, new Event('resize'));
Object.defineProperty(window, 'location', {
value: new URL('https://example.com'),
});
Expand Down Expand Up @@ -42,6 +44,24 @@ describe('Pagination Component', () => {
expect(getByText('5')).toBeInTheDocument();
});

it('rerenders the pagination buttons on window resize', () => {
const { getByText, queryByText } = render(
<CioPlp apiKey={DEMO_API_KEY}>
<Pagination totalNumResults={100} />
</CioPlp>,
);

expect(getByText('1')).toBeInTheDocument();
expect(getByText('2')).toBeInTheDocument();

window.innerWidth = 500;
fireEvent(window, new Event('resize'));

waitFor(() => {
expect(queryByText('2')).not.toBeInTheDocument();
});
});

it('renders with render props', () => {
const mockChildren = jest.fn().mockReturnValue(<div>Custom Pagination</div>);
const paginationProps = {
Expand Down
4 changes: 2 additions & 2 deletions spec/components/Sort/Sort.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,15 @@ describe('Testing Component: Sort', () => {
});

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

await waitFor(() => {
responseSortOptions.forEach((option) => {
expect(getByText(option.displayName)).toBeInTheDocument();
expect(getAllByText(option.displayName).length).toBeGreaterThanOrEqual(2);
});
});
});
Expand Down
79 changes: 58 additions & 21 deletions src/components/CioPlpGrid/CioPlpGrid.css
Original file line number Diff line number Diff line change
@@ -1,40 +1,77 @@
.cio-plp-grid {
display: grid;
grid-template-columns: 1fr 3fr;
gap: 20px;
padding: 20px;
display: grid;
grid-template-columns: 1fr 3fr;
gap: 20px;
padding: 20px;
}

.cio-filters-container {
padding: 20px;
padding: 20px;
}

.cio-filters-modal-button {
display: flex;
gap: 10px;
align-items: center;
background: none;
border: none;
cursor: pointer;
padding: 10px;
text-align: left;
outline: none;
font-size: 16px;
}

@media (max-width: 768px) {
.cio-plp-grid {
grid-template-columns: 1fr;
}

.cio-plp-grid .cio-product-tiles-container {
grid-template-columns: 1fr 1fr;
}

.cio-products-header-title {
width: 100%;
flex-grow: 1;
text-align: center;
}
}

.cio-products-container {
display: flex;
flex-direction: column;
display: flex;
flex-direction: column;
}

.cio-products-header-wrapper {
justify-content: space-between;
}

.cio-mobile-products-header-wrapper {
justify-content: center;
}

.cio-products-header-container {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
padding: 10px 5px;
.cio-products-header-wrapper,
.cio-mobile-products-header-wrapper {
display: flex;
flex-direction: row;
align-items: center;
padding: 10px 5px;
}

.cio-product-tiles-container {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 10px;
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 10px;
}

.cio-products-container .cio-product-tile {
padding: 5px;
padding: 5px;
}

.cio-products-container .cio-pagination-container {
display: flex;
justify-content: center;
padding: 10px;
margin-top: 20px;
display: flex;
justify-content: center;
padding: 10px;
margin-top: 20px;
}
41 changes: 35 additions & 6 deletions src/components/CioPlpGrid/CioPlpGrid.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import React from 'react';
import React, { useState } from 'react';
import { SearchResponse } from '@constructor-io/constructorio-client-javascript/lib/types';
import useSearchResults, { UseSearchResultsReturn } from '../../hooks/useSearchResults';
import ProductCard from '../ProductCard';
import Filters from '../Filters';
import FiltersIcon from '../Filters/FiltersIcon';
import MobileModal from '../MobileModal';
import Sort from '../Sort';
import Pagination from '../Pagination';
import ZeroResults from './ZeroResults/ZeroResults';
Expand All @@ -19,14 +21,28 @@ export type CioPlpGridWithRenderProps = IncludeRenderProps<CioPlpGridProps, UseS

export default function CioPlpGrid(props: CioPlpGridWithRenderProps) {
const { spinner, initialResponse, children } = props;
const [isFilterOpen, setIsFilterOpen] = useState(false);

const { data, status, refetch } = useSearchResults({ initialSearchResponse: initialResponse });

if (isPlpSearchDataRedirect(data)) {
// Do redirect
return null;
}

const response = data?.response;
const searchTerm = data?.request?.term;

const renderTitle = (
<span className='cio-products-header-title'>
<b>{response?.totalNumResults}</b> results
{searchTerm && (
<>
&nbsp;for <b>&quot;{searchTerm}&quot;</b>
</>
)}
</span>
);

return (
<>
Expand All @@ -43,17 +59,30 @@ export default function CioPlpGrid(props: CioPlpGridWithRenderProps) {
<>
{response?.results?.length ? (
<div className='cio-plp-grid'>
<div className='cio-filters-container'>
<div className='cio-filters-container cio-large-screen-only'>
<Filters facets={response.facets} />
</div>
<div className='cio-products-container'>
<div className='cio-products-header-container'>
<span>
<b>{response?.totalNumResults}</b> results
</span>
<Sort sortOptions={response.sortOptions} isOpen={false} />
<div className='cio-mobile-products-header-wrapper cio-mobile-only'>
{renderTitle}
</div>
<div className='cio-products-header-wrapper'>
<button
type='button'
className='cio-filters-modal-button cio-mobile-only'
onClick={() => setIsFilterOpen(!isFilterOpen)}>
{FiltersIcon}
Filters
</button>
<span className='cio-large-screen-only'>{renderTitle}</span>
<Sort sortOptions={response.sortOptions} isOpen={false} />
</div>
</div>
<div className='cio-product-tiles-container'>
<MobileModal isOpen={isFilterOpen} setIsOpen={setIsFilterOpen}>
<Filters facets={response.facets} />
</MobileModal>
{response?.results?.map((item) => (
<div className='cio-product-tile'>
<ProductCard key={item.itemId} item={item} />
Expand Down
2 changes: 2 additions & 0 deletions src/components/Filters/FilterOptionsList.css
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,10 @@

.cio-filter-option-name {
flex-grow: 1;
word-break: break-word;
}

.cio-filter-option-count {
color: #999;
margin-left: 8px;
}
7 changes: 7 additions & 0 deletions src/components/Filters/FilterRangeSlider.css
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,13 @@
padding: 2px;
}

@media (max-width: 320px) {
.cio-slider-inputs {
row-gap: 20px;
grid-template-columns: 1fr;
}
}

.cio-slider-input {
position: relative;
display: flex;
Expand Down
34 changes: 34 additions & 0 deletions src/components/Filters/FiltersIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import React from 'react';

export default (
<svg width='20' height='20' viewBox='0 0 20 20' fill='none' xmlns='http://www.w3.org/2000/svg'>
<path
d='M0.850098 5H3.5M19.1501 5H9.5'
stroke='#999999'
strokeWidth='1.7'
strokeLinecap='round'
strokeLinejoin='round'
/>
<path
d='M9.5 5C9.5 6.65685 8.15685 8 6.5 8C4.84315 8 3.5 6.65685 3.5 5C3.5 3.34315 4.84315 2 6.5 2C8.15685 2 9.5 3.34315 9.5 5Z'
stroke='#999999'
strokeWidth='1.7'
strokeLinecap='round'
strokeLinejoin='round'
/>
<path
d='M19.1499 15H16.5M0.849903 15H10.5'
stroke='#999999'
strokeWidth='1.7'
strokeLinecap='round'
strokeLinejoin='round'
/>
<path
d='M10.5 15C10.5 16.6569 11.8431 18 13.5 18C15.1569 18 16.5 16.6569 16.5 15C16.5 13.3431 15.1569 12 13.5 12C11.8431 12 10.5 13.3431 10.5 15Z'
stroke='#999999'
strokeWidth='1.7'
strokeLinecap='round'
strokeLinejoin='round'
/>
</svg>
);
Loading