Compare commits

...

11 Commits

Author SHA1 Message Date
undergroundwires
c65209e6a9 Unrecommend and complete Windows Push Notif. #101
- Add more script documentation in code and reference URLs.
- Unrecommend as "Standard" recommend as "Strict" due to lack of
  documentation for its privacy intrusive behavior.
- Add mising WpnUserService for disabling it completely.
2021-12-03 01:08:55 +01:00
undergroundwires
d2518b11a7 Improve Windows defender docs and errors #104
- Improve error messages with cause of the problem and suggested solution.
- Document:
  * Disabling `WinDefend` breaks `Set-MpPreference` and Microsoft Store
    (as reported in #104).
  * Document services that `netsh advfirewall` depends on.
- Fix some bad whitespace character in documentation.
2021-11-27 20:22:18 +01:00
undergroundwires
70cdf3865a Improve and unify disabling of Windows services
Refactor, unify and improve the logic to to start/stop and
enable/disable services, and also add more documentation.

Rework functions:
  - Unify way of disabling Windows services using templating.
  - Capitalize as `startupMode` (where startup is single word) everywhere.
  - Use also text parameters (automatic, manual..) instead of numeric
    values (2,3...) when providing parameters to any service disable
    function.

Improve documentation:
  - Add reference URLs about disabled services.
  - Add more code documentation for querying status and allowed values.

Logic improvements include:
  - Check if service is running before stopping/starting the service.
  - Do not start the service it's not an Automatic service.
  - Check whether service is already disabled.
  - When reverting, start the service if it has Automatic startup. But
    do not start the service it has different startup (e.g. manual).
    Also starts the service even though start up is configured as
    desired (before it quit before doing service start).

Improve outputs (logs):
  - Remove false-positive error messages.
  - When a service cannot be stopped/start; mention in output that the
    service will be started/stopped after reboot.
  - Show success message once service is enabled/disabled.
  - Fix reboot messages when enabling/disabling services,
  - Do not write stderr if service cannot be stopped/started as it's not
    not the main goal of the function.

Add missing revert code for the ones missing them:
  - Disable diagnostics telemetry
  - Disable Windows Media Player Network Sharing Service

> Function: DisableServiceInRegistry
- Fix not exitting if service does not exist when reverting
- Show success message once service is enabled/disabled
- Fix double "Enabled.." messages
- Fix unintended registry addition

> Function: DisablePerUserService
- Change implementation to call DisableServiceInRegistry.
- Fix both services are skipped if one of them fails.
- Fix reverting a service sets wrong startup mode.
2021-11-25 21:34:15 +01:00
undergroundwires
7c02ffb6c9 Fix Defender error due to non-english Windows #104
German edition of Windows returns German output for `schtasks.exe`
commands. So checking for "Running" fails immediately as reported #104.

Revert recent change from using `Get-ScheduledTask` and
`Unregister-ScheduledTask` to `schtasks.exe`. Also remove unused
`$powershellFile` variable.
2021-11-21 13:14:58 +01:00
undergroundwires
f2d9881382 Fix unintendedly inlined Windows scripts
- Fix reverting "Disable SQM OS key".
- Fix applying "Disable Visual Studio Code data collection" scripts.
- Fix reverting "Do not show recently used files in Quick Access".
- Add unit tests for automatically checking similar issues in future.
2021-11-19 21:07:22 +01:00
undergroundwires
d7761ab30e Fix Defender features errors in Windows #104
- Refactor to use `Set-MpPreference` in a function instead.
- Better support for both Windows and Windows 11 with platform-specific
  logic, due to poor `Remove-MpPreference` used in Windows 10:
     * Use `Remove-MpPreference` on Windows 11, but switch to
       `Set-MpPreference` for some edge cases using a flag.
     * Use `Set-MpPreference` on Windows 10 by default, and use
       `Remove-MpPreference` for only small amount of cases where it is
       supported.
- Set default value instead of `Remove-MpPreference` on Windows 10 when
  it does not work as expected.
- Improve error messages when:
  * Command name (cmdlet) is not supported
  * Command parameter is not support
  * Failing due to Defender service not working
  * Argument is not supported (e.g. for 'Broad')
- Skip if a parameter or argument is not supported instead of failing.
- Set OS defaults when using `Set-MpPreference` when `Remove-MpPreference`
  does not set the OS defaults.
- Skip setting the setting if it already is as desired.
- Remove redundant scripts in "Disable remediation actions" setting
  `LowThreatDefaultAction`, `ModerateThreatDefaultAction`,
  `HighThreatDefaultAction` and `SevereThreatDefaultAction`. As they are
  all controlled by and limited to value of `UnknownThreatDefaultAction`.
- Fix registry policies not matching cmdlet behavior:
     > CheckForSignaturesBeforeRunningScan
     > SignatureUpdateCatchupInterval
- Fix reverting registry policies (`reg delete` command and error
  output):
    > Disable Malicious Software Reporting tool diagnostic data
    > Turn off block at first sight
- Fix DisableCatchupQuickScan MpPreference command being in wrong
  category by moving it to its right category and adding its correct
  equivalent.
2021-11-17 00:03:59 +01:00
undergroundwires
bf83c58982 Refactor Saas naming, structure and modules
- Add more documentation.
- Use `main.scss` instead of importing components individually. This
  improves productivity without compilation errors due to missing
  imports and allows for easier future file/folder changes and
  refactorings inside `./styles`.
- Use partials with underscored naming. Because it documents that the
  files should not be individually imported.
- Introduce `third-party-extensions` folder to group styles that
  overwrites third party components.
- Refactor variable names from generic to specific.
- Use Sass modules (`@use` and `@forward`) over depreciated `@import`
  syntax.
- Separate font assets from Sass files (`styles/`). Create `assets/`
  folder that will contain both.
- Create `_globals.css` for global styling of common element instead of
  using `App.vue`.
2021-11-14 17:48:49 +01:00
undergroundwires
2e082932c9 Fix disabling/enabling Defender on Windows #104
Change behavior of registry reverting from adding default value to
removing value that overrides. It then leaves the system in cleaner
state, removes "managed by your organization" warning, and makes the
scripts more future-proof providing compatibility with Microsoft patches
updating the defaults. This is implemented by using `reg delete` over
`reg add` and `Remove-MpPreference` over `Set-MpPreference`.

> Disable Windows Defender Scheduled Scan task
Surpress the error when reverting the script as the task may not exist
in some Windows versions.

> Limit catch-up security intelligence (signature) updates
Change to "Disable" instead of "Limit", and bring back its revert code.

Fix reverting of following scripts setting non-default values:
  > Turn off Windows Defender SpyNet reporting
  > Disable checking for signatures before scan
  > Limit CPU usage during idle scans to minumum
  > Disable scanning when not idle
  > Disable scanning on mapped network drives on full-scan

Fix following scripts setting unexpected behavior:
  > Disable running scheduled auto-remediation
  > Limit CPU usage during idle scans to minumum
  > Disable randomizing scheduled task times
  > Disable creating system restore point on a daily basis

Add more documentation for MpPreference module:
  - Add more reference URLs
  - Add status query as documentation
  - Add information regarding default values
  - Describe meaning of enumeration values
  - Document commands not doing expected in Windows 11
2021-11-12 17:26:22 +01:00
undergroundwires
2f90cac52a Improve tests for UserSelection
- Refactor for more logic reuse
- Adds more assertments for events
2021-11-09 21:49:56 +01:00
undergroundwires
20a0071c0d Fix Windows TrustedInstaller session errors
- Fix errors (stderr stream) not being logged.
- Use `schtasks /delete` instead of `Unregister-ScheduledTask` as
  PowerShell command sometimes fail for existing tasks.
- Refactor to use `-TaskName` to explicit describe parameter, and use
  linebreaks for `Register-ScheduledTask` call with many parameters.
2021-11-09 00:14:56 +01:00
undergroundwires-bot
a40f83d6b6 ⬆️ bump everywhere to 0.11.1 2021-11-06 17:47:52 +00:00
51 changed files with 1892 additions and 883 deletions

View File

@@ -1,5 +1,16 @@
# Changelog # Changelog
## 0.11.1 (2021-11-04)
* Update dependencies | [64631a4](https://github.com/undergroundwires/privacy.sexy/commit/64631a4552fad7f7b06286aba8d3ca2d731f9342)
* Fix, document, unrecommend Windows browser cleanup | [5ead1a0](https://github.com/undergroundwires/privacy.sexy/commit/5ead1a087d91948890bc4ae6fea176123f18c285)
* Fix failing URL status checking integration tests | [799fb09](https://github.com/undergroundwires/privacy.sexy/commit/799fb091b8eb06c70ac0c67f2ef5385dce73501f)
* Refactor to remove "Async" function name suffix | [82c43ba](https://github.com/undergroundwires/privacy.sexy/commit/82c43ba2e37fb6e7f62ccd9bec8c5f48575f0613)
* Fix dead URLs and use forks as GitHub references | [97ddc02](https://github.com/undergroundwires/privacy.sexy/commit/97ddc027cb5395a74991cabc1d8c875ee945636d)
* Fix website not loading on Safari | [0db8cc4](https://github.com/undergroundwires/privacy.sexy/commit/0db8cc420655e01cbbed57c4658489b761a15899)
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.11.0...0.11.1)
## 0.11.0 (2021-10-21) ## 0.11.0 (2021-10-21)
* Change "grouping" to "view" | [c0c475f](https://github.com/undergroundwires/privacy.sexy/commit/c0c475ff564b23a4dabcc03ac2909207a8eb61ce) * Change "grouping" to "view" | [c0c475f](https://github.com/undergroundwires/privacy.sexy/commit/c0c475ff564b23a4dabcc03ac2909207a8eb61ce)

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.11.0/privacy.sexy-Setup-0.11.0.exe), [macOS](https://github.com/undergroundwires/privacy.sexy/releases/download/0.11.0/privacy.sexy-0.11.0.dmg) or [Linux](https://github.com/undergroundwires/privacy.sexy/releases/download/0.11.0/privacy.sexy-0.11.0.AppImage). - Alternatively download offline version for [Windows](https://github.com/undergroundwires/privacy.sexy/releases/download/0.11.1/privacy.sexy-Setup-0.11.1.exe), [macOS](https://github.com/undergroundwires/privacy.sexy/releases/download/0.11.1/privacy.sexy-0.11.1.dmg) or [Linux](https://github.com/undergroundwires/privacy.sexy/releases/download/0.11.1/privacy.sexy-0.11.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.
@@ -57,8 +57,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.11.0 .` 1. Build: `docker build -t undergroundwires/privacy.sexy:0.11.1 .`
2. Run: `docker run -it -p 8080:80 --rm --name privacy.sexy-0.11.0 undergroundwires/privacy.sexy:0.11.0` 2. Run: `docker run -it -p 8080:80 --rm --name privacy.sexy-0.11.1 undergroundwires/privacy.sexy:0.11.1`
## Architecture overview ## Architecture overview

View File

@@ -10,11 +10,16 @@
- [**`bootstrapping/`**](./../src/presentation/bootstrapping/): Registers Vue global objects including components and plugins. - [**`bootstrapping/`**](./../src/presentation/bootstrapping/): Registers Vue global objects including components and plugins.
- [**`components/`**](./../src/presentation/components/): Contains all Vue components and their helper classes. - [**`components/`**](./../src/presentation/components/): Contains all Vue components and their helper classes.
- [**`Shared/`**](./../src/presentation/components/Shared): Contains Vue components and component helpers that are shared across other components. - [**`Shared/`**](./../src/presentation/components/Shared): Contains Vue components and component helpers that are shared across other components.
- [**`styles/`**](./../src/presentation/styles/): Contains shared styles used throughout different components. - [**`assets/`**](./../src/presentation/assets/styles/): Contains assets that will be processed by webpack.
- [**`fonts/`**](./../src/presentation/assets/fonts/): Contains fonts
- [**`styles/`**](./../src/presentation/assets/styles/): Contains shared styles used throughout different components.
- [**`components/`**](./../src/presentation/assets/styles/components): Contains styles that are reusable and tightly coupled a Vue/HTML component.
- [**`vendors-extensions/`**](./../src/presentation/assets/styles/third-party-extensions): Contains styles that override third-party components used.
- [**`main.scss`**](./../src/presentation/assets/styles/main.scss): Primary Sass file, passes along all other styles, should be the only file used from other components.
- [**`main.ts`**](./../src/presentation/main.ts): Application entry point that mounts and starts Vue application. - [**`main.ts`**](./../src/presentation/main.ts): Application entry point that mounts and starts Vue application.
- [`electron/`](./../src/presentation/electron/): Electron configuration for the desktop application. - [**`electron/`**](./../src/presentation/electron/): Electron configuration for the desktop application.
- [**`main.ts`**](./../src/presentation/main.ts): Main process of Electron, started as first thing when app starts. - [**`main.ts`**](./../src/presentation/main.ts): Main process of Electron, started as first thing when app starts.
- [**`/public/`**](./../public/): Contains static assets that will simply be copied and not go through webpack. - [**`/public/`**](./../public/): Contains static assets that will directly be copied and not go through webpack.
- [**`/vue.config.js`**](./../vue.config.js): Global Vue CLI configurations loaded by `@vue/cli-service` - [**`/vue.config.js`**](./../vue.config.js): Global Vue CLI configurations loaded by `@vue/cli-service`
- [**`/postcss.config.js`**](./../postcss.config.js): PostCSS configurations that are used by Vue CLI internally - [**`/postcss.config.js`**](./../postcss.config.js): PostCSS configurations that are used by Vue CLI internally
- [**`/babel.config.js`**](./../babel.config.js): Babel configurations for polyfills used by `@vue/cli-plugin-babel` - [**`/babel.config.js`**](./../babel.config.js): Babel configurations for polyfills used by `@vue/cli-plugin-babel`
@@ -50,3 +55,18 @@
</Dialog> </Dialog>
<div @click="$refs.testDialog.show()">Show dialog</div> <div @click="$refs.testDialog.show()">Show dialog</div>
``` ```
## Sass naming convention
- Use lowercase for variables/functions/mixins e.g.
- Variable: `$variable: value;`
- Function: `@function function() {}`
- Mixin: `@mixin mixin() {}`
- Use - for a phrase/compound word e.g.
- Variable: `$some-variable: value;`
- Function: `@function some-function() {}`
- Mixin: `@mixin some-mixin() {}`
- Grouping and name variables from generic to specific e.g.
- ✅ `$border-blue`, `$border-blue-light`, `$border-blue-lightest`, `$border-red`
- ❌ `$blue-border`, `$light-blue-border`, `$lightest-blue-border`, `$red-border`

145
package-lock.json generated
View File

@@ -1,11 +1,12 @@
{ {
"name": "privacy.sexy", "name": "privacy.sexy",
"version": "0.11.0", "version": "0.11.1",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"version": "0.11.0", "name": "privacy.sexy",
"version": "0.11.1",
"hasInstallScript": true, "hasInstallScript": true,
"dependencies": { "dependencies": {
"@fortawesome/fontawesome-svg-core": "^1.2.36", "@fortawesome/fontawesome-svg-core": "^1.2.36",
@@ -45,6 +46,7 @@
"electron-updater": "^4.3.9", "electron-updater": "^4.3.9",
"js-yaml-loader": "^1.2.2", "js-yaml-loader": "^1.2.2",
"markdownlint-cli": "^0.29.0", "markdownlint-cli": "^0.29.0",
"raw-loader": "^4.0.2",
"remark-cli": "^10.0.0", "remark-cli": "^10.0.0",
"remark-lint-no-dead-urls": "^1.1.0", "remark-lint-no-dead-urls": "^1.1.0",
"remark-preset-lint-consistent": "^5.1.0", "remark-preset-lint-consistent": "^5.1.0",
@@ -2266,9 +2268,9 @@
"dev": true "dev": true
}, },
"node_modules/@types/json-schema": { "node_modules/@types/json-schema": {
"version": "7.0.6", "version": "7.0.9",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.6.tgz", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz",
"integrity": "sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw==", "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==",
"dev": true "dev": true
}, },
"node_modules/@types/mdast": { "node_modules/@types/mdast": {
@@ -18684,6 +18686,82 @@
"node": ">= 0.8" "node": ">= 0.8"
} }
}, },
"node_modules/raw-loader": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/raw-loader/-/raw-loader-4.0.2.tgz",
"integrity": "sha512-ZnScIV3ag9A4wPX/ZayxL/jZH+euYb6FcUinPcgiQW0+UBtEv0O6Q3lGd3cqJ+GHH+rksEv3Pj99oxJ3u3VIKA==",
"dev": true,
"dependencies": {
"loader-utils": "^2.0.0",
"schema-utils": "^3.0.0"
},
"engines": {
"node": ">= 10.13.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/webpack"
},
"peerDependencies": {
"webpack": "^4.0.0 || ^5.0.0"
}
},
"node_modules/raw-loader/node_modules/emojis-list": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz",
"integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==",
"dev": true,
"engines": {
"node": ">= 4"
}
},
"node_modules/raw-loader/node_modules/json5": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz",
"integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==",
"dev": true,
"dependencies": {
"minimist": "^1.2.5"
},
"bin": {
"json5": "lib/cli.js"
},
"engines": {
"node": ">=6"
}
},
"node_modules/raw-loader/node_modules/loader-utils": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.2.tgz",
"integrity": "sha512-TM57VeHptv569d/GKh6TAYdzKblwDNiumOdkFnejjD0XwTH87K90w3O7AiJRqdQoXygvi1VQTJTLGhJl7WqA7A==",
"dev": true,
"dependencies": {
"big.js": "^5.2.2",
"emojis-list": "^3.0.0",
"json5": "^2.1.2"
},
"engines": {
"node": ">=8.9.0"
}
},
"node_modules/raw-loader/node_modules/schema-utils": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz",
"integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==",
"dev": true,
"dependencies": {
"@types/json-schema": "^7.0.8",
"ajv": "^6.12.5",
"ajv-keywords": "^3.5.2"
},
"engines": {
"node": ">= 10.13.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/webpack"
}
},
"node_modules/rc": { "node_modules/rc": {
"version": "1.2.8", "version": "1.2.8",
"resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
@@ -27658,9 +27736,9 @@
"dev": true "dev": true
}, },
"@types/json-schema": { "@types/json-schema": {
"version": "7.0.6", "version": "7.0.9",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.6.tgz", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz",
"integrity": "sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw==", "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==",
"dev": true "dev": true
}, },
"@types/mdast": { "@types/mdast": {
@@ -27936,6 +28014,7 @@
"integrity": "sha512-P13AJv5FDt2XnpZ92K0VMxBS7Pe+gnibxtXMsa8rXLBkEE1NkmtaG5pyXh3fulkmF2/21efOcuh6yFP7k0KuZg==", "integrity": "sha512-P13AJv5FDt2XnpZ92K0VMxBS7Pe+gnibxtXMsa8rXLBkEE1NkmtaG5pyXh3fulkmF2/21efOcuh6yFP7k0KuZg==",
"dev": true, "dev": true,
"requires": { "requires": {
"@babel/core": "^7.11.0",
"@babel/helper-compilation-targets": "^7.9.6", "@babel/helper-compilation-targets": "^7.9.6",
"@babel/helper-module-imports": "^7.8.3", "@babel/helper-module-imports": "^7.8.3",
"@babel/plugin-proposal-class-properties": "^7.8.3", "@babel/plugin-proposal-class-properties": "^7.8.3",
@@ -27948,6 +28027,7 @@
"@vue/babel-plugin-jsx": "^1.0.3", "@vue/babel-plugin-jsx": "^1.0.3",
"@vue/babel-preset-jsx": "^1.2.4", "@vue/babel-preset-jsx": "^1.2.4",
"babel-plugin-dynamic-import-node": "^2.3.3", "babel-plugin-dynamic-import-node": "^2.3.3",
"core-js": "^3.6.5",
"core-js-compat": "^3.6.5", "core-js-compat": "^3.6.5",
"semver": "^6.1.0" "semver": "^6.1.0"
} }
@@ -40396,6 +40476,55 @@
"unpipe": "1.0.0" "unpipe": "1.0.0"
} }
}, },
"raw-loader": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/raw-loader/-/raw-loader-4.0.2.tgz",
"integrity": "sha512-ZnScIV3ag9A4wPX/ZayxL/jZH+euYb6FcUinPcgiQW0+UBtEv0O6Q3lGd3cqJ+GHH+rksEv3Pj99oxJ3u3VIKA==",
"dev": true,
"requires": {
"loader-utils": "^2.0.0",
"schema-utils": "^3.0.0"
},
"dependencies": {
"emojis-list": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz",
"integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==",
"dev": true
},
"json5": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz",
"integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==",
"dev": true,
"requires": {
"minimist": "^1.2.5"
}
},
"loader-utils": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.2.tgz",
"integrity": "sha512-TM57VeHptv569d/GKh6TAYdzKblwDNiumOdkFnejjD0XwTH87K90w3O7AiJRqdQoXygvi1VQTJTLGhJl7WqA7A==",
"dev": true,
"requires": {
"big.js": "^5.2.2",
"emojis-list": "^3.0.0",
"json5": "^2.1.2"
}
},
"schema-utils": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz",
"integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==",
"dev": true,
"requires": {
"@types/json-schema": "^7.0.8",
"ajv": "^6.12.5",
"ajv-keywords": "^3.5.2"
}
}
}
},
"rc": { "rc": {
"version": "1.2.8", "version": "1.2.8",
"resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",

View File

@@ -1,6 +1,6 @@
{ {
"name": "privacy.sexy", "name": "privacy.sexy",
"version": "0.11.0", "version": "0.11.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",
@@ -59,6 +59,7 @@
"electron-updater": "^4.3.9", "electron-updater": "^4.3.9",
"js-yaml-loader": "^1.2.2", "js-yaml-loader": "^1.2.2",
"markdownlint-cli": "^0.29.0", "markdownlint-cli": "^0.29.0",
"raw-loader": "^4.0.2",
"remark-cli": "^10.0.0", "remark-cli": "^10.0.0",
"remark-lint-no-dead-urls": "^1.1.0", "remark-lint-no-dead-urls": "^1.1.0",
"remark-preset-lint-consistent": "^5.1.0", "remark-preset-lint-consistent": "^5.1.0",

File diff suppressed because it is too large Load Diff

View File

Before

Width:  |  Height:  |  Size: 147 KiB

After

Width:  |  Height:  |  Size: 147 KiB

View File

@@ -5,13 +5,13 @@
font-family: 'Slabo 27px'; font-family: 'Slabo 27px';
font-style: normal; font-style: normal;
font-weight: 400; font-weight: 400;
src: url('~@/presentation/styles/fonts/slabo-27px-v6-latin-ext_latin-regular.eot'); /* IE9 Compat Modes */ src: url('~@/presentation/assets/fonts/slabo-27px-v6-latin-ext_latin-regular.eot'); /* IE9 Compat Modes */
src: local('Slabo 27px'), local('Slabo27px-Regular'), src: local('Slabo 27px'), local('Slabo27px-Regular'),
url('~@/presentation/styles/fonts/slabo-27px-v6-latin-ext_latin-regular.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */ url('~@/presentation/assets/fonts/slabo-27px-v6-latin-ext_latin-regular.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
url('~@/presentation/styles/fonts/slabo-27px-v6-latin-ext_latin-regular.woff2') format('woff2'), /* Super Modern Browsers */ url('~@/presentation/assets/fonts/slabo-27px-v6-latin-ext_latin-regular.woff2') format('woff2'), /* Super Modern Browsers */
url('~@/presentation/styles/fonts/slabo-27px-v6-latin-ext_latin-regular.woff') format('woff'), /* Modern Browsers */ url('~@/presentation/assets/fonts/slabo-27px-v6-latin-ext_latin-regular.woff') format('woff'), /* Modern Browsers */
url('~@/presentation/styles/fonts/slabo-27px-v6-latin-ext_latin-regular.ttf') format('truetype'), /* Safari, Android, iOS */ url('~@/presentation/assets/fonts/slabo-27px-v6-latin-ext_latin-regular.ttf') format('truetype'), /* Safari, Android, iOS */
url('~@/presentation/styles/fonts/slabo-27px-v6-latin-ext_latin-regular.svg#Slabo27px') format('svg'); /* Legacy iOS */ url('~@/presentation/assets/fonts/slabo-27px-v6-latin-ext_latin-regular.svg#Slabo27px') format('svg'); /* Legacy iOS */
} }
/* yesteryear-regular - latin */ /* yesteryear-regular - latin */
@@ -19,15 +19,15 @@
font-family: 'Yesteryear'; font-family: 'Yesteryear';
font-style: normal; font-style: normal;
font-weight: 400; font-weight: 400;
src: url('~@/presentation/styles/fonts/yesteryear-v8-latin-regular.eot'); /* IE9 Compat Modes */ src: url('~@/presentation/assets/fonts/yesteryear-v8-latin-regular.eot'); /* IE9 Compat Modes */
src: local('Yesteryear'), local('Yesteryear-Regular'), src: local('Yesteryear'), local('Yesteryear-Regular'),
url('~@/presentation/styles/fonts/yesteryear-v8-latin-regular.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */ url('~@/presentation/assets/fonts/yesteryear-v8-latin-regular.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
url('~@/presentation/styles/fonts/yesteryear-v8-latin-regular.woff2') format('woff2'), /* Super Modern Browsers */ url('~@/presentation/assets/fonts/yesteryear-v8-latin-regular.woff2') format('woff2'), /* Super Modern Browsers */
url('~@/presentation/styles/fonts/yesteryear-v8-latin-regular.woff') format('woff'), /* Modern Browsers */ url('~@/presentation/assets/fonts/yesteryear-v8-latin-regular.woff') format('woff'), /* Modern Browsers */
url('~@/presentation/styles/fonts/yesteryear-v8-latin-regular.ttf') format('truetype'), /* Safari, Android, iOS */ url('~@/presentation/assets/fonts/yesteryear-v8-latin-regular.ttf') format('truetype'), /* Safari, Android, iOS */
url('~@/presentation/styles/fonts/yesteryear-v8-latin-regular.svg#Yesteryear') format('svg'); /* Legacy iOS */ url('~@/presentation/assets/fonts/yesteryear-v8-latin-regular.svg#Yesteryear') format('svg'); /* Legacy iOS */
} }
$normal-font: 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', monospace; $font-normal : 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', monospace;
$artistic-font: 'Yesteryear', cursive; $font-artistic : 'Yesteryear', cursive;
$main-font: 'Slabo 27px'; $font-main : 'Slabo 27px';

View File

@@ -0,0 +1,25 @@
/*
Defines global styles that applies to globally defined tags by default (body, main, article, div etc.)
*/
@use "@/presentation/assets/styles/colors" as *;
@use "@/presentation/assets/styles/fonts" as *;
* {
box-sizing: border-box;
}
a {
color:inherit;
text-decoration: underline;
cursor: pointer;
&:hover {
color: $color-primary;
}
}
body {
background: $color-background;
font-family: $font-main;
}

View File

@@ -0,0 +1,5 @@
$media-screen-big-width : 992px;
$media-screen-medium-width : 768px;
$media-screen-small-width : 380px;
$media-vertical-view-breakpoint : 992px;

View File

@@ -0,0 +1,11 @@
/* This class is not supposed to more than forwarding other styles */
@forward "./fonts";
@forward "./media";
@forward "./colors";
@forward "./globals";
@forward "./components/card";
@forward "./third-party-extensions/tooltip.scss";
@forward "./third-party-extensions/tree.scss";

View File

@@ -1,5 +1,5 @@
// Based on https://github.com/Akryum/v-tooltip/blob/83615e394c96ca491a4df04b892ae87e833beb97/demo-src/src/App.vue#L179-L303 // Based on https://github.com/Akryum/v-tooltip/blob/83615e394c96ca491a4df04b892ae87e833beb97/demo-src/src/App.vue#L179-L303
@import "@/presentation/styles/colors.scss"; @use "@/presentation/assets/styles/colors" as *;
.tooltip { .tooltip {
display: block !important; display: block !important;

View File

@@ -0,0 +1,58 @@
// Overrides base styling for LiquorTree
@use "@/presentation/assets/styles/colors" as *;
$color-tree-bg : $color-primary-darker;
$color-node-arrow : $color-on-primary;
$color-node-fg : $color-on-primary;
$color-node-hover-bg : $color-primary-dark;
$color-node-keyboard-bg : $color-surface;
$color-node-keyboard-fg : $color-on-surface;
$color-node-checkbox-bg-checked : $color-secondary;
$color-node-checkbox-bg-unchecked : $color-primary-darkest;
$color-node-checkbox-border-checked : $color-secondary;
$color-node-checkbox-border-unchecked : $color-on-primary;
$color-node-checkbox-tick-checked : $color-on-secondary;
.tree {
background: $color-tree-bg;
&-node {
white-space: normal !important;
> .tree-content {
> .tree-anchor > span {
color: $color-node-fg;
text-transform: uppercase;
font-size: 1.5em;
}
&:hover {
background: $color-node-hover-bg !important;
}
}
&.selected { // When using keyboard navigation it highlights current item and its child items
background: $color-node-keyboard-bg;
.tree-text {
color: $color-node-keyboard-fg !important; // $block
}
}
}
&-checkbox {
border-color: $color-node-checkbox-border-unchecked !important;
&.checked {
background: $color-node-checkbox-bg-checked !important;
border-color: $color-node-checkbox-border-checked !important;
&:after {
border-color: $color-node-checkbox-tick-checked !important;
}
}
&.indeterminate {
border-color: $color-node-checkbox-border-unchecked !important;
}
background: $color-node-checkbox-bg-unchecked !important;
}
&-arrow {
&.has-child {
&.rtl:after, &:after {
border-color: $color-node-arrow !important;
}
}
}
}

View File

@@ -33,27 +33,7 @@ export default class App extends Vue {
</script> </script>
<style lang="scss"> <style lang="scss">
@import "@/presentation/styles/colors.scss"; @use "@/presentation/assets/styles/main" as *;
@import "@/presentation/styles/fonts.scss";
@import "@/presentation/styles/media.scss";
* {
box-sizing: border-box;
}
a {
color:inherit;
text-decoration: underline;
cursor: pointer;
&:hover {
color: $color-primary;
}
}
body {
background: $color-background;
font-family: $main-font;
}
#app { #app {
margin-right: auto; margin-right: auto;
@@ -76,6 +56,4 @@ body {
} }
} }
@import "@/presentation/styles/tooltip.scss";
@import "@/presentation/styles/tree.scss";
</style> </style>

View File

@@ -25,13 +25,12 @@ export default class Code extends Vue {
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@import "@/presentation/styles/colors.scss"; @use "@/presentation/assets/styles/main" as *;
@import "@/presentation/styles/fonts.scss";
.code-wrapper { .code-wrapper {
white-space: nowrap; white-space: nowrap;
justify-content: space-between; justify-content: space-between;
font-family: $normal-font; font-family: $font-normal;
background-color: $color-primary-darker; background-color: $color-primary-darker;
color: $color-on-primary; color: $color-on-primary;
padding-left: 0.3rem; padding-left: 0.3rem;

View File

@@ -24,8 +24,7 @@ export default class IconButton extends Vue {
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@import "@/presentation/styles/colors.scss"; @use "@/presentation/assets/styles/main" as *;
@import "@/presentation/styles/fonts.scss";
.button { .button {
display: flex; display: flex;
@@ -57,7 +56,7 @@ export default class IconButton extends Vue {
} }
&__text { &__text {
display: none; display: none;
font-family: $artistic-font; font-family: $font-artistic;
font-size: 1.5em; font-size: 1.5em;
color: $color-primary; color: $color-primary;
font-weight: 500; font-weight: 500;

View File

@@ -104,8 +104,7 @@ export default class MacOsInstructions extends Vue {
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@import "@/presentation/styles/colors.scss"; @use "@/presentation/assets/styles/main" as *;
@import "@/presentation/styles/fonts.scss";
li { li {
margin: 10px 0; margin: 10px 0;

View File

@@ -150,7 +150,8 @@ function getDefaultCode(language: ScriptingLanguage): string {
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@import "@/presentation/styles/colors.scss"; @use "@/presentation/assets/styles/main" as *;
::v-deep .code-area { ::v-deep .code-area {
min-height: 200px; min-height: 200px;
width: 100%; width: 100%;

View File

@@ -17,11 +17,11 @@ export default class MenuOptionList extends Vue {
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@import "@/presentation/styles/fonts.scss"; @use "@/presentation/assets/styles/main" as *;
$gap: 0.25rem; $gap: 0.25rem;
.list { .list {
font-family: $normal-font; font-family: $font-normal;
display: flex; display: flex;
align-items: center; align-items: center;
.items { .items {

View File

@@ -22,7 +22,7 @@ export default class MenuOptionListItem extends Vue {
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@import "@/presentation/styles/colors.scss"; @use "@/presentation/assets/styles/main" as *;
.enabled { .enabled {
cursor: pointer; cursor: pointer;

View File

@@ -43,7 +43,7 @@ export default class Handle extends Vue {
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import "@/presentation/styles/colors.scss"; @use "@/presentation/assets/styles/main" as *;
$color : $color-primary-dark; $color : $color-primary-dark;
$color-hover : $color-primary; $color-hover : $color-primary;

View File

@@ -40,7 +40,7 @@ export default class HorizontalResizeSlider extends Vue {
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import "@/presentation/styles/media.scss"; @use "@/presentation/assets/styles/main" as *;
.slider { .slider {
display: flex; display: flex;
@@ -53,7 +53,7 @@ export default class HorizontalResizeSlider extends Vue {
flex: 1; flex: 1;
min-width: var(--second-min-width); min-width: var(--second-min-width);
} }
@media screen and (max-width: $vertical-view-breakpoint) { @media screen and (max-width: $media-vertical-view-breakpoint) {
flex-direction: column; flex-direction: column;
.first { .first {
width: auto !important; width: auto !important;

View File

@@ -105,13 +105,12 @@ function isClickable(element: Element) {
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@import "@/presentation/styles/fonts.scss"; @use "@/presentation/assets/styles/main" as *;
@import "@/presentation/styles/components/card.scss";
.cards { .cards {
display: flex; display: flex;
flex-flow: row wrap; flex-flow: row wrap;
font-family: $main-font; font-family: $font-main;
gap: $card-gap; gap: $card-gap;
/* /*
Padding is used to allow scale animation (growing size) for cards on hover. Padding is used to allow scale animation (growing size) for cards on hover.
@@ -124,6 +123,6 @@ function isClickable(element: Element) {
width: 100%; width: 100%;
text-align: center; text-align: center;
font-size: 3.5em; font-size: 3.5em;
font-family: $normal-font; font-family: $font-normal;
} }
</style> </style>

View File

@@ -95,9 +95,7 @@ export default class CardListItem extends StatefulVue {
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@import "@/presentation/styles/colors.scss"; @use "@/presentation/assets/styles/main" as *;
@import "@/presentation/styles/media.scss";
@import "@/presentation/styles/components/card.scss";
$card-inner-padding : 30px; $card-inner-padding : 30px;
$arrow-size : 15px; $arrow-size : 15px;

View File

@@ -24,7 +24,8 @@
<style scoped lang="scss"> <style scoped lang="scss">
@import "@/presentation/styles/colors.scss"; @use "@/presentation/assets/styles/main" as *;
.documentationUrls { .documentationUrls {
display: flex; display: flex;
flex-direction: row; flex-direction: row;

View File

@@ -32,7 +32,8 @@
<style scoped lang="scss"> <style scoped lang="scss">
@import "@/presentation/styles/colors.scss"; @use "@/presentation/assets/styles/main" as *;
#node { #node {
display:flex; display:flex;
flex-direction: row; flex-direction: row;

View File

@@ -52,14 +52,14 @@ export default class RevertToggle extends StatefulVue {
<style scoped lang="scss"> <style scoped lang="scss">
@use 'sass:math'; @use 'sass:math';
@import "@/presentation/styles/colors.scss"; @use "@/presentation/assets/styles/main" as *;
$color-unchecked-bullet : $color-primary-darker; $color-bullet-unchecked : $color-primary-darker;
$color-unchecked-text : $color-on-primary; $color-bullet-checked : $color-on-secondary;
$color-unchecked-bg : $color-primary; $color-text-unchecked : $color-on-primary;
$color-checked-bg : $color-secondary; $color-text-checked : $color-on-secondary;
$color-checked-text : $color-on-secondary; $color-bg-unchecked : $color-primary;
$color-checked-bullet : $color-on-secondary; $color-bg-checked : $color-secondary;
$size-width : 85px; $size-width : 85px;
$size-height : 30px; $size-height : 30px;
@@ -94,7 +94,7 @@ $size-height : 30px;
position: relative; position: relative;
width: $size-width; width: $size-width;
height: $size-height; height: $size-height;
background-color: $color-unchecked-bg; background-color: $color-bg-unchecked;
-webkit-transition: background-color 0.25s ease-out 0s; -webkit-transition: background-color 0.25s ease-out 0s;
transition: background-color 0.25s ease-out 0s; transition: background-color 0.25s ease-out 0s;
@@ -109,7 +109,7 @@ $size-height : 30px;
height: $circle-size; height: $circle-size;
border-radius: $circle-size * 2; border-radius: $circle-size * 2;
-webkit-border-radius: $circle-size * 2; -webkit-border-radius: $circle-size * 2;
background-color: $color-unchecked-bullet; background-color: $color-bullet-unchecked;
top: $size-height * 0.16; top: $size-height * 0.16;
left: $size-width * 0.05; left: $size-width * 0.05;
-webkit-transition: left 0.3s ease-out 0s; -webkit-transition: left 0.3s ease-out 0s;
@@ -120,11 +120,11 @@ $size-height : 30px;
input.input-checkbox:checked { input.input-checkbox:checked {
+ .checkbox-animate { + .checkbox-animate {
background-color: $color-checked-bg; background-color: $color-bg-checked;
} }
+ .checkbox-animate:before { + .checkbox-animate:before {
left: ($size-width - math.div($size-width, 3.5)); left: ($size-width - math.div($size-width, 3.5));
background-color: $color-checked-bullet; background-color: $color-bullet-checked;
} }
+ .checkbox-animate .checkbox-off { + .checkbox-animate .checkbox-off {
display: none; display: none;
@@ -146,7 +146,7 @@ $size-height : 30px;
.checkbox-off { .checkbox-off {
margin-left: math.div($size-width, 3); margin-left: math.div($size-width, 3);
opacity: 1; opacity: 1;
color: $color-unchecked-text; color: $color-text-unchecked;
} }
.checkbox-on { .checkbox-on {
@@ -154,7 +154,7 @@ $size-height : 30px;
float: right; float: right;
margin-right: math.div($size-width, 3); margin-right: math.div($size-width, 3);
opacity: 0; opacity: 0;
color: $color-checked-text; color: $color-text-checked;
} }
} }
</style> </style>

View File

@@ -95,15 +95,13 @@ export default class TheScriptsView extends StatefulVue {
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@import "@/presentation/styles/colors.scss"; @use "@/presentation/assets/styles/main" as *;
@import "@/presentation/styles/fonts.scss";
@import "@/presentation/styles/media.scss";
$inner-margin: 4px; $margin-inner: 4px;
.scripts { .scripts {
margin-top: $inner-margin; margin-top: $margin-inner;
@media screen and (min-width: $vertical-view-breakpoint) { // so the current code is always visible @media screen and (min-width: $media-vertical-view-breakpoint) { // so the current code is always visible
overflow: auto; overflow: auto;
max-height: 70vh; max-height: 70vh;
} }

View File

@@ -31,12 +31,11 @@ export default class Dialog extends Vue {
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@import "@/presentation/styles/fonts.scss"; @use "@/presentation/assets/styles/main" as *;
@import "@/presentation/styles/colors.scss";
.dialog { .dialog {
color: $color-surface; color: $color-surface;
font-family: $normal-font; font-family: $font-normal;
margin-bottom: 10px; margin-bottom: 10px;
display: flex; display: flex;
flex-direction: row; flex-direction: row;

View File

@@ -38,8 +38,7 @@ export default class DownloadUrlList extends Vue {
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@import "@/presentation/styles/media.scss"; @use "@/presentation/assets/styles/main" as *;
@import "@/presentation/styles/colors.scss";
.container { .container {
display:flex; display:flex;

View File

@@ -56,11 +56,12 @@ export default class PrivacyPolicy extends Vue {
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@import "@/presentation/styles/fonts.scss"; @use "@/presentation/assets/styles/main" as *;
.privacy-policy { .privacy-policy {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
font-family: $normal-font; font-family: $font-normal;
text-align:center; text-align:center;
.line { .line {

View File

@@ -81,9 +81,7 @@ export default class TheFooter extends Vue {
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@import "@/presentation/styles/colors.scss"; @use "@/presentation/assets/styles/main" as *;
@import "@/presentation/styles/fonts.scss";
@import "@/presentation/styles/media.scss";
.icon { .icon {
margin-right: 0.5em; margin-right: 0.5em;
@@ -93,13 +91,13 @@ export default class TheFooter extends Vue {
.footer { .footer {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
@media screen and (max-width: $big-screen-width) { @media screen and (max-width: $media-screen-big-width) {
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
} }
&__section { &__section {
display: flex; display: flex;
@media screen and (max-width: $big-screen-width) { @media screen and (max-width: $media-screen-big-width) {
justify-content: space-around; justify-content: space-around;
width:100%; width:100%;
&:not(:first-child) { &:not(:first-child) {
@@ -108,13 +106,13 @@ export default class TheFooter extends Vue {
} }
flex-wrap: wrap; flex-wrap: wrap;
font-size: 1rem; font-size: 1rem;
font-family: $normal-font; font-family: $font-normal;
&__item:not(:first-child) { &__item:not(:first-child) {
&::before { &::before {
content: "|"; content: "|";
padding: 0 5px; padding: 0 5px;
} }
@media screen and (max-width: $big-screen-width) { @media screen and (max-width: $media-screen-big-width) {
margin-top: 3px; margin-top: 3px;
&::before { &::before {
content: ""; content: "";

View File

@@ -22,8 +22,8 @@ export default class TheHeader extends Vue {
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@import "@/presentation/styles/colors.scss"; @use "@/presentation/assets/styles/main" as *;
@import "@/presentation/styles/fonts.scss";
#container { #container {
display: flex; display: flex;
align-items: center; align-items: center;
@@ -37,7 +37,7 @@ export default class TheHeader extends Vue {
.title { .title {
margin: 0; margin: 0;
text-transform: uppercase; text-transform: uppercase;
font-family: $main-font; font-family: $font-main;
font-size: 2.5em; font-size: 2.5em;
line-height: 1.1; line-height: 1.1;
} }
@@ -45,7 +45,7 @@ export default class TheHeader extends Vue {
margin: 0; margin: 0;
font-size: 1.5em; font-size: 1.5em;
color: $color-primary; color: $color-primary;
font-family: $artistic-font; font-family: $font-artistic;
font-weight: 500; font-weight: 500;
line-height: 1.2; line-height: 1.2;
} }

View File

@@ -58,8 +58,7 @@ export default class TheSearchBar extends StatefulVue {
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@import "@/presentation/styles/colors.scss"; @use "@/presentation/assets/styles/main" as *;
@import "@/presentation/styles/fonts.scss";
.search { .search {
width: 100%; width: 100%;
@@ -81,7 +80,7 @@ export default class TheSearchBar extends StatefulVue {
padding-right:10px; padding-right:10px;
outline: none; outline: none;
color: $color-primary; color: $color-primary;
font-family: $normal-font; font-family: $font-normal;
font-size:1em; font-size:1em;
&:focus { &:focus {
color: $color-primary-darker; color: $color-primary-darker;

View File

@@ -1,5 +0,0 @@
$big-screen-width: 992px;
$medium-screen-width: 768px;
$small-screen-width: 380px;
$vertical-view-breakpoint: 992px;

View File

@@ -1,58 +0,0 @@
// Overrides base styling for LiquorTree
@import "@/presentation/styles/colors.scss";
$color-tree-bg : $color-primary-darker;
$color-node-arrow : $color-on-primary;
$color-node-fg : $color-on-primary;
$color-node-hover-bg : $color-primary-dark;
$color-node-keyboard-bg : $color-surface;
$color-node-keyboard-fg : $color-on-surface;
$color-node-checkbox-checked-bg : $color-secondary;
$color-node-checkbox-checked-border : $color-secondary;
$color-node-checkbox-checked-checked-tick : $color-on-secondary;
$color-node-checkbox-unchecked-bg : $color-primary-darkest;
$color-node-checkbox-unchecked-border : $color-on-primary;
.tree {
background: $color-tree-bg;
&-node {
white-space: normal !important;
> .tree-content {
> .tree-anchor > span {
color: $color-node-fg;
text-transform: uppercase;
font-size: 1.5em;
}
&:hover {
background: $color-node-hover-bg !important;
}
}
&.selected { // When using keyboard navigation it highlights current item and its child items
background: $color-node-keyboard-bg;
.tree-text {
color: $color-node-keyboard-fg !important; // $block
}
}
}
&-checkbox {
border-color: $color-node-checkbox-unchecked-border !important;
&.checked {
background: $color-node-checkbox-checked-bg !important;
border-color: $color-node-checkbox-checked-border !important;
&:after {
border-color: $color-node-checkbox-checked-checked-tick !important;
}
}
&.indeterminate {
border-color: $color-node-checkbox-unchecked-border !important;
}
background: $color-node-checkbox-unchecked-bg !important;
}
&-arrow {
&.has-child {
&.rtl:after, &:after {
border-color: $color-node-arrow !important;
}
}
}
}

View File

@@ -1,265 +1,283 @@
import 'mocha'; import 'mocha';
import { expect } from 'chai'; import { expect } from 'chai';
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 '@tests/unit/stubs/CategoryStub'; import { CategoryStub } from '@tests/unit/stubs/CategoryStub';
import { CategoryCollectionStub } from '@tests/unit/stubs/CategoryCollectionStub'; import { CategoryCollectionStub } from '@tests/unit/stubs/CategoryCollectionStub';
import { SelectedScriptStub } from '@tests/unit/stubs/SelectedScriptStub'; import { SelectedScriptStub } from '@tests/unit/stubs/SelectedScriptStub';
import { ScriptStub } from '@tests/unit/stubs/ScriptStub'; import { ScriptStub } from '@tests/unit/stubs/ScriptStub';
import { UserSelectionTestRunner } from './UserSelectionTestRunner';
describe('UserSelection', () => { describe('UserSelection', () => {
describe('ctor', () => { describe('ctor', () => {
it('has nothing with no initial selection', () => { describe('has nothing with no initial selection', () => {
// arrange // arrange
const collection = new CategoryCollectionStub().withAction(new CategoryStub(1).withScriptIds('s1')); const allScripts = [
const selection = []; new SelectedScriptStub('s1', false),
];
new UserSelectionTestRunner()
.withSelectedScripts([])
.withCategory(1, allScripts.map((s) => s.script))
// act // act
const sut = new UserSelection(collection, selection); .run()
// assert // assert
expect(sut.selectedScripts).to.have.lengthOf(0); .expectFinalScripts([]);
}); });
it('has initial selection', () => { describe('has initial selection', () => {
// arrange // arrange
const firstScript = new ScriptStub('1'); const scripts = [
const secondScript = new ScriptStub('2'); new SelectedScriptStub('s1', false),
const collection = new CategoryCollectionStub() new SelectedScriptStub('s2', false),
.withAction(new CategoryStub(1).withScript(firstScript).withScripts(secondScript)); ];
const expected = [ new SelectedScript(firstScript, false), new SelectedScript(secondScript, true) ]; new UserSelectionTestRunner()
.withSelectedScripts(scripts)
.withCategory(1, scripts.map((s) => s.script))
// act // act
const sut = new UserSelection(collection, expected); .run()
// assert // assert
expect(sut.selectedScripts).to.deep.include(expected[0]); .expectFinalScripts(scripts);
expect(sut.selectedScripts).to.deep.include(expected[1]);
}); });
}); });
it('deselectAll removes all items', () => { describe('deselectAll removes all items', () => {
// arrange // arrange
const events: Array<readonly SelectedScript[]> = []; const allScripts = [
const collection = new CategoryCollectionStub() new SelectedScriptStub('s1', false),
.withAction(new CategoryStub(1) new SelectedScriptStub('s2', false),
.withScriptIds('s1', 's2', 's3', 's4')); new SelectedScriptStub('s3', false),
const selectedScripts = [ new SelectedScriptStub('s4', false),
new SelectedScriptStub('s1'), new SelectedScriptStub('s2'), new SelectedScriptStub('s3'),
]; ];
const sut = new UserSelection(collection, selectedScripts); const selectedScripts = allScripts.filter(
sut.changed.on((newScripts) => events.push(newScripts)); (s) => ['s1', 's2', 's3'].includes(s.id));
new UserSelectionTestRunner()
.withSelectedScripts(selectedScripts)
.withCategory(1, allScripts.map((s) => s.script))
// act // act
sut.deselectAll(); .run((sut) => {
sut.deselectAll();
})
// assert // assert
expect(sut.selectedScripts).to.have.length(0); .expectTotalFiredEvents(1)
expect(events).to.have.lengthOf(1); .expectFinalScripts([])
expect(events[0]).to.have.length(0); .expectFinalScriptsInEvent(0, []);
}); });
it('selectOnly selects expected', () => { describe('selectOnly selects expected', () => {
// arrange // arrange
const events: Array<readonly SelectedScript[]> = []; const allScripts = [
const collection = new CategoryCollectionStub() new SelectedScriptStub('s1', false),
.withAction(new CategoryStub(1) new SelectedScriptStub('s2', false),
.withScriptIds('s1', 's2', 's3', 's4')); new SelectedScriptStub('s3', false),
const selectedScripts = [ new SelectedScriptStub('s4', false),
new SelectedScriptStub('s1'), new SelectedScriptStub('s2'), new SelectedScriptStub('s3'),
]; ];
const sut = new UserSelection(collection, selectedScripts); const selectedScripts = allScripts.filter(
sut.changed.on((newScripts) => events.push(newScripts)); (s) => ['s1', 's2', 's3'].includes(s.id));
const scripts = [new ScriptStub('s2'), new ScriptStub('s3'), new ScriptStub('s4')]; const scriptsToSelect = allScripts.filter(
const expected = [ new SelectedScriptStub('s2'), new SelectedScriptStub('s3'), (s) => ['s2', 's3', 's4'].includes(s.id));
new SelectedScript(scripts[2], false)]; new UserSelectionTestRunner()
.withSelectedScripts(selectedScripts)
.withCategory(1, allScripts.map((s) => s.script))
// act // act
sut.selectOnly(scripts); .run((sut) => {
sut.selectOnly(scriptsToSelect.map((s) => s.script));
})
// assert // assert
expect(sut.selectedScripts).to.have.deep.members(expected, .expectTotalFiredEvents(1)
`Expected: ${JSON.stringify(sut.selectedScripts)}\n` + .expectFinalScripts(scriptsToSelect)
`Actual: ${JSON.stringify(expected)}`); .expectFinalScriptsInEvent(0, scriptsToSelect);
expect(events).to.have.lengthOf(1);
expect(events[0]).to.deep.equal(expected);
}); });
it('selectAll selects as expected', () => { describe('selectAll selects as expected', () => {
// arrange // arrange
const events: Array<readonly SelectedScript[]> = []; const expected = [
const scripts: IScript[] = [new ScriptStub('s1'), new ScriptStub('s2'), new ScriptStub('s3'), new ScriptStub('s4')]; new SelectedScriptStub('s1', false),
const collection = new CategoryCollectionStub() new SelectedScriptStub('s2', false),
.withAction(new CategoryStub(1) ];
.withScripts(...scripts)); new UserSelectionTestRunner()
const sut = new UserSelection(collection, []); .withSelectedScripts([])
sut.changed.on((newScripts) => events.push(newScripts)); .withCategory(1, expected.map((s) => s.script))
const expected = scripts.map((script) => new SelectedScript(script, false));
// act // act
sut.selectAll(); .run((sut) => {
sut.selectAll();
})
// assert // assert
expect(sut.selectedScripts).to.deep.equal(expected); .expectTotalFiredEvents(1)
expect(events).to.have.lengthOf(1); .expectFinalScripts(expected)
expect(events[0]).to.deep.equal(expected); .expectFinalScriptsInEvent(0, expected);
}); });
describe('addOrUpdateSelectedScript', () => { describe('addOrUpdateSelectedScript', () => {
it('adds when item does not exist', () => { describe('adds when item does not exist', () => {
// arrange // arrange
const events: Array<readonly SelectedScript[]> = []; const scripts = [new ScriptStub('s1'), new ScriptStub('s2')];
const collection = new CategoryCollectionStub() const expected = [ new SelectedScript(scripts[0], false) ];
.withAction(new CategoryStub(1) new UserSelectionTestRunner()
.withScripts(new ScriptStub('s1'), new ScriptStub('s2'))); .withSelectedScripts([])
const sut = new UserSelection(collection, []); .withCategory(1, scripts)
sut.changed.on((scripts) => events.push(scripts));
const expected = [ new SelectedScript(new ScriptStub('s1'), false) ];
// act // act
sut.addOrUpdateSelectedScript('s1', false); .run((sut) => {
sut.addOrUpdateSelectedScript(scripts[0].id, false);
})
// assert // assert
expect(sut.selectedScripts).to.deep.equal(expected); .expectTotalFiredEvents(1)
expect(events).to.have.lengthOf(1); .expectFinalScripts(expected)
expect(events[0]).to.deep.equal(expected); .expectFinalScriptsInEvent(0, expected);
}); });
it('updates when item exists', () => { describe('updates when item exists', () => {
// arrange // arrange
const events: Array<readonly SelectedScript[]> = []; const scripts = [new ScriptStub('s1'), new ScriptStub('s2')];
const collection = new CategoryCollectionStub() const existing = new SelectedScript(scripts[0], false);
.withAction(new CategoryStub(1) const expected = new SelectedScript(scripts[0], true);
.withScripts(new ScriptStub('s1'), new ScriptStub('s2'))); new UserSelectionTestRunner()
const sut = new UserSelection(collection, []); .withSelectedScripts([existing])
sut.changed.on((scripts) => events.push(scripts)); .withCategory(1, scripts)
const expected = [ new SelectedScript(new ScriptStub('s1'), true) ];
// act // act
sut.addOrUpdateSelectedScript('s1', true); .run((sut) => {
sut.addOrUpdateSelectedScript(expected.id, expected.revert);
})
// assert // assert
expect(sut.selectedScripts).to.deep.equal(expected); .expectTotalFiredEvents(1)
expect(events).to.have.lengthOf(1); .expectFinalScripts([ expected ])
expect(events[0]).to.deep.equal(expected); .expectFinalScriptsInEvent(0, [ expected ]);
}); });
}); });
describe('removeAllInCategory', () => { describe('removeAllInCategory', () => {
it('does nothing when nothing exists', () => { describe('does nothing when nothing exists', () => {
// arrange // arrange
const events: Array<readonly SelectedScript[]> = []; const categoryId = 99;
const categoryId = 1; const scripts = [new ScriptStub('s1'), new ScriptStub('s2')];
const collection = new CategoryCollectionStub() new UserSelectionTestRunner()
.withAction(new CategoryStub(categoryId) .withSelectedScripts([])
.withScripts(new ScriptStub('s1'), new ScriptStub('s2'))); .withCategory(categoryId, scripts)
const sut = new UserSelection(collection, []);
sut.changed.on((s) => events.push(s));
// act // act
sut.removeAllInCategory(categoryId); .run((sut) => {
sut.removeAllInCategory(categoryId);
})
// assert // assert
expect(events).to.have.lengthOf(0); .expectTotalFiredEvents(0)
expect(sut.selectedScripts).to.have.lengthOf(0); .expectFinalScripts([]);
}); });
it('removes all when all exists', () => { describe('removes all when all exists', () => {
// arrange // arrange
const categoryId = 1; const categoryId = 34;
const scripts = [new SelectedScriptStub('s1'), new SelectedScriptStub('s2')]; const scripts = [new SelectedScriptStub('s1'), new SelectedScriptStub('s2')];
const collection = new CategoryCollectionStub() new UserSelectionTestRunner()
.withAction(new CategoryStub(categoryId) .withSelectedScripts(scripts)
.withScripts(...scripts.map((script) => script.script))); .withCategory(categoryId, scripts.map((s) => s.script))
const sut = new UserSelection(collection, scripts);
// act // act
sut.removeAllInCategory(categoryId); .run((sut) => {
sut.removeAllInCategory(categoryId);
})
// assert // assert
expect(sut.selectedScripts.length).to.equal(0); .expectTotalFiredEvents(1)
.expectFinalScripts([]);
}); });
it('removes existing some exists', () => { describe('removes existing when some exists', () => {
// arrange // arrange
const categoryId = 1; const categoryId = 55;
const existing = [new ScriptStub('s1'), new ScriptStub('s2')]; const existing = [new ScriptStub('s1'), new ScriptStub('s2')];
const notExisting = [new ScriptStub('s3'), new ScriptStub('s4')]; const notExisting = [new ScriptStub('s3'), new ScriptStub('s4')];
const collection = new CategoryCollectionStub() new UserSelectionTestRunner()
.withAction(new CategoryStub(categoryId) .withSelectedScripts(existing.map((script) => new SelectedScript(script, false)))
.withScripts(...existing, ...notExisting)); .withCategory(categoryId, [ ...existing, ...notExisting ])
const sut = new UserSelection(collection, existing.map((script) => new SelectedScript(script, false)));
// act // act
sut.removeAllInCategory(categoryId); .run((sut) => {
sut.removeAllInCategory(categoryId);
})
// assert // assert
expect(sut.selectedScripts.length).to.equal(0); .expectTotalFiredEvents(1)
.expectFinalScripts([]);
}); });
}); });
describe('addOrUpdateAllInCategory', () => { describe('addOrUpdateAllInCategory', () => {
it('does nothing when all already exists', () => { describe('when all already exists', () => {
// arrange describe('does nothing if nothing is changed', () => {
const events: Array<readonly SelectedScript[]> = []; // arrange
const categoryId = 1; const categoryId = 55;
const scripts = [new ScriptStub('s1'), new ScriptStub('s2')]; const existingScripts = [
const collection = new CategoryCollectionStub() new SelectedScriptStub('s1', false),
.withAction(new CategoryStub(categoryId) new SelectedScriptStub('s2', false),
.withScripts(...scripts)); ];
const sut = new UserSelection(collection, scripts.map((script) => new SelectedScript(script, false))); new UserSelectionTestRunner()
sut.changed.on((s) => events.push(s)); .withSelectedScripts(existingScripts)
// act .withCategory(categoryId, existingScripts.map((s) => s.script))
sut.addOrUpdateAllInCategory(categoryId); // act
// assert .run((sut) => {
expect(events).to.have.lengthOf(0); sut.addOrUpdateAllInCategory(categoryId);
expect(sut.selectedScripts.map((script) => script.id)) })
.to.have.deep.members(scripts.map((script) => script.id)); // assert
.expectTotalFiredEvents(0)
.expectFinalScripts(existingScripts);
});
describe('changes revert status of all', () => {
// arrange
const newStatus = false;
const scripts = [
new SelectedScriptStub('e1', !newStatus),
new SelectedScriptStub('e2', !newStatus),
new SelectedScriptStub('e3', newStatus),
];
const expectedScripts = scripts.map((s) => new SelectedScript(s.script, newStatus));
const categoryId = 31;
new UserSelectionTestRunner()
.withSelectedScripts(scripts)
.withCategory(categoryId, scripts.map((s) => s.script))
// act
.run((sut) => {
sut.addOrUpdateAllInCategory(categoryId, newStatus);
})
// assert
.expectTotalFiredEvents(1)
.expectFinalScripts(expectedScripts)
.expectFinalScriptsInEvent(0, expectedScripts);
});
}); });
it('adds all when nothing exists', () => { describe('when nothing exists; adds all with given revert status', () => {
// arrange const revertStatuses = [ true, false ];
const categoryId = 1; for (const revertStatus of revertStatuses) {
const expected = [new ScriptStub('s1'), new ScriptStub('s2')]; describe(`when revert status is ${revertStatus}`, () => {
const collection = new CategoryCollectionStub() // arrange
.withAction(new CategoryStub(categoryId) const categoryId = 1;
.withScripts(...expected)); const scripts = [
const sut = new UserSelection(collection, []); new SelectedScriptStub('s1', !revertStatus),
// act new SelectedScriptStub('s2', !revertStatus),
sut.addOrUpdateAllInCategory(categoryId); ];
// assert const expected = scripts.map((s) => new SelectedScript(s.script, revertStatus));
expect(sut.selectedScripts.map((script) => script.id)) new UserSelectionTestRunner()
.to.have.deep.members(expected.map((script) => script.id)); .withSelectedScripts([])
.withCategory(categoryId, scripts.map((s) => s.script))
// act
.run((sut) => {
sut.addOrUpdateAllInCategory(categoryId, revertStatus);
})
// assert
.expectTotalFiredEvents(1)
.expectFinalScripts(expected)
.expectFinalScriptsInEvent(0, expected);
});
}
}); });
it('adds all with given revert status when nothing exists', () => { describe('when some exists; changes revert status of all', () => {
// arrange // arrange
const categoryId = 1; const newStatus = true;
const expected = [new ScriptStub('s1'), new ScriptStub('s2')]; const existing = [
const collection = new CategoryCollectionStub() new SelectedScriptStub('e1', true),
.withAction(new CategoryStub(categoryId) new SelectedScriptStub('e2', false),
.withScripts(...expected)); ];
const sut = new UserSelection(collection, []); const notExisting = [
// act new SelectedScriptStub('n3', true),
sut.addOrUpdateAllInCategory(categoryId, true); new SelectedScriptStub('n4', false),
// assert ];
expect(sut.selectedScripts.every((script) => script.revert))
.to.equal(true);
});
it('changes revert status of all when some exists', () => {
// arrange
const categoryId = 1;
const notExisting = [ new ScriptStub('notExisting1'), new ScriptStub('notExisting2') ];
const existing = [ new ScriptStub('existing1'), new ScriptStub('existing2') ];
const allScripts = [ ...existing, ...notExisting ]; const allScripts = [ ...existing, ...notExisting ];
const collection = new CategoryCollectionStub() const expectedScripts = allScripts.map((s) => new SelectedScript(s.script, newStatus));
.withAction(new CategoryStub(categoryId) const categoryId = 77;
.withScripts(...allScripts)); new UserSelectionTestRunner()
const sut = new UserSelection(collection, existing.map((script) => new SelectedScript(script, false))); .withSelectedScripts(existing)
.withCategory(categoryId, allScripts.map((s) => s.script))
// act // act
sut.addOrUpdateAllInCategory(categoryId, true); .run((sut) => {
sut.addOrUpdateAllInCategory(categoryId, newStatus);
})
// assert // assert
expect(sut.selectedScripts.every((script) => script.revert)) .expectTotalFiredEvents(1)
.to.equal(true); .expectFinalScripts(expectedScripts)
}); .expectFinalScriptsInEvent(0, expectedScripts);
it('changes revert status of all when some exists', () => {
// arrange
const categoryId = 1;
const notExisting = [ new ScriptStub('notExisting1'), new ScriptStub('notExisting2') ];
const existing = [ new ScriptStub('existing1'), new ScriptStub('existing2') ];
const allScripts = [ ...existing, ...notExisting ];
const collection = new CategoryCollectionStub()
.withAction(new CategoryStub(categoryId)
.withScripts(...allScripts));
const sut = new UserSelection(collection, existing.map((script) => new SelectedScript(script, false)));
// act
sut.addOrUpdateAllInCategory(categoryId, true);
// assert
expect(sut.selectedScripts.every((script) => script.revert))
.to.equal(true);
});
it('changes revert status of all when all already exists', () => {
// arrange
const categoryId = 1;
const scripts = [ new ScriptStub('existing1'), new ScriptStub('existing2') ];
const collection = new CategoryCollectionStub()
.withAction(new CategoryStub(categoryId)
.withScripts(...scripts));
const sut = new UserSelection(collection, scripts.map((script) => new SelectedScript(script, false)));
// act
sut.addOrUpdateAllInCategory(categoryId, true);
// assert
expect(sut.selectedScripts.every((script) => script.revert))
.to.equal(true);
}); });
}); });
describe('isSelected', () => { describe('isSelected', () => {

View File

@@ -0,0 +1,78 @@
import { expect } from 'chai';
import 'mocha';
import { SelectedScript } from '@/application/Context/State/Selection/SelectedScript';
import { CategoryCollectionStub } from '@tests/unit/stubs/CategoryCollectionStub';
import { CategoryStub } from '@tests/unit/stubs/CategoryStub';
import { UserSelection } from '@/application/Context/State/Selection/UserSelection';
import { IScript } from '@/domain/IScript';
export class UserSelectionTestRunner {
private readonly collection = new CategoryCollectionStub();
private existingScripts: readonly SelectedScript[] = [];
private events: Array<readonly SelectedScript[]> = [];
private sut: UserSelection;
public withCategory(categoryId: number, scripts: readonly IScript[]) {
const category = new CategoryStub(categoryId)
.withScripts(...scripts);
this.collection
.withAction(category);
return this;
}
public withSelectedScripts(existingScripts: readonly SelectedScript[]) {
this.existingScripts = existingScripts;
return this;
}
public run(runner?: (sut: UserSelection) => void) {
this.sut = this.createSut();
if (runner) {
runner(this.sut);
}
return this;
}
public expectTotalFiredEvents(amount: number) {
const testName = amount === 0 ? 'does not fire changed event' : `fires changed event ${amount} times`;
it(testName, () => {
expect(this.events).to.have.lengthOf(amount);
});
return this;
}
public expectFinalScripts(finalScripts: readonly SelectedScript[]) {
expectSameScripts(finalScripts, this.sut.selectedScripts);
return this;
}
public expectFinalScriptsInEvent(eventIndex: number, finalScripts: readonly SelectedScript[]) {
expectSameScripts(this.events[eventIndex], finalScripts);
return this;
}
private createSut(): UserSelection {
const sut = new UserSelection(this.collection, this.existingScripts);
sut.changed.on((s) => this.events.push(s));
return sut;
}
}
function expectSameScripts(actual: readonly SelectedScript[], expected: readonly SelectedScript[]) {
it('has same expected scripts', () => {
const existingScriptIds = expected.map((script) => script.id).sort();
const expectedScriptIds = actual.map((script) => script.id).sort();
expect(existingScriptIds).to.deep.equal(expectedScriptIds);
});
it('has expected revert state', () => {
const scriptsWithDifferentStatus = actual
.filter((script) => {
const other = expected.find((existing) => existing.id === script.id);
if (!other) {
throw new Error(`Script "${script.id}" does not exist in expected scripts: ${JSON.stringify(expected, null, '\t')}`);
}
return script.revert !== other.revert;
});
expect(scriptsWithDifferentStatus).to.have
.lengthOf(0, 'Scripts with different statuses:\n' + scriptsWithDifferentStatus
.map((s) =>
`[id: ${s.id}, actual status: ${s.revert}, ` +
`expected status: ${expected.find((existing) => existing.id === s.id).revert}]`)
.join(' , '),
);
});
}

View File

@@ -0,0 +1,69 @@
import 'mocha';
import { expect } from 'chai';
import WindowsData from 'raw-loader!@/application/collections/windows.yaml';
import MacOsData from 'raw-loader!@/application/collections/macos.yaml';
/*
A common mistake when working with yaml files to forget mentioning that a value should
be interpreted as multi-line string using "|".
E.g.
```
code: |-
echo Hello
echo World
```
If "|" is missing then the code is inlined like `echo Hello echo World``, which can be
unintended. This test checks for similar issues in collection yaml files.
These tests can be considered as "linter" more than "unit-test" and therefore can lead
to false-positives.
*/
describe('collection files to have no unintended inlining', async () => {
// arrange
const testCases = [ {
name: 'macos',
fileContent: MacOsData,
}, {
name: 'windows',
fileContent: WindowsData,
},
];
for (const testCase of testCases) {
it(`${testCase.name}`, async () => {
const lines = await findBadLineNumbers(testCase.fileContent);
expect(lines).to.be.have.lengthOf(0,
`Did you intend to have multi-lined string in lines: `
+ lines.map(((line) => line.toString())).join(', '),
);
});
}
});
async function findBadLineNumbers(fileContent: string): Promise<number[]> {
return [
...findLineNumbersEndingWith(fileContent, 'revertCode:'),
...findLineNumbersEndingWith(fileContent, 'code:'),
];
}
function findLineNumbersEndingWith(content: string, ending: string): number[] {
sanityCheck(content, ending);
const lines = content.split(/\r\n|\r|\n/);
const results = new Array<number>();
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
if (line.trim().endsWith(ending)) {
results.push((i + 1 /* first line is 1 not 0 */));
}
}
return results;
}
function sanityCheck(content: string, ending: string): void {
if (!content.includes(ending)) {
throw new Error(
`File does not contain string "${ending}" string at all.`
+ `Did the word "${ending}" change? Or is this sanity check wrong?`,
);
}
}

View File

@@ -0,0 +1,4 @@
declare module 'raw-loader!@/*' {
const contents: string;
export default contents;
}