Skip to content

Commit c9e50d9

Browse files
authored
fix(breadcrumb): use hierarchical facets only (#6645)
No interaction with the menu widget, only the hierarchical menu widget
1 parent 7aca759 commit c9e50d9

File tree

9 files changed

+208
-4
lines changed

9 files changed

+208
-4
lines changed

packages/instantsearch.js/src/__tests__/common-shared.test.tsx

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,13 @@ import * as suites from '@instantsearch/tests/shared';
66

77
import { connectMenu, connectPagination } from '../connectors';
88
import instantsearch from '../index.es';
9-
import { menu, pagination, hits } from '../widgets';
9+
import {
10+
menu,
11+
pagination,
12+
hits,
13+
hierarchicalMenu,
14+
breadcrumb,
15+
} from '../widgets';
1016

1117
import type { TestOptionsMap, TestSetupsMap } from '@instantsearch/tests';
1218

@@ -44,10 +50,18 @@ const testSetups: TestSetupsMap<TestSuites> = {
4450
container: document.body.appendChild(document.createElement('div')),
4551
...widgetParams.menu,
4652
}),
53+
hierarchicalMenu({
54+
container: document.body.appendChild(document.createElement('div')),
55+
...widgetParams.hierarchicalMenu,
56+
}),
4757
menu({
4858
container: document.body.appendChild(document.createElement('div')),
4959
...widgetParams.menu,
5060
}),
61+
breadcrumb({
62+
container: document.body.appendChild(document.createElement('div')),
63+
attributes: widgetParams.hierarchicalMenu.attributes,
64+
}),
5165
hits({
5266
container: document.body.appendChild(document.createElement('div')),
5367
...widgetParams.hits,

packages/instantsearch.js/src/connectors/breadcrumb/__tests__/connectBreadcrumb-test.ts

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,85 @@ See documentation: https://www.algolia.com/doc/api-reference/widgets/breadcrumb/
345345
});
346346
});
347347

348+
it('returns an empty array of items if only non-hierarchicalFacets result exist', () => {
349+
const renderFn = jest.fn();
350+
const unmountFn = jest.fn();
351+
const createBreadcrumb = connectBreadcrumb(renderFn, unmountFn);
352+
const breadcrumb = createBreadcrumb({
353+
attributes: ['category', 'subCategory'],
354+
});
355+
const helper = algoliasearchHelper(
356+
createSearchClient(),
357+
'indexName',
358+
breadcrumb.getWidgetSearchParameters!(
359+
new SearchParameters({
360+
hierarchicalFacets: [
361+
{
362+
attributes: ['country'],
363+
name: 'country',
364+
},
365+
],
366+
}),
367+
{
368+
uiState: {},
369+
}
370+
)
371+
);
372+
373+
helper.toggleFacetRefinement('country', 'country');
374+
375+
const renderState1 = breadcrumb.getWidgetRenderState(
376+
createInitOptions({ helper })
377+
);
378+
379+
expect(renderState1).toEqual({
380+
canRefine: false,
381+
createURL: expect.any(Function),
382+
items: [],
383+
refine: expect.any(Function),
384+
widgetParams: { attributes: ['category', 'subCategory'] },
385+
});
386+
387+
helper.toggleFacetRefinement('category', 'Decoration');
388+
389+
const renderState2 = breadcrumb.getWidgetRenderState(
390+
createRenderOptions({
391+
helper,
392+
state: helper.state,
393+
results: new SearchResults(helper.state, [
394+
createSingleSearchResponse({
395+
hits: [],
396+
facets: {
397+
category: {
398+
Decoration: 880,
399+
},
400+
subCategory: {
401+
'Decoration > Candle holders & candles': 193,
402+
'Decoration > Frames & pictures': 173,
403+
},
404+
},
405+
}),
406+
createSingleSearchResponse({
407+
facets: {
408+
category: {
409+
Decoration: 880,
410+
Outdoor: 47,
411+
},
412+
},
413+
}),
414+
]),
415+
})
416+
);
417+
418+
expect(renderState2).toEqual({
419+
canRefine: true,
420+
createURL: expect.any(Function),
421+
items: [{ label: 'Decoration', value: null }],
422+
refine: expect.any(Function),
423+
widgetParams: { attributes: ['category', 'subCategory'] },
424+
});
425+
});
426+
348427
test('refine method called with null does not mutate the current helper state if no hierarchicalFacets exist', () => {
349428
const renderFn = jest.fn();
350429
const unmountFn = jest.fn();

packages/instantsearch.js/src/connectors/breadcrumb/connectBreadcrumb.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -194,9 +194,7 @@ const connectBreadcrumb: BreadcrumbConnector = function connectBreadcrumb(
194194
return [];
195195
}
196196

197-
const [{ name: facetName }] = state.hierarchicalFacets;
198-
199-
const facetValues = results.getFacetValues(facetName, {});
197+
const facetValues = results.getFacetValues(hierarchicalFacetName, {});
200198
const facetItems =
201199
facetValues && !Array.isArray(facetValues) && facetValues.data
202200
? facetValues.data

packages/react-instantsearch/src/__tests__/common-shared.test.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import {
1313
Hits,
1414
usePagination,
1515
useMenu,
16+
HierarchicalMenu,
17+
Breadcrumb,
1618
} from '..';
1719

1820
import type { UseMenuProps, UsePaginationProps } from '..';
@@ -44,7 +46,9 @@ const testSetups: TestSetupsMap<TestSuites> = {
4446
render(
4547
<InstantSearch {...instantSearchOptions}>
4648
<MenuURL {...widgetParams.menu} />
49+
<HierarchicalMenu {...widgetParams.hierarchicalMenu} />
4750
<Menu {...widgetParams.menu} />
51+
<Breadcrumb attributes={widgetParams.hierarchicalMenu.attributes} />
4852
<Hits {...widgetParams.hits} />
4953
<PaginationURL {...widgetParams.pagination} />
5054
<Pagination {...widgetParams.pagination} />

packages/vue-instantsearch/src/__tests__/common-shared.test.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import { connectMenu, connectPagination } from 'instantsearch.js/es/connectors';
77

88
import { nextTick, mountApp } from '../../test/utils';
99
import {
10+
AisBreadcrumb,
11+
AisHierarchicalMenu,
1012
AisHits,
1113
AisInstantSearch,
1214
AisMenu,
@@ -35,7 +37,11 @@ const testSetups = {
3537
render: renderCompat((h) =>
3638
h(AisInstantSearch, { props: instantSearchOptions }, [
3739
h(CustomMenu, { props: widgetParams.menu }),
40+
h(AisHierarchicalMenu, { props: widgetParams.hierarchicalMenu }),
3841
h(AisMenu, { props: widgetParams.menu }),
42+
h(AisBreadcrumb, {
43+
props: { attributes: widgetParams.hierarchicalMenu.attributes },
44+
}),
3945
h(AisHits, { props: widgetParams.hits }),
4046
h(CustomPagination, { props: widgetParams.pagination }),
4147
h(AisPagination, { props: widgetParams.pagination }),

tests/common/shared/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import { fakeAct } from '../common';
22

33
import { createInsightsTests } from './insights';
4+
import { createInteractionTests } from './interaction';
45
import { createRoutingTests } from './routing';
56

67
import type { TestOptions, TestSetup } from '../common';
8+
import type { HierarchicalMenuWidget } from 'instantsearch.js/es/widgets/hierarchical-menu/hierarchical-menu';
79
import type { HitsWidget } from 'instantsearch.js/es/widgets/hits/hits';
810
import type { MenuWidget } from 'instantsearch.js/es/widgets/menu/menu';
911
import type { PaginationWidget } from 'instantsearch.js/es/widgets/pagination/pagination';
@@ -13,6 +15,7 @@ export type SharedSetup = TestSetup<{
1315
menu: Omit<Parameters<MenuWidget>[0], 'container'>;
1416
hits: Omit<Parameters<HitsWidget>[0], 'container'>;
1517
pagination: Omit<Parameters<PaginationWidget>[0], 'container'>;
18+
hierarchicalMenu: Omit<Parameters<HierarchicalMenuWidget>[0], 'container'>;
1619
};
1720
}>;
1821

@@ -27,5 +30,6 @@ export function createSharedTests(
2730
describe('Shared common tests', () => {
2831
createRoutingTests(setup, { act, skippedTests });
2932
createInsightsTests(setup, { act, skippedTests });
33+
createInteractionTests(setup, { act, skippedTests });
3034
});
3135
}

tests/common/shared/insights.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ export function createInsightsTests(
2525
const delay = 100;
2626
const margin = 10;
2727
const attribute = 'one';
28+
const hierarchicalAttribute = 'hierarchicalCategories.lvl0';
29+
2830
window.aa = Object.assign(jest.fn(), { version: '2.17.2' });
2931

3032
const options = {
@@ -62,6 +64,7 @@ export function createInsightsTests(
6264
},
6365
hits: {},
6466
pagination: {},
67+
hierarchicalMenu: { attributes: [hierarchicalAttribute] },
6568
},
6669
};
6770

@@ -109,6 +112,8 @@ export function createInsightsTests(
109112
const delay = 100;
110113
const margin = 10;
111114
const attribute = 'one';
115+
const hierarchicalAttribute = 'hierarchicalCategories.lvl0';
116+
112117
window.aa = Object.assign(jest.fn(), { version: '2.17.2' });
113118

114119
const options = {
@@ -145,6 +150,7 @@ export function createInsightsTests(
145150
},
146151
hits: {},
147152
pagination: {},
153+
hierarchicalMenu: { attributes: [hierarchicalAttribute] },
148154
},
149155
};
150156

tests/common/shared/interaction.ts

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import {
2+
createSearchClient,
3+
createMultiSearchResponse,
4+
createSingleSearchResponse,
5+
} from '@instantsearch/mocks';
6+
import { wait } from '@instantsearch/testutils';
7+
import { screen } from '@testing-library/dom';
8+
import userEvent from '@testing-library/user-event';
9+
10+
import type { SharedSetup } from '.';
11+
import type { TestOptions } from '../../common';
12+
import type { MockSearchClient } from '@instantsearch/mocks';
13+
import type { SearchClient } from 'instantsearch.js';
14+
15+
export function createInteractionTests(
16+
setup: SharedSetup,
17+
{ act }: Required<TestOptions>
18+
) {
19+
describe('interaction', () => {
20+
describe('hierarchical menu and breadcrumb', () => {
21+
test('breadcrumb generation', async () => {
22+
const delay = 100;
23+
const margin = 10;
24+
const attribute = 'one';
25+
const hierarchicalAttribute = 'hierarchicalCategories.lvl0';
26+
27+
const options = {
28+
instantSearchOptions: {
29+
indexName: 'indexName',
30+
searchClient: createSearchClient({
31+
search: jest.fn(async (requests) => {
32+
await wait(delay);
33+
return createMultiSearchResponse(
34+
...requests.map(
35+
({
36+
params,
37+
}: Parameters<SearchClient['search']>[0][number]) =>
38+
createSingleSearchResponse({
39+
facets: {
40+
[attribute]: {
41+
Samsung: 100,
42+
Apple: 200,
43+
},
44+
[hierarchicalAttribute]: {
45+
'Computers & Tablets': 148,
46+
},
47+
},
48+
page: params.page,
49+
nbPages: 20,
50+
})
51+
)
52+
);
53+
}) as MockSearchClient['search'],
54+
}),
55+
},
56+
widgetParams: {
57+
menu: {
58+
attribute,
59+
},
60+
hits: {},
61+
pagination: {},
62+
hierarchicalMenu: { attributes: [hierarchicalAttribute] },
63+
},
64+
};
65+
66+
await setup(options);
67+
68+
// Wait for initial results to populate widgets with data
69+
await act(async () => {
70+
await wait(margin + delay);
71+
await wait(0);
72+
});
73+
74+
const hierarchicalLink = screen.getByRole('link', {
75+
name: 'Computers & Tablets 148',
76+
});
77+
userEvent.click(hierarchicalLink);
78+
79+
await act(async () => {
80+
await wait(0);
81+
});
82+
83+
const breadcrumbs = document.querySelectorAll(
84+
'.ais-Breadcrumb-item.ais-Breadcrumb-item--selected'
85+
);
86+
expect(breadcrumbs).toHaveLength(1);
87+
expect(breadcrumbs[0]).toHaveTextContent('Computers & Tablets');
88+
});
89+
});
90+
});
91+
}

tests/common/shared/routing.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ export function createRoutingTests(
2828
const delay = 100;
2929
const margin = 10;
3030
const attribute = 'one';
31+
const hierarchicalAttribute = 'hierarchicalCategories.lvl0';
3132

3233
const router = history({
3334
writeDelay: 0,
@@ -65,6 +66,7 @@ export function createRoutingTests(
6566
},
6667
hits: {},
6768
pagination: {},
69+
hierarchicalMenu: { attributes: [hierarchicalAttribute] },
6870
},
6971
};
7072

0 commit comments

Comments
 (0)