diff --git a/CHANGELOG.md b/CHANGELOG.md index e841e4d8a..8e6c98ef8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -104,6 +104,7 @@ * Provide an instance `tenantId` to the PO line form when creating an order from the instance. Refs UIIN-2614. * Bump @folio/stripes-acq-components dependency version to 5.0.0. Refs UIIN-2620. * ECS: Check when sharing instance with source=MARC is complete before re-fetching it. Refs UIIN-2605. +* Update all facets after changing a term or selecting a facet option. Refs UIIN-2610. ## [9.4.12](https://github.com/folio-org/ui-inventory/tree/v9.4.12) (2023-09-21) [Full Changelog](https://github.com/folio-org/ui-inventory/compare/v9.4.11...v9.4.12) diff --git a/src/common/hooks/useFacets.js b/src/common/hooks/useFacets.js index ae4d17ce8..a8ef5e659 100644 --- a/src/common/hooks/useFacets.js +++ b/src/common/hooks/useFacets.js @@ -4,15 +4,27 @@ import _ from 'lodash'; import { useFacetSettings } from '../../stores/facetsStore'; +// Facets behavior (useFacets and withFacets): +// - when the user opens a facet, the first 6 options must be fetched for it; +// - when the user clicks the "+More" button under the options, all options for that facet must be fetched; +// - when the user places the cursor in a facet's input field, all options for it must be fetched; +// - when multiple facets are open and the user enters a value in the search box, options must be fetched for all open facets. +// - when multiple facets are open and the user selects an option of any facet, options must be fetched for all open facets. + const useFacets = ( segmentAccordions, segmentOptions, selectedFacetFilters, getNewRecords, data, + isFetchFacetsAfterReset = true, ) => { const { - query: { query, filters = '' }, + query: { + query, + qindex, + filters = '', + }, onFetchFacets, parentResources: { facets }, } = data; @@ -30,7 +42,9 @@ const useFacets = ( const prevAccordionsState = useRef(accordions); const prevFilters = useRef({}); const prevUrl = useRef({}); - const prevQuery = useRef(''); + const prevQindex = useRef(''); + const isSearchOptionChanged = useRef(false); + const isReset = useRef(false); const onToggleSection = useCallback(({ id }) => { setAccordions(curState => { @@ -155,6 +169,12 @@ const useFacets = ( showLoadingForAllFacets ]); + useEffect(() => { + isSearchOptionChanged.current = !query && !filters && qindex && prevQindex.current && qindex !== prevQindex.current; + isReset.current = !query && !filters && !qindex; + prevQindex.current = qindex; + }, [qindex, filters, query]); + useEffect(() => { if (!_.isEmpty(records)) { const newRecords = getNewRecords(records); @@ -192,10 +212,16 @@ const useFacets = ( useEffect(() => { if (!_.isEmpty(accordionsData)) { - const isNoFilterSelected = _.every(accordionsData, value => !value?.isSelected); - if (!query && prevQuery.current && isNoFilterSelected) return; + // When there is a value in the search box and any facet option is selected and the user resets the search, + // and `isFetchFacetsAfterReset` is true, then two useEffects are called, one is tracking `query` and another is + // tracking `accordionsData`, hence 2 calls are fired, so let's check url to make only 1 call. + const areOpenFacetsAlreadyFetched = prevUrl.current.all === location.search; - handleFetchFacets({ focusedFacet: facetNameToOpen }); + if (isSearchOptionChanged.current || (!isFetchFacetsAfterReset && isReset.current) || areOpenFacetsAlreadyFetched) { + return; + } + handleFetchFacets(); + prevUrl.current.all = location.search; } }, [accordionsData]); @@ -207,15 +233,14 @@ const useFacets = ( useEffect(() => { const isSomeFacetOpened = _.some(accordions, isFacetOpened => isFacetOpened); - const isValidQuery = (query && query !== prevQuery.current) || (query !== undefined && prevQuery.current); + + if (isSearchOptionChanged.current || (!isFetchFacetsAfterReset && isReset.current)) { + return; + } if (isSomeFacetOpened) { - if (isValidQuery) { - prevQuery.current = query; - handleFetchFacets({ facetToOpen: facetNameToOpen }); - } - } else if (isValidQuery) { - prevQuery.current = query; + handleFetchFacets(); + prevUrl.current.all = location.search; } }, [query]); diff --git a/src/common/hooks/useFacets.test.js b/src/common/hooks/useFacets.test.js index 66f8bb28d..17d01ba0b 100644 --- a/src/common/hooks/useFacets.test.js +++ b/src/common/hooks/useFacets.test.js @@ -43,6 +43,378 @@ describe('useFacets', () => { useFacetSettings.mockReturnValue([selectedFacetFilters, jest.fn()]); }); + describe('onFetchFacets', () => { + const _segmentAccordions = { + resource: false, + format: false, + }; + + const _segmentOptions = { + resourceTypeOptions: [], + instanceFormatOptions: [], + }; + + const _selectedFacetFilters = { + resource: undefined, + format: undefined, + }; + + const _getNewRecords = jest.fn(); + + const _data = { + instanceFormats: [ + { + 'id': '8d511d33-5e85-4c5d-9bce-6e3c9cd0c324', + 'name': 'unmediated -- volume', + 'code': 'nc', + 'source': 'rdacarrier', + }, + { + 'id': 'f5e8210f-7640-459b-a71f-552567f92369', + 'name': 'computer -- online resource', + 'code': 'cr', + 'source': 'rdacarrier', + }, { + 'id': '5cb91d15-96b1-4b8a-bf60-ec310538da66', + 'name': 'audio -- audio disc', + 'code': 'sd', + 'source': 'rdacarrier', + } + ], + resourceTypes: [ + { + 'id': '6312d172-f0cf-40f6-b27d-9fa8feaf332f', + 'name': 'text', + 'code': 'txt', + 'source': 'rdacontent', + }, + { + 'id': '30fffe0e-e985-4144-b2e2-1e8179bdb41f', + 'name': 'unspecified', + 'code': 'zzz', + 'source': 'rdacontent', + }, + { + 'id': '535e3160-763a-42f9-b0c0-d8ed7df6e2a2', + 'name': 'still image', + 'code': 'sti', + 'source': 'rdacontent', + }, + ], + query: {}, + parentResources: { + facets: { records: [] }, + }, + onFetchFacets: jest.fn(), + }; + + describe('when user opens a facet', () => { + it('should fetch only 6 options for this facet', () => { + useLocation.mockReturnValue({ search: '?segment=instances&sort=title' }); + + const { result } = renderHook(() => useFacets( + _segmentAccordions, + _segmentOptions, + _selectedFacetFilters, + _getNewRecords, + _data, + )); + + const onToggleSection = result.current[1]; + + act(() => { onToggleSection({ id: 'resource' }); }); + + expect(_data.onFetchFacets).toHaveBeenCalledWith({ facetToOpen: 'resource' }); + }); + }); + + describe('when user clicks the "+More" button under options', () => { + it('should fetch all options for this facet', () => { + const mockSetFacetSettings = jest.fn(); + useFacetSettings.mockReturnValue([{}, mockSetFacetSettings]); + + const { result } = renderHook(() => useFacets( + _segmentAccordions, + _segmentOptions, + _selectedFacetFilters, + _getNewRecords, + _data, + )); + + const handleFetchFacets = result.current[2]; + act(() => { handleFetchFacets({ onMoreClickedFacet: 'resource' }); }); + + expect(mockSetFacetSettings).toHaveBeenCalledWith('resource', { isOnMoreClicked: true }); + expect(_data.onFetchFacets).toHaveBeenCalledWith({ onMoreClickedFacet: 'resource' }); + }); + }); + + describe('when user places the cursor in the facet`s input field', () => { + it('should fetch all options for this facet', () => { + const { result } = renderHook(() => useFacets( + _segmentAccordions, + _segmentOptions, + _selectedFacetFilters, + _getNewRecords, + _data, + )); + + const handleFetchFacets = result.current[2]; + + act(() => { handleFetchFacets({ focusedFacet: 'resource' }); }); + + expect(_data.onFetchFacets).toHaveBeenCalledWith({ focusedFacet: 'resource' }); + }); + }); + + describe('when several facets are open and user enters a value in the search box', () => { + it('should fetch options for all open facets', () => { + useFacetSettings.mockReturnValue([{}, jest.fn()]); + useLocation.mockReturnValue({ search: '' }); + + const { result, rerender } = renderHook(({ newData }) => useFacets( + _segmentAccordions, + _segmentOptions, + _selectedFacetFilters, + _getNewRecords, + newData, + ), { + initialProps: { newData: _data }, + }); + + const onToggleSection = result.current[1]; + + act(() => { onToggleSection({ id: 'resource' }); }); + act(() => { onToggleSection({ id: 'format' }); }); + + _data.onFetchFacets.mockClear(); + + useLocation.mockReturnValue({ search: '?query=Mark' }); + + rerender({ + newData: { + ..._data, + query: { ..._data.query, query: 'Mark' }, + }, + }); + + expect(_data.onFetchFacets).toHaveBeenCalledWith({ + accordions: { resource: true, format: true }, + accordionsData: {}, + }); + }); + }); + + describe('when several facets are open and user selects an option of any facet', () => { + it('should fetch options for all open facets', () => { + useFacetSettings.mockReturnValue([{}, jest.fn()]); + useLocation.mockReturnValue({ search: '' }); + const isFetchFacetsAfterReset = false; + + const { result, rerender } = renderHook(({ newData, newSelectedFacetFilters }) => useFacets( + _segmentAccordions, + _segmentOptions, + newSelectedFacetFilters, + _getNewRecords, + newData, + isFetchFacetsAfterReset, + ), { + initialProps: { + newData: _data, + newSelectedFacetFilters: _selectedFacetFilters, + }, + }); + + const onToggleSection = result.current[1]; + + act(() => { onToggleSection({ id: 'resource' }); }); + act(() => { onToggleSection({ id: 'format' }); }); + + _data.onFetchFacets.mockClear(); + + useLocation.mockReturnValue({ search: '?callNumbersTenantId=university&qindex=callNumbers' }); + + rerender({ + newData: { + ..._data, + query: { ..._data.query, filters: 'resource.6312d172-f0cf-40f6-b27d-9fa8feaf332f' }, + }, + newSelectedFacetFilters: { ..._selectedFacetFilters, resource: ['6312d172-f0cf-40f6-b27d-9fa8feaf332f'] }, + }); + + expect(_data.onFetchFacets).toHaveBeenCalledWith({ + accordions: { resource: true, format: true }, + accordionsData: { + resource: { isSelected: true }, + }, + }); + }); + }); + + describe('when there is a value in the search box and any facet option is selected', () => { + describe('and the user selects another search option', () => { + it('should not call onFetchFacets', () => { + useFacetSettings.mockReturnValue([{}, jest.fn()]); + useLocation.mockReturnValue({ search: '?callNumbersTenantId=university&qindex=callNumbers&query=Mark' }); + const isFetchFacetsAfterReset = false; + + const { rerender } = renderHook(({ newData, newSelectedFacetFilters }) => useFacets( + _segmentAccordions, + _segmentOptions, + newSelectedFacetFilters, + _getNewRecords, + newData, + isFetchFacetsAfterReset, + ), { + initialProps: { + newData: { + ..._data, + query: { + ..._data.query, + query: 'Mark', + qindex: 'callNumbers', + filters: 'resource.6312d172-f0cf-40f6-b27d-9fa8feaf332f', + }, + }, + newSelectedFacetFilters: { ..._selectedFacetFilters, resource: ['6312d172-f0cf-40f6-b27d-9fa8feaf332f'] }, + }, + }); + + _data.onFetchFacets.mockClear(); + + useLocation.mockReturnValue(''); + + rerender({ + newData: { + ..._data, + query: { + ..._data.query, + query: undefined, + qindex: 'local', + filters: undefined, + }, + }, + newSelectedFacetFilters: _selectedFacetFilters, + }); + + expect(_data.onFetchFacets).not.toHaveBeenCalled(); + }); + }); + }); + + describe('when the "Search" lookup and there is a value in the search box and facet option is selected', () => { + describe('and the user reset the search', () => { + it('should call onFetchFacets for all open facets once', () => { + useFacetSettings.mockReturnValue([{}, jest.fn()]); + useLocation.mockReturnValue({ search: '?segment=instances&sort=title&query=Mark' }); + + const { result, rerender } = renderHook(({ newData, newSelectedFacetFilters }) => useFacets( + _segmentAccordions, + _segmentOptions, + newSelectedFacetFilters, + _getNewRecords, + newData, + ), { + initialProps: { + newData: { + ..._data, + query: { + ..._data.query, + query: 'Mark', + qindex: '', + filters: 'resource.6312d172-f0cf-40f6-b27d-9fa8feaf332f', + }, + }, + newSelectedFacetFilters: { ..._selectedFacetFilters, resource: ['6312d172-f0cf-40f6-b27d-9fa8feaf332f'] }, + }, + }); + + const onToggleSection = result.current[1]; + + act(() => { onToggleSection({ id: 'resource' }); }); + + _data.onFetchFacets.mockClear(); + + useLocation.mockReturnValue({ search: '' }); + + rerender({ + newData: { + ..._data, + query: { + ..._data.query, + query: undefined, + qindex: undefined, + filters: undefined, + }, + }, + newSelectedFacetFilters: _selectedFacetFilters, + }); + + expect(_data.onFetchFacets).toHaveBeenCalledTimes(1); + expect(_data.onFetchFacets).toHaveBeenCalledWith({ + accordions: { format: false, resource: true }, + accordionsData: expect.anything(), + }); + }); + }); + }); + + describe('when the "Browse" lookup and there is a value in the search box and facet option is selected', () => { + describe('and the user reset the search', () => { + it('should not call onFetchFacets', () => { + useFacetSettings.mockReturnValue([{}, jest.fn()]); + useLocation.mockReturnValue({ search: '?query=Mark' }); + const isFetchFacetsAfterReset = false; + + const { result, rerender } = renderHook(({ newData, newSelectedFacetFilters }) => useFacets( + _segmentAccordions, + _segmentOptions, + newSelectedFacetFilters, + _getNewRecords, + newData, + isFetchFacetsAfterReset, + ), { + initialProps: { + newData: { + ..._data, + query: { + ..._data.query, + query: 'Mark', + qindex: '', + filters: 'resource.6312d172-f0cf-40f6-b27d-9fa8feaf332f', + }, + }, + newSelectedFacetFilters: { ..._selectedFacetFilters, resource: ['6312d172-f0cf-40f6-b27d-9fa8feaf332f'] }, + }, + }); + + const onToggleSection = result.current[1]; + + act(() => { onToggleSection({ id: 'resource' }); }); + + _data.onFetchFacets.mockClear(); + + useLocation.mockReturnValue({ search: '' }); + + rerender({ + newData: { + ..._data, + query: { + ..._data.query, + query: undefined, + qindex: undefined, + filters: undefined, + }, + }, + newSelectedFacetFilters: _selectedFacetFilters, + }); + + expect(_data.onFetchFacets).not.toHaveBeenCalled(); + }); + }); + }); + }); + it('returns initial state', () => { const { result } = renderHook(() => useFacets( segmentAccordions, diff --git a/src/components/BrowseInventoryFilters/BrowseInventoryFilters.js b/src/components/BrowseInventoryFilters/BrowseInventoryFilters.js index 55ad1ad28..d0e40898c 100644 --- a/src/components/BrowseInventoryFilters/BrowseInventoryFilters.js +++ b/src/components/BrowseInventoryFilters/BrowseInventoryFilters.js @@ -19,14 +19,16 @@ const BrowseInventoryFilters = ({ const data = useContext(DataContext); const filters = omit(activeFilters || {}, ['qindex', 'query']); + const isBrowseLookup = true; const filtersData = { ...data, browseType: searchIndex, - onFetchFacets: fetchFacets(data), + onFetchFacets: fetchFacets(data, isBrowseLookup), parentResources: resources, query: { query: activeFilters.query, filters: parseFiltersToStr(filters), + qindex: searchIndex, } }; diff --git a/src/components/CheckboxFacet/CheckboxFacet.js b/src/components/CheckboxFacet/CheckboxFacet.js index 818a55f9a..aa169ecfc 100644 --- a/src/components/CheckboxFacet/CheckboxFacet.js +++ b/src/components/CheckboxFacet/CheckboxFacet.js @@ -49,10 +49,6 @@ export default class CheckboxFacet extends React.Component { ) { this.updateMore(); } - - if (prevDataLength > currentDataLength && currentDataLength === DEFAULT_FILTERS_NUMBER) { - this.setDefaultMore(); - } } onMoreClick = (totalOptions) => { @@ -105,10 +101,6 @@ export default class CheckboxFacet extends React.Component { }); } - setDefaultMore = () => { - this.setState(({ more: SHOW_OPTIONS_COUNT })); - } - render() { const { dataOptions, diff --git a/src/components/InstanceFilters/InstanceFiltersBrowse/InstanceFiltersBrowse.js b/src/components/InstanceFilters/InstanceFiltersBrowse/InstanceFiltersBrowse.js index b2e0bd573..1fa52e953 100644 --- a/src/components/InstanceFilters/InstanceFiltersBrowse/InstanceFiltersBrowse.js +++ b/src/components/InstanceFilters/InstanceFiltersBrowse/InstanceFiltersBrowse.js @@ -119,7 +119,8 @@ const InstanceFiltersBrowse = props => { segmentOptions, selectedFacetFilters, getNewRecords, - props.data + props.data, + false, ); return ( diff --git a/src/constants.js b/src/constants.js index 4b963ee7b..118aaf841 100644 --- a/src/constants.js +++ b/src/constants.js @@ -372,50 +372,6 @@ export const FACETS_TO_REQUEST = { [FACETS.HOLDINGS_TYPE]: FACETS_CQL.HOLDINGS_TYPE, }; -const INSTANCES_FACET_ENDPOINT = 'search/instances/facets'; -const CONTRIBUTORS_FACET_ENDPOINT = 'search/contributors/facets'; -const SUBJECTS_FACET_ENDPOINT = 'search/subjects/facets'; - -export const FACETS_ENDPOINTS = { - [FACETS.SHARED]: INSTANCES_FACET_ENDPOINT, - [FACETS.CONTRIBUTORS_SHARED]: CONTRIBUTORS_FACET_ENDPOINT, - [FACETS.SUBJECTS_SHARED]: SUBJECTS_FACET_ENDPOINT, - [FACETS.HELD_BY]: INSTANCES_FACET_ENDPOINT, - [FACETS.CALL_NUMBERS_HELD_BY]: INSTANCES_FACET_ENDPOINT, - [FACETS.CONTRIBUTORS_HELD_BY]: CONTRIBUTORS_FACET_ENDPOINT, - [FACETS.SUBJECTS_HELD_BY]: SUBJECTS_FACET_ENDPOINT, - [FACETS.EFFECTIVE_LOCATION]: INSTANCES_FACET_ENDPOINT, - [FACETS.LANGUAGE]: INSTANCES_FACET_ENDPOINT, - [FACETS.RESOURCE]: INSTANCES_FACET_ENDPOINT, - [FACETS.FORMAT]: INSTANCES_FACET_ENDPOINT, - [FACETS.MODE]: INSTANCES_FACET_ENDPOINT, - [FACETS.NATURE_OF_CONTENT]: INSTANCES_FACET_ENDPOINT, - [FACETS.STAFF_SUPPRESS]: INSTANCES_FACET_ENDPOINT, - [FACETS.INSTANCES_DISCOVERY_SUPPRESS]: INSTANCES_FACET_ENDPOINT, - [FACETS.HOLDINGS_DISCOVERY_SUPPRESS]: INSTANCES_FACET_ENDPOINT, - [FACETS.ITEMS_DISCOVERY_SUPPRESS]: INSTANCES_FACET_ENDPOINT, - [FACETS.SOURCE]: INSTANCES_FACET_ENDPOINT, - [FACETS.STATUS]: INSTANCES_FACET_ENDPOINT, - [FACETS.INSTANCES_TAGS]: INSTANCES_FACET_ENDPOINT, - [FACETS.ITEMS_TAGS]: INSTANCES_FACET_ENDPOINT, - [FACETS.HOLDINGS_TAGS]: INSTANCES_FACET_ENDPOINT, - [FACETS.MATERIAL_TYPE]: INSTANCES_FACET_ENDPOINT, - [FACETS.ITEM_STATUS]: INSTANCES_FACET_ENDPOINT, - [FACETS.HOLDINGS_PERMANENT_LOCATION]: INSTANCES_FACET_ENDPOINT, - [FACETS.CREATED_DATE]: INSTANCES_FACET_ENDPOINT, - [FACETS.UPDATED_DATE]: INSTANCES_FACET_ENDPOINT, - [FACETS.HOLDINGS_CREATED_DATE]: INSTANCES_FACET_ENDPOINT, - [FACETS.HOLDINGS_UPDATED_DATE]: INSTANCES_FACET_ENDPOINT, - [FACETS.HOLDINGS_SOURCE]: INSTANCES_FACET_ENDPOINT, - [FACETS.ITEMS_CREATED_DATE]: INSTANCES_FACET_ENDPOINT, - [FACETS.ITEMS_UPDATED_DATE]: INSTANCES_FACET_ENDPOINT, - [FACETS.STATISTICAL_CODE_IDS]: INSTANCES_FACET_ENDPOINT, - [FACETS.HOLDINGS_STATISTICAL_CODE_IDS]: INSTANCES_FACET_ENDPOINT, - [FACETS.ITEMS_STATISTICAL_CODE_IDS]: INSTANCES_FACET_ENDPOINT, - [FACETS.NAME_TYPE]: CONTRIBUTORS_FACET_ENDPOINT, - [FACETS.HOLDINGS_TYPE]: INSTANCES_FACET_ENDPOINT, -}; - export const FACETS_OPTIONS = { SHARED_OPTIONS: 'sharedOptions', HELD_BY_OPTIONS: 'heldByOptions', diff --git a/src/withFacets.js b/src/withFacets.js index 85123e20d..1675d2fcf 100644 --- a/src/withFacets.js +++ b/src/withFacets.js @@ -1,8 +1,12 @@ -import { reduce } from 'lodash'; +import { + reduce, + omit, +} from 'lodash'; import React from 'react'; import PropTypes from 'prop-types'; import { makeQueryFunction } from '@folio/stripes/smart-components'; +import { buildFilterQuery } from '@folio/stripes-acq-components'; import { getQueryTemplate, @@ -13,9 +17,7 @@ import { } from './filterConfig'; import { DEFAULT_FILTERS_NUMBER, - FACETS, FACETS_TO_REQUEST, - FACETS_ENDPOINTS, CQL_FIND_ALL, browseModeOptions, browseModeMap, @@ -114,7 +116,62 @@ function withFacets(WrappedComponent) { }, ''); }; - fetchFacets = (data) => async (properties = {}) => { + getEndpoint = (queryIndex) => { + if (queryIndex === browseModeOptions.CONTRIBUTORS) { + return 'search/contributors/facets'; + } + + if (queryIndex === browseModeOptions.SUBJECTS) { + return 'search/subjects/facets'; + } + + return 'search/instances/facets'; + } + + getCqlQuery = (isBrowseLookup, query, queryIndex, data) => { + if (isBrowseLookup) { + const normalizedFilters = { + ...Object.entries(query).reduce((acc, [key, value]) => ({ + ...acc, + [FACETS_TO_REQUEST[key] || key]: value, + }), {}), + query: query.query || undefined, + }; + + const otherFilters = omit(normalizedFilters, 'query', 'qindex'); + const hasSelectedFacetOption = Object.values(otherFilters).some(Boolean); + + let queryForBrowseFacets = ''; + + const isTypedCallNumber = Object.values(browseCallNumberOptions).includes(queryIndex) + && queryIndex !== browseCallNumberOptions.CALL_NUMBERS; + + if (hasSelectedFacetOption) { + if (isTypedCallNumber) { + queryForBrowseFacets = `callNumberType="${queryIndex}"`; + } + } else if (isTypedCallNumber) { + queryForBrowseFacets = `callNumberType="${queryIndex}"`; + } else { + queryForBrowseFacets = 'cql.allRecords=1'; + } + + return buildFilterQuery( + { + query: queryForBrowseFacets, + qindex: normalizedFilters.qindex, + ...otherFilters, + }, + _query => _query, + undefined, + false, + ); + } + + return buildQuery(query, {}, { ...data, query }, { log: () => null }) || ''; + } + + fetchFacets = (data, isBrowseLookup) => async (properties = {}) => { const { onMoreClickedFacet, focusedFacet, @@ -136,24 +193,13 @@ function withFacets(WrappedComponent) { // temporary query value const params = { query: 'id = *' }; - const cqlQuery = buildQuery(query, {}, { ...data, query }, { log: () => null }) || ''; const facetName = facetToOpen || onMoreClickedFacet || focusedFacet; const facetNameToRequest = FACETS_TO_REQUEST[facetName]; const paramsUrl = new URLSearchParams(window.location.search); const queryIndex = paramsUrl.get('qindex') || query?.qindex; + const cqlQuery = this.getCqlQuery(isBrowseLookup, query, queryIndex, data); - if (facetName === FACETS.NAME_TYPE) { - params.query = 'contributorNameTypeId=*'; - } else if ([FACETS.CONTRIBUTORS_SHARED, FACETS.CONTRIBUTORS_HELD_BY].includes(facetName)) { - params.query = 'name=*'; - } else if ([FACETS.SUBJECTS_SHARED, FACETS.SUBJECTS_HELD_BY].includes(facetName)) { - params.query = 'value=*'; - } else if (cqlQuery - && Object.values(browseCallNumberOptions).includes(queryIndex) - && queryIndex !== browseCallNumberOptions.CALL_NUMBERS - ) { - params.query = `callNumberType="${queryIndex}"`; - } else if (cqlQuery && queryIndex !== browseModeOptions.CALL_NUMBERS) { + if (cqlQuery) { params.query = cqlQuery; } @@ -174,7 +220,7 @@ function withFacets(WrappedComponent) { try { reset(); - const requestPath = FACETS_ENDPOINTS[facetName] || 'search/instances/facets'; + const requestPath = this.getEndpoint(queryIndex); await GET({ path: requestPath, params }); } catch (error) { throw new Error(error); diff --git a/src/withFacets.test.js b/src/withFacets.test.js index 7f0e553b0..b8c789e99 100644 --- a/src/withFacets.test.js +++ b/src/withFacets.test.js @@ -9,17 +9,19 @@ import { queryIndexes, FACETS_CQL, browseCallNumberOptions, + browseModeOptions, } from './constants'; const WrappedComponent = ({ fetchFacets, data = {}, properties = {}, + isBrowseLookup, }) => { return ( @@ -36,12 +38,392 @@ const mutator = { }; describe('withFacets', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + describe('when opening a contributots shared facet', () => { - it('should make a request with correct params', () => { + describe('and there are no selected options in other facets', () => { + it('should make a request with correct request options', () => { + const resources = { + query: { + qindex: browseModeOptions.CONTRIBUTORS, + query: '', + }, + }; + + const { getByText } = render( + + ); + + fireEvent.click(getByText('fetchFacetsButton')); + + expect(mutator.facets.GET).toHaveBeenCalledWith(expect.objectContaining({ + params: { + facet: `${FACETS_CQL.INSTANCES_SHARED}:6`, + query: '(cql.allRecords=1)', + }, + path: 'search/contributors/facets', + })); + }); + }); + + describe('and there is a selected option in another facet', () => { + it('should make a request with correct request options', () => { + const resources = { + query: { + qindex: browseModeOptions.CONTRIBUTORS, + query: '', + contributorsTenantId: ['college'], + }, + }; + + const { getByText } = render( + + ); + + fireEvent.click(getByText('fetchFacetsButton')); + + expect(mutator.facets.GET).toHaveBeenCalledWith(expect.objectContaining({ + params: { + facet: `${FACETS_CQL.INSTANCES_SHARED}:6`, + query: 'instances.tenantId==("college")', + }, + path: 'search/contributors/facets', + })); + }); + }); + + describe('and there is a selected option in another facet and there is a value in the search box', () => { + it('should make a request with correct request options', () => { + const resources = { + query: { + qindex: browseModeOptions.CONTRIBUTORS, + query: 'Marc Twain', + contributorsTenantId: ['college'], + }, + }; + + const { getByText } = render( + + ); + + fireEvent.click(getByText('fetchFacetsButton')); + + expect(mutator.facets.GET).toHaveBeenCalledWith(expect.objectContaining({ + params: { + facet: `${FACETS_CQL.INSTANCES_SHARED}:6`, + query: 'instances.tenantId==("college")', + }, + path: 'search/contributors/facets', + })); + }); + }); + }); + + describe('when opening a subjects shared facet', () => { + describe('and there are no selected options in other facets', () => { + it('should make a request with correct request options', () => { + const resources = { + query: { + qindex: browseModeOptions.SUBJECTS, + query: '', + }, + }; + + const { getByText } = render( + + ); + + fireEvent.click(getByText('fetchFacetsButton')); + + expect(mutator.facets.GET).toHaveBeenCalledWith(expect.objectContaining({ + params: { + facet: `${FACETS_CQL.INSTANCES_SHARED}:6`, + query: '(cql.allRecords=1)', + }, + path: 'search/subjects/facets', + })); + }); + }); + + describe('and there is a selected option in another facet', () => { + it('should make a request with correct request options', () => { + const resources = { + query: { + qindex: browseModeOptions.SUBJECTS, + query: '', + contributorsTenantId: ['college'], + }, + }; + + const { getByText } = render( + + ); + + fireEvent.click(getByText('fetchFacetsButton')); + + expect(mutator.facets.GET).toHaveBeenCalledWith(expect.objectContaining({ + params: { + facet: `${FACETS_CQL.INSTANCES_SHARED}:6`, + query: 'instances.tenantId==("college")', + }, + path: 'search/subjects/facets', + })); + }); + }); + + describe('and there is a selected option in another facet and there is a value in the search box', () => { + it('should make a request with correct request options', () => { + const resources = { + query: { + qindex: browseModeOptions.SUBJECTS, + query: 'Marc Twain', + contributorsTenantId: ['college'], + }, + }; + + const { getByText } = render( + + ); + + fireEvent.click(getByText('fetchFacetsButton')); + + expect(mutator.facets.GET).toHaveBeenCalledWith(expect.objectContaining({ + params: { + facet: `${FACETS_CQL.INSTANCES_SHARED}:6`, + query: 'instances.tenantId==("college")', + }, + path: 'search/subjects/facets', + })); + }); + }); + }); + + describe('when opening call numbers browse shared facet', () => { + describe('and there are no selected options in other facets', () => { + it('should make a request with correct request options', () => { + const resources = { + query: { + qindex: browseCallNumberOptions.CALL_NUMBERS, + query: '', + }, + }; + + const { getByText } = render( + + ); + + fireEvent.click(getByText('fetchFacetsButton')); + + expect(mutator.facets.GET).toHaveBeenCalledWith(expect.objectContaining({ + params: { + facet: `${FACETS_CQL.SHARED}:6`, + query: '(cql.allRecords=1)', + }, + path: 'search/instances/facets', + })); + }); + }); + + describe('and there is a selected option in another facet', () => { + it('should make a request with correct request options', () => { + const resources = { + query: { + qindex: browseCallNumberOptions.CALL_NUMBERS, + query: '', + contributorsTenantId: ['college'], + }, + }; + + const { getByText } = render( + + ); + + fireEvent.click(getByText('fetchFacetsButton')); + + expect(mutator.facets.GET).toHaveBeenCalledWith(expect.objectContaining({ + params: { + facet: `${FACETS_CQL.SHARED}:6`, + query: 'instances.tenantId==("college")', + }, + path: 'search/instances/facets', + })); + }); + }); + + describe('and there is a selected option in another facet and there is a value in the search box', () => { + it('should make a request with correct request options', () => { + const resources = { + query: { + qindex: browseCallNumberOptions.CALL_NUMBERS, + query: 'Marc Twain', + contributorsTenantId: ['college'], + }, + }; + + const { getByText } = render( + + ); + + fireEvent.click(getByText('fetchFacetsButton')); + + expect(mutator.facets.GET).toHaveBeenCalledWith(expect.objectContaining({ + params: { + facet: `${FACETS_CQL.SHARED}:6`, + query: 'instances.tenantId==("college")', + }, + path: 'search/instances/facets', + })); + }); + }); + }); + + describe('when opening call numbers sub-type browse shared facet', () => { + describe('and there are no selected options in other facets', () => { + it('should make a request with correct request options', () => { + const resources = { + query: { + qindex: browseCallNumberOptions.DEWEY, + query: '', + }, + }; + + const { getByText } = render( + + ); + + fireEvent.click(getByText('fetchFacetsButton')); + + expect(mutator.facets.GET).toHaveBeenCalledWith(expect.objectContaining({ + params: { + facet: `${FACETS_CQL.SHARED}:6`, + query: '(callNumberType="dewey")', + }, + path: 'search/instances/facets', + })); + }); + }); + + describe('and there is a selected option in another facet', () => { + it('should make a request with correct request options', () => { + const resources = { + query: { + qindex: browseCallNumberOptions.DEWEY, + query: '', + contributorsTenantId: ['college'], + }, + }; + + const { getByText } = render( + + ); + + fireEvent.click(getByText('fetchFacetsButton')); + + expect(mutator.facets.GET).toHaveBeenCalledWith(expect.objectContaining({ + params: { + facet: `${FACETS_CQL.SHARED}:6`, + query: '(callNumberType="dewey") and instances.tenantId==("college")', + }, + path: 'search/instances/facets', + })); + }); + }); + + describe('and there is a selected option in another facet and there is a value in the search box', () => { + it('should make a request with correct request options', () => { + const resources = { + query: { + qindex: browseCallNumberOptions.DEWEY, + query: 'Marc Twain', + contributorsTenantId: ['college'], + }, + }; + + const { getByText } = render( + + ); + + fireEvent.click(getByText('fetchFacetsButton')); + + expect(mutator.facets.GET).toHaveBeenCalledWith(expect.objectContaining({ + params: { + facet: `${FACETS_CQL.SHARED}:6`, + query: '(callNumberType="dewey") and instances.tenantId==("college")', + }, + path: 'search/instances/facets', + })); + }); + }); + }); + + describe('when Advanced search is used', () => { + it('should fetch facets with the correct params', async () => { const resources = { query: { - qindex: queryIndexes.CONTRIBUTOR, - query: '', + qindex: queryIndexes.ADVANCED_SEARCH, + query: 'isbn containsAll test1 or title exactPhrase test2 or keyword startsWith test3', }, }; @@ -49,7 +431,7 @@ describe('withFacets', () => { ); @@ -57,27 +439,24 @@ describe('withFacets', () => { expect(mutator.facets.GET).toHaveBeenCalledWith(expect.objectContaining({ params: { - facet: `${FACETS_CQL.INSTANCES_SHARED}:6`, - query: 'name=*', + facet: 'source:6', + query: 'isbn="*test1*" or title=="test2" or keyword all "test3*"', }, })); }); }); - describe('when opening a subjects shared facet', () => { - it('should make a request with correct params', () => { + describe('when user opens a facet', () => { + it('should fetch only 6 options for this facet', () => { const resources = { - query: { - qindex: queryIndexes.SUBJECT, - query: '', - }, + query: {}, }; const { getByText } = render( ); @@ -85,27 +464,51 @@ describe('withFacets', () => { expect(mutator.facets.GET).toHaveBeenCalledWith(expect.objectContaining({ params: { - facet: `${FACETS_CQL.INSTANCES_SHARED}:6`, - query: 'value=*', + facet: `${FACETS_CQL.SHARED}:6`, + query: 'id = *', }, + path: 'search/instances/facets', })); }); }); - describe('when using call numbers browse', () => { - it('should make a request with correct params', () => { + describe('when user clicks the "+More" button under options', () => { + it('should fetch all options for this facet', () => { const resources = { - query: { - qindex: browseCallNumberOptions.CALL_NUMBERS, - query: 'test', + query: {}, + }; + + const { getByText } = render( + + ); + + fireEvent.click(getByText('fetchFacetsButton')); + + expect(mutator.facets.GET).toHaveBeenCalledWith(expect.objectContaining({ + params: { + facet: `${FACETS_CQL.INSTANCE_TYPE}`, + query: 'id = *', }, + path: 'search/instances/facets', + })); + }); + }); + + describe('when user places the cursor in the facet`s input field', () => { + it('should fetch all options for this facet', () => { + const resources = { + query: {}, }; const { getByText } = render( ); @@ -113,27 +516,33 @@ describe('withFacets', () => { expect(mutator.facets.GET).toHaveBeenCalledWith(expect.objectContaining({ params: { - facet: `${FACETS_CQL.SHARED}:6`, + facet: `${FACETS_CQL.INSTANCE_TYPE}`, query: 'id = *', }, + path: 'search/instances/facets', })); }); }); - describe('when using call numbers sub-type browse', () => { - it('should make a request with correct params', () => { + describe('when several facets are open and user enters a value in the search box', () => { + it('should fetch options for all open facets', () => { const resources = { query: { - qindex: browseCallNumberOptions.DEWEY, - query: 'test', + qindex: 'title', + query: 'Marc', }, }; + const properties = { + accordions: { resource: true, format: true }, + accordionsData: {}, + }; + const { getByText } = render( ); @@ -141,19 +550,27 @@ describe('withFacets', () => { expect(mutator.facets.GET).toHaveBeenCalledWith(expect.objectContaining({ params: { - facet: `${FACETS_CQL.SHARED}:6`, - query: 'callNumberType="dewey"', + facet: `${FACETS_CQL.INSTANCE_TYPE}:6,${FACETS_CQL.INSTANCE_FORMAT}:6`, + query: 'title all "Marc"', }, + path: 'search/instances/facets', })); }); }); - describe('when Advanced search is used', () => { - it('should fetch facets with the correct params', async () => { + describe('when several facets are open and user selects an option of any facet', () => { + it('should fetch options for all open facets', () => { const resources = { query: { - qindex: queryIndexes.ADVANCED_SEARCH, - query: 'isbn containsAll test1 or title exactPhrase test2 or keyword startsWith test3', + query: '', + filters: 'resource.6312d172-f0cf-40f6-b27d-9fa8feaf332f', + }, + }; + + const properties = { + accordions: { resource: true, format: true }, + accordionsData: { + resource: { isSelected: true }, }, }; @@ -161,7 +578,7 @@ describe('withFacets', () => { ); @@ -169,10 +586,49 @@ describe('withFacets', () => { expect(mutator.facets.GET).toHaveBeenCalledWith(expect.objectContaining({ params: { - facet: 'source:6', - query: 'isbn="*test1*" or title=="test2" or keyword all "test3*"', + facet: `${FACETS_CQL.INSTANCE_TYPE},${FACETS_CQL.INSTANCE_FORMAT}:6`, + query: 'instanceTypeId=="6312d172-f0cf-40f6-b27d-9fa8feaf332f"', }, + path: 'search/instances/facets', })); }); }); + + describe('when several facets are open and user selects an option of one facet', () => { + describe('and for another one the "+More" button is clicked', () => { + it('should fetch all options for the facet with selected option and for the facet with +More clicked', () => { + const resources = { + query: { + query: '', + filters: 'format.8d511d33-5e85-4c5d-9bce-6e3c9cd0c324', + }, + }; + + const properties = { + accordions: { shared: true, resource: true, format: true }, + accordionsData: { + resource: { isOnMoreClicked: true }, + format: { isSelected: true }, + }, + }; + + const { getByText } = render( + + ); + + fireEvent.click(getByText('fetchFacetsButton')); + + expect(mutator.facets.GET).toHaveBeenCalledWith(expect.objectContaining({ + params: { + facet: `${FACETS_CQL.SHARED}:6,${FACETS_CQL.INSTANCE_TYPE},${FACETS_CQL.INSTANCE_FORMAT}`, + query: expect.anything(), + }, + })); + }); + }); + }); });