From bdcdd1d26a89bb68e6c41aed304a8e403d7d6fa5 Mon Sep 17 00:00:00 2001 From: Kar Rui Lau Date: Thu, 9 Dec 2021 10:27:50 +0800 Subject: [PATCH] chore: remove e2e tests (#3146) * feat: remove tests/end-to-end folders * chore(ci): remove test-e2e ci job * feat(package.json): remove testcafe packages and related scripts * fix: add missing lodash types package due to removal of testcafe * docs: update README entry on end to end tests * chore(ci/mergify): remove test-e2e condition to auto-merge --- .github/mergify.yml | 2 - .github/workflows/ci.yml | 18 - README.md | 12 +- package-lock.json | 2842 ++--------------- package.json | 8 +- shared/package-lock.json | 6 + shared/package.json | 1 + .../bounce/__tests__/bounce.service.spec.ts | 2 +- tests/end-to-end/.eslintrc | 7 - tests/end-to-end/email-submission.e2e.js | 250 -- tests/end-to-end/encrypt-submission.e2e.js | 236 -- .../files/att-folder-1/test-att.txt | 1 - .../files/att-folder-2/test-att.txt | 1 - .../files/att-folder-3/test-att.txt | 1 - tests/end-to-end/files/logo.jpg | Bin 32796 -> 0 bytes tests/end-to-end/helpers/all-fields.js | 125 - tests/end-to-end/helpers/all-hidden-form.js | 67 - .../end-to-end/helpers/disabled-form-basic.js | 28 - .../helpers/disabled-form-chained.js | 62 - tests/end-to-end/helpers/email-mode.js | 102 - tests/end-to-end/helpers/encrypt-mode.js | 252 -- tests/end-to-end/helpers/get-mongo-binary.js | 17 - tests/end-to-end/helpers/myinfo-form.js | 24 - tests/end-to-end/helpers/selectors.js | 228 -- tests/end-to-end/helpers/template-fields.js | 106 - tests/end-to-end/helpers/triple-attachment.js | 34 - tests/end-to-end/helpers/util.js | 1281 -------- .../helpers/verifiable-email-field.js | 12 - tests/end-to-end/login.e2e.js | 211 -- 29 files changed, 202 insertions(+), 5734 deletions(-) delete mode 100644 tests/end-to-end/.eslintrc delete mode 100644 tests/end-to-end/email-submission.e2e.js delete mode 100644 tests/end-to-end/encrypt-submission.e2e.js delete mode 100644 tests/end-to-end/files/att-folder-1/test-att.txt delete mode 100644 tests/end-to-end/files/att-folder-2/test-att.txt delete mode 100644 tests/end-to-end/files/att-folder-3/test-att.txt delete mode 100644 tests/end-to-end/files/logo.jpg delete mode 100644 tests/end-to-end/helpers/all-fields.js delete mode 100644 tests/end-to-end/helpers/all-hidden-form.js delete mode 100644 tests/end-to-end/helpers/disabled-form-basic.js delete mode 100644 tests/end-to-end/helpers/disabled-form-chained.js delete mode 100644 tests/end-to-end/helpers/email-mode.js delete mode 100644 tests/end-to-end/helpers/encrypt-mode.js delete mode 100644 tests/end-to-end/helpers/get-mongo-binary.js delete mode 100644 tests/end-to-end/helpers/myinfo-form.js delete mode 100644 tests/end-to-end/helpers/selectors.js delete mode 100644 tests/end-to-end/helpers/template-fields.js delete mode 100644 tests/end-to-end/helpers/triple-attachment.js delete mode 100644 tests/end-to-end/helpers/util.js delete mode 100644 tests/end-to-end/helpers/verifiable-email-field.js delete mode 100644 tests/end-to-end/login.e2e.js diff --git a/.github/mergify.yml b/.github/mergify.yml index a2d4814bc8..f5ca2ad058 100644 --- a/.github/mergify.yml +++ b/.github/mergify.yml @@ -6,7 +6,6 @@ pull_request_rules: - check-success~=build - check-success~=test-frontend - check-success~=test-backend - - check-success~=test-e2e - check-success~=Analyze # CodeQL / Analyze - check-success~=CodeQL # CodeQL code scanning results - check-success~=GitGuardian @@ -26,7 +25,6 @@ pull_request_rules: - check-success~=build - check-success~=test-frontend - check-success~=test-backend - - check-success~=test-e2e - check-success~=Analyze # CodeQL / Analyze - check-success~=CodeQL # CodeQL code scanning results - check-success~=GitGuardian diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fcfd3b377c..d25d746324 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -62,24 +62,6 @@ jobs: - run: npm ci - run: npm run build - test-e2e: - needs: lint - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Use Node.js - uses: actions/setup-node@v1 - with: - node-version: '14.x' - - name: Load Node.js modules - uses: actions/cache@v2 - with: - # npm cache files are stored in `~/.npm` on Linux/macOS - path: ~/.npm - key: ${{ runner.OS }}-node-${{ hashFiles('**/package-lock.json') }} - - run: npm ci - - run: npm run test-e2e - test-frontend: needs: lint runs-on: ubuntu-latest diff --git a/README.md b/README.md index bd5efb8e73..7b584da37a 100755 --- a/README.md +++ b/README.md @@ -145,17 +145,7 @@ npm run test-ci #### End-to-end tests -```bash -npm run test-e2e -``` - -will build both the frontend and backend then run our end-to-end tests. The tests are located at [`tests/end-to-end`](./tests/end-to-end). You will need to stop the Docker dev container to be able to run the end-to-end tests. - -If you do not need to rebuild the frontend and backend, you can run - -```bash -npm run test-e2e-ci -``` +Removed in [#3146](https://github.com/opengovsg/FormSG/pull/3146). Will be reimplemented when the React app is ready. ## Architecture diff --git a/package-lock.json b/package-lock.json index ee446f51d3..d8995a6fb4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -232,34 +232,6 @@ "@babel/types": "^7.10.1" } }, - "@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.14.5.tgz", - "integrity": "sha512-YTA/Twn0vBXDVGJuAX6PwW7x5zQei1luDDo2Pl6q1qZ7hVNl0RZrhHCQG/ArGpR29Vl7ETiB8eJyrvpuRp300w==", - "dev": true, - "requires": { - "@babel/helper-explode-assignable-expression": "^7.14.5", - "@babel/types": "^7.14.5" - }, - "dependencies": { - "@babel/helper-validator-identifier": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.5.tgz", - "integrity": "sha512-5lsetuxCLilmVGyiLEfoHBRX8UCFD+1m2x3Rj97WrW3V7H3u4RWRXA4evMjImCsin2J2YT0QaVDGf+z8ondbAg==", - "dev": true - }, - "@babel/types": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.5.tgz", - "integrity": "sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.14.5", - "to-fast-properties": "^2.0.0" - } - } - } - }, "@babel/helper-compilation-targets": { "version": "7.16.3", "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.16.3.tgz", @@ -311,180 +283,6 @@ } } }, - "@babel/helper-create-class-features-plugin": { - "version": "7.14.6", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.14.6.tgz", - "integrity": "sha512-Z6gsfGofTxH/+LQXqYEK45kxmcensbzmk/oi8DmaQytlQCgqNZt9XQF8iqlI/SeXWVjaMNxvYvzaYw+kh42mDg==", - "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.14.5", - "@babel/helper-function-name": "^7.14.5", - "@babel/helper-member-expression-to-functions": "^7.14.5", - "@babel/helper-optimise-call-expression": "^7.14.5", - "@babel/helper-replace-supers": "^7.14.5", - "@babel/helper-split-export-declaration": "^7.14.5" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz", - "integrity": "sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==", - "dev": true, - "requires": { - "@babel/highlight": "^7.14.5" - } - }, - "@babel/generator": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.14.5.tgz", - "integrity": "sha512-y3rlP+/G25OIX3mYKKIOlQRcqj7YgrvHxOLbVmyLJ9bPmi5ttvUmpydVjcFjZphOktWuA7ovbx91ECloWTfjIA==", - "dev": true, - "requires": { - "@babel/types": "^7.14.5", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" - } - }, - "@babel/helper-annotate-as-pure": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.14.5.tgz", - "integrity": "sha512-EivH9EgBIb+G8ij1B2jAwSH36WnGvkQSEC6CkX/6v6ZFlw5fVOHvsgGF4uiEHO2GzMvunZb6tDLQEQSdrdocrA==", - "dev": true, - "requires": { - "@babel/types": "^7.14.5" - } - }, - "@babel/helper-function-name": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.14.5.tgz", - "integrity": "sha512-Gjna0AsXWfFvrAuX+VKcN/aNNWonizBj39yGwUzVDVTlMYJMK2Wp6xdpy72mfArFq5uK+NOuexfzZlzI1z9+AQ==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.14.5", - "@babel/template": "^7.14.5", - "@babel/types": "^7.14.5" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.14.5.tgz", - "integrity": "sha512-I1Db4Shst5lewOM4V+ZKJzQ0JGGaZ6VY1jYvMghRjqs6DWgxLCIyFt30GlnKkfUeFLpJt2vzbMVEXVSXlIFYUg==", - "dev": true, - "requires": { - "@babel/types": "^7.14.5" - } - }, - "@babel/helper-member-expression-to-functions": { - "version": "7.14.7", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.14.7.tgz", - "integrity": "sha512-TMUt4xKxJn6ccjcOW7c4hlwyJArizskAhoSTOCkA0uZ+KghIaci0Qg9R043kUMWI9mtQfgny+NQ5QATnZ+paaA==", - "dev": true, - "requires": { - "@babel/types": "^7.14.5" - } - }, - "@babel/helper-optimise-call-expression": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.14.5.tgz", - "integrity": "sha512-IqiLIrODUOdnPU9/F8ib1Fx2ohlgDhxnIDU7OEVi+kAbEZcyiF7BLU8W6PfvPi9LzztjS7kcbzbmL7oG8kD6VA==", - "dev": true, - "requires": { - "@babel/types": "^7.14.5" - } - }, - "@babel/helper-replace-supers": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.14.5.tgz", - "integrity": "sha512-3i1Qe9/8x/hCHINujn+iuHy+mMRLoc77b2nI9TB0zjH1hvn9qGlXjWlggdwUcju36PkPCy/lpM7LLUdcTyH4Ow==", - "dev": true, - "requires": { - "@babel/helper-member-expression-to-functions": "^7.14.5", - "@babel/helper-optimise-call-expression": "^7.14.5", - "@babel/traverse": "^7.14.5", - "@babel/types": "^7.14.5" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.14.5.tgz", - "integrity": "sha512-hprxVPu6e5Kdp2puZUmvOGjaLv9TCe58E/Fl6hRq4YiVQxIcNvuq6uTM2r1mT/oPskuS9CgR+I94sqAYv0NGKA==", - "dev": true, - "requires": { - "@babel/types": "^7.14.5" - } - }, - "@babel/helper-validator-identifier": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.5.tgz", - "integrity": "sha512-5lsetuxCLilmVGyiLEfoHBRX8UCFD+1m2x3Rj97WrW3V7H3u4RWRXA4evMjImCsin2J2YT0QaVDGf+z8ondbAg==", - "dev": true - }, - "@babel/highlight": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz", - "integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.14.5", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.14.7", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.7.tgz", - "integrity": "sha512-X67Z5y+VBJuHB/RjwECp8kSl5uYi0BvRbNeWqkaJCVh+LiTPl19WBUfG627psSgp9rSf6ojuXghQM3ha6qHHdA==", - "dev": true - }, - "@babel/template": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.14.5.tgz", - "integrity": "sha512-6Z3Po85sfxRGachLULUhOmvAaOo7xCvqGQtxINai2mEGPFm6pQ4z5QInFnUrRpfoSV60BnjyF5F3c+15fxFV1g==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.14.5", - "@babel/parser": "^7.14.5", - "@babel/types": "^7.14.5" - } - }, - "@babel/traverse": { - "version": "7.14.7", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.14.7.tgz", - "integrity": "sha512-9vDr5NzHu27wgwejuKL7kIOm4bwEtaPQ4Z6cpCmjSuaRqpH/7xc4qcGEscwMqlkwgcXl6MvqoAjZkQ24uSdIZQ==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.14.5", - "@babel/generator": "^7.14.5", - "@babel/helper-function-name": "^7.14.5", - "@babel/helper-hoist-variables": "^7.14.5", - "@babel/helper-split-export-declaration": "^7.14.5", - "@babel/parser": "^7.14.7", - "@babel/types": "^7.14.5", - "debug": "^4.1.0", - "globals": "^11.1.0" - } - }, - "@babel/types": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.5.tgz", - "integrity": "sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.14.5", - "to-fast-properties": "^2.0.0" - } - }, - "debug": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", - "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - } - } - }, "@babel/helper-create-regexp-features-plugin": { "version": "7.16.0", "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.16.0.tgz", @@ -691,33 +489,6 @@ } } }, - "@babel/helper-explode-assignable-expression": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.14.5.tgz", - "integrity": "sha512-Htb24gnGJdIGT4vnRKMdoXiOIlqOLmdiUYpAQ0mYfgVT/GDm8GOYhgi4GL+hMKrkiPRohO4ts34ELFsGAPQLDQ==", - "dev": true, - "requires": { - "@babel/types": "^7.14.5" - }, - "dependencies": { - "@babel/helper-validator-identifier": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.5.tgz", - "integrity": "sha512-5lsetuxCLilmVGyiLEfoHBRX8UCFD+1m2x3Rj97WrW3V7H3u4RWRXA4evMjImCsin2J2YT0QaVDGf+z8ondbAg==", - "dev": true - }, - "@babel/types": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.5.tgz", - "integrity": "sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.14.5", - "to-fast-properties": "^2.0.0" - } - } - } - }, "@babel/helper-function-name": { "version": "7.10.1", "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.10.1.tgz", @@ -738,33 +509,6 @@ "@babel/types": "^7.10.1" } }, - "@babel/helper-hoist-variables": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.14.5.tgz", - "integrity": "sha512-R1PXiz31Uc0Vxy4OEOm07x0oSjKAdPPCh3tPivn/Eo8cvz6gveAeuyUUPB21Hoiif0uoPQSSdhIPS3352nvdyQ==", - "dev": true, - "requires": { - "@babel/types": "^7.14.5" - }, - "dependencies": { - "@babel/helper-validator-identifier": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.5.tgz", - "integrity": "sha512-5lsetuxCLilmVGyiLEfoHBRX8UCFD+1m2x3Rj97WrW3V7H3u4RWRXA4evMjImCsin2J2YT0QaVDGf+z8ondbAg==", - "dev": true - }, - "@babel/types": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.5.tgz", - "integrity": "sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.14.5", - "to-fast-properties": "^2.0.0" - } - } - } - }, "@babel/helper-member-expression-to-functions": { "version": "7.10.1", "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.10.1.tgz", @@ -1010,221 +754,68 @@ "integrity": "sha512-fvoGeXt0bJc7VMWZGCAEBEMo/HAjW2mP8apF5eXK0wSqwLAVHAISCWRoLMBMUs2kqeaG77jltVqu4Hn8Egl3nA==", "dev": true }, - "@babel/helper-remap-async-to-generator": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.14.5.tgz", - "integrity": "sha512-rLQKdQU+HYlxBwQIj8dk4/0ENOUEhA/Z0l4hN8BexpvmSMN9oA9EagjnhnDpNsRdWCfjwa4mn/HyBXO9yhQP6A==", + "@babel/helper-replace-supers": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.10.1.tgz", + "integrity": "sha512-SOwJzEfpuQwInzzQJGjGaiG578UYmyi2Xw668klPWV5n07B73S0a9btjLk/52Mlcxa+5AdIYqws1KyXRfMoB7A==", + "dev": true, + "requires": { + "@babel/helper-member-expression-to-functions": "^7.10.1", + "@babel/helper-optimise-call-expression": "^7.10.1", + "@babel/traverse": "^7.10.1", + "@babel/types": "^7.10.1" + } + }, + "@babel/helper-simple-access": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.16.0.tgz", + "integrity": "sha512-o1rjBT/gppAqKsYfUdfHq5Rk03lMQrkPHG1OWzHWpLgVXRH4HnMM9Et9CVdIqwkCQlobnGHEJMsgWP/jE1zUiw==", "dev": true, "requires": { - "@babel/helper-annotate-as-pure": "^7.14.5", - "@babel/helper-wrap-function": "^7.14.5", - "@babel/types": "^7.14.5" + "@babel/types": "^7.16.0" }, "dependencies": { - "@babel/code-frame": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz", - "integrity": "sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==", - "dev": true, - "requires": { - "@babel/highlight": "^7.14.5" - } - }, - "@babel/generator": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.14.5.tgz", - "integrity": "sha512-y3rlP+/G25OIX3mYKKIOlQRcqj7YgrvHxOLbVmyLJ9bPmi5ttvUmpydVjcFjZphOktWuA7ovbx91ECloWTfjIA==", - "dev": true, - "requires": { - "@babel/types": "^7.14.5", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" - } - }, - "@babel/helper-annotate-as-pure": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.14.5.tgz", - "integrity": "sha512-EivH9EgBIb+G8ij1B2jAwSH36WnGvkQSEC6CkX/6v6ZFlw5fVOHvsgGF4uiEHO2GzMvunZb6tDLQEQSdrdocrA==", - "dev": true, - "requires": { - "@babel/types": "^7.14.5" - } - }, - "@babel/helper-function-name": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.14.5.tgz", - "integrity": "sha512-Gjna0AsXWfFvrAuX+VKcN/aNNWonizBj39yGwUzVDVTlMYJMK2Wp6xdpy72mfArFq5uK+NOuexfzZlzI1z9+AQ==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.14.5", - "@babel/template": "^7.14.5", - "@babel/types": "^7.14.5" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.14.5.tgz", - "integrity": "sha512-I1Db4Shst5lewOM4V+ZKJzQ0JGGaZ6VY1jYvMghRjqs6DWgxLCIyFt30GlnKkfUeFLpJt2vzbMVEXVSXlIFYUg==", - "dev": true, - "requires": { - "@babel/types": "^7.14.5" - } + "@babel/helper-validator-identifier": { + "version": "7.15.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz", + "integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==", + "dev": true }, - "@babel/helper-split-export-declaration": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.14.5.tgz", - "integrity": "sha512-hprxVPu6e5Kdp2puZUmvOGjaLv9TCe58E/Fl6hRq4YiVQxIcNvuq6uTM2r1mT/oPskuS9CgR+I94sqAYv0NGKA==", + "@babel/types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.16.0.tgz", + "integrity": "sha512-PJgg/k3SdLsGb3hhisFvtLOw5ts113klrpLuIPtCJIU+BB24fqq6lf8RWqKJEjzqXR9AEH1rIb5XTqwBHB+kQg==", "dev": true, "requires": { - "@babel/types": "^7.14.5" + "@babel/helper-validator-identifier": "^7.15.7", + "to-fast-properties": "^2.0.0" } - }, + } + } + }, + "@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.16.0.tgz", + "integrity": "sha512-+il1gTy0oHwUsBQZyJvukbB4vPMdcYBrFHa0Uc4AizLxbq6BOYC51Rv4tWocX9BLBDLZ4kc6qUFpQ6HRgL+3zw==", + "dev": true, + "requires": { + "@babel/types": "^7.16.0" + }, + "dependencies": { "@babel/helper-validator-identifier": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.5.tgz", - "integrity": "sha512-5lsetuxCLilmVGyiLEfoHBRX8UCFD+1m2x3Rj97WrW3V7H3u4RWRXA4evMjImCsin2J2YT0QaVDGf+z8ondbAg==", + "version": "7.15.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz", + "integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==", "dev": true }, - "@babel/helper-wrap-function": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.14.5.tgz", - "integrity": "sha512-YEdjTCq+LNuNS1WfxsDCNpgXkJaIyqco6DAelTUjT4f2KIWC1nBcaCaSdHTBqQVLnTBexBcVcFhLSU1KnYuePQ==", + "@babel/types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.16.0.tgz", + "integrity": "sha512-PJgg/k3SdLsGb3hhisFvtLOw5ts113klrpLuIPtCJIU+BB24fqq6lf8RWqKJEjzqXR9AEH1rIb5XTqwBHB+kQg==", "dev": true, "requires": { - "@babel/helper-function-name": "^7.14.5", - "@babel/template": "^7.14.5", - "@babel/traverse": "^7.14.5", - "@babel/types": "^7.14.5" - } - }, - "@babel/highlight": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz", - "integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.14.5", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.14.7", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.7.tgz", - "integrity": "sha512-X67Z5y+VBJuHB/RjwECp8kSl5uYi0BvRbNeWqkaJCVh+LiTPl19WBUfG627psSgp9rSf6ojuXghQM3ha6qHHdA==", - "dev": true - }, - "@babel/template": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.14.5.tgz", - "integrity": "sha512-6Z3Po85sfxRGachLULUhOmvAaOo7xCvqGQtxINai2mEGPFm6pQ4z5QInFnUrRpfoSV60BnjyF5F3c+15fxFV1g==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.14.5", - "@babel/parser": "^7.14.5", - "@babel/types": "^7.14.5" - } - }, - "@babel/traverse": { - "version": "7.14.7", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.14.7.tgz", - "integrity": "sha512-9vDr5NzHu27wgwejuKL7kIOm4bwEtaPQ4Z6cpCmjSuaRqpH/7xc4qcGEscwMqlkwgcXl6MvqoAjZkQ24uSdIZQ==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.14.5", - "@babel/generator": "^7.14.5", - "@babel/helper-function-name": "^7.14.5", - "@babel/helper-hoist-variables": "^7.14.5", - "@babel/helper-split-export-declaration": "^7.14.5", - "@babel/parser": "^7.14.7", - "@babel/types": "^7.14.5", - "debug": "^4.1.0", - "globals": "^11.1.0" - } - }, - "@babel/types": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.5.tgz", - "integrity": "sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.14.5", - "to-fast-properties": "^2.0.0" - } - }, - "debug": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", - "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - } - } - }, - "@babel/helper-replace-supers": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.10.1.tgz", - "integrity": "sha512-SOwJzEfpuQwInzzQJGjGaiG578UYmyi2Xw668klPWV5n07B73S0a9btjLk/52Mlcxa+5AdIYqws1KyXRfMoB7A==", - "dev": true, - "requires": { - "@babel/helper-member-expression-to-functions": "^7.10.1", - "@babel/helper-optimise-call-expression": "^7.10.1", - "@babel/traverse": "^7.10.1", - "@babel/types": "^7.10.1" - } - }, - "@babel/helper-simple-access": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.16.0.tgz", - "integrity": "sha512-o1rjBT/gppAqKsYfUdfHq5Rk03lMQrkPHG1OWzHWpLgVXRH4HnMM9Et9CVdIqwkCQlobnGHEJMsgWP/jE1zUiw==", - "dev": true, - "requires": { - "@babel/types": "^7.16.0" - }, - "dependencies": { - "@babel/helper-validator-identifier": { - "version": "7.15.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz", - "integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==", - "dev": true - }, - "@babel/types": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.16.0.tgz", - "integrity": "sha512-PJgg/k3SdLsGb3hhisFvtLOw5ts113klrpLuIPtCJIU+BB24fqq6lf8RWqKJEjzqXR9AEH1rIb5XTqwBHB+kQg==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.15.7", - "to-fast-properties": "^2.0.0" - } - } - } - }, - "@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.16.0.tgz", - "integrity": "sha512-+il1gTy0oHwUsBQZyJvukbB4vPMdcYBrFHa0Uc4AizLxbq6BOYC51Rv4tWocX9BLBDLZ4kc6qUFpQ6HRgL+3zw==", - "dev": true, - "requires": { - "@babel/types": "^7.16.0" - }, - "dependencies": { - "@babel/helper-validator-identifier": { - "version": "7.15.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz", - "integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==", - "dev": true - }, - "@babel/types": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.16.0.tgz", - "integrity": "sha512-PJgg/k3SdLsGb3hhisFvtLOw5ts113klrpLuIPtCJIU+BB24fqq6lf8RWqKJEjzqXR9AEH1rIb5XTqwBHB+kQg==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.15.7", - "to-fast-properties": "^2.0.0" + "@babel/helper-validator-identifier": "^7.15.7", + "to-fast-properties": "^2.0.0" } } } @@ -1456,43 +1047,6 @@ } } }, - "@babel/plugin-proposal-async-generator-functions": { - "version": "7.14.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.14.7.tgz", - "integrity": "sha512-RK8Wj7lXLY3bqei69/cc25gwS5puEc3dknoFPFbqfy3XxYQBQFvu4ioWpafMBAB+L9NyptQK4nMOa5Xz16og8Q==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5", - "@babel/helper-remap-async-to-generator": "^7.14.5", - "@babel/plugin-syntax-async-generators": "^7.8.4" - }, - "dependencies": { - "@babel/helper-plugin-utils": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.14.5.tgz", - "integrity": "sha512-/37qQCE3K0vvZKwoK4XU/irIJQdIfCJuhU5eKnNxpFDsOkgFaUAwbv+RYw6eYgsC0E4hS7r5KqGULUogqui0fQ==", - "dev": true - } - } - }, - "@babel/plugin-proposal-class-properties": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.14.5.tgz", - "integrity": "sha512-q/PLpv5Ko4dVc1LYMpCY7RVAAO4uk55qPwrIuJ5QJ8c6cVuAmhu7I/49JOppXL6gXf7ZHzpRVEUZdYoPLM04Gg==", - "dev": true, - "requires": { - "@babel/helper-create-class-features-plugin": "^7.14.5", - "@babel/helper-plugin-utils": "^7.14.5" - }, - "dependencies": { - "@babel/helper-plugin-utils": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.14.5.tgz", - "integrity": "sha512-/37qQCE3K0vvZKwoK4XU/irIJQdIfCJuhU5eKnNxpFDsOkgFaUAwbv+RYw6eYgsC0E4hS7r5KqGULUogqui0fQ==", - "dev": true - } - } - }, "@babel/plugin-proposal-class-static-block": { "version": "7.16.0", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.16.0.tgz", @@ -1693,25 +1247,6 @@ } } }, - "@babel/plugin-proposal-decorators": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.14.5.tgz", - "integrity": "sha512-LYz5nvQcvYeRVjui1Ykn28i+3aUiXwQ/3MGoEy0InTaz1pJo/lAzmIDXX+BQny/oufgHzJ6vnEEiXQ8KZjEVFg==", - "dev": true, - "requires": { - "@babel/helper-create-class-features-plugin": "^7.14.5", - "@babel/helper-plugin-utils": "^7.14.5", - "@babel/plugin-syntax-decorators": "^7.14.5" - }, - "dependencies": { - "@babel/helper-plugin-utils": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.14.5.tgz", - "integrity": "sha512-/37qQCE3K0vvZKwoK4XU/irIJQdIfCJuhU5eKnNxpFDsOkgFaUAwbv+RYw6eYgsC0E4hS7r5KqGULUogqui0fQ==", - "dev": true - } - } - }, "@babel/plugin-proposal-dynamic-import": { "version": "7.16.0", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.16.0.tgz", @@ -1831,94 +1366,6 @@ } } }, - "@babel/plugin-proposal-object-rest-spread": { - "version": "7.14.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.14.7.tgz", - "integrity": "sha512-082hsZz+sVabfmDWo1Oct1u1AgbKbUAyVgmX4otIc7bdsRgHBXwTwb3DpDmD4Eyyx6DNiuz5UAATT655k+kL5g==", - "dev": true, - "requires": { - "@babel/compat-data": "^7.14.7", - "@babel/helper-compilation-targets": "^7.14.5", - "@babel/helper-plugin-utils": "^7.14.5", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.14.5" - }, - "dependencies": { - "@babel/compat-data": { - "version": "7.14.7", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.14.7.tgz", - "integrity": "sha512-nS6dZaISCXJ3+518CWiBfEr//gHyMO02uDxBkXTKZDN5POruCnOZ1N4YBRZDCabwF8nZMWBpRxIicmXtBs+fvw==", - "dev": true - }, - "@babel/helper-compilation-targets": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.14.5.tgz", - "integrity": "sha512-v+QtZqXEiOnpO6EYvlImB6zCD2Lel06RzOPzmkz/D/XgQiUu3C/Jb1LOqSt/AIA34TYi/Q+KlT8vTQrgdxkbLw==", - "dev": true, - "requires": { - "@babel/compat-data": "^7.14.5", - "@babel/helper-validator-option": "^7.14.5", - "browserslist": "^4.16.6", - "semver": "^6.3.0" - } - }, - "@babel/helper-plugin-utils": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.14.5.tgz", - "integrity": "sha512-/37qQCE3K0vvZKwoK4XU/irIJQdIfCJuhU5eKnNxpFDsOkgFaUAwbv+RYw6eYgsC0E4hS7r5KqGULUogqui0fQ==", - "dev": true - }, - "@babel/helper-validator-option": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.14.5.tgz", - "integrity": "sha512-OX8D5eeX4XwcroVW45NMvoYaIuFI+GQpA2a8Gi+X/U/cDUIRsV37qQfF905F0htTRCREQIB4KqPeaveRJUl3Ow==", - "dev": true - }, - "browserslist": { - "version": "4.16.6", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.6.tgz", - "integrity": "sha512-Wspk/PqO+4W9qp5iUTJsa1B/QrYn1keNCcEP5OvP7WBwT4KaDly0uONYmC6Xa3Z5IqnUgS0KcgLYu1l74x0ZXQ==", - "dev": true, - "requires": { - "caniuse-lite": "^1.0.30001219", - "colorette": "^1.2.2", - "electron-to-chromium": "^1.3.723", - "escalade": "^3.1.1", - "node-releases": "^1.1.71" - } - }, - "caniuse-lite": { - "version": "1.0.30001244", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001244.tgz", - "integrity": "sha512-Wb4UFZPkPoJoKKVfELPWytRzpemjP/s0pe22NriANru1NoI+5bGNxzKtk7edYL8rmCWTfQO8eRiF0pn1Dqzx7Q==", - "dev": true - }, - "colorette": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.2.tgz", - "integrity": "sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==", - "dev": true - }, - "electron-to-chromium": { - "version": "1.3.775", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.775.tgz", - "integrity": "sha512-EGuiJW4yBPOTj2NtWGZcX93ZE8IGj33HJAx4d3ouE2zOfW2trbWU+t1e0yzLr1qQIw81++txbM3BH52QwSRE6Q==", - "dev": true - }, - "node-releases": { - "version": "1.1.73", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.73.tgz", - "integrity": "sha512-uW7fodD6pyW2FZNZnp/Z3hvWKeEW1Y8R1+1CnErE8cXFXzl5blBOoVB41CvMer6P6Q0S5FXDwcHgFd1Wj0U9zg==", - "dev": true - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } - } - }, "@babel/plugin-proposal-optional-catch-binding": { "version": "7.16.0", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.16.0.tgz", @@ -2426,23 +1873,6 @@ } } }, - "@babel/plugin-syntax-decorators": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.14.5.tgz", - "integrity": "sha512-c4sZMRWL4GSvP1EXy0woIP7m4jkVcEuG8R1TOZxPBPtp4FSM/kiPZub9UIs/Jrb5ZAOzvTUSGYrWsrSu1JvoPw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "dependencies": { - "@babel/helper-plugin-utils": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.14.5.tgz", - "integrity": "sha512-/37qQCE3K0vvZKwoK4XU/irIJQdIfCJuhU5eKnNxpFDsOkgFaUAwbv+RYw6eYgsC0E4hS7r5KqGULUogqui0fQ==", - "dev": true - } - } - }, "@babel/plugin-syntax-dynamic-import": { "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", @@ -2461,23 +1891,6 @@ "@babel/helper-plugin-utils": "^7.8.3" } }, - "@babel/plugin-syntax-flow": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.14.5.tgz", - "integrity": "sha512-9WK5ZwKCdWHxVuU13XNT6X73FGmutAXeor5lGFq6qhOFtMFUF4jkbijuyUdZZlpYq6E2hZeZf/u3959X9wsv0Q==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "dependencies": { - "@babel/helper-plugin-utils": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.14.5.tgz", - "integrity": "sha512-/37qQCE3K0vvZKwoK4XU/irIJQdIfCJuhU5eKnNxpFDsOkgFaUAwbv+RYw6eYgsC0E4hS7r5KqGULUogqui0fQ==", - "dev": true - } - } - }, "@babel/plugin-syntax-function-sent": { "version": "7.10.1", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-function-sent/-/plugin-syntax-function-sent-7.10.1.tgz", @@ -2513,23 +1926,6 @@ "@babel/helper-plugin-utils": "^7.8.0" } }, - "@babel/plugin-syntax-jsx": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.14.5.tgz", - "integrity": "sha512-ohuFIsOMXJnbOMRfX7/w7LocdR6R7whhuRD4ax8IipLcLPlZGJKkBxgHp++U4N/vKyU16/YDQr2f5seajD3jIw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "dependencies": { - "@babel/helper-plugin-utils": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.14.5.tgz", - "integrity": "sha512-/37qQCE3K0vvZKwoK4XU/irIJQdIfCJuhU5eKnNxpFDsOkgFaUAwbv+RYw6eYgsC0E4hS7r5KqGULUogqui0fQ==", - "dev": true - } - } - }, "@babel/plugin-syntax-logical-assignment-operators": { "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", @@ -2643,60 +2039,16 @@ "@babel/helper-plugin-utils": "^7.10.1" } }, - "@babel/plugin-transform-async-to-generator": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.14.5.tgz", - "integrity": "sha512-szkbzQ0mNk0rpu76fzDdqSyPu0MuvpXgC+6rz5rpMb5OIRxdmHfQxrktL8CYolL2d8luMCZTR0DpIMIdL27IjA==", + "@babel/plugin-transform-block-scoped-functions": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.16.0.tgz", + "integrity": "sha512-V14As3haUOP4ZWrLJ3VVx5rCnrYhMSHN/jX7z6FAt5hjRkLsb0snPCmJwSOML5oxkKO4FNoNv7V5hw/y2bjuvg==", "dev": true, "requires": { - "@babel/helper-module-imports": "^7.14.5", - "@babel/helper-plugin-utils": "^7.14.5", - "@babel/helper-remap-async-to-generator": "^7.14.5" + "@babel/helper-plugin-utils": "^7.14.5" }, "dependencies": { - "@babel/helper-module-imports": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.14.5.tgz", - "integrity": "sha512-SwrNHu5QWS84XlHwGYPDtCxcA0hrSlL2yhWYLgeOc0w7ccOl2qv4s/nARI0aYZW+bSwAL5CukeXA47B/1NKcnQ==", - "dev": true, - "requires": { - "@babel/types": "^7.14.5" - } - }, - "@babel/helper-plugin-utils": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.14.5.tgz", - "integrity": "sha512-/37qQCE3K0vvZKwoK4XU/irIJQdIfCJuhU5eKnNxpFDsOkgFaUAwbv+RYw6eYgsC0E4hS7r5KqGULUogqui0fQ==", - "dev": true - }, - "@babel/helper-validator-identifier": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.5.tgz", - "integrity": "sha512-5lsetuxCLilmVGyiLEfoHBRX8UCFD+1m2x3Rj97WrW3V7H3u4RWRXA4evMjImCsin2J2YT0QaVDGf+z8ondbAg==", - "dev": true - }, - "@babel/types": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.5.tgz", - "integrity": "sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.14.5", - "to-fast-properties": "^2.0.0" - } - } - } - }, - "@babel/plugin-transform-block-scoped-functions": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.16.0.tgz", - "integrity": "sha512-V14As3haUOP4ZWrLJ3VVx5rCnrYhMSHN/jX7z6FAt5hjRkLsb0snPCmJwSOML5oxkKO4FNoNv7V5hw/y2bjuvg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "dependencies": { - "@babel/helper-plugin-utils": { + "@babel/helper-plugin-utils": { "version": "7.14.5", "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.14.5.tgz", "integrity": "sha512-/37qQCE3K0vvZKwoK4XU/irIJQdIfCJuhU5eKnNxpFDsOkgFaUAwbv+RYw6eYgsC0E4hS7r5KqGULUogqui0fQ==", @@ -2799,42 +2151,6 @@ } } }, - "@babel/plugin-transform-exponentiation-operator": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.14.5.tgz", - "integrity": "sha512-jFazJhMBc9D27o9jDnIE5ZErI0R0m7PbKXVq77FFvqFbzvTMuv8jaAwLZ5PviOLSFttqKIW0/wxNSDbjLk0tYA==", - "dev": true, - "requires": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.14.5", - "@babel/helper-plugin-utils": "^7.14.5" - }, - "dependencies": { - "@babel/helper-plugin-utils": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.14.5.tgz", - "integrity": "sha512-/37qQCE3K0vvZKwoK4XU/irIJQdIfCJuhU5eKnNxpFDsOkgFaUAwbv+RYw6eYgsC0E4hS7r5KqGULUogqui0fQ==", - "dev": true - } - } - }, - "@babel/plugin-transform-flow-strip-types": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.14.5.tgz", - "integrity": "sha512-KhcolBKfXbvjwI3TV7r7TkYm8oNXHNBqGOy6JDVwtecFaRoKYsUUqJdS10q0YDKW1c6aZQgO+Ys3LfGkox8pXA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5", - "@babel/plugin-syntax-flow": "^7.14.5" - }, - "dependencies": { - "@babel/helper-plugin-utils": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.14.5.tgz", - "integrity": "sha512-/37qQCE3K0vvZKwoK4XU/irIJQdIfCJuhU5eKnNxpFDsOkgFaUAwbv+RYw6eYgsC0E4hS7r5KqGULUogqui0fQ==", - "dev": true - } - } - }, "@babel/plugin-transform-for-of": { "version": "7.10.1", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.10.1.tgz", @@ -3274,23 +2590,6 @@ } } }, - "@babel/plugin-transform-parameters": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.14.5.tgz", - "integrity": "sha512-Tl7LWdr6HUxTmzQtzuU14SqbgrSKmaR77M0OKyq4njZLQTPfOvzblNKyNkGwOfEFCEx7KeYHQHDI0P3F02IVkA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "dependencies": { - "@babel/helper-plugin-utils": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.14.5.tgz", - "integrity": "sha512-/37qQCE3K0vvZKwoK4XU/irIJQdIfCJuhU5eKnNxpFDsOkgFaUAwbv+RYw6eYgsC0E4hS7r5KqGULUogqui0fQ==", - "dev": true - } - } - }, "@babel/plugin-transform-property-literals": { "version": "7.16.0", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.16.0.tgz", @@ -3308,130 +2607,6 @@ } } }, - "@babel/plugin-transform-react-display-name": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.14.5.tgz", - "integrity": "sha512-07aqY1ChoPgIxsuDviptRpVkWCSbXWmzQqcgy65C6YSFOfPFvb/DX3bBRHh7pCd/PMEEYHYWUTSVkCbkVainYQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "dependencies": { - "@babel/helper-plugin-utils": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.14.5.tgz", - "integrity": "sha512-/37qQCE3K0vvZKwoK4XU/irIJQdIfCJuhU5eKnNxpFDsOkgFaUAwbv+RYw6eYgsC0E4hS7r5KqGULUogqui0fQ==", - "dev": true - } - } - }, - "@babel/plugin-transform-react-jsx": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.14.5.tgz", - "integrity": "sha512-7RylxNeDnxc1OleDm0F5Q/BSL+whYRbOAR+bwgCxIr0L32v7UFh/pz1DLMZideAUxKT6eMoS2zQH6fyODLEi8Q==", - "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.14.5", - "@babel/helper-module-imports": "^7.14.5", - "@babel/helper-plugin-utils": "^7.14.5", - "@babel/plugin-syntax-jsx": "^7.14.5", - "@babel/types": "^7.14.5" - }, - "dependencies": { - "@babel/helper-annotate-as-pure": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.14.5.tgz", - "integrity": "sha512-EivH9EgBIb+G8ij1B2jAwSH36WnGvkQSEC6CkX/6v6ZFlw5fVOHvsgGF4uiEHO2GzMvunZb6tDLQEQSdrdocrA==", - "dev": true, - "requires": { - "@babel/types": "^7.14.5" - } - }, - "@babel/helper-module-imports": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.14.5.tgz", - "integrity": "sha512-SwrNHu5QWS84XlHwGYPDtCxcA0hrSlL2yhWYLgeOc0w7ccOl2qv4s/nARI0aYZW+bSwAL5CukeXA47B/1NKcnQ==", - "dev": true, - "requires": { - "@babel/types": "^7.14.5" - } - }, - "@babel/helper-plugin-utils": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.14.5.tgz", - "integrity": "sha512-/37qQCE3K0vvZKwoK4XU/irIJQdIfCJuhU5eKnNxpFDsOkgFaUAwbv+RYw6eYgsC0E4hS7r5KqGULUogqui0fQ==", - "dev": true - }, - "@babel/helper-validator-identifier": { - "version": "7.14.8", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.8.tgz", - "integrity": "sha512-ZGy6/XQjllhYQrNw/3zfWRwZCTVSiBLZ9DHVZxn9n2gip/7ab8mv2TWlKPIBk26RwedCBoWdjLmn+t9na2Gcow==", - "dev": true - }, - "@babel/types": { - "version": "7.14.8", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.8.tgz", - "integrity": "sha512-iob4soQa7dZw8nodR/KlOQkPh9S4I8RwCxwRIFuiMRYjOzH/KJzdUfDgz6cGi5dDaclXF4P2PAhCdrBJNIg68Q==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.14.8", - "to-fast-properties": "^2.0.0" - } - } - } - }, - "@babel/plugin-transform-react-jsx-development": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.14.5.tgz", - "integrity": "sha512-rdwG/9jC6QybWxVe2UVOa7q6cnTpw8JRRHOxntG/h6g/guAOe6AhtQHJuJh5FwmnXIT1bdm5vC2/5huV8ZOorQ==", - "dev": true, - "requires": { - "@babel/plugin-transform-react-jsx": "^7.14.5" - } - }, - "@babel/plugin-transform-react-pure-annotations": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.14.5.tgz", - "integrity": "sha512-3X4HpBJimNxW4rhUy/SONPyNQHp5YRr0HhJdT2OH1BRp0of7u3Dkirc7x9FRJMKMqTBI079VZ1hzv7Ouuz///g==", - "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.14.5", - "@babel/helper-plugin-utils": "^7.14.5" - }, - "dependencies": { - "@babel/helper-annotate-as-pure": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.14.5.tgz", - "integrity": "sha512-EivH9EgBIb+G8ij1B2jAwSH36WnGvkQSEC6CkX/6v6ZFlw5fVOHvsgGF4uiEHO2GzMvunZb6tDLQEQSdrdocrA==", - "dev": true, - "requires": { - "@babel/types": "^7.14.5" - } - }, - "@babel/helper-plugin-utils": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.14.5.tgz", - "integrity": "sha512-/37qQCE3K0vvZKwoK4XU/irIJQdIfCJuhU5eKnNxpFDsOkgFaUAwbv+RYw6eYgsC0E4hS7r5KqGULUogqui0fQ==", - "dev": true - }, - "@babel/helper-validator-identifier": { - "version": "7.14.8", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.8.tgz", - "integrity": "sha512-ZGy6/XQjllhYQrNw/3zfWRwZCTVSiBLZ9DHVZxn9n2gip/7ab8mv2TWlKPIBk26RwedCBoWdjLmn+t9na2Gcow==", - "dev": true - }, - "@babel/types": { - "version": "7.14.8", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.8.tgz", - "integrity": "sha512-iob4soQa7dZw8nodR/KlOQkPh9S4I8RwCxwRIFuiMRYjOzH/KJzdUfDgz6cGi5dDaclXF4P2PAhCdrBJNIg68Q==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.14.8", - "to-fast-properties": "^2.0.0" - } - } - } - }, "@babel/plugin-transform-regenerator": { "version": "7.16.0", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.16.0.tgz", @@ -4163,31 +3338,6 @@ } } }, - "@babel/preset-flow": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/preset-flow/-/preset-flow-7.14.5.tgz", - "integrity": "sha512-pP5QEb4qRUSVGzzKx9xqRuHUrM/jEzMqdrZpdMA+oUCRgd5zM1qGr5y5+ZgAL/1tVv1H0dyk5t4SKJntqyiVtg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5", - "@babel/helper-validator-option": "^7.14.5", - "@babel/plugin-transform-flow-strip-types": "^7.14.5" - }, - "dependencies": { - "@babel/helper-plugin-utils": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.14.5.tgz", - "integrity": "sha512-/37qQCE3K0vvZKwoK4XU/irIJQdIfCJuhU5eKnNxpFDsOkgFaUAwbv+RYw6eYgsC0E4hS7r5KqGULUogqui0fQ==", - "dev": true - }, - "@babel/helper-validator-option": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.14.5.tgz", - "integrity": "sha512-OX8D5eeX4XwcroVW45NMvoYaIuFI+GQpA2a8Gi+X/U/cDUIRsV37qQfF905F0htTRCREQIB4KqPeaveRJUl3Ow==", - "dev": true - } - } - }, "@babel/preset-modules": { "version": "0.1.5", "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.5.tgz", @@ -4201,34 +3351,6 @@ "esutils": "^2.0.2" } }, - "@babel/preset-react": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.14.5.tgz", - "integrity": "sha512-XFxBkjyObLvBaAvkx1Ie95Iaq4S/GUEIrejyrntQ/VCMKUYvKLoyKxOBzJ2kjA3b6rC9/KL6KXfDC2GqvLiNqQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5", - "@babel/helper-validator-option": "^7.14.5", - "@babel/plugin-transform-react-display-name": "^7.14.5", - "@babel/plugin-transform-react-jsx": "^7.14.5", - "@babel/plugin-transform-react-jsx-development": "^7.14.5", - "@babel/plugin-transform-react-pure-annotations": "^7.14.5" - }, - "dependencies": { - "@babel/helper-plugin-utils": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.14.5.tgz", - "integrity": "sha512-/37qQCE3K0vvZKwoK4XU/irIJQdIfCJuhU5eKnNxpFDsOkgFaUAwbv+RYw6eYgsC0E4hS7r5KqGULUogqui0fQ==", - "dev": true - }, - "@babel/helper-validator-option": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.14.5.tgz", - "integrity": "sha512-OX8D5eeX4XwcroVW45NMvoYaIuFI+GQpA2a8Gi+X/U/cDUIRsV37qQfF905F0htTRCREQIB4KqPeaveRJUl3Ow==", - "dev": true - } - } - }, "@babel/runtime": { "version": "7.16.3", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.16.3.tgz", @@ -5661,18 +4783,6 @@ "integrity": "sha512-DCg+Ka+uDQ31lJ/UtEXVlaeV3d6t81gifaVWKJy4MYVVgvJttyX/viREy+If7fz+tK/gVxTGMtyrFPnm4gjrVA==", "dev": true }, - "@types/error-stack-parser": { - "version": "1.3.18", - "resolved": "https://registry.npmjs.org/@types/error-stack-parser/-/error-stack-parser-1.3.18.tgz", - "integrity": "sha1-4ByfjIXKg7YQMgxiJYsMkCat4Pc=", - "dev": true - }, - "@types/estree": { - "version": "0.0.46", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.46.tgz", - "integrity": "sha512-laIjwTQaD+5DukBZaygQ79K1Z0jb1bPEMRrkXSLjtCcZm+abyp5YbrqpSLzD42FwWW6gK/aS4NYpJ804nG2brg==", - "dev": true - }, "@types/express": { "version": "4.17.13", "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.13.tgz", @@ -5723,16 +4833,6 @@ "@types/express": "*" } }, - "@types/glob": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.4.tgz", - "integrity": "sha512-w+LsMxKyYQm347Otw+IfBXOv9UWVjpHpCDdbBMt8Kz/xbvCYNjP+0qPh91Km3iKfSRLBB0P7fAMf0KHrPu+MyA==", - "dev": true, - "requires": { - "@types/minimatch": "*", - "@types/node": "*" - } - }, "@types/graceful-fs": { "version": "4.1.4", "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.4.tgz", @@ -5955,9 +5055,9 @@ } }, "@types/lodash": { - "version": "4.14.171", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.171.tgz", - "integrity": "sha512-7eQ2xYLLI/LsicL2nejW9Wyko3lcpN6O/z0ZLHrEQsg280zIdCv1t/0m6UtBjUHokCGBQ3gYTbHzDkZ1xOBwwg==", + "version": "4.14.177", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.177.tgz", + "integrity": "sha512-0fDwydE2clKe9MNfvXHBHF9WEahRuj+msTuQqOmAApNORFvhMYZKNGGJdCzuhheVjMps/ti0Ak/iJPACMaevvw==", "dev": true }, "@types/mdast": { @@ -5975,12 +5075,6 @@ "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==", "dev": true }, - "@types/minimatch": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.5.tgz", - "integrity": "sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==", - "dev": true - }, "@types/minimist": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.1.tgz", @@ -6641,15 +5735,6 @@ "acorn-walk": "^7.1.1" } }, - "acorn-hammerhead": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/acorn-hammerhead/-/acorn-hammerhead-0.5.0.tgz", - "integrity": "sha512-TI9TFfJBfduhcM2GggayNhdYvdJ3UgS/Bu3sB7FB2AUmNCmCJ+TSOT6GXu+bodG5/xL74D5zE4XRaqyjgjsYVQ==", - "dev": true, - "requires": { - "@types/estree": "0.0.46" - } - }, "acorn-jsx": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", @@ -6719,12 +5804,6 @@ "integrity": "sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM=", "dev": true }, - "amdefine": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", - "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", - "dev": true - }, "angular": { "version": "1.8.2", "resolved": "https://registry.npmjs.org/angular/-/angular-1.8.2.tgz", @@ -6900,12 +5979,6 @@ "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", "dev": true }, - "array-find": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-find/-/array-find-1.0.0.tgz", - "integrity": "sha1-bI4obRHtdoMn+OYuzuhzU8o+eLg=", - "dev": true - }, "array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", @@ -6997,12 +6070,6 @@ "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", "dev": true }, - "array-uniq": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", - "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", - "dev": true - }, "array-unique": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", @@ -7104,30 +6171,6 @@ "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" }, - "asar": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/asar/-/asar-2.1.0.tgz", - "integrity": "sha512-d2Ovma+bfqNpvBzY/KU8oPY67ZworixTpkjSx0PCXnQi67c2cXmssaTxpFDUM0ttopXoGx/KRxNg/GDThYbXQA==", - "dev": true, - "requires": { - "@types/glob": "^7.1.1", - "chromium-pickle-js": "^0.2.0", - "commander": "^2.20.0", - "cuint": "^0.2.2", - "glob": "^7.1.3", - "minimatch": "^3.0.4", - "mkdirp": "^0.5.1", - "tmp-promise": "^1.0.5" - }, - "dependencies": { - "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true - } - } - }, "asn1": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", @@ -7181,12 +6224,6 @@ "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", "dev": true }, - "assertion-error": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", - "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", - "dev": true - }, "assign-symbols": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", @@ -7220,12 +6257,6 @@ "dev": true, "optional": true }, - "async-exit-hook": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/async-exit-hook/-/async-exit-hook-1.1.2.tgz", - "integrity": "sha1-gJXXXkiMKazuBVH+hyUhadeJz7o=", - "dev": true - }, "async-limiter": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", @@ -7591,77 +6622,10 @@ "@types/babel__traverse": "^7.0.6" } }, - "babel-plugin-module-resolver": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/babel-plugin-module-resolver/-/babel-plugin-module-resolver-4.1.0.tgz", - "integrity": "sha512-MlX10UDheRr3lb3P0WcaIdtCSRlxdQsB1sBqL7W0raF070bGl1HQQq5K3T2vf2XAYie+ww+5AKC/WrkjRO2knA==", - "dev": true, - "requires": { - "find-babel-config": "^1.2.0", - "glob": "^7.1.6", - "pkg-up": "^3.1.0", - "reselect": "^4.0.0", - "resolve": "^1.13.1" - }, - "dependencies": { - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "requires": { - "locate-path": "^3.0.0" - } - }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, - "requires": { - "p-limit": "^2.0.0" - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true - }, - "pkg-up": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-3.1.0.tgz", - "integrity": "sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==", - "dev": true, - "requires": { - "find-up": "^3.0.0" - } - } - } - }, - "babel-plugin-polyfill-corejs2": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.0.tgz", - "integrity": "sha512-wMDoBJ6uG4u4PNFh72Ty6t3EgfA91puCuAwKIazbQlci+ENb/UU9A3xG5lutjUIiXCIn1CY5L15r9LimiJyrSA==", + "babel-plugin-polyfill-corejs2": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.0.tgz", + "integrity": "sha512-wMDoBJ6uG4u4PNFh72Ty6t3EgfA91puCuAwKIazbQlci+ENb/UU9A3xG5lutjUIiXCIn1CY5L15r9LimiJyrSA==", "dev": true, "requires": { "@babel/compat-data": "^7.13.11", @@ -7696,12 +6660,6 @@ "@babel/helper-define-polyfill-provider": "^0.3.0" } }, - "babel-plugin-syntax-trailing-function-commas": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-6.22.0.tgz", - "integrity": "sha1-ugNgk3+NBuQBgKQ/4NVhb/9TLPM=", - "dev": true - }, "babel-preset-current-node-syntax": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.0.tgz", @@ -7895,12 +6853,6 @@ "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", "dev": true }, - "bin-v8-flags-filter": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/bin-v8-flags-filter/-/bin-v8-flags-filter-1.2.0.tgz", - "integrity": "sha512-g8aeYkY7GhyyKRvQMBsJQZjhm2iCX3dKYvfrMpwVR8IxmUGrkpCBFoKbB9Rh0o3sTLCjU/1tFpZ4C7j3f+D+3g==", - "dev": true - }, "binary": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/binary/-/binary-0.3.0.tgz", @@ -8007,12 +6959,6 @@ "moment": "^2.9.0" } }, - "bowser": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.11.0.tgz", - "integrity": "sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==", - "dev": true - }, "boxicons": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/boxicons/-/boxicons-1.8.0.tgz", @@ -8042,15 +6988,6 @@ "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=", "dev": true }, - "brotli": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/brotli/-/brotli-1.3.2.tgz", - "integrity": "sha1-UlqcrU/LqWR119OI9q7LE+7VL0Y=", - "dev": true, - "requires": { - "base64-js": "^1.1.2" - } - }, "browser-process-hrtime": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", @@ -8445,22 +7382,6 @@ "integrity": "sha1-KAOY5dZkvXQDi28JBRU+borxvCA=", "dev": true }, - "callsite-record": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/callsite-record/-/callsite-record-4.1.3.tgz", - "integrity": "sha512-otAcPmu8TiHZ38cIL3NjQa1nGoSQRRe8WDDUgj5ZUwJWn1wzOYBwVSJbpVyzZ0sesQeKlYsPu9DG70fhh6AK9g==", - "dev": true, - "requires": { - "@types/error-stack-parser": "^1.3.18", - "@types/lodash": "^4.14.72", - "callsite": "^1.0.0", - "chalk": "^2.4.0", - "error-stack-parser": "^1.3.3", - "highlight-es": "^1.0.0", - "lodash": "4.6.1 || ^4.16.1", - "pinkie-promise": "^2.0.0" - } - }, "callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -8546,20 +7467,6 @@ "lodash": "4.17.x" } }, - "chai": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.4.tgz", - "integrity": "sha512-yS5H68VYOCtN1cjfwumDSuzn/9c+yza4f3reKXlE5rUg7SFcCEy90gJvydNgOYtblyf4Zi6jIWRnXOgErta0KA==", - "dev": true, - "requires": { - "assertion-error": "^1.1.0", - "check-error": "^1.0.2", - "deep-eql": "^3.0.1", - "get-func-name": "^2.0.0", - "pathval": "^1.1.1", - "type-detect": "^4.0.5" - } - }, "chainsaw": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/chainsaw/-/chainsaw-0.1.0.tgz", @@ -8603,12 +7510,6 @@ "integrity": "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==", "dev": true }, - "check-error": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", - "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", - "dev": true - }, "chokidar": { "version": "3.5.2", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz", @@ -8658,24 +7559,6 @@ "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" }, - "chrome-remote-interface": { - "version": "0.30.1", - "resolved": "https://registry.npmjs.org/chrome-remote-interface/-/chrome-remote-interface-0.30.1.tgz", - "integrity": "sha512-emKaqCjYAgrT35nm6PvTUKJ++2NX9qAmrcNRPRGyryG9Kc7wlkvO0bmvEdNMrr8Bih2e149WctJZFzUiM1UNwg==", - "dev": true, - "requires": { - "commander": "2.11.x", - "ws": "^7.2.0" - }, - "dependencies": { - "commander": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.11.0.tgz", - "integrity": "sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ==", - "dev": true - } - } - }, "chrome-trace-event": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz", @@ -8685,12 +7568,6 @@ "tslib": "^1.9.0" } }, - "chromium-pickle-js": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/chromium-pickle-js/-/chromium-pickle-js-0.2.0.tgz", - "integrity": "sha1-BKEGZywYsIWrd02YPfo+oTjyIgU=", - "dev": true - }, "ci-info": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", @@ -8948,12 +7825,6 @@ "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" }, - "coffeescript": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/coffeescript/-/coffeescript-2.5.1.tgz", - "integrity": "sha512-J2jRPX0eeFh5VKyVnoLrfVFgLZtnnmp96WQSLAS8OrLm2wtQLcnikYKe1gViJKDH7vucjuhHvBKKBP3rKcD1tQ==", - "dev": true - }, "collect-v8-coverage": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", @@ -9842,35 +8713,6 @@ "randomfill": "^1.0.3" } }, - "crypto-md5": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/crypto-md5/-/crypto-md5-1.0.0.tgz", - "integrity": "sha1-zMjadQx1PH7curxUKWdHKjhOhrs=", - "dev": true - }, - "css": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/css/-/css-2.2.3.tgz", - "integrity": "sha512-0W171WccAjQGGTKLhw4m2nnl0zPHUlTO/I8td4XzJgIB8Hg3ZZx71qT4G4eX8OVsSiaAKiUMy73E3nsbPlg2DQ==", - "dev": true, - "requires": { - "inherits": "^2.0.1", - "source-map": "^0.1.38", - "source-map-resolve": "^0.5.1", - "urix": "^0.1.0" - }, - "dependencies": { - "source-map": { - "version": "0.1.43", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.43.tgz", - "integrity": "sha1-wkvBRspRfBRx9drL4lcbK3+eM0Y=", - "dev": true, - "requires": { - "amdefine": ">=0.0.4" - } - } - } - }, "css-color-names": { "version": "0.0.4", "resolved": "https://registry.npmjs.org/css-color-names/-/css-color-names-0.0.4.tgz", @@ -10158,12 +9000,6 @@ "resolved": "https://registry.npmjs.org/csv-string/-/csv-string-4.0.1.tgz", "integrity": "sha512-nCdK+EWDbqLvZ2MmVQhHTmidMEsHbK3ncgTJb4oguNRpkmH5OOr+KkDRB4nqsVrJ7oK0AdO1QEsBp0+z7KBtGQ==" }, - "cuint": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/cuint/-/cuint-0.2.2.tgz", - "integrity": "sha1-QICG1AlVDCYxFVYZ6fp7ytw7mRs=", - "dev": true - }, "cyclist": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-1.0.1.tgz", @@ -10327,26 +9163,11 @@ } } }, - "dedent": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", - "integrity": "sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw=", - "dev": true - }, "dedent-js": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dedent-js/-/dedent-js-1.0.1.tgz", "integrity": "sha1-vuX7fJ5yfYXf+iRZDRDsGrElUwU=" }, - "deep-eql": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", - "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", - "dev": true, - "requires": { - "type-detect": "^4.0.0" - } - }, "deep-extend": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", @@ -10455,73 +9276,6 @@ } } }, - "del": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/del/-/del-3.0.0.tgz", - "integrity": "sha1-U+z2mf/LyzljdpGrE7rxYIGXZuU=", - "dev": true, - "requires": { - "globby": "^6.1.0", - "is-path-cwd": "^1.0.0", - "is-path-in-cwd": "^1.0.0", - "p-map": "^1.1.1", - "pify": "^3.0.0", - "rimraf": "^2.2.8" - }, - "dependencies": { - "array-union": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", - "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", - "dev": true, - "requires": { - "array-uniq": "^1.0.1" - } - }, - "globby": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", - "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", - "dev": true, - "requires": { - "array-union": "^1.0.1", - "glob": "^7.0.3", - "object-assign": "^4.0.1", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" - }, - "dependencies": { - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true - } - } - }, - "p-map": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-1.2.0.tgz", - "integrity": "sha512-r6zKACMNhjPJMTl8KcFH4li//gkrXWfbD6feV8l6doRHlzljFWGJ2AP6iKaCJXyZmAUMOPtvbW7EXkbWO/pLEA==", - "dev": true - }, - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true - }, - "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - } - } - }, "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -10598,12 +9352,6 @@ } } }, - "device-specs": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/device-specs/-/device-specs-1.0.0.tgz", - "integrity": "sha512-fYXbFSeilT7bnKWFi4OERSPHdtaEoDGn4aUhV5Nly6/I+Tp6JZ/6Icmd7LVIF5euyodGpxz2e/bfUmDnIdSIDw==", - "dev": true - }, "devtools-protocol": { "version": "0.0.799653", "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.799653.tgz", @@ -10828,12 +9576,6 @@ "integrity": "sha512-EGuiJW4yBPOTj2NtWGZcX93ZE8IGj33HJAx4d3ouE2zOfW2trbWU+t1e0yzLr1qQIw81++txbM3BH52QwSRE6Q==", "dev": true }, - "elegant-spinner": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/elegant-spinner/-/elegant-spinner-1.0.1.tgz", - "integrity": "sha1-2wQ1IcldfjA/2PNFvtwzSc+wcp4=", - "dev": true - }, "elliptic": { "version": "6.5.4", "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", @@ -10849,12 +9591,6 @@ "minimalistic-crypto-utils": "^1.0.1" } }, - "emittery": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.4.1.tgz", - "integrity": "sha512-r4eRSeStEGf6M5SKdrQhhLK5bOwOBxQhIE3YSTnZE3GpKiLfnnhE+tPtrJE79+eDJgm39BM6LSoI8SCx4HbwlQ==", - "dev": true - }, "emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -10885,33 +9621,6 @@ "once": "^1.4.0" } }, - "endpoint-utils": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/endpoint-utils/-/endpoint-utils-1.0.2.tgz", - "integrity": "sha1-CAjDNppyfNeWejn/NOvJJriBRqg=", - "dev": true, - "requires": { - "ip": "^1.1.3", - "pinkie-promise": "^1.0.0" - }, - "dependencies": { - "pinkie": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-1.0.0.tgz", - "integrity": "sha1-Wkfyi6EBXQIBvae/DzWOR77Ix+Q=", - "dev": true - }, - "pinkie-promise": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-1.0.0.tgz", - "integrity": "sha1-0dpn9UglY7t89X8oauKCLs+/NnA=", - "dev": true, - "requires": { - "pinkie": "^1.0.0" - } - } - } - }, "engine.io": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.3.2.tgz", @@ -11108,15 +9817,6 @@ } } }, - "error-stack-parser": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-1.3.6.tgz", - "integrity": "sha1-4Oc7k+QXE40c18C3RrGkoUhUwpI=", - "dev": true, - "requires": { - "stackframe": "^0.3.1" - } - }, "es-abstract": { "version": "1.18.3", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.3.tgz", @@ -11979,15 +10679,6 @@ "integrity": "sha512-WFb4ihckKil6hu3Dp798xdzSfddwKKU3+nGniKF6HfeW6OLd2OUDEPP7TcHtB5+QXOKg2s6B2DaMPE1Nn/kxKQ==", "dev": true }, - "esotope-hammerhead": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/esotope-hammerhead/-/esotope-hammerhead-0.6.1.tgz", - "integrity": "sha512-RG4orJ1xy+zD6fTEKuDYaqCuL1ymYa1/Bp+j9c7b/u7B8yI6+Qgg8o4lT1EDAOG9eBzBtwtTWR0chqt3hr0hZw==", - "dev": true, - "requires": { - "@types/estree": "0.0.46" - } - }, "espree": { "version": "7.3.1", "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", @@ -12664,24 +11355,6 @@ } } }, - "find-babel-config": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/find-babel-config/-/find-babel-config-1.2.0.tgz", - "integrity": "sha512-jB2CHJeqy6a820ssiqwrKMeyC6nNdmrcgkKWJWmpoxpE8RKciYJXCcXRq1h2AzCo5I5BJeN2tkGEO3hLTuePRA==", - "dev": true, - "requires": { - "json5": "^0.5.1", - "path-exists": "^3.0.0" - }, - "dependencies": { - "json5": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", - "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=", - "dev": true - } - } - }, "find-cache-dir": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", @@ -13056,12 +11729,6 @@ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true }, - "get-func-name": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", - "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", - "dev": true - }, "get-intrinsic": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", @@ -13084,12 +11751,6 @@ "integrity": "sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==", "dev": true }, - "get-stdin": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", - "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=", - "dev": true - }, "get-stream": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.1.0.tgz", @@ -13468,15 +12129,6 @@ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==" }, - "graphlib": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/graphlib/-/graphlib-2.1.8.tgz", - "integrity": "sha512-jcLLfkpoVGmH7/InMC/1hIvOPSUh38oJtGhvrOFGzioE1DZ+0YW16RgmOJhHiuWTvGiJQ9Z1Ik43JvkRPRvE+A==", - "dev": true, - "requires": { - "lodash": "^4.17.15" - } - }, "growly": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz", @@ -13725,25 +12377,6 @@ "integrity": "sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ==", "dev": true }, - "highlight-es": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/highlight-es/-/highlight-es-1.0.3.tgz", - "integrity": "sha512-s/SIX6yp/5S1p8aC/NRDC1fwEb+myGIfp8/TzZz0rtAv8fzsdX7vGl3Q1TrXCsczFq8DI3CBFBCySPClfBSdbg==", - "dev": true, - "requires": { - "chalk": "^2.4.0", - "is-es2016-keyword": "^1.0.0", - "js-tokens": "^3.0.0" - }, - "dependencies": { - "js-tokens": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", - "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", - "dev": true - } - } - }, "hmac-drbg": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", @@ -14143,12 +12776,6 @@ "resolved": "https://registry.npmjs.org/humanize/-/humanize-0.0.9.tgz", "integrity": "sha1-GZT/rs3+nEQe0r2sdFK3u0yeQaQ=" }, - "humanize-duration": { - "version": "3.27.0", - "resolved": "https://registry.npmjs.org/humanize-duration/-/humanize-duration-3.27.0.tgz", - "integrity": "sha512-qLo/08cNc3Tb0uD7jK0jAcU5cnqCM0n568918E7R2XhMr/+7F37p4EY062W/stg7tmzvknNn9b/1+UhVRzsYrQ==", - "dev": true - }, "husky": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/husky/-/husky-7.0.4.tgz", @@ -14580,7 +13207,8 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.0.0.tgz", "integrity": "sha512-pJEdRugimx4fBMra5z2/5iRdZ63OhYV0vr0Dwm5+xtW4D1FvRkB8hamMIhnWfyJeDdyr/aa7BDyNbtG38VxgoQ==", - "dev": true + "dev": true, + "optional": true }, "is-dotfile": { "version": "1.0.3", @@ -14588,12 +13216,6 @@ "integrity": "sha1-pqLzL/0t+wT1yiXs0Pa4PPeYoeE=", "dev": true }, - "is-es2016-keyword": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-es2016-keyword/-/is-es2016-keyword-1.0.0.tgz", - "integrity": "sha1-9uVOEQxeT40mXmnS7Q6vjPX0dxg=", - "dev": true - }, "is-extendable": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", @@ -14606,12 +13228,6 @@ "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", "dev": true }, - "is-finite": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.1.0.tgz", - "integrity": "sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w==", - "dev": true - }, "is-fullwidth-code-point": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", @@ -14641,12 +13257,6 @@ "integrity": "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==", "dev": true }, - "is-jquery-obj": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-jquery-obj/-/is-jquery-obj-0.1.1.tgz", - "integrity": "sha512-18toSebUVF7y717dgw/Dzn6djOCqrkiDp3MhB8P6TdKyCVkbD1ZwE7Uz8Hwx6hUPTvKjbyYH9ncXT4ts4qLaSA==", - "dev": true - }, "is-negative-zero": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz", @@ -14671,30 +13281,6 @@ "integrity": "sha1-iVJojF7C/9awPsyF52ngKQMINHA=", "dev": true }, - "is-path-cwd": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz", - "integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=", - "dev": true - }, - "is-path-in-cwd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.1.tgz", - "integrity": "sha512-FjV1RTW48E7CWM7eE/J2NJvAEEVektecDBVBE5Hh3nM1Jd0kvhHtX68Pr3xsDf857xt3Y4AkwVULK1Vku62aaQ==", - "dev": true, - "requires": { - "is-path-inside": "^1.0.0" - } - }, - "is-path-inside": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", - "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", - "dev": true, - "requires": { - "path-is-inside": "^1.0.1" - } - }, "is-plain-obj": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", @@ -14779,12 +13365,6 @@ "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", "dev": true }, - "is-utf8": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", - "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", - "dev": true - }, "is-weakref": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.1.tgz", @@ -16928,15 +15508,6 @@ } } }, - "linux-platform-info": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/linux-platform-info/-/linux-platform-info-0.0.3.tgz", - "integrity": "sha1-La4yQ4Xmbj11W+yD+Gx77qYc64M=", - "dev": true, - "requires": { - "os-family": "^1.0.0" - } - }, "listr2": { "version": "3.13.4", "resolved": "https://registry.npmjs.org/listr2/-/listr2-3.13.4.tgz", @@ -17243,51 +15814,6 @@ "wrap-ansi": "^6.2.0" } }, - "log-update-async-hook": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/log-update-async-hook/-/log-update-async-hook-2.0.2.tgz", - "integrity": "sha512-HQwkKFTZeUOrDi1Duf2CSUa/pSpcaCHKLdx3D/Z16DsipzByOBffcg5y0JZA1q0n80dYgLXe2hFM9JGNgBsTDw==", - "dev": true, - "requires": { - "ansi-escapes": "^2.0.0", - "async-exit-hook": "^1.1.2", - "onetime": "^2.0.1", - "wrap-ansi": "^2.1.0" - }, - "dependencies": { - "ansi-escapes": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-2.0.0.tgz", - "integrity": "sha1-W65SvkJIeN2Xg+iRDj/Cki6DyBs=", - "dev": true - }, - "mimic-fn": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", - "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", - "dev": true - }, - "onetime": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", - "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", - "dev": true, - "requires": { - "mimic-fn": "^1.0.0" - } - }, - "wrap-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", - "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", - "dev": true, - "requires": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1" - } - } - } - }, "logform": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/logform/-/logform-2.2.0.tgz", @@ -17460,15 +15986,6 @@ "object-visit": "^1.0.0" } }, - "match-url-wildcard": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/match-url-wildcard/-/match-url-wildcard-0.0.4.tgz", - "integrity": "sha512-R1XhQaamUZPWLOPtp4ig5j+3jctN+skhgRmEQTUamMzmNtRG69QEirQs0NZKLtHMR7tzWpmtnS4Eqv65DcgXUA==", - "dev": true, - "requires": { - "escape-string-regexp": "^1.0.5" - } - }, "mathml-tag-names": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz", @@ -18022,12 +16539,6 @@ "resolved": "https://registry.npmjs.org/moment/-/moment-2.26.0.tgz", "integrity": "sha512-oIixUO+OamkUkwjhAVE18rAMfRJNsNe/Stid/gwHSOfHrOtw9EhAY2AHvdKZ/k/MggcYELFCJz/Sn2pL8b8JMw==" }, - "moment-duration-format-commonjs": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/moment-duration-format-commonjs/-/moment-duration-format-commonjs-1.0.1.tgz", - "integrity": "sha512-KhKZRH21/+ihNRWrmdNFOyBptFi7nAWZFeFsRRpXkzgk/Yublb4fxyP0jU6EY1VDxUL/VUPdCmm/wAnpbfXdfw==", - "dev": true - }, "moment-timezone": { "version": "0.5.34", "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.34.tgz", @@ -18507,12 +17018,6 @@ "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==" }, - "nanoid": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-1.3.4.tgz", - "integrity": "sha512-4ug4BsuHxiVHoRUe1ud6rUFT3WUMmjXt1W0quL0CviZQANdan7D8kqN5/maw53hmAApY/jfzMRkC57BNNs60ZQ==", - "dev": true - }, "nanomatch": { "version": "1.2.13", "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", @@ -19213,18 +17718,6 @@ "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=", "dev": true }, - "os-family": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/os-family/-/os-family-1.1.0.tgz", - "integrity": "sha512-E3Orl5pvDJXnVmpaAA2TeNNpNhTMl4o5HghuWhOivBjEiTnJSrMYSa5uZMek1lBEvu8kKEsa2YgVcGFVDqX/9w==", - "dev": true - }, - "os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", - "dev": true - }, "p-cancelable": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.0.tgz", @@ -19541,12 +18034,6 @@ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" }, - "path-is-inside": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", - "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", - "dev": true - }, "path-key": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", @@ -19564,12 +18051,6 @@ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" }, - "pathval": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", - "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", - "dev": true - }, "pbkdf2": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.1.tgz", @@ -19612,21 +18093,6 @@ "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", "dev": true }, - "pinkie": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", - "dev": true - }, - "pinkie-promise": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", - "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", - "dev": true, - "requires": { - "pinkie": "^2.0.0" - } - }, "pirates": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.1.tgz", @@ -19690,12 +18156,6 @@ } } }, - "pngjs": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-3.4.0.tgz", - "integrity": "sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==", - "dev": true - }, "pop-iterate": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/pop-iterate/-/pop-iterate-1.0.1.tgz", @@ -20275,12 +18735,6 @@ } } }, - "pretty-hrtime": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", - "integrity": "sha1-t+PqQkNaTJsnWdmeDyAesZWALuE=", - "dev": true - }, "private": { "version": "0.1.8", "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", @@ -20317,15 +18771,6 @@ "retry": "^0.12.0" } }, - "promisify-event": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/promisify-event/-/promisify-event-1.0.0.tgz", - "integrity": "sha1-vXUj6ga3AWLzcJeQFrU6aGxg6Q8=", - "dev": true, - "requires": { - "pinkie-promise": "^2.0.0" - } - }, "prompts": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.0.tgz", @@ -20572,12 +19017,6 @@ "weak-map": "^1.0.5" } }, - "qrcode-terminal": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/qrcode-terminal/-/qrcode-terminal-0.10.0.tgz", - "integrity": "sha1-p2pI4mEKGPl/o6K9UytoKs/4bFM=", - "dev": true - }, "qs": { "version": "6.5.2", "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", @@ -20663,15 +19102,6 @@ "integrity": "sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA==", "dev": true }, - "read-file-relative": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/read-file-relative/-/read-file-relative-1.2.0.tgz", - "integrity": "sha1-mPfZbqoh0rTHov69Y9L8jPNen5s=", - "dev": true, - "requires": { - "callsite": "^1.0.0" - } - }, "read-pkg-up": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", @@ -21046,21 +19476,6 @@ "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", "dev": true }, - "repeating": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/repeating/-/repeating-1.1.3.tgz", - "integrity": "sha1-PUEUIYh3U3SU+X93+Xhfq4EPpKw=", - "dev": true, - "requires": { - "is-finite": "^1.0.0" - } - }, - "replicator": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/replicator/-/replicator-1.0.5.tgz", - "integrity": "sha512-saxS4y7NFkLMa92BR4bPHR41GD+f/qoDAwD2xZmN+MpDXgibkxwLO2qk7dCHYtskSkd/bWS8Jy6kC5MZUkg1tw==", - "dev": true - }, "request": { "version": "2.88.2", "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", @@ -21166,12 +19581,6 @@ "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=" }, - "reselect": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.0.0.tgz", - "integrity": "sha512-qUgANli03jjAyGlnbYVAV5vvnOmJnODyABz51RdBN7M4WaVu8mecZWgyQNkG8Yqe3KRGRt0l4K4B3XVEULC4CA==", - "dev": true - }, "resolve": { "version": "1.17.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", @@ -21516,15 +19925,6 @@ } } }, - "sanitize-filename": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/sanitize-filename/-/sanitize-filename-1.6.3.tgz", - "integrity": "sha512-y/52Mcy7aw3gRm7IrcGDFx/bCk4AhRh2eI9luHOQM86nZsqwiRkkq2GekHXBBD+SmPidc8i2PqtYZl+pWJ8Oeg==", - "dev": true, - "requires": { - "truncate-utf8-bytes": "^1.0.0" - } - }, "saslprep": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.3.tgz", @@ -22353,12 +20753,6 @@ } } }, - "stackframe": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-0.3.1.tgz", - "integrity": "sha1-M6qE8Rd6VUjIk1Uzy/6zQgl19aQ=", - "dev": true - }, "static-extend": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", @@ -23285,936 +21679,175 @@ "dependencies": { "bl": { "version": "4.0.3", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.0.3.tgz", - "integrity": "sha512-fs4G6/Hu4/EE+F75J8DuN/0IpQqNjAdC7aEQv7Qt8MHGUH7Ckv2MwTEEeN9QehD0pfIDkMI1bkHYkKy7xHyKIg==", - "requires": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - } - } - }, - "terminal-link": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", - "integrity": "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==", - "dev": true, - "requires": { - "ansi-escapes": "^4.2.1", - "supports-hyperlinks": "^2.0.0" - } - }, - "terser": { - "version": "4.8.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-4.8.0.tgz", - "integrity": "sha512-EAPipTNeWsb/3wLPeup1tVPaXfIaU68xMnVdPafIL1TV05OhASArYyIfFvnvJCNrR2NIOvDVNNTFRa+Re2MWyw==", - "dev": true, - "requires": { - "commander": "^2.20.0", - "source-map": "~0.6.1", - "source-map-support": "~0.5.12" - }, - "dependencies": { - "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, - "terser-webpack-plugin": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.4.tgz", - "integrity": "sha512-U4mACBHIegmfoEe5fdongHESNJWqsGU+W0S/9+BmYGVQDw1+c2Ow05TpMhxjPK1sRb7cuYq1BPl1e5YHJMTCqA==", - "dev": true, - "requires": { - "cacache": "^12.0.2", - "find-cache-dir": "^2.1.0", - "is-wsl": "^1.1.0", - "schema-utils": "^1.0.0", - "serialize-javascript": "^3.1.0", - "source-map": "^0.6.1", - "terser": "^4.1.2", - "webpack-sources": "^1.4.0", - "worker-farm": "^1.7.0" - }, - "dependencies": { - "ajv": { - "version": "6.12.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.2.tgz", - "integrity": "sha512-k+V+hzjm5q/Mr8ef/1Y9goCmlsK4I6Sm74teeyGvFk1XrOsbsKLjEdrvny42CZ+a8sXbk8KWpY/bDwS+FLL2UQ==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "cacache": { - "version": "12.0.4", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.4.tgz", - "integrity": "sha512-a0tMB40oefvuInr4Cwb3GerbL9xTj1D5yg0T5xrjGCGyfvbxseIXX7BAO/u/hIXdafzOI5JC3wDwHyf24buOAQ==", - "dev": true, - "requires": { - "bluebird": "^3.5.5", - "chownr": "^1.1.1", - "figgy-pudding": "^3.5.1", - "glob": "^7.1.4", - "graceful-fs": "^4.1.15", - "infer-owner": "^1.0.3", - "lru-cache": "^5.1.1", - "mississippi": "^3.0.0", - "mkdirp": "^0.5.1", - "move-concurrently": "^1.0.1", - "promise-inflight": "^1.0.1", - "rimraf": "^2.6.3", - "ssri": "^6.0.1", - "unique-filename": "^1.1.1", - "y18n": "^4.0.0" - } - }, - "is-wsl": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", - "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=", - "dev": true - }, - "lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "requires": { - "yallist": "^3.0.2" - } - }, - "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "schema-utils": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", - "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", - "dev": true, - "requires": { - "ajv": "^6.1.0", - "ajv-errors": "^1.0.0", - "ajv-keywords": "^3.1.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "ssri": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz", - "integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==", - "dev": true, - "requires": { - "figgy-pudding": "^3.5.1" - } - } - } - }, - "test-exclude": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", - "dev": true, - "requires": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" - } - }, - "testcafe": { - "version": "1.15.1", - "resolved": "https://registry.npmjs.org/testcafe/-/testcafe-1.15.1.tgz", - "integrity": "sha512-t4OYDCFLKeQTg228ss2ZFbJFec0w68Z+LxA4BzQkp9kFXsDA4FFBHk6R+X7gQ3gv1wGfWCkggrskHDx7vjSh1A==", - "dev": true, - "requires": { - "@babel/core": "^7.12.1", - "@babel/plugin-proposal-async-generator-functions": "^7.12.1", - "@babel/plugin-proposal-class-properties": "^7.12.1", - "@babel/plugin-proposal-decorators": "^7.12.1", - "@babel/plugin-proposal-object-rest-spread": "^7.12.1", - "@babel/plugin-syntax-dynamic-import": "^7.8.3", - "@babel/plugin-syntax-import-meta": "^7.10.4", - "@babel/plugin-transform-async-to-generator": "^7.12.1", - "@babel/plugin-transform-exponentiation-operator": "^7.12.1", - "@babel/plugin-transform-for-of": "^7.12.1", - "@babel/plugin-transform-runtime": "^7.12.1", - "@babel/preset-env": "^7.12.1", - "@babel/preset-flow": "^7.12.1", - "@babel/preset-react": "^7.12.1", - "@babel/runtime": "^7.12.5", - "@types/node": "^12.20.10", - "async-exit-hook": "^1.1.2", - "babel-plugin-module-resolver": "^4.0.0", - "babel-plugin-syntax-trailing-function-commas": "^6.22.0", - "bin-v8-flags-filter": "^1.1.2", - "bowser": "^2.8.1", - "callsite": "^1.0.0", - "callsite-record": "^4.0.0", - "chai": "^4.1.2", - "chalk": "^2.3.0", - "chrome-remote-interface": "^0.30.0", - "coffeescript": "^2.3.1", - "commander": "^8.0.0", - "debug": "^4.3.1", - "dedent": "^0.4.0", - "del": "^3.0.0", - "device-specs": "^1.0.0", - "diff": "^4.0.2", - "elegant-spinner": "^1.0.1", - "emittery": "^0.4.1", - "endpoint-utils": "^1.0.2", - "error-stack-parser": "^1.3.6", - "execa": "^4.0.3", - "globby": "^11.0.4", - "graceful-fs": "^4.1.11", - "graphlib": "^2.1.5", - "humanize-duration": "^3.25.0", - "import-lazy": "^3.1.0", - "indent-string": "^1.2.2", - "is-ci": "^1.0.10", - "is-docker": "^2.0.0", - "is-glob": "^2.0.1", - "is-stream": "^2.0.0", - "json5": "^2.1.0", - "lodash": "^4.17.13", - "log-update-async-hook": "^2.0.2", - "make-dir": "^3.0.0", - "mime-db": "^1.41.0", - "moment": "^2.10.3", - "moment-duration-format-commonjs": "^1.0.0", - "mustache": "^2.1.2", - "nanoid": "^1.0.1", - "os-family": "^1.0.0", - "parse5": "^1.5.0", - "pify": "^2.3.0", - "pinkie": "^2.0.4", - "pngjs": "^3.3.1", - "pretty-hrtime": "^1.0.3", - "promisify-event": "^1.0.0", - "qrcode-terminal": "^0.10.0", - "read-file-relative": "^1.2.0", - "replicator": "^1.0.5", - "resolve-cwd": "^1.0.0", - "resolve-from": "^4.0.0", - "sanitize-filename": "^1.6.0", - "semver": "^5.6.0", - "source-map-support": "^0.5.16", - "strip-bom": "^2.0.0", - "testcafe-browser-tools": "2.0.16", - "testcafe-hammerhead": "24.4.1", - "testcafe-legacy-api": "5.0.2", - "testcafe-reporter-json": "^2.1.0", - "testcafe-reporter-list": "^2.1.0", - "testcafe-reporter-minimal": "^2.1.0", - "testcafe-reporter-spec": "^2.1.1", - "testcafe-reporter-xunit": "^2.1.0", - "time-limit-promise": "^1.0.2", - "tmp": "0.0.28", - "tree-kill": "^1.2.2", - "typescript": "^3.3.3", - "unquote": "^1.1.1" - }, - "dependencies": { - "@babel/helper-plugin-utils": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.14.5.tgz", - "integrity": "sha512-/37qQCE3K0vvZKwoK4XU/irIJQdIfCJuhU5eKnNxpFDsOkgFaUAwbv+RYw6eYgsC0E4hS7r5KqGULUogqui0fQ==", - "dev": true - }, - "@babel/plugin-transform-for-of": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.14.5.tgz", - "integrity": "sha512-CfmqxSUZzBl0rSjpoQSFoR9UEj3HzbGuGNL21/iFTmjb5gFggJp3ph0xR1YBhexmLoKRHzgxuFvty2xdSt6gTA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@types/node": { - "version": "12.20.17", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.17.tgz", - "integrity": "sha512-so8EHl4S6MmatPS0f9sE1ND94/ocbcEshW5OpyYthRqeRpiYyW2uXYTo/84kmfdfeNrDycARkvuiXl6nO40NGg==", - "dev": true - }, - "ci-info": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.6.0.tgz", - "integrity": "sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A==", - "dev": true - }, - "commander": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-8.1.0.tgz", - "integrity": "sha512-mf45ldcuHSYShkplHHGKWb4TrmwQadxOn7v4WuhDJy0ZVoY5JFajaRDKD0PNe5qXzBX0rhovjTnP6Kz9LETcuA==", - "dev": true - }, - "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } - }, - "debug": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", - "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "dedent": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.4.0.tgz", - "integrity": "sha1-h979BAvUwVldljKC7FfzwqhSVkI=", - "dev": true - }, - "execa": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz", - "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==", - "dev": true, - "requires": { - "cross-spawn": "^7.0.0", - "get-stream": "^5.0.0", - "human-signals": "^1.1.1", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.0", - "onetime": "^5.1.0", - "signal-exit": "^3.0.2", - "strip-final-newline": "^2.0.0" - } - }, - "globby": { - "version": "11.0.4", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.4.tgz", - "integrity": "sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg==", - "dev": true, - "requires": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.1.1", - "ignore": "^5.1.4", - "merge2": "^1.3.0", - "slash": "^3.0.0" - } - }, - "import-lazy": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-3.1.0.tgz", - "integrity": "sha512-8/gvXvX2JMn0F+CDlSC4l6kOmVaLOO3XLkksI7CI3Ud95KDYJuYur2b9P/PUt/i/pDAMd/DulQsNbbbmRRsDIQ==", - "dev": true - }, - "indent-string": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-1.2.2.tgz", - "integrity": "sha1-25m8xYPrarux5I3LsZmamGBBy2s=", - "dev": true, - "requires": { - "get-stdin": "^4.0.1", - "minimist": "^1.1.0", - "repeating": "^1.1.0" - } - }, - "is-ci": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-1.2.1.tgz", - "integrity": "sha512-s6tfsaQaQi3JNciBH6shVqEDvhGut0SUXr31ag8Pd8BBbVVlcGfWhpPmEOoM6RJ5TFhbypvf5yyRw/VXW1IiWg==", - "dev": true, - "requires": { - "ci-info": "^1.5.0" - } - }, - "is-extglob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", - "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", - "dev": true - }, - "is-glob": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", - "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", - "dev": true, - "requires": { - "is-extglob": "^1.0.0" - } - }, - "is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "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" - } - }, - "make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, - "requires": { - "semver": "^6.0.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } - } - }, - "mustache": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/mustache/-/mustache-2.3.2.tgz", - "integrity": "sha512-KpMNwdQsYz3O/SBS1qJ/o3sqUJ5wSb8gb0pul8CO0S56b9Y2ALm8zCfsjPXsqGFfoNBkDwZuZIAjhsZI03gYVQ==", - "dev": true - }, - "npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "requires": { - "path-key": "^3.0.0" - } - }, - "parse5": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-1.5.1.tgz", - "integrity": "sha1-m387DeMr543CQBsXVzzK8Pb1nZQ=", - "dev": true - }, - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true - }, - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true - }, - "resolve-cwd": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-1.0.0.tgz", - "integrity": "sha1-Tq7qQe0EDRcCRX32SkKysH0kb58=", - "dev": true, - "requires": { - "resolve-from": "^2.0.0" - }, - "dependencies": { - "resolve-from": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-2.0.0.tgz", - "integrity": "sha1-lICrIOlP+h2egKgEx+oUdhGWa1c=", - "dev": true - } - } - }, - "resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true - }, - "shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "requires": { - "shebang-regex": "^3.0.0" - } - }, - "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true - }, - "strip-bom": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", - "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", - "dev": true, - "requires": { - "is-utf8": "^0.2.0" - } - }, - "tmp": { - "version": "0.0.28", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.28.tgz", - "integrity": "sha1-Fyc1t/YU6nrzlmT6hM8N5OUV0SA=", - "dev": true, - "requires": { - "os-tmpdir": "~1.0.1" - } - }, - "typescript": { - "version": "3.9.10", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.10.tgz", - "integrity": "sha512-w6fIxVE/H1PkLKcCPsFqKE7Kv7QUwhU8qQY2MueZXWx5cPZdwFupLgKK3vntcK98BtNHZtAF4LA/yl2a7k8R6Q==", - "dev": true - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - } - } - }, - "testcafe-browser-tools": { - "version": "2.0.16", - "resolved": "https://registry.npmjs.org/testcafe-browser-tools/-/testcafe-browser-tools-2.0.16.tgz", - "integrity": "sha512-JljbS0FboABksIMEH1L7P4ZdI82AQ8saWb/7WsxkDCOtDuHID5ZSEb/w9tqLN1+4BQaCgS5veN3lWUnfb0saEA==", - "dev": true, - "requires": { - "array-find": "^1.0.0", - "debug": "^4.3.1", - "dedent": "^0.7.0", - "del": "^5.1.0", - "execa": "^3.3.0", - "graceful-fs": "^4.1.11", - "linux-platform-info": "^0.0.3", - "lodash": "^4.17.15", - "mkdirp": "^0.5.1", - "mustache": "^2.1.2", - "nanoid": "^2.1.3", - "os-family": "^1.0.0", - "pify": "^2.3.0", - "pinkie": "^2.0.1", - "read-file-relative": "^1.2.0", - "which-promise": "^1.0.0" - }, - "dependencies": { - "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } - }, - "debug": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", - "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "del": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/del/-/del-5.1.0.tgz", - "integrity": "sha512-wH9xOVHnczo9jN2IW68BabcecVPxacIA3g/7z6vhSU/4stOKQzeCRK0yD0A24WiAAUJmmVpWqrERcTxnLo3AnA==", - "dev": true, - "requires": { - "globby": "^10.0.1", - "graceful-fs": "^4.2.2", - "is-glob": "^4.0.1", - "is-path-cwd": "^2.2.0", - "is-path-inside": "^3.0.1", - "p-map": "^3.0.0", - "rimraf": "^3.0.0", - "slash": "^3.0.0" - } - }, - "execa": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-3.4.0.tgz", - "integrity": "sha512-r9vdGQk4bmCuK1yKQu1KTwcT2zwfWdbdaXfCtAh+5nU/4fSX+JAb7vZGvI5naJrQlvONrEB20jeruESI69530g==", - "dev": true, - "requires": { - "cross-spawn": "^7.0.0", - "get-stream": "^5.0.0", - "human-signals": "^1.1.1", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.0", - "onetime": "^5.1.0", - "p-finally": "^2.0.0", - "signal-exit": "^3.0.2", - "strip-final-newline": "^2.0.0" - } - }, - "globby": { - "version": "10.0.2", - "resolved": "https://registry.npmjs.org/globby/-/globby-10.0.2.tgz", - "integrity": "sha512-7dUi7RvCoT/xast/o/dLN53oqND4yk0nsHkhRgn9w65C4PofCLOoJ39iSOg+qVDdWQPIEj+eszMHQ+aLVwwQSg==", - "dev": true, - "requires": { - "@types/glob": "^7.1.1", - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.0.3", - "glob": "^7.1.3", - "ignore": "^5.1.1", - "merge2": "^1.2.3", - "slash": "^3.0.0" - } - }, - "is-path-cwd": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz", - "integrity": "sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==", - "dev": true - }, - "is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true - }, - "is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true - }, - "mustache": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/mustache/-/mustache-2.3.2.tgz", - "integrity": "sha512-KpMNwdQsYz3O/SBS1qJ/o3sqUJ5wSb8gb0pul8CO0S56b9Y2ALm8zCfsjPXsqGFfoNBkDwZuZIAjhsZI03gYVQ==", - "dev": true - }, - "nanoid": { - "version": "2.1.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-2.1.11.tgz", - "integrity": "sha512-s/snB+WGm6uwi0WjsZdaVcuf3KJXlfGl2LcxgwkEwJF0D/BWzVWAZW/XY4bFaiR7s0Jk3FPvlnepg1H1b1UwlA==", - "dev": true - }, - "npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "requires": { - "path-key": "^3.0.0" - } - }, - "p-finally": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-2.0.1.tgz", - "integrity": "sha512-vpm09aKwq6H9phqRQzecoDpD8TmVyGw70qmWlyq5onxY7tqyTTFVvxMykxQSQKILBSFlbXpypIw2T1Ml7+DDtw==", - "dev": true - }, - "p-map": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", - "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", - "dev": true, - "requires": { - "aggregate-error": "^3.0.0" - } - }, - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true - }, - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true - }, - "shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, + "resolved": "https://registry.npmjs.org/bl/-/bl-4.0.3.tgz", + "integrity": "sha512-fs4G6/Hu4/EE+F75J8DuN/0IpQqNjAdC7aEQv7Qt8MHGUH7Ckv2MwTEEeN9QehD0pfIDkMI1bkHYkKy7xHyKIg==", "requires": { - "shebang-regex": "^3.0.0" + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" } }, - "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", "requires": { - "isexe": "^2.0.0" + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" } } } }, - "testcafe-hammerhead": { - "version": "24.4.1", - "resolved": "https://registry.npmjs.org/testcafe-hammerhead/-/testcafe-hammerhead-24.4.1.tgz", - "integrity": "sha512-+H3+tz4n3hoFVDHsyNXHkNOi5QYVBWGcCXu2kxMwNsfsk31njjJvmPPl3Ew1trpkWgnekwZVGRPJzQwcmbsJZQ==", + "terminal-link": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", + "integrity": "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==", + "dev": true, + "requires": { + "ansi-escapes": "^4.2.1", + "supports-hyperlinks": "^2.0.0" + } + }, + "terser": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-4.8.0.tgz", + "integrity": "sha512-EAPipTNeWsb/3wLPeup1tVPaXfIaU68xMnVdPafIL1TV05OhASArYyIfFvnvJCNrR2NIOvDVNNTFRa+Re2MWyw==", "dev": true, "requires": { - "acorn-hammerhead": "0.5.0", - "asar": "^2.0.1", - "bowser": "1.6.0", - "brotli": "^1.3.1", - "crypto-md5": "^1.0.0", - "css": "2.2.3", - "debug": "4.3.1", - "esotope-hammerhead": "0.6.1", - "http-cache-semantics": "^4.1.0", - "iconv-lite": "0.5.1", - "lodash": "^4.17.20", - "lru-cache": "2.6.3", - "match-url-wildcard": "0.0.4", - "merge-stream": "^1.0.1", - "mime": "~1.4.1", - "mustache": "^2.1.1", - "nanoid": "^3.1.12", - "os-family": "^1.0.0", - "parse5": "2.2.3", - "pinkie": "2.0.4", - "read-file-relative": "^1.2.0", - "semver": "5.5.0", - "tough-cookie": "2.3.3", - "tunnel-agent": "0.6.0", - "webauth": "^1.1.0" + "commander": "^2.20.0", + "source-map": "~0.6.1", + "source-map-support": "~0.5.12" }, "dependencies": { - "bowser": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/bowser/-/bowser-1.6.0.tgz", - "integrity": "sha1-N/w4e2Fstq7zcNq01r1AK3TFxU0=", + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "dev": true }, - "debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "terser-webpack-plugin": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.4.tgz", + "integrity": "sha512-U4mACBHIegmfoEe5fdongHESNJWqsGU+W0S/9+BmYGVQDw1+c2Ow05TpMhxjPK1sRb7cuYq1BPl1e5YHJMTCqA==", + "dev": true, + "requires": { + "cacache": "^12.0.2", + "find-cache-dir": "^2.1.0", + "is-wsl": "^1.1.0", + "schema-utils": "^1.0.0", + "serialize-javascript": "^3.1.0", + "source-map": "^0.6.1", + "terser": "^4.1.2", + "webpack-sources": "^1.4.0", + "worker-farm": "^1.7.0" + }, + "dependencies": { + "ajv": { + "version": "6.12.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.2.tgz", + "integrity": "sha512-k+V+hzjm5q/Mr8ef/1Y9goCmlsK4I6Sm74teeyGvFk1XrOsbsKLjEdrvny42CZ+a8sXbk8KWpY/bDwS+FLL2UQ==", "dev": true, "requires": { - "ms": "2.1.2" + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" } }, - "iconv-lite": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.5.1.tgz", - "integrity": "sha512-ONHr16SQvKZNSqjQT9gy5z24Jw+uqfO02/ngBSBoqChZ+W8qXX7GPRa1RoUnzGADw8K63R1BXUMzarCVQBpY8Q==", + "cacache": { + "version": "12.0.4", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.4.tgz", + "integrity": "sha512-a0tMB40oefvuInr4Cwb3GerbL9xTj1D5yg0T5xrjGCGyfvbxseIXX7BAO/u/hIXdafzOI5JC3wDwHyf24buOAQ==", "dev": true, "requires": { - "safer-buffer": ">= 2.1.2 < 3" + "bluebird": "^3.5.5", + "chownr": "^1.1.1", + "figgy-pudding": "^3.5.1", + "glob": "^7.1.4", + "graceful-fs": "^4.1.15", + "infer-owner": "^1.0.3", + "lru-cache": "^5.1.1", + "mississippi": "^3.0.0", + "mkdirp": "^0.5.1", + "move-concurrently": "^1.0.1", + "promise-inflight": "^1.0.1", + "rimraf": "^2.6.3", + "ssri": "^6.0.1", + "unique-filename": "^1.1.1", + "y18n": "^4.0.0" } }, - "lru-cache": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.6.3.tgz", - "integrity": "sha1-UczQtPwMhDWH16VwnOTTt2Kb7cU=", + "is-wsl": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", + "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=", "dev": true }, - "merge-stream": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-1.0.1.tgz", - "integrity": "sha1-QEEgLVCKNCugAXQAjfDCUbjBNeE=", + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", "dev": true, "requires": { - "readable-stream": "^2.0.1" + "yallist": "^3.0.2" } }, - "mime": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", - "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==", - "dev": true - }, - "mustache": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/mustache/-/mustache-2.3.2.tgz", - "integrity": "sha512-KpMNwdQsYz3O/SBS1qJ/o3sqUJ5wSb8gb0pul8CO0S56b9Y2ALm8zCfsjPXsqGFfoNBkDwZuZIAjhsZI03gYVQ==", - "dev": true - }, - "nanoid": { - "version": "3.1.23", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.23.tgz", - "integrity": "sha512-FiB0kzdP0FFVGDKlRLEQ1BgDzU87dy5NnzjeW9YZNt+/c3+q82EQDUwniSAUxp/F0gFNI1ZhKU1FqYsMuqZVnw==", - "dev": true - }, - "parse5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-2.2.3.tgz", - "integrity": "sha1-DE/EHBAAxea5PUiwP4CDg3g06fY=", - "dev": true - }, - "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", - "dev": true - }, - "semver": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", - "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==", - "dev": true - }, - "tough-cookie": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.3.tgz", - "integrity": "sha1-C2GKVWW23qkL80JdBNVe3EdadWE=", + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", "dev": true, "requires": { - "punycode": "^1.4.1" + "glob": "^7.1.3" } - } - } - }, - "testcafe-legacy-api": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/testcafe-legacy-api/-/testcafe-legacy-api-5.0.2.tgz", - "integrity": "sha512-2BWjCIN5YOUOTyOT4B0wy2TiaJgV8dWhIGpKqE3S34RjNEH62WR+JNhcnh4BSE+btp6H8n1TefcP/AObqSDSDQ==", - "dev": true, - "requires": { - "async": "0.2.6", - "dedent": "^0.6.0", - "highlight-es": "^1.0.0", - "is-jquery-obj": "^0.1.0", - "lodash": "^4.14.0", - "moment": "^2.14.1", - "mustache": "^2.2.1", - "os-family": "^1.0.0", - "parse5": "^2.1.5", - "pify": "^2.3.0", - "pinkie": "^2.0.1", - "read-file-relative": "^1.2.0", - "strip-bom": "^2.0.0", - "testcafe-hammerhead": ">=19.4.0" - }, - "dependencies": { - "async": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/async/-/async-0.2.6.tgz", - "integrity": "sha1-rT83PZJJrjJIgVZVgryQ4VKrvWg=", - "dev": true - }, - "dedent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.6.0.tgz", - "integrity": "sha1-Dm2o8M5Sg471zsXI+TlrDBtko8s=", - "dev": true - }, - "mustache": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/mustache/-/mustache-2.3.2.tgz", - "integrity": "sha512-KpMNwdQsYz3O/SBS1qJ/o3sqUJ5wSb8gb0pul8CO0S56b9Y2ALm8zCfsjPXsqGFfoNBkDwZuZIAjhsZI03gYVQ==", - "dev": true }, - "parse5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-2.2.3.tgz", - "integrity": "sha1-DE/EHBAAxea5PUiwP4CDg3g06fY=", - "dev": true + "schema-utils": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", + "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", + "dev": true, + "requires": { + "ajv": "^6.1.0", + "ajv-errors": "^1.0.0", + "ajv-keywords": "^3.1.0" + } }, - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true }, - "strip-bom": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", - "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", + "ssri": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz", + "integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==", "dev": true, "requires": { - "is-utf8": "^0.2.0" + "figgy-pudding": "^3.5.1" } } } }, - "testcafe-reporter-json": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/testcafe-reporter-json/-/testcafe-reporter-json-2.2.0.tgz", - "integrity": "sha512-wfpNaZgGP2WoqdmnIXOyxcpwSzdH1HvzXSN397lJkXOrQrwhuGUThPDvyzPnZqxZSzXdDUvIPJm55tCMWbfymQ==", - "dev": true - }, - "testcafe-reporter-list": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/testcafe-reporter-list/-/testcafe-reporter-list-2.1.0.tgz", - "integrity": "sha1-n6ifcbl9Pf5ktDAtXiJ97mmuxrk=", - "dev": true - }, - "testcafe-reporter-minimal": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/testcafe-reporter-minimal/-/testcafe-reporter-minimal-2.1.0.tgz", - "integrity": "sha1-Z28DVHY0FDxurzq1KGgnOkvr9CE=", - "dev": true - }, - "testcafe-reporter-spec": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/testcafe-reporter-spec/-/testcafe-reporter-spec-2.1.1.tgz", - "integrity": "sha1-gVb87Q9RMkhlWa1WC8gGdkaSdew=", - "dev": true - }, - "testcafe-reporter-xunit": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/testcafe-reporter-xunit/-/testcafe-reporter-xunit-2.1.0.tgz", - "integrity": "sha1-5tZsVyzhWvJmcGrw/WELKoQd1EM=", - "dev": true + "test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "requires": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + } }, "text-encoding": { "version": "0.7.0", @@ -24253,12 +21886,6 @@ "xtend": "~4.0.1" } }, - "time-limit-promise": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/time-limit-promise/-/time-limit-promise-1.0.4.tgz", - "integrity": "sha512-FLHDDsIDducw7MBcRWlFtW2Tm50DoKOSFf0Nzx17qwXj8REXCte0eUkHrJl9QU3Bl9arG3XNYX0PcHpZ9xyuLw==", - "dev": true - }, "timers-browserify": { "version": "2.0.12", "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.12.tgz", @@ -24279,36 +21906,6 @@ "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz", "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==" }, - "tmp-promise": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/tmp-promise/-/tmp-promise-1.1.0.tgz", - "integrity": "sha512-8+Ah9aB1IRXCnIOxXZ0uFozV1nMU5xiu7hhFVUSxZ3bYu+psD4TzagCzVbexUCgNNGJnsmNDQlS4nG3mTyoNkw==", - "dev": true, - "requires": { - "bluebird": "^3.5.0", - "tmp": "0.1.0" - }, - "dependencies": { - "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "tmp": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.1.0.tgz", - "integrity": "sha512-J7Z2K08jbGcdA1kkQpJSqLF6T0tdQqpR2pnSUXsIchbPdTI9v3e85cLW0d6WDhwuAleOV71j2xWs8qMPfK7nKw==", - "dev": true, - "requires": { - "rimraf": "^2.6.3" - } - } - } - }, "tmpl": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", @@ -24461,15 +22058,6 @@ "integrity": "sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA==", "dev": true }, - "truncate-utf8-bytes": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz", - "integrity": "sha1-QFkjkJWS1W94pYGENLC3hInKXys=", - "dev": true, - "requires": { - "utf8-byte-length": "^1.0.1" - } - }, "ts-essentials": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/ts-essentials/-/ts-essentials-9.0.0.tgz", @@ -25174,12 +22762,6 @@ "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", "dev": true }, - "utf8-byte-length": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.4.tgz", - "integrity": "sha1-9F8VDExm7uloGGUFq5P8u4rWv2E=", - "dev": true - }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -25634,12 +23216,6 @@ "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.0.tgz", "integrity": "sha512-EqPmREeOzttaLRm5HS7io98goBgZ7IVz79aDvqjD0kYXLtFZTc0T/U6wHTPKyIjb+MdN7DFIIX6hgdBEpWmfPA==" }, - "webauth": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/webauth/-/webauth-1.1.0.tgz", - "integrity": "sha1-ZHBPa4AmmGYFvDymKZUubib90QA=", - "dev": true - }, "webidl-conversions": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz", @@ -26120,40 +23696,6 @@ "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", "dev": true }, - "which-promise": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/which-promise/-/which-promise-1.0.0.tgz", - "integrity": "sha1-ILch3wWzW3Bhdv+hCwkJq6RgMDU=", - "dev": true, - "requires": { - "pify": "^2.2.0", - "pinkie-promise": "^1.0.0", - "which": "^1.1.2" - }, - "dependencies": { - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true - }, - "pinkie": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-1.0.0.tgz", - "integrity": "sha1-Wkfyi6EBXQIBvae/DzWOR77Ix+Q=", - "dev": true - }, - "pinkie-promise": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-1.0.0.tgz", - "integrity": "sha1-0dpn9UglY7t89X8oauKCLs+/NnA=", - "dev": true, - "requires": { - "pinkie": "^1.0.0" - } - } - } - }, "wide-align": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", diff --git a/package.json b/package.json index 8749f799fa..75679e75c2 100644 --- a/package.json +++ b/package.json @@ -29,12 +29,6 @@ "dev": "docker-compose up --build", "docker-dev": "npm run build-frontend-dev:watch & ts-node-dev --respawn --transpile-only --inspect=0.0.0.0 --exit-child -r dotenv/config -- src/app/server.ts", "test": "npm run test-backend && npm run test-frontend", - "download-binary": "node tests/end-to-end/helpers/get-mongo-binary.js", - "test-e2e": "npm run test-e2e-build && npm run test-e2e-ci", - "test-e2e-build": "npm run build-backend && npm run build-frontend-dev", - "test-e2e-ci": "env-cmd -f tests/.test-env --use-shell \"npm run download-binary && npm run testcafe-command\"", - "testcafe-command": "testcafe --skip-js-errors -c 2 chrome:headless ./tests/end-to-end --app \"npm run test-e2e-server\" --app-init-delay 10000", - "test-e2e-server": "concurrently --success last --kill-others \"mockpass\" \"maildev\" \"node dist/backend/src/app/server.js\" \"node ./tests/mock-webhook-server.js\"", "lint-code": "eslint src/ --quiet --fix", "lint-style": "stylelint '*/**/*.css' --quiet --fix", "lint-html": "htmlhint && prettier --write './src/public/**/*.html' --ignore-path './dist/**' --loglevel silent", @@ -181,6 +175,7 @@ "@types/ip": "^1.1.0", "@types/jest": "^27.0.3", "@types/json-stringify-safe": "^5.0.0", + "@types/lodash": "^4.14.177", "@types/mongodb": "^3.6.20", "@types/mongodb-uri": "^0.9.1", "@types/node": "^14.18.0", @@ -243,7 +238,6 @@ "supertest": "^6.1.6", "supertest-session": "^4.1.0", "terser-webpack-plugin": "^1.2.3", - "testcafe": "=1.15.1", "ts-essentials": "^9.0.0", "ts-jest": "^26.5.6", "ts-loader": "^7.0.5", diff --git a/shared/package-lock.json b/shared/package-lock.json index c5e10ff140..c3637ad732 100644 --- a/shared/package-lock.json +++ b/shared/package-lock.json @@ -36,6 +36,12 @@ "integrity": "sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA==", "dev": true }, + "@types/lodash": { + "version": "4.14.177", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.177.tgz", + "integrity": "sha512-0fDwydE2clKe9MNfvXHBHF9WEahRuj+msTuQqOmAApNORFvhMYZKNGGJdCzuhheVjMps/ti0Ak/iJPACMaevvw==", + "dev": true + }, "@typescript-eslint/eslint-plugin": { "version": "4.28.2", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.28.2.tgz", diff --git a/shared/package.json b/shared/package.json index 34abde724f..b322e2f813 100644 --- a/shared/package.json +++ b/shared/package.json @@ -13,6 +13,7 @@ "zod": "^3.7.2" }, "devDependencies": { + "@types/lodash": "^4.14.177", "@typescript-eslint/eslint-plugin": "^4.28.2", "@typescript-eslint/parser": "^4.28.2", "eslint-config-prettier": "^8.3.0", diff --git a/src/app/modules/bounce/__tests__/bounce.service.spec.ts b/src/app/modules/bounce/__tests__/bounce.service.spec.ts index 1c9c807c8a..845fbb42a0 100644 --- a/src/app/modules/bounce/__tests__/bounce.service.spec.ts +++ b/src/app/modules/bounce/__tests__/bounce.service.spec.ts @@ -2,7 +2,7 @@ import axios from 'axios' import { ObjectId } from 'bson' import crypto from 'crypto' -import dedent from 'dedent' +import dedent from 'dedent-js' import { cloneDeep, omit, pick } from 'lodash' import mongoose from 'mongoose' import { errAsync, okAsync } from 'neverthrow' diff --git a/tests/end-to-end/.eslintrc b/tests/end-to-end/.eslintrc deleted file mode 100644 index ccdb15c147..0000000000 --- a/tests/end-to-end/.eslintrc +++ /dev/null @@ -1,7 +0,0 @@ -{ - "globals": { - "fixture": true, - "test": true, - "window": true - } -} diff --git a/tests/end-to-end/email-submission.e2e.js b/tests/end-to-end/email-submission.e2e.js deleted file mode 100644 index b5d7c62f56..0000000000 --- a/tests/end-to-end/email-submission.e2e.js +++ /dev/null @@ -1,250 +0,0 @@ -const { - makeMongooseFixtures, - makeModel, - deleteDocById, - createFormFromTemplate, - createForm, - getOptionalVersion, - getBlankVersion, - verifySubmissionDisabled, - getFeatureState, - makeField, -} = require('./helpers/util') - -const { verifySubmissionE2e } = require('./helpers/email-mode') -const { verifiableEmailField } = require('./helpers/verifiable-email-field') -const { allFields } = require('./helpers/all-fields') -const { templateFields } = require('./helpers/template-fields') -const { - hiddenFieldsData, - hiddenFieldsLogicData, -} = require('./helpers/all-hidden-form') -const { tripleAttachment } = require('./helpers/triple-attachment') -const chainDisabled = require('./helpers/disabled-form-chained') - -const { cloneDeep } = require('lodash') -const { myInfoFields } = require('./helpers/myinfo-form') - -let db -let User -let Form -let Agency -let govTech -const testSpNric = 'S9912374E' -const testCpNric = 'S8979373D' -const testCpUen = '123456789A' -let captchaEnabled -fixture('Email mode submissions') - .before(async () => { - db = await makeMongooseFixtures() - Agency = makeModel(db, 'agency.server.model', 'Agency') - User = makeModel(db, 'user.server.model', 'User') - Form = makeModel(db, 'form.server.model', 'Form') - govTech = await Agency.findOne({ shortName: 'govtech' }).exec() - // Check whether captcha is enabled in environment - captchaEnabled = await getFeatureState('captcha') - }) - .after(async () => { - // Delete models defined by mongoose and close connection - db.models = {} - await db.close() - }) - .beforeEach(async (t) => { - await t.resizeWindow(1280, 800) - }) - .afterEach(async (t) => { - await deleteDocById(User, t.ctx.formData.user._id) - await deleteDocById(Form, t.ctx.form._id) - }) - -// For each of the following tests, a public form is created in the DB -// using the fields and options passed to createForm. - -// The tests check that a browser is able to navigate to the form start -// page, fill in the form using the values given in formFields.val, submit -// the form and reach the end page. - -// Form with all basic field types -test.before(async (t) => { - const formData = await getDefaultFormOptions() - formData.formFields = cloneDeep(allFields) - t.ctx.formData = formData -})('Create and submit form with all form fields', async (t) => { - t.ctx.form = await createForm(t, t.ctx.formData, Form, captchaEnabled) - await verifySubmissionE2e(t, t.ctx.form, t.ctx.formData) -}) - -// Form where all basic field types are hidden by logic -test.before(async (t) => { - const formData = await getDefaultFormOptions() - formData.formFields = cloneDeep(hiddenFieldsData) - formData.logicData = cloneDeep(hiddenFieldsLogicData) - t.ctx.formData = formData -})('Create and submit form with all field types hidden', async (t) => { - t.ctx.form = await createForm(t, t.ctx.formData, Form, captchaEnabled) - await verifySubmissionE2e(t, t.ctx.form, t.ctx.formData) -}) - -// Form where all fields are optional and no field is answered -test.before(async (t) => { - const formData = await getDefaultFormOptions() - formData.formFields = allFields.map((field) => { - return getBlankVersion(getOptionalVersion(field)) - }) - t.ctx.formData = formData -})('Create and submit form with all field types optional', async (t) => { - t.ctx.form = await createForm(t, t.ctx.formData, Form, captchaEnabled) - await verifySubmissionE2e(t, t.ctx.form, t.ctx.formData) -}) - -// Form with three attachments to test de-duplication of attachment names -test.before(async (t) => { - const formData = await getDefaultFormOptions() - formData.formFields = cloneDeep(tripleAttachment) - t.ctx.formData = formData -})('Create and submit form with identical attachment names', async (t) => { - t.ctx.form = await createForm(t, t.ctx.formData, Form, captchaEnabled) - await verifySubmissionE2e(t, t.ctx.form, t.ctx.formData) -}) - -// Form with optional attachment in between mandatory ones -test.before(async (t) => { - const formData = await getDefaultFormOptions() - formData.formFields = cloneDeep(tripleAttachment) - // Modify middle attachment field to be optional and unfilled - formData.formFields[1] = getBlankVersion( - getOptionalVersion(formData.formFields[1]), - ) - // Modify first filename to account for middle field left blank - formData.formFields[0].val = '1-test-att.txt' - t.ctx.formData = formData -})( - 'Create and submit form with optional and required attachments', - async (t) => { - t.ctx.form = await createForm(t, t.ctx.formData, Form, captchaEnabled) - await verifySubmissionE2e(t, t.ctx.form, t.ctx.formData) - }, -) - -// Form where submission is prevented using chained logic -test.before(async (t) => { - const formData = await getDefaultFormOptions() - formData.formFields = cloneDeep(chainDisabled.fields) - formData.logicData = cloneDeep(chainDisabled.logicData) - t.ctx.formData = formData -})('Create and disable form with chained logic', async (t) => { - t.ctx.form = await createForm(t, t.ctx.formData, Form, captchaEnabled) - await verifySubmissionDisabled( - t, - t.ctx.form, - t.ctx.formData, - chainDisabled.toastMessage, - ) -}) - -test.before(async (t) => { - const formData = await getDefaultFormOptions() - t.ctx.formData = formData - // cloneDeep in case other tests in future import and modify templateFields - t.ctx.formData.formFields = cloneDeep(templateFields) -})('Create a form from COVID19 Templates', async (t) => { - t.ctx.form = await createFormFromTemplate( - t, - t.ctx.formData, - Form, - captchaEnabled, - ) - t.ctx.endPageTitle = 'Thank you for registering your interest.' - await verifySubmissionE2e(t, t.ctx.form, t.ctx.formData) -}) - -// Basic form with only one field and CP authentication -test.before(async (t) => { - const formData = await getDefaultFormOptions({ - authType: 'CP', - status: 'PRIVATE', - esrvcId: 'Test-eServiceId-Cp', - }) - formData.formFields = [ - { - title: 'short text', - fieldType: 'textfield', - val: 'Lorem Ipsum', - }, - ].map(makeField) - t.ctx.formData = formData -})('Create and submit basic form with CorpPass authentication', async (t) => { - let authData = { testCpNric, testCpUen } - t.ctx.form = await createForm(t, t.ctx.formData, Form, captchaEnabled) - await verifySubmissionE2e(t, t.ctx.form, t.ctx.formData, authData) -}) - -// Basic form with only one field and SP authentication -test.before(async (t) => { - const formData = await getDefaultFormOptions({ - authType: 'SP', - status: 'PRIVATE', - esrvcId: 'Test-eServiceId-Sp', - }) - formData.formFields = [ - { - title: 'short text', - fieldType: 'textfield', - val: 'Lorem Ipsum', - }, - ].map(makeField) - t.ctx.formData = formData -})('Create and submit basic form with SingPass authentication', async (t) => { - let authData = { testSpNric } - t.ctx.form = await createForm(t, t.ctx.formData, Form, captchaEnabled) - await verifySubmissionE2e(t, t.ctx.form, t.ctx.formData, authData) -}) - -// Form with a mix of autofilled and non-autofilled MyInfo fields -test.before(async (t) => { - const formData = await getDefaultFormOptions({ - authType: 'MyInfo', - esrvcId: 'Test-eServiceId-Sp', - }) - formData.formFields = myInfoFields - t.ctx.formData = formData -})('Create and submit basic MyInfo form', async (t) => { - let authData = { testSpNric } - t.ctx.form = await createForm(t, t.ctx.formData, Form, captchaEnabled) - await verifySubmissionE2e(t, t.ctx.form, t.ctx.formData, authData) -}) - -test.before(async (t) => { - const formData = await getDefaultFormOptions() - formData.formFields = cloneDeep(verifiableEmailField) - t.ctx.formData = formData -})('Create and submit form with verifiable email field', async (t) => { - t.ctx.form = await createForm(t, t.ctx.formData, Form, captchaEnabled) - await verifySubmissionE2e(t, t.ctx.form, t.ctx.formData) -}) - -// Creates an object with default form options, with optional modifications. -const getDefaultFormOptions = async ({ - title = 'Submission e2e Form', - authType = 'NIL', - status = 'PUBLIC', - esrvcId = '', -} = {}) => { - title += String(Date.now()) - const user = await User.create({ - email: String(Date.now()) + '@data.gov.sg', - agency: govTech._id, - contact: '+6587654321', - }) - return { - user, - formOptions: { - responseMode: 'email', - hasCaptcha: false, - status, - title, - authType, - esrvcId, - }, - } -} diff --git a/tests/end-to-end/encrypt-submission.e2e.js b/tests/end-to-end/encrypt-submission.e2e.js deleted file mode 100644 index 75c30a7aab..0000000000 --- a/tests/end-to-end/encrypt-submission.e2e.js +++ /dev/null @@ -1,236 +0,0 @@ -const { - makeMongooseFixtures, - makeModel, - deleteDocById, - createForm, - getOptionalVersion, - getBlankVersion, - verifySubmissionDisabled, - getFeatureState, - makeField, -} = require('./helpers/util') -const { - verifySubmissionE2e, - clearDownloadsFolder, - verifyWebhookSubmission, - createWebhookConfig, - removeWebhookConfig, -} = require('./helpers/encrypt-mode') -const { allFieldsEncrypt } = require('./helpers/all-fields') -const { verifiableEmailField } = require('./helpers/verifiable-email-field') -const { - hiddenFieldsDataEncrypt, - hiddenFieldsLogicDataEncrypt, -} = require('./helpers/all-hidden-form') - -const chainDisabled = require('./helpers/disabled-form-chained') - -const { cloneDeep } = require('lodash') - -let User -let Form -let Agency -let Submission -let govTech -let db -const testSpNric = 'S6005038D' -const testCpNric = 'S8979373D' -const testCpUen = '123456789A' -let captchaEnabled - -fixture('Storage mode submissions') - .before(async () => { - db = await makeMongooseFixtures() - Agency = makeModel(db, 'agency.server.model', 'Agency') - User = makeModel(db, 'user.server.model', 'User') - Form = makeModel(db, 'form.server.model', 'Form') - Submission = makeModel(db, 'submission.server.model', 'Submission') - govTech = await Agency.findOne({ shortName: 'govtech' }).exec() - // Check whether captcha is enabled in environment - captchaEnabled = await getFeatureState('captcha') - }) - .after(async () => { - // Delete models defined by mongoose and close connection - db.models = {} - await db.close() - }) - .beforeEach(async (t) => { - await t.resizeWindow(1280, 800) - }) - .afterEach(async (t) => { - await deleteDocById(User, t.ctx.formData.user._id) - await deleteDocById(Form, t.ctx.form._id) - // Clear used files - clearDownloadsFolder(t.ctx.form.title, t.ctx.form._id) - }) - -// Form with all field types available in storage mode -test.before(async (t) => { - const formData = await getDefaultFormOptions() - formData.formFields = cloneDeep(allFieldsEncrypt) - t.ctx.formData = formData -})('Create and submit form with all field types', async (t) => { - t.ctx.form = await createForm(t, t.ctx.formData, Form, captchaEnabled) - await verifySubmissionE2e(t, t.ctx.form, t.ctx.formData) -}) - -// Form where all basic field types are hidden by logic -test.before(async (t) => { - const formData = await getDefaultFormOptions() - formData.formFields = cloneDeep(hiddenFieldsDataEncrypt) - formData.logicData = cloneDeep(hiddenFieldsLogicDataEncrypt) - t.ctx.formData = formData -})('Create and submit form with all field types hidden', async (t) => { - t.ctx.form = await createForm(t, t.ctx.formData, Form, captchaEnabled) - await verifySubmissionE2e(t, t.ctx.form, t.ctx.formData) -}) - -// Form where all fields are optional and no field is answered -test.before(async (t) => { - const formData = await getDefaultFormOptions() - formData.formFields = allFieldsEncrypt.map((field) => { - return getBlankVersion(getOptionalVersion(field)) - }) - t.ctx.formData = formData -})('Create and submit form with all field types optional', async (t) => { - t.ctx.form = await createForm(t, t.ctx.formData, Form, captchaEnabled) - await verifySubmissionE2e(t, t.ctx.form, t.ctx.formData) -}) - -// Form where submission is prevented using chained logic -test.before(async (t) => { - const formData = await getDefaultFormOptions() - formData.formFields = cloneDeep(chainDisabled.fields) - formData.logicData = cloneDeep(chainDisabled.logicData) - t.ctx.formData = formData -})('Create and disable form with chained logic', async (t) => { - t.ctx.form = await createForm(t, t.ctx.formData, Form, captchaEnabled) - await verifySubmissionDisabled( - t, - t.ctx.form, - t.ctx.formData, - chainDisabled.toastMessage, - ) -}) - -// Basic form with only one field and SP authentication -test.before(async (t) => { - const formData = await getDefaultFormOptions({ - authType: 'SP', - status: 'PRIVATE', - esrvcId: 'Test-eServiceId-Sp', - }) - formData.formFields = [ - { - title: 'short text', - fieldType: 'textfield', - val: 'Lorem Ipsum', - }, - ].map(makeField) - t.ctx.formData = formData -})('Create and submit basic form with SingPass authentication', async (t) => { - let authData = { testSpNric } - t.ctx.form = await createForm(t, t.ctx.formData, Form, captchaEnabled) - await verifySubmissionE2e(t, t.ctx.form, t.ctx.formData, authData) -}) - -// Basic form with only one field and CP authentication -test.before(async (t) => { - const formData = await getDefaultFormOptions({ - authType: 'CP', - status: 'PRIVATE', - esrvcId: 'Test-eServiceId-Cp', - }) - formData.formFields = [ - { - title: 'short text', - fieldType: 'textfield', - val: 'Lorem Ipsum', - }, - ].map(makeField) - t.ctx.formData = formData -})('Create and submit basic form with CorpPass authentication', async (t) => { - let authData = { testCpNric, testCpUen } - t.ctx.form = await createForm(t, t.ctx.formData, Form, captchaEnabled) - await verifySubmissionE2e(t, t.ctx.form, t.ctx.formData, authData) -}) - -// Basic form with verifiable email field -test.before(async (t) => { - const formData = await getDefaultFormOptions() - formData.formFields = cloneDeep(verifiableEmailField) - t.ctx.formData = formData -})('Create and submit form with verifiable email field', async (t) => { - t.ctx.form = await createForm(t, t.ctx.formData, Form, captchaEnabled) - await verifySubmissionE2e(t, t.ctx.form, t.ctx.formData) -}) - -// Basic form with only one field -test.before(async (t) => { - const formData = await getDefaultFormOptions() - formData.formFields = [ - { - title: 'short text', - fieldType: 'textfield', - val: 'Lorem Ipsum', - }, - ].map(makeField) - t.ctx.formData = formData -})('Submit form with webhook integration', async (t) => { - // Create webhookUrl and write webhook configuration to disk for mock webhook server to access - const webhookUrl = await createWebhookConfig(t.ctx.formData.formOptions.title) - // Create form - t.ctx.form = await createForm( - t, - t.ctx.formData, - Form, - captchaEnabled, - webhookUrl, - ) - // Make and verify submission - await verifySubmissionE2e(t, t.ctx.form, t.ctx.formData) - // Verify webhook submission (request body is returned and stored as webhook response) - let submission = await Submission.findOne({ form: t.ctx.form._id }) - await t - .expect(submission.webhookResponses.length) - .eql(1) - .expect(submission.webhookResponses[0].webhookUrl) - .eql(webhookUrl) - .expect(submission.webhookResponses[0].response.status) - .eql(200) - const webhookRequestData = JSON.parse( - submission.webhookResponses[0].response.data, - ) - await verifyWebhookSubmission(t, t.ctx.formData, webhookRequestData) - // Remove webhook config - await removeWebhookConfig(webhookUrl) -}) - -// Creates an object with default encrypt-mode form options, with optional modifications. -// Note that a new user needs to be created for each test, otherwise the extractOTP function -// may get the wrong OTP due to a concurrency issue where it grabs the wrong email from the -// user inbox. -const getDefaultFormOptions = async ({ - title = 'Submission e2e Form', - authType = 'NIL', - status = 'PUBLIC', - esrvcId = '', -} = {}) => { - title += String(Date.now()) - const user = await User.create({ - email: String(Date.now()) + '@data.gov.sg', - agency: govTech._id, - contact: '+6587654321', - }) - return { - user, - formOptions: { - responseMode: 'encrypt', - hasCaptcha: false, - status, - title, - authType, - esrvcId, - }, - } -} diff --git a/tests/end-to-end/files/att-folder-1/test-att.txt b/tests/end-to-end/files/att-folder-1/test-att.txt deleted file mode 100644 index 6a5b9689a7..0000000000 --- a/tests/end-to-end/files/att-folder-1/test-att.txt +++ /dev/null @@ -1 +0,0 @@ -att-folder-1 \ No newline at end of file diff --git a/tests/end-to-end/files/att-folder-2/test-att.txt b/tests/end-to-end/files/att-folder-2/test-att.txt deleted file mode 100644 index 66eb7161ae..0000000000 --- a/tests/end-to-end/files/att-folder-2/test-att.txt +++ /dev/null @@ -1 +0,0 @@ -att-folder-2 \ No newline at end of file diff --git a/tests/end-to-end/files/att-folder-3/test-att.txt b/tests/end-to-end/files/att-folder-3/test-att.txt deleted file mode 100644 index 00f17494ea..0000000000 --- a/tests/end-to-end/files/att-folder-3/test-att.txt +++ /dev/null @@ -1 +0,0 @@ -att-folder-3 \ No newline at end of file diff --git a/tests/end-to-end/files/logo.jpg b/tests/end-to-end/files/logo.jpg deleted file mode 100644 index 5ebf43c17d35122bcf583ed764c3c2d885b03d39..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 32796 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 v$ovMB{s#PBBLDmIHv)en@HYZ~Bk(r@e field.fieldType !== 'attachment', - ), -} diff --git a/tests/end-to-end/helpers/all-hidden-form.js b/tests/end-to-end/helpers/all-hidden-form.js deleted file mode 100644 index 9c4c79cbce..0000000000 --- a/tests/end-to-end/helpers/all-hidden-form.js +++ /dev/null @@ -1,67 +0,0 @@ -// Exports data for a form containing all basic field types, where all the fields are -// hidden due to logic depending on the value of the first field. - -const { allFields, allFieldsEncrypt } = require('./all-fields') -const { - getBlankVersion, - getHiddenVersion, - makeField, - listIntsInclusive, -} = require('./util') -const shownFields = [ - { - title: 'Yes/No', - fieldType: 'yes_no', - val: 'No', - }, -].map(makeField) -const hiddenFields = allFields.map((field) => - getHiddenVersion(getBlankVersion(field)), -) -const hiddenFieldsEncrypt = allFieldsEncrypt.map((field) => - getHiddenVersion(getBlankVersion(field)), -) - -const hiddenFieldsLogicData = [ - { - showFieldIndices: listIntsInclusive( - shownFields.length, - shownFields.length + hiddenFields.length - 1, - ), - conditions: [ - { - fieldIndex: 0, - state: 'is equals to', - value: 'Yes', - ifValueType: 'single-select', - }, - ], - logicType: 'showFields', - }, -] -const hiddenFieldsLogicDataEncrypt = [ - { - showFieldIndices: listIntsInclusive( - shownFields.length, - shownFields.length + hiddenFieldsEncrypt.length - 1, - ), - conditions: [ - { - fieldIndex: 0, - state: 'is equals to', - value: 'Yes', - ifValueType: 'single-select', - }, - ], - logicType: 'showFields', - }, -] -const hiddenFieldsData = [...shownFields, ...hiddenFields] -const hiddenFieldsDataEncrypt = [...shownFields, ...hiddenFieldsEncrypt] - -module.exports = { - hiddenFieldsData, - hiddenFieldsLogicData, - hiddenFieldsDataEncrypt, - hiddenFieldsLogicDataEncrypt, -} diff --git a/tests/end-to-end/helpers/disabled-form-basic.js b/tests/end-to-end/helpers/disabled-form-basic.js deleted file mode 100644 index d48b18ac83..0000000000 --- a/tests/end-to-end/helpers/disabled-form-basic.js +++ /dev/null @@ -1,28 +0,0 @@ -const { makeField } = require('./util') -const fields = [ - { - title: 'Yes/No', - fieldType: 'yes_no', - val: 'Yes', - }, -].map(makeField) -const logicData = [ - { - conditions: [ - { - fieldIndex: 0, - value: 'Yes', - state: 'is equals to', - ifValueType: 'single-select', - }, - ], - logicType: 'preventSubmit', - preventSubmitMessage: 'You shall not pass', - }, -] - -module.exports = { - fields, - logicData, - toastMessage: logicData[0].preventSubmitMessage, -} diff --git a/tests/end-to-end/helpers/disabled-form-chained.js b/tests/end-to-end/helpers/disabled-form-chained.js deleted file mode 100644 index 87643738c8..0000000000 --- a/tests/end-to-end/helpers/disabled-form-chained.js +++ /dev/null @@ -1,62 +0,0 @@ -const { makeField } = require('./util') -const fields = [ - { - title: 'Number', - fieldType: 'number', - val: '10', - }, - { - title: 'Favourite Food', - fieldType: 'dropdown', - fieldOptions: ['Rice', 'Chocolate', 'Ice-Cream'], - val: 'Chocolate', - }, - { - title: 'Yes/No', - fieldType: 'yes_no', - val: 'Yes', - }, -].map(makeField) -const logicData = [ - { - showFieldIndices: [1], - conditions: [ - { - fieldIndex: 0, - state: 'is more than or equal to', - value: '10', - ifValueType: 'number', - }, - ], - logicType: 'showFields', - }, - { - showFieldIndices: [2], - conditions: [ - { - fieldIndex: 1, - state: 'is either', - value: ['Rice', 'Chocolate'], - ifValueType: 'multi-select', - }, - ], - logicType: 'showFields', - }, - { - conditions: [ - { - fieldIndex: 2, - state: 'is equals to', - value: 'Yes', - ifValueType: 'single-select', - }, - ], - logicType: 'preventSubmit', - preventSubmitMessage: 'Bring me a shrubbery', - }, -] -module.exports = { - fields, - logicData, - toastMessage: logicData[2].preventSubmitMessage, -} diff --git a/tests/end-to-end/helpers/email-mode.js b/tests/end-to-end/helpers/email-mode.js deleted file mode 100644 index 72c9f7794b..0000000000 --- a/tests/end-to-end/helpers/email-mode.js +++ /dev/null @@ -1,102 +0,0 @@ -const { - getSubmission, - expectStartPage, - fillInForm, - submitForm, - expectEndPage, - appUrl, - getSubstringBetween, - decodeHtmlEntities, - expectContains, - getResponseArray, - getResponseTitle, - expectSpcpLogin, - getAuthFields, -} = require('./util') - -// Checks that an attachment field's attachment is contained in the email. -const expectAttachment = async (t, field, attachments) => { - // Attachments can only be in fields that are visible AND either required - // or optional but filled - if (field.isVisible && (field.required || !field.isLeftBlank)) { - const attachment = attachments.find((att) => att.fileName === field.val) - // Check that attachment exists - await t.expect(attachment).ok() - // Check that contents match - await t.expect(attachment.content).eql(field.content) - } -} - -// Grab submission email and verify its contents -const verifySubmission = async (t, testFormData, authData) => { - let { user, formFields, formOptions } = testFormData - const authType = formOptions ? formOptions.authType : 'NIL' - // Add verified authentication data (NRIC/UEN/UID) at the end - formFields = formFields.concat(getAuthFields(authType, authData)) - formFields = formFields.filter((f) => f.fieldType !== 'section') - - const title = formOptions ? formOptions.title : '' - const { html, subject, to, from, attachments } = await getSubmission(title) - // Verify recipient of email - await t.expect(to).eql(user.email) - - // Verify sender of email - await t.expect(from).eql('FormSG ') - - // Verify subject of email - await t.expect(subject).contains(`formsg-auto: ${title} (#`) - // Verify form content in email - for (let field of formFields) { - const contained = [ - getResponseTitle(field, false, 'email'), - ...getResponseArray(field, 'email'), - ] - await expectContains(t, html, contained) - if (field.fieldType === 'attachment') { - await expectAttachment(t, field, attachments) - } - } - - // Verify JSON for data collation - const emailJSONStr = getSubstringBetween( - html, - '

-- Start of JSON --

', - '

-- End of JSON --

', - ) - await t.expect(emailJSONStr).notEql(null) - const emailJSON = JSON.parse(decodeHtmlEntities(emailJSONStr)) - const response = emailJSON.filter( - ({ question }) => !/(Response ID|Timestamp)/.test(question), - ) - await t.expect(response.length).notEql(0) - let rowIdx = 0 // A table could result in multiple rows of answers, so there might be more answers than form fields - for (let field of formFields) { - await t - .expect(response[rowIdx].question) - .eql(getResponseTitle(field, true, 'email')) - for (let expectedAnswer of getResponseArray(field, 'email')) { - await t.expect(response[rowIdx].answer).eql(expectedAnswer) - rowIdx++ - } - } -} - -// Open form, fill it in, submit and verify submission -const verifySubmissionE2e = async (t, form, formData, authData) => { - // Verify that form can be accessed - await expectStartPage(t, form, formData, appUrl) - if (authData) { - await expectSpcpLogin(t, formData.formOptions.authType, authData) - } - // Fill in form= - await fillInForm(t, form, formData) - await submitForm(t) - // Verify that end page is shown - await expectEndPage(t) - // Verify that submission is as expected - await verifySubmission(t, formData, authData) -} - -module.exports = { - verifySubmissionE2e, -} diff --git a/tests/end-to-end/helpers/encrypt-mode.js b/tests/end-to-end/helpers/encrypt-mode.js deleted file mode 100644 index 71418fc989..0000000000 --- a/tests/end-to-end/helpers/encrypt-mode.js +++ /dev/null @@ -1,252 +0,0 @@ -const { adminTabs, dataTab } = require('./selectors') - -const { - expectStartPage, - fillInForm, - submitForm, - expectEndPage, - appUrl, - getResponseArray, - getResponseTitle, - expectContains, - expectSpcpLogin, - getAuthFields, - getDownloadsFolder, -} = require('./util') - -const fs = require('fs') -const rimraf = require('rimraf') -const parse = require('csv-parse/sync').parse -const ngrok = require('ngrok') - -// Index of the column headers in the exported CSV. The first 4 rows are -// metadata about the number of responses decrypted. -const CSV_HEADER_ROW_INDEX = 5 -// The first two columns are "Response ID" and "Timestamp", so the -// fields to check only start from the third column. -const CSV_ANSWER_COL_INDEX = 3 - -const WEBHOOK_PORT = process.env.MOCK_WEBHOOK_PORT -const WEBHOOK_CONFIG_FILE = process.env.MOCK_WEBHOOK_CONFIG_FILE - -// We can't just call getResponseArray because CSVs use different -// delimiters for checkbox and table. Hence we treat checbox and table -// specially, and call getResponseArray for the rest. -const addExpectedCsvAnswer = (field, answers) => { - const numCols = field.fieldType === 'table' ? field.val.length : 1 - switch (field.fieldType) { - case 'table': - for (let i = 0; i < numCols; i++) { - answers.push(field.val[i].join(';')) - } - break - case 'checkbox': - if (!field.isVisible || field.isLeftBlank) { - answers.push('') - } else { - answers.push( - field.val - .map((selected) => { - return field.fieldOptions.includes(selected) - ? selected - : `Others: ${selected}` - }) - .join(';'), - ) - } - break - default: - answers.push(...getResponseArray(field)) - } -} - -// We can't just call getResponseTitle because the prefixes are different -// for verifiable fields in CSVs, and the number of repetitions of the title -// depends on the number of rows in the answer for table fields. -const addExpectedCsvTitle = (field, headers) => { - let responseTitle = getResponseTitle(field, false, 'encrypt') - const numCols = field.fieldType === 'table' ? field.val.length : 1 - for (let i = 0; i < numCols; i++) { - headers.push(responseTitle) - } -} - -const addExpectedCsvResponse = (field, headers, answers) => { - addExpectedCsvTitle(field, headers) - addExpectedCsvAnswer(field, answers) -} - -const emptyCallback = () => {} - -const clearDownloadsFolder = (formTile, formId) => { - const downloadsFolder = getDownloadsFolder() - rimraf( - `${downloadsFolder}/Form Secret Key - ${formTile}.txt`, - { - glob: false, - }, - emptyCallback, - ) - rimraf( - `${downloadsFolder}/${formTile}-${formId}.csv`, - { - glob: false, - }, - emptyCallback, - ) -} - -const createWebhookConfig = async (formTitle) => { - const encodedTitle = encodeURI(formTitle) - const webhookUrl = await ngrok.connect(WEBHOOK_PORT) - const downloadsFolder = getDownloadsFolder() - fs.writeFileSync( - `${downloadsFolder}/${WEBHOOK_CONFIG_FILE}`, - `${encodedTitle},${webhookUrl}`, - 'utf8', - ) - return webhookUrl -} - -const removeWebhookConfig = async (url) => { - await ngrok.disconnect(url) - const downloadsFolder = getDownloadsFolder() - rimraf( - `${downloadsFolder}/${WEBHOOK_CONFIG_FILE}`, - { - glob: false, - }, - emptyCallback, - ) -} - -// Download the CSV and verify its contents -async function checkDownloadCsv(t, formData, authData, formId) { - let { formFields, formOptions } = formData - formFields = formFields.concat(getAuthFields(formOptions.authType, authData)) - await t.click(dataTab.exportBtn) - await t.click(dataTab.exportBtnDropdownResponses) - await t.wait(5000) - const csvContent = await fs.promises.readFile( - `${getDownloadsFolder()}/${formOptions.title}-${formId}.csv`, - ) - const records = parse(csvContent, { relax_column_count: true }) - const headers = ['Response ID', 'Timestamp', 'Download Status'] - const answers = [] - formFields.forEach((field) => addExpectedCsvResponse(field, headers, answers)) - await t.expect(records[CSV_HEADER_ROW_INDEX]).eql(headers) - const actualAnswers = - records[CSV_HEADER_ROW_INDEX + 1].slice(CSV_ANSWER_COL_INDEX) - await t.expect(actualAnswers).eql(answers) -} - -// Check that a table response in the responses view is correct -async function checkTableResponse(t, field, index) { - for (let i = 0; i < field.val.length; i++) { - const row = field.val[i] - for (let j = 0; j < row.length; j++) { - await t - .expect(dataTab.getNthFieldTableCell(index, i, j).value) - .eql(row[j]) - } - } -} - -// Download file and check that its contents are correct -async function checkAttachmentContent(t, field, fieldIndex) { - await t.click(dataTab.getNthFieldDownloadLink(fieldIndex)) - const filePath = `${getDownloadsFolder()}/${field.val}` - const fileContent = await fs.promises.readFile(filePath, 'ascii') - await t.expect(fileContent).eql(field.content) - rimraf(filePath, { glob: false }, emptyCallback) -} - -// Click on the given row -async function clickResponseRow(t, index) { - await t.click(dataTab.getNthSubmission(index)) -} - -// Navigate to the results tab -async function navigateToResults(t, id) { - await t.navigateTo(`${appUrl}/#!/${id}/admin`).click(adminTabs.data) -} - -// Type in the secretKey -async function enterSecretKey(t, secretKey) { - await t - .typeText(dataTab.secretKeyInput, secretKey, { paste: true }) - .click(dataTab.unlockResponsesBtn) -} - -// Checking if the decrypted response is correct -async function checkDecryptedResponses(t, formData, authData) { - let { formFields, formOptions } = formData - formFields = formFields.concat(getAuthFields(formOptions.authType, authData)) - for (let i = 0; i < formFields.length; i++) { - const field = formFields[i] - await t - .expect(dataTab.getNthFieldTitle(i).textContent) - .contains(getResponseTitle(field, false, 'encrypt')) - if (field.fieldType === 'table') { - await checkTableResponse(t, field, i) - } else { - await expectContains( - t, - dataTab.getNthFieldAnswer(i).textContent, - getResponseArray(field, 'encrypt'), - ) - } - if ( - field.fieldType === 'attachment' && - field.isVisible && - !field.isLeftBlank - ) { - await checkAttachmentContent(t, field, i) - } - } -} - -// Open form, fill it in, submit, then log in and verify response -// in data tab -const verifySubmissionE2e = async (t, form, formData, authData) => { - // Going to the form, filling it in and submitting - await expectStartPage(t, form, formData, appUrl) - if (authData) { - await expectSpcpLogin(t, formData.formOptions.authType, authData) - } - await fillInForm(t, form, formData) - await submitForm(t) - await expectEndPage(t) - - // Ensuring that submission is decrypted and values are correct - // No need to log in again as signin was already done to create form - const { _id } = form - await navigateToResults(t, _id) - await enterSecretKey(t, formData.formOptions.secretKey) - await checkDownloadCsv(t, formData, authData, _id) - await clickResponseRow(t, 0) - await checkDecryptedResponses(t, formData, authData) -} - -const verifyWebhookSubmission = async (t, formData, webhookRequestData) => { - let { formFields } = formData - for (let i = 0; i < formFields.length; i++) { - await t - .expect(formFields[i].title) - .eql(webhookRequestData.responses[i].question) - await t - .expect(formFields[i].fieldType) - .eql(webhookRequestData.responses[i].fieldType) - await t - .expect(formFields[i].val) - .eql(webhookRequestData.responses[i].answer) - } -} - -module.exports = { - verifySubmissionE2e, - clearDownloadsFolder, - verifyWebhookSubmission, - createWebhookConfig, - removeWebhookConfig, -} diff --git a/tests/end-to-end/helpers/get-mongo-binary.js b/tests/end-to-end/helpers/get-mongo-binary.js deleted file mode 100644 index 719add314f..0000000000 --- a/tests/end-to-end/helpers/get-mongo-binary.js +++ /dev/null @@ -1,17 +0,0 @@ -const MongoBinary = require('mongodb-memory-server-core').MongoBinary -if (!process.env.MONGO_BINARY_VERSION) { - console.error('Environment var MONGO_BINARY_VERSION is missing') - process.exit(1) -} - -// mongodb-memory-server-core does not automatically download any binary version -// Therefore, trigger download of binary before tests run -MongoBinary.getPath({ version: String(process.env.MONGO_BINARY_VERSION) }) - .then((binPath) => { - console.info(`mongodb-memory-server: binary path is ${binPath}`) - }) - .catch((err) => { - console.error(`failed to download/install MongoDB binaries. The error: -${err}`) - process.exit(1) - }) diff --git a/tests/end-to-end/helpers/myinfo-form.js b/tests/end-to-end/helpers/myinfo-form.js deleted file mode 100644 index 0a277cc80f..0000000000 --- a/tests/end-to-end/helpers/myinfo-form.js +++ /dev/null @@ -1,24 +0,0 @@ -const { makeField } = require('./util') - -const myInfoFields = [ - { - myInfo: { attr: 'name' }, - disabled: true, - title: '[MyInfo] Name', - val: 'TIMOTHY TAN CHENG GUAN', - }, - { - myInfo: { attr: 'sex' }, - disabled: true, - title: '[MyInfo] Gender', - val: 'MALE', - }, - { - myInfo: { attr: 'workpassstatus' }, - val: 'Live', - title: 'Workpass status', - fieldType: 'dropdown', - }, -].map(makeField) - -module.exports = { myInfoFields } diff --git a/tests/end-to-end/helpers/selectors.js b/tests/end-to-end/helpers/selectors.js deleted file mode 100644 index 8aab05329e..0000000000 --- a/tests/end-to-end/helpers/selectors.js +++ /dev/null @@ -1,228 +0,0 @@ -const { Selector } = require('testcafe') -const { - types: basicTypes, -} = require('../../../dist/backend/shared/constants/field/basic') -const { - types: myInfoTypes, -} = require('../../../dist/backend/shared/constants/field/myinfo') - -const landingPage = { - tagline: Selector('#tagline'), -} - -// Signin page -const signInPage = { - emailInput: Selector('#email-input'), - getStartedBtn: Selector('.btn-grp').child('button').withText('GET STARTED'), - otpMsg: Selector('.alert-custom'), - otpInput: Selector('#otp-input'), - signInBtn: Selector('.btn-grp').child('button').withText('SIGN IN'), - emailErrorMsg: Selector('.alert-error'), - resendOtpLink: Selector('a').withText('Resend OTP'), -} -// Form list dashboard and create form modal -const formList = { - createFormBtn: Selector('#list-form #create-new, #list-form #welcome-btn'), - welcomeMessage: Selector('#list-form #welcome'), - avatarDropdown: Selector('.navbar__avatar'), - logOutBtn: Selector('.navbar__dropdown__logout'), -} -const createFormModal = { - startFromScratchBtn: Selector('#start-from-scratch-button'), - templateCard: Selector('#form-card').nth(3), // volunteer registration - formTitleInput: Selector('#settings-name').parent(), - emailModeRadio: Selector('#settings-form input[value="email"]').parent(), - encryptModeRadio: Selector('#settings-form input[value="encrypt"]').parent(), - emailListInput: Selector('#settings-email').parent(), - startBtn: Selector('#btn-create'), - secretKeyDiv: Selector('.copy-key .text'), - downloadKeyBtn: Selector('#create-form-secret-key i.bx-download').parent(), - encryptModeContinueBtn: Selector('#btn-continue'), -} -const createFormTemplateModal = { - useTemplateBtn: Selector('.use-template-btn'), -} -// Admin tabs navbar -const adminTabParent = Selector('#admin-tabs-container') -const adminTabs = { - build: adminTabParent.child('li[heading="Build"]'), - logic: adminTabParent.child('li[heading="Logic"]'), - settings: adminTabParent.child('li[heading="Settings"]'), - share: adminTabParent.child('li[heading="Share"]'), - data: adminTabParent.child('li[heading="Data"]'), -} -// Maps fieldType property to text of field panels in the Build tab -const BASIC_FIELD_TYPE_TO_VALUE = {} -basicTypes.forEach((type) => { - BASIC_FIELD_TYPE_TO_VALUE[type.name] = type.value -}) -const MYINFO_ATTR_TO_VALUE = {} -myInfoTypes.forEach((type) => { - MYINFO_ATTR_TO_VALUE[type.name] = type.value -}) -// Admin tab contents -const buildTab = { - basicTab: Selector('#add-field .nav-tabs li[heading="Basic"]'), - myInfoTab: Selector('#add-field .nav-tabs li').withText('MyInfo'), - getFieldPanel: (fieldType) => - // We need withExactText otherwise Mobile Number will match Number - Selector('.add-field-panel .add-field-text').withExactText( - BASIC_FIELD_TYPE_TO_VALUE[fieldType], - ), - getMyInfoPanel: (myInfoAttr) => - Selector('.add-field-panel .add-field-text').withExactText( - MYINFO_ATTR_TO_VALUE[myInfoAttr], - ), -} -const logicTab = { - addLogicBtn: Selector('#add-new-logic'), -} - -const settingsTab = { - formStatus: Selector('#golive-option'), - activateBtn: Selector('#btn-live'), - getAuthRadioInput: (authType) => Selector(`#auth-type-${authType}`), - getAuthRadioLabel: (authType) => Selector(`#auth-type-${authType}`).parent(), - esrvcIdInput: Selector('#enable-auth-options input[type="text"]'), - captchaToggleInput: Selector('#enable-captcha input'), - captchaToggleLabel: Selector('#enable-captcha input').parent(), - formTitleInput: Selector('#settings-name'), - emailListInput: Selector('#settings-email'), - webhookUrlInput: Selector('#settings-webhook-url'), -} - -const dataTab = { - secretKeyInput: Selector('#secretKeyInput'), - unlockResponsesBtn: Selector('button').withText('UNLOCK RESPONSES'), - getNthSubmission: (n) => Selector('#responses-tab tbody tr').nth(n), - getNthFieldTitle: (n) => Selector('.response-title-container').nth(n), - getNthFieldAnswer: (n) => Selector('.response-answer').nth(n), - getNthFieldDownloadLink: (n) => Selector('.response-answer').nth(n).find('a'), - exportBtn: Selector('#btn-export-dropdown'), - exportBtnDropdownResponses: Selector('#btn-export-dropdown-responses'), - getNthFieldTableCell: (n, row, col) => - Selector('.response-answer') - .nth(n) - .find('.table-row') - .nth(row + 1) // Header is also a row - .find('.table-column') - .nth(col) - .find('input'), -} - -// Edit field modal -const editFieldModal = { - title: Selector('input[name="title"]'), - saveBtn: Selector('.modal-save-btn'), - getToggle: (toggleText) => - Selector('.toggle-option div') - .withText(toggleText) - .nextSibling() - .find('.toggle-selector'), - getOptionInput: (n) => Selector('.option-panel .option').nth(n).find('input'), - optionTextArea: Selector('.optionFrom').find('textarea'), - minValInput: Selector('.min-val-input'), - maxValInput: Selector('.max-val-input'), - addOption: Selector('a').withText('Add Option'), - rating: { - ratingStepsDropdown: Selector('.ui-select-container').nth(0), - ratingShapeDropdown: Selector('.ui-select-container').nth(1), - }, - table: { - tableMinRows: Selector('input[type="number"]').parent(), - getNthColTitle: (n) => - Selector('.table-column-card').nth(n).find('input[type="text"]'), - getNthColFieldType: (n) => - Selector('.table-column-card').nth(n).find('.table-column-dropdown'), - getNthColRequiredToggle: (n) => - Selector('.table-column-card').nth(n).find('.toggle-selector'), - getNthColTextArea: (n) => - Selector('.table-column-card').nth(n).find('textarea'), - addColumn: Selector('#add-column'), - }, - attachmentSizeDropdown: Selector('.ui-select-container'), -} - -// Activate form modal -const activateFormModal = { - secretKeyInput: Selector('#secretKeyInput'), - acknowledgementInput: Selector('#acknowledgementInput'), - activateFormBtn: Selector('button').withText('ACTIVATE FORM'), - closeModalBtn: Selector('#btn-close'), -} - -// Edit logic modal -const editLogicModal = { - getNthConditionField: (n) => - Selector('.if-container').nth(n).find('.field-input').nth(0), - getNthConditionState: (n) => - Selector('.if-container').nth(n).find('.field-input').nth(1), - getNthConditionValue: (n) => - Selector('.if-container').nth(n).find('.field-input').nth(2), - // The multi option is used for ifValueType=multi-select, when - // selecting more than one element - getNthConditionValueMulti: (n) => - Selector('.if-container') - .nth(n) - .find('.field-input') - .nth(2) - .find('input[type="search"]'), - addConditionBtn: Selector('.if-btn-container') - .nth(0) - .find('.add-condition-btn'), - showFieldsBtn: Selector('.show-fields-option'), - preventSubmitBtn: Selector('.prevent-submit-option'), - showFieldsDropdown: Selector('.show-fields-container .field-input'), - showFieldsSearch: Selector('.show-fields-container input[type="search"]'), - preventSubmitTextArea: Selector('.prevent-submit-container textarea'), - saveBtn: Selector('.modal-save-btn'), -} - -// Form submission -const formPage = { - startTitle: Selector('#start-page-title'), - endTitle: Selector('#end-page-title'), - getFieldElement: (id, element = '') => - Selector(`div[data-id="${id}"] ${element}`), - getTableCell: (id, row, col) => { - return Selector(`div[data-id="${id}"] .table-row`) - .nth(row + 1) // + 1 because the header is also a row - .find(`.table-column`) - .nth(col) - }, - submitBtn: Selector('#form-submit button'), - submitPreventedMessage: Selector('#submit-prevented-message'), - spcpLoginBtn: Selector('#start-page-btn-container button span').withText( - 'LOGIN', - ), - spcpLogoutBtn: Selector('#start-page-btn-container button span').withText( - 'LOG OUT', - ), -} - -const mockpass = { - loginBtn: Selector('.container.visible-lg #loginModelbtn'), - consentBtn: Selector('input[type=submit]'), - nricDropdownBtn: Selector('#dropdownMenuButton'), - getNricOption: (nric) => Selector('.dropdown-menu li').withText(nric), - getNricUenOption: (nric, uen) => - Selector('.dropdown-menu li').withText(nric).withText(uen), -} - -module.exports = { - formList, - createFormModal, - createFormTemplateModal, - adminTabs, - settingsTab, - signInPage, - dataTab, - landingPage, - formPage, - buildTab, - editFieldModal, - activateFormModal, - logicTab, - editLogicModal, - mockpass, -} diff --git a/tests/end-to-end/helpers/template-fields.js b/tests/end-to-end/helpers/template-fields.js deleted file mode 100644 index f3e5ee8435..0000000000 --- a/tests/end-to-end/helpers/template-fields.js +++ /dev/null @@ -1,106 +0,0 @@ -// Exports data for all basic field types. -const { makeField } = require('./util') - -const templateFieldsInfo = [ - { - title: 'Personal Particulars', - fieldType: 'section', - val: '', - }, - { - title: 'Full Name', - fieldType: 'textfield', - val: 'Lorem Ipsum', - }, - { - title: 'Email Address', - fieldType: 'email', - val: 'user@domain.com', - }, - { - title: 'Contact Number', - fieldType: 'mobile', - val: '+6581234567', - }, - { - title: 'Residential Address', - fieldType: 'textarea', - val: 'some place i am living', - }, - { - title: 'Postal Code', - fieldType: 'number', - val: '123456', - }, - { - title: 'Availability', - fieldType: 'section', - val: '', - }, - { - fieldOptions: [ - 'Monday', - 'Tuesday', - 'Wednesday', - 'Thursday', - 'Friday', - 'Saturday', - 'Sunday', - ], - title: 'Preferred Days', - fieldType: 'checkbox', - fieldValue: [false, false, false, false, false, false, false, false], - val: ['Monday'], - }, - { - fieldOptions: ['1 Jul 2020', '2 Jul 2020', '3 Jul 2020'], - title: 'Preferred Dates', - fieldType: 'checkbox', - fieldValue: [false, false, false, false], - val: ['1 Jul 2020'], - }, - { - fieldOptions: ['9.00am - 12.00pm', '1.00pm - 4.00pm', '6.00pm - 9.00pm'], - title: 'Preferred Timeslots', - fieldType: 'checkbox', - fieldValue: [false, false, false, false], - val: ['9.00am - 12.00pm'], - }, - { - fieldOptions: ['North', 'South', 'East', 'West', 'Central'], - title: 'Preferred Locations', - fieldType: 'checkbox', - fieldValue: [false, false, false, false, false, false], - val: ['North'], - }, - { - title: 'Health Declaration', - fieldType: 'section', - val: '', - }, - { - title: 'Have you, or anyone living with you, been diagnosed with COVID-19?', - fieldType: 'yes_no', - val: 'No', - }, - { - title: - 'Are you, or anyone living with you, experiencing respiratory or flu-like symptoms?', - fieldType: 'yes_no', - val: 'No', - }, - { - title: - 'Are you, or anyone living with you, under a Home Quarantine Order, Stay Home Notice, or Leave of Absence?', - fieldType: 'yes_no', - val: 'No', - }, - { - title: - 'Within the past 14 days, have you or anyone living with you had close contact with', - fieldType: 'yes_no', - val: 'No', - }, -] -const templateFields = templateFieldsInfo.map(makeField) -module.exports = { templateFields } diff --git a/tests/end-to-end/helpers/triple-attachment.js b/tests/end-to-end/helpers/triple-attachment.js deleted file mode 100644 index 4bf8cd3b45..0000000000 --- a/tests/end-to-end/helpers/triple-attachment.js +++ /dev/null @@ -1,34 +0,0 @@ -// Form fields in a form with multiple attachments with the same filename. -// Meant to test de-duplication of attachment filenames. Note that the appending -// of the number to the file name is reversed: if attachment field 1 and attachment -// field 2 get the same filename 'fn.jpg', then attachment field 2 retains the old -// filename, while attachment field 1 gets renamed to '1-fn.jpg'. -const { makeField } = require('./util') -module.exports = { - tripleAttachment: [ - { - title: 'Attachment 1', - fieldType: 'attachment', - attachmentSize: '1', - val: '2-test-att.txt', - path: '../files/att-folder-1/test-att.txt', - content: 'att-folder-1', - }, - { - title: 'Attachment 2', - fieldType: 'attachment', - attachmentSize: '1', - val: '1-test-att.txt', - path: '../files/att-folder-2/test-att.txt', - content: 'att-folder-2', - }, - { - title: 'Attachment 3', - fieldType: 'attachment', - attachmentSize: '1', - val: 'test-att.txt', - path: '../files/att-folder-3/test-att.txt', - content: 'att-folder-3', - }, - ].map(makeField), -} diff --git a/tests/end-to-end/helpers/util.js b/tests/end-to-end/helpers/util.js deleted file mode 100644 index 0f0bf14ef8..0000000000 --- a/tests/end-to-end/helpers/util.js +++ /dev/null @@ -1,1281 +0,0 @@ -const axios = require('axios') -const { ClientFunction } = require('testcafe') -const _ = require('lodash') -const mongoose = require('mongoose') -mongoose.Promise = global.Promise -const fs = require('fs') - -const appUrl = 'http://localhost:5000' -const mailUrl = 'http://localhost:1080' -const dbUri = 'mongodb://127.0.0.1:3000/formsg' -const DATE = 17 - -const { - formList, - signInPage, - formPage, - createFormModal, - createFormTemplateModal, - adminTabs, - settingsTab, - activateFormModal, - buildTab, - editFieldModal, - logicTab, - editLogicModal, - mockpass, -} = require('./selectors') - -const { types } = require('../../../dist/backend/shared/constants/field/basic') - -const { SPCPFieldTitle } = require('../../../dist/backend/src/types/field') - -const NON_SUBMITTED_FIELDS = types - .filter((field) => !field.submitted) - .map((field) => field.name) - -/** - * Utility to manage receiving of emails - */ -const mailClient = { - getAll: () => axios.get(`${mailUrl}/email`).then((res) => res.data), - - deleteAll: () => axios.delete(`${mailUrl}/email/all`), - - deleteById: (id) => axios.delete(`${mailUrl}/email/${id}`), - - getAttachment: (id, filename) => - axios.get(`${mailUrl}/email/${id}/attachment/${filename}`), -} - -const getDownloadsFolder = () => { - let downloadsFolder = `${process.env.HOME}/Downloads` - try { - fs.statSync(downloadsFolder) - } catch (e) { - downloadsFolder = '/tmp' - } - return downloadsFolder -} - -/** - * Enters given email in sign-in page. - * @param {Object} t Testcafe browser - * @param {string} email Email to log in - */ -async function enterEmail(t, email) { - await t - .typeText(signInPage.emailInput, email, { paste: true }) - .click(signInPage.getStartedBtn) -} - -/** - * Returns whether a particular bonus feature is enabled - * @param {string} feature - * @returns boolean - */ -async function getFeatureState(feature) { - const featureStates = await axios.get(`${appUrl}/api/v3/client/features`) - return featureStates.data[feature] -} - -/** - * Retrieves an email sent by FormSG. - * @param {string} formName Title of form - * @returns Object containing subject, sender, recipient and html - * content of email - */ -async function getSubmission(formName) { - let submission - let lastEmail - let subject = `formsg-auto: ${formName}` - try { - let inbox = await mailClient.getAll() - let emails = getEmailsWithSubject(inbox, subject) - lastEmail = emails.pop() - submission = { - html: lastEmail.html, - subject: lastEmail.subject, - to: lastEmail.headers.to, - from: lastEmail.headers.from, - attachments: await getSubmissionAttachments(lastEmail), - } - } catch (e) { - throw Error('Failed to get submission email') - } finally { - if (lastEmail) { - await mailClient.deleteById(lastEmail.id) - } - } - return submission -} - -// Fetches the file contents of each attachment and adds it to email.attachments -async function getSubmissionAttachments(email) { - if (!email.attachments) { - return [] - } - for (let att of email.attachments) { - const response = await mailClient.getAttachment(email.id, att.fileName) - att.content = response.data - } - return email.attachments -} - -function getEmailsTo(inbox, toEmail) { - return inbox - .filter((e) => _.get(e, 'to[0].address') === toEmail) - .sort((a, b) => a.time > b.time) -} - -function getEmailsWithSubject(inbox, subject) { - return inbox - .filter((e) => _.get(e, 'subject').indexOf(subject) !== -1) - .sort((a, b) => a.time > b.time) -} - -/** - * Retrieves an OTP from an email's inbox. - * @param {string} email - */ -async function extractOTP(email) { - let otp - let lastEmail - try { - let inbox = await mailClient.getAll() - let emails = getEmailsTo(inbox, email) - lastEmail = emails.pop() - otp = lastEmail.html.match(/\d{6}/)[0] - } catch (e) { - throw Error('otp was not found in email') - } finally { - if (lastEmail) { - await mailClient.deleteById(lastEmail.id) - } - } - return otp -} - -/** - * Tests for either valid or invalid OTP to log in to FormSG. - * @param {Object} t Testcafe browser - * @param {string} otp - * @param {boolean} isValid Whether OTP is expected to be valid - * @param {string} email Email address of user - */ -async function enterOTPAndExpect({ t, otp, isValid, email }) { - let userName = email - ? email - .charAt(0) - .toUpperCase() - .concat(email.toLowerCase().substring(1, email.indexOf('@'))) - : '' - await t - .typeText(signInPage.otpInput, otp, { paste: true }) - .click(signInPage.signInBtn) - if (isValid) { - await t - .expect(getPageUrl()) - .eql(appUrl + '/#!/forms') - .expect(formList.welcomeMessage.textContent) - .contains('Welcome ' + userName + '!') - } else { - await t - .expect(signInPage.otpMsg.textContent) - .contains('OTP is invalid. Please try again.') - } -} - -/** - * Tests that login OTP was sent. - * @param {Object} t Testcafe browser - * @param {string} email User's email - */ -async function expectOtpSent(t, email) { - await t.expect(signInPage.otpMsg.textContent).contains('OTP sent to ' + email) -} - -/** - * Tests that verification OTP was sent. - * @param {Object} t Testcafe browser - * @param {string} fieldId ID of verifiable field - */ -async function expectVfnOtpSent(t, fieldId) { - await t.expect(formPage.getFieldElement(fieldId, '.vfn-section').visible).ok() -} - -// Grab page url -const getPageUrl = ClientFunction(() => window.location.href) - -// Get absolute path of file -function spec(path) { - let fullPath = `${process.env.PWD}/${path}` - return require(fullPath) -} - -/** - * Connects to mongo-memory-server instance. - */ -async function makeMongooseFixtures() { - const connection = await mongoose.createConnection(dbUri, { - reconnectTries: 5, - useNewUrlParser: true, - }) - return connection -} - -/** - * Creates Mongoose model. - * @param {Object} db Return value of makeMongooseFixtures - * @param {string} modelFilename Name of file which exports model in app/models - * @param {string} modelName Name of exported model - */ -function makeModel(db, modelFilename, modelName) { - if (modelName !== undefined && modelName !== null) { - // check if model has already been compiled - try { - return db.model(modelName) - } catch (error) { - if (error.name !== 'MissingSchemaError') { - console.error(error) - } - // else fail silently as we will create the model - } - } - - // Need this try catch block as some schemas may have been converted to - // TypeScript and use default exports instead. - try { - return spec(`dist/backend/src/app/models/${modelFilename}`)(db) - } catch (e) { - return spec(`dist/backend/src/app/models/${modelFilename}`).default(db) - } -} - -/** - * Create login credentials of user with specified email. - * @param {string} email - */ -const logInWithEmail = async (t, email) => { - await t.navigateTo(`${appUrl}/#!/signin`) - await enterEmail(t, email) - await expectOtpSent(t, email) - const otp = await extractOTP(email) - await enterOTP(t, otp) -} - -/** - * Clears a collection of a document with the given id. - * Usually used in 'after' hooks in tests. - * @param {Object} collection MongoDB collection - * @param {Object} id Document's ID as given by its _id field. - */ -function deleteDocById(collection, id) { - return new Promise((resolve, reject) => { - collection.deleteOne({ _id: id }, (err, _result) => { - if (err) { - reject(err) - } else { - resolve() - } - }) - }) -} - -/** - * Creates a form by selecting a template - * @param {Object} user User object from DB - * @param {Object} formOptions Example as follows: - * basicFormData.formOptions = { - responseMode: 'encrypt', - status: 'PUBLIC', - authType: 'NIL', - hasCaptcha: false, - title: 'Basic Form', - publicKey: publicKey, - * } - * @param {Object} Form Mongoose model of Form - */ -async function createFormFromTemplate( - t, - { user, formOptions }, - Form, - captchaEnabled, -) { - const { email } = user - await logInWithEmail(t, email) - await addNewFormBySelectTemplate(t, formOptions) - await addSettings(t, formOptions, captchaEnabled) - const formId = getFormIdFromUrl(await getPageUrl()) - return Form.findOne({ _id: formId }) -} - -/** - * Creates a form with the given specifications. - * @param {Object} user User object from DB - * @param {Object} formOptions Example as follows: - * basicFormData.formOptions = { - responseMode: 'encrypt', - status: 'PUBLIC', - authType: 'NIL', - hasCaptcha: false, - title: 'Basic Form', - publicKey: publicKey, - * } - * @param {Array} formFields List of fields to be created - * @param {Object} Form Mongoose model of Form - */ -async function createForm( - t, - { user, formOptions, formFields, logicData }, - Form, - captchaEnabled, - webhookUrl, -) { - const { email } = user - await logInWithEmail(t, email) - await addNewForm(t, formOptions) - // Need to add settings first so MyInfo fields can be added - await addSettings(t, formOptions, captchaEnabled, webhookUrl) - await addFields(t, formFields) - if (logicData) { - await addLogic(t, logicData, formFields) - } - const formId = getFormIdFromUrl(await getPageUrl()) - return Form.findOne({ _id: formId }) -} - -async function addFields(t, formFields) { - await t.click(adminTabs.build) - for (let field of formFields) { - await createField(t, field) - } -} - -async function addLogic(t, logicData, formFields) { - await t.click(adminTabs.logic) - for (let logicUnit of logicData) { - await t.click(logicTab.addLogicBtn) - await addLogicUnit(t, logicUnit, formFields) - } -} - -async function addLogicUnit(t, logicUnit, formFields) { - await addLogicCondition(t, 0, logicUnit.conditions[0], formFields) - for (let i = 1; i < logicUnit.conditions.length; i++) { - await t.click(editLogicModal.addConditionBtn) - await addLogicCondition(t, i, logicUnit.conditions[i], formFields) - } - if (logicUnit.logicType === 'showFields') { - await t.click(editLogicModal.showFieldsBtn) - for (let i = 0; i < logicUnit.showFieldIndices.length; i++) { - await addLogicShowField(t, i, logicUnit.showFieldIndices[i], formFields) - } - } else { - await t.click(editLogicModal.preventSubmitBtn) - await t.typeText( - editLogicModal.preventSubmitTextArea, - logicUnit.preventSubmitMessage, - ) - } - await t.click(editLogicModal.saveBtn) -} - -async function addLogicShowField( - t, - indexInShowFields, - indexInForm, - formFields, -) { - if (indexInShowFields === 0) { - await selectDropdownOption( - t, - editLogicModal.showFieldsDropdown, - formFields[indexInForm].title, - ) - } else { - await selectDropdownOptionSeparate( - t, - editLogicModal.showFieldsSearch, - editLogicModal.showFieldsDropdown, - formFields[indexInForm].title, - ) - } -} - -// n is the index of the condition in the conditions array -async function addLogicCondition(t, n, condition, formFields) { - const fieldTitle = formFields[condition.fieldIndex].title - await selectDropdownOption( - t, - editLogicModal.getNthConditionField(n), - fieldTitle, - ) - await selectDropdownOption( - t, - editLogicModal.getNthConditionState(n), - condition.state, - ) - if (condition.ifValueType === 'number') { - await t.typeText(editLogicModal.getNthConditionValue(n), condition.value) - } else if (condition.ifValueType === 'single-select') { - await selectDropdownOption( - t, - editLogicModal.getNthConditionValue(n), - condition.value, - ) - } else { - await selectDropdownOption( - t, - editLogicModal.getNthConditionValue(n), - condition.value[0], - ) - for (let i = 1; i < condition.value.length; i++) { - // These gymnastics are necessary because if there is already one field - // selected, the dropdown doesn't show if you click exactly in the middle - // of the div, as is TestCafe's default. Hence we click one div to open the - // dropdown, and get the option to click from another div. - await selectDropdownOptionSeparate( - t, - editLogicModal.getNthConditionValueMulti(n), - editLogicModal.getNthConditionValue(n), - condition.value[i], - ) - } - } -} - -// Note that this assumes that you don't switch between MyInfo and Basic fields. -async function createField(t, field) { - if (_.get(field, 'myInfo.attr')) { - await t - .click(buildTab.myInfoTab) - .click(buildTab.getMyInfoPanel(field.myInfo.attr)) - .click(editFieldModal.saveBtn) - } else { - await createBasicField(t, field) - } -} - -async function createBasicField(t, field) { - await t.click(buildTab.getFieldPanel(field.fieldType)) - // Enter field title - if (!NON_SUBMITTED_FIELDS.includes(field.fieldType)) { - await t.selectText(editFieldModal.title).pressKey('delete') - await t.typeText(editFieldModal.title, field.title) - } - switch (field.fieldType) { - case 'email': - if (field.isVerifiable) { - await t.click(editFieldModal.getToggle('OTP verification')) - } - break - case 'radiobutton': - await setRatingCheckboxOptions(t, 0, field.fieldOptions[0]) - for (let i = 1; i < field.fieldOptions.length; i++) { - await t.click(editFieldModal.addOption) - await setRatingCheckboxOptions(t, i, field.fieldOptions[i]) - } - if (field.othersRadioButton) { - await t.click(editFieldModal.getToggle('Others option')) - } - break - case 'checkbox': - await t.selectText(editFieldModal.optionTextArea).pressKey('delete') - // The wait(500) is necessary because of the debounce time in reloadDropdownField - await t - .typeText(editFieldModal.optionTextArea, field.fieldOptions.join('\n')) - .wait(500) - if (field.othersRadioButton) { - await t.click(editFieldModal.getToggle('Others option')) - } - break - case 'dropdown': - await t.selectText(editFieldModal.optionTextArea).pressKey('delete') - // The wait(500) is necessary because of the debounce time in reloadDropdownField - await t - .typeText(editFieldModal.optionTextArea, field.fieldOptions.join('\n')) - .wait(500) - break - case 'rating': - await selectDropdownOption( - t, - editFieldModal.rating.ratingStepsDropdown, - field.ratingOptions.steps, - ) - await selectDropdownOption( - t, - editFieldModal.rating.ratingShapeDropdown, - field.ratingOptions.shape, - ) - break - case 'table': - await setTableColOptions(t, 0, field.columns[0]) - for (let i = 1; i < field.columns.length; i++) { - await t.click(editFieldModal.table.addColumn) - await setTableColOptions(t, i, field.columns[i]) - } - break - case 'decimal': - if (field.validateByValue) { - await t.click(editFieldModal.getToggle('Range Validation')) - if (field.ValidationOptions.customMin) { - await t.typeText( - editFieldModal.minValInput, - String(field.ValidationOptions.customMin), - ) - } - if (field.ValidationOptions.customMax) { - await t.typeText( - editFieldModal.maxValInput, - String(field.ValidationOptions.customMax), - ) - } - } - break - case 'attachment': - await selectDropdownOption( - t, - editFieldModal.attachmentSizeDropdown, - field.attachmentSize, - ) - break - default: - break - } - // Set field to optional. Ignore tables as the required option is - // set for individual columns. - if (!field.required && field.fieldType !== 'table') { - await t.click(editFieldModal.getToggle('Required')) - } - await t.click(editFieldModal.saveBtn) -} - -async function setRatingCheckboxOptions(t, n, option) { - const input = editFieldModal.getOptionInput(n) - await t.selectText(input).pressKey('delete') - await t.typeText(input, option) -} - -async function setTableColOptions(t, n, colOptions) { - await t.selectText(editFieldModal.table.getNthColTitle(n)).pressKey('delete') - await t.typeText(editFieldModal.table.getNthColTitle(n), colOptions.title) - if (colOptions.columnType === 'textfield') { - await selectDropdownOption( - t, - editFieldModal.table.getNthColFieldType(n), - 'Text Field', - ) - } else { - await selectDropdownOption( - t, - editFieldModal.table.getNthColFieldType(n), - 'Dropdown', - ) - await t - .typeText( - editFieldModal.table.getNthColTextArea(n), - colOptions.fieldOptions.join('\n'), - ) - .wait(400) // necessary due to debounce time in textarea - } - if (!colOptions.required) { - await t.click(editFieldModal.table.getNthColRequiredToggle(n)) - } -} - -async function selectDropdownOption(t, selector, text) { - await t.click(selector).click(getDropdownOption(selector, text)) -} - -// This is for the case where a separate element has to be clicked to -// open the dropdown vs select the option, e.g. in the logic modal -async function selectDropdownOptionSeparate( - t, - dropdownSelector, - optionSelector, - text, -) { - await t.click(dropdownSelector).click(getDropdownOption(optionSelector, text)) -} - -function getDropdownOption(selector, text) { - return selector.find('.ui-select-choices-row').withText(String(text)) -} - -function getFormIdFromUrl(url) { - return getSubstringBetween(url, '#!/', '/admin') -} - -async function addSettings(t, formOptions, captchaEnabled, webhookUrl) { - await t.click(adminTabs.settings) - - // Expect auth type to be none by default, then change it - await t.expect(settingsTab.getAuthRadioInput('NIL').checked).ok() - if (formOptions.authType !== 'NIL') { - await t.click(settingsTab.getAuthRadioLabel(formOptions.authType)) - await t.typeText(settingsTab.esrvcIdInput, formOptions.esrvcId) - } - - // Expect form title to be correct - await t.expect(settingsTab.formTitleInput.value).eql(formOptions.title) - - if (captchaEnabled) { - // Expect captcha to be active, then set it - await t.expect(settingsTab.captchaToggleInput.checked).ok() - if (!formOptions.hasCaptcha) { - await t.click(settingsTab.captchaToggleLabel) - } - } - - if (webhookUrl) { - await t.typeText(settingsTab.webhookUrlInput, webhookUrl) - } - - // Expect form to be inactive, then activate it - await t - .expect(settingsTab.formStatus.textContent) - .contains('Your form is inactive') - await t.click(settingsTab.activateBtn) - if (formOptions.responseMode === 'encrypt') { - await t - .typeText(activateFormModal.secretKeyInput, formOptions.secretKey) - .typeText( - activateFormModal.acknowledgementInput, - 'I have shared my secret key with a colleague', - ) - .click(activateFormModal.activateFormBtn) - } else if (formOptions.authType !== 'NIL') { - await t.click(activateFormModal.closeModalBtn) - } - await t - .expect(settingsTab.formStatus.textContent) - .contains('Your form is active') -} - -async function addNewFormBySelectTemplate(t, formOptions) { - await t - .click(formList.createFormBtn) - .click(createFormModal.templateCard) // temperature taking template - .click(createFormTemplateModal.useTemplateBtn) - if (formOptions.responseMode === 'email') { - await addNewEmailForm(t, formOptions) - } else { - await addNewEncryptForm(t, formOptions) - } -} - -async function addNewForm(t, formOptions) { - await t - .click(formList.createFormBtn) - .click(createFormModal.startFromScratchBtn) - if (formOptions.responseMode === 'email') { - await addNewEmailForm(t, formOptions) - } else { - await addNewEncryptForm(t, formOptions) - } -} - -async function addNewEmailForm(t, formOptions) { - await t - .typeText(createFormModal.formTitleInput, formOptions.title, { - paste: true, - }) - .click(createFormModal.emailModeRadio) - .click(createFormModal.startBtn) -} - -async function addNewEncryptForm(t, formOptions) { - await t - .typeText(createFormModal.formTitleInput, formOptions.title, { - paste: true, - }) - .click(createFormModal.encryptModeRadio) - .click(createFormModal.startBtn) - formOptions.secretKey = await createFormModal.secretKeyDiv.innerText - await t - .click(createFormModal.downloadKeyBtn) - .click(createFormModal.encryptModeContinueBtn) -} - -/** - * Expect start page to be shown with form title. - * @param {Object} t Testcafe browser - * @param {Object} testForm Form object from DB - * @param {Object} testFormData.formOptions Object specifying form options as per createForm - * @param {string} appUrl URL of app - */ -async function expectStartPage(t, testForm, testFormData, appUrl) { - let { _id } = testForm - let { formOptions } = testFormData - // Client side redirect from /123 to /#!/123 should work - await t - .navigateTo(`${appUrl}/${_id}`) - .expect(getPageUrl()) - .contains(_id) - .expect(formPage.startTitle.textContent) - .contains(formOptions.title) -} - -/** - * Fill in form based in field type. - * @param {Object} t Testcafe browser - * @param {Object} testForm Form object from DB - * @param {Object} testFormData.formFields Object specifying responses to each field - * @param {string} appUrl URL of app - */ -async function fillInForm(t, testForm, testFormData) { - let { formFields } = testFormData - for (let i in formFields) { - const field = formFields[i] - const fieldId = testForm.form_fields[i]._id - if (field.isLeftBlank || !field.isVisible || field.disabled) { - continue - } - switch (field.fieldType) { - case 'textarea': - await t.typeText( - formPage.getFieldElement(fieldId, 'textarea'), - field.val, - { - paste: true, - }, - ) - break - case 'nric': - case 'number': - case 'email': - case 'textfield': - case 'decimal': - case 'mobile': - await t.typeText( - formPage.getFieldElement(fieldId, 'input'), - field.val, - { - paste: true, - }, - ) - if (field.isVerifiable) { - await verifyField(t, fieldId, field.val) - } - break - case 'radiobutton': { - let radioLabels = formPage.getFieldElement(fieldId, 'label') - let radio = radioLabels.withText(field.val) - let radioExists = await radio.exists - if (radioExists) { - await t.click(radio.child('.radiomark')) - } else { - radio = radioLabels.withText('Others') - await t - .click(radio) - .typeText( - formPage.getFieldElement(fieldId, 'input[type="text"]'), - field.val, - { paste: true }, - ) - } - break - } - case 'yes_no': { - let yesNo = formPage.getFieldElement(fieldId) - let toggle = yesNo.find('label').withText(field.val.toUpperCase()) - await t.click(toggle) - break - } - case 'checkbox': { - let checkboxLabels = formPage.getFieldElement(fieldId, 'label') - for (let option of field.val) { - let checkbox = checkboxLabels.withText(option) - let checkboxExists = await checkbox.exists - if (checkboxExists) { - await t.click(checkbox) - } else { - checkbox = checkboxLabels.withText('Others') - await t - .click(checkbox) - .typeText( - formPage.getFieldElement(fieldId, 'input[type="text"]'), - option, - { - paste: true, - }, - ) - } - } - break - } - case 'dropdown': { - await selectDropdownOption( - t, - formPage.getFieldElement(fieldId), - field.val, - ) - break - } - case 'date': { - let datePicker = formPage.getFieldElement(fieldId, 'input') - let today = datePicker - .nextSibling(0) - .find('button') - .withText(new RegExp(`^(${DATE})$`)) - await t.click(datePicker).click(today) - break - } - case 'rating': { - let rating = formPage.getFieldElement(fieldId, 'ul') - let star = rating.child(parseInt(field.val) - 1) - await t.click(star) - break - } - case 'table': { - const tableData = field - const table = testForm.form_fields[i] - - for (let col = 0; col < table.columns.length; col++) { - for (let row = 0; row < table.minimumRows; row++) { - let valueToFillIn = tableData.val[row][col] - switch (table.columns[col].columnType) { - case 'textfield': - await t.typeText( - formPage.getTableCell(fieldId, row, col), - valueToFillIn, - { - paste: true, - }, - ) - break - case 'dropdown': { - await selectDropdownOption( - t, - formPage.getTableCell(fieldId, row, col), - valueToFillIn, - ) - } - } - } - } - break - } - case 'attachment': - // Note that the given path must be relative to the file containing the test - await t.setFilesToUpload( - formPage.getFieldElement(fieldId, 'input'), - field.path, - ) - break - default: - continue - } - } -} - -/** - * Verifies a verifiable field. - * @param {Object} t Testcafe browser - * @param {string} fieldId ID of field to verify - * @param {string} email Email used in field response - */ -async function verifyField(t, fieldId, email) { - await t.click(formPage.getFieldElement(fieldId, '.vfn-btn')) - await expectVfnOtpSent(t, fieldId) - const otp = await extractOTP(email) - await t.typeText(formPage.getFieldElement(fieldId, '.vfn-section input'), otp) - await t.click(formPage.getFieldElement(fieldId, '.vfn-section button')) -} - -/** - * Expect end page to be shown with thank you message. - * @param {Object} t Testcafe browser - */ -async function expectEndPage(t) { - let endPageTitle - if (_.isUndefined(t.ctx.endPageTitle)) { - endPageTitle = 'Thank you for filling out the form.' - } else { - endPageTitle = t.ctx.endPageTitle - } - - await t.expect(formPage.endTitle.textContent).contains(endPageTitle) -} - -/** - * Clicks the submit button. - * @param {Object} t Testcafe browser - */ -async function submitForm(t) { - await t.click(formPage.submitBtn) -} - -/** - * Types an OTP into the sign-in text box. - * @param {Object} t Testcafe browser - * @param {string} otp - */ -async function enterOTP(t, otp) { - await t - .typeText(signInPage.otpInput, otp, { paste: true }) - .click(signInPage.signInBtn) -} - -// Fills nested arrays with given value. -function fillRecursive(obj, value) { - if (Array.isArray(obj)) { - return obj.map((elt) => fillRecursive(elt, value)) - } - return value -} - -/** - * Returns an optional version of a field. - * @param {Object} field - */ -function getOptionalVersion(field) { - const optionalField = _.cloneDeep(field) - optionalField.required = false - if (optionalField.fieldType === 'table') { - optionalField.columns.forEach((column) => { - column.required = false - }) - } - return optionalField -} - -/** - * Returns a field with a blank response. - * @param {Object} field - */ -function getBlankVersion(field) { - const blankField = _.cloneDeep(field) - blankField.isLeftBlank = true - blankField.val = - blankField.fieldType === 'table' ? fillRecursive(blankField.val, '') : '' - return blankField -} - -/** - * Returns a hidden version of a field. - * @param {Object} field - */ -function getHiddenVersion(field) { - const hiddenField = _.cloneDeep(field) - hiddenField.isVisible = false - return hiddenField -} - -/** - * Creates a field for e2e tests with default options (visible, required, not blank). - * @param {Object} fieldObj Custom options of the field. - * @param {string} fieldObj.title Mandatory title of field - * @param {string} fieldObj.fieldType Type of field. Mandatory unless makeField is - * being called to create an artificial field for verified SingPass/CorpPass data. - * @param {string} fieldObj.val Mandatory answer to field - */ -function makeField(fieldObj) { - return Object.assign( - { - required: true, - isVisible: true, - isLeftBlank: false, - }, - fieldObj, - ) -} - -/** - * Fetches a substring of text between two string markers. - * @param {string} text Text to substring - * @param {string} markerStart String after which result starts - * @param {string} markerEnd String before which result ends - */ -const getSubstringBetween = (text, markerStart, markerEnd) => { - const start = text.indexOf(markerStart) - if (start === -1) { - return null - } else { - const end = text.indexOf(markerEnd, start) - return end === -1 ? null : text.substring(start + markerStart.length, end) - } -} - -/** - * Converts given string from encoded HTML to plain text. - * @param {string} html - */ -const decodeHtmlEntities = (html) => - html.replace(/&#(\d+);/g, (match, dec) => String.fromCharCode(dec)) - -/** - * Tests that container contains all the values in contained. - * @param {Object} t Testcafe browser - * @param {string} container String in which to search - * @param {Array[string]} containedArray Array of values to search for - */ -const expectContains = async (t, container, containedArray) => { - for (let contained of containedArray) { - await t.expect(container).contains(contained) - } -} - -/** - * Creates a list of integers, inclusive of the parameters. - * @param {number} from starting index (inclusive) - * @param {number} to ending index (inclusive) - */ -const listIntsInclusive = (from, to) => { - const result = [] - for (let i = from; i <= to; i++) { - result.push(i) - } - return result -} - -// Utility for getting responses for table -const tableHandler = { - getName: (tableField, formMode) => { - const { title, columns } = tableField - let tableTitle = `${title} (${columns.map((x) => x.title).join(', ')})` - if (formMode === 'email') { - tableTitle = '[table] ' + tableTitle - } - return tableTitle - }, - getValues: (tableField, formMode) => { - if (formMode === 'email') { - return tableField.val.map((row) => row.join(',')) - } else { - // storage mode has a space - return tableField.val.map((row) => row.join(', ')) - } - }, -} - -/** - * Gets answers for a field as displayed in response email or decrypted submission. - * Always returns an array, so caller must always loop through result. - * @param {Object} field Field as created by makeField - * @param {string} formMode 'email' or 'encrypt' - */ -const getResponseArray = (field, formMode) => { - // Deal with table first to avoid special cases later - if (field.fieldType === 'table') { - return tableHandler.getValues(field, formMode) - } - // Only allow blanks if field is not visible or optional - if (!field.isVisible || (!field.required && field.isLeftBlank)) { - return [''] - } - switch (field.fieldType) { - case 'checkbox': - return [ - field.val - .map((selected) => { - return field.fieldOptions.includes(selected) - ? selected - : `Others: ${selected}` - }) - .join(', '), - ] - case 'radiobutton': - return [ - field.fieldOptions.includes(field.val) - ? field.val - : `Others: ${field.val}`, - ] - default: - return [ - field.val instanceof Array ? field.val.join(', ') : String(field.val), - ] - } -} - -/** - * Gets the title of a field as it is displayed in a response. - * @param {Object} field Field as created by makeField - * @param {boolean} isInJson Whether the title is within the - * JSON data for email submissions - * @param {string} formMode 'email' or 'encrypt' - */ -const getResponseTitle = (field, isInJson, formMode) => { - if (field.fieldType === 'table') { - return tableHandler.getName(field, formMode) - } else if (field.fieldType === 'attachment') { - let title = field.title - if (formMode === 'email') { - title = `[attachment] ${title}` - } - return title - } else { - if (isInJson) { - if (field.title.startsWith('[verified] ')) { - return field.title.substr(11) - } else if (field.title.startsWith('[MyInfo] ')) { - return field.title.substr(9) - } - } - return field.title - } -} - -/** - * Tests for the SP/CP login page and logs in. - * @param {Object} t Testcafe browser - * @param {string} authType SP, CP or NIL - * @param {Object} authData Must contain a testSpNric or (testCpNric and testCpUen) - */ -const expectSpcpLogin = async (t, authType, authData) => { - const { testSpNric, testCpNric, testCpUen } = authData - switch (authType) { - case 'MyInfo': - await t - .expect(formPage.spcpLoginBtn.textContent) - .contains(`Login with Singpass`) - .click(formPage.spcpLoginBtn) - .click(mockpass.loginBtn) - .click(mockpass.nricDropdownBtn) - .click(mockpass.getNricOption(testSpNric)) - .click(mockpass.consentBtn) - .expect(formPage.spcpLogoutBtn.textContent) - .contains(`${testSpNric} - Log out`) - break - case 'SP': - await t - .expect(formPage.spcpLoginBtn.textContent) - .contains(`Login with Singpass`) - .click(formPage.spcpLoginBtn) - .click(mockpass.loginBtn) - .click(mockpass.nricDropdownBtn) - .click(mockpass.getNricOption(testSpNric)) - .expect(formPage.spcpLogoutBtn.textContent) - .contains(`${testSpNric} - Log out`) - break - case 'CP': - await t - .expect(formPage.spcpLoginBtn.textContent) - .contains(`Login with Singpass (Corporate)`) - .click(formPage.spcpLoginBtn) - .click(mockpass.loginBtn) - .click(mockpass.nricDropdownBtn) - .click(mockpass.getNricUenOption(testCpNric, testCpUen)) - .expect(formPage.spcpLogoutBtn.textContent) - .contains(`${testCpUen} - Log out`) - break - default: - throw new Error('Invalid authentication type!') - } -} - -/** - * Creates a field to imitate the verified NRIC for SingPass or verified - * UEN or UID for CorpPass. Used to validate submissions for SP/CP - * authenticated forms. - * @param {string} authType one of 'NIL', 'SP' or 'CP' - * @param {Object} authData Contains testSpNric for authType === 'SP' and - * both testCpNric and testCpUen for authType === 'CP' - * @returns {Array} Array of new artificial authenticated fields - */ -const getAuthFields = (authType, authData) => { - switch (authType) { - case 'NIL': - return [] - case 'MyInfo': - case 'SP': - return [ - makeField({ - title: SPCPFieldTitle.SpNric, - val: authData.testSpNric, - }), - ] - case 'CP': - return [ - { title: SPCPFieldTitle.CpUen, val: authData.testCpUen }, - { - title: SPCPFieldTitle.CpUid, - val: authData.testCpNric, - }, - ].map(makeField) - default: - throw new Error('Invalid authentication type!') - } -} - -/** - * Expects form submisision to be disabled and toast message to be shown. - * @param {Object} browser Testcafe browser - * @param {string} disabledMessage message to be shown underneath the submit button when form is disabled - */ -async function expectFormDisabled(browser, disabledMessage) { - await browser.expect(formPage.submitBtn.getAttribute('disabled')).ok() - await browser - .expect(formPage.submitPreventedMessage.textContent) - .eql(disabledMessage) -} - -/** - * Opens a form and verifies that submission is disabled when the given answers are filled. - * @param {Object} browser Testcafe browser - * @param {Object} form Form object from DB - * @param {Object} formData Data based on which to fill in form - * @param {Object} formData.formOptions Object specifying form options as per createForm - * @param {string} disabledMessage Message to show in toaster when form is disabled - * @param {Object} [authData] Authentication data for SingPass/CorpPass if form is authenticated - * @param {string} [authData.testSpNric] NRIC for SingPass login - * @param {string} [authData.testCpNric] NRIC for CorpPass login - * @param {string} [authData.testCpUen] UEN for CorpPass login - */ -const verifySubmissionDisabled = async ( - browser, - form, - formData, - disabledMessage, - authData, -) => { - // Verify that form can be accessed - await expectStartPage(browser, form, formData, appUrl) - if (authData) { - await expectSpcpLogin(browser, formData.formOptions.authType, authData) - } - // Fill in form - await fillInForm(browser, form, formData) - // Expect form to be disabled with message - await expectFormDisabled(browser, disabledMessage) -} - -module.exports = { - mailClient, - enterEmail, - extractOTP, - enterOTPAndExpect, - expectOtpSent, - getPageUrl, - makeMongooseFixtures, - makeModel, - logInWithEmail, - appUrl, - deleteDocById, - getSubmission, - emptyMailServer: mailClient.deleteAll, - createFormFromTemplate, - createForm, - expectStartPage, - fillInForm, - submitForm, - expectEndPage, - enterOTP, - addLogic, - getOptionalVersion, - getBlankVersion, - getHiddenVersion, - makeField, - getSubstringBetween, - decodeHtmlEntities, - expectContains, - listIntsInclusive, - getResponseArray, - getResponseTitle, - expectSpcpLogin, - getAuthFields, - verifySubmissionDisabled, - getDownloadsFolder, - getFeatureState, -} diff --git a/tests/end-to-end/helpers/verifiable-email-field.js b/tests/end-to-end/helpers/verifiable-email-field.js deleted file mode 100644 index ef0f0eab76..0000000000 --- a/tests/end-to-end/helpers/verifiable-email-field.js +++ /dev/null @@ -1,12 +0,0 @@ -// Exports data for just verifiable email field -const { makeField } = require('./util') -const verifiableEmailFieldInfo = [ - { - title: 'Personal Email', - fieldType: 'email', - isVerifiable: true, - val: 'test@test.gov.sg', - }, -] -const verifiableEmailField = verifiableEmailFieldInfo.map(makeField) -module.exports = { verifiableEmailField } diff --git a/tests/end-to-end/login.e2e.js b/tests/end-to-end/login.e2e.js deleted file mode 100644 index e0345b296d..0000000000 --- a/tests/end-to-end/login.e2e.js +++ /dev/null @@ -1,211 +0,0 @@ -const { signInPage, formList, landingPage } = require('./helpers/selectors') -const { - getPageUrl, - enterEmail, - extractOTP, - enterOTPAndExpect, - expectOtpSent, - makeMongooseFixtures, - makeModel, - appUrl, - deleteDocById, -} = require('./helpers/util') - -let db -let User -let Agency -let Token -let govTech -let createUser -let deleteToken -fixture('login') - .page(appUrl + '/#!/signin') - .before(async () => { - db = await makeMongooseFixtures() - Agency = makeModel(db, 'agency.server.model', 'Agency') - User = makeModel(db, 'user.server.model', 'User') - Token = makeModel(db, 'token.server.model', 'Token') - govTech = await Agency.findOne({ shortName: 'govtech' }).exec() - createUser = async (email) => { - return new User({ - email, - agency: govTech._id, - contact: '+6587654321', - }) - .save() - .catch((error) => console.error(error)) - } - deleteToken = async (email) => { - return Token.deleteOne({ email }).catch((error) => console.error(error)) - } - }) - .after(async () => { - // Delete models defined by mongoose and close connection - db.models = {} - await db.close() - }) - -test('Reject emails that do not have white-listed domains', async (t) => { - // Enter email - await enterEmail(t, 'user@non-white-listed-agency.com') - - // Ensure error message is seen - await t - .expect(signInPage.emailErrorMsg.textContent) - .contains( - 'Please log in with your official government or government-linked email address.', - ) -}) - -test - - .before(async (t) => { - t.ctx.user = await createUser('existinguser@data.gov.sg') - }) - .after(async (t) => { - await deleteToken(t.ctx.user.email) - await deleteDocById(User, t.ctx.user._id) - })( - 'Send otp to emails that have white-listed domains (Current User flow)', - async (t) => { - let email = t.ctx.user.email - // Enter email - await enterEmail(t, email) - - // Ensure that 'OTP sent' success message is shown - await expectOtpSent(t, email) - - // Enter OTP - let otp = await extractOTP(email) - await enterOTPAndExpect({ t, otp, isValid: true, email }) - }, -) - -test - - .before((t) => { - t.ctx.email = 'newuser@data.gov.sg' - }) - .after(async (t) => { - await deleteToken(t.ctx.email) - })( - 'Send otp to emails that have white-listed domains (New User flow)', - async (t) => { - let email = t.ctx.email - // Enter email - await enterEmail(t, email) - - // Ensure that 'OTP sent' success message is shown - await expectOtpSent(t, email) - - // Enter OTP - let otp = await extractOTP(email) - await enterOTPAndExpect({ t, otp, isValid: true, email }) - }, -) - -test - - .before(async (t) => { - t.ctx.user = await createUser('preventuseremail@data.gov.sg') - }) - .after(async (t) => { - await deleteToken(t.ctx.user.email) - await deleteDocById(User, t.ctx.user._id) - })('Prevent sign-in if OTP is incorrect', async (t) => { - let email = t.ctx.user.email - // Enter email - await enterEmail(t, email) - - // Ensure that 'OTP sent' success message is shown - await expectOtpSent(t, email) - - // Get correct otp - let correctOtp = await extractOTP(email) - - // Generate incorrect otp by replacing digit in first index with its complement - // i.e. 123456 becomes 923456 - let incorrectOtp = - 9 - parseInt(correctOtp.substring(0, 1)) + correctOtp.substring(1) - - // Ensure that invalid otp is not accepted - await enterOTPAndExpect({ t, otp: incorrectOtp, isValid: false, email }) - - // Remove current OTP and enter correct OTP - await t.selectText(signInPage.otpInput).pressKey('delete') - await enterOTPAndExpect({ t, otp: correctOtp, isValid: true, email }) -}) - -test - - .before(async (t) => { - t.ctx.user = await createUser('resenduseremail@data.gov.sg') - }) - .after(async (t) => { - await deleteToken(t.ctx.user.email) - await deleteDocById(User, t.ctx.user._id) - })('Resend OTP on request and invalidate old OTP', async (t) => { - let email = t.ctx.user.email - // Enter email - await enterEmail(t, email) - - // Ensure that 'OTP sent' success message is shown - await expectOtpSent(t, email) - - // Extract first OTP - let firstOtp = await extractOTP(email) - - // Click resend Otp - await t.click(signInPage.resendOtpLink) - await expectOtpSent(t, email) - - // Extract second OTP - let secondOtp = await extractOTP(email) - - // Reject invalidated OTP - await enterOTPAndExpect({ t, otp: firstOtp, isValid: false, email }) - - // Remove current OTP and enter correct OTP - await t.selectText(signInPage.otpInput).pressKey('delete') - await enterOTPAndExpect({ t, otp: secondOtp, isValid: true, email }) -}) - -test - - .before(async (t) => { - t.ctx.user = await createUser('logoutuseremail@data.gov.sg') - }) - .after(async (t) => { - await deleteToken(t.ctx.user.email) - await deleteDocById(User, t.ctx.user) - })('Should logout to main screen', async (t) => { - let email = t.ctx.user.email - // Enter email - await enterEmail(t, email) - - // Ensure that 'OTP sent' success message is shown - await expectOtpSent(t, email) - - // Extract OTP and log in - let otp = await extractOTP(email) - await enterOTPAndExpect({ t, otp, isValid: true, email }) - - await t.click(formList.avatarDropdown) - await t.click(formList.logOutBtn) - await t - .expect(getPageUrl()) - .eql(appUrl + '/#!/') - // Due to text spanning multiple spans, remove all whitespace - .expect((await landingPage.tagline.textContent).replace(/ +/g, ' ')) - .contains('Build government forms in minutes') -}) - -test('Prevent sign-in if email is invalid', async (t) => { - let email = 'ani@open.gov.rg' - // Enter email - await enterEmail(t, email) - - await t - .expect(signInPage.emailErrorMsg().textContent) - .contains('Please enter a valid email') -})