Compare commits
30 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7dd15ed064 | ||
|
|
d169434157 | ||
|
|
6efed72bf2 | ||
|
|
15db311801 | ||
|
|
82d509129b | ||
|
|
939d838e35 | ||
|
|
6de4ce58c4 | ||
|
|
ee66196d9a | ||
|
|
3c13a9e837 | ||
|
|
22b23a9ece | ||
|
|
4ae385b7fc | ||
|
|
d9abc7f0b2 | ||
|
|
1f19b2528a | ||
|
|
1f11c39773 | ||
|
|
b6ccb5927a | ||
|
|
1d465ee318 | ||
|
|
3ab48b1cf5 | ||
|
|
de4ac978bd | ||
|
|
8df5faf4ef | ||
|
|
99a2035fdb | ||
|
|
a0d61728ea | ||
|
|
312bf6102c | ||
|
|
f4885b6f1c | ||
|
|
ca63a0979e | ||
|
|
1f266c3353 | ||
|
|
c7b2a70312 | ||
|
|
255133af4d | ||
|
|
db74531cd4 | ||
|
|
f36d8bfc78 | ||
|
|
3b31ace726 |
2
.github/workflows/quality-checks.yaml
vendored
2
.github/workflows/quality-checks.yaml
vendored
@@ -1,6 +1,6 @@
|
|||||||
name: Quality checks
|
name: Quality checks
|
||||||
|
|
||||||
on: push
|
on: [ push, pull_request ]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
lint:
|
lint:
|
||||||
|
|||||||
1
.github/workflows/security-checks.yaml
vendored
1
.github/workflows/security-checks.yaml
vendored
@@ -2,6 +2,7 @@ name: Security checks
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
|
pull_request:
|
||||||
schedule:
|
schedule:
|
||||||
- cron: '0 0 * * 0'
|
- cron: '0 0 * * 0'
|
||||||
|
|
||||||
|
|||||||
2
.github/workflows/test.yaml
vendored
2
.github/workflows/test.yaml
vendored
@@ -1,6 +1,6 @@
|
|||||||
name: Test
|
name: Test
|
||||||
|
|
||||||
on: push
|
on: [ push, pull_request ]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
run-tests:
|
run-tests:
|
||||||
|
|||||||
44
CHANGELOG.md
44
CHANGELOG.md
@@ -1,5 +1,49 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 0.7.4 (2020-09-12)
|
||||||
|
|
||||||
|
* fix checked checkbox has blue border | [commit](https://github.com/undergroundwires/privacy.sexy/commit/4ae385b7fcea9014a68442714b7d99e2ee7df7d0)
|
||||||
|
* fix spectre protection getting single lined #31 | [commit](https://github.com/undergroundwires/privacy.sexy/commit/22b23a9ece446c7f9abd4ede293051eb616ad50a)
|
||||||
|
* fix missing reg value in denying app access to account | [commit](https://github.com/undergroundwires/privacy.sexy/commit/3c13a9e837e06e097450b31d7eb0c0e6bf20cefb)
|
||||||
|
* fix wrong path in clear all firefox user profile settings | [commit](https://github.com/undergroundwires/privacy.sexy/commit/ee66196d9a60f27d17ae7f62d02b4f119a47e6e0)
|
||||||
|
|
||||||
|
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.7.3...0.7.4)
|
||||||
|
|
||||||
|
## 0.7.3 (2020-09-12)
|
||||||
|
|
||||||
|
* fix vscode settings file override and add more configs | [commit](https://github.com/undergroundwires/privacy.sexy/commit/a0d61728ead04b4455437f85820121a848db9e00)
|
||||||
|
* fix nvidia tweak error message, categorize and add reversibility | [commit](https://github.com/undergroundwires/privacy.sexy/commit/99a2035fdb0766a4dfc2753133eab0d7666516cd)
|
||||||
|
* improve CPU specific tweaks by conditional platform checks and reversibility | [commit](https://github.com/undergroundwires/privacy.sexy/commit/8df5faf4ef05a49da63973bd0fbb5c5d07d5bd93)
|
||||||
|
* fix wrong path to the main telemetry file | [commit](https://github.com/undergroundwires/privacy.sexy/commit/de4ac978bdda79573b36d355697b8a028d2c0beb)
|
||||||
|
* fix naming of firefox cleanup to mention profiles | [commit](https://github.com/undergroundwires/privacy.sexy/commit/3ab48b1cf5f7f934f07e468ef2318ccee07f530c)
|
||||||
|
* add reversibility and more scripts to denying app access with better structure | [commit](https://github.com/undergroundwires/privacy.sexy/commit/1d465ee3189d0e5a827453b3f0eb4361efe23770)
|
||||||
|
* fix comment lines are being detected as duplicate in validation | [commit](https://github.com/undergroundwires/privacy.sexy/commit/b6ccb5927a20412976a54fd2215eb645092f98a8)
|
||||||
|
* add more detailed error message | [commit](https://github.com/undergroundwires/privacy.sexy/commit/1f11c39773c12eccfb3efb898b58c2f6f37ab9ca)
|
||||||
|
* fix typo in a test | [commit](https://github.com/undergroundwires/privacy.sexy/commit/1f19b2528a69383e63e579d2885f01cd804abf6c)
|
||||||
|
|
||||||
|
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.7.2...0.7.3)
|
||||||
|
|
||||||
|
## 0.7.2 (2020-09-06)
|
||||||
|
|
||||||
|
* update onesync documentation and do not recommend it as it breaks other apps | [commit](https://github.com/undergroundwires/privacy.sexy/commit/f36d8bfc7848bb65ac0c641e318a689bf3816ccf)
|
||||||
|
* add reversibility for biometric disabling and do not recommend it | [commit](https://github.com/undergroundwires/privacy.sexy/commit/db74531cd4139615c6d595959217d3651f099019)
|
||||||
|
* fix bad highlighting of selected nodes when using keyboard navigation | [commit](https://github.com/undergroundwires/privacy.sexy/commit/255133af4dfae40171406648a3e2920f16d71cb3)
|
||||||
|
* add reversibility to removing bloatware | [commit](https://github.com/undergroundwires/privacy.sexy/commit/c7b2a703128470a05f12c9c6e8002444def37ef8)
|
||||||
|
* fix indeterminate state being lost | [commit](https://github.com/undergroundwires/privacy.sexy/commit/1f266c33535f72b69c65985bf2eff27cd2c5a104)
|
||||||
|
* fix wording in default text in text area | [commit](https://github.com/undergroundwires/privacy.sexy/commit/ca63a0979ef55d07d09d9443e5cea9aa888870a5)
|
||||||
|
* add best practice suggestion to come back | [commit](https://github.com/undergroundwires/privacy.sexy/commit/f4885b6f1c82752f2143934e336d6d1b1af03015)
|
||||||
|
|
||||||
|
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.7.1...0.7.2)
|
||||||
|
|
||||||
|
## 0.7.1 (2020-09-04)
|
||||||
|
|
||||||
|
* fix some browsers (including firefox) downloading the script as a text file | [commit](https://github.com/undergroundwires/privacy.sexy/commit/8c17929151f9c4fa5f48564492bbf400ced95eea)
|
||||||
|
* rename screenshot image file | [commit](https://github.com/undergroundwires/privacy.sexy/commit/b8682a852a14ed6cf49986695d9510b840ac9d3d)
|
||||||
|
* fix new/changed script higlighting not working on production builds | [commit](https://github.com/undergroundwires/privacy.sexy/commit/8c38dd73d8c7b77d8d341c0389f4d7229f9b97fd)
|
||||||
|
* refactor unused imports | [commit](https://github.com/undergroundwires/privacy.sexy/commit/6badfef9daace0c5de3fd33652a82bfe22261b11)
|
||||||
|
|
||||||
|
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.7.0...0.7.1)
|
||||||
|
|
||||||
## 0.7.0 (2020-09-02)
|
## 0.7.0 (2020-09-02)
|
||||||
|
|
||||||
* [search] better (multilined) message when there are no results | [commit](https://github.com/undergroundwires/privacy.sexy/commit/ec15af01dd020b364c2174fe562fd66227c2320c)
|
* [search] better (multilined) message when there are no results | [commit](https://github.com/undergroundwires/privacy.sexy/commit/ec15af01dd020b364c2174fe562fd66227c2320c)
|
||||||
|
|||||||
@@ -15,7 +15,8 @@
|
|||||||
## Get started
|
## Get started
|
||||||
|
|
||||||
- Online version: [https://privacy.sexy](https://privacy.sexy)
|
- Online version: [https://privacy.sexy](https://privacy.sexy)
|
||||||
- or download latest desktop version for [Windows](https://github.com/undergroundwires/privacy.sexy/releases/download/0.7.0/privacy.sexy-Setup-0.7.0.exe), [Linux](https://github.com/undergroundwires/privacy.sexy/releases/download/0.7.0/privacy.sexy-0.7.0.AppImage), [macOS](https://github.com/undergroundwires/privacy.sexy/releases/download/0.7.0/privacy.sexy-0.7.0.dmg)
|
- or download latest desktop version for [Windows](https://github.com/undergroundwires/privacy.sexy/releases/download/0.7.4/privacy.sexy-Setup-0.7.4.exe), [Linux](https://github.com/undergroundwires/privacy.sexy/releases/download/0.7.4/privacy.sexy-0.7.4.AppImage), [macOS](https://github.com/undergroundwires/privacy.sexy/releases/download/0.7.4/privacy.sexy-0.7.4.dmg)
|
||||||
|
- 💡 Come back regularly to apply latest version for stronger privacy and security.
|
||||||
|
|
||||||
[](https://privacy.sexy)
|
[](https://privacy.sexy)
|
||||||
|
|
||||||
@@ -48,8 +49,8 @@
|
|||||||
- Development: `npm run serve` to compile & hot-reload for development.
|
- Development: `npm run serve` to compile & hot-reload for development.
|
||||||
- Production: `npm run build` to prepare files for distribution.
|
- Production: `npm run build` to prepare files for distribution.
|
||||||
- Or run using Docker:
|
- Or run using Docker:
|
||||||
1. Build: `docker build -t undergroundwires/privacy.sexy:0.7.0 .`
|
1. Build: `docker build -t undergroundwires/privacy.sexy:0.7.4 .`
|
||||||
2. Run: `docker run -it -p 8080:80 --rm --name privacy.sexy-0.7.0 undergroundwires/privacy.sexy:0.7.0`
|
2. Run: `docker run -it -p 8080:80 --rm --name privacy.sexy-0.7.4 undergroundwires/privacy.sexy:0.7.4`
|
||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "privacy.sexy",
|
"name": "privacy.sexy",
|
||||||
"version": "0.7.0",
|
"version": "0.7.4",
|
||||||
"author": "undergroundwires",
|
"author": "undergroundwires",
|
||||||
"description": "Enforce privacy & security best-practices on Windows, because privacy is sexy 🍑🍆",
|
"description": "Enforce privacy & security best-practices on Windows, because privacy is sexy 🍑🍆",
|
||||||
"private": true,
|
"private": true,
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ function parseCategoryChild(
|
|||||||
children.subScripts.push(script);
|
children.subScripts.push(script);
|
||||||
} else {
|
} else {
|
||||||
throw new Error(`Child element is neither a category or a script.
|
throw new Error(`Child element is neither a category or a script.
|
||||||
Parent: ${parent.category}, element: ${categoryOrScript}`);
|
Parent: ${parent.category}, element: ${JSON.stringify(categoryOrScript)}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { IFilterResult } from './IFilterResult';
|
|||||||
import { ISignal } from '@/infrastructure/Events/Signal';
|
import { ISignal } from '@/infrastructure/Events/Signal';
|
||||||
|
|
||||||
export interface IUserFilter {
|
export interface IUserFilter {
|
||||||
|
readonly currentFilter: IFilterResult | undefined;
|
||||||
readonly filtered: ISignal<IFilterResult>;
|
readonly filtered: ISignal<IFilterResult>;
|
||||||
readonly filterRemoved: ISignal<void>;
|
readonly filterRemoved: ISignal<void>;
|
||||||
setFilter(filter: string): void;
|
setFilter(filter: string): void;
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { Signal } from '@/infrastructure/Events/Signal';
|
|||||||
export class UserFilter implements IUserFilter {
|
export class UserFilter implements IUserFilter {
|
||||||
public readonly filtered = new Signal<IFilterResult>();
|
public readonly filtered = new Signal<IFilterResult>();
|
||||||
public readonly filterRemoved = new Signal<void>();
|
public readonly filterRemoved = new Signal<void>();
|
||||||
|
public currentFilter: IFilterResult | undefined;
|
||||||
|
|
||||||
constructor(private application: IApplication) {
|
constructor(private application: IApplication) {
|
||||||
|
|
||||||
@@ -28,11 +29,12 @@ export class UserFilter implements IUserFilter {
|
|||||||
filteredCategories,
|
filteredCategories,
|
||||||
filter,
|
filter,
|
||||||
);
|
);
|
||||||
|
this.currentFilter = matches;
|
||||||
this.filtered.notify(matches);
|
this.filtered.notify(matches);
|
||||||
}
|
}
|
||||||
|
|
||||||
public removeFilter(): void {
|
public removeFilter(): void {
|
||||||
|
this.currentFilter = undefined;
|
||||||
this.filterRemoved.notify();
|
this.filterRemoved.notify();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -41,6 +41,9 @@ function mayBeUniqueLine(codeLine: string): boolean {
|
|||||||
if (trimmed === ')' || trimmed === '(') { // "(" and ")" are used often in batch code
|
if (trimmed === ')' || trimmed === '(') { // "(" and ")" are used often in batch code
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (codeLine.startsWith(':: ') || codeLine.startsWith('REM ')) { // Is comment?
|
||||||
|
return false;
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import App from './App.vue';
|
import App from './App.vue';
|
||||||
import { ApplicationBootstrapper } from './presentation/Bootstrapping/ApplicationBootstrapper';
|
import { ApplicationBootstrapper } from './presentation/Bootstrapping/ApplicationBootstrapper';
|
||||||
|
import 'core-js/fn/array/flat-map'; // Here until Vue 3 & CLI v4 https://github.com/vuejs/vue-cli/issues/3834
|
||||||
|
|
||||||
new ApplicationBootstrapper()
|
new ApplicationBootstrapper()
|
||||||
.bootstrap(Vue);
|
.bootstrap(Vue);
|
||||||
|
|||||||
@@ -49,6 +49,7 @@
|
|||||||
state.filter.filtered.on(this.handleFiltered);
|
state.filter.filtered.on(this.handleFiltered);
|
||||||
// Update initial state
|
// Update initial state
|
||||||
await this.initializeNodesAsync(this.categoryId);
|
await this.initializeNodesAsync(this.categoryId);
|
||||||
|
await this.initializeFilter(state.filter.currentFilter);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async toggleNodeSelectionAsync(event: INodeSelectedEvent) {
|
public async toggleNodeSelectionAsync(event: INodeSelectedEvent) {
|
||||||
@@ -84,6 +85,14 @@
|
|||||||
(category: ICategory) => node.id === getCategoryNodeId(category));
|
(category: ICategory) => node.id === getCategoryNodeId(category));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private initializeFilter(currentFilter: IFilterResult | undefined) {
|
||||||
|
if (!currentFilter) {
|
||||||
|
this.handleFilterRemoved();
|
||||||
|
} else {
|
||||||
|
this.handleFiltered(currentFilter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private handleSelectionChanged(selectedScripts: ReadonlyArray<SelectedScript>): void {
|
private handleSelectionChanged(selectedScripts: ReadonlyArray<SelectedScript>): void {
|
||||||
this.selectedNodeIds = selectedScripts
|
this.selectedNodeIds = selectedScripts
|
||||||
.map((node) => node.id);
|
.map((node) => node.id);
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ declare module 'liquor-tree' {
|
|||||||
// https://github.com/amsik/liquor-tree/blob/master/src/lib/Node.js
|
// https://github.com/amsik/liquor-tree/blob/master/src/lib/Node.js
|
||||||
export interface ILiquorTreeNodeState {
|
export interface ILiquorTreeNodeState {
|
||||||
checked: boolean;
|
checked: boolean;
|
||||||
|
indeterminate: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ILiquorTreeNode {
|
export interface ILiquorTreeNode {
|
||||||
|
|||||||
@@ -5,9 +5,10 @@ export class LiquorTreeOptions implements ILiquorTreeOptions {
|
|||||||
public readonly checkbox = true;
|
public readonly checkbox = true;
|
||||||
public readonly checkOnSelect = true;
|
public readonly checkOnSelect = true;
|
||||||
/* For checkbox mode only. Children will have the same checked state as their parent.
|
/* For checkbox mode only. Children will have the same checked state as their parent.
|
||||||
|
⚠️ Setting this false, does not update indeterminate state of nodes.
|
||||||
This is false as it's handled manually to be able to batch select for performance + highlighting */
|
This is false as it's handled manually to be able to batch select for performance + highlighting */
|
||||||
public readonly autoCheckChildren = false;
|
public readonly autoCheckChildren = false;
|
||||||
public readonly parentSelect = false;
|
public readonly parentSelect = true;
|
||||||
public readonly keyboardNavigation = true;
|
public readonly keyboardNavigation = true;
|
||||||
public readonly filter = { // Wrap this in an arrow function as setting filter directly does not work JS APIs
|
public readonly filter = { // Wrap this in an arrow function as setting filter directly does not work JS APIs
|
||||||
emptyText: this.liquorTreeFilter.emptyText,
|
emptyText: this.liquorTreeFilter.emptyText,
|
||||||
|
|||||||
@@ -1,14 +1,37 @@
|
|||||||
import { ILiquorTreeNode } from 'liquor-tree';
|
import { ILiquorTreeNode, ILiquorTreeNodeState } from 'liquor-tree';
|
||||||
import { NodeType } from './../../Node/INode';
|
import { NodeType } from './../../Node/INode';
|
||||||
|
|
||||||
export function getNewCheckedState(
|
export function getNewState(
|
||||||
oldNode: ILiquorTreeNode,
|
node: ILiquorTreeNode,
|
||||||
|
selectedNodeIds: ReadonlyArray<string>): ILiquorTreeNodeState {
|
||||||
|
const checked = getNewCheckedState(node, selectedNodeIds);
|
||||||
|
const indeterminate = !checked && getNewIndeterminateState(node, selectedNodeIds);
|
||||||
|
return {
|
||||||
|
checked, indeterminate,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function getNewIndeterminateState(
|
||||||
|
node: ILiquorTreeNode,
|
||||||
selectedNodeIds: ReadonlyArray<string>): boolean {
|
selectedNodeIds: ReadonlyArray<string>): boolean {
|
||||||
switch (oldNode.data.type) {
|
switch (node.data.type) {
|
||||||
case NodeType.Script:
|
case NodeType.Script:
|
||||||
return selectedNodeIds.some((id) => id === oldNode.id);
|
return false;
|
||||||
case NodeType.Category:
|
case NodeType.Category:
|
||||||
return parseAllSubScriptIds(oldNode).every((id) => selectedNodeIds.includes(id));
|
return parseAllSubScriptIds(node).some((id) => selectedNodeIds.includes(id));
|
||||||
|
default:
|
||||||
|
throw new Error('Unknown node type');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getNewCheckedState(
|
||||||
|
node: ILiquorTreeNode,
|
||||||
|
selectedNodeIds: ReadonlyArray<string>): boolean {
|
||||||
|
switch (node.data.type) {
|
||||||
|
case NodeType.Script:
|
||||||
|
return selectedNodeIds.some((id) => id === node.id);
|
||||||
|
case NodeType.Category:
|
||||||
|
return parseAllSubScriptIds(node).every((id) => selectedNodeIds.includes(id));
|
||||||
default:
|
default:
|
||||||
throw new Error('Unknown node type');
|
throw new Error('Unknown node type');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ export function toNewLiquorTreeNode(node: INode): ILiquorTreeNewNode {
|
|||||||
text: node.text,
|
text: node.text,
|
||||||
state: {
|
state: {
|
||||||
checked: false,
|
checked: false,
|
||||||
|
indeterminate: false,
|
||||||
},
|
},
|
||||||
children: convertChildren(node.children, toNewLiquorTreeNode),
|
children: convertChildren(node.children, toNewLiquorTreeNode),
|
||||||
data: {
|
data: {
|
||||||
|
|||||||
@@ -18,14 +18,15 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Component, Prop, Vue, Watch } from 'vue-property-decorator';
|
import { Component, Prop, Vue, Watch } from 'vue-property-decorator';
|
||||||
import LiquorTree, { ILiquorTreeNewNode, ILiquorTreeExistingNode, ILiquorTree } from 'liquor-tree';
|
import LiquorTree from 'liquor-tree';
|
||||||
import Node from './Node/Node.vue';
|
import Node from './Node/Node.vue';
|
||||||
import { INode } from './Node/INode';
|
import { INode } from './Node/INode';
|
||||||
import { convertExistingToNode, toNewLiquorTreeNode } from './LiquorTree/NodeWrapper/NodeTranslator';
|
import { convertExistingToNode, toNewLiquorTreeNode } from './LiquorTree/NodeWrapper/NodeTranslator';
|
||||||
import { INodeSelectedEvent } from './/INodeSelectedEvent';
|
import { INodeSelectedEvent } from './/INodeSelectedEvent';
|
||||||
import { getNewCheckedState } from './LiquorTree/NodeWrapper/NodeStateUpdater';
|
import { getNewState } from './LiquorTree/NodeWrapper/NodeStateUpdater';
|
||||||
import { LiquorTreeOptions } from './LiquorTree/LiquorTreeOptions';
|
import { LiquorTreeOptions } from './LiquorTree/LiquorTreeOptions';
|
||||||
import { FilterPredicate, NodePredicateFilter } from './LiquorTree/NodeWrapper/NodePredicateFilter';
|
import { FilterPredicate, NodePredicateFilter } from './LiquorTree/NodeWrapper/NodePredicateFilter';
|
||||||
|
import { ILiquorTreeNewNode, ILiquorTreeExistingNode, ILiquorTree, ILiquorTreeNode, ILiquorTreeNodeState } from 'liquor-tree';
|
||||||
|
|
||||||
/** Wrapper for Liquor Tree, reveals only abstracted INode for communication */
|
/** Wrapper for Liquor Tree, reveals only abstracted INode for communication */
|
||||||
@Component({
|
@Component({
|
||||||
@@ -34,7 +35,7 @@
|
|||||||
Node,
|
Node,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
export default class SelectableTree extends Vue {
|
export default class SelectableTree extends Vue { // Keep it stateless to make it easier to switch out
|
||||||
@Prop() public filterPredicate?: FilterPredicate;
|
@Prop() public filterPredicate?: FilterPredicate;
|
||||||
@Prop() public filterText?: string;
|
@Prop() public filterText?: string;
|
||||||
@Prop() public selectedNodeIds?: ReadonlyArray<string>;
|
@Prop() public selectedNodeIds?: ReadonlyArray<string>;
|
||||||
@@ -49,7 +50,7 @@
|
|||||||
const initialNodes = this.initialNodes.map((node) => toNewLiquorTreeNode(node));
|
const initialNodes = this.initialNodes.map((node) => toNewLiquorTreeNode(node));
|
||||||
if (this.selectedNodeIds) {
|
if (this.selectedNodeIds) {
|
||||||
recurseDown(initialNodes,
|
recurseDown(initialNodes,
|
||||||
(node) => node.state.checked = getNewCheckedState(node, this.selectedNodeIds));
|
(node) => node.state = updateState(node.state, node, this.selectedNodeIds));
|
||||||
}
|
}
|
||||||
this.initialLiquourTreeNodes = initialNodes;
|
this.initialLiquourTreeNodes = initialNodes;
|
||||||
} else {
|
} else {
|
||||||
@@ -82,11 +83,11 @@
|
|||||||
@Watch('selectedNodeIds')
|
@Watch('selectedNodeIds')
|
||||||
public setSelectedStatusAsync(selectedNodeIds: ReadonlyArray<string>) {
|
public setSelectedStatusAsync(selectedNodeIds: ReadonlyArray<string>) {
|
||||||
if (!selectedNodeIds) {
|
if (!selectedNodeIds) {
|
||||||
throw new Error('Selected nodes are undefined');
|
throw new Error('SelectedrecurseDown nodes are undefined');
|
||||||
}
|
}
|
||||||
this.getLiquorTreeApi().recurseDown((node) => {
|
this.getLiquorTreeApi().recurseDown(
|
||||||
node.states.checked = getNewCheckedState(node, selectedNodeIds);
|
(node) => node.states = updateState(node.states, node, selectedNodeIds),
|
||||||
});
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private getLiquorTreeApi(): ILiquorTree {
|
private getLiquorTreeApi(): ILiquorTree {
|
||||||
@@ -97,6 +98,13 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function updateState(
|
||||||
|
old: ILiquorTreeNodeState,
|
||||||
|
node: ILiquorTreeNode,
|
||||||
|
selectedNodeIds: ReadonlyArray<string>): ILiquorTreeNodeState {
|
||||||
|
return {...old, ...getNewState(node, selectedNodeIds)};
|
||||||
|
}
|
||||||
|
|
||||||
function recurseDown(
|
function recurseDown(
|
||||||
nodes: ReadonlyArray<ILiquorTreeNewNode>,
|
nodes: ReadonlyArray<ILiquorTreeNewNode>,
|
||||||
handler: (node: ILiquorTreeNewNode) => void) {
|
handler: (node: ILiquorTreeNewNode) => void) {
|
||||||
|
|||||||
@@ -72,7 +72,6 @@
|
|||||||
public isSearching = false;
|
public isSearching = false;
|
||||||
public searchHasMatches = false;
|
public searchHasMatches = false;
|
||||||
|
|
||||||
|
|
||||||
public async mounted() {
|
public async mounted() {
|
||||||
const state = await this.getCurrentStateAsync();
|
const state = await this.getCurrentStateAsync();
|
||||||
this.repositoryUrl = state.app.repositoryUrl;
|
this.repositoryUrl = state.app.repositoryUrl;
|
||||||
|
|||||||
@@ -18,7 +18,8 @@ const NothingChosenCode =
|
|||||||
.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(' 📙 You can select "Recommended" on the top to select "safer" tweaks. Always double check!')
|
||||||
.appendCommentLine(' 📙 After you choose any tweak, you can download & 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.')
|
||||||
.appendLine()
|
.appendLine()
|
||||||
.appendCommentLine('-- 🧐 Why privacy.sexy')
|
.appendCommentLine('-- 🧐 Why privacy.sexy')
|
||||||
.appendCommentLine(' ✔️ Rich tweak pool to harden security & privacy of the OS and other softwares on it.')
|
.appendCommentLine(' ✔️ Rich tweak pool to harden security & privacy of the OS and other softwares on it.')
|
||||||
|
|||||||
@@ -2,42 +2,41 @@
|
|||||||
@import "@/presentation/styles/colors.scss";
|
@import "@/presentation/styles/colors.scss";
|
||||||
|
|
||||||
.tree {
|
.tree {
|
||||||
background-color: $slate;
|
background: $slate;
|
||||||
}
|
&-node {
|
||||||
|
white-space: normal !important;
|
||||||
.tree-node > .tree-content > .tree-anchor > span {
|
> .tree-content {
|
||||||
color: $white !important;
|
> .tree-anchor > span {
|
||||||
text-transform: uppercase;
|
color: $white;
|
||||||
color: $light-gray;
|
text-transform: uppercase;
|
||||||
font-size: 1.5em;
|
font-size: 1.5em;
|
||||||
}
|
}
|
||||||
|
&:hover {
|
||||||
.tree-node {
|
background: $dark-gray !important;
|
||||||
white-space: normal !important;
|
}
|
||||||
}
|
}
|
||||||
|
&.selected { // When using keyboard navigation it higlights current item and its child items
|
||||||
.tree-arrow.has-child {
|
background: $gray;
|
||||||
&.rtl:after, &:after {
|
.tree-text {
|
||||||
border-color: $white !important;
|
color: $black !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&-checkbox {
|
||||||
|
&.checked {
|
||||||
|
background: $accent !important;
|
||||||
|
border-color: $accent !important;
|
||||||
|
}
|
||||||
|
&.indeterminate {
|
||||||
|
border-color: $gray !important;
|
||||||
|
}
|
||||||
|
background: $dark-slate !important;
|
||||||
|
}
|
||||||
|
&-arrow {
|
||||||
|
&.has-child {
|
||||||
|
&.rtl:after, &:after {
|
||||||
|
border-color: $white !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.tree-node.selected > .tree-content {
|
|
||||||
> .tree-anchor > span {
|
|
||||||
font-weight: bolder;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.tree-content:hover {
|
|
||||||
background: $dark-gray !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tree-checkbox {
|
|
||||||
&.checked {
|
|
||||||
background: $accent !important;
|
|
||||||
}
|
|
||||||
&.indeterminate {
|
|
||||||
border-color: $gray !important;
|
|
||||||
}
|
|
||||||
background: $dark-slate !important;
|
|
||||||
}
|
|
||||||
@@ -7,129 +7,156 @@ import 'mocha';
|
|||||||
import { expect } from 'chai';
|
import { expect } from 'chai';
|
||||||
|
|
||||||
describe('UserFilter', () => {
|
describe('UserFilter', () => {
|
||||||
it('signals when removing filter', () => {
|
describe('removeFilter', () => {
|
||||||
// arrange
|
it('signals when removing filter', () => {
|
||||||
let isCalled = false;
|
|
||||||
const sut = new UserFilter(new ApplicationStub());
|
|
||||||
sut.filterRemoved.on(() => isCalled = true);
|
|
||||||
// act
|
|
||||||
sut.removeFilter();
|
|
||||||
// assert
|
|
||||||
expect(isCalled).to.be.equal(true);
|
|
||||||
});
|
|
||||||
it('signals when no matches', () => {
|
|
||||||
// arrange
|
|
||||||
let actual: IFilterResult;
|
|
||||||
const nonMatchingFilter = 'non matching filter';
|
|
||||||
const sut = new UserFilter(new ApplicationStub());
|
|
||||||
sut.filtered.on((filterResult) => actual = filterResult);
|
|
||||||
// act
|
|
||||||
sut.setFilter(nonMatchingFilter);
|
|
||||||
// assert
|
|
||||||
expect(actual.hasAnyMatches()).be.equal(false);
|
|
||||||
expect(actual.categoryMatches).to.have.lengthOf(0);
|
|
||||||
expect(actual.scriptMatches).to.have.lengthOf(0);
|
|
||||||
expect(actual.query).to.equal(nonMatchingFilter);
|
|
||||||
});
|
|
||||||
describe('signals when script matches', () => {
|
|
||||||
it('code matches', () => {
|
|
||||||
// arrange
|
// arrange
|
||||||
const code = 'HELLO world';
|
let isCalled = false;
|
||||||
const filter = 'Hello WoRLD';
|
const sut = new UserFilter(new ApplicationStub());
|
||||||
let actual: IFilterResult;
|
sut.filterRemoved.on(() => isCalled = true);
|
||||||
const script = new ScriptStub('id').withCode(code);
|
|
||||||
const category = new CategoryStub(33).withScript(script);
|
|
||||||
const sut = new UserFilter(new ApplicationStub()
|
|
||||||
.withAction(category));
|
|
||||||
sut.filtered.on((filterResult) => actual = filterResult);
|
|
||||||
// act
|
// act
|
||||||
sut.setFilter(filter);
|
sut.removeFilter();
|
||||||
// assert
|
// assert
|
||||||
expect(actual.hasAnyMatches()).be.equal(true);
|
expect(isCalled).to.be.equal(true);
|
||||||
expect(actual.categoryMatches).to.have.lengthOf(0);
|
|
||||||
expect(actual.scriptMatches).to.have.lengthOf(1);
|
|
||||||
expect(actual.scriptMatches[0]).to.deep.equal(script);
|
|
||||||
expect(actual.query).to.equal(filter);
|
|
||||||
});
|
});
|
||||||
it('revertCode matches', () => {
|
it('currentFilter is undefined', () => {
|
||||||
// arrange
|
// arrange
|
||||||
const revertCode = 'HELLO world';
|
const sut = new UserFilter(new ApplicationStub());
|
||||||
const filter = 'Hello WoRLD';
|
|
||||||
let actual: IFilterResult;
|
|
||||||
const script = new ScriptStub('id').withRevertCode(revertCode);
|
|
||||||
const category = new CategoryStub(33).withScript(script);
|
|
||||||
const sut = new UserFilter(new ApplicationStub()
|
|
||||||
.withAction(category));
|
|
||||||
sut.filtered.on((filterResult) => actual = filterResult);
|
|
||||||
// act
|
// act
|
||||||
sut.setFilter(filter);
|
sut.removeFilter();
|
||||||
// assert
|
// assert
|
||||||
expect(actual.hasAnyMatches()).be.equal(true);
|
expect(sut.currentFilter).to.be.equal(undefined);
|
||||||
expect(actual.categoryMatches).to.have.lengthOf(0);
|
|
||||||
expect(actual.scriptMatches).to.have.lengthOf(1);
|
|
||||||
expect(actual.scriptMatches[0]).to.deep.equal(script);
|
|
||||||
expect(actual.query).to.equal(filter);
|
|
||||||
});
|
|
||||||
it('name matches', () => {
|
|
||||||
// arrange
|
|
||||||
const name = 'HELLO world';
|
|
||||||
const filter = 'Hello WoRLD';
|
|
||||||
let actual: IFilterResult;
|
|
||||||
const script = new ScriptStub('id').withName(name);
|
|
||||||
const category = new CategoryStub(33).withScript(script);
|
|
||||||
const sut = new UserFilter(new ApplicationStub()
|
|
||||||
.withAction(category));
|
|
||||||
sut.filtered.on((filterResult) => actual = filterResult);
|
|
||||||
// act
|
|
||||||
sut.setFilter(filter);
|
|
||||||
// assert
|
|
||||||
expect(actual.hasAnyMatches()).be.equal(true);
|
|
||||||
expect(actual.categoryMatches).to.have.lengthOf(0);
|
|
||||||
expect(actual.scriptMatches).to.have.lengthOf(1);
|
|
||||||
expect(actual.scriptMatches[0]).to.deep.equal(script);
|
|
||||||
expect(actual.query).to.equal(filter);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
it('signals when category matches', () => {
|
describe('setFilter', () => {
|
||||||
// arrange
|
it('signals when no matches', () => {
|
||||||
const categoryName = 'HELLO world';
|
// arrange
|
||||||
const filter = 'Hello WoRLD';
|
let actual: IFilterResult;
|
||||||
let actual: IFilterResult;
|
const nonMatchingFilter = 'non matching filter';
|
||||||
const category = new CategoryStub(55).withName(categoryName);
|
const sut = new UserFilter(new ApplicationStub());
|
||||||
const sut = new UserFilter(new ApplicationStub()
|
sut.filtered.on((filterResult) => actual = filterResult);
|
||||||
.withAction(category));
|
// act
|
||||||
sut.filtered.on((filterResult) => actual = filterResult);
|
sut.setFilter(nonMatchingFilter);
|
||||||
// act
|
// assert
|
||||||
sut.setFilter(filter);
|
expect(actual.hasAnyMatches()).be.equal(false);
|
||||||
// assert
|
expect(actual.query).to.equal(nonMatchingFilter);
|
||||||
expect(actual.hasAnyMatches()).be.equal(true);
|
});
|
||||||
expect(actual.categoryMatches).to.have.lengthOf(1);
|
it('sets currentFilter as expected when no matches', () => {
|
||||||
expect(actual.categoryMatches[0]).to.deep.equal(category);
|
// arrange
|
||||||
expect(actual.scriptMatches).to.have.lengthOf(0);
|
const nonMatchingFilter = 'non matching filter';
|
||||||
expect(actual.query).to.equal(filter);
|
const sut = new UserFilter(new ApplicationStub());
|
||||||
});
|
// act
|
||||||
it('signals when category and script matches', () => {
|
sut.setFilter(nonMatchingFilter);
|
||||||
// arrange
|
// assert
|
||||||
const matchingText = 'HELLO world';
|
const actual = sut.currentFilter;
|
||||||
const filter = 'Hello WoRLD';
|
expect(actual.hasAnyMatches()).be.equal(false);
|
||||||
let actual: IFilterResult;
|
expect(actual.query).to.equal(nonMatchingFilter);
|
||||||
const script = new ScriptStub('script')
|
});
|
||||||
.withName(matchingText);
|
describe('signals when script matches', () => {
|
||||||
const category = new CategoryStub(55)
|
it('code matches', () => {
|
||||||
.withName(matchingText)
|
// arrange
|
||||||
.withScript(script);
|
const code = 'HELLO world';
|
||||||
const app = new ApplicationStub()
|
const filter = 'Hello WoRLD';
|
||||||
.withAction(category);
|
let actual: IFilterResult;
|
||||||
const sut = new UserFilter(app);
|
const script = new ScriptStub('id').withCode(code);
|
||||||
sut.filtered.on((filterResult) => actual = filterResult);
|
const category = new CategoryStub(33).withScript(script);
|
||||||
// act
|
const sut = new UserFilter(new ApplicationStub()
|
||||||
sut.setFilter(filter);
|
.withAction(category));
|
||||||
// assert
|
sut.filtered.on((filterResult) => actual = filterResult);
|
||||||
expect(actual.hasAnyMatches()).be.equal(true);
|
// act
|
||||||
expect(actual.categoryMatches).to.have.lengthOf(1);
|
sut.setFilter(filter);
|
||||||
expect(actual.categoryMatches[0]).to.deep.equal(category);
|
// assert
|
||||||
expect(actual.scriptMatches).to.have.lengthOf(1);
|
expect(actual.hasAnyMatches()).be.equal(true);
|
||||||
expect(actual.scriptMatches[0]).to.deep.equal(script);
|
expect(actual.categoryMatches).to.have.lengthOf(0);
|
||||||
expect(actual.query).to.equal(filter);
|
expect(actual.scriptMatches).to.have.lengthOf(1);
|
||||||
|
expect(actual.scriptMatches[0]).to.deep.equal(script);
|
||||||
|
expect(actual.query).to.equal(filter);
|
||||||
|
expect(sut.currentFilter).to.deep.equal(actual);
|
||||||
|
});
|
||||||
|
it('revertCode matches', () => {
|
||||||
|
// arrange
|
||||||
|
const revertCode = 'HELLO world';
|
||||||
|
const filter = 'Hello WoRLD';
|
||||||
|
let actual: IFilterResult;
|
||||||
|
const script = new ScriptStub('id').withRevertCode(revertCode);
|
||||||
|
const category = new CategoryStub(33).withScript(script);
|
||||||
|
const sut = new UserFilter(new ApplicationStub()
|
||||||
|
.withAction(category));
|
||||||
|
sut.filtered.on((filterResult) => actual = filterResult);
|
||||||
|
// act
|
||||||
|
sut.setFilter(filter);
|
||||||
|
// assert
|
||||||
|
expect(actual.hasAnyMatches()).be.equal(true);
|
||||||
|
expect(actual.categoryMatches).to.have.lengthOf(0);
|
||||||
|
expect(actual.scriptMatches).to.have.lengthOf(1);
|
||||||
|
expect(actual.scriptMatches[0]).to.deep.equal(script);
|
||||||
|
expect(actual.query).to.equal(filter);
|
||||||
|
expect(sut.currentFilter).to.deep.equal(actual);
|
||||||
|
});
|
||||||
|
it('name matches', () => {
|
||||||
|
// arrange
|
||||||
|
const name = 'HELLO world';
|
||||||
|
const filter = 'Hello WoRLD';
|
||||||
|
let actual: IFilterResult;
|
||||||
|
const script = new ScriptStub('id').withName(name);
|
||||||
|
const category = new CategoryStub(33).withScript(script);
|
||||||
|
const sut = new UserFilter(new ApplicationStub()
|
||||||
|
.withAction(category));
|
||||||
|
sut.filtered.on((filterResult) => actual = filterResult);
|
||||||
|
// act
|
||||||
|
sut.setFilter(filter);
|
||||||
|
// assert
|
||||||
|
expect(actual.hasAnyMatches()).be.equal(true);
|
||||||
|
expect(actual.categoryMatches).to.have.lengthOf(0);
|
||||||
|
expect(actual.scriptMatches).to.have.lengthOf(1);
|
||||||
|
expect(actual.scriptMatches[0]).to.deep.equal(script);
|
||||||
|
expect(actual.query).to.equal(filter);
|
||||||
|
expect(sut.currentFilter).to.deep.equal(actual);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('signals when category matches', () => {
|
||||||
|
// arrange
|
||||||
|
const categoryName = 'HELLO world';
|
||||||
|
const filter = 'Hello WoRLD';
|
||||||
|
let actual: IFilterResult;
|
||||||
|
const category = new CategoryStub(55).withName(categoryName);
|
||||||
|
const sut = new UserFilter(new ApplicationStub()
|
||||||
|
.withAction(category));
|
||||||
|
sut.filtered.on((filterResult) => actual = filterResult);
|
||||||
|
// act
|
||||||
|
sut.setFilter(filter);
|
||||||
|
// assert
|
||||||
|
expect(actual.hasAnyMatches()).be.equal(true);
|
||||||
|
expect(actual.categoryMatches).to.have.lengthOf(1);
|
||||||
|
expect(actual.categoryMatches[0]).to.deep.equal(category);
|
||||||
|
expect(actual.scriptMatches).to.have.lengthOf(0);
|
||||||
|
expect(actual.query).to.equal(filter);
|
||||||
|
expect(sut.currentFilter).to.deep.equal(actual);
|
||||||
|
});
|
||||||
|
it('signals when category and script matches', () => {
|
||||||
|
// arrange
|
||||||
|
const matchingText = 'HELLO world';
|
||||||
|
const filter = 'Hello WoRLD';
|
||||||
|
let actual: IFilterResult;
|
||||||
|
const script = new ScriptStub('script')
|
||||||
|
.withName(matchingText);
|
||||||
|
const category = new CategoryStub(55)
|
||||||
|
.withName(matchingText)
|
||||||
|
.withScript(script);
|
||||||
|
const app = new ApplicationStub()
|
||||||
|
.withAction(category);
|
||||||
|
const sut = new UserFilter(app);
|
||||||
|
sut.filtered.on((filterResult) => actual = filterResult);
|
||||||
|
// act
|
||||||
|
sut.setFilter(filter);
|
||||||
|
// assert
|
||||||
|
expect(actual.hasAnyMatches()).be.equal(true);
|
||||||
|
expect(actual.categoryMatches).to.have.lengthOf(1);
|
||||||
|
expect(actual.categoryMatches[0]).to.deep.equal(category);
|
||||||
|
expect(actual.scriptMatches).to.have.lengthOf(1);
|
||||||
|
expect(actual.scriptMatches[0]).to.deep.equal(script);
|
||||||
|
expect(actual.query).to.equal(filter);
|
||||||
|
expect(sut.currentFilter).to.deep.equal(actual);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,97 +2,189 @@ import 'mocha';
|
|||||||
import { expect } from 'chai';
|
import { expect } from 'chai';
|
||||||
import { ILiquorTreeNode } from 'liquor-tree';
|
import { ILiquorTreeNode } from 'liquor-tree';
|
||||||
import { NodeType } from '@/presentation/Scripts/ScriptsTree/SelectableTree/Node/INode';
|
import { NodeType } from '@/presentation/Scripts/ScriptsTree/SelectableTree/Node/INode';
|
||||||
import { getNewCheckedState } from '@/presentation/Scripts/ScriptsTree/SelectableTree/LiquorTree/NodeWrapper/NodeStateUpdater';
|
import { getNewState } from '@/presentation/Scripts/ScriptsTree/SelectableTree/LiquorTree/NodeWrapper/NodeStateUpdater';
|
||||||
|
|
||||||
describe('getNewCheckedState', () => {
|
describe('getNewState', () => {
|
||||||
describe('script node', () => {
|
describe('checked', () => {
|
||||||
it('state is 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 actual = getNewCheckedState(node, selectedScriptNodeIds);
|
// act
|
||||||
// assert
|
const state = getNewState(node, selectedScriptNodeIds);
|
||||||
expect(actual).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('state is 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 actual = getNewCheckedState(node, selectedScriptNodeIds);
|
data: { type: NodeType.Category, documentationUrls: [], isReversible: false },
|
||||||
// assert
|
children: [
|
||||||
expect(actual).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('state is 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 actual = getNewCheckedState(node, selectedScriptNodeIds);
|
});
|
||||||
// assert
|
|
||||||
expect(actual).to.equal(true);
|
|
||||||
});
|
});
|
||||||
it('state is 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 actual = getNewCheckedState(node, selectedScriptNodeIds);
|
// act
|
||||||
// assert
|
const state = getNewState(node, selectedScriptNodeIds);
|
||||||
expect(actual).to.equal(false);
|
// assert
|
||||||
});
|
expect(state.indeterminate).to.equal(false);
|
||||||
it('state is 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 actual = getNewCheckedState(node, selectedScriptNodeIds);
|
// assert
|
||||||
// assert
|
expect(state.indeterminate).to.equal(true);
|
||||||
expect(actual).to.equal(false);
|
});
|
||||||
|
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);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -108,6 +108,7 @@ function getNewNode(): ILiquorTreeNewNode {
|
|||||||
const base = getNode();
|
const base = getNode();
|
||||||
const commonState = {
|
const commonState = {
|
||||||
checked: false,
|
checked: false,
|
||||||
|
indeterminate: false,
|
||||||
};
|
};
|
||||||
return {
|
return {
|
||||||
id: base.id,
|
id: base.id,
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ describe('ScriptReverter', () => {
|
|||||||
selection: [ new SelectedScript(script, true)], revert: true, expectRevert: true,
|
selection: [ new SelectedScript(script, true)], revert: true, expectRevert: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'keeps revert state deselected when already selected wtih non revert state',
|
name: 'keeps revert state deselected when already selected with non revert state',
|
||||||
selection: [ new SelectedScript(script, false)], revert: false, expectRevert: false,
|
selection: [ new SelectedScript(script, false)], revert: false, expectRevert: false,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
Reference in New Issue
Block a user