add support for different recommendation levels: strict and standard
This commit is contained in:
@@ -28,7 +28,25 @@
|
|||||||
### Extend scripts
|
### Extend scripts
|
||||||
|
|
||||||
- Create a [pull request](#Pull-Request-Process) for [application.yaml](./src/application/application.yaml)
|
- Create a [pull request](#Pull-Request-Process) for [application.yaml](./src/application/application.yaml)
|
||||||
- 🙏 For any new script, try to add `revertCode` that'll revert the changes caused by the script.
|
- 🙏 For any new script, please add `revertCode` and `docs` values if possible.
|
||||||
|
- Structure of `script` object:
|
||||||
|
- `name`: *`string`* (**required**)
|
||||||
|
- Name of the script
|
||||||
|
- E.g. `Disable targeted ads`
|
||||||
|
- `code`: *`string`* (**required**)
|
||||||
|
- Batch file commands that will be executed
|
||||||
|
- `docs`: *`string`* | `[ string, ... ]`
|
||||||
|
- Documentation URL or list of URLs for those who wants to learn more about the script
|
||||||
|
- E.g. `https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_telemetry`
|
||||||
|
- `revertCode`: `string`
|
||||||
|
- Code that'll undo the change done by `code` property.
|
||||||
|
- E.g. let's say `code` sets an environment variable as `setx POWERSHELL_TELEMETRY_OPTOUT 1`
|
||||||
|
- then `revertCode` should be doing `setx POWERSHELL_TELEMETRY_OPTOUT 0`
|
||||||
|
- `recommend`: `"standard"` | `"strict"` | `undefined` (default)
|
||||||
|
- If not defined then the script will not be recommended
|
||||||
|
- If defined it can be either
|
||||||
|
- `standard`: Will be recommended for general users
|
||||||
|
- `strict`: Will only be recommended with a warning
|
||||||
- See [typings](./src/application/application.yaml.d.ts) for documentation as code.
|
- See [typings](./src/application/application.yaml.d.ts) for documentation as code.
|
||||||
|
|
||||||
### Handle the state in presentation layer
|
### Handle the state in presentation layer
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Script } from '@/domain/Script';
|
import { Script } from '@/domain/Script';
|
||||||
import { YamlScript } from 'js-yaml-loader!./application.yaml';
|
import { YamlScript } from 'js-yaml-loader!./application.yaml';
|
||||||
import { parseDocUrls } from './DocumentationParser';
|
import { parseDocUrls } from './DocumentationParser';
|
||||||
|
import { RecommendationLevelNames, RecommendationLevel } from '@/domain/RecommendationLevel';
|
||||||
|
|
||||||
export function parseScript(yamlScript: YamlScript): Script {
|
export function parseScript(yamlScript: YamlScript): Script {
|
||||||
if (!yamlScript) {
|
if (!yamlScript) {
|
||||||
@@ -11,6 +12,21 @@ export function parseScript(yamlScript: YamlScript): Script {
|
|||||||
/* code */ yamlScript.code,
|
/* code */ yamlScript.code,
|
||||||
/* revertCode */ yamlScript.revertCode,
|
/* revertCode */ yamlScript.revertCode,
|
||||||
/* docs */ parseDocUrls(yamlScript),
|
/* docs */ parseDocUrls(yamlScript),
|
||||||
/* isRecommended */ yamlScript.recommend);
|
/* level */ getLevel(yamlScript.recommend));
|
||||||
return script;
|
return script;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getLevel(level: string): RecommendationLevel | undefined {
|
||||||
|
if (!level) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
if (typeof level !== 'string') {
|
||||||
|
throw new Error(`level must be a string but it was ${typeof level}`);
|
||||||
|
}
|
||||||
|
const typedLevel = RecommendationLevelNames
|
||||||
|
.find((l) => l.toLowerCase() === level.toLowerCase());
|
||||||
|
if (!typedLevel) {
|
||||||
|
throw new Error(`unknown level: \"${level}\"`);
|
||||||
|
}
|
||||||
|
return RecommendationLevel[typedLevel as keyof typeof RecommendationLevel];
|
||||||
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
2
src/application/application.yaml.d.ts
vendored
2
src/application/application.yaml.d.ts
vendored
@@ -10,7 +10,7 @@ declare module 'js-yaml-loader!*' {
|
|||||||
name: string;
|
name: string;
|
||||||
code: string;
|
code: string;
|
||||||
revertCode: string;
|
revertCode: string;
|
||||||
recommend: boolean;
|
recommend: string | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface YamlCategory extends YamlDocumentable {
|
export interface YamlCategory extends YamlDocumentable {
|
||||||
|
|||||||
@@ -3,12 +3,13 @@ import { ICategory } from './ICategory';
|
|||||||
import { IScript } from './IScript';
|
import { IScript } from './IScript';
|
||||||
import { IApplication } from './IApplication';
|
import { IApplication } from './IApplication';
|
||||||
import { IProjectInformation } from './IProjectInformation';
|
import { IProjectInformation } from './IProjectInformation';
|
||||||
|
import { RecommendationLevel, RecommendationLevelNames, RecommendationLevels } from './RecommendationLevel';
|
||||||
|
|
||||||
export class Application implements IApplication {
|
export class Application implements IApplication {
|
||||||
public get totalScripts(): number { return this.flattened.allScripts.length; }
|
public get totalScripts(): number { return this.queryable.allScripts.length; }
|
||||||
public get totalCategories(): number { return this.flattened.allCategories.length; }
|
public get totalCategories(): number { return this.queryable.allCategories.length; }
|
||||||
|
|
||||||
private readonly flattened: IFlattenedApplication;
|
private readonly queryable: IQueryableApplication;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public readonly info: IProjectInformation,
|
public readonly info: IProjectInformation,
|
||||||
@@ -16,30 +17,36 @@ export class Application implements IApplication {
|
|||||||
if (!info) {
|
if (!info) {
|
||||||
throw new Error('info is undefined');
|
throw new Error('info is undefined');
|
||||||
}
|
}
|
||||||
this.flattened = flatten(actions);
|
this.queryable = makeQueryable(actions);
|
||||||
ensureValid(this.flattened);
|
ensureValid(this.queryable);
|
||||||
ensureNoDuplicates(this.flattened.allCategories);
|
ensureNoDuplicates(this.queryable.allCategories);
|
||||||
ensureNoDuplicates(this.flattened.allScripts);
|
ensureNoDuplicates(this.queryable.allScripts);
|
||||||
}
|
}
|
||||||
|
|
||||||
public findCategory(categoryId: number): ICategory | undefined {
|
public findCategory(categoryId: number): ICategory | undefined {
|
||||||
return this.flattened.allCategories.find((category) => category.id === categoryId);
|
return this.queryable.allCategories.find((category) => category.id === categoryId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public getRecommendedScripts(): readonly IScript[] {
|
public getScriptsByLevel(level: RecommendationLevel): readonly IScript[] {
|
||||||
return this.flattened.allScripts.filter((script) => script.isRecommended);
|
if (isNaN(level)) {
|
||||||
|
throw new Error('undefined level');
|
||||||
|
}
|
||||||
|
if (!(level in RecommendationLevel)) {
|
||||||
|
throw new Error(`invalid level: ${level}`);
|
||||||
|
}
|
||||||
|
return this.queryable.scriptsByLevel.get(level);
|
||||||
}
|
}
|
||||||
|
|
||||||
public findScript(scriptId: string): IScript | undefined {
|
public findScript(scriptId: string): IScript | undefined {
|
||||||
return this.flattened.allScripts.find((script) => script.id === scriptId);
|
return this.queryable.allScripts.find((script) => script.id === scriptId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public getAllScripts(): IScript[] {
|
public getAllScripts(): IScript[] {
|
||||||
return this.flattened.allScripts;
|
return this.queryable.allScripts;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getAllCategories(): ICategory[] {
|
public getAllCategories(): ICategory[] {
|
||||||
return this.flattened.allCategories;
|
return this.queryable.allCategories;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,55 +68,85 @@ function ensureNoDuplicates<TKey>(entities: ReadonlyArray<IEntity<TKey>>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IFlattenedApplication {
|
interface IQueryableApplication {
|
||||||
allCategories: ICategory[];
|
allCategories: ICategory[];
|
||||||
allScripts: IScript[];
|
allScripts: IScript[];
|
||||||
|
scriptsByLevel: Map<RecommendationLevel, readonly IScript[]>;
|
||||||
}
|
}
|
||||||
|
|
||||||
function ensureValid(application: IFlattenedApplication) {
|
function ensureValid(application: IQueryableApplication) {
|
||||||
if (!application.allCategories || application.allCategories.length === 0) {
|
ensureValidCategories(application.allCategories);
|
||||||
|
ensureValidScripts(application.allScripts);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureValidCategories(allCategories: readonly ICategory[]) {
|
||||||
|
if (!allCategories || allCategories.length === 0) {
|
||||||
throw new Error('Application must consist of at least one category');
|
throw new Error('Application must consist of at least one category');
|
||||||
}
|
}
|
||||||
if (!application.allScripts || application.allScripts.length === 0) {
|
}
|
||||||
|
|
||||||
|
function ensureValidScripts(allScripts: readonly IScript[]) {
|
||||||
|
if (!allScripts || allScripts.length === 0) {
|
||||||
throw new Error('Application must consist of at least one script');
|
throw new Error('Application must consist of at least one script');
|
||||||
}
|
}
|
||||||
if (application.allScripts.filter((script) => script.isRecommended).length === 0) {
|
for (const level of RecommendationLevels) {
|
||||||
throw new Error('Application must consist of at least one recommended script');
|
if (allScripts.every((script) => script.level !== level)) {
|
||||||
|
throw new Error(`none of the scripts are recommended as ${RecommendationLevel[level]}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function flattenApplication(categories: ReadonlyArray<ICategory>): [ICategory[], IScript[]] {
|
||||||
|
const allCategories = new Array<ICategory>();
|
||||||
|
const allScripts = new Array<IScript>();
|
||||||
|
flattenCategories(categories, allCategories, allScripts);
|
||||||
|
return [
|
||||||
|
allCategories,
|
||||||
|
allScripts,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
function flattenCategories(
|
function flattenCategories(
|
||||||
categories: ReadonlyArray<ICategory>,
|
categories: ReadonlyArray<ICategory>,
|
||||||
flattened: IFlattenedApplication): IFlattenedApplication {
|
allCategories: ICategory[],
|
||||||
|
allScripts: IScript[]): IQueryableApplication {
|
||||||
if (!categories || categories.length === 0) {
|
if (!categories || categories.length === 0) {
|
||||||
return flattened;
|
return;
|
||||||
}
|
}
|
||||||
for (const category of categories) {
|
for (const category of categories) {
|
||||||
flattened.allCategories.push(category);
|
allCategories.push(category);
|
||||||
flattened = flattenScripts(category.scripts, flattened);
|
flattenScripts(category.scripts, allScripts);
|
||||||
flattened = flattenCategories(category.subCategories, flattened);
|
flattenCategories(category.subCategories, allCategories, allScripts);
|
||||||
}
|
}
|
||||||
return flattened;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function flattenScripts(
|
function flattenScripts(
|
||||||
scripts: ReadonlyArray<IScript>,
|
scripts: ReadonlyArray<IScript>,
|
||||||
flattened: IFlattenedApplication): IFlattenedApplication {
|
allScripts: IScript[]): IScript[] {
|
||||||
if (!scripts) {
|
if (!scripts) {
|
||||||
return flattened;
|
return;
|
||||||
}
|
}
|
||||||
for (const script of scripts) {
|
for (const script of scripts) {
|
||||||
flattened.allScripts.push(script);
|
allScripts.push(script);
|
||||||
}
|
}
|
||||||
return flattened;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function flatten(
|
function makeQueryable(
|
||||||
categories: ReadonlyArray<ICategory>): IFlattenedApplication {
|
actions: ReadonlyArray<ICategory>): IQueryableApplication {
|
||||||
let flattened: IFlattenedApplication = {
|
const flattened = flattenApplication(actions);
|
||||||
allCategories: new Array<ICategory>(),
|
return {
|
||||||
allScripts: new Array<IScript>(),
|
allCategories: flattened[0],
|
||||||
|
allScripts: flattened[1],
|
||||||
|
scriptsByLevel: groupByLevel(flattened[1]),
|
||||||
};
|
};
|
||||||
flattened = flattenCategories(categories, flattened);
|
}
|
||||||
return flattened;
|
|
||||||
|
function groupByLevel(allScripts: readonly IScript[]): Map<RecommendationLevel, readonly IScript[]> {
|
||||||
|
const map = new Map<RecommendationLevel, readonly IScript[]>();
|
||||||
|
for (const levelName of RecommendationLevelNames) {
|
||||||
|
const level = RecommendationLevel[levelName];
|
||||||
|
const scripts = allScripts.filter((script) => script.level !== undefined && script.level <= level);
|
||||||
|
map.set(level, scripts);
|
||||||
|
}
|
||||||
|
return map;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { IScript } from '@/domain/IScript';
|
import { IScript } from '@/domain/IScript';
|
||||||
import { ICategory } from '@/domain/ICategory';
|
import { ICategory } from '@/domain/ICategory';
|
||||||
import { IProjectInformation } from './IProjectInformation';
|
import { IProjectInformation } from './IProjectInformation';
|
||||||
|
import { RecommendationLevel } from './RecommendationLevel';
|
||||||
|
|
||||||
export interface IApplication {
|
export interface IApplication {
|
||||||
readonly info: IProjectInformation;
|
readonly info: IProjectInformation;
|
||||||
@@ -8,7 +9,7 @@ export interface IApplication {
|
|||||||
readonly totalCategories: number;
|
readonly totalCategories: number;
|
||||||
readonly actions: ReadonlyArray<ICategory>;
|
readonly actions: ReadonlyArray<ICategory>;
|
||||||
|
|
||||||
getRecommendedScripts(): ReadonlyArray<IScript>;
|
getScriptsByLevel(level: RecommendationLevel): ReadonlyArray<IScript>;
|
||||||
findCategory(categoryId: number): ICategory | undefined;
|
findCategory(categoryId: number): ICategory | undefined;
|
||||||
findScript(scriptId: string): IScript | undefined;
|
findScript(scriptId: string): IScript | undefined;
|
||||||
getAllScripts(): ReadonlyArray<IScript>;
|
getAllScripts(): ReadonlyArray<IScript>;
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import { IEntity } from '../infrastructure/Entity/IEntity';
|
import { IEntity } from '../infrastructure/Entity/IEntity';
|
||||||
import { IDocumentable } from './IDocumentable';
|
import { IDocumentable } from './IDocumentable';
|
||||||
|
import { RecommendationLevel } from './RecommendationLevel';
|
||||||
|
|
||||||
export interface IScript extends IEntity<string>, IDocumentable {
|
export interface IScript extends IEntity<string>, IDocumentable {
|
||||||
readonly name: string;
|
readonly name: string;
|
||||||
readonly isRecommended: boolean;
|
readonly level?: RecommendationLevel;
|
||||||
readonly documentationUrls: ReadonlyArray<string>;
|
readonly documentationUrls: ReadonlyArray<string>;
|
||||||
readonly code: string;
|
readonly code: string;
|
||||||
readonly revertCode: string;
|
readonly revertCode: string;
|
||||||
|
|||||||
11
src/domain/RecommendationLevel.ts
Normal file
11
src/domain/RecommendationLevel.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
export enum RecommendationLevel {
|
||||||
|
Standard = 0,
|
||||||
|
Strict = 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const RecommendationLevelNames = Object
|
||||||
|
.values(RecommendationLevel)
|
||||||
|
.filter((level) => typeof level === 'string') as string[];
|
||||||
|
|
||||||
|
export const RecommendationLevels = RecommendationLevelNames
|
||||||
|
.map((level) => RecommendationLevel[level]) as RecommendationLevel[];
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import { BaseEntity } from '@/infrastructure/Entity/BaseEntity';
|
import { BaseEntity } from '@/infrastructure/Entity/BaseEntity';
|
||||||
import { IScript } from './IScript';
|
import { IScript } from './IScript';
|
||||||
|
import { RecommendationLevel } from './RecommendationLevel';
|
||||||
|
|
||||||
export class Script extends BaseEntity<string> implements IScript {
|
export class Script extends BaseEntity<string> implements IScript {
|
||||||
constructor(
|
constructor(
|
||||||
@@ -7,9 +8,10 @@ export class Script extends BaseEntity<string> implements IScript {
|
|||||||
public readonly code: string,
|
public readonly code: string,
|
||||||
public readonly revertCode: string,
|
public readonly revertCode: string,
|
||||||
public readonly documentationUrls: ReadonlyArray<string>,
|
public readonly documentationUrls: ReadonlyArray<string>,
|
||||||
public readonly isRecommended: boolean) {
|
public readonly level?: RecommendationLevel) {
|
||||||
super(name);
|
super(name);
|
||||||
validateCode(name, code);
|
validateCode(name, code);
|
||||||
|
validateLevel(level);
|
||||||
if (revertCode) {
|
if (revertCode) {
|
||||||
validateCode(name, revertCode);
|
validateCode(name, revertCode);
|
||||||
if (code === revertCode) {
|
if (code === revertCode) {
|
||||||
@@ -22,6 +24,12 @@ export class Script extends BaseEntity<string> implements IScript {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function validateLevel(level?: RecommendationLevel) {
|
||||||
|
if (level !== undefined && !(level in RecommendationLevel)) {
|
||||||
|
throw new Error(`invalid level: ${level}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function validateCode(name: string, code: string): void {
|
function validateCode(name: string, code: string): void {
|
||||||
if (!code || code.length === 0) {
|
if (!code || code.length === 0) {
|
||||||
throw new Error(`Code of ${name} is empty or null`);
|
throw new Error(`Code of ${name} is empty or null`);
|
||||||
|
|||||||
@@ -5,23 +5,37 @@
|
|||||||
<div class="part">
|
<div class="part">
|
||||||
<SelectableOption
|
<SelectableOption
|
||||||
label="None"
|
label="None"
|
||||||
:enabled="isNoneSelected"
|
:enabled="this.currentSelection == SelectionState.None"
|
||||||
@click="selectNoneAsync()">
|
@click="selectAsync(SelectionState.None)"
|
||||||
</SelectableOption>
|
v-tooltip="'Deselect all selected scripts. Good start to dive deeper into tweaks and select only what you want.'"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="part"> | </div>
|
<div class="part"> | </div>
|
||||||
<div class="part">
|
<div class="part">
|
||||||
<SelectableOption
|
<SelectableOption
|
||||||
label="Recommended"
|
label="Standard"
|
||||||
:enabled="isRecommendedSelected"
|
:enabled="this.currentSelection == SelectionState.Standard"
|
||||||
@click="selectRecommendedAsync()" />
|
@click="selectAsync(SelectionState.Standard)"
|
||||||
|
v-tooltip="'🛡️ Balanced for privacy and functionality. OS and applications will function normally.'"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="part"> | </div>
|
||||||
|
<div class="part">
|
||||||
|
<SelectableOption
|
||||||
|
label="Strict"
|
||||||
|
:enabled="this.currentSelection == SelectionState.Strict"
|
||||||
|
@click="selectAsync(SelectionState.Strict)"
|
||||||
|
v-tooltip="'🚫 Stronger privacy, disables risky functions that may leak your data. Double check selected tweaks!'"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="part"> | </div>
|
<div class="part"> | </div>
|
||||||
<div class="part">
|
<div class="part">
|
||||||
<SelectableOption
|
<SelectableOption
|
||||||
label="All"
|
label="All"
|
||||||
:enabled="isAllSelected"
|
:enabled="this.currentSelection == SelectionState.All"
|
||||||
@click="selectAllAsync()" />
|
@click="selectAsync(SelectionState.All)"
|
||||||
|
v-tooltip="'🔒 Strongest privacy. Disables any functionality that may leak your data. ⚠️ Not recommended for inexperienced users'"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -31,19 +45,26 @@
|
|||||||
import { Component } from 'vue-property-decorator';
|
import { Component } from 'vue-property-decorator';
|
||||||
import { StatefulVue } from '@/presentation/StatefulVue';
|
import { StatefulVue } from '@/presentation/StatefulVue';
|
||||||
import SelectableOption from './SelectableOption.vue';
|
import SelectableOption from './SelectableOption.vue';
|
||||||
import { IApplicationState } from '@/application/State/IApplicationState';
|
import { IApplicationState, IUserSelection } from '@/application/State/IApplicationState';
|
||||||
import { IScript } from '@/domain/IScript';
|
import { IScript } from '@/domain/IScript';
|
||||||
import { SelectedScript } from '../../../application/State/Selection/SelectedScript';
|
import { SelectedScript } from '@/application/State/Selection/SelectedScript';
|
||||||
|
import { RecommendationLevel } from '@/domain/RecommendationLevel';
|
||||||
|
|
||||||
|
enum SelectionState {
|
||||||
|
Standard,
|
||||||
|
Strict,
|
||||||
|
All,
|
||||||
|
None,
|
||||||
|
Custom,
|
||||||
|
}
|
||||||
@Component({
|
@Component({
|
||||||
components: {
|
components: {
|
||||||
SelectableOption,
|
SelectableOption,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
export default class TheSelector extends StatefulVue {
|
export default class TheSelector extends StatefulVue {
|
||||||
public isAllSelected = false;
|
public SelectionState = SelectionState;
|
||||||
public isNoneSelected = false;
|
public currentSelection = SelectionState.None;
|
||||||
public isRecommendedSelected = false;
|
|
||||||
|
|
||||||
public async mounted() {
|
public async mounted() {
|
||||||
const state = await this.getCurrentStateAsync();
|
const state = await this.getCurrentStateAsync();
|
||||||
@@ -52,43 +73,73 @@ export default class TheSelector extends StatefulVue {
|
|||||||
this.updateSelections(state);
|
this.updateSelections(state);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
public async selectAsync(type: SelectionState): Promise<void> {
|
||||||
public async selectAllAsync(): Promise<void> {
|
if (this.currentSelection === type) {
|
||||||
if (this.isAllSelected) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const state = await this.getCurrentStateAsync();
|
const state = await this.getCurrentStateAsync();
|
||||||
state.selection.selectAll();
|
selectType(state, type);
|
||||||
}
|
|
||||||
|
|
||||||
public async selectRecommendedAsync(): Promise<void> {
|
|
||||||
if (this.isRecommendedSelected) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const state = await this.getCurrentStateAsync();
|
|
||||||
state.selection.selectOnly(state.app.getRecommendedScripts());
|
|
||||||
}
|
|
||||||
|
|
||||||
public async selectNoneAsync(): Promise<void> {
|
|
||||||
if (this.isNoneSelected) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const state = await this.getCurrentStateAsync();
|
|
||||||
state.selection.deselectAll();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private updateSelections(state: IApplicationState) {
|
private updateSelections(state: IApplicationState) {
|
||||||
this.isNoneSelected = state.selection.totalSelected === 0;
|
this.currentSelection = getCurrentSelectionState(state);
|
||||||
this.isAllSelected = state.selection.totalSelected === state.app.totalScripts;
|
|
||||||
this.isRecommendedSelected = this.areAllRecommended(state.app.getRecommendedScripts(),
|
|
||||||
state.selection.selectedScripts);
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private areAllRecommended(scripts: ReadonlyArray<IScript>, other: ReadonlyArray<SelectedScript>): boolean {
|
interface ITypeSelector {
|
||||||
other = other.filter((selected) => !(selected).revert);
|
isSelected: (state: IApplicationState) => boolean;
|
||||||
return (scripts.length === other.length) &&
|
select: (state: IApplicationState) => void;
|
||||||
scripts.every((script) => other.some((selected) => selected.id === script.id));
|
}
|
||||||
|
|
||||||
|
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.app.getScriptsByLevel(RecommendationLevel.Standard)),
|
||||||
|
isSelected: (state) => hasAllSelectedLevelOf(RecommendationLevel.Standard, state),
|
||||||
|
}],
|
||||||
|
[SelectionState.Strict, {
|
||||||
|
select: (state) => state.selection.selectOnly(state.app.getScriptsByLevel(RecommendationLevel.Strict)),
|
||||||
|
isSelected: (state) => hasAllSelectedLevelOf(RecommendationLevel.Strict, state),
|
||||||
|
}],
|
||||||
|
[SelectionState.All, {
|
||||||
|
select: (state) => state.selection.selectAll(),
|
||||||
|
isSelected: (state) => state.selection.totalSelected === state.app.totalScripts,
|
||||||
|
}],
|
||||||
|
]);
|
||||||
|
|
||||||
|
function selectType(state: IApplicationState, type: SelectionState) {
|
||||||
|
const selector = selectors.get(type);
|
||||||
|
selector.select(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCurrentSelectionState(state: IApplicationState): SelectionState {
|
||||||
|
for (const [type, selector] of Array.from(selectors.entries())) {
|
||||||
|
if (selector.isSelected(state)) {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return SelectionState.Custom;
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasAllSelectedLevelOf(level: RecommendationLevel, state: IApplicationState) {
|
||||||
|
const scripts = state.app.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>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ const NothingChosenCode =
|
|||||||
.appendLine()
|
.appendLine()
|
||||||
.appendCommentLine('-- 🤔 How to use')
|
.appendCommentLine('-- 🤔 How to use')
|
||||||
.appendCommentLine(' 📙 Start by exploring different categories and choosing different tweaks.')
|
.appendCommentLine(' 📙 Start by exploring different categories and choosing different tweaks.')
|
||||||
.appendCommentLine(' 📙 You can select "Recommended" on the top to select "safer" tweaks. Always double check!')
|
.appendCommentLine(' 📙 On top left, you can apply predefined selections for privacy level you\'d like.')
|
||||||
.appendCommentLine(' 📙 After you choose any tweak, you can download or copy to execute your script.')
|
.appendCommentLine(' 📙 After you choose any tweak, you can download or copy to execute your script.')
|
||||||
.appendCommentLine(' 📙 Come back regularly to apply latest version for stronger privacy and security.')
|
.appendCommentLine(' 📙 Come back regularly to apply latest version for stronger privacy and security.')
|
||||||
.appendLine()
|
.appendLine()
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { parseApplication } from '@/application/Parser/ApplicationParser';
|
|||||||
import 'mocha';
|
import 'mocha';
|
||||||
import { expect } from 'chai';
|
import { expect } from 'chai';
|
||||||
import { parseCategory } from '@/application/Parser/CategoryParser';
|
import { parseCategory } from '@/application/Parser/CategoryParser';
|
||||||
|
import { RecommendationLevel } from '@/domain/RecommendationLevel';
|
||||||
|
|
||||||
describe('ApplicationParser', () => {
|
describe('ApplicationParser', () => {
|
||||||
describe('parseApplication', () => {
|
describe('parseApplication', () => {
|
||||||
@@ -86,19 +87,22 @@ describe('ApplicationParser', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function getTestCategory(scriptName = 'testScript'): YamlCategory {
|
function getTestCategory(scriptPrefix = 'testScript'): YamlCategory {
|
||||||
return {
|
return {
|
||||||
category: 'category name',
|
category: 'category name',
|
||||||
children: [ getTestScript(scriptName) ],
|
children: [
|
||||||
|
getTestScript(`${scriptPrefix}-standard`, RecommendationLevel.Standard),
|
||||||
|
getTestScript(`${scriptPrefix}-strict`, RecommendationLevel.Strict),
|
||||||
|
],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function getTestScript(scriptName: string): YamlScript {
|
function getTestScript(scriptName: string, level: RecommendationLevel = RecommendationLevel.Standard): YamlScript {
|
||||||
return {
|
return {
|
||||||
name: scriptName,
|
name: scriptName,
|
||||||
code: 'script code',
|
code: 'script code',
|
||||||
revertCode: 'revert code',
|
revertCode: 'revert code',
|
||||||
recommend: true,
|
recommend: RecommendationLevel[level].toLowerCase(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { parseCategory } from '@/application/Parser/CategoryParser';
|
|||||||
import { YamlCategory, CategoryOrScript, YamlScript } from 'js-yaml-loader!./application.yaml';
|
import { YamlCategory, CategoryOrScript, YamlScript } from 'js-yaml-loader!./application.yaml';
|
||||||
import { parseScript } from '@/application/Parser/ScriptParser';
|
import { parseScript } from '@/application/Parser/ScriptParser';
|
||||||
import { parseDocUrls } from '@/application/Parser/DocumentationParser';
|
import { parseDocUrls } from '@/application/Parser/DocumentationParser';
|
||||||
|
import { RecommendationLevel } from '@/domain/RecommendationLevel';
|
||||||
|
|
||||||
describe('CategoryParser', () => {
|
describe('CategoryParser', () => {
|
||||||
describe('parseCategory', () => {
|
describe('parseCategory', () => {
|
||||||
@@ -104,6 +105,6 @@ function getTestScript(): YamlScript {
|
|||||||
name: 'script name',
|
name: 'script name',
|
||||||
code: 'script code',
|
code: 'script code',
|
||||||
revertCode: 'revert code',
|
revertCode: 'revert code',
|
||||||
recommend: true,
|
recommend: RecommendationLevel[RecommendationLevel.Standard],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,26 +3,116 @@ import 'mocha';
|
|||||||
import { expect } from 'chai';
|
import { expect } from 'chai';
|
||||||
import { parseScript } from '@/application/Parser/ScriptParser';
|
import { parseScript } from '@/application/Parser/ScriptParser';
|
||||||
import { parseDocUrls } from '@/application/Parser/DocumentationParser';
|
import { parseDocUrls } from '@/application/Parser/DocumentationParser';
|
||||||
|
import { RecommendationLevelNames, RecommendationLevel } from '@/domain/RecommendationLevel';
|
||||||
|
|
||||||
describe('ScriptParser', () => {
|
describe('ScriptParser', () => {
|
||||||
describe('parseScript', () => {
|
describe('parseScript', () => {
|
||||||
it('parseScript parses as expected', () => {
|
it('parses name as expected', () => {
|
||||||
// arrange
|
// arrange
|
||||||
const expected: YamlScript = {
|
const script = getValidScript();
|
||||||
name: 'expected name',
|
script.name = 'expected-name';
|
||||||
code: 'expected code',
|
|
||||||
revertCode: 'expected revert code',
|
|
||||||
docs: ['hello.com'],
|
|
||||||
recommend: true,
|
|
||||||
};
|
|
||||||
// act
|
// act
|
||||||
const actual = parseScript(expected);
|
const actual = parseScript(script);
|
||||||
// assert
|
// assert
|
||||||
expect(actual.name).to.equal(expected.name);
|
expect(actual.name).to.equal(script.name);
|
||||||
expect(actual.code).to.equal(expected.code);
|
});
|
||||||
expect(actual.revertCode).to.equal(expected.revertCode);
|
it('parses code as expected', () => {
|
||||||
expect(actual.documentationUrls).to.deep.equal(parseDocUrls(expected));
|
// arrange
|
||||||
expect(actual.isRecommended).to.equal(expected.recommend);
|
const script = getValidScript();
|
||||||
|
script.code = 'expected-code';
|
||||||
|
// act
|
||||||
|
const actual = parseScript(script);
|
||||||
|
// assert
|
||||||
|
expect(actual.code).to.equal(script.code);
|
||||||
|
});
|
||||||
|
it('parses revertCode as expected', () => {
|
||||||
|
// arrange
|
||||||
|
const script = getValidScript();
|
||||||
|
script.code = 'expected-code';
|
||||||
|
// act
|
||||||
|
const actual = parseScript(script);
|
||||||
|
// assert
|
||||||
|
expect(actual.revertCode).to.equal(script.revertCode);
|
||||||
|
});
|
||||||
|
it('parses docs as expected', () => {
|
||||||
|
// arrange
|
||||||
|
const script = getValidScript();
|
||||||
|
script.docs = [ 'https://expected-doc1.com', 'https://expected-doc2.com' ];
|
||||||
|
const expected = parseDocUrls(script);
|
||||||
|
// act
|
||||||
|
const actual = parseScript(script);
|
||||||
|
// assert
|
||||||
|
expect(actual.documentationUrls).to.deep.equal(expected);
|
||||||
|
});
|
||||||
|
describe('level', () => {
|
||||||
|
it('accepts undefined level', () => {
|
||||||
|
const undefinedLevels: string[] = [ '', undefined ];
|
||||||
|
undefinedLevels.forEach((undefinedLevel) => {
|
||||||
|
// arrange
|
||||||
|
const script = getValidScript();
|
||||||
|
script.recommend = undefinedLevel;
|
||||||
|
// act
|
||||||
|
const actual = parseScript(script);
|
||||||
|
// assert
|
||||||
|
expect(actual.level).to.equal(undefined);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('throws on unknown level', () => {
|
||||||
|
// arrange
|
||||||
|
const unknownLevel = 'boi';
|
||||||
|
const script = getValidScript();
|
||||||
|
script.recommend = unknownLevel;
|
||||||
|
// act
|
||||||
|
const act = () => parseScript(script);
|
||||||
|
// assert
|
||||||
|
expect(act).to.throw(`unknown level: "${unknownLevel}"`);
|
||||||
|
});
|
||||||
|
it('throws on non-string type', () => {
|
||||||
|
const nonStringTypes: any[] = [ 5, true ];
|
||||||
|
nonStringTypes.forEach((nonStringType) => {
|
||||||
|
// arrange
|
||||||
|
const script = getValidScript();
|
||||||
|
script.recommend = nonStringType;
|
||||||
|
// act
|
||||||
|
const act = () => parseScript(script);
|
||||||
|
// assert
|
||||||
|
expect(act).to.throw(`level must be a string but it was ${typeof nonStringType}`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('parses level as expected', () => {
|
||||||
|
for (const levelText of RecommendationLevelNames) {
|
||||||
|
it(levelText, () => {
|
||||||
|
// arrange
|
||||||
|
const expectedLevel = RecommendationLevel[levelText];
|
||||||
|
const script = getValidScript();
|
||||||
|
script.recommend = levelText;
|
||||||
|
// act
|
||||||
|
const actual = parseScript(script);
|
||||||
|
// assert
|
||||||
|
expect(actual.level).to.equal(expectedLevel);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
it('parses level case insensitive', () => {
|
||||||
|
// arrange
|
||||||
|
const script = getValidScript();
|
||||||
|
const expected = RecommendationLevel.Standard;
|
||||||
|
script.recommend = RecommendationLevel[expected].toUpperCase();
|
||||||
|
// act
|
||||||
|
const actual = parseScript(script);
|
||||||
|
// assert
|
||||||
|
expect(actual.level).to.equal(expected);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function getValidScript(): YamlScript {
|
||||||
|
return {
|
||||||
|
name: 'valid-name',
|
||||||
|
code: 'valid-code',
|
||||||
|
revertCode: 'expected revert code',
|
||||||
|
docs: ['hello.com'],
|
||||||
|
recommend: RecommendationLevel[RecommendationLevel.Standard].toLowerCase(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,25 +5,79 @@ import 'mocha';
|
|||||||
import { expect } from 'chai';
|
import { expect } from 'chai';
|
||||||
import { ProjectInformation } from '@/domain/ProjectInformation';
|
import { ProjectInformation } from '@/domain/ProjectInformation';
|
||||||
import { IProjectInformation } from '@/domain/IProjectInformation';
|
import { IProjectInformation } from '@/domain/IProjectInformation';
|
||||||
|
import { RecommendationLevel, RecommendationLevels } from '@/domain/RecommendationLevel';
|
||||||
|
|
||||||
describe('Application', () => {
|
describe('Application', () => {
|
||||||
it('getRecommendedScripts returns as expected', () => {
|
describe('getScriptsByLevel', () => {
|
||||||
// arrange
|
it('filters out scripts without levels', () => {
|
||||||
const expected = [
|
// arrange
|
||||||
new ScriptStub('S3').withIsRecommended(true),
|
const scriptsWithLevels = RecommendationLevels.map((level, index) =>
|
||||||
new ScriptStub('S4').withIsRecommended(true),
|
new ScriptStub(`Script${index}`).withLevel(level),
|
||||||
];
|
);
|
||||||
const sut = new Application(createInformation(), [
|
const toIgnore = new ScriptStub('script-to-ignore').withLevel(undefined);
|
||||||
new CategoryStub(3).withScripts(expected[0], new ScriptStub('S1').withIsRecommended(false)),
|
for (const currentLevel of RecommendationLevels) {
|
||||||
new CategoryStub(2).withScripts(expected[1], new ScriptStub('S2').withIsRecommended(false)),
|
const category = new CategoryStub(0)
|
||||||
]);
|
.withScripts(...scriptsWithLevels)
|
||||||
// act
|
.withScript(toIgnore);
|
||||||
const actual = sut.getRecommendedScripts();
|
const sut = new Application(createInformation(), [category]);
|
||||||
// assert
|
// act
|
||||||
expect(expected[0]).to.deep.equal(actual[0]);
|
const actual = sut.getScriptsByLevel(currentLevel);
|
||||||
expect(expected[1]).to.deep.equal(actual[1]);
|
// assert
|
||||||
|
expect(actual).to.not.include(toIgnore);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
it(`${RecommendationLevel[RecommendationLevel.Standard]} filters ${RecommendationLevel[RecommendationLevel.Strict]}`, () => {
|
||||||
|
// arrange
|
||||||
|
const level = RecommendationLevel.Standard;
|
||||||
|
const expected = [
|
||||||
|
new ScriptStub('S1').withLevel(level),
|
||||||
|
new ScriptStub('S2').withLevel(level),
|
||||||
|
];
|
||||||
|
const sut = new Application(createInformation(), [
|
||||||
|
new CategoryStub(3).withScripts(...expected,
|
||||||
|
new ScriptStub('S3').withLevel(RecommendationLevel.Strict)),
|
||||||
|
]);
|
||||||
|
// act
|
||||||
|
const actual = sut.getScriptsByLevel(level);
|
||||||
|
// assert
|
||||||
|
expect(expected).to.deep.equal(actual);
|
||||||
|
});
|
||||||
|
it(`${RecommendationLevel[RecommendationLevel.Strict]} includes ${RecommendationLevel[RecommendationLevel.Standard]}`, () => {
|
||||||
|
// arrange
|
||||||
|
const level = RecommendationLevel.Strict;
|
||||||
|
const expected = [
|
||||||
|
new ScriptStub('S1').withLevel(RecommendationLevel.Standard),
|
||||||
|
new ScriptStub('S2').withLevel(RecommendationLevel.Strict),
|
||||||
|
];
|
||||||
|
const sut = new Application(createInformation(), [
|
||||||
|
new CategoryStub(3).withScripts(...expected),
|
||||||
|
]);
|
||||||
|
// act
|
||||||
|
const actual = sut.getScriptsByLevel(level);
|
||||||
|
// assert
|
||||||
|
expect(expected).to.deep.equal(actual);
|
||||||
|
});
|
||||||
|
it('throws when level is undefined', () => {
|
||||||
|
// arrange
|
||||||
|
const sut = new Application(createInformation(), [ getCategoryForValidApplication() ]);
|
||||||
|
// act
|
||||||
|
const act = () => sut.getScriptsByLevel(undefined);
|
||||||
|
// assert
|
||||||
|
expect(act).to.throw('undefined level');
|
||||||
|
});
|
||||||
|
it('throws when level is out of range', () => {
|
||||||
|
// arrange
|
||||||
|
const invalidValue = 66;
|
||||||
|
const sut = new Application(createInformation(), [
|
||||||
|
getCategoryForValidApplication(),
|
||||||
|
]);
|
||||||
|
// act
|
||||||
|
const act = () => sut.getScriptsByLevel(invalidValue);
|
||||||
|
// assert
|
||||||
|
expect(act).to.throw(`invalid level: ${invalidValue}`);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
describe('parameter validation', () => {
|
describe('ctor', () => {
|
||||||
it('cannot construct without categories', () => {
|
it('cannot construct without categories', () => {
|
||||||
// arrange
|
// arrange
|
||||||
const categories = [];
|
const categories = [];
|
||||||
@@ -43,20 +97,24 @@ describe('Application', () => {
|
|||||||
// assert
|
// assert
|
||||||
expect(construct).to.throw('Application must consist of at least one script');
|
expect(construct).to.throw('Application must consist of at least one script');
|
||||||
});
|
});
|
||||||
it('cannot construct without any recommended scripts', () => {
|
describe('cannot construct without any recommended scripts', () => {
|
||||||
// arrange
|
for (const missingLevel of RecommendationLevels) {
|
||||||
const categories = [
|
// arrange
|
||||||
new CategoryStub(3).withScripts(new ScriptStub('S1').withIsRecommended(false)),
|
const expectedError = `none of the scripts are recommended as ${RecommendationLevel[missingLevel]}`;
|
||||||
new CategoryStub(2).withScripts(new ScriptStub('S2').withIsRecommended(false)),
|
const otherLevels = RecommendationLevels.filter((level) => level !== missingLevel);
|
||||||
];
|
const categories = otherLevels.map((level, index) =>
|
||||||
// act
|
new CategoryStub(index).withScript(new ScriptStub(`Script${index}`).withLevel(level)),
|
||||||
function construct() { return new Application(createInformation(), categories); }
|
);
|
||||||
// assert
|
// act
|
||||||
expect(construct).to.throw('Application must consist of at least one recommended script');
|
const construct = () => new Application(createInformation(), categories);
|
||||||
|
// assert
|
||||||
|
expect(construct).to.throw(expectedError);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
it('cannot construct without information', () => {
|
it('cannot construct without information', () => {
|
||||||
// arrange
|
// arrange
|
||||||
const categories = [new CategoryStub(1).withScripts(new ScriptStub('S1').withIsRecommended(true))];
|
const categories = [ new CategoryStub(1).withScripts(
|
||||||
|
new ScriptStub('S1').withLevel(RecommendationLevel.Standard))];
|
||||||
const information = undefined;
|
const information = undefined;
|
||||||
// act
|
// act
|
||||||
function construct() { return new Application(information, categories); }
|
function construct() { return new Application(information, categories); }
|
||||||
@@ -64,42 +122,57 @@ describe('Application', () => {
|
|||||||
expect(construct).to.throw('info is undefined');
|
expect(construct).to.throw('info is undefined');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
it('totalScripts counts right', () => {
|
describe('totalScripts', () => {
|
||||||
// arrange
|
it('returns total of initial scripts', () => {
|
||||||
const categories = [
|
// arrange
|
||||||
new CategoryStub(1).withScripts(new ScriptStub('S1').withIsRecommended(true)),
|
const categories = [
|
||||||
new CategoryStub(2).withScripts(new ScriptStub('S2'), new ScriptStub('S3')),
|
new CategoryStub(1).withScripts(
|
||||||
new CategoryStub(3).withCategories(new CategoryStub(4).withScripts(new ScriptStub('S4'))),
|
new ScriptStub('S1').withLevel(RecommendationLevel.Standard)),
|
||||||
];
|
new CategoryStub(2).withScripts(
|
||||||
// act
|
new ScriptStub('S2'),
|
||||||
const sut = new Application(createInformation(), categories);
|
new ScriptStub('S3').withLevel(RecommendationLevel.Strict)),
|
||||||
// assert
|
new CategoryStub(3).withCategories(
|
||||||
expect(sut.totalScripts).to.equal(4);
|
new CategoryStub(4).withScripts(new ScriptStub('S4'))),
|
||||||
|
];
|
||||||
|
// act
|
||||||
|
const sut = new Application(createInformation(), categories);
|
||||||
|
// assert
|
||||||
|
expect(sut.totalScripts).to.equal(4);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
it('totalCategories counts right', () => {
|
describe('totalCategories', () => {
|
||||||
// arrange
|
it('returns total of initial categories', () => {
|
||||||
const categories = [
|
// arrange
|
||||||
new CategoryStub(1).withScripts(new ScriptStub('S1').withIsRecommended(true)),
|
const categories = [
|
||||||
new CategoryStub(2).withScripts(new ScriptStub('S2'), new ScriptStub('S3')),
|
new CategoryStub(1).withScripts(new ScriptStub('S1').withLevel(RecommendationLevel.Strict)),
|
||||||
new CategoryStub(3).withCategories(new CategoryStub(4).withScripts(new ScriptStub('S4'))),
|
new CategoryStub(2).withScripts(new ScriptStub('S2'), new ScriptStub('S3')),
|
||||||
];
|
new CategoryStub(3).withCategories(new CategoryStub(4).withScripts(new ScriptStub('S4'))),
|
||||||
// act
|
];
|
||||||
const sut = new Application(createInformation(), categories);
|
// act
|
||||||
// assert
|
const sut = new Application(createInformation(), categories);
|
||||||
expect(sut.totalCategories).to.equal(4);
|
// assert
|
||||||
|
expect(sut.totalCategories).to.equal(4);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
it('sets information as expected', () => {
|
describe('info', () => {
|
||||||
// arrange
|
it('returns initial information', () => {
|
||||||
const expected = createInformation();
|
// arrange
|
||||||
// act
|
const expected = createInformation();
|
||||||
const sut = new Application(
|
// act
|
||||||
expected,
|
const sut = new Application(
|
||||||
[new CategoryStub(1).withScripts(new ScriptStub('S1').withIsRecommended(true))]);
|
expected, [ getCategoryForValidApplication() ]);
|
||||||
// assert
|
// assert
|
||||||
expect(sut.info).to.deep.equal(expected);
|
expect(sut.info).to.deep.equal(expected);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function getCategoryForValidApplication() {
|
||||||
|
return new CategoryStub(1).withScripts(
|
||||||
|
new ScriptStub('S1').withLevel(RecommendationLevel.Standard),
|
||||||
|
new ScriptStub('S2').withLevel(RecommendationLevel.Strict));
|
||||||
|
}
|
||||||
|
|
||||||
function createInformation(): IProjectInformation {
|
function createInformation(): IProjectInformation {
|
||||||
return new ProjectInformation('name', 'repo', '0.1.0', 'homepage');
|
return new ProjectInformation('name', 'repo', '0.1.0', 'homepage');
|
||||||
}
|
}
|
||||||
|
|||||||
17
tests/unit/domain/RecommendationLevel.ts
Normal file
17
tests/unit/domain/RecommendationLevel.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import 'mocha';
|
||||||
|
import { expect } from 'chai';
|
||||||
|
import { RecommendationLevelNames, RecommendationLevel } from '@/domain/RecommendationLevel';
|
||||||
|
|
||||||
|
describe('RecommendationLevel', () => {
|
||||||
|
describe('RecommendationLevelNames', () => {
|
||||||
|
// arrange
|
||||||
|
const expected = [
|
||||||
|
RecommendationLevel[RecommendationLevel.Strict],
|
||||||
|
RecommendationLevel[RecommendationLevel.Standard],
|
||||||
|
];
|
||||||
|
// act
|
||||||
|
const actual = RecommendationLevelNames;
|
||||||
|
// assert
|
||||||
|
expect(actual).to.have.deep.members(expected);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import 'mocha';
|
import 'mocha';
|
||||||
import { expect } from 'chai';
|
import { expect } from 'chai';
|
||||||
import { Script } from '@/domain/Script';
|
import { Script } from '@/domain/Script';
|
||||||
|
import { RecommendationLevelNames, RecommendationLevel } from '@/domain/RecommendationLevel';
|
||||||
|
|
||||||
describe('Script', () => {
|
describe('Script', () => {
|
||||||
describe('ctor', () => {
|
describe('ctor', () => {
|
||||||
@@ -13,6 +14,11 @@ describe('Script', () => {
|
|||||||
const code = 'duplicate\n\n\ntest\nduplicate';
|
const code = 'duplicate\n\n\ntest\nduplicate';
|
||||||
expect(() => createWithCode(code)).to.throw();
|
expect(() => createWithCode(code)).to.throw();
|
||||||
});
|
});
|
||||||
|
it('sets as expected', () => {
|
||||||
|
const expected = 'expected-revert';
|
||||||
|
const sut = createWithCode(expected);
|
||||||
|
expect(sut.code).to.equal(expected);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
describe('revertCode', () => {
|
describe('revertCode', () => {
|
||||||
it('cannot construct with duplicate lines', () => {
|
it('cannot construct with duplicate lines', () => {
|
||||||
@@ -27,6 +33,11 @@ describe('Script', () => {
|
|||||||
const code = 'REM';
|
const code = 'REM';
|
||||||
expect(() => createWithCode(code, code)).to.throw();
|
expect(() => createWithCode(code, code)).to.throw();
|
||||||
});
|
});
|
||||||
|
it('sets as expected', () => {
|
||||||
|
const expected = 'expected-revert';
|
||||||
|
const sut = createWithCode('abc', expected);
|
||||||
|
expect(sut.revertCode).to.equal(expected);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
describe('canRevert', () => {
|
describe('canRevert', () => {
|
||||||
it('returns false without revert code', () => {
|
it('returns false without revert code', () => {
|
||||||
@@ -38,9 +49,28 @@ describe('Script', () => {
|
|||||||
expect(sut.canRevert()).to.equal(true);
|
expect(sut.canRevert()).to.equal(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
describe('level', () => {
|
||||||
|
it('cannot construct with invalid wrong value', () => {
|
||||||
|
expect(() => createWithLevel(55)).to.throw('invalid level');
|
||||||
|
});
|
||||||
|
it('sets undefined as expected', () => {
|
||||||
|
const sut = createWithLevel(undefined);
|
||||||
|
expect(sut.level).to.equal(undefined);
|
||||||
|
});
|
||||||
|
it('sets as expected', () => {
|
||||||
|
for (const expected of RecommendationLevelNames) {
|
||||||
|
const sut = createWithLevel(RecommendationLevel[expected]);
|
||||||
|
const actual = RecommendationLevel[sut.level];
|
||||||
|
expect(actual).to.equal(expected);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function createWithCode(code: string, revertCode?: string): Script {
|
function createWithCode(code: string, revertCode?: string): Script {
|
||||||
return new Script('name', code, revertCode, [], false);
|
return new Script('name', code, revertCode, [], RecommendationLevel.Standard);
|
||||||
|
}
|
||||||
|
function createWithLevel(level: RecommendationLevel): Script {
|
||||||
|
return new Script('name', 'code', 'revertCode', [], level);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,199 +4,200 @@ import { ILiquorTreeNode } from 'liquor-tree';
|
|||||||
import { NodeType } from '@/presentation/Scripts/ScriptsTree/SelectableTree/Node/INode';
|
import { NodeType } from '@/presentation/Scripts/ScriptsTree/SelectableTree/Node/INode';
|
||||||
import { getNewState } from '@/presentation/Scripts/ScriptsTree/SelectableTree/LiquorTree/NodeWrapper/NodeStateUpdater';
|
import { getNewState } from '@/presentation/Scripts/ScriptsTree/SelectableTree/LiquorTree/NodeWrapper/NodeStateUpdater';
|
||||||
|
|
||||||
describe('getNewState', () => {
|
describe('NodeStateUpdater', () => {
|
||||||
describe('checked', () => {
|
describe('getNewState', () => {
|
||||||
describe('script node', () => {
|
describe('checked', () => {
|
||||||
it('true when selected', () => {
|
describe('script node', () => {
|
||||||
// arrange
|
it('true when selected', () => {
|
||||||
const node = getScriptNode();
|
// arrange
|
||||||
const selectedScriptNodeIds = [ 'a', 'b', node.id, 'c' ];
|
const node = getScriptNode();
|
||||||
// act
|
const selectedScriptNodeIds = [ 'a', 'b', node.id, 'c' ];
|
||||||
const state = getNewState(node, selectedScriptNodeIds);
|
// act
|
||||||
// assert
|
const state = getNewState(node, selectedScriptNodeIds);
|
||||||
expect(state.checked).to.equal(true);
|
// assert
|
||||||
|
expect(state.checked).to.equal(true);
|
||||||
|
});
|
||||||
|
it('false when unselected', () => {
|
||||||
|
// arrange
|
||||||
|
const node = getScriptNode();
|
||||||
|
const selectedScriptNodeIds = [ 'a', 'b', 'c' ];
|
||||||
|
// act
|
||||||
|
const state = getNewState(node, selectedScriptNodeIds);
|
||||||
|
// assert
|
||||||
|
expect(state.checked).to.equal(false);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
it('false when unselected', () => {
|
describe('category node', () => {
|
||||||
// arrange
|
it('true when every child selected', () => {
|
||||||
const node = getScriptNode();
|
// arrange
|
||||||
const selectedScriptNodeIds = [ 'a', 'b', 'c' ];
|
const node = {
|
||||||
// act
|
id: '1',
|
||||||
const state = getNewState(node, selectedScriptNodeIds);
|
data: { type: NodeType.Category, documentationUrls: [], isReversible: false },
|
||||||
// assert
|
children: [
|
||||||
expect(state.checked).to.equal(false);
|
{ id: '2',
|
||||||
|
data: { type: NodeType.Category, documentationUrls: [], isReversible: false },
|
||||||
|
children: [ getScriptNode('a'), getScriptNode('b') ],
|
||||||
|
},
|
||||||
|
{ id: '3',
|
||||||
|
data: { type: NodeType.Category, documentationUrls: [], isReversible: false },
|
||||||
|
children: [ getScriptNode('c') ],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
const selectedScriptNodeIds = [ 'a', 'b', 'c' ];
|
||||||
|
// act
|
||||||
|
const state = getNewState(node, selectedScriptNodeIds);
|
||||||
|
// assert
|
||||||
|
expect(state.checked).to.equal(true);
|
||||||
|
});
|
||||||
|
it('false when none of the children is selected', () => {
|
||||||
|
// arrange
|
||||||
|
const node = {
|
||||||
|
id: '1',
|
||||||
|
data: { type: NodeType.Category, documentationUrls: [], isReversible: false },
|
||||||
|
children: [
|
||||||
|
{ id: '2',
|
||||||
|
data: { type: NodeType.Category, documentationUrls: [], isReversible: false },
|
||||||
|
children: [ getScriptNode('a'), getScriptNode('b') ],
|
||||||
|
},
|
||||||
|
{ id: '3',
|
||||||
|
data: { type: NodeType.Category, documentationUrls: [], isReversible: false },
|
||||||
|
children: [ getScriptNode('c') ],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
const selectedScriptNodeIds = [ 'none', 'of', 'them', 'are', 'selected' ];
|
||||||
|
// act
|
||||||
|
const state = getNewState(node, selectedScriptNodeIds);
|
||||||
|
// assert
|
||||||
|
expect(state.checked).to.equal(false);
|
||||||
|
});
|
||||||
|
it('false when some of the children is selected', () => {
|
||||||
|
// arrange
|
||||||
|
const node = {
|
||||||
|
id: '1',
|
||||||
|
data: { type: NodeType.Category, documentationUrls: [], isReversible: false },
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
data: { type: NodeType.Category, documentationUrls: [], isReversible: false },
|
||||||
|
children: [ getScriptNode('a'), getScriptNode('b') ],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '3',
|
||||||
|
data: { type: NodeType.Category, documentationUrls: [], isReversible: false },
|
||||||
|
children: [ getScriptNode('c') ],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
const selectedScriptNodeIds = [ 'a', 'c', 'unrelated' ];
|
||||||
|
// act
|
||||||
|
const state = getNewState(node, selectedScriptNodeIds);
|
||||||
|
// assert
|
||||||
|
expect(state.checked).to.equal(false);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('category node', () => {
|
describe('indeterminate', () => {
|
||||||
it('true when every child selected', () => {
|
describe('script node', () => {
|
||||||
// arrange
|
it('false when selected', () => {
|
||||||
const node = {
|
// arrange
|
||||||
id: '1',
|
const node = getScriptNode();
|
||||||
data: { type: NodeType.Category, documentationUrls: [], isReversible: false },
|
const selectedScriptNodeIds = [ 'a', 'b', node.id, 'c' ];
|
||||||
children: [
|
// act
|
||||||
{ id: '2',
|
const state = getNewState(node, selectedScriptNodeIds);
|
||||||
data: { type: NodeType.Category, documentationUrls: [], isReversible: false },
|
// assert
|
||||||
children: [ getScriptNode('a'), getScriptNode('b') ],
|
expect(state.indeterminate).to.equal(false);
|
||||||
},
|
});
|
||||||
{ id: '3',
|
it('false when not selected', () => {
|
||||||
data: { type: NodeType.Category, documentationUrls: [], isReversible: false },
|
// arrange
|
||||||
children: [ getScriptNode('c') ],
|
const node = getScriptNode();
|
||||||
},
|
const selectedScriptNodeIds = [ 'a', 'b', 'c' ];
|
||||||
],
|
// act
|
||||||
};
|
const state = getNewState(node, selectedScriptNodeIds);
|
||||||
const selectedScriptNodeIds = [ 'a', 'b', 'c' ];
|
// assert
|
||||||
// act
|
expect(state.indeterminate).to.equal(false);
|
||||||
const state = getNewState(node, selectedScriptNodeIds);
|
});
|
||||||
// assert
|
|
||||||
expect(state.checked).to.equal(true);
|
|
||||||
});
|
});
|
||||||
it('false when none of the children is selected', () => {
|
describe('category node', () => {
|
||||||
// arrange
|
it('false when all children are selected', () => {
|
||||||
const node = {
|
// arrange
|
||||||
id: '1',
|
const node = {
|
||||||
data: { type: NodeType.Category, documentationUrls: [], isReversible: false },
|
id: '1',
|
||||||
children: [
|
data: { type: NodeType.Category, documentationUrls: [], isReversible: false },
|
||||||
{ id: '2',
|
children: [
|
||||||
data: { type: NodeType.Category, documentationUrls: [], isReversible: false },
|
{ id: '2',
|
||||||
children: [ getScriptNode('a'), getScriptNode('b') ],
|
data: { type: NodeType.Category, documentationUrls: [], isReversible: false },
|
||||||
},
|
children: [ getScriptNode('a'), getScriptNode('b') ],
|
||||||
{ id: '3',
|
},
|
||||||
data: { type: NodeType.Category, documentationUrls: [], isReversible: false },
|
{ id: '3',
|
||||||
children: [ getScriptNode('c') ],
|
data: { type: NodeType.Category, documentationUrls: [], isReversible: false },
|
||||||
},
|
children: [ getScriptNode('c') ],
|
||||||
],
|
},
|
||||||
};
|
],
|
||||||
const selectedScriptNodeIds = [ 'none', 'of', 'them', 'are', 'selected' ];
|
};
|
||||||
// act
|
const selectedScriptNodeIds = [ 'a', 'b', 'c' ];
|
||||||
const state = getNewState(node, selectedScriptNodeIds);
|
// act
|
||||||
// assert
|
const state = getNewState(node, selectedScriptNodeIds);
|
||||||
expect(state.checked).to.equal(false);
|
// assert
|
||||||
});
|
expect(state.indeterminate).to.equal(false);
|
||||||
it('false when some of the children is selected', () => {
|
});
|
||||||
// arrange
|
it('true when all some are selected', () => {
|
||||||
const node = {
|
// arrange
|
||||||
id: '1',
|
const node = {
|
||||||
data: { type: NodeType.Category, documentationUrls: [], isReversible: false },
|
id: '1',
|
||||||
children: [
|
data: { type: NodeType.Category, documentationUrls: [], isReversible: false },
|
||||||
{
|
children: [
|
||||||
id: '2',
|
{ id: '2',
|
||||||
data: { type: NodeType.Category, documentationUrls: [], isReversible: false },
|
data: { type: NodeType.Category, documentationUrls: [], isReversible: false },
|
||||||
children: [ getScriptNode('a'), getScriptNode('b') ],
|
children: [ getScriptNode('a'), getScriptNode('b') ],
|
||||||
},
|
},
|
||||||
{
|
{ id: '3',
|
||||||
id: '3',
|
data: { type: NodeType.Category, documentationUrls: [], isReversible: false },
|
||||||
data: { type: NodeType.Category, documentationUrls: [], isReversible: false },
|
children: [ getScriptNode('c') ],
|
||||||
children: [ getScriptNode('c') ],
|
},
|
||||||
},
|
],
|
||||||
],
|
};
|
||||||
};
|
const selectedScriptNodeIds = [ 'a' ];
|
||||||
const selectedScriptNodeIds = [ 'a', 'c', 'unrelated' ];
|
// act
|
||||||
// act
|
const state = getNewState(node, selectedScriptNodeIds);
|
||||||
const state = getNewState(node, selectedScriptNodeIds);
|
// assert
|
||||||
// assert
|
expect(state.indeterminate).to.equal(true);
|
||||||
expect(state.checked).to.equal(false);
|
});
|
||||||
});
|
it('false when no children are selected', () => {
|
||||||
});
|
// arrange
|
||||||
});
|
const node = {
|
||||||
describe('indeterminate', () => {
|
id: '1',
|
||||||
describe('script node', () => {
|
data: { type: NodeType.Category, documentationUrls: [], isReversible: false },
|
||||||
it('false when selected', () => {
|
children: [
|
||||||
// arrange
|
{ id: '2',
|
||||||
const node = getScriptNode();
|
data: { type: NodeType.Category, documentationUrls: [], isReversible: false },
|
||||||
const selectedScriptNodeIds = [ 'a', 'b', node.id, 'c' ];
|
children: [ getScriptNode('a'), getScriptNode('b') ],
|
||||||
// act
|
},
|
||||||
const state = getNewState(node, selectedScriptNodeIds);
|
{ id: '3',
|
||||||
// assert
|
data: { type: NodeType.Category, documentationUrls: [], isReversible: false },
|
||||||
expect(state.indeterminate).to.equal(false);
|
children: [ getScriptNode('c') ],
|
||||||
});
|
},
|
||||||
it('false when not selected', () => {
|
],
|
||||||
// arrange
|
};
|
||||||
const node = getScriptNode();
|
const selectedScriptNodeIds = [ 'none', 'of', 'them', 'are', 'selected' ];
|
||||||
const selectedScriptNodeIds = [ 'a', 'b', 'c' ];
|
// act
|
||||||
// act
|
const state = getNewState(node, selectedScriptNodeIds);
|
||||||
const state = getNewState(node, selectedScriptNodeIds);
|
// assert
|
||||||
// assert
|
expect(state.indeterminate).to.equal(false);
|
||||||
expect(state.indeterminate).to.equal(false);
|
});
|
||||||
});
|
|
||||||
});
|
|
||||||
describe('category node', () => {
|
|
||||||
it('false when all children are selected', () => {
|
|
||||||
// arrange
|
|
||||||
const node = {
|
|
||||||
id: '1',
|
|
||||||
data: { type: NodeType.Category, documentationUrls: [], isReversible: false },
|
|
||||||
children: [
|
|
||||||
{ id: '2',
|
|
||||||
data: { type: NodeType.Category, documentationUrls: [], isReversible: false },
|
|
||||||
children: [ getScriptNode('a'), getScriptNode('b') ],
|
|
||||||
},
|
|
||||||
{ id: '3',
|
|
||||||
data: { type: NodeType.Category, documentationUrls: [], isReversible: false },
|
|
||||||
children: [ getScriptNode('c') ],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
const selectedScriptNodeIds = [ 'a', 'b', 'c' ];
|
|
||||||
// act
|
|
||||||
const state = getNewState(node, selectedScriptNodeIds);
|
|
||||||
// assert
|
|
||||||
expect(state.indeterminate).to.equal(false);
|
|
||||||
});
|
|
||||||
it('true when all some are selected', () => {
|
|
||||||
// arrange
|
|
||||||
const node = {
|
|
||||||
id: '1',
|
|
||||||
data: { type: NodeType.Category, documentationUrls: [], isReversible: false },
|
|
||||||
children: [
|
|
||||||
{ id: '2',
|
|
||||||
data: { type: NodeType.Category, documentationUrls: [], isReversible: false },
|
|
||||||
children: [ getScriptNode('a'), getScriptNode('b') ],
|
|
||||||
},
|
|
||||||
{ id: '3',
|
|
||||||
data: { type: NodeType.Category, documentationUrls: [], isReversible: false },
|
|
||||||
children: [ getScriptNode('c') ],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
const selectedScriptNodeIds = [ 'a' ];
|
|
||||||
// act
|
|
||||||
const state = getNewState(node, selectedScriptNodeIds);
|
|
||||||
// assert
|
|
||||||
expect(state.indeterminate).to.equal(true);
|
|
||||||
});
|
|
||||||
it('false when no children are selected', () => {
|
|
||||||
// arrange
|
|
||||||
const node = {
|
|
||||||
id: '1',
|
|
||||||
data: { type: NodeType.Category, documentationUrls: [], isReversible: false },
|
|
||||||
children: [
|
|
||||||
{ id: '2',
|
|
||||||
data: { type: NodeType.Category, documentationUrls: [], isReversible: false },
|
|
||||||
children: [ getScriptNode('a'), getScriptNode('b') ],
|
|
||||||
},
|
|
||||||
{ id: '3',
|
|
||||||
data: { type: NodeType.Category, documentationUrls: [], isReversible: false },
|
|
||||||
children: [ getScriptNode('c') ],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
const selectedScriptNodeIds = [ 'none', 'of', 'them', 'are', 'selected' ];
|
|
||||||
// act
|
|
||||||
const state = getNewState(node, selectedScriptNodeIds);
|
|
||||||
// assert
|
|
||||||
expect(state.indeterminate).to.equal(false);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
function getScriptNode(scriptNodeId: string = 'script'): ILiquorTreeNode {
|
||||||
|
return {
|
||||||
|
id: scriptNodeId,
|
||||||
|
data: {
|
||||||
|
type: NodeType.Script,
|
||||||
|
documentationUrls: [],
|
||||||
|
isReversible: false,
|
||||||
|
},
|
||||||
|
children: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
function getScriptNode(scriptNodeId: string = 'script'): ILiquorTreeNode {
|
|
||||||
return {
|
|
||||||
id: scriptNodeId,
|
|
||||||
data: {
|
|
||||||
type: NodeType.Script,
|
|
||||||
documentationUrls: [],
|
|
||||||
isReversible: false,
|
|
||||||
},
|
|
||||||
children: [],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -15,8 +15,8 @@ export class ApplicationStub implements IApplication {
|
|||||||
return this.getAllCategories().find(
|
return this.getAllCategories().find(
|
||||||
(category) => category.id === categoryId);
|
(category) => category.id === categoryId);
|
||||||
}
|
}
|
||||||
public getRecommendedScripts(): readonly IScript[] {
|
public getScriptsByLevel(): readonly IScript[] {
|
||||||
throw new Error('Method not implemented: getRecommendedScripts');
|
throw new Error('Method not implemented: getScriptsByLevel');
|
||||||
}
|
}
|
||||||
public findScript(scriptId: string): IScript {
|
public findScript(scriptId: string): IScript {
|
||||||
return this.getAllScripts().find((script) => scriptId === script.id);
|
return this.getAllScripts().find((script) => scriptId === script.id);
|
||||||
|
|||||||
@@ -1,32 +1,37 @@
|
|||||||
import { BaseEntity } from '@/infrastructure/Entity/BaseEntity';
|
import { BaseEntity } from '@/infrastructure/Entity/BaseEntity';
|
||||||
import { IScript } from '@/domain/IScript';
|
import { IScript } from '@/domain/IScript';
|
||||||
|
import { RecommendationLevel } from '@/domain/RecommendationLevel';
|
||||||
|
|
||||||
export class ScriptStub extends BaseEntity<string> implements IScript {
|
export class ScriptStub extends BaseEntity<string> implements IScript {
|
||||||
public name = `name${this.id}`;
|
public name = `name${this.id}`;
|
||||||
public code = `REM code${this.id}`;
|
public code = `REM code${this.id}`;
|
||||||
public revertCode = `REM revertCode${this.id}`;
|
public revertCode = `REM revertCode${this.id}`;
|
||||||
public readonly documentationUrls = new Array<string>();
|
public readonly documentationUrls = new Array<string>();
|
||||||
public isRecommended = true;
|
public level = RecommendationLevel.Standard;
|
||||||
|
|
||||||
constructor(public readonly id: string) {
|
constructor(public readonly id: string) {
|
||||||
super(id);
|
super(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public canRevert(): boolean {
|
public canRevert(): boolean {
|
||||||
return Boolean(this.revertCode);
|
return Boolean(this.revertCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
public withIsRecommended(value: boolean): ScriptStub {
|
public withLevel(value: RecommendationLevel): ScriptStub {
|
||||||
this.isRecommended = value;
|
this.level = value;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public withCode(value: string): ScriptStub {
|
public withCode(value: string): ScriptStub {
|
||||||
this.code = value;
|
this.code = value;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public withName(name: string): ScriptStub {
|
public withName(name: string): ScriptStub {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public withRevertCode(revertCode: string): ScriptStub {
|
public withRevertCode(revertCode: string): ScriptStub {
|
||||||
this.revertCode = revertCode;
|
this.revertCode = revertCode;
|
||||||
return this;
|
return this;
|
||||||
|
|||||||
Reference in New Issue
Block a user