diff --git a/src/app/shared/stores/global-search/global-search.state.spec.ts b/src/app/shared/stores/global-search/global-search.state.spec.ts index b43908615..8d77956a5 100644 --- a/src/app/shared/stores/global-search/global-search.state.spec.ts +++ b/src/app/shared/stores/global-search/global-search.state.spec.ts @@ -92,6 +92,16 @@ describe('GlobalSearchState', () => { expect(mockGetFilterOptions).not.toHaveBeenCalled(); }); + it('should skip the API call for a CEDAR filter found only in extraFilters (before first fetch)', () => { + const { store, mockGetFilterOptions } = setup(); + store.dispatch(new SetExtraFilters([CEDAR_FILTER])); + // Intentionally no FetchResources — state.filters is still empty + + store.dispatch(new LoadFilterOptions(CEDAR_FILTER.key)); + + expect(mockGetFilterOptions).not.toHaveBeenCalled(); + }); + it('should set isLoaded to true for a CEDAR filter when short-circuiting', () => { const { store } = setup(); @@ -239,4 +249,59 @@ describe('GlobalSearchState', () => { ]); }); }); + + describe('updateResourcesState (CEDAR filter key collision)', () => { + const COLLIDING_API_FILTER: DiscoverableFilter = { + key: CEDAR_FILTER.key, + label: 'School Type (from API)', + operator: FilterOperatorOption.AnyOf, + resultCount: 5, + }; + + const MOCK_RESOURCES_WITH_COLLIDING_FILTER: ResourcesData = { + resources: [], + filters: [COLLIDING_API_FILTER], + count: 5, + self: '', + first: null, + next: null, + previous: null, + }; + + it('should preserve cedarPropertyIri when API returns a filter with the same key as an extra filter', () => { + const { store, mockGetResources } = setup(); + mockGetResources.mockReturnValue(of(MOCK_RESOURCES_WITH_COLLIDING_FILTER)); + + store.dispatch(new SetExtraFilters([CEDAR_FILTER])); + store.dispatch(new FetchResources()); + + const filters = store.selectSnapshot(GlobalSearchSelectors.getFilters); + const filter = filters.find((f) => f.key === CEDAR_FILTER.key); + expect(filter?.cedarPropertyIri).toBe(CEDAR_FILTER.cedarPropertyIri); + expect(filter?.options).toEqual(CEDAR_FILTER.options); + }); + + it('should use cardSearchText even when the API returns a filter with the same key', () => { + const { store, mockGetResources } = setup(); + mockGetResources.mockReturnValue(of(MOCK_RESOURCES_WITH_COLLIDING_FILTER)); + + store.dispatch(new SetExtraFilters([CEDAR_FILTER])); + store.dispatch(new FetchResources()); + mockGetResources.mockClear(); + mockGetResources.mockReturnValue(of(MOCK_RESOURCES_DATA)); + + store.dispatch( + new LoadFilterOptionsAndSetValues({ + [CEDAR_FILTER.key]: [{ label: 'High School', value: 'High School', cardSearchResultCount: null }], + }) + ); + store.dispatch(new FetchResources()); + + const params = mockGetResources.mock.calls[0][0]; + expect(params[`cardSearchText[osf:hasCedarRecord.cedar:${CEDAR_FILTER.cedarPropertyIri}][]`]).toEqual([ + '"High School"', + ]); + expect(params[`cardSearchFilter[${CEDAR_FILTER.key}][]`]).toBeUndefined(); + }); + }); }); diff --git a/src/app/shared/stores/global-search/global-search.state.ts b/src/app/shared/stores/global-search/global-search.state.ts index 5e80a3541..d0901f2f6 100644 --- a/src/app/shared/stores/global-search/global-search.state.ts +++ b/src/app/shared/stores/global-search/global-search.state.ts @@ -63,7 +63,8 @@ export class GlobalSearchState { const state = ctx.getState(); const filterKey = action.filterKey; - const filter = state.filters.find((f) => f.key === filterKey); + const filter = + state.filters.find((f) => f.key === filterKey) ?? state.extraFilters.find((f) => f.key === filterKey); if (filter?.cedarPropertyIri) { ctx.patchState({ filters: state.filters.map((f) => (f.key === filterKey ? { ...f, isLoaded: true } : f)), @@ -291,14 +292,21 @@ export class GlobalSearchState { private updateResourcesState(ctx: StateContext, response: ResourcesData) { const { extraFilters } = ctx.getState(); const apiFilterKeys = new Set(response.filters.map((f) => f.key)); - const merged = [...response.filters, ...extraFilters.filter((f) => !apiFilterKeys.has(f.key))]; + + const merged = response.filters.map((apiFilter) => { + const cedarExtra = extraFilters.find((ef) => ef.key === apiFilter.key); + return cedarExtra?.cedarPropertyIri + ? { ...apiFilter, cedarPropertyIri: cedarExtra.cedarPropertyIri, options: cedarExtra.options } + : apiFilter; + }); + const extraFiltersNotInApi = extraFilters.filter((ef) => !apiFilterKeys.has(ef.key)); ctx.patchState({ resources: { data: response.resources, isLoading: false, error: null }, filterOptionsCache: {}, filterSearchCache: {}, filterPaginationCache: {}, - filters: merged, + filters: [...merged, ...extraFiltersNotInApi], resourcesCount: response.count, first: response.first, next: response.next,