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 dd78a40d0..4efcf706d 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 @@ -264,4 +264,59 @@ describe('GlobalSearchState', () => { expect(params['cardSearchFilter[defaultKey][]']).toBe('default-value'); }); }); + + 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 bc7bb2710..f998b620b 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,27 @@ 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 extraByKey = new Map(extraFilters.map((ef) => [ef.key, ef])); + + const merged = response.filters.map((apiFilter) => { + const cedarExtra = extraByKey.get(apiFilter.key); + if (!cedarExtra) return apiFilter; + + return { + ...apiFilter, + cedarPropertyIri: cedarExtra.cedarPropertyIri, + ...(cedarExtra.options !== undefined && { options: cedarExtra.options }), + }; + }); + 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,