Skip to content

Commit 1b25193

Browse files
HHHindawyHossam HindawyMudaafi
authored
[CDX-284] Add the option to hide groups + show groups on mobile (#191)
* [CDX-284] Add the option to hide groups + show groups on mobile * [CDX-284] Add tests * [CDX-284] Remove useless story * [CDX-284] npm audit * Avoid duplication of code examples * Remove CioPlpProps md file * EOF line * docs updates --------- Co-authored-by: Hossam Hindawy <hossam.hindawy@hossam.hindawy-Q7M9F6QK6G> Co-authored-by: mudaafi <ahmad.mudaafi@constructor.io>
1 parent 55beced commit 1b25193

File tree

16 files changed

+688
-210
lines changed

16 files changed

+688
-210
lines changed

spec/components/CioPlp/CioPlp.server.test.jsx

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import ReactDOMServer from 'react-dom/server';
33
import CioPlp from '../../../src/components/CioPlp';
44
import { useCioPlpContext } from '../../../src/hooks/useCioPlpContext';
55
import { DEMO_API_KEY } from '../../../src/constants';
6+
import mockSearchResponse from '../../local_examples/apiSearchResponse.json';
67

78
describe('CioPlp React Server-Side Rendering', () => {
89
it("throws an error if apiKey isn't provided", () => {
@@ -60,4 +61,32 @@ describe('CioPlp React Server-Side Rendering', () => {
6061
'<div class="cio-plp"><div>{&quot;cioClient&quot;:null,&quot;cioClientOptions&quot;:{},&quot;staticRequestConfigs&quot;:{},&quot;itemFieldGetters&quot;:{},&quot;formatters&quot;:{},&quot;callbacks&quot;:{},&quot;urlHelpers&quot;:{&quot;defaultQueryStringMap&quot;:{&quot;query&quot;:&quot;q&quot;,&quot;page&quot;:&quot;page&quot;,&quot;offset&quot;:&quot;offset&quot;,&quot;resultsPerPage&quot;:&quot;numResults&quot;,&quot;filters&quot;:&quot;filters&quot;,&quot;sortBy&quot;:&quot;sortBy&quot;,&quot;sortOrder&quot;:&quot;sortOrder&quot;,&quot;section&quot;:&quot;section&quot;}},&quot;renderOverrides&quot;:{}}</div></div>',
6162
);
6263
});
64+
65+
it('renders CioPlp with hideGroups set to true on the server', () => {
66+
// Render the component with hideGroups config and initial data containing groups
67+
const html = ReactDOMServer.renderToString(
68+
<CioPlp
69+
apiKey={DEMO_API_KEY}
70+
groupsConfigs={{ hideGroups: true }}
71+
initialSearchResponse={mockSearchResponse}
72+
/>,
73+
);
74+
// Groups container should not be present when hideGroups is true
75+
expect(html).not.toContain('cio-groups-container');
76+
expect(html).not.toContain('cio-groups-breadcrumbs');
77+
});
78+
79+
it('renders CioPlp with hideGroups set to false on the server', () => {
80+
// Render the component with hideGroups config and initial data containing groups
81+
const html = ReactDOMServer.renderToString(
82+
<CioPlp
83+
apiKey={DEMO_API_KEY}
84+
groupsConfigs={{ hideGroups: false }}
85+
initialSearchResponse={mockSearchResponse}
86+
/>,
87+
);
88+
// Groups container should be present when hideGroups is false
89+
expect(html).toContain('cio-groups-container');
90+
expect(html).toContain('cio-groups-breadcrumbs');
91+
});
6392
});

spec/components/CioPlp/CioPlp.test.jsx

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import React from 'react';
2-
import { render } from '@testing-library/react';
2+
import { render, waitFor } from '@testing-library/react';
33
import CioPlp from '../../../src/components/CioPlp';
44
import { useCioPlpContext } from '../../../src/hooks/useCioPlpContext';
55
import { DEMO_API_KEY } from '../../../src/constants';
66
import '@testing-library/jest-dom';
77
import { mockConstructorIOClient } from '../../test-utils';
8+
import mockSearchResponse from '../../local_examples/apiSearchResponse.json';
89

910
const originalWindowLocation = window.location;
1011

@@ -71,4 +72,34 @@ describe('CioPlp React Client-Side Rendering', () => {
7172
);
7273
expect(getByText('https://ac.cnstrc.com')).toBeInTheDocument();
7374
});
75+
76+
it('renders CioPlp with hideGroups set to true on the client', async () => {
77+
const { container } = render(
78+
<CioPlp
79+
apiKey={DEMO_API_KEY}
80+
groupsConfigs={{ hideGroups: true }}
81+
initialSearchResponse={mockSearchResponse}
82+
/>,
83+
);
84+
// Groups container should not be present when hideGroups is true
85+
await waitFor(() => {
86+
expect(container.querySelector('.cio-groups-container')).not.toBeInTheDocument();
87+
expect(container.querySelector('.cio-groups-breadcrumbs')).not.toBeInTheDocument();
88+
});
89+
});
90+
91+
it('renders CioPlp with hideGroups set to false on the client', async () => {
92+
const { container } = render(
93+
<CioPlp
94+
apiKey={DEMO_API_KEY}
95+
groupsConfigs={{ hideGroups: false }}
96+
initialSearchResponse={mockSearchResponse}
97+
/>,
98+
);
99+
// Groups container should be present when hideGroups is false
100+
await waitFor(() => {
101+
expect(container.querySelector('.cio-groups-container')).toBeInTheDocument();
102+
expect(container.querySelector('.cio-groups-breadcrumbs')).toBeInTheDocument();
103+
});
104+
});
74105
});

spec/components/CioPlpGrid/CioPlpGrid.server.test.jsx

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,4 +89,34 @@ describe('Testing Component on the server: CioPlpGrid', () => {
8989
expect(html).toContain(`data-cnstrc-num-results="${mockSearchData.response.totalNumResults}"`);
9090
expect(html).toContain(`data-cnstrc-result-id="${mockSearchData.resultId}"`);
9191
});
92+
93+
it('Should render CioPlpGrid with hideGroups set to true on the server', () => {
94+
const html = renderToString(
95+
<CioPlp apiKey={DEMO_API_KEY}>
96+
<CioPlpGrid
97+
groupsConfigs={{ hideGroups: true }}
98+
initialSearchResponse={mockSearchResponse}
99+
/>
100+
</CioPlp>,
101+
);
102+
103+
// Groups container should not be present when hideGroups is true
104+
expect(html).not.toContain('cio-groups-container');
105+
expect(html).not.toContain('cio-groups-breadcrumbs');
106+
});
107+
108+
it('Should render CioPlpGrid with hideGroups set to false on the server', () => {
109+
const html = renderToString(
110+
<CioPlp apiKey={DEMO_API_KEY}>
111+
<CioPlpGrid
112+
groupsConfigs={{ hideGroups: false }}
113+
initialSearchResponse={mockSearchResponse}
114+
/>
115+
</CioPlp>,
116+
);
117+
118+
// Groups container should be present when hideGroups is false
119+
expect(html).toContain('cio-groups-container');
120+
expect(html).toContain('cio-groups-breadcrumbs');
121+
});
92122
});

spec/components/CioPlpGrid/CioPlpGrid.test.jsx

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -341,4 +341,38 @@ describe('Testing Component: CioPlpGrid', () => {
341341
).toEqual('test-zero-results');
342342
});
343343
});
344+
345+
it('Should render CioPlpGrid with hideGroups set to true on the client', async () => {
346+
const { container } = render(
347+
<CioPlp apiKey={DEMO_API_KEY}>
348+
<CioPlpGrid
349+
groupsConfigs={{ hideGroups: true }}
350+
initialSearchResponse={mockApiSearchResponse}
351+
/>
352+
</CioPlp>,
353+
);
354+
355+
// Groups container should not be present when hideGroups is true
356+
await waitFor(() => {
357+
expect(container.querySelector('.cio-groups-container')).not.toBeInTheDocument();
358+
expect(container.querySelector('.cio-groups-breadcrumbs')).not.toBeInTheDocument();
359+
});
360+
});
361+
362+
it('Should render CioPlpGrid with hideGroups set to false on the client', async () => {
363+
const { container } = render(
364+
<CioPlp apiKey={DEMO_API_KEY}>
365+
<CioPlpGrid
366+
groupsConfigs={{ hideGroups: false }}
367+
initialSearchResponse={mockApiSearchResponse}
368+
/>
369+
</CioPlp>,
370+
);
371+
372+
// Groups container should be present when hideGroups is false
373+
await waitFor(() => {
374+
expect(container.querySelector('.cio-groups-container')).toBeInTheDocument();
375+
expect(container.querySelector('.cio-groups-breadcrumbs')).toBeInTheDocument();
376+
});
377+
});
344378
});

spec/components/Groups/Groups.server.test.jsx

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,4 +51,38 @@ describe('Testing Component on the server: Groups', () => {
5151
expect(mockChildren).toHaveBeenCalled();
5252
expect(html).toContain('Custom Groups');
5353
});
54+
55+
it('Should not render groups when hideGroups is true', () => {
56+
const groupsPropsWithHideGroups = {
57+
...groupsProps,
58+
hideGroups: true,
59+
};
60+
61+
const html = renderToString(
62+
<CioPlp apiKey={DEMO_API_KEY}>
63+
<Groups {...groupsPropsWithHideGroups} />
64+
</CioPlp>,
65+
);
66+
67+
mockTransformedGroups.forEach((group) => {
68+
expect(html).not.toContain(group.displayName);
69+
});
70+
});
71+
72+
it('Should render groups when hideGroups is false', () => {
73+
const groupsPropsWithHideGroups = {
74+
...groupsProps,
75+
hideGroups: false,
76+
};
77+
78+
const html = renderToString(
79+
<CioPlp apiKey={DEMO_API_KEY}>
80+
<Groups {...groupsPropsWithHideGroups} />
81+
</CioPlp>,
82+
);
83+
84+
mockTransformedGroups.forEach((group) => {
85+
expect(html).toContain(group.displayName);
86+
});
87+
});
5488
});

spec/components/Groups/Groups.test.jsx

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,43 @@ describe('Testing Component: Groups', () => {
124124
expect(getByText('Custom Filters')).toBeInTheDocument();
125125
});
126126

127+
it('Should not render groups when hideGroups is true', async () => {
128+
const groupsPropsWithHideGroups = {
129+
...groupsProps,
130+
hideGroups: true,
131+
};
132+
133+
const { container, queryByText } = render(
134+
<CioPlp apiKey={DEMO_API_KEY}>
135+
<Groups {...groupsPropsWithHideGroups} />
136+
</CioPlp>,
137+
);
138+
139+
await waitFor(() => {
140+
mockTransformedGroups.forEach((group) => {
141+
expect(queryByText(group.displayName)).not.toBeInTheDocument();
142+
});
143+
expect(container.querySelector('.cio-groups')).not.toBeInTheDocument();
144+
});
145+
});
146+
147+
it('Should render groups when hideGroups is false', async () => {
148+
const groupsPropsWithHideGroups = {
149+
...groupsProps,
150+
hideGroups: false,
151+
};
152+
153+
const { getByText } = render(
154+
<CioPlp apiKey={DEMO_API_KEY}>
155+
<Groups {...groupsPropsWithHideGroups} />
156+
</CioPlp>,
157+
);
158+
159+
await waitFor(() => {
160+
expect(getByText(mockTransformedGroups[0].displayName)).toBeInTheDocument();
161+
});
162+
});
163+
127164
it('Should exclude groups excluded by isHiddenGroupFn', () => {
128165
const filtersPropsWithChildren = {
129166
...groupsProps,

src/components/CioPlpGrid/CioPlpGrid.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,12 @@ export type CioPlpGridProps = {
3939
*/
4040
filterConfigs?: Omit<UseFilterProps, 'facets'>;
4141
/**
42-
* Used to set the `initialNumOptions` to limit the number of options shown initially.
42+
* Configuration options for the Groups component.
43+
* - `initialNumOptions`: Number of group options to show initially (default: 5).
44+
* Remaining options are hidden under "Show All" button.
45+
* - `isCollapsed`: Whether the groups section starts collapsed (default: false).
46+
* - `title`: Custom title for the groups section (default: "Categories").
47+
* - `hideGroups`: Whether to hide the groups component entirely (default: false).
4348
*/
4449
groupsConfigs?: Omit<GroupsProps, 'groups'>;
4550
};
@@ -153,6 +158,9 @@ export default function CioPlpGrid(props: CioPlpGridWithRenderProps) {
153158

154159
<div className='cio-product-tiles-container'>
155160
<MobileModal isOpen={isFilterOpen} setIsOpen={setIsFilterOpen}>
161+
{isSearchPage && (
162+
<Groups groups={data.response.groups} {...groupsConfigs} />
163+
)}
156164
<Filters facets={filters.facets} />
157165
</MobileModal>
158166

src/components/Filters/index.css

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
background: none;
1717
display: flex;
1818
flex-direction: row;
19+
gap: 8px;
1920
width: 100%;
2021
justify-content: space-between;
2122
align-items: center;
@@ -25,6 +26,7 @@
2526
padding: 0 0 8px 0;
2627
border-bottom: 1px solid #e1e1e1;
2728
margin-bottom: 12px;
29+
text-align: start;
2830
}
2931

3032
.cio-see-all {

src/components/Groups/Groups.tsx

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,37 @@ import useGroups, { UseGroupProps } from '../../hooks/useGroups';
44
import FilterOptionListRow from '../Filters/FilterOptionListRow';
55
import { IncludeRenderProps } from '../../types';
66

7+
/**
8+
* Props for the Groups component
9+
*/
710
export interface GroupsProps extends UseGroupProps {
11+
/**
12+
* Initial collapsed state of the groups filter panel
13+
* @default false
14+
*/
815
isCollapsed?: boolean;
16+
/**
17+
* Title displayed in the groups filter header
18+
* @default Categories
19+
*/
920
title?: string;
21+
/**
22+
* Whether to hide the entire groups component
23+
* @default false
24+
*/
25+
hideGroups?: boolean;
1026
}
1127

1228
export type GroupsWithRenderProps = IncludeRenderProps<GroupsProps, ReturnType<typeof useGroups>>;
1329

1430
export default function Groups(props: GroupsWithRenderProps) {
15-
const { isCollapsed: isCollapsedDefault = false, title = 'Categories', children, groups } = props;
31+
const {
32+
isCollapsed: isCollapsedDefault = false,
33+
title = 'Categories',
34+
children,
35+
groups,
36+
hideGroups,
37+
} = props;
1638
const useGroupsReturn = useGroups(props);
1739
const {
1840
optionsToRender,
@@ -28,12 +50,14 @@ export default function Groups(props: GroupsWithRenderProps) {
2850
const [isCollapsed, setIsCollapsed] = useState(isCollapsedDefault);
2951

3052
if (breadcrumbs.length === 0 && optionsToRender.length === 0) return null;
53+
54+
if (hideGroups) return null;
3155
return (
3256
<>
3357
{typeof children === 'function' ? (
3458
children(useGroupsReturn)
3559
) : (
36-
<>
60+
<div className='cio-groups-container'>
3761
<button
3862
className='cio-filter-header'
3963
type='button'
@@ -86,7 +110,7 @@ export default function Groups(props: GroupsWithRenderProps) {
86110
</ul>
87111
</div>
88112
</div>
89-
</>
113+
</div>
90114
)}
91115
</>
92116
);

src/hooks/useCioPlp.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ import {
1414
getPlpContainerCnstrcDataAttributes,
1515
} from '../utils';
1616
import useBrowseResults, { UseBrowseResultsProps } from './useBrowseResults';
17-
import useGroups, { UseGroupProps } from './useGroups';
17+
import useGroups from './useGroups';
18+
import { GroupsProps } from '../components/Groups';
1819
import useRequestConfigs from './useRequestConfigs';
1920

2021
export interface UseCioPlpHook extends PlpContextValue {}
@@ -34,9 +35,15 @@ export type UseCioPlpProps = UseSearchResultsProps &
3435
*/
3536
filterConfigs?: Omit<UseFilterProps, 'facets'>;
3637
/**
37-
* Used to set the `initialNumOptions` to limit the number of options shown initially.
38+
* Configuration options for the Groups component.
39+
* - `initialNumOptions`: Number of group options to show initially (default: 5).
40+
* Remaining options are hidden under "Show All" button.
41+
* - `isCollapsed`: Whether the groups section starts collapsed (default: false).
42+
* - `title`: Custom title for the groups section (default: "Categories").
43+
* - `hideGroups`: Whether to hide the groups component entirely (default: false).
44+
* - `isHiddenGroupFn`: Function to determine if a group should be hidden (default: undefined).
3845
*/
39-
groupsConfigs?: Omit<UseGroupProps, 'groups'>;
46+
groupsConfigs?: Omit<GroupsProps, 'groups'>;
4047
};
4148

4249
export default function useCioPlp(props: UseCioPlpProps = {}) {

0 commit comments

Comments
 (0)