Compare commits
43 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d9d7f62d81 | ||
|
|
11e0613165 | ||
|
|
77c3d2bbb8 | ||
|
|
784a67afff | ||
|
|
19a092dd31 | ||
|
|
4c2f74949b | ||
|
|
a3fc3782ef | ||
|
|
cdc93f032a | ||
|
|
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 | ||
|
|
6badfef9da | ||
|
|
8c38dd73d8 | ||
|
|
b8682a852a | ||
|
|
8c17929151 | ||
|
|
bb92c9ec28 |
2
.github/workflows/quality-checks.yaml
vendored
2
.github/workflows/quality-checks.yaml
vendored
@@ -1,6 +1,6 @@
|
||||
name: Quality checks
|
||||
|
||||
on: push
|
||||
on: [ push, pull_request ]
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
|
||||
1
.github/workflows/security-checks.yaml
vendored
1
.github/workflows/security-checks.yaml
vendored
@@ -2,6 +2,7 @@ name: Security checks
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
schedule:
|
||||
- cron: '0 0 * * 0'
|
||||
|
||||
|
||||
7
.github/workflows/test.yaml
vendored
7
.github/workflows/test.yaml
vendored
@@ -1,10 +1,13 @@
|
||||
name: Test
|
||||
|
||||
on: push
|
||||
on: [ push, pull_request ]
|
||||
|
||||
jobs:
|
||||
run-tests:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
os: [macos, ubuntu, windows]
|
||||
runs-on: ${{ matrix.os }}-latest
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
|
||||
82
CHANGELOG.md
82
CHANGELOG.md
@@ -1,5 +1,87 @@
|
||||
# Changelog
|
||||
|
||||
## 0.7.5 (2020-09-14)
|
||||
|
||||
* fix reverting (reinstalling) capabilities not working | [commit](https://github.com/undergroundwires/privacy.sexy/commit/939d838e3535bb1c9b00c8ea9dacb735ae41d700)
|
||||
* fix tests and checks are not running on PRs | [commit](https://github.com/undergroundwires/privacy.sexy/commit/82d509129b4e4a5df4b84786a0d6842a7d26e888)
|
||||
* fix the recycling bin option (#32) | [commit](https://github.com/undergroundwires/privacy.sexy/commit/15db3118012a172a2191a2afad57084a65b34642)
|
||||
* fix rendering issue in older edge/IE | [commit](https://github.com/undergroundwires/privacy.sexy/commit/6efed72bf25c2ddf0901caab7f22966ca13cd47a)
|
||||
* fix pasting in search bar after page load showing no results | [commit](https://github.com/undergroundwires/privacy.sexy/commit/d1694341578288eeaf8b80caf9296a38d76789f0)
|
||||
* fix typo | [commit](https://github.com/undergroundwires/privacy.sexy/commit/7dd15ed06433e0e6583ab0fa46a683ce6554bbea)
|
||||
|
||||
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.7.4...0.7.5)
|
||||
|
||||
## 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)
|
||||
|
||||
* [search] better (multilined) message when there are no results | [commit](https://github.com/undergroundwires/privacy.sexy/commit/ec15af01dd020b364c2174fe562fd66227c2320c)
|
||||
* [search] added clear/close button | [commit](https://github.com/undergroundwires/privacy.sexy/commit/d6fa9a2a03c0ebe68b94f0b80cc52b4e200c9213)
|
||||
* move script generation to /generation | [commit](https://github.com/undergroundwires/privacy.sexy/commit/5df458739d076719e350ba194c4f3f772884fcdb)
|
||||
* add auto-highlighting of selected/updated code | [commit](https://github.com/undergroundwires/privacy.sexy/commit/b789250cb89e2130b08e1a927df8181cf945dfeb)
|
||||
* prompt admin priviliges automatically | [commit](https://github.com/undergroundwires/privacy.sexy/commit/f8ba5c46e4923d9c35f200f8a08aa6437f7c0ecc)
|
||||
* add removal of ghost (default0) telemetry user | [commit](https://github.com/undergroundwires/privacy.sexy/commit/c262681011f39b4412669b6cf233476f676ca550)
|
||||
* add more windows defender tweaks, categorization and reversibility | [commit](https://github.com/undergroundwires/privacy.sexy/commit/1a34c7374ba56bafa0209bbb55c81b233bb419ed)
|
||||
* fix NTP script documentation is on wrong place | [commit](https://github.com/undergroundwires/privacy.sexy/commit/3060ebf79cf242370433495cc3e1878b7581b202)
|
||||
* updated dependencies to latest and audit fixes (#25) | [commit](https://github.com/undergroundwires/privacy.sexy/commit/c628aa9aef8ab7c815661d3c1711e7fbc65c69a2)
|
||||
* categorize, fix and extend windows log files cleanup | [commit](https://github.com/undergroundwires/privacy.sexy/commit/594a14d6ca76cbd27a21877b8c373c1930589ca6)
|
||||
* add more OneDrive cleanup scripts and categorize them | [commit](https://github.com/undergroundwires/privacy.sexy/commit/978d7d08638dd161082f239ed088b12302f29458)
|
||||
* add disabling firefox telemetry | [commit](https://github.com/undergroundwires/privacy.sexy/commit/f8b8b4c97ab734d5ba7370894b694993924388da)
|
||||
* add disabling ccleaner telemetry | [commit](https://github.com/undergroundwires/privacy.sexy/commit/018b7e270f207aac926cb12f8069ebfcdce193ce)
|
||||
* Add disabling of PowerShell 7+ telemetry (#29) | [commit](https://github.com/undergroundwires/privacy.sexy/commit/456e40bedf9afcc846f9b13f1ea144cef6115cf6)
|
||||
* categorize, fix, make scripts reversible in "UI for privacy", "security improvements" and "configure browsers" | [commit](https://github.com/undergroundwires/privacy.sexy/commit/532915b95da9fecd6b981d91bf489359e4e53caa)
|
||||
* fix "Configure Defender" being in wrong category #28 | [commit](https://github.com/undergroundwires/privacy.sexy/commit/f709d6a566ed7846b677b383863deda9680a2a9c)
|
||||
* do not hardcode capability versions and make them reversible | [commit](https://github.com/undergroundwires/privacy.sexy/commit/2afef4ea3d0d3d09aa1fa1eedba8493680bd8f10)
|
||||
* exclude paint, wordpad and notepad from bloatware removal | [commit](https://github.com/undergroundwires/privacy.sexy/commit/d235dee95514a01745aef9479d07f88ffb4b40b8)
|
||||
* add reversibility on category level | [commit](https://github.com/undergroundwires/privacy.sexy/commit/f51e8859eeb32c944126d692cfe03a0320c8b568)
|
||||
* refactor unused imports & variables | [commit](https://github.com/undergroundwires/privacy.sexy/commit/a23d28f2cfa2d64d45460697cf5ee9d6b5920752)
|
||||
* fix search (got broken in b789250) with tests and refactorings | [commit](https://github.com/undergroundwires/privacy.sexy/commit/8bbe6ebf750f1a1cbab493fb99b5ea91f4e21609)
|
||||
* update the screenshot to show off highlighting | [commit](https://github.com/undergroundwires/privacy.sexy/commit/b4aacea2a3e0bbcf2d8a79ff67f51c0f19e888a6)
|
||||
|
||||
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.6.2...0.7.0)
|
||||
|
||||
## 0.6.2 (2020-08-16)
|
||||
|
||||
* 🐛 fixed disabling error reporting for november 2019 update | [commit](https://github.com/undergroundwires/privacy.sexy/commit/5967347b80976a519f6f4eb1972a62f3e600df2b)
|
||||
|
||||
22
README.md
22
README.md
@@ -15,20 +15,20 @@
|
||||
## Get started
|
||||
|
||||
- Online version: [https://privacy.sexy](https://privacy.sexy)
|
||||
- or download latest desktop version for [Windows](https://github.com/undergroundwires/privacy.sexy/releases/download/0.6.2/privacy.sexy-Setup-0.6.2.exe), [Linux](https://github.com/undergroundwires/privacy.sexy/releases/download/0.6.2/privacy.sexy-0.6.2.AppImage), [macOS](https://github.com/undergroundwires/privacy.sexy/releases/download/0.6.2/privacy.sexy-0.6.2.dmg)
|
||||
- or download latest desktop version for [Windows](https://github.com/undergroundwires/privacy.sexy/releases/download/0.7.5/privacy.sexy-Setup-0.7.5.exe), [Linux](https://github.com/undergroundwires/privacy.sexy/releases/download/0.7.5/privacy.sexy-0.7.5.AppImage), [macOS](https://github.com/undergroundwires/privacy.sexy/releases/download/0.7.5/privacy.sexy-0.7.5.dmg)
|
||||
- 💡 Come back regularly to apply latest version for stronger privacy and security.
|
||||
|
||||
[](https://privacy.sexy)
|
||||
[](https://privacy.sexy)
|
||||
|
||||
## Why
|
||||
|
||||
- You don't need to run any compiled software that has access to your system, just run the generated scripts.
|
||||
- Have full visibility into what the tweaks do as you enable them.
|
||||
- Ability to revert applied scripts
|
||||
- Rich tweak pool to harden security & privacy of the OS and other software on it
|
||||
- Free (both free as in beer and free as in speech)
|
||||
- No need to run any compiled software that has access to your system, just run the generated scripts
|
||||
- Have full visibility into what the tweaks do as you enable them
|
||||
- Ability to revert (undo) applied scripts
|
||||
- Easily extendable
|
||||
- Everything is open-sourced including both application and infrastructure
|
||||
- Fully automated CI/CD pipeline using GitHub actions
|
||||
- to AWS for provisioning serverless infrastructure
|
||||
- for building and sharing the desktop applications
|
||||
- Everything is open-source and automated (both application and its infrastructure)
|
||||
|
||||
## Extend scripts
|
||||
|
||||
@@ -48,8 +48,8 @@
|
||||
- Development: `npm run serve` to compile & hot-reload for development.
|
||||
- Production: `npm run build` to prepare files for distribution.
|
||||
- Or run using Docker:
|
||||
1. Build: `docker build -t undergroundwires/privacy.sexy:0.6.2 .`
|
||||
2. Run: `docker run -it -p 8080:80 --rm --name privacy.sexy-0.6.2 undergroundwires/privacy.sexy:0.6.2`
|
||||
1. Build: `docker build -t undergroundwires/privacy.sexy:0.7.5 .`
|
||||
2. Run: `docker run -it -p 8080:80 --rm --name privacy.sexy-0.7.5 undergroundwires/privacy.sexy:0.7.5`
|
||||
|
||||
## Architecture
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 62 KiB |
1453
package-lock.json
generated
1453
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
49
package.json
49
package.json
@@ -1,9 +1,14 @@
|
||||
{
|
||||
"name": "privacy.sexy",
|
||||
"version": "0.6.2",
|
||||
"version": "0.7.5",
|
||||
"author": "undergroundwires",
|
||||
"description": "Enforce privacy & security best-practices on Windows, because privacy is sexy 🍑🍆",
|
||||
"homepage": "https://privacy.sexy",
|
||||
"private": true,
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/undergroundwires/privacy.sexy.git"
|
||||
},
|
||||
"scripts": {
|
||||
"serve": "vue-cli-service serve",
|
||||
"build": "vue-cli-service build",
|
||||
@@ -21,44 +26,44 @@
|
||||
},
|
||||
"main": "background.js",
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-svg-core": "^1.2.30",
|
||||
"@fortawesome/free-brands-svg-icons": "^5.14.0",
|
||||
"@fortawesome/free-regular-svg-icons": "^5.14.0",
|
||||
"@fortawesome/free-solid-svg-icons": "^5.14.0",
|
||||
"@fortawesome/vue-fontawesome": "^0.1.10",
|
||||
"@fortawesome/fontawesome-svg-core": "^1.2.32",
|
||||
"@fortawesome/free-brands-svg-icons": "^5.15.1",
|
||||
"@fortawesome/free-regular-svg-icons": "^5.15.1",
|
||||
"@fortawesome/free-solid-svg-icons": "^5.15.1",
|
||||
"@fortawesome/vue-fontawesome": "^2.0.0",
|
||||
"ace-builds": "^1.4.12",
|
||||
"file-saver": "^2.0.2",
|
||||
"inversify": "^5.0.1",
|
||||
"liquor-tree": "^0.2.70",
|
||||
"v-tooltip": "2.0.2",
|
||||
"vue": "^2.6.12",
|
||||
"vue-class-component": "^7.2.5",
|
||||
"vue-class-component": "^7.2.6",
|
||||
"vue-js-modal": "^2.0.0-rc.6",
|
||||
"vue-property-decorator": "^9.0.0"
|
||||
"vue-property-decorator": "^9.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/ace": "0.0.43",
|
||||
"@types/chai": "^4.2.12",
|
||||
"@types/ace": "0.0.44",
|
||||
"@types/chai": "^4.2.14",
|
||||
"@types/file-saver": "^2.0.1",
|
||||
"@types/mocha": "^8.0.3",
|
||||
"@vue/cli-plugin-typescript": "^4.5.4",
|
||||
"@vue/cli-plugin-unit-mocha": "^4.5.4",
|
||||
"@vue/cli-service": "^4.5.4",
|
||||
"@vue/test-utils": "1.0.4",
|
||||
"@vue/cli-plugin-typescript": "^4.5.7",
|
||||
"@vue/cli-plugin-unit-mocha": "^4.5.7",
|
||||
"@vue/cli-service": "^4.5.7",
|
||||
"@vue/test-utils": "1.1.0",
|
||||
"chai": "^4.2.0",
|
||||
"electron": "^10.1.0",
|
||||
"electron": "^10.1.3",
|
||||
"electron-devtools-installer": "^3.1.1",
|
||||
"electron-log": "^4.2.4",
|
||||
"electron-updater": "^4.3.4",
|
||||
"electron-updater": "^4.3.5",
|
||||
"js-yaml-loader": "^1.2.2",
|
||||
"markdownlint-cli": "^0.23.2",
|
||||
"remark-cli": "^8.0.1",
|
||||
"markdownlint-cli": "^0.24.0",
|
||||
"remark-cli": "^9.0.0",
|
||||
"remark-lint-no-dead-urls": "^1.1.0",
|
||||
"remark-preset-lint-consistent": "^3.0.1",
|
||||
"remark-preset-lint-consistent": "^4.0.0",
|
||||
"remark-validate-links": "^10.0.2",
|
||||
"sass": "^1.26.10",
|
||||
"sass-loader": "^10.0.1",
|
||||
"typescript": "^4.0.2",
|
||||
"sass": "^1.27.0",
|
||||
"sass-loader": "^10.0.3",
|
||||
"typescript": "^4.0.3",
|
||||
"vue-cli-plugin-electron-builder": "^2.0.0-rc.4",
|
||||
"vue-template-compiler": "^2.6.12",
|
||||
"yaml-lint": "^1.2.4"
|
||||
|
||||
2
public/robots.txt
Normal file
2
public/robots.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
User-agent: *
|
||||
Disallow:
|
||||
@@ -1,4 +1,4 @@
|
||||
import { OperatingSystem } from '../OperatingSystem';
|
||||
import { OperatingSystem } from '@/domain/OperatingSystem';
|
||||
import { DetectorBuilder } from './DetectorBuilder';
|
||||
import { IBrowserOsDetector } from './IBrowserOsDetector';
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { IBrowserOsDetector } from './IBrowserOsDetector';
|
||||
import { OperatingSystem } from '../OperatingSystem';
|
||||
import { OperatingSystem } from '@/domain/OperatingSystem';
|
||||
|
||||
export class DetectorBuilder {
|
||||
private readonly existingPartsInUserAgent = new Array<string>();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { OperatingSystem } from '../OperatingSystem';
|
||||
import { OperatingSystem } from '@/domain/OperatingSystem';
|
||||
|
||||
export interface IBrowserOsDetector {
|
||||
detect(userAgent: string): OperatingSystem;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { BrowserOsDetector } from './BrowserOs/BrowserOsDetector';
|
||||
import { IBrowserOsDetector } from './BrowserOs/IBrowserOsDetector';
|
||||
import { IEnvironment } from './IEnvironment';
|
||||
import { OperatingSystem } from './OperatingSystem';
|
||||
import { OperatingSystem } from '@/domain/OperatingSystem';
|
||||
|
||||
interface IEnvironmentVariables {
|
||||
readonly window: Window & typeof globalThis;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { OperatingSystem } from './OperatingSystem';
|
||||
import { OperatingSystem } from '@/domain/OperatingSystem';
|
||||
|
||||
export interface IEnvironment {
|
||||
isDesktop: boolean;
|
||||
|
||||
@@ -1,24 +1,35 @@
|
||||
import { Category } from '@/domain/Category';
|
||||
import { Application } from '@/domain/Application';
|
||||
import { IApplication } from '@/domain/IApplication';
|
||||
import { IProjectInformation } from '@/domain/IProjectInformation';
|
||||
import { ApplicationYaml } from 'js-yaml-loader!./../application.yaml';
|
||||
import { parseCategory } from './CategoryParser';
|
||||
import { ProjectInformation } from '../../domain/ProjectInformation';
|
||||
|
||||
export function parseApplication(content: ApplicationYaml): IApplication {
|
||||
|
||||
export function parseApplication(content: ApplicationYaml, env: NodeJS.ProcessEnv = process.env): IApplication {
|
||||
validate(content);
|
||||
const categories = new Array<Category>();
|
||||
for (const action of content.actions) {
|
||||
const category = parseCategory(action);
|
||||
categories.push(category);
|
||||
}
|
||||
const info = readAppInformation(env);
|
||||
const app = new Application(
|
||||
content.name,
|
||||
content.repositoryUrl,
|
||||
process.env.VUE_APP_VERSION,
|
||||
info,
|
||||
categories);
|
||||
return app;
|
||||
}
|
||||
|
||||
function readAppInformation(environment): IProjectInformation {
|
||||
return new ProjectInformation(
|
||||
environment.VUE_APP_NAME,
|
||||
environment.VUE_APP_VERSION,
|
||||
environment.VUE_APP_REPOSITORY_URL,
|
||||
environment.VUE_APP_HOMEPAGE_URL,
|
||||
);
|
||||
}
|
||||
|
||||
function validate(content: ApplicationYaml): void {
|
||||
if (!content) {
|
||||
throw new Error('application is null or undefined');
|
||||
|
||||
@@ -52,7 +52,7 @@ function parseCategoryChild(
|
||||
children.subScripts.push(script);
|
||||
} else {
|
||||
throw new Error(`Child element is neither a category or a script.
|
||||
Parent: ${parent.category}, element: ${categoryOrScript}`);
|
||||
Parent: ${parent.category}, element: ${JSON.stringify(categoryOrScript)}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ export class ApplicationState implements IApplicationState {
|
||||
/** Initially selected scripts */
|
||||
public readonly defaultScripts: Script[]) {
|
||||
this.selection = new UserSelection(app, defaultScripts.map((script) => new SelectedScript(script, false)));
|
||||
this.code = new ApplicationCode(this.selection, app.version);
|
||||
this.code = new ApplicationCode(this.selection, app.info.version);
|
||||
this.filter = new UserFilter(app);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { IFilterResult } from './IFilterResult';
|
||||
import { ISignal } from '@/infrastructure/Events/Signal';
|
||||
|
||||
export interface IUserFilter {
|
||||
readonly currentFilter: IFilterResult | undefined;
|
||||
readonly filtered: ISignal<IFilterResult>;
|
||||
readonly filterRemoved: ISignal<void>;
|
||||
setFilter(filter: string): void;
|
||||
|
||||
@@ -8,6 +8,7 @@ import { Signal } from '@/infrastructure/Events/Signal';
|
||||
export class UserFilter implements IUserFilter {
|
||||
public readonly filtered = new Signal<IFilterResult>();
|
||||
public readonly filterRemoved = new Signal<void>();
|
||||
public currentFilter: IFilterResult | undefined;
|
||||
|
||||
constructor(private application: IApplication) {
|
||||
|
||||
@@ -28,11 +29,12 @@ export class UserFilter implements IUserFilter {
|
||||
filteredCategories,
|
||||
filter,
|
||||
);
|
||||
|
||||
this.currentFilter = matches;
|
||||
this.filtered.notify(matches);
|
||||
}
|
||||
|
||||
public removeFilter(): void {
|
||||
this.currentFilter = undefined;
|
||||
this.filterRemoved.notify();
|
||||
}
|
||||
}
|
||||
|
||||
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
@@ -19,8 +19,6 @@ declare module 'js-yaml-loader!*' {
|
||||
}
|
||||
|
||||
export interface ApplicationYaml {
|
||||
name: string;
|
||||
repositoryUrl: string;
|
||||
actions: ReadonlyArray<YamlCategory>;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import { IEntity } from '../infrastructure/Entity/IEntity';
|
||||
import { ICategory } from './ICategory';
|
||||
import { IScript } from './IScript';
|
||||
import { IApplication } from './IApplication';
|
||||
import { IProjectInformation } from './IProjectInformation';
|
||||
|
||||
export class Application implements IApplication {
|
||||
public get totalScripts(): number { return this.flattened.allScripts.length; }
|
||||
@@ -10,13 +11,11 @@ export class Application implements IApplication {
|
||||
private readonly flattened: IFlattenedApplication;
|
||||
|
||||
constructor(
|
||||
public readonly name: string,
|
||||
public readonly repositoryUrl: string,
|
||||
public readonly version: string,
|
||||
public readonly info: IProjectInformation,
|
||||
public readonly actions: ReadonlyArray<ICategory>) {
|
||||
if (!name) { throw Error('Application has no name'); }
|
||||
if (!repositoryUrl) { throw Error('Application has no repository url'); }
|
||||
if (!version) { throw Error('Version cannot be empty'); }
|
||||
if (!info) {
|
||||
throw new Error('info is undefined');
|
||||
}
|
||||
this.flattened = flatten(actions);
|
||||
ensureValid(this.flattened);
|
||||
ensureNoDuplicates(this.flattened.allCategories);
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { IScript } from '@/domain/IScript';
|
||||
import { ICategory } from '@/domain/ICategory';
|
||||
import { IProjectInformation } from './IProjectInformation';
|
||||
|
||||
export interface IApplication {
|
||||
readonly name: string;
|
||||
readonly repositoryUrl: string;
|
||||
readonly version: string;
|
||||
readonly info: IProjectInformation;
|
||||
readonly totalScripts: number;
|
||||
readonly totalCategories: number;
|
||||
readonly actions: ReadonlyArray<ICategory>;
|
||||
|
||||
11
src/domain/IProjectInformation.ts
Normal file
11
src/domain/IProjectInformation.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { OperatingSystem } from './OperatingSystem';
|
||||
export interface IProjectInformation {
|
||||
readonly name: string;
|
||||
readonly version: string;
|
||||
readonly repositoryUrl: string;
|
||||
readonly homepage: string;
|
||||
readonly feedbackUrl: string;
|
||||
readonly releaseUrl: string;
|
||||
readonly repositoryWebUrl: string;
|
||||
getDownloadUrl(os: OperatingSystem): string;
|
||||
}
|
||||
55
src/domain/ProjectInformation.ts
Normal file
55
src/domain/ProjectInformation.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import { IProjectInformation } from './IProjectInformation';
|
||||
import { OperatingSystem } from './OperatingSystem';
|
||||
|
||||
export class ProjectInformation implements IProjectInformation {
|
||||
public readonly repositoryWebUrl: string;
|
||||
constructor(
|
||||
public readonly name: string,
|
||||
public readonly version: string,
|
||||
public readonly repositoryUrl: string,
|
||||
public readonly homepage: string,
|
||||
) {
|
||||
if (!name) {
|
||||
throw new Error('name is undefined');
|
||||
}
|
||||
if (!version || +version <= 0) {
|
||||
throw new Error('version should be higher than zero');
|
||||
}
|
||||
if (!repositoryUrl) {
|
||||
throw new Error('repositoryUrl is undefined');
|
||||
}
|
||||
if (!homepage) {
|
||||
throw new Error('homepage is undefined');
|
||||
}
|
||||
this.repositoryWebUrl = getWebUrl(this.repositoryUrl);
|
||||
}
|
||||
public getDownloadUrl(os: OperatingSystem): string {
|
||||
return `${this.repositoryWebUrl}/releases/download/${this.version}/${getFileName(os, this.version)}`;
|
||||
}
|
||||
public get feedbackUrl(): string {
|
||||
return `${this.repositoryWebUrl}/issues`;
|
||||
}
|
||||
public get releaseUrl(): string {
|
||||
return `${this.repositoryWebUrl}/releases/tag/${this.version}`;
|
||||
}
|
||||
}
|
||||
|
||||
function getWebUrl(gitUrl: string) {
|
||||
if (gitUrl.endsWith('.git')) {
|
||||
return gitUrl.substring(0, gitUrl.length - 4);
|
||||
}
|
||||
return gitUrl;
|
||||
}
|
||||
|
||||
function getFileName(os: OperatingSystem, version: string): string {
|
||||
switch (os) {
|
||||
case OperatingSystem.Linux:
|
||||
return `privacy.sexy-${version}.AppImage`;
|
||||
case OperatingSystem.macOS:
|
||||
return `privacy.sexy-${version}.dmg`;
|
||||
case OperatingSystem.Windows:
|
||||
return `privacy.sexy-Setup-${version}.exe`;
|
||||
default:
|
||||
throw new Error(`Unsupported os: ${OperatingSystem[os]}`);
|
||||
}
|
||||
}
|
||||
@@ -41,6 +41,9 @@ function mayBeUniqueLine(codeLine: string): boolean {
|
||||
if (trimmed === ')' || trimmed === '(') { // "(" and ")" are used often in batch code
|
||||
return false;
|
||||
}
|
||||
if (codeLine.startsWith(':: ') || codeLine.startsWith('REM ')) { // Is comment?
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,18 @@
|
||||
import fileSaver from 'file-saver';
|
||||
|
||||
export enum FileType {
|
||||
BatchFile,
|
||||
}
|
||||
export class SaveFileDialog {
|
||||
public static saveText(text: string, fileName: string): void {
|
||||
this.saveBlob(text, 'text/plain;charset=utf-8', fileName);
|
||||
public static saveFile(text: string, fileName: string, type: FileType): void {
|
||||
const mimeType = this.mimeTypes.get(type);
|
||||
this.saveBlob(text, mimeType, fileName);
|
||||
}
|
||||
private static readonly mimeTypes = new Map<FileType, string>([
|
||||
// Some browsers (including firefox + IE) require right mime type
|
||||
// otherwise they ignore extension and save the file as text.
|
||||
[ FileType.BatchFile, 'application/bat' ], // https://en.wikipedia.org/wiki/Batch_file
|
||||
]);
|
||||
|
||||
private static saveBlob(file: BlobPart, fileType: string, fileName: string): void {
|
||||
try {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import Vue from 'vue';
|
||||
import App from './App.vue';
|
||||
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()
|
||||
.bootstrap(Vue);
|
||||
|
||||
@@ -49,6 +49,7 @@
|
||||
state.filter.filtered.on(this.handleFiltered);
|
||||
// Update initial state
|
||||
await this.initializeNodesAsync(this.categoryId);
|
||||
await this.initializeFilter(state.filter.currentFilter);
|
||||
}
|
||||
|
||||
public async toggleNodeSelectionAsync(event: INodeSelectedEvent) {
|
||||
@@ -84,6 +85,14 @@
|
||||
(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 {
|
||||
this.selectedNodeIds = selectedScripts
|
||||
.map((node) => node.id);
|
||||
|
||||
@@ -20,6 +20,7 @@ declare module 'liquor-tree' {
|
||||
// https://github.com/amsik/liquor-tree/blob/master/src/lib/Node.js
|
||||
export interface ILiquorTreeNodeState {
|
||||
checked: boolean;
|
||||
indeterminate: boolean;
|
||||
}
|
||||
|
||||
export interface ILiquorTreeNode {
|
||||
|
||||
@@ -5,9 +5,10 @@ export class LiquorTreeOptions implements ILiquorTreeOptions {
|
||||
public readonly checkbox = true;
|
||||
public readonly checkOnSelect = true;
|
||||
/* 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 */
|
||||
public readonly autoCheckChildren = false;
|
||||
public readonly parentSelect = false;
|
||||
public readonly parentSelect = true;
|
||||
public readonly keyboardNavigation = true;
|
||||
public readonly filter = { // Wrap this in an arrow function as setting filter directly does not work JS APIs
|
||||
emptyText: this.liquorTreeFilter.emptyText,
|
||||
|
||||
@@ -1,14 +1,37 @@
|
||||
import { ILiquorTreeNode } from 'liquor-tree';
|
||||
import { ILiquorTreeNode, ILiquorTreeNodeState } from 'liquor-tree';
|
||||
import { NodeType } from './../../Node/INode';
|
||||
|
||||
export function getNewCheckedState(
|
||||
oldNode: ILiquorTreeNode,
|
||||
export function getNewState(
|
||||
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 {
|
||||
switch (oldNode.data.type) {
|
||||
switch (node.data.type) {
|
||||
case NodeType.Script:
|
||||
return selectedNodeIds.some((id) => id === oldNode.id);
|
||||
return false;
|
||||
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:
|
||||
throw new Error('Unknown node type');
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ export function toNewLiquorTreeNode(node: INode): ILiquorTreeNewNode {
|
||||
text: node.text,
|
||||
state: {
|
||||
checked: false,
|
||||
indeterminate: false,
|
||||
},
|
||||
children: convertChildren(node.children, toNewLiquorTreeNode),
|
||||
data: {
|
||||
|
||||
@@ -18,8 +18,6 @@
|
||||
import { StatefulVue } from '@/presentation/StatefulVue';
|
||||
import { INode } from './INode';
|
||||
import { SelectedScript } from '@/application/State/Selection/SelectedScript';
|
||||
import { IApplicationState } from '@/application/State/IApplicationState';
|
||||
import { getCategoryId, getScriptId } from './../../ScriptNodeParser';
|
||||
import { getReverter } from './Reverter/ReverterFactory';
|
||||
|
||||
@Component
|
||||
|
||||
@@ -18,14 +18,15 @@
|
||||
|
||||
<script lang="ts">
|
||||
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 { INode } from './Node/INode';
|
||||
import { convertExistingToNode, toNewLiquorTreeNode } from './LiquorTree/NodeWrapper/NodeTranslator';
|
||||
import { INodeSelectedEvent } from './/INodeSelectedEvent';
|
||||
import { getNewCheckedState } from './LiquorTree/NodeWrapper/NodeStateUpdater';
|
||||
import { getNewState } from './LiquorTree/NodeWrapper/NodeStateUpdater';
|
||||
import { LiquorTreeOptions } from './LiquorTree/LiquorTreeOptions';
|
||||
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 */
|
||||
@Component({
|
||||
@@ -34,7 +35,7 @@
|
||||
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 filterText?: string;
|
||||
@Prop() public selectedNodeIds?: ReadonlyArray<string>;
|
||||
@@ -49,7 +50,7 @@
|
||||
const initialNodes = this.initialNodes.map((node) => toNewLiquorTreeNode(node));
|
||||
if (this.selectedNodeIds) {
|
||||
recurseDown(initialNodes,
|
||||
(node) => node.state.checked = getNewCheckedState(node, this.selectedNodeIds));
|
||||
(node) => node.state = updateState(node.state, node, this.selectedNodeIds));
|
||||
}
|
||||
this.initialLiquourTreeNodes = initialNodes;
|
||||
} else {
|
||||
@@ -82,11 +83,11 @@
|
||||
@Watch('selectedNodeIds')
|
||||
public setSelectedStatusAsync(selectedNodeIds: ReadonlyArray<string>) {
|
||||
if (!selectedNodeIds) {
|
||||
throw new Error('Selected nodes are undefined');
|
||||
throw new Error('SelectedrecurseDown nodes are undefined');
|
||||
}
|
||||
this.getLiquorTreeApi().recurseDown((node) => {
|
||||
node.states.checked = getNewCheckedState(node, selectedNodeIds);
|
||||
});
|
||||
this.getLiquorTreeApi().recurseDown(
|
||||
(node) => node.states = updateState(node.states, node, selectedNodeIds),
|
||||
);
|
||||
}
|
||||
|
||||
private getLiquorTreeApi(): ILiquorTree {
|
||||
@@ -97,6 +98,13 @@
|
||||
}
|
||||
}
|
||||
|
||||
function updateState(
|
||||
old: ILiquorTreeNodeState,
|
||||
node: ILiquorTreeNode,
|
||||
selectedNodeIds: ReadonlyArray<string>): ILiquorTreeNodeState {
|
||||
return {...old, ...getNewState(node, selectedNodeIds)};
|
||||
}
|
||||
|
||||
function recurseDown(
|
||||
nodes: ReadonlyArray<ILiquorTreeNewNode>,
|
||||
handler: (node: ILiquorTreeNewNode) => void) {
|
||||
|
||||
@@ -72,10 +72,9 @@
|
||||
public isSearching = false;
|
||||
public searchHasMatches = false;
|
||||
|
||||
|
||||
public async mounted() {
|
||||
const state = await this.getCurrentStateAsync();
|
||||
this.repositoryUrl = state.app.repositoryUrl;
|
||||
this.repositoryUrl = state.app.info.repositoryWebUrl;
|
||||
state.filter.filterRemoved.on(() => {
|
||||
this.isSearching = false;
|
||||
});
|
||||
|
||||
@@ -18,13 +18,14 @@ const NothingChosenCode =
|
||||
.appendCommentLine('-- 🤔 How to use')
|
||||
.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(' 📙 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()
|
||||
.appendCommentLine('-- 🧐 Why privacy.sexy')
|
||||
.appendCommentLine(' ✔️ Rich tweak pool to harden security & privacy of the OS and other softwares on it.')
|
||||
.appendCommentLine(' ✔️ You don\'t need to run any compiled software on your system, just run the generated scripts.')
|
||||
.appendCommentLine(' ✔️ Rich tweak pool to harden security & privacy of the OS and other software on it.')
|
||||
.appendCommentLine(' ✔️ No need to run any compiled software on your system, just run the generated scripts.')
|
||||
.appendCommentLine(' ✔️ Have full visibility into what the tweaks do as you enable them.')
|
||||
.appendCommentLine(' ✔️ Free software, 100% transparency: both application & infrastructure code are open-sourced.')
|
||||
.appendCommentLine(' ✔️ Open-source and free (both free as in beer and free as in speech).')
|
||||
.toString();
|
||||
|
||||
@Component
|
||||
@@ -114,7 +115,7 @@ function initializeEditor(theme: string, editorId: string): ace.Ace.Editor {
|
||||
min-height: 200px;
|
||||
&__highlight {
|
||||
background-color:$accent;
|
||||
opacity: 20%;
|
||||
opacity: 0.2; // having procent fails in production (minified) build
|
||||
position:absolute;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
<script lang="ts">
|
||||
import { Component } from 'vue-property-decorator';
|
||||
import { StatefulVue } from './StatefulVue';
|
||||
import { SaveFileDialog } from './../infrastructure/SaveFileDialog';
|
||||
import { SaveFileDialog, FileType } from './../infrastructure/SaveFileDialog';
|
||||
import { Clipboard } from './../infrastructure/Clipboard';
|
||||
import IconButton from './IconButton.vue';
|
||||
|
||||
@@ -43,7 +43,7 @@ export default class TheCodeButtons extends StatefulVue {
|
||||
|
||||
public async saveCodeAsync() {
|
||||
const state = await this.getCurrentStateAsync();
|
||||
SaveFileDialog.saveText(state.code.current, 'privacy-script.bat');
|
||||
SaveFileDialog.saveFile(state.code.current, 'privacy-script.bat', FileType.BatchFile);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<script lang="ts">
|
||||
import { Component, Vue } from 'vue-property-decorator';
|
||||
import { Environment } from '@/application/Environment/Environment';
|
||||
import { OperatingSystem } from '@/application/Environment/OperatingSystem';
|
||||
import { OperatingSystem } from '@/domain/OperatingSystem';
|
||||
import DownloadUrlListItem from './DownloadUrlListItem.vue';
|
||||
|
||||
@Component({
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
import { Component, Prop, Watch } from 'vue-property-decorator';
|
||||
import { StatefulVue } from '@/presentation/StatefulVue';
|
||||
import { Environment } from '@/application/Environment/Environment';
|
||||
import { OperatingSystem } from '@/application/Environment/OperatingSystem';
|
||||
import { OperatingSystem } from '@/domain/OperatingSystem';
|
||||
|
||||
@Component
|
||||
export default class DownloadUrlListItem extends StatefulVue {
|
||||
@@ -39,7 +39,7 @@ export default class DownloadUrlListItem extends StatefulVue {
|
||||
|
||||
private async getDownloadUrlAsync(os: OperatingSystem): Promise<string> {
|
||||
const state = await this.getCurrentStateAsync();
|
||||
return `${state.app.repositoryUrl}/releases/download/${state.app.version}/${getFileName(os, state.app.version)}`;
|
||||
return state.app.info.getDownloadUrl(os);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,18 +62,6 @@ function getOperatingSystemName(os: OperatingSystem): string {
|
||||
}
|
||||
}
|
||||
|
||||
function getFileName(os: OperatingSystem, version: string): string {
|
||||
switch (os) {
|
||||
case OperatingSystem.Linux:
|
||||
return `privacy.sexy-${version}.AppImage`;
|
||||
case OperatingSystem.macOS:
|
||||
return `privacy.sexy-${version}.dmg`;
|
||||
case OperatingSystem.Windows:
|
||||
return `privacy.sexy-Setup-${version}.exe`;
|
||||
default:
|
||||
throw new Error(`Unsupported os: ${OperatingSystem[os]}`);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
@@ -48,8 +48,8 @@ export default class TheFooter extends StatefulVue {
|
||||
|
||||
public async mounted() {
|
||||
const state = await this.getCurrentStateAsync();
|
||||
this.repositoryUrl = state.app.repositoryUrl;
|
||||
this.feedbackUrl = `${state.app.repositoryUrl}/issues`;
|
||||
this.repositoryUrl = state.app.info.repositoryWebUrl;
|
||||
this.feedbackUrl = state.app.info.feedbackUrl;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<div class="footer__section">
|
||||
<span v-if="isDesktop" class="footer__section__item">
|
||||
<font-awesome-icon class="icon" :icon="['fas', 'globe']" />
|
||||
<span>Online version at <a href="https://privacy.sexy" target="_blank">https://privacy.sexy</a></span>
|
||||
<span>Online version at <a :href="homepageUrl" target="_blank">{{ homepageUrl }}</a></span>
|
||||
</span>
|
||||
<span v-else class="footer__section__item">
|
||||
<DownloadUrlList />
|
||||
@@ -66,6 +66,7 @@ export default class TheFooter extends StatefulVue {
|
||||
public repositoryUrl: string = '';
|
||||
public releaseUrl: string = '';
|
||||
public feedbackUrl: string = '';
|
||||
public homepageUrl: string = '';
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
@@ -74,12 +75,15 @@ export default class TheFooter extends StatefulVue {
|
||||
|
||||
public async mounted() {
|
||||
const state = await this.getCurrentStateAsync();
|
||||
this.version = state.app.version;
|
||||
this.repositoryUrl = state.app.repositoryUrl;
|
||||
this.releaseUrl = `${state.app.repositoryUrl}/releases/tag/${state.app.version}`;
|
||||
this.feedbackUrl = `${state.app.repositoryUrl}/issues`;
|
||||
const info = state.app.info;
|
||||
this.version = info.version;
|
||||
this.homepageUrl = info.homepage;
|
||||
this.repositoryUrl = info.repositoryWebUrl;
|
||||
this.releaseUrl = info.releaseUrl;
|
||||
this.feedbackUrl = info.feedbackUrl;
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
@@ -16,7 +16,7 @@ export default class TheHeader extends StatefulVue {
|
||||
|
||||
public async mounted() {
|
||||
const state = await this.getCurrentStateAsync();
|
||||
this.title = state.app.name;
|
||||
this.title = state.app.info.name;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -2,42 +2,41 @@
|
||||
@import "@/presentation/styles/colors.scss";
|
||||
|
||||
.tree {
|
||||
background-color: $slate;
|
||||
}
|
||||
|
||||
.tree-node > .tree-content > .tree-anchor > span {
|
||||
color: $white !important;
|
||||
text-transform: uppercase;
|
||||
color: $light-gray;
|
||||
font-size: 1.5em;
|
||||
}
|
||||
|
||||
.tree-node {
|
||||
white-space: normal !important;
|
||||
}
|
||||
|
||||
.tree-arrow.has-child {
|
||||
&.rtl:after, &:after {
|
||||
border-color: $white !important;
|
||||
background: $slate;
|
||||
&-node {
|
||||
white-space: normal !important;
|
||||
> .tree-content {
|
||||
> .tree-anchor > span {
|
||||
color: $white;
|
||||
text-transform: uppercase;
|
||||
font-size: 1.5em;
|
||||
}
|
||||
&:hover {
|
||||
background: $dark-gray !important;
|
||||
}
|
||||
}
|
||||
&.selected { // When using keyboard navigation it higlights current item and its child items
|
||||
background: $gray;
|
||||
.tree-text {
|
||||
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;
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { expect } from 'chai';
|
||||
import { OperatingSystem } from '@/application/Environment/OperatingSystem';
|
||||
import { OperatingSystem } from '@/domain/OperatingSystem';
|
||||
import { BrowserOsDetector } from '@/application/Environment/BrowserOs/BrowserOsDetector';
|
||||
import { BrowserOsTestCases } from './BrowserOsTestCases';
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { OperatingSystem } from '@/application/Environment/OperatingSystem';
|
||||
import { OperatingSystem } from '@/domain/OperatingSystem';
|
||||
|
||||
interface IBrowserOsTestCase {
|
||||
userAgent: string;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { OperatingSystem } from '@/application/Environment/OperatingSystem';
|
||||
import { OperatingSystem } from '@/domain/OperatingSystem';
|
||||
|
||||
interface IDesktopTestCase {
|
||||
processPlatform: string;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { IBrowserOsDetector } from '@/application/Environment/BrowserOs/IBrowserOsDetector';
|
||||
import { OperatingSystem } from '@/application/Environment/OperatingSystem';
|
||||
import { OperatingSystem } from '@/domain/OperatingSystem';
|
||||
import { DesktopOsTestCases } from './DesktopOsTestCases';
|
||||
import { Environment } from '@/application/Environment/Environment';
|
||||
import { expect } from 'chai';
|
||||
|
||||
@@ -5,8 +5,6 @@ import 'mocha';
|
||||
import { expect } from 'chai';
|
||||
import { parseCategory } from '@/application/Parser/CategoryParser';
|
||||
|
||||
declare var process;
|
||||
|
||||
describe('ApplicationParser', () => {
|
||||
describe('parseApplication', () => {
|
||||
it('can parse current application file', () => {
|
||||
@@ -16,74 +14,64 @@ describe('ApplicationParser', () => {
|
||||
expect(() => parseApplication(undefined)).to.throw('application is null or undefined');
|
||||
});
|
||||
it('throws when undefined actions', () => {
|
||||
const sut: ApplicationYaml = {
|
||||
name: 'test',
|
||||
repositoryUrl: 'https://privacy.sexy',
|
||||
actions: undefined,
|
||||
};
|
||||
const sut: ApplicationYaml = { actions: undefined };
|
||||
expect(() => parseApplication(sut)).to.throw('application does not define any action');
|
||||
});
|
||||
it('throws when has no actions', () => {
|
||||
const sut: ApplicationYaml = {
|
||||
name: 'test',
|
||||
repositoryUrl: 'https://privacy.sexy',
|
||||
actions: [],
|
||||
};
|
||||
const sut: ApplicationYaml = { actions: [] };
|
||||
expect(() => parseApplication(sut)).to.throw('application does not define any action');
|
||||
});
|
||||
it('returns expected name', () => {
|
||||
// arrange
|
||||
const expected = 'test-app-name';
|
||||
const sut: ApplicationYaml = {
|
||||
name: expected,
|
||||
repositoryUrl: 'https://privacy.sexy',
|
||||
actions: [ getTestCategory() ],
|
||||
};
|
||||
// act
|
||||
const actual = parseApplication(sut).name;
|
||||
// assert
|
||||
expect(actual).to.be.equal(actual);
|
||||
});
|
||||
it('returns expected repository url', () => {
|
||||
// arrange
|
||||
const expected = 'https://privacy.sexy';
|
||||
const sut: ApplicationYaml = {
|
||||
name: 'name',
|
||||
repositoryUrl: expected,
|
||||
actions: [ getTestCategory() ],
|
||||
};
|
||||
// act
|
||||
const actual = parseApplication(sut).repositoryUrl;
|
||||
// assert
|
||||
expect(actual).to.be.equal(actual);
|
||||
});
|
||||
it('returns expected repository version', () => {
|
||||
// arrange
|
||||
const expected = '1.0.0';
|
||||
process = {
|
||||
env: {
|
||||
VUE_APP_VERSION: expected,
|
||||
},
|
||||
};
|
||||
const sut: ApplicationYaml = {
|
||||
name: 'name',
|
||||
repositoryUrl: 'https://privacy.sexy',
|
||||
actions: [ getTestCategory() ],
|
||||
};
|
||||
// act
|
||||
const actual = parseApplication(sut).version;
|
||||
// assert
|
||||
expect(actual).to.be.equal(actual);
|
||||
describe('information', () => {
|
||||
it('returns expected repository version', () => {
|
||||
// arrange
|
||||
const expected = 'expected-version';
|
||||
const env = getProcessEnvironmentStub();
|
||||
env.VUE_APP_VERSION = expected;
|
||||
const sut: ApplicationYaml = { actions: [ getTestCategory() ] };
|
||||
// act
|
||||
const actual = parseApplication(sut, env).info.version;
|
||||
// assert
|
||||
expect(actual).to.be.equal(expected);
|
||||
});
|
||||
it('returns expected repository url', () => {
|
||||
// arrange
|
||||
const expected = 'https://expected-repository.url';
|
||||
const env = getProcessEnvironmentStub();
|
||||
env.VUE_APP_REPOSITORY_URL = expected;
|
||||
const sut: ApplicationYaml = { actions: [ getTestCategory() ] };
|
||||
// act
|
||||
const actual = parseApplication(sut, env).info.repositoryUrl;
|
||||
// assert
|
||||
expect(actual).to.be.equal(expected);
|
||||
});
|
||||
it('returns expected name', () => {
|
||||
// arrange
|
||||
const expected = 'expected-app-name';
|
||||
const env = getProcessEnvironmentStub();
|
||||
env.VUE_APP_NAME = expected;
|
||||
const sut: ApplicationYaml = { actions: [ getTestCategory() ] };
|
||||
// act
|
||||
const actual = parseApplication(sut, env).info.name;
|
||||
// assert
|
||||
expect(actual).to.be.equal(expected);
|
||||
});
|
||||
it('returns expected homepage url', () => {
|
||||
// arrange
|
||||
const expected = 'https://expected.sexy';
|
||||
const env = getProcessEnvironmentStub();
|
||||
env.VUE_APP_HOMEPAGE_URL = expected;
|
||||
const sut: ApplicationYaml = { actions: [ getTestCategory() ] };
|
||||
// act
|
||||
const actual = parseApplication(sut, env).info.homepage;
|
||||
// assert
|
||||
expect(actual).to.be.equal(expected);
|
||||
});
|
||||
});
|
||||
it('parses actions', () => {
|
||||
// arrange
|
||||
const actions = [ getTestCategory('test1'), getTestCategory('test2') ];
|
||||
const expected = [ parseCategory(actions[0]), parseCategory(actions[1]) ];
|
||||
const sut: ApplicationYaml = {
|
||||
name: 'name',
|
||||
repositoryUrl: 'https://privacy.sexy',
|
||||
actions,
|
||||
};
|
||||
const sut: ApplicationYaml = { actions };
|
||||
// act
|
||||
const actual = parseApplication(sut).actions;
|
||||
// assert
|
||||
@@ -113,3 +101,12 @@ function getTestScript(scriptName: string): YamlScript {
|
||||
recommend: true,
|
||||
};
|
||||
}
|
||||
|
||||
function getProcessEnvironmentStub(): NodeJS.ProcessEnv {
|
||||
return {
|
||||
VUE_APP_VERSION: 'stub-version',
|
||||
VUE_APP_NAME: 'stub-name',
|
||||
VUE_APP_REPOSITORY_URL: 'stub-repository-url',
|
||||
VUE_APP_HOMEPAGE_URL: 'stub-homepage-url',
|
||||
};
|
||||
}
|
||||
|
||||
@@ -7,129 +7,156 @@ import 'mocha';
|
||||
import { expect } from 'chai';
|
||||
|
||||
describe('UserFilter', () => {
|
||||
it('signals when removing filter', () => {
|
||||
// arrange
|
||||
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', () => {
|
||||
describe('removeFilter', () => {
|
||||
it('signals when removing filter', () => {
|
||||
// arrange
|
||||
const code = 'HELLO world';
|
||||
const filter = 'Hello WoRLD';
|
||||
let actual: IFilterResult;
|
||||
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);
|
||||
let isCalled = false;
|
||||
const sut = new UserFilter(new ApplicationStub());
|
||||
sut.filterRemoved.on(() => isCalled = true);
|
||||
// act
|
||||
sut.setFilter(filter);
|
||||
sut.removeFilter();
|
||||
// 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(isCalled).to.be.equal(true);
|
||||
});
|
||||
it('revertCode matches', () => {
|
||||
it('currentFilter is undefined', () => {
|
||||
// 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);
|
||||
const sut = new UserFilter(new ApplicationStub());
|
||||
// act
|
||||
sut.setFilter(filter);
|
||||
sut.removeFilter();
|
||||
// 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('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.be.equal(undefined);
|
||||
});
|
||||
});
|
||||
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);
|
||||
});
|
||||
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);
|
||||
describe('setFilter', () => {
|
||||
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.query).to.equal(nonMatchingFilter);
|
||||
});
|
||||
it('sets currentFilter as expected when no matches', () => {
|
||||
// arrange
|
||||
const nonMatchingFilter = 'non matching filter';
|
||||
const sut = new UserFilter(new ApplicationStub());
|
||||
// act
|
||||
sut.setFilter(nonMatchingFilter);
|
||||
// assert
|
||||
const actual = sut.currentFilter;
|
||||
expect(actual.hasAnyMatches()).be.equal(false);
|
||||
expect(actual.query).to.equal(nonMatchingFilter);
|
||||
});
|
||||
describe('signals when script matches', () => {
|
||||
it('code matches', () => {
|
||||
// arrange
|
||||
const code = 'HELLO world';
|
||||
const filter = 'Hello WoRLD';
|
||||
let actual: IFilterResult;
|
||||
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
|
||||
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('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);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { ScriptStub } from './../stubs/ScriptStub';
|
||||
import { CategoryStub } from './../stubs/CategoryStub';
|
||||
import { Application } from './../../../src/domain/Application';
|
||||
import { Application } from '@/domain/Application';
|
||||
import 'mocha';
|
||||
import { expect } from 'chai';
|
||||
import { ProjectInformation } from '@/domain/ProjectInformation';
|
||||
import { IProjectInformation } from '@/domain/IProjectInformation';
|
||||
|
||||
describe('Application', () => {
|
||||
it('getRecommendedScripts returns as expected', () => {
|
||||
@@ -11,53 +13,56 @@ describe('Application', () => {
|
||||
new ScriptStub('S3').withIsRecommended(true),
|
||||
new ScriptStub('S4').withIsRecommended(true),
|
||||
];
|
||||
const sut = new Application('name', 'repo', '0.1.0', [
|
||||
const sut = new Application(createInformation(), [
|
||||
new CategoryStub(3).withScripts(expected[0], new ScriptStub('S1').withIsRecommended(false)),
|
||||
new CategoryStub(2).withScripts(expected[1], new ScriptStub('S2').withIsRecommended(false)),
|
||||
]);
|
||||
|
||||
// act
|
||||
const actual = sut.getRecommendedScripts();
|
||||
|
||||
// assert
|
||||
expect(expected[0]).to.deep.equal(actual[0]);
|
||||
expect(expected[1]).to.deep.equal(actual[1]);
|
||||
});
|
||||
it('cannot construct without categories', () => {
|
||||
// arrange
|
||||
const categories = [];
|
||||
|
||||
// act
|
||||
function construct() { return new Application('name', 'repo', '0.1.0', categories); }
|
||||
|
||||
// assert
|
||||
expect(construct).to.throw('Application must consist of at least one category');
|
||||
});
|
||||
it('cannot construct without scripts', () => {
|
||||
// arrange
|
||||
const categories = [
|
||||
new CategoryStub(3),
|
||||
new CategoryStub(2),
|
||||
];
|
||||
|
||||
// act
|
||||
function construct() { return new Application('name', 'repo', '0.1.0', categories); }
|
||||
|
||||
// assert
|
||||
expect(construct).to.throw('Application must consist of at least one script');
|
||||
});
|
||||
it('cannot construct without any recommended scripts', () => {
|
||||
// arrange
|
||||
const categories = [
|
||||
new CategoryStub(3).withScripts(new ScriptStub('S1').withIsRecommended(false)),
|
||||
new CategoryStub(2).withScripts(new ScriptStub('S2').withIsRecommended(false)),
|
||||
];
|
||||
|
||||
// act
|
||||
function construct() { return new Application('name', 'repo', '0.1.0', categories); }
|
||||
|
||||
// assert
|
||||
expect(construct).to.throw('Application must consist of at least one recommended script');
|
||||
describe('parameter validation', () => {
|
||||
it('cannot construct without categories', () => {
|
||||
// arrange
|
||||
const categories = [];
|
||||
// act
|
||||
function construct() { return new Application(createInformation(), categories); }
|
||||
// assert
|
||||
expect(construct).to.throw('Application must consist of at least one category');
|
||||
});
|
||||
it('cannot construct without scripts', () => {
|
||||
// arrange
|
||||
const categories = [
|
||||
new CategoryStub(3),
|
||||
new CategoryStub(2),
|
||||
];
|
||||
// act
|
||||
function construct() { return new Application(createInformation(), categories); }
|
||||
// assert
|
||||
expect(construct).to.throw('Application must consist of at least one script');
|
||||
});
|
||||
it('cannot construct without any recommended scripts', () => {
|
||||
// arrange
|
||||
const categories = [
|
||||
new CategoryStub(3).withScripts(new ScriptStub('S1').withIsRecommended(false)),
|
||||
new CategoryStub(2).withScripts(new ScriptStub('S2').withIsRecommended(false)),
|
||||
];
|
||||
// act
|
||||
function construct() { return new Application(createInformation(), categories); }
|
||||
// assert
|
||||
expect(construct).to.throw('Application must consist of at least one recommended script');
|
||||
});
|
||||
it('cannot construct without information', () => {
|
||||
// arrange
|
||||
const categories = [new CategoryStub(1).withScripts(new ScriptStub('S1').withIsRecommended(true))];
|
||||
const information = undefined;
|
||||
// act
|
||||
function construct() { return new Application(information, categories); }
|
||||
// assert
|
||||
expect(construct).to.throw('info is undefined');
|
||||
});
|
||||
});
|
||||
it('totalScripts counts right', () => {
|
||||
// arrange
|
||||
@@ -67,9 +72,9 @@ describe('Application', () => {
|
||||
new CategoryStub(3).withCategories(new CategoryStub(4).withScripts(new ScriptStub('S4'))),
|
||||
];
|
||||
// act
|
||||
const application = new Application('name', 'repo', '0.1.0', categories);
|
||||
const sut = new Application(createInformation(), categories);
|
||||
// assert
|
||||
expect(application.totalScripts).to.equal(4);
|
||||
expect(sut.totalScripts).to.equal(4);
|
||||
});
|
||||
it('totalCategories counts right', () => {
|
||||
// arrange
|
||||
@@ -79,8 +84,22 @@ describe('Application', () => {
|
||||
new CategoryStub(3).withCategories(new CategoryStub(4).withScripts(new ScriptStub('S4'))),
|
||||
];
|
||||
// act
|
||||
const application = new Application('name', 'repo', '0.1.0', categories);
|
||||
const sut = new Application(createInformation(), categories);
|
||||
// assert
|
||||
expect(application.totalCategories).to.equal(4);
|
||||
expect(sut.totalCategories).to.equal(4);
|
||||
});
|
||||
it('sets information as expected', () => {
|
||||
// arrange
|
||||
const expected = createInformation();
|
||||
// act
|
||||
const sut = new Application(
|
||||
expected,
|
||||
[new CategoryStub(1).withScripts(new ScriptStub('S1').withIsRecommended(true))]);
|
||||
// assert
|
||||
expect(sut.info).to.deep.equal(expected);
|
||||
});
|
||||
});
|
||||
|
||||
function createInformation(): IProjectInformation {
|
||||
return new ProjectInformation('name', 'repo', '0.1.0', 'homepage');
|
||||
}
|
||||
|
||||
128
tests/unit/domain/ProjectInformation.spec.ts
Normal file
128
tests/unit/domain/ProjectInformation.spec.ts
Normal file
@@ -0,0 +1,128 @@
|
||||
import 'mocha';
|
||||
import { expect } from 'chai';
|
||||
import { ProjectInformation } from '@/domain/ProjectInformation';
|
||||
import { OperatingSystem } from '@/domain/OperatingSystem';
|
||||
|
||||
describe('ProjectInformation', () => {
|
||||
it('sets name as expected', () => {
|
||||
// arrange
|
||||
const expected = 'expected-name';
|
||||
const sut = new ProjectInformation(expected, 'version', 'repositoryUrl', 'homepage');
|
||||
// act
|
||||
const actual = sut.name;
|
||||
// assert
|
||||
expect(actual).to.equal(expected);
|
||||
});
|
||||
it('sets version as expected', () => {
|
||||
// arrange
|
||||
const expected = 'expected-version';
|
||||
const sut = new ProjectInformation('name', expected, 'repositoryUrl', 'homepage');
|
||||
// act
|
||||
const actual = sut.version;
|
||||
// assert
|
||||
expect(actual).to.equal(expected);
|
||||
});
|
||||
it('sets repositoryUrl as expected', () => {
|
||||
// arrange
|
||||
const expected = 'expected-repository-url';
|
||||
const sut = new ProjectInformation('name', 'version', expected, 'homepage');
|
||||
// act
|
||||
const actual = sut.repositoryUrl;
|
||||
// assert
|
||||
expect(actual).to.equal(expected);
|
||||
});
|
||||
describe('sets repositoryWebUrl as expected', () => {
|
||||
it('sets repositoryUrl when it does not end with .git', () => {
|
||||
// arrange
|
||||
const expected = 'expected-repository-url';
|
||||
const sut = new ProjectInformation('name', 'version', expected, 'homepage');
|
||||
// act
|
||||
const actual = sut.repositoryWebUrl;
|
||||
// assert
|
||||
expect(actual).to.equal(expected);
|
||||
});
|
||||
it('removes ".git" from the end when it ends with ".git"', () => {
|
||||
// arrange
|
||||
const expected = 'expected-repository-url';
|
||||
const sut = new ProjectInformation('name', 'version', `${expected}.git`, 'homepage');
|
||||
// act
|
||||
const actual = sut.repositoryWebUrl;
|
||||
// assert
|
||||
expect(actual).to.equal(expected);
|
||||
});
|
||||
});
|
||||
it('sets homepage as expected', () => {
|
||||
// arrange
|
||||
const expected = 'expected-homepage';
|
||||
const sut = new ProjectInformation('name', 'version', 'repositoryUrl', expected);
|
||||
// act
|
||||
const actual = sut.homepage;
|
||||
// assert
|
||||
expect(actual).to.equal(expected);
|
||||
});
|
||||
it('sets feedbackUrl to github issues page', () => {
|
||||
// arrange
|
||||
const repositoryUrl = 'https://github.com/undergroundwires/privacy.sexy.git';
|
||||
const expected = 'https://github.com/undergroundwires/privacy.sexy/issues';
|
||||
const sut = new ProjectInformation('name', 'version', repositoryUrl, 'homepage');
|
||||
// act
|
||||
const actual = sut.feedbackUrl;
|
||||
// assert
|
||||
expect(actual).to.equal(expected);
|
||||
});
|
||||
it('sets releaseUrl to github releases page', () => {
|
||||
// arrange
|
||||
const repositoryUrl = 'https://github.com/undergroundwires/privacy.sexy.git';
|
||||
const version = '0.7.2';
|
||||
const expected = 'https://github.com/undergroundwires/privacy.sexy/releases/tag/0.7.2';
|
||||
const sut = new ProjectInformation('name', version, repositoryUrl, 'homepage');
|
||||
// act
|
||||
const actual = sut.releaseUrl;
|
||||
// assert
|
||||
expect(actual).to.equal(expected);
|
||||
});
|
||||
describe('getDownloadUrl', () => {
|
||||
it('gets expected url for macOS', () => {
|
||||
// arrange
|
||||
const expected = 'https://github.com/undergroundwires/privacy.sexy/releases/download/0.7.2/privacy.sexy-0.7.2.dmg';
|
||||
const repositoryUrl = 'https://github.com/undergroundwires/privacy.sexy.git';
|
||||
const version = '0.7.2';
|
||||
const sut = new ProjectInformation('name', version, repositoryUrl, 'homepage');
|
||||
// act
|
||||
const actual = sut.getDownloadUrl(OperatingSystem.macOS);
|
||||
// assert
|
||||
expect(actual).to.equal(expected);
|
||||
});
|
||||
it('gets expected url for Linux', () => {
|
||||
// arrange
|
||||
const expected = 'https://github.com/undergroundwires/privacy.sexy/releases/download/0.7.2/privacy.sexy-0.7.2.AppImage';
|
||||
const repositoryUrl = 'https://github.com/undergroundwires/privacy.sexy.git';
|
||||
const version = '0.7.2';
|
||||
const sut = new ProjectInformation('name', version, repositoryUrl, 'homepage');
|
||||
// act
|
||||
const actual = sut.getDownloadUrl(OperatingSystem.Linux);
|
||||
// assert
|
||||
expect(actual).to.equal(expected);
|
||||
});
|
||||
it('gets expected url for Windows', () => {
|
||||
// arrange
|
||||
const expected = 'https://github.com/undergroundwires/privacy.sexy/releases/download/0.7.2/privacy.sexy-Setup-0.7.2.exe';
|
||||
const repositoryUrl = 'https://github.com/undergroundwires/privacy.sexy.git';
|
||||
const version = '0.7.2';
|
||||
const sut = new ProjectInformation('name', version, repositoryUrl, 'homepage');
|
||||
// act
|
||||
const actual = sut.getDownloadUrl(OperatingSystem.Windows);
|
||||
// assert
|
||||
expect(actual).to.equal(expected);
|
||||
});
|
||||
it('throws when OS is unknown', () => {
|
||||
// arrange
|
||||
const sut = new ProjectInformation('name', 'version', 'repositoryUrl', 'homepage');
|
||||
const os = OperatingSystem.Unknown;
|
||||
// act
|
||||
const act = () => sut.getDownloadUrl(os);
|
||||
// assert
|
||||
expect(act).to.throw(`Unsupported os: ${OperatingSystem[os]}`);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -2,97 +2,189 @@ import 'mocha';
|
||||
import { expect } from 'chai';
|
||||
import { ILiquorTreeNode } from 'liquor-tree';
|
||||
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('script node', () => {
|
||||
it('state is true when selected', () => {
|
||||
// arrange
|
||||
const node = getScriptNode();
|
||||
const selectedScriptNodeIds = [ 'a', 'b', node.id, 'c' ];
|
||||
// act
|
||||
const actual = getNewCheckedState(node, selectedScriptNodeIds);
|
||||
// assert
|
||||
expect(actual).to.equal(true);
|
||||
describe('getNewState', () => {
|
||||
describe('checked', () => {
|
||||
describe('script node', () => {
|
||||
it('true when selected', () => {
|
||||
// arrange
|
||||
const node = getScriptNode();
|
||||
const selectedScriptNodeIds = [ 'a', 'b', node.id, 'c' ];
|
||||
// act
|
||||
const state = getNewState(node, selectedScriptNodeIds);
|
||||
// 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', () => {
|
||||
// arrange
|
||||
const node = getScriptNode();
|
||||
const selectedScriptNodeIds = [ 'a', 'b', 'c' ];
|
||||
// act
|
||||
const actual = getNewCheckedState(node, selectedScriptNodeIds);
|
||||
// assert
|
||||
expect(actual).to.equal(false);
|
||||
describe('category node', () => {
|
||||
it('true when every child 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.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', () => {
|
||||
it('state is true when every child 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 actual = getNewCheckedState(node, selectedScriptNodeIds);
|
||||
// assert
|
||||
expect(actual).to.equal(true);
|
||||
describe('indeterminate', () => {
|
||||
describe('script node', () => {
|
||||
it('false when selected', () => {
|
||||
// arrange
|
||||
const node = getScriptNode();
|
||||
const selectedScriptNodeIds = [ 'a', 'b', node.id, 'c' ];
|
||||
// act
|
||||
const state = getNewState(node, selectedScriptNodeIds);
|
||||
// assert
|
||||
expect(state.indeterminate).to.equal(false);
|
||||
});
|
||||
it('false when not selected', () => {
|
||||
// arrange
|
||||
const node = getScriptNode();
|
||||
const selectedScriptNodeIds = [ 'a', 'b', 'c' ];
|
||||
// act
|
||||
const state = getNewState(node, selectedScriptNodeIds);
|
||||
// assert
|
||||
expect(state.indeterminate).to.equal(false);
|
||||
});
|
||||
});
|
||||
it('state is 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 actual = getNewCheckedState(node, selectedScriptNodeIds);
|
||||
// assert
|
||||
expect(actual).to.equal(false);
|
||||
});
|
||||
it('state is 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 actual = getNewCheckedState(node, selectedScriptNodeIds);
|
||||
// assert
|
||||
expect(actual).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);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -108,6 +108,7 @@ function getNewNode(): ILiquorTreeNewNode {
|
||||
const base = getNode();
|
||||
const commonState = {
|
||||
checked: false,
|
||||
indeterminate: false,
|
||||
};
|
||||
return {
|
||||
id: base.id,
|
||||
|
||||
@@ -68,7 +68,7 @@ describe('ScriptReverter', () => {
|
||||
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,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import { IApplication, ICategory, IScript } from '@/domain/IApplication';
|
||||
import { ProjectInformation } from '@/domain/ProjectInformation';
|
||||
|
||||
export class ApplicationStub implements IApplication {
|
||||
public totalScripts = 0;
|
||||
public totalCategories = 0;
|
||||
public readonly name = 'StubApplication';
|
||||
public readonly repositoryUrl = 'https://privacy.sexy';
|
||||
public readonly version = '0.1.0';
|
||||
public readonly info = new ProjectInformation('StubApplication', '0.1.0', 'https://github.com/undergroundwires/privacy.sexy', 'https://privacy.sexy');
|
||||
public readonly actions = new Array<ICategory>();
|
||||
|
||||
public withAction(category: ICategory): ApplicationStub {
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
process.env.VUE_APP_VERSION = require('./package.json').version;
|
||||
const packageJson = require('./package.json');
|
||||
|
||||
process.env.VUE_APP_VERSION = packageJson.version;
|
||||
process.env.VUE_APP_NAME = packageJson.name;
|
||||
process.env.VUE_APP_REPOSITORY_URL = packageJson.repository.url;
|
||||
process.env.VUE_APP_HOMEPAGE_URL = packageJson.homepage;
|
||||
|
||||
module.exports = {
|
||||
pluginOptions: {
|
||||
|
||||
Reference in New Issue
Block a user