Compare commits

..

13 Commits

Author SHA1 Message Date
undergroundwires
b25b8cc805 fix vue warning for undefined property during render
currentOs is not recognized as reactive property as it's set to "undefined". JavaScript does not accept "undefined" as valid value to initialize. A property needs to be initialized with a non-undefined value to become reactive in a class-based component. Otherwise Vue warns: Property or method "currentOs" is not defined on the instance but referenced during render.
2021-04-19 18:21:06 +02:00
Marc05
8141a01ef7 fix typo and dead URL in Windows scripts (#70)
Co-authored-by: Marc05 <git@marc05.net>
2021-04-18 19:12:50 +02:00
undergroundwires
a2f10857e2 fix script revert activating recommendation level
Reverting any single of the scripts from standard recommendation pool
shows "Standard" selection as selected which is wrong. This commit fixes
it, refactors selection handling in a separate class and it also adds
missing tests. It removes UserSelection.totalSelected propertty in favor of using
UserSelection.selectedScripts.length to provide unified way of accessing
the information.
2021-04-17 14:34:29 +01:00
undergroundwires
aea04e5f7c document chromium warning for policy changes
Chromium shows "Your browser is managed" or "Your browser is managed by an organization" warnings when its behavior is manipulated using policies. This message confuses some users, so the commit marks this behavior to let users know why the box appears.
Read more:
- https://chromium.googlesource.com/chromium/src/+/refs/tags/92.0.4475.1/chrome/browser/ui/managed_ui.cc#67
- https://support.google.com/chrome/thread/3262871
2021-04-16 17:56:31 +02:00
undergroundwires
60c80611ea add module alias '@tests/'
Alias would remove unnecessary repetitions and less relative paths make changes easier when moving around files. This commit cleans also up some relative paths ('../../../') by using the alias and orders imports. It updates both path alias in tsconfig and module alias in Vue CLI's bundler (vuejs/vue-cli#2398).
2021-04-15 18:34:40 +02:00
undergroundwires
b1ed3ce55f document breaking behavior in script name #64
Removing Cloud Experience Hosting app breaks Microsoft cloud and
corporate sign in. It's now documented more cleary in the name of the
script.
2021-04-14 15:44:22 +01:00
undergroundwires
040ed2701c improve disabling ads and marketing #65
This commit documents the behavior better with more granularity of
choice and also adds options to revert the code.
2021-04-13 16:39:44 +01:00
undergroundwires
00d8e551db refactor extra code, duplicates, complexity
- refactor array equality check and add tests
- remove OperatingSystem.Unknown causing extra logic, return undefined instead
- refactor enum validation to share same logic
- refactor scripting language factories to share same logic
- refactor too many args in runCodeAsync
- refactor ScriptCode constructor to reduce complexity
- fix writing useless write to member object since another property write always override it
2021-04-11 14:37:02 +01:00
dependabot[bot]
3e9c99f5f8 Bump y18n from 3.2.1 to 3.2.2 (#66)
Bumps [y18n](https://github.com/yargs/y18n) from 3.2.1 to 3.2.2.
- [Release notes](https://github.com/yargs/y18n/releases)
- [Changelog](https://github.com/yargs/y18n/blob/master/CHANGELOG.md)
- [Commits](https://github.com/yargs/y18n/commits)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-04-06 20:29:15 +02:00
undergroundwires
02bdc4cf04 fix desktop initial window size being bigger than current display size on smaller Linux/Windows screens 2021-04-05 14:31:31 +01:00
undergroundwires
5c43965f0b in CI/CD, run other tests/check even if one of them fails 2021-03-28 14:26:20 +01:00
dependabot[bot]
b2376ecc30 Bump elliptic from 6.5.3 to 6.5.4 (#62)
Bumps [elliptic](https://github.com/indutny/elliptic) from 6.5.3 to 6.5.4.
- [Release notes](https://github.com/indutny/elliptic/releases)
- [Commits](https://github.com/indutny/elliptic/compare/v6.5.3...v6.5.4)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-03-27 17:15:30 +01:00
undergroundwires-bot
aeaa6deeb4 ⬆️ bump everywhere to 0.10.1 2021-03-26 14:57:03 +00:00
107 changed files with 1157 additions and 568 deletions

View File

@@ -13,6 +13,7 @@ jobs:
- npm run lint:md
- npm run lint:md:relative-urls
- npm run lint:md:consistency
fail-fast: false # So it continues with other commands if one fails
steps:
- name: Checkout
uses: actions/checkout@v2

View File

@@ -7,6 +7,7 @@ jobs:
strategy:
matrix:
os: [macos, ubuntu, windows]
fail-fast: false # So it still runs on other OSes if one of them fails
runs-on: ${{ matrix.os }}-latest
steps:
-

View File

@@ -1,5 +1,20 @@
# Changelog
## 0.10.1 (2021-03-25)
* refactor script compilation to make it easy to add new expressions #41 #53 | [646db90](https://github.com/undergroundwires/privacy.sexy/commit/646db9058541cebd0af437554de04fdc6bb63a6e)
* restructure presentation layer | [f3c7413](https://github.com/undergroundwires/privacy.sexy/commit/f3c7413f529be4a00dba7b0ab23904b48ea13a35)
* fix a test where "it" is not used inside "describe" | [1a5f920](https://github.com/undergroundwires/privacy.sexy/commit/1a5f92021f7423cd039f8f5326cd6f99b355c962)
* bump dependencies to latest | [1f515e7](https://github.com/undergroundwires/privacy.sexy/commit/1f515e7be525291c960ccb71db05312db6da53f5)
* fix throttle function not being able to run with argument(s) | [1935db1](https://github.com/undergroundwires/privacy.sexy/commit/1935db10192051401ab00ca2cd767955d0d3b866)
* fix fs module hanging not allowing code to run | [5f527a0](https://github.com/undergroundwires/privacy.sexy/commit/5f527a00cf225d3e74b3f6577d6e2456e919de24)
* refactor all modals to use same dialog component | [6f46cdb](https://github.com/undergroundwires/privacy.sexy/commit/6f46cdb4ed49a8941c6c0dde5c5e2a816c06daef)
* fix safari cleanup scripts that are not working on modern versions | [05932c5](https://github.com/undergroundwires/privacy.sexy/commit/05932c5a36446d551c5bc811165e3295fbe15e3f)
* refactor features to use shared functions #41 | [ac2249f](https://github.com/undergroundwires/privacy.sexy/commit/ac2249f25664827d8a6d2c7ebd659ccf126b0cde)
* increase performance by polyfilling ResizeObserver only if required | [448e378](https://github.com/undergroundwires/privacy.sexy/commit/448e378dc4501f9de69af63634c87d0e5060bf52)
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.10.0...0.10.1)
## 0.10.0 (2021-03-02)
* allow functions to call other functions #53 | [7661575](https://github.com/undergroundwires/privacy.sexy/commit/7661575573c6d3e8f4bc28bfa7a124a764c72ef9)

View File

@@ -16,7 +16,7 @@
- Online version at [https://privacy.sexy](https://privacy.sexy)
- 💡 No need to run any compiled software on your computer.
- Alternatively download offline version for [Windows](https://github.com/undergroundwires/privacy.sexy/releases/download/0.10.0/privacy.sexy-Setup-0.10.0.exe), [macOS](https://github.com/undergroundwires/privacy.sexy/releases/download/0.10.0/privacy.sexy-0.10.0.dmg) or [Linux](https://github.com/undergroundwires/privacy.sexy/releases/download/0.10.0/privacy.sexy-0.10.0.AppImage).
- Alternatively download offline version for [Windows](https://github.com/undergroundwires/privacy.sexy/releases/download/0.10.1/privacy.sexy-Setup-0.10.1.exe), [macOS](https://github.com/undergroundwires/privacy.sexy/releases/download/0.10.1/privacy.sexy-0.10.1.dmg) or [Linux](https://github.com/undergroundwires/privacy.sexy/releases/download/0.10.1/privacy.sexy-0.10.1.AppImage).
- 💡 Single click to execute your script.
- ❗ Come back regularly to apply latest version for stronger privacy and security.
@@ -53,8 +53,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.10.0 .`
2. Run: `docker run -it -p 8080:80 --rm --name privacy.sexy-0.10.0 undergroundwires/privacy.sexy:0.10.0`
1. Build: `docker build -t undergroundwires/privacy.sexy:0.10.1 .`
2. Run: `docker run -it -p 8080:80 --rm --name privacy.sexy-0.10.1 undergroundwires/privacy.sexy:0.10.1`
## Architecture overview

View File

@@ -3,6 +3,14 @@
- It's mainly responsible for
- creating and event based [application state](#application-state)
- [parsing](#parsing) and [compiling](#compiling) [application data](#application-data)
- Consumed by [presentation layer](./presentation.md)
## Structure
- [`/src/` **`application/`**](./../src/application/): Contains all application related code.
- [**`collections/`**](./../src/application/collections/): Holds [collection files](./collection-files.md)
- [**`Common/`**](./../src/application/Common/): Contains common functionality that is shared in application layer.
- `..`: other classes are categorized using folders-by-feature structure
## Application state

View File

@@ -1,16 +1,20 @@
# Unit tests
# Tests
- 💡 You can use path/module alias `@/tests` in import statements.
## Unit tests
- Unit tests are defined in [`./tests`](./../tests)
- They follow same folder structure as [`./src`](./../src)
## Naming
### Naming
- Each test suite first describe the system under test
- E.g. tests for class `Application` is categorized under `Application`
- Tests for specific methods are categorized under method name (if applicable)
- E.g. test for `run()` is categorized under `run`
## Act, arrange, assert
### Act, arrange, assert
- Tests use act, arrange and assert (AAA) pattern when applicable
- **Arrange**
@@ -23,7 +27,7 @@
- Should elicit some sort of response
- Starts with comment line `// assert`
## Stubs
### Stubs
- Stubs are defined in [`./tests/stubs`](./../tests/unit/stubs)
- They implement dummy behavior to be functional

81
package-lock.json generated
View File

@@ -1,6 +1,6 @@
{
"name": "privacy.sexy",
"version": "0.10.0",
"version": "0.10.1",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -1681,8 +1681,7 @@
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz",
"integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==",
"dev": true,
"optional": true
"dev": true
},
"@types/q": {
"version": "1.5.4",
@@ -4109,8 +4108,7 @@
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
"integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
"dev": true,
"optional": true
"dev": true
},
"camel-case": {
"version": "3.0.0",
@@ -5106,7 +5104,6 @@
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz",
"integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==",
"dev": true,
"optional": true,
"requires": {
"@types/parse-json": "^4.0.0",
"import-fresh": "^3.1.0",
@@ -5565,8 +5562,7 @@
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz",
"integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==",
"dev": true,
"optional": true
"dev": true
},
"default-gateway": {
"version": "5.0.5",
@@ -7258,7 +7254,6 @@
"resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-5.2.1.tgz",
"integrity": "sha512-SVi+ZAQOGbtAsUWrZvGzz38ga2YqjWvca1pXQFUArIVXqli0lLoDQ8uS0wg0kSpcwpZmaW5jVCZXQebkyUQSsw==",
"dev": true,
"optional": true,
"requires": {
"@babel/code-frame": "^7.8.3",
"@types/json-schema": "^7.0.5",
@@ -7278,7 +7273,6 @@
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dev": true,
"optional": true,
"requires": {
"color-convert": "^2.0.1"
}
@@ -7288,7 +7282,6 @@
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz",
"integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==",
"dev": true,
"optional": true,
"requires": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
@@ -7299,7 +7292,6 @@
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"optional": true,
"requires": {
"color-name": "~1.1.4"
}
@@ -7308,22 +7300,19 @@
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true,
"optional": true
"dev": true
},
"has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true,
"optional": true
"dev": true
},
"lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
"dev": true,
"optional": true,
"requires": {
"yallist": "^4.0.0"
}
@@ -7333,7 +7322,6 @@
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz",
"integrity": "sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==",
"dev": true,
"optional": true,
"requires": {
"@types/json-schema": "^7.0.4",
"ajv": "^6.12.2",
@@ -7341,11 +7329,10 @@
}
},
"semver": {
"version": "7.3.4",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz",
"integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==",
"version": "7.3.5",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz",
"integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==",
"dev": true,
"optional": true,
"requires": {
"lru-cache": "^6.0.0"
}
@@ -7355,7 +7342,6 @@
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"dev": true,
"optional": true,
"requires": {
"has-flag": "^4.0.0"
}
@@ -7364,8 +7350,7 @@
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true,
"optional": true
"dev": true
}
}
},
@@ -7513,8 +7498,7 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.1.tgz",
"integrity": "sha512-fcSa+wyTqZa46iWweI7/ZiUfegOZl0SG8+dltIwFXo7+zYU9J9kpS3NB6pZcSlJdhvIwp81Adx2XhZorncxiaA==",
"dev": true,
"optional": true
"dev": true
},
"fs-write-stream-atomic": {
"version": "1.0.10",
@@ -8296,7 +8280,6 @@
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
"integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
"dev": true,
"optional": true,
"requires": {
"parent-module": "^1.0.0",
"resolve-from": "^4.0.0"
@@ -9844,7 +9827,6 @@
"resolved": "https://registry.npmjs.org/memfs/-/memfs-3.2.0.tgz",
"integrity": "sha512-f/xxz2TpdKv6uDn6GtHee8ivFyxwxmPuXatBb1FBwxYNuVpbM3k/Y1Z+vC0mH/dIXXrukYfe3qe5J32Dfjg93A==",
"dev": true,
"optional": true,
"requires": {
"fs-monkey": "1.0.1"
}
@@ -10812,9 +10794,9 @@
}
},
"y18n": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz",
"integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=",
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.2.tgz",
"integrity": "sha512-uGZHXkHnhF0XeeAPgnKfPv1bgKAYyVvmNL1xlKsPYZPaIHxGti2hHqvOCQv71XMsLxu1QjergkqogUnms5D3YQ==",
"dev": true
},
"yargs": {
@@ -11456,7 +11438,6 @@
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
"integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
"dev": true,
"optional": true,
"requires": {
"callsites": "^3.0.0"
}
@@ -13274,8 +13255,7 @@
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
"integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
"dev": true,
"optional": true
"dev": true
},
"resolve-url": {
"version": "0.2.1",
@@ -16424,11 +16404,10 @@
}
},
"vue-loader-v16": {
"version": "npm:vue-loader@16.1.2",
"resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-16.1.2.tgz",
"integrity": "sha512-8QTxh+Fd+HB6fiL52iEVLKqE9N1JSlMXLR92Ijm6g8PZrwIxckgpqjPDWRP5TWxdiPaHR+alUWsnu1ShQOwt+Q==",
"version": "npm:vue-loader@16.2.0",
"resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-16.2.0.tgz",
"integrity": "sha512-TitGhqSQ61RJljMmhIGvfWzJ2zk9m1Qug049Ugml6QP3t0e95o0XJjk29roNEiPKJQBEi8Ord5hFuSuELzSp8Q==",
"dev": true,
"optional": true,
"requires": {
"chalk": "^4.1.0",
"hash-sum": "^2.0.0",
@@ -16440,7 +16419,6 @@
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dev": true,
"optional": true,
"requires": {
"color-convert": "^2.0.1"
}
@@ -16450,7 +16428,6 @@
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz",
"integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==",
"dev": true,
"optional": true,
"requires": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
@@ -16461,7 +16438,6 @@
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"optional": true,
"requires": {
"color-name": "~1.1.4"
}
@@ -16470,29 +16446,25 @@
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true,
"optional": true
"dev": true
},
"emojis-list": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz",
"integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==",
"dev": true,
"optional": true
"dev": true
},
"has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true,
"optional": true
"dev": true
},
"json5": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz",
"integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==",
"dev": true,
"optional": true,
"requires": {
"minimist": "^1.2.5"
}
@@ -16502,7 +16474,6 @@
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz",
"integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==",
"dev": true,
"optional": true,
"requires": {
"big.js": "^5.2.2",
"emojis-list": "^3.0.0",
@@ -16514,7 +16485,6 @@
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"dev": true,
"optional": true,
"requires": {
"has-flag": "^4.0.0"
}
@@ -17397,11 +17367,10 @@
"dev": true
},
"yaml": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.0.tgz",
"integrity": "sha512-yr2icI4glYaNG+KWONODapy2/jDdMSDnrONSjblABjD9B4Z5LgiircSt8m8sRZFNi08kG9Sm0uSHtEmP3zaEGg==",
"dev": true,
"optional": true
"version": "1.10.2",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
"integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
"dev": true
},
"yaml-lint": {
"version": "1.2.4",

View File

@@ -1,6 +1,6 @@
{
"name": "privacy.sexy",
"version": "0.10.0",
"version": "0.10.1",
"private": true,
"description": "Enforce privacy & security best-practices on Windows and macOS, because privacy is sexy 🍑🍆",
"author": "undergroundwires",

View File

@@ -0,0 +1,21 @@
// Compares to Array<T> objects for equality, ignoring order
export function scrambledEqual<T>(array1: readonly T[], array2: readonly T[]) {
if (!array1) { throw new Error('undefined first array'); }
if (!array2) { throw new Error('undefined second array'); }
const sortedArray1 = sort(array1);
const sortedArray2 = sort(array2);
return sequenceEqual(sortedArray1, sortedArray2);
function sort(array: readonly T[]) {
return array.slice().sort();
}
}
// Compares to Array<T> objects for equality in same order
export function sequenceEqual<T>(array1: readonly T[], array2: readonly T[]) {
if (!array1) { throw new Error('undefined first array'); }
if (!array2) { throw new Error('undefined second array'); }
if (array1.length !== array2.length) {
return false;
}
return array1.every((val, index) => val === array2[index]);
}

View File

@@ -1,6 +1,6 @@
// Because we cannot do "T extends enum" 😞 https://github.com/microsoft/TypeScript/issues/30611
type EnumType = number | string;
type EnumVariable<T extends EnumType, TEnumValue extends EnumType> = { [key in T]: TEnumValue };
export type EnumType = number | string;
export type EnumVariable<T extends EnumType, TEnumValue extends EnumType> = { [key in T]: TEnumValue };
export interface IEnumParser<TEnum> {
parseEnum(value: string, propertyName: string): TEnum;
@@ -41,3 +41,14 @@ export function getEnumValues<T extends EnumType, TEnumValue extends EnumType>(
return getEnumNames(enumVariable)
.map((level) => enumVariable[level]) as TEnumValue[];
}
export function assertInRange<T extends EnumType, TEnumValue extends EnumType>(
value: TEnumValue,
enumVariable: EnumVariable<T, TEnumValue>) {
if (value === undefined) {
throw new Error('undefined enum value');
}
if (!(value in enumVariable)) {
throw new RangeError(`enum value "${value}" is out of range`);
}
}

View File

@@ -0,0 +1,5 @@
import { ScriptingLanguage } from '@/domain/ScriptingLanguage';
export interface IScriptingLanguageFactory<T> {
create(language: ScriptingLanguage): T;
}

View File

@@ -0,0 +1,31 @@
import { ScriptingLanguage } from '@/domain/ScriptingLanguage';
import { IScriptingLanguageFactory } from './IScriptingLanguageFactory';
import { assertInRange } from '@/application/Common/Enum';
type Getter<T> = () => T;
export abstract class ScriptingLanguageFactory<T> implements IScriptingLanguageFactory<T> {
private readonly getters = new Map<ScriptingLanguage, Getter<T>>();
public create(language: ScriptingLanguage): T {
assertInRange(language, ScriptingLanguage);
if (!this.getters.has(language)) {
throw new RangeError(`unknown language: "${ScriptingLanguage[language]}"`);
}
const getter = this.getters.get(language);
const instance = getter();
return instance;
}
protected registerGetter(language: ScriptingLanguage, getter: Getter<T>) {
assertInRange(language, ScriptingLanguage);
if (!getter) {
throw new Error('undefined getter');
}
if (this.getters.has(language)) {
throw new Error(`${ScriptingLanguage[language]} is already registered`);
}
this.getters.set(language, getter);
}
}

View File

@@ -5,6 +5,7 @@ import { IApplication } from '@/domain/IApplication';
import { OperatingSystem } from '@/domain/OperatingSystem';
import { ICategoryCollection } from '@/domain/ICategoryCollection';
import { EventSource } from '@/infrastructure/Events/EventSource';
import { assertInRange } from '@/application/Common/Enum';
type StateMachine = Map<OperatingSystem, ICategoryCollectionState>;
@@ -22,7 +23,7 @@ export class ApplicationContext implements IApplicationContext {
public readonly app: IApplication,
initialContext: OperatingSystem) {
validateApp(app);
validateOs(initialContext);
assertInRange(initialContext, OperatingSystem);
this.states = initializeStates(app);
this.changeContext(initialContext);
}
@@ -50,18 +51,6 @@ function validateApp(app: IApplication) {
}
}
function validateOs(os: OperatingSystem) {
if (os === undefined) {
throw new Error('undefined os');
}
if (os === OperatingSystem.Unknown) {
throw new Error('unknown os');
}
if (!(os in OperatingSystem)) {
throw new Error(`os "${os}" is out of range`);
}
}
function initializeStates(app: IApplication): StateMachine {
const machine = new Map<OperatingSystem, ICategoryCollectionState>();
for (const collection of app.collections) {

View File

@@ -1,15 +1,14 @@
import { ScriptingLanguageFactory } from '@/application/Common/ScriptingLanguage/ScriptingLanguageFactory';
import { ScriptingLanguage } from '@/domain/ScriptingLanguage';
import { ICodeBuilder } from './ICodeBuilder';
import { ICodeBuilderFactory } from './ICodeBuilderFactory';
import { BatchBuilder } from './Languages/BatchBuilder';
import { ShellBuilder } from './Languages/ShellBuilder';
import { ICodeBuilderFactory } from './ICodeBuilderFactory';
export class CodeBuilderFactory implements ICodeBuilderFactory {
public create(language: ScriptingLanguage): ICodeBuilder {
switch (language) {
case ScriptingLanguage.shellscript: return new ShellBuilder();
case ScriptingLanguage.batchfile: return new BatchBuilder();
default: throw new RangeError(`unknown language: "${ScriptingLanguage[language]}"`);
}
export class CodeBuilderFactory extends ScriptingLanguageFactory<ICodeBuilder> implements ICodeBuilderFactory {
constructor() {
super();
this.registerGetter(ScriptingLanguage.shellscript, () => new ShellBuilder());
this.registerGetter(ScriptingLanguage.batchfile, () => new BatchBuilder());
}
}

View File

@@ -1,6 +1,5 @@
import { ScriptingLanguage } from '@/domain/ScriptingLanguage';
import { ICodeBuilder } from './ICodeBuilder';
import { IScriptingLanguageFactory } from '@/application/Common/ScriptingLanguage/IScriptingLanguageFactory';
export interface ICodeBuilderFactory {
create(language: ScriptingLanguage): ICodeBuilder;
export interface ICodeBuilderFactory extends IScriptingLanguageFactory<ICodeBuilder> {
}

View File

@@ -6,7 +6,6 @@ import { IEventSource } from '@/infrastructure/Events/IEventSource';
export interface IUserSelection {
readonly changed: IEventSource<ReadonlyArray<SelectedScript>>;
readonly selectedScripts: ReadonlyArray<SelectedScript>;
readonly totalSelected: number;
areAllSelected(category: ICategory): boolean;
isAnySelected(category: ICategory): boolean;
removeAllInCategory(categoryId: number): void;

View File

@@ -101,10 +101,6 @@ export class UserSelection implements IUserSelection {
return this.scripts.getItems();
}
public get totalSelected(): number {
return this.scripts.getItems().length;
}
public selectAll(): void {
for (const script of this.collection.getAllScripts()) {
if (!this.scripts.exists(script.id)) {

View File

@@ -4,17 +4,17 @@ import { IBrowserOsDetector } from './IBrowserOsDetector';
export class BrowserOsDetector implements IBrowserOsDetector {
private readonly detectors = BrowserDetectors;
public detect(userAgent: string): OperatingSystem {
public detect(userAgent: string): OperatingSystem | undefined {
if (!userAgent) {
return OperatingSystem.Unknown;
return undefined;
}
for (const detector of this.detectors) {
const os = detector.detect(userAgent);
if (os !== OperatingSystem.Unknown) {
if (os !== undefined) {
return os;
}
}
return OperatingSystem.Unknown;
return undefined;
}
}

View File

@@ -29,10 +29,10 @@ export class DetectorBuilder {
throw new Error('User agent is null or undefined');
}
if (this.existingPartsInUserAgent.some((part) => !userAgent.includes(part))) {
return OperatingSystem.Unknown;
return undefined;
}
if (this.notExistingPartsInUserAgent.some((part) => userAgent.includes(part))) {
return OperatingSystem.Unknown;
return undefined;
}
return this.os;
}

View File

@@ -1,5 +1,5 @@
import { OperatingSystem } from '@/domain/OperatingSystem';
export interface IBrowserOsDetector {
detect(userAgent: string): OperatingSystem;
detect(userAgent: string): OperatingSystem | undefined;
}

View File

@@ -44,7 +44,7 @@ function getProcessPlatform(variables: IEnvironmentVariables): string {
return variables.process.platform;
}
function getDesktopOsType(processPlatform: string): OperatingSystem {
function getDesktopOsType(processPlatform: string): OperatingSystem | undefined {
// https://nodejs.org/api/process.html#process_process_platform
if (processPlatform === 'darwin') {
return OperatingSystem.macOS;
@@ -53,7 +53,7 @@ function getDesktopOsType(processPlatform: string): OperatingSystem {
} else if (processPlatform === 'linux') {
return OperatingSystem.Linux;
}
return OperatingSystem.Unknown;
return undefined;
}
function isDesktop(variables: IEnvironmentVariables): boolean {

View File

@@ -1,6 +1,6 @@
import { IScriptingDefinition } from '@/domain/IScriptingDefinition';
import { ILanguageSyntax } from '@/domain/ScriptCode';
import { FunctionData } from 'js-yaml-loader!*';
import { FunctionData } from 'js-yaml-loader!@/*';
import { IScriptCompiler } from './Compiler/IScriptCompiler';
import { ScriptCompiler } from './Compiler/ScriptCompiler';
import { ICategoryCollectionParseContext } from './ICategoryCollectionParseContext';

View File

@@ -1,4 +1,4 @@
import { FunctionData, InstructionHolder } from 'js-yaml-loader!*';
import { FunctionData, InstructionHolder } from 'js-yaml-loader!@/*';
import { SharedFunction } from './SharedFunction';
import { SharedFunctionCollection } from './SharedFunctionCollection';
import { ISharedFunctionCollection } from './ISharedFunctionCollection';

View File

@@ -1,4 +1,4 @@
import { FunctionData } from 'js-yaml-loader!*';
import { FunctionData } from 'js-yaml-loader!@/*';
import { ISharedFunctionCollection } from './ISharedFunctionCollection';
export interface IFunctionCompiler {

View File

@@ -1,9 +1,10 @@
import { ISharedFunction } from './ISharedFunction';
export class SharedFunction implements ISharedFunction {
public readonly parameters: readonly string[];
constructor(
public readonly name: string,
public readonly parameters: readonly string[],
parameters: readonly string[],
public readonly code: string,
public readonly revertCode: string,
) {

View File

@@ -1,4 +1,4 @@
import { FunctionCallData, FunctionCallParametersData, FunctionData, ScriptFunctionCallData } from 'js-yaml-loader!*';
import { FunctionCallData, FunctionCallParametersData, FunctionData, ScriptFunctionCallData } from 'js-yaml-loader!@/*';
import { ICompiledCode } from './ICompiledCode';
import { ISharedFunctionCollection } from '../Function/ISharedFunctionCollection';
import { IFunctionCallCompiler } from './IFunctionCallCompiler';

View File

@@ -1,4 +1,4 @@
import { ScriptFunctionCallData } from 'js-yaml-loader!*';
import { ScriptFunctionCallData } from 'js-yaml-loader!@/*';
import { ICompiledCode } from './ICompiledCode';
import { ISharedFunctionCollection } from '../Function/ISharedFunctionCollection';

View File

@@ -1,6 +1,5 @@
import { ILanguageSyntax } from '@/domain/ScriptCode';
import { ScriptingLanguage } from '@/domain/ScriptingLanguage';
import { IScriptingLanguageFactory } from '@/application/Common/ScriptingLanguage/IScriptingLanguageFactory';
export interface ISyntaxFactory {
create(language: ScriptingLanguage): ILanguageSyntax;
export interface ISyntaxFactory extends IScriptingLanguageFactory<ILanguageSyntax> {
}

View File

@@ -1,15 +1,14 @@
import { ILanguageSyntax } from '@/domain/ScriptCode';
import { ScriptingLanguage } from '@/domain/ScriptingLanguage';
import { ISyntaxFactory } from './ISyntaxFactory';
import { ScriptingLanguageFactory } from '@/application/Common/ScriptingLanguage/ScriptingLanguageFactory';
import { BatchFileSyntax } from './BatchFileSyntax';
import { ShellScriptSyntax } from './ShellScriptSyntax';
import { ISyntaxFactory } from './ISyntaxFactory';
export class SyntaxFactory implements ISyntaxFactory {
public create(language: ScriptingLanguage): ILanguageSyntax {
switch (language) {
case ScriptingLanguage.batchfile: return new BatchFileSyntax();
case ScriptingLanguage.shellscript: return new ShellScriptSyntax();
default: throw new RangeError(`unknown language: "${ScriptingLanguage[language]}"`);
}
export class SyntaxFactory extends ScriptingLanguageFactory<ILanguageSyntax> implements ISyntaxFactory {
constructor() {
super();
this.registerGetter(ScriptingLanguage.batchfile, () => new BatchFileSyntax());
this.registerGetter(ScriptingLanguage.shellscript, () => new ShellScriptSyntax());
}
}

View File

@@ -1,4 +1,4 @@
declare module 'js-yaml-loader!*' {
declare module 'js-yaml-loader!@/*' {
export interface CollectionData {
readonly os: string;
readonly scripting: ScriptingDefinitionData;

View File

@@ -1248,25 +1248,52 @@ actions:
-
name: Disable ad customization with Advertising ID
recommend: standard
docs: https://docs.microsoft.com/en-us/windows/privacy/manage-connections-from-windows-operating-system-components-to-microsoft-services#181-general
code: |-
reg add "HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\AdvertisingInfo" /v "Enabled" /t REG_DWORD /d 0 /f
reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\AdvertisingInfo" /v "DisabledByGroupPolicy" /t REG_DWORD /d 1 /f
reg add "HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\AdvertisingInfo" /v "Enabled" /t REG_DWORD /d "0" /f
reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\AdvertisingInfo" /v "DisabledByGroupPolicy" /t REG_DWORD /d "1" /f
revertCode: |-
reg add "HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\AdvertisingInfo" /v "Enabled" /t REG_DWORD /d "1" /f
reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\AdvertisingInfo" /v "DisabledByGroupPolicy" /t REG_DWORD /d "0" /f
-
category: Disable cloud-based tips and ads
children:
-
name: Disable Windows Tips
recommend: standard
docs: https://admx.help/?Category=Windows_10_2016&Policy=Microsoft.Policies.CloudContent::DisableSoftLanding
code: reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\CloudContent" /v "DisableSoftLanding" /t REG_DWORD /d "1" /f
revertCode: reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\CloudContent" /v "DisableSoftLanding" /t REG_DWORD /d "0" /f
-
name: Disable Windows Spotlight (random wallpaper on lock screen)
recommend: standard
docs:
- https://docs.microsoft.com/en-us/windows/configuration/windows-spotlight
- https://docs.microsoft.com/en-us/windows/privacy/manage-connections-from-windows-operating-system-components-to-microsoft-services#25-windows-spotlight
code: reg add "HKLM\Software\Policies\Microsoft\Windows\CloudContent" /v "DisableWindowsSpotlightFeatures" /t "REG_DWORD" /d "1" /f
revertCode: reg add "HKLM\Software\Policies\Microsoft\Windows\CloudContent" /v "DisableWindowsSpotlightFeatures" /t "REG_DWORD" /d "0" /f
-
name: Disable Microsoft consumer experiences
recommend: standard
docs:
- https://www.stigviewer.com/stig/windows_10/2018-04-06/finding/V-71771
- https://admx.help/?Category=Windows_10_2016&Policy=Microsoft.Policies.CloudContent::DisableWindowsConsumerFeatures
- https://docs.microsoft.com/en-us/windows/privacy/manage-connections-from-windows-operating-system-components-to-microsoft-services#1816-feedback--diagnostics
code: reg add "HKLM\Software\Policies\Microsoft\Windows\CloudContent" /v "DisableWindowsConsumerFeatures" /t "REG_DWORD" /d "1" /f
revertCode: reg add "HKLM\Software\Policies\Microsoft\Windows\CloudContent" /v "DisableWindowsConsumerFeatures" /t "REG_DWORD" /d "0" /f
-
name: Disable targeted tips
recommend: standard
code: |-
reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\CloudContent" /v "DisableSoftLanding" /t REG_DWORD /d 1 /f
reg add "HKLM\Software\Policies\Microsoft\Windows\CloudContent" /v "DisableWindowsSpotlightFeatures" /t "REG_DWORD" /d "1" /f
reg add "HKLM\Software\Policies\Microsoft\Windows\CloudContent" /v "DisableWindowsConsumerFeatures" /t "REG_DWORD" /d "1" /f
-
name: Turn Off Suggested Content in Settings app
recommend: standard
docs: https://www.tenforums.com/tutorials/100541-turn-off-suggested-content-settings-app-windows-10-a.html
code: |-
reg add HKCU\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager /v "SubscribedContent-338393Enabled" /d "0" /t REG_DWORD /f
reg add HKCU\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager /v "SubscribedContent-353694Enabled" /d "0" /t REG_DWORD /f
reg add HKCU\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager /v "SubscribedContent-353696Enabled" /d "0" /t REG_DWORD /f
-
reg add "HKCU\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager" /v "SubscribedContent-338393Enabled" /d "0" /t REG_DWORD /f
reg add "HKCU\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager" /v "SubscribedContent-353694Enabled" /d "0" /t REG_DWORD /f
reg add "HKCU\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager" /v "SubscribedContent-353696Enabled" /d "0" /t REG_DWORD /f
revertCode: |-
reg add "HKCU\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager" /v "SubscribedContent-338393Enabled" /d "1" /t REG_DWORD /f
reg add "HKCU\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager" /v "SubscribedContent-353694Enabled" /d "1" /t REG_DWORD /f
reg add "HKCU\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager" /v "SubscribedContent-353696Enabled" /d "1" /t REG_DWORD /f
-
category: Disable biometrics (breaks fingerprinting/facial login)
children:
-
@@ -1788,7 +1815,7 @@ actions:
category: Chromium Edge settings
children:
-
name: Disable Edge usage and crash-related data reporting # Obselete since Microsoft Edge version 89
name: Disable Edge usage and crash-related data reporting (shows "Your browser is managed") # Obselete since Microsoft Edge version 89
recommend: standard
docs:
- https://admx.help/?Category=EdgeChromium&Policy=Microsoft.Policies.Edge::MetricsReportingEnabled
@@ -1796,7 +1823,7 @@ actions:
code: reg add "HKLM\SOFTWARE\Policies\Microsoft\Edge" /v "MetricsReportingEnabled" /t REG_DWORD /d 0 /f
revertCode: reg delete "HKLM\SOFTWARE\Policies\Microsoft\Edge" /v "MetricsReportingEnabled" /f
-
name: Disable sending site information # Obselete since Microsoft Edge version 89
name: Disable sending site information (shows "Your browser is managed") # Obselete since Microsoft Edge version 89
recommend: standard
docs:
- https://admx.help/?Category=EdgeChromium&Policy=Microsoft.Policies.Edge::SendSiteInfoToImproveServices
@@ -1894,7 +1921,7 @@ actions:
category: Chrome cleanup
children:
-
name: Do not share scanned software data to Google
name: Do not share scanned software data to Google (shows "Your browser is managed")
recommend: standard
docs:
- https://www.chromium.org/administrators/policy-list-3#ChromeCleanupReportingEnabled
@@ -1902,7 +1929,7 @@ actions:
code: reg add "HKLM\SOFTWARE\Policies\Google\Chrome" /v "ChromeCleanupReportingEnabled" /t REG_DWORD /d 0 /f
revertCode: reg delete "HKLM\SOFTWARE\Policies\Google\Chrome" /v "ChromeCleanupReportingEnabled" /f
-
name: Prevent Chrome from scanning the system for cleanup
name: Prevent Chrome from scanning the system for cleanup (shows "Your browser is managed")
recommend: standard
docs:
- https://www.chromium.org/administrators/policy-list-3#ChromeCleanupEnabled
@@ -1910,7 +1937,7 @@ actions:
code: reg add "HKLM\SOFTWARE\Policies\Google\Chrome" /v "ChromeCleanupEnabled" /t REG_DWORD /d 0 /f
revertCode: reg delete "HKLM\SOFTWARE\Policies\Google\Chrome" /v "ChromeCleanupEnabled" /f
-
name: Disable Chrome metrics reporting
name: Disable Chrome metrics reporting (shows "Your browser is managed")
recommend: standard
docs: https://www.stigviewer.com/stig/google_chrome_v23_windows/2013-01-11/finding/V-35780
code: reg add "HKLM\SOFTWARE\Policies\Google\Chrome" /v "MetricsReportingEnabled" /t REG_DWORD /d 0 /f
@@ -2009,7 +2036,7 @@ actions:
reg add "HKCU\Software\Policies\Microsoft\WindowsMediaPlayer" /v "PreventRadioPresetsRetrieval" /t REG_DWORD /d 1 /f
reg add "HKLM\SOFTWARE\Policies\Microsoft\WMDRM" /v "DisableOnline" /t REG_DWORD /d 1 /f
-
name: Disable dows Media Player Network Sharing Service
name: Disable Windows Media Player Network Sharing Service
recommend: standard
code: sc stop "WMPNetworkSvc" & sc config "WMPNetworkSvc" start=disabled
-
@@ -2986,7 +3013,7 @@ actions:
packageName: Microsoft.Messaging
-
name: Mixed Reality Portal app
docs: https://www.microsoft.com/en-us/p/mixed-reality-portal
docs: https://www.microsoft.com/en-us/p/mixed-reality-portal/9ng1h8b3zc7m
call:
function: UninstallStoreApp
parameters:
@@ -3004,7 +3031,7 @@ actions:
packageName: Microsoft.MicrosoftOfficeHub
-
name: OneNote app
docs: https://www.microsoft.com/en-us/p/onenote-for-windows-10
docs: https://www.microsoft.com/en-us/p/onenote-for-windows-10/9wzdncrfhvjl
call:
function: UninstallStoreApp
parameters:
@@ -3511,7 +3538,7 @@ actions:
parameters:
packageName: Microsoft.Windows.CapturePicker
-
name: Cloud Experience Host app # Allows to connect to corporate domains or Microsoft cloud based services
name: Cloud Experience Host app (breaks Microsoft cloud/corporate sign in) # Allows to connect to corporate domains or Microsoft cloud based services
recommend: strict
call:
function: UninstallSystemApp

View File

@@ -1,4 +1,4 @@
import { getEnumNames, getEnumValues } from '@/application/Common/Enum';
import { getEnumNames, getEnumValues, assertInRange } from '@/application/Common/Enum';
import { IEntity } from '../infrastructure/Entity/IEntity';
import { ICategory } from './ICategory';
import { IScript } from './IScript';
@@ -21,7 +21,7 @@ export class CategoryCollection implements ICategoryCollection {
throw new Error('undefined scripting definition');
}
this.queryable = makeQueryable(actions);
ensureValidOs(os);
assertInRange(os, OperatingSystem);
ensureValid(this.queryable);
ensureNoDuplicates(this.queryable.allCategories);
ensureNoDuplicates(this.queryable.allScripts);
@@ -54,18 +54,6 @@ export class CategoryCollection implements ICategoryCollection {
}
}
function ensureValidOs(os: OperatingSystem): void {
if (os === undefined) {
throw new Error('undefined os');
}
if (os === OperatingSystem.Unknown) {
throw new Error('unknown os');
}
if (!(os in OperatingSystem)) {
throw new Error(`os "${os}" is out of range`);
}
}
function ensureNoDuplicates<TKey>(entities: ReadonlyArray<IEntity<TKey>>) {
const totalOccurrencesById = new Map<TKey, number>();
for (const entity of entities) {

View File

@@ -10,5 +10,4 @@ export enum OperatingSystem {
Android,
iOS,
WindowsPhone,
Unknown,
}

View File

@@ -1,5 +1,6 @@
import { IProjectInformation } from './IProjectInformation';
import { OperatingSystem } from './OperatingSystem';
import { assertInRange } from '@/application/Common/Enum';
export class ProjectInformation implements IProjectInformation {
public readonly repositoryWebUrl: string;
@@ -42,6 +43,7 @@ function getWebUrl(gitUrl: string) {
}
function getFileName(os: OperatingSystem, version: string): string {
assertInRange(os, OperatingSystem);
switch (os) {
case OperatingSystem.Linux:
return `privacy.sexy-${version}.AppImage`;
@@ -50,6 +52,6 @@ function getFileName(os: OperatingSystem, version: string): string {
case OperatingSystem.Windows:
return `privacy.sexy-Setup-${version}.exe`;
default:
throw new Error(`Unsupported os: ${OperatingSystem[os]}`);
throw new RangeError(`Unsupported os: ${OperatingSystem[os]}`);
}
}

View File

@@ -7,16 +7,7 @@ export class ScriptCode implements IScriptCode {
syntax: ILanguageSyntax) {
if (!syntax) { throw new Error('undefined syntax'); }
validateCode(execute, syntax);
if (revert) {
try {
validateCode(revert, syntax);
if (execute === revert) {
throw new Error(`Code itself and its reverting code cannot be the same`);
}
} catch (err) {
throw Error(`(revert): ${err.message}`);
}
}
validateRevertCode(revert, execute, syntax);
}
}
@@ -25,6 +16,20 @@ export interface ILanguageSyntax {
readonly commonCodeParts: string[];
}
function validateRevertCode(revertCode: string, execute: string, syntax: ILanguageSyntax) {
if (!revertCode) {
return;
}
try {
validateCode(revertCode, syntax);
if (execute === revertCode) {
throw new Error(`Code itself and its reverting code cannot be the same`);
}
} catch (err) {
throw Error(`(revert): ${err.message}`);
}
}
function validateCode(code: string, syntax: ILanguageSyntax): void {
if (!code || code.length === 0) {
throw new Error(`code is empty or undefined`);

View File

@@ -5,16 +5,20 @@ import fs from 'fs';
import child_process from 'child_process';
import { OperatingSystem } from '@/domain/OperatingSystem';
export async function runCodeAsync(
code: string, folderName: string, fileExtension: string,
node = getNodeJs(), environment = Environment.CurrentEnvironment): Promise<void> {
const dir = node.path.join(node.os.tmpdir(), folderName);
await node.fs.promises.mkdir(dir, {recursive: true});
const filePath = node.path.join(dir, `run.${fileExtension}`);
await node.fs.promises.writeFile(filePath, code);
await node.fs.promises.chmod(filePath, '755');
const command = getExecuteCommand(filePath, environment);
node.child_process.exec(command);
export class CodeRunner {
constructor(
private readonly node = getNodeJs(),
private readonly environment = Environment.CurrentEnvironment) {
}
public async runCodeAsync(code: string, folderName: string, fileExtension: string): Promise<void> {
const dir = this.node.path.join(this.node.os.tmpdir(), folderName);
await this.node.fs.promises.mkdir(dir, {recursive: true});
const filePath = this.node.path.join(dir, `run.${fileExtension}`);
await this.node.fs.promises.writeFile(filePath, code);
await this.node.fs.promises.chmod(filePath, '755');
const command = getExecuteCommand(filePath, this.environment);
this.node.child_process.exec(command);
}
}
function getExecuteCommand(scriptPath: string, environment: Environment): string {

View File

@@ -4,7 +4,7 @@
// This script is running through entire life of the application.
// It doesn't have any windows which you can see on screen, opens the main window from here.
import { app, protocol, BrowserWindow, shell } from 'electron';
import { app, protocol, BrowserWindow, shell, screen } from 'electron';
import { createProtocol } from 'vue-cli-plugin-electron-builder/lib';
import installExtension, { VUEJS_DEVTOOLS } from 'electron-devtools-installer';
import path from 'path';
@@ -34,9 +34,10 @@ if (!process.env.IS_TEST) {
function createWindow() {
// Create the browser window.
const size = getWindowSize(1350, 955);
win = new BrowserWindow({
width: 1350,
height: 955,
width: size.width,
height: size.height,
webPreferences: {
contextIsolation: false, // To reach node https://github.com/nklayman/vue-cli-plugin-electron-builder/issues/1285
// Use pluginOptions.nodeIntegration, leave this alone
@@ -143,3 +144,12 @@ function loadUrlWithNodeWorkaround(window: BrowserWindow, url: string) {
window.loadURL(url);
}, 10);
}
function getWindowSize(idealWidth: number, idealHeight: number) {
let { width, height } = screen.getPrimaryDisplay().workAreaSize;
// To ensure not creating a screen bigger than current screen size
// Not using "enableLargerThanScreen" as it's macOS only (see https://www.electronjs.org/docs/api/browser-window)
width = Math.min(width, idealWidth);
height = Math.min(height, idealHeight);
return { width, height };
}

View File

@@ -37,7 +37,7 @@ import { ScriptingLanguage } from '@/domain/ScriptingLanguage';
import { IApplicationCode } from '@/application/Context/State/Code/IApplicationCode';
import { IScriptingDefinition } from '@/domain/IScriptingDefinition';
import { OperatingSystem } from '@/domain/OperatingSystem';
import { runCodeAsync } from '@/infrastructure/CodeRunner';
import { CodeRunner } from '@/infrastructure/CodeRunner';
import { IApplicationContext } from '@/application/Context/IApplicationContext';
@Component({
@@ -116,11 +116,12 @@ function buildFileName(scripting: IScriptingDefinition) {
}
async function executeCodeAsync(context: IApplicationContext) {
await runCodeAsync(
/*code*/ context.state.code.current,
/*appName*/ context.app.info.name,
/*fileExtension*/ context.state.collection.scripting.fileExtension,
);
const runner = new CodeRunner();
await runner.runCodeAsync(
/*code*/ context.state.code.current,
/*appName*/ context.app.info.name,
/*fileExtension*/ context.state.collection.scripting.fileExtension,
);
}
</script>

View File

@@ -0,0 +1,86 @@
import { IScript } from '@/domain/IScript';
import { SelectedScript } from '@/application/Context/State/Selection/SelectedScript';
import { RecommendationLevel } from '@/domain/RecommendationLevel';
import { scrambledEqual } from '@/application/Common/Array';
import { ICategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState';
export enum SelectionType {
Standard,
Strict,
All,
None,
Custom,
}
export class SelectionTypeHandler {
constructor(private readonly state: ICategoryCollectionState) {
if (!state) { throw new Error('undefined state'); }
}
public selectType(type: SelectionType) {
if (type === SelectionType.Custom) {
throw new Error('cannot select custom type');
}
const selector = selectors.get(type);
selector.select(this.state);
}
public getCurrentSelectionType(): SelectionType {
for (const [type, selector] of Array.from(selectors.entries())) {
if (selector.isSelected(this.state)) {
return type;
}
}
return SelectionType.Custom;
}
}
interface ISingleTypeHandler {
isSelected: (state: ICategoryCollectionState) => boolean;
select: (state: ICategoryCollectionState) => void;
}
const selectors = new Map<SelectionType, ISingleTypeHandler>([
[SelectionType.None, {
select: (state) =>
state.selection.deselectAll(),
isSelected: (state) =>
state.selection.selectedScripts.length === 0,
}],
[SelectionType.Standard, getRecommendationLevelSelector(RecommendationLevel.Standard)],
[SelectionType.Strict, getRecommendationLevelSelector(RecommendationLevel.Strict)],
[SelectionType.All, {
select: (state) =>
state.selection.selectAll(),
isSelected: (state) =>
state.selection.selectedScripts.length === state.collection.totalScripts,
}],
]);
function getRecommendationLevelSelector(level: RecommendationLevel): ISingleTypeHandler {
return {
select: (state) => selectOnly(level, state),
isSelected: (state) => hasAllSelectedLevelOf(level, state),
};
}
function hasAllSelectedLevelOf(level: RecommendationLevel, state: ICategoryCollectionState) {
const scripts = state.collection.getScriptsByLevel(level);
const selectedScripts = state.selection.selectedScripts;
return areAllSelected(scripts, selectedScripts);
}
function selectOnly(level: RecommendationLevel, state: ICategoryCollectionState) {
const scripts = state.collection.getScriptsByLevel(level);
state.selection.selectOnly(scripts);
}
function areAllSelected(
expectedScripts: ReadonlyArray<IScript>,
selection: ReadonlyArray<SelectedScript>): boolean {
selection = selection.filter((selected) => !selected.revert);
if (expectedScripts.length < selection.length) {
return false;
}
const selectedScriptIds = selection.map((script) => script.id);
const expectedScriptIds = expectedScripts.map((script) => script.id);
return scrambledEqual(selectedScriptIds, expectedScriptIds);
}

View File

@@ -5,8 +5,8 @@
<div class="part">
<SelectableOption
label="None"
:enabled="this.currentSelection == SelectionState.None"
@click="selectAsync(SelectionState.None)"
:enabled="this.currentSelection == SelectionType.None"
@click="selectType(SelectionType.None)"
v-tooltip=" 'Deselect all selected scripts.<br/>' +
'💡 Good start to dive deeper into tweaks and select only what you want.'"
/>
@@ -15,8 +15,8 @@
<div class="part">
<SelectableOption
label="Standard"
:enabled="this.currentSelection == SelectionState.Standard"
@click="selectAsync(SelectionState.Standard)"
:enabled="this.currentSelection == SelectionType.Standard"
@click="selectType(SelectionType.Standard)"
v-tooltip=" '🛡️ Balanced for privacy and functionality.<br/>' +
'OS and applications will function normally.<br/>' +
'💡 Recommended for everyone'"
@@ -26,8 +26,8 @@
<div class="part">
<SelectableOption
label="Strict"
:enabled="this.currentSelection == SelectionState.Strict"
@click="selectAsync(SelectionState.Strict)"
:enabled="this.currentSelection == SelectionType.Strict"
@click="selectType(SelectionType.Strict)"
v-tooltip=" '🚫 Stronger privacy, disables risky functions that may leak your data.<br/>' +
'⚠️ Double check to remove sripts where you would trade functionality for privacy<br/>' +
'💡 Recommended for daily users that prefers more privacy over non-essential functions'"
@@ -37,8 +37,8 @@
<div class="part">
<SelectableOption
label="All"
:enabled="this.currentSelection == SelectionState.All"
@click="selectAsync(SelectionState.All)"
:enabled="this.currentSelection == SelectionType.All"
@click="selectType(SelectionType.All)"
v-tooltip=" '🔒 Strongest privacy, disabling any functionality that may leak your data.<br/>' +
'🛑 Not designed for daily users, it will break important functionalities.<br/>' +
'💡 Only recommended for extreme use-cases like crime labs where no leak is acceptable'"
@@ -52,112 +52,40 @@
import { Component } from 'vue-property-decorator';
import { StatefulVue } from '@/presentation/components/Shared/StatefulVue';
import SelectableOption from './SelectableOption.vue';
import { IScript } from '@/domain/IScript';
import { SelectedScript } from '@/application/Context/State/Selection/SelectedScript';
import { RecommendationLevel } from '@/domain/RecommendationLevel';
import { ICategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState';
import { SelectionType, SelectionTypeHandler } from './SelectionTypeHandler';
enum SelectionState {
Standard,
Strict,
All,
None,
Custom,
}
@Component({
components: {
SelectableOption,
},
})
export default class TheSelector extends StatefulVue {
public SelectionState = SelectionState;
public currentSelection = SelectionState.None;
public SelectionType = SelectionType;
public currentSelection = SelectionType.None;
private selectionTypeHandler: SelectionTypeHandler;
public async selectAsync(type: SelectionState): Promise<void> {
public async selectType(type: SelectionType) {
if (this.currentSelection === type) {
return;
}
const context = await this.getCurrentContextAsync();
selectType(context.state, type);
this.selectionTypeHandler.selectType(type);
}
protected handleCollectionState(newState: ICategoryCollectionState, oldState: ICategoryCollectionState): void {
this.updateSelections(newState);
newState.selection.changed.on(() => this.updateSelections(newState));
this.selectionTypeHandler = new SelectionTypeHandler(newState);
this.updateSelections();
newState.selection.changed.on(() => this.updateSelections());
if (oldState) {
oldState.selection.changed.on(() => this.updateSelections(oldState));
oldState.selection.changed.on(() => this.updateSelections());
}
}
private updateSelections(state: ICategoryCollectionState) {
this.currentSelection = getCurrentSelectionState(state);
private updateSelections() {
this.currentSelection = this.selectionTypeHandler.getCurrentSelectionType();
}
}
interface ITypeSelector {
isSelected: (state: ICategoryCollectionState) => boolean;
select: (state: ICategoryCollectionState) => void;
}
const selectors = new Map<SelectionState, ITypeSelector>([
[SelectionState.None, {
select: (state) =>
state.selection.deselectAll(),
isSelected: (state) =>
state.selection.totalSelected === 0,
}],
[SelectionState.Standard, {
select: (state) =>
state.selection.selectOnly(
state.collection.getScriptsByLevel(RecommendationLevel.Standard)),
isSelected: (state) =>
hasAllSelectedLevelOf(RecommendationLevel.Standard, state),
}],
[SelectionState.Strict, {
select: (state) =>
state.selection.selectOnly(state.collection.getScriptsByLevel(RecommendationLevel.Strict)),
isSelected: (state) =>
hasAllSelectedLevelOf(RecommendationLevel.Strict, state),
}],
[SelectionState.All, {
select: (state) =>
state.selection.selectAll(),
isSelected: (state) =>
state.selection.totalSelected === state.collection.totalScripts,
}],
]);
function selectType(state: ICategoryCollectionState, type: SelectionState) {
const selector = selectors.get(type);
selector.select(state);
}
function getCurrentSelectionState(state: ICategoryCollectionState): SelectionState {
for (const [type, selector] of Array.from(selectors.entries())) {
if (selector.isSelected(state)) {
return type;
}
}
return SelectionState.Custom;
}
function hasAllSelectedLevelOf(level: RecommendationLevel, state: ICategoryCollectionState) {
const scripts = state.collection.getScriptsByLevel(level);
const selectedScripts = state.selection.selectedScripts;
return areAllSelected(scripts, selectedScripts);
}
function areAllSelected(
expectedScripts: ReadonlyArray<IScript>,
selection: ReadonlyArray<SelectedScript>): boolean {
selection = selection.filter((selected) => !selected.revert);
if (expectedScripts.length < selection.length) {
return false;
}
const selectedScriptIds = selection.map((script) => script.id).sort();
const expectedScriptIds = expectedScripts.map((script) => script.id).sort();
return selectedScriptIds.every((id, index) => id === expectedScriptIds[index]);
}
</script>
<style scoped lang="scss">

View File

@@ -24,7 +24,7 @@ import { ApplicationFactory } from '@/application/ApplicationFactory';
@Component
export default class TheOsChanger extends StatefulVue {
public allOses: Array<{ name: string, os: OperatingSystem }> = [];
public currentOs: OperatingSystem = OperatingSystem.Unknown;
public currentOs?: OperatingSystem = null;
public async created() {
const app = await ApplicationFactory.Current.getAppAsync();

View File

@@ -1,7 +1,7 @@
import 'mocha';
import { expect } from 'chai';
import { ApplicationFactory, ApplicationGetter } from '@/application/ApplicationFactory';
import { ApplicationStub } from '../stubs/ApplicationStub';
import { ApplicationStub } from '@tests/unit/stubs/ApplicationStub';
describe('ApplicationFactory', () => {
describe('ctor', () => {

View File

@@ -0,0 +1,69 @@
interface IComparerTestCase<T> {
readonly name: string;
readonly first: readonly T[];
readonly second: readonly T[];
readonly expected: boolean;
}
export class ComparerTestScenario {
private readonly testCases: Array<IComparerTestCase<number>> = [];
public addEmptyArrays(expectedResult: boolean) {
return this.addTestCase({
name: 'empty array',
first: [ ],
second: [ ],
expected: expectedResult,
}, true);
}
public addSameItemsWithSameOrder(expectedResult: boolean) {
return this.addTestCase({
name: 'same items with same order',
first: [ 1, 2, 3 ],
second: [ 1, 2, 3 ],
expected: expectedResult,
}, true);
}
public addSameItemsWithDifferentOrder(expectedResult: boolean) {
return this.addTestCase({
name: 'same items with different order',
first: [ 1, 2, 3 ],
second: [ 2, 3, 1 ],
expected: expectedResult,
}, true);
}
public addDifferentItemsWithSameLength(expectedResult: boolean) {
return this.addTestCase({
name: 'different items with same length',
first: [ 1, 2, 3 ],
second: [ 4, 5, 6 ],
expected: expectedResult,
}, true);
}
public addDifferentItemsWithDifferentLength(expectedResult: boolean) {
return this.addTestCase({
name: 'different items with different length',
first: [ 1, 2 ],
second: [ 3, 4, 5 ],
expected: expectedResult,
}, true);
}
public forEachCase(handler: (testCase: IComparerTestCase<number>) => void) {
for (const testCase of this.testCases) {
handler(testCase);
}
}
private addTestCase(testCase: IComparerTestCase<number>, addReversed: boolean) {
this.testCases.push(testCase);
if (addReversed) {
this.testCases.push({
name: `${testCase.name} (reversed)`,
first: testCase.second,
second: testCase.first,
expected: testCase.expected,
});
}
return this;
}
}

View File

@@ -0,0 +1,68 @@
import 'mocha';
import { expect } from 'chai';
import { scrambledEqual } from '@/application/Common/Array';
import { sequenceEqual } from '@/application/Common/Array';
import { ComparerTestScenario } from './Array.ComparerTestScenario';
describe('Array', () => {
describe('scrambledEqual', () => {
describe('throws if arguments are undefined', () => {
it('first argument is undefined', () => {
const expectedError = 'undefined first array';
const act = () => scrambledEqual(undefined, []);
expect(act).to.throw(expectedError);
});
it('second arguments is undefined', () => {
const expectedError = 'undefined second array';
const act = () => scrambledEqual([], undefined);
expect(act).to.throw(expectedError);
});
});
describe('returns as expected', () => {
// arrange
const scenario = new ComparerTestScenario()
.addSameItemsWithSameOrder(true)
.addSameItemsWithDifferentOrder(true)
.addDifferentItemsWithSameLength(false)
.addDifferentItemsWithDifferentLength(false);
// act
scenario.forEachCase((testCase) => {
it(testCase.name, () => {
const actual = scrambledEqual(testCase.first, testCase.second);
// assert
expect(actual).to.equal(testCase.expected);
});
});
});
});
describe('sequenceEqual', () => {
describe('throws if arguments are undefined', () => {
it('first argument is undefined', () => {
const expectedError = 'undefined first array';
const act = () => sequenceEqual(undefined, []);
expect(act).to.throw(expectedError);
});
it('second arguments is undefined', () => {
const expectedError = 'undefined second array';
const act = () => sequenceEqual([], undefined);
expect(act).to.throw(expectedError);
});
});
describe('returns as expected', () => {
// arrange
const scenario = new ComparerTestScenario()
.addSameItemsWithSameOrder(true)
.addSameItemsWithDifferentOrder(true)
.addDifferentItemsWithSameLength(false)
.addDifferentItemsWithDifferentLength(false);
// act
scenario.forEachCase((testCase) => {
it(testCase.name, () => {
const actual = scrambledEqual(testCase.first, testCase.second);
// assert
expect(actual).to.equal(testCase.expected);
});
});
});
});
});

View File

@@ -1,6 +1,8 @@
import 'mocha';
import { expect } from 'chai';
import { getEnumNames, getEnumValues, createEnumParser } from '@/application/Common/Enum';
import { getEnumNames, getEnumValues, createEnumParser, assertInRange } from '@/application/Common/Enum';
import { EnumRangeTestRunner } from './EnumRangeTestRunner';
import { scrambledEqual } from '@/application/Common/Array';
describe('Enum', () => {
describe('createEnumParser', () => {
@@ -78,7 +80,7 @@ describe('Enum', () => {
// act
const actual = getEnumNames(TestEnum);
// assert
expect(expected.sort()).to.deep.equal(actual.sort());
expect(scrambledEqual(expected, actual));
});
});
describe('getEnumValues', () => {
@@ -89,7 +91,19 @@ describe('Enum', () => {
// act
const actual = getEnumValues(TestEnum);
// assert
expect(expected.sort()).to.deep.equal(actual.sort());
expect(scrambledEqual(expected, actual));
});
});
describe('assertInRange', () => {
// arrange
enum TestEnum { Red, Green, Blue }
const validValue = TestEnum.Red;
// act
const act = (value: TestEnum) => assertInRange(value, TestEnum);
// assert
new EnumRangeTestRunner(act)
.testOutOfRangeThrows()
.testUndefinedValueThrows()
.testValidValueDoesNotThrow(validValue);
});
});

View File

@@ -0,0 +1,54 @@
import 'mocha';
import { expect } from 'chai';
import { EnumType } from '@/application/Common/Enum';
export class EnumRangeTestRunner<TEnumValue extends EnumType> {
constructor(private readonly runner: (value: TEnumValue) => any) {
}
public testOutOfRangeThrows() {
it('throws when value is out of range', () => {
// arrange
const value = Number.MAX_SAFE_INTEGER as TEnumValue;
const expectedError = `enum value "${value}" is out of range`;
// act
const act = () => this.runner(value);
// assert
expect(act).to.throw(expectedError);
});
return this;
}
public testUndefinedValueThrows() {
it('throws when value is undefined', () => {
// arrange
const value = undefined;
const expectedError = 'undefined enum value';
// act
const act = () => this.runner(value);
// assert
expect(act).to.throw(expectedError);
});
return this;
}
public testInvalidValueThrows(invalidValue: TEnumValue, expectedError: string) {
it(`throws ${expectedError}`, () => {
// arrange
const value = invalidValue;
// act
const act = () => this.runner(value);
// assert
expect(act).to.throw(expectedError);
});
return this;
}
public testValidValueDoesNotThrow(validValue: TEnumValue) {
it('throws when value is undefined', () => {
// arrange
const value = validValue;
// act
const act = () => this.runner(value);
// assert
expect(act).to.not.throw();
});
return this;
}
}

View File

@@ -0,0 +1,60 @@
import 'mocha';
import { expect } from 'chai';
import { ScriptingLanguage } from '@/domain/ScriptingLanguage';
import { ScriptingLanguageFactory } from '@/application/Common/ScriptingLanguage/ScriptingLanguageFactory';
import { ScriptingLanguageFactoryTestRunner } from './ScriptingLanguageFactoryTestRunner';
import { EnumRangeTestRunner } from '@tests/unit/application/Common/EnumRangeTestRunner';
class ScriptingLanguageConcrete extends ScriptingLanguageFactory<number> {
public registerGetter(language: ScriptingLanguage, getter: () => number) {
super.registerGetter(language, getter);
}
}
describe('ScriptingLanguageFactory', () => {
describe('registerGetter', () => {
describe('validates language', () => {
// arrange
const validValue = ScriptingLanguage.batchfile;
const getter = () => undefined;
const sut = new ScriptingLanguageConcrete();
// act
const act = (language: ScriptingLanguage) => sut.registerGetter(language, getter);
// assert
new EnumRangeTestRunner(act)
.testOutOfRangeThrows()
.testUndefinedValueThrows()
.testValidValueDoesNotThrow(validValue);
});
it('throw when getter is undefined', () => {
// arrange
const expectedError = `undefined getter`;
const language = ScriptingLanguage.batchfile;
const getter = undefined;
const sut = new ScriptingLanguageConcrete();
// act
const act = () => sut.registerGetter(language, getter);
// assert
expect(act).to.throw(expectedError);
});
it('throw when language is already registered', () => {
// arrange
const language = ScriptingLanguage.batchfile;
const expectedError = `${ScriptingLanguage[language]} is already registered`;
const getter = () => undefined;
const sut = new ScriptingLanguageConcrete();
// act
sut.registerGetter(language, getter);
const reRegister = () => sut.registerGetter(language, getter);
// assert
expect(reRegister).to.throw(expectedError);
});
});
describe('create', () => {
const sut = new ScriptingLanguageConcrete();
sut.registerGetter(ScriptingLanguage.batchfile, () => undefined);
const runner = new ScriptingLanguageFactoryTestRunner();
runner.testCreateMethod(sut);
});
});

View File

@@ -0,0 +1,49 @@
import 'mocha';
import { expect } from 'chai';
import { IScriptingLanguageFactory } from '@/application/Common/ScriptingLanguage/IScriptingLanguageFactory';
import { ScriptingLanguage } from '@/domain/ScriptingLanguage';
import { EnumRangeTestRunner } from '@tests/unit/application/Common/EnumRangeTestRunner';
export class ScriptingLanguageFactoryTestRunner<T> {
private expectedTypes = new Map<ScriptingLanguage, T>();
public expect(language: ScriptingLanguage, resultType: T) {
this.expectedTypes.set(language, resultType);
return this;
}
public testCreateMethod(sut: IScriptingLanguageFactory<T>) {
if (!sut) { throw new Error('undefined sut'); }
testLanguageValidation(sut);
testExpectedInstanceTypes(sut, this.expectedTypes);
}
}
function testExpectedInstanceTypes<T>(
sut: IScriptingLanguageFactory<T>,
expectedTypes: Map<ScriptingLanguage, T>) {
describe('create returns expected instances', () => {
// arrange
for (const language of Array.from(expectedTypes.keys())) {
it(ScriptingLanguage[language], () => {
// act
const expected = expectedTypes.get(language);
const result = sut.create(language);
// assert
expect(result).to.be.instanceOf(expected, `Actual was: ${result.constructor.name}`);
});
}
});
}
function testLanguageValidation<T>(sut: IScriptingLanguageFactory<T>) {
describe('validates language', () => {
// arrange
const validValue = ScriptingLanguage.batchfile;
// act
const act = (value: ScriptingLanguage) => sut.create(value);
// assert
new EnumRangeTestRunner(act)
.testOutOfRangeThrows()
.testUndefinedValueThrows()
.testValidValueDoesNotThrow(validValue);
});
}

View File

@@ -5,8 +5,9 @@ import { OperatingSystem } from '@/domain/OperatingSystem';
import { ICategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState';
import { IApplicationContext, IApplicationContextChangedEvent } from '@/application/Context/IApplicationContext';
import { IApplication } from '@/domain/IApplication';
import { ApplicationStub } from '../../stubs/ApplicationStub';
import { CategoryCollectionStub } from '../../stubs/CategoryCollectionStub';
import { ApplicationStub } from '@tests/unit/stubs/ApplicationStub';
import { CategoryCollectionStub } from '@tests/unit/stubs/CategoryCollectionStub';
import { EnumRangeTestRunner } from '@tests/unit/application/Common/EnumRangeTestRunner';
describe('ApplicationContext', () => {
describe('changeContext', () => {
@@ -180,40 +181,15 @@ describe('ApplicationContext', () => {
expect(actual).to.deep.equal(expected);
});
describe('throws when OS is invalid', () => {
// arrange
const testCases = [
{
name: 'out of range',
expectedError: 'os "9999" is out of range',
os: 9999,
},
{
name: 'undefined',
expectedError: 'undefined os',
os: undefined,
},
{
name: 'unknown',
expectedError: 'unknown os',
os: OperatingSystem.Unknown,
},
{
name: 'does not exist in application',
expectedError: 'os "Android" is not defined in application',
os: OperatingSystem.Android,
},
];
// act
for (const testCase of testCases) {
it(testCase.name, () => {
const act = () =>
new ObservableApplicationContextFactory()
.withInitialOs(testCase.os)
.construct();
// assert
expect(act).to.throw(testCase.expectedError);
});
}
const act = (os: OperatingSystem) => new ObservableApplicationContextFactory()
.withInitialOs(os)
.construct();
// assert
new EnumRangeTestRunner(act)
.testOutOfRangeThrows()
.testUndefinedValueThrows()
.testInvalidValueThrows(OperatingSystem.Android, 'os "Android" is not defined in application');
});
});
describe('app', () => {

View File

@@ -2,12 +2,12 @@ import 'mocha';
import { expect } from 'chai';
import { OperatingSystem } from '@/domain/OperatingSystem';
import { ICategoryCollection } from '@/domain/ICategoryCollection';
import { CategoryCollectionStub } from '../../stubs/CategoryCollectionStub';
import { EnvironmentStub } from '../../stubs/EnvironmentStub';
import { ApplicationStub } from '../../stubs/ApplicationStub';
import { buildContextAsync } from '@/application/Context/ApplicationContextFactory';
import { IApplicationFactory } from '@/application/IApplicationFactory';
import { IApplication } from '@/domain/IApplication';
import { EnvironmentStub } from '@tests/unit/stubs/EnvironmentStub';
import { ApplicationStub } from '@tests/unit/stubs/ApplicationStub';
import { CategoryCollectionStub } from '@tests/unit/stubs/CategoryCollectionStub';
describe('ApplicationContextFactory', () => {
describe('buildContextAsync', () => {

View File

@@ -5,9 +5,9 @@ import { ApplicationCode } from '@/application/Context/State/Code/ApplicationCod
import { CategoryCollectionState } from '@/application/Context/State/CategoryCollectionState';
import { OperatingSystem } from '@/domain/OperatingSystem';
import { IScript } from '@/domain/IScript';
import { ScriptStub } from '../../../stubs/ScriptStub';
import { CategoryStub } from '../../../stubs/CategoryStub';
import { CategoryCollectionStub } from '../../../stubs/CategoryCollectionStub';
import { ScriptStub } from '@tests/unit/stubs/ScriptStub';
import { CategoryStub } from '@tests/unit/stubs/CategoryStub';
import { CategoryCollectionStub } from '@tests/unit/stubs/CategoryCollectionStub';
describe('CategoryCollectionState', () => {
describe('code', () => {
@@ -55,7 +55,7 @@ describe('CategoryCollectionState', () => {
const collection = new CategoryCollectionStub();
const sut = new CategoryCollectionState(collection);
// act
const actual = sut.selection.totalSelected;
const actual = sut.selection.selectedScripts.length;
// assert
expect(actual).to.equal(0);
});
@@ -68,7 +68,7 @@ describe('CategoryCollectionState', () => {
// act
sut.selection.selectAll();
// assert
expect(sut.selection.totalSelected).to.equal(1);
expect(sut.selection.selectedScripts.length).to.equal(1);
expect(sut.selection.isSelected(expectedScript.id)).to.equal(true);
});
});

View File

@@ -9,10 +9,10 @@ import { CodePosition } from '@/application/Context/State/Code/Position/CodePosi
import { ICodePosition } from '@/application/Context/State/Code/Position/ICodePosition';
import { IScriptingDefinition } from '@/domain/IScriptingDefinition';
import { IUserScript } from '@/application/Context/State/Code/Generation/IUserScript';
import { ScriptingDefinitionStub } from '../../../../stubs/ScriptingDefinitionStub';
import { CategoryStub } from '../../../../stubs/CategoryStub';
import { ScriptStub } from '../../../../stubs/ScriptStub';
import { CategoryCollectionStub } from '../../../../stubs/CategoryCollectionStub';
import { ScriptingDefinitionStub } from '@tests/unit/stubs/ScriptingDefinitionStub';
import { CategoryStub } from '@tests/unit/stubs/CategoryStub';
import { ScriptStub } from '@tests/unit/stubs/ScriptStub';
import { CategoryCollectionStub } from '@tests/unit/stubs/CategoryCollectionStub';
describe('ApplicationCode', () => {
describe('ctor', () => {

View File

@@ -4,8 +4,8 @@ import { CodeChangedEvent } from '@/application/Context/State/Code/Event/CodeCha
import { SelectedScript } from '@/application/Context/State/Selection/SelectedScript';
import { ICodePosition } from '@/application/Context/State/Code/Position/ICodePosition';
import { CodePosition } from '@/application/Context/State/Code/Position/CodePosition';
import { SelectedScriptStub } from '../../../../../stubs/SelectedScriptStub';
import { ScriptStub } from '../../../../../stubs/ScriptStub';
import { SelectedScriptStub } from '@tests/unit/stubs/SelectedScriptStub';
import { ScriptStub } from '@tests/unit/stubs/ScriptStub';
describe('CodeChangedEvent', () => {
describe('ctor', () => {

View File

@@ -1,36 +1,13 @@
import 'mocha';
import { expect } from 'chai';
import { ScriptingLanguage } from '@/domain/ScriptingLanguage';
import { ShellBuilder } from '@/application/Context/State/Code/Generation/Languages/ShellBuilder';
import { BatchBuilder } from '@/application/Context/State/Code/Generation/Languages/BatchBuilder';
import { CodeBuilderFactory } from '@/application/Context/State/Code/Generation/CodeBuilderFactory';
import { ScriptingLanguageFactoryTestRunner } from '@tests/unit/application/Common/ScriptingLanguage/ScriptingLanguageFactoryTestRunner';
describe('CodeBuilderFactory', () => {
describe('create', () => {
describe('creates expected type', () => {
// arrange
const testCases: Array< { language: ScriptingLanguage, expected: any} > = [
{ language: ScriptingLanguage.shellscript, expected: ShellBuilder},
{ language: ScriptingLanguage.batchfile, expected: BatchBuilder},
];
for (const testCase of testCases) {
it(ScriptingLanguage[testCase.language], () => {
// act
const sut = new CodeBuilderFactory();
const result = sut.create(testCase.language);
// assert
expect(result).to.be.instanceOf(testCase.expected,
`Actual was: ${result.constructor.name}`);
});
}
});
it('throws on unknown scripting language', () => {
// arrange
const sut = new CodeBuilderFactory();
// act
const act = () => sut.create(3131313131);
// assert
expect(act).to.throw(`unknown language: "${ScriptingLanguage[3131313131]}"`);
});
});
const sut = new CodeBuilderFactory();
const runner = new ScriptingLanguageFactoryTestRunner()
.expect(ScriptingLanguage.shellscript, ShellBuilder)
.expect(ScriptingLanguage.batchfile, BatchBuilder);
runner.testCreateMethod(sut);
});

View File

@@ -4,8 +4,8 @@ import { UserScriptGenerator } from '@/application/Context/State/Code/Generation
import { SelectedScript } from '@/application/Context/State/Selection/SelectedScript';
import { ICodeBuilderFactory } from '@/application/Context/State/Code/Generation/ICodeBuilderFactory';
import { ICodeBuilder } from '@/application/Context/State/Code/Generation/ICodeBuilder';
import { ScriptStub } from '../../../../../stubs/ScriptStub';
import { ScriptingDefinitionStub } from '../../../../../stubs/ScriptingDefinitionStub';
import { ScriptStub } from '@tests/unit/stubs/ScriptStub';
import { ScriptingDefinitionStub } from '@tests/unit/stubs/ScriptingDefinitionStub';
describe('UserScriptGenerator', () => {
describe('scriptingDefinition', () => {

View File

@@ -1,6 +1,6 @@
import { CodePosition } from '@/application/Context/State/Code/Position/CodePosition';
import 'mocha';
import { expect } from 'chai';
import { CodePosition } from '@/application/Context/State/Code/Position/CodePosition';
describe('CodePosition', () => {
describe('ctor', () => {

View File

@@ -1,8 +1,8 @@
import { CategoryStub } from '../../../../stubs/CategoryStub';
import { ScriptStub } from '../../../../stubs/ScriptStub';
import { FilterResult } from '@/application/Context/State/Filter/FilterResult';
import 'mocha';
import { expect } from 'chai';
import { FilterResult } from '@/application/Context/State/Filter/FilterResult';
import { CategoryStub } from '@tests/unit/stubs/CategoryStub';
import { ScriptStub } from '@tests/unit/stubs/ScriptStub';
describe('FilterResult', () => {
describe('hasAnyMatches', () => {

View File

@@ -1,11 +1,10 @@
import 'mocha';
import { expect } from 'chai';
import { IFilterResult } from '@/application/Context/State/Filter/IFilterResult';
import { UserFilter } from '@/application/Context/State/Filter/UserFilter';
import { CategoryStub } from '../../../../stubs/CategoryStub';
import { ScriptStub } from '../../../../stubs/ScriptStub';
import { CategoryCollectionStub } from '../../../../stubs/CategoryCollectionStub';
import { CategoryStub } from '@tests/unit/stubs/CategoryStub';
import { ScriptStub } from '@tests/unit/stubs/ScriptStub';
import { CategoryCollectionStub } from '@tests/unit/stubs/CategoryCollectionStub';
describe('UserFilter', () => {
describe('removeFilter', () => {

View File

@@ -1,7 +1,7 @@
import { ScriptStub } from '../../../../stubs/ScriptStub';
import { SelectedScript } from '@/application/Context/State/Selection/SelectedScript';
import 'mocha';
import { expect } from 'chai';
import { ScriptStub } from '@tests/unit/stubs/ScriptStub';
import { SelectedScript } from '@/application/Context/State/Selection/SelectedScript';
describe('SelectedScript', () => {
it('id is same as script id', () => {

View File

@@ -3,10 +3,10 @@ import { expect } from 'chai';
import { IScript } from '@/domain/IScript';
import { SelectedScript } from '@/application/Context/State/Selection/SelectedScript';
import { UserSelection } from '@/application/Context/State/Selection/UserSelection';
import { CategoryStub } from '../../../../stubs/CategoryStub';
import { CategoryCollectionStub } from '../../../../stubs/CategoryCollectionStub';
import { SelectedScriptStub } from '../../../../stubs/SelectedScriptStub';
import { ScriptStub } from '../../../../stubs/ScriptStub';
import { CategoryStub } from '@tests/unit/stubs/CategoryStub';
import { CategoryCollectionStub } from '@tests/unit/stubs/CategoryCollectionStub';
import { SelectedScriptStub } from '@tests/unit/stubs/SelectedScriptStub';
import { ScriptStub } from '@tests/unit/stubs/ScriptStub';
describe('UserSelection', () => {
describe('ctor', () => {
@@ -152,7 +152,6 @@ describe('UserSelection', () => {
// act
sut.removeAllInCategory(categoryId);
// assert
expect(sut.totalSelected).to.equal(0);
expect(sut.selectedScripts.length).to.equal(0);
});
it('removes existing some exists', () => {
@@ -167,7 +166,6 @@ describe('UserSelection', () => {
// act
sut.removeAllInCategory(categoryId);
// assert
expect(sut.totalSelected).to.equal(0);
expect(sut.selectedScripts.length).to.equal(0);
});
});

View File

@@ -1,16 +1,18 @@
import 'mocha';
import { expect } from 'chai';
import { OperatingSystem } from '@/domain/OperatingSystem';
import { BrowserOsDetector } from '@/application/Environment/BrowserOs/BrowserOsDetector';
import { BrowserOsTestCases } from './BrowserOsTestCases';
describe('BrowserOsDetector', () => {
it('unkown when user agent is undefined', () => {
it('returns undefined when user agent is undefined', () => {
// arrange
const expected = undefined;
const sut = new BrowserOsDetector();
// act
const actual = sut.detect(undefined);
// assert
expect(actual).to.equal(OperatingSystem.Unknown);
expect(actual).to.equal(expected);
});
it('detects as expected', () => {
for (const testCase of BrowserOsTestCases) {

View File

@@ -9,7 +9,7 @@ interface IDesktopTestCase {
export const DesktopOsTestCases: ReadonlyArray<IDesktopTestCase> = [
{
processPlatform: 'aix',
expectedOs: OperatingSystem.Unknown,
expectedOs: undefined,
},
{
processPlatform: 'darwin',
@@ -17,7 +17,7 @@ export const DesktopOsTestCases: ReadonlyArray<IDesktopTestCase> = [
},
{
processPlatform: 'freebsd',
expectedOs: OperatingSystem.Unknown,
expectedOs: undefined,
},
{
processPlatform: 'linux',
@@ -25,11 +25,11 @@ export const DesktopOsTestCases: ReadonlyArray<IDesktopTestCase> = [
},
{
processPlatform: 'openbsd',
expectedOs: OperatingSystem.Unknown,
expectedOs: undefined,
},
{
processPlatform: 'sunos',
expectedOs: OperatingSystem.Unknown,
expectedOs: undefined,
},
{
processPlatform: 'win32',

View File

@@ -1,8 +1,9 @@
import 'mocha';
import { expect } from 'chai';
import { IBrowserOsDetector } from '@/application/Environment/BrowserOs/IBrowserOsDetector';
import { OperatingSystem } from '@/domain/OperatingSystem';
import { DesktopOsTestCases } from './DesktopOsTestCases';
import { Environment } from '@/application/Environment/Environment';
import { expect } from 'chai';
interface EnvironmentVariables {
window?: any;

View File

@@ -10,9 +10,9 @@ import { ProjectInformation } from '@/domain/ProjectInformation';
import { ICategoryCollection } from '@/domain/ICategoryCollection';
import { OperatingSystem } from '@/domain/OperatingSystem';
import { getEnumValues } from '@/application/Common/Enum';
import { CategoryCollectionStub } from '../../stubs/CategoryCollectionStub';
import { getProcessEnvironmentStub } from '../../stubs/ProcessEnvironmentStub';
import { CollectionDataStub } from '../../stubs/CollectionDataStub';
import { CategoryCollectionStub } from '@tests/unit/stubs/CategoryCollectionStub';
import { getProcessEnvironmentStub } from '@tests/unit/stubs/ProcessEnvironmentStub';
import { CollectionDataStub } from '@tests/unit/stubs/CollectionDataStub';
describe('ApplicationParser', () => {
describe('parseApplication', () => {

View File

@@ -7,13 +7,13 @@ import { parseProjectInformation } from '@/application/Parser/ProjectInformation
import { OperatingSystem } from '@/domain/OperatingSystem';
import { RecommendationLevel } from '@/domain/RecommendationLevel';
import { ScriptingDefinitionParser } from '@/application/Parser/ScriptingDefinition/ScriptingDefinitionParser';
import { EnumParserStub } from '../../stubs/EnumParserStub';
import { ProjectInformationStub } from '../../stubs/ProjectInformationStub';
import { getCategoryStub, CollectionDataStub } from '../../stubs/CollectionDataStub';
import { CategoryCollectionParseContextStub } from '../../stubs/CategoryCollectionParseContextStub';
import { CategoryDataStub } from '../../stubs/CategoryDataStub';
import { ScriptDataStub } from '../../stubs/ScriptDataStub';
import { FunctionDataStub } from '../../stubs/FunctionDataStub';
import { EnumParserStub } from '@tests/unit/stubs/EnumParserStub';
import { ProjectInformationStub } from '@tests/unit/stubs/ProjectInformationStub';
import { getCategoryStub, CollectionDataStub } from '@tests/unit/stubs/CollectionDataStub';
import { CategoryCollectionParseContextStub } from '@tests/unit/stubs/CategoryCollectionParseContextStub';
import { CategoryDataStub } from '@tests/unit/stubs/CategoryDataStub';
import { ScriptDataStub } from '@tests/unit/stubs/ScriptDataStub';
import { FunctionDataStub } from '@tests/unit/stubs/FunctionDataStub';
describe('CategoryCollectionParser', () => {
describe('parseCategoryCollection', () => {

View File

@@ -3,11 +3,11 @@ import { expect } from 'chai';
import { parseCategory } from '@/application/Parser/CategoryParser';
import { parseScript } from '@/application/Parser/Script/ScriptParser';
import { parseDocUrls } from '@/application/Parser/DocumentationParser';
import { ScriptCompilerStub } from '../../stubs/ScriptCompilerStub';
import { ScriptDataStub } from '../../stubs/ScriptDataStub';
import { CategoryCollectionParseContextStub } from '../../stubs/CategoryCollectionParseContextStub';
import { LanguageSyntaxStub } from '../../stubs/LanguageSyntaxStub';
import { CategoryDataStub } from '../../stubs/CategoryDataStub';
import { ScriptCompilerStub } from '@tests/unit/stubs/ScriptCompilerStub';
import { ScriptDataStub } from '@tests/unit/stubs/ScriptDataStub';
import { CategoryCollectionParseContextStub } from '@tests/unit/stubs/CategoryCollectionParseContextStub';
import { LanguageSyntaxStub } from '@tests/unit/stubs/LanguageSyntaxStub';
import { CategoryDataStub } from '@tests/unit/stubs/CategoryDataStub';
describe('CategoryParser', () => {
describe('parseCategory', () => {

View File

@@ -1,7 +1,7 @@
import 'mocha';
import { expect } from 'chai';
import { parseProjectInformation } from '@/application/Parser/ProjectInformationParser';
import { getProcessEnvironmentStub } from '../../stubs/ProcessEnvironmentStub';
import { getProcessEnvironmentStub } from '@tests/unit/stubs/ProcessEnvironmentStub';
describe('ProjectInformationParser', () => {
describe('parseProjectInformation', () => {

View File

@@ -1,14 +1,14 @@
import 'mocha';
import { expect } from 'chai';
import { FunctionData } from 'js-yaml-loader!@/*';
import { ISyntaxFactory } from '@/application/Parser/Script/Syntax/ISyntaxFactory';
import { ScriptingLanguage } from '@/domain/ScriptingLanguage';
import { LanguageSyntaxStub } from '../../../stubs/LanguageSyntaxStub';
import { CategoryCollectionParseContext } from '@/application/Parser/Script/CategoryCollectionParseContext';
import { ScriptingDefinitionStub } from '../../../stubs/ScriptingDefinitionStub';
import { FunctionDataStub } from '../../../stubs/FunctionDataStub';
import { ILanguageSyntax } from '@/domain/ScriptCode';
import { ScriptCompiler } from '@/application/Parser/Script/Compiler/ScriptCompiler';
import { FunctionData } from 'js-yaml-loader!*';
import { LanguageSyntaxStub } from '@tests/unit/stubs/LanguageSyntaxStub';
import { ScriptingDefinitionStub } from '@tests/unit/stubs/ScriptingDefinitionStub';
import { FunctionDataStub } from '@tests/unit/stubs/FunctionDataStub';
describe('CategoryCollectionParseContext', () => {
describe('ctor', () => {

View File

@@ -2,8 +2,8 @@ import 'mocha';
import { expect } from 'chai';
import { ExpressionsCompiler } from '@/application/Parser/Script/Compiler/Expressions/ExpressionsCompiler';
import { IExpressionParser } from '@/application/Parser/Script/Compiler/Expressions/Parser/IExpressionParser';
import { ExpressionStub } from '../../../../../stubs/ExpressionStub';
import { ExpressionParserStub } from '../../../../../stubs/ExpressionParserStub';
import { ExpressionStub } from '@tests/unit/stubs/ExpressionStub';
import { ExpressionParserStub } from '@tests/unit/stubs/ExpressionParserStub';
describe('ExpressionsCompiler', () => {
describe('compileExpressions', () => {

View File

@@ -3,7 +3,7 @@ import { expect } from 'chai';
import { IExpression } from '@/application/Parser/Script/Compiler/Expressions/Expression/IExpression';
import { IExpressionParser } from '@/application/Parser/Script/Compiler/Expressions/Parser/IExpressionParser';
import { CompositeExpressionParser } from '@/application/Parser/Script/Compiler/Expressions/Parser/CompositeExpressionParser';
import { ExpressionStub } from '../../../../../../stubs/ExpressionStub';
import { ExpressionStub } from '@tests/unit/stubs/ExpressionStub';
describe('CompositeExpressionParser', () => {
describe('ctor', () => {

View File

@@ -1,11 +1,11 @@
import 'mocha';
import { expect } from 'chai';
import { ISharedFunction } from '@/application/Parser/Script/Compiler/Function/ISharedFunction';
import { FunctionData } from 'js-yaml-loader!*';
import { FunctionData } from 'js-yaml-loader!@/*';
import { IFunctionCallCompiler } from '@/application/Parser/Script/Compiler/FunctionCall/IFunctionCallCompiler';
import { FunctionCompiler } from '@/application/Parser/Script/Compiler/Function/FunctionCompiler';
import { FunctionCallCompilerStub } from '../../../../../stubs/FunctionCallCompilerStub';
import { FunctionDataStub } from '../../../../../stubs/FunctionDataStub';
import { FunctionCallCompilerStub } from '@tests/unit/stubs/FunctionCallCompilerStub';
import { FunctionDataStub } from '@tests/unit/stubs/FunctionDataStub';
describe('FunctionsCompiler', () => {
describe('compileFunctions', () => {

View File

@@ -1,7 +1,7 @@
import 'mocha';
import { expect } from 'chai';
import { SharedFunctionCollection } from '@/application/Parser/Script/Compiler/Function/SharedFunctionCollection';
import { SharedFunctionStub } from '../../../../../stubs/SharedFunctionStub';
import { SharedFunctionStub } from '@tests/unit/stubs/SharedFunctionStub';
describe('SharedFunctionCollection', () => {
describe('addFunction', () => {

View File

@@ -1,12 +1,12 @@
import 'mocha';
import { expect } from 'chai';
import { FunctionCallData, FunctionCallParametersData } from 'js-yaml-loader!*';
import { FunctionCallData, FunctionCallParametersData } from 'js-yaml-loader!@/*';
import { FunctionCallCompiler } from '@/application/Parser/Script/Compiler/FunctionCall/FunctionCallCompiler';
import { ISharedFunctionCollection } from '@/application/Parser/Script/Compiler/Function/ISharedFunctionCollection';
import { IExpressionsCompiler } from '@/application/Parser/Script/Compiler/Expressions/IExpressionsCompiler';
import { ExpressionsCompilerStub } from '../../../../../stubs/ExpressionsCompilerStub';
import { SharedFunctionCollectionStub } from '../../../../../stubs/SharedFunctionCollectionStub';
import { SharedFunctionStub } from '../../../../../stubs/SharedFunctionStub';
import { ExpressionsCompilerStub } from '@tests/unit/stubs/ExpressionsCompilerStub';
import { SharedFunctionCollectionStub } from '@tests/unit/stubs/SharedFunctionCollectionStub';
import { SharedFunctionStub } from '@tests/unit/stubs/SharedFunctionStub';
describe('FunctionCallCompiler', () => {
describe('compileCall', () => {

View File

@@ -6,12 +6,12 @@ import { ILanguageSyntax } from '@/domain/ScriptCode';
import { IFunctionCompiler } from '@/application/Parser/Script/Compiler/Function/IFunctionCompiler';
import { IFunctionCallCompiler } from '@/application/Parser/Script/Compiler/FunctionCall/IFunctionCallCompiler';
import { ICompiledCode } from '@/application/Parser/Script/Compiler/FunctionCall/ICompiledCode';
import { LanguageSyntaxStub } from '../../../../stubs/LanguageSyntaxStub';
import { ScriptDataStub } from '../../../../stubs/ScriptDataStub';
import { FunctionDataStub } from '../../../../stubs/FunctionDataStub';
import { FunctionCallCompilerStub } from '../../../../stubs/FunctionCallCompilerStub';
import { FunctionCompilerStub } from '../../../../stubs/FunctionCompilerStub';
import { SharedFunctionCollectionStub } from '../../../../stubs/SharedFunctionCollectionStub';
import { LanguageSyntaxStub } from '@tests/unit/stubs/LanguageSyntaxStub';
import { ScriptDataStub } from '@tests/unit/stubs/ScriptDataStub';
import { FunctionDataStub } from '@tests/unit/stubs/FunctionDataStub';
import { FunctionCallCompilerStub } from '@tests/unit/stubs/FunctionCallCompilerStub';
import { FunctionCompilerStub } from '@tests/unit/stubs/FunctionCompilerStub';
import { SharedFunctionCollectionStub } from '@tests/unit/stubs/SharedFunctionCollectionStub';
describe('ScriptCompiler', () => {
describe('ctor', () => {

View File

@@ -4,12 +4,12 @@ import { parseScript } from '@/application/Parser/Script/ScriptParser';
import { parseDocUrls } from '@/application/Parser/DocumentationParser';
import { RecommendationLevel } from '@/domain/RecommendationLevel';
import { ICategoryCollectionParseContext } from '@/application/Parser/Script/ICategoryCollectionParseContext';
import { ScriptCompilerStub } from '../../../stubs/ScriptCompilerStub';
import { ScriptDataStub } from '../../../stubs/ScriptDataStub';
import { EnumParserStub } from '../../../stubs/EnumParserStub';
import { ScriptCodeStub } from '../../../stubs/ScriptCodeStub';
import { CategoryCollectionParseContextStub } from '../../../stubs/CategoryCollectionParseContextStub';
import { LanguageSyntaxStub } from '../../../stubs/LanguageSyntaxStub';
import { ScriptCompilerStub } from '@tests/unit/stubs/ScriptCompilerStub';
import { ScriptDataStub } from '@tests/unit/stubs/ScriptDataStub';
import { EnumParserStub } from '@tests/unit/stubs/EnumParserStub';
import { ScriptCodeStub } from '@tests/unit/stubs/ScriptCodeStub';
import { CategoryCollectionParseContextStub } from '@tests/unit/stubs/CategoryCollectionParseContextStub';
import { LanguageSyntaxStub } from '@tests/unit/stubs/LanguageSyntaxStub';
describe('ScriptParser', () => {
describe('parseScript', () => {

View File

@@ -4,7 +4,6 @@ import { ILanguageSyntax } from '@/domain/ScriptCode';
import { BatchFileSyntax } from '@/application/Parser/Script/Syntax/BatchFileSyntax';
import { ShellScriptSyntax } from '@/application/Parser/Script/Syntax/ShellScriptSyntax';
function getSystemsUnderTest(): ILanguageSyntax[] {
return [ new BatchFileSyntax(), new ShellScriptSyntax() ];
}

View File

@@ -1,38 +1,14 @@
import 'mocha';
import { expect } from 'chai';
import { SyntaxFactory } from '@/application/Parser/Script/Syntax/SyntaxFactory';
import { ScriptingLanguage } from '@/domain/ScriptingLanguage';
import { ShellScriptSyntax } from '@/application/Parser/Script/Syntax/ShellScriptSyntax';
import { BatchFileSyntax } from '@/application/Parser/Script/Syntax/BatchFileSyntax';
import { ScriptingLanguageFactoryTestRunner } from '@tests/unit/application/Common/ScriptingLanguage/ScriptingLanguageFactoryTestRunner';
describe('SyntaxFactory', () => {
describe('getSyntax', () => {
describe('creates expected type', () => {
describe('shellscript returns ShellBuilder', () => {
// arrange
const testCases: Array< { language: ScriptingLanguage, expected: any} > = [
{ language: ScriptingLanguage.shellscript, expected: ShellScriptSyntax},
{ language: ScriptingLanguage.batchfile, expected: BatchFileSyntax},
];
for (const testCase of testCases) {
it(ScriptingLanguage[testCase.language], () => {
// act
const sut = new SyntaxFactory();
const result = sut.create(testCase.language);
// assert
expect(result).to.be.instanceOf(testCase.expected,
`Actual was: ${result.constructor.name}`);
});
}
});
});
it('throws on unknown scripting language', () => {
// arrange
const sut = new SyntaxFactory();
// act
const act = () => sut.create(3131313131);
// assert
expect(act).to.throw(`unknown language: "${ScriptingLanguage[3131313131]}"`);
});
});
const sut = new SyntaxFactory();
const runner = new ScriptingLanguageFactoryTestRunner()
.expect(ScriptingLanguage.shellscript, ShellScriptSyntax)
.expect(ScriptingLanguage.batchfile, BatchFileSyntax);
runner.testCreateMethod(sut);
});

View File

@@ -2,8 +2,8 @@ import 'mocha';
import { expect } from 'chai';
import { CodeSubstituter } from '@/application/Parser/ScriptingDefinition/CodeSubstituter';
import { IExpressionsCompiler } from '@/application/Parser/Script/Compiler/Expressions/IExpressionsCompiler';
import { ProjectInformationStub } from '../../../stubs/ProjectInformationStub';
import { ExpressionsCompilerStub } from '../../../stubs/ExpressionsCompilerStub';
import { ProjectInformationStub } from '@tests/unit/stubs/ProjectInformationStub';
import { ExpressionsCompilerStub } from '@tests/unit/stubs/ExpressionsCompilerStub';
describe('CodeSubstituter', () => {
describe('throws with invalid parameters', () => {

View File

@@ -5,10 +5,10 @@ import { ScriptingDefinitionParser } from '@/application/Parser/ScriptingDefinit
import { IEnumParser } from '@/application/Common/Enum';
import { ICodeSubstituter } from '@/application/Parser/ScriptingDefinition/ICodeSubstituter';
import { IScriptingDefinition } from '@/domain/IScriptingDefinition';
import { ProjectInformationStub } from '../../../stubs/ProjectInformationStub';
import { EnumParserStub } from '../../../stubs/EnumParserStub';
import { ScriptingDefinitionDataStub } from '../../../stubs/ScriptingDefinitionDataStub';
import { CodeSubstituterStub } from '../../../stubs/CodeSubstituterStub';
import { ProjectInformationStub } from '@tests/unit/stubs/ProjectInformationStub';
import { EnumParserStub } from '@tests/unit/stubs/EnumParserStub';
import { ScriptingDefinitionDataStub } from '@tests/unit/stubs/ScriptingDefinitionDataStub';
import { CodeSubstituterStub } from '@tests/unit/stubs/CodeSubstituterStub';
describe('ScriptingDefinitionParser', () => {
describe('parseScriptingDefinition', () => {

View File

@@ -1,9 +1,9 @@
import 'mocha';
import { expect } from 'chai';
import { Application } from '@/domain/Application';
import { CategoryCollectionStub } from '../stubs/CategoryCollectionStub';
import { ProjectInformationStub } from '../stubs/ProjectInformationStub';
import { OperatingSystem } from '@/domain/OperatingSystem';
import { CategoryCollectionStub } from '@tests/unit/stubs/CategoryCollectionStub';
import { ProjectInformationStub } from '@tests/unit/stubs/ProjectInformationStub';
describe('Application', () => {
describe('getCollection', () => {

View File

@@ -1,8 +1,8 @@
import 'mocha';
import { expect } from 'chai';
import { Category } from '@/domain/Category';
import { CategoryStub } from '../stubs/CategoryStub';
import { ScriptStub } from '../stubs/ScriptStub';
import { CategoryStub } from '@tests/unit/stubs/CategoryStub';
import { ScriptStub } from '@tests/unit/stubs/ScriptStub';
describe('Category', () => {
describe('ctor', () => {

View File

@@ -7,8 +7,9 @@ import { ScriptingLanguage } from '@/domain/ScriptingLanguage';
import { RecommendationLevel } from '@/domain/RecommendationLevel';
import { getEnumValues } from '@/application/Common/Enum';
import { CategoryCollection } from '@/domain/CategoryCollection';
import { ScriptStub } from '../stubs/ScriptStub';
import { CategoryStub } from '../stubs/CategoryStub';
import { ScriptStub } from '@tests/unit/stubs/ScriptStub';
import { CategoryStub } from '@tests/unit/stubs/CategoryStub';
import { EnumRangeTestRunner } from '@tests/unit/application/Common/EnumRangeTestRunner';
describe('CategoryCollection', () => {
describe('getScriptsByLevel', () => {
@@ -186,35 +187,15 @@ describe('CategoryCollection', () => {
// assert
expect(sut.os).to.deep.equal(expected);
});
it('cannot construct with unknown os', () => {
// arrange
const os = OperatingSystem.Unknown;
describe('throws when invalid', () => {
// act
const construct = () => new CategoryCollectionBuilder()
const act = (os: OperatingSystem) => new CategoryCollectionBuilder()
.withOs(os)
.construct();
// assert
expect(construct).to.throw('unknown os');
});
it('cannot construct with undefined os', () => {
// arrange
const os = undefined;
// act
const construct = () => new CategoryCollectionBuilder()
.withOs(os)
.construct();
// assert
expect(construct).to.throw('undefined os');
});
it('cannot construct with OS not in range', () => {
// arrange
const os: OperatingSystem = 666;
// act
const construct = () => new CategoryCollectionBuilder()
.withOs(os)
.construct();
// assert
expect(construct).to.throw(`os "${os}" is out of range`);
new EnumRangeTestRunner(act)
.testOutOfRangeThrows()
.testUndefinedValueThrows();
});
});
describe('scriptingDefinition', () => {

View File

@@ -2,6 +2,7 @@ import 'mocha';
import { expect } from 'chai';
import { ProjectInformation } from '@/domain/ProjectInformation';
import { OperatingSystem } from '@/domain/OperatingSystem';
import { EnumRangeTestRunner } from '@tests/unit/application/Common/EnumRangeTestRunner';
describe('ProjectInformation', () => {
it('sets name as expected', () => {
@@ -115,14 +116,16 @@ describe('ProjectInformation', () => {
// assert
expect(actual).to.equal(expected);
});
it('throws when OS is unknown', () => {
describe('throws when os is invalid', () => {
// arrange
const sut = new ProjectInformation('name', 'version', 'repositoryUrl', 'homepage');
const os = OperatingSystem.Unknown;
// act
const act = () => sut.getDownloadUrl(os);
const act = (os: OperatingSystem) => sut.getDownloadUrl(os);
// assert
expect(act).to.throw(`Unsupported os: ${OperatingSystem[os]}`);
new EnumRangeTestRunner(act)
.testOutOfRangeThrows()
.testUndefinedValueThrows()
.testInvalidValueThrows(OperatingSystem.KaiOS, `Unsupported os: ${OperatingSystem[OperatingSystem.KaiOS]}`);
});
});
});

View File

@@ -1,10 +1,10 @@
import { getEnumValues } from '@/application/Common/Enum';
import 'mocha';
import { expect } from 'chai';
import { getEnumValues } from '@/application/Common/Enum';
import { Script } from '@/domain/Script';
import { RecommendationLevel } from '@/domain/RecommendationLevel';
import { IScriptCode } from '@/domain/IScriptCode';
import { ScriptCodeStub } from '../stubs/ScriptCodeStub';
import { ScriptCodeStub } from '@tests/unit/stubs/ScriptCodeStub';
describe('Script', () => {
describe('ctor', () => {

View File

@@ -3,7 +3,7 @@ import { expect } from 'chai';
import { ScriptCode } from '@/domain/ScriptCode';
import { IScriptCode } from '@/domain/IScriptCode';
import { ILanguageSyntax } from '@/domain/ScriptCode';
import { LanguageSyntaxStub } from '../stubs/LanguageSyntaxStub';
import { LanguageSyntaxStub } from '@tests/unit/stubs/LanguageSyntaxStub';
describe('ScriptCode', () => {
describe('code', () => {

View File

@@ -1,28 +1,24 @@
import { AsyncLazy } from '@/infrastructure/Threading/AsyncLazy';
import 'mocha';
import { expect } from 'chai';
import { AsyncLazy } from '@/infrastructure/Threading/AsyncLazy';
describe('AsyncLazy', () => {
it('returns value from lambda', async () => {
// arrange
const expected = 'test';
const lambda = () => Promise.resolve(expected);
const sut = new AsyncLazy(lambda);
// act
const actual = await sut.getValueAsync();
// assert
expect(actual).to.equal(expected);
});
describe('when running multiple times', () => {
// arrange
let totalExecuted: number = 0;
beforeEach(() => totalExecuted = 0);
it('when running sync', async () => {
// act
const sut = new AsyncLazy(() => {
totalExecuted++;
return Promise.resolve(totalExecuted);
@@ -31,11 +27,12 @@ describe('AsyncLazy', () => {
for (let i = 0; i < 5; i++) {
results.push(await sut.getValueAsync());
}
// assert
expect(totalExecuted).to.equal(1);
expect(results).to.deep.equal([1, 1, 1, 1, 1]);
});
it('when running long-running task in parallel', async () => {
// act
const sleepAsync = (time: number) => new Promise(((resolve) => setTimeout(resolve, time)));
const sut = new AsyncLazy(async () => {
await sleepAsync(100);
@@ -48,6 +45,7 @@ describe('AsyncLazy', () => {
sut.getValueAsync(),
sut.getValueAsync(),
sut.getValueAsync()]);
// assert
expect(totalExecuted).to.equal(1);
expect(results).to.deep.equal([1, 1, 1, 1, 1]);
});

View File

@@ -1,8 +1,8 @@
import { EnvironmentStub } from './../stubs/EnvironmentStub';
import { OperatingSystem } from '@/domain/OperatingSystem';
import 'mocha';
import { expect } from 'chai';
import { runCodeAsync } from '@/infrastructure/CodeRunner';
import { EnvironmentStub } from '@tests/unit/stubs/EnvironmentStub';
import { OperatingSystem } from '@/domain/OperatingSystem';
import { CodeRunner } from '@/infrastructure/CodeRunner';
describe('CodeRunner', () => {
describe('runCodeAsync', () => {
@@ -127,7 +127,8 @@ class TestContext {
private env = mockEnvironment(OperatingSystem.Windows);
public async runCodeAsync(): Promise<void> {
await runCodeAsync(this.code, this.folderName, this.fileExtension, this.mocks, this.env);
const runner = new CodeRunner(this.mocks, this.env);
await runner.runCodeAsync(this.code, this.folderName, this.fileExtension);
}
public withOs(os: OperatingSystem) {
this.env = mockEnvironment(os);

View File

@@ -1,7 +1,7 @@
import 'mocha';
import { expect } from 'chai';
import { EventHandler, IEventSource, IEventSubscription } from '@/infrastructure/Events/IEventSource';
import { EventSource } from '@/infrastructure/Events/EventSource';
import { expect } from 'chai';
import 'mocha';
describe('EventSource', () => {
class ObserverMock {
@@ -42,7 +42,6 @@ describe('EventSource', () => {
expect(observer.onReceiveCalls).to.have.lengthOf(0);
});
});
describe('multiple observers', () => {
// arrange
let observers: ObserverMock[];

View File

@@ -1,7 +1,7 @@
import 'mocha';
import { expect } from 'chai';
import { EventSubscriptionCollection } from '@/infrastructure/Events/EventSubscriptionCollection';
import { IEventSubscription } from '@/infrastructure/Events/IEventSource';
import { expect } from 'chai';
import 'mocha';
describe('EventSubscriptionCollection', () => {
it('unsubscribeAll unsubscribes from all registered subscriptions', () => {

View File

@@ -1,6 +1,7 @@
import { NumericEntityStub } from './../stubs/NumericEntityStub';
import { InMemoryRepository } from '@/infrastructure/Repository/InMemoryRepository';
import 'mocha';
import { expect } from 'chai';
import { NumericEntityStub } from '@tests/unit/stubs/NumericEntityStub';
import { InMemoryRepository } from '@/infrastructure/Repository/InMemoryRepository';
describe('InMemoryRepository', () => {
describe('exists', () => {

View File

@@ -0,0 +1,32 @@
import { SelectedScript } from '@/application/Context/State/Selection/SelectedScript';
import { RecommendationLevel } from '@/domain/RecommendationLevel';
import { CategoryCollectionStateStub } from '@tests/unit/stubs/CategoryCollectionStateStub';
import { ScriptStub } from '@tests/unit/stubs/ScriptStub';
export class SelectionStateTestScenario {
public readonly all: readonly SelectedScript[];
public readonly allStandard: readonly SelectedScript[];
public readonly someStandard: readonly SelectedScript[];
public readonly someStrict: readonly SelectedScript[];
public readonly allStrict: readonly SelectedScript[];
public readonly someUnrecommended: readonly SelectedScript[];
public readonly allUnrecommended: readonly SelectedScript[];
constructor() {
this.someStandard = createSelectedScripts(RecommendationLevel.Standard, 'standard-some-1', 'standard-some-2');
this.allStandard = [...this.someStandard, ...createSelectedScripts(RecommendationLevel.Standard, 'standard-all-1', 'standard-all-2')];
this.someStrict = createSelectedScripts(RecommendationLevel.Strict, 'strict-some-1', 'strict-some-2');
this.allStrict = [...this.someStrict, ...createSelectedScripts(RecommendationLevel.Strict, 'strict-all-1', 'strict-all-2')];
this.someUnrecommended = createSelectedScripts(undefined, 'unrecommended-some-1', 'unrecommended-some-2');
this.allUnrecommended = [...this.someUnrecommended, ...createSelectedScripts(undefined, 'unrecommended-all-1', 'unrecommended-all-2')];
this.all = [...this.allStandard, ...this.allStrict, ...this.allUnrecommended];
}
public generateState(selectedScripts: readonly SelectedScript[]) {
const allScripts = this.all.map((s) => s.script);
return new CategoryCollectionStateStub(allScripts)
.withSelectedScripts(selectedScripts);
}
}
function createSelectedScripts(level?: RecommendationLevel, ...ids: string[]) {
return ids.map((id) => new SelectedScript(new ScriptStub(id).withLevel(level), false));
}

View File

@@ -0,0 +1,132 @@
import 'mocha';
import { expect } from 'chai';
import { SelectionType, SelectionTypeHandler } from '@/presentation/components/Scripts/Menu/Selector/SelectionTypeHandler';
import { scrambledEqual } from '@/application/Common/Array';
import { RecommendationLevel } from '@/domain/RecommendationLevel';
import { SelectionStateTestScenario } from './SelectionStateTestScenario';
describe('SelectionTypeHandler', () => {
describe('ctor', () => {
it('throws when state is undefined', () => {
// arrange
const expectedError = 'undefined state';
const state = undefined;
// act
const sut = () => new SelectionTypeHandler(state);
// assert
expect(sut).to.throw(expectedError);
});
});
describe('selectType', () => {
it('throws when type is custom', () => {
// arrange
const expectedError = 'cannot select custom type';
const scenario = new SelectionStateTestScenario();
const state = scenario.generateState([]);
const sut = new SelectionTypeHandler(state);
// act
const act = () => sut.selectType(SelectionType.Custom);
// assert
expect(act).to.throw(expectedError);
});
describe('select types as expected', () => {
// arrange
const scenario = new SelectionStateTestScenario();
const initialScriptsCases = [{
name: 'when nothing is selected',
initialScripts: [],
}, {
name: 'when some scripts are selected',
initialScripts: [...scenario.allStandard, ...scenario.someStrict],
}, {
name: 'when all scripts are selected',
initialScripts: scenario.all,
} ];
for (const initialScriptsCase of initialScriptsCases) {
describe(initialScriptsCase.name, () => {
const state = scenario.generateState(initialScriptsCase.initialScripts);
const sut = new SelectionTypeHandler(state);
const typeExpectations = [{
input: SelectionType.None,
output: [],
}, {
input: SelectionType.Standard,
output: scenario.allStandard,
}, {
input: SelectionType.Strict,
output: [...scenario.allStandard, ...scenario.allStrict],
}, {
input: SelectionType.All,
output: scenario.all,
}];
for (const expectation of typeExpectations) {
// act
it(`${SelectionType[expectation.input]} returns as expected`, () => {
sut.selectType(expectation.input);
// assert
const actual = state.selection.selectedScripts;
const expected = expectation.output;
expect(scrambledEqual(actual, expected));
});
}
});
}
});
});
describe('getCurrentSelectionType', () => {
// arrange
const scenario = new SelectionStateTestScenario();
const testCases = [{
name: 'when nothing is selected',
selection: [],
expected: SelectionType.None,
}, {
name: 'when some standard scripts are selected',
selection: scenario.someStandard,
expected: SelectionType.Custom,
}, {
name: 'when all standard scripts are selected',
selection: scenario.allStandard,
expected: SelectionType.Standard,
}, {
name: 'when all standard and some strict scripts are selected',
selection: [...scenario.allStandard, ...scenario.someStrict],
expected: SelectionType.Custom,
}, {
name: 'when all standard and strict scripts are selected',
selection: [...scenario.allStandard, ...scenario.allStrict],
expected: SelectionType.Strict,
}, {
name: 'when strict scripts are selected but not standard',
selection: scenario.allStrict,
expected: SelectionType.Custom,
}, {
name: 'when all standard and strict, and some unrecommended are selected',
selection: [...scenario.allStandard, ...scenario.allStrict, ...scenario.someUnrecommended],
expected: SelectionType.Custom,
}, {
name: 'when all scripts are selected',
selection: scenario.all,
expected: SelectionType.All,
} ];
for (const testCase of testCases) {
it(testCase.name, () => {
const state = scenario.generateState(testCase.selection);
const sut = new SelectionTypeHandler(state);
// act
const actual = sut.getCurrentSelectionType();
// assert
expect(actual).to.deep.equal(testCase.expected,
`Actual: "${SelectionType[actual]}", expected: "${SelectionType[testCase.expected]}"` +
`\nSelection: ${printSelection()}`);
function printSelection() {
return `total: ${testCase.selection.length}\n` +
'scripts:\n' +
testCase.selection
.map((s) => `{ id: ${s.script.id}, level: ${s.script.level === undefined ? 'unknown' : RecommendationLevel[s.script.level]} }`)
.join(' | ');
}
});
}
});
});

View File

@@ -6,9 +6,9 @@ import { parseSingleCategory,
import { INode, NodeType } from '@/presentation/components/Scripts/ScriptsTree/SelectableTree/Node/INode';
import { IScript } from '@/domain/IScript';
import { ICategory } from '@/domain/ICategory';
import { CategoryStub } from '../../../../stubs/CategoryStub';
import { ScriptStub } from '../../../../stubs/ScriptStub';
import { CategoryCollectionStub } from '../../../../stubs/CategoryCollectionStub';
import { CategoryStub } from '@tests/unit/stubs/CategoryStub';
import { ScriptStub } from '@tests/unit/stubs/ScriptStub';
import { CategoryCollectionStub } from '@tests/unit/stubs/CategoryCollectionStub';
describe('ScriptNodeParser', () => {
it('can convert script id and back', () => {

View File

@@ -4,9 +4,9 @@ import { CategoryReverter } from '@/presentation/components/Scripts/ScriptsTree/
import { getCategoryNodeId } from '@/presentation/components/Scripts/ScriptsTree/ScriptNodeParser';
import { SelectedScript } from '@/application/Context/State/Selection/SelectedScript';
import { UserSelection } from '@/application/Context/State/Selection/UserSelection';
import { CategoryStub } from '../../../../../../../stubs/CategoryStub';
import { CategoryCollectionStub } from '../../../../../../../stubs/CategoryCollectionStub';
import { ScriptStub } from '../../../../../../../stubs/ScriptStub';
import { CategoryStub } from '@tests/unit/stubs/CategoryStub';
import { CategoryCollectionStub } from '@tests/unit/stubs/CategoryCollectionStub';
import { ScriptStub } from '@tests/unit/stubs/ScriptStub';
describe('CategoryReverter', () => {
describe('getState', () => {

View File

@@ -6,9 +6,9 @@ import {
import { ScriptReverter } from '@/presentation/components/Scripts/ScriptsTree/SelectableTree/Node/Reverter/ScriptReverter';
import { CategoryReverter } from '@/presentation/components/Scripts/ScriptsTree/SelectableTree/Node/Reverter/CategoryReverter';
import { getScriptNodeId, getCategoryNodeId } from '@/presentation/components/Scripts/ScriptsTree/ScriptNodeParser';
import { CategoryCollectionStub } from '../../../../../../../stubs/CategoryCollectionStub';
import { CategoryStub } from '../../../../../../../stubs/CategoryStub';
import { ScriptStub } from '../../../../../../../stubs/ScriptStub';
import { CategoryCollectionStub } from '@tests/unit/stubs/CategoryCollectionStub';
import { CategoryStub } from '@tests/unit/stubs/CategoryStub';
import { ScriptStub } from '@tests/unit/stubs/ScriptStub';
describe('ReverterFactory', () => {
describe('getReverter', () => {

View File

@@ -4,10 +4,10 @@ import { ScriptReverter } from '@/presentation/components/Scripts/ScriptsTree/Se
import { getScriptNodeId } from '@/presentation/components/Scripts/ScriptsTree/ScriptNodeParser';
import { UserSelection } from '@/application/Context/State/Selection/UserSelection';
import { SelectedScript } from '@/application/Context/State/Selection/SelectedScript';
import { CategoryCollectionStub } from '../../../../../../../stubs/CategoryCollectionStub';
import { CategoryStub } from '../../../../../../../stubs/CategoryStub';
import { ScriptStub } from '../../../../../../../stubs/ScriptStub';
import { SelectedScriptStub } from '../../../../../../../stubs/SelectedScriptStub';
import { CategoryCollectionStub } from '@tests/unit/stubs/CategoryCollectionStub';
import { CategoryStub } from '@tests/unit/stubs/CategoryStub';
import { ScriptStub } from '@tests/unit/stubs/ScriptStub';
import { SelectedScriptStub } from '@tests/unit/stubs/SelectedScriptStub';
describe('ScriptReverter', () => {
describe('getState', () => {

View File

@@ -0,0 +1,9 @@
import { ICodeChangedEvent } from '@/application/Context/State/Code/Event/ICodeChangedEvent';
import { IApplicationCode } from '@/application/Context/State/Code/IApplicationCode';
import { IEventSource } from '@/infrastructure/Events/IEventSource';
import { EventSource } from '@/infrastructure/Events/EventSource';
export class ApplicationCodeStub implements IApplicationCode {
public changed: IEventSource<ICodeChangedEvent> = new EventSource<ICodeChangedEvent>();
public current: string = '';
}

View File

@@ -1,8 +1,8 @@
import { ICategoryCollectionParseContext } from '@/application/Parser/Script/ICategoryCollectionParseContext';
import { ScriptCompilerStub } from './ScriptCompilerStub';
import { LanguageSyntaxStub } from './LanguageSyntaxStub';
import { IScriptCompiler } from '@/application/Parser/Script/Compiler/IScriptCompiler';
import { ILanguageSyntax } from '@/domain/ScriptCode';
import { ScriptCompilerStub } from './ScriptCompilerStub';
import { LanguageSyntaxStub } from './LanguageSyntaxStub';
export class CategoryCollectionParseContextStub implements ICategoryCollectionParseContext {
public compiler: IScriptCompiler = new ScriptCompilerStub();

View File

@@ -0,0 +1,30 @@
import { IApplicationCode } from '@/application/Context/State/Code/IApplicationCode';
import { IUserFilter } from '@/application/Context/State/Filter/IUserFilter';
import { ICategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState';
import { OperatingSystem } from '@/domain/OperatingSystem';
import { CategoryCollectionStub } from './CategoryCollectionStub';
import { UserSelectionStub } from './UserSelectionStub';
import { UserFilterStub } from './UserFilterStub';
import { ApplicationCodeStub } from './ApplicationCodeStub';
import { SelectedScript } from '@/application/Context/State/Selection/SelectedScript';
import { IScript } from '@/domain/IScript';
import { CategoryStub } from './CategoryStub';
export class CategoryCollectionStateStub implements ICategoryCollectionState {
public readonly code: IApplicationCode = new ApplicationCodeStub();
public readonly filter: IUserFilter = new UserFilterStub();
public readonly os = OperatingSystem.Windows;
public readonly collection: CategoryCollectionStub;
public readonly selection: UserSelectionStub;
constructor(readonly allScripts: IScript[]) {
this.selection = new UserSelectionStub(allScripts);
this.collection = new CategoryCollectionStub()
.withOs(this.os)
.withTotalScripts(this.allScripts.length)
.withAction(new CategoryStub(0).withScripts(...allScripts));
}
public withSelectedScripts(initialScripts: readonly SelectedScript[]) {
this.selection.withSelectedScripts(initialScripts);
return this;
}
}

View File

@@ -1,10 +1,11 @@
import { ScriptingDefinitionStub } from './ScriptingDefinitionStub';
import { OperatingSystem } from '@/domain/OperatingSystem';
import { ScriptStub } from './ScriptStub';
import { IScriptingDefinition } from '@/domain/IScriptingDefinition';
import { IScript } from '@/domain/IScript';
import { ICategory } from '@/domain/ICategory';
import { ICategoryCollection } from '@/domain/ICategoryCollection';
import { ScriptStub } from './ScriptStub';
import { ScriptingDefinitionStub } from './ScriptingDefinitionStub';
import { RecommendationLevel } from '@/domain/RecommendationLevel';
export class CategoryCollectionStub implements ICategoryCollection {
public scripting: IScriptingDefinition = new ScriptingDefinitionStub();
@@ -36,14 +37,16 @@ export class CategoryCollectionStub implements ICategoryCollection {
}
public findCategory(categoryId: number): ICategory {
return this.getAllCategories().find(
(category) => category.id === categoryId);
return this.getAllCategories()
.find((category) => category.id === categoryId);
}
public getScriptsByLevel(): readonly IScript[] {
throw new Error('Method not implemented: getScriptsByLevel');
public getScriptsByLevel(level: RecommendationLevel): readonly IScript[] {
return this.getAllScripts()
.filter((script) => script.level !== undefined && script.level <= level);
}
public findScript(scriptId: string): IScript {
return this.getAllScripts().find((script) => scriptId === script.id);
return this.getAllScripts()
.find((script) => scriptId === script.id);
}
public getAllScripts(): ReadonlyArray<IScript> {
const scripts = [];
@@ -79,9 +82,7 @@ function getSubCategoriesRecursively(category: ICategory): ReadonlyArray<ICatego
function getScriptsRecursively(category: ICategory): ReadonlyArray<IScript> {
const categoryScripts = [];
if (category.scripts) {
for (const script of category.scripts) {
categoryScripts.push(script);
}
categoryScripts.push(...category.scripts);
}
if (category.subCategories) {
for (const subCategory of category.subCategories) {

Some files were not shown because too many files have changed in this diff Show More