Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(commerce): add commerce category facets #3495

Merged
merged 67 commits into from
Mar 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
67 commits
Select commit Hold shift + click to select a range
7e30c35
Add commerce category facets
fbeaudoincoveo Dec 19, 2023
7572360
Apply review comments
fbeaudoincoveo Dec 19, 2023
4770d4e
Add category facet exports
fbeaudoincoveo Dec 19, 2023
86cf244
Merge branch 'master' into CAPI-90-category-facets
fbeaudoincoveo Dec 20, 2023
0d78fd0
Fix tests
fbeaudoincoveo Dec 20, 2023
3e39436
Change behavior on invalid type + improve tests
fbeaudoincoveo Dec 20, 2023
d2fed8e
Log errors with engine.logger
fbeaudoincoveo Dec 20, 2023
36d826e
Fix typing issues
fbeaudoincoveo Dec 20, 2023
53ca172
Move state-derived attributes out of state + rename type
fbeaudoincoveo Dec 22, 2023
3925e1a
Add selectedValueWithAncestry method + override state-derived methods
fbeaudoincoveo Dec 22, 2023
1ddd44f
Update export name
fbeaudoincoveo Dec 22, 2023
bfe115a
Add support for deselecting all values in a given facet
fbeaudoincoveo Dec 22, 2023
ea891ae
Merge branch 'master' into CAPI-90-category-facets
fbeaudoincoveo Jan 31, 2024
34281c2
Move commerce category facets to core/ƒacets folder
fbeaudoincoveo Jan 31, 2024
109e317
Update export
fbeaudoincoveo Jan 31, 2024
beaf4d9
Update imports
fbeaudoincoveo Jan 31, 2024
c5dea0c
Merge branch 'master' into CAPI-90-category-facets
fbeaudoincoveo Feb 20, 2024
f38b2b2
Make selectedValueWithAncestry optional
fbeaudoincoveo Feb 26, 2024
3a753f7
Get rid of barrel import
fbeaudoincoveo Feb 26, 2024
f4e2391
Move state derived properties back to controller state
fbeaudoincoveo Feb 26, 2024
6690166
Move state derived properties back to controller state
fbeaudoincoveo Feb 26, 2024
13b6072
Adjust toggle category facet value selection behavior
fbeaudoincoveo Feb 26, 2024
a9c0586
Start reworking tests
fbeaudoincoveo Feb 26, 2024
fb6b8c4
Merge branch 'CAPI-90-category-facets' of github.com:coveo/ui-kit int…
fbeaudoincoveo Feb 26, 2024
dc625a7
Make selectedValueAncestry optional
fbeaudoincoveo Feb 26, 2024
4428400
Support show more / show less + rework reducer
fbeaudoincoveo Feb 26, 2024
7bc7d7c
Revert "Start reworking tests"
fbeaudoincoveo Feb 26, 2024
4970c80
Fix failing tests
fbeaudoincoveo Feb 26, 2024
c958e5e
Add some missing tests
fbeaudoincoveo Feb 26, 2024
9929686
Eliminate logic that works around API caching issues
fbeaudoincoveo Feb 27, 2024
513c600
Merge branch 'master' into CAPI-90-category-facets
fbeaudoincoveo Feb 28, 2024
a70bd4a
Fix type
fbeaudoincoveo Feb 28, 2024
819c5b8
Fix tests
fbeaudoincoveo Feb 28, 2024
5153af4
Merge branch 'master' into CAPI-90-category-facets
fbeaudoincoveo Feb 29, 2024
721e0ef
Minore changes in state
fbeaudoincoveo Mar 1, 2024
d11a4f0
Rewrite tests
fbeaudoincoveo Mar 1, 2024
35f1844
Always return canShowLessValues = false when no value is selected
fbeaudoincoveo Mar 1, 2024
0f31815
The great renaming
fbeaudoincoveo Mar 1, 2024
305162d
Reorganize / fix / improve tests
fbeaudoincoveo Mar 2, 2024
0e06348
Harmonize descriptions
fbeaudoincoveo Mar 2, 2024
230cf2a
Rename function
fbeaudoincoveo Mar 2, 2024
37eada3
SImplify test suite structure
fbeaudoincoveo Mar 2, 2024
89dc11a
Get rid of any's
fbeaudoincoveo Mar 2, 2024
4c79c57
Migrate + flatten tests
fbeaudoincoveo Mar 2, 2024
fd36bd9
Add category facet test
fbeaudoincoveo Mar 2, 2024
a24854b
Migrate and flatten tests
fbeaudoincoveo Mar 2, 2024
f64d99c
Avoid duplicating helper code
fbeaudoincoveo Mar 2, 2024
421092a
Merge branch 'master' into CAPI-90-category-facets
fbeaudoincoveo Mar 2, 2024
2ade722
fix show more values condition
Spuffynism Mar 5, 2024
684e18e
assert for no call
Spuffynism Mar 5, 2024
9943bba
link jira to facet search todos
Spuffynism Mar 5, 2024
088d415
complete missing test
Spuffynism Mar 5, 2024
dfaecfa
switch to this jira
Spuffynism Mar 5, 2024
7961baf
Merge branch 'master' into CAPI-90-category-facets
Spuffynism Mar 6, 2024
5fa90da
Merge branch 'master' into CAPI-90-category-facets
Spuffynism Mar 13, 2024
6dd9a47
clean up controller
Spuffynism Mar 13, 2024
f63b6ee
use default number of desired values
Spuffynism Mar 13, 2024
4394c41
reflect that default value is used
Spuffynism Mar 13, 2024
8aa410a
Merge branch 'master' into CAPI-90-category-facets
Spuffynism Mar 13, 2024
f86806c
Merge branch 'master' into CAPI-90-category-facets
Spuffynism Mar 15, 2024
130be28
Merge branch 'master' into CAPI-90-category-facets
fbeaudoincoveo Mar 20, 2024
52678c6
Add delimiting character
fbeaudoincoveo Mar 21, 2024
2988003
Fix canShowLessValues
fbeaudoincoveo Mar 21, 2024
49adaf5
Fix tests
fbeaudoincoveo Mar 21, 2024
d902a93
Add missing tests
fbeaudoincoveo Mar 22, 2024
ffbe270
Merge branch 'master' into CAPI-90-category-facets
fbeaudoincoveo Mar 22, 2024
53038c9
Merge branch 'master' into CAPI-90-category-facets
fbeaudoincoveo Mar 22, 2024
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
4 changes: 2 additions & 2 deletions packages/headless/src/api/commerce/commerce-api-params.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {AnyCommerceFacetRequest} from '../../features/commerce/facets/facet-set/interfaces/request';
import {AnyFacetRequest} from '../../features/commerce/facets/facet-set/interfaces/request';
import {SortOption} from './common/sort';

export interface TrackingIdParam {
Expand Down Expand Up @@ -62,7 +62,7 @@ export interface CartItemParam {
}

export interface FacetsParam {
facets?: AnyCommerceFacetRequest[];
facets?: AnyFacetRequest[];
}

export interface PageParam {
Expand Down
3 changes: 3 additions & 0 deletions packages/headless/src/commerce.index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ export {
export {buildProductListingSort} from './controllers/commerce/product-listing/sort/headless-product-listing-sort';
export {buildSearchSort} from './controllers/commerce/search/sort/headless-search-sort';

export type {CategoryFacet} from './controllers/commerce/core/facets/category/headless-commerce-category-facet';
export type {RegularFacet} from './controllers/commerce/core/facets/regular/headless-commerce-regular-facet';
export type {NumericFacet} from './controllers/commerce/core/facets/numeric/headless-commerce-numeric-facet';
export type {DateFacet} from './controllers/commerce/core/facets/date/headless-commerce-date-facet';
Expand All @@ -108,6 +109,8 @@ export type {
NumericFacetValue,
DateRangeRequest,
DateFacetValue,
CategoryFacetValueRequest,
CategoryFacetValue,
} from './controllers/commerce/core/facets/headless-core-commerce-facet';
export type {ProductListingFacetGenerator} from './controllers/commerce/product-listing/facets/headless-product-listing-facet-generator';
export {buildProductListingFacetGenerator} from './controllers/commerce/product-listing/facets/headless-product-listing-facet-generator';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,315 @@
import {
CategoryFacetValueRequest,
CommerceFacetRequest,
} from '../../../../../features/commerce/facets/facet-set/interfaces/request';
import {CategoryFacetValue} from '../../../../../features/commerce/facets/facet-set/interfaces/response';
import {
toggleSelectCategoryFacetValue,
updateCategoryFacetNumberOfValues,
} from '../../../../../features/facets/category-facet-set/category-facet-set-actions';
import {CommerceAppState} from '../../../../../state/commerce-app-state';
import {buildMockCommerceFacetRequest} from '../../../../../test/mock-commerce-facet-request';
import {buildMockCategoryFacetResponse} from '../../../../../test/mock-commerce-facet-response';
import {buildMockCommerceFacetSlice} from '../../../../../test/mock-commerce-facet-slice';
import {buildMockCategoryFacetValue} from '../../../../../test/mock-commerce-facet-value';
import {buildMockCommerceState} from '../../../../../test/mock-commerce-state';
import {
MockedCommerceEngine,
buildMockCommerceEngine,
} from '../../../../../test/mock-engine-v2';
import {commonOptions} from '../../../product-listing/facets/headless-product-listing-facet-options';
import {
CategoryFacet,
CategoryFacetOptions,
buildCategoryFacet,
} from './headless-commerce-category-facet';

jest.mock(
'../../../../../features/facets/category-facet-set/category-facet-set-actions'
);

describe('CategoryFacet', () => {
const facetId: string = 'category_facet_id';
let engine: MockedCommerceEngine;
let state: CommerceAppState;
let options: CategoryFacetOptions;
let facet: CategoryFacet;

function initEngine(preloadedState = buildMockCommerceState()) {
engine = buildMockCommerceEngine(preloadedState);
}

function initCategoryFacet() {
facet = buildCategoryFacet(engine, options);
}

function setFacetState(
config: Partial<CommerceFacetRequest<CategoryFacetValueRequest>> = {},
moreValuesAvailable = false
) {
state.commerceFacetSet[facetId] = buildMockCommerceFacetSlice({
request: buildMockCommerceFacetRequest({
facetId,
type: 'hierarchical',
...config,
}),
});
state.productListing.facets = [
buildMockCategoryFacetResponse({
moreValuesAvailable,
facetId,
type: 'hierarchical',
values: (config.values as CategoryFacetValue[]) ?? [],
}),
];
}

// eslint-disable-next-line @cspell/spellchecker
// TODO CAPI-90: Test facet search
fbeaudoincoveo marked this conversation as resolved.
Show resolved Hide resolved
/*function setFacetSearch() {
state.facetSearchSet[facetId] = buildMockFacetSearch();
}*/

beforeEach(() => {
jest.resetAllMocks();

options = {
facetId,
...commonOptions,
};

state = buildMockCommerceState();
setFacetState();
// eslint-disable-next-line @cspell/spellchecker
// TODO CAPI-90: Test facet search
// setFacetSearch();

initEngine(state);
initCategoryFacet();
});

describe('initialization', () => {
it('initializes', () => {
expect(facet).toBeTruthy();
});

it('exposes #subscribe method', () => {
expect(facet.subscribe).toBeTruthy();
});
});

it('#toggleSelect dispatches #toggleSelectCategoryFacetValue with correct payload', () => {
const facetValue = buildMockCategoryFacetValue();
facet.toggleSelect(facetValue);

expect(toggleSelectCategoryFacetValue).toHaveBeenCalledWith({
facetId,
selection: facetValue,
});
});

it('#showLessValues dispatches #updateCategoryFacetNumberOfValues with correct payload', () => {
facet.showLessValues();

expect(updateCategoryFacetNumberOfValues).toHaveBeenCalledWith({
facetId,
numberOfValues: 5,
});
});

it('#showMoreValues dispatches #updateCategoryFacetNumberOfValues with correct payload', () => {
facet.showMoreValues();

expect(updateCategoryFacetNumberOfValues).toHaveBeenCalledWith({
facetId,
numberOfValues: 5,
});
});

describe('#state', () => {
describe('#activeValue', () => {
it('when no value is selected, returns undefined', () => {
expect(facet.state.activeValue).toBeUndefined();
});
it('when a value is selected, returns the selected value', () => {
const activeValue = buildMockCategoryFacetValue({
state: 'selected',
});
setFacetState({
values: [activeValue, buildMockCategoryFacetValue()],
});

expect(facet.state.activeValue).toBe(activeValue);
});
});

describe('#canShowLessValues', () => {
describe('when no value is selected', () => {
it('when there are no values, returns false', () => {
expect(facet.state.canShowLessValues).toBe(false);
});
it('when there are fewer values than default number of values, returns false', () => {
setFacetState({
values: [buildMockCategoryFacetValue()],
});

expect(facet.state.canShowLessValues).toBe(false);
});
it('when there are more values than default number of values, returns true', () => {
setFacetState({
values: [
buildMockCategoryFacetValue(),
buildMockCategoryFacetValue(),
buildMockCategoryFacetValue(),
buildMockCategoryFacetValue(),
buildMockCategoryFacetValue(),
buildMockCategoryFacetValue(),
],
});

expect(facet.state.canShowLessValues).toBe(false);
});
});

describe('when a value is selected', () => {
it('when selected value has no children, returns false', () => {
setFacetState({
values: [
buildMockCategoryFacetValue({
state: 'selected',
}),
],
});

expect(facet.state.canShowLessValues).toBe(false);
});
it('when selected value fewer children than default number of values, returns false', () => {
setFacetState({
values: [
buildMockCategoryFacetValue({
state: 'selected',
children: [buildMockCategoryFacetValue()],
}),
],
});

expect(facet.state.canShowLessValues).toBe(false);
});
it('when selected value has more children than default number of values, return true', () => {
setFacetState({
values: [
buildMockCategoryFacetValue({
state: 'selected',
children: [
buildMockCategoryFacetValue(),
buildMockCategoryFacetValue(),
buildMockCategoryFacetValue(),
buildMockCategoryFacetValue(),
buildMockCategoryFacetValue(),
buildMockCategoryFacetValue(),
],
}),
],
});

expect(facet.state.canShowLessValues).toBe(true);
});
});
});

describe('#canShowMoreValues', () => {
describe('when no value is selected', () => {
it('when there are no more values available, returns false', () => {
expect(facet.state.canShowMoreValues).toBe(false);
});

it('when there are more values available, returns true', () => {
setFacetState({}, true);

expect(facet.state.canShowMoreValues).toBe(true);
});
});

describe('when a value is selected', () => {
it('when selected values has no more values available, returns false', () => {
setFacetState({
values: [buildMockCategoryFacetValue({state: 'selected'})],
});

expect(facet.state.canShowMoreValues).toBe(false);
});
it('when selected value has more values available, returns true', () => {
setFacetState({
values: [
buildMockCategoryFacetValue({
state: 'selected',
moreValuesAvailable: true,
}),
],
});

expect(facet.state.canShowMoreValues).toBe(true);
});
});
});

describe('#hasActiveValues', () => {
it('when no value is selected, returns false', () => {
expect(facet.state.hasActiveValues).toBe(false);
});

it('when a value is selected, returns true', () => {
setFacetState({
values: [buildMockCategoryFacetValue({state: 'selected'})],
});

expect(facet.state.hasActiveValues).toBe(true);
});
});

describe('#selectedValueAncestry', () => {
it('when no value is selected, returns empty array', () => {
expect(facet.state.selectedValueAncestry).toEqual([]);
});

it('when a value is selected, returns the selected value ancestry', () => {
const activeValue = buildMockCategoryFacetValue({
value: 'c',
path: ['a', 'b', 'c'],
state: 'selected',
children: [
buildMockCategoryFacetValue({
value: 'd',
path: ['a', 'b', 'c', 'd'],
}),
buildMockCategoryFacetValue({
value: 'e',
path: ['a', 'b', 'c', 'e'],
}),
],
});
const parentValue = buildMockCategoryFacetValue({
value: 'b',
path: ['a', 'b'],
children: [activeValue],
});

const rootValue = buildMockCategoryFacetValue({
value: 'a',
path: ['a'],
children: [parentValue],
});

setFacetState({
values: [rootValue],
});

expect(facet.state.selectedValueAncestry).toEqual([
rootValue,
parentValue,
activeValue,
]);
});
});
});
});
Loading
Loading