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 5ebf43c17d..0000000000 Binary files a/tests/end-to-end/files/logo.jpg and /dev/null differ diff --git a/tests/end-to-end/helpers/all-fields.js b/tests/end-to-end/helpers/all-fields.js deleted file mode 100644 index 7dacad9338..0000000000 --- a/tests/end-to-end/helpers/all-fields.js +++ /dev/null @@ -1,125 +0,0 @@ -// Exports data for all basic field types. -const { makeField } = require('./util') -const moment = require('moment-timezone') -const DATE = 17 -const allFieldInfo = [ - { - title: 'Name', - fieldType: 'textfield', - val: 'Lorem Ipsum', - }, - { - title: 'Your Life Story', - fieldType: 'textarea', - val: - 'Vestibulum sed facilisis nibh, vel semper nisl. Phasellus dictum sem et ligula vulputate malesuada. Phasellus posuere luctus sapien eu molestie. In euismod vestibulum orci ac blandit. Suspendisse est arcu, vestibulum id viverra sed, tempus in ligula. Maecenas consequat pharetra lorem, ac vulputate neque sodales non. Mauris vel lacus ipsum.', - }, - { - title: 'Personal Email', - fieldType: 'email', - isVerifiable: false, - val: 'test@test.gov.sg', - }, - { - title: 'Phone Number', - fieldType: 'number', - val: '61234567', - }, - { - title: 'NRIC Number', - fieldType: 'nric', - val: 'S9991334G', - }, - { - title: 'Yes or No?', - fieldType: 'yes_no', - val: 'Yes', - }, - { - title: 'Favourite Food', - fieldType: 'dropdown', - fieldOptions: ['Rice', 'Chocolate', 'Ice-Cream'], - val: 'Chocolate', - }, - { - title: 'Birthday', - fieldType: 'date', - val: moment().set('date', DATE).format('DD MMM YYYY'), - }, - { - title: 'Happiness Score', - fieldType: 'rating', - ratingOptions: { - steps: 5, - shape: 'Heart', - }, - val: '3', - }, - { - title: 'Family Members', - fieldType: 'table', - minimumRows: 2, - addMoreRows: false, - columns: [ - { - title: 'Name', - required: true, - columnType: 'textfield', - }, - { - title: 'Gender', - required: true, - fieldOptions: ['Male', 'Female'], - columnType: 'dropdown', - }, - ], - val: [ - ['John', 'Male'], - ['Lisa', 'Female'], - ], - }, - { - title: 'Pi', - fieldType: 'decimal', - ValidationOptions: { - customMin: 3, - customMax: null, - }, - validateByValue: true, - val: '3.1415926535', - }, - { - title: 'Mobile', - fieldType: 'mobile', - val: '+6598889999', - }, - { - title: 'How did you hear about the event?', - fieldType: 'checkbox', - fieldOptions: ['School', 'Career Fairs / Talks', 'Online and Social Media'], - othersRadioButton: true, - val: ['School', 'Mailing list'], - }, - { - title: 'Mother Tongue Language', - fieldType: 'radiobutton', - fieldOptions: ['Chinese', 'Malay', 'Tamil'], - othersRadioButton: true, - val: 'Klingon', - }, - { - title: 'Attachment', - fieldType: 'attachment', - attachmentSize: '1', - val: 'test-att.txt', - path: '../files/att-folder-1/test-att.txt', - content: 'att-folder-1', - }, -] -const allFields = allFieldInfo.map(makeField) -module.exports = { - allFields, - allFieldsEncrypt: allFields.filter( - (field) => 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') -})