diff --git a/packages/mock-converse-api/src/schema-response-search.test.ts b/packages/mock-converse-api/src/schema-response-search.test.ts index 2ee174eac44..cd6d8619f29 100644 --- a/packages/mock-converse-api/src/schema-response-search.test.ts +++ b/packages/mock-converse-api/src/schema-response-search.test.ts @@ -671,3 +671,67 @@ describe('schema-response-search state per surface', () => { expect(new Set(brands(second)).size).toBeGreaterThan(1); }); }); + +describe('schema-response-search restoreState', () => { + beforeEach(() => { + resetSurface(); + }); + + function restore(context: Record) { + return act('restoreState', {query: 'surf', page: 0, pageSize: 12, ...context}, 'root'); + } + + it('restores the query, page, page size and sort', () => { + const components = restore({page: 2, pageSize: 6, sortCriteria: 'price_asc'}); + expect((components['query-summary-2'] as Record).query).toBe('surf'); + expect(components['pagination-2']).toMatchObject({page: 2, pageSize: 6}); + expect((components['sort-2'] as Record).appliedSort).toMatchObject({ + sortCriteria: 'price_asc', + }); + }); + + it('restores the selected values of a regular facet', () => { + const components = restore({facets: [{facetId: 'ec_brand', values: ['Billabong']}]}); + expect(brandState(components, 'Billabong')).toBe('selected'); + const products = (components['product-list-2'] as Record).products as Array< + Record + >; + expect(new Set(products.map((product) => product.ec_brand))).toEqual(new Set(['Billabong'])); + }); + + it('restores the selected range of a numeric facet', () => { + const components = restore({ + facets: [{facetId: 'ec_price', numericRanges: [{start: 0, end: 100, endInclusive: true}]}], + }); + const values = (components['facet-price-2'] as Record).values as Array< + Record + >; + expect(values.find((value) => value.start === 0 && value.end === 100)?.state).toBe('selected'); + }); + + it('restores the selected path of a category facet', () => { + const components = restore({ + facets: [{facetId: 'ec_category', path: ['Surfing', 'Surfboards']}], + }); + const values = (components['facet-category-2'] as Record).values as Record< + string, + Record + >; + expect(values.selected.path).toEqual(['Surfing', 'Surfboards']); + expect(breadboxFacets(components)).toMatchObject([ + {field: 'ec_category', path: ['Surfing', 'Surfboards']}, + ]); + }); + + it('replaces the current search instead of merging into it', () => { + act('toggleSelect', {value: 'Hurley'}, 'facet-brand-2'); + act('toggleExclude', {value: 'Xcel'}, 'facet-brand-2'); + act('toggleSelect', {start: 0, end: 100}, 'facet-price-2'); + act('selectSort', {sortCriteria: 'price_desc'}); + const components = restore({}); + expect(breadboxFacets(components)).toEqual([]); + expect((components['sort-2'] as Record).appliedSort).toMatchObject({ + sortCriteria: 'relevance', + }); + }); +}); diff --git a/packages/platform-mock-api/src/converse/templates/schema-response-search.ts b/packages/platform-mock-api/src/converse/templates/schema-response-search.ts index eef1cfe7c61..68c3731e0d9 100644 --- a/packages/platform-mock-api/src/converse/templates/schema-response-search.ts +++ b/packages/platform-mock-api/src/converse/templates/schema-response-search.ts @@ -697,6 +697,7 @@ interface PriceRange { } interface SearchViewState { + query: string; page: number; pageSize: number; sortCriteria: string; @@ -718,6 +719,7 @@ interface SearchViewState { } const DEFAULT_VIEW: SearchViewState = { + query: 'Water Sports', page: 0, pageSize: DEFAULT_PAGE_SIZE, sortCriteria: 'relevance', @@ -1097,7 +1099,7 @@ function computeComponentsState(view: SearchViewState): Record // The query-summary owns its own backend-computed aggregate (query + result window) so it does // not read the search-box, pagination, and product-list state. Indices are 1-based; on an empty // result set the window collapses to 0-0 and the renderer shows the no-results/empty state. - const query = 'Water Sports'; + const {query} = view; const firstIndex = totalEntries === 0 ? 0 : start + 1; const lastIndex = totalEntries === 0 ? 0 : start + pageProducts.length; @@ -1636,8 +1638,36 @@ function deriveBreadboxViewState(action: {name: string; context: Record): SearchViewState { + const facets = (Array.isArray(context.facets) ? context.facets : []) as FacetRestore[]; + const restored = (field: string) => facets.find((facet) => facet.facetId === field); + // The mock's price facet holds a single selected range, so later ranges are dropped. + const range = restored(FACET_FIELDS.numeric.field)?.numericRanges?.[0]; + return { + ...DEFAULT_VIEW, + query: String(context.query ?? DEFAULT_VIEW.query), + page: Number(context.page) || 0, + pageSize: Number(context.pageSize) || DEFAULT_VIEW.pageSize, + sortCriteria: String(context.sortCriteria ?? DEFAULT_VIEW.sortCriteria), + selectedBrands: (restored(FACET_FIELDS.regular.field)?.values ?? []).map(String), + selectedPriceRange: range ? {start: Number(range.start), end: Number(range.end)} : null, + selectedCategoryPath: (restored(FACET_FIELDS.category.field)?.path ?? []).map(String), + }; +} + function deriveNonFacetState(action: {name: string; context: Record}): void { switch (action.name) { + case 'restoreState': + currentView = restoreView(action.context); + break; case 'selectPage': currentView = {...currentView, page: Number(action.context.page) || 0}; break;