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
- npm run lint:md:relative-urls - npm run lint:md:relative-urls
- npm run lint:md:consistency - npm run lint:md:consistency
fail-fast: false # So it continues with other commands if one fails
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v2 uses: actions/checkout@v2

View File

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

View File

@@ -1,5 +1,20 @@
# Changelog # 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) ## 0.10.0 (2021-03-02)
* allow functions to call other functions #53 | [7661575](https://github.com/undergroundwires/privacy.sexy/commit/7661575573c6d3e8f4bc28bfa7a124a764c72ef9) * 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) - Online version at [https://privacy.sexy](https://privacy.sexy)
- 💡 No need to run any compiled software on your computer. - 💡 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. - 💡 Single click to execute your script.
- ❗ Come back regularly to apply latest version for stronger privacy and security. - ❗ 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. - Development: `npm run serve` to compile & hot-reload for development.
- Production: `npm run build` to prepare files for distribution. - Production: `npm run build` to prepare files for distribution.
- Or run using Docker: - Or run using Docker:
1. Build: `docker build -t undergroundwires/privacy.sexy:0.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.0 undergroundwires/privacy.sexy:0.10.0` 2. Run: `docker run -it -p 8080:80 --rm --name privacy.sexy-0.10.1 undergroundwires/privacy.sexy:0.10.1`
## Architecture overview ## Architecture overview

View File

@@ -3,6 +3,14 @@
- It's mainly responsible for - It's mainly responsible for
- creating and event based [application state](#application-state) - creating and event based [application state](#application-state)
- [parsing](#parsing) and [compiling](#compiling) [application data](#application-data) - [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 ## 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) - Unit tests are defined in [`./tests`](./../tests)
- They follow same folder structure as [`./src`](./../src) - They follow same folder structure as [`./src`](./../src)
## Naming ### Naming
- Each test suite first describe the system under test - Each test suite first describe the system under test
- E.g. tests for class `Application` is categorized under `Application` - E.g. tests for class `Application` is categorized under `Application`
- Tests for specific methods are categorized under method name (if applicable) - Tests for specific methods are categorized under method name (if applicable)
- E.g. test for `run()` is categorized under `run` - 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 - Tests use act, arrange and assert (AAA) pattern when applicable
- **Arrange** - **Arrange**
@@ -23,7 +27,7 @@
- Should elicit some sort of response - Should elicit some sort of response
- Starts with comment line `// assert` - Starts with comment line `// assert`
## Stubs ### Stubs
- Stubs are defined in [`./tests/stubs`](./../tests/unit/stubs) - Stubs are defined in [`./tests/stubs`](./../tests/unit/stubs)
- They implement dummy behavior to be functional - They implement dummy behavior to be functional

81
package-lock.json generated
View File

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

View File

@@ -1,6 +1,6 @@
{ {
"name": "privacy.sexy", "name": "privacy.sexy",
"version": "0.10.0", "version": "0.10.1",
"private": true, "private": true,
"description": "Enforce privacy & security best-practices on Windows and macOS, because privacy is sexy 🍑🍆", "description": "Enforce privacy & security best-practices on Windows and macOS, because privacy is sexy 🍑🍆",
"author": "undergroundwires", "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 // Because we cannot do "T extends enum" 😞 https://github.com/microsoft/TypeScript/issues/30611
type EnumType = number | string; export type EnumType = number | string;
type EnumVariable<T extends EnumType, TEnumValue extends EnumType> = { [key in T]: TEnumValue }; export type EnumVariable<T extends EnumType, TEnumValue extends EnumType> = { [key in T]: TEnumValue };
export interface IEnumParser<TEnum> { export interface IEnumParser<TEnum> {
parseEnum(value: string, propertyName: string): TEnum; parseEnum(value: string, propertyName: string): TEnum;
@@ -41,3 +41,14 @@ export function getEnumValues<T extends EnumType, TEnumValue extends EnumType>(
return getEnumNames(enumVariable) return getEnumNames(enumVariable)
.map((level) => enumVariable[level]) as TEnumValue[]; .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 { OperatingSystem } from '@/domain/OperatingSystem';
import { ICategoryCollection } from '@/domain/ICategoryCollection'; import { ICategoryCollection } from '@/domain/ICategoryCollection';
import { EventSource } from '@/infrastructure/Events/EventSource'; import { EventSource } from '@/infrastructure/Events/EventSource';
import { assertInRange } from '@/application/Common/Enum';
type StateMachine = Map<OperatingSystem, ICategoryCollectionState>; type StateMachine = Map<OperatingSystem, ICategoryCollectionState>;
@@ -22,7 +23,7 @@ export class ApplicationContext implements IApplicationContext {
public readonly app: IApplication, public readonly app: IApplication,
initialContext: OperatingSystem) { initialContext: OperatingSystem) {
validateApp(app); validateApp(app);
validateOs(initialContext); assertInRange(initialContext, OperatingSystem);
this.states = initializeStates(app); this.states = initializeStates(app);
this.changeContext(initialContext); 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 { function initializeStates(app: IApplication): StateMachine {
const machine = new Map<OperatingSystem, ICategoryCollectionState>(); const machine = new Map<OperatingSystem, ICategoryCollectionState>();
for (const collection of app.collections) { 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 { ScriptingLanguage } from '@/domain/ScriptingLanguage';
import { ICodeBuilder } from './ICodeBuilder'; import { ICodeBuilder } from './ICodeBuilder';
import { ICodeBuilderFactory } from './ICodeBuilderFactory';
import { BatchBuilder } from './Languages/BatchBuilder'; import { BatchBuilder } from './Languages/BatchBuilder';
import { ShellBuilder } from './Languages/ShellBuilder'; import { ShellBuilder } from './Languages/ShellBuilder';
import { ICodeBuilderFactory } from './ICodeBuilderFactory';
export class CodeBuilderFactory implements ICodeBuilderFactory { export class CodeBuilderFactory extends ScriptingLanguageFactory<ICodeBuilder> implements ICodeBuilderFactory {
public create(language: ScriptingLanguage): ICodeBuilder { constructor() {
switch (language) { super();
case ScriptingLanguage.shellscript: return new ShellBuilder(); this.registerGetter(ScriptingLanguage.shellscript, () => new ShellBuilder());
case ScriptingLanguage.batchfile: return new BatchBuilder(); this.registerGetter(ScriptingLanguage.batchfile, () => new BatchBuilder());
default: throw new RangeError(`unknown language: "${ScriptingLanguage[language]}"`);
}
} }
} }

View File

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

View File

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

View File

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

View File

@@ -4,17 +4,17 @@ import { IBrowserOsDetector } from './IBrowserOsDetector';
export class BrowserOsDetector implements IBrowserOsDetector { export class BrowserOsDetector implements IBrowserOsDetector {
private readonly detectors = BrowserDetectors; private readonly detectors = BrowserDetectors;
public detect(userAgent: string): OperatingSystem { public detect(userAgent: string): OperatingSystem | undefined {
if (!userAgent) { if (!userAgent) {
return OperatingSystem.Unknown; return undefined;
} }
for (const detector of this.detectors) { for (const detector of this.detectors) {
const os = detector.detect(userAgent); const os = detector.detect(userAgent);
if (os !== OperatingSystem.Unknown) { if (os !== undefined) {
return os; 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'); throw new Error('User agent is null or undefined');
} }
if (this.existingPartsInUserAgent.some((part) => !userAgent.includes(part))) { if (this.existingPartsInUserAgent.some((part) => !userAgent.includes(part))) {
return OperatingSystem.Unknown; return undefined;
} }
if (this.notExistingPartsInUserAgent.some((part) => userAgent.includes(part))) { if (this.notExistingPartsInUserAgent.some((part) => userAgent.includes(part))) {
return OperatingSystem.Unknown; return undefined;
} }
return this.os; return this.os;
} }

View File

@@ -1,5 +1,5 @@
import { OperatingSystem } from '@/domain/OperatingSystem'; import { OperatingSystem } from '@/domain/OperatingSystem';
export interface IBrowserOsDetector { 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; return variables.process.platform;
} }
function getDesktopOsType(processPlatform: string): OperatingSystem { function getDesktopOsType(processPlatform: string): OperatingSystem | undefined {
// https://nodejs.org/api/process.html#process_process_platform // https://nodejs.org/api/process.html#process_process_platform
if (processPlatform === 'darwin') { if (processPlatform === 'darwin') {
return OperatingSystem.macOS; return OperatingSystem.macOS;
@@ -53,7 +53,7 @@ function getDesktopOsType(processPlatform: string): OperatingSystem {
} else if (processPlatform === 'linux') { } else if (processPlatform === 'linux') {
return OperatingSystem.Linux; return OperatingSystem.Linux;
} }
return OperatingSystem.Unknown; return undefined;
} }
function isDesktop(variables: IEnvironmentVariables): boolean { function isDesktop(variables: IEnvironmentVariables): boolean {

View File

@@ -1,6 +1,6 @@
import { IScriptingDefinition } from '@/domain/IScriptingDefinition'; import { IScriptingDefinition } from '@/domain/IScriptingDefinition';
import { ILanguageSyntax } from '@/domain/ScriptCode'; import { ILanguageSyntax } from '@/domain/ScriptCode';
import { FunctionData } from 'js-yaml-loader!*'; import { FunctionData } from 'js-yaml-loader!@/*';
import { IScriptCompiler } from './Compiler/IScriptCompiler'; import { IScriptCompiler } from './Compiler/IScriptCompiler';
import { ScriptCompiler } from './Compiler/ScriptCompiler'; import { ScriptCompiler } from './Compiler/ScriptCompiler';
import { ICategoryCollectionParseContext } from './ICategoryCollectionParseContext'; 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 { SharedFunction } from './SharedFunction';
import { SharedFunctionCollection } from './SharedFunctionCollection'; import { SharedFunctionCollection } from './SharedFunctionCollection';
import { ISharedFunctionCollection } from './ISharedFunctionCollection'; 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'; import { ISharedFunctionCollection } from './ISharedFunctionCollection';
export interface IFunctionCompiler { export interface IFunctionCompiler {

View File

@@ -1,9 +1,10 @@
import { ISharedFunction } from './ISharedFunction'; import { ISharedFunction } from './ISharedFunction';
export class SharedFunction implements ISharedFunction { export class SharedFunction implements ISharedFunction {
public readonly parameters: readonly string[];
constructor( constructor(
public readonly name: string, public readonly name: string,
public readonly parameters: readonly string[], parameters: readonly string[],
public readonly code: string, public readonly code: string,
public readonly revertCode: 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 { ICompiledCode } from './ICompiledCode';
import { ISharedFunctionCollection } from '../Function/ISharedFunctionCollection'; import { ISharedFunctionCollection } from '../Function/ISharedFunctionCollection';
import { IFunctionCallCompiler } from './IFunctionCallCompiler'; 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 { ICompiledCode } from './ICompiledCode';
import { ISharedFunctionCollection } from '../Function/ISharedFunctionCollection'; import { ISharedFunctionCollection } from '../Function/ISharedFunctionCollection';

View File

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

View File

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

View File

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

View File

@@ -1248,24 +1248,51 @@ actions:
- -
name: Disable ad customization with Advertising ID name: Disable ad customization with Advertising ID
recommend: standard recommend: standard
docs: https://docs.microsoft.com/en-us/windows/privacy/manage-connections-from-windows-operating-system-components-to-microsoft-services#181-general
code: |- code: |-
reg add "HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\AdvertisingInfo" /v "Enabled" /t REG_DWORD /d 0 /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 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
- -
name: Disable targeted tips category: Disable cloud-based tips and ads
children:
-
name: Disable Windows Tips
recommend: standard recommend: standard
code: |- docs: https://admx.help/?Category=Windows_10_2016&Policy=Microsoft.Policies.CloudContent::DisableSoftLanding
reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\CloudContent" /v "DisableSoftLanding" /t REG_DWORD /d 1 /f 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 revertCode: reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\CloudContent" /v "DisableSoftLanding" /t REG_DWORD /d "0" /f
reg add "HKLM\Software\Policies\Microsoft\Windows\CloudContent" /v "DisableWindowsConsumerFeatures" /t "REG_DWORD" /d "1" /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: Turn Off Suggested Content in Settings app name: Turn Off Suggested Content in Settings app
recommend: standard recommend: standard
docs: https://www.tenforums.com/tutorials/100541-turn-off-suggested-content-settings-app-windows-10-a.html docs: https://www.tenforums.com/tutorials/100541-turn-off-suggested-content-settings-app-windows-10-a.html
code: |- 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-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-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-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) category: Disable biometrics (breaks fingerprinting/facial login)
children: children:
@@ -1788,7 +1815,7 @@ actions:
category: Chromium Edge settings category: Chromium Edge settings
children: 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 recommend: standard
docs: docs:
- https://admx.help/?Category=EdgeChromium&Policy=Microsoft.Policies.Edge::MetricsReportingEnabled - 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 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 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 recommend: standard
docs: docs:
- https://admx.help/?Category=EdgeChromium&Policy=Microsoft.Policies.Edge::SendSiteInfoToImproveServices - https://admx.help/?Category=EdgeChromium&Policy=Microsoft.Policies.Edge::SendSiteInfoToImproveServices
@@ -1894,7 +1921,7 @@ actions:
category: Chrome cleanup category: Chrome cleanup
children: 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 recommend: standard
docs: docs:
- https://www.chromium.org/administrators/policy-list-3#ChromeCleanupReportingEnabled - 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 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 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 recommend: standard
docs: docs:
- https://www.chromium.org/administrators/policy-list-3#ChromeCleanupEnabled - 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 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 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 recommend: standard
docs: https://www.stigviewer.com/stig/google_chrome_v23_windows/2013-01-11/finding/V-35780 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 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 "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 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 recommend: standard
code: sc stop "WMPNetworkSvc" & sc config "WMPNetworkSvc" start=disabled code: sc stop "WMPNetworkSvc" & sc config "WMPNetworkSvc" start=disabled
- -
@@ -2986,7 +3013,7 @@ actions:
packageName: Microsoft.Messaging packageName: Microsoft.Messaging
- -
name: Mixed Reality Portal app 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: call:
function: UninstallStoreApp function: UninstallStoreApp
parameters: parameters:
@@ -3004,7 +3031,7 @@ actions:
packageName: Microsoft.MicrosoftOfficeHub packageName: Microsoft.MicrosoftOfficeHub
- -
name: OneNote app 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: call:
function: UninstallStoreApp function: UninstallStoreApp
parameters: parameters:
@@ -3511,7 +3538,7 @@ actions:
parameters: parameters:
packageName: Microsoft.Windows.CapturePicker 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 recommend: strict
call: call:
function: UninstallSystemApp 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 { IEntity } from '../infrastructure/Entity/IEntity';
import { ICategory } from './ICategory'; import { ICategory } from './ICategory';
import { IScript } from './IScript'; import { IScript } from './IScript';
@@ -21,7 +21,7 @@ export class CategoryCollection implements ICategoryCollection {
throw new Error('undefined scripting definition'); throw new Error('undefined scripting definition');
} }
this.queryable = makeQueryable(actions); this.queryable = makeQueryable(actions);
ensureValidOs(os); assertInRange(os, OperatingSystem);
ensureValid(this.queryable); ensureValid(this.queryable);
ensureNoDuplicates(this.queryable.allCategories); ensureNoDuplicates(this.queryable.allCategories);
ensureNoDuplicates(this.queryable.allScripts); 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>>) { function ensureNoDuplicates<TKey>(entities: ReadonlyArray<IEntity<TKey>>) {
const totalOccurrencesById = new Map<TKey, number>(); const totalOccurrencesById = new Map<TKey, number>();
for (const entity of entities) { for (const entity of entities) {

View File

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

View File

@@ -1,5 +1,6 @@
import { IProjectInformation } from './IProjectInformation'; import { IProjectInformation } from './IProjectInformation';
import { OperatingSystem } from './OperatingSystem'; import { OperatingSystem } from './OperatingSystem';
import { assertInRange } from '@/application/Common/Enum';
export class ProjectInformation implements IProjectInformation { export class ProjectInformation implements IProjectInformation {
public readonly repositoryWebUrl: string; public readonly repositoryWebUrl: string;
@@ -42,6 +43,7 @@ function getWebUrl(gitUrl: string) {
} }
function getFileName(os: OperatingSystem, version: string): string { function getFileName(os: OperatingSystem, version: string): string {
assertInRange(os, OperatingSystem);
switch (os) { switch (os) {
case OperatingSystem.Linux: case OperatingSystem.Linux:
return `privacy.sexy-${version}.AppImage`; return `privacy.sexy-${version}.AppImage`;
@@ -50,6 +52,6 @@ function getFileName(os: OperatingSystem, version: string): string {
case OperatingSystem.Windows: case OperatingSystem.Windows:
return `privacy.sexy-Setup-${version}.exe`; return `privacy.sexy-Setup-${version}.exe`;
default: 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) { syntax: ILanguageSyntax) {
if (!syntax) { throw new Error('undefined syntax'); } if (!syntax) { throw new Error('undefined syntax'); }
validateCode(execute, syntax); validateCode(execute, syntax);
if (revert) { validateRevertCode(revert, execute, syntax);
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}`);
}
}
} }
} }
@@ -25,6 +16,20 @@ export interface ILanguageSyntax {
readonly commonCodeParts: string[]; 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 { function validateCode(code: string, syntax: ILanguageSyntax): void {
if (!code || code.length === 0) { if (!code || code.length === 0) {
throw new Error(`code is empty or undefined`); 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 child_process from 'child_process';
import { OperatingSystem } from '@/domain/OperatingSystem'; import { OperatingSystem } from '@/domain/OperatingSystem';
export async function runCodeAsync( export class CodeRunner {
code: string, folderName: string, fileExtension: string, constructor(
node = getNodeJs(), environment = Environment.CurrentEnvironment): Promise<void> { private readonly node = getNodeJs(),
const dir = node.path.join(node.os.tmpdir(), folderName); private readonly environment = Environment.CurrentEnvironment) {
await node.fs.promises.mkdir(dir, {recursive: true}); }
const filePath = node.path.join(dir, `run.${fileExtension}`); public async runCodeAsync(code: string, folderName: string, fileExtension: string): Promise<void> {
await node.fs.promises.writeFile(filePath, code); const dir = this.node.path.join(this.node.os.tmpdir(), folderName);
await node.fs.promises.chmod(filePath, '755'); await this.node.fs.promises.mkdir(dir, {recursive: true});
const command = getExecuteCommand(filePath, environment); const filePath = this.node.path.join(dir, `run.${fileExtension}`);
node.child_process.exec(command); 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 { function getExecuteCommand(scriptPath: string, environment: Environment): string {

View File

@@ -4,7 +4,7 @@
// This script is running through entire life of the application. // 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. // 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 { createProtocol } from 'vue-cli-plugin-electron-builder/lib';
import installExtension, { VUEJS_DEVTOOLS } from 'electron-devtools-installer'; import installExtension, { VUEJS_DEVTOOLS } from 'electron-devtools-installer';
import path from 'path'; import path from 'path';
@@ -34,9 +34,10 @@ if (!process.env.IS_TEST) {
function createWindow() { function createWindow() {
// Create the browser window. // Create the browser window.
const size = getWindowSize(1350, 955);
win = new BrowserWindow({ win = new BrowserWindow({
width: 1350, width: size.width,
height: 955, height: size.height,
webPreferences: { webPreferences: {
contextIsolation: false, // To reach node https://github.com/nklayman/vue-cli-plugin-electron-builder/issues/1285 contextIsolation: false, // To reach node https://github.com/nklayman/vue-cli-plugin-electron-builder/issues/1285
// Use pluginOptions.nodeIntegration, leave this alone // Use pluginOptions.nodeIntegration, leave this alone
@@ -143,3 +144,12 @@ function loadUrlWithNodeWorkaround(window: BrowserWindow, url: string) {
window.loadURL(url); window.loadURL(url);
}, 10); }, 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 { IApplicationCode } from '@/application/Context/State/Code/IApplicationCode';
import { IScriptingDefinition } from '@/domain/IScriptingDefinition'; import { IScriptingDefinition } from '@/domain/IScriptingDefinition';
import { OperatingSystem } from '@/domain/OperatingSystem'; import { OperatingSystem } from '@/domain/OperatingSystem';
import { runCodeAsync } from '@/infrastructure/CodeRunner'; import { CodeRunner } from '@/infrastructure/CodeRunner';
import { IApplicationContext } from '@/application/Context/IApplicationContext'; import { IApplicationContext } from '@/application/Context/IApplicationContext';
@Component({ @Component({
@@ -116,7 +116,8 @@ function buildFileName(scripting: IScriptingDefinition) {
} }
async function executeCodeAsync(context: IApplicationContext) { async function executeCodeAsync(context: IApplicationContext) {
await runCodeAsync( const runner = new CodeRunner();
await runner.runCodeAsync(
/*code*/ context.state.code.current, /*code*/ context.state.code.current,
/*appName*/ context.app.info.name, /*appName*/ context.app.info.name,
/*fileExtension*/ context.state.collection.scripting.fileExtension, /*fileExtension*/ context.state.collection.scripting.fileExtension,

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"> <div class="part">
<SelectableOption <SelectableOption
label="None" label="None"
:enabled="this.currentSelection == SelectionState.None" :enabled="this.currentSelection == SelectionType.None"
@click="selectAsync(SelectionState.None)" @click="selectType(SelectionType.None)"
v-tooltip=" 'Deselect all selected scripts.<br/>' + v-tooltip=" 'Deselect all selected scripts.<br/>' +
'💡 Good start to dive deeper into tweaks and select only what you want.'" '💡 Good start to dive deeper into tweaks and select only what you want.'"
/> />
@@ -15,8 +15,8 @@
<div class="part"> <div class="part">
<SelectableOption <SelectableOption
label="Standard" label="Standard"
:enabled="this.currentSelection == SelectionState.Standard" :enabled="this.currentSelection == SelectionType.Standard"
@click="selectAsync(SelectionState.Standard)" @click="selectType(SelectionType.Standard)"
v-tooltip=" '🛡️ Balanced for privacy and functionality.<br/>' + v-tooltip=" '🛡️ Balanced for privacy and functionality.<br/>' +
'OS and applications will function normally.<br/>' + 'OS and applications will function normally.<br/>' +
'💡 Recommended for everyone'" '💡 Recommended for everyone'"
@@ -26,8 +26,8 @@
<div class="part"> <div class="part">
<SelectableOption <SelectableOption
label="Strict" label="Strict"
:enabled="this.currentSelection == SelectionState.Strict" :enabled="this.currentSelection == SelectionType.Strict"
@click="selectAsync(SelectionState.Strict)" @click="selectType(SelectionType.Strict)"
v-tooltip=" '🚫 Stronger privacy, disables risky functions that may leak your data.<br/>' + 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/>' + '⚠️ 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'" '💡 Recommended for daily users that prefers more privacy over non-essential functions'"
@@ -37,8 +37,8 @@
<div class="part"> <div class="part">
<SelectableOption <SelectableOption
label="All" label="All"
:enabled="this.currentSelection == SelectionState.All" :enabled="this.currentSelection == SelectionType.All"
@click="selectAsync(SelectionState.All)" @click="selectType(SelectionType.All)"
v-tooltip=" '🔒 Strongest privacy, disabling any functionality that may leak your data.<br/>' + v-tooltip=" '🔒 Strongest privacy, disabling any functionality that may leak your data.<br/>' +
'🛑 Not designed for daily users, it will break important functionalities.<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'" '💡 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 { Component } from 'vue-property-decorator';
import { StatefulVue } from '@/presentation/components/Shared/StatefulVue'; import { StatefulVue } from '@/presentation/components/Shared/StatefulVue';
import SelectableOption from './SelectableOption.vue'; 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 { ICategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState';
import { SelectionType, SelectionTypeHandler } from './SelectionTypeHandler';
enum SelectionState {
Standard,
Strict,
All,
None,
Custom,
}
@Component({ @Component({
components: { components: {
SelectableOption, SelectableOption,
}, },
}) })
export default class TheSelector extends StatefulVue { export default class TheSelector extends StatefulVue {
public SelectionState = SelectionState; public SelectionType = SelectionType;
public currentSelection = SelectionState.None; public currentSelection = SelectionType.None;
private selectionTypeHandler: SelectionTypeHandler;
public async selectAsync(type: SelectionState): Promise<void> { public async selectType(type: SelectionType) {
if (this.currentSelection === type) { if (this.currentSelection === type) {
return; return;
} }
const context = await this.getCurrentContextAsync(); this.selectionTypeHandler.selectType(type);
selectType(context.state, type);
} }
protected handleCollectionState(newState: ICategoryCollectionState, oldState: ICategoryCollectionState): void { protected handleCollectionState(newState: ICategoryCollectionState, oldState: ICategoryCollectionState): void {
this.updateSelections(newState); this.selectionTypeHandler = new SelectionTypeHandler(newState);
newState.selection.changed.on(() => this.updateSelections(newState)); this.updateSelections();
newState.selection.changed.on(() => this.updateSelections());
if (oldState) { if (oldState) {
oldState.selection.changed.on(() => this.updateSelections(oldState)); oldState.selection.changed.on(() => this.updateSelections());
} }
} }
private updateSelections(state: ICategoryCollectionState) { private updateSelections() {
this.currentSelection = getCurrentSelectionState(state); 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> </script>
<style scoped lang="scss"> <style scoped lang="scss">

View File

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

View File

@@ -1,7 +1,7 @@
import 'mocha'; import 'mocha';
import { expect } from 'chai'; import { expect } from 'chai';
import { ApplicationFactory, ApplicationGetter } from '@/application/ApplicationFactory'; import { ApplicationFactory, ApplicationGetter } from '@/application/ApplicationFactory';
import { ApplicationStub } from '../stubs/ApplicationStub'; import { ApplicationStub } from '@tests/unit/stubs/ApplicationStub';
describe('ApplicationFactory', () => { describe('ApplicationFactory', () => {
describe('ctor', () => { 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 'mocha';
import { expect } from 'chai'; 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('Enum', () => {
describe('createEnumParser', () => { describe('createEnumParser', () => {
@@ -78,7 +80,7 @@ describe('Enum', () => {
// act // act
const actual = getEnumNames(TestEnum); const actual = getEnumNames(TestEnum);
// assert // assert
expect(expected.sort()).to.deep.equal(actual.sort()); expect(scrambledEqual(expected, actual));
}); });
}); });
describe('getEnumValues', () => { describe('getEnumValues', () => {
@@ -89,7 +91,19 @@ describe('Enum', () => {
// act // act
const actual = getEnumValues(TestEnum); const actual = getEnumValues(TestEnum);
// assert // 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 { ICategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState';
import { IApplicationContext, IApplicationContextChangedEvent } from '@/application/Context/IApplicationContext'; import { IApplicationContext, IApplicationContextChangedEvent } from '@/application/Context/IApplicationContext';
import { IApplication } from '@/domain/IApplication'; import { IApplication } from '@/domain/IApplication';
import { ApplicationStub } from '../../stubs/ApplicationStub'; import { ApplicationStub } from '@tests/unit/stubs/ApplicationStub';
import { CategoryCollectionStub } from '../../stubs/CategoryCollectionStub'; import { CategoryCollectionStub } from '@tests/unit/stubs/CategoryCollectionStub';
import { EnumRangeTestRunner } from '@tests/unit/application/Common/EnumRangeTestRunner';
describe('ApplicationContext', () => { describe('ApplicationContext', () => {
describe('changeContext', () => { describe('changeContext', () => {
@@ -180,40 +181,15 @@ describe('ApplicationContext', () => {
expect(actual).to.deep.equal(expected); expect(actual).to.deep.equal(expected);
}); });
describe('throws when OS is invalid', () => { 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 // act
for (const testCase of testCases) { const act = (os: OperatingSystem) => new ObservableApplicationContextFactory()
it(testCase.name, () => { .withInitialOs(os)
const act = () =>
new ObservableApplicationContextFactory()
.withInitialOs(testCase.os)
.construct(); .construct();
// assert // assert
expect(act).to.throw(testCase.expectedError); new EnumRangeTestRunner(act)
}); .testOutOfRangeThrows()
} .testUndefinedValueThrows()
.testInvalidValueThrows(OperatingSystem.Android, 'os "Android" is not defined in application');
}); });
}); });
describe('app', () => { describe('app', () => {

View File

@@ -2,12 +2,12 @@ import 'mocha';
import { expect } from 'chai'; import { expect } from 'chai';
import { OperatingSystem } from '@/domain/OperatingSystem'; import { OperatingSystem } from '@/domain/OperatingSystem';
import { ICategoryCollection } from '@/domain/ICategoryCollection'; 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 { buildContextAsync } from '@/application/Context/ApplicationContextFactory';
import { IApplicationFactory } from '@/application/IApplicationFactory'; import { IApplicationFactory } from '@/application/IApplicationFactory';
import { IApplication } from '@/domain/IApplication'; 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('ApplicationContextFactory', () => {
describe('buildContextAsync', () => { describe('buildContextAsync', () => {

View File

@@ -5,9 +5,9 @@ import { ApplicationCode } from '@/application/Context/State/Code/ApplicationCod
import { CategoryCollectionState } from '@/application/Context/State/CategoryCollectionState'; import { CategoryCollectionState } from '@/application/Context/State/CategoryCollectionState';
import { OperatingSystem } from '@/domain/OperatingSystem'; import { OperatingSystem } from '@/domain/OperatingSystem';
import { IScript } from '@/domain/IScript'; import { IScript } from '@/domain/IScript';
import { ScriptStub } from '../../../stubs/ScriptStub'; import { ScriptStub } from '@tests/unit/stubs/ScriptStub';
import { CategoryStub } from '../../../stubs/CategoryStub'; import { CategoryStub } from '@tests/unit/stubs/CategoryStub';
import { CategoryCollectionStub } from '../../../stubs/CategoryCollectionStub'; import { CategoryCollectionStub } from '@tests/unit/stubs/CategoryCollectionStub';
describe('CategoryCollectionState', () => { describe('CategoryCollectionState', () => {
describe('code', () => { describe('code', () => {
@@ -55,7 +55,7 @@ describe('CategoryCollectionState', () => {
const collection = new CategoryCollectionStub(); const collection = new CategoryCollectionStub();
const sut = new CategoryCollectionState(collection); const sut = new CategoryCollectionState(collection);
// act // act
const actual = sut.selection.totalSelected; const actual = sut.selection.selectedScripts.length;
// assert // assert
expect(actual).to.equal(0); expect(actual).to.equal(0);
}); });
@@ -68,7 +68,7 @@ describe('CategoryCollectionState', () => {
// act // act
sut.selection.selectAll(); sut.selection.selectAll();
// assert // 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); 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 { ICodePosition } from '@/application/Context/State/Code/Position/ICodePosition';
import { IScriptingDefinition } from '@/domain/IScriptingDefinition'; import { IScriptingDefinition } from '@/domain/IScriptingDefinition';
import { IUserScript } from '@/application/Context/State/Code/Generation/IUserScript'; import { IUserScript } from '@/application/Context/State/Code/Generation/IUserScript';
import { ScriptingDefinitionStub } from '../../../../stubs/ScriptingDefinitionStub'; import { ScriptingDefinitionStub } from '@tests/unit/stubs/ScriptingDefinitionStub';
import { CategoryStub } from '../../../../stubs/CategoryStub'; import { CategoryStub } from '@tests/unit/stubs/CategoryStub';
import { ScriptStub } from '../../../../stubs/ScriptStub'; import { ScriptStub } from '@tests/unit/stubs/ScriptStub';
import { CategoryCollectionStub } from '../../../../stubs/CategoryCollectionStub'; import { CategoryCollectionStub } from '@tests/unit/stubs/CategoryCollectionStub';
describe('ApplicationCode', () => { describe('ApplicationCode', () => {
describe('ctor', () => { 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 { SelectedScript } from '@/application/Context/State/Selection/SelectedScript';
import { ICodePosition } from '@/application/Context/State/Code/Position/ICodePosition'; import { ICodePosition } from '@/application/Context/State/Code/Position/ICodePosition';
import { CodePosition } from '@/application/Context/State/Code/Position/CodePosition'; import { CodePosition } from '@/application/Context/State/Code/Position/CodePosition';
import { SelectedScriptStub } from '../../../../../stubs/SelectedScriptStub'; import { SelectedScriptStub } from '@tests/unit/stubs/SelectedScriptStub';
import { ScriptStub } from '../../../../../stubs/ScriptStub'; import { ScriptStub } from '@tests/unit/stubs/ScriptStub';
describe('CodeChangedEvent', () => { describe('CodeChangedEvent', () => {
describe('ctor', () => { describe('ctor', () => {

View File

@@ -1,36 +1,13 @@
import 'mocha';
import { expect } from 'chai';
import { ScriptingLanguage } from '@/domain/ScriptingLanguage'; import { ScriptingLanguage } from '@/domain/ScriptingLanguage';
import { ShellBuilder } from '@/application/Context/State/Code/Generation/Languages/ShellBuilder'; import { ShellBuilder } from '@/application/Context/State/Code/Generation/Languages/ShellBuilder';
import { BatchBuilder } from '@/application/Context/State/Code/Generation/Languages/BatchBuilder'; import { BatchBuilder } from '@/application/Context/State/Code/Generation/Languages/BatchBuilder';
import { CodeBuilderFactory } from '@/application/Context/State/Code/Generation/CodeBuilderFactory'; import { CodeBuilderFactory } from '@/application/Context/State/Code/Generation/CodeBuilderFactory';
import { ScriptingLanguageFactoryTestRunner } from '@tests/unit/application/Common/ScriptingLanguage/ScriptingLanguageFactoryTestRunner';
describe('CodeBuilderFactory', () => { 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 sut = new CodeBuilderFactory();
const result = sut.create(testCase.language); const runner = new ScriptingLanguageFactoryTestRunner()
// assert .expect(ScriptingLanguage.shellscript, ShellBuilder)
expect(result).to.be.instanceOf(testCase.expected, .expect(ScriptingLanguage.batchfile, BatchBuilder);
`Actual was: ${result.constructor.name}`); runner.testCreateMethod(sut);
});
}
});
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]}"`);
});
});
}); });

View File

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

View File

@@ -1,6 +1,6 @@
import { CodePosition } from '@/application/Context/State/Code/Position/CodePosition';
import 'mocha'; import 'mocha';
import { expect } from 'chai'; import { expect } from 'chai';
import { CodePosition } from '@/application/Context/State/Code/Position/CodePosition';
describe('CodePosition', () => { describe('CodePosition', () => {
describe('ctor', () => { 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 'mocha';
import { expect } from 'chai'; 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('FilterResult', () => {
describe('hasAnyMatches', () => { describe('hasAnyMatches', () => {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -3,7 +3,7 @@ import { expect } from 'chai';
import { IExpression } from '@/application/Parser/Script/Compiler/Expressions/Expression/IExpression'; import { IExpression } from '@/application/Parser/Script/Compiler/Expressions/Expression/IExpression';
import { IExpressionParser } from '@/application/Parser/Script/Compiler/Expressions/Parser/IExpressionParser'; import { IExpressionParser } from '@/application/Parser/Script/Compiler/Expressions/Parser/IExpressionParser';
import { CompositeExpressionParser } from '@/application/Parser/Script/Compiler/Expressions/Parser/CompositeExpressionParser'; 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('CompositeExpressionParser', () => {
describe('ctor', () => { describe('ctor', () => {

View File

@@ -1,11 +1,11 @@
import 'mocha'; import 'mocha';
import { expect } from 'chai'; import { expect } from 'chai';
import { ISharedFunction } from '@/application/Parser/Script/Compiler/Function/ISharedFunction'; 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 { IFunctionCallCompiler } from '@/application/Parser/Script/Compiler/FunctionCall/IFunctionCallCompiler';
import { FunctionCompiler } from '@/application/Parser/Script/Compiler/Function/FunctionCompiler'; import { FunctionCompiler } from '@/application/Parser/Script/Compiler/Function/FunctionCompiler';
import { FunctionCallCompilerStub } from '../../../../../stubs/FunctionCallCompilerStub'; import { FunctionCallCompilerStub } from '@tests/unit/stubs/FunctionCallCompilerStub';
import { FunctionDataStub } from '../../../../../stubs/FunctionDataStub'; import { FunctionDataStub } from '@tests/unit/stubs/FunctionDataStub';
describe('FunctionsCompiler', () => { describe('FunctionsCompiler', () => {
describe('compileFunctions', () => { describe('compileFunctions', () => {

View File

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

View File

@@ -1,12 +1,12 @@
import 'mocha'; import 'mocha';
import { expect } from 'chai'; 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 { FunctionCallCompiler } from '@/application/Parser/Script/Compiler/FunctionCall/FunctionCallCompiler';
import { ISharedFunctionCollection } from '@/application/Parser/Script/Compiler/Function/ISharedFunctionCollection'; import { ISharedFunctionCollection } from '@/application/Parser/Script/Compiler/Function/ISharedFunctionCollection';
import { IExpressionsCompiler } from '@/application/Parser/Script/Compiler/Expressions/IExpressionsCompiler'; import { IExpressionsCompiler } from '@/application/Parser/Script/Compiler/Expressions/IExpressionsCompiler';
import { ExpressionsCompilerStub } from '../../../../../stubs/ExpressionsCompilerStub'; import { ExpressionsCompilerStub } from '@tests/unit/stubs/ExpressionsCompilerStub';
import { SharedFunctionCollectionStub } from '../../../../../stubs/SharedFunctionCollectionStub'; import { SharedFunctionCollectionStub } from '@tests/unit/stubs/SharedFunctionCollectionStub';
import { SharedFunctionStub } from '../../../../../stubs/SharedFunctionStub'; import { SharedFunctionStub } from '@tests/unit/stubs/SharedFunctionStub';
describe('FunctionCallCompiler', () => { describe('FunctionCallCompiler', () => {
describe('compileCall', () => { describe('compileCall', () => {

View File

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

View File

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

View File

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

View File

@@ -1,38 +1,14 @@
import 'mocha'; import 'mocha';
import { expect } from 'chai';
import { SyntaxFactory } from '@/application/Parser/Script/Syntax/SyntaxFactory'; import { SyntaxFactory } from '@/application/Parser/Script/Syntax/SyntaxFactory';
import { ScriptingLanguage } from '@/domain/ScriptingLanguage'; import { ScriptingLanguage } from '@/domain/ScriptingLanguage';
import { ShellScriptSyntax } from '@/application/Parser/Script/Syntax/ShellScriptSyntax'; import { ShellScriptSyntax } from '@/application/Parser/Script/Syntax/ShellScriptSyntax';
import { BatchFileSyntax } from '@/application/Parser/Script/Syntax/BatchFileSyntax'; import { BatchFileSyntax } from '@/application/Parser/Script/Syntax/BatchFileSyntax';
import { ScriptingLanguageFactoryTestRunner } from '@tests/unit/application/Common/ScriptingLanguage/ScriptingLanguageFactoryTestRunner';
describe('SyntaxFactory', () => { 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 sut = new SyntaxFactory();
const result = sut.create(testCase.language); const runner = new ScriptingLanguageFactoryTestRunner()
// assert .expect(ScriptingLanguage.shellscript, ShellScriptSyntax)
expect(result).to.be.instanceOf(testCase.expected, .expect(ScriptingLanguage.batchfile, BatchFileSyntax);
`Actual was: ${result.constructor.name}`); runner.testCreateMethod(sut);
});
}
});
});
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]}"`);
});
});
}); });

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -2,6 +2,7 @@ import 'mocha';
import { expect } from 'chai'; import { expect } from 'chai';
import { ProjectInformation } from '@/domain/ProjectInformation'; import { ProjectInformation } from '@/domain/ProjectInformation';
import { OperatingSystem } from '@/domain/OperatingSystem'; import { OperatingSystem } from '@/domain/OperatingSystem';
import { EnumRangeTestRunner } from '@tests/unit/application/Common/EnumRangeTestRunner';
describe('ProjectInformation', () => { describe('ProjectInformation', () => {
it('sets name as expected', () => { it('sets name as expected', () => {
@@ -115,14 +116,16 @@ describe('ProjectInformation', () => {
// assert // assert
expect(actual).to.equal(expected); expect(actual).to.equal(expected);
}); });
it('throws when OS is unknown', () => { describe('throws when os is invalid', () => {
// arrange // arrange
const sut = new ProjectInformation('name', 'version', 'repositoryUrl', 'homepage'); const sut = new ProjectInformation('name', 'version', 'repositoryUrl', 'homepage');
const os = OperatingSystem.Unknown;
// act // act
const act = () => sut.getDownloadUrl(os); const act = (os: OperatingSystem) => sut.getDownloadUrl(os);
// assert // 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 'mocha';
import { expect } from 'chai'; import { expect } from 'chai';
import { getEnumValues } from '@/application/Common/Enum';
import { Script } from '@/domain/Script'; import { Script } from '@/domain/Script';
import { RecommendationLevel } from '@/domain/RecommendationLevel'; import { RecommendationLevel } from '@/domain/RecommendationLevel';
import { IScriptCode } from '@/domain/IScriptCode'; import { IScriptCode } from '@/domain/IScriptCode';
import { ScriptCodeStub } from '../stubs/ScriptCodeStub'; import { ScriptCodeStub } from '@tests/unit/stubs/ScriptCodeStub';
describe('Script', () => { describe('Script', () => {
describe('ctor', () => { describe('ctor', () => {

View File

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

View File

@@ -1,28 +1,24 @@
import { AsyncLazy } from '@/infrastructure/Threading/AsyncLazy';
import 'mocha'; import 'mocha';
import { expect } from 'chai'; import { expect } from 'chai';
import { AsyncLazy } from '@/infrastructure/Threading/AsyncLazy';
describe('AsyncLazy', () => { describe('AsyncLazy', () => {
it('returns value from lambda', async () => { it('returns value from lambda', async () => {
// arrange // arrange
const expected = 'test'; const expected = 'test';
const lambda = () => Promise.resolve(expected); const lambda = () => Promise.resolve(expected);
const sut = new AsyncLazy(lambda); const sut = new AsyncLazy(lambda);
// act // act
const actual = await sut.getValueAsync(); const actual = await sut.getValueAsync();
// assert // assert
expect(actual).to.equal(expected); expect(actual).to.equal(expected);
}); });
describe('when running multiple times', () => { describe('when running multiple times', () => {
// arrange
let totalExecuted: number = 0; let totalExecuted: number = 0;
beforeEach(() => totalExecuted = 0); beforeEach(() => totalExecuted = 0);
it('when running sync', async () => { it('when running sync', async () => {
// act
const sut = new AsyncLazy(() => { const sut = new AsyncLazy(() => {
totalExecuted++; totalExecuted++;
return Promise.resolve(totalExecuted); return Promise.resolve(totalExecuted);
@@ -31,11 +27,12 @@ describe('AsyncLazy', () => {
for (let i = 0; i < 5; i++) { for (let i = 0; i < 5; i++) {
results.push(await sut.getValueAsync()); results.push(await sut.getValueAsync());
} }
// assert
expect(totalExecuted).to.equal(1); expect(totalExecuted).to.equal(1);
expect(results).to.deep.equal([1, 1, 1, 1, 1]); expect(results).to.deep.equal([1, 1, 1, 1, 1]);
}); });
it('when running long-running task in parallel', async () => { it('when running long-running task in parallel', async () => {
// act
const sleepAsync = (time: number) => new Promise(((resolve) => setTimeout(resolve, time))); const sleepAsync = (time: number) => new Promise(((resolve) => setTimeout(resolve, time)));
const sut = new AsyncLazy(async () => { const sut = new AsyncLazy(async () => {
await sleepAsync(100); await sleepAsync(100);
@@ -48,6 +45,7 @@ describe('AsyncLazy', () => {
sut.getValueAsync(), sut.getValueAsync(),
sut.getValueAsync(), sut.getValueAsync(),
sut.getValueAsync()]); sut.getValueAsync()]);
// assert
expect(totalExecuted).to.equal(1); expect(totalExecuted).to.equal(1);
expect(results).to.deep.equal([1, 1, 1, 1, 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 'mocha';
import { expect } from 'chai'; 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('CodeRunner', () => {
describe('runCodeAsync', () => { describe('runCodeAsync', () => {
@@ -127,7 +127,8 @@ class TestContext {
private env = mockEnvironment(OperatingSystem.Windows); private env = mockEnvironment(OperatingSystem.Windows);
public async runCodeAsync(): Promise<void> { 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) { public withOs(os: OperatingSystem) {
this.env = mockEnvironment(os); 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 { EventHandler, IEventSource, IEventSubscription } from '@/infrastructure/Events/IEventSource';
import { EventSource } from '@/infrastructure/Events/EventSource'; import { EventSource } from '@/infrastructure/Events/EventSource';
import { expect } from 'chai';
import 'mocha';
describe('EventSource', () => { describe('EventSource', () => {
class ObserverMock { class ObserverMock {
@@ -42,7 +42,6 @@ describe('EventSource', () => {
expect(observer.onReceiveCalls).to.have.lengthOf(0); expect(observer.onReceiveCalls).to.have.lengthOf(0);
}); });
}); });
describe('multiple observers', () => { describe('multiple observers', () => {
// arrange // arrange
let observers: ObserverMock[]; let observers: ObserverMock[];

View File

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

View File

@@ -1,6 +1,7 @@
import { NumericEntityStub } from './../stubs/NumericEntityStub'; import 'mocha';
import { InMemoryRepository } from '@/infrastructure/Repository/InMemoryRepository';
import { expect } from 'chai'; import { expect } from 'chai';
import { NumericEntityStub } from '@tests/unit/stubs/NumericEntityStub';
import { InMemoryRepository } from '@/infrastructure/Repository/InMemoryRepository';
describe('InMemoryRepository', () => { describe('InMemoryRepository', () => {
describe('exists', () => { 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 { INode, NodeType } from '@/presentation/components/Scripts/ScriptsTree/SelectableTree/Node/INode';
import { IScript } from '@/domain/IScript'; import { IScript } from '@/domain/IScript';
import { ICategory } from '@/domain/ICategory'; import { ICategory } from '@/domain/ICategory';
import { CategoryStub } from '../../../../stubs/CategoryStub'; import { CategoryStub } from '@tests/unit/stubs/CategoryStub';
import { ScriptStub } from '../../../../stubs/ScriptStub'; import { ScriptStub } from '@tests/unit/stubs/ScriptStub';
import { CategoryCollectionStub } from '../../../../stubs/CategoryCollectionStub'; import { CategoryCollectionStub } from '@tests/unit/stubs/CategoryCollectionStub';
describe('ScriptNodeParser', () => { describe('ScriptNodeParser', () => {
it('can convert script id and back', () => { 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 { getCategoryNodeId } from '@/presentation/components/Scripts/ScriptsTree/ScriptNodeParser';
import { SelectedScript } from '@/application/Context/State/Selection/SelectedScript'; import { SelectedScript } from '@/application/Context/State/Selection/SelectedScript';
import { UserSelection } from '@/application/Context/State/Selection/UserSelection'; import { UserSelection } from '@/application/Context/State/Selection/UserSelection';
import { CategoryStub } from '../../../../../../../stubs/CategoryStub'; import { CategoryStub } from '@tests/unit/stubs/CategoryStub';
import { CategoryCollectionStub } from '../../../../../../../stubs/CategoryCollectionStub'; import { CategoryCollectionStub } from '@tests/unit/stubs/CategoryCollectionStub';
import { ScriptStub } from '../../../../../../../stubs/ScriptStub'; import { ScriptStub } from '@tests/unit/stubs/ScriptStub';
describe('CategoryReverter', () => { describe('CategoryReverter', () => {
describe('getState', () => { describe('getState', () => {

View File

@@ -6,9 +6,9 @@ import {
import { ScriptReverter } from '@/presentation/components/Scripts/ScriptsTree/SelectableTree/Node/Reverter/ScriptReverter'; import { ScriptReverter } from '@/presentation/components/Scripts/ScriptsTree/SelectableTree/Node/Reverter/ScriptReverter';
import { CategoryReverter } from '@/presentation/components/Scripts/ScriptsTree/SelectableTree/Node/Reverter/CategoryReverter'; import { CategoryReverter } from '@/presentation/components/Scripts/ScriptsTree/SelectableTree/Node/Reverter/CategoryReverter';
import { getScriptNodeId, getCategoryNodeId } from '@/presentation/components/Scripts/ScriptsTree/ScriptNodeParser'; import { getScriptNodeId, getCategoryNodeId } from '@/presentation/components/Scripts/ScriptsTree/ScriptNodeParser';
import { CategoryCollectionStub } from '../../../../../../../stubs/CategoryCollectionStub'; import { CategoryCollectionStub } from '@tests/unit/stubs/CategoryCollectionStub';
import { CategoryStub } from '../../../../../../../stubs/CategoryStub'; import { CategoryStub } from '@tests/unit/stubs/CategoryStub';
import { ScriptStub } from '../../../../../../../stubs/ScriptStub'; import { ScriptStub } from '@tests/unit/stubs/ScriptStub';
describe('ReverterFactory', () => { describe('ReverterFactory', () => {
describe('getReverter', () => { 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 { getScriptNodeId } from '@/presentation/components/Scripts/ScriptsTree/ScriptNodeParser';
import { UserSelection } from '@/application/Context/State/Selection/UserSelection'; import { UserSelection } from '@/application/Context/State/Selection/UserSelection';
import { SelectedScript } from '@/application/Context/State/Selection/SelectedScript'; import { SelectedScript } from '@/application/Context/State/Selection/SelectedScript';
import { CategoryCollectionStub } from '../../../../../../../stubs/CategoryCollectionStub'; import { CategoryCollectionStub } from '@tests/unit/stubs/CategoryCollectionStub';
import { CategoryStub } from '../../../../../../../stubs/CategoryStub'; import { CategoryStub } from '@tests/unit/stubs/CategoryStub';
import { ScriptStub } from '../../../../../../../stubs/ScriptStub'; import { ScriptStub } from '@tests/unit/stubs/ScriptStub';
import { SelectedScriptStub } from '../../../../../../../stubs/SelectedScriptStub'; import { SelectedScriptStub } from '@tests/unit/stubs/SelectedScriptStub';
describe('ScriptReverter', () => { describe('ScriptReverter', () => {
describe('getState', () => { 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 { ICategoryCollectionParseContext } from '@/application/Parser/Script/ICategoryCollectionParseContext';
import { ScriptCompilerStub } from './ScriptCompilerStub';
import { LanguageSyntaxStub } from './LanguageSyntaxStub';
import { IScriptCompiler } from '@/application/Parser/Script/Compiler/IScriptCompiler'; import { IScriptCompiler } from '@/application/Parser/Script/Compiler/IScriptCompiler';
import { ILanguageSyntax } from '@/domain/ScriptCode'; import { ILanguageSyntax } from '@/domain/ScriptCode';
import { ScriptCompilerStub } from './ScriptCompilerStub';
import { LanguageSyntaxStub } from './LanguageSyntaxStub';
export class CategoryCollectionParseContextStub implements ICategoryCollectionParseContext { export class CategoryCollectionParseContextStub implements ICategoryCollectionParseContext {
public compiler: IScriptCompiler = new ScriptCompilerStub(); 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 { OperatingSystem } from '@/domain/OperatingSystem';
import { ScriptStub } from './ScriptStub';
import { IScriptingDefinition } from '@/domain/IScriptingDefinition'; import { IScriptingDefinition } from '@/domain/IScriptingDefinition';
import { IScript } from '@/domain/IScript'; import { IScript } from '@/domain/IScript';
import { ICategory } from '@/domain/ICategory'; import { ICategory } from '@/domain/ICategory';
import { ICategoryCollection } from '@/domain/ICategoryCollection'; import { ICategoryCollection } from '@/domain/ICategoryCollection';
import { ScriptStub } from './ScriptStub';
import { ScriptingDefinitionStub } from './ScriptingDefinitionStub';
import { RecommendationLevel } from '@/domain/RecommendationLevel';
export class CategoryCollectionStub implements ICategoryCollection { export class CategoryCollectionStub implements ICategoryCollection {
public scripting: IScriptingDefinition = new ScriptingDefinitionStub(); public scripting: IScriptingDefinition = new ScriptingDefinitionStub();
@@ -36,14 +37,16 @@ export class CategoryCollectionStub implements ICategoryCollection {
} }
public findCategory(categoryId: number): ICategory { public findCategory(categoryId: number): ICategory {
return this.getAllCategories().find( return this.getAllCategories()
(category) => category.id === categoryId); .find((category) => category.id === categoryId);
} }
public getScriptsByLevel(): readonly IScript[] { public getScriptsByLevel(level: RecommendationLevel): readonly IScript[] {
throw new Error('Method not implemented: getScriptsByLevel'); return this.getAllScripts()
.filter((script) => script.level !== undefined && script.level <= level);
} }
public findScript(scriptId: string): IScript { public findScript(scriptId: string): IScript {
return this.getAllScripts().find((script) => scriptId === script.id); return this.getAllScripts()
.find((script) => scriptId === script.id);
} }
public getAllScripts(): ReadonlyArray<IScript> { public getAllScripts(): ReadonlyArray<IScript> {
const scripts = []; const scripts = [];
@@ -79,9 +82,7 @@ function getSubCategoriesRecursively(category: ICategory): ReadonlyArray<ICatego
function getScriptsRecursively(category: ICategory): ReadonlyArray<IScript> { function getScriptsRecursively(category: ICategory): ReadonlyArray<IScript> {
const categoryScripts = []; const categoryScripts = [];
if (category.scripts) { if (category.scripts) {
for (const script of category.scripts) { categoryScripts.push(...category.scripts);
categoryScripts.push(script);
}
} }
if (category.subCategories) { if (category.subCategories) {
for (const subCategory of category.subCategories) { for (const subCategory of category.subCategories) {

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