Skip to content

Commit f585214

Browse files
committed
fix(ENG-11334): preserve cedar_roperty_iri on api filter key collision
1 parent bfd5bc9 commit f585214

2 files changed

Lines changed: 76 additions & 3 deletions

File tree

src/app/shared/stores/global-search/global-search.state.spec.ts

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,16 @@ describe('GlobalSearchState', () => {
9494
expect(mockGetFilterOptions).not.toHaveBeenCalled();
9595
});
9696

97+
it('should skip the API call for a CEDAR filter found only in extraFilters (before first fetch)', () => {
98+
const { store, mockGetFilterOptions } = setup();
99+
store.dispatch(new SetExtraFilters([CEDAR_FILTER]));
100+
// Intentionally no FetchResources — state.filters is still empty
101+
102+
store.dispatch(new LoadFilterOptions(CEDAR_FILTER.key));
103+
104+
expect(mockGetFilterOptions).not.toHaveBeenCalled();
105+
});
106+
97107
it('should set isLoaded to true for a CEDAR filter when short-circuiting', () => {
98108
const { store } = setup();
99109

@@ -264,4 +274,59 @@ describe('GlobalSearchState', () => {
264274
expect(params['cardSearchFilter[defaultKey][]']).toBe('default-value');
265275
});
266276
});
277+
278+
describe('updateResourcesState (CEDAR filter key collision)', () => {
279+
const COLLIDING_API_FILTER: DiscoverableFilter = {
280+
key: CEDAR_FILTER.key,
281+
label: 'School Type (from API)',
282+
operator: FilterOperatorOption.AnyOf,
283+
resultCount: 5,
284+
};
285+
286+
const MOCK_RESOURCES_WITH_COLLIDING_FILTER: ResourcesData = {
287+
resources: [],
288+
filters: [COLLIDING_API_FILTER],
289+
count: 5,
290+
self: '',
291+
first: null,
292+
next: null,
293+
previous: null,
294+
};
295+
296+
it('should preserve cedarPropertyIri when API returns a filter with the same key as an extra filter', () => {
297+
const { store, mockGetResources } = setup();
298+
mockGetResources.mockReturnValue(of(MOCK_RESOURCES_WITH_COLLIDING_FILTER));
299+
300+
store.dispatch(new SetExtraFilters([CEDAR_FILTER]));
301+
store.dispatch(new FetchResources());
302+
303+
const filters = store.selectSnapshot(GlobalSearchSelectors.getFilters);
304+
const filter = filters.find((f) => f.key === CEDAR_FILTER.key);
305+
expect(filter?.cedarPropertyIri).toBe(CEDAR_FILTER.cedarPropertyIri);
306+
expect(filter?.options).toEqual(CEDAR_FILTER.options);
307+
});
308+
309+
it('should use cardSearchText even when the API returns a filter with the same key', () => {
310+
const { store, mockGetResources } = setup();
311+
mockGetResources.mockReturnValue(of(MOCK_RESOURCES_WITH_COLLIDING_FILTER));
312+
313+
store.dispatch(new SetExtraFilters([CEDAR_FILTER]));
314+
store.dispatch(new FetchResources());
315+
mockGetResources.mockClear();
316+
mockGetResources.mockReturnValue(of(MOCK_RESOURCES_DATA));
317+
318+
store.dispatch(
319+
new LoadFilterOptionsAndSetValues({
320+
[CEDAR_FILTER.key]: [{ label: 'High School', value: 'High School', cardSearchResultCount: null }],
321+
})
322+
);
323+
store.dispatch(new FetchResources());
324+
325+
const params = mockGetResources.mock.calls[0][0];
326+
expect(params[`cardSearchText[osf:hasCedarRecord.cedar:${CEDAR_FILTER.cedarPropertyIri}][]`]).toEqual([
327+
'"High School"',
328+
]);
329+
expect(params[`cardSearchFilter[${CEDAR_FILTER.key}][]`]).toBeUndefined();
330+
});
331+
});
267332
});

src/app/shared/stores/global-search/global-search.state.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,8 @@ export class GlobalSearchState {
6363
const state = ctx.getState();
6464
const filterKey = action.filterKey;
6565

66-
const filter = state.filters.find((f) => f.key === filterKey);
66+
const filter =
67+
state.filters.find((f) => f.key === filterKey) ?? state.extraFilters.find((f) => f.key === filterKey);
6768
if (filter?.cedarPropertyIri) {
6869
ctx.patchState({
6970
filters: state.filters.map((f) => (f.key === filterKey ? { ...f, isLoaded: true } : f)),
@@ -291,14 +292,21 @@ export class GlobalSearchState {
291292
private updateResourcesState(ctx: StateContext<GlobalSearchStateModel>, response: ResourcesData) {
292293
const { extraFilters } = ctx.getState();
293294
const apiFilterKeys = new Set(response.filters.map((f) => f.key));
294-
const merged = [...response.filters, ...extraFilters.filter((f) => !apiFilterKeys.has(f.key))];
295+
296+
const merged = response.filters.map((apiFilter) => {
297+
const cedarExtra = extraFilters.find((ef) => ef.key === apiFilter.key);
298+
return cedarExtra?.cedarPropertyIri
299+
? { ...apiFilter, cedarPropertyIri: cedarExtra.cedarPropertyIri, options: cedarExtra.options }
300+
: apiFilter;
301+
});
302+
const extraFiltersNotInApi = extraFilters.filter((ef) => !apiFilterKeys.has(ef.key));
295303

296304
ctx.patchState({
297305
resources: { data: response.resources, isLoading: false, error: null },
298306
filterOptionsCache: {},
299307
filterSearchCache: {},
300308
filterPaginationCache: {},
301-
filters: merged,
309+
filters: [...merged, ...extraFiltersNotInApi],
302310
resourcesCount: response.count,
303311
first: response.first,
304312
next: response.next,

0 commit comments

Comments
 (0)