Merged PR 219: #823 Reset Filters in Shelf Filter Overlay

Related work items: #666, #667, #668, #683, #685, #772, #773, #816, #823
This commit is contained in:
Sebastian Neumair
2020-07-13 08:34:44 +00:00
committed by Lorenz Hilpert
25 changed files with 643 additions and 22 deletions

View File

@@ -5,4 +5,5 @@ export interface SelectFilterOption extends FilterOptionBase {
expanded?: boolean;
options?: SelectFilterOption[];
id?: string;
initial_selected_state?: boolean;
}

View File

@@ -0,0 +1 @@
export type FilterResetStrategy = 'all' | 'default';

View File

@@ -0,0 +1,4 @@
// start:ng42.barrel
export * from './filter-reset-strategy.model';
// end:ng42.barrel

View File

@@ -1,10 +1,10 @@
<div class="container" *ngIf="value">
<div class="clear-all">
<div class="clear-all" *ngIf="showResetButton">
<button
class="isa-btn isa-btn-primary-link remove-all isa-pt-0 isa-pb-0 isa-pl-15 isa-pr-15"
(click)="clearAllFilters()"
(click)="resetFilters()"
>
Alle Filter Entfernen
{{ resetStrategy | filterResetWording }}
</button>
</div>

View File

@@ -0,0 +1,150 @@
import { TestBed, ComponentFixture } from '@angular/core/testing';
import { SelectedFilterOptionsComponent } from './selected-filter-options.component';
import { ChangeDetectorRef, Renderer2 } from '@angular/core';
import { IconModule } from '@libs/ui';
import {
FilterResetWordingPipe,
SelectedFiltersPipe,
CheckAllChildOptionsSelectedPipe,
} from '../pipe';
import { By } from '@angular/platform-browser';
import { PrimaryFilterOption } from '../../../shelf/defs';
import {
mockFilters,
primaryFiltersMock,
} from '../../../shelf/shared/mockdata';
import { SelectFilter } from '../../models';
fdescribe('SelectedFilterOptionsComponent', () => {
let fixture: ComponentFixture<SelectedFilterOptionsComponent>;
let component: SelectedFilterOptionsComponent;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [IconModule, IconModule],
providers: [ChangeDetectorRef, Renderer2, SelectedFilterOptionsComponent],
declarations: [
SelectedFilterOptionsComponent,
SelectedFiltersPipe,
FilterResetWordingPipe,
CheckAllChildOptionsSelectedPipe,
],
}).compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(SelectedFilterOptionsComponent);
component = fixture.componentInstance;
component.value = [];
fixture.detectChanges();
});
it('should be created', () => {
expect(component).toBeTruthy();
});
describe('#ResetFilters', () => {
let button: HTMLButtonElement;
beforeEach(() => {
spyOn(component, 'resetFilters').and.callThrough();
component.resetStrategy = 'all';
component.showResetButton = true;
button = fixture.debugElement.query(
By.css('.isa-btn.isa-btn-primary-link.remove-all')
).nativeElement;
fixture.detectChanges();
});
it('should be shown by default', () => {
expect(button).toBeTruthy();
});
it('should not be shwon if showResetButton is false', async () => {
component.showResetButton = false;
fixture.detectChanges();
await fixture.whenStable();
const debugButtonEl = fixture.debugElement.query(
By.css('.isa-btn.isa-btn-primary-link.remove-all')
);
button = debugButtonEl && debugButtonEl.nativeElement;
expect(button).toBeFalsy();
});
it('should deselect all filters when resetStrategy is all', () => {
spyOn(component, 'deselectAllFilters').and.callThrough();
button.click();
fixture.detectChanges();
expect(component.resetFilters).toHaveBeenCalled();
expect(component.deselectAllFilters).toHaveBeenCalled();
});
it('should revert to the default selection of filters when resetStrategy is default', () => {
spyOn(component, 'resetToDefault').and.callThrough();
component.resetStrategy = 'default';
fixture.detectChanges();
button.click();
fixture.detectChanges();
expect(component.resetFilters).toHaveBeenCalled();
expect(component.resetToDefault).toHaveBeenCalled();
});
});
describe('resetToDefault', () => {
let primaryFilters: PrimaryFilterOption[];
let selectFilters: SelectFilter[];
beforeEach(() => {
primaryFilters = primaryFiltersMock;
selectFilters = mockFilters;
});
it('should reset the filters to its initial state', () => {
selectFilters[0].options[0].selected = true;
selectFilters[0].options[0].initial_selected_state = false;
spyOn(component, 'resetToDefault').and.callThrough();
const result = component.resetToDefault(selectFilters);
expect(result[0].options[0].selected).toBe(false);
expect(result[0].options[0].initial_selected_state).toBe(false);
expect(result.length).toEqual(selectFilters.length);
});
});
describe('deselectAllFilters', () => {
let primaryFilters: PrimaryFilterOption[];
let selectFilters: SelectFilter[];
beforeEach(() => {
primaryFilters = primaryFiltersMock;
selectFilters = mockFilters;
});
it('should deselect all filters', () => {
spyOn(component, 'deselectAllFilters').and.callThrough();
const result = component.deselectAllFilters(selectFilters);
expect(
result.every((filter) => {
if (filter.options) {
return filter.options.every((o) => !o.selected);
}
return true;
})
);
expect(result.length).toEqual(selectFilters.length);
});
});
});

View File

@@ -13,6 +13,7 @@ import {
} from '@angular/core';
import { Filter, SelectFilterOption } from '../../models';
import { cloneFilter } from '../../utils';
import { FilterResetStrategy } from './defs';
@Component({
selector: 'app-selected-filter-options',
@@ -21,11 +22,13 @@ import { cloneFilter } from '../../utils';
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class SelectedFilterOptionsComponent implements AfterViewInit {
@Input()
value: Filter[];
@Input() value: Filter[];
@Input() module: 'Customer' | 'Branch' = 'Branch';
@Input() resetStrategy: FilterResetStrategy = 'all';
@Input() showResetButton = true;
@Output() filterChanged = new EventEmitter<Filter[]>();
@Output() reset = new EventEmitter<void>();
@ViewChildren('filteredItem') items: QueryList<ElementRef>;
@@ -61,8 +64,18 @@ export class SelectedFilterOptionsComponent implements AfterViewInit {
this.cdr.detectChanges();
}
clearAllFilters() {
this.filterChanged.emit(this.deselectAllFilters(this.value));
resetFilters() {
switch (this.resetStrategy) {
case 'default':
this.filterChanged.emit(this.resetToDefault(this.value));
break;
case 'all':
this.filterChanged.emit(this.deselectAllFilters(this.value));
break;
}
this.reset.emit();
}
clearFilter(filter: SelectFilterOption) {
@@ -106,7 +119,7 @@ export class SelectedFilterOptionsComponent implements AfterViewInit {
this.filterChanged.emit(clonedFilter);
}
private deselectAllFilters(filters: Filter[]): Filter[] {
deselectAllFilters(filters: Filter[]): Filter[] {
return filters.reduce((acc, curr) => {
if (Array.isArray(curr.options)) {
curr.options.forEach((option) => this.deselect(option));
@@ -115,6 +128,21 @@ export class SelectedFilterOptionsComponent implements AfterViewInit {
}, []);
}
resetToDefault(filters: Filter[]): Filter[] {
const updatedFilters = filters.map((filter) => {
let updatedOptions = filter.options || [];
if (filter.options) {
updatedOptions = filter.options.map((f) => ({
...this.resetToDefaultValue(f),
}));
}
return { ...filter, options: updatedOptions };
});
return updatedFilters;
}
private deselectAllMatches(filter: SelectFilterOption, match?: string) {
if (filter.name === match) {
filter.selected = false;
@@ -142,6 +170,18 @@ export class SelectedFilterOptionsComponent implements AfterViewInit {
return filter;
}
private resetToDefaultValue(filter: SelectFilterOption): SelectFilterOption {
return {
...filter,
selected: filter.initial_selected_state,
options: [
...(filter.options
? filter.options.map((option) => this.resetToDefaultValue(option))
: []),
],
};
}
private hasNestedOptions(filter: SelectFilterOption): boolean {
return filter && Array.isArray(filter.options) && !!filter.options.length;
}

View File

@@ -0,0 +1,19 @@
import { FilterResetWordingPipe } from './filter-reset-wording.pipe';
describe('#FilterResetWordingPipe', () => {
let pipe: FilterResetWordingPipe;
beforeEach(() => {
pipe = new FilterResetWordingPipe();
});
it('should show Alle Filter Entfernen for the default strategy', () => {
const result = pipe.transform('all');
expect(result).toEqual('Alle Filter Entfernen');
});
it('should show Filter zurücksetzen for the default strategy', () => {
const result = pipe.transform('default');
expect(result).toEqual('Filter zurücksetzen');
});
});

View File

@@ -0,0 +1,18 @@
import { Pipe, PipeTransform } from '@angular/core';
import { FilterResetStrategy } from '../components/defs';
@Pipe({
name: 'filterResetWording',
})
export class FilterResetWordingPipe implements PipeTransform {
RESET_TO_DEFAULT_WORDING = 'Filter zurücksetzen';
RESET_All_WORDING = 'Alle Filter Entfernen';
transform(strategy: FilterResetStrategy): string {
if (strategy === 'default') {
return this.RESET_TO_DEFAULT_WORDING;
}
return this.RESET_All_WORDING;
}
}

View File

@@ -1,5 +1,5 @@
// start:ng42.barrel
export * from './check-all-selected.pipe';
export * from './selected-filters.pipe';
export * from './filter-reset-wording.pipe';
// end:ng42.barrel

View File

@@ -2,15 +2,25 @@ import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { IconModule } from '@libs/ui';
import { SelectedFilterOptionsComponent } from './components';
import { SelectedFiltersPipe, CheckAllChildOptionsSelectedPipe} from './pipe';
import {
SelectedFiltersPipe,
CheckAllChildOptionsSelectedPipe,
FilterResetWordingPipe,
} from './pipe';
@NgModule({
imports: [CommonModule, IconModule],
exports: [SelectedFilterOptionsComponent, CheckAllChildOptionsSelectedPipe],
declarations:
[SelectedFilterOptionsComponent,
exports: [
SelectedFilterOptionsComponent,
CheckAllChildOptionsSelectedPipe,
FilterResetWordingPipe,
],
declarations: [
SelectedFilterOptionsComponent,
SelectedFiltersPipe,
CheckAllChildOptionsSelectedPipe],
FilterResetWordingPipe,
CheckAllChildOptionsSelectedPipe,
],
providers: [],
})
export class SelectedFilterOptionsModule {}

View File

@@ -3,4 +3,5 @@ export interface PrimaryFilterOption {
id: string;
key: string;
selected: boolean;
initial_selected_state?: boolean;
}

View File

@@ -19,6 +19,9 @@
<ng-container *ngIf="pendingFilters$ | async as filters">
<app-selected-filter-options
*ngIf="hasSelectedFilter$ | async"
[resetStrategy]="resetStrategy"
[showResetButton]="filtersChangedFromDefault$ | async"
(reset)="onResetFilters()"
[value]="filters"
[module]="'Customer'"
(filterChanged)="onFiltersChange($event)"

View File

@@ -13,10 +13,11 @@ import { slideIn } from 'apps/sales/src/app/core/overlay';
import { Observable, BehaviorSubject, interval } from 'rxjs';
import { Filter, SelectFilter } from '../../../filter';
import { ShelfFilterService } from '../../services/shelf-filter.service';
import { startWith, first } from 'rxjs/operators';
import { startWith, first, tap } from 'rxjs/operators';
import { isNullOrUndefined } from 'util';
import { ShelfSearchInputComponent } from '../../pages/shelf-search/search';
import { cloneFilter } from '../../../filter/utils';
import { FilterResetStrategy } from '../../../filter/selected-filter-options/components/defs';
@Component({
selector: 'app-shelf-filter',
@@ -27,6 +28,7 @@ import { cloneFilter } from '../../../filter/utils';
})
export class ShelfFilterComponent implements OnInit, OnDestroy {
animate = 'in';
resetStrategy: FilterResetStrategy = 'default';
@ViewChild('selectionContainer', { static: false })
selectionContainer: ElementRef;
@@ -42,6 +44,7 @@ export class ShelfFilterComponent implements OnInit, OnDestroy {
selectedFilter$: Observable<Filter>;
pendingFilters$ = new BehaviorSubject<Filter[]>(null);
hasSelectedFilter$: Observable<boolean>;
filtersChangedFromDefault$: Observable<boolean>;
constructor(
private overlayRef: IsaOverlayRef,
@@ -55,6 +58,7 @@ export class ShelfFilterComponent implements OnInit, OnDestroy {
this.getSelectedFilter();
this.getPendingFilters();
this.getHasSelectedFilters();
this.getFiltersChangedFromDefault();
}
ngOnDestroy() {}
@@ -75,6 +79,10 @@ export class ShelfFilterComponent implements OnInit, OnDestroy {
this.hasSelectedFilter$ = this.filterService.hasSelectedFilters$;
}
getFiltersChangedFromDefault() {
this.filtersChangedFromDefault$ = this.filterService.filtersChangedFromDefault$;
}
onFiltersChange(updatedFilters: SelectFilter[]) {
this.pendingFilters$.next(updatedFilters.map(cloneFilter));
this.setSearchFocus();
@@ -144,6 +152,13 @@ export class ShelfFilterComponent implements OnInit, OnDestroy {
}
}
onResetFilters() {
if (this.resetStrategy === 'default') {
this.filterService.resetPrimaryFilters();
this.filterService.resetSelectedFilters();
}
}
private initiateSearchAndCloseOverlay() {
if (this.searchInput) {
const searchbarValue =

View File

@@ -1 +1,43 @@
describe('#ShelfFilterService', () => {});
import { TestBed } from '@angular/core/testing';
import { SearchStateFacade } from '@shelf-store';
import { ShelfFilterService } from './shelf-filter.service';
import { mockFilters } from '../shared/mockdata';
fdescribe('#ShelfFilterService', () => {
let service: ShelfFilterService;
let facade: jasmine.SpyObj<SearchStateFacade>;
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
{
provide: SearchStateFacade,
useValue: jasmine.createSpyObj('searchStateFacade', [
'selectFilters$',
'primaryFilterChangedFromDefault$',
]),
},
],
});
spyOn(
ShelfFilterService.prototype,
'initPendingFilters'
).and.callFake(() => {});
facade = TestBed.get(SearchStateFacade);
service = TestBed.get(ShelfFilterService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
describe('#FiltersChangedFromDefault', () => {
it('should return true if the primary Filters changed', async () => {
const mockSelectFilters = mockFilters;
});
it('should return true if the pending Filters changed', () => {});
it('should return false if neither pending nor primary filters changed', () => {});
});
});

View File

@@ -1,7 +1,14 @@
import { Injectable, OnDestroy } from '@angular/core';
import { Observable, BehaviorSubject, Subject, combineLatest } from 'rxjs';
import { SelectFilter, Filter, SelectFilterOption } from '../../filter';
import { tap, startWith, map, filter } from 'rxjs/operators';
import {
tap,
startWith,
map,
filter,
withLatestFrom,
first,
} from 'rxjs/operators';
import { SearchStateFacade } from '../../../store/customer';
import { flatten } from '../../../shared/utils';
import { isNullOrUndefined } from 'util';
@@ -32,6 +39,24 @@ export class ShelfFilterService implements OnDestroy {
return this.pendingFilters$.pipe(map(this.computeHasSelectedFilters));
}
get filtersChangedFromDefault$(): Observable<boolean> {
return this.pendingFilters$.pipe(
filter((pendingFilters) => !isNullOrUndefined(pendingFilters)),
withLatestFrom(this.searchStateFacade.primaryFilterChangedFromDefault$),
map(([pendingFilters, primaryFiltersChanged]) => {
return pendingFilters.some((f) =>
f.options
? f.options.some((o) =>
isNullOrUndefined(o.initial_selected_state)
? o.selected !== false
: o.selected !== o.initial_selected_state
)
: primaryFiltersChanged
);
})
);
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
@@ -54,6 +79,14 @@ export class ShelfFilterService implements OnDestroy {
this.searchStateFacade.setSelectedFilters(updatedFilters);
}
resetPrimaryFilters(): void {
this.searchStateFacade.resetPrimaryFilters();
}
resetSelectedFilters() {
this.searchStateFacade.resetSelectedFilters();
}
public overlayClosed() {
this.resetFilters$.next();
this.overlayClosed$.next();

View File

@@ -11,41 +11,49 @@ export const mockFilters: SelectFilter[] = [
id: 'abgeholt',
name: 'abgeholt',
selected: false,
initial_selected_state: false,
},
{
id: 'storniert Kunde',
name: 'storniert Kunde',
selected: false,
initial_selected_state: false,
},
{
id: 'storniert Lieferant',
name: 'storniert Lieferant',
selected: false,
initial_selected_state: false,
},
{
id: 'storniert',
name: 'storniert',
selected: false,
initial_selected_state: false,
},
{
id: 'ans Lager (nicht abgeholt)',
name: 'ans Lager (nicht abgeholt)',
selected: false,
initial_selected_state: false,
},
{
id: 'zugestellt',
name: 'abgeholt',
selected: false,
initial_selected_state: false,
},
{
id: 'nicht lieferbar',
name: 'nicht lieferbar',
selected: false,
initial_selected_state: false,
},
{
id: 'derzeit nicht lieferbar',
name: 'derzeit nicht lieferbar',
selected: false,
initial_selected_state: false,
},
],
},
@@ -59,31 +67,37 @@ export const mockFilters: SelectFilter[] = [
id: 'Online',
name: 'Online',
selected: false,
initial_selected_state: false,
},
{
id: 'Hugendubel.de',
name: 'Hugendubel.de',
selected: false,
initial_selected_state: false,
},
{
id: 'Filiale',
name: 'Filiale',
selected: false,
initial_selected_state: false,
},
{
id: 'HSC',
name: 'HSC',
selected: false,
initial_selected_state: false,
},
{
id: 'Amazon',
name: 'Amazon',
selected: false,
initial_selected_state: false,
},
{
id: 'Etsy',
name: 'Etsy',
selected: false,
initial_selected_state: false,
},
],
},
@@ -97,21 +111,25 @@ export const mockFilters: SelectFilter[] = [
id: 'Kundenkarte',
name: 'Kundenkarte',
selected: false,
initial_selected_state: false,
},
{
id: 'Onlinekonto',
name: 'Onlinekonto',
selected: false,
initial_selected_state: false,
},
{
id: 'B2B',
name: 'B2B',
selected: false,
initial_selected_state: false,
},
{
id: 'Gastkunde',
name: 'Gastkunde',
selected: false,
initial_selected_state: false,
},
],
},

View File

@@ -6,11 +6,13 @@ export const primaryFiltersMock: PrimaryFilterOption[] = [
id: 'filter1',
key: 'One',
selected: false,
initial_selected_state: false,
},
{
name: 'Primary Filter Two',
id: 'filter2',
key: 'Two',
selected: false,
initial_selected_state: false,
},
];

View File

@@ -9,5 +9,6 @@ export function inputDtoToPrimaryFilterOption(
key: inputDTO.key,
name: inputDTO.label,
selected: false,
initial_selected_state: false,
};
}

View File

@@ -24,6 +24,7 @@ export function optionDtoToSelectFilterOption(
name: optionDto.label,
id: optionDto.key || optionDto.value,
selected: optionDto.selected || false,
initial_selected_state: optionDto.selected || false,
expanded: false,
options:
optionDto.values && optionDto.values.map(optionDtoToSelectFilterOption),

View File

@@ -38,11 +38,21 @@ export const setSelectFilters = createAction(
props<{ id: number; filters: SelectFilter[] }>()
);
export const resetSelectFilters = createAction(
`${prefix} Reset Selected Filters`,
props<{ id: number }>()
);
export const setPrimaryFilters = createAction(
`${prefix} Set Primary Filters`,
props<{ id: number; filters: PrimaryFilterOption[] }>()
);
export const resetPrimaryFilters = createAction(
`${prefix} Reset Primary Filters`,
props<{ id: number }>()
);
export const setInput = createAction(
`${prefix} Search Input`,
props<{ id: number; input: string }>()

View File

@@ -2,7 +2,7 @@ import { TestBed } from '@angular/core/testing';
import { SearchEffects } from './search.effects';
import { provideMockStore, MockStore } from '@ngrx/store/testing';
import { provideMockActions } from '@ngrx/effects/testing';
import { Observable, Subject } from 'rxjs';
import { Observable } from 'rxjs';
import {
StrictHttpResponse,
ResponseArgsOfIEnumerableOfInputDTO,
@@ -12,7 +12,7 @@ import {
import { hot, cold } from 'jasmine-marbles';
import * as actions from './search.actions';
import * as processActions from 'apps/sales/src/app/core/store/actions/process.actions';
import { Store as NgxsStore, ofActionDispatched } from '@ngxs/store';
import { Store as NgxsStore } from '@ngxs/store';
import { Store } from '@ngrx/store';
import { Actions, NgxsModule } from '@ngxs/store';
import { BranchService } from '@sales/core-services';

View File

@@ -2,12 +2,16 @@ import { SearchStateFacade } from './search.facade';
import { TestBed } from '@angular/core/testing';
import { Store, StoreModule } from '@ngrx/store';
import { Store as NgxsStore } from '@ngxs/store';
import { primaryFiltersMock } from 'apps/sales/src/app/modules/shelf/shared/mockdata';
import {
primaryFiltersMock,
mockFilters,
} from 'apps/sales/src/app/modules/shelf/shared/mockdata';
import { PrimaryFilterOption } from 'apps/sales/src/app/modules/shelf/defs';
import { of, Observable } from 'rxjs';
import * as actions from './search.actions';
import { SelectFilter } from 'apps/sales/src/app/modules/filter';
import { first } from 'rxjs/operators';
import { hot, cold } from 'jasmine-marbles';
fdescribe('SearchFacade', () => {
let facade: SearchStateFacade;
@@ -117,4 +121,89 @@ fdescribe('SearchFacade', () => {
});
});
});
describe('#filtersChangedFromDefault$', () => {
let selectFilters: SelectFilter[];
let primaryFilters: PrimaryFilterOption[];
beforeEach(() => {
selectFilters = mockFilters;
primaryFilters = primaryFiltersMock;
});
it('should return true if the select filters are changed', async () => {
spyOnProperty(facade, 'primaryFilters$', 'get').and.returnValue(
of(primaryFilters)
);
const updatedSelectFilters = [
...selectFilters.map((filter) =>
filter.options
? {
...filter,
options: filter.options.map((o, i) =>
!i ? { ...o, selected: !o.initial_selected_state } : o
),
}
: filter
),
];
spyOnProperty(facade, 'selectFilters$', 'get').and.returnValue(
of(updatedSelectFilters)
);
const result = await facade.filtersChangedFromDefault$
.pipe(first())
.toPromise();
expect(result).toBe(true);
});
it('should return false if the select filters are unchanged', async () => {
spyOnProperty(facade, 'primaryFilters$', 'get').and.returnValue(
of(primaryFilters)
);
spyOnProperty(facade, 'selectFilters$', 'get').and.returnValue(
of(selectFilters)
);
const result = await facade.filtersChangedFromDefault$
.pipe(first())
.toPromise();
expect(result).toBe(false);
});
it('should return false if the primary filters are unchanged', () => {
const selectedFilterChangedFromDefault$ = hot('--a', { a: false });
const primaryFilterChangedFromDefault$ = hot('--a', { a: false });
const expected = cold('--b', { b: false });
spyOnProperty(
facade,
'selectedFilterChangedFromDefault$',
'get'
).and.returnValue(selectedFilterChangedFromDefault$);
spyOnProperty(
facade,
'primaryFilterChangedFromDefault$',
'get'
).and.returnValue(primaryFilterChangedFromDefault$);
expect(facade.filtersChangedFromDefault$).toBeObservable(expected);
});
it('should return true if the primary filters are changed', async () => {
const selectFilters$ = hot('--a', { a: selectFilters });
const primaryFilters$ = hot('--a', {
a: primaryFilters.map((f, i) => (!i ? { ...f, selected: true } : f)),
});
const expected = cold('--b', { b: true });
spyOnProperty(facade, 'selectFilters$', 'get').and.returnValue(
selectFilters$
);
spyOnProperty(facade, 'primaryFilters$', 'get').and.returnValue(
primaryFilters$
);
expect(facade.filtersChangedFromDefault$).toBeObservable(expected);
});
});
});

View File

@@ -3,7 +3,7 @@ import { Store } from '@ngrx/store';
import { Store as NgxsStore } from '@ngxs/store';
import * as actions from './search.actions';
import * as selectors from './search.selectors';
import { switchMap, filter, map, first, withLatestFrom } from 'rxjs/operators';
import { switchMap, filter, map, first } from 'rxjs/operators';
import { SharedSelectors } from 'apps/sales/src/app/core/store/selectors/shared.selectors';
import { combineLatest } from 'rxjs';
import {
@@ -90,6 +90,47 @@ export class SearchStateFacade {
);
}
get selectedFilterChangedFromDefault$() {
return this.selectFilters$.pipe(
map((selectFilters) => {
return selectFilters.some((f) => {
if (f.options) {
return f.options.some((o) => {
return isNullOrUndefined(o.initial_selected_state)
? o.selected !== true
: o.selected !== o.initial_selected_state;
});
}
return false;
});
})
);
}
get primaryFilterChangedFromDefault$() {
return this.primaryFilters$.pipe(
map((primaryFilters) => {
return primaryFilters.some((pf) =>
isNullOrUndefined(pf.initial_selected_state)
? pf.selected !== false
: pf.selected !== pf.initial_selected_state
);
})
);
}
get filtersChangedFromDefault$(): Observable<boolean> {
return combineLatest([
this.selectedFilterChangedFromDefault$,
this.primaryFilterChangedFromDefault$,
]).pipe(
map(([selectFiltersChanged, primaryFiltersChanged]) => {
return selectFiltersChanged || primaryFiltersChanged;
})
);
}
get selectedFilter$(): Observable<{ [key: string]: string }> {
return combineLatest([this.selectFilters$, this.primaryFilters$]).pipe(
map(([selectFilters, primaryFilters]) => {
@@ -216,6 +257,24 @@ export class SearchStateFacade {
);
}
async resetPrimaryFilters(id?: number) {
let processId = id;
if (typeof processId !== 'number') {
processId = await this.getProcessId();
}
this.store.dispatch(actions.resetPrimaryFilters({ id: processId }));
}
async resetSelectedFilters(id?: number) {
let processId = id;
if (typeof processId !== 'number') {
processId = await this.getProcessId();
}
this.store.dispatch(actions.resetSelectFilters({ id: processId }));
}
async fetchResult(id?: number) {
let processId = id;
if (typeof processId !== 'number') {

View File

@@ -1,7 +1,10 @@
import { SearchState, INITIAL_SEARCH_STATE } from './search.state';
import { TypedAction } from '@ngrx/store/src/models';
import { PrimaryFilterOption } from 'apps/sales/src/app/modules/shelf/defs';
import { primaryFiltersMock } from 'apps/sales/src/app/modules/shelf/shared/mockdata';
import {
primaryFiltersMock,
mockFilters,
} from 'apps/sales/src/app/modules/shelf/shared/mockdata';
import * as actions from './search.actions';
import { searchReducer } from './search.reducer';
@@ -43,4 +46,60 @@ fdescribe('#SearchStateReducer', () => {
expect(entity.filters.primaryFilters[1]).toEqual(primaryFiltersMock[1]);
});
});
describe('resetPrimaryFilters', () => {
let action: {
id: number;
} & TypedAction<string>;
beforeEach(() => {
action = actions.resetPrimaryFilters({ id });
});
it('should reset the primary Filters to their default state', () => {
const updatedPrimaryFilters = primaryFiltersMock.map((primaryFilter, i) =>
i % 2 === 0 ? primaryFilter : { ...primaryFilter, selected: true }
);
const setPrimaryFilterAction = actions.setPrimaryFilters({
id,
filters: updatedPrimaryFilters,
});
let state = searchReducer(initialState, setPrimaryFilterAction);
state = searchReducer(state, action);
expect(state.entities[id].filters.primaryFilters).toEqual(
primaryFiltersMock
);
});
});
describe('resetSelectedFilters', () => {
let action: {
id: number;
} & TypedAction<string>;
beforeEach(() => {
action = actions.resetSelectFilters({ id });
});
it('should reset all selected filters to their default state', () => {
const selectFilters = [
...mockFilters.map((filter) => ({
...filter,
options: filter.options.map((o) => ({ ...o, selected: true })),
})),
];
const setSelectedFilters = actions.setSelectFilters({
id,
filters: selectFilters,
});
let state = searchReducer(initialState, setSelectedFilters);
state = searchReducer(state, action);
expect(state.entities[id].filters.selectedFilters).toEqual(mockFilters);
});
});
});

View File

@@ -44,6 +44,51 @@ const _searchReducer = createReducer(
s
)
),
on(actions.resetSelectFilters, (s, a) =>
searchStateAdapter.updateOne(
{
id: a.id,
changes: {
filters: {
...s.entities[a.id].filters,
selectedFilters: s.entities[a.id].filters.selectedFilters.map(
(filter) => {
let options = filter.options || [];
if (filter.options) {
options = filter.options.map((o) => ({
...o,
selected: o.initial_selected_state,
}));
}
return { ...filter, options };
}
),
},
},
},
s
)
),
on(actions.resetPrimaryFilters, (s, a) =>
searchStateAdapter.updateOne(
{
id: a.id,
changes: {
filters: {
...s.entities[a.id].filters,
primaryFilters: s.entities[a.id].filters.primaryFilters.map(
(primaryFilter) => ({
...primaryFilter,
selected: primaryFilter.initial_selected_state,
})
),
},
},
},
s
)
),
on(actions.clearError, (s, a) =>
searchStateAdapter.updateOne(
{