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,0 +1,4 @@
|
|||||||
|
export enum FilterActionType {
|
||||||
|
Apply,
|
||||||
|
Clear,
|
||||||
|
}
|
||||||
37
src/application/Context/State/Filter/Event/FilterChange.ts
Normal file
37
src/application/Context/State/Filter/Event/FilterChange.ts
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import { IFilterResult } from '@/application/Context/State/Filter/IFilterResult';
|
||||||
|
import { FilterActionType } from './FilterActionType';
|
||||||
|
import { IFilterChangeDetails, IFilterChangeDetailsVisitor } from './IFilterChangeDetails';
|
||||||
|
|
||||||
|
export class FilterChange implements IFilterChangeDetails {
|
||||||
|
public static forApply(filter: IFilterResult) {
|
||||||
|
if (!filter) {
|
||||||
|
throw new Error('missing filter');
|
||||||
|
}
|
||||||
|
return new FilterChange(FilterActionType.Apply, filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static forClear() {
|
||||||
|
return new FilterChange(FilterActionType.Clear);
|
||||||
|
}
|
||||||
|
|
||||||
|
private constructor(
|
||||||
|
public readonly actionType: FilterActionType,
|
||||||
|
public readonly filter?: IFilterResult,
|
||||||
|
) { }
|
||||||
|
|
||||||
|
public visit(visitor: IFilterChangeDetailsVisitor): void {
|
||||||
|
if (!visitor) {
|
||||||
|
throw new Error('missing visitor');
|
||||||
|
}
|
||||||
|
switch (this.actionType) {
|
||||||
|
case FilterActionType.Apply:
|
||||||
|
visitor.onApply(this.filter);
|
||||||
|
break;
|
||||||
|
case FilterActionType.Clear:
|
||||||
|
visitor.onClear();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error(`Unknown action type: ${this.actionType}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import { IFilterResult } from '@/application/Context/State/Filter/IFilterResult';
|
||||||
|
import { FilterActionType } from './FilterActionType';
|
||||||
|
|
||||||
|
export interface IFilterChangeDetails {
|
||||||
|
readonly actionType: FilterActionType;
|
||||||
|
readonly filter?: IFilterResult;
|
||||||
|
|
||||||
|
visit(visitor: IFilterChangeDetailsVisitor): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IFilterChangeDetailsVisitor {
|
||||||
|
onClear(): void;
|
||||||
|
onApply(filter: IFilterResult): void;
|
||||||
|
}
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
import { IEventSource } from '@/infrastructure/Events/IEventSource';
|
import { IEventSource } from '@/infrastructure/Events/IEventSource';
|
||||||
import { IFilterResult } from './IFilterResult';
|
import { IFilterResult } from './IFilterResult';
|
||||||
|
import { IFilterChangeDetails } from './Event/IFilterChangeDetails';
|
||||||
|
|
||||||
export interface IReadOnlyUserFilter {
|
export interface IReadOnlyUserFilter {
|
||||||
readonly currentFilter: IFilterResult | undefined;
|
readonly currentFilter: IFilterResult | undefined;
|
||||||
readonly filtered: IEventSource<IFilterResult>;
|
readonly filterChanged: IEventSource<IFilterChangeDetails>;
|
||||||
readonly filterRemoved: IEventSource<void>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IUserFilter extends IReadOnlyUserFilter {
|
export interface IUserFilter extends IReadOnlyUserFilter {
|
||||||
setFilter(filter: string): void;
|
applyFilter(filter: string): void;
|
||||||
removeFilter(): void;
|
clearFilter(): void;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,11 +4,11 @@ import { ICategoryCollection } from '@/domain/ICategoryCollection';
|
|||||||
import { FilterResult } from './FilterResult';
|
import { FilterResult } from './FilterResult';
|
||||||
import { IFilterResult } from './IFilterResult';
|
import { IFilterResult } from './IFilterResult';
|
||||||
import { IUserFilter } from './IUserFilter';
|
import { IUserFilter } from './IUserFilter';
|
||||||
|
import { IFilterChangeDetails } from './Event/IFilterChangeDetails';
|
||||||
|
import { FilterChange } from './Event/FilterChange';
|
||||||
|
|
||||||
export class UserFilter implements IUserFilter {
|
export class UserFilter implements IUserFilter {
|
||||||
public readonly filtered = new EventSource<IFilterResult>();
|
public readonly filterChanged = new EventSource<IFilterChangeDetails>();
|
||||||
|
|
||||||
public readonly filterRemoved = new EventSource<void>();
|
|
||||||
|
|
||||||
public currentFilter: IFilterResult | undefined;
|
public currentFilter: IFilterResult | undefined;
|
||||||
|
|
||||||
@@ -16,9 +16,9 @@ export class UserFilter implements IUserFilter {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public setFilter(filter: string): void {
|
public applyFilter(filter: string): void {
|
||||||
if (!filter) {
|
if (!filter) {
|
||||||
throw new Error('Filter must be defined and not empty. Use removeFilter() to remove the filter');
|
throw new Error('Filter must be defined and not empty. Use clearFilter() to remove the filter');
|
||||||
}
|
}
|
||||||
const filterLowercase = filter.toLocaleLowerCase();
|
const filterLowercase = filter.toLocaleLowerCase();
|
||||||
const filteredScripts = this.collection.getAllScripts().filter(
|
const filteredScripts = this.collection.getAllScripts().filter(
|
||||||
@@ -33,12 +33,12 @@ export class UserFilter implements IUserFilter {
|
|||||||
filter,
|
filter,
|
||||||
);
|
);
|
||||||
this.currentFilter = matches;
|
this.currentFilter = matches;
|
||||||
this.filtered.notify(matches);
|
this.filterChanged.notify(FilterChange.forApply(this.currentFilter));
|
||||||
}
|
}
|
||||||
|
|
||||||
public removeFilter(): void {
|
public clearFilter(): void {
|
||||||
this.currentFilter = undefined;
|
this.currentFilter = undefined;
|
||||||
this.filterRemoved.notify();
|
this.filterChanged.notify(FilterChange.forClear());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import {
|
|||||||
defineComponent, ref, onUnmounted, inject,
|
defineComponent, ref, onUnmounted, inject,
|
||||||
} from 'vue';
|
} from 'vue';
|
||||||
import { useCollectionStateKey } from '@/presentation/injectionSymbols';
|
import { useCollectionStateKey } from '@/presentation/injectionSymbols';
|
||||||
import { IReadOnlyCategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState';
|
import { IReadOnlyUserFilter } from '@/application/Context/State/Filter/IUserFilter';
|
||||||
import TheOsChanger from './TheOsChanger.vue';
|
import TheOsChanger from './TheOsChanger.vue';
|
||||||
import TheSelector from './Selector/TheSelector.vue';
|
import TheSelector from './Selector/TheSelector.vue';
|
||||||
import TheViewChanger from './View/TheViewChanger.vue';
|
import TheViewChanger from './View/TheViewChanger.vue';
|
||||||
@@ -31,20 +31,22 @@ export default defineComponent({
|
|||||||
const isSearching = ref(false);
|
const isSearching = ref(false);
|
||||||
|
|
||||||
onStateChange((state) => {
|
onStateChange((state) => {
|
||||||
subscribe(state);
|
subscribeToFilterChanges(state.filter);
|
||||||
}, { immediate: true });
|
}, { immediate: true });
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
unsubscribeAll();
|
unsubscribeAll();
|
||||||
});
|
});
|
||||||
|
|
||||||
function subscribe(state: IReadOnlyCategoryCollectionState) {
|
function subscribeToFilterChanges(filter: IReadOnlyUserFilter) {
|
||||||
events.register(state.filter.filterRemoved.on(() => {
|
events.register(
|
||||||
isSearching.value = false;
|
filter.filterChanged.on((event) => {
|
||||||
}));
|
event.visit({
|
||||||
events.register(state.filter.filtered.on(() => {
|
onApply: () => { isSearching.value = true; },
|
||||||
isSearching.value = true;
|
onClear: () => { isSearching.value = false; },
|
||||||
}));
|
});
|
||||||
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function unsubscribeAll() {
|
function unsubscribeAll() {
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import {
|
|||||||
import { useCollectionStateKey } from '@/presentation/injectionSymbols';
|
import { useCollectionStateKey } from '@/presentation/injectionSymbols';
|
||||||
import { IScript } from '@/domain/IScript';
|
import { IScript } from '@/domain/IScript';
|
||||||
import { ICategory } from '@/domain/ICategory';
|
import { ICategory } from '@/domain/ICategory';
|
||||||
import { ICategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState';
|
import { ICategoryCollectionState, IReadOnlyCategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState';
|
||||||
import { IFilterResult } from '@/application/Context/State/Filter/IFilterResult';
|
import { IFilterResult } from '@/application/Context/State/Filter/IFilterResult';
|
||||||
import { SelectedScript } from '@/application/Context/State/Selection/SelectedScript';
|
import { SelectedScript } from '@/application/Context/State/Selection/SelectedScript';
|
||||||
import {
|
import {
|
||||||
@@ -64,9 +64,7 @@ export default defineComponent({
|
|||||||
nodes.value = parseAllCategories(state.collection);
|
nodes.value = parseAllCategories(state.collection);
|
||||||
}
|
}
|
||||||
events.unsubscribeAll();
|
events.unsubscribeAll();
|
||||||
modifyCurrentState((mutableState) => {
|
subscribeToState(state);
|
||||||
registerStateMutators(mutableState);
|
|
||||||
});
|
|
||||||
}, { immediate: true });
|
}, { immediate: true });
|
||||||
|
|
||||||
function toggleNodeSelection(event: INodeSelectedEvent) {
|
function toggleNodeSelection(event: INodeSelectedEvent) {
|
||||||
@@ -99,20 +97,26 @@ export default defineComponent({
|
|||||||
.map((selected) => getScriptNodeId(selected.script));
|
.map((selected) => getScriptNodeId(selected.script));
|
||||||
}
|
}
|
||||||
|
|
||||||
function registerStateMutators(state: ICategoryCollectionState) {
|
function subscribeToState(state: IReadOnlyCategoryCollectionState) {
|
||||||
events.register(
|
events.register(
|
||||||
state.selection.changed.on((scripts) => handleSelectionChanged(scripts)),
|
state.selection.changed.on((scripts) => handleSelectionChanged(scripts)),
|
||||||
state.filter.filterRemoved.on(() => handleFilterRemoved()),
|
state.filter.filterChanged.on((event) => {
|
||||||
state.filter.filtered.on((filterResult) => handleFiltered(filterResult)),
|
event.visit({
|
||||||
|
onApply: (filter) => {
|
||||||
|
filterText.value = filter.query;
|
||||||
|
filtered = filter;
|
||||||
|
},
|
||||||
|
onClear: () => {
|
||||||
|
filterText.value = '';
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function setCurrentFilter(currentFilter: IFilterResult | undefined) {
|
function setCurrentFilter(currentFilter: IFilterResult | undefined) {
|
||||||
if (!currentFilter) {
|
filtered = currentFilter;
|
||||||
handleFilterRemoved();
|
filterText.value = currentFilter?.query || '';
|
||||||
} else {
|
|
||||||
handleFiltered(currentFilter);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleSelectionChanged(selectedScripts: ReadonlyArray<SelectedScript>): void {
|
function handleSelectionChanged(selectedScripts: ReadonlyArray<SelectedScript>): void {
|
||||||
@@ -120,15 +124,6 @@ export default defineComponent({
|
|||||||
.map((node) => node.id);
|
.map((node) => node.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleFilterRemoved() {
|
|
||||||
filterText.value = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleFiltered(result: IFilterResult) {
|
|
||||||
filterText.value = result.query;
|
|
||||||
filtered = result;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
nodes,
|
nodes,
|
||||||
selectedNodeIds,
|
selectedNodeIds,
|
||||||
|
|||||||
@@ -40,10 +40,8 @@ import { useApplicationKey, useCollectionStateKey } from '@/presentation/injecti
|
|||||||
import ScriptsTree from '@/presentation/components/Scripts/View/ScriptsTree/ScriptsTree.vue';
|
import ScriptsTree from '@/presentation/components/Scripts/View/ScriptsTree/ScriptsTree.vue';
|
||||||
import CardList from '@/presentation/components/Scripts/View/Cards/CardList.vue';
|
import CardList from '@/presentation/components/Scripts/View/Cards/CardList.vue';
|
||||||
import { ViewType } from '@/presentation/components/Scripts/Menu/View/ViewType';
|
import { ViewType } from '@/presentation/components/Scripts/Menu/View/ViewType';
|
||||||
import { IFilterResult } from '@/application/Context/State/Filter/IFilterResult';
|
import { IReadOnlyUserFilter } from '@/application/Context/State/Filter/IUserFilter';
|
||||||
import { IReadOnlyCategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState';
|
|
||||||
|
|
||||||
/** Shows content of single category or many categories */
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: {
|
components: {
|
||||||
ScriptsTree,
|
ScriptsTree,
|
||||||
@@ -74,25 +72,29 @@ export default defineComponent({
|
|||||||
|
|
||||||
onStateChange((newState) => {
|
onStateChange((newState) => {
|
||||||
events.unsubscribeAll();
|
events.unsubscribeAll();
|
||||||
subscribeState(newState);
|
subscribeToFilterChanges(newState.filter);
|
||||||
});
|
});
|
||||||
|
|
||||||
function clearSearchQuery() {
|
function clearSearchQuery() {
|
||||||
modifyCurrentState((state) => {
|
modifyCurrentState((state) => {
|
||||||
const { filter } = state;
|
const { filter } = state;
|
||||||
filter.removeFilter();
|
filter.clearFilter();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function subscribeState(state: IReadOnlyCategoryCollectionState) {
|
function subscribeToFilterChanges(filter: IReadOnlyUserFilter) {
|
||||||
events.register(
|
events.register(
|
||||||
state.filter.filterRemoved.on(() => {
|
filter.filterChanged.on((event) => {
|
||||||
isSearching.value = false;
|
event.visit({
|
||||||
}),
|
onApply: (newFilter) => {
|
||||||
state.filter.filtered.on((result: IFilterResult) => {
|
searchQuery.value = newFilter.query;
|
||||||
searchQuery.value = result.query;
|
isSearching.value = true;
|
||||||
isSearching.value = true;
|
searchHasMatches.value = newFilter.hasAnyMatches();
|
||||||
searchHasMatches.value = result.hasAnyMatches();
|
},
|
||||||
|
onClear: () => {
|
||||||
|
isSearching.value = false;
|
||||||
|
},
|
||||||
|
});
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ import { useCollectionStateKey } from '@/presentation/injectionSymbols';
|
|||||||
import { NonCollapsing } from '@/presentation/components/Scripts/View/Cards/NonCollapsingDirective';
|
import { NonCollapsing } from '@/presentation/components/Scripts/View/Cards/NonCollapsingDirective';
|
||||||
import { IReadOnlyUserFilter } from '@/application/Context/State/Filter/IUserFilter';
|
import { IReadOnlyUserFilter } from '@/application/Context/State/Filter/IUserFilter';
|
||||||
import { IFilterResult } from '@/application/Context/State/Filter/IFilterResult';
|
import { IFilterResult } from '@/application/Context/State/Filter/IFilterResult';
|
||||||
import { IReadOnlyCategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState';
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
directives: {
|
directives: {
|
||||||
@@ -44,38 +43,38 @@ export default defineComponent({
|
|||||||
modifyCurrentState((state) => {
|
modifyCurrentState((state) => {
|
||||||
const { filter } = state;
|
const { filter } = state;
|
||||||
if (!newFilter) {
|
if (!newFilter) {
|
||||||
filter.removeFilter();
|
filter.clearFilter();
|
||||||
} else {
|
} else {
|
||||||
filter.setFilter(newFilter);
|
filter.applyFilter(newFilter);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onStateChange((newState) => {
|
onStateChange((newState) => {
|
||||||
events.unsubscribeAll();
|
events.unsubscribeAll();
|
||||||
subscribeSearchQuery(newState);
|
updateFromInitialFilter(newState.filter.currentFilter);
|
||||||
|
subscribeToFilterChanges(newState.filter);
|
||||||
}, { immediate: true });
|
}, { immediate: true });
|
||||||
|
|
||||||
function subscribeSearchQuery(newState: IReadOnlyCategoryCollectionState) {
|
function updateFromInitialFilter(filter?: IFilterResult) {
|
||||||
searchQuery.value = newState.filter.currentFilter ? newState.filter.currentFilter.query : '';
|
searchQuery.value = filter?.query || '';
|
||||||
subscribeFilter(newState.filter);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function subscribeFilter(filter: IReadOnlyUserFilter) {
|
function subscribeToFilterChanges(filter: IReadOnlyUserFilter) {
|
||||||
events.register(
|
events.register(
|
||||||
filter.filtered.on((result) => handleFiltered(result)),
|
filter.filterChanged.on((event) => {
|
||||||
filter.filterRemoved.on(() => handleFilterRemoved()),
|
event.visit({
|
||||||
|
onApply: (result) => {
|
||||||
|
searchQuery.value = result.query;
|
||||||
|
},
|
||||||
|
onClear: () => {
|
||||||
|
searchQuery.value = '';
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleFilterRemoved() {
|
|
||||||
searchQuery.value = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleFiltered(result: IFilterResult) {
|
|
||||||
searchQuery.value = result.query;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
searchPlaceholder,
|
searchPlaceholder,
|
||||||
searchQuery,
|
searchQuery,
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ describe('ApplicationContext', () => {
|
|||||||
const sut = testContext
|
const sut = testContext
|
||||||
.withInitialOs(OperatingSystem.Windows)
|
.withInitialOs(OperatingSystem.Windows)
|
||||||
.construct();
|
.construct();
|
||||||
sut.state.filter.setFilter('filtered');
|
sut.state.filter.applyFilter('filtered');
|
||||||
sut.changeContext(OperatingSystem.macOS);
|
sut.changeContext(OperatingSystem.macOS);
|
||||||
// assert
|
// assert
|
||||||
expectEmptyState(sut.state);
|
expectEmptyState(sut.state);
|
||||||
@@ -65,10 +65,10 @@ describe('ApplicationContext', () => {
|
|||||||
.withInitialOs(os)
|
.withInitialOs(os)
|
||||||
.construct();
|
.construct();
|
||||||
const firstState = sut.state;
|
const firstState = sut.state;
|
||||||
firstState.filter.setFilter(expectedFilter);
|
firstState.filter.applyFilter(expectedFilter);
|
||||||
sut.changeContext(os);
|
sut.changeContext(os);
|
||||||
sut.changeContext(changedOs);
|
sut.changeContext(changedOs);
|
||||||
sut.state.filter.setFilter('second-state');
|
sut.state.filter.applyFilter('second-state');
|
||||||
sut.changeContext(os);
|
sut.changeContext(os);
|
||||||
// assert
|
// assert
|
||||||
const actualFilter = sut.state.filter.currentFilter.query;
|
const actualFilter = sut.state.filter.currentFilter.query;
|
||||||
@@ -103,7 +103,7 @@ describe('ApplicationContext', () => {
|
|||||||
.withInitialOs(os)
|
.withInitialOs(os)
|
||||||
.construct();
|
.construct();
|
||||||
const initialState = sut.state;
|
const initialState = sut.state;
|
||||||
initialState.filter.setFilter('dirty-state');
|
initialState.filter.applyFilter('dirty-state');
|
||||||
sut.changeContext(os);
|
sut.changeContext(os);
|
||||||
// assert
|
// assert
|
||||||
expect(testContext.firedEvents.length).to.equal(0);
|
expect(testContext.firedEvents.length).to.equal(0);
|
||||||
|
|||||||
@@ -91,11 +91,11 @@ describe('CategoryCollectionState', () => {
|
|||||||
.withAction(new CategoryStub(0).withScript(expectedScript));
|
.withAction(new CategoryStub(0).withScript(expectedScript));
|
||||||
const sut = new CategoryCollectionState(collection);
|
const sut = new CategoryCollectionState(collection);
|
||||||
// act
|
// act
|
||||||
let actualScript: IScript;
|
let actualScript: IScript | undefined;
|
||||||
sut.filter.filtered.on((result) => {
|
sut.filter.filterChanged.on((result) => {
|
||||||
[actualScript] = result.scriptMatches;
|
[actualScript] = result.filter?.scriptMatches ?? [undefined];
|
||||||
});
|
});
|
||||||
sut.filter.setFilter(scriptNameFilter);
|
sut.filter.applyFilter(scriptNameFilter);
|
||||||
// assert
|
// assert
|
||||||
expect(expectedScript).to.equal(actualScript);
|
expect(expectedScript).to.equal(actualScript);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,122 @@
|
|||||||
|
import 'mocha';
|
||||||
|
import { expect } from 'chai';
|
||||||
|
import { FilterChange } from '@/application/Context/State/Filter/Event/FilterChange';
|
||||||
|
import { itEachAbsentObjectValue } from '@tests/unit/shared/TestCases/AbsentTests';
|
||||||
|
import { FilterResultStub } from '@tests/unit/shared/Stubs/FilterResultStub';
|
||||||
|
import { FilterActionType } from '@/application/Context/State/Filter/Event/FilterActionType';
|
||||||
|
import { FilterChangeDetailsVisitorStub } from '@tests/unit/shared/Stubs/FilterChangeDetailsVisitorStub';
|
||||||
|
|
||||||
|
describe('FilterChange', () => {
|
||||||
|
describe('forApply', () => {
|
||||||
|
describe('throws when filter is absent', () => {
|
||||||
|
itEachAbsentObjectValue((absentValue) => {
|
||||||
|
// arrange
|
||||||
|
const expectedError = 'missing filter';
|
||||||
|
const filterValue = absentValue;
|
||||||
|
// act
|
||||||
|
const act = () => FilterChange.forApply(filterValue);
|
||||||
|
// assert
|
||||||
|
expect(act).to.throw(expectedError);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('sets filter result', () => {
|
||||||
|
// arrange
|
||||||
|
const expectedFilter = new FilterResultStub();
|
||||||
|
// act
|
||||||
|
const sut = FilterChange.forApply(expectedFilter);
|
||||||
|
// assert
|
||||||
|
const actualFilter = sut.filter;
|
||||||
|
expect(actualFilter).to.equal(expectedFilter);
|
||||||
|
});
|
||||||
|
it('sets action as expected', () => {
|
||||||
|
// arrange
|
||||||
|
const expectedAction = FilterActionType.Apply;
|
||||||
|
// act
|
||||||
|
const sut = FilterChange.forApply(new FilterResultStub());
|
||||||
|
// assert
|
||||||
|
const actualAction = sut.actionType;
|
||||||
|
expect(actualAction).to.equal(expectedAction);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('forClear', () => {
|
||||||
|
it('does not set filter result', () => {
|
||||||
|
// arrange
|
||||||
|
const expectedFilter = undefined;
|
||||||
|
// act
|
||||||
|
const sut = FilterChange.forClear();
|
||||||
|
// assert
|
||||||
|
const actualFilter = sut.filter;
|
||||||
|
expect(actualFilter).to.equal(expectedFilter);
|
||||||
|
});
|
||||||
|
it('sets action as expected', () => {
|
||||||
|
// arrange
|
||||||
|
const expectedAction = FilterActionType.Clear;
|
||||||
|
// act
|
||||||
|
const sut = FilterChange.forClear();
|
||||||
|
// assert
|
||||||
|
const actualAction = sut.actionType;
|
||||||
|
expect(actualAction).to.equal(expectedAction);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('visit', () => {
|
||||||
|
describe('throws when visitor is absent', () => {
|
||||||
|
itEachAbsentObjectValue((absentValue) => {
|
||||||
|
// arrange
|
||||||
|
const expectedError = 'missing visitor';
|
||||||
|
const visitorValue = absentValue;
|
||||||
|
const sut = FilterChange.forClear();
|
||||||
|
// act
|
||||||
|
const act = () => sut.visit(visitorValue);
|
||||||
|
// assert
|
||||||
|
expect(act).to.throw(expectedError);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('onClear', () => {
|
||||||
|
itVisitsOnce(
|
||||||
|
() => FilterChange.forClear(),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
describe('onApply', () => {
|
||||||
|
itVisitsOnce(
|
||||||
|
() => FilterChange.forApply(new FilterResultStub()),
|
||||||
|
);
|
||||||
|
|
||||||
|
it('visits with expected filter', () => {
|
||||||
|
// arrange
|
||||||
|
const expectedFilter = new FilterResultStub();
|
||||||
|
const sut = FilterChange.forApply(expectedFilter);
|
||||||
|
const visitor = new FilterChangeDetailsVisitorStub();
|
||||||
|
// act
|
||||||
|
sut.visit(visitor);
|
||||||
|
// assert
|
||||||
|
expect(visitor.visitedResults).to.have.lengthOf(1);
|
||||||
|
expect(visitor.visitedResults).to.include(expectedFilter);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function itVisitsOnce(sutFactory: () => FilterChange) {
|
||||||
|
it('visits', () => {
|
||||||
|
// arrange
|
||||||
|
const sut = sutFactory();
|
||||||
|
const expectedType = sut.actionType;
|
||||||
|
const visitor = new FilterChangeDetailsVisitorStub();
|
||||||
|
// act
|
||||||
|
sut.visit(visitor);
|
||||||
|
// assert
|
||||||
|
expect(visitor.visitedEvents).to.include(expectedType);
|
||||||
|
});
|
||||||
|
it('visits once', () => {
|
||||||
|
// arrange
|
||||||
|
const sut = sutFactory();
|
||||||
|
const expectedType = sut.actionType;
|
||||||
|
const visitor = new FilterChangeDetailsVisitorStub();
|
||||||
|
// act
|
||||||
|
sut.visit(visitor);
|
||||||
|
// assert
|
||||||
|
expect(
|
||||||
|
visitor.visitedEvents.filter((action) => action === expectedType),
|
||||||
|
).to.have.lengthOf(1);
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -5,173 +5,182 @@ import { UserFilter } from '@/application/Context/State/Filter/UserFilter';
|
|||||||
import { CategoryStub } from '@tests/unit/shared/Stubs/CategoryStub';
|
import { CategoryStub } from '@tests/unit/shared/Stubs/CategoryStub';
|
||||||
import { ScriptStub } from '@tests/unit/shared/Stubs/ScriptStub';
|
import { ScriptStub } from '@tests/unit/shared/Stubs/ScriptStub';
|
||||||
import { CategoryCollectionStub } from '@tests/unit/shared/Stubs/CategoryCollectionStub';
|
import { CategoryCollectionStub } from '@tests/unit/shared/Stubs/CategoryCollectionStub';
|
||||||
|
import { FilterChange } from '@/application/Context/State/Filter/Event/FilterChange';
|
||||||
|
import { IFilterChangeDetails } from '@/application/Context/State/Filter/Event/IFilterChangeDetails';
|
||||||
|
import { ICategoryCollection } from '@/domain/ICategoryCollection';
|
||||||
|
|
||||||
describe('UserFilter', () => {
|
describe('UserFilter', () => {
|
||||||
describe('removeFilter', () => {
|
describe('clearFilter', () => {
|
||||||
it('signals when removing filter', () => {
|
it('signals when removing filter', () => {
|
||||||
// arrange
|
// arrange
|
||||||
let isCalled = false;
|
const expectedChange = FilterChange.forClear();
|
||||||
|
let actualChange: IFilterChangeDetails;
|
||||||
const sut = new UserFilter(new CategoryCollectionStub());
|
const sut = new UserFilter(new CategoryCollectionStub());
|
||||||
sut.filterRemoved.on(() => {
|
sut.filterChanged.on((change) => {
|
||||||
isCalled = true;
|
actualChange = change;
|
||||||
});
|
});
|
||||||
// act
|
// act
|
||||||
sut.removeFilter();
|
sut.clearFilter();
|
||||||
// assert
|
// assert
|
||||||
expect(isCalled).to.be.equal(true);
|
expect(actualChange).to.deep.equal(expectedChange);
|
||||||
});
|
});
|
||||||
it('sets currentFilter to undefined', () => {
|
it('sets currentFilter to undefined', () => {
|
||||||
// arrange
|
// arrange
|
||||||
const sut = new UserFilter(new CategoryCollectionStub());
|
const sut = new UserFilter(new CategoryCollectionStub());
|
||||||
// act
|
// act
|
||||||
sut.setFilter('non-important');
|
sut.applyFilter('non-important');
|
||||||
sut.removeFilter();
|
sut.clearFilter();
|
||||||
// assert
|
// assert
|
||||||
expect(sut.currentFilter).to.be.equal(undefined);
|
expect(sut.currentFilter).to.be.equal(undefined);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('setFilter', () => {
|
describe('applyFilter', () => {
|
||||||
it('signals when no matches', () => {
|
interface IApplyFilterTestCase {
|
||||||
// arrange
|
readonly name: string;
|
||||||
let actual: IFilterResult;
|
readonly filter: string;
|
||||||
const nonMatchingFilter = 'non matching filter';
|
readonly collection: ICategoryCollection;
|
||||||
const sut = new UserFilter(new CategoryCollectionStub());
|
readonly assert: (result: IFilterResult) => void;
|
||||||
sut.filtered.on((filterResult) => {
|
}
|
||||||
actual = filterResult;
|
const testCases: readonly IApplyFilterTestCase[] = [
|
||||||
});
|
(() => {
|
||||||
// act
|
const nonMatchingFilter = 'non matching filter';
|
||||||
sut.setFilter(nonMatchingFilter);
|
return {
|
||||||
// assert
|
name: 'given no matches',
|
||||||
expect(actual.hasAnyMatches()).be.equal(false);
|
filter: nonMatchingFilter,
|
||||||
expect(actual.query).to.equal(nonMatchingFilter);
|
collection: new CategoryCollectionStub(),
|
||||||
});
|
assert: (filter) => {
|
||||||
it('sets currentFilter as expected when no matches', () => {
|
expect(filter.hasAnyMatches()).be.equal(false);
|
||||||
// arrange
|
expect(filter.query).to.equal(nonMatchingFilter);
|
||||||
const nonMatchingFilter = 'non matching filter';
|
},
|
||||||
const sut = new UserFilter(new CategoryCollectionStub());
|
};
|
||||||
// act
|
})(),
|
||||||
sut.setFilter(nonMatchingFilter);
|
(() => {
|
||||||
// assert
|
const code = 'HELLO world';
|
||||||
const actual = sut.currentFilter;
|
const matchingFilter = 'Hello WoRLD';
|
||||||
expect(actual.hasAnyMatches()).be.equal(false);
|
const script = new ScriptStub('id').withCode(code);
|
||||||
expect(actual.query).to.equal(nonMatchingFilter);
|
return {
|
||||||
});
|
name: 'given script match with case-insensitive code',
|
||||||
describe('signals when matches', () => {
|
filter: matchingFilter,
|
||||||
describe('signals when script matches', () => {
|
collection: new CategoryCollectionStub()
|
||||||
it('code matches', () => {
|
.withAction(new CategoryStub(33).withScript(script)),
|
||||||
// arrange
|
assert: (filter) => {
|
||||||
const code = 'HELLO world';
|
expect(filter.hasAnyMatches()).be.equal(true);
|
||||||
const filter = 'Hello WoRLD';
|
expect(filter.categoryMatches).to.have.lengthOf(0);
|
||||||
let actual: IFilterResult;
|
expect(filter.scriptMatches).to.have.lengthOf(1);
|
||||||
const script = new ScriptStub('id').withCode(code);
|
expect(filter.scriptMatches[0]).to.deep.equal(script);
|
||||||
const category = new CategoryStub(33).withScript(script);
|
expect(filter.query).to.equal(matchingFilter);
|
||||||
const sut = new UserFilter(new CategoryCollectionStub()
|
},
|
||||||
.withAction(category));
|
};
|
||||||
sut.filtered.on((filterResult) => {
|
})(),
|
||||||
actual = filterResult;
|
(() => {
|
||||||
});
|
const revertCode = 'HELLO world';
|
||||||
// act
|
const matchingFilter = 'Hello WoRLD';
|
||||||
sut.setFilter(filter);
|
const script = new ScriptStub('id').withRevertCode(revertCode);
|
||||||
// assert
|
return {
|
||||||
expect(actual.hasAnyMatches()).be.equal(true);
|
name: 'given script match with case-insensitive revertCode',
|
||||||
expect(actual.categoryMatches).to.have.lengthOf(0);
|
filter: matchingFilter,
|
||||||
expect(actual.scriptMatches).to.have.lengthOf(1);
|
collection: new CategoryCollectionStub()
|
||||||
expect(actual.scriptMatches[0]).to.deep.equal(script);
|
.withAction(new CategoryStub(33).withScript(script)),
|
||||||
expect(actual.query).to.equal(filter);
|
assert: (filter) => {
|
||||||
expect(sut.currentFilter).to.deep.equal(actual);
|
expect(filter.hasAnyMatches()).be.equal(true);
|
||||||
});
|
expect(filter.categoryMatches).to.have.lengthOf(0);
|
||||||
it('revertCode matches', () => {
|
expect(filter.scriptMatches).to.have.lengthOf(1);
|
||||||
// arrange
|
expect(filter.scriptMatches[0]).to.deep.equal(script);
|
||||||
const revertCode = 'HELLO world';
|
expect(filter.query).to.equal(matchingFilter);
|
||||||
const filter = 'Hello WoRLD';
|
},
|
||||||
let actual: IFilterResult;
|
};
|
||||||
const script = new ScriptStub('id').withRevertCode(revertCode);
|
})(),
|
||||||
const category = new CategoryStub(33).withScript(script);
|
(() => {
|
||||||
const sut = new UserFilter(new CategoryCollectionStub()
|
const name = 'HELLO world';
|
||||||
.withAction(category));
|
const matchingFilter = 'Hello WoRLD';
|
||||||
sut.filtered.on((filterResult) => {
|
const script = new ScriptStub('id').withName(name);
|
||||||
actual = filterResult;
|
return {
|
||||||
});
|
name: 'given script match with case-insensitive name',
|
||||||
// act
|
filter: matchingFilter,
|
||||||
sut.setFilter(filter);
|
collection: new CategoryCollectionStub()
|
||||||
// assert
|
.withAction(new CategoryStub(33).withScript(script)),
|
||||||
expect(actual.hasAnyMatches()).be.equal(true);
|
assert: (filter) => {
|
||||||
expect(actual.categoryMatches).to.have.lengthOf(0);
|
expect(filter.hasAnyMatches()).be.equal(true);
|
||||||
expect(actual.scriptMatches).to.have.lengthOf(1);
|
expect(filter.categoryMatches).to.have.lengthOf(0);
|
||||||
expect(actual.scriptMatches[0]).to.deep.equal(script);
|
expect(filter.scriptMatches).to.have.lengthOf(1);
|
||||||
expect(actual.query).to.equal(filter);
|
expect(filter.scriptMatches[0]).to.deep.equal(script);
|
||||||
expect(sut.currentFilter).to.deep.equal(actual);
|
expect(filter.query).to.equal(matchingFilter);
|
||||||
});
|
},
|
||||||
it('name matches', () => {
|
};
|
||||||
// arrange
|
})(),
|
||||||
const name = 'HELLO world';
|
(() => {
|
||||||
const filter = 'Hello WoRLD';
|
|
||||||
let actual: IFilterResult;
|
|
||||||
const script = new ScriptStub('id').withName(name);
|
|
||||||
const category = new CategoryStub(33).withScript(script);
|
|
||||||
const sut = new UserFilter(new CategoryCollectionStub()
|
|
||||||
.withAction(category));
|
|
||||||
sut.filtered.on((filterResult) => {
|
|
||||||
actual = filterResult;
|
|
||||||
});
|
|
||||||
// act
|
|
||||||
sut.setFilter(filter);
|
|
||||||
// assert
|
|
||||||
expect(actual.hasAnyMatches()).be.equal(true);
|
|
||||||
expect(actual.categoryMatches).to.have.lengthOf(0);
|
|
||||||
expect(actual.scriptMatches).to.have.lengthOf(1);
|
|
||||||
expect(actual.scriptMatches[0]).to.deep.equal(script);
|
|
||||||
expect(actual.query).to.equal(filter);
|
|
||||||
expect(sut.currentFilter).to.deep.equal(actual);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
it('signals when category matches', () => {
|
|
||||||
// arrange
|
|
||||||
const categoryName = 'HELLO world';
|
const categoryName = 'HELLO world';
|
||||||
const filter = 'Hello WoRLD';
|
const matchingFilter = 'Hello WoRLD';
|
||||||
let actual: IFilterResult;
|
|
||||||
const category = new CategoryStub(55).withName(categoryName);
|
const category = new CategoryStub(55).withName(categoryName);
|
||||||
const sut = new UserFilter(new CategoryCollectionStub()
|
return {
|
||||||
.withAction(category));
|
name: 'given category match with case-insensitive name',
|
||||||
sut.filtered.on((filterResult) => {
|
filter: matchingFilter,
|
||||||
actual = filterResult;
|
collection: new CategoryCollectionStub()
|
||||||
});
|
.withAction(category),
|
||||||
// act
|
assert: (filter) => {
|
||||||
sut.setFilter(filter);
|
expect(filter.hasAnyMatches()).be.equal(true);
|
||||||
// assert
|
expect(filter.categoryMatches).to.have.lengthOf(1);
|
||||||
expect(actual.hasAnyMatches()).be.equal(true);
|
expect(filter.categoryMatches[0]).to.deep.equal(category);
|
||||||
expect(actual.categoryMatches).to.have.lengthOf(1);
|
expect(filter.scriptMatches).to.have.lengthOf(0);
|
||||||
expect(actual.categoryMatches[0]).to.deep.equal(category);
|
expect(filter.query).to.equal(matchingFilter);
|
||||||
expect(actual.scriptMatches).to.have.lengthOf(0);
|
},
|
||||||
expect(actual.query).to.equal(filter);
|
};
|
||||||
expect(sut.currentFilter).to.deep.equal(actual);
|
})(),
|
||||||
});
|
(() => {
|
||||||
it('signals when category and script matches', () => {
|
|
||||||
// arrange
|
|
||||||
const matchingText = 'HELLO world';
|
const matchingText = 'HELLO world';
|
||||||
const filter = 'Hello WoRLD';
|
const matchingFilter = 'Hello WoRLD';
|
||||||
let actual: IFilterResult;
|
|
||||||
const script = new ScriptStub('script')
|
const script = new ScriptStub('script')
|
||||||
.withName(matchingText);
|
.withName(matchingText);
|
||||||
const category = new CategoryStub(55)
|
const category = new CategoryStub(55)
|
||||||
.withName(matchingText)
|
.withName(matchingText)
|
||||||
.withScript(script);
|
.withScript(script);
|
||||||
const collection = new CategoryCollectionStub()
|
return {
|
||||||
.withAction(category);
|
name: 'given category and script matches with case-insensitive names',
|
||||||
const sut = new UserFilter(collection);
|
filter: matchingFilter,
|
||||||
sut.filtered.on((filterResult) => {
|
collection: new CategoryCollectionStub()
|
||||||
actual = filterResult;
|
.withAction(category),
|
||||||
|
assert: (filter) => {
|
||||||
|
expect(filter.hasAnyMatches()).be.equal(true);
|
||||||
|
expect(filter.categoryMatches).to.have.lengthOf(1);
|
||||||
|
expect(filter.categoryMatches[0]).to.deep.equal(category);
|
||||||
|
expect(filter.scriptMatches).to.have.lengthOf(1);
|
||||||
|
expect(filter.scriptMatches[0]).to.deep.equal(script);
|
||||||
|
expect(filter.query).to.equal(matchingFilter);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
})(),
|
||||||
|
];
|
||||||
|
describe('sets currentFilter as expected', () => {
|
||||||
|
testCases.forEach(({
|
||||||
|
name, filter, collection, assert,
|
||||||
|
}) => {
|
||||||
|
it(name, () => {
|
||||||
|
// arrange
|
||||||
|
const sut = new UserFilter(collection);
|
||||||
|
// act
|
||||||
|
sut.applyFilter(filter);
|
||||||
|
// assert
|
||||||
|
const actual = sut.currentFilter;
|
||||||
|
assert(actual);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('signals as expected', () => {
|
||||||
|
testCases.forEach(({
|
||||||
|
name, filter, collection, assert,
|
||||||
|
}) => {
|
||||||
|
it(name, () => {
|
||||||
|
// arrange
|
||||||
|
const sut = new UserFilter(collection);
|
||||||
|
let actualFilterResult: IFilterResult;
|
||||||
|
sut.filterChanged.on((filterResult) => {
|
||||||
|
actualFilterResult = filterResult.filter;
|
||||||
|
});
|
||||||
|
// act
|
||||||
|
sut.applyFilter(filter);
|
||||||
|
// assert
|
||||||
|
assert(actualFilterResult);
|
||||||
});
|
});
|
||||||
// act
|
|
||||||
sut.setFilter(filter);
|
|
||||||
// assert
|
|
||||||
expect(actual.hasAnyMatches()).be.equal(true);
|
|
||||||
expect(actual.categoryMatches).to.have.lengthOf(1);
|
|
||||||
expect(actual.categoryMatches[0]).to.deep.equal(category);
|
|
||||||
expect(actual.scriptMatches).to.have.lengthOf(1);
|
|
||||||
expect(actual.scriptMatches[0]).to.deep.equal(script);
|
|
||||||
expect(actual.query).to.equal(filter);
|
|
||||||
expect(sut.currentFilter).to.deep.equal(actual);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
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 { IFilterResult } from '@/application/Context/State/Filter/IFilterResult';
|
||||||
import { IUserFilter } from '@/application/Context/State/Filter/IUserFilter';
|
import { IUserFilter } from '@/application/Context/State/Filter/IUserFilter';
|
||||||
import { IEventSource } from '@/infrastructure/Events/IEventSource';
|
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 {
|
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 {
|
public notifyFilterChange(change: IFilterChangeDetails) {
|
||||||
throw new Error('Method not implemented.');
|
this.filterChangedSource.notify(change);
|
||||||
|
this.currentFilter = change.filter;
|
||||||
}
|
}
|
||||||
|
|
||||||
public removeFilter(): void {
|
public withNoCurrentFilter() {
|
||||||
throw new Error('Method not implemented.');
|
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