fix script revert activating recommendation level
Reverting any single of the scripts from standard recommendation pool shows "Standard" selection as selected which is wrong. This commit fixes it, refactors selection handling in a separate class and it also adds missing tests. It removes UserSelection.totalSelected propertty in favor of using UserSelection.selectedScripts.length to provide unified way of accessing the information.
This commit is contained in:
@@ -6,7 +6,6 @@ import { IEventSource } from '@/infrastructure/Events/IEventSource';
|
||||
export interface IUserSelection {
|
||||
readonly changed: IEventSource<ReadonlyArray<SelectedScript>>;
|
||||
readonly selectedScripts: ReadonlyArray<SelectedScript>;
|
||||
readonly totalSelected: number;
|
||||
areAllSelected(category: ICategory): boolean;
|
||||
isAnySelected(category: ICategory): boolean;
|
||||
removeAllInCategory(categoryId: number): void;
|
||||
|
||||
@@ -101,10 +101,6 @@ export class UserSelection implements IUserSelection {
|
||||
return this.scripts.getItems();
|
||||
}
|
||||
|
||||
public get totalSelected(): number {
|
||||
return this.scripts.getItems().length;
|
||||
}
|
||||
|
||||
public selectAll(): void {
|
||||
for (const script of this.collection.getAllScripts()) {
|
||||
if (!this.scripts.exists(script.id)) {
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
import { IScript } from '@/domain/IScript';
|
||||
import { SelectedScript } from '@/application/Context/State/Selection/SelectedScript';
|
||||
import { RecommendationLevel } from '@/domain/RecommendationLevel';
|
||||
import { scrambledEqual } from '@/application/Common/Array';
|
||||
import { ICategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState';
|
||||
|
||||
export enum SelectionType {
|
||||
Standard,
|
||||
Strict,
|
||||
All,
|
||||
None,
|
||||
Custom,
|
||||
}
|
||||
|
||||
export class SelectionTypeHandler {
|
||||
constructor(private readonly state: ICategoryCollectionState) {
|
||||
if (!state) { throw new Error('undefined state'); }
|
||||
}
|
||||
public selectType(type: SelectionType) {
|
||||
if (type === SelectionType.Custom) {
|
||||
throw new Error('cannot select custom type');
|
||||
}
|
||||
const selector = selectors.get(type);
|
||||
selector.select(this.state);
|
||||
}
|
||||
public getCurrentSelectionType(): SelectionType {
|
||||
for (const [type, selector] of Array.from(selectors.entries())) {
|
||||
if (selector.isSelected(this.state)) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
return SelectionType.Custom;
|
||||
}
|
||||
}
|
||||
|
||||
interface ISingleTypeHandler {
|
||||
isSelected: (state: ICategoryCollectionState) => boolean;
|
||||
select: (state: ICategoryCollectionState) => void;
|
||||
}
|
||||
|
||||
const selectors = new Map<SelectionType, ISingleTypeHandler>([
|
||||
[SelectionType.None, {
|
||||
select: (state) =>
|
||||
state.selection.deselectAll(),
|
||||
isSelected: (state) =>
|
||||
state.selection.selectedScripts.length === 0,
|
||||
}],
|
||||
[SelectionType.Standard, getRecommendationLevelSelector(RecommendationLevel.Standard)],
|
||||
[SelectionType.Strict, getRecommendationLevelSelector(RecommendationLevel.Strict)],
|
||||
[SelectionType.All, {
|
||||
select: (state) =>
|
||||
state.selection.selectAll(),
|
||||
isSelected: (state) =>
|
||||
state.selection.selectedScripts.length === state.collection.totalScripts,
|
||||
}],
|
||||
]);
|
||||
|
||||
function getRecommendationLevelSelector(level: RecommendationLevel): ISingleTypeHandler {
|
||||
return {
|
||||
select: (state) => selectOnly(level, state),
|
||||
isSelected: (state) => hasAllSelectedLevelOf(level, state),
|
||||
};
|
||||
}
|
||||
|
||||
function hasAllSelectedLevelOf(level: RecommendationLevel, state: ICategoryCollectionState) {
|
||||
const scripts = state.collection.getScriptsByLevel(level);
|
||||
const selectedScripts = state.selection.selectedScripts;
|
||||
return areAllSelected(scripts, selectedScripts);
|
||||
}
|
||||
|
||||
function selectOnly(level: RecommendationLevel, state: ICategoryCollectionState) {
|
||||
const scripts = state.collection.getScriptsByLevel(level);
|
||||
state.selection.selectOnly(scripts);
|
||||
}
|
||||
|
||||
function areAllSelected(
|
||||
expectedScripts: ReadonlyArray<IScript>,
|
||||
selection: ReadonlyArray<SelectedScript>): boolean {
|
||||
selection = selection.filter((selected) => !selected.revert);
|
||||
if (expectedScripts.length < selection.length) {
|
||||
return false;
|
||||
}
|
||||
const selectedScriptIds = selection.map((script) => script.id);
|
||||
const expectedScriptIds = expectedScripts.map((script) => script.id);
|
||||
return scrambledEqual(selectedScriptIds, expectedScriptIds);
|
||||
}
|
||||
@@ -5,8 +5,8 @@
|
||||
<div class="part">
|
||||
<SelectableOption
|
||||
label="None"
|
||||
:enabled="this.currentSelection == SelectionState.None"
|
||||
@click="selectAsync(SelectionState.None)"
|
||||
:enabled="this.currentSelection == SelectionType.None"
|
||||
@click="selectType(SelectionType.None)"
|
||||
v-tooltip=" 'Deselect all selected scripts.<br/>' +
|
||||
'💡 Good start to dive deeper into tweaks and select only what you want.'"
|
||||
/>
|
||||
@@ -15,8 +15,8 @@
|
||||
<div class="part">
|
||||
<SelectableOption
|
||||
label="Standard"
|
||||
:enabled="this.currentSelection == SelectionState.Standard"
|
||||
@click="selectAsync(SelectionState.Standard)"
|
||||
:enabled="this.currentSelection == SelectionType.Standard"
|
||||
@click="selectType(SelectionType.Standard)"
|
||||
v-tooltip=" '🛡️ Balanced for privacy and functionality.<br/>' +
|
||||
'OS and applications will function normally.<br/>' +
|
||||
'💡 Recommended for everyone'"
|
||||
@@ -26,8 +26,8 @@
|
||||
<div class="part">
|
||||
<SelectableOption
|
||||
label="Strict"
|
||||
:enabled="this.currentSelection == SelectionState.Strict"
|
||||
@click="selectAsync(SelectionState.Strict)"
|
||||
:enabled="this.currentSelection == SelectionType.Strict"
|
||||
@click="selectType(SelectionType.Strict)"
|
||||
v-tooltip=" '🚫 Stronger privacy, disables risky functions that may leak your data.<br/>' +
|
||||
'⚠️ Double check to remove sripts where you would trade functionality for privacy<br/>' +
|
||||
'💡 Recommended for daily users that prefers more privacy over non-essential functions'"
|
||||
@@ -37,8 +37,8 @@
|
||||
<div class="part">
|
||||
<SelectableOption
|
||||
label="All"
|
||||
:enabled="this.currentSelection == SelectionState.All"
|
||||
@click="selectAsync(SelectionState.All)"
|
||||
:enabled="this.currentSelection == SelectionType.All"
|
||||
@click="selectType(SelectionType.All)"
|
||||
v-tooltip=" '🔒 Strongest privacy, disabling any functionality that may leak your data.<br/>' +
|
||||
'🛑 Not designed for daily users, it will break important functionalities.<br/>' +
|
||||
'💡 Only recommended for extreme use-cases like crime labs where no leak is acceptable'"
|
||||
@@ -52,112 +52,40 @@
|
||||
import { Component } from 'vue-property-decorator';
|
||||
import { StatefulVue } from '@/presentation/components/Shared/StatefulVue';
|
||||
import SelectableOption from './SelectableOption.vue';
|
||||
import { IScript } from '@/domain/IScript';
|
||||
import { SelectedScript } from '@/application/Context/State/Selection/SelectedScript';
|
||||
import { RecommendationLevel } from '@/domain/RecommendationLevel';
|
||||
import { ICategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState';
|
||||
import { SelectionType, SelectionTypeHandler } from './SelectionTypeHandler';
|
||||
|
||||
enum SelectionState {
|
||||
Standard,
|
||||
Strict,
|
||||
All,
|
||||
None,
|
||||
Custom,
|
||||
}
|
||||
@Component({
|
||||
components: {
|
||||
SelectableOption,
|
||||
},
|
||||
})
|
||||
export default class TheSelector extends StatefulVue {
|
||||
public SelectionState = SelectionState;
|
||||
public currentSelection = SelectionState.None;
|
||||
public SelectionType = SelectionType;
|
||||
public currentSelection = SelectionType.None;
|
||||
private selectionTypeHandler: SelectionTypeHandler;
|
||||
|
||||
public async selectAsync(type: SelectionState): Promise<void> {
|
||||
public async selectType(type: SelectionType) {
|
||||
if (this.currentSelection === type) {
|
||||
return;
|
||||
}
|
||||
const context = await this.getCurrentContextAsync();
|
||||
selectType(context.state, type);
|
||||
this.selectionTypeHandler.selectType(type);
|
||||
}
|
||||
|
||||
protected handleCollectionState(newState: ICategoryCollectionState, oldState: ICategoryCollectionState): void {
|
||||
this.updateSelections(newState);
|
||||
newState.selection.changed.on(() => this.updateSelections(newState));
|
||||
this.selectionTypeHandler = new SelectionTypeHandler(newState);
|
||||
this.updateSelections();
|
||||
newState.selection.changed.on(() => this.updateSelections());
|
||||
if (oldState) {
|
||||
oldState.selection.changed.on(() => this.updateSelections(oldState));
|
||||
oldState.selection.changed.on(() => this.updateSelections());
|
||||
}
|
||||
}
|
||||
|
||||
private updateSelections(state: ICategoryCollectionState) {
|
||||
this.currentSelection = getCurrentSelectionState(state);
|
||||
private updateSelections() {
|
||||
this.currentSelection = this.selectionTypeHandler.getCurrentSelectionType();
|
||||
}
|
||||
}
|
||||
|
||||
interface ITypeSelector {
|
||||
isSelected: (state: ICategoryCollectionState) => boolean;
|
||||
select: (state: ICategoryCollectionState) => void;
|
||||
}
|
||||
|
||||
const selectors = new Map<SelectionState, ITypeSelector>([
|
||||
[SelectionState.None, {
|
||||
select: (state) =>
|
||||
state.selection.deselectAll(),
|
||||
isSelected: (state) =>
|
||||
state.selection.totalSelected === 0,
|
||||
}],
|
||||
[SelectionState.Standard, {
|
||||
select: (state) =>
|
||||
state.selection.selectOnly(
|
||||
state.collection.getScriptsByLevel(RecommendationLevel.Standard)),
|
||||
isSelected: (state) =>
|
||||
hasAllSelectedLevelOf(RecommendationLevel.Standard, state),
|
||||
}],
|
||||
[SelectionState.Strict, {
|
||||
select: (state) =>
|
||||
state.selection.selectOnly(state.collection.getScriptsByLevel(RecommendationLevel.Strict)),
|
||||
isSelected: (state) =>
|
||||
hasAllSelectedLevelOf(RecommendationLevel.Strict, state),
|
||||
}],
|
||||
[SelectionState.All, {
|
||||
select: (state) =>
|
||||
state.selection.selectAll(),
|
||||
isSelected: (state) =>
|
||||
state.selection.totalSelected === state.collection.totalScripts,
|
||||
}],
|
||||
]);
|
||||
|
||||
function selectType(state: ICategoryCollectionState, type: SelectionState) {
|
||||
const selector = selectors.get(type);
|
||||
selector.select(state);
|
||||
}
|
||||
|
||||
function getCurrentSelectionState(state: ICategoryCollectionState): SelectionState {
|
||||
for (const [type, selector] of Array.from(selectors.entries())) {
|
||||
if (selector.isSelected(state)) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
return SelectionState.Custom;
|
||||
}
|
||||
|
||||
function hasAllSelectedLevelOf(level: RecommendationLevel, state: ICategoryCollectionState) {
|
||||
const scripts = state.collection.getScriptsByLevel(level);
|
||||
const selectedScripts = state.selection.selectedScripts;
|
||||
return areAllSelected(scripts, selectedScripts);
|
||||
}
|
||||
|
||||
function areAllSelected(
|
||||
expectedScripts: ReadonlyArray<IScript>,
|
||||
selection: ReadonlyArray<SelectedScript>): boolean {
|
||||
selection = selection.filter((selected) => !selected.revert);
|
||||
if (expectedScripts.length < selection.length) {
|
||||
return false;
|
||||
}
|
||||
const selectedScriptIds = selection.map((script) => script.id).sort();
|
||||
const expectedScriptIds = expectedScripts.map((script) => script.id).sort();
|
||||
return selectedScriptIds.every((id, index) => id === expectedScriptIds[index]);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
@@ -55,7 +55,7 @@ describe('CategoryCollectionState', () => {
|
||||
const collection = new CategoryCollectionStub();
|
||||
const sut = new CategoryCollectionState(collection);
|
||||
// act
|
||||
const actual = sut.selection.totalSelected;
|
||||
const actual = sut.selection.selectedScripts.length;
|
||||
// assert
|
||||
expect(actual).to.equal(0);
|
||||
});
|
||||
@@ -68,7 +68,7 @@ describe('CategoryCollectionState', () => {
|
||||
// act
|
||||
sut.selection.selectAll();
|
||||
// assert
|
||||
expect(sut.selection.totalSelected).to.equal(1);
|
||||
expect(sut.selection.selectedScripts.length).to.equal(1);
|
||||
expect(sut.selection.isSelected(expectedScript.id)).to.equal(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -152,7 +152,6 @@ describe('UserSelection', () => {
|
||||
// act
|
||||
sut.removeAllInCategory(categoryId);
|
||||
// assert
|
||||
expect(sut.totalSelected).to.equal(0);
|
||||
expect(sut.selectedScripts.length).to.equal(0);
|
||||
});
|
||||
it('removes existing some exists', () => {
|
||||
@@ -167,7 +166,6 @@ describe('UserSelection', () => {
|
||||
// act
|
||||
sut.removeAllInCategory(categoryId);
|
||||
// assert
|
||||
expect(sut.totalSelected).to.equal(0);
|
||||
expect(sut.selectedScripts.length).to.equal(0);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import { SelectedScript } from '@/application/Context/State/Selection/SelectedScript';
|
||||
import { RecommendationLevel } from '@/domain/RecommendationLevel';
|
||||
import { CategoryCollectionStateStub } from '@tests/unit/stubs/CategoryCollectionStateStub';
|
||||
import { ScriptStub } from '@tests/unit/stubs/ScriptStub';
|
||||
|
||||
export class SelectionStateTestScenario {
|
||||
public readonly all: readonly SelectedScript[];
|
||||
public readonly allStandard: readonly SelectedScript[];
|
||||
public readonly someStandard: readonly SelectedScript[];
|
||||
public readonly someStrict: readonly SelectedScript[];
|
||||
public readonly allStrict: readonly SelectedScript[];
|
||||
public readonly someUnrecommended: readonly SelectedScript[];
|
||||
public readonly allUnrecommended: readonly SelectedScript[];
|
||||
constructor() {
|
||||
this.someStandard = createSelectedScripts(RecommendationLevel.Standard, 'standard-some-1', 'standard-some-2');
|
||||
this.allStandard = [...this.someStandard, ...createSelectedScripts(RecommendationLevel.Standard, 'standard-all-1', 'standard-all-2')];
|
||||
this.someStrict = createSelectedScripts(RecommendationLevel.Strict, 'strict-some-1', 'strict-some-2');
|
||||
this.allStrict = [...this.someStrict, ...createSelectedScripts(RecommendationLevel.Strict, 'strict-all-1', 'strict-all-2')];
|
||||
this.someUnrecommended = createSelectedScripts(undefined, 'unrecommended-some-1', 'unrecommended-some-2');
|
||||
this.allUnrecommended = [...this.someUnrecommended, ...createSelectedScripts(undefined, 'unrecommended-all-1', 'unrecommended-all-2')];
|
||||
this.all = [...this.allStandard, ...this.allStrict, ...this.allUnrecommended];
|
||||
}
|
||||
public generateState(selectedScripts: readonly SelectedScript[]) {
|
||||
const allScripts = this.all.map((s) => s.script);
|
||||
return new CategoryCollectionStateStub(allScripts)
|
||||
.withSelectedScripts(selectedScripts);
|
||||
}
|
||||
}
|
||||
|
||||
function createSelectedScripts(level?: RecommendationLevel, ...ids: string[]) {
|
||||
return ids.map((id) => new SelectedScript(new ScriptStub(id).withLevel(level), false));
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
import 'mocha';
|
||||
import { expect } from 'chai';
|
||||
import { SelectionType, SelectionTypeHandler } from '@/presentation/components/Scripts/Menu/Selector/SelectionTypeHandler';
|
||||
import { scrambledEqual } from '@/application/Common/Array';
|
||||
import { RecommendationLevel } from '@/domain/RecommendationLevel';
|
||||
import { SelectionStateTestScenario } from './SelectionStateTestScenario';
|
||||
|
||||
describe('SelectionTypeHandler', () => {
|
||||
describe('ctor', () => {
|
||||
it('throws when state is undefined', () => {
|
||||
// arrange
|
||||
const expectedError = 'undefined state';
|
||||
const state = undefined;
|
||||
// act
|
||||
const sut = () => new SelectionTypeHandler(state);
|
||||
// assert
|
||||
expect(sut).to.throw(expectedError);
|
||||
});
|
||||
});
|
||||
describe('selectType', () => {
|
||||
it('throws when type is custom', () => {
|
||||
// arrange
|
||||
const expectedError = 'cannot select custom type';
|
||||
const scenario = new SelectionStateTestScenario();
|
||||
const state = scenario.generateState([]);
|
||||
const sut = new SelectionTypeHandler(state);
|
||||
// act
|
||||
const act = () => sut.selectType(SelectionType.Custom);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
describe('select types as expected', () => {
|
||||
// arrange
|
||||
const scenario = new SelectionStateTestScenario();
|
||||
const initialScriptsCases = [{
|
||||
name: 'when nothing is selected',
|
||||
initialScripts: [],
|
||||
}, {
|
||||
name: 'when some scripts are selected',
|
||||
initialScripts: [...scenario.allStandard, ...scenario.someStrict],
|
||||
}, {
|
||||
name: 'when all scripts are selected',
|
||||
initialScripts: scenario.all,
|
||||
} ];
|
||||
for (const initialScriptsCase of initialScriptsCases) {
|
||||
describe(initialScriptsCase.name, () => {
|
||||
const state = scenario.generateState(initialScriptsCase.initialScripts);
|
||||
const sut = new SelectionTypeHandler(state);
|
||||
const typeExpectations = [{
|
||||
input: SelectionType.None,
|
||||
output: [],
|
||||
}, {
|
||||
input: SelectionType.Standard,
|
||||
output: scenario.allStandard,
|
||||
}, {
|
||||
input: SelectionType.Strict,
|
||||
output: [...scenario.allStandard, ...scenario.allStrict],
|
||||
}, {
|
||||
input: SelectionType.All,
|
||||
output: scenario.all,
|
||||
}];
|
||||
for (const expectation of typeExpectations) {
|
||||
// act
|
||||
it(`${SelectionType[expectation.input]} returns as expected`, () => {
|
||||
sut.selectType(expectation.input);
|
||||
// assert
|
||||
const actual = state.selection.selectedScripts;
|
||||
const expected = expectation.output;
|
||||
expect(scrambledEqual(actual, expected));
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
describe('getCurrentSelectionType', () => {
|
||||
// arrange
|
||||
const scenario = new SelectionStateTestScenario();
|
||||
const testCases = [{
|
||||
name: 'when nothing is selected',
|
||||
selection: [],
|
||||
expected: SelectionType.None,
|
||||
}, {
|
||||
name: 'when some standard scripts are selected',
|
||||
selection: scenario.someStandard,
|
||||
expected: SelectionType.Custom,
|
||||
}, {
|
||||
name: 'when all standard scripts are selected',
|
||||
selection: scenario.allStandard,
|
||||
expected: SelectionType.Standard,
|
||||
}, {
|
||||
name: 'when all standard and some strict scripts are selected',
|
||||
selection: [...scenario.allStandard, ...scenario.someStrict],
|
||||
expected: SelectionType.Custom,
|
||||
}, {
|
||||
name: 'when all standard and strict scripts are selected',
|
||||
selection: [...scenario.allStandard, ...scenario.allStrict],
|
||||
expected: SelectionType.Strict,
|
||||
}, {
|
||||
name: 'when strict scripts are selected but not standard',
|
||||
selection: scenario.allStrict,
|
||||
expected: SelectionType.Custom,
|
||||
}, {
|
||||
name: 'when all standard and strict, and some unrecommended are selected',
|
||||
selection: [...scenario.allStandard, ...scenario.allStrict, ...scenario.someUnrecommended],
|
||||
expected: SelectionType.Custom,
|
||||
}, {
|
||||
name: 'when all scripts are selected',
|
||||
selection: scenario.all,
|
||||
expected: SelectionType.All,
|
||||
} ];
|
||||
for (const testCase of testCases) {
|
||||
it(testCase.name, () => {
|
||||
const state = scenario.generateState(testCase.selection);
|
||||
const sut = new SelectionTypeHandler(state);
|
||||
// act
|
||||
const actual = sut.getCurrentSelectionType();
|
||||
// assert
|
||||
expect(actual).to.deep.equal(testCase.expected,
|
||||
`Actual: "${SelectionType[actual]}", expected: "${SelectionType[testCase.expected]}"` +
|
||||
`\nSelection: ${printSelection()}`);
|
||||
function printSelection() {
|
||||
return `total: ${testCase.selection.length}\n` +
|
||||
'scripts:\n' +
|
||||
testCase.selection
|
||||
.map((s) => `{ id: ${s.script.id}, level: ${s.script.level === undefined ? 'unknown' : RecommendationLevel[s.script.level]} }`)
|
||||
.join(' | ');
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
9
tests/unit/stubs/ApplicationCodeStub.ts
Normal file
9
tests/unit/stubs/ApplicationCodeStub.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { ICodeChangedEvent } from '@/application/Context/State/Code/Event/ICodeChangedEvent';
|
||||
import { IApplicationCode } from '@/application/Context/State/Code/IApplicationCode';
|
||||
import { IEventSource } from '@/infrastructure/Events/IEventSource';
|
||||
import { EventSource } from '@/infrastructure/Events/EventSource';
|
||||
|
||||
export class ApplicationCodeStub implements IApplicationCode {
|
||||
public changed: IEventSource<ICodeChangedEvent> = new EventSource<ICodeChangedEvent>();
|
||||
public current: string = '';
|
||||
}
|
||||
30
tests/unit/stubs/CategoryCollectionStateStub.ts
Normal file
30
tests/unit/stubs/CategoryCollectionStateStub.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { IApplicationCode } from '@/application/Context/State/Code/IApplicationCode';
|
||||
import { IUserFilter } from '@/application/Context/State/Filter/IUserFilter';
|
||||
import { ICategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState';
|
||||
import { OperatingSystem } from '@/domain/OperatingSystem';
|
||||
import { CategoryCollectionStub } from './CategoryCollectionStub';
|
||||
import { UserSelectionStub } from './UserSelectionStub';
|
||||
import { UserFilterStub } from './UserFilterStub';
|
||||
import { ApplicationCodeStub } from './ApplicationCodeStub';
|
||||
import { SelectedScript } from '@/application/Context/State/Selection/SelectedScript';
|
||||
import { IScript } from '@/domain/IScript';
|
||||
import { CategoryStub } from './CategoryStub';
|
||||
|
||||
export class CategoryCollectionStateStub implements ICategoryCollectionState {
|
||||
public readonly code: IApplicationCode = new ApplicationCodeStub();
|
||||
public readonly filter: IUserFilter = new UserFilterStub();
|
||||
public readonly os = OperatingSystem.Windows;
|
||||
public readonly collection: CategoryCollectionStub;
|
||||
public readonly selection: UserSelectionStub;
|
||||
constructor(readonly allScripts: IScript[]) {
|
||||
this.selection = new UserSelectionStub(allScripts);
|
||||
this.collection = new CategoryCollectionStub()
|
||||
.withOs(this.os)
|
||||
.withTotalScripts(this.allScripts.length)
|
||||
.withAction(new CategoryStub(0).withScripts(...allScripts));
|
||||
}
|
||||
public withSelectedScripts(initialScripts: readonly SelectedScript[]) {
|
||||
this.selection.withSelectedScripts(initialScripts);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import { ICategory } from '@/domain/ICategory';
|
||||
import { ICategoryCollection } from '@/domain/ICategoryCollection';
|
||||
import { ScriptStub } from './ScriptStub';
|
||||
import { ScriptingDefinitionStub } from './ScriptingDefinitionStub';
|
||||
import { RecommendationLevel } from '@/domain/RecommendationLevel';
|
||||
|
||||
export class CategoryCollectionStub implements ICategoryCollection {
|
||||
public scripting: IScriptingDefinition = new ScriptingDefinitionStub();
|
||||
@@ -36,14 +37,16 @@ export class CategoryCollectionStub implements ICategoryCollection {
|
||||
}
|
||||
|
||||
public findCategory(categoryId: number): ICategory {
|
||||
return this.getAllCategories().find(
|
||||
(category) => category.id === categoryId);
|
||||
return this.getAllCategories()
|
||||
.find((category) => category.id === categoryId);
|
||||
}
|
||||
public getScriptsByLevel(): readonly IScript[] {
|
||||
throw new Error('Method not implemented: getScriptsByLevel');
|
||||
public getScriptsByLevel(level: RecommendationLevel): readonly IScript[] {
|
||||
return this.getAllScripts()
|
||||
.filter((script) => script.level !== undefined && script.level <= level);
|
||||
}
|
||||
public findScript(scriptId: string): IScript {
|
||||
return this.getAllScripts().find((script) => scriptId === script.id);
|
||||
return this.getAllScripts()
|
||||
.find((script) => scriptId === script.id);
|
||||
}
|
||||
public getAllScripts(): ReadonlyArray<IScript> {
|
||||
const scripts = [];
|
||||
@@ -79,9 +82,7 @@ function getSubCategoriesRecursively(category: ICategory): ReadonlyArray<ICatego
|
||||
function getScriptsRecursively(category: ICategory): ReadonlyArray<IScript> {
|
||||
const categoryScripts = [];
|
||||
if (category.scripts) {
|
||||
for (const script of category.scripts) {
|
||||
categoryScripts.push(script);
|
||||
}
|
||||
categoryScripts.push(...category.scripts);
|
||||
}
|
||||
if (category.subCategories) {
|
||||
for (const subCategory of category.subCategories) {
|
||||
|
||||
15
tests/unit/stubs/UserFilterStub.ts
Normal file
15
tests/unit/stubs/UserFilterStub.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { IFilterResult } from '@/application/Context/State/Filter/IFilterResult';
|
||||
import { IUserFilter } from '@/application/Context/State/Filter/IUserFilter';
|
||||
import { IEventSource } from '@/infrastructure/Events/IEventSource';
|
||||
|
||||
export class UserFilterStub implements IUserFilter {
|
||||
public currentFilter: IFilterResult;
|
||||
public filtered: IEventSource<IFilterResult>;
|
||||
public filterRemoved: IEventSource<void>;
|
||||
public setFilter(): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
public removeFilter(): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
}
|
||||
49
tests/unit/stubs/UserSelectionStub.ts
Normal file
49
tests/unit/stubs/UserSelectionStub.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { IUserSelection } from '@/application/Context/State/Selection/IUserSelection';
|
||||
import { SelectedScript } from '@/application/Context/State/Selection/SelectedScript';
|
||||
import { IScript } from '@/domain/IScript';
|
||||
import { IEventSource } from '@/infrastructure/Events/IEventSource';
|
||||
import { EventSource } from '@/infrastructure/Events/EventSource';
|
||||
|
||||
export class UserSelectionStub implements IUserSelection {
|
||||
public readonly changed: IEventSource<readonly SelectedScript[]> = new EventSource<readonly SelectedScript[]>();
|
||||
public selectedScripts: readonly SelectedScript[] = [];
|
||||
constructor(private readonly allScripts: readonly IScript[]) {
|
||||
|
||||
}
|
||||
public withSelectedScripts(selectedScripts: readonly SelectedScript[]) {
|
||||
this.selectedScripts = selectedScripts;
|
||||
}
|
||||
public areAllSelected(): boolean {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
public isAnySelected(): boolean {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
public removeAllInCategory(): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
public addOrUpdateAllInCategory(): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
public addSelectedScript(): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
public addOrUpdateSelectedScript(): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
public removeSelectedScript(): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
public selectOnly(scripts: ReadonlyArray<IScript>): void {
|
||||
this.selectedScripts = scripts.map((s) => new SelectedScript(s, false));
|
||||
}
|
||||
public isSelected(): boolean {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
public selectAll(): void {
|
||||
this.selectOnly(this.allScripts);
|
||||
}
|
||||
public deselectAll(): void {
|
||||
this.selectedScripts = [];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user