mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2025-05-12 17:04:57 +00:00
ADW Saved Search (#10306)
Co-authored-by: MichalKinas <michal.kinas@hyland.com>
This commit is contained in:
parent
537b4f6605
commit
d1462253d0
@ -25,15 +25,16 @@ Represents an input with autocomplete options.
|
|||||||
|
|
||||||
### Properties
|
### Properties
|
||||||
|
|
||||||
| Name | Type | Default value | Description |
|
| Name | Type | Default value | Description |
|
||||||
|---------------------------|--------------------------|----|-----------------------------------------------------------------------------------------------------------------------------------------------|
|
|----------------------------|------------------------------------------------------------------------|----|-----------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
| autocompleteOptions | `AutocompleteOption[]` | [] | Options for autocomplete |
|
| autocompleteOptions | `AutocompleteOption[]` | [] | Options for autocomplete |
|
||||||
| onReset$ | [`Observable`](https://rxjs.dev/guide/observable)`<void>` | | Observable that will listen to any reset event causing component to clear the chips and input |
|
| preselectedOptions | `AutocompleteOption[]` | [] | Options which are selected from start |
|
||||||
| allowOnlyPredefinedValues | boolean | true | A flag that indicates whether it is possible to add a value not from the predefined ones |
|
| onReset$ | [`Observable`](https://rxjs.dev/guide/observable)`<void>` | | Observable that will listen to any reset event causing component to clear the chips and input |
|
||||||
| placeholder | string | 'SEARCH.FILTER.ACTIONS.ADD_OPTION' | Placeholder which should be displayed in input. |
|
| allowOnlyPredefinedValues | boolean | true | A flag that indicates whether it is possible to add a value not from the predefined ones |
|
||||||
| compareOption | (option1: AutocompleteOption, option2: AutocompleteOption) => boolean | | Function which is used to selected options with all options so it allows to detect which options are already selected. |
|
| placeholder | string | 'SEARCH.FILTER.ACTIONS.ADD_OPTION' | Placeholder which should be displayed in input. |
|
||||||
| formatChipValue | (option: string) => string | | Function which is used to format custom typed options. |
|
| compareOption | (option1: AutocompleteOption, option2: AutocompleteOption) => boolean | | Function which is used to selected options with all options so it allows to detect which options are already selected. |
|
||||||
| filter | (options: AutocompleteOption[], value: string) => AutocompleteOption[] | | Function which is used to filter out possible options from hint. By default it checks if option includes typed value and is case insensitive. |
|
| formatChipValue | (option: string) => string | | Function which is used to format custom typed options. |
|
||||||
|
| filter | (options: AutocompleteOption[], value: string) => AutocompleteOption[] | | Function which is used to filter out possible options from hint. By default it checks if option includes typed value and is case insensitive. |
|
||||||
|
|
||||||
### Events
|
### Events
|
||||||
|
|
||||||
|
@ -23,6 +23,8 @@ Stores information from all the custom search and faceted search widgets, compil
|
|||||||
- **buildQuery**(): `SearchRequest`<br/>
|
- **buildQuery**(): `SearchRequest`<br/>
|
||||||
Builds the current query.
|
Builds the current query.
|
||||||
- **Returns** `SearchRequest` - The finished query
|
- **Returns** `SearchRequest` - The finished query
|
||||||
|
- **encodeQuery**()<br/>
|
||||||
|
Encodes query shards stored in `filterRawParams` property.
|
||||||
- **execute**(queryBody?: `SearchRequest`)<br/>
|
- **execute**(queryBody?: `SearchRequest`)<br/>
|
||||||
Builds and executes the current query.
|
Builds and executes the current query.
|
||||||
- _queryBody:_ `SearchRequest` - (Optional)
|
- _queryBody:_ `SearchRequest` - (Optional)
|
||||||
@ -72,6 +74,11 @@ Stores information from all the custom search and faceted search widgets, compil
|
|||||||
|
|
||||||
- **Returns** [`SearchConfiguration`](../../../lib/content-services/src/lib/search/models/search-configuration.interface.ts) -
|
- **Returns** [`SearchConfiguration`](../../../lib/content-services/src/lib/search/models/search-configuration.interface.ts) -
|
||||||
|
|
||||||
|
- **navigateToSearch**(query: `string`, searchUrl: `string`) <br/>
|
||||||
|
Updates user query, executes existing search configuration, encodes the query and navigates to searchUrl.
|
||||||
|
- _query:_ `string` - The query to use as user query
|
||||||
|
- _searchUrl:_ `string` - Search url to navigate to
|
||||||
|
|
||||||
- **removeFilterQuery**(query: `string`)<br/>
|
- **removeFilterQuery**(query: `string`)<br/>
|
||||||
Removes an existing filter query.
|
Removes an existing filter query.
|
||||||
- _query:_ `string` - The query to remove
|
- _query:_ `string` - The query to remove
|
||||||
@ -93,6 +100,8 @@ Stores information from all the custom search and faceted search widgets, compil
|
|||||||
- **update**(queryBody?: `SearchRequest`)<br/>
|
- **update**(queryBody?: `SearchRequest`)<br/>
|
||||||
Builds the current query and triggers the `updated` event.
|
Builds the current query and triggers the `updated` event.
|
||||||
- _queryBody:_ `SearchRequest` - (Optional)
|
- _queryBody:_ `SearchRequest` - (Optional)
|
||||||
|
- **updateSearchQueryParams**() <br/>
|
||||||
|
Encodes the query and navigates to existing search route adding encoded query as a search param.
|
||||||
- **updateSelectedConfiguration**(index: `number`)<br/>
|
- **updateSelectedConfiguration**(index: `number`)<br/>
|
||||||
|
|
||||||
- _index:_ `number` -
|
- _index:_ `number` -
|
||||||
|
64
docs/core/services/saved-searches.service.md
Normal file
64
docs/core/services/saved-searches.service.md
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
|
||||||
|
# Saved Searches Service
|
||||||
|
|
||||||
|
Manages operations related to saving and retrieving user-defined searches in the Alfresco Process Services (APS) environment.
|
||||||
|
|
||||||
|
## Class members
|
||||||
|
|
||||||
|
### Properties
|
||||||
|
|
||||||
|
- **savedSearches$**: [`ReplaySubject`](https://rxjs.dev/api/index/class/ReplaySubject)`<SavedSearch[]>`<br/>
|
||||||
|
Stores the list of saved searches and emits new value whenever there is a change.
|
||||||
|
|
||||||
|
### Methods
|
||||||
|
|
||||||
|
#### getSavedSearches(): [`Observable`](https://rxjs.dev/api/index/class/Observable)`<SavedSearch[]>`
|
||||||
|
|
||||||
|
Fetches the file with list of saved searches either from a locally cached node ID or by querying the APS server. Then it reads the file and maps JSON objects into SavedSearches
|
||||||
|
|
||||||
|
- **Returns**:
|
||||||
|
- [`Observable`](https://rxjs.dev/api/index/class/Observable)`<SavedSearch[]>` - An observable that emits the list of saved searches.
|
||||||
|
|
||||||
|
#### saveSearch(newSaveSearch: Pick<SavedSearch, 'name' | 'description' | 'encodedUrl'>): [`Observable`](https://rxjs.dev/api/index/class/Observable)`<NodeEntry>`
|
||||||
|
|
||||||
|
Saves a new search and updates the existing list of saved searches stored in file and in service property savedSearches$.
|
||||||
|
|
||||||
|
- **Parameters**:
|
||||||
|
- `newSaveSearch`: An object containing the `name`, `description`, and `encodedUrl` of the new search.
|
||||||
|
|
||||||
|
- **Returns**:
|
||||||
|
- [`Observable`](https://rxjs.dev/api/index/class/Observable)`<NodeEntry>` - An observable that emits the response of the node entry after saving.
|
||||||
|
|
||||||
|
### Usage Examples
|
||||||
|
|
||||||
|
#### Fetching Saved Searches
|
||||||
|
|
||||||
|
The following example shows how to fetch saved searches:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
this.savedSearchService.getSavedSearches().subscribe((searches: SavedSearch[]) => {
|
||||||
|
console.log('Saved searches:', searches);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Saving a New Search
|
||||||
|
|
||||||
|
To save a new search:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const newSearch = { name: 'New Search', description: 'A sample search', encodedUrl: 'url3' };
|
||||||
|
this.savedSearchService.saveSearch(newSearch).subscribe((response) => {
|
||||||
|
console.log('Saved new search:', response);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Creating Saved Searches Node
|
||||||
|
|
||||||
|
When the saved searches file does not exist, it will be created:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
this.savedSearchService.createSavedSearchesNode('parent-node-id').subscribe((node) => {
|
||||||
|
console.log('Created saved-searches.json node:', node);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
@ -0,0 +1,23 @@
|
|||||||
|
/*!
|
||||||
|
* @license
|
||||||
|
* Copyright © 2005-2024 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface SavedSearch {
|
||||||
|
name: string;
|
||||||
|
description?: string;
|
||||||
|
encodedUrl: string;
|
||||||
|
order: number;
|
||||||
|
}
|
@ -24,6 +24,7 @@ export * from './services/nodes-api.service';
|
|||||||
export * from './services/discovery-api.service';
|
export * from './services/discovery-api.service';
|
||||||
export * from './services/people-content.service';
|
export * from './services/people-content.service';
|
||||||
export * from './services/content.service';
|
export * from './services/content.service';
|
||||||
|
export * from './services/saved-searches.service';
|
||||||
|
|
||||||
export * from './events/file.event';
|
export * from './events/file.event';
|
||||||
|
|
||||||
@ -36,4 +37,5 @@ export * from './models/permissions.enum';
|
|||||||
export * from './models/allowable-operations.enum';
|
export * from './models/allowable-operations.enum';
|
||||||
|
|
||||||
export * from './interfaces/search-configuration.interface';
|
export * from './interfaces/search-configuration.interface';
|
||||||
|
export * from './interfaces/saved-search.interface';
|
||||||
export * from './mocks/ecm-user.service.mock';
|
export * from './mocks/ecm-user.service.mock';
|
||||||
|
@ -0,0 +1,159 @@
|
|||||||
|
/*!
|
||||||
|
* @license
|
||||||
|
* Copyright © 2005-2024 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 { AlfrescoApiService } from '../../services/alfresco-api.service';
|
||||||
|
import { NodeEntry } from '@alfresco/js-api';
|
||||||
|
import { SavedSearchesService } from './saved-searches.service';
|
||||||
|
import { AlfrescoApiServiceMock } from '@alfresco/adf-content-services';
|
||||||
|
import { HttpClientTestingModule } from '@angular/common/http/testing';
|
||||||
|
import { AuthenticationService } from '@alfresco/adf-core';
|
||||||
|
import { Subject } from 'rxjs';
|
||||||
|
|
||||||
|
describe('SavedSearchesService', () => {
|
||||||
|
let service: SavedSearchesService;
|
||||||
|
let authService: AuthenticationService;
|
||||||
|
let testUserName: string;
|
||||||
|
|
||||||
|
const testNodeId = 'test-node-id';
|
||||||
|
const SAVED_SEARCHES_NODE_ID = 'saved-searches-node-id__';
|
||||||
|
const SAVED_SEARCHES_CONTENT = JSON.stringify([
|
||||||
|
{ name: 'Search 1', description: 'Description 1', encodedUrl: 'url1', order: 0 },
|
||||||
|
{ name: 'Search 2', description: 'Description 2', encodedUrl: 'url2', order: 1 }
|
||||||
|
]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a stub with Promise returning a Blob
|
||||||
|
*
|
||||||
|
* @returns Promise with Blob
|
||||||
|
*/
|
||||||
|
function createBlob() {
|
||||||
|
return Promise.resolve(new Blob([SAVED_SEARCHES_CONTENT]));
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
testUserName = 'test-user';
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [HttpClientTestingModule],
|
||||||
|
providers: [
|
||||||
|
{ provide: AlfrescoApiService, useClass: AlfrescoApiServiceMock },
|
||||||
|
{ provide: AuthenticationService, useValue: { getUsername: () => {}, onLogin: new Subject() } },
|
||||||
|
SavedSearchesService
|
||||||
|
]
|
||||||
|
});
|
||||||
|
service = TestBed.inject(SavedSearchesService);
|
||||||
|
authService = TestBed.inject(AuthenticationService);
|
||||||
|
spyOn(service.nodesApi, 'getNode').and.callFake(() => Promise.resolve({ entry: { id: testNodeId } } as NodeEntry));
|
||||||
|
spyOn(service.searchApi, 'search').and.callFake(() => Promise.resolve({ list: { entries: [] } }));
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
localStorage.removeItem(SAVED_SEARCHES_NODE_ID + testUserName);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should retrieve saved searches from the saved-searches.json file', (done) => {
|
||||||
|
spyOn(authService, 'getUsername').and.callFake(() => testUserName);
|
||||||
|
spyOn(localStorage, 'getItem').and.callFake(() => testNodeId);
|
||||||
|
spyOn(service.nodesApi, 'getNodeContent').and.callFake(() => createBlob());
|
||||||
|
service.innit();
|
||||||
|
|
||||||
|
service.getSavedSearches().subscribe((searches) => {
|
||||||
|
expect(localStorage.getItem).toHaveBeenCalledWith(SAVED_SEARCHES_NODE_ID + testUserName);
|
||||||
|
expect(service.nodesApi.getNodeContent).toHaveBeenCalledWith(testNodeId);
|
||||||
|
expect(searches.length).toBe(2);
|
||||||
|
expect(searches[0].name).toBe('Search 1');
|
||||||
|
expect(searches[1].name).toBe('Search 2');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create saved-searches.json file if it does not exist', (done) => {
|
||||||
|
spyOn(authService, 'getUsername').and.callFake(() => testUserName);
|
||||||
|
spyOn(service.nodesApi, 'createNode').and.callFake(() => Promise.resolve({ entry: { id: 'new-node-id' } }));
|
||||||
|
spyOn(service.nodesApi, 'getNodeContent').and.callFake(() => Promise.resolve(new Blob([''])));
|
||||||
|
service.innit();
|
||||||
|
|
||||||
|
service.getSavedSearches().subscribe((searches) => {
|
||||||
|
expect(service.nodesApi.getNode).toHaveBeenCalledWith('-my-');
|
||||||
|
expect(service.searchApi.search).toHaveBeenCalled();
|
||||||
|
expect(service.nodesApi.createNode).toHaveBeenCalledWith(testNodeId, jasmine.objectContaining({ name: 'saved-searches.json' }));
|
||||||
|
expect(searches.length).toBe(0);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should save a new search', (done) => {
|
||||||
|
spyOn(authService, 'getUsername').and.callFake(() => testUserName);
|
||||||
|
const nodeId = 'saved-searches-node-id';
|
||||||
|
spyOn(localStorage, 'getItem').and.callFake(() => nodeId);
|
||||||
|
spyOn(service.nodesApi, 'getNodeContent').and.callFake(() => createBlob());
|
||||||
|
const newSearch = { name: 'Search 3', description: 'Description 3', encodedUrl: 'url3' };
|
||||||
|
spyOn(service.nodesApi, 'updateNodeContent').and.callFake(() => Promise.resolve({ entry: {} } as NodeEntry));
|
||||||
|
service.innit();
|
||||||
|
|
||||||
|
service.saveSearch(newSearch).subscribe(() => {
|
||||||
|
expect(service.nodesApi.updateNodeContent).toHaveBeenCalledWith(nodeId, jasmine.any(String));
|
||||||
|
expect(service.savedSearches$).toBeDefined();
|
||||||
|
service.savedSearches$.subscribe((searches) => {
|
||||||
|
expect(searches.length).toBe(3);
|
||||||
|
expect(searches[2].name).toBe('Search 3');
|
||||||
|
expect(searches[2].order).toBe(2);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should emit initial saved searches on subscription', (done) => {
|
||||||
|
const nodeId = 'saved-searches-node-id';
|
||||||
|
spyOn(localStorage, 'getItem').and.returnValue(nodeId);
|
||||||
|
spyOn(service.nodesApi, 'getNodeContent').and.returnValue(createBlob());
|
||||||
|
service.innit();
|
||||||
|
|
||||||
|
service.savedSearches$.pipe().subscribe((searches) => {
|
||||||
|
expect(searches.length).toBe(2);
|
||||||
|
expect(searches[0].name).toBe('Search 1');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
service.getSavedSearches().subscribe();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should emit updated saved searches after saving a new search', (done) => {
|
||||||
|
spyOn(authService, 'getUsername').and.callFake(() => testUserName);
|
||||||
|
spyOn(localStorage, 'getItem').and.callFake(() => testNodeId);
|
||||||
|
spyOn(service.nodesApi, 'getNodeContent').and.callFake(() => createBlob());
|
||||||
|
const newSearch = { name: 'Search 3', description: 'Description 3', encodedUrl: 'url3' };
|
||||||
|
spyOn(service.nodesApi, 'updateNodeContent').and.callFake(() => Promise.resolve({ entry: {} } as NodeEntry));
|
||||||
|
service.innit();
|
||||||
|
|
||||||
|
let emissionCount = 0;
|
||||||
|
|
||||||
|
service.savedSearches$.subscribe((searches) => {
|
||||||
|
emissionCount++;
|
||||||
|
if (emissionCount === 1) {
|
||||||
|
expect(searches.length).toBe(2);
|
||||||
|
}
|
||||||
|
if (emissionCount === 2) {
|
||||||
|
expect(searches.length).toBe(3);
|
||||||
|
expect(searches[2].name).toBe('Search 3');
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
service.saveSearch(newSearch).subscribe();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,158 @@
|
|||||||
|
/*!
|
||||||
|
* @license
|
||||||
|
* Copyright © 2005-2024 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 { NodesApi, NodeEntry, SearchApi, SEARCH_LANGUAGE, ResultSetPaging } from '@alfresco/js-api';
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { Observable, of, from, ReplaySubject, throwError } from 'rxjs';
|
||||||
|
import { catchError, concatMap, first, map, switchMap, take, tap } from 'rxjs/operators';
|
||||||
|
import { AlfrescoApiService } from '../../services/alfresco-api.service';
|
||||||
|
import { SavedSearch } from '../interfaces/saved-search.interface';
|
||||||
|
import { AuthenticationService } from '@alfresco/adf-core';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class SavedSearchesService {
|
||||||
|
private _searchApi: SearchApi;
|
||||||
|
get searchApi(): SearchApi {
|
||||||
|
this._searchApi = this._searchApi ?? new SearchApi(this.apiService.getInstance());
|
||||||
|
return this._searchApi;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _nodesApi: NodesApi;
|
||||||
|
get nodesApi(): NodesApi {
|
||||||
|
this._nodesApi = this._nodesApi ?? new NodesApi(this.apiService.getInstance());
|
||||||
|
return this._nodesApi;
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly savedSearches$ = new ReplaySubject<SavedSearch[]>(1);
|
||||||
|
|
||||||
|
private savedSearchFileNodeId: string;
|
||||||
|
private currentUserLocalStorageKey: string;
|
||||||
|
private createFileAttempt = false;
|
||||||
|
|
||||||
|
constructor(private readonly apiService: AlfrescoApiService, private readonly authService: AuthenticationService) {}
|
||||||
|
|
||||||
|
innit(): void {
|
||||||
|
this.fetchSavedSearches();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a list of saved searches by user.
|
||||||
|
*
|
||||||
|
* @returns SavedSearch list containing user saved searches
|
||||||
|
*/
|
||||||
|
getSavedSearches(): Observable<SavedSearch[]> {
|
||||||
|
return this.getSavedSearchesNodeId().pipe(
|
||||||
|
concatMap(() => {
|
||||||
|
return from(
|
||||||
|
this.nodesApi.getNodeContent(this.savedSearchFileNodeId).then((content) => this.mapFileContentToSavedSearches(content))
|
||||||
|
).pipe(
|
||||||
|
catchError((error) => {
|
||||||
|
if (!this.createFileAttempt) {
|
||||||
|
this.createFileAttempt = true;
|
||||||
|
localStorage.removeItem(this.getLocalStorageKey());
|
||||||
|
return this.getSavedSearches();
|
||||||
|
}
|
||||||
|
return throwError(() => error);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a list of saved searches by user.
|
||||||
|
*
|
||||||
|
* @param newSaveSearch object { name: string, description: string, encodedUrl: string }
|
||||||
|
* @returns Adds and saves search also updating current saved search state
|
||||||
|
*/
|
||||||
|
saveSearch(newSaveSearch: Pick<SavedSearch, 'name' | 'description' | 'encodedUrl'>): Observable<NodeEntry> {
|
||||||
|
return this.getSavedSearches().pipe(
|
||||||
|
take(1),
|
||||||
|
switchMap((savedSearches: Array<SavedSearch>) => {
|
||||||
|
const updatedSavedSearches = [...savedSearches, { ...newSaveSearch, order: savedSearches.length }];
|
||||||
|
return from(this.nodesApi.updateNodeContent(this.savedSearchFileNodeId, JSON.stringify(updatedSavedSearches))).pipe(
|
||||||
|
tap(() => this.savedSearches$.next(updatedSavedSearches))
|
||||||
|
);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private getSavedSearchesNodeId(): Observable<string> {
|
||||||
|
const localStorageKey = this.getLocalStorageKey();
|
||||||
|
if (this.currentUserLocalStorageKey && this.currentUserLocalStorageKey !== localStorageKey) {
|
||||||
|
this.savedSearches$.next([]);
|
||||||
|
}
|
||||||
|
this.currentUserLocalStorageKey = localStorageKey;
|
||||||
|
let savedSearchesNodeId = localStorage.getItem(this.currentUserLocalStorageKey) ?? '';
|
||||||
|
if (savedSearchesNodeId === '') {
|
||||||
|
return from(this.nodesApi.getNode('-my-')).pipe(
|
||||||
|
first(),
|
||||||
|
map((node) => node.entry.id),
|
||||||
|
concatMap((parentNodeId) =>
|
||||||
|
from(
|
||||||
|
this.searchApi.search({
|
||||||
|
query: {
|
||||||
|
language: SEARCH_LANGUAGE.AFTS,
|
||||||
|
query: `cm:name:"saved-searches.json" AND PARENT:"${parentNodeId}"`
|
||||||
|
}
|
||||||
|
})
|
||||||
|
).pipe(
|
||||||
|
first(),
|
||||||
|
concatMap((searchResult: ResultSetPaging) => {
|
||||||
|
if (searchResult.list.entries.length > 0) {
|
||||||
|
savedSearchesNodeId = searchResult.list.entries[0].entry.id;
|
||||||
|
localStorage.setItem(this.currentUserLocalStorageKey, savedSearchesNodeId);
|
||||||
|
} else {
|
||||||
|
return this.createSavedSearchesNode(parentNodeId).pipe(
|
||||||
|
first(),
|
||||||
|
map((node) => {
|
||||||
|
localStorage.setItem(this.currentUserLocalStorageKey, node.entry.id);
|
||||||
|
return node.entry.id;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
this.savedSearchFileNodeId = savedSearchesNodeId;
|
||||||
|
return savedSearchesNodeId;
|
||||||
|
})
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
this.savedSearchFileNodeId = savedSearchesNodeId;
|
||||||
|
return of(savedSearchesNodeId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private createSavedSearchesNode(parentNodeId: string): Observable<NodeEntry> {
|
||||||
|
return from(this.nodesApi.createNode(parentNodeId, { name: 'saved-searches.json', nodeType: 'cm:content' }));
|
||||||
|
}
|
||||||
|
|
||||||
|
private async mapFileContentToSavedSearches(blob: Blob): Promise<Array<SavedSearch>> {
|
||||||
|
return blob.text().then((content) => (content ? JSON.parse(content) : []));
|
||||||
|
}
|
||||||
|
|
||||||
|
private getLocalStorageKey(): string {
|
||||||
|
return `saved-searches-node-id__${this.authService.getUsername()}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private fetchSavedSearches(): void {
|
||||||
|
this.getSavedSearches()
|
||||||
|
.pipe(take(1))
|
||||||
|
.subscribe((searches) => this.savedSearches$.next(searches));
|
||||||
|
}
|
||||||
|
}
|
@ -174,7 +174,7 @@ describe('ContentNodeSelectorPanelComponent', () => {
|
|||||||
tick(debounceSearch);
|
tick(debounceSearch);
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
expect(searchSpy).toHaveBeenCalledWith(mockSearchRequest);
|
expect(searchSpy).toHaveBeenCalledWith(false, mockSearchRequest);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should NOT perform a search and clear the results when the search request gets updated and it is NOT defined', async () => {
|
it('should NOT perform a search and clear the results when the search request gets updated and it is NOT defined', async () => {
|
||||||
@ -212,7 +212,7 @@ describe('ContentNodeSelectorPanelComponent', () => {
|
|||||||
tick(debounceSearch);
|
tick(debounceSearch);
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
expect(searchSpy).toHaveBeenCalledWith(mockSearchRequest);
|
expect(searchSpy).toHaveBeenCalledWith(false, mockSearchRequest);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should the query include the show files filterQuery', fakeAsync(() => {
|
it('should the query include the show files filterQuery', fakeAsync(() => {
|
||||||
@ -227,7 +227,7 @@ describe('ContentNodeSelectorPanelComponent', () => {
|
|||||||
tick(debounceSearch);
|
tick(debounceSearch);
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
expect(searchSpy).toHaveBeenCalledWith(expectedRequest);
|
expect(searchSpy).toHaveBeenCalledWith(false, expectedRequest);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should reset the currently chosen node in case of starting a new search', fakeAsync(() => {
|
it('should reset the currently chosen node in case of starting a new search', fakeAsync(() => {
|
||||||
@ -261,7 +261,7 @@ describe('ContentNodeSelectorPanelComponent', () => {
|
|||||||
expectedRequest.filterQueries = [{ query: `ANCESTOR:'workspace://SpacesStore/namek'` }];
|
expectedRequest.filterQueries = [{ query: `ANCESTOR:'workspace://SpacesStore/namek'` }];
|
||||||
|
|
||||||
expect(searchSpy.calls.count()).toBe(2);
|
expect(searchSpy.calls.count()).toBe(2);
|
||||||
expect(searchSpy).toHaveBeenCalledWith(expectedRequest);
|
expect(searchSpy).toHaveBeenCalledWith(false, expectedRequest);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should create the query with the right parameters on changing the site selectBox value from a custom dropdown menu', fakeAsync(() => {
|
it('should create the query with the right parameters on changing the site selectBox value from a custom dropdown menu', fakeAsync(() => {
|
||||||
@ -286,8 +286,8 @@ describe('ContentNodeSelectorPanelComponent', () => {
|
|||||||
|
|
||||||
expect(searchSpy).toHaveBeenCalled();
|
expect(searchSpy).toHaveBeenCalled();
|
||||||
expect(searchSpy.calls.count()).toBe(2);
|
expect(searchSpy.calls.count()).toBe(2);
|
||||||
expect(searchSpy).toHaveBeenCalledWith(mockSearchRequest);
|
expect(searchSpy).toHaveBeenCalledWith(false, mockSearchRequest);
|
||||||
expect(searchSpy).toHaveBeenCalledWith(expectedRequest);
|
expect(searchSpy).toHaveBeenCalledWith(false, expectedRequest);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should get the corresponding node ids on search when a known alias is selected from dropdown', fakeAsync(() => {
|
it('should get the corresponding node ids on search when a known alias is selected from dropdown', fakeAsync(() => {
|
||||||
@ -407,7 +407,7 @@ describe('ContentNodeSelectorPanelComponent', () => {
|
|||||||
const expectedRequest = mockSearchRequest;
|
const expectedRequest = mockSearchRequest;
|
||||||
expectedRequest.filterQueries = [{ query: `ANCESTOR:'workspace://SpacesStore/my-root-id'` }];
|
expectedRequest.filterQueries = [{ query: `ANCESTOR:'workspace://SpacesStore/my-root-id'` }];
|
||||||
|
|
||||||
expect(searchSpy).toHaveBeenCalledWith(expectedRequest);
|
expect(searchSpy).toHaveBeenCalledWith(false, expectedRequest);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should emit showingSearch event with true while searching', async () => {
|
it('should emit showingSearch event with true while searching', async () => {
|
||||||
@ -416,7 +416,7 @@ describe('ContentNodeSelectorPanelComponent', () => {
|
|||||||
spyOn(customResourcesService, 'hasCorrespondingNodeIds').and.returnValue(true);
|
spyOn(customResourcesService, 'hasCorrespondingNodeIds').and.returnValue(true);
|
||||||
const showingSearchSpy = spyOn(component.showingSearch, 'emit');
|
const showingSearchSpy = spyOn(component.showingSearch, 'emit');
|
||||||
|
|
||||||
await searchQueryBuilderService.execute({ query: { query: 'search' } });
|
await searchQueryBuilderService.execute(true, { query: { query: 'search' } });
|
||||||
|
|
||||||
triggerSearchResults(fakeResultSetPaging);
|
triggerSearchResults(fakeResultSetPaging);
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
@ -460,7 +460,7 @@ describe('ContentNodeSelectorPanelComponent', () => {
|
|||||||
searchQueryBuilderService.update();
|
searchQueryBuilderService.update();
|
||||||
getCorrespondingNodeIdsSpy.and.throwError('Failed');
|
getCorrespondingNodeIdsSpy.and.throwError('Failed');
|
||||||
const showingSearchSpy = spyOn(component.showingSearch, 'emit');
|
const showingSearchSpy = spyOn(component.showingSearch, 'emit');
|
||||||
await searchQueryBuilderService.execute({ query: { query: 'search' } });
|
await searchQueryBuilderService.execute(true, { query: { query: 'search' } });
|
||||||
|
|
||||||
triggerSearchResults(fakeResultSetPaging);
|
triggerSearchResults(fakeResultSetPaging);
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
@ -479,7 +479,7 @@ describe('ContentNodeSelectorPanelComponent', () => {
|
|||||||
const expectedRequest = mockSearchRequest;
|
const expectedRequest = mockSearchRequest;
|
||||||
expectedRequest.filterQueries = [{ query: `ANCESTOR:'workspace://SpacesStore/my-site-id'` }];
|
expectedRequest.filterQueries = [{ query: `ANCESTOR:'workspace://SpacesStore/my-site-id'` }];
|
||||||
|
|
||||||
expect(searchSpy).toHaveBeenCalledWith(expectedRequest);
|
expect(searchSpy).toHaveBeenCalledWith(false, expectedRequest);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should restrict the breadcrumb to the currentFolderId in case restrictedRoot is true', async () => {
|
it('should restrict the breadcrumb to the currentFolderId in case restrictedRoot is true', async () => {
|
||||||
|
@ -343,7 +343,7 @@ export class ContentNodeSelectorPanelComponent implements OnInit, OnDestroy {
|
|||||||
if (searchRequest) {
|
if (searchRequest) {
|
||||||
this.hasValidQuery = true;
|
this.hasValidQuery = true;
|
||||||
this.prepareDialogForNewSearch(searchRequest);
|
this.prepareDialogForNewSearch(searchRequest);
|
||||||
this.queryBuilderService.execute(searchRequest);
|
this.queryBuilderService.execute(false, searchRequest);
|
||||||
} else {
|
} else {
|
||||||
this.hasValidQuery = false;
|
this.hasValidQuery = false;
|
||||||
this.resetFolderToShow();
|
this.resetFolderToShow();
|
||||||
|
@ -22,7 +22,7 @@ import { zipNode, downloadEntry } from './download-zip-data.mock';
|
|||||||
|
|
||||||
export class AlfrescoApiServiceMock {
|
export class AlfrescoApiServiceMock {
|
||||||
nodeUpdated = new Subject<Node>();
|
nodeUpdated = new Subject<Node>();
|
||||||
alfrescoApiInitialized: ReplaySubject<boolean> = new ReplaySubject(1);
|
alfrescoApiInitialized = new ReplaySubject<boolean>(1);
|
||||||
alfrescoApi = new AlfrescoApiMock();
|
alfrescoApi = new AlfrescoApiMock();
|
||||||
|
|
||||||
load() {}
|
load() {}
|
||||||
|
@ -24,6 +24,7 @@ import { HarnessLoader, TestKey } from '@angular/cdk/testing';
|
|||||||
import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
|
import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
|
||||||
import { MatCheckboxHarness } from '@angular/material/checkbox/testing';
|
import { MatCheckboxHarness } from '@angular/material/checkbox/testing';
|
||||||
import { MatButtonHarness } from '@angular/material/button/testing';
|
import { MatButtonHarness } from '@angular/material/button/testing';
|
||||||
|
import { ReplaySubject } from 'rxjs';
|
||||||
|
|
||||||
describe('SearchCheckListComponent', () => {
|
describe('SearchCheckListComponent', () => {
|
||||||
let loader: HarnessLoader;
|
let loader: HarnessLoader;
|
||||||
@ -37,6 +38,13 @@ describe('SearchCheckListComponent', () => {
|
|||||||
fixture = TestBed.createComponent(SearchCheckListComponent);
|
fixture = TestBed.createComponent(SearchCheckListComponent);
|
||||||
component = fixture.componentInstance;
|
component = fixture.componentInstance;
|
||||||
loader = TestbedHarnessEnvironment.loader(fixture);
|
loader = TestbedHarnessEnvironment.loader(fixture);
|
||||||
|
|
||||||
|
component.context = {
|
||||||
|
queryFragments: {},
|
||||||
|
filterRawParams: {},
|
||||||
|
populateFilters: new ReplaySubject(1),
|
||||||
|
update: jasmine.createSpy()
|
||||||
|
} as any;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should setup options from settings', () => {
|
it('should setup options from settings', () => {
|
||||||
@ -87,22 +95,17 @@ describe('SearchCheckListComponent', () => {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
component.id = 'checklist';
|
component.id = 'checklist';
|
||||||
component.context = {
|
|
||||||
queryFragments: {},
|
|
||||||
update: () => {}
|
|
||||||
} as any;
|
|
||||||
|
|
||||||
component.ngOnInit();
|
component.ngOnInit();
|
||||||
|
|
||||||
spyOn(component.context, 'update').and.stub();
|
|
||||||
|
|
||||||
component.changeHandler({ checked: true } as any, component.options.items[0]);
|
component.changeHandler({ checked: true } as any, component.options.items[0]);
|
||||||
|
|
||||||
expect(component.context.queryFragments[component.id]).toEqual(`TYPE:'cm:folder'`);
|
expect(component.context.queryFragments[component.id]).toEqual(`TYPE:'cm:folder'`);
|
||||||
|
expect(component.context.filterRawParams[component.id]).toEqual([`TYPE:'cm:folder'`]);
|
||||||
|
|
||||||
component.changeHandler({ checked: true } as any, component.options.items[1]);
|
component.changeHandler({ checked: true } as any, component.options.items[1]);
|
||||||
|
|
||||||
expect(component.context.queryFragments[component.id]).toEqual(`TYPE:'cm:folder' OR TYPE:'cm:content'`);
|
expect(component.context.queryFragments[component.id]).toEqual(`TYPE:'cm:folder' OR TYPE:'cm:content'`);
|
||||||
|
expect(component.context.filterRawParams[component.id]).toEqual([`TYPE:'cm:folder'`, `TYPE:'cm:content'`]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should reset selected boxes', () => {
|
it('should reset selected boxes', () => {
|
||||||
@ -119,13 +122,8 @@ describe('SearchCheckListComponent', () => {
|
|||||||
|
|
||||||
it('should update query builder on reset', () => {
|
it('should update query builder on reset', () => {
|
||||||
component.id = 'checklist';
|
component.id = 'checklist';
|
||||||
component.context = {
|
component.context.queryFragments[component.id] = 'query';
|
||||||
queryFragments: {
|
component.context.filterRawParams[component.id] = 'test';
|
||||||
checklist: 'query'
|
|
||||||
},
|
|
||||||
update: () => {}
|
|
||||||
} as any;
|
|
||||||
spyOn(component.context, 'update').and.stub();
|
|
||||||
|
|
||||||
component.ngOnInit();
|
component.ngOnInit();
|
||||||
component.options = new SearchFilterList<SearchListOption>([
|
component.options = new SearchFilterList<SearchListOption>([
|
||||||
@ -137,17 +135,13 @@ describe('SearchCheckListComponent', () => {
|
|||||||
|
|
||||||
expect(component.context.update).toHaveBeenCalled();
|
expect(component.context.update).toHaveBeenCalled();
|
||||||
expect(component.context.queryFragments[component.id]).toBe('');
|
expect(component.context.queryFragments[component.id]).toBe('');
|
||||||
|
expect(component.context.filterRawParams[component.id]).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Pagination', () => {
|
describe('Pagination', () => {
|
||||||
it('should show 5 items when pageSize not defined', async () => {
|
it('should show 5 items when pageSize not defined', async () => {
|
||||||
component.id = 'checklist';
|
component.id = 'checklist';
|
||||||
component.context = {
|
component.context.queryFragments[component.id] = 'query';
|
||||||
queryFragments: {
|
|
||||||
checklist: 'query'
|
|
||||||
},
|
|
||||||
update: () => {}
|
|
||||||
} as any;
|
|
||||||
component.settings = { options: sizeOptions } as any;
|
component.settings = { options: sizeOptions } as any;
|
||||||
|
|
||||||
component.ngOnInit();
|
component.ngOnInit();
|
||||||
@ -162,12 +156,7 @@ describe('SearchCheckListComponent', () => {
|
|||||||
|
|
||||||
it('should show all items when pageSize is high', async () => {
|
it('should show all items when pageSize is high', async () => {
|
||||||
component.id = 'checklist';
|
component.id = 'checklist';
|
||||||
component.context = {
|
component.context.queryFragments[component.id] = 'query';
|
||||||
queryFragments: {
|
|
||||||
checklist: 'query'
|
|
||||||
},
|
|
||||||
update: () => {}
|
|
||||||
} as any;
|
|
||||||
component.settings = { pageSize: 15, options: sizeOptions } as any;
|
component.settings = { pageSize: 15, options: sizeOptions } as any;
|
||||||
component.ngOnInit();
|
component.ngOnInit();
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
@ -182,12 +171,7 @@ describe('SearchCheckListComponent', () => {
|
|||||||
|
|
||||||
it('should able to check/reset the checkbox', async () => {
|
it('should able to check/reset the checkbox', async () => {
|
||||||
component.id = 'checklist';
|
component.id = 'checklist';
|
||||||
component.context = {
|
component.context.queryFragments[component.id] = 'query';
|
||||||
queryFragments: {
|
|
||||||
checklist: 'query'
|
|
||||||
},
|
|
||||||
update: () => {}
|
|
||||||
} as any;
|
|
||||||
component.settings = { options: sizeOptions } as any;
|
component.settings = { options: sizeOptions } as any;
|
||||||
spyOn(component, 'submitValues').and.stub();
|
spyOn(component, 'submitValues').and.stub();
|
||||||
component.ngOnInit();
|
component.ngOnInit();
|
||||||
@ -212,10 +196,7 @@ describe('SearchCheckListComponent', () => {
|
|||||||
{ name: 'Document', value: `TYPE:'cm:content'`, checked: false }
|
{ name: 'Document', value: `TYPE:'cm:content'`, checked: false }
|
||||||
]);
|
]);
|
||||||
component.startValue = `TYPE:'cm:folder'`;
|
component.startValue = `TYPE:'cm:folder'`;
|
||||||
component.context = {
|
component.context.queryFragments[component.id] = 'query';
|
||||||
queryFragments: {},
|
|
||||||
update: jasmine.createSpy()
|
|
||||||
} as any;
|
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
expect(component.context.queryFragments[component.id]).toBe(`TYPE:'cm:folder'`);
|
expect(component.context.queryFragments[component.id]).toBe(`TYPE:'cm:folder'`);
|
||||||
@ -229,15 +210,31 @@ describe('SearchCheckListComponent', () => {
|
|||||||
{ name: 'Document', value: `TYPE:'cm:content'`, checked: false }
|
{ name: 'Document', value: `TYPE:'cm:content'`, checked: false }
|
||||||
]);
|
]);
|
||||||
component.startValue = undefined;
|
component.startValue = undefined;
|
||||||
component.context = {
|
component.context.queryFragments[component.id] = `TYPE:'cm:folder'`;
|
||||||
queryFragments: {
|
|
||||||
checkList: `TYPE:'cm:folder'`
|
|
||||||
},
|
|
||||||
update: jasmine.createSpy()
|
|
||||||
} as any;
|
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
expect(component.context.queryFragments[component.id]).toBe('');
|
expect(component.context.queryFragments[component.id]).toBe('');
|
||||||
expect(component.context.update).not.toHaveBeenCalled();
|
expect(component.context.update).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should populate filter state when populate filters event has been observed', () => {
|
||||||
|
component.id = 'checkList';
|
||||||
|
component.options = new SearchFilterList<SearchListOption>([
|
||||||
|
{ name: 'Folder', value: `TYPE:'cm:folder'`, checked: false },
|
||||||
|
{ name: 'Document', value: `TYPE:'cm:content'`, checked: false }
|
||||||
|
]);
|
||||||
|
component.startValue = undefined;
|
||||||
|
component.context.filterLoaded = new ReplaySubject(1);
|
||||||
|
spyOn(component.context.filterLoaded, 'next').and.stub();
|
||||||
|
spyOn(component.displayValue$, 'next').and.stub();
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
component.context.populateFilters.next({ checkList: [`TYPE:'cm:content'`] });
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(component.options.items[1].checked).toBeTrue();
|
||||||
|
expect(component.displayValue$.next).toHaveBeenCalledWith('Document');
|
||||||
|
expect(component.context.filterRawParams[component.id]).toEqual([`TYPE:'cm:content'`]);
|
||||||
|
expect(component.context.filterLoaded.next).toHaveBeenCalled();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -15,14 +15,15 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Component, OnInit, ViewEncapsulation } from '@angular/core';
|
import { Component, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core';
|
||||||
import { MatCheckboxChange, MatCheckboxModule } from '@angular/material/checkbox';
|
import { MatCheckboxChange, MatCheckboxModule } from '@angular/material/checkbox';
|
||||||
import { SearchWidget } from '../../models/search-widget.interface';
|
import { SearchWidget } from '../../models/search-widget.interface';
|
||||||
import { SearchWidgetSettings } from '../../models/search-widget-settings.interface';
|
import { SearchWidgetSettings } from '../../models/search-widget-settings.interface';
|
||||||
import { SearchQueryBuilderService } from '../../services/search-query-builder.service';
|
import { SearchQueryBuilderService } from '../../services/search-query-builder.service';
|
||||||
import { SearchFilterList } from '../../models/search-filter-list.model';
|
import { SearchFilterList } from '../../models/search-filter-list.model';
|
||||||
import { TranslationService } from '@alfresco/adf-core';
|
import { TranslationService } from '@alfresco/adf-core';
|
||||||
import { Subject } from 'rxjs';
|
import { ReplaySubject, Subject } from 'rxjs';
|
||||||
|
import { map, takeUntil } from 'rxjs/operators';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
import { MatButtonModule } from '@angular/material/button';
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
@ -43,7 +44,7 @@ export interface SearchListOption {
|
|||||||
encapsulation: ViewEncapsulation.None,
|
encapsulation: ViewEncapsulation.None,
|
||||||
host: { class: 'adf-search-check-list' }
|
host: { class: 'adf-search-check-list' }
|
||||||
})
|
})
|
||||||
export class SearchCheckListComponent implements SearchWidget, OnInit {
|
export class SearchCheckListComponent implements SearchWidget, OnInit, OnDestroy {
|
||||||
id: string;
|
id: string;
|
||||||
settings?: SearchWidgetSettings;
|
settings?: SearchWidgetSettings;
|
||||||
context?: SearchQueryBuilderService;
|
context?: SearchQueryBuilderService;
|
||||||
@ -53,7 +54,9 @@ export class SearchCheckListComponent implements SearchWidget, OnInit {
|
|||||||
pageSize = 5;
|
pageSize = 5;
|
||||||
isActive = false;
|
isActive = false;
|
||||||
enableChangeUpdate = true;
|
enableChangeUpdate = true;
|
||||||
displayValue$: Subject<string> = new Subject<string>();
|
displayValue$ = new ReplaySubject<string>(1);
|
||||||
|
|
||||||
|
private readonly destroy$ = new Subject<void>();
|
||||||
|
|
||||||
constructor(private translationService: TranslationService) {
|
constructor(private translationService: TranslationService) {
|
||||||
this.options = new SearchFilterList<SearchListOption>();
|
this.options = new SearchFilterList<SearchListOption>();
|
||||||
@ -77,6 +80,31 @@ export class SearchCheckListComponent implements SearchWidget, OnInit {
|
|||||||
this.context.queryFragments[this.id] = '';
|
this.context.queryFragments[this.id] = '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
this.context.populateFilters
|
||||||
|
.asObservable()
|
||||||
|
.pipe(
|
||||||
|
map((filtersQueries) => filtersQueries[this.id]),
|
||||||
|
takeUntil(this.destroy$)
|
||||||
|
)
|
||||||
|
.subscribe((filterQuery) => {
|
||||||
|
if (filterQuery) {
|
||||||
|
filterQuery.forEach((value) => {
|
||||||
|
const option = this.options.items.find((searchListOption) => searchListOption.value === value);
|
||||||
|
if (option) {
|
||||||
|
option.checked = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.submitValues(false);
|
||||||
|
} else {
|
||||||
|
this.reset(false);
|
||||||
|
}
|
||||||
|
this.context.filterLoaded.next();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy() {
|
||||||
|
this.destroy$.next();
|
||||||
|
this.destroy$.complete();
|
||||||
}
|
}
|
||||||
|
|
||||||
clear() {
|
clear() {
|
||||||
@ -95,15 +123,18 @@ export class SearchCheckListComponent implements SearchWidget, OnInit {
|
|||||||
|
|
||||||
if (this.id && this.context) {
|
if (this.id && this.context) {
|
||||||
this.context.queryFragments[this.id] = '';
|
this.context.queryFragments[this.id] = '';
|
||||||
|
this.context.filterRawParams[this.id] = undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
reset() {
|
reset(updateContext = true) {
|
||||||
this.isActive = false;
|
this.isActive = false;
|
||||||
this.clearOptions();
|
this.clearOptions();
|
||||||
if (this.id && this.context) {
|
if (this.id && this.context) {
|
||||||
this.updateDisplayValue();
|
this.updateDisplayValue();
|
||||||
this.context.update();
|
if (updateContext) {
|
||||||
|
this.context.update();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -142,13 +173,18 @@ export class SearchCheckListComponent implements SearchWidget, OnInit {
|
|||||||
return this.options.items.filter((option) => option.checked).map((option) => option.value);
|
return this.options.items.filter((option) => option.checked).map((option) => option.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
submitValues() {
|
submitValues(updateContext = true) {
|
||||||
const checkedValues = this.getCheckedValues();
|
const checkedValues = this.getCheckedValues();
|
||||||
|
if (checkedValues.length !== 0) {
|
||||||
|
this.context.filterRawParams[this.id] = checkedValues;
|
||||||
|
}
|
||||||
const query = checkedValues.join(` ${this.operator} `);
|
const query = checkedValues.join(` ${this.operator} `);
|
||||||
if (this.id && this.context) {
|
if (this.id && this.context) {
|
||||||
this.context.queryFragments[this.id] = query;
|
this.context.queryFragments[this.id] = query;
|
||||||
this.updateDisplayValue();
|
this.updateDisplayValue();
|
||||||
this.context.update();
|
if (updateContext) {
|
||||||
|
this.context.update();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -122,6 +122,12 @@ describe('SearchChipAutocompleteInputComponent', () => {
|
|||||||
return fixture.debugElement.queryAll(By.css('.adf-autocomplete-added-option'));
|
return fixture.debugElement.queryAll(By.css('.adf-autocomplete-added-option'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
it('should assign preselected values to selected options on init', () => {
|
||||||
|
component.preselectedOptions = [{ value: 'option1' }];
|
||||||
|
component.ngOnInit();
|
||||||
|
expect(component.selectedOptions).toEqual([{ value: 'option1' }]);
|
||||||
|
});
|
||||||
|
|
||||||
it('should add new option only if value is predefined when allowOnlyPredefinedValues = true', async () => {
|
it('should add new option only if value is predefined when allowOnlyPredefinedValues = true', async () => {
|
||||||
addNewOption('test');
|
addNewOption('test');
|
||||||
addNewOption('option1');
|
addNewOption('option1');
|
||||||
|
@ -55,6 +55,9 @@ export class SearchChipAutocompleteInputComponent implements OnInit, OnDestroy,
|
|||||||
@Input()
|
@Input()
|
||||||
autocompleteOptions: AutocompleteOption[] = [];
|
autocompleteOptions: AutocompleteOption[] = [];
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
preselectedOptions: AutocompleteOption[] = [];
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
onReset$: Observable<void>;
|
onReset$: Observable<void>;
|
||||||
|
|
||||||
@ -106,6 +109,7 @@ export class SearchChipAutocompleteInputComponent implements OnInit, OnDestroy,
|
|||||||
this.inputChanged.emit(value);
|
this.inputChanged.emit(value);
|
||||||
});
|
});
|
||||||
this.onReset$?.pipe(takeUntil(this.onDestroy$)).subscribe(() => this.reset());
|
this.onReset$?.pipe(takeUntil(this.onDestroy$)).subscribe(() => this.reset());
|
||||||
|
this.selectedOptions = this.preselectedOptions ?? [];
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnChanges(changes: SimpleChanges) {
|
ngOnChanges(changes: SimpleChanges) {
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
[dateFormat]="settings.dateFormat"
|
[dateFormat]="settings.dateFormat"
|
||||||
[maxDate]="settings.maxDate"
|
[maxDate]="settings.maxDate"
|
||||||
[field]="field"
|
[field]="field"
|
||||||
[initialValue]="startValue"
|
[initialValue]="preselectedValues[field]"
|
||||||
(changed)="onDateRangedValueChanged($event, field)"
|
(changed)="onDateRangedValueChanged($event, field)"
|
||||||
(valid)="tabsValidity[field]=$event">
|
(valid)="tabsValidity[field]=$event">
|
||||||
</adf-search-date-range>
|
</adf-search-date-range>
|
||||||
|
@ -25,6 +25,7 @@ import { SearchDateRangeTabbedComponent } from './search-date-range-tabbed.compo
|
|||||||
import { DateRangeType } from './search-date-range/date-range-type';
|
import { DateRangeType } from './search-date-range/date-range-type';
|
||||||
import { InLastDateType } from './search-date-range/in-last-date-type';
|
import { InLastDateType } from './search-date-range/in-last-date-type';
|
||||||
import { endOfDay, endOfToday, formatISO, parse, startOfDay, startOfMonth, startOfWeek, subDays, subMonths, subWeeks } from 'date-fns';
|
import { endOfDay, endOfToday, formatISO, parse, startOfDay, startOfMonth, startOfWeek, subDays, subMonths, subWeeks } from 'date-fns';
|
||||||
|
import { ReplaySubject } from 'rxjs';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'adf-search-filter-tabbed',
|
selector: 'adf-search-filter-tabbed',
|
||||||
@ -75,13 +76,15 @@ describe('SearchDateRangeTabbedComponent', () => {
|
|||||||
queryFragments: {
|
queryFragments: {
|
||||||
dateRange: ''
|
dateRange: ''
|
||||||
},
|
},
|
||||||
|
filterRawParams: {},
|
||||||
|
populateFilters: new ReplaySubject(1),
|
||||||
update: jasmine.createSpy('update')
|
update: jasmine.createSpy('update')
|
||||||
} as any;
|
} as any;
|
||||||
component.settings = {
|
component.settings = {
|
||||||
hideDefaultAction: false,
|
hideDefaultAction: false,
|
||||||
dateFormat: 'dd-MMM-yy',
|
dateFormat: 'dd-MMM-yy',
|
||||||
maxDate: 'today',
|
maxDate: 'today',
|
||||||
field: 'createdDate, modifiedDate',
|
field: 'createdDate,modifiedDate',
|
||||||
displayedLabelsByField: {
|
displayedLabelsByField: {
|
||||||
createdDate: 'Created Date',
|
createdDate: 'Created Date',
|
||||||
modifiedDate: 'Modified Date'
|
modifiedDate: 'Modified Date'
|
||||||
@ -163,6 +166,8 @@ describe('SearchDateRangeTabbedComponent', () => {
|
|||||||
`createdDate:['${formatISO(startOfDay(betweenMockData.betweenStartDate))}' TO '${formatISO(endOfDay(betweenMockData.betweenEndDate))}']` +
|
`createdDate:['${formatISO(startOfDay(betweenMockData.betweenStartDate))}' TO '${formatISO(endOfDay(betweenMockData.betweenEndDate))}']` +
|
||||||
` AND modifiedDate:['${formatISO(startOfDay(inLastStartDate))}' TO '${formatISO(endOfToday())}']`;
|
` AND modifiedDate:['${formatISO(startOfDay(inLastStartDate))}' TO '${formatISO(endOfToday())}']`;
|
||||||
expect(component.combinedQuery).toEqual(query);
|
expect(component.combinedQuery).toEqual(query);
|
||||||
|
expect(component.context.filterRawParams[component.id].createdDate).toEqual(betweenMockData);
|
||||||
|
expect(component.context.filterRawParams[component.id].modifiedDate).toEqual(inLastMockData);
|
||||||
|
|
||||||
inLastMockData = {
|
inLastMockData = {
|
||||||
dateRangeType: DateRangeType.IN_LAST,
|
dateRangeType: DateRangeType.IN_LAST,
|
||||||
@ -178,6 +183,7 @@ describe('SearchDateRangeTabbedComponent', () => {
|
|||||||
`createdDate:['${formatISO(startOfDay(betweenMockData.betweenStartDate))}' TO '${formatISO(endOfDay(betweenMockData.betweenEndDate))}']` +
|
`createdDate:['${formatISO(startOfDay(betweenMockData.betweenStartDate))}' TO '${formatISO(endOfDay(betweenMockData.betweenEndDate))}']` +
|
||||||
` AND modifiedDate:['${formatISO(startOfDay(inLastStartDate))}' TO '${formatISO(endOfToday())}']`;
|
` AND modifiedDate:['${formatISO(startOfDay(inLastStartDate))}' TO '${formatISO(endOfToday())}']`;
|
||||||
expect(component.combinedQuery).toEqual(query);
|
expect(component.combinedQuery).toEqual(query);
|
||||||
|
expect(component.context.filterRawParams[component.id].modifiedDate).toEqual(inLastMockData);
|
||||||
|
|
||||||
inLastMockData = {
|
inLastMockData = {
|
||||||
dateRangeType: DateRangeType.IN_LAST,
|
dateRangeType: DateRangeType.IN_LAST,
|
||||||
@ -193,12 +199,14 @@ describe('SearchDateRangeTabbedComponent', () => {
|
|||||||
`createdDate:['${formatISO(startOfDay(betweenMockData.betweenStartDate))}' TO '${formatISO(endOfDay(betweenMockData.betweenEndDate))}']` +
|
`createdDate:['${formatISO(startOfDay(betweenMockData.betweenStartDate))}' TO '${formatISO(endOfDay(betweenMockData.betweenEndDate))}']` +
|
||||||
` AND modifiedDate:['${formatISO(startOfDay(inLastStartDate))}' TO '${formatISO(endOfToday())}']`;
|
` AND modifiedDate:['${formatISO(startOfDay(inLastStartDate))}' TO '${formatISO(endOfToday())}']`;
|
||||||
expect(component.combinedQuery).toEqual(query);
|
expect(component.combinedQuery).toEqual(query);
|
||||||
expect(component.combinedQuery).toEqual(query);
|
expect(component.context.filterRawParams[component.id].modifiedDate).toEqual(inLastMockData);
|
||||||
|
|
||||||
component.onDateRangedValueChanged(anyMockDate, 'createdDate');
|
component.onDateRangedValueChanged(anyMockDate, 'createdDate');
|
||||||
component.onDateRangedValueChanged(anyMockDate, 'modifiedDate');
|
component.onDateRangedValueChanged(anyMockDate, 'modifiedDate');
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
expect(component.combinedQuery).toEqual('');
|
expect(component.combinedQuery).toEqual('');
|
||||||
|
expect(component.context.filterRawParams[component.id].createdDate).toEqual(anyMockDate);
|
||||||
|
expect(component.context.filterRawParams[component.id].modifiedDate).toEqual(anyMockDate);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should trigger context.update() when values are submitted', () => {
|
it('should trigger context.update() when values are submitted', () => {
|
||||||
@ -224,6 +232,31 @@ describe('SearchDateRangeTabbedComponent', () => {
|
|||||||
expect(component.displayValue$.next).toHaveBeenCalledWith('');
|
expect(component.displayValue$.next).toHaveBeenCalledWith('');
|
||||||
expect(component.context.queryFragments['dateRange']).toEqual('');
|
expect(component.context.queryFragments['dateRange']).toEqual('');
|
||||||
expect(component.context.update).toHaveBeenCalled();
|
expect(component.context.update).toHaveBeenCalled();
|
||||||
|
component.fields.forEach((field) => expect(component.context.filterRawParams[field]).toBeUndefined());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should populate filter state when populate filters event has been observed', () => {
|
||||||
|
component.context.filterLoaded = new ReplaySubject(1);
|
||||||
|
spyOn(component.context.filterLoaded, 'next').and.stub();
|
||||||
|
spyOn(component.displayValue$, 'next').and.stub();
|
||||||
|
const createdDateMock = {
|
||||||
|
dateRangeType: DateRangeType.BETWEEN,
|
||||||
|
inLastValueType: InLastDateType.DAYS,
|
||||||
|
inLastValue: undefined,
|
||||||
|
betweenStartDate: '2023-06-05',
|
||||||
|
betweenEndDate: '2023-06-07'
|
||||||
|
};
|
||||||
|
component.context.populateFilters.next({ dateRange: { createdDate: createdDateMock, modifiedDate: inLastMockData } });
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(component.displayValue$.next).toHaveBeenCalledWith(
|
||||||
|
'CREATED DATE: 05-Jun-23 - 07-Jun-23 MODIFIED DATE: SEARCH.DATE_RANGE_ADVANCED.IN_LAST_DISPLAY_LABELS.WEEKS'
|
||||||
|
);
|
||||||
|
expect(component.preselectedValues['createdDate']).toEqual(betweenMockData);
|
||||||
|
expect(component.preselectedValues['modifiedDate']).toEqual(inLastMockData);
|
||||||
|
expect(component.context.filterRawParams[component.id].createdDate).toEqual(betweenMockData);
|
||||||
|
expect(component.context.filterRawParams[component.id].modifiedDate).toEqual(inLastMockData);
|
||||||
|
expect(component.context.filterLoaded.next).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('SearchDateRangeTabbedComponent getTabLabel', () => {
|
describe('SearchDateRangeTabbedComponent getTabLabel', () => {
|
||||||
|
@ -15,8 +15,9 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Component, OnInit, ViewEncapsulation } from '@angular/core';
|
import { Component, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core';
|
||||||
import { Subject } from 'rxjs';
|
import { ReplaySubject, Subject } from 'rxjs';
|
||||||
|
import { map, takeUntil } from 'rxjs/operators';
|
||||||
import { DateRangeType } from './search-date-range/date-range-type';
|
import { DateRangeType } from './search-date-range/date-range-type';
|
||||||
import { SearchDateRange } from './search-date-range/search-date-range';
|
import { SearchDateRange } from './search-date-range/search-date-range';
|
||||||
import { SearchWidget } from '../../models/search-widget.interface';
|
import { SearchWidget } from '../../models/search-widget.interface';
|
||||||
@ -24,7 +25,7 @@ import { SearchWidgetSettings } from '../../models/search-widget-settings.interf
|
|||||||
import { SearchQueryBuilderService } from '../../services/search-query-builder.service';
|
import { SearchQueryBuilderService } from '../../services/search-query-builder.service';
|
||||||
import { InLastDateType } from './search-date-range/in-last-date-type';
|
import { InLastDateType } from './search-date-range/in-last-date-type';
|
||||||
import { TranslationService } from '@alfresco/adf-core';
|
import { TranslationService } from '@alfresco/adf-core';
|
||||||
import { endOfDay, endOfToday, format, formatISO, startOfDay, startOfMonth, startOfWeek, subDays, subMonths, subWeeks } from 'date-fns';
|
import { endOfDay, endOfToday, format, formatISO, parseISO, startOfDay, startOfMonth, startOfWeek, subDays, subMonths, subWeeks } from 'date-fns';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { SearchFilterTabbedComponent } from '../search-filter-tabbed/search-filter-tabbed.component';
|
import { SearchFilterTabbedComponent } from '../search-filter-tabbed/search-filter-tabbed.component';
|
||||||
import { SearchDateRangeComponent } from './search-date-range/search-date-range.component';
|
import { SearchDateRangeComponent } from './search-date-range/search-date-range.component';
|
||||||
@ -40,8 +41,8 @@ const DEFAULT_DATE_DISPLAY_FORMAT = 'dd-MMM-yy';
|
|||||||
styleUrls: ['./search-date-range-tabbed.component.scss'],
|
styleUrls: ['./search-date-range-tabbed.component.scss'],
|
||||||
encapsulation: ViewEncapsulation.None
|
encapsulation: ViewEncapsulation.None
|
||||||
})
|
})
|
||||||
export class SearchDateRangeTabbedComponent implements SearchWidget, OnInit {
|
export class SearchDateRangeTabbedComponent implements SearchWidget, OnInit, OnDestroy {
|
||||||
displayValue$ = new Subject<string>();
|
displayValue$ = new ReplaySubject<string>(1);
|
||||||
id: string;
|
id: string;
|
||||||
startValue: SearchDateRange = {
|
startValue: SearchDateRange = {
|
||||||
dateRangeType: DateRangeType.ANY,
|
dateRangeType: DateRangeType.ANY,
|
||||||
@ -50,6 +51,7 @@ export class SearchDateRangeTabbedComponent implements SearchWidget, OnInit {
|
|||||||
betweenStartDate: undefined,
|
betweenStartDate: undefined,
|
||||||
betweenEndDate: undefined
|
betweenEndDate: undefined
|
||||||
};
|
};
|
||||||
|
preselectedValues: { [key: string]: SearchDateRange } = {};
|
||||||
settings?: SearchWidgetSettings;
|
settings?: SearchWidgetSettings;
|
||||||
context?: SearchQueryBuilderService;
|
context?: SearchQueryBuilderService;
|
||||||
fields: string[];
|
fields: string[];
|
||||||
@ -60,12 +62,42 @@ export class SearchDateRangeTabbedComponent implements SearchWidget, OnInit {
|
|||||||
private value: { [key: string]: Partial<SearchDateRange> } = {};
|
private value: { [key: string]: Partial<SearchDateRange> } = {};
|
||||||
private queryMapByField: Map<string, string> = new Map<string, string>();
|
private queryMapByField: Map<string, string> = new Map<string, string>();
|
||||||
private displayValueMapByField: Map<string, string> = new Map<string, string>();
|
private displayValueMapByField: Map<string, string> = new Map<string, string>();
|
||||||
|
private readonly destroy$ = new Subject<void>();
|
||||||
|
|
||||||
constructor(private translateService: TranslationService) {}
|
constructor(private translateService: TranslationService) {}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.fields = this.settings?.field.split(',').map((field) => field.trim());
|
this.fields = this.settings?.field.split(',').map((field) => field.trim());
|
||||||
this.setDefaultDateFormatSettings();
|
this.setDefaultDateFormatSettings();
|
||||||
|
this.context.populateFilters
|
||||||
|
.asObservable()
|
||||||
|
.pipe(
|
||||||
|
map((filtersQueries) => filtersQueries[this.id]),
|
||||||
|
takeUntil(this.destroy$)
|
||||||
|
)
|
||||||
|
.subscribe((filterQuery) => {
|
||||||
|
if (filterQuery) {
|
||||||
|
Object.keys(filterQuery).forEach((field) => {
|
||||||
|
filterQuery[field].betweenStartDate = filterQuery[field].betweenStartDate
|
||||||
|
? parseISO(filterQuery[field].betweenStartDate)
|
||||||
|
: undefined;
|
||||||
|
filterQuery[field].betweenEndDate = filterQuery[field].betweenEndDate
|
||||||
|
? parseISO(filterQuery[field].betweenEndDate)
|
||||||
|
: undefined;
|
||||||
|
this.preselectedValues[field] = filterQuery[field];
|
||||||
|
this.onDateRangedValueChanged(filterQuery[field], field);
|
||||||
|
});
|
||||||
|
this.submitValues(false);
|
||||||
|
} else {
|
||||||
|
this.reset(false);
|
||||||
|
}
|
||||||
|
this.context.filterLoaded.next();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy() {
|
||||||
|
this.destroy$.next();
|
||||||
|
this.destroy$.complete();
|
||||||
}
|
}
|
||||||
|
|
||||||
private setDefaultDateFormatSettings() {
|
private setDefaultDateFormatSettings() {
|
||||||
@ -82,13 +114,18 @@ export class SearchDateRangeTabbedComponent implements SearchWidget, OnInit {
|
|||||||
return Object.values(this.tabsValidity).every((valid) => valid);
|
return Object.values(this.tabsValidity).every((valid) => valid);
|
||||||
}
|
}
|
||||||
|
|
||||||
reset() {
|
reset(updateContext = true) {
|
||||||
this.combinedQuery = '';
|
this.combinedQuery = '';
|
||||||
this.combinedDisplayValue = '';
|
this.combinedDisplayValue = '';
|
||||||
this.startValue = {
|
this.startValue = {
|
||||||
...this.startValue
|
...this.startValue
|
||||||
};
|
};
|
||||||
this.submitValues();
|
this.fields.forEach((field) => {
|
||||||
|
this.context.filterRawParams[field] = undefined;
|
||||||
|
});
|
||||||
|
this.context.queryFragments[this.id] = undefined;
|
||||||
|
this.context.filterRawParams[this.id] = undefined;
|
||||||
|
this.submitValues(updateContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
setValue(value: { [key: string]: SearchDateRange }) {
|
setValue(value: { [key: string]: SearchDateRange }) {
|
||||||
@ -99,14 +136,16 @@ export class SearchDateRangeTabbedComponent implements SearchWidget, OnInit {
|
|||||||
return this.settings?.displayedLabelsByField?.[field] ? this.settings.displayedLabelsByField[field] : field;
|
return this.settings?.displayedLabelsByField?.[field] ? this.settings.displayedLabelsByField[field] : field;
|
||||||
}
|
}
|
||||||
|
|
||||||
submitValues() {
|
submitValues(updateContext = true) {
|
||||||
this.context.queryFragments[this.id] = this.combinedQuery;
|
this.context.queryFragments[this.id] = this.combinedQuery;
|
||||||
this.displayValue$.next(this.combinedDisplayValue);
|
this.displayValue$.next(this.combinedDisplayValue);
|
||||||
if (this.id && this.context) {
|
if (this.id && this.context && updateContext) {
|
||||||
this.context.update();
|
this.context.update();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
onDateRangedValueChanged(value: Partial<SearchDateRange>, field: string) {
|
onDateRangedValueChanged(value: Partial<SearchDateRange>, field: string) {
|
||||||
|
this.context.filterRawParams[this.id] ||= {};
|
||||||
|
this.context.filterRawParams[this.id][field] = value;
|
||||||
this.value[field] = value;
|
this.value[field] = value;
|
||||||
this.updateQuery(value, field);
|
this.updateQuery(value, field);
|
||||||
this.updateDisplayValue(value, field);
|
this.updateDisplayValue(value, field);
|
||||||
|
@ -87,7 +87,7 @@ export class SearchDateRangeComponent implements OnInit, OnDestroy {
|
|||||||
betweenStartDateFormControl = this.form.controls.betweenStartDate;
|
betweenStartDateFormControl = this.form.controls.betweenStartDate;
|
||||||
betweenEndDateFormControl = this.form.controls.betweenEndDate;
|
betweenEndDateFormControl = this.form.controls.betweenEndDate;
|
||||||
convertedMaxDate: Date;
|
convertedMaxDate: Date;
|
||||||
private destroy$ = new Subject<void>();
|
private readonly destroy$ = new Subject<void>();
|
||||||
|
|
||||||
readonly DateRangeType = DateRangeType;
|
readonly DateRangeType = DateRangeType;
|
||||||
readonly InLastDateType = InLastDateType;
|
readonly InLastDateType = InLastDateType;
|
||||||
|
@ -20,7 +20,8 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|||||||
import { ContentTestingModule } from '../../../testing/content.testing.module';
|
import { ContentTestingModule } from '../../../testing/content.testing.module';
|
||||||
import { MatDatetimepickerInputEvent } from '@mat-datetimepicker/core';
|
import { MatDatetimepickerInputEvent } from '@mat-datetimepicker/core';
|
||||||
import { DateFnsUtils } from '@alfresco/adf-core';
|
import { DateFnsUtils } from '@alfresco/adf-core';
|
||||||
import { isValid } from 'date-fns';
|
import { endOfMinute, isValid, startOfMinute } from 'date-fns';
|
||||||
|
import { ReplaySubject } from 'rxjs';
|
||||||
|
|
||||||
describe('SearchDatetimeRangeComponent', () => {
|
describe('SearchDatetimeRangeComponent', () => {
|
||||||
let fixture: ComponentFixture<SearchDatetimeRangeComponent>;
|
let fixture: ComponentFixture<SearchDatetimeRangeComponent>;
|
||||||
@ -36,6 +37,16 @@ describe('SearchDatetimeRangeComponent', () => {
|
|||||||
});
|
});
|
||||||
fixture = TestBed.createComponent(SearchDatetimeRangeComponent);
|
fixture = TestBed.createComponent(SearchDatetimeRangeComponent);
|
||||||
component = fixture.componentInstance;
|
component = fixture.componentInstance;
|
||||||
|
component.id = 'createdDateRange';
|
||||||
|
component.context = {
|
||||||
|
queryFragments: {
|
||||||
|
createdDatetimeRange: ''
|
||||||
|
},
|
||||||
|
filterRawParams: {},
|
||||||
|
populateFilters: new ReplaySubject(1),
|
||||||
|
update: jasmine.createSpy('update')
|
||||||
|
} as any;
|
||||||
|
component.settings = { field: 'cm:created' };
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => fixture.destroy());
|
afterEach(() => fixture.destroy());
|
||||||
@ -76,7 +87,7 @@ describe('SearchDatetimeRangeComponent', () => {
|
|||||||
expect(component.from.value).toBeNull();
|
expect(component.from.value).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should reset form', async () => {
|
it('should reset form and filter params', async () => {
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
await fixture.whenStable();
|
await fixture.whenStable();
|
||||||
|
|
||||||
@ -93,6 +104,7 @@ describe('SearchDatetimeRangeComponent', () => {
|
|||||||
expect(component.from.value).toBeNull();
|
expect(component.from.value).toBeNull();
|
||||||
expect(component.to.value).toBeNull();
|
expect(component.to.value).toBeNull();
|
||||||
expect(component.form.value).toEqual({ from: null, to: null });
|
expect(component.form.value).toEqual({ from: null, to: null });
|
||||||
|
expect(component.context.filterRawParams[component.id]).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should reset fromMaxDatetime on reset', async () => {
|
it('should reset fromMaxDatetime on reset', async () => {
|
||||||
@ -106,39 +118,19 @@ describe('SearchDatetimeRangeComponent', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should update query builder on reset', async () => {
|
it('should update query builder on reset', async () => {
|
||||||
const context: any = {
|
component.context.queryFragments[component.id] = 'query';
|
||||||
queryFragments: {
|
|
||||||
createdDatetimeRange: 'query'
|
|
||||||
},
|
|
||||||
update: () => {}
|
|
||||||
};
|
|
||||||
|
|
||||||
component.id = 'createdDatetimeRange';
|
|
||||||
component.context = context;
|
|
||||||
|
|
||||||
spyOn(context, 'update').and.stub();
|
|
||||||
|
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
await fixture.whenStable();
|
await fixture.whenStable();
|
||||||
|
|
||||||
component.reset();
|
component.reset();
|
||||||
|
|
||||||
expect(context.queryFragments.createdDatetimeRange).toEqual('');
|
expect(component.context.queryFragments.createdDatetimeRange).toEqual('');
|
||||||
expect(context.update).toHaveBeenCalled();
|
expect(component.context.update).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should update the query in UTC format when values change', async () => {
|
it('should update the query in UTC format when values change', async () => {
|
||||||
const context: any = {
|
|
||||||
queryFragments: {},
|
|
||||||
update: () => {}
|
|
||||||
};
|
|
||||||
|
|
||||||
component.id = 'createdDateRange';
|
|
||||||
component.context = context;
|
|
||||||
component.settings = { field: 'cm:created' };
|
component.settings = { field: 'cm:created' };
|
||||||
|
|
||||||
spyOn(context, 'update').and.stub();
|
|
||||||
|
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
await fixture.whenStable();
|
await fixture.whenStable();
|
||||||
|
|
||||||
@ -151,25 +143,19 @@ describe('SearchDatetimeRangeComponent', () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const expectedQuery = `cm:created:['2016-10-16T12:30:00.000Z' TO '2017-10-16T20:00:59.000Z']`;
|
const expectedQuery = `cm:created:['2016-10-16T12:30:00.000Z' TO '2017-10-16T20:00:59.000Z']`;
|
||||||
|
const expectedFromDate = DateFnsUtils.utcToLocal(startOfMinute(fromDatetime)).toISOString();
|
||||||
|
const expectedToDate = DateFnsUtils.utcToLocal(endOfMinute(toDatetime)).toISOString();
|
||||||
|
|
||||||
expect(context.queryFragments[component.id]).toEqual(expectedQuery);
|
expect(component.context.queryFragments[component.id]).toEqual(expectedQuery);
|
||||||
expect(context.update).toHaveBeenCalled();
|
expect(component.context.filterRawParams[component.id]).toEqual({ start: expectedFromDate, end: expectedToDate });
|
||||||
|
expect(component.context.update).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be able to update the query in UTC format from a GMT format', async () => {
|
it('should be able to update the query in UTC format from a GMT format', async () => {
|
||||||
const context: any = {
|
|
||||||
queryFragments: {},
|
|
||||||
update: () => {}
|
|
||||||
};
|
|
||||||
const fromInGmt = new Date('2021-02-24T17:00:00+02:00');
|
const fromInGmt = new Date('2021-02-24T17:00:00+02:00');
|
||||||
const toInGmt = new Date('2021-02-28T15:00:00+02:00');
|
const toInGmt = new Date('2021-02-28T15:00:00+02:00');
|
||||||
|
|
||||||
component.id = 'createdDateRange';
|
|
||||||
component.context = context;
|
|
||||||
component.settings = { field: 'cm:created' };
|
component.settings = { field: 'cm:created' };
|
||||||
|
|
||||||
spyOn(context, 'update').and.stub();
|
|
||||||
|
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
await fixture.whenStable();
|
await fixture.whenStable();
|
||||||
|
|
||||||
@ -183,8 +169,8 @@ describe('SearchDatetimeRangeComponent', () => {
|
|||||||
|
|
||||||
const expectedQuery = `cm:created:['2021-02-24T15:00:00.000Z' TO '2021-02-28T13:00:59.000Z']`;
|
const expectedQuery = `cm:created:['2021-02-24T15:00:00.000Z' TO '2021-02-28T13:00:59.000Z']`;
|
||||||
|
|
||||||
expect(context.queryFragments[component.id]).toEqual(expectedQuery);
|
expect(component.context.queryFragments[component.id]).toEqual(expectedQuery);
|
||||||
expect(context.update).toHaveBeenCalled();
|
expect(component.context.update).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should show datetime-format error when an invalid datetime is set', async () => {
|
it('should show datetime-format error when an invalid datetime is set', async () => {
|
||||||
@ -232,4 +218,22 @@ describe('SearchDatetimeRangeComponent', () => {
|
|||||||
expect(inputs[1]).toBeDefined();
|
expect(inputs[1]).toBeDefined();
|
||||||
expect(inputs[1]).not.toBeNull();
|
expect(inputs[1]).not.toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should populate filter state when populate filters event has been observed', () => {
|
||||||
|
component.context.filterLoaded = new ReplaySubject(1);
|
||||||
|
spyOn(component.context.filterLoaded, 'next').and.stub();
|
||||||
|
spyOn(component.displayValue$, 'next').and.stub();
|
||||||
|
fixture.detectChanges();
|
||||||
|
const fromDateString = startOfMinute(fromDatetime).toISOString();
|
||||||
|
const toDateString = endOfMinute(toDatetime).toISOString();
|
||||||
|
const expectedFromDate = DateFnsUtils.utcToLocal(startOfMinute(fromDatetime)).toISOString();
|
||||||
|
const expectedToDate = DateFnsUtils.utcToLocal(endOfMinute(toDatetime)).toISOString();
|
||||||
|
component.context.populateFilters.next({ createdDateRange: { start: fromDateString, end: toDateString } });
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(component.displayValue$.next).toHaveBeenCalledWith('16/10/2016 12:30 - 16/10/2017 20:00');
|
||||||
|
expect(component.context.filterRawParams[component.id].start).toEqual(expectedFromDate);
|
||||||
|
expect(component.context.filterRawParams[component.id].end).toEqual(expectedToDate);
|
||||||
|
expect(component.context.filterLoaded.next).toHaveBeenCalled();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -15,17 +15,18 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Component, OnInit, ViewEncapsulation } from '@angular/core';
|
import { Component, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core';
|
||||||
import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
|
import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
|
||||||
import { ADF_DATE_FORMATS, ADF_DATETIME_FORMATS, AdfDateFnsAdapter, AdfDateTimeFnsAdapter, DateFnsUtils } from '@alfresco/adf-core';
|
import { ADF_DATE_FORMATS, ADF_DATETIME_FORMATS, AdfDateFnsAdapter, AdfDateTimeFnsAdapter, DateFnsUtils } from '@alfresco/adf-core';
|
||||||
import { SearchWidget } from '../../models/search-widget.interface';
|
import { SearchWidget } from '../../models/search-widget.interface';
|
||||||
import { SearchWidgetSettings } from '../../models/search-widget-settings.interface';
|
import { SearchWidgetSettings } from '../../models/search-widget-settings.interface';
|
||||||
import { SearchQueryBuilderService } from '../../services/search-query-builder.service';
|
import { SearchQueryBuilderService } from '../../services/search-query-builder.service';
|
||||||
import { LiveErrorStateMatcher } from '../../forms/live-error-state-matcher';
|
import { LiveErrorStateMatcher } from '../../forms/live-error-state-matcher';
|
||||||
import { Subject } from 'rxjs';
|
import { ReplaySubject, Subject } from 'rxjs';
|
||||||
|
import { map, takeUntil } from 'rxjs/operators';
|
||||||
import { DatetimeAdapter, MAT_DATETIME_FORMATS, MatDatetimepickerInputEvent, MatDatetimepickerModule } from '@mat-datetimepicker/core';
|
import { DatetimeAdapter, MAT_DATETIME_FORMATS, MatDatetimepickerInputEvent, MatDatetimepickerModule } from '@mat-datetimepicker/core';
|
||||||
import { DateAdapter, MAT_DATE_FORMATS } from '@angular/material/core';
|
import { DateAdapter, MAT_DATE_FORMATS } from '@angular/material/core';
|
||||||
import { isValid, isBefore, startOfMinute, endOfMinute } from 'date-fns';
|
import { isValid, isBefore, startOfMinute, endOfMinute, parseISO } from 'date-fns';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||||
import { MatInputModule } from '@angular/material/input';
|
import { MatInputModule } from '@angular/material/input';
|
||||||
@ -58,7 +59,7 @@ export const DEFAULT_DATETIME_FORMAT: string = 'dd/MM/yyyy HH:mm';
|
|||||||
encapsulation: ViewEncapsulation.None,
|
encapsulation: ViewEncapsulation.None,
|
||||||
host: { class: 'adf-search-date-range' }
|
host: { class: 'adf-search-date-range' }
|
||||||
})
|
})
|
||||||
export class SearchDatetimeRangeComponent implements SearchWidget, OnInit {
|
export class SearchDatetimeRangeComponent implements SearchWidget, OnInit, OnDestroy {
|
||||||
from: FormControl<Date>;
|
from: FormControl<Date>;
|
||||||
to: FormControl<Date>;
|
to: FormControl<Date>;
|
||||||
|
|
||||||
@ -74,7 +75,9 @@ export class SearchDatetimeRangeComponent implements SearchWidget, OnInit {
|
|||||||
isActive = false;
|
isActive = false;
|
||||||
startValue: any;
|
startValue: any;
|
||||||
enableChangeUpdate: boolean;
|
enableChangeUpdate: boolean;
|
||||||
displayValue$: Subject<string> = new Subject<string>();
|
displayValue$ = new ReplaySubject<string>(1);
|
||||||
|
|
||||||
|
private readonly destroy$ = new Subject<void>();
|
||||||
|
|
||||||
constructor(private dateAdapter: DateAdapter<Date>, private dateTimeAdapter: DatetimeAdapter<Date>) {}
|
constructor(private dateAdapter: DateAdapter<Date>, private dateTimeAdapter: DatetimeAdapter<Date>) {}
|
||||||
|
|
||||||
@ -133,9 +136,32 @@ export class SearchDatetimeRangeComponent implements SearchWidget, OnInit {
|
|||||||
|
|
||||||
this.setFromMaxDatetime();
|
this.setFromMaxDatetime();
|
||||||
this.enableChangeUpdate = this.settings?.allowUpdateOnChange ?? true;
|
this.enableChangeUpdate = this.settings?.allowUpdateOnChange ?? true;
|
||||||
|
this.context.populateFilters
|
||||||
|
.asObservable()
|
||||||
|
.pipe(
|
||||||
|
map((filtersQueries) => filtersQueries[this.id]),
|
||||||
|
takeUntil(this.destroy$)
|
||||||
|
)
|
||||||
|
.subscribe((filterQuery) => {
|
||||||
|
if (filterQuery) {
|
||||||
|
const start = parseISO(filterQuery.start);
|
||||||
|
const end = parseISO(filterQuery.end);
|
||||||
|
this.form.patchValue({ from: start, to: end });
|
||||||
|
this.form.markAsDirty();
|
||||||
|
this.apply({ from: start, to: end }, true, false);
|
||||||
|
} else {
|
||||||
|
this.reset();
|
||||||
|
}
|
||||||
|
this.context.filterLoaded.next();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
apply(model: Partial<{ from: Date; to: Date }>, isValidValue: boolean) {
|
ngOnDestroy() {
|
||||||
|
this.destroy$.next();
|
||||||
|
this.destroy$.complete();
|
||||||
|
}
|
||||||
|
|
||||||
|
apply(model: Partial<{ from: Date; to: Date }>, isValidValue: boolean, updateContext = true) {
|
||||||
if (isValidValue && this.id && this.context && this.settings && this.settings.field) {
|
if (isValidValue && this.id && this.context && this.settings && this.settings.field) {
|
||||||
this.isActive = true;
|
this.isActive = true;
|
||||||
|
|
||||||
@ -143,8 +169,14 @@ export class SearchDatetimeRangeComponent implements SearchWidget, OnInit {
|
|||||||
const end = DateFnsUtils.utcToLocal(endOfMinute(model.to)).toISOString();
|
const end = DateFnsUtils.utcToLocal(endOfMinute(model.to)).toISOString();
|
||||||
|
|
||||||
this.context.queryFragments[this.id] = `${this.settings.field}:['${start}' TO '${end}']`;
|
this.context.queryFragments[this.id] = `${this.settings.field}:['${start}' TO '${end}']`;
|
||||||
|
const filterParam = this.context.filterRawParams[this.id] ?? {};
|
||||||
|
this.context.filterRawParams[this.id] = filterParam;
|
||||||
|
filterParam.start = start;
|
||||||
|
filterParam.end = end;
|
||||||
this.updateDisplayValue();
|
this.updateDisplayValue();
|
||||||
this.context.update();
|
if (updateContext) {
|
||||||
|
this.context.update();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -197,6 +229,7 @@ export class SearchDatetimeRangeComponent implements SearchWidget, OnInit {
|
|||||||
});
|
});
|
||||||
if (this.id && this.context) {
|
if (this.id && this.context) {
|
||||||
this.context.queryFragments[this.id] = '';
|
this.context.queryFragments[this.id] = '';
|
||||||
|
this.context.filterRawParams[this.id] = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.id && this.context && this.enableChangeUpdate) {
|
if (this.id && this.context && this.enableChangeUpdate) {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
<adf-search-chip-autocomplete-input
|
<adf-search-chip-autocomplete-input
|
||||||
[autocompleteOptions]="autocompleteOptions$ | async"
|
[autocompleteOptions]="autocompleteOptions$ | async"
|
||||||
|
[preselectedOptions]="selectedOptions"
|
||||||
[onReset$]="reset$"
|
[onReset$]="reset$"
|
||||||
[allowOnlyPredefinedValues]="settings.allowOnlyPredefinedValues"
|
[allowOnlyPredefinedValues]="settings.allowOnlyPredefinedValues"
|
||||||
(inputChanged)="onInputChange($event)"
|
(inputChanged)="onInputChange($event)"
|
||||||
|
@ -19,7 +19,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|||||||
import { By } from '@angular/platform-browser';
|
import { By } from '@angular/platform-browser';
|
||||||
import { ContentTestingModule } from '../../../testing/content.testing.module';
|
import { ContentTestingModule } from '../../../testing/content.testing.module';
|
||||||
import { SearchFilterAutocompleteChipsComponent } from './search-filter-autocomplete-chips.component';
|
import { SearchFilterAutocompleteChipsComponent } from './search-filter-autocomplete-chips.component';
|
||||||
import { EMPTY, of } from 'rxjs';
|
import { EMPTY, of, ReplaySubject } from 'rxjs';
|
||||||
import { AutocompleteField } from '../../models/autocomplete-option.interface';
|
import { AutocompleteField } from '../../models/autocomplete-option.interface';
|
||||||
import { TagService } from '../../../tag/services/tag.service';
|
import { TagService } from '../../../tag/services/tag.service';
|
||||||
|
|
||||||
@ -44,8 +44,12 @@ describe('SearchFilterAutocompleteChipsComponent', () => {
|
|||||||
tagService = TestBed.inject(TagService);
|
tagService = TestBed.inject(TagService);
|
||||||
component.id = 'test-id';
|
component.id = 'test-id';
|
||||||
component.context = {
|
component.context = {
|
||||||
queryFragments: {},
|
queryFragments: {
|
||||||
update: () => EMPTY
|
createdDatetimeRange: ''
|
||||||
|
},
|
||||||
|
filterRawParams: {},
|
||||||
|
populateFilters: new ReplaySubject(1),
|
||||||
|
update: jasmine.createSpy('update')
|
||||||
} as any;
|
} as any;
|
||||||
component.settings = {
|
component.settings = {
|
||||||
field: 'test',
|
field: 'test',
|
||||||
@ -109,7 +113,7 @@ describe('SearchFilterAutocompleteChipsComponent', () => {
|
|||||||
component.setValue([{ value: 'option1' }, { value: 'option2' }]);
|
component.setValue([{ value: 'option1' }, { value: 'option2' }]);
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
expect(component.selectedOptions).toEqual([{ value: 'option1' }, { value: 'option2' }]);
|
expect(component.selectedOptions).toEqual([{ value: 'option1' }, { value: 'option2' }]);
|
||||||
spyOn(component.context, 'update');
|
expect(component.context.filterRawParams[component.id]).toEqual([{ value: 'option1' }, { value: 'option2' }]);
|
||||||
spyOn(component.displayValue$, 'next');
|
spyOn(component.displayValue$, 'next');
|
||||||
const clearBtn: HTMLButtonElement = fixture.debugElement.query(
|
const clearBtn: HTMLButtonElement = fixture.debugElement.query(
|
||||||
By.css('[data-automation-id="adf-search-chip-autocomplete-btn-clear"]')
|
By.css('[data-automation-id="adf-search-chip-autocomplete-btn-clear"]')
|
||||||
@ -120,10 +124,10 @@ describe('SearchFilterAutocompleteChipsComponent', () => {
|
|||||||
expect(component.context.update).toHaveBeenCalled();
|
expect(component.context.update).toHaveBeenCalled();
|
||||||
expect(component.selectedOptions).toEqual([]);
|
expect(component.selectedOptions).toEqual([]);
|
||||||
expect(component.displayValue$.next).toHaveBeenCalledWith('');
|
expect(component.displayValue$.next).toHaveBeenCalledWith('');
|
||||||
|
expect(component.context.filterRawParams[component.id]).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should correctly compose the search query', () => {
|
it('should correctly compose the search query', () => {
|
||||||
spyOn(component.context, 'update');
|
|
||||||
component.selectedOptions = [{ value: 'option2' }, { value: 'option1' }];
|
component.selectedOptions = [{ value: 'option2' }, { value: 'option1' }];
|
||||||
const applyBtn: HTMLButtonElement = fixture.debugElement.query(
|
const applyBtn: HTMLButtonElement = fixture.debugElement.query(
|
||||||
By.css('[data-automation-id="adf-search-chip-autocomplete-btn-apply"]')
|
By.css('[data-automation-id="adf-search-chip-autocomplete-btn-apply"]')
|
||||||
@ -133,11 +137,27 @@ describe('SearchFilterAutocompleteChipsComponent', () => {
|
|||||||
|
|
||||||
expect(component.context.update).toHaveBeenCalled();
|
expect(component.context.update).toHaveBeenCalled();
|
||||||
expect(component.context.queryFragments[component.id]).toBe('test:"option2" OR test:"option1"');
|
expect(component.context.queryFragments[component.id]).toBe('test:"option2" OR test:"option1"');
|
||||||
|
expect(component.context.filterRawParams[component.id]).toEqual([{ value: 'option2' }, { value: 'option1' }]);
|
||||||
|
|
||||||
component.settings.field = AutocompleteField.CATEGORIES;
|
component.settings.field = AutocompleteField.CATEGORIES;
|
||||||
component.selectedOptions = [{ id: 'test-id', value: 'test' }];
|
component.selectedOptions = [{ id: 'test-id', value: 'test' }];
|
||||||
applyBtn.click();
|
applyBtn.click();
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
expect(component.context.queryFragments[component.id]).toBe('cm:categories:"workspace://SpacesStore/test-id"');
|
expect(component.context.queryFragments[component.id]).toBe('cm:categories:"workspace://SpacesStore/test-id"');
|
||||||
|
expect(component.context.filterRawParams[component.id]).toEqual([{ id: 'test-id', value: 'test' }]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should populate filter state when populate filters event has been observed', () => {
|
||||||
|
component.context.filterLoaded = new ReplaySubject(1);
|
||||||
|
spyOn(component.context.filterLoaded, 'next').and.stub();
|
||||||
|
spyOn(component.displayValue$, 'next').and.stub();
|
||||||
|
fixture.detectChanges();
|
||||||
|
component.context.populateFilters.next({ 'test-id': [{ value: 'option2' }, { value: 'option1' }] });
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(component.displayValue$.next).toHaveBeenCalledWith('option2, option1');
|
||||||
|
expect(component.context.filterRawParams[component.id]).toEqual([{ value: 'option2' }, { value: 'option1' }]);
|
||||||
|
expect(component.selectedOptions).toEqual([{ value: 'option2' }, { value: 'option1' }]);
|
||||||
|
expect(component.context.filterLoaded.next).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -15,8 +15,9 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Component, ViewEncapsulation, OnInit } from '@angular/core';
|
import { Component, ViewEncapsulation, OnInit, OnDestroy } from '@angular/core';
|
||||||
import { BehaviorSubject, Observable, Subject } from 'rxjs';
|
import { BehaviorSubject, Observable, ReplaySubject, Subject } from 'rxjs';
|
||||||
|
import { map, takeUntil } from 'rxjs/operators';
|
||||||
import { SearchWidget } from '../../models/search-widget.interface';
|
import { SearchWidget } from '../../models/search-widget.interface';
|
||||||
import { SearchWidgetSettings } from '../../models/search-widget-settings.interface';
|
import { SearchWidgetSettings } from '../../models/search-widget-settings.interface';
|
||||||
import { SearchQueryBuilderService } from '../../services/search-query-builder.service';
|
import { SearchQueryBuilderService } from '../../services/search-query-builder.service';
|
||||||
@ -36,13 +37,13 @@ import { MatButtonModule } from '@angular/material/button';
|
|||||||
templateUrl: './search-filter-autocomplete-chips.component.html',
|
templateUrl: './search-filter-autocomplete-chips.component.html',
|
||||||
encapsulation: ViewEncapsulation.None
|
encapsulation: ViewEncapsulation.None
|
||||||
})
|
})
|
||||||
export class SearchFilterAutocompleteChipsComponent implements SearchWidget, OnInit {
|
export class SearchFilterAutocompleteChipsComponent implements SearchWidget, OnInit, OnDestroy {
|
||||||
id: string;
|
id: string;
|
||||||
settings?: SearchWidgetSettings;
|
settings?: SearchWidgetSettings;
|
||||||
context?: SearchQueryBuilderService;
|
context?: SearchQueryBuilderService;
|
||||||
options: SearchFilterList<AutocompleteOption[]>;
|
options: SearchFilterList<AutocompleteOption[]>;
|
||||||
startValue: AutocompleteOption[] = [];
|
startValue: AutocompleteOption[] = [];
|
||||||
displayValue$ = new Subject<string>();
|
displayValue$ = new ReplaySubject<string>(1);
|
||||||
selectedOptions: AutocompleteOption[] = [];
|
selectedOptions: AutocompleteOption[] = [];
|
||||||
enableChangeUpdate: boolean;
|
enableChangeUpdate: boolean;
|
||||||
|
|
||||||
@ -50,6 +51,7 @@ export class SearchFilterAutocompleteChipsComponent implements SearchWidget, OnI
|
|||||||
reset$: Observable<void> = this.resetSubject$.asObservable();
|
reset$: Observable<void> = this.resetSubject$.asObservable();
|
||||||
private autocompleteOptionsSubject$ = new BehaviorSubject<AutocompleteOption[]>([]);
|
private autocompleteOptionsSubject$ = new BehaviorSubject<AutocompleteOption[]>([]);
|
||||||
autocompleteOptions$: Observable<AutocompleteOption[]> = this.autocompleteOptionsSubject$.asObservable();
|
autocompleteOptions$: Observable<AutocompleteOption[]> = this.autocompleteOptionsSubject$.asObservable();
|
||||||
|
private readonly destroy$ = new Subject<void>();
|
||||||
|
|
||||||
constructor(private tagService: TagService, private categoryService: CategoryService) {
|
constructor(private tagService: TagService, private categoryService: CategoryService) {
|
||||||
this.options = new SearchFilterList<AutocompleteOption[]>();
|
this.options = new SearchFilterList<AutocompleteOption[]>();
|
||||||
@ -58,17 +60,38 @@ export class SearchFilterAutocompleteChipsComponent implements SearchWidget, OnI
|
|||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
if (this.settings) {
|
if (this.settings) {
|
||||||
this.setOptions();
|
this.setOptions();
|
||||||
if (this.startValue) {
|
if (this.startValue?.length > 0) {
|
||||||
this.setValue(this.startValue);
|
this.setValue(this.startValue);
|
||||||
}
|
}
|
||||||
this.enableChangeUpdate = this.settings.allowUpdateOnChange ?? true;
|
this.enableChangeUpdate = this.settings.allowUpdateOnChange ?? true;
|
||||||
}
|
}
|
||||||
|
this.context.populateFilters
|
||||||
|
.asObservable()
|
||||||
|
.pipe(
|
||||||
|
map((filterQueries) => filterQueries[this.id]),
|
||||||
|
takeUntil(this.destroy$)
|
||||||
|
)
|
||||||
|
.subscribe((filterQuery) => {
|
||||||
|
if (filterQuery) {
|
||||||
|
this.selectedOptions = filterQuery;
|
||||||
|
this.updateQuery(false);
|
||||||
|
} else if (!filterQuery && this.selectedOptions.length) {
|
||||||
|
this.reset(false);
|
||||||
|
}
|
||||||
|
this.context.filterLoaded.next();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
reset() {
|
ngOnDestroy() {
|
||||||
|
this.destroy$.next();
|
||||||
|
this.destroy$.complete();
|
||||||
|
}
|
||||||
|
|
||||||
|
reset(updateContext = true) {
|
||||||
this.selectedOptions = [];
|
this.selectedOptions = [];
|
||||||
|
this.context.filterRawParams[this.id] = undefined;
|
||||||
this.resetSubject$.next();
|
this.resetSubject$.next();
|
||||||
this.updateQuery();
|
this.updateQuery(updateContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
submitValues() {
|
submitValues() {
|
||||||
@ -107,7 +130,8 @@ export class SearchFilterAutocompleteChipsComponent implements SearchWidget, OnI
|
|||||||
return option1.id ? option1.id.toUpperCase() === option2.id.toUpperCase() : option1.value.toUpperCase() === option2.value.toUpperCase();
|
return option1.id ? option1.id.toUpperCase() === option2.id.toUpperCase() : option1.value.toUpperCase() === option2.value.toUpperCase();
|
||||||
}
|
}
|
||||||
|
|
||||||
private updateQuery() {
|
private updateQuery(updateContext = true) {
|
||||||
|
this.context.filterRawParams[this.id] = this.selectedOptions.length > 0 ? this.selectedOptions : undefined;
|
||||||
this.displayValue$.next(this.selectedOptions.map((option) => option.value).join(', '));
|
this.displayValue$.next(this.selectedOptions.map((option) => option.value).join(', '));
|
||||||
if (this.context && this.settings && this.settings.field) {
|
if (this.context && this.settings && this.settings.field) {
|
||||||
let queryFragments;
|
let queryFragments;
|
||||||
@ -117,7 +141,9 @@ export class SearchFilterAutocompleteChipsComponent implements SearchWidget, OnI
|
|||||||
queryFragments = this.selectedOptions.map((val) => val.query ?? `${this.settings.field}:"${val.value}"`);
|
queryFragments = this.selectedOptions.map((val) => val.query ?? `${this.settings.field}:"${val.value}"`);
|
||||||
}
|
}
|
||||||
this.context.queryFragments[this.id] = queryFragments.join(' OR ');
|
this.context.queryFragments[this.id] = queryFragments.join(' OR ');
|
||||||
this.context.update();
|
if (updateContext) {
|
||||||
|
this.context.update();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Component, ElementRef, Input, ViewChild, ViewEncapsulation } from '@angular/core';
|
import { AfterViewInit, ChangeDetectorRef, Component, ElementRef, Input, ViewChild, ViewEncapsulation } from '@angular/core';
|
||||||
import { SearchCategory } from '../../../models/search-category.interface';
|
import { SearchCategory } from '../../../models/search-category.interface';
|
||||||
import { ConfigurableFocusTrap, ConfigurableFocusTrapFactory } from '@angular/cdk/a11y';
|
import { ConfigurableFocusTrap, ConfigurableFocusTrapFactory } from '@angular/cdk/a11y';
|
||||||
import { MatMenuModule, MatMenuTrigger } from '@angular/material/menu';
|
import { MatMenuModule, MatMenuTrigger } from '@angular/material/menu';
|
||||||
@ -26,6 +26,7 @@ import { TranslateModule } from '@ngx-translate/core';
|
|||||||
import { MatIconModule } from '@angular/material/icon';
|
import { MatIconModule } from '@angular/material/icon';
|
||||||
import { SearchFilterMenuCardComponent } from '../search-filter-menu-card/search-filter-menu-card.component';
|
import { SearchFilterMenuCardComponent } from '../search-filter-menu-card/search-filter-menu-card.component';
|
||||||
import { MatButtonModule } from '@angular/material/button';
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
|
import { first } from 'rxjs/operators';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'adf-search-widget-chip',
|
selector: 'adf-search-widget-chip',
|
||||||
@ -50,7 +51,7 @@ import { MatButtonModule } from '@angular/material/button';
|
|||||||
],
|
],
|
||||||
encapsulation: ViewEncapsulation.None
|
encapsulation: ViewEncapsulation.None
|
||||||
})
|
})
|
||||||
export class SearchWidgetChipComponent {
|
export class SearchWidgetChipComponent implements AfterViewInit {
|
||||||
@Input()
|
@Input()
|
||||||
category: SearchCategory;
|
category: SearchCategory;
|
||||||
|
|
||||||
@ -66,7 +67,16 @@ export class SearchWidgetChipComponent {
|
|||||||
focusTrap: ConfigurableFocusTrap;
|
focusTrap: ConfigurableFocusTrap;
|
||||||
chipIcon = 'keyboard_arrow_down';
|
chipIcon = 'keyboard_arrow_down';
|
||||||
|
|
||||||
constructor(private focusTrapFactory: ConfigurableFocusTrapFactory) {}
|
constructor(private readonly cd: ChangeDetectorRef, private readonly focusTrapFactory: ConfigurableFocusTrapFactory) {}
|
||||||
|
|
||||||
|
ngAfterViewInit(): void {
|
||||||
|
this.widgetContainerComponent
|
||||||
|
?.getDisplayValue()
|
||||||
|
.pipe(first())
|
||||||
|
.subscribe(() => {
|
||||||
|
this.cd.detectChanges();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
onMenuOpen() {
|
onMenuOpen() {
|
||||||
if (this.menuContainer && !this.focusTrap) {
|
if (this.menuContainer && !this.focusTrap) {
|
||||||
|
@ -19,6 +19,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|||||||
import { By } from '@angular/platform-browser';
|
import { By } from '@angular/platform-browser';
|
||||||
import { ContentTestingModule } from '../../../testing/content.testing.module';
|
import { ContentTestingModule } from '../../../testing/content.testing.module';
|
||||||
import { LogicalSearchCondition, LogicalSearchFields, SearchLogicalFilterComponent } from './search-logical-filter.component';
|
import { LogicalSearchCondition, LogicalSearchFields, SearchLogicalFilterComponent } from './search-logical-filter.component';
|
||||||
|
import { ReplaySubject } from 'rxjs';
|
||||||
|
|
||||||
describe('SearchLogicalFilterComponent', () => {
|
describe('SearchLogicalFilterComponent', () => {
|
||||||
let component: SearchLogicalFilterComponent;
|
let component: SearchLogicalFilterComponent;
|
||||||
@ -36,7 +37,9 @@ describe('SearchLogicalFilterComponent', () => {
|
|||||||
queryFragments: {
|
queryFragments: {
|
||||||
logic: ''
|
logic: ''
|
||||||
},
|
},
|
||||||
update: () => {}
|
filterRawParams: {},
|
||||||
|
populateFilters: new ReplaySubject(1),
|
||||||
|
update: jasmine.createSpy('update')
|
||||||
} as any;
|
} as any;
|
||||||
component.settings = { field: 'field1,field2', allowUpdateOnChange: true, hideDefaultAction: false };
|
component.settings = { field: 'field1,field2', allowUpdateOnChange: true, hideDefaultAction: false };
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
@ -131,51 +134,50 @@ describe('SearchLogicalFilterComponent', () => {
|
|||||||
const searchCondition: LogicalSearchCondition = { matchAll: 'test1', matchAny: 'test2', exclude: 'test3', matchExact: 'test4' };
|
const searchCondition: LogicalSearchCondition = { matchAll: 'test1', matchAny: 'test2', exclude: 'test3', matchExact: 'test4' };
|
||||||
component.setValue(searchCondition);
|
component.setValue(searchCondition);
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
spyOn(component.context, 'update');
|
|
||||||
spyOn(component.displayValue$, 'next');
|
spyOn(component.displayValue$, 'next');
|
||||||
component.reset();
|
component.reset();
|
||||||
expect(component.context.queryFragments[component.id]).toBe('');
|
expect(component.context.queryFragments[component.id]).toBe('');
|
||||||
expect(component.context.update).toHaveBeenCalled();
|
expect(component.context.update).toHaveBeenCalled();
|
||||||
expect(component.getCurrentValue()).toEqual({ matchAll: '', matchAny: '', exclude: '', matchExact: '' });
|
expect(component.getCurrentValue()).toEqual({ matchAll: '', matchAny: '', exclude: '', matchExact: '' });
|
||||||
expect(component.displayValue$.next).toHaveBeenCalledWith('');
|
expect(component.displayValue$.next).toHaveBeenCalledWith('');
|
||||||
|
expect(component.context.filterRawParams[component.id]).toEqual(component.getCurrentValue());
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should form correct query from match all field', () => {
|
it('should form correct query from match all field', () => {
|
||||||
spyOn(component.context, 'update');
|
|
||||||
enterNewPhrase(' test1 test2 ', 0);
|
enterNewPhrase(' test1 test2 ', 0);
|
||||||
component.submitValues();
|
component.submitValues();
|
||||||
expect(component.context.update).toHaveBeenCalled();
|
expect(component.context.update).toHaveBeenCalled();
|
||||||
expect(component.context.queryFragments[component.id]).toBe('((field1:"test1" AND field1:"test2") OR (field2:"test1" AND field2:"test2"))');
|
expect(component.context.queryFragments[component.id]).toBe('((field1:"test1" AND field1:"test2") OR (field2:"test1" AND field2:"test2"))');
|
||||||
|
expect(component.context.filterRawParams[component.id]).toEqual(component.getCurrentValue());
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should form correct query from match any field', () => {
|
it('should form correct query from match any field', () => {
|
||||||
spyOn(component.context, 'update');
|
|
||||||
enterNewPhrase(' test3 test4', 1);
|
enterNewPhrase(' test3 test4', 1);
|
||||||
component.submitValues();
|
component.submitValues();
|
||||||
expect(component.context.update).toHaveBeenCalled();
|
expect(component.context.update).toHaveBeenCalled();
|
||||||
expect(component.context.queryFragments[component.id]).toBe('((field1:"test3" OR field1:"test4") OR (field2:"test3" OR field2:"test4"))');
|
expect(component.context.queryFragments[component.id]).toBe('((field1:"test3" OR field1:"test4") OR (field2:"test3" OR field2:"test4"))');
|
||||||
|
expect(component.context.filterRawParams[component.id]).toEqual(component.getCurrentValue());
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should form correct query from exclude field', () => {
|
it('should form correct query from exclude field', () => {
|
||||||
spyOn(component.context, 'update');
|
|
||||||
enterNewPhrase('test5 test6 ', 2);
|
enterNewPhrase('test5 test6 ', 2);
|
||||||
component.submitValues();
|
component.submitValues();
|
||||||
expect(component.context.update).toHaveBeenCalled();
|
expect(component.context.update).toHaveBeenCalled();
|
||||||
expect(component.context.queryFragments[component.id]).toBe(
|
expect(component.context.queryFragments[component.id]).toBe(
|
||||||
'((NOT field1:"test5" AND NOT field1:"test6") AND (NOT field2:"test5" AND NOT field2:"test6"))'
|
'((NOT field1:"test5" AND NOT field1:"test6") AND (NOT field2:"test5" AND NOT field2:"test6"))'
|
||||||
);
|
);
|
||||||
|
expect(component.context.filterRawParams[component.id]).toEqual(component.getCurrentValue());
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should form correct query from match exact field and trim it', () => {
|
it('should form correct query from match exact field and trim it', () => {
|
||||||
spyOn(component.context, 'update');
|
|
||||||
enterNewPhrase(' test7 test8 ', 3);
|
enterNewPhrase(' test7 test8 ', 3);
|
||||||
component.submitValues();
|
component.submitValues();
|
||||||
expect(component.context.update).toHaveBeenCalled();
|
expect(component.context.update).toHaveBeenCalled();
|
||||||
expect(component.context.queryFragments[component.id]).toBe('((field1:"test7 test8") OR (field2:"test7 test8"))');
|
expect(component.context.queryFragments[component.id]).toBe('((field1:"test7 test8") OR (field2:"test7 test8"))');
|
||||||
|
expect(component.context.filterRawParams[component.id]).toEqual(component.getCurrentValue());
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should form correct joined query from all fields', () => {
|
it('should form correct joined query from all fields', () => {
|
||||||
spyOn(component.context, 'update');
|
|
||||||
enterNewPhrase('test1', 0);
|
enterNewPhrase('test1', 0);
|
||||||
enterNewPhrase('test2', 1);
|
enterNewPhrase('test2', 1);
|
||||||
enterNewPhrase('test3', 2);
|
enterNewPhrase('test3', 2);
|
||||||
@ -187,5 +189,20 @@ describe('SearchLogicalFilterComponent', () => {
|
|||||||
const subQuery4 = '((field1:"test4") OR (field2:"test4"))';
|
const subQuery4 = '((field1:"test4") OR (field2:"test4"))';
|
||||||
expect(component.context.update).toHaveBeenCalled();
|
expect(component.context.update).toHaveBeenCalled();
|
||||||
expect(component.context.queryFragments[component.id]).toBe(`${subQuery1} AND ${subQuery2} AND ${subQuery4} AND ${subQuery3}`);
|
expect(component.context.queryFragments[component.id]).toBe(`${subQuery1} AND ${subQuery2} AND ${subQuery4} AND ${subQuery3}`);
|
||||||
|
expect(component.context.filterRawParams[component.id]).toEqual(component.getCurrentValue());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should populate filter state when populate filters event has been observed', () => {
|
||||||
|
component.context.filterLoaded = new ReplaySubject(1);
|
||||||
|
spyOn(component.context.filterLoaded, 'next').and.stub();
|
||||||
|
spyOn(component.displayValue$, 'next').and.stub();
|
||||||
|
fixture.detectChanges();
|
||||||
|
component.context.populateFilters.next({ logic: { matchAll: 'test', matchAny: 'test2', matchExact: '', exclude: '' } });
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(component.displayValue$.next).toHaveBeenCalledWith(' SEARCH.LOGICAL_SEARCH.MATCH_ALL: test SEARCH.LOGICAL_SEARCH.MATCH_ANY: test2');
|
||||||
|
expect(component.context.filterRawParams[component.id]).toEqual({ matchAll: 'test', matchAny: 'test2', matchExact: '', exclude: '' });
|
||||||
|
expect(component.searchCondition).toEqual({ matchAll: 'test', matchAny: 'test2', matchExact: '', exclude: '' });
|
||||||
|
expect(component.context.filterLoaded.next).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -15,11 +15,12 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Component, OnInit, ViewEncapsulation } from '@angular/core';
|
import { Component, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core';
|
||||||
import { SearchWidget } from '../../models/search-widget.interface';
|
import { SearchWidget } from '../../models/search-widget.interface';
|
||||||
import { SearchWidgetSettings } from '../../models/search-widget-settings.interface';
|
import { SearchWidgetSettings } from '../../models/search-widget-settings.interface';
|
||||||
import { SearchQueryBuilderService } from '../../services/search-query-builder.service';
|
import { SearchQueryBuilderService } from '../../services/search-query-builder.service';
|
||||||
import { Subject } from 'rxjs';
|
import { ReplaySubject, Subject } from 'rxjs';
|
||||||
|
import { map, takeUntil } from 'rxjs/operators';
|
||||||
import { TranslationService } from '@alfresco/adf-core';
|
import { TranslationService } from '@alfresco/adf-core';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||||
@ -45,7 +46,7 @@ export interface LogicalSearchCondition extends LogicalSearchConditionEnumValued
|
|||||||
styleUrls: ['./search-logical-filter.component.scss'],
|
styleUrls: ['./search-logical-filter.component.scss'],
|
||||||
encapsulation: ViewEncapsulation.None
|
encapsulation: ViewEncapsulation.None
|
||||||
})
|
})
|
||||||
export class SearchLogicalFilterComponent implements SearchWidget, OnInit {
|
export class SearchLogicalFilterComponent implements SearchWidget, OnInit, OnDestroy {
|
||||||
id: string;
|
id: string;
|
||||||
settings?: SearchWidgetSettings;
|
settings?: SearchWidgetSettings;
|
||||||
context?: SearchQueryBuilderService;
|
context?: SearchQueryBuilderService;
|
||||||
@ -53,15 +54,37 @@ export class SearchLogicalFilterComponent implements SearchWidget, OnInit {
|
|||||||
searchCondition: LogicalSearchCondition;
|
searchCondition: LogicalSearchCondition;
|
||||||
fields = Object.keys(LogicalSearchFields);
|
fields = Object.keys(LogicalSearchFields);
|
||||||
LogicalSearchFields = LogicalSearchFields;
|
LogicalSearchFields = LogicalSearchFields;
|
||||||
displayValue$: Subject<string> = new Subject();
|
displayValue$ = new ReplaySubject<string>(1);
|
||||||
|
|
||||||
|
private readonly destroy$ = new Subject<void>();
|
||||||
|
|
||||||
constructor(private translationService: TranslationService) {}
|
constructor(private translationService: TranslationService) {}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.clearSearchInputs();
|
this.clearSearchInputs();
|
||||||
|
this.context.populateFilters
|
||||||
|
.asObservable()
|
||||||
|
.pipe(
|
||||||
|
map((filtersQueries) => filtersQueries[this.id]),
|
||||||
|
takeUntil(this.destroy$)
|
||||||
|
)
|
||||||
|
.subscribe((filterQuery) => {
|
||||||
|
if (filterQuery) {
|
||||||
|
this.searchCondition = filterQuery;
|
||||||
|
this.submitValues(false);
|
||||||
|
} else {
|
||||||
|
this.reset(false);
|
||||||
|
}
|
||||||
|
this.context.filterLoaded.next();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
submitValues() {
|
ngOnDestroy() {
|
||||||
|
this.destroy$.next();
|
||||||
|
this.destroy$.complete();
|
||||||
|
}
|
||||||
|
|
||||||
|
submitValues(updateContext = true) {
|
||||||
if (this.hasValidValue() && this.id && this.context && this.settings && this.settings.field) {
|
if (this.hasValidValue() && this.id && this.context && this.settings && this.settings.field) {
|
||||||
this.updateDisplayValue();
|
this.updateDisplayValue();
|
||||||
const fields = this.settings.field.split(',').map((field) => (field += ':'));
|
const fields = this.settings.field.split(',').map((field) => (field += ':'));
|
||||||
@ -108,9 +131,11 @@ export class SearchLogicalFilterComponent implements SearchWidget, OnInit {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.context.queryFragments[this.id] = query;
|
this.context.queryFragments[this.id] = query;
|
||||||
this.context.update();
|
if (updateContext) {
|
||||||
|
this.context.update();
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
this.reset();
|
this.reset(updateContext);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -127,15 +152,19 @@ export class SearchLogicalFilterComponent implements SearchWidget, OnInit {
|
|||||||
this.updateDisplayValue();
|
this.updateDisplayValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
reset() {
|
reset(updateContext = true) {
|
||||||
if (this.id && this.context) {
|
if (this.id && this.context) {
|
||||||
this.context.queryFragments[this.id] = '';
|
this.context.queryFragments[this.id] = '';
|
||||||
this.clearSearchInputs();
|
this.clearSearchInputs();
|
||||||
this.context.update();
|
this.context.filterRawParams[this.id] = this.searchCondition;
|
||||||
|
if (updateContext) {
|
||||||
|
this.context.update();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private updateDisplayValue(): void {
|
private updateDisplayValue(): void {
|
||||||
|
this.context.filterRawParams[this.id] = this.searchCondition;
|
||||||
if (this.hasValidValue()) {
|
if (this.hasValidValue()) {
|
||||||
const displayValue = Object.keys(this.searchCondition).reduce((acc, key) => {
|
const displayValue = Object.keys(this.searchCondition).reduce((acc, key) => {
|
||||||
const fieldIndex = Object.values(LogicalSearchFields).indexOf(key as LogicalSearchFields);
|
const fieldIndex = Object.values(LogicalSearchFields).indexOf(key as LogicalSearchFields);
|
||||||
|
@ -15,15 +15,31 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { ReplaySubject } from 'rxjs';
|
||||||
import { SearchNumberRangeComponent } from './search-number-range.component';
|
import { SearchNumberRangeComponent } from './search-number-range.component';
|
||||||
import { UntypedFormControl, UntypedFormGroup } from '@angular/forms';
|
import { UntypedFormControl, UntypedFormGroup } from '@angular/forms';
|
||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { ContentTestingModule } from '../../../testing/content.testing.module';
|
||||||
|
|
||||||
describe('SearchNumberRangeComponent', () => {
|
describe('SearchNumberRangeComponent', () => {
|
||||||
|
|
||||||
let component: SearchNumberRangeComponent;
|
let component: SearchNumberRangeComponent;
|
||||||
|
let fixture: ComponentFixture<SearchNumberRangeComponent>;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
component = new SearchNumberRangeComponent();
|
TestBed.configureTestingModule({
|
||||||
|
imports: [ContentTestingModule, SearchNumberRangeComponent]
|
||||||
|
});
|
||||||
|
fixture = TestBed.createComponent(SearchNumberRangeComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
component.id = 'contentSize';
|
||||||
|
component.context = {
|
||||||
|
queryFragments: {
|
||||||
|
contentSize: ''
|
||||||
|
},
|
||||||
|
filterRawParams: {},
|
||||||
|
populateFilters: new ReplaySubject(1),
|
||||||
|
update: jasmine.createSpy('update')
|
||||||
|
} as any;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should setup form elements on init', () => {
|
it('should setup form elements on init', () => {
|
||||||
@ -44,46 +60,32 @@ describe('SearchNumberRangeComponent', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should update query builder on reset', () => {
|
it('should update query builder on reset', () => {
|
||||||
const context: any = {
|
component.context.queryFragments[component.id] = 'query';
|
||||||
queryFragments: {
|
|
||||||
contentSize: 'query'
|
|
||||||
},
|
|
||||||
update: () => {}
|
|
||||||
};
|
|
||||||
|
|
||||||
component.id = 'contentSize';
|
|
||||||
component.context = context;
|
|
||||||
|
|
||||||
spyOn(context, 'update').and.stub();
|
|
||||||
|
|
||||||
component.ngOnInit();
|
component.ngOnInit();
|
||||||
component.reset();
|
component.reset();
|
||||||
|
|
||||||
expect(context.queryFragments.contentSize).toEqual('');
|
expect(component.context.queryFragments.contentSize).toEqual('');
|
||||||
expect(context.update).toHaveBeenCalled();
|
expect(component.context.update).toHaveBeenCalled();
|
||||||
|
expect(component.context.filterRawParams[component.id]).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should update query builder on value changes', () => {
|
it('should update query builder on value changes', () => {
|
||||||
const context: any = {
|
|
||||||
queryFragments: {},
|
|
||||||
update: () => {}
|
|
||||||
};
|
|
||||||
|
|
||||||
component.id = 'contentSize';
|
|
||||||
component.context = context;
|
|
||||||
component.settings = { field: 'cm:content.size' };
|
component.settings = { field: 'cm:content.size' };
|
||||||
|
|
||||||
spyOn(context, 'update').and.stub();
|
|
||||||
|
|
||||||
component.ngOnInit();
|
component.ngOnInit();
|
||||||
component.apply({
|
component.apply(
|
||||||
from: '10',
|
{
|
||||||
to: '20'
|
from: '10',
|
||||||
}, true);
|
to: '20'
|
||||||
|
},
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
const expectedQuery = 'cm:content.size:[10 TO 20]';
|
const expectedQuery = 'cm:content.size:[10 TO 20]';
|
||||||
expect(context.queryFragments[component.id]).toEqual(expectedQuery);
|
expect(component.context.queryFragments[component.id]).toEqual(expectedQuery);
|
||||||
expect(context.update).toHaveBeenCalled();
|
expect(component.context.update).toHaveBeenCalled();
|
||||||
|
expect(component.context.filterRawParams[component.id].from).toEqual('10');
|
||||||
|
expect(component.context.filterRawParams[component.id].to).toEqual('20');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should fetch format from the settings', () => {
|
it('should fetch format from the settings', () => {
|
||||||
@ -108,31 +110,27 @@ describe('SearchNumberRangeComponent', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should format value based on the current pattern', () => {
|
it('should format value based on the current pattern', () => {
|
||||||
const context: any = {
|
|
||||||
queryFragments: {},
|
|
||||||
update: () => {}
|
|
||||||
};
|
|
||||||
|
|
||||||
component.id = 'range1';
|
|
||||||
component.settings = {
|
component.settings = {
|
||||||
field: 'cm:content.size',
|
field: 'cm:content.size',
|
||||||
format: '<{FROM} TO {TO}>'
|
format: '<{FROM} TO {TO}>'
|
||||||
};
|
};
|
||||||
component.context = context;
|
|
||||||
component.ngOnInit();
|
component.ngOnInit();
|
||||||
|
|
||||||
component.apply({ from: '0', to: '100' }, true);
|
component.apply({ from: '0', to: '100' }, true);
|
||||||
expect(context.queryFragments['range1']).toEqual('cm:content.size:<0 TO 100>');
|
expect(component.context.queryFragments[component.id]).toEqual('cm:content.size:<0 TO 100>');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return true if TO value is bigger than FROM value', () => {
|
it('should return true if TO value is bigger than FROM value', () => {
|
||||||
component.ngOnInit();
|
component.ngOnInit();
|
||||||
component.from = new UntypedFormControl('10');
|
component.from = new UntypedFormControl('10');
|
||||||
component.to = new UntypedFormControl('20');
|
component.to = new UntypedFormControl('20');
|
||||||
component.form = new UntypedFormGroup({
|
component.form = new UntypedFormGroup(
|
||||||
from: component.from,
|
{
|
||||||
to: component.to
|
from: component.from,
|
||||||
}, component.formValidator);
|
to: component.to
|
||||||
|
},
|
||||||
|
component.formValidator
|
||||||
|
);
|
||||||
|
|
||||||
expect(component.formValidator).toBeTruthy();
|
expect(component.formValidator).toBeTruthy();
|
||||||
});
|
});
|
||||||
@ -166,4 +164,21 @@ describe('SearchNumberRangeComponent', () => {
|
|||||||
component.from = new UntypedFormControl(-100, component.validators);
|
component.from = new UntypedFormControl(-100, component.validators);
|
||||||
expect(component.from.hasError('min')).toBe(true);
|
expect(component.from.hasError('min')).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should populate filter state when populate filters event has been observed', () => {
|
||||||
|
component.settings = {
|
||||||
|
field: 'cm:content.size'
|
||||||
|
};
|
||||||
|
component.context.filterLoaded = new ReplaySubject(1);
|
||||||
|
spyOn(component.context.filterLoaded, 'next').and.stub();
|
||||||
|
spyOn(component.displayValue$, 'next').and.stub();
|
||||||
|
fixture.detectChanges();
|
||||||
|
component.context.populateFilters.next({ contentSize: { from: '10', to: '100' } });
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(component.displayValue$.next).toHaveBeenCalledWith('10 - 100 ');
|
||||||
|
expect(component.context.filterRawParams[component.id]).toEqual({ from: '10', to: '100' });
|
||||||
|
expect(component.form.value).toEqual({ from: '10', to: '100' });
|
||||||
|
expect(component.context.filterLoaded.next).toHaveBeenCalled();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -21,7 +21,8 @@ import { SearchWidget } from '../../models/search-widget.interface';
|
|||||||
import { SearchWidgetSettings } from '../../models/search-widget-settings.interface';
|
import { SearchWidgetSettings } from '../../models/search-widget-settings.interface';
|
||||||
import { SearchQueryBuilderService } from '../../services/search-query-builder.service';
|
import { SearchQueryBuilderService } from '../../services/search-query-builder.service';
|
||||||
import { LiveErrorStateMatcher } from '../../forms/live-error-state-matcher';
|
import { LiveErrorStateMatcher } from '../../forms/live-error-state-matcher';
|
||||||
import { Subject } from 'rxjs';
|
import { ReplaySubject } from 'rxjs';
|
||||||
|
import { first, map } from 'rxjs/operators';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||||
import { MatInputModule } from '@angular/material/input';
|
import { MatInputModule } from '@angular/material/input';
|
||||||
@ -56,7 +57,7 @@ export class SearchNumberRangeComponent implements SearchWidget, OnInit {
|
|||||||
|
|
||||||
validators: Validators;
|
validators: Validators;
|
||||||
enableChangeUpdate: boolean;
|
enableChangeUpdate: boolean;
|
||||||
displayValue$: Subject<string> = new Subject<string>();
|
displayValue$ = new ReplaySubject<string>(1);
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
if (this.settings) {
|
if (this.settings) {
|
||||||
@ -84,32 +85,54 @@ export class SearchNumberRangeComponent implements SearchWidget, OnInit {
|
|||||||
|
|
||||||
this.enableChangeUpdate = this.settings?.allowUpdateOnChange ?? true;
|
this.enableChangeUpdate = this.settings?.allowUpdateOnChange ?? true;
|
||||||
this.updateDisplayValue();
|
this.updateDisplayValue();
|
||||||
|
this.context.populateFilters
|
||||||
|
.asObservable()
|
||||||
|
.pipe(
|
||||||
|
map((filtersQueries) => filtersQueries[this.id]),
|
||||||
|
first()
|
||||||
|
)
|
||||||
|
.subscribe((filterQuery) => {
|
||||||
|
if (filterQuery) {
|
||||||
|
this.form.patchValue({ from: filterQuery.from, to: filterQuery.to });
|
||||||
|
this.form.markAsDirty();
|
||||||
|
this.apply({ from: filterQuery.from, to: filterQuery.to }, true, false);
|
||||||
|
} else {
|
||||||
|
this.reset(false);
|
||||||
|
}
|
||||||
|
this.context.filterLoaded.next();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
formValidator(formGroup: UntypedFormGroup) {
|
formValidator(formGroup: UntypedFormGroup) {
|
||||||
return parseInt(formGroup.get('from').value, 10) < parseInt(formGroup.get('to').value, 10) ? null : { mismatch: true };
|
return parseInt(formGroup.get('from').value, 10) < parseInt(formGroup.get('to').value, 10) ? null : { mismatch: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
apply(model: { from: string; to: string }, isValid: boolean) {
|
apply(model: { from: string; to: string }, isValid: boolean, updateContext = true) {
|
||||||
if (isValid && this.id && this.context && this.field) {
|
if (isValid && this.id && this.context && this.field) {
|
||||||
this.updateDisplayValue();
|
this.updateDisplayValue();
|
||||||
this.isActive = true;
|
this.isActive = true;
|
||||||
|
|
||||||
const map = new Map<string, string>();
|
const destinationObject = new Map<string, string>();
|
||||||
map.set('FROM', model.from);
|
destinationObject.set('FROM', model.from);
|
||||||
map.set('TO', model.to);
|
destinationObject.set('TO', model.to);
|
||||||
|
|
||||||
const value = this.formatString(this.format, map);
|
const value = this.formatString(this.format, destinationObject);
|
||||||
|
|
||||||
this.context.queryFragments[this.id] = `${this.field}:${value}`;
|
this.context.queryFragments[this.id] = `${this.field}:${value}`;
|
||||||
this.context.update();
|
const filterParam = this.context.filterRawParams[this.id] ?? {};
|
||||||
|
this.context.filterRawParams[this.id] = filterParam;
|
||||||
|
filterParam.from = model.from;
|
||||||
|
filterParam.to = model.to;
|
||||||
|
if (updateContext) {
|
||||||
|
this.context.update();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private formatString(str: string, map: Map<string, string>): string {
|
private formatString(str: string, destinationObject: Map<string, string>): string {
|
||||||
let result = str;
|
let result = str;
|
||||||
|
|
||||||
map.forEach((value, key) => {
|
destinationObject.forEach((value, key) => {
|
||||||
const expr = new RegExp('{' + key + '}', 'gm');
|
const expr = new RegExp('{' + key + '}', 'gm');
|
||||||
result = result.replace(expr, value);
|
result = result.replace(expr, value);
|
||||||
});
|
});
|
||||||
@ -143,7 +166,7 @@ export class SearchNumberRangeComponent implements SearchWidget, OnInit {
|
|||||||
this.updateDisplayValue();
|
this.updateDisplayValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
clear() {
|
clear(updateContext = true) {
|
||||||
this.isActive = false;
|
this.isActive = false;
|
||||||
|
|
||||||
this.form.reset({
|
this.form.reset({
|
||||||
@ -153,16 +176,17 @@ export class SearchNumberRangeComponent implements SearchWidget, OnInit {
|
|||||||
|
|
||||||
if (this.id && this.context) {
|
if (this.id && this.context) {
|
||||||
this.context.queryFragments[this.id] = '';
|
this.context.queryFragments[this.id] = '';
|
||||||
|
this.context.filterRawParams[this.id] = undefined;
|
||||||
this.updateDisplayValue();
|
this.updateDisplayValue();
|
||||||
if (this.enableChangeUpdate) {
|
if (this.enableChangeUpdate && updateContext) {
|
||||||
this.context.update();
|
this.context.update();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
reset() {
|
reset(updateContext = true) {
|
||||||
this.clear();
|
this.clear();
|
||||||
if (this.id && this.context) {
|
if (this.id && this.context && updateContext) {
|
||||||
this.context.update();
|
this.context.update();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,194 +15,44 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { SearchCheckListComponent, SearchListOption } from '../search-check-list/search-check-list.component';
|
|
||||||
import { SearchFilterList } from '../../models/search-filter-list.model';
|
|
||||||
import { ContentTestingModule } from '../../../testing/content.testing.module';
|
import { ContentTestingModule } from '../../../testing/content.testing.module';
|
||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
import { sizeOptions, stepOne, stepThree } from '../../../mock';
|
import { SearchPanelComponent } from './search-panel.component';
|
||||||
import { HarnessLoader, TestKey } from '@angular/cdk/testing';
|
|
||||||
import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
|
|
||||||
import { MatCheckboxHarness } from '@angular/material/checkbox/testing';
|
|
||||||
import { By } from '@angular/platform-browser';
|
import { By } from '@angular/platform-browser';
|
||||||
|
import { ContentNodeSelectorPanelService } from '../../../content-node-selector';
|
||||||
|
import { SearchCategory } from '../../models';
|
||||||
|
|
||||||
describe('SearchCheckListComponent', () => {
|
describe('SearchPanelComponent', () => {
|
||||||
let loader: HarnessLoader;
|
let fixture: ComponentFixture<SearchPanelComponent>;
|
||||||
let fixture: ComponentFixture<SearchCheckListComponent>;
|
let contentNodeSelectorPanelService: ContentNodeSelectorPanelService;
|
||||||
let component: SearchCheckListComponent;
|
|
||||||
|
const getSearchFilter = () => fixture.debugElement.query(By.css('.app-search-settings'));
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [ContentTestingModule]
|
imports: [ContentTestingModule]
|
||||||
});
|
});
|
||||||
fixture = TestBed.createComponent(SearchCheckListComponent);
|
fixture = TestBed.createComponent(SearchPanelComponent);
|
||||||
component = fixture.componentInstance;
|
contentNodeSelectorPanelService = TestBed.inject(ContentNodeSelectorPanelService);
|
||||||
|
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
loader = TestbedHarnessEnvironment.loader(fixture);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should setup options from settings', () => {
|
it('should not render search filter when no custom models are available', () => {
|
||||||
const options: any = [
|
contentNodeSelectorPanelService.customModels = [];
|
||||||
{ name: 'Folder', value: `TYPE:'cm:folder'` },
|
spyOn(contentNodeSelectorPanelService, 'convertCustomModelPropertiesToSearchCategories').and.returnValue([]);
|
||||||
{ name: 'Document', value: `TYPE:'cm:content'` }
|
fixture.detectChanges();
|
||||||
|
expect(getSearchFilter()).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render search filter when some custom models are available', () => {
|
||||||
|
const categoriesMock: SearchCategory[] = [
|
||||||
|
{ id: 'model1', name: 'model1', enabled: true, expanded: false, component: { selector: 'test', settings: { field: 'test' } } },
|
||||||
|
{ id: 'model2', name: 'model2', enabled: true, expanded: false, component: { selector: 'test2', settings: { field: 'test2' } } }
|
||||||
];
|
];
|
||||||
component.settings = { options } as any;
|
contentNodeSelectorPanelService.customModels = ['model1', 'model2'];
|
||||||
component.ngOnInit();
|
spyOn(contentNodeSelectorPanelService, 'convertCustomModelPropertiesToSearchCategories').and.returnValue(categoriesMock);
|
||||||
|
|
||||||
expect(component.options.items).toEqual(options);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle enter key as click on checkboxes', async () => {
|
|
||||||
component.options = new SearchFilterList<SearchListOption>([
|
|
||||||
{ name: 'Folder', value: `TYPE:'cm:folder'`, checked: false },
|
|
||||||
{ name: 'Document', value: `TYPE:'cm:content'`, checked: false }
|
|
||||||
]);
|
|
||||||
|
|
||||||
component.ngOnInit();
|
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
expect(getSearchFilter()).toBeDefined();
|
||||||
const options = await loader.getAllHarnesses(MatCheckboxHarness);
|
|
||||||
await (await options[0].host()).sendKeys(TestKey.ENTER);
|
|
||||||
expect(await options[0].isChecked()).toBe(true);
|
|
||||||
|
|
||||||
await (await options[0].host()).sendKeys(TestKey.ENTER);
|
|
||||||
expect(await options[0].isChecked()).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should setup operator from the settings', () => {
|
|
||||||
component.settings = { operator: 'AND' } as any;
|
|
||||||
component.ngOnInit();
|
|
||||||
expect(component.operator).toBe('AND');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should use OR operator by default', () => {
|
|
||||||
component.settings = { operator: null } as any;
|
|
||||||
component.ngOnInit();
|
|
||||||
expect(component.operator).toBe('OR');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should update query builder on checkbox change', () => {
|
|
||||||
component.options = new SearchFilterList<SearchListOption>([
|
|
||||||
{ name: 'Folder', value: `TYPE:'cm:folder'`, checked: false },
|
|
||||||
{ name: 'Document', value: `TYPE:'cm:content'`, checked: false }
|
|
||||||
]);
|
|
||||||
|
|
||||||
component.id = 'checklist';
|
|
||||||
component.context = {
|
|
||||||
queryFragments: {},
|
|
||||||
update: () => {}
|
|
||||||
} as any;
|
|
||||||
|
|
||||||
component.ngOnInit();
|
|
||||||
|
|
||||||
spyOn(component.context, 'update').and.stub();
|
|
||||||
|
|
||||||
component.changeHandler({ checked: true } as any, component.options.items[0]);
|
|
||||||
|
|
||||||
expect(component.context.queryFragments[component.id]).toEqual(`TYPE:'cm:folder'`);
|
|
||||||
|
|
||||||
component.changeHandler({ checked: true } as any, component.options.items[1]);
|
|
||||||
|
|
||||||
expect(component.context.queryFragments[component.id]).toEqual(`TYPE:'cm:folder' OR TYPE:'cm:content'`);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should reset selected boxes', () => {
|
|
||||||
component.options = new SearchFilterList<SearchListOption>([
|
|
||||||
{ name: 'Folder', value: `TYPE:'cm:folder'`, checked: true },
|
|
||||||
{ name: 'Document', value: `TYPE:'cm:content'`, checked: true }
|
|
||||||
]);
|
|
||||||
|
|
||||||
component.reset();
|
|
||||||
|
|
||||||
expect(component.options.items[0].checked).toBeFalsy();
|
|
||||||
expect(component.options.items[1].checked).toBeFalsy();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should update query builder on reset', () => {
|
|
||||||
component.id = 'checklist';
|
|
||||||
component.context = {
|
|
||||||
queryFragments: {
|
|
||||||
checklist: 'query'
|
|
||||||
},
|
|
||||||
update: () => {}
|
|
||||||
} as any;
|
|
||||||
spyOn(component.context, 'update').and.stub();
|
|
||||||
|
|
||||||
component.ngOnInit();
|
|
||||||
component.options = new SearchFilterList<SearchListOption>([
|
|
||||||
{ name: 'Folder', value: `TYPE:'cm:folder'`, checked: true },
|
|
||||||
{ name: 'Document', value: `TYPE:'cm:content'`, checked: true }
|
|
||||||
]);
|
|
||||||
|
|
||||||
component.reset();
|
|
||||||
|
|
||||||
expect(component.context.update).toHaveBeenCalled();
|
|
||||||
expect(component.context.queryFragments[component.id]).toBe('');
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Pagination', () => {
|
|
||||||
it('should show 5 items when pageSize not defined', async () => {
|
|
||||||
component.id = 'checklist';
|
|
||||||
component.context = {
|
|
||||||
queryFragments: {
|
|
||||||
checklist: 'query'
|
|
||||||
},
|
|
||||||
update: () => {}
|
|
||||||
} as any;
|
|
||||||
component.settings = { options: sizeOptions } as any;
|
|
||||||
|
|
||||||
component.ngOnInit();
|
|
||||||
fixture.detectChanges();
|
|
||||||
|
|
||||||
const options = await loader.getAllHarnesses(MatCheckboxHarness);
|
|
||||||
expect(options.length).toEqual(5);
|
|
||||||
|
|
||||||
const labels = await Promise.all(Array.from(options).map(async (element) => element.getLabelText()));
|
|
||||||
expect(labels).toEqual(stepOne);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should show all items when pageSize is high', async () => {
|
|
||||||
component.id = 'checklist';
|
|
||||||
component.context = {
|
|
||||||
queryFragments: {
|
|
||||||
checklist: 'query'
|
|
||||||
},
|
|
||||||
update: () => {}
|
|
||||||
} as any;
|
|
||||||
component.settings = { pageSize: 15, options: sizeOptions } as any;
|
|
||||||
component.ngOnInit();
|
|
||||||
fixture.detectChanges();
|
|
||||||
|
|
||||||
const options = await loader.getAllHarnesses(MatCheckboxHarness);
|
|
||||||
expect(options.length).toEqual(13);
|
|
||||||
|
|
||||||
const labels = await Promise.all(Array.from(options).map(async (element) => element.getLabelText()));
|
|
||||||
expect(labels).toEqual(stepThree);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should able to check/reset the checkbox', async () => {
|
|
||||||
component.id = 'checklist';
|
|
||||||
component.context = {
|
|
||||||
queryFragments: {
|
|
||||||
checklist: 'query'
|
|
||||||
},
|
|
||||||
update: () => {}
|
|
||||||
} as any;
|
|
||||||
component.settings = { options: sizeOptions } as any;
|
|
||||||
spyOn(component, 'submitValues').and.stub();
|
|
||||||
component.ngOnInit();
|
|
||||||
fixture.detectChanges();
|
|
||||||
|
|
||||||
const checkbox = await loader.getHarness(MatCheckboxHarness);
|
|
||||||
await checkbox.check();
|
|
||||||
|
|
||||||
expect(component.submitValues).toHaveBeenCalled();
|
|
||||||
|
|
||||||
const clearAllElement = fixture.debugElement.query(By.css('button[title="SEARCH.FILTER.ACTIONS.CLEAR-ALL"]'));
|
|
||||||
clearAllElement.triggerEventHandler('click', {});
|
|
||||||
fixture.detectChanges();
|
|
||||||
|
|
||||||
expect(await checkbox.isChecked()).toBe(false);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -46,6 +46,7 @@
|
|||||||
<p class="adf-search-properties-file-type-label">{{ 'SEARCH.SEARCH_PROPERTIES.FILE_TYPE' | translate }}</p>
|
<p class="adf-search-properties-file-type-label">{{ 'SEARCH.SEARCH_PROPERTIES.FILE_TYPE' | translate }}</p>
|
||||||
<adf-search-chip-autocomplete-input
|
<adf-search-chip-autocomplete-input
|
||||||
[autocompleteOptions]="autocompleteOptions"
|
[autocompleteOptions]="autocompleteOptions"
|
||||||
|
[preselectedOptions]="preselectedOptions"
|
||||||
(optionsChanged)="selectedExtensions = $event"
|
(optionsChanged)="selectedExtensions = $event"
|
||||||
[onReset$]="reset$"
|
[onReset$]="reset$"
|
||||||
[allowOnlyPredefinedValues]="false"
|
[allowOnlyPredefinedValues]="false"
|
||||||
|
@ -24,7 +24,7 @@ import { FileSizeUnit } from './file-size-unit.enum';
|
|||||||
import { FileSizeOperator } from './file-size-operator.enum';
|
import { FileSizeOperator } from './file-size-operator.enum';
|
||||||
import { SearchProperties } from './search-properties';
|
import { SearchProperties } from './search-properties';
|
||||||
import { SearchChipAutocompleteInputComponent } from '../search-chip-autocomplete-input';
|
import { SearchChipAutocompleteInputComponent } from '../search-chip-autocomplete-input';
|
||||||
import { SearchQueryBuilderService } from '../../services/search-query-builder.service';
|
import { ReplaySubject } from 'rxjs';
|
||||||
|
|
||||||
describe('SearchPropertiesComponent', () => {
|
describe('SearchPropertiesComponent', () => {
|
||||||
let component: SearchPropertiesComponent;
|
let component: SearchPropertiesComponent;
|
||||||
@ -66,6 +66,15 @@ describe('SearchPropertiesComponent', () => {
|
|||||||
|
|
||||||
fixture = TestBed.createComponent(SearchPropertiesComponent);
|
fixture = TestBed.createComponent(SearchPropertiesComponent);
|
||||||
component = fixture.componentInstance;
|
component = fixture.componentInstance;
|
||||||
|
component.id = 'properties';
|
||||||
|
component.context = {
|
||||||
|
queryFragments: {
|
||||||
|
properties: ''
|
||||||
|
},
|
||||||
|
filterRawParams: {},
|
||||||
|
populateFilters: new ReplaySubject(1),
|
||||||
|
update: jasmine.createSpy('update')
|
||||||
|
} as any;
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('File size', () => {
|
describe('File size', () => {
|
||||||
@ -187,14 +196,11 @@ describe('SearchPropertiesComponent', () => {
|
|||||||
const nameField = 'cm:name';
|
const nameField = 'cm:name';
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
component.id = 'properties';
|
|
||||||
component.settings = {
|
component.settings = {
|
||||||
field: `${sizeField},${nameField}`
|
field: `${sizeField},${nameField}`
|
||||||
};
|
};
|
||||||
component.context = TestBed.inject(SearchQueryBuilderService);
|
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
spyOn(component.displayValue$, 'next');
|
spyOn(component.displayValue$, 'next');
|
||||||
spyOn(component.context, 'update');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not search when settings is not set', () => {
|
it('should not search when settings is not set', () => {
|
||||||
@ -203,7 +209,6 @@ describe('SearchPropertiesComponent', () => {
|
|||||||
|
|
||||||
component.submitValues();
|
component.submitValues();
|
||||||
expect(component.displayValue$.next).not.toHaveBeenCalled();
|
expect(component.displayValue$.next).not.toHaveBeenCalled();
|
||||||
expect(component.context.queryFragments[component.id]).toBeUndefined();
|
|
||||||
expect(component.context.update).not.toHaveBeenCalled();
|
expect(component.context.update).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -219,6 +224,10 @@ describe('SearchPropertiesComponent', () => {
|
|||||||
component.submitValues();
|
component.submitValues();
|
||||||
expect(component.displayValue$.next).toHaveBeenCalledWith('');
|
expect(component.displayValue$.next).toHaveBeenCalledWith('');
|
||||||
expect(component.context.queryFragments[component.id]).toBe('');
|
expect(component.context.queryFragments[component.id]).toBe('');
|
||||||
|
expect(component.context.filterRawParams[component.id]).toEqual({
|
||||||
|
fileExtensions: undefined,
|
||||||
|
fileSizeCondition: { fileSize: null, fileSizeOperator: FileSizeOperator.AT_LEAST, fileSizeUnit: FileSizeUnit.KB }
|
||||||
|
});
|
||||||
expect(component.context.update).toHaveBeenCalled();
|
expect(component.context.update).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -230,6 +239,14 @@ describe('SearchPropertiesComponent', () => {
|
|||||||
'SEARCH.SEARCH_PROPERTIES.FILE_SIZE_OPERATOR.AT_LEAST 321 SEARCH.SEARCH_PROPERTIES.FILE_SIZE_UNIT_ABBREVIATION.KB'
|
'SEARCH.SEARCH_PROPERTIES.FILE_SIZE_OPERATOR.AT_LEAST 321 SEARCH.SEARCH_PROPERTIES.FILE_SIZE_UNIT_ABBREVIATION.KB'
|
||||||
);
|
);
|
||||||
expect(component.context.queryFragments[component.id]).toBe(`${sizeField}:[328704 TO MAX]`);
|
expect(component.context.queryFragments[component.id]).toBe(`${sizeField}:[328704 TO MAX]`);
|
||||||
|
expect(component.context.filterRawParams[component.id]).toEqual({
|
||||||
|
fileExtensions: undefined,
|
||||||
|
fileSizeCondition: {
|
||||||
|
fileSizeOperator: 'SEARCH.SEARCH_PROPERTIES.FILE_SIZE_OPERATOR.AT_LEAST',
|
||||||
|
fileSize: 321,
|
||||||
|
fileSizeUnit: FileSizeUnit.KB
|
||||||
|
}
|
||||||
|
});
|
||||||
expect(component.context.update).toHaveBeenCalled();
|
expect(component.context.update).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -247,6 +264,14 @@ describe('SearchPropertiesComponent', () => {
|
|||||||
'SEARCH.SEARCH_PROPERTIES.FILE_SIZE_OPERATOR.AT_MOST 321 SEARCH.SEARCH_PROPERTIES.FILE_SIZE_UNIT_ABBREVIATION.MB'
|
'SEARCH.SEARCH_PROPERTIES.FILE_SIZE_OPERATOR.AT_MOST 321 SEARCH.SEARCH_PROPERTIES.FILE_SIZE_UNIT_ABBREVIATION.MB'
|
||||||
);
|
);
|
||||||
expect(component.context.queryFragments[component.id]).toBe(`${sizeField}:[0 TO 336592896]`);
|
expect(component.context.queryFragments[component.id]).toBe(`${sizeField}:[0 TO 336592896]`);
|
||||||
|
expect(component.context.filterRawParams[component.id]).toEqual({
|
||||||
|
fileExtensions: undefined,
|
||||||
|
fileSizeCondition: {
|
||||||
|
fileSizeOperator: 'SEARCH.SEARCH_PROPERTIES.FILE_SIZE_OPERATOR.AT_MOST',
|
||||||
|
fileSize: 321,
|
||||||
|
fileSizeUnit: FileSizeUnit.MB
|
||||||
|
}
|
||||||
|
});
|
||||||
expect(component.context.update).toHaveBeenCalled();
|
expect(component.context.update).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -264,6 +289,14 @@ describe('SearchPropertiesComponent', () => {
|
|||||||
'SEARCH.SEARCH_PROPERTIES.FILE_SIZE_OPERATOR.EXACTLY 321 SEARCH.SEARCH_PROPERTIES.FILE_SIZE_UNIT_ABBREVIATION.GB'
|
'SEARCH.SEARCH_PROPERTIES.FILE_SIZE_OPERATOR.EXACTLY 321 SEARCH.SEARCH_PROPERTIES.FILE_SIZE_UNIT_ABBREVIATION.GB'
|
||||||
);
|
);
|
||||||
expect(component.context.queryFragments[component.id]).toBe(`${sizeField}:[344671125504 TO 344671125504]`);
|
expect(component.context.queryFragments[component.id]).toBe(`${sizeField}:[344671125504 TO 344671125504]`);
|
||||||
|
expect(component.context.filterRawParams[component.id]).toEqual({
|
||||||
|
fileExtensions: undefined,
|
||||||
|
fileSizeCondition: {
|
||||||
|
fileSizeOperator: 'SEARCH.SEARCH_PROPERTIES.FILE_SIZE_OPERATOR.EXACTLY',
|
||||||
|
fileSize: 321,
|
||||||
|
fileSizeUnit: FileSizeUnit.GB
|
||||||
|
}
|
||||||
|
});
|
||||||
expect(component.context.update).toHaveBeenCalled();
|
expect(component.context.update).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -274,6 +307,14 @@ describe('SearchPropertiesComponent', () => {
|
|||||||
component.submitValues();
|
component.submitValues();
|
||||||
expect(component.displayValue$.next).toHaveBeenCalledWith('pdf');
|
expect(component.displayValue$.next).toHaveBeenCalledWith('pdf');
|
||||||
expect(component.context.queryFragments[component.id]).toBe(`${nameField}:("*.${extension.value}")`);
|
expect(component.context.queryFragments[component.id]).toBe(`${nameField}:("*.${extension.value}")`);
|
||||||
|
expect(component.context.filterRawParams[component.id]).toEqual({
|
||||||
|
fileExtensions: ['pdf'],
|
||||||
|
fileSizeCondition: {
|
||||||
|
fileSizeOperator: 'SEARCH.SEARCH_PROPERTIES.FILE_SIZE_OPERATOR.AT_LEAST',
|
||||||
|
fileSize: null,
|
||||||
|
fileSizeUnit: FileSizeUnit.KB
|
||||||
|
}
|
||||||
|
});
|
||||||
expect(component.context.update).toHaveBeenCalled();
|
expect(component.context.update).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -283,6 +324,14 @@ describe('SearchPropertiesComponent', () => {
|
|||||||
component.submitValues();
|
component.submitValues();
|
||||||
expect(component.displayValue$.next).toHaveBeenCalledWith('pdf, txt');
|
expect(component.displayValue$.next).toHaveBeenCalledWith('pdf, txt');
|
||||||
expect(component.context.queryFragments[component.id]).toBe(`${nameField}:("*.pdf" OR "*.txt")`);
|
expect(component.context.queryFragments[component.id]).toBe(`${nameField}:("*.pdf" OR "*.txt")`);
|
||||||
|
expect(component.context.filterRawParams[component.id]).toEqual({
|
||||||
|
fileExtensions: ['pdf', 'txt'],
|
||||||
|
fileSizeCondition: {
|
||||||
|
fileSizeOperator: 'SEARCH.SEARCH_PROPERTIES.FILE_SIZE_OPERATOR.AT_LEAST',
|
||||||
|
fileSize: null,
|
||||||
|
fileSizeUnit: FileSizeUnit.KB
|
||||||
|
}
|
||||||
|
});
|
||||||
expect(component.context.update).toHaveBeenCalled();
|
expect(component.context.update).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -295,6 +344,14 @@ describe('SearchPropertiesComponent', () => {
|
|||||||
'SEARCH.SEARCH_PROPERTIES.FILE_SIZE_OPERATOR.AT_LEAST 321 SEARCH.SEARCH_PROPERTIES.FILE_SIZE_UNIT_ABBREVIATION.KB, pdf, txt'
|
'SEARCH.SEARCH_PROPERTIES.FILE_SIZE_OPERATOR.AT_LEAST 321 SEARCH.SEARCH_PROPERTIES.FILE_SIZE_UNIT_ABBREVIATION.KB, pdf, txt'
|
||||||
);
|
);
|
||||||
expect(component.context.queryFragments[component.id]).toBe(`${sizeField}:[328704 TO MAX] AND ${nameField}:("*.pdf" OR "*.txt")`);
|
expect(component.context.queryFragments[component.id]).toBe(`${sizeField}:[328704 TO MAX] AND ${nameField}:("*.pdf" OR "*.txt")`);
|
||||||
|
expect(component.context.filterRawParams[component.id]).toEqual({
|
||||||
|
fileExtensions: ['pdf', 'txt'],
|
||||||
|
fileSizeCondition: {
|
||||||
|
fileSizeOperator: 'SEARCH.SEARCH_PROPERTIES.FILE_SIZE_OPERATOR.AT_LEAST',
|
||||||
|
fileSize: 321,
|
||||||
|
fileSizeUnit: FileSizeUnit.KB
|
||||||
|
}
|
||||||
|
});
|
||||||
expect(component.context.update).toHaveBeenCalled();
|
expect(component.context.update).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -377,14 +434,12 @@ describe('SearchPropertiesComponent', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should clear the queryFragments for the component id and call update', () => {
|
it('should clear the queryFragments for the component id and call update', () => {
|
||||||
component.context = TestBed.inject(SearchQueryBuilderService);
|
|
||||||
component.id = 'test-id';
|
|
||||||
component.context.queryFragments[component.id] = 'test-query';
|
component.context.queryFragments[component.id] = 'test-query';
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
spyOn(component.context, 'update');
|
|
||||||
component.reset();
|
component.reset();
|
||||||
|
|
||||||
expect(component.context.queryFragments[component.id]).toBe('');
|
expect(component.context.queryFragments[component.id]).toBe('');
|
||||||
|
expect(component.context.filterRawParams[component.id]).toBeUndefined();
|
||||||
expect(component.context.update).toHaveBeenCalled();
|
expect(component.context.update).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -411,20 +466,25 @@ describe('SearchPropertiesComponent', () => {
|
|||||||
it('should search based on passed value', () => {
|
it('should search based on passed value', () => {
|
||||||
const sizeField = 'content.size';
|
const sizeField = 'content.size';
|
||||||
const nameField = 'cm:name';
|
const nameField = 'cm:name';
|
||||||
component.id = 'properties';
|
|
||||||
component.settings = {
|
component.settings = {
|
||||||
field: `${sizeField},${nameField}`
|
field: `${sizeField},${nameField}`
|
||||||
};
|
};
|
||||||
component.context = TestBed.inject(SearchQueryBuilderService);
|
|
||||||
component.ngOnInit();
|
component.ngOnInit();
|
||||||
spyOn(component.displayValue$, 'next');
|
spyOn(component.displayValue$, 'next');
|
||||||
spyOn(component.context, 'update');
|
|
||||||
|
|
||||||
component.setValue(searchProperties);
|
component.setValue(searchProperties);
|
||||||
expect(component.displayValue$.next).toHaveBeenCalledWith(
|
expect(component.displayValue$.next).toHaveBeenCalledWith(
|
||||||
'SEARCH.SEARCH_PROPERTIES.FILE_SIZE_OPERATOR.AT_MOST 321 SEARCH.SEARCH_PROPERTIES.FILE_SIZE_UNIT_ABBREVIATION.MB, pdf, txt'
|
'SEARCH.SEARCH_PROPERTIES.FILE_SIZE_OPERATOR.AT_MOST 321 SEARCH.SEARCH_PROPERTIES.FILE_SIZE_UNIT_ABBREVIATION.MB, pdf, txt'
|
||||||
);
|
);
|
||||||
expect(component.context.queryFragments[component.id]).toBe(`${sizeField}:[0 TO 336592896] AND ${nameField}:("*.pdf" OR "*.txt")`);
|
expect(component.context.queryFragments[component.id]).toBe(`${sizeField}:[0 TO 336592896] AND ${nameField}:("*.pdf" OR "*.txt")`);
|
||||||
|
expect(component.context.filterRawParams[component.id]).toEqual({
|
||||||
|
fileExtensions: ['pdf', 'txt'],
|
||||||
|
fileSizeCondition: {
|
||||||
|
fileSizeOperator: 'SEARCH.SEARCH_PROPERTIES.FILE_SIZE_OPERATOR.AT_MOST',
|
||||||
|
fileSize: 321,
|
||||||
|
fileSizeUnit: FileSizeUnit.MB
|
||||||
|
}
|
||||||
|
});
|
||||||
expect(component.context.update).toHaveBeenCalled();
|
expect(component.context.update).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -470,4 +530,37 @@ describe('SearchPropertiesComponent', () => {
|
|||||||
).toBeTrue();
|
).toBeTrue();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should populate filter state when populate filters event has been observed', () => {
|
||||||
|
component.settings = {
|
||||||
|
field: 'field'
|
||||||
|
};
|
||||||
|
component.context.filterLoaded = new ReplaySubject(1);
|
||||||
|
spyOn(component.context.filterLoaded, 'next').and.stub();
|
||||||
|
spyOn(component.displayValue$, 'next').and.stub();
|
||||||
|
fixture.detectChanges();
|
||||||
|
component.context.populateFilters.next({
|
||||||
|
properties: {
|
||||||
|
fileExtensions: ['pdf', 'txt'],
|
||||||
|
fileSizeCondition: {
|
||||||
|
fileSizeOperator: 'SEARCH.SEARCH_PROPERTIES.FILE_SIZE_OPERATOR.AT_MOST',
|
||||||
|
fileSize: 321,
|
||||||
|
fileSizeUnit: FileSizeUnit.MB
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(component.displayValue$.next).toHaveBeenCalledWith(
|
||||||
|
'SEARCH.SEARCH_PROPERTIES.FILE_SIZE_OPERATOR.AT_MOST 321 SEARCH.SEARCH_PROPERTIES.FILE_SIZE_UNIT_ABBREVIATION.MB, pdf, txt'
|
||||||
|
);
|
||||||
|
expect(component.selectedExtensions).toEqual([{ value: 'pdf' }, { value: 'txt' }]);
|
||||||
|
expect(component.preselectedOptions).toEqual([{ value: 'pdf' }, { value: 'txt' }]);
|
||||||
|
expect(component.form.value).toEqual({
|
||||||
|
fileSizeOperator: 'SEARCH.SEARCH_PROPERTIES.FILE_SIZE_OPERATOR.AT_MOST',
|
||||||
|
fileSize: 321,
|
||||||
|
fileSizeUnit: FileSizeUnit.MB
|
||||||
|
});
|
||||||
|
expect(component.context.filterLoaded.next).toHaveBeenCalled();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -15,12 +15,12 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { AfterViewChecked, Component, ElementRef, OnInit, ViewChild, ViewEncapsulation } from '@angular/core';
|
import { AfterViewChecked, Component, ElementRef, OnDestroy, OnInit, ViewChild, ViewEncapsulation } from '@angular/core';
|
||||||
import { FormBuilder, ReactiveFormsModule } from '@angular/forms';
|
import { FormBuilder, ReactiveFormsModule } from '@angular/forms';
|
||||||
import { FileSizeCondition } from './file-size-condition';
|
import { FileSizeCondition } from './file-size-condition';
|
||||||
import { FileSizeOperator } from './file-size-operator.enum';
|
import { FileSizeOperator } from './file-size-operator.enum';
|
||||||
import { FileSizeUnit } from './file-size-unit.enum';
|
import { FileSizeUnit } from './file-size-unit.enum';
|
||||||
import { Subject } from 'rxjs';
|
import { ReplaySubject, Subject } from 'rxjs';
|
||||||
import { SearchWidgetSettings } from '../../models/search-widget-settings.interface';
|
import { SearchWidgetSettings } from '../../models/search-widget-settings.interface';
|
||||||
import { SearchQueryBuilderService } from '../../services/search-query-builder.service';
|
import { SearchQueryBuilderService } from '../../services/search-query-builder.service';
|
||||||
import { SearchProperties } from './search-properties';
|
import { SearchProperties } from './search-properties';
|
||||||
@ -31,6 +31,7 @@ import { CommonModule } from '@angular/common';
|
|||||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||||
import { MatSelectModule } from '@angular/material/select';
|
import { MatSelectModule } from '@angular/material/select';
|
||||||
import { SearchChipAutocompleteInputComponent } from '../search-chip-autocomplete-input';
|
import { SearchChipAutocompleteInputComponent } from '../search-chip-autocomplete-input';
|
||||||
|
import { map, takeUntil } from 'rxjs/operators';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'adf-search-properties',
|
selector: 'adf-search-properties',
|
||||||
@ -40,13 +41,14 @@ import { SearchChipAutocompleteInputComponent } from '../search-chip-autocomplet
|
|||||||
styleUrls: ['./search-properties.component.scss'],
|
styleUrls: ['./search-properties.component.scss'],
|
||||||
encapsulation: ViewEncapsulation.None
|
encapsulation: ViewEncapsulation.None
|
||||||
})
|
})
|
||||||
export class SearchPropertiesComponent implements OnInit, AfterViewChecked, SearchWidget {
|
export class SearchPropertiesComponent implements OnInit, AfterViewChecked, OnDestroy, SearchWidget {
|
||||||
id: string;
|
id: string;
|
||||||
settings?: SearchWidgetSettings;
|
settings?: SearchWidgetSettings;
|
||||||
context?: SearchQueryBuilderService;
|
context?: SearchQueryBuilderService;
|
||||||
startValue: SearchProperties;
|
startValue: SearchProperties;
|
||||||
displayValue$ = new Subject<string>();
|
displayValue$ = new ReplaySubject<string>(1);
|
||||||
autocompleteOptions: AutocompleteOption[] = [];
|
autocompleteOptions: AutocompleteOption[] = [];
|
||||||
|
preselectedOptions: AutocompleteOption[] = [];
|
||||||
|
|
||||||
private _form = this.formBuilder.nonNullable.group<FileSizeCondition>({
|
private _form = this.formBuilder.nonNullable.group<FileSizeCondition>({
|
||||||
fileSizeOperator: FileSizeOperator.AT_LEAST,
|
fileSizeOperator: FileSizeOperator.AT_LEAST,
|
||||||
@ -85,10 +87,16 @@ export class SearchPropertiesComponent implements OnInit, AfterViewChecked, Sear
|
|||||||
return this._reset$;
|
return this._reset$;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get selectedExtensions(): AutocompleteOption[] {
|
||||||
|
return this.parseToAutocompleteOptions(this._selectedExtensions);
|
||||||
|
}
|
||||||
|
|
||||||
set selectedExtensions(extensions: AutocompleteOption[]) {
|
set selectedExtensions(extensions: AutocompleteOption[]) {
|
||||||
this._selectedExtensions = this.parseFromAutocompleteOptions(extensions);
|
this._selectedExtensions = this.parseFromAutocompleteOptions(extensions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private readonly destroy$ = new Subject<void>();
|
||||||
|
|
||||||
constructor(private formBuilder: FormBuilder, private translateService: TranslateService) {}
|
constructor(private formBuilder: FormBuilder, private translateService: TranslateService) {}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
@ -102,6 +110,27 @@ export class SearchPropertiesComponent implements OnInit, AfterViewChecked, Sear
|
|||||||
if (this.startValue) {
|
if (this.startValue) {
|
||||||
this.setValue(this.startValue);
|
this.setValue(this.startValue);
|
||||||
}
|
}
|
||||||
|
this.context.populateFilters
|
||||||
|
.asObservable()
|
||||||
|
.pipe(
|
||||||
|
map((filtersQueries) => filtersQueries[this.id]),
|
||||||
|
takeUntil(this.destroy$)
|
||||||
|
)
|
||||||
|
.subscribe((filterQuery) => {
|
||||||
|
if (filterQuery) {
|
||||||
|
filterQuery.fileSizeCondition.fileSizeUnit = this.fileSizeUnits.find(
|
||||||
|
(fileSizeUnit) => fileSizeUnit.bytes === filterQuery.fileSizeCondition.fileSizeUnit.bytes
|
||||||
|
);
|
||||||
|
this.form.patchValue(filterQuery.fileSizeCondition);
|
||||||
|
this.form.updateValueAndValidity();
|
||||||
|
this._selectedExtensions = filterQuery.fileExtensions ?? [];
|
||||||
|
this.preselectedOptions = this.parseToAutocompleteOptions(this._selectedExtensions);
|
||||||
|
this.submitValues(false);
|
||||||
|
} else {
|
||||||
|
this.reset(false);
|
||||||
|
}
|
||||||
|
this.context.filterLoaded.next();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
ngAfterViewChecked() {
|
ngAfterViewChecked() {
|
||||||
@ -120,6 +149,11 @@ export class SearchPropertiesComponent implements OnInit, AfterViewChecked, Sear
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ngOnDestroy() {
|
||||||
|
this.destroy$.next();
|
||||||
|
this.destroy$.complete();
|
||||||
|
}
|
||||||
|
|
||||||
narrowDownAllowedCharacters(event: Event) {
|
narrowDownAllowedCharacters(event: Event) {
|
||||||
const value = (event.target as HTMLInputElement).value;
|
const value = (event.target as HTMLInputElement).value;
|
||||||
if (!(event.target as HTMLInputElement).value) {
|
if (!(event.target as HTMLInputElement).value) {
|
||||||
@ -160,47 +194,28 @@ export class SearchPropertiesComponent implements OnInit, AfterViewChecked, Sear
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
reset() {
|
reset(updateContext = true) {
|
||||||
this.form.reset();
|
this.form.reset();
|
||||||
if (this.id && this.context) {
|
if (this.id && this.context) {
|
||||||
this.context.queryFragments[this.id] = '';
|
this.context.queryFragments[this.id] = '';
|
||||||
this.context.update();
|
this.context.filterRawParams[this.id] = undefined;
|
||||||
|
if (updateContext) {
|
||||||
|
this.context.update();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
this.reset$.next();
|
this.reset$.next();
|
||||||
this.displayValue$.next('');
|
this.displayValue$.next('');
|
||||||
}
|
}
|
||||||
|
|
||||||
submitValues() {
|
submitValues(updateContext = true) {
|
||||||
|
if (this.context?.filterRawParams) {
|
||||||
|
this.context.filterRawParams[this.id] = {
|
||||||
|
fileExtensions: this._selectedExtensions,
|
||||||
|
fileSizeCondition: this.form.value
|
||||||
|
};
|
||||||
|
}
|
||||||
if (this.settings && this.context) {
|
if (this.settings && this.context) {
|
||||||
let query = '';
|
this.updateSettingsAndContext(updateContext);
|
||||||
let displayedValue = '';
|
|
||||||
if (this.form.value.fileSize !== undefined && this.form.value.fileSize !== null) {
|
|
||||||
displayedValue = `${this.translateService.instant(this.form.value.fileSizeOperator)} ${
|
|
||||||
this.form.value.fileSize
|
|
||||||
} ${this.translateService.instant(this.form.value.fileSizeUnit.abbreviation)}`;
|
|
||||||
const size = this.form.value.fileSize * this.form.value.fileSizeUnit.bytes;
|
|
||||||
switch (this.form.value.fileSizeOperator) {
|
|
||||||
case FileSizeOperator.AT_MOST:
|
|
||||||
query = `${this.sizeField}:[0 TO ${size}]`;
|
|
||||||
break;
|
|
||||||
case FileSizeOperator.AT_LEAST:
|
|
||||||
query = `${this.sizeField}:[${size} TO MAX]`;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
query = `${this.sizeField}:[${size} TO ${size}]`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (this._selectedExtensions?.length) {
|
|
||||||
if (query) {
|
|
||||||
query += ' AND ';
|
|
||||||
displayedValue += ', ';
|
|
||||||
}
|
|
||||||
query += `${this.nameField}:("*.${this._selectedExtensions.join('" OR "*.')}")`;
|
|
||||||
displayedValue += this._selectedExtensions.join(', ');
|
|
||||||
}
|
|
||||||
this.displayValue$.next(displayedValue);
|
|
||||||
this.context.queryFragments[this.id] = query;
|
|
||||||
this.context.update();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -217,10 +232,44 @@ export class SearchPropertiesComponent implements OnInit, AfterViewChecked, Sear
|
|||||||
|
|
||||||
setValue(searchProperties: SearchProperties) {
|
setValue(searchProperties: SearchProperties) {
|
||||||
this.form.patchValue(searchProperties.fileSizeCondition);
|
this.form.patchValue(searchProperties.fileSizeCondition);
|
||||||
this.selectedExtensions = this.parseToAutocompleteOptions(searchProperties.fileExtensions);
|
this.selectedExtensions = this.parseToAutocompleteOptions(searchProperties.fileExtensions ?? []);
|
||||||
this.submitValues();
|
this.submitValues();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private updateSettingsAndContext(updateContext = true): void {
|
||||||
|
let query = '';
|
||||||
|
let displayedValue = '';
|
||||||
|
if (this.form.value.fileSize !== undefined && this.form.value.fileSize !== null) {
|
||||||
|
displayedValue = `${this.translateService.instant(this.form.value.fileSizeOperator)} ${
|
||||||
|
this.form.value.fileSize
|
||||||
|
} ${this.translateService.instant(this.form.value.fileSizeUnit.abbreviation)}`;
|
||||||
|
const size = this.form.value.fileSize * this.form.value.fileSizeUnit.bytes;
|
||||||
|
switch (this.form.value.fileSizeOperator) {
|
||||||
|
case FileSizeOperator.AT_MOST:
|
||||||
|
query = `${this.sizeField}:[0 TO ${size}]`;
|
||||||
|
break;
|
||||||
|
case FileSizeOperator.AT_LEAST:
|
||||||
|
query = `${this.sizeField}:[${size} TO MAX]`;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
query = `${this.sizeField}:[${size} TO ${size}]`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (this._selectedExtensions?.length) {
|
||||||
|
if (query) {
|
||||||
|
query += ' AND ';
|
||||||
|
displayedValue += ', ';
|
||||||
|
}
|
||||||
|
query += `${this.nameField}:("*.${this._selectedExtensions.join('" OR "*.')}")`;
|
||||||
|
displayedValue += this._selectedExtensions.join(', ');
|
||||||
|
}
|
||||||
|
this.displayValue$.next(displayedValue);
|
||||||
|
this.context.queryFragments[this.id] = query;
|
||||||
|
if (updateContext) {
|
||||||
|
this.context.update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private parseToAutocompleteOptions(array: string[]): AutocompleteOption[] {
|
private parseToAutocompleteOptions(array: string[]): AutocompleteOption[] {
|
||||||
return array.map((value) => ({ value }));
|
return array.map((value) => ({ value }));
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,7 @@ import { ContentTestingModule } from '../../../testing/content.testing.module';
|
|||||||
import { HarnessLoader } from '@angular/cdk/testing';
|
import { HarnessLoader } from '@angular/cdk/testing';
|
||||||
import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
|
import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
|
||||||
import { MatRadioButtonHarness, MatRadioGroupHarness } from '@angular/material/radio/testing';
|
import { MatRadioButtonHarness, MatRadioGroupHarness } from '@angular/material/radio/testing';
|
||||||
|
import { ReplaySubject } from 'rxjs';
|
||||||
|
|
||||||
describe('SearchRadioComponent', () => {
|
describe('SearchRadioComponent', () => {
|
||||||
let loader: HarnessLoader;
|
let loader: HarnessLoader;
|
||||||
@ -36,20 +37,20 @@ describe('SearchRadioComponent', () => {
|
|||||||
component = fixture.componentInstance;
|
component = fixture.componentInstance;
|
||||||
|
|
||||||
loader = TestbedHarnessEnvironment.loader(fixture);
|
loader = TestbedHarnessEnvironment.loader(fixture);
|
||||||
|
component.id = 'radio';
|
||||||
|
component.context = {
|
||||||
|
queryFragments: {
|
||||||
|
radio: 'query'
|
||||||
|
},
|
||||||
|
filterRawParams: {},
|
||||||
|
populateFilters: new ReplaySubject(1),
|
||||||
|
update: jasmine.createSpy('update')
|
||||||
|
} as any;
|
||||||
|
component.settings = { options: sizeOptions } as any;
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Pagination', () => {
|
describe('Pagination', () => {
|
||||||
it('should show 5 items when pageSize not defined', async () => {
|
it('should show 5 items when pageSize not defined', async () => {
|
||||||
component.id = 'radio';
|
|
||||||
component.context = {
|
|
||||||
queryFragments: {
|
|
||||||
radio: 'query'
|
|
||||||
},
|
|
||||||
update: () => {}
|
|
||||||
} as any;
|
|
||||||
component.settings = { options: sizeOptions } as any;
|
|
||||||
|
|
||||||
component.ngOnInit();
|
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
const options = await loader.getAllHarnesses(MatRadioButtonHarness);
|
const options = await loader.getAllHarnesses(MatRadioButtonHarness);
|
||||||
@ -60,15 +61,7 @@ describe('SearchRadioComponent', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should show all items when pageSize is high', async () => {
|
it('should show all items when pageSize is high', async () => {
|
||||||
component.id = 'radio';
|
component.settings['pageSize'] = 15;
|
||||||
component.context = {
|
|
||||||
queryFragments: {
|
|
||||||
radio: 'query'
|
|
||||||
},
|
|
||||||
update: () => {}
|
|
||||||
} as any;
|
|
||||||
component.settings = { pageSize: 15, options: sizeOptions } as any;
|
|
||||||
component.ngOnInit();
|
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
const options = await loader.getAllHarnesses(MatRadioButtonHarness);
|
const options = await loader.getAllHarnesses(MatRadioButtonHarness);
|
||||||
@ -80,18 +73,40 @@ describe('SearchRadioComponent', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should able to check the radio button', async () => {
|
it('should able to check the radio button', async () => {
|
||||||
component.id = 'radio';
|
|
||||||
component.context = {
|
|
||||||
queryFragments: {
|
|
||||||
radio: 'query'
|
|
||||||
},
|
|
||||||
update: () => {}
|
|
||||||
} as any;
|
|
||||||
component.settings = { options: sizeOptions } as any;
|
|
||||||
|
|
||||||
const group = await loader.getHarness(MatRadioGroupHarness);
|
const group = await loader.getHarness(MatRadioGroupHarness);
|
||||||
await group.checkRadioButton({ selector: `[data-automation-id="search-radio-${sizeOptions[0].name}"]` });
|
await group.checkRadioButton({ selector: `[data-automation-id="search-radio-${sizeOptions[1].name}"]` });
|
||||||
|
|
||||||
|
expect(component.context.queryFragments[component.id]).toBe(sizeOptions[1].value);
|
||||||
|
expect(component.context.filterRawParams[component.id]).toBe(sizeOptions[1].value);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reset to initial value ', async () => {
|
||||||
|
const group = await loader.getHarness(MatRadioGroupHarness);
|
||||||
|
await group.checkRadioButton({ selector: `[data-automation-id="search-radio-${sizeOptions[2].name}"]` });
|
||||||
|
|
||||||
|
expect(component.context.queryFragments[component.id]).toBe(sizeOptions[2].value);
|
||||||
|
expect(component.context.filterRawParams[component.id]).toBe(sizeOptions[2].value);
|
||||||
|
|
||||||
|
component.reset();
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
expect(component.context.queryFragments[component.id]).toBe(sizeOptions[0].value);
|
expect(component.context.queryFragments[component.id]).toBe(sizeOptions[0].value);
|
||||||
|
expect(component.context.filterRawParams[component.id]).toBe(sizeOptions[0].value);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should populate filter state when populate filters event has been observed', async () => {
|
||||||
|
component.context.filterLoaded = new ReplaySubject(1);
|
||||||
|
spyOn(component.context.filterLoaded, 'next').and.stub();
|
||||||
|
spyOn(component.displayValue$, 'next').and.stub();
|
||||||
|
fixture.detectChanges();
|
||||||
|
component.context.populateFilters.next({ radio: sizeOptions[1].value });
|
||||||
|
fixture.detectChanges();
|
||||||
|
const group = await loader.getHarness(MatRadioGroupHarness);
|
||||||
|
|
||||||
|
expect(component.displayValue$.next).toHaveBeenCalledWith(sizeOptions[1].name);
|
||||||
|
expect(component.value).toEqual(sizeOptions[1].value);
|
||||||
|
expect(component.context.filterRawParams[component.id]).toBe(sizeOptions[1].value);
|
||||||
|
expect(component.context.filterLoaded.next).toHaveBeenCalled();
|
||||||
|
expect(await group.getCheckedValue()).toEqual(sizeOptions[1].value);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -22,7 +22,8 @@ import { SearchWidget } from '../../models/search-widget.interface';
|
|||||||
import { SearchWidgetSettings } from '../../models/search-widget-settings.interface';
|
import { SearchWidgetSettings } from '../../models/search-widget-settings.interface';
|
||||||
import { SearchQueryBuilderService } from '../../services/search-query-builder.service';
|
import { SearchQueryBuilderService } from '../../services/search-query-builder.service';
|
||||||
import { SearchFilterList } from '../../models/search-filter-list.model';
|
import { SearchFilterList } from '../../models/search-filter-list.model';
|
||||||
import { Subject } from 'rxjs';
|
import { ReplaySubject } from 'rxjs';
|
||||||
|
import { first } from 'rxjs/operators';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { FormsModule } from '@angular/forms';
|
import { FormsModule } from '@angular/forms';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
@ -56,7 +57,7 @@ export class SearchRadioComponent implements SearchWidget, OnInit {
|
|||||||
isActive = false;
|
isActive = false;
|
||||||
startValue: any;
|
startValue: any;
|
||||||
enableChangeUpdate: boolean;
|
enableChangeUpdate: boolean;
|
||||||
displayValue$: Subject<string> = new Subject<string>();
|
displayValue$ = new ReplaySubject<string>(1);
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.options = new SearchFilterList<SearchRadioOption>();
|
this.options = new SearchFilterList<SearchRadioOption>();
|
||||||
@ -82,6 +83,18 @@ export class SearchRadioComponent implements SearchWidget, OnInit {
|
|||||||
}
|
}
|
||||||
this.enableChangeUpdate = this.settings.allowUpdateOnChange ?? true;
|
this.enableChangeUpdate = this.settings.allowUpdateOnChange ?? true;
|
||||||
this.updateDisplayValue();
|
this.updateDisplayValue();
|
||||||
|
this.context.populateFilters
|
||||||
|
.asObservable()
|
||||||
|
.pipe(first())
|
||||||
|
.subscribe((filtersQueries) => {
|
||||||
|
if (filtersQueries[this.id]) {
|
||||||
|
this.value = filtersQueries[this.id];
|
||||||
|
this.submitValues(false);
|
||||||
|
} else {
|
||||||
|
this.reset(false);
|
||||||
|
}
|
||||||
|
this.context.filterLoaded.next();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private getSelectedValue(): string {
|
private getSelectedValue(): string {
|
||||||
@ -98,10 +111,12 @@ export class SearchRadioComponent implements SearchWidget, OnInit {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
submitValues() {
|
submitValues(updateContext = true) {
|
||||||
this.setValue(this.value);
|
this.setValue(this.value);
|
||||||
this.updateDisplayValue();
|
this.updateDisplayValue();
|
||||||
this.context.update();
|
if (updateContext) {
|
||||||
|
this.context.update();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
hasValidValue() {
|
hasValidValue() {
|
||||||
@ -112,6 +127,7 @@ export class SearchRadioComponent implements SearchWidget, OnInit {
|
|||||||
setValue(newValue: string) {
|
setValue(newValue: string) {
|
||||||
this.value = newValue;
|
this.value = newValue;
|
||||||
this.context.queryFragments[this.id] = newValue;
|
this.context.queryFragments[this.id] = newValue;
|
||||||
|
this.context.filterRawParams[this.id] = newValue;
|
||||||
if (this.enableChangeUpdate) {
|
if (this.enableChangeUpdate) {
|
||||||
this.updateDisplayValue();
|
this.updateDisplayValue();
|
||||||
this.context.update();
|
this.context.update();
|
||||||
@ -143,12 +159,14 @@ export class SearchRadioComponent implements SearchWidget, OnInit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
reset() {
|
reset(updateContext = true) {
|
||||||
const initialValue = this.getSelectedValue();
|
const initialValue = this.getSelectedValue();
|
||||||
if (initialValue !== null) {
|
if (initialValue !== null) {
|
||||||
this.setValue(initialValue);
|
this.setValue(initialValue);
|
||||||
this.updateDisplayValue();
|
this.updateDisplayValue();
|
||||||
this.context.update();
|
if (updateContext) {
|
||||||
|
this.context.update();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
import { SearchSliderComponent } from './search-slider.component';
|
import { SearchSliderComponent } from './search-slider.component';
|
||||||
import { ContentTestingModule } from '../../../testing/content.testing.module';
|
import { ContentTestingModule } from '../../../testing/content.testing.module';
|
||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { ReplaySubject } from 'rxjs';
|
||||||
|
|
||||||
describe('SearchSliderComponent', () => {
|
describe('SearchSliderComponent', () => {
|
||||||
let fixture: ComponentFixture<SearchSliderComponent>;
|
let fixture: ComponentFixture<SearchSliderComponent>;
|
||||||
@ -29,101 +30,84 @@ describe('SearchSliderComponent', () => {
|
|||||||
});
|
});
|
||||||
fixture = TestBed.createComponent(SearchSliderComponent);
|
fixture = TestBed.createComponent(SearchSliderComponent);
|
||||||
component = fixture.componentInstance;
|
component = fixture.componentInstance;
|
||||||
});
|
component.id = 'slider';
|
||||||
|
component.context = {
|
||||||
it('should setup slider from settings', () => {
|
queryFragments: {
|
||||||
const settings: any = {
|
slider: ''
|
||||||
|
},
|
||||||
|
filterRawParams: {},
|
||||||
|
populateFilters: new ReplaySubject(1),
|
||||||
|
update: jasmine.createSpy('update')
|
||||||
|
} as any;
|
||||||
|
component.settings = {
|
||||||
|
field: 'field1',
|
||||||
min: 10,
|
min: 10,
|
||||||
max: 100,
|
max: 100,
|
||||||
step: 2,
|
step: 2,
|
||||||
thumbLabel: true
|
thumbLabel: true
|
||||||
};
|
};
|
||||||
|
});
|
||||||
|
|
||||||
component.settings = settings;
|
it('should setup slider from settings', () => {
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
expect(component.min).toEqual(settings.min);
|
expect(component.min).toEqual(10);
|
||||||
expect(component.max).toEqual(settings.max);
|
expect(component.max).toEqual(100);
|
||||||
expect(component.step).toEqual(settings.step);
|
expect(component.step).toEqual(2);
|
||||||
expect(component.thumbLabel).toEqual(settings.thumbLabel);
|
expect(component.thumbLabel).toEqual(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should update its query part on slider change', () => {
|
it('should update its query part on slider change', () => {
|
||||||
const context: any = {
|
component.settings['field'] = 'cm:content.size';
|
||||||
queryFragments: {},
|
|
||||||
update: () => {}
|
|
||||||
};
|
|
||||||
|
|
||||||
spyOn(context, 'update').and.stub();
|
|
||||||
|
|
||||||
component.context = context;
|
|
||||||
component.id = 'contentSize';
|
|
||||||
component.settings = { field: 'cm:content.size' };
|
|
||||||
component.value = 10;
|
component.value = 10;
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
component.onChangedHandler();
|
component.onChangedHandler();
|
||||||
expect(context.queryFragments[component.id]).toEqual('cm:content.size:[0 TO 10]');
|
expect(component.context.queryFragments[component.id]).toEqual('cm:content.size:[0 TO 10]');
|
||||||
expect(context.update).toHaveBeenCalled();
|
expect(component.context.filterRawParams[component.id]).toEqual(10);
|
||||||
|
expect(component.context.update).toHaveBeenCalled();
|
||||||
|
|
||||||
component.value = 20;
|
component.value = 20;
|
||||||
component.onChangedHandler();
|
component.onChangedHandler();
|
||||||
expect(context.queryFragments[component.id]).toEqual('cm:content.size:[0 TO 20]');
|
expect(component.context.queryFragments[component.id]).toEqual('cm:content.size:[0 TO 20]');
|
||||||
|
expect(component.context.filterRawParams[component.id]).toEqual(20);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should reset the value for query builder', () => {
|
it('should reset the value for query builder', () => {
|
||||||
const settings: any = {
|
|
||||||
field: 'field1',
|
|
||||||
min: 10,
|
|
||||||
max: 100,
|
|
||||||
step: 2,
|
|
||||||
thumbLabel: true
|
|
||||||
};
|
|
||||||
|
|
||||||
const context: any = {
|
|
||||||
queryFragments: {},
|
|
||||||
update: () => {}
|
|
||||||
};
|
|
||||||
|
|
||||||
component.settings = settings;
|
|
||||||
component.context = context;
|
|
||||||
component.value = 20;
|
component.value = 20;
|
||||||
component.id = 'slider';
|
|
||||||
spyOn(context, 'update').and.stub();
|
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
component.reset();
|
component.reset();
|
||||||
|
|
||||||
expect(component.value).toBe(settings.min);
|
expect(component.value).toBe(10);
|
||||||
expect(context.queryFragments[component.id]).toBe('');
|
expect(component.context.queryFragments[component.id]).toBe('');
|
||||||
expect(context.update).toHaveBeenCalled();
|
expect(component.context.filterRawParams[component.id]).toBe(null);
|
||||||
|
expect(component.context.update).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should reset to 0 if min not provided', () => {
|
it('should reset to 0 if min not provided', () => {
|
||||||
const settings: any = {
|
component.settings.min = null;
|
||||||
field: 'field1',
|
|
||||||
min: null,
|
|
||||||
max: 100,
|
|
||||||
step: 2,
|
|
||||||
thumbLabel: true
|
|
||||||
};
|
|
||||||
|
|
||||||
const context: any = {
|
|
||||||
queryFragments: {},
|
|
||||||
update: () => {}
|
|
||||||
};
|
|
||||||
|
|
||||||
component.settings = settings;
|
|
||||||
component.context = context;
|
|
||||||
component.value = 20;
|
component.value = 20;
|
||||||
component.id = 'slider';
|
|
||||||
spyOn(context, 'update').and.stub();
|
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
component.reset();
|
component.reset();
|
||||||
|
|
||||||
expect(component.value).toBe(0);
|
expect(component.value).toBe(0);
|
||||||
expect(context.queryFragments['slider']).toBe('');
|
expect(component.context.queryFragments['slider']).toBe('');
|
||||||
expect(context.update).toHaveBeenCalled();
|
expect(component.context.update).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should populate filter state when populate filters event has been observed', async () => {
|
||||||
|
component.context.filterLoaded = new ReplaySubject(1);
|
||||||
|
spyOn(component.context.filterLoaded, 'next').and.stub();
|
||||||
|
spyOn(component.displayValue$, 'next').and.stub();
|
||||||
|
fixture.detectChanges();
|
||||||
|
component.context.populateFilters.next({ slider: 20 });
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(component.displayValue$.next).toHaveBeenCalledWith('20 ');
|
||||||
|
expect(component.value).toBe(20);
|
||||||
|
expect(component.context.filterRawParams[component.id]).toBe(20);
|
||||||
|
expect(component.context.filterLoaded.next).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -15,11 +15,12 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Component, Input, OnInit, ViewEncapsulation } from '@angular/core';
|
import { Component, Input, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core';
|
||||||
import { SearchWidget } from '../../models/search-widget.interface';
|
import { SearchWidget } from '../../models/search-widget.interface';
|
||||||
import { SearchWidgetSettings } from '../../models/search-widget-settings.interface';
|
import { SearchWidgetSettings } from '../../models/search-widget-settings.interface';
|
||||||
import { SearchQueryBuilderService } from '../../services/search-query-builder.service';
|
import { SearchQueryBuilderService } from '../../services/search-query-builder.service';
|
||||||
import { Subject } from 'rxjs';
|
import { ReplaySubject, Subject } from 'rxjs';
|
||||||
|
import { takeUntil } from 'rxjs/operators';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { MatSliderModule } from '@angular/material/slider';
|
import { MatSliderModule } from '@angular/material/slider';
|
||||||
import { FormsModule } from '@angular/forms';
|
import { FormsModule } from '@angular/forms';
|
||||||
@ -35,7 +36,11 @@ import { TranslateModule } from '@ngx-translate/core';
|
|||||||
encapsulation: ViewEncapsulation.None,
|
encapsulation: ViewEncapsulation.None,
|
||||||
host: { class: 'adf-search-slider' }
|
host: { class: 'adf-search-slider' }
|
||||||
})
|
})
|
||||||
export class SearchSliderComponent implements SearchWidget, OnInit {
|
export class SearchSliderComponent implements SearchWidget, OnInit, OnDestroy {
|
||||||
|
/** The numeric value represented by the slider. */
|
||||||
|
@Input()
|
||||||
|
value: number | null;
|
||||||
|
|
||||||
isActive?: boolean;
|
isActive?: boolean;
|
||||||
startValue: any;
|
startValue: any;
|
||||||
|
|
||||||
@ -47,11 +52,9 @@ export class SearchSliderComponent implements SearchWidget, OnInit {
|
|||||||
max: number;
|
max: number;
|
||||||
thumbLabel = false;
|
thumbLabel = false;
|
||||||
enableChangeUpdate: boolean;
|
enableChangeUpdate: boolean;
|
||||||
displayValue$: Subject<string> = new Subject<string>();
|
displayValue$ = new ReplaySubject<string>(1);
|
||||||
|
|
||||||
/** The numeric value represented by the slider. */
|
private readonly destroy$ = new Subject<void>();
|
||||||
@Input()
|
|
||||||
value: number | null;
|
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
if (this.settings) {
|
if (this.settings) {
|
||||||
@ -74,6 +77,23 @@ export class SearchSliderComponent implements SearchWidget, OnInit {
|
|||||||
if (this.startValue) {
|
if (this.startValue) {
|
||||||
this.setValue(this.startValue);
|
this.setValue(this.startValue);
|
||||||
}
|
}
|
||||||
|
this.context.populateFilters
|
||||||
|
.asObservable()
|
||||||
|
.pipe(takeUntil(this.destroy$))
|
||||||
|
.subscribe((filtersQueries) => {
|
||||||
|
if (filtersQueries[this.id]) {
|
||||||
|
this.value = filtersQueries[this.id];
|
||||||
|
this.updateQuery(this.value, false);
|
||||||
|
} else {
|
||||||
|
this.reset(false);
|
||||||
|
}
|
||||||
|
this.context.filterLoaded.next();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy() {
|
||||||
|
this.destroy$.next();
|
||||||
|
this.destroy$.complete();
|
||||||
}
|
}
|
||||||
|
|
||||||
clear() {
|
clear() {
|
||||||
@ -83,9 +103,9 @@ export class SearchSliderComponent implements SearchWidget, OnInit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
reset() {
|
reset(updateContext = true) {
|
||||||
this.value = this.min || 0;
|
this.value = this.min || 0;
|
||||||
this.updateQuery(null);
|
this.updateQuery(null, updateContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
onChangedHandler() {
|
onChangedHandler() {
|
||||||
@ -111,7 +131,8 @@ export class SearchSliderComponent implements SearchWidget, OnInit {
|
|||||||
this.submitValues();
|
this.submitValues();
|
||||||
}
|
}
|
||||||
|
|
||||||
private updateQuery(value: number | null) {
|
private updateQuery(value: number | null, updateContext = true) {
|
||||||
|
this.context.filterRawParams[this.id] = value;
|
||||||
this.displayValue$.next(this.value ? `${this.value} ${this.settings.unit ?? ''}` : '');
|
this.displayValue$.next(this.value ? `${this.value} ${this.settings.unit ?? ''}` : '');
|
||||||
if (this.id && this.context && this.settings && this.settings.field) {
|
if (this.id && this.context && this.settings && this.settings.field) {
|
||||||
if (value === null) {
|
if (value === null) {
|
||||||
@ -119,7 +140,9 @@ export class SearchSliderComponent implements SearchWidget, OnInit {
|
|||||||
} else {
|
} else {
|
||||||
this.context.queryFragments[this.id] = `${this.settings.field}:[0 TO ${value}]`;
|
this.context.queryFragments[this.id] = `${this.settings.field}:[0 TO ${value}]`;
|
||||||
}
|
}
|
||||||
this.context.update();
|
if (updateContext) {
|
||||||
|
this.context.update();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,42 +17,46 @@
|
|||||||
|
|
||||||
import { SearchSortingPickerComponent } from './search-sorting-picker.component';
|
import { SearchSortingPickerComponent } from './search-sorting-picker.component';
|
||||||
import { SearchQueryBuilderService } from '../../services/search-query-builder.service';
|
import { SearchQueryBuilderService } from '../../services/search-query-builder.service';
|
||||||
import { AppConfigService } from '@alfresco/adf-core';
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
import { SearchConfiguration } from '../../models/search-configuration.interface';
|
|
||||||
import { TestBed } from '@angular/core/testing';
|
|
||||||
import { ContentTestingModule } from '../../../testing/content.testing.module';
|
import { ContentTestingModule } from '../../../testing/content.testing.module';
|
||||||
import { AlfrescoApiService } from '../../../services/alfresco-api.service';
|
import { SearchConfiguration } from '../../models';
|
||||||
|
import { BaseQueryBuilderService } from '../../services/base-query-builder.service';
|
||||||
|
|
||||||
describe('SearchSortingPickerComponent', () => {
|
describe('SearchSortingPickerComponent', () => {
|
||||||
let queryBuilder: SearchQueryBuilderService;
|
let fixture: ComponentFixture<SearchSortingPickerComponent>;
|
||||||
let component: SearchSortingPickerComponent;
|
let component: SearchSortingPickerComponent;
|
||||||
|
|
||||||
const buildConfig = (searchSettings): AppConfigService => {
|
const config: SearchConfiguration = {
|
||||||
const config = TestBed.inject(AppConfigService);
|
sorting: {
|
||||||
config.config.search = searchSettings;
|
options: [
|
||||||
return config;
|
{ key: 'name', label: 'Name', type: 'FIELD', field: 'cm:name', ascending: true },
|
||||||
|
{ key: 'content.sizeInBytes', label: 'Size', type: 'FIELD', field: 'content.size', ascending: true },
|
||||||
|
{ key: 'description', label: 'Description', type: 'FIELD', field: 'cm:description', ascending: true }
|
||||||
|
],
|
||||||
|
defaults: [{ key: 'name', type: 'FIELD', field: 'cm:name', ascending: false } as any]
|
||||||
|
},
|
||||||
|
categories: [{ id: 'cat1', enabled: true } as any]
|
||||||
|
};
|
||||||
|
|
||||||
|
const queryBuilder: Partial<BaseQueryBuilderService> = {
|
||||||
|
getSortingOptions: () => config.sorting.options,
|
||||||
|
getPrimarySorting: () => config.sorting.defaults[0],
|
||||||
|
sorting: config.sorting.options,
|
||||||
|
update: jasmine.createSpy('update')
|
||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [ContentTestingModule]
|
imports: [ContentTestingModule],
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: SearchQueryBuilderService,
|
||||||
|
useValue: queryBuilder
|
||||||
|
}
|
||||||
|
]
|
||||||
});
|
});
|
||||||
|
fixture = TestBed.createComponent(SearchSortingPickerComponent);
|
||||||
const config: SearchConfiguration = {
|
component = fixture.componentInstance;
|
||||||
sorting: {
|
|
||||||
options: [
|
|
||||||
{ key: 'name', label: 'Name', type: 'FIELD', field: 'cm:name', ascending: true },
|
|
||||||
{ key: 'content.sizeInBytes', label: 'Size', type: 'FIELD', field: 'content.size', ascending: true },
|
|
||||||
{ key: 'description', label: 'Description', type: 'FIELD', field: 'cm:description', ascending: true }
|
|
||||||
],
|
|
||||||
defaults: [{ key: 'name', type: 'FIELD', field: 'cm:name', ascending: false } as any]
|
|
||||||
},
|
|
||||||
categories: [{ id: 'cat1', enabled: true } as any]
|
|
||||||
};
|
|
||||||
const alfrescoApiService = TestBed.inject(AlfrescoApiService);
|
|
||||||
|
|
||||||
queryBuilder = new SearchQueryBuilderService(buildConfig(config), alfrescoApiService);
|
|
||||||
component = new SearchSortingPickerComponent(queryBuilder);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should load options from query builder', () => {
|
it('should load options from query builder', () => {
|
||||||
@ -72,8 +76,6 @@ describe('SearchSortingPickerComponent', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should update query builder each time selection is changed', () => {
|
it('should update query builder each time selection is changed', () => {
|
||||||
spyOn(queryBuilder, 'update').and.stub();
|
|
||||||
|
|
||||||
component.ngOnInit();
|
component.ngOnInit();
|
||||||
component.onValueChanged('description');
|
component.onValueChanged('description');
|
||||||
|
|
||||||
@ -84,8 +86,6 @@ describe('SearchSortingPickerComponent', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should update query builder each time sorting is changed', () => {
|
it('should update query builder each time sorting is changed', () => {
|
||||||
spyOn(queryBuilder, 'update').and.stub();
|
|
||||||
|
|
||||||
component.ngOnInit();
|
component.ngOnInit();
|
||||||
component.onSortingChanged(false);
|
component.onSortingChanged(false);
|
||||||
|
|
||||||
|
@ -22,6 +22,7 @@ import { HarnessLoader } from '@angular/cdk/testing';
|
|||||||
import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
|
import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
|
||||||
import { MatInputHarness } from '@angular/material/input/testing';
|
import { MatInputHarness } from '@angular/material/input/testing';
|
||||||
import { MatButtonHarness } from '@angular/material/button/testing';
|
import { MatButtonHarness } from '@angular/material/button/testing';
|
||||||
|
import { ReplaySubject } from 'rxjs';
|
||||||
|
|
||||||
describe('SearchTextComponent', () => {
|
describe('SearchTextComponent', () => {
|
||||||
let loader: HarnessLoader;
|
let loader: HarnessLoader;
|
||||||
@ -40,10 +41,13 @@ describe('SearchTextComponent', () => {
|
|||||||
field: 'cm:name',
|
field: 'cm:name',
|
||||||
placeholder: 'Enter the name'
|
placeholder: 'Enter the name'
|
||||||
};
|
};
|
||||||
|
|
||||||
component.context = {
|
component.context = {
|
||||||
queryFragments: {},
|
queryFragments: {
|
||||||
update: () => {}
|
slider: ''
|
||||||
|
},
|
||||||
|
filterRawParams: {},
|
||||||
|
populateFilters: new ReplaySubject(1),
|
||||||
|
update: jasmine.createSpy('update')
|
||||||
} as any;
|
} as any;
|
||||||
|
|
||||||
loader = TestbedHarnessEnvironment.loader(fixture);
|
loader = TestbedHarnessEnvironment.loader(fixture);
|
||||||
@ -65,8 +69,6 @@ describe('SearchTextComponent', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should update query builder on change', () => {
|
it('should update query builder on change', () => {
|
||||||
spyOn(component.context, 'update').and.stub();
|
|
||||||
|
|
||||||
component.onChangedHandler({
|
component.onChangedHandler({
|
||||||
target: {
|
target: {
|
||||||
value: 'top-secret.doc'
|
value: 'top-secret.doc'
|
||||||
@ -75,6 +77,7 @@ describe('SearchTextComponent', () => {
|
|||||||
|
|
||||||
expect(component.value).toBe('top-secret.doc');
|
expect(component.value).toBe('top-secret.doc');
|
||||||
expect(component.context.queryFragments[component.id]).toBe(`cm:name:'top-secret.doc'`);
|
expect(component.context.queryFragments[component.id]).toBe(`cm:name:'top-secret.doc'`);
|
||||||
|
expect(component.context.filterRawParams[component.id]).toBe('top-secret.doc');
|
||||||
expect(component.context.update).toHaveBeenCalled();
|
expect(component.context.update).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -87,6 +90,7 @@ describe('SearchTextComponent', () => {
|
|||||||
|
|
||||||
expect(component.value).toBe('top-secret.doc');
|
expect(component.value).toBe('top-secret.doc');
|
||||||
expect(component.context.queryFragments[component.id]).toBe(`cm:name:'top-secret.doc'`);
|
expect(component.context.queryFragments[component.id]).toBe(`cm:name:'top-secret.doc'`);
|
||||||
|
expect(component.context.filterRawParams[component.id]).toBe('top-secret.doc');
|
||||||
|
|
||||||
component.onChangedHandler({
|
component.onChangedHandler({
|
||||||
target: {
|
target: {
|
||||||
@ -96,6 +100,7 @@ describe('SearchTextComponent', () => {
|
|||||||
|
|
||||||
expect(component.value).toBe('');
|
expect(component.value).toBe('');
|
||||||
expect(component.context.queryFragments[component.id]).toBe('');
|
expect(component.context.queryFragments[component.id]).toBe('');
|
||||||
|
expect(component.context.filterRawParams[component.id]).toBe('');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should show the custom/default name', async () => {
|
it('should show the custom/default name', async () => {
|
||||||
@ -118,10 +123,10 @@ describe('SearchTextComponent', () => {
|
|||||||
|
|
||||||
expect(component.value).toBe('');
|
expect(component.value).toBe('');
|
||||||
expect(component.context.queryFragments[component.id]).toBe('');
|
expect(component.context.queryFragments[component.id]).toBe('');
|
||||||
|
expect(component.context.filterRawParams[component.id]).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should update query with startValue on init, if provided', () => {
|
it('should update query with startValue on init, if provided', () => {
|
||||||
spyOn(component.context, 'update');
|
|
||||||
component.startValue = 'mock-start-value';
|
component.startValue = 'mock-start-value';
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
@ -132,7 +137,6 @@ describe('SearchTextComponent', () => {
|
|||||||
|
|
||||||
it('should parse value and set query context as blank, and not call query update, if no start value was provided', () => {
|
it('should parse value and set query context as blank, and not call query update, if no start value was provided', () => {
|
||||||
component.context.queryFragments[component.id] = `cm:name:'secret.pdf'`;
|
component.context.queryFragments[component.id] = `cm:name:'secret.pdf'`;
|
||||||
spyOn(component.context, 'update');
|
|
||||||
component.startValue = undefined;
|
component.startValue = undefined;
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
@ -140,4 +144,18 @@ describe('SearchTextComponent', () => {
|
|||||||
expect(component.value).toBe('secret.pdf');
|
expect(component.value).toBe('secret.pdf');
|
||||||
expect(component.context.update).not.toHaveBeenCalled();
|
expect(component.context.update).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should populate filter state when populate filters event has been observed', async () => {
|
||||||
|
component.context.filterLoaded = new ReplaySubject(1);
|
||||||
|
spyOn(component.context.filterLoaded, 'next').and.stub();
|
||||||
|
spyOn(component.displayValue$, 'next').and.stub();
|
||||||
|
fixture.detectChanges();
|
||||||
|
component.context.populateFilters.next({ text: 'secret.pdf' });
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(component.displayValue$.next).toHaveBeenCalledWith('secret.pdf');
|
||||||
|
expect(component.value).toBe('secret.pdf');
|
||||||
|
expect(component.context.filterRawParams[component.id]).toBe('secret.pdf');
|
||||||
|
expect(component.context.filterLoaded.next).toHaveBeenCalled();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -15,11 +15,12 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Component, Input, OnInit, ViewEncapsulation } from '@angular/core';
|
import { Component, Input, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core';
|
||||||
import { SearchWidget } from '../../models/search-widget.interface';
|
import { SearchWidget } from '../../models/search-widget.interface';
|
||||||
import { SearchWidgetSettings } from '../../models/search-widget-settings.interface';
|
import { SearchWidgetSettings } from '../../models/search-widget-settings.interface';
|
||||||
import { SearchQueryBuilderService } from '../../services/search-query-builder.service';
|
import { SearchQueryBuilderService } from '../../services/search-query-builder.service';
|
||||||
import { Subject } from 'rxjs';
|
import { ReplaySubject, Subject } from 'rxjs';
|
||||||
|
import { map, takeUntil } from 'rxjs/operators';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
@ -37,7 +38,7 @@ import { MatIconModule } from '@angular/material/icon';
|
|||||||
encapsulation: ViewEncapsulation.None,
|
encapsulation: ViewEncapsulation.None,
|
||||||
host: { class: 'adf-search-text' }
|
host: { class: 'adf-search-text' }
|
||||||
})
|
})
|
||||||
export class SearchTextComponent implements SearchWidget, OnInit {
|
export class SearchTextComponent implements SearchWidget, OnInit, OnDestroy {
|
||||||
/** The content of the text box. */
|
/** The content of the text box. */
|
||||||
@Input()
|
@Input()
|
||||||
value = '';
|
value = '';
|
||||||
@ -48,7 +49,9 @@ export class SearchTextComponent implements SearchWidget, OnInit {
|
|||||||
startValue: string;
|
startValue: string;
|
||||||
isActive = false;
|
isActive = false;
|
||||||
enableChangeUpdate = true;
|
enableChangeUpdate = true;
|
||||||
displayValue$: Subject<string> = new Subject<string>();
|
displayValue$ = new ReplaySubject<string>(1);
|
||||||
|
|
||||||
|
private readonly destroy$ = new Subject<void>();
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
if (this.context && this.settings && this.settings.pattern) {
|
if (this.context && this.settings && this.settings.pattern) {
|
||||||
@ -70,6 +73,26 @@ export class SearchTextComponent implements SearchWidget, OnInit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
this.context.populateFilters
|
||||||
|
.asObservable()
|
||||||
|
.pipe(
|
||||||
|
map((filtersQueries) => filtersQueries[this.id]),
|
||||||
|
takeUntil(this.destroy$)
|
||||||
|
)
|
||||||
|
.subscribe((filterQuery) => {
|
||||||
|
if (filterQuery) {
|
||||||
|
this.value = filterQuery;
|
||||||
|
this.updateQuery(this.value, false);
|
||||||
|
} else {
|
||||||
|
this.reset(false);
|
||||||
|
}
|
||||||
|
this.context.filterLoaded.next();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy() {
|
||||||
|
this.destroy$.next();
|
||||||
|
this.destroy$.complete();
|
||||||
}
|
}
|
||||||
|
|
||||||
clear() {
|
clear() {
|
||||||
@ -80,9 +103,9 @@ export class SearchTextComponent implements SearchWidget, OnInit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
reset() {
|
reset(updateContext = true) {
|
||||||
this.value = '';
|
this.value = '';
|
||||||
this.updateQuery(null);
|
this.updateQuery(null, updateContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
onChangedHandler(event) {
|
onChangedHandler(event) {
|
||||||
@ -93,11 +116,14 @@ export class SearchTextComponent implements SearchWidget, OnInit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private updateQuery(value: string) {
|
private updateQuery(value: string, updateContext = true) {
|
||||||
|
this.context.filterRawParams[this.id] = value;
|
||||||
this.displayValue$.next(value);
|
this.displayValue$.next(value);
|
||||||
if (this.context && this.settings && this.settings.field) {
|
if (this.context && this.settings && this.settings.field) {
|
||||||
this.context.queryFragments[this.id] = value ? `${this.settings.field}:'${this.getSearchPrefix()}${value}${this.getSearchSuffix()}'` : '';
|
this.context.queryFragments[this.id] = value ? `${this.settings.field}:'${this.getSearchPrefix()}${value}${this.getSearchSuffix()}'` : '';
|
||||||
this.context.update();
|
if (updateContext) {
|
||||||
|
this.context.update();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
|
|
||||||
import { SearchWidgetSettings } from './search-widget-settings.interface';
|
import { SearchWidgetSettings } from './search-widget-settings.interface';
|
||||||
import { SearchQueryBuilderService } from '../services/search-query-builder.service';
|
import { SearchQueryBuilderService } from '../services/search-query-builder.service';
|
||||||
import { Subject } from 'rxjs';
|
import { ReplaySubject } from 'rxjs';
|
||||||
|
|
||||||
export interface SearchWidget {
|
export interface SearchWidget {
|
||||||
id: string;
|
id: string;
|
||||||
@ -27,7 +27,7 @@ export interface SearchWidget {
|
|||||||
isActive?: boolean;
|
isActive?: boolean;
|
||||||
startValue: any;
|
startValue: any;
|
||||||
/* stream emit value on changes */
|
/* stream emit value on changes */
|
||||||
displayValue$: Subject<string>;
|
displayValue$: ReplaySubject<string>;
|
||||||
/* reset the value and update the search */
|
/* reset the value and update the search */
|
||||||
reset(): void;
|
reset(): void;
|
||||||
/* update the search with field value */
|
/* update the search with field value */
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Subject, Observable, from, ReplaySubject } from 'rxjs';
|
import { Subject, Observable, from, ReplaySubject, BehaviorSubject } from 'rxjs';
|
||||||
import { AppConfigService } from '@alfresco/adf-core';
|
import { AppConfigService } from '@alfresco/adf-core';
|
||||||
import {
|
import {
|
||||||
SearchRequest,
|
SearchRequest,
|
||||||
@ -37,8 +37,13 @@ import { FacetField } from '../models/facet-field.interface';
|
|||||||
import { FacetFieldBucket } from '../models/facet-field-bucket.interface';
|
import { FacetFieldBucket } from '../models/facet-field-bucket.interface';
|
||||||
import { SearchForm } from '../models/search-form.interface';
|
import { SearchForm } from '../models/search-form.interface';
|
||||||
import { AlfrescoApiService } from '../../services/alfresco-api.service';
|
import { AlfrescoApiService } from '../../services/alfresco-api.service';
|
||||||
|
import { Buffer } from 'buffer';
|
||||||
|
import { inject } from '@angular/core';
|
||||||
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
|
|
||||||
export abstract class BaseQueryBuilderService {
|
export abstract class BaseQueryBuilderService {
|
||||||
|
private readonly router = inject(Router);
|
||||||
|
private readonly activatedRoute = inject(ActivatedRoute);
|
||||||
private _searchApi: SearchApi;
|
private _searchApi: SearchApi;
|
||||||
get searchApi(): SearchApi {
|
get searchApi(): SearchApi {
|
||||||
this._searchApi = this._searchApi ?? new SearchApi(this.alfrescoApiService.getInstance());
|
this._searchApi = this._searchApi ?? new SearchApi(this.alfrescoApiService.getInstance());
|
||||||
@ -48,6 +53,9 @@ export abstract class BaseQueryBuilderService {
|
|||||||
/* Stream that emits the search configuration whenever the user change the search forms */
|
/* Stream that emits the search configuration whenever the user change the search forms */
|
||||||
configUpdated = new Subject<SearchConfiguration>();
|
configUpdated = new Subject<SearchConfiguration>();
|
||||||
|
|
||||||
|
/* Stream that emits the event each time when search filter finishes loading initial value */
|
||||||
|
filterLoaded = new Subject<void>();
|
||||||
|
|
||||||
/* Stream that emits the query before search whenever user search */
|
/* Stream that emits the query before search whenever user search */
|
||||||
updated = new Subject<SearchRequest>();
|
updated = new Subject<SearchRequest>();
|
||||||
|
|
||||||
@ -60,12 +68,17 @@ export abstract class BaseQueryBuilderService {
|
|||||||
/* Stream that emits search forms */
|
/* Stream that emits search forms */
|
||||||
searchForms = new ReplaySubject<SearchForm[]>(1);
|
searchForms = new ReplaySubject<SearchForm[]>(1);
|
||||||
|
|
||||||
|
/* Stream that emits the initial value for some or all search filters */
|
||||||
|
populateFilters = new BehaviorSubject<{ [key: string]: any }>({});
|
||||||
|
|
||||||
categories: SearchCategory[] = [];
|
categories: SearchCategory[] = [];
|
||||||
queryFragments: { [id: string]: string } = {};
|
queryFragments: { [id: string]: string } = {};
|
||||||
filterQueries: FilterQuery[] = [];
|
filterQueries: FilterQuery[] = [];
|
||||||
|
filterRawParams: { [key: string]: any } = {};
|
||||||
paging: { maxItems?: number; skipCount?: number } = null;
|
paging: { maxItems?: number; skipCount?: number } = null;
|
||||||
sorting: SearchSortingDefinition[] = [];
|
sorting: SearchSortingDefinition[] = [];
|
||||||
sortingOptions: SearchSortingDefinition[] = [];
|
sortingOptions: SearchSortingDefinition[] = [];
|
||||||
|
private encodedQuery: string;
|
||||||
private scope: RequestScope;
|
private scope: RequestScope;
|
||||||
private selectedConfiguration: number;
|
private selectedConfiguration: number;
|
||||||
private _userQuery = '';
|
private _userQuery = '';
|
||||||
@ -88,10 +101,7 @@ export abstract class BaseQueryBuilderService {
|
|||||||
// TODO: to be supported in future iterations
|
// TODO: to be supported in future iterations
|
||||||
ranges: { [id: string]: SearchRange } = {};
|
ranges: { [id: string]: SearchRange } = {};
|
||||||
|
|
||||||
protected constructor(
|
protected constructor(protected readonly appConfig: AppConfigService, protected readonly alfrescoApiService: AlfrescoApiService) {
|
||||||
protected appConfig: AppConfigService,
|
|
||||||
protected alfrescoApiService: AlfrescoApiService
|
|
||||||
) {
|
|
||||||
this.resetToDefaults();
|
this.resetToDefaults();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -99,7 +109,14 @@ export abstract class BaseQueryBuilderService {
|
|||||||
|
|
||||||
public abstract isFilterServiceActive(): boolean;
|
public abstract isFilterServiceActive(): boolean;
|
||||||
|
|
||||||
public resetToDefaults() {
|
public resetToDefaults(withNavigate = false) {
|
||||||
|
if (withNavigate) {
|
||||||
|
this.router.navigate([], {
|
||||||
|
queryParams: { q: null },
|
||||||
|
relativeTo: this.activatedRoute,
|
||||||
|
queryParamsHandling: 'merge'
|
||||||
|
});
|
||||||
|
}
|
||||||
const currentConfig = this.getDefaultConfiguration();
|
const currentConfig = this.getDefaultConfiguration();
|
||||||
this.resetSearchOptions();
|
this.resetSearchOptions();
|
||||||
this.configUpdated.next(currentConfig);
|
this.configUpdated.next(currentConfig);
|
||||||
@ -140,6 +157,9 @@ export abstract class BaseQueryBuilderService {
|
|||||||
this.sortingOptions = [];
|
this.sortingOptions = [];
|
||||||
this.userFacetBuckets = {};
|
this.userFacetBuckets = {};
|
||||||
this.scope = null;
|
this.scope = null;
|
||||||
|
this.filterRawParams = {};
|
||||||
|
this._userQuery = '';
|
||||||
|
this.populateFilters.next({});
|
||||||
}
|
}
|
||||||
|
|
||||||
public getSearchFormDetails(): SearchForm[] {
|
public getSearchFormDetails(): SearchForm[] {
|
||||||
@ -297,12 +317,16 @@ export abstract class BaseQueryBuilderService {
|
|||||||
/**
|
/**
|
||||||
* Builds and executes the current query.
|
* Builds and executes the current query.
|
||||||
*
|
*
|
||||||
|
* @param updateQueryParams whether query params should be updated with encoded query
|
||||||
* @param queryBody query settings
|
* @param queryBody query settings
|
||||||
*/
|
*/
|
||||||
async execute(queryBody?: SearchRequest) {
|
async execute(updateQueryParams = true, queryBody?: SearchRequest) {
|
||||||
try {
|
try {
|
||||||
const query = queryBody ? queryBody : this.buildQuery();
|
const query = queryBody ? queryBody : this.buildQuery();
|
||||||
if (query) {
|
if (query) {
|
||||||
|
if (updateQueryParams) {
|
||||||
|
this.updateSearchQueryParams();
|
||||||
|
}
|
||||||
const resultSetPaging: ResultSetPaging = await this.searchApi.search(query);
|
const resultSetPaging: ResultSetPaging = await this.searchApi.search(query);
|
||||||
this.executed.next(resultSetPaging);
|
this.executed.next(resultSetPaging);
|
||||||
}
|
}
|
||||||
@ -461,9 +485,9 @@ export abstract class BaseQueryBuilderService {
|
|||||||
end: set.end,
|
end: set.end,
|
||||||
startInclusive: set.startInclusive,
|
startInclusive: set.startInclusive,
|
||||||
endInclusive: set.endInclusive
|
endInclusive: set.endInclusive
|
||||||
}) as any
|
} as any)
|
||||||
)
|
)
|
||||||
}) as any
|
} as any)
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -477,7 +501,9 @@ export abstract class BaseQueryBuilderService {
|
|||||||
|
|
||||||
protected getFinalQuery(): string {
|
protected getFinalQuery(): string {
|
||||||
let query = '';
|
let query = '';
|
||||||
|
if (this.userQuery) {
|
||||||
|
this.filterRawParams['userQuery'] = this.userQuery;
|
||||||
|
}
|
||||||
this.categories.forEach((facet) => {
|
this.categories.forEach((facet) => {
|
||||||
const customQuery = this.queryFragments[facet.id];
|
const customQuery = this.queryFragments[facet.id];
|
||||||
if (customQuery) {
|
if (customQuery) {
|
||||||
@ -522,7 +548,7 @@ export abstract class BaseQueryBuilderService {
|
|||||||
limit: facet.limit,
|
limit: facet.limit,
|
||||||
offset: facet.offset,
|
offset: facet.offset,
|
||||||
prefix: facet.prefix
|
prefix: facet.prefix
|
||||||
}) as any
|
} as any)
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -543,4 +569,38 @@ export abstract class BaseQueryBuilderService {
|
|||||||
}
|
}
|
||||||
return configLabel;
|
return configLabel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encodes filter configuration stored in filterRawParams object.
|
||||||
|
*/
|
||||||
|
encodeQuery() {
|
||||||
|
this.encodedQuery = Buffer.from(JSON.stringify(this.filterRawParams)).toString('base64');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encodes existing filters configuration and updates search query param value.
|
||||||
|
*/
|
||||||
|
updateSearchQueryParams() {
|
||||||
|
this.encodeQuery();
|
||||||
|
this.router.navigate([], {
|
||||||
|
relativeTo: this.activatedRoute,
|
||||||
|
queryParams: { q: this.encodedQuery },
|
||||||
|
queryParamsHandling: 'merge'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds search query with provided user query, executes query, encodes latest filter config and navigates to search.
|
||||||
|
*
|
||||||
|
* @param query user query to search for
|
||||||
|
* @param searchUrl search url to navigate to
|
||||||
|
*/
|
||||||
|
async navigateToSearch(query: string, searchUrl: string) {
|
||||||
|
this.userQuery = query;
|
||||||
|
await this.execute();
|
||||||
|
await this.router.navigate([searchUrl], {
|
||||||
|
queryParams: { q: this.encodedQuery },
|
||||||
|
queryParamsHandling: 'merge'
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -69,7 +69,7 @@ export class SearchFacetFiltersService implements OnDestroy {
|
|||||||
this.responseFacets = null;
|
this.responseFacets = null;
|
||||||
});
|
});
|
||||||
|
|
||||||
this.queryBuilder.updated.pipe(takeUntil(this.onDestroy$)).subscribe((query) => this.queryBuilder.execute(query));
|
this.queryBuilder.updated.pipe(takeUntil(this.onDestroy$)).subscribe((query) => this.queryBuilder.execute(true, query));
|
||||||
|
|
||||||
this.queryBuilder.executed.pipe(takeUntil(this.onDestroy$)).subscribe((resultSetPaging: ResultSetPaging) => {
|
this.queryBuilder.executed.pipe(takeUntil(this.onDestroy$)).subscribe((resultSetPaging: ResultSetPaging) => {
|
||||||
this.onDataLoaded(resultSetPaging);
|
this.onDataLoaded(resultSetPaging);
|
||||||
@ -447,7 +447,7 @@ export class SearchFacetFiltersService implements OnDestroy {
|
|||||||
this.responseFacets = [];
|
this.responseFacets = [];
|
||||||
this.selectedBuckets = [];
|
this.selectedBuckets = [];
|
||||||
this.tabbedFacet = null;
|
this.tabbedFacet = null;
|
||||||
this.queryBuilder.resetToDefaults();
|
this.queryBuilder.resetToDefaults(true);
|
||||||
this.queryBuilder.update();
|
this.queryBuilder.update();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,7 +23,6 @@ import { ContentTestingModule } from '../../testing/content.testing.module';
|
|||||||
import { AlfrescoApiService } from '../../services/alfresco-api.service';
|
import { AlfrescoApiService } from '../../services/alfresco-api.service';
|
||||||
|
|
||||||
describe('SearchHeaderQueryBuilderService', () => {
|
describe('SearchHeaderQueryBuilderService', () => {
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [ContentTestingModule]
|
imports: [ContentTestingModule]
|
||||||
@ -36,21 +35,22 @@ describe('SearchHeaderQueryBuilderService', () => {
|
|||||||
return config;
|
return config;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const createQueryBuilder = (searchSettings): SearchHeaderQueryBuilderService => {
|
||||||
|
let builder: SearchHeaderQueryBuilderService;
|
||||||
|
TestBed.runInInjectionContext(() => {
|
||||||
|
const alfrescoApiService = TestBed.inject(AlfrescoApiService);
|
||||||
|
builder = new SearchHeaderQueryBuilderService(buildConfig(searchSettings), alfrescoApiService, null);
|
||||||
|
});
|
||||||
|
return builder;
|
||||||
|
};
|
||||||
|
|
||||||
it('should load the configuration from app config', () => {
|
it('should load the configuration from app config', () => {
|
||||||
const config: SearchConfiguration = {
|
const config: SearchConfiguration = {
|
||||||
categories: [
|
categories: [{ id: 'cat1', enabled: true } as any, { id: 'cat2', enabled: true } as any],
|
||||||
{ id: 'cat1', enabled: true } as any,
|
|
||||||
{ id: 'cat2', enabled: true } as any
|
|
||||||
],
|
|
||||||
filterQueries: [{ query: 'query1' }, { query: 'query2' }]
|
filterQueries: [{ query: 'query1' }, { query: 'query2' }]
|
||||||
};
|
};
|
||||||
|
|
||||||
const alfrescoApiService = TestBed.inject(AlfrescoApiService);
|
const builder = createQueryBuilder(config);
|
||||||
const builder = new SearchHeaderQueryBuilderService(
|
|
||||||
buildConfig(config),
|
|
||||||
alfrescoApiService,
|
|
||||||
null
|
|
||||||
);
|
|
||||||
|
|
||||||
builder.categories = [];
|
builder.categories = [];
|
||||||
builder.filterQueries = [];
|
builder.filterQueries = [];
|
||||||
@ -73,12 +73,7 @@ describe('SearchHeaderQueryBuilderService', () => {
|
|||||||
filterQueries: [{ query: 'query1' }, { query: 'query2' }]
|
filterQueries: [{ query: 'query1' }, { query: 'query2' }]
|
||||||
};
|
};
|
||||||
|
|
||||||
const alfrescoApiService = TestBed.inject(AlfrescoApiService);
|
const service = createQueryBuilder(config);
|
||||||
const service = new SearchHeaderQueryBuilderService(
|
|
||||||
buildConfig(config),
|
|
||||||
alfrescoApiService,
|
|
||||||
null
|
|
||||||
);
|
|
||||||
|
|
||||||
const category = service.getCategoryForColumn('fake-key-1');
|
const category = service.getCategoryForColumn('fake-key-1');
|
||||||
expect(category).not.toBeNull();
|
expect(category).not.toBeNull();
|
||||||
@ -87,34 +82,19 @@ describe('SearchHeaderQueryBuilderService', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should have empty user query by default', () => {
|
it('should have empty user query by default', () => {
|
||||||
const alfrescoApiService = TestBed.inject(AlfrescoApiService);
|
const builder = createQueryBuilder({});
|
||||||
const builder = new SearchHeaderQueryBuilderService(
|
|
||||||
buildConfig({}),
|
|
||||||
alfrescoApiService,
|
|
||||||
null
|
|
||||||
);
|
|
||||||
expect(builder.userQuery).toBe('');
|
expect(builder.userQuery).toBe('');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should add the extra filter for the parent node', () => {
|
it('should add the extra filter for the parent node', () => {
|
||||||
const config: SearchConfiguration = {
|
const config: SearchConfiguration = {
|
||||||
categories: [
|
categories: [{ id: 'cat1', enabled: true } as any, { id: 'cat2', enabled: true } as any],
|
||||||
{ id: 'cat1', enabled: true } as any,
|
|
||||||
{ id: 'cat2', enabled: true } as any
|
|
||||||
],
|
|
||||||
filterQueries: [{ query: 'query1' }, { query: 'query2' }]
|
filterQueries: [{ query: 'query1' }, { query: 'query2' }]
|
||||||
};
|
};
|
||||||
|
|
||||||
const expectedResult = [
|
const expectedResult = [{ query: 'PARENT:"workspace://SpacesStore/fake-node-id"' }];
|
||||||
{ query: 'PARENT:"workspace://SpacesStore/fake-node-id"' }
|
|
||||||
];
|
|
||||||
|
|
||||||
const alfrescoApiService = TestBed.inject(AlfrescoApiService);
|
const searchHeaderService = createQueryBuilder(config);
|
||||||
const searchHeaderService = new SearchHeaderQueryBuilderService(
|
|
||||||
buildConfig(config),
|
|
||||||
alfrescoApiService,
|
|
||||||
null
|
|
||||||
);
|
|
||||||
|
|
||||||
searchHeaderService.setCurrentRootFolderId('fake-node-id');
|
searchHeaderService.setCurrentRootFolderId('fake-node-id');
|
||||||
|
|
||||||
@ -122,52 +102,28 @@ describe('SearchHeaderQueryBuilderService', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should not add again the parent filter if that node is already added', () => {
|
it('should not add again the parent filter if that node is already added', () => {
|
||||||
|
const expectedResult = [{ query: 'PARENT:"workspace://SpacesStore/fake-node-id"' }];
|
||||||
const expectedResult = [
|
|
||||||
{ query: 'PARENT:"workspace://SpacesStore/fake-node-id"' }
|
|
||||||
];
|
|
||||||
|
|
||||||
const config: SearchConfiguration = {
|
const config: SearchConfiguration = {
|
||||||
categories: [
|
categories: [{ id: 'cat1', enabled: true } as any, { id: 'cat2', enabled: true } as any],
|
||||||
{ id: 'cat1', enabled: true } as any,
|
|
||||||
{ id: 'cat2', enabled: true } as any
|
|
||||||
],
|
|
||||||
filterQueries: expectedResult
|
filterQueries: expectedResult
|
||||||
};
|
};
|
||||||
|
|
||||||
const alfrescoApiService = TestBed.inject(AlfrescoApiService);
|
const searchHeaderService = createQueryBuilder(config);
|
||||||
const searchHeaderService = new SearchHeaderQueryBuilderService(
|
|
||||||
buildConfig(config),
|
|
||||||
alfrescoApiService,
|
|
||||||
null
|
|
||||||
);
|
|
||||||
|
|
||||||
searchHeaderService.setCurrentRootFolderId('fake-node-id');
|
searchHeaderService.setCurrentRootFolderId('fake-node-id');
|
||||||
|
|
||||||
expect(searchHeaderService.filterQueries).toEqual(
|
expect(searchHeaderService.filterQueries).toEqual(expectedResult, 'Filters are not as expected');
|
||||||
expectedResult,
|
|
||||||
'Filters are not as expected'
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not add duplicate column names in activeFilters', () => {
|
it('should not add duplicate column names in activeFilters', () => {
|
||||||
const activeFilter = 'FakeColumn';
|
const activeFilter = 'FakeColumn';
|
||||||
|
|
||||||
const config: SearchConfiguration = {
|
const config: SearchConfiguration = {
|
||||||
categories: [
|
categories: [{ id: 'cat1', enabled: true } as any],
|
||||||
{ id: 'cat1', enabled: true } as any
|
filterQueries: [{ query: 'PARENT:"workspace://SpacesStore/fake-node-id' }]
|
||||||
],
|
|
||||||
filterQueries: [
|
|
||||||
{ query: 'PARENT:"workspace://SpacesStore/fake-node-id' }
|
|
||||||
]
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const alfrescoApiService = TestBed.inject(AlfrescoApiService);
|
const searchHeaderService = createQueryBuilder(config);
|
||||||
const searchHeaderService = new SearchHeaderQueryBuilderService(
|
|
||||||
buildConfig(config),
|
|
||||||
alfrescoApiService,
|
|
||||||
null
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(searchHeaderService.activeFilters.length).toBe(0);
|
expect(searchHeaderService.activeFilters.length).toBe(0);
|
||||||
|
|
||||||
|
@ -23,6 +23,16 @@ import { FacetField } from '../models/facet-field.interface';
|
|||||||
import { TestBed } from '@angular/core/testing';
|
import { TestBed } from '@angular/core/testing';
|
||||||
import { ContentTestingModule } from '../../testing/content.testing.module';
|
import { ContentTestingModule } from '../../testing/content.testing.module';
|
||||||
import { ADF_SEARCH_CONFIGURATION } from '../search-configuration.token';
|
import { ADF_SEARCH_CONFIGURATION } from '../search-configuration.token';
|
||||||
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
|
|
||||||
|
const buildConfig = (searchSettings = {}): AppConfigService => {
|
||||||
|
let config: AppConfigService;
|
||||||
|
TestBed.runInInjectionContext(() => {
|
||||||
|
config = TestBed.inject(AppConfigService);
|
||||||
|
});
|
||||||
|
config.config.search = searchSettings;
|
||||||
|
return config;
|
||||||
|
};
|
||||||
|
|
||||||
describe('SearchQueryBuilder (runtime config)', () => {
|
describe('SearchQueryBuilder (runtime config)', () => {
|
||||||
const runtimeConfig: SearchConfiguration = {};
|
const runtimeConfig: SearchConfiguration = {};
|
||||||
@ -30,32 +40,32 @@ describe('SearchQueryBuilder (runtime config)', () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [ContentTestingModule],
|
imports: [ContentTestingModule],
|
||||||
providers: [
|
providers: [{ provide: ADF_SEARCH_CONFIGURATION, useValue: runtimeConfig }]
|
||||||
{ provide: ADF_SEARCH_CONFIGURATION, useValue: runtimeConfig }
|
|
||||||
]
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const buildConfig = (searchSettings): AppConfigService => {
|
|
||||||
const config = TestBed.inject(AppConfigService);
|
|
||||||
config.config.search = searchSettings;
|
|
||||||
return config;
|
|
||||||
};
|
|
||||||
|
|
||||||
it('should use custom search configuration via dependency injection', () => {
|
it('should use custom search configuration via dependency injection', () => {
|
||||||
const builder = TestBed.inject(SearchQueryBuilderService);
|
let builder: SearchQueryBuilderService;
|
||||||
|
TestBed.runInInjectionContext(() => {
|
||||||
|
builder = TestBed.inject(SearchQueryBuilderService);
|
||||||
|
});
|
||||||
const currentConfig = builder.loadConfiguration();
|
const currentConfig = builder.loadConfiguration();
|
||||||
|
|
||||||
expect(currentConfig).toEqual(runtimeConfig);
|
expect(currentConfig).toEqual(runtimeConfig);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should prioritise runtime config over configuration file', () => {
|
it('should prioritise runtime config over configuration file', () => {
|
||||||
const config: SearchConfiguration = {
|
const config: SearchConfiguration = {
|
||||||
categories: [{ id: 'cat1', enabled: true } as any, { id: 'cat2', enabled: true } as any],
|
categories: [{ id: 'cat1', enabled: true } as any, { id: 'cat2', enabled: true } as any],
|
||||||
filterQueries: [{ query: 'query1' }, { query: 'query2' }]
|
filterQueries: [{ query: 'query1' }, { query: 'query2' }]
|
||||||
};
|
};
|
||||||
const alfrescoApiService = TestBed.inject(AlfrescoApiService);
|
|
||||||
const builder = new SearchQueryBuilderService(buildConfig(config), alfrescoApiService, runtimeConfig);
|
let alfrescoApiService: AlfrescoApiService;
|
||||||
|
let builder: SearchQueryBuilderService;
|
||||||
|
TestBed.runInInjectionContext(() => {
|
||||||
|
alfrescoApiService = TestBed.inject(AlfrescoApiService);
|
||||||
|
builder = new SearchQueryBuilderService(buildConfig(config), alfrescoApiService, runtimeConfig);
|
||||||
|
});
|
||||||
const currentConfig = builder.loadConfiguration();
|
const currentConfig = builder.loadConfiguration();
|
||||||
|
|
||||||
expect(currentConfig).toEqual(runtimeConfig);
|
expect(currentConfig).toEqual(runtimeConfig);
|
||||||
@ -63,16 +73,24 @@ describe('SearchQueryBuilder (runtime config)', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('SearchQueryBuilder', () => {
|
describe('SearchQueryBuilder', () => {
|
||||||
|
let router: Router;
|
||||||
|
let activatedRoute: ActivatedRoute;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [ContentTestingModule]
|
imports: [ContentTestingModule]
|
||||||
});
|
});
|
||||||
|
router = TestBed.inject(Router);
|
||||||
|
activatedRoute = TestBed.inject(ActivatedRoute);
|
||||||
});
|
});
|
||||||
|
|
||||||
const buildConfig = (searchSettings = {}): AppConfigService => {
|
const createQueryBuilder = (config?: any) => {
|
||||||
const config = TestBed.inject(AppConfigService);
|
let builder: SearchQueryBuilderService;
|
||||||
config.config.search = searchSettings;
|
TestBed.runInInjectionContext(() => {
|
||||||
return config;
|
const alfrescoApiService = TestBed.inject(AlfrescoApiService);
|
||||||
|
builder = new SearchQueryBuilderService(buildConfig(config), alfrescoApiService);
|
||||||
|
});
|
||||||
|
return builder;
|
||||||
};
|
};
|
||||||
|
|
||||||
it('should reset to defaults', () => {
|
it('should reset to defaults', () => {
|
||||||
@ -80,8 +98,8 @@ describe('SearchQueryBuilder', () => {
|
|||||||
categories: [{ id: 'cat1', enabled: true } as any, { id: 'cat2', enabled: true } as any],
|
categories: [{ id: 'cat1', enabled: true } as any, { id: 'cat2', enabled: true } as any],
|
||||||
filterQueries: [{ query: 'query1' }, { query: 'query2' }]
|
filterQueries: [{ query: 'query1' }, { query: 'query2' }]
|
||||||
};
|
};
|
||||||
const alfrescoApiService = TestBed.inject(AlfrescoApiService);
|
|
||||||
const builder = new SearchQueryBuilderService(buildConfig(config), alfrescoApiService);
|
const builder = createQueryBuilder(config);
|
||||||
|
|
||||||
builder.categories = [];
|
builder.categories = [];
|
||||||
builder.filterQueries = [];
|
builder.filterQueries = [];
|
||||||
@ -96,23 +114,18 @@ describe('SearchQueryBuilder', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should have empty user query by default', () => {
|
it('should have empty user query by default', () => {
|
||||||
const alfrescoApiService = TestBed.inject(AlfrescoApiService);
|
const builder = createQueryBuilder();
|
||||||
const builder = new SearchQueryBuilderService(buildConfig(), alfrescoApiService);
|
|
||||||
expect(builder.userQuery).toBe('');
|
expect(builder.userQuery).toBe('');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should wrap user query with brackets', () => {
|
it('should wrap user query with brackets', () => {
|
||||||
const alfrescoApiService = TestBed.inject(AlfrescoApiService);
|
const builder = createQueryBuilder();
|
||||||
|
|
||||||
const builder = new SearchQueryBuilderService(buildConfig(), alfrescoApiService);
|
|
||||||
builder.userQuery = 'my query';
|
builder.userQuery = 'my query';
|
||||||
expect(builder.userQuery).toEqual('(my query)');
|
expect(builder.userQuery).toEqual('(my query)');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should trim user query value', () => {
|
it('should trim user query value', () => {
|
||||||
const alfrescoApiService = TestBed.inject(AlfrescoApiService);
|
const builder = createQueryBuilder();
|
||||||
|
|
||||||
const builder = new SearchQueryBuilderService(buildConfig(), alfrescoApiService);
|
|
||||||
builder.userQuery = ' something ';
|
builder.userQuery = ' something ';
|
||||||
expect(builder.userQuery).toEqual('(something)');
|
expect(builder.userQuery).toEqual('(something)');
|
||||||
});
|
});
|
||||||
@ -121,9 +134,7 @@ describe('SearchQueryBuilder', () => {
|
|||||||
const config: SearchConfiguration = {
|
const config: SearchConfiguration = {
|
||||||
categories: [{ id: 'cat1', enabled: true } as any, { id: 'cat2', enabled: false } as any, { id: 'cat3', enabled: true } as any]
|
categories: [{ id: 'cat1', enabled: true } as any, { id: 'cat2', enabled: false } as any, { id: 'cat3', enabled: true } as any]
|
||||||
};
|
};
|
||||||
const alfrescoApiService = TestBed.inject(AlfrescoApiService);
|
const builder = createQueryBuilder(config);
|
||||||
|
|
||||||
const builder = new SearchQueryBuilderService(buildConfig(config), alfrescoApiService);
|
|
||||||
|
|
||||||
expect(builder.categories.length).toBe(2);
|
expect(builder.categories.length).toBe(2);
|
||||||
expect(builder.categories[0].id).toBe('cat1');
|
expect(builder.categories[0].id).toBe('cat1');
|
||||||
@ -135,8 +146,7 @@ describe('SearchQueryBuilder', () => {
|
|||||||
categories: [],
|
categories: [],
|
||||||
filterQueries: [{ query: 'query1' }, { query: 'query2' }]
|
filterQueries: [{ query: 'query1' }, { query: 'query2' }]
|
||||||
};
|
};
|
||||||
const alfrescoApiService = TestBed.inject(AlfrescoApiService);
|
const builder = createQueryBuilder(config);
|
||||||
const builder = new SearchQueryBuilderService(buildConfig(config), alfrescoApiService);
|
|
||||||
|
|
||||||
expect(builder.filterQueries.length).toBe(2);
|
expect(builder.filterQueries.length).toBe(2);
|
||||||
expect(builder.filterQueries[0].query).toBe('query1');
|
expect(builder.filterQueries[0].query).toBe('query1');
|
||||||
@ -144,10 +154,7 @@ describe('SearchQueryBuilder', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should add new filter query', () => {
|
it('should add new filter query', () => {
|
||||||
const alfrescoApiService = TestBed.inject(AlfrescoApiService);
|
const builder = createQueryBuilder();
|
||||||
|
|
||||||
const builder = new SearchQueryBuilderService(buildConfig(), alfrescoApiService);
|
|
||||||
|
|
||||||
builder.addFilterQuery('q1');
|
builder.addFilterQuery('q1');
|
||||||
|
|
||||||
expect(builder.filterQueries.length).toBe(1);
|
expect(builder.filterQueries.length).toBe(1);
|
||||||
@ -155,10 +162,7 @@ describe('SearchQueryBuilder', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should not add empty filter query', () => {
|
it('should not add empty filter query', () => {
|
||||||
const alfrescoApiService = TestBed.inject(AlfrescoApiService);
|
const builder = createQueryBuilder();
|
||||||
|
|
||||||
const builder = new SearchQueryBuilderService(buildConfig(), alfrescoApiService);
|
|
||||||
|
|
||||||
builder.addFilterQuery(null);
|
builder.addFilterQuery(null);
|
||||||
builder.addFilterQuery('');
|
builder.addFilterQuery('');
|
||||||
|
|
||||||
@ -166,10 +170,7 @@ describe('SearchQueryBuilder', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should not add duplicate filter query', () => {
|
it('should not add duplicate filter query', () => {
|
||||||
const alfrescoApiService = TestBed.inject(AlfrescoApiService);
|
const builder = createQueryBuilder();
|
||||||
|
|
||||||
const builder = new SearchQueryBuilderService(buildConfig(), alfrescoApiService);
|
|
||||||
|
|
||||||
builder.addFilterQuery('q1');
|
builder.addFilterQuery('q1');
|
||||||
builder.addFilterQuery('q1');
|
builder.addFilterQuery('q1');
|
||||||
builder.addFilterQuery('q1');
|
builder.addFilterQuery('q1');
|
||||||
@ -179,10 +180,7 @@ describe('SearchQueryBuilder', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should remove filter query', () => {
|
it('should remove filter query', () => {
|
||||||
const alfrescoApiService = TestBed.inject(AlfrescoApiService);
|
const builder = createQueryBuilder();
|
||||||
|
|
||||||
const builder = new SearchQueryBuilderService(buildConfig(), alfrescoApiService);
|
|
||||||
|
|
||||||
builder.addFilterQuery('q1');
|
builder.addFilterQuery('q1');
|
||||||
builder.addFilterQuery('q2');
|
builder.addFilterQuery('q2');
|
||||||
expect(builder.filterQueries.length).toBe(2);
|
expect(builder.filterQueries.length).toBe(2);
|
||||||
@ -193,9 +191,7 @@ describe('SearchQueryBuilder', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should not remove empty query', () => {
|
it('should not remove empty query', () => {
|
||||||
const alfrescoApiService = TestBed.inject(AlfrescoApiService);
|
const builder = createQueryBuilder();
|
||||||
|
|
||||||
const builder = new SearchQueryBuilderService(buildConfig(), alfrescoApiService);
|
|
||||||
builder.addFilterQuery('q1');
|
builder.addFilterQuery('q1');
|
||||||
builder.addFilterQuery('q2');
|
builder.addFilterQuery('q2');
|
||||||
expect(builder.filterQueries.length).toBe(2);
|
expect(builder.filterQueries.length).toBe(2);
|
||||||
@ -215,9 +211,7 @@ describe('SearchQueryBuilder', () => {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const alfrescoApiService = TestBed.inject(AlfrescoApiService);
|
const builder = createQueryBuilder(config);
|
||||||
|
|
||||||
const builder = new SearchQueryBuilderService(buildConfig(config), alfrescoApiService);
|
|
||||||
const query = builder.getFacetQuery('query2');
|
const query = builder.getFacetQuery('query2');
|
||||||
|
|
||||||
expect(query.query).toBe('q2');
|
expect(query.query).toBe('q2');
|
||||||
@ -231,9 +225,7 @@ describe('SearchQueryBuilder', () => {
|
|||||||
queries: [{ query: 'q1', label: 'query1' }]
|
queries: [{ query: 'q1', label: 'query1' }]
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const alfrescoApiService = TestBed.inject(AlfrescoApiService);
|
const builder = createQueryBuilder(config);
|
||||||
|
|
||||||
const builder = new SearchQueryBuilderService(buildConfig(config), alfrescoApiService);
|
|
||||||
|
|
||||||
const query1 = builder.getFacetQuery('');
|
const query1 = builder.getFacetQuery('');
|
||||||
expect(query1).toBeNull();
|
expect(query1).toBeNull();
|
||||||
@ -252,9 +244,7 @@ describe('SearchQueryBuilder', () => {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const alfrescoApiService = TestBed.inject(AlfrescoApiService);
|
const builder = createQueryBuilder(config);
|
||||||
|
|
||||||
const builder = new SearchQueryBuilderService(buildConfig(config), alfrescoApiService);
|
|
||||||
const field = builder.getFacetField('Size');
|
const field = builder.getFacetField('Size');
|
||||||
|
|
||||||
expect(field.label).toBe('Size');
|
expect(field.label).toBe('Size');
|
||||||
@ -271,9 +261,7 @@ describe('SearchQueryBuilder', () => {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const alfrescoApiService = TestBed.inject(AlfrescoApiService);
|
const builder = createQueryBuilder(config);
|
||||||
|
|
||||||
const builder = new SearchQueryBuilderService(buildConfig(config), alfrescoApiService);
|
|
||||||
const field = builder.getFacetField('Missing');
|
const field = builder.getFacetField('Missing');
|
||||||
|
|
||||||
expect(field).toBeFalsy();
|
expect(field).toBeFalsy();
|
||||||
@ -286,9 +274,7 @@ describe('SearchQueryBuilder', () => {
|
|||||||
fields: [{ field: 'content.size', mincount: 1, label: 'Label with spaces' }]
|
fields: [{ field: 'content.size', mincount: 1, label: 'Label with spaces' }]
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const alfrescoApiService = TestBed.inject(AlfrescoApiService);
|
const builder = createQueryBuilder(config);
|
||||||
|
|
||||||
const builder = new SearchQueryBuilderService(buildConfig(config), alfrescoApiService);
|
|
||||||
const field = builder.getFacetField('Label with spaces');
|
const field = builder.getFacetField('Label with spaces');
|
||||||
|
|
||||||
expect(field.label).toBe('"Label with spaces"');
|
expect(field.label).toBe('"Label with spaces"');
|
||||||
@ -299,9 +285,7 @@ describe('SearchQueryBuilder', () => {
|
|||||||
const config: SearchConfiguration = {
|
const config: SearchConfiguration = {
|
||||||
categories: [{ id: 'cat1', enabled: true } as any]
|
categories: [{ id: 'cat1', enabled: true } as any]
|
||||||
};
|
};
|
||||||
const alfrescoApiService = TestBed.inject(AlfrescoApiService);
|
const builder = createQueryBuilder(config);
|
||||||
|
|
||||||
const builder = new SearchQueryBuilderService(buildConfig(config), alfrescoApiService);
|
|
||||||
builder.queryFragments['cat1'] = null;
|
builder.queryFragments['cat1'] = null;
|
||||||
|
|
||||||
const compiled = builder.buildQuery();
|
const compiled = builder.buildQuery();
|
||||||
@ -312,10 +296,7 @@ describe('SearchQueryBuilder', () => {
|
|||||||
const config: SearchConfiguration = {
|
const config: SearchConfiguration = {
|
||||||
categories: [{ id: 'cat1', enabled: true } as any]
|
categories: [{ id: 'cat1', enabled: true } as any]
|
||||||
};
|
};
|
||||||
const alfrescoApiService = TestBed.inject(AlfrescoApiService);
|
const builder = createQueryBuilder(config);
|
||||||
|
|
||||||
const builder = new SearchQueryBuilderService(buildConfig(config), alfrescoApiService);
|
|
||||||
|
|
||||||
builder.queryFragments['cat1'] = 'cm:name:test';
|
builder.queryFragments['cat1'] = 'cm:name:test';
|
||||||
|
|
||||||
const compiled = builder.buildQuery();
|
const compiled = builder.buildQuery();
|
||||||
@ -326,9 +307,7 @@ describe('SearchQueryBuilder', () => {
|
|||||||
const config: SearchConfiguration = {
|
const config: SearchConfiguration = {
|
||||||
categories: [{ id: 'cat1', enabled: true } as any, { id: 'cat2', enabled: true } as any]
|
categories: [{ id: 'cat1', enabled: true } as any, { id: 'cat2', enabled: true } as any]
|
||||||
};
|
};
|
||||||
const alfrescoApiService = TestBed.inject(AlfrescoApiService);
|
const builder = createQueryBuilder(config);
|
||||||
|
|
||||||
const builder = new SearchQueryBuilderService(buildConfig(config), alfrescoApiService);
|
|
||||||
|
|
||||||
builder.queryFragments['cat1'] = 'cm:name:test';
|
builder.queryFragments['cat1'] = 'cm:name:test';
|
||||||
builder.queryFragments['cat2'] = 'NOT cm:creator:System';
|
builder.queryFragments['cat2'] = 'NOT cm:creator:System';
|
||||||
@ -342,9 +321,7 @@ describe('SearchQueryBuilder', () => {
|
|||||||
fields: ['field1', 'field2'],
|
fields: ['field1', 'field2'],
|
||||||
categories: [{ id: 'cat1', enabled: true } as any, { id: 'cat2', enabled: true } as any]
|
categories: [{ id: 'cat1', enabled: true } as any, { id: 'cat2', enabled: true } as any]
|
||||||
};
|
};
|
||||||
const alfrescoApiService = TestBed.inject(AlfrescoApiService);
|
const builder = createQueryBuilder(config);
|
||||||
|
|
||||||
const builder = new SearchQueryBuilderService(buildConfig(config), alfrescoApiService);
|
|
||||||
|
|
||||||
builder.queryFragments['cat1'] = 'cm:name:test';
|
builder.queryFragments['cat1'] = 'cm:name:test';
|
||||||
|
|
||||||
@ -357,10 +334,7 @@ describe('SearchQueryBuilder', () => {
|
|||||||
fields: [],
|
fields: [],
|
||||||
categories: [{ id: 'cat1', enabled: true } as any, { id: 'cat2', enabled: true } as any]
|
categories: [{ id: 'cat1', enabled: true } as any, { id: 'cat2', enabled: true } as any]
|
||||||
};
|
};
|
||||||
const alfrescoApiService = TestBed.inject(AlfrescoApiService);
|
const builder = createQueryBuilder(config);
|
||||||
|
|
||||||
const builder = new SearchQueryBuilderService(buildConfig(config), alfrescoApiService);
|
|
||||||
|
|
||||||
builder.queryFragments['cat1'] = 'cm:name:test';
|
builder.queryFragments['cat1'] = 'cm:name:test';
|
||||||
|
|
||||||
const compiled = builder.buildQuery();
|
const compiled = builder.buildQuery();
|
||||||
@ -371,9 +345,7 @@ describe('SearchQueryBuilder', () => {
|
|||||||
const config: SearchConfiguration = {
|
const config: SearchConfiguration = {
|
||||||
categories: [{ id: 'cat1', enabled: true } as any]
|
categories: [{ id: 'cat1', enabled: true } as any]
|
||||||
};
|
};
|
||||||
const alfrescoApiService = TestBed.inject(AlfrescoApiService);
|
const builder = createQueryBuilder(config);
|
||||||
|
|
||||||
const builder = new SearchQueryBuilderService(buildConfig(config), alfrescoApiService);
|
|
||||||
builder.queryFragments['cat1'] = 'cm:name:test';
|
builder.queryFragments['cat1'] = 'cm:name:test';
|
||||||
builder.addFilterQuery('query1');
|
builder.addFilterQuery('query1');
|
||||||
|
|
||||||
@ -388,9 +360,7 @@ describe('SearchQueryBuilder', () => {
|
|||||||
queries: [{ query: 'q1', label: 'q2', group: 'group-name' }]
|
queries: [{ query: 'q1', label: 'q2', group: 'group-name' }]
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const alfrescoApiService = TestBed.inject(AlfrescoApiService);
|
const builder = createQueryBuilder(config);
|
||||||
|
|
||||||
const builder = new SearchQueryBuilderService(buildConfig(config), alfrescoApiService);
|
|
||||||
builder.queryFragments['cat1'] = 'cm:name:test';
|
builder.queryFragments['cat1'] = 'cm:name:test';
|
||||||
|
|
||||||
const compiled = builder.buildQuery();
|
const compiled = builder.buildQuery();
|
||||||
@ -407,9 +377,7 @@ describe('SearchQueryBuilder', () => {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const alfrescoApiService = TestBed.inject(AlfrescoApiService);
|
const builder = createQueryBuilder(config);
|
||||||
|
|
||||||
const builder = new SearchQueryBuilderService(buildConfig(config), alfrescoApiService);
|
|
||||||
builder.queryFragments['cat1'] = 'cm:name:test';
|
builder.queryFragments['cat1'] = 'cm:name:test';
|
||||||
|
|
||||||
const compiled = builder.buildQuery();
|
const compiled = builder.buildQuery();
|
||||||
@ -449,9 +417,7 @@ describe('SearchQueryBuilder', () => {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const alfrescoApiService = TestBed.inject(AlfrescoApiService);
|
const builder = createQueryBuilder(config);
|
||||||
|
|
||||||
const builder = new SearchQueryBuilderService(buildConfig(config), alfrescoApiService);
|
|
||||||
builder.queryFragments['cat1'] = 'cm:name:test';
|
builder.queryFragments['cat1'] = 'cm:name:test';
|
||||||
|
|
||||||
const compiled = builder.buildQuery();
|
const compiled = builder.buildQuery();
|
||||||
@ -485,9 +451,7 @@ describe('SearchQueryBuilder', () => {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const alfrescoApiService = TestBed.inject(AlfrescoApiService);
|
const builder = createQueryBuilder(config);
|
||||||
|
|
||||||
const builder = new SearchQueryBuilderService(buildConfig(config), alfrescoApiService);
|
|
||||||
builder.queryFragments['cat1'] = 'cm:name:test';
|
builder.queryFragments['cat1'] = 'cm:name:test';
|
||||||
|
|
||||||
const compiled = builder.buildQuery();
|
const compiled = builder.buildQuery();
|
||||||
@ -531,9 +495,7 @@ describe('SearchQueryBuilder', () => {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const alfrescoApiService = TestBed.inject(AlfrescoApiService);
|
const builder = createQueryBuilder(config);
|
||||||
|
|
||||||
const builder = new SearchQueryBuilderService(buildConfig(config), alfrescoApiService);
|
|
||||||
builder.queryFragments['cat1'] = 'cm:name:test';
|
builder.queryFragments['cat1'] = 'cm:name:test';
|
||||||
|
|
||||||
const compiled = builder.buildQuery();
|
const compiled = builder.buildQuery();
|
||||||
@ -550,8 +512,7 @@ describe('SearchQueryBuilder', () => {
|
|||||||
fields: [],
|
fields: [],
|
||||||
categories: [{ id: 'cat1', enabled: true } as any, { id: 'cat2', enabled: true } as any]
|
categories: [{ id: 'cat1', enabled: true } as any, { id: 'cat2', enabled: true } as any]
|
||||||
};
|
};
|
||||||
const alfrescoApiService = TestBed.inject(AlfrescoApiService);
|
const builder = createQueryBuilder(config);
|
||||||
const builder = new SearchQueryBuilderService(buildConfig(config), alfrescoApiService);
|
|
||||||
const sorting: any = { type: 'FIELD', field: 'cm:name', ascending: true };
|
const sorting: any = { type: 'FIELD', field: 'cm:name', ascending: true };
|
||||||
builder.sorting = [sorting];
|
builder.sorting = [sorting];
|
||||||
|
|
||||||
@ -565,8 +526,7 @@ describe('SearchQueryBuilder', () => {
|
|||||||
const config: SearchConfiguration = {
|
const config: SearchConfiguration = {
|
||||||
categories: [{ id: 'cat1', enabled: true } as any]
|
categories: [{ id: 'cat1', enabled: true } as any]
|
||||||
};
|
};
|
||||||
const alfrescoApiService = TestBed.inject(AlfrescoApiService);
|
const builder = createQueryBuilder(config);
|
||||||
const builder = new SearchQueryBuilderService(buildConfig(config), alfrescoApiService);
|
|
||||||
builder.queryFragments['cat1'] = 'cm:name:test';
|
builder.queryFragments['cat1'] = 'cm:name:test';
|
||||||
builder.paging = { maxItems: 5, skipCount: 5 };
|
builder.paging = { maxItems: 5, skipCount: 5 };
|
||||||
|
|
||||||
@ -581,8 +541,7 @@ describe('SearchQueryBuilder', () => {
|
|||||||
const config: SearchConfiguration = {
|
const config: SearchConfiguration = {
|
||||||
categories: [{ id: 'cat1', enabled: true } as any]
|
categories: [{ id: 'cat1', enabled: true } as any]
|
||||||
};
|
};
|
||||||
const alfrescoApiService = TestBed.inject(AlfrescoApiService);
|
const builder = createQueryBuilder(config);
|
||||||
const builder = new SearchQueryBuilderService(buildConfig(config), alfrescoApiService);
|
|
||||||
builder.userQuery = 'my query';
|
builder.userQuery = 'my query';
|
||||||
|
|
||||||
builder.queryFragments['cat1'] = 'cm:name:test';
|
builder.queryFragments['cat1'] = 'cm:name:test';
|
||||||
@ -615,9 +574,7 @@ describe('SearchQueryBuilder', () => {
|
|||||||
const config: SearchConfiguration = {
|
const config: SearchConfiguration = {
|
||||||
categories: [{ id: 'cat1', enabled: true } as any]
|
categories: [{ id: 'cat1', enabled: true } as any]
|
||||||
};
|
};
|
||||||
const alfrescoApiService = TestBed.inject(AlfrescoApiService);
|
const builder = createQueryBuilder(config);
|
||||||
|
|
||||||
const builder = new SearchQueryBuilderService(buildConfig(config), alfrescoApiService);
|
|
||||||
|
|
||||||
builder.addUserFacetBucket(field1.field, field1buckets[0]);
|
builder.addUserFacetBucket(field1.field, field1buckets[0]);
|
||||||
builder.addUserFacetBucket(field1.field, field1buckets[1]);
|
builder.addUserFacetBucket(field1.field, field1buckets[1]);
|
||||||
@ -630,7 +587,7 @@ describe('SearchQueryBuilder', () => {
|
|||||||
expect(compiledQuery.query.query).toBe(expectedResult);
|
expect(compiledQuery.query.query).toBe(expectedResult);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should use highlight in the queries', () => {
|
it('should use highlight in the queries', () => {
|
||||||
const config: SearchConfiguration = {
|
const config: SearchConfiguration = {
|
||||||
highlight: {
|
highlight: {
|
||||||
prefix: 'my-prefix',
|
prefix: 'my-prefix',
|
||||||
@ -638,9 +595,7 @@ describe('SearchQueryBuilder', () => {
|
|||||||
mergeContiguous: true
|
mergeContiguous: true
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const alfrescoApiService = TestBed.inject(AlfrescoApiService);
|
const builder = createQueryBuilder(config);
|
||||||
|
|
||||||
const builder = new SearchQueryBuilderService(buildConfig(config), alfrescoApiService);
|
|
||||||
builder.userQuery = 'my query';
|
builder.userQuery = 'my query';
|
||||||
|
|
||||||
builder.queryFragments['cat1'] = 'cm:name:test';
|
builder.queryFragments['cat1'] = 'cm:name:test';
|
||||||
@ -655,9 +610,7 @@ describe('SearchQueryBuilder', () => {
|
|||||||
const config: SearchConfiguration = {
|
const config: SearchConfiguration = {
|
||||||
categories: [{ id: 'cat1', enabled: true } as any]
|
categories: [{ id: 'cat1', enabled: true } as any]
|
||||||
};
|
};
|
||||||
const alfrescoApiService = TestBed.inject(AlfrescoApiService);
|
const builder = createQueryBuilder(config);
|
||||||
|
|
||||||
const builder = new SearchQueryBuilderService(buildConfig(config), alfrescoApiService);
|
|
||||||
spyOn(builder, 'buildQuery').and.throwError('some error');
|
spyOn(builder, 'buildQuery').and.throwError('some error');
|
||||||
|
|
||||||
builder.error.subscribe((error) => {
|
builder.error.subscribe((error) => {
|
||||||
@ -671,9 +624,7 @@ describe('SearchQueryBuilder', () => {
|
|||||||
const config: SearchConfiguration = {
|
const config: SearchConfiguration = {
|
||||||
categories: [{ id: 'cat1', enabled: true } as any]
|
categories: [{ id: 'cat1', enabled: true } as any]
|
||||||
};
|
};
|
||||||
const alfrescoApiService = TestBed.inject(AlfrescoApiService);
|
const builder = createQueryBuilder(config);
|
||||||
|
|
||||||
const builder = new SearchQueryBuilderService(buildConfig(config), alfrescoApiService);
|
|
||||||
spyOn(builder, 'buildQuery').and.throwError('some error');
|
spyOn(builder, 'buildQuery').and.throwError('some error');
|
||||||
|
|
||||||
builder.executed.subscribe((data) => {
|
builder.executed.subscribe((data) => {
|
||||||
@ -686,9 +637,7 @@ describe('SearchQueryBuilder', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should include contain the path and allowableOperations by default', () => {
|
it('should include contain the path and allowableOperations by default', () => {
|
||||||
const alfrescoApiService = TestBed.inject(AlfrescoApiService);
|
const builder = createQueryBuilder();
|
||||||
|
|
||||||
const builder = new SearchQueryBuilderService(buildConfig(), alfrescoApiService);
|
|
||||||
builder.userQuery = 'nuka cola quantum';
|
builder.userQuery = 'nuka cola quantum';
|
||||||
const searchRequest = builder.buildQuery();
|
const searchRequest = builder.buildQuery();
|
||||||
|
|
||||||
@ -700,9 +649,7 @@ describe('SearchQueryBuilder', () => {
|
|||||||
const config: SearchConfiguration = {
|
const config: SearchConfiguration = {
|
||||||
include: includeConfig
|
include: includeConfig
|
||||||
};
|
};
|
||||||
const alfrescoApiService = TestBed.inject(AlfrescoApiService);
|
const builder = createQueryBuilder(config);
|
||||||
|
|
||||||
const builder = new SearchQueryBuilderService(buildConfig(config), alfrescoApiService);
|
|
||||||
builder.userQuery = 'nuka cola quantum';
|
builder.userQuery = 'nuka cola quantum';
|
||||||
const searchRequest = builder.buildQuery();
|
const searchRequest = builder.buildQuery();
|
||||||
|
|
||||||
@ -710,9 +657,7 @@ describe('SearchQueryBuilder', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should the query contain the pagination', () => {
|
it('should the query contain the pagination', () => {
|
||||||
const alfrescoApiService = TestBed.inject(AlfrescoApiService);
|
const builder = createQueryBuilder();
|
||||||
|
|
||||||
const builder = new SearchQueryBuilderService(buildConfig(), alfrescoApiService);
|
|
||||||
builder.userQuery = 'nuka cola quantum';
|
builder.userQuery = 'nuka cola quantum';
|
||||||
const mockPagination = {
|
const mockPagination = {
|
||||||
maxItems: 10,
|
maxItems: 10,
|
||||||
@ -725,9 +670,7 @@ describe('SearchQueryBuilder', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should the query contain the scope in case it is defined', () => {
|
it('should the query contain the scope in case it is defined', () => {
|
||||||
const alfrescoApiService = TestBed.inject(AlfrescoApiService);
|
const builder = createQueryBuilder();
|
||||||
|
|
||||||
const builder = new SearchQueryBuilderService(buildConfig(), alfrescoApiService);
|
|
||||||
const mockScope = { locations: 'mock-location' };
|
const mockScope = { locations: 'mock-location' };
|
||||||
builder.userQuery = 'nuka cola quantum';
|
builder.userQuery = 'nuka cola quantum';
|
||||||
builder.setScope(mockScope);
|
builder.setScope(mockScope);
|
||||||
@ -737,15 +680,50 @@ describe('SearchQueryBuilder', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should return empty if array of search config not found', (done) => {
|
it('should return empty if array of search config not found', (done) => {
|
||||||
const alfrescoApiService = TestBed.inject(AlfrescoApiService);
|
const builder = createQueryBuilder(null);
|
||||||
|
|
||||||
const builder = new SearchQueryBuilderService(buildConfig(null), alfrescoApiService);
|
|
||||||
builder.searchForms.subscribe((forms) => {
|
builder.searchForms.subscribe((forms) => {
|
||||||
expect(forms).toEqual([]);
|
expect(forms).toEqual([]);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should add user query to filter raw params when query is built', () => {
|
||||||
|
const builder = createQueryBuilder();
|
||||||
|
builder.userQuery = 'nuka cola quantum';
|
||||||
|
builder.buildQuery();
|
||||||
|
|
||||||
|
expect(builder.filterRawParams).toEqual({ userQuery: '(nuka cola quantum)' });
|
||||||
|
});
|
||||||
|
|
||||||
|
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)' });
|
||||||
|
expect(router.navigate).toHaveBeenCalledWith([], {
|
||||||
|
relativeTo: activatedRoute,
|
||||||
|
queryParams: { q: 'eyJ1c2VyUXVlcnkiOiIobnVrYSBjb2xhIHF1YW50dW0pIn0=' },
|
||||||
|
queryParamsHandling: 'merge'
|
||||||
|
});
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
builder.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');
|
||||||
|
|
||||||
|
expect(builder.filterRawParams).toEqual({ userQuery: '(test query)' });
|
||||||
|
expect(router.navigate).toHaveBeenCalledWith([], {
|
||||||
|
relativeTo: activatedRoute,
|
||||||
|
queryParams: { q: 'eyJ1c2VyUXVlcnkiOiIodGVzdCBxdWVyeSkifQ==' },
|
||||||
|
queryParamsHandling: 'merge'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('Multiple search configuration', () => {
|
describe('Multiple search configuration', () => {
|
||||||
let configs: SearchConfiguration[];
|
let configs: SearchConfiguration[];
|
||||||
let builder: SearchQueryBuilderService;
|
let builder: SearchQueryBuilderService;
|
||||||
@ -768,9 +746,7 @@ describe('SearchQueryBuilder', () => {
|
|||||||
default: false
|
default: false
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
const alfrescoApiService = TestBed.inject(AlfrescoApiService);
|
builder = createQueryBuilder(configs);
|
||||||
|
|
||||||
builder = new SearchQueryBuilderService(buildConfig(configs), alfrescoApiService);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should pick the default configuration from list', () => {
|
it('should pick the default configuration from list', () => {
|
||||||
|
@ -27,7 +27,7 @@ export const ALFRESCO_API_FACTORY = new InjectionToken('ALFRESCO_API_FACTORY');
|
|||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
})
|
})
|
||||||
export class AlfrescoApiService {
|
export class AlfrescoApiService {
|
||||||
alfrescoApiInitialized: ReplaySubject<boolean> = new ReplaySubject(1);
|
alfrescoApiInitialized = new ReplaySubject<boolean>(1);
|
||||||
|
|
||||||
protected alfrescoApi: AlfrescoApi;
|
protected alfrescoApi: AlfrescoApi;
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user