From dcf133b37e2b3ec2dd3239a9548a1774e94f16a5 Mon Sep 17 00:00:00 2001 From: Dominik Iwanek <141320833+dominikiwanekhyland@users.noreply.github.com> Date: Mon, 9 Feb 2026 08:03:39 +0100 Subject: [PATCH] =?UTF-8?q?[MNT-25410]=20ADW=20-=20Saved=20Searches=20can?= =?UTF-8?q?=20load=20the=20wrong=20search=20set,=20and=20t=E2=80=A6=20(#11?= =?UTF-8?q?599)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../search-filter-chips.component.spec.ts | 7 +- .../search-filter.component.spec.ts | 9 +- .../search-form/search-form.component.spec.ts | 8 +- .../search-form/search-form.component.ts | 2 +- .../models/search-configuration.interface.ts | 1 + .../search/models/search-form.interface.ts | 1 + .../base-query-builder.service.spec.ts | 583 ++++++++++++++++++ .../services/base-query-builder.service.ts | 78 ++- .../search-facet-filters.service.spec.ts | 15 + ...earch-header-query-builder.service.spec.ts | 16 +- .../search-query-builder.service.spec.ts | 66 +- 11 files changed, 736 insertions(+), 50 deletions(-) create mode 100644 lib/content-services/src/lib/search/services/base-query-builder.service.spec.ts diff --git a/lib/content-services/src/lib/search/components/search-filter-chips/search-filter-chips.component.spec.ts b/lib/content-services/src/lib/search/components/search-filter-chips/search-filter-chips.component.spec.ts index 4e39d83bbf..c11e2fe14a 100644 --- a/lib/content-services/src/lib/search/components/search-filter-chips/search-filter-chips.component.spec.ts +++ b/lib/content-services/src/lib/search/components/search-filter-chips/search-filter-chips.component.spec.ts @@ -53,6 +53,7 @@ describe('SearchFilterChipsComponent', () => { it('should fetch facet fields from response payload and show the already checked items', async () => { spyOn(queryBuilder, 'execute').and.stub(); queryBuilder.config = { + id: 'test-config', categories: [], facetFields: { fields: [ @@ -115,6 +116,7 @@ describe('SearchFilterChipsComponent', () => { it('should fetch facet fields from response payload and show the newly checked items', async () => { spyOn(queryBuilder, 'execute').and.stub(); queryBuilder.config = { + id: 'test-config', categories: [], facetFields: { fields: [ @@ -176,6 +178,7 @@ describe('SearchFilterChipsComponent', () => { it('should show buckets with 0 values when there are no facet fields on the response payload', async () => { spyOn(queryBuilder, 'execute').and.stub(); queryBuilder.config = { + id: 'test-config', categories: [], facetFields: { fields: [ @@ -253,7 +256,7 @@ describe('SearchFilterChipsComponent', () => { describe('widgets', () => { it('should not show the disabled widget', async () => { - appConfigService.config.search = { categories: disabledCategories }; + appConfigService.config.search = { id: 'test-config', categories: disabledCategories }; queryBuilder.resetToDefaults(); const chips = await loader.getAllHarnesses(MatChipHarness); @@ -262,7 +265,7 @@ describe('SearchFilterChipsComponent', () => { }); it('should show the widgets only if configured', async () => { - appConfigService.config.search = { categories: simpleCategories }; + appConfigService.config.search = { id: 'test-config', categories: simpleCategories }; queryBuilder.resetToDefaults(); const chips = await loader.getAllHarnesses(MatChipHarness); diff --git a/lib/content-services/src/lib/search/components/search-filter/search-filter.component.spec.ts b/lib/content-services/src/lib/search/components/search-filter/search-filter.component.spec.ts index 62c64f92fe..ccbb0de7e1 100644 --- a/lib/content-services/src/lib/search/components/search-filter/search-filter.component.spec.ts +++ b/lib/content-services/src/lib/search/components/search-filter/search-filter.component.spec.ts @@ -80,6 +80,7 @@ describe('SearchFilterComponent', () => { it('should fetch facet fields from response payload and show the already checked items', () => { spyOn(queryBuilder, 'execute').and.stub(); queryBuilder.config = { + id: 'test-config', categories: [], facetFields: { fields: [ @@ -138,6 +139,7 @@ describe('SearchFilterComponent', () => { it('should fetch facet fields from response payload and show the newly checked items', () => { spyOn(queryBuilder, 'execute').and.stub(); queryBuilder.config = { + id: 'test-config', categories: [], facetFields: { fields: [ @@ -196,6 +198,7 @@ describe('SearchFilterComponent', () => { it('should show buckets with 0 values when there are no facet fields on the response payload', () => { spyOn(queryBuilder, 'execute').and.stub(); queryBuilder.config = { + id: 'test-config', categories: [], facetFields: { fields: [ @@ -278,7 +281,7 @@ describe('SearchFilterComponent', () => { }); it('should not show the disabled widget', async () => { - appConfigService.config.search = { categories: disabledCategories }; + appConfigService.config.search = { id: 'test-config', categories: disabledCategories }; queryBuilder.resetToDefaults(); fixture.detectChanges(); @@ -289,7 +292,7 @@ describe('SearchFilterComponent', () => { }); it('should show the widget in expanded mode', async () => { - appConfigService.config.search = { categories: expandedCategories }; + appConfigService.config.search = { id: 'test-config', categories: expandedCategories }; queryBuilder.resetToDefaults(); fixture.detectChanges(); @@ -303,7 +306,7 @@ describe('SearchFilterComponent', () => { }); it('should show the widgets only if configured', async () => { - appConfigService.config.search = { categories: simpleCategories }; + appConfigService.config.search = { id: 'test-config', categories: simpleCategories }; queryBuilder.resetToDefaults(); fixture.detectChanges(); diff --git a/lib/content-services/src/lib/search/components/search-form/search-form.component.spec.ts b/lib/content-services/src/lib/search/components/search-form/search-form.component.spec.ts index f045f8fcac..8dd5792319 100644 --- a/lib/content-services/src/lib/search/components/search-form/search-form.component.spec.ts +++ b/lib/content-services/src/lib/search/components/search-form/search-form.component.spec.ts @@ -32,9 +32,9 @@ describe('SearchFormComponent', () => { let component: SearchFormComponent; let queryBuilder: SearchQueryBuilderService; const mockSearchForms: SearchForm[] = [ - { default: false, index: 0, name: 'All', selected: false }, - { default: true, index: 1, name: 'First', selected: true }, - { default: false, index: 2, name: 'Second', selected: false } + { id: 'form-all', default: false, index: 0, name: 'All', selected: false }, + { id: 'form-first', default: true, index: 1, name: 'First', selected: true }, + { id: 'form-second', default: false, index: 2, name: 'Second', selected: false } ]; beforeEach(() => { @@ -75,7 +75,7 @@ describe('SearchFormComponent', () => { }); it('should not show menu if only one config found', async () => { - queryBuilder.searchForms.next([{ name: 'one', selected: true, default: true, index: 0 }]); + queryBuilder.searchForms.next([{ id: 'form-one', name: 'one', selected: true, default: true, index: 0 }]); fixture.detectChanges(); const button = await loader.getHarness(MatButtonHarness.with({ selector: '.adf-search-form' })); diff --git a/lib/content-services/src/lib/search/components/search-form/search-form.component.ts b/lib/content-services/src/lib/search/components/search-form/search-form.component.ts index 0255b63424..1313fe22bc 100644 --- a/lib/content-services/src/lib/search/components/search-form/search-form.component.ts +++ b/lib/content-services/src/lib/search/components/search-form/search-form.component.ts @@ -41,7 +41,7 @@ export class SearchFormComponent { formChange: EventEmitter = new EventEmitter(); onSelectionChange(form: SearchForm) { - this.queryBuilder.updateSelectedConfiguration(form.index); + this.queryBuilder.updateSelectedConfiguration(form.id); this.formChange.emit(form); } diff --git a/lib/content-services/src/lib/search/models/search-configuration.interface.ts b/lib/content-services/src/lib/search/models/search-configuration.interface.ts index 0e53d12253..19f333467a 100644 --- a/lib/content-services/src/lib/search/models/search-configuration.interface.ts +++ b/lib/content-services/src/lib/search/models/search-configuration.interface.ts @@ -23,6 +23,7 @@ import { SearchSortingDefinition } from './search-sorting-definition.interface'; import { RequestHighlight } from '@alfresco/js-api'; export interface SearchConfiguration { + id: string; include?: string[]; fields?: string[]; categories?: SearchCategory[]; diff --git a/lib/content-services/src/lib/search/models/search-form.interface.ts b/lib/content-services/src/lib/search/models/search-form.interface.ts index a8bd5eaa09..6820b1aff5 100644 --- a/lib/content-services/src/lib/search/models/search-form.interface.ts +++ b/lib/content-services/src/lib/search/models/search-form.interface.ts @@ -16,6 +16,7 @@ */ export interface SearchForm { + id: string; index: number; name: string; default: boolean; diff --git a/lib/content-services/src/lib/search/services/base-query-builder.service.spec.ts b/lib/content-services/src/lib/search/services/base-query-builder.service.spec.ts new file mode 100644 index 0000000000..a71df6007a --- /dev/null +++ b/lib/content-services/src/lib/search/services/base-query-builder.service.spec.ts @@ -0,0 +1,583 @@ +/*! + * @license + * Copyright © 2005-2025 Hyland Software, Inc. and its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { TestBed } from '@angular/core/testing'; +import { provideRouter, Router } from '@angular/router'; +import { SearchQueryBuilderService } from './search-query-builder.service'; +import { AppConfigService } from '@alfresco/adf-core'; +import { SearchConfiguration } from '../models/search-configuration.interface'; +import { FacetFieldBucket } from '../models/facet-field-bucket.interface'; +import { ResultSetPaging } from '@alfresco/js-api'; +import { skip } from 'rxjs/operators'; + +describe('BaseQueryBuilderService', () => { + let service: SearchQueryBuilderService; + let appConfig: AppConfigService; + let router: Router; + + const mockSearchConfig: SearchConfiguration = { + id: 'config-default', + categories: [ + { + id: 'cat1', + name: 'Category 1', + enabled: true, + expanded: false, + component: { + selector: 'test', + settings: undefined + } + }, + { + id: 'cat2', + name: 'Category 2', + enabled: false, + expanded: false, + component: { + selector: 'test', + settings: undefined + } + } + ], + filterQueries: [{ query: 'TYPE:"cm:content"' }], + sorting: { + options: [ + { key: 'name', label: 'Name', type: 'FIELD', field: 'cm:name', ascending: true }, + { key: 'date', label: 'Date', type: 'FIELD', field: 'cm:created', ascending: false } + ], + defaults: [{ key: 'name', label: 'Name', type: 'FIELD', field: 'cm:name', ascending: true }] + } + }; + + const mockMultipleConfigs: SearchConfiguration[] = [ + { ...mockSearchConfig, id: 'config-1', name: 'Config 1', default: true }, + { ...mockSearchConfig, id: 'config-2', name: 'Config 2', default: false }, + { ...mockSearchConfig, id: 'config-3', name: 'Config 3', default: false } + ]; + + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [ + provideRouter([]), + SearchQueryBuilderService, + { + provide: AppConfigService, + useValue: { + get: jasmine.createSpy('get').and.returnValue(mockSearchConfig) + } + } + ] + }); + + service = TestBed.inject(SearchQueryBuilderService); + appConfig = TestBed.inject(AppConfigService); + router = TestBed.inject(Router); + }); + + describe('userQuery', () => { + it('should set userQuery with parentheses', () => { + service.userQuery = 'test query'; + expect(service.userQuery).toBe('(test query)'); + }); + + it('should trim userQuery', () => { + service.userQuery = ' test query '; + expect(service.userQuery).toBe('(test query)'); + }); + + it('should return empty string for null input', () => { + service.userQuery = null; + expect(service.userQuery).toBe(''); + }); + }); + + describe('queryFragments', () => { + it('should emit queryFragmentsUpdate when queryFragments change after initial fragments emited', (done) => { + service.queryFragmentsUpdate.pipe(skip(1)).subscribe((fragments) => { + expect(fragments['testId']).toBe('test query'); + done(); + }); + + service.queryFragments['testId'] = 'test query'; + }); + + it('should emit queryFragmentsUpdate when setting new queryFragments object after initial fragments emited', (done) => { + const newFragments = { id1: 'query1', id2: 'query2' }; + + service.queryFragmentsUpdate.pipe(skip(1)).subscribe((fragments) => { + expect(fragments).toEqual(newFragments); + done(); + }); + + service.queryFragments = newFragments; + }); + }); + + describe('filterQueries', () => { + it('should add filter query', () => { + service.filterQueries = []; + service.addFilterQuery('TYPE:"cm:folder"'); + + expect(service.filterQueries.length).toBe(1); + expect(service.filterQueries[0].query).toBe('TYPE:"cm:folder"'); + }); + + it('should not add duplicate filter query', () => { + service.filterQueries = []; + service.addFilterQuery('TYPE:"cm:folder"'); + service.addFilterQuery('TYPE:"cm:folder"'); + + expect(service.filterQueries.length).toBe(1); + }); + + it('should remove filter query', () => { + service.filterQueries = [{ query: 'TYPE:"cm:folder"' }, { query: 'TYPE:"cm:content"' }]; + service.removeFilterQuery('TYPE:"cm:folder"'); + + expect(service.filterQueries.length).toBe(1); + expect(service.filterQueries[0].query).toBe('TYPE:"cm:content"'); + }); + + it('should not add empty filter query', () => { + service.filterQueries = []; + service.addFilterQuery(''); + + expect(service.filterQueries.length).toBe(0); + }); + }); + + describe('userFacetBuckets', () => { + const mockBucket: FacetFieldBucket = { + label: 'bucket1', + count: 10, + filterQuery: 'field:value1' + }; + + const mockBucket2: FacetFieldBucket = { + label: 'bucket2', + count: 5, + filterQuery: 'field:value2' + }; + + it('should add user facet bucket', () => { + service.addUserFacetBucket('field1', mockBucket); + + const buckets = service.getUserFacetBuckets('field1'); + expect(buckets.length).toBe(1); + expect(buckets[0].label).toBe('bucket1'); + }); + + it('should not add duplicate bucket', () => { + service.addUserFacetBucket('field1', mockBucket); + service.addUserFacetBucket('field1', mockBucket); + + const buckets = service.getUserFacetBuckets('field1'); + expect(buckets.length).toBe(1); + }); + + it('should remove user facet bucket', () => { + service.addUserFacetBucket('field1', mockBucket); + service.addUserFacetBucket('field1', mockBucket2); + service.removeUserFacetBucket('field1', mockBucket); + + const buckets = service.getUserFacetBuckets('field1'); + expect(buckets.length).toBe(1); + expect(buckets[0].label).toBe('bucket2'); + }); + + it('should reset user facet buckets', () => { + service.addUserFacetBucket('field1', mockBucket); + service.addUserFacetBucket('field2', mockBucket2); + service.resetUserFacetBucket(); + + expect(service.getUserFacetBuckets('field1').length).toBe(0); + expect(service.getUserFacetBuckets('field2').length).toBe(0); + }); + + }); + + describe('buildQuery', () => { + it('should return null when no query is set', () => { + service.userQuery = ''; + expect(service.buildQuery()).toBeNull(); + }); + + it('should build query with userQuery', () => { + service.userQuery = 'test'; + const query = service.buildQuery(); + + expect(query).toBeTruthy(); + expect(query.query.query).toBe('(test)'); + }); + + it('should include scope in query when set', () => { + service.userQuery = 'test'; + service.setScope({ locations: 'nodes' }); + const query = service.buildQuery(); + + expect(query.scope).toEqual({ locations: 'nodes' }); + }); + + it('should include default includes when none configured', () => { + service.config = { id: 'test-config', categories: [] }; + service.userQuery = 'test'; + const query = service.buildQuery(); + + expect(query.include).toContain('path'); + expect(query.include).toContain('allowableOperations'); + }); + }); + + describe('update', () => { + it('should emit updated event with built query', (done) => { + service.userQuery = 'test'; + + service.updated.subscribe((query) => { + expect(query.query.query).toBe('(test)'); + done(); + }); + + service.update(); + }); + + it('should emit updated event with provided query body', (done) => { + const customQuery = { + query: { query: 'custom query', language: 'afts' } + }; + + service.updated.subscribe((query) => { + expect(query.query.query).toBe('custom query'); + done(); + }); + + service.update(customQuery); + }); + }); + + describe('execute', () => { + it('should update search query params by default', async () => { + spyOn(router, 'navigate').and.returnValue(Promise.resolve(true)); + spyOn(service.searchApi, 'search').and.returnValue(Promise.resolve({ list: { entries: [] } } as ResultSetPaging)); + service.userQuery = 'test'; + + await service.execute(); + + expect(router.navigate).toHaveBeenCalled(); + }); + + it('should not update search query params when disabled', async () => { + spyOn(router, 'navigate').and.returnValue(Promise.resolve(true)); + spyOn(service.searchApi, 'search').and.returnValue(Promise.resolve({ list: { entries: [] } } as ResultSetPaging)); + service.userQuery = 'test'; + + await service.execute(false); + + expect(router.navigate).not.toHaveBeenCalled(); + }); + + it('should emit executed event on success', async () => { + const mockResult: ResultSetPaging = { + list: { + entries: [ + { + entry: { + id: '1', + name: '', + nodeType: '', + isFolder: false, + isFile: false + } + } + ] + } + }; + spyOn(router, 'navigate').and.returnValue(Promise.resolve(true)); + spyOn(service.searchApi, 'search').and.returnValue(Promise.resolve(mockResult)); + service.userQuery = 'test'; + + const executedSpy = jasmine.createSpy('executedSpy'); + service.executed.subscribe(executedSpy); + + await service.execute(); + + expect(executedSpy).toHaveBeenCalledWith(mockResult); + }); + + it('should emit error and empty result on failure', async () => { + const mockError = new Error('Search failed'); + spyOn(router, 'navigate').and.returnValue(Promise.resolve(true)); + spyOn(service.searchApi, 'search').and.returnValue(Promise.reject(mockError)); + service.userQuery = 'test'; + + const errorSpy = jasmine.createSpy('errorSpy'); + const executedSpy = jasmine.createSpy('executedSpy'); + service.error.subscribe(errorSpy); + service.executed.subscribe(executedSpy); + + await service.execute(); + + expect(errorSpy).toHaveBeenCalledWith(mockError); + expect(executedSpy).toHaveBeenCalledWith( + jasmine.objectContaining({ + list: jasmine.objectContaining({ + pagination: { totalItems: 0 }, + entries: [] + }) + }) + ); + }); + + it('should update URL params even when query is null', async () => { + spyOn(router, 'navigate').and.returnValue(Promise.resolve(true)); + service.userQuery = ''; + + await service.execute(); + + expect(router.navigate).toHaveBeenCalled(); + }); + }); + + describe('sorting', () => { + it('should return primary sorting', () => { + service.sorting = [ + { key: 'name', label: 'Name', type: 'FIELD', field: 'cm:name', ascending: true }, + { key: 'date', label: 'Date', type: 'FIELD', field: 'cm:created', ascending: false } + ]; + + const primary = service.getPrimarySorting(); + expect(primary.key).toBe('name'); + }); + + it('should return null when no sorting defined', () => { + service.sorting = []; + expect(service.getPrimarySorting()).toBeNull(); + }); + + it('should return sorting options from config', () => { + service.config = { + id: 'test-config', + categories: [], + sorting: { + options: [{ key: 'name', label: 'Name', type: 'FIELD', field: 'cm:name', ascending: true }], + defaults: [] + } + }; + + const options = service.getSortingOptions(); + expect(options.length).toBe(1); + expect(options[0].key).toBe('name'); + }); + }); + + describe('multiple configurations', () => { + beforeEach(() => { + (appConfig.get as jasmine.Spy).and.returnValue(mockMultipleConfigs); + service.resetToDefaults(); + }); + + it('should return default configuration', () => { + const config = service.getDefaultConfiguration(); + expect(config.name).toBe('Config 1'); + }); + + it('should return search form details for multiple configurations', () => { + const forms = service.getSearchFormDetails(); + + expect(forms.length).toBe(3); + expect(forms[0].id).toBe('config-1'); + expect(forms[0].name).toBe('Config 1'); + expect(forms[0].default).toBe(true); + expect(forms[0].selected).toBe(true); + expect(forms[1].id).toBe('config-2'); + expect(forms[1].name).toBe('Config 2'); + expect(forms[1].selected).toBe(false); + }); + + it('should update selected configuration', (done) => { + spyOn(router, 'navigate').and.returnValue(Promise.resolve(true)); + spyOn(service.searchApi, 'search').and.returnValue(Promise.resolve({ list: { entries: [] } } as ResultSetPaging)); + + service.configUpdated.subscribe((config) => { + expect(config.name).toBe('Config 2'); + done(); + }); + + service.updateSelectedConfiguration('config-2'); + }); + + it('should update searchForms when configuration changes after initial forms emited', (done) => { + spyOn(router, 'navigate').and.returnValue(Promise.resolve(true)); + spyOn(service.searchApi, 'search').and.returnValue(Promise.resolve({ list: { entries: [] } } as ResultSetPaging)); + + let callCount = 0; + service.searchForms.pipe(skip(1)).subscribe((forms) => { + callCount++; + expect(forms[1].selected).toBe(true); + expect(forms[0].selected).toBe(false); + done(); + }); + + service.updateSelectedConfiguration('config-2'); + }); + + it('should store selectedConfigurationId in filterRawParams', async () => { + spyOn(router, 'navigate').and.returnValue(Promise.resolve(true)); + spyOn(service.searchApi, 'search').and.returnValue(Promise.resolve({ list: { entries: [] } } as ResultSetPaging)); + + service.updateSelectedConfiguration('config-2'); + + expect(service.filterRawParams['selectedConfigurationId']).toBe('config-2'); + }); + + it('should call execute when updating configuration', async () => { + spyOn(router, 'navigate').and.returnValue(Promise.resolve(true)); + spyOn(service.searchApi, 'search').and.returnValue(Promise.resolve({ list: { entries: [] } } as ResultSetPaging)); + service.userQuery = 'test'; + + service.updateSelectedConfiguration('config-2'); + + expect(router.navigate).toHaveBeenCalled(); + }); + }); + + describe('populateFilters and selectedConfiguration restoration', () => { + beforeEach(() => { + (appConfig.get as jasmine.Spy<(key: string, defaultValue?: T) => T>).and.returnValue(mockMultipleConfigs); + service.resetToDefaults(); + }); + + it('should restore selectedConfiguration from populateFilters', (done) => { + spyOn(router, 'navigate').and.returnValue(Promise.resolve(true)); + + service.configUpdated.subscribe((config) => { + expect(config.name).toBe('Config 3'); + done(); + }); + + service.populateFilters.next({ selectedConfigurationId: 'config-3', someOtherFilter: 'value' }); + }); + + it('should reset to default configuration when populateFilters has no selectedConfigurationId', (done) => { + spyOn(router, 'navigate').and.returnValue(Promise.resolve(true)); + spyOn(service.searchApi, 'search').and.returnValue(Promise.resolve({ list: { entries: [] } } as ResultSetPaging)); + + service.updateSelectedConfiguration('config-3'); + + setTimeout(() => { + service.configUpdated.subscribe((config) => { + expect(config.name).toBe('Config 1'); + done(); + }); + + service.populateFilters.next({ someOtherFilter: 'value' }); + }, 0); + }); + + it('should not change configuration when populateFilters is empty', () => { + spyOn(router, 'navigate').and.returnValue(Promise.resolve(true)); + const configUpdatedSpy = jasmine.createSpy('configUpdatedSpy'); + + service.configUpdated.subscribe(configUpdatedSpy); + configUpdatedSpy.calls.reset(); + + service.populateFilters.next({}); + + expect(configUpdatedSpy).not.toHaveBeenCalled(); + }); + + it('should not change configuration when same configuration is already selected', (done) => { + spyOn(router, 'navigate').and.returnValue(Promise.resolve(true)); + spyOn(service.searchApi, 'search').and.returnValue(Promise.resolve({ list: { entries: [] } } as ResultSetPaging)); + + service.updateSelectedConfiguration('config-2'); + + setTimeout(() => { + const configUpdatedSpy = jasmine.createSpy('configUpdatedSpy'); + service.configUpdated.subscribe(configUpdatedSpy); + + service.populateFilters.next({ selectedConfigurationId: 'config-2' }); + + setTimeout(() => { + expect(configUpdatedSpy).not.toHaveBeenCalled(); + done(); + }, 0); + }, 0); + }); + + it('should update filterRawParams when restoring configuration from populateFilters', (done) => { + service.configUpdated.subscribe(() => { + expect(service.filterRawParams['selectedConfigurationId']).toBe('config-2'); + done(); + }); + + service.populateFilters.next({ selectedConfigurationId: 'config-2' }); + }); + }); + + describe('resetToDefaults', () => { + it('should emit configUpdated', (done) => { + service.configUpdated.subscribe((config) => { + expect(config).toBeTruthy(); + done(); + }); + + service.resetToDefaults(); + }); + + it('should emit searchForms', () => { + let formsCalled = false; + + service.searchForms.subscribe((forms) => { + if (!formsCalled) { + formsCalled = true; + expect(forms).toBeTruthy(); + } + }); + + service.resetToDefaults(); + + expect(formsCalled).toBe(true); + }); + + it('should navigate when withNavigate is true', () => { + spyOn(router, 'navigate').and.returnValue(Promise.resolve(true)); + + service.resetToDefaults(true); + + expect(router.navigate).toHaveBeenCalledWith([], jasmine.objectContaining({ queryParams: { q: null } })); + }); + + it('should reset categories', () => { + service.categories = [ + { + id: 'test', + name: 'Test', + enabled: true, + expanded: false, + component: { + selector: 'test', + settings: undefined + } + } + ]; + service.resetToDefaults(); + + expect(service.categories.length).toBe(1); + expect(service.categories[0].id).toBe('cat1'); + }); + }); +}); diff --git a/lib/content-services/src/lib/search/services/base-query-builder.service.ts b/lib/content-services/src/lib/search/services/base-query-builder.service.ts index 94292325f6..ae6b8ed75f 100644 --- a/lib/content-services/src/lib/search/services/base-query-builder.service.ts +++ b/lib/content-services/src/lib/search/services/base-query-builder.service.ts @@ -88,10 +88,11 @@ export abstract class BaseQueryBuilderService { private encodedQuery: string; private scope: RequestScope; - private selectedConfiguration: number; + private selectedConfigurationId: string; private _userQuery = ''; private _queryFragments: { [id: string]: string } = {}; + private readonly selectedConfigurationKey = 'selectedConfigurationId'; private readonly queryFragmentsHandler: ProxyHandler<{ [key: string]: any }> = { set: (target: { [key: string]: any }, property: string, value: any) => { target[property as keyof typeof target] = value; @@ -121,7 +122,8 @@ export abstract class BaseQueryBuilderService { } config: SearchConfiguration = { - categories: [] + categories: [], + id: 'SEARCH.UNKNOWN_CONFIGURATION' }; // TODO: to be supported in future iterations @@ -133,6 +135,8 @@ export abstract class BaseQueryBuilderService { ) { this.resetToDefaults(); this._queryFragments = this.createQueryFragmentsProxy({}); + + this.populateFilters.subscribe((filters) => this.handleSelectedConfigurationChange(filters)); } public abstract loadConfiguration(): SearchConfiguration | SearchConfiguration[]; @@ -158,8 +162,8 @@ export abstract class BaseQueryBuilderService { const configurations = this.loadConfiguration(); if (Array.isArray(configurations)) { - if (this.selectedConfiguration !== undefined) { - return configurations[this.selectedConfiguration]; + if (this.selectedConfigurationId !== undefined) { + return configurations.find((config) => config.id === this.selectedConfigurationId); } return configurations.find((configuration) => configuration.default); @@ -167,15 +171,19 @@ export abstract class BaseQueryBuilderService { return configurations; } - public updateSelectedConfiguration(index: number): void { + public updateSelectedConfiguration(id: string): void { const currentConfig = this.loadConfiguration(); - if (Array.isArray(currentConfig) && currentConfig[index] !== undefined) { - this.selectedConfiguration = index; - this.searchForms.next(this.getSearchFormDetails()); - this.resetSearchOptions(); - this.setUpSearchConfiguration(currentConfig[index]); - this.configUpdated.next(currentConfig[index]); - this.update(); + if (Array.isArray(currentConfig)) { + const selectedConfig = currentConfig.find((config) => config.id === id); + if (selectedConfig) { + this.selectedConfigurationId = id; + this.searchForms.next(this.getSearchFormDetails()); + this.resetSearchOptions(); + this.setUpSearchConfiguration(selectedConfig); + this.filterRawParams[this.selectedConfigurationKey] = id; + this.configUpdated.next(selectedConfig); + this.execute(); + } } } @@ -196,14 +204,16 @@ export abstract class BaseQueryBuilderService { const configurations = this.loadConfiguration(); if (Array.isArray(configurations)) { return configurations.map((configuration, index) => ({ + id: configuration.id, index, name: configuration.name || 'SEARCH.UNKNOWN_CONFIGURATION', default: configuration.default || false, - selected: this.selectedConfiguration !== undefined ? index === this.selectedConfiguration : configuration.default + selected: this.selectedConfigurationId !== undefined ? configuration.id === this.selectedConfigurationId : configuration.default })); } else if (configurations) { return [ { + id: 'SEARCH.UNKNOWN_CONFIGURATION', index: 0, name: configurations.name || 'SEARCH.UNKNOWN_CONFIGURATION', default: true, @@ -364,11 +374,11 @@ export abstract class BaseQueryBuilderService { */ async execute(updateQueryParams = true, queryBody?: SearchRequest) { try { + if (updateQueryParams) { + this.updateSearchQueryParams(); + } const query = queryBody ? queryBody : this.buildQuery(); if (query) { - if (updateQueryParams) { - this.updateSearchQueryParams(); - } const resultSetPaging: ResultSetPaging = await this.searchApi.search(query); this.executed.next(resultSetPaging); } @@ -654,4 +664,40 @@ export abstract class BaseQueryBuilderService { private createQueryFragmentsProxy(target: { [key: string]: any }): { [key: string]: any } { return new Proxy(target, this.queryFragmentsHandler); } + + private setSelectedConfiguration(id: string): void { + const currentConfig = this.loadConfiguration(); + if (Array.isArray(currentConfig)) { + const selectedConfig = currentConfig.find((config) => config.id === id); + if (selectedConfig) { + this.selectedConfigurationId = id; + this.searchForms.next(this.getSearchFormDetails()); + this.setUpSearchConfiguration(selectedConfig); + this.filterRawParams[this.selectedConfigurationKey] = id; + this.configUpdated.next(selectedConfig); + } + } + } + + private handleSelectedConfigurationChange(filters: { [key: string]: string }): void { + if (Object.keys(filters ?? {}).length === 0) { + return; + } + + const newSelectedConfig = filters?.[this.selectedConfigurationKey]; + + if (newSelectedConfig) { + if (newSelectedConfig !== this.selectedConfigurationId) { + this.setSelectedConfiguration(newSelectedConfig); + } + } else { + const configurations = this.loadConfiguration(); + if (Array.isArray(configurations)) { + const defaultConfig = configurations.find(config => config.default); + if (defaultConfig && this.selectedConfigurationId !== defaultConfig.id) { + this.setSelectedConfiguration(defaultConfig.id); + } + } + } + } } diff --git a/lib/content-services/src/lib/search/services/search-facet-filters.service.spec.ts b/lib/content-services/src/lib/search/services/search-facet-filters.service.spec.ts index 6f01420f1e..67c12c592c 100644 --- a/lib/content-services/src/lib/search/services/search-facet-filters.service.spec.ts +++ b/lib/content-services/src/lib/search/services/search-facet-filters.service.spec.ts @@ -59,6 +59,7 @@ describe('SearchFacetFiltersService', () => { searchFacetFiltersService.responseFacets = null; queryBuilder.config = { + id: 'test-config', categories: [], facetQueries: { label: 'label1', @@ -97,6 +98,7 @@ describe('SearchFacetFiltersService', () => { searchFacetFiltersService.responseFacets = null; queryBuilder.config = { + id: 'test-config', categories: [], facetQueries: { label: 'label1', @@ -140,6 +142,7 @@ describe('SearchFacetFiltersService', () => { searchFacetFiltersService.responseFacets = null; queryBuilder.config = { + id: 'test-config', categories: [], facetQueries: { queries: [] @@ -163,6 +166,7 @@ describe('SearchFacetFiltersService', () => { searchFacetFiltersService.responseFacets = null; queryBuilder.config = { + id: 'test-config', categories: [], facetFields: { fields: [ @@ -196,6 +200,7 @@ describe('SearchFacetFiltersService', () => { it('should filter response facet fields based on search filter config method', () => { queryBuilder.config = { + id: 'test-config', categories: [], facetFields: { fields: [{ label: 'f1', field: 'f1' }] }, facetQueries: { @@ -247,6 +252,7 @@ describe('SearchFacetFiltersService', () => { searchFacetFiltersService.responseFacets = null; queryBuilder.config = { + id: 'test-config', categories: [], facetFields: { fields: [ @@ -286,6 +292,7 @@ describe('SearchFacetFiltersService', () => { it('should fetch facet fields from response payload and update the existing bucket values', () => { queryBuilder.config = { + id: 'test-config', categories: [], facetFields: { fields: [ @@ -343,6 +350,7 @@ describe('SearchFacetFiltersService', () => { searchFacetFiltersService.responseFacets = null; queryBuilder.config = { + id: 'test-config', categories: [], facetFields: { fields: [{ label: 'f1', field: 'f1' }] }, facetQueries: { queries: [] } @@ -374,6 +382,7 @@ describe('SearchFacetFiltersService', () => { it('should fetch facet intervals from response payload', () => { searchFacetFiltersService.responseFacets = null; queryBuilder.config = { + id: 'test-config', categories: [], facetIntervals: { intervals: [ @@ -426,6 +435,7 @@ describe('SearchFacetFiltersService', () => { it('should filter out the fetched facet intervals that have bucket values less than their set mincount', () => { searchFacetFiltersService.responseFacets = null; queryBuilder.config = { + id: 'test-config', categories: [], facetIntervals: { intervals: [ @@ -479,6 +489,7 @@ describe('SearchFacetFiltersService', () => { it('should sort the facets based on the order set in the settings', () => { searchFacetFiltersService.responseFacets = null; queryBuilder.config = { + id: 'test-config', categories: [], facetQueries: { label: 'Query 1', @@ -525,6 +536,7 @@ describe('SearchFacetFiltersService', () => { spyOn(categoryService, 'getCategory').and.returnValue(of({ entry })); queryBuilder.config = { + id: 'test-config', categories: [], facetFields: { fields: [ @@ -569,6 +581,7 @@ describe('SearchFacetFiltersService', () => { it('should extract creator and modifier facets and create tabbed facet for them', () => { searchFacetFiltersService.responseFacets = null; queryBuilder.config = { + id: 'test-config', categories: [], facetFields: { fields: [ @@ -671,6 +684,7 @@ describe('SearchFacetFiltersService', () => { it('should sort the buckets by label', () => { queryBuilder.config = { + id: 'test-config', categories: [], facetQueries: { queries: [] }, facetFields: { @@ -690,6 +704,7 @@ describe('SearchFacetFiltersService', () => { it('should sort the buckets by count', () => { queryBuilder.config = { + id: 'test-config', categories: [], facetQueries: { queries: [] }, facetFields: { diff --git a/lib/content-services/src/lib/search/services/search-header-query-builder.service.spec.ts b/lib/content-services/src/lib/search/services/search-header-query-builder.service.spec.ts index 3c11c9044a..85e0531822 100644 --- a/lib/content-services/src/lib/search/services/search-header-query-builder.service.spec.ts +++ b/lib/content-services/src/lib/search/services/search-header-query-builder.service.spec.ts @@ -45,7 +45,8 @@ describe('SearchHeaderQueryBuilderService', () => { it('should load the configuration from app config', () => { TestBed.runInInjectionContext(() => { - const config = { + const config: SearchConfiguration = { + id: 'test-config', categories: [ { id: 'cat1', enabled: true }, { id: 'cat2', enabled: true } @@ -71,7 +72,8 @@ describe('SearchHeaderQueryBuilderService', () => { it('should return the category assigned to a column key', () => { TestBed.runInInjectionContext(() => { - const config = { + const config: SearchConfiguration = { + id: 'test-config', categories: [ { id: 'cat1', columnKey: 'fake-key-1', enabled: true }, { id: 'cat2', columnKey: 'fake-key-2', enabled: true } @@ -92,6 +94,7 @@ describe('SearchHeaderQueryBuilderService', () => { it('should return operator for a category by id', () => { TestBed.runInInjectionContext(() => { const config: SearchConfiguration = { + id: 'test-config', categories: [ { id: 'cat1', columnKey: 'fake-key-1', enabled: true, component: { settings: { operator: 'operator' } } }, { id: 'cat2', columnKey: 'fake-key-2', enabled: true } @@ -118,7 +121,8 @@ describe('SearchHeaderQueryBuilderService', () => { it('should add the extra filter for the parent node', () => { TestBed.runInInjectionContext(() => { - const config = { + const config: SearchConfiguration = { + id: 'test-config', categories: [ { id: 'cat1', enabled: true }, { id: 'cat2', enabled: true } @@ -141,7 +145,8 @@ describe('SearchHeaderQueryBuilderService', () => { TestBed.runInInjectionContext(() => { const expectedResult = [{ query: 'PARENT:"workspace://SpacesStore/fake-node-id"' }]; - const config = { + const config: SearchConfiguration = { + id: 'test-config', categories: [ { id: 'cat1', enabled: true }, { id: 'cat2', enabled: true } @@ -162,7 +167,8 @@ describe('SearchHeaderQueryBuilderService', () => { TestBed.runInInjectionContext(() => { const activeFilter = 'FakeColumn'; - const config = { + const config: SearchConfiguration = { + id: 'test-config', categories: [{ id: 'cat1', enabled: true }], filterQueries: [{ query: 'PARENT:"workspace://SpacesStore/fake-node-id' }] } as SearchConfiguration; diff --git a/lib/content-services/src/lib/search/services/search-query-builder.service.spec.ts b/lib/content-services/src/lib/search/services/search-query-builder.service.spec.ts index be8f25d18f..a1085a74b8 100644 --- a/lib/content-services/src/lib/search/services/search-query-builder.service.spec.ts +++ b/lib/content-services/src/lib/search/services/search-query-builder.service.spec.ts @@ -35,7 +35,7 @@ const buildConfig = (searchSettings = {}): AppConfigService => { }; describe('SearchQueryBuilder (runtime config)', () => { - const runtimeConfig: SearchConfiguration = {}; + const runtimeConfig: SearchConfiguration = { id: 'runtime-config' }; beforeEach(() => { TestBed.configureTestingModule({ @@ -55,6 +55,7 @@ describe('SearchQueryBuilder (runtime config)', () => { it('should prioritise runtime config over configuration file', () => { const config: SearchConfiguration = { + id: 'file-config', categories: [{ id: 'cat1', enabled: true } as any, { id: 'cat2', enabled: true } as any], filterQueries: [{ query: 'query1' }, { query: 'query2' }] }; @@ -96,6 +97,7 @@ describe('SearchQueryBuilder', () => { it('should reset to defaults', () => { const config: SearchConfiguration = { + id: 'test-config', categories: [{ id: 'cat1', enabled: true } as any, { id: 'cat2', enabled: true } as any], filterQueries: [{ query: 'query1' }, { query: 'query2' }] }; @@ -133,6 +135,7 @@ describe('SearchQueryBuilder', () => { it('should use only enabled categories', () => { const config: SearchConfiguration = { + id: 'test-config', categories: [{ id: 'cat1', enabled: true } as any, { id: 'cat2', enabled: false } as any, { id: 'cat3', enabled: true } as any] }; const builder = createQueryBuilder(config); @@ -144,6 +147,7 @@ describe('SearchQueryBuilder', () => { it('should fetch filter queries from config', () => { const config: SearchConfiguration = { + id: 'test-config', categories: [], filterQueries: [{ query: 'query1' }, { query: 'query2' }] }; @@ -212,6 +216,7 @@ describe('SearchQueryBuilder', () => { it('should fetch facet query from config', () => { const config: SearchConfiguration = { + id: 'test-config', categories: [], facetQueries: { queries: [ @@ -229,6 +234,7 @@ describe('SearchQueryBuilder', () => { it('should not fetch empty facet query from the config', () => { const config: SearchConfiguration = { + id: 'test-config', categories: [], facetQueries: { queries: [{ query: 'q1', label: 'query1' }] @@ -245,6 +251,7 @@ describe('SearchQueryBuilder', () => { it('should fetch facet from the config by label', () => { const config: SearchConfiguration = { + id: 'test-config', categories: [], facetFields: { fields: [ @@ -262,6 +269,7 @@ describe('SearchQueryBuilder', () => { it('should not fetch facet from the config by label', () => { const config: SearchConfiguration = { + id: 'test-config', categories: [], facetFields: { fields: [ @@ -278,6 +286,7 @@ describe('SearchQueryBuilder', () => { it('should fetch facets from the config by label with spaces and return field with request compatible label (escaped)', () => { const config: SearchConfiguration = { + id: 'test-config', categories: [], facetFields: { fields: [{ field: 'content.size', mincount: 1, label: 'Label with spaces' }] @@ -292,6 +301,7 @@ describe('SearchQueryBuilder', () => { it('should require a query fragment to build query', () => { const config: SearchConfiguration = { + id: 'test-config', categories: [{ id: 'cat1', enabled: true } as any] }; const builder = createQueryBuilder(config); @@ -303,6 +313,7 @@ describe('SearchQueryBuilder', () => { it('should build query with single fragment', () => { const config: SearchConfiguration = { + id: 'test-config', categories: [{ id: 'cat1', enabled: true } as any] }; const builder = createQueryBuilder(config); @@ -314,6 +325,7 @@ describe('SearchQueryBuilder', () => { it('should build query with multiple fragments', () => { const config: SearchConfiguration = { + id: 'test-config', categories: [{ id: 'cat1', enabled: true } as any, { id: 'cat2', enabled: true } as any] }; const builder = createQueryBuilder(config); @@ -327,6 +339,7 @@ describe('SearchQueryBuilder', () => { it('should build query with custom fields', () => { const config: SearchConfiguration = { + id: 'test-config', fields: ['field1', 'field2'], categories: [{ id: 'cat1', enabled: true } as any, { id: 'cat2', enabled: true } as any] }; @@ -340,6 +353,7 @@ describe('SearchQueryBuilder', () => { it('should build query with empty custom fields', () => { const config: SearchConfiguration = { + id: 'test-config', fields: [], categories: [{ id: 'cat1', enabled: true } as any, { id: 'cat2', enabled: true } as any] }; @@ -352,6 +366,7 @@ describe('SearchQueryBuilder', () => { it('should build query with custom filter queries', () => { const config: SearchConfiguration = { + id: 'test-config', categories: [{ id: 'cat1', enabled: true } as any] }; const builder = createQueryBuilder(config); @@ -364,6 +379,7 @@ describe('SearchQueryBuilder', () => { it('should build query with custom facet queries', () => { const config: SearchConfiguration = { + id: 'test-config', categories: [{ id: 'cat1', enabled: true } as any], facetQueries: { queries: [{ query: 'q1', label: 'q2', group: 'group-name' }] @@ -378,6 +394,7 @@ describe('SearchQueryBuilder', () => { it('should build query with custom facet fields', () => { const config: SearchConfiguration = { + id: 'test-config', categories: [{ id: 'cat1', enabled: true } as any], facetFields: { fields: [ @@ -404,6 +421,7 @@ describe('SearchQueryBuilder', () => { }; const config: SearchConfiguration = { + id: 'test-config', categories: [{ id: 'cat1', enabled: true } as any], facetFields: { fields: [ @@ -438,6 +456,7 @@ describe('SearchQueryBuilder', () => { it('should build query with custom facet intervals', () => { const config: SearchConfiguration = { + id: 'test-config', categories: [{ id: 'cat1', enabled: true } as any], facetIntervals: { intervals: [ @@ -482,6 +501,7 @@ describe('SearchQueryBuilder', () => { }; const config: SearchConfiguration = { + id: 'test-config', categories: [{ id: 'cat1', enabled: true } as any], facetIntervals: { intervals: [ @@ -518,6 +538,7 @@ describe('SearchQueryBuilder', () => { it('should build query with sorting', () => { const config: SearchConfiguration = { + id: 'test-config', fields: [], categories: [{ id: 'cat1', enabled: true } as any, { id: 'cat2', enabled: true } as any] }; @@ -533,6 +554,7 @@ describe('SearchQueryBuilder', () => { it('should use pagination settings', () => { const config: SearchConfiguration = { + id: 'test-config', categories: [{ id: 'cat1', enabled: true } as any] }; const builder = createQueryBuilder(config); @@ -548,6 +570,7 @@ describe('SearchQueryBuilder', () => { it('should build final request with user and custom queries', () => { const config: SearchConfiguration = { + id: 'test-config', categories: [{ id: 'cat1', enabled: true } as any] }; const builder = createQueryBuilder(config); @@ -581,6 +604,7 @@ describe('SearchQueryBuilder', () => { ]; const config: SearchConfiguration = { + id: 'test-config', categories: [{ id: 'cat1', enabled: true } as any] }; const builder = createQueryBuilder(config); @@ -598,6 +622,7 @@ describe('SearchQueryBuilder', () => { it('should use highlight in the queries', () => { const config: SearchConfiguration = { + id: 'test-config', highlight: { prefix: 'my-prefix', postfix: 'my-postfix', @@ -617,6 +642,7 @@ describe('SearchQueryBuilder', () => { it('should emit error event', () => { const config: SearchConfiguration = { + id: 'test-config', categories: [{ id: 'cat1', enabled: true } as any] }; const builder = createQueryBuilder(config); @@ -631,6 +657,7 @@ describe('SearchQueryBuilder', () => { it('should emit empty results on error', (done) => { const config: SearchConfiguration = { + id: 'test-config', categories: [{ id: 'cat1', enabled: true } as any] }; const builder = createQueryBuilder(config); @@ -656,6 +683,7 @@ describe('SearchQueryBuilder', () => { it('should fetch the include config from the app config', () => { const includeConfig = ['path', 'allowableOperations', 'properties']; const config: SearchConfiguration = { + id: 'test-config', include: includeConfig }; const builder = createQueryBuilder(config); @@ -706,10 +734,9 @@ describe('SearchQueryBuilder', () => { it('should encode query from filter raw params and update query params on executing query', (done) => { spyOn(router, 'navigate'); - const builder = createQueryBuilder(); - builder.userQuery = 'nuka cola quantum'; - builder.executed.subscribe(() => { - expect(builder.filterRawParams).toEqual({ userQuery: '(nuka cola quantum)' }); + service.userQuery = 'nuka cola quantum'; + service.filterRawParams = { userQuery: '(nuka cola quantum)' }; + service.executed.subscribe(() => { expect(router.navigate).toHaveBeenCalledWith([], { relativeTo: activatedRoute, queryParams: { q: 'eyJ1c2VyUXVlcnkiOiIobnVrYSBjb2xhIHF1YW50dW0pIn0=' }, @@ -717,17 +744,15 @@ describe('SearchQueryBuilder', () => { }); done(); }); - builder.execute(); + service.execute(); }); it('should encode query from filter raw params and update query params on navigating to search', async () => { spyOn(router, 'navigate'); - const builder = createQueryBuilder(); - await builder.navigateToSearch('test query', '/search'); + service.filterRawParams = { userQuery: '(test query)' }; + await service.navigateToSearch('test query', '/search'); - expect(builder.filterRawParams).toEqual({ userQuery: '(test query)' }); - expect(router.navigate).toHaveBeenCalledWith([], { - relativeTo: activatedRoute, + expect(router.navigate).toHaveBeenCalledWith(['/search'], { queryParams: { q: 'eyJ1c2VyUXVlcnkiOiIodGVzdCBxdWVyeSkifQ==' }, queryParamsHandling: 'merge' }); @@ -739,18 +764,21 @@ describe('SearchQueryBuilder', () => { beforeEach(() => { configs = [ { + id: 'config1', categories: [{ id: 'cat1', enabled: true } as any, { id: 'cat2', enabled: true } as any], filterQueries: [{ query: 'query1' }, { query: 'query2' }], name: 'config1', default: true }, { + id: 'config2', categories: [{ id: 'mouse', enabled: true } as any], filterQueries: [{ query: 'query1' }, { query: 'query2' }], name: 'config2', default: false }, { + id: 'config3', categories: [{ id: 'cat_and_mouse', enabled: true } as any], default: false } @@ -775,28 +803,28 @@ describe('SearchQueryBuilder', () => { it('should list available search form names', (done) => { builder.searchForms.subscribe((forms) => { expect(forms).toEqual([ - { index: 0, name: 'config1', default: true, selected: true }, - { index: 1, name: 'config2', default: false, selected: false }, - { index: 2, name: 'SEARCH.UNKNOWN_CONFIGURATION', default: false, selected: false } + { id: 'config1', index: 0, name: 'config1', default: true, selected: true }, + { id: 'config2', index: 1, name: 'config2', default: false, selected: false }, + { id: 'config3', index: 2, name: 'SEARCH.UNKNOWN_CONFIGURATION', default: false, selected: false } ]); done(); }); }); it('should allow the user switch the form', () => { - builder.updateSelectedConfiguration(1); + builder.updateSelectedConfiguration('config2'); expect(builder.categories.length).toBe(1); expect(builder.filterQueries.length).toBe(2); }); it('should keep the selected configuration value', (done) => { - builder.updateSelectedConfiguration(1); + builder.updateSelectedConfiguration('config2'); builder.searchForms.subscribe((forms) => { expect(forms).toEqual([ - { index: 0, name: 'config1', default: true, selected: false }, - { index: 1, name: 'config2', default: false, selected: true }, - { index: 2, name: 'SEARCH.UNKNOWN_CONFIGURATION', default: false, selected: false } + { id: 'config1', index: 0, name: 'config1', default: true, selected: false }, + { id: 'config2', index: 1, name: 'config2', default: false, selected: true }, + { id: 'config3', index: 2, name: 'SEARCH.UNKNOWN_CONFIGURATION', default: false, selected: false } ]); done(); });