Refactor filter (search query) event handling
Refactor filter event handling to a unified event with visitor pattern
to simplify the code, avoid future bugs and provide better test
coverage.
This commit shifts from using separate `filtered` and `filterRemoved`
events to a singular, more expressive `filterChanged` event. The new
approach emits a detailed payload that explicitly indicates the filter
action and the associated filter data. The event object unifies the way
the presentation layer reacts to the events.
Benefits with this approach include:
- Simplifying event listeners by reducing the number of events to
handle.
- Increasing code clarity and reduces potential for oversight by
providing explicit action details in the event payload.
- Offering extensibility for future actions without introducing new
events.
- Providing visitor pattern to handle different kind of events in easy
and robust manner without code repetition.
Other changes:
- Refactor components handling of events to follow DRY and KISS
principles better.
- Refactor `UserFilter.spec.ts` to:
- Make it easier to add new tests.
- Increase code coverage by running all event-based tests on the
current property.
This commit is contained in:
0
tests/unit/shared/Stubs/FilterChangeDetailsStub.ts
Normal file
0
tests/unit/shared/Stubs/FilterChangeDetailsStub.ts
Normal file
18
tests/unit/shared/Stubs/FilterChangeDetailsVisitorStub.ts
Normal file
18
tests/unit/shared/Stubs/FilterChangeDetailsVisitorStub.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { FilterActionType } from '@/application/Context/State/Filter/Event/FilterActionType';
|
||||
import { IFilterChangeDetailsVisitor } from '@/application/Context/State/Filter/Event/IFilterChangeDetails';
|
||||
import { IFilterResult } from '@/application/Context/State/Filter/IFilterResult';
|
||||
|
||||
export class FilterChangeDetailsVisitorStub implements IFilterChangeDetailsVisitor {
|
||||
public readonly visitedEvents = new Array<FilterActionType>();
|
||||
|
||||
public readonly visitedResults = new Array<IFilterResult>();
|
||||
|
||||
onClear(): void {
|
||||
this.visitedEvents.push(FilterActionType.Clear);
|
||||
}
|
||||
|
||||
onApply(filter: IFilterResult): void {
|
||||
this.visitedEvents.push(FilterActionType.Apply);
|
||||
this.visitedResults.push(filter);
|
||||
}
|
||||
}
|
||||
44
tests/unit/shared/Stubs/FilterResultStub.ts
Normal file
44
tests/unit/shared/Stubs/FilterResultStub.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import { IFilterResult } from '@/application/Context/State/Filter/IFilterResult';
|
||||
import { ICategory } from '@/domain/ICategory';
|
||||
import { IScript } from '@/domain/IScript';
|
||||
import { CategoryStub } from './CategoryStub';
|
||||
import { ScriptStub } from './ScriptStub';
|
||||
|
||||
export class FilterResultStub implements IFilterResult {
|
||||
public categoryMatches: readonly ICategory[] = [];
|
||||
|
||||
public scriptMatches: readonly IScript[] = [];
|
||||
|
||||
public query = '';
|
||||
|
||||
public withEmptyMatches() {
|
||||
return this
|
||||
.withCategoryMatches([])
|
||||
.withScriptMatches([]);
|
||||
}
|
||||
|
||||
public withSomeMatches() {
|
||||
return this
|
||||
.withCategoryMatches([new CategoryStub(3).withScriptIds('script-1')])
|
||||
.withScriptMatches([new ScriptStub('script-2')]);
|
||||
}
|
||||
|
||||
public withCategoryMatches(matches: readonly ICategory[]) {
|
||||
this.categoryMatches = matches;
|
||||
return this;
|
||||
}
|
||||
|
||||
public withScriptMatches(matches: readonly IScript[]) {
|
||||
this.scriptMatches = matches;
|
||||
return this;
|
||||
}
|
||||
|
||||
public withQuery(query: string) {
|
||||
this.query = query;
|
||||
return this;
|
||||
}
|
||||
|
||||
public hasAnyMatches(): boolean {
|
||||
return this.categoryMatches.length > 0 || this.scriptMatches.length > 0;
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,32 @@
|
||||
import { IFilterResult } from '@/application/Context/State/Filter/IFilterResult';
|
||||
import { IUserFilter } from '@/application/Context/State/Filter/IUserFilter';
|
||||
import { IEventSource } from '@/infrastructure/Events/IEventSource';
|
||||
import { IFilterChangeDetails } from '@/application/Context/State/Filter/Event/IFilterChangeDetails';
|
||||
import { FilterResultStub } from './FilterResultStub';
|
||||
import { EventSourceStub } from './EventSourceStub';
|
||||
|
||||
export class UserFilterStub implements IUserFilter {
|
||||
public currentFilter: IFilterResult;
|
||||
private readonly filterChangedSource = new EventSourceStub<IFilterChangeDetails>();
|
||||
|
||||
public filtered: IEventSource<IFilterResult>;
|
||||
public currentFilter: IFilterResult | undefined = new FilterResultStub();
|
||||
|
||||
public filterRemoved: IEventSource<void>;
|
||||
public filterChanged: IEventSource<IFilterChangeDetails> = this.filterChangedSource;
|
||||
|
||||
public setFilter(): void {
|
||||
throw new Error('Method not implemented.');
|
||||
public notifyFilterChange(change: IFilterChangeDetails) {
|
||||
this.filterChangedSource.notify(change);
|
||||
this.currentFilter = change.filter;
|
||||
}
|
||||
|
||||
public removeFilter(): void {
|
||||
throw new Error('Method not implemented.');
|
||||
public withNoCurrentFilter() {
|
||||
return this.withCurrentFilterResult(undefined);
|
||||
}
|
||||
|
||||
public withCurrentFilterResult(filter: IFilterResult | undefined) {
|
||||
this.currentFilter = filter;
|
||||
return this;
|
||||
}
|
||||
|
||||
public applyFilter(): void { /* NO OP */ }
|
||||
|
||||
public clearFilter(): void { /* NO OP */ }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user