diff --git a/.gitignore b/.gitignore index 9acd6f5f69..ed21cb0ff9 100644 --- a/.gitignore +++ b/.gitignore @@ -192,3 +192,7 @@ fabric.properties ### VisualStudioCode template .vs/* .vscode/* + +# video and screenshot of the tests from Cypress +./e2e/screenshots +./e2e/videos diff --git a/README.md b/README.md index cf9c7079e3..3ebdc5c032 100644 --- a/README.md +++ b/README.md @@ -97,8 +97,10 @@ Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github. ### Running end-to-end tests -Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/). -Before running the tests make sure you are serving the app via `ng serve`. +Run `npm run e2e` to execute the end-to-end tests via [Cypress](http://www.cypress.io/) in the terminal. + +Run `npm run e2e-open` to execute the end-to-end tests via Cypress own User Interface. + ### Build a docker image locally Deployment on a server can be done through a docker image, our [ndb-setup project](https://github.com/Aam-Digital/ndb-setup) provides tools and a starting point to run the system using docker. diff --git a/angular.json b/angular.json index 95fc8d588a..8bb51d2f6e 100644 --- a/angular.json +++ b/angular.json @@ -10,7 +10,8 @@ "i18n": { "sourceLocale": "en-US", "locales": { - "de": "src/locale/messages.de.xlf" + "de": "src/locale/messages.de.xlf", + "fr": "src/locale/messages.fr.xlf" } }, "architect": { @@ -63,7 +64,11 @@ "serviceWorker": true }, "localized": { - "localize": ["de", "en-US"] + "localize": [ + "de", + "en-US", + "fr" + ] } } }, @@ -119,24 +124,151 @@ }, "ndb-core-e2e": { "root": "", - "sourceRoot": "", + "sourceRoot": "src", "projectType": "application", + "i18n": { + "sourceLocale": "en-US", + "locales": { + "de": "src/locale/messages.de.xlf" + } + }, "architect": { - "e2e": { - "builder": "@angular-devkit/build-angular:protractor", + "build": { + "builder": "@angular-devkit/build-angular:browser", + "options": { + "aot": true, + "outputPath": "dist", + "index": "src/index.html", + "main": "src/main.ts", + "tsConfig": "src/tsconfig.app.json", + "polyfills": "src/polyfills.ts", + "i18nMissingTranslation": "warning", + "assets": [ + "src/assets", + "src/favicon.ico", + "src/manifest.json" + ], + "styles": [ + "src/ndb-theme.scss", + "src/styles.scss", + "node_modules/flag-icon-css/css/flag-icon.min.css" + ], + "scripts": [ + "node_modules/marked/lib/marked.js" + ] + }, + "configurations": { + "production": { + "budgets": [ + { + "type": "anyComponentStyle", + "maximumWarning": "6kb" + } + ], + "optimization": true, + "outputHashing": "all", + "sourceMap": true, + "namedChunks": false, + "aot": true, + "extractLicenses": true, + "vendorChunk": false, + "buildOptimizer": true, + "fileReplacements": [ + { + "replace": "src/environments/environment.ts", + "with": "src/environments/environment.prod.ts" + } + ], + "serviceWorker": true + }, + "localized": { + "localize": [ + "de", + "en-US" + ] + } + } + }, + "serve": { + "builder": "@angular-devkit/build-angular:dev-server", + "options": { + "browserTarget": "ndb-core-e2e:build" + }, + "configurations": { + "production": { + "browserTarget": "ndb-core-e2e:build:production" + } + } + }, + "extract-i18n": { + "builder": "@angular-devkit/build-angular:extract-i18n", + "options": { + "browserTarget": "ndb-core-e2e:build" + } + }, + "test": { + "builder": "@angular-devkit/build-angular:karma", "options": { - "protractorConfig": "./protractor.conf.js", - "devServerTarget": "ndb-core:serve" + "main": "src/test.ts", + "karmaConfig": "./karma.conf.js", + "polyfills": "src/polyfills.ts", + "tsConfig": "src/tsconfig.spec.json", + "scripts": [ + "node_modules/marked/lib/marked.js" + ], + "styles": [ + "src/ndb-theme.scss", + "src/styles.scss" + ], + "assets": [ + "src/assets", + "src/favicon.ico", + "src/manifest.json" + ] } }, "lint": { "builder": "@angular-devkit/build-angular:tslint", "options": { "tsConfig": [ - "e2e/tsconfig.e2e.json" + "src/tsconfig.app.json", + "src/tsconfig.spec.json", + "e2e/tsconfig.json" ], "exclude": [] } + }, + "cypress-run": { + "builder": "@cypress/schematic:cypress", + "options": { + "devServerTarget": "ndb-core-e2e:serve" + }, + "configurations": { + "production": { + "devServerTarget": "ndb-core-e2e:serve:production" + } + } + }, + "cypress-open": { + "builder": "@cypress/schematic:cypress", + "options": { + "watch": true, + "headless": false, + "devServerTarget": "ndb-core-e2e:serve" + } + }, + "e2e": { + "builder": "@cypress/schematic:cypress", + "options": { + "devServerTarget": "ndb-core-e2e:serve", + "watch": false, + "headless": true + }, + "configurations": { + "production": { + "devServerTarget": "ndb-core-e2e:serve:production" + } + } } } } diff --git a/build/default.conf b/build/default.conf index d38723fd83..5309b33f0b 100644 --- a/build/default.conf +++ b/build/default.conf @@ -19,6 +19,11 @@ server { try_files $uri $uri/ /de/index.html; } + location /fr { + index index.html index.htm; + try_files $uri $uri/ /fr/index.html; + } + location /en { index index.html index.htm; try_files $uri $uri/ /en-US/index.html; diff --git a/cypress.json b/cypress.json new file mode 100644 index 0000000000..ca6554bbef --- /dev/null +++ b/cypress.json @@ -0,0 +1,10 @@ +{ + "integrationFolder": "e2e/integration", + "supportFile": "e2e/support/index.ts", + "videosFolder": "e2e/videos", + "screenshotsFolder": "e2e/screenshots", + "pluginsFile": "e2e/plugins/index.ts", + "fixturesFolder": "e2e/fixtures", + "baseUrl": "http://localhost:4200", + "video": false +} diff --git a/e2e/app.e2e-spec.ts b/e2e/app.e2e-spec.ts deleted file mode 100644 index 1a006594a3..0000000000 --- a/e2e/app.e2e-spec.ts +++ /dev/null @@ -1,31 +0,0 @@ -/* - * This file is part of ndb-core. - * - * ndb-core is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ndb-core is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ndb-core. If not, see . - */ - -import { NdbCorePage } from "./app.po"; - -describe("ndb-core App", () => { - let page: NdbCorePage; - - beforeEach(() => { - page = new NdbCorePage(); - }); - - it("should display message saying app works", () => { - page.navigateTo(); - // expect(page.getParagraphText()).toEqual("app works!"); - }); -}); diff --git a/e2e/app.po.ts b/e2e/app.po.ts deleted file mode 100644 index ea45250cbb..0000000000 --- a/e2e/app.po.ts +++ /dev/null @@ -1,28 +0,0 @@ -/* - * This file is part of ndb-core. - * - * ndb-core is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ndb-core is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ndb-core. If not, see . - */ - -import { browser, by, element } from "protractor"; - -export class NdbCorePage { - navigateTo() { - return browser.get("/"); - } - - getParagraphText() { - return element(by.css("app-root h1")).getText(); - } -} diff --git a/e2e/fixtures/example.json b/e2e/fixtures/example.json new file mode 100644 index 0000000000..02e4254378 --- /dev/null +++ b/e2e/fixtures/example.json @@ -0,0 +1,5 @@ +{ + "name": "Using fixtures to represent data", + "email": "hello@cypress.io", + "body": "Fixtures are a great way to mock data for responses to routes" +} diff --git a/e2e/integration/LinkingChildToSchool/LinkingChildToSchool.ts b/e2e/integration/LinkingChildToSchool/LinkingChildToSchool.ts new file mode 100644 index 0000000000..61b8d3d7e5 --- /dev/null +++ b/e2e/integration/LinkingChildToSchool/LinkingChildToSchool.ts @@ -0,0 +1,61 @@ +describe("Scenario: Linking a child to a school - E2E test", () => { + before(() => { + // GIVEN I am on the details page of a child + cy.wrap("Aaditya School").as("schoolName"); + // cy.wrap(0).as('studentsCount') + cy.visit("http://localhost:4200/child/1"); + cy.wait(10000); + + cy.get(".page-header > .ng-star-inserted").invoke("text").as("studentName"); + }); + + // WHEN I add an entry in the 'Previous Schools' section with a specific school + // (with todays date - that might be added to this e2e test later) + it("Add an entry in the Previous School section", function () { + // get the Education button and click on it + cy.get("#mat-expansion-panel-header-1 > .mat-content") + .should("contain", "Education") + .click(); + // get the Show All button and toggle it, forcing the click is needed because the element has a hidden feature + cy.get( + "#mat-slide-toggle-1 > .mat-slide-toggle-label > .mat-slide-toggle-bar > .mat-slide-toggle-thumb-container > .mat-slide-toggle-thumb" + ).click({ force: true }); + // get the Add School button and click on it + cy.get( + "app-previous-schools.ng-star-inserted > app-entity-subrecord > .container > .mat-table > thead > .mat-header-row > .cdk-column-actions > .mat-focus-indicator" + ).click(); + + // choose the school to add + cy.get('[ng-reflect-placeholder="Select School"]') + .type(this.schoolName) + .click(); + // save school in child profile + cy.contains("button", "Save").click(); + }); + + // THEN I can see that child in the 'Children Overview' of the details page of this school + it("Check for child in Children Overview of specific school", function () { + // Go to the school overview page + cy.contains("mat-list-item", "Schools").click(); + + // Choose the school that was added to the child profile + cy.contains( + "tbody > :nth-child(1) > .cdk-column-name", + this.schoolName + ).click(); + // Open the students overview + cy.contains("mat-expansion-panel-header", "Students").click(); + + // Here a wait function is implemented because cypress is faster then the website + cy.wait(2000); + + // Toggle 'Show All' switch to display all students + cy.get(".mat-slide-toggle-content").click({ force: true }); + + // Check if student is in the school students list + cy.contains( + "#cdk-accordion-child-10 > .mat-expansion-panel-body", + this.studentName + ); + }); +}); diff --git a/e2e/plugins/index.ts b/e2e/plugins/index.ts new file mode 100644 index 0000000000..edf74d3186 --- /dev/null +++ b/e2e/plugins/index.ts @@ -0,0 +1,3 @@ +// Plugins enable you to tap into, modify, or extend the internal behavior of Cypress +// For more info, visit https://on.cypress.io/plugins-api +module.exports = (on, config) => {}; diff --git a/e2e/support/commands.ts b/e2e/support/commands.ts new file mode 100644 index 0000000000..af1f44a0fc --- /dev/null +++ b/e2e/support/commands.ts @@ -0,0 +1,43 @@ +// *********************************************** +// This example namespace declaration will help +// with Intellisense and code completion in your +// IDE or Text Editor. +// *********************************************** +// declare namespace Cypress { +// interface Chainable { +// customCommand(param: any): typeof customCommand; +// } +// } +// +// function customCommand(param: any): void { +// console.warn(param); +// } +// +// NOTE: You can use it like so: +// Cypress.Commands.add('customCommand', customCommand); +// +// *********************************************** +// This example commands.js shows you how to +// create various custom commands and overwrite +// existing commands. +// +// For more comprehensive examples of custom +// commands please read more here: +// https://on.cypress.io/custom-commands +// *********************************************** +// +// +// -- This is a parent command -- +// Cypress.Commands.add("login", (email, password) => { ... }) +// +// +// -- This is a child command -- +// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) +// +// +// -- This is a dual command -- +// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) +// +// +// -- This will overwrite an existing command -- +// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) diff --git a/e2e/support/index.ts b/e2e/support/index.ts new file mode 100644 index 0000000000..ac293b6165 --- /dev/null +++ b/e2e/support/index.ts @@ -0,0 +1,17 @@ +// *********************************************************** +// This example support/index.js is processed and +// loaded automatically before your test files. +// +// This is a great place to put global configuration and +// behavior that modifies Cypress. +// +// You can change the location of this file or turn off +// automatically serving support files with the +// 'supportFile' configuration option. +// +// You can read more here: +// https://on.cypress.io/configuration +// *********************************************************** + +// When a command from ./commands is ready to use, import with `import './commands'` syntax +// import './commands'; diff --git a/e2e/tsconfig.e2e.json b/e2e/tsconfig.e2e.json deleted file mode 100644 index e2a9a2fc77..0000000000 --- a/e2e/tsconfig.e2e.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "extends": "../tsconfig.json", - "compilerOptions": { - "outDir": "../out-tsc/e2e", - "module": "commonjs", - "target": "es5", - "types": [ - "jasmine", - "node" - ] - } -} diff --git a/e2e/tsconfig.json b/e2e/tsconfig.json new file mode 100644 index 0000000000..d046303b59 --- /dev/null +++ b/e2e/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../tsconfig.json", + "include": ["**/*.ts"], + "compilerOptions": { + "sourceMap": false, + "types": ["cypress", "node"] + } +} diff --git a/package-lock.json b/package-lock.json index 18e191cc99..c10f1e0f0e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,13 +28,13 @@ "@fortawesome/free-regular-svg-icons": "^5.15.2", "@fortawesome/free-solid-svg-icons": "^5.15.4", "@ngneat/until-destroy": "^8.1.4", - "@sentry/browser": "^6.13.2", - "angulartics2": "^10.0.0", + "@sentry/browser": "^6.15.0", + "angulartics2": "^10.1.0", "crypto-js": "^4.1.1", "deep-object-diff": "^1.1.0", "faker": "^5.5.3", "flag-icon-css": "^3.5.0", - "idb": "^6.1.2", + "idb": "^6.1.5", "json-query": "^2.2.2", "lodash": "^4.17.21", "md5": "^2.3.0", @@ -59,6 +59,7 @@ "@angular/compiler-cli": "^11.2.4", "@babel/core": "^7.13.8", "@compodoc/compodoc": "1.1.11", + "@cypress/schematic": "^1.5.1", "@schematics/angular": "^11.2.14", "@storybook/addon-actions": "^6.1.21", "@storybook/addon-essentials": "^6.1.21", @@ -73,6 +74,7 @@ "@types/pouchdb": "^6.4.0", "babel-loader": "^8.2.2", "codelyzer": "6.0.1", + "cypress": "8.5.0", "husky": "^5.1.3", "jasmine-core": "^3.8.0", "jasmine-spec-reporter": "^6.0.0", @@ -86,7 +88,6 @@ "lint-staged": "^10.5.4", "ngx-i18nsupport": "^0.17.1", "prettier": "~2.2.1", - "protractor": "~7.0.0", "ts-node": "^9.1.1", "tslint": "^6.1.3", "tslint-config-prettier": "^1.18.0", @@ -102,13 +103,12 @@ "dev": true }, "node_modules/@angular-devkit/architect": { - "version": "0.1202.6", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1202.6.tgz", - "integrity": "sha512-DQHK5VGfPof1TuSmRmt2Usw2BuNVLzxKSSy7+tEJbYzqf8N/wQO+1M67ye8qf8gAU88xGo378dD9++DFc/PJZA==", + "version": "0.1200.5", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1200.5.tgz", + "integrity": "sha512-222VZ4OeaDK3vON8V5m+w15SRWfUs5uOb4H9ij/H9/6tyHD83uWfCDoOGg+ax4wJVdWEFJIS+Vn4ijGcZCq9WQ==", "dev": true, - "peer": true, "dependencies": { - "@angular-devkit/core": "12.2.6", + "@angular-devkit/core": "12.0.5", "rxjs": "6.6.7" }, "engines": { @@ -117,6 +117,25 @@ "yarn": ">= 1.13.0" } }, + "node_modules/@angular-devkit/architect/node_modules/@angular-devkit/core": { + "version": "12.0.5", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-12.0.5.tgz", + "integrity": "sha512-zVSQV+8/vjUjsUKGlj8Kf5LioA6AXJTGI0yhHW9q1dFX4dPpbW63k0R1UoIB2wJ0F/AbYVgpnPGPe9BBm2fvZA==", + "dev": true, + "dependencies": { + "ajv": "8.2.0", + "ajv-formats": "2.0.2", + "fast-json-stable-stringify": "2.1.0", + "magic-string": "0.25.7", + "rxjs": "6.6.7", + "source-map": "0.7.3" + }, + "engines": { + "node": "^12.14.1 || >=14.0.0", + "npm": "^6.11.0 || ^7.5.6", + "yarn": ">= 1.13.0" + } + }, "node_modules/@angular-devkit/build-angular": { "version": "0.1102.14", "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-0.1102.14.tgz", @@ -1021,11 +1040,10 @@ "dev": true }, "node_modules/@angular-devkit/core": { - "version": "12.2.6", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-12.2.6.tgz", - "integrity": "sha512-E+OhY34Vmwyy1/PaX/nzao40XM70wOr7Urh00sAtV8sPLXMLeW0gHk4DUchCKohxQkrIL0AxYt1aeUVgIc7bSA==", + "version": "12.2.7", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-12.2.7.tgz", + "integrity": "sha512-WeLlDZaudpx10OGDPfVcWu/CaEWiWzAaLTUQz0Ww/yM+01FxR/P8yeH1sYAV1MS6d6KHvXGw7Lpf8PV7IA/zHA==", "dev": true, - "peer": true, "dependencies": { "ajv": "8.6.2", "ajv-formats": "2.1.0", @@ -1040,6 +1058,39 @@ "yarn": ">= 1.13.0" } }, + "node_modules/@angular-devkit/core/node_modules/ajv": { + "version": "8.6.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.6.2.tgz", + "integrity": "sha512-9807RlWAgT564wT+DjeyU5OFMPjmzxVobvDFmNAhY+5zD6A2ly3jDp6sgnfyDtlIQ+7H97oc/DGCzzfu9rjw9w==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@angular-devkit/core/node_modules/ajv-formats": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.0.tgz", + "integrity": "sha512-USH2jBb+C/hIpwD2iRjp0pe0k+MvzG0mlSn/FIdCgQhUb9ALPRjt2KIQdfZDS9r0ZIeUAg7gOu9KL0PFqGqr5Q==", + "dev": true, + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, "node_modules/@angular-devkit/schematics": { "version": "11.2.14", "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-11.2.14.tgz", @@ -3575,6 +3626,199 @@ "node": ">= 10.0.0" } }, + "node_modules/@cypress/request": { + "version": "2.88.6", + "resolved": "https://registry.npmjs.org/@cypress/request/-/request-2.88.6.tgz", + "integrity": "sha512-z0UxBE/+qaESAHY9p9sM2h8Y4XqtsbDCt0/DPOrqA/RZgKi4PkxdpXyK4wCCnSk1xHqWHZZAE+gV6aDAR6+caQ==", + "dev": true, + "dependencies": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^8.3.2" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/@cypress/request/node_modules/form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "dev": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 0.12" + } + }, + "node_modules/@cypress/request/node_modules/qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", + "dev": true, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/@cypress/schematic": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@cypress/schematic/-/schematic-1.5.1.tgz", + "integrity": "sha512-Ak8teyJ2rzwWMxxhNBmJvW+pJrXb27pJ267yS3Gi0OJ/l2sp1fb+zb58VQot4zj3HrWaL2GbcwbmjeA8Sj7X9g==", + "dev": true, + "dependencies": { + "@angular-devkit/architect": "^0.1200.0", + "@angular-devkit/core": "^12.0.0", + "@angular-devkit/schematics": "^12.0.0", + "@schematics/angular": "^12.0.0", + "jsonc-parser": "^3.0.0", + "rxjs": "~6.6.0" + } + }, + "node_modules/@cypress/schematic/node_modules/@angular-devkit/schematics": { + "version": "12.2.7", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-12.2.7.tgz", + "integrity": "sha512-E0hCFyyfbixjerf0Okt4ynC6F1dsT2Wl7MwAePe+wzPTHCnKIRTa2PQTxJzdWeTlSkQMkSK6ft2iyWOD/FODng==", + "dev": true, + "dependencies": { + "@angular-devkit/core": "12.2.7", + "ora": "5.4.1", + "rxjs": "6.6.7" + }, + "engines": { + "node": "^12.14.1 || >=14.0.0", + "npm": "^6.11.0 || ^7.5.6", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@cypress/schematic/node_modules/@schematics/angular": { + "version": "12.2.7", + "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-12.2.7.tgz", + "integrity": "sha512-wGqp0jC545Fwf0ydBkeoJHx9snFW+uqn40WwVqs/27Nh4AEHB5uzwzLY7Ykae95Zn802a61KPqSNWpez2fWWGA==", + "dev": true, + "dependencies": { + "@angular-devkit/core": "12.2.7", + "@angular-devkit/schematics": "12.2.7", + "jsonc-parser": "3.0.0" + }, + "engines": { + "node": "^12.14.1 || >=14.0.0", + "npm": "^6.11.0 || ^7.5.6", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@cypress/schematic/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@cypress/schematic/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@cypress/schematic/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@cypress/schematic/node_modules/ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "dev": true, + "dependencies": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@cypress/schematic/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@cypress/xvfb": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@cypress/xvfb/-/xvfb-1.2.4.tgz", + "integrity": "sha512-skbBzPggOVYCbnGgV+0dmBdW/s77ZkAOXIC1knS8NagwDjBrNC1LuXtQJeiN6l+m7lzmHtaoUw/ctJKdqkG57Q==", + "dev": true, + "dependencies": { + "debug": "^3.1.0", + "lodash.once": "^4.1.1" + } + }, + "node_modules/@cypress/xvfb/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, "node_modules/@discoveryjs/json-ext": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.2.tgz", @@ -4669,13 +4913,13 @@ "dev": true }, "node_modules/@sentry/browser": { - "version": "6.13.2", - "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-6.13.2.tgz", - "integrity": "sha512-bkFXK4vAp2UX/4rQY0pj2Iky55Gnwr79CtveoeeMshoLy5iDgZ8gvnLNAz7om4B9OQk1u7NzLEa4IXAmHTUyag==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-6.15.0.tgz", + "integrity": "sha512-ZiqfHK5DMVgDsgMTuSwxilWIqEnZzy4yuJ9Sr6Iap1yZddPSiKHYjbBieSHn57UsWHViRB3ojbwu44LfvXKJdQ==", "dependencies": { - "@sentry/core": "6.13.2", - "@sentry/types": "6.13.2", - "@sentry/utils": "6.13.2", + "@sentry/core": "6.15.0", + "@sentry/types": "6.15.0", + "@sentry/utils": "6.15.0", "tslib": "^1.9.3" }, "engines": { @@ -4688,14 +4932,14 @@ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" }, "node_modules/@sentry/core": { - "version": "6.13.2", - "resolved": "https://registry.npmjs.org/@sentry/core/-/core-6.13.2.tgz", - "integrity": "sha512-snXNNFLwlS7yYxKTX4DBXebvJK+6ikBWN6noQ1CHowvM3ReFBlrdrs0Z0SsSFEzXm2S4q7f6HHbm66GSQZ/8FQ==", - "dependencies": { - "@sentry/hub": "6.13.2", - "@sentry/minimal": "6.13.2", - "@sentry/types": "6.13.2", - "@sentry/utils": "6.13.2", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-6.15.0.tgz", + "integrity": "sha512-mCbKyqvD1G3Re6gv6N8tRkBz84gvVWDfLtC6d1WBArIopzter6ktEbvq0cMT6EOvGI2OLXuJ6mtHA93/Q0gGpw==", + "dependencies": { + "@sentry/hub": "6.15.0", + "@sentry/minimal": "6.15.0", + "@sentry/types": "6.15.0", + "@sentry/utils": "6.15.0", "tslib": "^1.9.3" }, "engines": { @@ -4708,12 +4952,12 @@ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" }, "node_modules/@sentry/hub": { - "version": "6.13.2", - "resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-6.13.2.tgz", - "integrity": "sha512-sppSuJdNMiMC/vFm/dQowCBh11uTrmvks00fc190YWgxHshodJwXMdpc+pN61VSOmy2QA4MbQ5aMAgHzPzel3A==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-6.15.0.tgz", + "integrity": "sha512-cUbHPeG6kKpGBaEMgbTWeU03Y1Up5T3urGF+cgtrn80PmPYYSUPvVvWlZQWPb8CJZ1yQ0gySWo5RUTatBFrEHA==", "dependencies": { - "@sentry/types": "6.13.2", - "@sentry/utils": "6.13.2", + "@sentry/types": "6.15.0", + "@sentry/utils": "6.15.0", "tslib": "^1.9.3" }, "engines": { @@ -4726,12 +4970,12 @@ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" }, "node_modules/@sentry/minimal": { - "version": "6.13.2", - "resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-6.13.2.tgz", - "integrity": "sha512-6iJfEvHzzpGBHDfLxSHcGObh73XU1OSQKWjuhDOe7UQDyI4BQmTfcXAC+Fr8sm8C/tIsmpVi/XJhs8cubFdSMw==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-6.15.0.tgz", + "integrity": "sha512-7RJIvZsjBa1qFUfMrAzQsWdfZT6Gm4t6ZTYfkpsXPBA35hkzglKbBrhhsUvkxGIhUGw/PiCUqxBUjcmzQP0vfg==", "dependencies": { - "@sentry/hub": "6.13.2", - "@sentry/types": "6.13.2", + "@sentry/hub": "6.15.0", + "@sentry/types": "6.15.0", "tslib": "^1.9.3" }, "engines": { @@ -4744,19 +4988,19 @@ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" }, "node_modules/@sentry/types": { - "version": "6.13.2", - "resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.13.2.tgz", - "integrity": "sha512-6WjGj/VjjN8LZDtqJH5ikeB1o39rO1gYS6anBxiS3d0sXNBb3Ux0pNNDFoBxQpOhmdDHXYS57MEptX9EV82gmg==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.15.0.tgz", + "integrity": "sha512-zBw5gPUsofXUSpS3ZAXqRNedLRBvirl3sqkj2Lez7X2EkKRgn5D8m9fQIrig/X3TsKcXUpijDW5Buk5zeCVzJA==", "engines": { "node": ">=6" } }, "node_modules/@sentry/utils": { - "version": "6.13.2", - "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.13.2.tgz", - "integrity": "sha512-foF4PbxqPMWNbuqdXkdoOmKm3quu3PP7Q7j/0pXkri4DtCuvF/lKY92mbY0V9rHS/phCoj+3/Se5JvM2ymh2/w==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.15.0.tgz", + "integrity": "sha512-gnhKKyFtnNmKWjDizo7VKD0/Vx8cgW1lCusM6WI7jy2jlO3bQA0+Dzgmr4mIReZ74mq4VpOd2Vfrx7ZldW1DMw==", "dependencies": { - "@sentry/types": "6.13.2", + "@sentry/types": "6.15.0", "tslib": "^1.9.3" }, "engines": { @@ -7670,12 +7914,6 @@ "integrity": "sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ==", "dev": true }, - "node_modules/@types/q": { - "version": "0.0.32", - "resolved": "https://registry.npmjs.org/@types/q/-/q-0.0.32.tgz", - "integrity": "sha1-vShOV8hPEyXacCur/IKlMoGQwMU=", - "dev": true - }, "node_modules/@types/qs": { "version": "6.9.7", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", @@ -7723,10 +7961,16 @@ "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==", "dev": true }, - "node_modules/@types/selenium-webdriver": { - "version": "3.0.19", - "resolved": "https://registry.npmjs.org/@types/selenium-webdriver/-/selenium-webdriver-3.0.19.tgz", - "integrity": "sha512-OFUilxQg+rWL2FMxtmIgCkUDlJB6pskkpvmew7yeXfzzsOBb5rc+y2+DjHm+r3r1ZPPcJefK3DveNSYWGiy68g==", + "node_modules/@types/sinonjs__fake-timers": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-6.0.4.tgz", + "integrity": "sha512-IFQTJARgMUBF+xVd2b+hIgXWrZEjND3vJtRCvIelcFB5SIXfjV4bOHbHJ0eXKh+0COrBRc8MqteKAz/j88rE0A==", + "dev": true + }, + "node_modules/@types/sizzle": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.3.tgz", + "integrity": "sha512-JYM8x9EGF163bEyhdJBpR2QX1R5naCJHC8ucJylJ3w9/CVBaskdQ8WqBf8MmQrd1kRvp/a4TS8HJ+bxzR7ZJYQ==", "dev": true }, "node_modules/@types/source-list-map": { @@ -7826,6 +8070,16 @@ "integrity": "sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw==", "dev": true }, + "node_modules/@types/yauzl": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.9.2.tgz", + "integrity": "sha512-8uALY5LTvSuHgloDVUvWP3pIauILm+8/0pDMokuDYIoNsOkSwd5AiHBTSEJjKTDcZr5z8UpgOWZkxBF4iJftoA==", + "dev": true, + "optional": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@webassemblyjs/ast": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.0.tgz", @@ -8115,15 +8369,6 @@ "node": ">=8.9" } }, - "node_modules/adm-zip": { - "version": "0.4.16", - "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.16.tgz", - "integrity": "sha512-TFi4HBKSGfIKsK5YCkKaaFG2m4PEDyViZmEwof3MTIgzimHLto6muaHVpbrljdIvIrFZzEq/p4nafOeLcYegrg==", - "dev": true, - "engines": { - "node": ">=0.3.0" - } - }, "node_modules/agent-base": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", @@ -8189,11 +8434,10 @@ } }, "node_modules/ajv": { - "version": "8.6.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.6.2.tgz", - "integrity": "sha512-9807RlWAgT564wT+DjeyU5OFMPjmzxVobvDFmNAhY+5zD6A2ly3jDp6sgnfyDtlIQ+7H97oc/DGCzzfu9rjw9w==", + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.2.0.tgz", + "integrity": "sha512-WSNGFuyWd//XO8n/m/EaOlNLtO0yL8EXT/74LqT4khdhpZjP7lkj/kT5uwRmGitKEVp/Oj7ZUHeGfPtgHhQ5CA==", "dev": true, - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "json-schema-traverse": "^1.0.0", @@ -8215,11 +8459,10 @@ } }, "node_modules/ajv-formats": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.0.tgz", - "integrity": "sha512-USH2jBb+C/hIpwD2iRjp0pe0k+MvzG0mlSn/FIdCgQhUb9ALPRjt2KIQdfZDS9r0ZIeUAg7gOu9KL0PFqGqr5Q==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.0.2.tgz", + "integrity": "sha512-Brah4Uo5/U8v76c6euTwtjVFFaVishwnJrQBYpev1JRh4vjA1F4HY3UzQez41YUCszUCXKagG8v6eVRBHV1gkw==", "dev": true, - "peer": true, "dependencies": { "ajv": "^8.0.0" }, @@ -8249,9 +8492,9 @@ } }, "node_modules/angulartics2": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/angulartics2/-/angulartics2-10.0.0.tgz", - "integrity": "sha512-ebn4uZb74WtG5S/OI69eY1J1MLyvIOXpUo64nYHAL7nUqp0PSbz+djPSaWsHKu4ga9G8lMmURzfxZ/EY1X+lmg==", + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/angulartics2/-/angulartics2-10.1.0.tgz", + "integrity": "sha512-MnwQxRXJkfbBF7417Cs7L/SIuTRNWHCOBnGolZXHFz5ogw1e51KdCKUaUkfgBogR7JpXP279FU9UDkzerIS3xw==", "dependencies": { "tslib": "^2.0.0" }, @@ -8502,6 +8745,26 @@ "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", "dev": true }, + "node_modules/arch": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/arch/-/arch-2.2.0.tgz", + "integrity": "sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/are-we-there-yet": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.7.tgz", @@ -9454,20 +9717,11 @@ "node": ">= 6" } }, - "node_modules/blocking-proxy": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/blocking-proxy/-/blocking-proxy-1.0.1.tgz", - "integrity": "sha512-KE8NFMZr3mN2E0HcvCgRtX7DjhiIQrwle+nSVJVC/yqFb9+xznHl2ZcoBp2L9qzkI4t4cBFJ1efXF8Dwi132RA==", - "dev": true, - "dependencies": { - "minimist": "^1.2.0" - }, - "bin": { - "blocking-proxy": "built/lib/bin.js" - }, - "engines": { - "node": ">=6.9.x" - } + "node_modules/blob-util": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/blob-util/-/blob-util-2.0.2.tgz", + "integrity": "sha512-T7JQa+zsXXEa6/8ZhHcQEW1UFfVM49Ts65uBkFL6fz2QmrElqmbajIDJvuA0tEhRe5eIjpV9ZF+0RfZR9voJFQ==", + "dev": true }, "node_modules/bluebird": { "version": "3.7.2", @@ -9837,49 +10091,6 @@ "url": "https://opencollective.com/browserslist" } }, - "node_modules/browserstack": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/browserstack/-/browserstack-1.6.1.tgz", - "integrity": "sha512-GxtFjpIaKdbAyzHfFDKixKO8IBT7wR3NjbzrGc78nNs/Ciys9wU3/nBtsqsWv5nDSrdI5tz0peKuzCPuNXNUiw==", - "dev": true, - "dependencies": { - "https-proxy-agent": "^2.2.1" - } - }, - "node_modules/browserstack/node_modules/agent-base": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", - "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", - "dev": true, - "dependencies": { - "es6-promisify": "^5.0.0" - }, - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/browserstack/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/browserstack/node_modules/https-proxy-agent": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz", - "integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==", - "dev": true, - "dependencies": { - "agent-base": "^4.3.0", - "debug": "^3.1.0" - }, - "engines": { - "node": ">= 4.5.0" - } - }, "node_modules/bser": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", @@ -9912,6 +10123,15 @@ "ieee754": "^1.1.13" } }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=", + "dev": true, + "engines": { + "node": "*" + } + }, "node_modules/buffer-equal": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-0.0.1.tgz", @@ -10016,6 +10236,15 @@ "node": ">=0.10.0" } }, + "node_modules/cachedir": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/cachedir/-/cachedir-2.3.0.tgz", + "integrity": "sha512-A+Fezp4zxnit6FanDmv9EqXNAi3vt9DWp51/71UEhXukb7QUuvtv9344h91dyAxuTLoSYJFU299qzR3tzwPAhw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/call-bind": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", @@ -10225,6 +10454,15 @@ "node": "*" } }, + "node_modules/check-more-types": { + "version": "2.24.0", + "resolved": "https://registry.npmjs.org/check-more-types/-/check-more-types-2.24.0.tgz", + "integrity": "sha1-FCD/sQ/URNz8ebQ4kbv//TKoRgA=", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/cheerio": { "version": "1.0.0-rc.10", "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.10.tgz", @@ -10768,6 +11006,15 @@ "integrity": "sha512-Gar0ASD4BDyKC4hl4DwHqDrmvjoxWKZigVnAbn5H1owvm4CxCPdb0HQDehwNYMJpla5+M2tPmPARzhtYuwpHow==", "dev": true }, + "node_modules/common-tags": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.0.tgz", + "integrity": "sha512-6P6g0uetGpW/sdyUy/iQQCbFF0kWVMSIVSyYz7Zgjcgh8mgw8PQzDNZeyZ5DQ2gM7LBoZPHmnjz8rUthkBG5tw==", + "dev": true, + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/commondir": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", @@ -12148,6 +12395,228 @@ "integrity": "sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=", "dev": true }, + "node_modules/cypress": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/cypress/-/cypress-8.5.0.tgz", + "integrity": "sha512-MMkXIS+Ro2KETn4gAlG3tIc/7FiljuuCZP0zpd9QsRG6MZSyZW/l1J3D4iQM6WHsVxuX4rFChn5jPFlC2tNSvQ==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "@cypress/request": "^2.88.6", + "@cypress/xvfb": "^1.2.4", + "@types/node": "^14.14.31", + "@types/sinonjs__fake-timers": "^6.0.2", + "@types/sizzle": "^2.3.2", + "arch": "^2.2.0", + "blob-util": "^2.0.2", + "bluebird": "^3.7.2", + "cachedir": "^2.3.0", + "chalk": "^4.1.0", + "check-more-types": "^2.24.0", + "cli-cursor": "^3.1.0", + "cli-table3": "~0.6.0", + "commander": "^5.1.0", + "common-tags": "^1.8.0", + "dayjs": "^1.10.4", + "debug": "^4.3.2", + "enquirer": "^2.3.6", + "eventemitter2": "^6.4.3", + "execa": "4.1.0", + "executable": "^4.1.1", + "extract-zip": "2.0.1", + "figures": "^3.2.0", + "fs-extra": "^9.1.0", + "getos": "^3.2.1", + "is-ci": "^3.0.0", + "is-installed-globally": "~0.4.0", + "lazy-ass": "^1.6.0", + "listr2": "^3.8.3", + "lodash": "^4.17.21", + "log-symbols": "^4.0.0", + "minimist": "^1.2.5", + "ospath": "^1.2.2", + "pretty-bytes": "^5.6.0", + "proxy-from-env": "1.0.0", + "ramda": "~0.27.1", + "request-progress": "^3.0.0", + "supports-color": "^8.1.1", + "tmp": "~0.2.1", + "untildify": "^4.0.0", + "url": "^0.11.0", + "yauzl": "^2.10.0" + }, + "bin": { + "cypress": "bin/cypress" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/cypress/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/cypress/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/cypress/node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cypress/node_modules/ci-info": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.2.0.tgz", + "integrity": "sha512-dVqRX7fLUm8J6FgHJ418XuIgDLZDkYcDFTeL6TA2gt5WlIZUQrrH6EZrNClwT/H0FateUsZkGIOPRrLbP+PR9A==", + "dev": true + }, + "node_modules/cypress/node_modules/commander": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", + "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/cypress/node_modules/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, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/cypress/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/cypress/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/cypress/node_modules/is-ci": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.0.tgz", + "integrity": "sha512-kDXyttuLeslKAHYL/K28F2YkM3x5jvFPEw3yXbRptXydjD9rpLEz+C5K5iutY9ZiUu6AP41JdvRQwF4Iqs4ZCQ==", + "dev": true, + "dependencies": { + "ci-info": "^3.1.1" + }, + "bin": { + "is-ci": "bin.js" + } + }, + "node_modules/cypress/node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/cypress/node_modules/ramda": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.27.1.tgz", + "integrity": "sha512-PgIdVpn5y5Yns8vqb8FzBUEYn98V3xcPgawAkkgj0YJ0qDsnHCiNmZYfOGMgOvoB0eWFLpYbhxUR3mxfDIMvpw==", + "dev": true + }, + "node_modules/cypress/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/cypress/node_modules/tmp": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", + "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", + "dev": true, + "dependencies": { + "rimraf": "^3.0.0" + }, + "engines": { + "node": ">=8.17.0" + } + }, + "node_modules/cypress/node_modules/universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "dev": true, + "engines": { + "node": ">= 10.0.0" + } + }, "node_modules/d": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", @@ -12191,6 +12660,12 @@ "node": ">=4.0" } }, + "node_modules/dayjs": { + "version": "1.10.7", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.10.7.tgz", + "integrity": "sha512-P6twpd70BcPK34K26uJ1KT3wlhpuOAPoMwJzpsIWUxHZ7wpmbdZL/hQqBDfz7hGurYSa5PhzdhDHtt319hL3ig==", + "dev": true + }, "node_modules/debug": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", @@ -12457,83 +12932,6 @@ "node": ">=0.10.0" } }, - "node_modules/del": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/del/-/del-2.2.2.tgz", - "integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=", - "dev": true, - "dependencies": { - "globby": "^5.0.0", - "is-path-cwd": "^1.0.0", - "is-path-in-cwd": "^1.0.0", - "object-assign": "^4.0.1", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0", - "rimraf": "^2.2.8" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/del/node_modules/array-union": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", - "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", - "dev": true, - "dependencies": { - "array-uniq": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/del/node_modules/arrify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/del/node_modules/globby": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz", - "integrity": "sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=", - "dev": true, - "dependencies": { - "array-union": "^1.0.1", - "arrify": "^1.0.0", - "glob": "^7.0.3", - "object-assign": "^4.0.1", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/del/node_modules/pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/del/node_modules/rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - } - }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -13326,21 +13724,6 @@ "event-emitter": "~0.3.5" } }, - "node_modules/es6-promise": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", - "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==", - "dev": true - }, - "node_modules/es6-promisify": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", - "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", - "dev": true, - "dependencies": { - "es6-promise": "^4.0.3" - } - }, "node_modules/es6-set": { "version": "0.1.5", "resolved": "https://registry.npmjs.org/es6-set/-/es6-set-0.1.5.tgz", @@ -13555,6 +13938,12 @@ "through": "~2.3.1" } }, + "node_modules/eventemitter2": { + "version": "6.4.4", + "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.4.tgz", + "integrity": "sha512-HLU3NDY6wARrLCEwyGKRBvuWYyvW6mHYv72SJJAH3iJN3a6eVUvkjFkcxah1bcTgGVBBrFdIopBJPhCQFMLyXw==", + "dev": true + }, "node_modules/eventemitter3": { "version": "4.0.7", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", @@ -13621,13 +14010,25 @@ "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, - "node_modules/exit": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", + "node_modules/executable": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/executable/-/executable-4.1.1.tgz", + "integrity": "sha512-8iA79xD3uAch729dUG8xaaBBFGaEa0wdD2VkYLFHwlqosEj/jT66AzcreRDSgV7ehnNLBW2WR5jIXwGKjVdTLg==", "dev": true, + "dependencies": { + "pify": "^2.2.0" + }, "engines": { - "node": ">= 0.8.0" + "node": ">=4" + } + }, + "node_modules/executable/node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true, + "engines": { + "node": ">=0.10.0" } }, "node_modules/expand-brackets": { @@ -13932,6 +14333,26 @@ "node": ">=0.10.0" } }, + "node_modules/extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + }, + "engines": { + "node": ">= 10.17.0" + }, + "optionalDependencies": { + "@types/yauzl": "^2.9.1" + } + }, "node_modules/extsprintf": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", @@ -14065,6 +14486,15 @@ "bser": "2.1.1" } }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=", + "dev": true, + "dependencies": { + "pend": "~1.2.0" + } + }, "node_modules/figgy-pudding": { "version": "3.5.2", "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.2.tgz", @@ -14996,6 +15426,21 @@ "node": ">=0.10.0" } }, + "node_modules/getos": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/getos/-/getos-3.2.1.tgz", + "integrity": "sha512-U56CfOK17OKgTVqozZjUKNdkfEv6jk5WISBJ8SHoagjE6L69zOwl3Z+O8myjY9MEW3i2HPWQBt/LTbCgcC973Q==", + "dev": true, + "dependencies": { + "async": "^3.2.0" + } + }, + "node_modules/getos/node_modules/async": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.1.tgz", + "integrity": "sha512-XdD5lRO/87udXCMC9meWdYiR+Nq6ZjUfXidViUZGu2F1MO4T3XwZ1et0hb2++BgLfhyJwy44BGB/yx80ABx8hg==", + "dev": true + }, "node_modules/getpass": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", @@ -15116,6 +15561,21 @@ "process": "^0.11.10" } }, + "node_modules/global-dirs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.0.tgz", + "integrity": "sha512-v8ho2DS5RiCjftj1nD9NmnfaOzTdud7RRnVd9kFNOjqZbISlx5DQ+OrTkywgd0dIt7oFCvKetZSHoHcP3sDdiA==", + "dev": true, + "dependencies": { + "ini": "2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/global-modules": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz", @@ -15307,27 +15767,6 @@ "node": ">= 0.4.0" } }, - "node_modules/has-ansi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", - "dev": true, - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-ansi/node_modules/ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/has-bigints": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz", @@ -16220,9 +16659,9 @@ } }, "node_modules/idb": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/idb/-/idb-6.1.4.tgz", - "integrity": "sha512-DshI5yxIB3NYc47cPpfipYX8MSIgQPqVR+WoaGI9EDq6cnLGgGYR1fp6z8/Bq9vMS8Jq1bS3eWUgXpFO5+ypSA==" + "version": "6.1.5", + "resolved": "https://registry.npmjs.org/idb/-/idb-6.1.5.tgz", + "integrity": "sha512-IJtugpKkiVXQn5Y+LteyBCNk1N8xpGV3wWZk9EVtZWH8DYkjBn0bX1XnGP9RkyZF0sAcywa6unHqSWKe7q4LGw==" }, "node_modules/ieee754": { "version": "1.2.1", @@ -16909,6 +17348,22 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/is-installed-globally": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz", + "integrity": "sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==", + "dev": true, + "dependencies": { + "global-dirs": "^3.0.0", + "is-path-inside": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-interactive": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", @@ -16995,37 +17450,13 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/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, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/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, - "dependencies": { - "is-path-inside": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/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=", + "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, - "dependencies": { - "path-is-inside": "^1.0.1" - }, "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, "node_modules/is-plain-obj": { @@ -17444,20 +17875,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/jasmine": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/jasmine/-/jasmine-2.8.0.tgz", - "integrity": "sha1-awicChFXax8W3xG4AUbZHU6Lij4=", - "dev": true, - "dependencies": { - "exit": "^0.1.2", - "glob": "^7.0.6", - "jasmine-core": "~2.8.0" - }, - "bin": { - "jasmine": "bin/jasmine.js" - } - }, "node_modules/jasmine-core": { "version": "3.9.0", "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-3.9.0.tgz", @@ -17473,21 +17890,6 @@ "colors": "1.4.0" } }, - "node_modules/jasmine/node_modules/jasmine-core": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-2.8.0.tgz", - "integrity": "sha1-vMl5rh+f0FcB5F5S5l06XWPxok4=", - "dev": true - }, - "node_modules/jasminewd2": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/jasminewd2/-/jasminewd2-2.2.0.tgz", - "integrity": "sha1-43zwsX8ZnM4jvqcbIDk5Uka07E4=", - "dev": true, - "engines": { - "node": ">= 6.9.x" - } - }, "node_modules/jest-docblock": { "version": "21.2.0", "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-21.2.0.tgz", @@ -17721,8 +18123,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true, - "peer": true + "dev": true }, "node_modules/json-stringify-safe": { "version": "5.0.1", @@ -17788,18 +18189,6 @@ "verror": "1.10.0" } }, - "node_modules/jszip": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.7.1.tgz", - "integrity": "sha512-ghL0tz1XG9ZEmRMcEN2vt7xabrDdqHHeykgARpmZ0BiIctWxM47Vt63ZO2dnp4QYt/xJVLLy5Zv1l/xRdh2byg==", - "dev": true, - "dependencies": { - "lie": "~3.3.0", - "pako": "~1.0.2", - "readable-stream": "~2.3.6", - "set-immediate-shim": "~1.0.1" - } - }, "node_modules/junk": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/junk/-/junk-3.1.0.tgz", @@ -18053,6 +18442,15 @@ "resolved": "https://registry.npmjs.org/layerr/-/layerr-0.1.2.tgz", "integrity": "sha512-ob5kTd9H3S4GOG2nVXyQhOu9O8nBgP555XxWPkJI0tR0JeRilfyTp8WtPdIJHLXBmHMSdEq5+KMxiYABeScsIQ==" }, + "node_modules/lazy-ass": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/lazy-ass/-/lazy-ass-1.6.0.tgz", + "integrity": "sha1-eZllXoZGwX8In90YfRUNMyTVRRM=", + "dev": true, + "engines": { + "node": "> 0.8" + } + }, "node_modules/lazy-universal-dotenv": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/lazy-universal-dotenv/-/lazy-universal-dotenv-3.0.1.tgz", @@ -18341,21 +18739,6 @@ "source-map": "~0.6.1" } }, - "node_modules/lie": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", - "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", - "dev": true, - "dependencies": { - "immediate": "~3.0.5" - } - }, - "node_modules/lie/node_modules/immediate": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", - "integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=", - "dev": true - }, "node_modules/linebreak": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/linebreak/-/linebreak-1.0.2.tgz", @@ -18666,6 +19049,43 @@ "fsevents": "^1.2.7" } }, + "node_modules/live-server/node_modules/colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", + "dev": true, + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/live-server/node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dev": true, + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/live-server/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/live-server/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, "node_modules/live-server/node_modules/fill-range": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", @@ -18803,6 +19223,33 @@ "node": ">=0.10.0" } }, + "node_modules/live-server/node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/live-server/node_modules/ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + }, + "node_modules/live-server/node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/live-server/node_modules/readdirp": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", @@ -18817,6 +19264,30 @@ "node": ">=0.10" } }, + "node_modules/live-server/node_modules/send": { + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", + "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "~1.7.2", + "mime": "1.6.0", + "ms": "2.1.1", + "on-finished": "~2.3.0", + "range-parser": "~1.2.1", + "statuses": "~1.5.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/live-server/node_modules/to-regex-range": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", @@ -18885,6 +19356,12 @@ "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=", "dev": true }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=", + "dev": true + }, "node_modules/lodash.uniq": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", @@ -21027,6 +21504,12 @@ "node": ">=0.10.0" } }, + "node_modules/ospath": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/ospath/-/ospath-1.2.2.tgz", + "integrity": "sha1-EnZjl3Sj+O8lcvf+QoDg6kVQwHs=", + "dev": true + }, "node_modules/overlayscrollbars": { "version": "1.13.1", "resolved": "https://registry.npmjs.org/overlayscrollbars/-/overlayscrollbars-1.13.1.tgz", @@ -21539,6 +22022,12 @@ "node": ">=0.10.0" } }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=", + "dev": true + }, "node_modules/performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", @@ -22806,284 +23295,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/protractor": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/protractor/-/protractor-7.0.0.tgz", - "integrity": "sha512-UqkFjivi4GcvUQYzqGYNe0mLzfn5jiLmO8w9nMhQoJRLhy2grJonpga2IWhI6yJO30LibWXJJtA4MOIZD2GgZw==", - "dev": true, - "dependencies": { - "@types/q": "^0.0.32", - "@types/selenium-webdriver": "^3.0.0", - "blocking-proxy": "^1.0.0", - "browserstack": "^1.5.1", - "chalk": "^1.1.3", - "glob": "^7.0.3", - "jasmine": "2.8.0", - "jasminewd2": "^2.1.0", - "q": "1.4.1", - "saucelabs": "^1.5.0", - "selenium-webdriver": "3.6.0", - "source-map-support": "~0.4.0", - "webdriver-js-extender": "2.1.0", - "webdriver-manager": "^12.1.7", - "yargs": "^15.3.1" - }, - "bin": { - "protractor": "bin/protractor", - "webdriver-manager": "bin/webdriver-manager" - }, - "engines": { - "node": ">=10.13.x" - } - }, - "node_modules/protractor/node_modules/ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/protractor/node_modules/ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/protractor/node_modules/chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "dependencies": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/protractor/node_modules/cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" - } - }, - "node_modules/protractor/node_modules/cliui/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/protractor/node_modules/cliui/node_modules/strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/protractor/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/protractor/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/protractor/node_modules/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, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/protractor/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/protractor/node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/protractor/node_modules/source-map-support": { - "version": "0.4.18", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", - "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", - "dev": true, - "dependencies": { - "source-map": "^0.5.6" - } - }, - "node_modules/protractor/node_modules/strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/protractor/node_modules/supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/protractor/node_modules/wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/protractor/node_modules/wrap-ansi/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/protractor/node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/protractor/node_modules/wrap-ansi/node_modules/strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/protractor/node_modules/y18n": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", - "dev": true - }, - "node_modules/protractor/node_modules/yargs": { - "version": "15.4.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", - "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", - "dev": true, - "dependencies": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^18.1.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/protractor/node_modules/yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "dev": true, - "dependencies": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -23097,6 +23308,12 @@ "node": ">= 0.10" } }, + "node_modules/proxy-from-env": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.0.0.tgz", + "integrity": "sha1-M8UDmPcOp+uW0h97gXYwpVeRx+4=", + "dev": true + }, "node_modules/proxy-middleware": { "version": "0.15.0", "resolved": "https://registry.npmjs.org/proxy-middleware/-/proxy-middleware-0.15.0.tgz", @@ -23177,16 +23394,6 @@ "node": ">=6" } }, - "node_modules/q": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/q/-/q-1.4.1.tgz", - "integrity": "sha1-VXBbzZPF82c1MMLCy8DCs63cKG4=", - "dev": true, - "engines": { - "node": ">=0.6.0", - "teleport": ">=0.2.0" - } - }, "node_modules/qjobs": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/qjobs/-/qjobs-1.2.0.tgz", @@ -24363,6 +24570,15 @@ "node": ">= 6" } }, + "node_modules/request-progress": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/request-progress/-/request-progress-3.0.0.tgz", + "integrity": "sha1-TKdUCBx/7GP1BeT6qCWqBs1mnb4=", + "dev": true, + "dependencies": { + "throttleit": "^1.0.0" + } + }, "node_modules/request/node_modules/form-data": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", @@ -24409,7 +24625,6 @@ "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", "dev": true, - "peer": true, "engines": { "node": ">=0.10.0" } @@ -25111,52 +25326,6 @@ "url": "https://opencollective.com/webpack" } }, - "node_modules/saucelabs": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/saucelabs/-/saucelabs-1.5.0.tgz", - "integrity": "sha512-jlX3FGdWvYf4Q3LFfFWS1QvPg3IGCGWxIc8QBFdPTbpTJnt/v17FHXYVAn7C8sHf1yUXo2c7yIM0isDryfYtHQ==", - "dev": true, - "dependencies": { - "https-proxy-agent": "^2.2.1" - }, - "engines": { - "node": "*" - } - }, - "node_modules/saucelabs/node_modules/agent-base": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", - "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", - "dev": true, - "dependencies": { - "es6-promisify": "^5.0.0" - }, - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/saucelabs/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/saucelabs/node_modules/https-proxy-agent": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz", - "integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==", - "dev": true, - "dependencies": { - "agent-base": "^4.3.0", - "debug": "^3.1.0" - }, - "engines": { - "node": ">= 4.5.0" - } - }, "node_modules/sax": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", @@ -25243,45 +25412,6 @@ "integrity": "sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo=", "dev": true }, - "node_modules/selenium-webdriver": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-3.6.0.tgz", - "integrity": "sha512-WH7Aldse+2P5bbFBO4Gle/nuQOdVwpHMTL6raL3uuBj/vPG07k6uzt3aiahu352ONBr5xXh0hDlM3LhtXPOC4Q==", - "dev": true, - "dependencies": { - "jszip": "^3.1.3", - "rimraf": "^2.5.4", - "tmp": "0.0.30", - "xml2js": "^0.4.17" - }, - "engines": { - "node": ">= 6.9.0" - } - }, - "node_modules/selenium-webdriver/node_modules/rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - } - }, - "node_modules/selenium-webdriver/node_modules/tmp": { - "version": "0.0.30", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.30.tgz", - "integrity": "sha1-ckGdSovn1s51FI/YsyTlk6cRwu0=", - "dev": true, - "dependencies": { - "os-tmpdir": "~1.0.1" - }, - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/selfsigned": { "version": "1.10.11", "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.11.tgz", @@ -25523,15 +25653,6 @@ "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", "dev": true }, - "node_modules/set-immediate-shim": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", - "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/set-value": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", @@ -27501,6 +27622,12 @@ "node": ">=10" } }, + "node_modules/throttleit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.0.tgz", + "integrity": "sha1-nnhYNtr0Z0MUWlmEtiaNgoUorGw=", + "dev": true + }, "node_modules/through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", @@ -28963,6 +29090,15 @@ "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", "dev": true }, + "node_modules/untildify": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz", + "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/upath": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", @@ -29704,126 +29840,6 @@ "url-parse": "^1.5.3" } }, - "node_modules/webdriver-js-extender": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/webdriver-js-extender/-/webdriver-js-extender-2.1.0.tgz", - "integrity": "sha512-lcUKrjbBfCK6MNsh7xaY2UAUmZwe+/ib03AjVOpFobX4O7+83BUveSrLfU0Qsyb1DaKJdQRbuU+kM9aZ6QUhiQ==", - "dev": true, - "dependencies": { - "@types/selenium-webdriver": "^3.0.0", - "selenium-webdriver": "^3.0.1" - }, - "engines": { - "node": ">=6.9.x" - } - }, - "node_modules/webdriver-manager": { - "version": "12.1.8", - "resolved": "https://registry.npmjs.org/webdriver-manager/-/webdriver-manager-12.1.8.tgz", - "integrity": "sha512-qJR36SXG2VwKugPcdwhaqcLQOD7r8P2Xiv9sfNbfZrKBnX243iAkOueX1yAmeNgIKhJ3YAT/F2gq6IiEZzahsg==", - "dev": true, - "dependencies": { - "adm-zip": "^0.4.9", - "chalk": "^1.1.1", - "del": "^2.2.0", - "glob": "^7.0.3", - "ini": "^1.3.4", - "minimist": "^1.2.0", - "q": "^1.4.1", - "request": "^2.87.0", - "rimraf": "^2.5.2", - "semver": "^5.3.0", - "xml2js": "^0.4.17" - }, - "bin": { - "webdriver-manager": "bin/webdriver-manager" - }, - "engines": { - "node": ">=6.9.x" - } - }, - "node_modules/webdriver-manager/node_modules/ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webdriver-manager/node_modules/ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webdriver-manager/node_modules/chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "dependencies": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webdriver-manager/node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "dev": true - }, - "node_modules/webdriver-manager/node_modules/rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - } - }, - "node_modules/webdriver-manager/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true, - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/webdriver-manager/node_modules/strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webdriver-manager/node_modules/supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, "node_modules/webpack": { "version": "4.46.0", "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.46.0.tgz", @@ -31698,28 +31714,6 @@ } } }, - "node_modules/xml2js": { - "version": "0.4.23", - "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", - "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", - "dev": true, - "dependencies": { - "sax": ">=0.6.0", - "xmlbuilder": "~11.0.0" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/xmlbuilder": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", - "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, "node_modules/xmldoc": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/xmldoc/-/xmldoc-1.1.2.tgz", @@ -31795,6 +31789,16 @@ "node": ">=10" } }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=", + "dev": true, + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, "node_modules/yn": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", @@ -31843,14 +31847,29 @@ "dev": true }, "@angular-devkit/architect": { - "version": "0.1202.6", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1202.6.tgz", - "integrity": "sha512-DQHK5VGfPof1TuSmRmt2Usw2BuNVLzxKSSy7+tEJbYzqf8N/wQO+1M67ye8qf8gAU88xGo378dD9++DFc/PJZA==", + "version": "0.1200.5", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1200.5.tgz", + "integrity": "sha512-222VZ4OeaDK3vON8V5m+w15SRWfUs5uOb4H9ij/H9/6tyHD83uWfCDoOGg+ax4wJVdWEFJIS+Vn4ijGcZCq9WQ==", "dev": true, - "peer": true, "requires": { - "@angular-devkit/core": "12.2.6", + "@angular-devkit/core": "12.0.5", "rxjs": "6.6.7" + }, + "dependencies": { + "@angular-devkit/core": { + "version": "12.0.5", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-12.0.5.tgz", + "integrity": "sha512-zVSQV+8/vjUjsUKGlj8Kf5LioA6AXJTGI0yhHW9q1dFX4dPpbW63k0R1UoIB2wJ0F/AbYVgpnPGPe9BBm2fvZA==", + "dev": true, + "requires": { + "ajv": "8.2.0", + "ajv-formats": "2.0.2", + "fast-json-stable-stringify": "2.1.0", + "magic-string": "0.25.7", + "rxjs": "6.6.7", + "source-map": "0.7.3" + } + } } }, "@angular-devkit/build-angular": { @@ -32546,11 +32565,10 @@ } }, "@angular-devkit/core": { - "version": "12.2.6", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-12.2.6.tgz", - "integrity": "sha512-E+OhY34Vmwyy1/PaX/nzao40XM70wOr7Urh00sAtV8sPLXMLeW0gHk4DUchCKohxQkrIL0AxYt1aeUVgIc7bSA==", + "version": "12.2.7", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-12.2.7.tgz", + "integrity": "sha512-WeLlDZaudpx10OGDPfVcWu/CaEWiWzAaLTUQz0Ww/yM+01FxR/P8yeH1sYAV1MS6d6KHvXGw7Lpf8PV7IA/zHA==", "dev": true, - "peer": true, "requires": { "ajv": "8.6.2", "ajv-formats": "2.1.0", @@ -32558,6 +32576,29 @@ "magic-string": "0.25.7", "rxjs": "6.6.7", "source-map": "0.7.3" + }, + "dependencies": { + "ajv": { + "version": "8.6.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.6.2.tgz", + "integrity": "sha512-9807RlWAgT564wT+DjeyU5OFMPjmzxVobvDFmNAhY+5zD6A2ly3jDp6sgnfyDtlIQ+7H97oc/DGCzzfu9rjw9w==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + }, + "ajv-formats": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.0.tgz", + "integrity": "sha512-USH2jBb+C/hIpwD2iRjp0pe0k+MvzG0mlSn/FIdCgQhUb9ALPRjt2KIQdfZDS9r0ZIeUAg7gOu9KL0PFqGqr5Q==", + "dev": true, + "requires": { + "ajv": "^8.0.0" + } + } } }, "@angular-devkit/schematics": { @@ -34362,6 +34403,162 @@ } } }, + "@cypress/request": { + "version": "2.88.6", + "resolved": "https://registry.npmjs.org/@cypress/request/-/request-2.88.6.tgz", + "integrity": "sha512-z0UxBE/+qaESAHY9p9sM2h8Y4XqtsbDCt0/DPOrqA/RZgKi4PkxdpXyK4wCCnSk1xHqWHZZAE+gV6aDAR6+caQ==", + "dev": true, + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^8.3.2" + }, + "dependencies": { + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", + "dev": true + } + } + }, + "@cypress/schematic": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@cypress/schematic/-/schematic-1.5.1.tgz", + "integrity": "sha512-Ak8teyJ2rzwWMxxhNBmJvW+pJrXb27pJ267yS3Gi0OJ/l2sp1fb+zb58VQot4zj3HrWaL2GbcwbmjeA8Sj7X9g==", + "dev": true, + "requires": { + "@angular-devkit/architect": "^0.1200.0", + "@angular-devkit/core": "^12.0.0", + "@angular-devkit/schematics": "^12.0.0", + "@schematics/angular": "^12.0.0", + "jsonc-parser": "^3.0.0", + "rxjs": "~6.6.0" + }, + "dependencies": { + "@angular-devkit/schematics": { + "version": "12.2.7", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-12.2.7.tgz", + "integrity": "sha512-E0hCFyyfbixjerf0Okt4ynC6F1dsT2Wl7MwAePe+wzPTHCnKIRTa2PQTxJzdWeTlSkQMkSK6ft2iyWOD/FODng==", + "dev": true, + "requires": { + "@angular-devkit/core": "12.2.7", + "ora": "5.4.1", + "rxjs": "6.6.7" + } + }, + "@schematics/angular": { + "version": "12.2.7", + "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-12.2.7.tgz", + "integrity": "sha512-wGqp0jC545Fwf0ydBkeoJHx9snFW+uqn40WwVqs/27Nh4AEHB5uzwzLY7Ykae95Zn802a61KPqSNWpez2fWWGA==", + "dev": true, + "requires": { + "@angular-devkit/core": "12.2.7", + "@angular-devkit/schematics": "12.2.7", + "jsonc-parser": "3.0.0" + } + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "dev": true, + "requires": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "@cypress/xvfb": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@cypress/xvfb/-/xvfb-1.2.4.tgz", + "integrity": "sha512-skbBzPggOVYCbnGgV+0dmBdW/s77ZkAOXIC1knS8NagwDjBrNC1LuXtQJeiN6l+m7lzmHtaoUw/ctJKdqkG57Q==", + "dev": true, + "requires": { + "debug": "^3.1.0", + "lodash.once": "^4.1.1" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + } + } + }, "@discoveryjs/json-ext": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.2.tgz", @@ -35221,13 +35418,13 @@ } }, "@sentry/browser": { - "version": "6.13.2", - "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-6.13.2.tgz", - "integrity": "sha512-bkFXK4vAp2UX/4rQY0pj2Iky55Gnwr79CtveoeeMshoLy5iDgZ8gvnLNAz7om4B9OQk1u7NzLEa4IXAmHTUyag==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-6.15.0.tgz", + "integrity": "sha512-ZiqfHK5DMVgDsgMTuSwxilWIqEnZzy4yuJ9Sr6Iap1yZddPSiKHYjbBieSHn57UsWHViRB3ojbwu44LfvXKJdQ==", "requires": { - "@sentry/core": "6.13.2", - "@sentry/types": "6.13.2", - "@sentry/utils": "6.13.2", + "@sentry/core": "6.15.0", + "@sentry/types": "6.15.0", + "@sentry/utils": "6.15.0", "tslib": "^1.9.3" }, "dependencies": { @@ -35239,14 +35436,14 @@ } }, "@sentry/core": { - "version": "6.13.2", - "resolved": "https://registry.npmjs.org/@sentry/core/-/core-6.13.2.tgz", - "integrity": "sha512-snXNNFLwlS7yYxKTX4DBXebvJK+6ikBWN6noQ1CHowvM3ReFBlrdrs0Z0SsSFEzXm2S4q7f6HHbm66GSQZ/8FQ==", - "requires": { - "@sentry/hub": "6.13.2", - "@sentry/minimal": "6.13.2", - "@sentry/types": "6.13.2", - "@sentry/utils": "6.13.2", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-6.15.0.tgz", + "integrity": "sha512-mCbKyqvD1G3Re6gv6N8tRkBz84gvVWDfLtC6d1WBArIopzter6ktEbvq0cMT6EOvGI2OLXuJ6mtHA93/Q0gGpw==", + "requires": { + "@sentry/hub": "6.15.0", + "@sentry/minimal": "6.15.0", + "@sentry/types": "6.15.0", + "@sentry/utils": "6.15.0", "tslib": "^1.9.3" }, "dependencies": { @@ -35258,12 +35455,12 @@ } }, "@sentry/hub": { - "version": "6.13.2", - "resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-6.13.2.tgz", - "integrity": "sha512-sppSuJdNMiMC/vFm/dQowCBh11uTrmvks00fc190YWgxHshodJwXMdpc+pN61VSOmy2QA4MbQ5aMAgHzPzel3A==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-6.15.0.tgz", + "integrity": "sha512-cUbHPeG6kKpGBaEMgbTWeU03Y1Up5T3urGF+cgtrn80PmPYYSUPvVvWlZQWPb8CJZ1yQ0gySWo5RUTatBFrEHA==", "requires": { - "@sentry/types": "6.13.2", - "@sentry/utils": "6.13.2", + "@sentry/types": "6.15.0", + "@sentry/utils": "6.15.0", "tslib": "^1.9.3" }, "dependencies": { @@ -35275,12 +35472,12 @@ } }, "@sentry/minimal": { - "version": "6.13.2", - "resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-6.13.2.tgz", - "integrity": "sha512-6iJfEvHzzpGBHDfLxSHcGObh73XU1OSQKWjuhDOe7UQDyI4BQmTfcXAC+Fr8sm8C/tIsmpVi/XJhs8cubFdSMw==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-6.15.0.tgz", + "integrity": "sha512-7RJIvZsjBa1qFUfMrAzQsWdfZT6Gm4t6ZTYfkpsXPBA35hkzglKbBrhhsUvkxGIhUGw/PiCUqxBUjcmzQP0vfg==", "requires": { - "@sentry/hub": "6.13.2", - "@sentry/types": "6.13.2", + "@sentry/hub": "6.15.0", + "@sentry/types": "6.15.0", "tslib": "^1.9.3" }, "dependencies": { @@ -35292,16 +35489,16 @@ } }, "@sentry/types": { - "version": "6.13.2", - "resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.13.2.tgz", - "integrity": "sha512-6WjGj/VjjN8LZDtqJH5ikeB1o39rO1gYS6anBxiS3d0sXNBb3Ux0pNNDFoBxQpOhmdDHXYS57MEptX9EV82gmg==" + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.15.0.tgz", + "integrity": "sha512-zBw5gPUsofXUSpS3ZAXqRNedLRBvirl3sqkj2Lez7X2EkKRgn5D8m9fQIrig/X3TsKcXUpijDW5Buk5zeCVzJA==" }, "@sentry/utils": { - "version": "6.13.2", - "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.13.2.tgz", - "integrity": "sha512-foF4PbxqPMWNbuqdXkdoOmKm3quu3PP7Q7j/0pXkri4DtCuvF/lKY92mbY0V9rHS/phCoj+3/Se5JvM2ymh2/w==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.15.0.tgz", + "integrity": "sha512-gnhKKyFtnNmKWjDizo7VKD0/Vx8cgW1lCusM6WI7jy2jlO3bQA0+Dzgmr4mIReZ74mq4VpOd2Vfrx7ZldW1DMw==", "requires": { - "@sentry/types": "6.13.2", + "@sentry/types": "6.15.0", "tslib": "^1.9.3" }, "dependencies": { @@ -37461,12 +37658,6 @@ "integrity": "sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ==", "dev": true }, - "@types/q": { - "version": "0.0.32", - "resolved": "https://registry.npmjs.org/@types/q/-/q-0.0.32.tgz", - "integrity": "sha1-vShOV8hPEyXacCur/IKlMoGQwMU=", - "dev": true - }, "@types/qs": { "version": "6.9.7", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", @@ -37516,10 +37707,16 @@ "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==", "dev": true }, - "@types/selenium-webdriver": { - "version": "3.0.19", - "resolved": "https://registry.npmjs.org/@types/selenium-webdriver/-/selenium-webdriver-3.0.19.tgz", - "integrity": "sha512-OFUilxQg+rWL2FMxtmIgCkUDlJB6pskkpvmew7yeXfzzsOBb5rc+y2+DjHm+r3r1ZPPcJefK3DveNSYWGiy68g==", + "@types/sinonjs__fake-timers": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-6.0.4.tgz", + "integrity": "sha512-IFQTJARgMUBF+xVd2b+hIgXWrZEjND3vJtRCvIelcFB5SIXfjV4bOHbHJ0eXKh+0COrBRc8MqteKAz/j88rE0A==", + "dev": true + }, + "@types/sizzle": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.3.tgz", + "integrity": "sha512-JYM8x9EGF163bEyhdJBpR2QX1R5naCJHC8ucJylJ3w9/CVBaskdQ8WqBf8MmQrd1kRvp/a4TS8HJ+bxzR7ZJYQ==", "dev": true }, "@types/source-list-map": { @@ -37617,6 +37814,16 @@ "integrity": "sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw==", "dev": true }, + "@types/yauzl": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.9.2.tgz", + "integrity": "sha512-8uALY5LTvSuHgloDVUvWP3pIauILm+8/0pDMokuDYIoNsOkSwd5AiHBTSEJjKTDcZr5z8UpgOWZkxBF4iJftoA==", + "dev": true, + "optional": true, + "requires": { + "@types/node": "*" + } + }, "@webassemblyjs/ast": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.0.tgz", @@ -37886,12 +38093,6 @@ "regex-parser": "^2.2.11" } }, - "adm-zip": { - "version": "0.4.16", - "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.16.tgz", - "integrity": "sha512-TFi4HBKSGfIKsK5YCkKaaFG2m4PEDyViZmEwof3MTIgzimHLto6muaHVpbrljdIvIrFZzEq/p4nafOeLcYegrg==", - "dev": true - }, "agent-base": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", @@ -37948,11 +38149,10 @@ } }, "ajv": { - "version": "8.6.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.6.2.tgz", - "integrity": "sha512-9807RlWAgT564wT+DjeyU5OFMPjmzxVobvDFmNAhY+5zD6A2ly3jDp6sgnfyDtlIQ+7H97oc/DGCzzfu9rjw9w==", + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.2.0.tgz", + "integrity": "sha512-WSNGFuyWd//XO8n/m/EaOlNLtO0yL8EXT/74LqT4khdhpZjP7lkj/kT5uwRmGitKEVp/Oj7ZUHeGfPtgHhQ5CA==", "dev": true, - "peer": true, "requires": { "fast-deep-equal": "^3.1.1", "json-schema-traverse": "^1.0.0", @@ -37968,11 +38168,10 @@ "requires": {} }, "ajv-formats": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.0.tgz", - "integrity": "sha512-USH2jBb+C/hIpwD2iRjp0pe0k+MvzG0mlSn/FIdCgQhUb9ALPRjt2KIQdfZDS9r0ZIeUAg7gOu9KL0PFqGqr5Q==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.0.2.tgz", + "integrity": "sha512-Brah4Uo5/U8v76c6euTwtjVFFaVishwnJrQBYpev1JRh4vjA1F4HY3UzQez41YUCszUCXKagG8v6eVRBHV1gkw==", "dev": true, - "peer": true, "requires": { "ajv": "^8.0.0" } @@ -37991,9 +38190,9 @@ "optional": true }, "angulartics2": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/angulartics2/-/angulartics2-10.0.0.tgz", - "integrity": "sha512-ebn4uZb74WtG5S/OI69eY1J1MLyvIOXpUo64nYHAL7nUqp0PSbz+djPSaWsHKu4ga9G8lMmURzfxZ/EY1X+lmg==", + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/angulartics2/-/angulartics2-10.1.0.tgz", + "integrity": "sha512-MnwQxRXJkfbBF7417Cs7L/SIuTRNWHCOBnGolZXHFz5ogw1e51KdCKUaUkfgBogR7JpXP279FU9UDkzerIS3xw==", "requires": { "tslib": "^2.0.0" } @@ -38176,6 +38375,12 @@ "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", "dev": true }, + "arch": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/arch/-/arch-2.2.0.tgz", + "integrity": "sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==", + "dev": true + }, "are-we-there-yet": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.7.tgz", @@ -38937,14 +39142,11 @@ } } }, - "blocking-proxy": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/blocking-proxy/-/blocking-proxy-1.0.1.tgz", - "integrity": "sha512-KE8NFMZr3mN2E0HcvCgRtX7DjhiIQrwle+nSVJVC/yqFb9+xznHl2ZcoBp2L9qzkI4t4cBFJ1efXF8Dwi132RA==", - "dev": true, - "requires": { - "minimist": "^1.2.0" - } + "blob-util": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/blob-util/-/blob-util-2.0.2.tgz", + "integrity": "sha512-T7JQa+zsXXEa6/8ZhHcQEW1UFfVM49Ts65uBkFL6fz2QmrElqmbajIDJvuA0tEhRe5eIjpV9ZF+0RfZR9voJFQ==", + "dev": true }, "bluebird": { "version": "3.7.2", @@ -39261,45 +39463,6 @@ "node-releases": "^1.1.75" } }, - "browserstack": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/browserstack/-/browserstack-1.6.1.tgz", - "integrity": "sha512-GxtFjpIaKdbAyzHfFDKixKO8IBT7wR3NjbzrGc78nNs/Ciys9wU3/nBtsqsWv5nDSrdI5tz0peKuzCPuNXNUiw==", - "dev": true, - "requires": { - "https-proxy-agent": "^2.2.1" - }, - "dependencies": { - "agent-base": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", - "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", - "dev": true, - "requires": { - "es6-promisify": "^5.0.0" - } - }, - "debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "https-proxy-agent": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz", - "integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==", - "dev": true, - "requires": { - "agent-base": "^4.3.0", - "debug": "^3.1.0" - } - } - } - }, "bser": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", @@ -39318,6 +39481,12 @@ "ieee754": "^1.1.13" } }, + "buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=", + "dev": true + }, "buffer-equal": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-0.0.1.tgz", @@ -39407,6 +39576,12 @@ "unset-value": "^1.0.0" } }, + "cachedir": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/cachedir/-/cachedir-2.3.0.tgz", + "integrity": "sha512-A+Fezp4zxnit6FanDmv9EqXNAi3vt9DWp51/71UEhXukb7QUuvtv9344h91dyAxuTLoSYJFU299qzR3tzwPAhw==", + "dev": true + }, "call-bind": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", @@ -39563,6 +39738,12 @@ "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", "integrity": "sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc=" }, + "check-more-types": { + "version": "2.24.0", + "resolved": "https://registry.npmjs.org/check-more-types/-/check-more-types-2.24.0.tgz", + "integrity": "sha1-FCD/sQ/URNz8ebQ4kbv//TKoRgA=", + "dev": true + }, "cheerio": { "version": "1.0.0-rc.10", "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.10.tgz", @@ -39979,6 +40160,12 @@ "integrity": "sha512-Gar0ASD4BDyKC4hl4DwHqDrmvjoxWKZigVnAbn5H1owvm4CxCPdb0HQDehwNYMJpla5+M2tPmPARzhtYuwpHow==", "dev": true }, + "common-tags": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.0.tgz", + "integrity": "sha512-6P6g0uetGpW/sdyUy/iQQCbFF0kWVMSIVSyYz7Zgjcgh8mgw8PQzDNZeyZ5DQ2gM7LBoZPHmnjz8rUthkBG5tw==", + "dev": true + }, "commondir": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", @@ -41095,6 +41282,176 @@ "integrity": "sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=", "dev": true }, + "cypress": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/cypress/-/cypress-8.5.0.tgz", + "integrity": "sha512-MMkXIS+Ro2KETn4gAlG3tIc/7FiljuuCZP0zpd9QsRG6MZSyZW/l1J3D4iQM6WHsVxuX4rFChn5jPFlC2tNSvQ==", + "dev": true, + "requires": { + "@cypress/request": "^2.88.6", + "@cypress/xvfb": "^1.2.4", + "@types/node": "^14.14.31", + "@types/sinonjs__fake-timers": "^6.0.2", + "@types/sizzle": "^2.3.2", + "arch": "^2.2.0", + "blob-util": "^2.0.2", + "bluebird": "^3.7.2", + "cachedir": "^2.3.0", + "chalk": "^4.1.0", + "check-more-types": "^2.24.0", + "cli-cursor": "^3.1.0", + "cli-table3": "~0.6.0", + "commander": "^5.1.0", + "common-tags": "^1.8.0", + "dayjs": "^1.10.4", + "debug": "^4.3.2", + "enquirer": "^2.3.6", + "eventemitter2": "^6.4.3", + "execa": "4.1.0", + "executable": "^4.1.1", + "extract-zip": "2.0.1", + "figures": "^3.2.0", + "fs-extra": "^9.1.0", + "getos": "^3.2.1", + "is-ci": "^3.0.0", + "is-installed-globally": "~0.4.0", + "lazy-ass": "^1.6.0", + "listr2": "^3.8.3", + "lodash": "^4.17.21", + "log-symbols": "^4.0.0", + "minimist": "^1.2.5", + "ospath": "^1.2.2", + "pretty-bytes": "^5.6.0", + "proxy-from-env": "1.0.0", + "ramda": "~0.27.1", + "request-progress": "^3.0.0", + "supports-color": "^8.1.1", + "tmp": "~0.2.1", + "untildify": "^4.0.0", + "url": "^0.11.0", + "yauzl": "^2.10.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "dependencies": { + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "ci-info": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.2.0.tgz", + "integrity": "sha512-dVqRX7fLUm8J6FgHJ418XuIgDLZDkYcDFTeL6TA2gt5WlIZUQrrH6EZrNClwT/H0FateUsZkGIOPRrLbP+PR9A==", + "dev": true + }, + "commander": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", + "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", + "dev": true + }, + "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" + } + }, + "fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "requires": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "is-ci": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.0.tgz", + "integrity": "sha512-kDXyttuLeslKAHYL/K28F2YkM3x5jvFPEw3yXbRptXydjD9rpLEz+C5K5iutY9ZiUu6AP41JdvRQwF4Iqs4ZCQ==", + "dev": true, + "requires": { + "ci-info": "^3.1.1" + } + }, + "jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6", + "universalify": "^2.0.0" + } + }, + "ramda": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.27.1.tgz", + "integrity": "sha512-PgIdVpn5y5Yns8vqb8FzBUEYn98V3xcPgawAkkgj0YJ0qDsnHCiNmZYfOGMgOvoB0eWFLpYbhxUR3mxfDIMvpw==", + "dev": true + }, + "supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "tmp": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", + "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", + "dev": true, + "requires": { + "rimraf": "^3.0.0" + } + }, + "universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "dev": true + } + } + }, "d": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", @@ -41132,6 +41489,12 @@ "integrity": "sha512-eyTcpKOcamdhWJXj56DpQMo1ylSQpcGtGKXcU0Tb97+K56/CF5amAqqqNj0+KvA0iw2ynxtHWFsPDSClCxe48w==", "dev": true }, + "dayjs": { + "version": "1.10.7", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.10.7.tgz", + "integrity": "sha512-P6twpd70BcPK34K26uJ1KT3wlhpuOAPoMwJzpsIWUxHZ7wpmbdZL/hQqBDfz7hGurYSa5PhzdhDHtt319hL3ig==", + "dev": true + }, "debug": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", @@ -41340,67 +41703,6 @@ "isobject": "^3.0.1" } }, - "del": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/del/-/del-2.2.2.tgz", - "integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=", - "dev": true, - "requires": { - "globby": "^5.0.0", - "is-path-cwd": "^1.0.0", - "is-path-in-cwd": "^1.0.0", - "object-assign": "^4.0.1", - "pify": "^2.0.0", - "pinkie-promise": "^2.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" - } - }, - "arrify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", - "dev": true - }, - "globby": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz", - "integrity": "sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=", - "dev": true, - "requires": { - "array-union": "^1.0.1", - "arrify": "^1.0.0", - "glob": "^7.0.3", - "object-assign": "^4.0.1", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" - } - }, - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "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", @@ -42076,21 +42378,6 @@ "event-emitter": "~0.3.5" } }, - "es6-promise": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", - "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==", - "dev": true - }, - "es6-promisify": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", - "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", - "dev": true, - "requires": { - "es6-promise": "^4.0.3" - } - }, "es6-set": { "version": "0.1.5", "resolved": "https://registry.npmjs.org/es6-set/-/es6-set-0.1.5.tgz", @@ -42262,6 +42549,12 @@ "through": "~2.3.1" } }, + "eventemitter2": { + "version": "6.4.4", + "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.4.tgz", + "integrity": "sha512-HLU3NDY6wARrLCEwyGKRBvuWYyvW6mHYv72SJJAH3iJN3a6eVUvkjFkcxah1bcTgGVBBrFdIopBJPhCQFMLyXw==", + "dev": true + }, "eventemitter3": { "version": "4.0.7", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", @@ -42316,11 +42609,22 @@ "strip-final-newline": "^2.0.0" } }, - "exit": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", - "dev": true + "executable": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/executable/-/executable-4.1.1.tgz", + "integrity": "sha512-8iA79xD3uAch729dUG8xaaBBFGaEa0wdD2VkYLFHwlqosEj/jT66AzcreRDSgV7ehnNLBW2WR5jIXwGKjVdTLg==", + "dev": true, + "requires": { + "pify": "^2.2.0" + }, + "dependencies": { + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + } + } }, "expand-brackets": { "version": "2.1.4", @@ -42582,6 +42886,18 @@ } } }, + "extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "dev": true, + "requires": { + "@types/yauzl": "^2.9.1", + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + } + }, "extsprintf": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", @@ -42692,6 +43008,15 @@ "bser": "2.1.1" } }, + "fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=", + "dev": true, + "requires": { + "pend": "~1.2.0" + } + }, "figgy-pudding": { "version": "3.5.2", "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.2.tgz", @@ -43420,6 +43745,23 @@ "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", "dev": true }, + "getos": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/getos/-/getos-3.2.1.tgz", + "integrity": "sha512-U56CfOK17OKgTVqozZjUKNdkfEv6jk5WISBJ8SHoagjE6L69zOwl3Z+O8myjY9MEW3i2HPWQBt/LTbCgcC973Q==", + "dev": true, + "requires": { + "async": "^3.2.0" + }, + "dependencies": { + "async": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.1.tgz", + "integrity": "sha512-XdD5lRO/87udXCMC9meWdYiR+Nq6ZjUfXidViUZGu2F1MO4T3XwZ1et0hb2++BgLfhyJwy44BGB/yx80ABx8hg==", + "dev": true + } + } + }, "getpass": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", @@ -43518,6 +43860,15 @@ "process": "^0.11.10" } }, + "global-dirs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.0.tgz", + "integrity": "sha512-v8ho2DS5RiCjftj1nD9NmnfaOzTdud7RRnVd9kFNOjqZbISlx5DQ+OrTkywgd0dIt7oFCvKetZSHoHcP3sDdiA==", + "dev": true, + "requires": { + "ini": "2.0.0" + } + }, "global-modules": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz", @@ -43666,23 +44017,6 @@ "function-bind": "^1.1.1" } }, - "has-ansi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true - } - } - }, "has-bigints": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz", @@ -44386,9 +44720,9 @@ "requires": {} }, "idb": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/idb/-/idb-6.1.4.tgz", - "integrity": "sha512-DshI5yxIB3NYc47cPpfipYX8MSIgQPqVR+WoaGI9EDq6cnLGgGYR1fp6z8/Bq9vMS8Jq1bS3eWUgXpFO5+ypSA==" + "version": "6.1.5", + "resolved": "https://registry.npmjs.org/idb/-/idb-6.1.5.tgz", + "integrity": "sha512-IJtugpKkiVXQn5Y+LteyBCNk1N8xpGV3wWZk9EVtZWH8DYkjBn0bX1XnGP9RkyZF0sAcywa6unHqSWKe7q4LGw==" }, "ieee754": { "version": "1.2.1", @@ -44885,6 +45219,16 @@ "integrity": "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==", "dev": true }, + "is-installed-globally": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz", + "integrity": "sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==", + "dev": true, + "requires": { + "global-dirs": "^3.0.0", + "is-path-inside": "^3.0.2" + } + }, "is-interactive": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", @@ -44941,29 +45285,11 @@ "integrity": "sha512-2rRIahhZr2UWb45fIOuvZGpFtz0TyOZLf32KxBbSoUCeZR495zCKlWUKKUByk3geS2eAs7ZAABt0Y/Rx0GiQGA==", "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" - } + "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-plain-obj": { "version": "2.1.0", @@ -45268,25 +45594,6 @@ "iterate-iterator": "^1.0.1" } }, - "jasmine": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/jasmine/-/jasmine-2.8.0.tgz", - "integrity": "sha1-awicChFXax8W3xG4AUbZHU6Lij4=", - "dev": true, - "requires": { - "exit": "^0.1.2", - "glob": "^7.0.6", - "jasmine-core": "~2.8.0" - }, - "dependencies": { - "jasmine-core": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-2.8.0.tgz", - "integrity": "sha1-vMl5rh+f0FcB5F5S5l06XWPxok4=", - "dev": true - } - } - }, "jasmine-core": { "version": "3.9.0", "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-3.9.0.tgz", @@ -45302,12 +45609,6 @@ "colors": "1.4.0" } }, - "jasminewd2": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/jasminewd2/-/jasminewd2-2.2.0.tgz", - "integrity": "sha1-43zwsX8ZnM4jvqcbIDk5Uka07E4=", - "dev": true - }, "jest-docblock": { "version": "21.2.0", "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-21.2.0.tgz", @@ -45489,8 +45790,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true, - "peer": true + "dev": true }, "json-stringify-safe": { "version": "5.0.1", @@ -45544,18 +45844,6 @@ "verror": "1.10.0" } }, - "jszip": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.7.1.tgz", - "integrity": "sha512-ghL0tz1XG9ZEmRMcEN2vt7xabrDdqHHeykgARpmZ0BiIctWxM47Vt63ZO2dnp4QYt/xJVLLy5Zv1l/xRdh2byg==", - "dev": true, - "requires": { - "lie": "~3.3.0", - "pako": "~1.0.2", - "readable-stream": "~2.3.6", - "set-immediate-shim": "~1.0.1" - } - }, "junk": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/junk/-/junk-3.1.0.tgz", @@ -45754,6 +46042,12 @@ "resolved": "https://registry.npmjs.org/layerr/-/layerr-0.1.2.tgz", "integrity": "sha512-ob5kTd9H3S4GOG2nVXyQhOu9O8nBgP555XxWPkJI0tR0JeRilfyTp8WtPdIJHLXBmHMSdEq5+KMxiYABeScsIQ==" }, + "lazy-ass": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/lazy-ass/-/lazy-ass-1.6.0.tgz", + "integrity": "sha1-eZllXoZGwX8In90YfRUNMyTVRRM=", + "dev": true + }, "lazy-universal-dotenv": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/lazy-universal-dotenv/-/lazy-universal-dotenv-3.0.1.tgz", @@ -45976,23 +46270,6 @@ } } }, - "lie": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", - "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", - "dev": true, - "requires": { - "immediate": "~3.0.5" - }, - "dependencies": { - "immediate": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", - "integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=", - "dev": true - } - } - }, "linebreak": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/linebreak/-/linebreak-1.0.2.tgz", @@ -46236,6 +46513,39 @@ "upath": "^1.1.1" } }, + "colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", + "dev": true + }, + "cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dev": true, + "requires": { + "object-assign": "^4", + "vary": "^1" + } + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + }, + "dependencies": { + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, "fill-range": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", @@ -46347,6 +46657,24 @@ "to-regex": "^3.0.2" } }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true + }, "readdirp": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", @@ -46358,6 +46686,27 @@ "readable-stream": "^2.0.2" } }, + "send": { + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", + "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", + "dev": true, + "requires": { + "debug": "2.6.9", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "~1.7.2", + "mime": "1.6.0", + "ms": "2.1.1", + "on-finished": "~2.3.0", + "range-parser": "~1.2.1", + "statuses": "~1.5.0" + } + }, "to-regex-range": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", @@ -46413,6 +46762,12 @@ "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=", "dev": true }, + "lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=", + "dev": true + }, "lodash.uniq": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", @@ -48106,6 +48461,12 @@ "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", "dev": true }, + "ospath": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/ospath/-/ospath-1.2.2.tgz", + "integrity": "sha1-EnZjl3Sj+O8lcvf+QoDg6kVQwHs=", + "dev": true + }, "overlayscrollbars": { "version": "1.13.1", "resolved": "https://registry.npmjs.org/overlayscrollbars/-/overlayscrollbars-1.13.1.tgz", @@ -48534,6 +48895,12 @@ } } }, + "pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=", + "dev": true + }, "performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", @@ -49461,223 +49828,6 @@ "xtend": "^4.0.0" } }, - "protractor": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/protractor/-/protractor-7.0.0.tgz", - "integrity": "sha512-UqkFjivi4GcvUQYzqGYNe0mLzfn5jiLmO8w9nMhQoJRLhy2grJonpga2IWhI6yJO30LibWXJJtA4MOIZD2GgZw==", - "dev": true, - "requires": { - "@types/q": "^0.0.32", - "@types/selenium-webdriver": "^3.0.0", - "blocking-proxy": "^1.0.0", - "browserstack": "^1.5.1", - "chalk": "^1.1.3", - "glob": "^7.0.3", - "jasmine": "2.8.0", - "jasminewd2": "^2.1.0", - "q": "1.4.1", - "saucelabs": "^1.5.0", - "selenium-webdriver": "3.6.0", - "source-map-support": "~0.4.0", - "webdriver-js-extender": "2.1.0", - "webdriver-manager": "^12.1.7", - "yargs": "^15.3.1" - }, - "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true - }, - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - } - }, - "cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", - "dev": true, - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" - }, - "dependencies": { - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true - }, - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.0" - } - } - } - }, - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "requires": { - "p-locate": "^4.1.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": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "requires": { - "p-limit": "^2.2.0" - } - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - }, - "source-map-support": { - "version": "0.4.18", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", - "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", - "dev": true, - "requires": { - "source-map": "^0.5.6" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - }, - "wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.0" - } - } - } - }, - "y18n": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", - "dev": true - }, - "yargs": { - "version": "15.4.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", - "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", - "dev": true, - "requires": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^18.1.2" - } - }, - "yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "dev": true, - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } - } - } - }, "proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -49688,6 +49838,12 @@ "ipaddr.js": "1.9.1" } }, + "proxy-from-env": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.0.0.tgz", + "integrity": "sha1-M8UDmPcOp+uW0h97gXYwpVeRx+4=", + "dev": true + }, "proxy-middleware": { "version": "0.15.0", "resolved": "https://registry.npmjs.org/proxy-middleware/-/proxy-middleware-0.15.0.tgz", @@ -49766,12 +49922,6 @@ "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", "dev": true }, - "q": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/q/-/q-1.4.1.tgz", - "integrity": "sha1-VXBbzZPF82c1MMLCy8DCs63cKG4=", - "dev": true - }, "qjobs": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/qjobs/-/qjobs-1.2.0.tgz", @@ -50719,6 +50869,15 @@ } } }, + "request-progress": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/request-progress/-/request-progress-3.0.0.tgz", + "integrity": "sha1-TKdUCBx/7GP1BeT6qCWqBs1mnb4=", + "dev": true, + "requires": { + "throttleit": "^1.0.0" + } + }, "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -50728,8 +50887,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true, - "peer": true + "dev": true }, "require-main-filename": { "version": "2.0.0", @@ -51248,45 +51406,6 @@ } } }, - "saucelabs": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/saucelabs/-/saucelabs-1.5.0.tgz", - "integrity": "sha512-jlX3FGdWvYf4Q3LFfFWS1QvPg3IGCGWxIc8QBFdPTbpTJnt/v17FHXYVAn7C8sHf1yUXo2c7yIM0isDryfYtHQ==", - "dev": true, - "requires": { - "https-proxy-agent": "^2.2.1" - }, - "dependencies": { - "agent-base": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", - "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", - "dev": true, - "requires": { - "es6-promisify": "^5.0.0" - } - }, - "debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "https-proxy-agent": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz", - "integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==", - "dev": true, - "requires": { - "agent-base": "^4.3.0", - "debug": "^3.1.0" - } - } - } - }, "sax": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", @@ -51362,38 +51481,6 @@ "integrity": "sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo=", "dev": true }, - "selenium-webdriver": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-3.6.0.tgz", - "integrity": "sha512-WH7Aldse+2P5bbFBO4Gle/nuQOdVwpHMTL6raL3uuBj/vPG07k6uzt3aiahu352ONBr5xXh0hDlM3LhtXPOC4Q==", - "dev": true, - "requires": { - "jszip": "^3.1.3", - "rimraf": "^2.5.4", - "tmp": "0.0.30", - "xml2js": "^0.4.17" - }, - "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.0.30", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.30.tgz", - "integrity": "sha1-ckGdSovn1s51FI/YsyTlk6cRwu0=", - "dev": true, - "requires": { - "os-tmpdir": "~1.0.1" - } - } - } - }, "selfsigned": { "version": "1.10.11", "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.11.tgz", @@ -51614,12 +51701,6 @@ "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", "dev": true }, - "set-immediate-shim": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", - "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=", - "dev": true - }, "set-value": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", @@ -53196,6 +53277,12 @@ "integrity": "sha512-dTEWWNu6JmeVXY0ZYoPuH5cRIwc0MeGbJwah9KUNYSJwommQpCzTySTpEe8Gs1J23aeWEuAobe4Ag7EHVt/LOg==", "dev": true }, + "throttleit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.0.tgz", + "integrity": "sha1-nnhYNtr0Z0MUWlmEtiaNgoUorGw=", + "dev": true + }, "through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", @@ -54332,6 +54419,12 @@ } } }, + "untildify": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz", + "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==", + "dev": true + }, "upath": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", @@ -54933,98 +55026,6 @@ "url-parse": "^1.5.3" } }, - "webdriver-js-extender": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/webdriver-js-extender/-/webdriver-js-extender-2.1.0.tgz", - "integrity": "sha512-lcUKrjbBfCK6MNsh7xaY2UAUmZwe+/ib03AjVOpFobX4O7+83BUveSrLfU0Qsyb1DaKJdQRbuU+kM9aZ6QUhiQ==", - "dev": true, - "requires": { - "@types/selenium-webdriver": "^3.0.0", - "selenium-webdriver": "^3.0.1" - } - }, - "webdriver-manager": { - "version": "12.1.8", - "resolved": "https://registry.npmjs.org/webdriver-manager/-/webdriver-manager-12.1.8.tgz", - "integrity": "sha512-qJR36SXG2VwKugPcdwhaqcLQOD7r8P2Xiv9sfNbfZrKBnX243iAkOueX1yAmeNgIKhJ3YAT/F2gq6IiEZzahsg==", - "dev": true, - "requires": { - "adm-zip": "^0.4.9", - "chalk": "^1.1.1", - "del": "^2.2.0", - "glob": "^7.0.3", - "ini": "^1.3.4", - "minimist": "^1.2.0", - "q": "^1.4.1", - "request": "^2.87.0", - "rimraf": "^2.5.2", - "semver": "^5.3.0", - "xml2js": "^0.4.17" - }, - "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true - }, - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - } - }, - "ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "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" - } - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - } - } - }, "webpack": { "version": "4.46.0", "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.46.0.tgz", @@ -56514,22 +56515,6 @@ "dev": true, "requires": {} }, - "xml2js": { - "version": "0.4.23", - "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", - "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", - "dev": true, - "requires": { - "sax": ">=0.6.0", - "xmlbuilder": "~11.0.0" - } - }, - "xmlbuilder": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", - "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", - "dev": true - }, "xmldoc": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/xmldoc/-/xmldoc-1.1.2.tgz", @@ -56586,6 +56571,16 @@ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==" }, + "yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=", + "dev": true, + "requires": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, "yn": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", diff --git a/package.json b/package.json index 1fae8252e3..771b07f482 100644 --- a/package.json +++ b/package.json @@ -11,12 +11,15 @@ "test-ci": "ng test --code-coverage --karmaConfig=build/karma-ci.conf.js ", "lint": "ng lint ndb-core --type-check", "e2e": "ng e2e", + "e2e-open": "ng run ndb-core-e2e:cypress-open", "compodoc": "npx compodoc -c doc/compodoc_sources/.compodocrc.json", "postinstall": "ngcc && node patch-webpack.js", "docs:json": "compodoc -p ./tsconfig.json -e json -d .", "storybook": "npm run docs:json && start-storybook -p 6006", "build-storybook": "npm run docs:json && build-storybook", - "extract-i18n": "ng extract-i18n --output-path ./src/locale/ && xliffmerge --profile xliffmerge.json" + "extract-i18n": "ng extract-i18n --output-path ./src/locale/ && xliffmerge --profile xliffmerge.json", + "cypress:open": "e2e open", + "cypress:run": "e2e run" }, "private": true, "dependencies": { @@ -38,13 +41,13 @@ "@fortawesome/free-regular-svg-icons": "^5.15.2", "@fortawesome/free-solid-svg-icons": "^5.15.4", "@ngneat/until-destroy": "^8.1.4", - "@sentry/browser": "^6.13.2", - "angulartics2": "^10.0.0", + "@sentry/browser": "^6.15.0", + "angulartics2": "^10.1.0", "crypto-js": "^4.1.1", "deep-object-diff": "^1.1.0", "faker": "^5.5.3", "flag-icon-css": "^3.5.0", - "idb": "^6.1.2", + "idb": "^6.1.5", "json-query": "^2.2.2", "lodash": "^4.17.21", "md5": "^2.3.0", @@ -69,6 +72,7 @@ "@angular/compiler-cli": "^11.2.4", "@babel/core": "^7.13.8", "@compodoc/compodoc": "1.1.11", + "@cypress/schematic": "^1.5.1", "@schematics/angular": "^11.2.14", "@storybook/addon-actions": "^6.1.21", "@storybook/addon-essentials": "^6.1.21", @@ -96,13 +100,13 @@ "lint-staged": "^10.5.4", "ngx-i18nsupport": "^0.17.1", "prettier": "~2.2.1", - "protractor": "~7.0.0", "ts-node": "^9.1.1", "tslint": "^6.1.3", "tslint-config-prettier": "^1.18.0", "tslint-plugin-prettier": "^2.3.0", "typescript": "~4.1.6", - "webpack": "^4.6.0" + "webpack": "^4.6.0", + "cypress": "8.5.0" }, "browser": { "crypto": false diff --git a/protractor.conf.js b/protractor.conf.js deleted file mode 100644 index 786d3b8686..0000000000 --- a/protractor.conf.js +++ /dev/null @@ -1,45 +0,0 @@ -/* - * This file is part of ndb-core. - * - * ndb-core is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ndb-core is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ndb-core. If not, see . - */ - -// Protractor configuration file, see link for more information -// https://github.com/angular/protractor/blob/master/lib/config.ts - -const { SpecReporter } = require('jasmine-spec-reporter'); - -exports.config = { - allScriptsTimeout: 11000, - specs: [ - './e2e/**/*.e2e-spec.ts' - ], - capabilities: { - 'browserName': 'chrome' - }, - directConnect: true, - baseUrl: 'http://localhost:4200/', - framework: 'jasmine', - jasmineNodeOpts: { - showColors: true, - defaultTimeoutInterval: 30000, - print: function() {} - }, - onPrepare() { - require('ts-node').register({ - project: 'e2e/tsconfig.e2e.json' - }); - jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); - } -}; diff --git a/src/app/app.component.spec.ts b/src/app/app.component.spec.ts index 4259024ce3..617bffc9fa 100644 --- a/src/app/app.component.spec.ts +++ b/src/app/app.component.spec.ts @@ -28,11 +28,13 @@ import { ApplicationInitStatus } from "@angular/core"; import { AppModule } from "./app.module"; import { AppConfig } from "./core/app-config/app-config"; import { IAppConfig } from "./core/app-config/app-config.model"; -import { Angulartics2Piwik } from "angulartics2/piwik"; +import { Angulartics2Matomo } from "angulartics2/matomo"; import { EntityMapperService } from "./core/entity/entity-mapper.service"; import { Config } from "./core/config/config"; import { USAGE_ANALYTICS_CONFIG_ID } from "./core/analytics/usage-analytics-config"; import { environment } from "../environments/environment"; +import { HttpClientTestingModule } from "@angular/common/http/testing"; +import { DynamicEntityService } from "./core/entity/dynamic-entity.service"; describe("AppComponent", () => { let component: AppComponent; @@ -49,11 +51,15 @@ describe("AppComponent", () => { AppConfig.settings = mockAppSettings; TestBed.configureTestingModule({ - imports: [AppModule], + imports: [AppModule, HttpClientTestingModule], providers: [ { provide: AppConfig, useValue: jasmine.createSpyObj(["load"]) }, ], }).compileComponents(); + + // Otherwise multiple registrations throw an error + spyOn(DynamicEntityService, "registerNewEntity"); + TestBed.inject(ApplicationInitStatus); // This ensures that the AppConfig is loaded before test execution }) ); @@ -84,7 +90,7 @@ describe("AppComponent", () => { }; const entityMapper = TestBed.inject(EntityMapperService); spyOn(entityMapper, "load").and.resolveTo(new Config(testConfig)); - const angulartics = TestBed.inject(Angulartics2Piwik); + const angulartics = TestBed.inject(Angulartics2Matomo); const startTrackingSpy = spyOn(angulartics, "startTracking"); createComponent(); diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 7805c36641..602a97c16b 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -24,18 +24,14 @@ import { EntityMapperService } from "./core/entity/entity-mapper.service"; import { ConfigService } from "./core/config/config.service"; import { RouterService } from "./core/view/dynamic-routing/router.service"; import { EntityConfigService } from "./core/entity/entity-config.service"; -import { Child } from "./child-dev-project/children/model/child"; import { SessionService } from "./core/session/session-service/session.service"; import { SyncState } from "./core/session/session-states/sync-state.enum"; import { ActivatedRoute, Router } from "@angular/router"; -import { RecurringActivity } from "./child-dev-project/attendance/model/recurring-activity"; -import { School } from "./child-dev-project/schools/model/school"; -import { HistoricalEntityData } from "./features/historical-data/historical-entity-data"; -import { Note } from "./child-dev-project/notes/model/note"; -import { EventNote } from "./child-dev-project/attendance/model/event-note"; import { waitForChangeTo } from "./core/session/session-states/session-utils"; import { environment } from "../environments/environment"; -import { ChildSchoolRelation } from "./child-dev-project/children/model/childSchoolRelation"; +import { DynamicEntityService } from "./core/entity/dynamic-entity.service"; +import { Child } from "./child-dev-project/children/model/child"; +import { School } from "./child-dev-project/schools/model/school"; @Component({ selector: "app-root", @@ -62,6 +58,11 @@ export class AppComponent implements OnInit { } private async initBasicServices() { + // TODO: remove this with issue #886 + // This needs to be in the app module (as opposed to the dynamic entity service) + // to prevent circular dependencies + DynamicEntityService.registerNewEntity("Participant", Child); + DynamicEntityService.registerNewEntity("Team", School); // first register to events // Reload config once the database is synced @@ -76,13 +77,7 @@ export class AppComponent implements OnInit { // These functions will be executed whenever a new config is available this.configService.configUpdates.subscribe(() => { this.routerService.initRouting(); - this.entityConfigService.addConfigAttributes(Child); - this.entityConfigService.addConfigAttributes(School); - this.entityConfigService.addConfigAttributes(RecurringActivity); - this.entityConfigService.addConfigAttributes(HistoricalEntityData); - this.entityConfigService.addConfigAttributes(Note); - this.entityConfigService.addConfigAttributes(EventNote); - this.entityConfigService.addConfigAttributes(ChildSchoolRelation); + this.entityConfigService.setupEntitiesFromConfig(); }); // If loading the config earlier (in a module constructor or through APP_INITIALIZER) a runtime error occurs. diff --git a/src/app/app.module.ts b/src/app/app.module.ts index b065887774..b14cce8e77 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -57,7 +57,7 @@ import { FormDialogModule } from "./core/form-dialog/form-dialog.module"; import { LoggingService } from "./core/logging/logging.service"; import { Angulartics2Module } from "angulartics2"; import { AnalyticsService } from "./core/analytics/analytics.service"; -import { Angulartics2Piwik } from "angulartics2/piwik"; +import { Angulartics2Matomo } from "angulartics2/matomo"; import { ViewModule } from "./core/view/view.module"; import { DashboardModule } from "./core/dashboard/dashboard.module"; import { EntityDetailsModule } from "./core/entity-components/entity-details/entity-details.module"; @@ -156,7 +156,7 @@ import { far } from "@fortawesome/free-regular-svg-icons"; { provide: ErrorHandler, useClass: LoggingErrorHandler }, { provide: MatPaginatorIntl, useValue: TranslatableMatPaginator() }, AnalyticsService, - Angulartics2Piwik, + Angulartics2Matomo, ], bootstrap: [AppComponent], }) diff --git a/src/app/child-dev-project/aser/model/aser.spec.ts b/src/app/child-dev-project/aser/model/aser.spec.ts index 63ed13cebb..b5172ffbf0 100644 --- a/src/app/child-dev-project/aser/model/aser.spec.ts +++ b/src/app/child-dev-project/aser/model/aser.spec.ts @@ -16,7 +16,6 @@ */ import { Aser } from "./aser"; -import { waitForAsync } from "@angular/core/testing"; import { Entity } from "../../../core/entity/model/entity"; import { EntitySchemaService } from "../../../core/entity/schema/entity-schema.service"; import { mathLevels } from "./mathLevels"; @@ -27,8 +26,6 @@ describe("Aser", () => { const ENTITY_TYPE = "Aser"; const entitySchemaService = new EntitySchemaService(); - beforeEach(waitForAsync(() => {})); - it("has correct _id and entityId and type", function () { const id = "test1"; const entity = new Aser(id); @@ -90,4 +87,21 @@ describe("Aser", () => { expect(entity.getWarningLevel()).toBe(WarningLevel.WARNING); }); + + it("has a warning level of OK if english is at it's highest level", () => { + const entity = new Aser(); + entity.english = readingLevels[readingLevels.length - 1]; + + expect(entity.getWarningLevel()).toBe(WarningLevel.OK); + }); + + it("has a warning level of OK if all values are at it's highest level", () => { + const entity = new Aser(); + entity.math = mathLevels[mathLevels.length - 1]; + entity.english = readingLevels[readingLevels.length - 1]; + entity.hindi = readingLevels[readingLevels.length - 1]; + entity.bengali = readingLevels[readingLevels.length - 1]; + + expect(entity.getWarningLevel()).toBe(WarningLevel.OK); + }); }); diff --git a/src/app/child-dev-project/aser/model/aser.ts b/src/app/child-dev-project/aser/model/aser.ts index 41ac3366cd..c672b3593c 100644 --- a/src/app/child-dev-project/aser/model/aser.ts +++ b/src/app/child-dev-project/aser/model/aser.ts @@ -18,27 +18,34 @@ import { Entity } from "../../../core/entity/model/entity"; import { DatabaseField } from "../../../core/entity/database-field.decorator"; import { DatabaseEntity } from "../../../core/entity/database-entity.decorator"; -import { ConfigurableEnumValue } from "../../../core/configurable-enum/configurable-enum.interface"; -import { mathLevels } from "./mathLevels"; -import { readingLevels } from "./readingLevels"; +import { + ConfigurableEnumConfig, + ConfigurableEnumValue, +} from "../../../core/configurable-enum/configurable-enum.interface"; +import { MathLevel, mathLevels } from "./mathLevels"; +import { ReadingLevel, readingLevels } from "./readingLevels"; import { WarningLevel } from "../../../core/entity/model/warning-level"; @DatabaseEntity("Aser") export class Aser extends Entity { - static isReadingPassedOrNA(level: ConfigurableEnumValue) { - if (!level || level.id === "") { - // not applicable - return true; - } - return level === readingLevels.find((it) => it.id === "read_paragraph"); + static isReadingPassedOrNA(level: ConfigurableEnumValue): boolean { + return this.isHighestLevelOrNA(level, readingLevels); + } + + static isMathPassedOrNA(level: ConfigurableEnumValue): boolean { + return this.isHighestLevelOrNA(level, mathLevels); } - static isMathPassedOrNA(level: ConfigurableEnumValue) { + static isHighestLevelOrNA( + level: ConfigurableEnumValue, + source: ConfigurableEnumConfig + ): boolean { if (!level || level.id === "") { // not applicable return true; } - return level === mathLevels.find((it) => it.id === "division"); + const index = source.findIndex((cEnumValue) => cEnumValue.id === level.id); + return index === source.length - 1; } @DatabaseField() child: string; // id of Child entity @@ -51,25 +58,25 @@ export class Aser extends Entity { dataType: "configurable-enum", innerDataType: "reading-levels", }) - hindi: ConfigurableEnumValue; + hindi: ReadingLevel; @DatabaseField({ label: $localize`:Label of the Bengali ASER result:Bengali`, dataType: "configurable-enum", innerDataType: "reading-levels", }) - bengali: ConfigurableEnumValue; + bengali: ReadingLevel; @DatabaseField({ label: $localize`:Label of the English ASER result:English`, dataType: "configurable-enum", innerDataType: "reading-levels", }) - english: ConfigurableEnumValue; + english: ReadingLevel; @DatabaseField({ label: $localize`:Label of the Math ASER result:Math`, dataType: "configurable-enum", innerDataType: "math-levels", }) - math: ConfigurableEnumValue; + math: MathLevel; @DatabaseField({ label: $localize`:Label for the remarks of a ASER result:Remarks`, }) diff --git a/src/app/child-dev-project/aser/model/mathLevels.ts b/src/app/child-dev-project/aser/model/mathLevels.ts index b88fe930a5..00af8d1eb5 100644 --- a/src/app/child-dev-project/aser/model/mathLevels.ts +++ b/src/app/child-dev-project/aser/model/mathLevels.ts @@ -1,10 +1,12 @@ -import { ConfigurableEnumValue } from "../../../core/configurable-enum/configurable-enum.interface"; +import { + ConfigurableEnumConfig, + ConfigurableEnumValue, + EMPTY, +} from "../../../core/configurable-enum/configurable-enum.interface"; -export const mathLevels: ConfigurableEnumValue[] = [ - { - id: "", - label: "", - }, +export type MathLevel = ConfigurableEnumValue; +export const mathLevels: ConfigurableEnumConfig = [ + EMPTY, { id: "Nothing", label: $localize`:Label math level:Nothing`, diff --git a/src/app/child-dev-project/aser/model/readingLevels.ts b/src/app/child-dev-project/aser/model/readingLevels.ts index c594316cfb..577f7a0f44 100644 --- a/src/app/child-dev-project/aser/model/readingLevels.ts +++ b/src/app/child-dev-project/aser/model/readingLevels.ts @@ -1,10 +1,12 @@ -import { ConfigurableEnumValue } from "../../../core/configurable-enum/configurable-enum.interface"; +import { + ConfigurableEnumConfig, + ConfigurableEnumValue, + EMPTY, +} from "../../../core/configurable-enum/configurable-enum.interface"; -export const readingLevels: ConfigurableEnumValue[] = [ - { - id: "", - label: "", - }, +export type ReadingLevel = ConfigurableEnumValue; +export const readingLevels: ConfigurableEnumConfig = [ + EMPTY, { id: "Nothing", label: $localize`:Label reading level:Nothing`, diff --git a/src/app/child-dev-project/attendance/add-day-attendance/add-day-attendance.component.html b/src/app/child-dev-project/attendance/add-day-attendance/add-day-attendance.component.html index 141e719f62..0c89d90457 100644 --- a/src/app/child-dev-project/attendance/add-day-attendance/add-day-attendance.component.html +++ b/src/app/child-dev-project/attendance/add-day-attendance/add-day-attendance.component.html @@ -20,6 +20,7 @@ diff --git a/src/app/child-dev-project/attendance/add-day-attendance/add-day-attendance.component.ts b/src/app/child-dev-project/attendance/add-day-attendance/add-day-attendance.component.ts index e5127bf7e2..eac4c74a9f 100644 --- a/src/app/child-dev-project/attendance/add-day-attendance/add-day-attendance.component.ts +++ b/src/app/child-dev-project/attendance/add-day-attendance/add-day-attendance.component.ts @@ -1,6 +1,16 @@ import { Component } from "@angular/core"; import { EntityMapperService } from "../../../core/entity/entity-mapper.service"; import { Note } from "../../notes/model/note"; +import { ActivatedRoute } from "@angular/router"; +import { RouteData } from "../../../core/view/dynamic-routing/view-config.interface"; + +/** + * additional config specifically for AddDayAttendanceComponent + */ +export interface AddDayAttendanceConfig { + /** (optional) property name of the participant entities by which they are sorted for the roll call */ + sortParticipantsBy?: string; +} @Component({ selector: "app-add-day-attendance", @@ -8,6 +18,8 @@ import { Note } from "../../notes/model/note"; styleUrls: ["./add-day-attendance.component.scss"], }) export class AddDayAttendanceComponent { + config?: AddDayAttendanceConfig; + currentStage = 0; day = new Date(); @@ -20,7 +32,14 @@ export class AddDayAttendanceComponent { $localize`:One of the stages while recording child-attendances:Record Attendance`, ]; - constructor(private entityMapper: EntityMapperService) {} + constructor( + private entityMapper: EntityMapperService, + private route: ActivatedRoute + ) { + this.route.data.subscribe((data: RouteData) => { + this.config = data.config; + }); + } finishBasicInformationStage(event: Note) { this.event = event; diff --git a/src/app/child-dev-project/attendance/add-day-attendance/roll-call/roll-call.component.spec.ts b/src/app/child-dev-project/attendance/add-day-attendance/roll-call/roll-call.component.spec.ts index 11f66f79ef..0b6c685934 100644 --- a/src/app/child-dev-project/attendance/add-day-attendance/roll-call/roll-call.component.spec.ts +++ b/src/app/child-dev-project/attendance/add-day-attendance/roll-call/roll-call.component.spec.ts @@ -13,7 +13,6 @@ import { By } from "@angular/platform-browser"; import { ConfigService } from "../../../../core/config/config.service"; import { ConfigurableEnumConfig } from "../../../../core/configurable-enum/configurable-enum.interface"; import { Child } from "../../../children/model/child"; -import { EntityMapperService } from "../../../../core/entity/entity-mapper.service"; import { LoggingService } from "../../../../core/logging/logging.service"; import { defaultAttendanceStatusTypes } from "../../../../core/config/default-config/default-attendance-status-types"; import { AttendanceModule } from "../../attendance.module"; @@ -22,6 +21,8 @@ import { MockSessionModule } from "../../../../core/session/mock-session.module" import { ConfirmationDialogService } from "../../../../core/confirmation-dialog/confirmation-dialog.service"; import { of } from "rxjs"; import { FontAwesomeTestingModule } from "@fortawesome/angular-fontawesome/testing"; +import { LoginState } from "../../../../core/session/session-states/login-state.enum"; +import { SimpleChange } from "@angular/core"; describe("RollCallComponent", () => { let component: RollCallComponent; @@ -29,28 +30,38 @@ describe("RollCallComponent", () => { const testEvent = Note.create(new Date()); let mockConfigService: jasmine.SpyObj; - let mockEntityMapper: jasmine.SpyObj; let mockLoggingService: jasmine.SpyObj; + let participant1: Child, participant2: Child, participant3: Child; + + const dummyChanges = { + eventEntity: new SimpleChange(undefined, {}, true), + }; + beforeEach( waitForAsync(() => { + participant1 = new Child("child1"); + participant2 = new Child("child2"); + participant3 = new Child("child3"); + mockConfigService = jasmine.createSpyObj("mockConfigService", [ "getConfig", ]); mockConfigService.getConfig.and.returnValue([]); - mockEntityMapper = jasmine.createSpyObj(["load"]); - mockEntityMapper.load.and.resolveTo(); mockLoggingService = jasmine.createSpyObj(["warn"]); TestBed.configureTestingModule({ imports: [ AttendanceModule, - MockSessionModule, + MockSessionModule.withState(LoginState.LOGGED_IN, [ + participant1, + participant2, + participant3, + ]), FontAwesomeTestingModule, ], providers: [ { provide: ConfigService, useValue: mockConfigService }, - { provide: EntityMapperService, useValue: mockEntityMapper }, { provide: LoggingService, useValue: mockLoggingService }, { provide: ChildrenService, useValue: {} }, ], @@ -87,9 +98,9 @@ describe("RollCallComponent", () => { }, ]; mockConfigService.getConfig.and.returnValue(testStatusEnumConfig); - component.eventEntity = Note.create(new Date()); - component.eventEntity.addChild("1"); - await component.ngOnInit(); + component.eventEntity = new Note(); + component.eventEntity.addChild(participant1.getId()); + await component.ngOnChanges(dummyChanges); fixture.detectChanges(); await fixture.whenStable(); @@ -100,22 +111,17 @@ describe("RollCallComponent", () => { }); it("should not record attendance if childId does not exist", fakeAsync(() => { - const existingChild = new Child("existingChild"); + const nonExistingChildId = "notExistingChild"; const noteWithNonExistingChild = new Note(); - noteWithNonExistingChild.addChild(existingChild.getId()); - noteWithNonExistingChild.addChild("notExistingChild"); + noteWithNonExistingChild.addChild(participant1.getId()); + noteWithNonExistingChild.addChild(nonExistingChildId); component.eventEntity = noteWithNonExistingChild; - mockEntityMapper.load.and.callFake((con, id) => - id === existingChild.getId() - ? Promise.resolve(existingChild as any) - : Promise.reject() - ); - - component.ngOnInit(); + component.ngOnChanges(dummyChanges); tick(); - expect(component.entries.map((e) => e.child)).toEqual([existingChild]); + expect(component.entries.map((e) => e.child)).toEqual([participant1]); + expect(component.eventEntity.children).not.toContain(nonExistingChildId); expect(mockLoggingService.warn).toHaveBeenCalled(); flush(); })); @@ -127,56 +133,41 @@ describe("RollCallComponent", () => { const absentStatus = defaultAttendanceStatusTypes.find( (it) => it.countAs === "ABSENT" ); - const attendedChild = new Child("attendedChild"); - const absentChild = new Child("absentChild"); const note = new Note("noteWithAttendance"); - note.addChild(attendedChild.getId()); - note.addChild(absentChild.getId()); - mockEntityMapper.load.and.callFake((t, id) => { - if (id === absentChild.getId()) { - return Promise.resolve(absentChild) as any; - } - if (id === attendedChild.getId()) { - return Promise.resolve(attendedChild) as any; - } - }); + note.addChild(participant1.getId()); + note.addChild(participant2.getId()); + component.eventEntity = note; - component.ngOnInit(); + component.ngOnChanges(dummyChanges); tick(); const attendedChildAttendance = component.entries.find( - ({ child }) => child === attendedChild + ({ child }) => child === participant1 ).attendance; const absentChildAttendance = component.entries.find( - ({ child }) => child === absentChild + ({ child }) => child === participant2 ).attendance; component.markAttendance(attendedChildAttendance, attendedStatus); component.markAttendance(absentChildAttendance, absentStatus); - expect(note.getAttendance(attendedChild.getId()).status).toEqual( + expect(note.getAttendance(participant1.getId()).status).toEqual( attendedStatus ); - expect(note.getAttendance(absentChild.getId()).status).toEqual( + expect(note.getAttendance(participant2.getId()).status).toEqual( absentStatus ); flush(); })); it("should mark roll call as done when all existing children are finished", fakeAsync(() => { - const existingChild1 = new Child("existingChild1"); - const existingChild2 = new Child("existingChild2"); const note = new Note(); - note.addChild(existingChild1.getId()); + note.addChild(participant1.getId()); note.addChild("notExistingChild"); - note.addChild(existingChild2.getId()); - mockEntityMapper.load.and.returnValues( - Promise.resolve(existingChild2), - Promise.reject(), - Promise.resolve(existingChild1) - ); + note.addChild(participant2.getId()); + spyOn(component.complete, "emit"); component.eventEntity = note; - component.ngOnInit(); + component.ngOnChanges(dummyChanges); tick(); component.goToNextParticipant(); @@ -238,4 +229,68 @@ describe("RollCallComponent", () => { expect(confirmationDialogService.openDialog).toHaveBeenCalled(); }); + + it("should not sort participants without sortParticipantsBy configured", fakeAsync(() => { + participant1.name = "Zoey"; + participant2.name = "Adam"; + + testParticipantsAreSorted( + [participant1, participant2], + [participant1, participant2], + undefined + ); + })); + + it("should sort participants alphabetically for sortParticipantsBy", fakeAsync(() => { + participant1.name = "Zoey"; + participant2.name = undefined; + participant3.name = "Adam"; + + testParticipantsAreSorted( + [participant1, participant2, participant3], + [participant3, participant1, participant2], + "name" + ); + })); + + it("should sort participants numerically for sortParticipantsBy", fakeAsync(() => { + // @ts-ignore + participant1.priority = 99; + // @ts-ignore + participant2.priority = 1; + + testParticipantsAreSorted( + [participant1, participant2], + [participant2, participant1], + "priority" + ); + })); + + function testParticipantsAreSorted( + participantsInput: Child[], + expectedParticipantsOrder: Child[], + sortParticipantsBy: string + ) { + const event = new Note(); + for (const p of participantsInput) { + event.addChild(p.getId()); + } + component.eventEntity = event; + component.ngOnChanges(dummyChanges); + tick(); + + component.sortParticipantsBy = sortParticipantsBy; + component.ngOnChanges({ + sortParticipantsBy: new SimpleChange(undefined, "name", false), + }); + tick(); + + expect(component.entries.map((e) => e.child)).toEqual( + expectedParticipantsOrder + ); + expect(component.eventEntity.children).toEqual( + expectedParticipantsOrder.map((p) => p.getId()) + ); + flush(); + } }); diff --git a/src/app/child-dev-project/attendance/add-day-attendance/roll-call/roll-call.component.ts b/src/app/child-dev-project/attendance/add-day-attendance/roll-call/roll-call.component.ts index 91e3528ddb..f18e5189ab 100644 --- a/src/app/child-dev-project/attendance/add-day-attendance/roll-call/roll-call.component.ts +++ b/src/app/child-dev-project/attendance/add-day-attendance/roll-call/roll-call.component.ts @@ -1,4 +1,11 @@ -import { Component, EventEmitter, Input, OnInit, Output } from "@angular/core"; +import { + Component, + EventEmitter, + Input, + OnChanges, + Output, + SimpleChanges, +} from "@angular/core"; import { animate, style, transition, trigger } from "@angular/animations"; import { ATTENDANCE_STATUS_CONFIG_ID, @@ -16,6 +23,7 @@ import { Child } from "../../../children/model/child"; import { LoggingService } from "../../../../core/logging/logging.service"; import { FormGroup } from "@angular/forms"; import { ConfirmationDialogService } from "../../../../core/confirmation-dialog/confirmation-dialog.service"; +import { sortByAttribute } from "../../../../utils/utils"; /** * Displays the participants of the given event one by one to mark attendance status. @@ -33,12 +41,17 @@ import { ConfirmationDialogService } from "../../../../core/confirmation-dialog/ ]), ], }) -export class RollCallComponent implements OnInit { +export class RollCallComponent implements OnChanges { /** * The event to be displayed and edited. */ @Input() eventEntity: Note; + /** + * (optional) property name of the participant entities by which they are sorted + */ + @Input() sortParticipantsBy?: string; + /** * Emitted when the roll call is finished and results can be saved. */ @@ -64,9 +77,14 @@ export class RollCallComponent implements OnInit { private confirmationDialog: ConfirmationDialogService ) {} - async ngOnInit() { - this.loadAttendanceStatusTypes(); - await this.loadParticipants(); + async ngOnChanges(changes: SimpleChanges) { + if (changes.eventEntity) { + this.loadAttendanceStatusTypes(); + await this.loadParticipants(); + } + if (changes.sortParticipantsBy) { + this.sortParticipants(); + } } private loadAttendanceStatusTypes() { @@ -89,6 +107,7 @@ export class RollCallComponent implements OnInit { " for event " + this.eventEntity.getId() ); + this.eventEntity.removeChild(childId); continue; } this.entries.push({ @@ -96,6 +115,19 @@ export class RollCallComponent implements OnInit { attendance: this.eventEntity.getAttendance(childId), }); } + this.sortParticipants(); + } + + private sortParticipants() { + if (!this.sortParticipantsBy) { + return; + } + + this.entries.sort((a, b) => + sortByAttribute(this.sortParticipantsBy, "asc")(a.child, b.child) + ); + // also sort the participants in the Note entity itself for display in details view later + this.eventEntity.children = this.entries.map((e) => e.child.getId()); } markAttendance(attendance: EventAttendance, status: AttendanceStatusType) { diff --git a/src/app/child-dev-project/children/child-block/child-block.component.html b/src/app/child-dev-project/children/child-block/child-block.component.html index cec854c1f8..ef87a9a93e 100644 --- a/src/app/child-dev-project/children/child-block/child-block.component.html +++ b/src/app/child-dev-project/children/child-block/child-block.component.html @@ -26,17 +26,14 @@

{{ entity?.name }}

{{ entity?.phone }}

- -

- , -

+

, - + class {{ entity?.schoolClass }}

diff --git a/src/app/child-dev-project/children/demo-data-generators/demo-child-generator.service.ts b/src/app/child-dev-project/children/demo-data-generators/demo-child-generator.service.ts index ea524f82ef..533462aa75 100644 --- a/src/app/child-dev-project/children/demo-data-generators/demo-child-generator.service.ts +++ b/src/app/child-dev-project/children/demo-data-generators/demo-child-generator.service.ts @@ -38,6 +38,11 @@ export class DemoChildGenerator extends DemoDataGenerator { child.dateOfBirth = faker.dateOfBirth(5, 20); child["motherTongue"] = faker.random.arrayElement(languages); child.center = faker.random.arrayElement(centersWithProbability); + child.phone = + "+" + + faker.datatype.number({ min: 10, max: 99 }) + + " " + + faker.datatype.number({ min: 10000000, max: 99999999 }); child.admissionDate = faker.date.past(child.age - 4); diff --git a/src/app/child-dev-project/children/model/child.ts b/src/app/child-dev-project/children/model/child.ts index 7bcb584ccb..466625a8e5 100644 --- a/src/app/child-dev-project/children/model/child.ts +++ b/src/app/child-dev-project/children/model/child.ts @@ -100,6 +100,11 @@ export class Child extends Entity { }) photo: Photo; + @DatabaseField({ + label: $localize`:Label for the phone number of a child:Phone Number`, + }) + phone: string; + get age(): number { return this.dateOfBirth ? calculateAge(this.dateOfBirth) : null; } diff --git a/src/app/child-dev-project/notes/note-details/note-details.component.html b/src/app/child-dev-project/notes/note-details/note-details.component.html index 04c440825b..75c3edd48a 100644 --- a/src/app/child-dev-project/notes/note-details/note-details.component.html +++ b/src/app/child-dev-project/notes/note-details/note-details.component.html @@ -107,6 +107,7 @@

{{ entity.date?.toLocaleDateString() }}: {{ entity.subject }}

placeholder="Topic / Summary" name="subject" type="text" + cdkFocusInitial [(ngModel)]="entity.subject" /> @@ -137,7 +138,7 @@

{{ entity.date?.toLocaleDateString() }}: {{ entity.subject }}

[(selection)]="entity.children" (selectionChange)="entityForm.form.markAsDirty()" [additionalFilter]="filterInactiveChildren" - [showEntities]="!entity.category.isMeeting" + [showEntities]="!isMeeting" label="Participants" i18n-label="Participants of a note" placeholder="Add participant ..." @@ -155,7 +156,7 @@

{{ entity.date?.toLocaleDateString() }}: {{ entity.subject }}

-
+
{ it("should create", () => { expect(component).toBeTruthy(); }); + + it("should show the child meeting note attendance component when the event is a meeting", () => { + component.entity.category.isMeeting = true; + const element = fixture.debugElement.query( + By.directive(ChildMeetingNoteAttendanceComponent) + ); + expect(element).toBeTruthy(); + }); + + it("should not show the child meeting note attendance component when the event's category is undefined", () => { + component.entity.category = undefined; + fixture.detectChanges(); + const element = fixture.debugElement.query( + By.directive(ChildMeetingNoteAttendanceComponent) + ); + expect(element).toBeFalsy(); + }); }); diff --git a/src/app/child-dev-project/notes/note-details/note-details.component.ts b/src/app/child-dev-project/notes/note-details/note-details.component.ts index e0fbb66b0d..e538e13eb0 100644 --- a/src/app/child-dev-project/notes/note-details/note-details.component.ts +++ b/src/app/child-dev-project/notes/note-details/note-details.component.ts @@ -51,4 +51,8 @@ export class NoteDetailsComponent implements ShowsEntity { .beforeClosed() .subscribe(() => this.matDialogRef.close(entity)); } + + get isMeeting(): boolean { + return this.entity.category?.isMeeting || false; + } } diff --git a/src/app/child-dev-project/schools/school-block/school-block.component.html b/src/app/child-dev-project/schools/school-block/school-block.component.html index f201ad2e44..d62b8c88f6 100644 --- a/src/app/child-dev-project/schools/school-block/school-block.component.html +++ b/src/app/child-dev-project/schools/school-block/school-block.component.html @@ -1,14 +1,4 @@ - + {{ entity?.name }} - -
-
-
{{ entity?.language || entity?.medium}}
-
-
diff --git a/src/app/child-dev-project/schools/school-block/school-block.component.ts b/src/app/child-dev-project/schools/school-block/school-block.component.ts index 542449053f..d5e5d23242 100644 --- a/src/app/child-dev-project/schools/school-block/school-block.component.ts +++ b/src/app/child-dev-project/schools/school-block/school-block.component.ts @@ -22,8 +22,6 @@ export class SchoolBlockComponent implements OnInitDynamicComponent, OnChanges { @Input() entity: School = new School(""); @Input() entityId: string; @Input() linkDisabled: boolean; - tooltip = false; - tooltipTimeout; constructor( private router: Router, @@ -57,19 +55,6 @@ export class SchoolBlockComponent implements OnInitDynamicComponent, OnChanges { this.entity = await this.entityMapper.load(School, this.entityId); } - showTooltip() { - if (this.tooltipTimeout) { - clearTimeout(this.tooltipTimeout); - } - this.tooltipTimeout = setTimeout(() => (this.tooltip = true), 1000); - } - hideTooltip() { - if (this.tooltipTimeout) { - clearTimeout(this.tooltipTimeout); - } - this.tooltipTimeout = setTimeout(() => (this.tooltip = false), 150); - } - @HostListener("click") onClick() { this.showDetailsPage(); } diff --git a/src/app/core/admin/admin/admin.component.html b/src/app/core/admin/admin/admin.component.html index fae1db1ac8..9f6e8c8437 100644 --- a/src/app/core/admin/admin/admin.component.html +++ b/src/app/core/admin/admin/admin.component.html @@ -71,7 +71,7 @@

Backup

#backupImport type="file" style="display: none" - (change)="loadBackup($event.target.files[0])" + (change)="loadBackup($event)" />

@@ -113,7 +113,7 @@

Application Configuration

#configImport type="file" style="display: none" - (change)="uploadConfigFile($event.target.files[0])" + (change)="uploadConfigFile($event)" />

diff --git a/src/app/core/admin/admin/admin.component.spec.ts b/src/app/core/admin/admin/admin.component.spec.ts index 56e31ccfa3..212aaf60ae 100644 --- a/src/app/core/admin/admin/admin.component.spec.ts +++ b/src/app/core/admin/admin/admin.component.spec.ts @@ -24,6 +24,7 @@ import { NotesMigrationService } from "../../../child-dev-project/notes/notes-mi import { AttendanceMigrationService } from "../../../child-dev-project/attendance/attendance-migration/attendance-migration.service"; import { ChildrenMigrationService } from "../../../child-dev-project/children/child-photo-service/children-migration.service"; import { PermissionsMigrationService } from "../../permissions/permissions-migration.service"; +import { ConfigMigrationService } from "../../config/config-migration.service"; describe("AdminComponent", () => { let component: AdminComponent; @@ -118,6 +119,10 @@ describe("AdminComponent", () => { provide: PermissionsMigrationService, useValue: {}, }, + { + provide: ConfigMigrationService, + useValue: {}, + }, ], }).compileComponents(); }) @@ -162,7 +167,7 @@ describe("AdminComponent", () => { it("should save and apply new configuration", fakeAsync(() => { const mockFileReader = createFileReaderMock("{}"); mockConfigService.saveConfig.and.returnValue(Promise.resolve(null)); - component.uploadConfigFile(null); + component.uploadConfigFile({ target: { files: [] } } as any); tick(); expect(mockFileReader.readAsText).toHaveBeenCalled(); expect(mockConfigService.saveConfig).toHaveBeenCalled(); @@ -173,7 +178,7 @@ describe("AdminComponent", () => { mockBackupService.getJsonExport.and.returnValue(Promise.resolve("[]")); createDialogMock(); - component.loadBackup(null); + component.loadBackup({ target: { files: [] } } as any); expect(mockBackupService.getJsonExport).toHaveBeenCalled(); tick(); expect(mockFileReader.readAsText).toHaveBeenCalled(); diff --git a/src/app/core/admin/admin/admin.component.ts b/src/app/core/admin/admin/admin.component.ts index a41631c6a2..72efa2788d 100644 --- a/src/app/core/admin/admin/admin.component.ts +++ b/src/app/core/admin/admin/admin.component.ts @@ -97,8 +97,8 @@ export class AdminComponent implements OnInit { this.startDownload(configString, "text/json", "config.json"); } - async uploadConfigFile(file: Blob) { - const loadedFile = await readFile(file); + async uploadConfigFile(inputEvent: Event) { + const loadedFile = await readFile(this.getFileFromInputEvent(inputEvent)); await this.configService.saveConfig( this.entityMapper, JSON.parse(loadedFile) @@ -116,11 +116,11 @@ export class AdminComponent implements OnInit { /** * Reset the database to the state from the loaded backup file. - * @param file The file object of the backup to be restored + * @param inputEvent for the input where a file has been selected */ - async loadBackup(file) { + async loadBackup(inputEvent: Event) { const restorePoint = await this.backupService.getJsonExport(); - const newData = await readFile(file); + const newData = await readFile(this.getFileFromInputEvent(inputEvent)); const dialogRef = this.confirmationDialog.openDialog( $localize`Overwrite complete database?`, @@ -154,6 +154,11 @@ export class AdminComponent implements OnInit { }); } + private getFileFromInputEvent(inputEvent: Event): Blob { + const target = inputEvent.target as HTMLInputElement; + return target.files[0]; + } + /** * Reset the database removing all entities except user accounts. */ diff --git a/src/app/core/analytics/analytics.service.spec.ts b/src/app/core/analytics/analytics.service.spec.ts index 4d0d839067..2f837820e8 100644 --- a/src/app/core/analytics/analytics.service.spec.ts +++ b/src/app/core/analytics/analytics.service.spec.ts @@ -6,7 +6,7 @@ import { RouterTestingModule } from "@angular/router/testing"; import { MockSessionModule } from "../session/mock-session.module"; import { ConfigService } from "../config/config.service"; import { UsageAnalyticsConfig } from "./usage-analytics-config"; -import { Angulartics2Piwik } from "angulartics2/piwik"; +import { Angulartics2Matomo } from "angulartics2/matomo"; import { AppConfig } from "../app-config/app-config"; import { IAppConfig } from "../app-config/app-config.model"; @@ -14,17 +14,16 @@ describe("AnalyticsService", () => { let service: AnalyticsService; let mockConfigService: jasmine.SpyObj; - let mockAngulartics: jasmine.SpyObj; + let mockMatomo: jasmine.SpyObj; beforeEach(() => { AppConfig.settings = { site_name: "unit-testing" } as IAppConfig; mockConfigService = jasmine.createSpyObj("mockConfigService", [ "getConfig", ]); - mockAngulartics = jasmine.createSpyObj("mockAngulartics", [ - "startTracking", - "setUserProperties", + mockMatomo = jasmine.createSpyObj("mockMatomo", [ "setUsername", + "startTracking", ]); TestBed.configureTestingModule({ @@ -36,7 +35,7 @@ describe("AnalyticsService", () => { providers: [ AnalyticsService, { provide: ConfigService, useValue: mockConfigService }, - { provide: Angulartics2Piwik, useValue: mockAngulartics }, + { provide: Angulartics2Matomo, useValue: mockMatomo }, ], }); service = TestBed.inject(AnalyticsService); @@ -52,13 +51,13 @@ describe("AnalyticsService", () => { it("should not track if no url or site_id", () => { mockConfigService.getConfig.and.returnValue({}); service.init(); - expect(mockAngulartics.startTracking).not.toHaveBeenCalled(); + expect(mockMatomo.startTracking).not.toHaveBeenCalled(); }); it("should not track if no usage analytics config", () => { mockConfigService.getConfig.and.returnValue(undefined); service.init(); - expect(mockAngulartics.startTracking).not.toHaveBeenCalled(); + expect(mockMatomo.startTracking).not.toHaveBeenCalled(); }); it("should track correct site_id", () => { @@ -70,7 +69,7 @@ describe("AnalyticsService", () => { service.init(); - expect(mockAngulartics.startTracking).toHaveBeenCalledTimes(1); + expect(mockMatomo.startTracking).toHaveBeenCalledTimes(1); expect(window["_paq"]).toContain([ "setSiteId", testAnalyticsConfig.site_id, diff --git a/src/app/core/analytics/analytics.service.ts b/src/app/core/analytics/analytics.service.ts index 547f681163..437e4c4294 100644 --- a/src/app/core/analytics/analytics.service.ts +++ b/src/app/core/analytics/analytics.service.ts @@ -1,5 +1,5 @@ import { Injectable } from "@angular/core"; -import { Angulartics2Piwik } from "angulartics2/piwik"; +import { Angulartics2Matomo } from "angulartics2/matomo"; import { environment } from "../../../environments/environment"; import { AppConfig } from "../app-config/app-config"; import { ConfigService } from "../config/config.service"; @@ -9,6 +9,7 @@ import { USAGE_ANALYTICS_CONFIG_ID, UsageAnalyticsConfig, } from "./usage-analytics-config"; +import { Angulartics2 } from "angulartics2"; const md5 = require("md5"); @@ -26,7 +27,8 @@ export class AnalyticsService { } constructor( - private angulartics2Piwik: Angulartics2Piwik, + private angulartics2: Angulartics2, + private angulartics2Matomo: Angulartics2Matomo, private configService: ConfigService, private sessionService: SessionService ) { @@ -34,19 +36,19 @@ export class AnalyticsService { } private setUser(username: string): void { - this.angulartics2Piwik.setUsername( + this.angulartics2Matomo.setUsername( AnalyticsService.getUserHash(username ?? "") ); } private setVersion(): void { - this.angulartics2Piwik.setUserProperties({ + this.angulartics2.setUserProperties.next({ dimension1: "ndb-core@" + environment.appVersion, }); } private setOrganization(orgName: string): void { - this.angulartics2Piwik.setUserProperties({ + this.angulartics2.setUserProperties.next({ dimension2: orgName, }); } @@ -80,7 +82,7 @@ export class AnalyticsService { this.setOrganization(AppConfig.settings.site_name); this.setUser(undefined); - this.angulartics2Piwik.startTracking(); + this.angulartics2Matomo.startTracking(); } /** @@ -138,6 +140,9 @@ export class AnalyticsService { label: "no_label", } ): void { - this.angulartics2Piwik.eventTrack(action, properties); + this.angulartics2.eventTrack.next({ + action: action, + properties: properties, + }); } } diff --git a/src/app/core/config/config-fix.ts b/src/app/core/config/config-fix.ts index a403182eff..cc7904a5ec 100644 --- a/src/app/core/config/config-fix.ts +++ b/src/app/core/config/config-fix.ts @@ -530,6 +530,7 @@ export const defaultJsonConfig = { "center", "status", "address", + "phone" ], ] } diff --git a/src/app/core/config/config-migration.service.spec.ts b/src/app/core/config/config-migration.service.spec.ts index 1e61306687..02720984b5 100644 --- a/src/app/core/config/config-migration.service.spec.ts +++ b/src/app/core/config/config-migration.service.spec.ts @@ -16,6 +16,8 @@ import { genders } from "../../child-dev-project/children/model/genders"; import { EntitySchemaField } from "../entity/schema/entity-schema-field"; import { Child } from "../../child-dev-project/children/model/child"; import { HistoricalEntityData } from "../../features/historical-data/historical-entity-data"; +import { DynamicEntityService } from "../entity/dynamic-entity.service"; +import { EntitySchemaService } from "../entity/schema/entity-schema.service"; describe("ConfigMigrationService", () => { let service: ConfigMigrationService; @@ -344,6 +346,8 @@ describe("ConfigMigrationService", () => { providers: [ EntityConfigService, ConfigService, + EntitySchemaService, + DynamicEntityService, { provide: EntityMapperService, useValue: mockEntityMapper }, ], }); diff --git a/src/app/core/config/config-migration.service.ts b/src/app/core/config/config-migration.service.ts index e6ecb494c3..28539db7fd 100644 --- a/src/app/core/config/config-migration.service.ts +++ b/src/app/core/config/config-migration.service.ts @@ -9,7 +9,6 @@ import { FilterConfig, } from "../entity-components/entity-list/EntityListConfig"; import { FormFieldConfig } from "../entity-components/entity-form/entity-form/FormConfig"; -import { ENTITY_MAP } from "../entity-components/entity-details/entity-details.component"; import { Entity, EntityConstructor } from "../entity/model/entity"; import { EntityConfig, @@ -33,6 +32,7 @@ import { } from "../configurable-enum/configurable-enum.interface"; import { warningLevels } from "../../child-dev-project/warning-levels"; import { User } from "../user/user"; +import { DynamicEntityService } from "../entity/dynamic-entity.service"; @Injectable({ providedIn: "root", @@ -41,7 +41,8 @@ export class ConfigMigrationService { private config: Config; constructor( private configService: ConfigService, - private entityMapper: EntityMapperService + private entityMapper: EntityMapperService, + private dynamicEntityService: DynamicEntityService ) {} async migrateConfig(): Promise { @@ -92,7 +93,7 @@ export class ConfigMigrationService { .split("-") .map((s) => s.charAt(0).toUpperCase() + s.slice(1)) .join(""); - return ENTITY_MAP.get(entityType); + return this.dynamicEntityService.getEntityConstructor(entityType); } private migrateEntityListConfig( diff --git a/src/app/core/configurable-enum/configurable-enum.interface.ts b/src/app/core/configurable-enum/configurable-enum.interface.ts index 6ca3054b5b..c269d2ac58 100644 --- a/src/app/core/configurable-enum/configurable-enum.interface.ts +++ b/src/app/core/configurable-enum/configurable-enum.interface.ts @@ -32,6 +32,11 @@ export interface ConfigurableEnumValue { [x: string]: any; } +export const EMPTY: ConfigurableEnumValue = { + id: "", + label: "", +}; + /** * The prefix of all enum collection entries in the config database. * diff --git a/src/app/core/configurable-enum/edit-configurable-enum/edit-configurable-enum.component.spec.ts b/src/app/core/configurable-enum/edit-configurable-enum/edit-configurable-enum.component.spec.ts index 3c1760e04e..d0b22a059f 100644 --- a/src/app/core/configurable-enum/edit-configurable-enum/edit-configurable-enum.component.spec.ts +++ b/src/app/core/configurable-enum/edit-configurable-enum/edit-configurable-enum.component.spec.ts @@ -8,6 +8,8 @@ import { ConfigService } from "../../config/config.service"; import { MatFormFieldModule } from "@angular/material/form-field"; import { MatSelectModule } from "@angular/material/select"; import { ConfigurableEnumModule } from "../configurable-enum.module"; +import { TypedFormControl } from "../../entity-components/entity-utils/dynamic-form-components/edit-component"; +import { ConfigurableEnumValue } from "../configurable-enum.interface"; describe("EditConfigurableEnumComponent", () => { let component: EditConfigurableEnumComponent; @@ -38,7 +40,7 @@ describe("EditConfigurableEnumComponent", () => { const formControl = new FormControl(); const formGroup = new FormGroup({}); component.formControlName = "testControl"; - component.formControl = formControl; + component.formControl = formControl as TypedFormControl; formGroup.registerControl(component.formControlName, formControl); component.enumId = ""; fixture.detectChanges(); diff --git a/src/app/core/entity-components/entity-details/entity-details.component.ts b/src/app/core/entity-components/entity-details/entity-details.component.ts index 8972a24256..69a2ce2b54 100644 --- a/src/app/core/entity-components/entity-details/entity-details.component.ts +++ b/src/app/core/entity-components/entity-details/entity-details.component.ts @@ -7,18 +7,13 @@ import { PanelComponent, PanelConfig, } from "./EntityDetailsConfig"; -import { Entity, EntityConstructor } from "../../entity/model/entity"; -import { School } from "../../../child-dev-project/schools/model/school"; +import { Entity } from "../../entity/model/entity"; import { EntityMapperService } from "../../entity/entity-mapper.service"; import { getUrlWithoutParams } from "../../../utils/utils"; -import { Child } from "../../../child-dev-project/children/model/child"; -import { RecurringActivity } from "../../../child-dev-project/attendance/model/recurring-activity"; import { EntityPermissionsService, OperationType, } from "../../permissions/entity-permissions.service"; -import { User } from "../../user/user"; -import { Note } from "../../../child-dev-project/notes/model/note"; import { UntilDestroy } from "@ngneat/until-destroy"; import { RouteData } from "../../view/dynamic-routing/view-config.interface"; import { AnalyticsService } from "../../analytics/analytics.service"; @@ -26,19 +21,7 @@ import { EntityRemoveService, RemoveResult, } from "../../entity/entity-remove.service"; - -export const ENTITY_MAP: Map> = new Map< - string, - EntityConstructor ->([ - ["Child", Child], - ["Participant", Child], - ["School", School], - ["Team", School], - ["RecurringActivity", RecurringActivity], - ["Note", Note], - ["User", User], -]); +import { DynamicEntityService } from "../../entity/dynamic-entity.service"; /** * This component can be used to display a entity in more detail. @@ -69,7 +52,8 @@ export class EntityDetailsComponent { private location: Location, private analyticsService: AnalyticsService, private permissionService: EntityPermissionsService, - private entityRemoveService: EntityRemoveService + private entityRemoveService: EntityRemoveService, + private dynamicEntityService: DynamicEntityService ) { this.route.data.subscribe((data: RouteData) => { this.config = data.config; @@ -81,7 +65,7 @@ export class EntityDetailsComponent { } private loadEntity(id: string) { - const constr: EntityConstructor = ENTITY_MAP.get( + const constr = this.dynamicEntityService.getEntityConstructor( this.config.entity ); if (id === "new") { diff --git a/src/app/core/entity-components/entity-list/filter-generator.service.spec.ts b/src/app/core/entity-components/entity-list/filter-generator.service.spec.ts index 17b3ef0a76..638c0a04c3 100644 --- a/src/app/core/entity-components/entity-list/filter-generator.service.spec.ts +++ b/src/app/core/entity-components/entity-list/filter-generator.service.spec.ts @@ -12,6 +12,8 @@ import { ChildSchoolRelation } from "../../../child-dev-project/children/model/c import { Child } from "../../../child-dev-project/children/model/child"; import moment from "moment"; import { EntityConfigService } from "app/core/entity/entity-config.service"; +import { EntitySchemaService } from "../../entity/schema/entity-schema.service"; +import { DynamicEntityService } from "../../entity/dynamic-entity.service"; describe("FilterGeneratorService", () => { let service: FilterGeneratorService; @@ -23,7 +25,9 @@ describe("FilterGeneratorService", () => { TestBed.configureTestingModule({ providers: [ ConfigService, + EntitySchemaService, { provide: EntityMapperService, useValue: mockEntityMapper }, + DynamicEntityService, LoggingService, EntityConfigService, ], diff --git a/src/app/core/entity-components/entity-list/filter-generator.service.ts b/src/app/core/entity-components/entity-list/filter-generator.service.ts index 68a77d9850..a7324a7aa5 100644 --- a/src/app/core/entity-components/entity-list/filter-generator.service.ts +++ b/src/app/core/entity-components/entity-list/filter-generator.service.ts @@ -8,7 +8,6 @@ import { FilterConfig, PrebuiltFilterConfig, } from "./EntityListConfig"; -import { ENTITY_MAP } from "../entity-details/entity-details.component"; import { Entity, EntityConstructor } from "../../entity/model/entity"; import { CONFIGURABLE_ENUM_CONFIG_PREFIX, @@ -16,9 +15,9 @@ import { } from "../../configurable-enum/configurable-enum.interface"; import { ConfigService } from "../../config/config.service"; import { LoggingService } from "../../logging/logging.service"; -import { EntityMapperService } from "../../entity/entity-mapper.service"; import { EntitySchemaField } from "../../entity/schema/entity-schema-field"; import { FilterComponentSettings } from "./filter-component.settings"; +import { DynamicEntityService } from "../../entity/dynamic-entity.service"; @Injectable({ providedIn: "root", @@ -27,7 +26,7 @@ export class FilterGeneratorService { constructor( private configService: ConfigService, private loggingService: LoggingService, - private entityMapperService: EntityMapperService + private dynamicEntityService: DynamicEntityService ) {} /** @@ -97,8 +96,10 @@ export class FilterGeneratorService { schema.innerDataType ); } else if ( - ENTITY_MAP.has(config.type) || - ENTITY_MAP.has(schema.additional) + this.dynamicEntityService.hasAnyRegisteredEntity( + config.type, + schema.additional + ) ) { return await this.createEntityFilterOption( config.id, @@ -159,10 +160,7 @@ export class FilterGeneratorService { property: string, entityType: string ): Promise[]> { - const entityConstructor = ENTITY_MAP.get(entityType); - const filterEntities = await this.entityMapperService.loadType( - entityConstructor - ); + const filterEntities = await this.dynamicEntityService.loadType(entityType); const options = [ { diff --git a/src/app/core/entity-components/entity-subrecord/list-paginator/list-paginator.component.spec.ts b/src/app/core/entity-components/entity-subrecord/list-paginator/list-paginator.component.spec.ts index aad7eb4089..f63aeb66cf 100644 --- a/src/app/core/entity-components/entity-subrecord/list-paginator/list-paginator.component.spec.ts +++ b/src/app/core/entity-components/entity-subrecord/list-paginator/list-paginator.component.spec.ts @@ -15,8 +15,8 @@ import { MockSessionModule } from "../../../session/mock-session.module"; import { EntityMapperService } from "../../../entity/entity-mapper.service"; describe("ListPaginatorComponent", () => { - let component: ListPaginatorComponent; - let fixture: ComponentFixture>; + let component: ListPaginatorComponent; + let fixture: ComponentFixture; beforeEach( waitForAsync(() => { diff --git a/src/app/core/entity-components/entity-subrecord/list-paginator/list-paginator.component.ts b/src/app/core/entity-components/entity-subrecord/list-paginator/list-paginator.component.ts index 71b51c97fc..c453565bcb 100644 --- a/src/app/core/entity-components/entity-subrecord/list-paginator/list-paginator.component.ts +++ b/src/app/core/entity-components/entity-subrecord/list-paginator/list-paginator.component.ts @@ -6,7 +6,6 @@ import { SimpleChanges, AfterViewInit, } from "@angular/core"; -import { Entity } from "../../../entity/model/entity"; import { MatPaginator, PageEvent } from "@angular/material/paginator"; import { MatTableDataSource } from "@angular/material/table"; import { User } from "../../../user/user"; @@ -21,12 +20,11 @@ import { filter } from "rxjs/operators"; templateUrl: "./list-paginator.component.html", styleUrls: ["./list-paginator.component.scss"], }) -export class ListPaginatorComponent - implements OnChanges, AfterViewInit { +export class ListPaginatorComponent implements OnChanges, AfterViewInit { readonly pageSizeOptions = [10, 20, 50]; readonly defaultPageSize = 10; - @Input() dataSource: MatTableDataSource; + @Input() dataSource: MatTableDataSource; @Input() idForSavingPagination: string; @ViewChild(MatPaginator) paginator: MatPaginator; diff --git a/src/app/core/entity-components/entity-utils/dynamic-form-components/edit-age/edit-age.component.spec.ts b/src/app/core/entity-components/entity-utils/dynamic-form-components/edit-age/edit-age.component.spec.ts index e14f6b9835..a0e2053699 100644 --- a/src/app/core/entity-components/entity-utils/dynamic-form-components/edit-age/edit-age.component.spec.ts +++ b/src/app/core/entity-components/entity-utils/dynamic-form-components/edit-age/edit-age.component.spec.ts @@ -1,7 +1,7 @@ import { ComponentFixture, TestBed } from "@angular/core/testing"; import { EditAgeComponent } from "./edit-age.component"; -import { FormControl, FormGroup, ReactiveFormsModule } from "@angular/forms"; +import { ReactiveFormsModule } from "@angular/forms"; import { NoopAnimationsModule } from "@angular/platform-browser/animations"; import { MatFormFieldModule } from "@angular/material/form-field"; import { MatDatepickerModule } from "@angular/material/datepicker"; @@ -9,6 +9,7 @@ import { FontAwesomeModule } from "@fortawesome/angular-fontawesome"; import { MatNativeDateModule } from "@angular/material/core"; import { FontAwesomeTestingModule } from "@fortawesome/angular-fontawesome/testing"; import { MatInputModule } from "@angular/material/input"; +import { setupEditComponent } from "../edit-component.spec"; describe("EditAgeComponent", () => { let component: EditAgeComponent; @@ -33,11 +34,7 @@ describe("EditAgeComponent", () => { beforeEach(() => { fixture = TestBed.createComponent(EditAgeComponent); component = fixture.componentInstance; - const formControl = new FormControl(); - const formGroup = new FormGroup({}); - component.formControlName = "testControl"; - component.formControl = formControl; - formGroup.registerControl(component.formControlName, formControl); + setupEditComponent(component); fixture.detectChanges(); }); diff --git a/src/app/core/entity-components/entity-utils/dynamic-form-components/edit-boolean/edit-boolean.component.spec.ts b/src/app/core/entity-components/entity-utils/dynamic-form-components/edit-boolean/edit-boolean.component.spec.ts index d8e6be9445..90e62306b0 100644 --- a/src/app/core/entity-components/entity-utils/dynamic-form-components/edit-boolean/edit-boolean.component.spec.ts +++ b/src/app/core/entity-components/entity-utils/dynamic-form-components/edit-boolean/edit-boolean.component.spec.ts @@ -1,9 +1,10 @@ import { ComponentFixture, TestBed } from "@angular/core/testing"; import { EditBooleanComponent } from "./edit-boolean.component"; -import { FormControl, FormGroup, ReactiveFormsModule } from "@angular/forms"; +import { ReactiveFormsModule } from "@angular/forms"; import { NoopAnimationsModule } from "@angular/platform-browser/animations"; import { MatCheckboxModule } from "@angular/material/checkbox"; +import { setupEditComponent } from "../edit-component.spec"; describe("EditBooleanComponent", () => { let component: EditBooleanComponent; @@ -19,11 +20,7 @@ describe("EditBooleanComponent", () => { beforeEach(() => { fixture = TestBed.createComponent(EditBooleanComponent); component = fixture.componentInstance; - const formControl = new FormControl(); - const formGroup = new FormGroup({}); - component.formControlName = "testControl"; - component.formControl = formControl; - formGroup.registerControl(component.formControlName, formControl); + setupEditComponent(component); fixture.detectChanges(); }); diff --git a/src/app/core/entity-components/entity-utils/dynamic-form-components/edit-component.spec.ts b/src/app/core/entity-components/entity-utils/dynamic-form-components/edit-component.spec.ts new file mode 100644 index 0000000000..e05c441ceb --- /dev/null +++ b/src/app/core/entity-components/entity-utils/dynamic-form-components/edit-component.spec.ts @@ -0,0 +1,23 @@ +import { EditComponent } from "./edit-component"; +import { FormControl, FormGroup } from "@angular/forms"; + +/** + * A simple helper class that sets up a EditComponent with the required FormGroup + * @param component that extends EditComponent + * @param propertyName (optional) the name of the property for which the edit component is created + */ +export function setupEditComponent( + component: EditComponent, + propertyName = "testProperty" +): FormGroup { + const formControl = new FormControl(); + const fromGroupConfig = {}; + fromGroupConfig[propertyName] = formControl; + const formGroup = new FormGroup(fromGroupConfig); + component.onInitFromDynamicConfig({ + formControl: formControl, + propertySchema: {}, + formFieldConfig: { id: propertyName }, + }); + return formGroup; +} diff --git a/src/app/core/entity-components/entity-utils/dynamic-form-components/edit-component.ts b/src/app/core/entity-components/entity-utils/dynamic-form-components/edit-component.ts index 4c50b498b0..0f53ad4d33 100644 --- a/src/app/core/entity-components/entity-utils/dynamic-form-components/edit-component.ts +++ b/src/app/core/entity-components/entity-utils/dynamic-form-components/edit-component.ts @@ -1,5 +1,5 @@ import { OnInitDynamicComponent } from "../../../view/dynamic-components/on-init-dynamic-component.interface"; -import { AbstractControl, FormControl } from "@angular/forms"; +import { AbstractControl, FormControl, FormGroup } from "@angular/forms"; import { FormFieldConfig } from "../../entity-form/entity-form/FormConfig"; import { EntitySchemaField } from "../../../entity/schema/entity-schema-field"; @@ -40,6 +40,10 @@ export class TypedFormControl extends FormControl { ) { super.setValue(value, options); } + + get parent(): FormGroup { + return super.parent as FormGroup; + } } /** diff --git a/src/app/core/entity-components/entity-utils/dynamic-form-components/edit-date/edit-date.component.spec.ts b/src/app/core/entity-components/entity-utils/dynamic-form-components/edit-date/edit-date.component.spec.ts index 45fa70584f..7d4da5c27a 100644 --- a/src/app/core/entity-components/entity-utils/dynamic-form-components/edit-date/edit-date.component.spec.ts +++ b/src/app/core/entity-components/entity-utils/dynamic-form-components/edit-date/edit-date.component.spec.ts @@ -1,12 +1,13 @@ import { ComponentFixture, TestBed } from "@angular/core/testing"; import { EditDateComponent } from "./edit-date.component"; -import { FormControl, FormGroup, ReactiveFormsModule } from "@angular/forms"; +import { ReactiveFormsModule } from "@angular/forms"; import { NoopAnimationsModule } from "@angular/platform-browser/animations"; import { MatFormFieldModule } from "@angular/material/form-field"; import { MatDatepickerModule } from "@angular/material/datepicker"; import { MatInputModule } from "@angular/material/input"; import { MatNativeDateModule } from "@angular/material/core"; +import { setupEditComponent } from "../edit-component.spec"; describe("EditDateComponent", () => { let component: EditDateComponent; @@ -29,11 +30,7 @@ describe("EditDateComponent", () => { beforeEach(() => { fixture = TestBed.createComponent(EditDateComponent); component = fixture.componentInstance; - const formControl = new FormControl(); - const formGroup = new FormGroup({}); - component.formControlName = "testControl"; - component.formControl = formControl; - formGroup.registerControl(component.formControlName, formControl); + setupEditComponent(component); fixture.detectChanges(); }); diff --git a/src/app/core/entity-components/entity-utils/dynamic-form-components/edit-entity-array/edit-entity-array.component.spec.ts b/src/app/core/entity-components/entity-utils/dynamic-form-components/edit-entity-array/edit-entity-array.component.spec.ts index ab761f23d4..b0756516fd 100644 --- a/src/app/core/entity-components/entity-utils/dynamic-form-components/edit-entity-array/edit-entity-array.component.spec.ts +++ b/src/app/core/entity-components/entity-utils/dynamic-form-components/edit-entity-array/edit-entity-array.component.spec.ts @@ -1,12 +1,12 @@ import { ComponentFixture, TestBed } from "@angular/core/testing"; import { EditEntityArrayComponent } from "./edit-entity-array.component"; -import { FormControl } from "@angular/forms"; import { EntityMapperService } from "../../../../entity/entity-mapper.service"; import { Child } from "../../../../../child-dev-project/children/model/child"; import { NoopAnimationsModule } from "@angular/platform-browser/animations"; import { EntityUtilsModule } from "../../entity-utils.module"; import { EntitySchemaService } from "../../../../entity/schema/entity-schema.service"; +import { setupEditComponent } from "../edit-component.spec"; describe("EditEntityArrayComponent", () => { let component: EditEntityArrayComponent; @@ -29,7 +29,7 @@ describe("EditEntityArrayComponent", () => { beforeEach(() => { fixture = TestBed.createComponent(EditEntityArrayComponent); component = fixture.componentInstance; - component.formControl = new FormControl(); + setupEditComponent(component); component.entityName = Child.ENTITY_TYPE; fixture.detectChanges(); }); diff --git a/src/app/core/entity-components/entity-utils/dynamic-form-components/edit-entity-array/edit-entity-array.component.ts b/src/app/core/entity-components/entity-utils/dynamic-form-components/edit-entity-array/edit-entity-array.component.ts index 67a081bcf8..1b1b9c2dcf 100644 --- a/src/app/core/entity-components/entity-utils/dynamic-form-components/edit-entity-array/edit-entity-array.component.ts +++ b/src/app/core/entity-components/entity-utils/dynamic-form-components/edit-entity-array/edit-entity-array.component.ts @@ -1,15 +1,12 @@ import { Component } from "@angular/core"; import { EditComponent, EditPropertyConfig } from "../edit-component"; -import { Entity } from "../../../../entity/model/entity"; @Component({ selector: "app-edit-entity-array", templateUrl: "./edit-entity-array.component.html", styleUrls: ["./edit-entity-array.component.scss"], }) -export class EditEntityArrayComponent extends EditComponent< - (string | Entity)[] -> { +export class EditEntityArrayComponent extends EditComponent { placeholder: string; entityName: string; onInitFromDynamicConfig(config: EditPropertyConfig) { diff --git a/src/app/core/entity-components/entity-utils/dynamic-form-components/edit-long-text/edit-long-text.component.spec.ts b/src/app/core/entity-components/entity-utils/dynamic-form-components/edit-long-text/edit-long-text.component.spec.ts index 3bd63009d7..d3bf505e84 100644 --- a/src/app/core/entity-components/entity-utils/dynamic-form-components/edit-long-text/edit-long-text.component.spec.ts +++ b/src/app/core/entity-components/entity-utils/dynamic-form-components/edit-long-text/edit-long-text.component.spec.ts @@ -2,9 +2,10 @@ import { ComponentFixture, TestBed } from "@angular/core/testing"; import { EditLongTextComponent } from "./edit-long-text.component"; import { NoopAnimationsModule } from "@angular/platform-browser/animations"; -import { FormControl, FormGroup, ReactiveFormsModule } from "@angular/forms"; +import { ReactiveFormsModule } from "@angular/forms"; import { MatFormFieldModule } from "@angular/material/form-field"; import { MatInputModule } from "@angular/material/input"; +import { setupEditComponent } from "../edit-component.spec"; describe("EditLongTextComponent", () => { let component: EditLongTextComponent; @@ -25,11 +26,7 @@ describe("EditLongTextComponent", () => { beforeEach(() => { fixture = TestBed.createComponent(EditLongTextComponent); component = fixture.componentInstance; - const formControl = new FormControl(); - const formGroup = new FormGroup({}); - component.formControlName = "testControl"; - component.formControl = formControl; - formGroup.registerControl(component.formControlName, formControl); + setupEditComponent(component); fixture.detectChanges(); }); diff --git a/src/app/core/entity-components/entity-utils/dynamic-form-components/edit-number/edit-number.component.spec.ts b/src/app/core/entity-components/entity-utils/dynamic-form-components/edit-number/edit-number.component.spec.ts index 010fe838a0..569b63251b 100644 --- a/src/app/core/entity-components/entity-utils/dynamic-form-components/edit-number/edit-number.component.spec.ts +++ b/src/app/core/entity-components/entity-utils/dynamic-form-components/edit-number/edit-number.component.spec.ts @@ -1,10 +1,11 @@ import { ComponentFixture, TestBed } from "@angular/core/testing"; import { EditNumberComponent } from "./edit-number.component"; -import { FormControl, FormGroup, ReactiveFormsModule } from "@angular/forms"; +import { FormGroup, ReactiveFormsModule } from "@angular/forms"; import { MatFormFieldModule } from "@angular/material/form-field"; import { MatInputModule } from "@angular/material/input"; import { NoopAnimationsModule } from "@angular/platform-browser/animations"; +import { setupEditComponent } from "../edit-component.spec"; describe("EditNumberComponent", () => { let component: EditNumberComponent; @@ -26,13 +27,7 @@ describe("EditNumberComponent", () => { beforeEach(() => { fixture = TestBed.createComponent(EditNumberComponent); component = fixture.componentInstance; - const formControl = new FormControl(); - formGroup = new FormGroup({ testProperty: formControl }); - component.onInitFromDynamicConfig({ - formControl: formControl, - propertySchema: {}, - formFieldConfig: { id: "testProperty" }, - }); + formGroup = setupEditComponent(component); fixture.detectChanges(); }); diff --git a/src/app/core/entity-components/entity-utils/dynamic-form-components/edit-percentage/edit-percentage.component.spec.ts b/src/app/core/entity-components/entity-utils/dynamic-form-components/edit-percentage/edit-percentage.component.spec.ts index 9980967174..587f6eaba5 100644 --- a/src/app/core/entity-components/entity-utils/dynamic-form-components/edit-percentage/edit-percentage.component.spec.ts +++ b/src/app/core/entity-components/entity-utils/dynamic-form-components/edit-percentage/edit-percentage.component.spec.ts @@ -10,11 +10,13 @@ import { } from "@angular/forms"; import { MatInputModule } from "@angular/material/input"; import { NoopAnimationsModule } from "@angular/platform-browser/animations"; +import { setupEditComponent } from "../edit-component.spec"; describe("EditPercentageComponent", () => { let component: EditPercentageComponent; let fixture: ComponentFixture; let formGroup: FormGroup; + const propertyName = "percentageProperty"; beforeEach(async () => { await TestBed.configureTestingModule({ @@ -31,13 +33,7 @@ describe("EditPercentageComponent", () => { beforeEach(() => { fixture = TestBed.createComponent(EditPercentageComponent); component = fixture.componentInstance; - const formControl = new FormControl(); - formGroup = new FormGroup({ testProperty: formControl }); - component.onInitFromDynamicConfig({ - formControl: formControl, - propertySchema: {}, - formFieldConfig: { id: "testProperty" }, - }); + formGroup = setupEditComponent(component, propertyName); fixture.detectChanges(); }); @@ -74,11 +70,11 @@ describe("EditPercentageComponent", () => { expect(formGroup.valid).toBeTrue(); const control = new FormControl(0, [Validators.required]); - formGroup.setControl("testProperty", control); + formGroup.setControl(propertyName, control); component.onInitFromDynamicConfig({ formControl: control, propertySchema: {}, - formFieldConfig: { id: "testProperty" }, + formFieldConfig: { id: propertyName }, }); component.formControl.setValue(null); diff --git a/src/app/core/entity-components/entity-utils/dynamic-form-components/edit-photo/edit-photo.component.spec.ts b/src/app/core/entity-components/entity-utils/dynamic-form-components/edit-photo/edit-photo.component.spec.ts index 30b5e1851f..d06bc8a274 100644 --- a/src/app/core/entity-components/entity-utils/dynamic-form-components/edit-photo/edit-photo.component.spec.ts +++ b/src/app/core/entity-components/entity-utils/dynamic-form-components/edit-photo/edit-photo.component.spec.ts @@ -2,8 +2,8 @@ import { ComponentFixture, TestBed } from "@angular/core/testing"; import { EditPhotoComponent } from "./edit-photo.component"; import { NoopAnimationsModule } from "@angular/platform-browser/animations"; -import { FormControl, FormGroup } from "@angular/forms"; import { SessionService } from "../../../../session/session-service/session.service"; +import { setupEditComponent } from "../edit-component.spec"; describe("EditPhotoComponent", () => { let component: EditPhotoComponent; @@ -22,11 +22,7 @@ describe("EditPhotoComponent", () => { beforeEach(() => { fixture = TestBed.createComponent(EditPhotoComponent); component = fixture.componentInstance; - const formControl = new FormControl(); - const formGroup = new FormGroup({}); - component.formControlName = "testControl"; - component.formControl = formControl; - formGroup.registerControl(component.formControlName, formControl); + setupEditComponent(component); fixture.detectChanges(); }); diff --git a/src/app/core/entity-components/entity-utils/dynamic-form-components/edit-single-entity/edit-single-entity.component.spec.ts b/src/app/core/entity-components/entity-utils/dynamic-form-components/edit-single-entity/edit-single-entity.component.spec.ts index 172ea9dcaa..db8cbd61d5 100644 --- a/src/app/core/entity-components/entity-utils/dynamic-form-components/edit-single-entity/edit-single-entity.component.spec.ts +++ b/src/app/core/entity-components/entity-utils/dynamic-form-components/edit-single-entity/edit-single-entity.component.spec.ts @@ -7,7 +7,7 @@ import { import { EditSingleEntityComponent } from "./edit-single-entity.component"; import { EntityMapperService } from "../../../../entity/entity-mapper.service"; -import { FormControl, Validators } from "@angular/forms"; +import { Validators } from "@angular/forms"; import { EntitySchemaService } from "../../../../entity/schema/entity-schema.service"; import { EntityFormService } from "../../../entity-form/entity-form.service"; import { NoopAnimationsModule } from "@angular/platform-browser/animations"; @@ -15,6 +15,7 @@ import { ChildSchoolRelation } from "../../../../../child-dev-project/children/m import { School } from "../../../../../child-dev-project/schools/model/school"; import { EntityUtilsModule } from "../../entity-utils.module"; import { Child } from "../../../../../child-dev-project/children/model/child"; +import { TypedFormControl } from "../edit-component"; describe("EditSingleEntityComponent", () => { let component: EditSingleEntityComponent; @@ -42,7 +43,7 @@ describe("EditSingleEntityComponent", () => { const entityFormService = TestBed.inject(EntityFormService); component.formControl = entityFormService .createFormGroup([{ id: "schoolId" }], new ChildSchoolRelation()) - .get("schoolId") as FormControl; + .get("schoolId") as TypedFormControl; component.formControlName = "schoolId"; fixture.detectChanges(); }); diff --git a/src/app/core/entity-components/entity-utils/dynamic-form-components/edit-single-entity/edit-single-entity.component.ts b/src/app/core/entity-components/entity-utils/dynamic-form-components/edit-single-entity/edit-single-entity.component.ts index 46e4b44141..8d6632b959 100644 --- a/src/app/core/entity-components/entity-utils/dynamic-form-components/edit-single-entity/edit-single-entity.component.ts +++ b/src/app/core/entity-components/entity-utils/dynamic-form-components/edit-single-entity/edit-single-entity.component.ts @@ -1,12 +1,11 @@ import { Component, ElementRef, ViewChild } from "@angular/core"; import { EditComponent, EditPropertyConfig } from "../edit-component"; -import { ENTITY_MAP } from "../../../entity-details/entity-details.component"; -import { EntityMapperService } from "../../../../entity/entity-mapper.service"; import { Entity } from "../../../../entity/model/entity"; import { Observable } from "rxjs"; import { filter, map } from "rxjs/operators"; import { UntilDestroy, untilDestroyed } from "@ngneat/until-destroy"; import { FormControl } from "@angular/forms"; +import { DynamicEntityService } from "../../../../entity/dynamic-entity.service"; @UntilDestroy() @Component({ @@ -24,7 +23,7 @@ export class EditSingleEntityComponent extends EditComponent { @ViewChild("inputElement") input: ElementRef; - constructor(private entityMapper: EntityMapperService) { + constructor(private dynamicEntityService: DynamicEntityService) { super(); this.filteredEntities = this.entityNameFormControl.valueChanges.pipe( untilDestroyed(this), @@ -49,12 +48,8 @@ export class EditSingleEntityComponent extends EditComponent { this.placeholder = $localize`:Placeholder for input to set an entity|context Select User:Select ${this.label}`; const entityType: string = config.formFieldConfig.additional || config.propertySchema.additional; - const entityConstructor = ENTITY_MAP.get(entityType); - if (!entityConstructor) { - throw new Error(`Entity-Type ${entityType} not in EntityMap`); - } - this.entities = await this.entityMapper - .loadType(entityConstructor) + this.entities = await this.dynamicEntityService + .loadType(entityType) .then((entities) => entities.sort((e1, e2) => e1.toString().localeCompare(e2.toString())) ); diff --git a/src/app/core/entity-components/entity-utils/dynamic-form-components/edit-text/edit-text.component.spec.ts b/src/app/core/entity-components/entity-utils/dynamic-form-components/edit-text/edit-text.component.spec.ts index 7e6c04e2b0..953688500a 100644 --- a/src/app/core/entity-components/entity-utils/dynamic-form-components/edit-text/edit-text.component.spec.ts +++ b/src/app/core/entity-components/entity-utils/dynamic-form-components/edit-text/edit-text.component.spec.ts @@ -1,10 +1,11 @@ import { ComponentFixture, TestBed } from "@angular/core/testing"; import { EditTextComponent } from "./edit-text.component"; -import { FormControl, FormGroup, ReactiveFormsModule } from "@angular/forms"; +import { ReactiveFormsModule } from "@angular/forms"; import { NoopAnimationsModule } from "@angular/platform-browser/animations"; import { MatFormFieldModule } from "@angular/material/form-field"; import { MatInputModule } from "@angular/material/input"; +import { setupEditComponent } from "../edit-component.spec"; describe("EditTextComponent", () => { let component: EditTextComponent; @@ -25,11 +26,7 @@ describe("EditTextComponent", () => { beforeEach(() => { fixture = TestBed.createComponent(EditTextComponent); component = fixture.componentInstance; - const formControl = new FormControl(); - const formGroup = new FormGroup({}); - component.formControlName = "testControl"; - component.formControl = formControl; - formGroup.registerControl(component.formControlName, formControl); + setupEditComponent(component); fixture.detectChanges(); }); diff --git a/src/app/core/entity-components/entity-utils/entity-select/entity-select.component.html b/src/app/core/entity-components/entity-utils/entity-select/entity-select.component.html index 894ee4994d..0db4135273 100644 --- a/src/app/core/entity-components/entity-utils/entity-select/entity-select.component.html +++ b/src/app/core/entity-components/entity-utils/entity-select/entity-select.component.html @@ -17,7 +17,7 @@ diff --git a/src/app/core/entity-components/entity-utils/entity-select/entity-select.component.spec.ts b/src/app/core/entity-components/entity-utils/entity-select/entity-select.component.spec.ts index e0f16f6d15..d7ead594fe 100644 --- a/src/app/core/entity-components/entity-utils/entity-select/entity-select.component.spec.ts +++ b/src/app/core/entity-components/entity-utils/entity-select/entity-select.component.spec.ts @@ -17,6 +17,8 @@ import { User } from "../../../user/user"; import { Child } from "../../../../child-dev-project/children/model/child"; import { FlexLayoutModule } from "@angular/flex-layout"; import { Subscription } from "rxjs"; +import { EntitySchemaService } from "../../../entity/schema/entity-schema.service"; +import { DynamicEntityService } from "../../../entity/dynamic-entity.service"; describe("EntitySelectComponent", () => { let component: EntitySelectComponent; @@ -38,6 +40,8 @@ describe("EntitySelectComponent", () => { provide: EntityMapperService, useValue: mockEntityMapper(testUsers.concat(otherEntities)), }, + EntitySchemaService, + DynamicEntityService, ], imports: [ MatAutocompleteModule, @@ -86,47 +90,44 @@ describe("EntitySelectComponent", () => { fixture.detectChanges(); }); - it("contains the initial selection when passed as entity-arguments", fakeAsync(() => { + it("contains the initial selection as entities", fakeAsync(() => { component.entityType = User.ENTITY_TYPE; - fixture.detectChanges(); - tick(); - component.selectionInputType = "entity"; - const expectation = testUsers.slice(2, 3); - component.selection = expectation; - expect(component.selection_).toEqual(expectation); - })); + const expectation = testUsers.slice(2, 3).map((user) => user.getId()); - it("contains the initial selection when passed as id-arguments", fakeAsync(() => { - component.entityType = User.ENTITY_TYPE; - component.selectionInputType = "id"; - const expectation = testUsers.slice(2, 3).map((child) => child.getId()); component.selection = expectation; fixture.detectChanges(); tick(); - expect(component.selection_.every((s) => typeof s === "object")).toBeTrue(); - expect(component.selection_.map((s) => s.getId())).toEqual(expectation); + + component.selectedEntities.forEach((s) => expect(s).toBeInstanceOf(User)); + expect(component.selectedEntities.map((s) => s.getId())).toEqual( + expectation + ); })); it("emits whenever a new entity is selected", fakeAsync(() => { spyOn(component.selectionChange, "emit"); component.entityType = User.ENTITY_TYPE; tick(); - component.selectionInputType = "entity"; + component.selectEntity(testUsers[0]); - expect(component.selectionChange.emit).toHaveBeenCalledWith([testUsers[0]]); + expect(component.selectionChange.emit).toHaveBeenCalledWith([ + testUsers[0].getId(), + ]); + component.selectEntity(testUsers[1]); expect(component.selectionChange.emit).toHaveBeenCalledWith([ - testUsers[0], - testUsers[1], + testUsers[0].getId(), + testUsers[1].getId(), ]); tick(); })); it("emits whenever a selected entity is removed", () => { spyOn(component.selectionChange, "emit"); - component.selection_ = [...testUsers]; - component.selectionInputType = "id"; + component.selectedEntities = [...testUsers]; + component.unselectEntity(testUsers[0]); + const remainingChildren = testUsers .filter((c) => c.getId() !== testUsers[0].getId()) .map((c) => c.getId()); @@ -138,13 +139,13 @@ describe("EntitySelectComponent", () => { it("adds a new entity if it matches a known entity", () => { component.allEntities = testUsers; component.select({ input: null, value: testUsers[0]["name"] }); - expect(component.selection_).toEqual([testUsers[0]]); + expect(component.selectedEntities).toEqual([testUsers[0]]); }); it("does not add anything if a new entity doesn't match", () => { component.allEntities = testUsers; component.select({ input: null, value: "ZZ" }); - expect(component.selection_).toEqual([]); + expect(component.selectedEntities).toEqual([]); }); it("autocompletes with the default accessor", (done) => { diff --git a/src/app/core/entity-components/entity-utils/entity-select/entity-select.component.ts b/src/app/core/entity-components/entity-utils/entity-select/entity-select.component.ts index e600a8817f..22b2ea636b 100644 --- a/src/app/core/entity-components/entity-utils/entity-select/entity-select.component.ts +++ b/src/app/core/entity-components/entity-utils/entity-select/entity-select.component.ts @@ -9,15 +9,14 @@ import { ViewChild, } from "@angular/core"; import { COMMA, ENTER } from "@angular/cdk/keycodes"; -import { Entity, EntityConstructor } from "../../../entity/model/entity"; -import { EntityMapperService } from "../../../entity/entity-mapper.service"; +import { Entity } from "../../../entity/model/entity"; import { BehaviorSubject, Observable } from "rxjs"; import { FormControl } from "@angular/forms"; import { filter, map } from "rxjs/operators"; import { MatChipInputEvent } from "@angular/material/chips"; import { MatAutocompleteTrigger } from "@angular/material/autocomplete"; -import { ENTITY_MAP } from "../../entity-details/entity-details.component"; import { UntilDestroy, untilDestroyed } from "@ngneat/until-destroy"; +import { DynamicEntityService } from "../../../entity/dynamic-entity.service"; @Component({ selector: "app-entity-select", @@ -31,18 +30,13 @@ export class EntitySelectComponent implements OnChanges { /** * The entity-type (e.g. 'Child', 'School', e.t.c.) to set. - * The entity-type has to be inside {@link ENTITY_MAP} * @param type The ENTITY_TYPE of a Entity. This affects the entities which will be loaded and the component * that displays the entities. * @throws Error when `type` is not in the entity-map */ @Input() set entityType(type: string) { - const entityConstructor = ENTITY_MAP.get(type) as EntityConstructor; - if (!entityConstructor) { - throw new Error(`Entity-Type ${type} not in EntityMap`); - } this.loading.next(true); - this.entityMapperService.loadType(entityConstructor).then((entities) => { + this.dynamicEntityService.loadType(type).then((entities) => { this.allEntities = entities; this.loading.next(false); this.formControl.setValue(null); @@ -51,45 +45,33 @@ export class EntitySelectComponent implements OnChanges { /** * The (initial) selection. Can be used in combination with {@link selectionChange} - * to enable two-way binding to either an array of entities or an array of strings - * corresponding to the id's of the entities. - * The type (id's or entities) will be determined by the setting of the - * {@link selectionInputType} + * to enable two-way binding to an array of strings corresponding to the id's of the entities. * @param sel The initial selection */ - @Input() set selection(sel: (string | E)[]) { + @Input() set selection(sel: string[]) { if (!Array.isArray(sel)) { - this.selection_ = []; + this.selectedEntities = []; return; } - if (this.selectionInputType === "id") { - this.loading - .pipe( - untilDestroyed(this), - filter((isLoading) => !isLoading) - ) - .subscribe((_) => { - this.selection_ = this.allEntities.filter((e) => - sel.find((s) => s === e.getId()) - ); - }); - } else { - this.selection_ = sel as E[]; - } + this.loading + .pipe( + untilDestroyed(this), + filter((isLoading) => !isLoading) + ) + .subscribe((_) => { + this.selectedEntities = this.allEntities.filter((e) => + sel.find((s) => s === e.getId()) + ); + }); } /** Underlying data-array */ - selection_: E[] = []; - /** - * The type to publish and receive; either string-id's or entities - * Defaults to string-id's - */ - @Input() selectionInputType: "id" | "entity" = "id"; + selectedEntities: E[] = []; /** * called whenever the selection changes. * This happens when a new entity is being added or an existing * one is removed */ - @Output() selectionChange = new EventEmitter<(string | E)[]>(); + @Output() selectionChange = new EventEmitter(); /** * The label is what is seen above the list. For example when used * in the note-details-view, this is "Children" @@ -149,7 +131,7 @@ export class EntitySelectComponent implements OnChanges { @ViewChild("inputField") inputField: ElementRef; @ViewChild(MatAutocompleteTrigger) autocomplete: MatAutocompleteTrigger; - constructor(private entityMapperService: EntityMapperService) { + constructor(private dynamicEntityService: DynamicEntityService) { this.filteredEntities = this.formControl.valueChanges.pipe( untilDestroyed(this), filter((value) => value === null || typeof value === "string"), // sometimes produces entities @@ -184,7 +166,7 @@ export class EntitySelectComponent implements OnChanges { * @param entity the entity to select */ selectEntity(entity: E) { - this.selection_.push(entity); + this.selectedEntities.push(entity); this.emitChange(); this.inputField.nativeElement.value = ""; this.formControl.setValue(null); @@ -236,11 +218,11 @@ export class EntitySelectComponent implements OnChanges { * @param entity The entity to remove */ unselectEntity(entity: E) { - const index = this.selection_.findIndex( + const index = this.selectedEntities.findIndex( (e) => e.getId() === entity.getId() ); if (index !== -1) { - this.selection_.splice(index, 1); + this.selectedEntities.splice(index, 1); this.emitChange(); // Update the form control to re-run the filter function this.formControl.updateValueAndValidity(); @@ -248,14 +230,10 @@ export class EntitySelectComponent implements OnChanges { } private emitChange() { - if (this.selectionInputType === "id") { - this.selectionChange.emit(this.selection_.map((e) => e.getId())); - } else { - this.selectionChange.emit(this.selection_); - } + this.selectionChange.emit(this.selectedEntities.map((e) => e.getId())); } private isSelected(entity: E): boolean { - return this.selection_.some((e) => e.getId() === entity.getId()); + return this.selectedEntities.some((e) => e.getId() === entity.getId()); } } diff --git a/src/app/core/entity-components/entity-utils/view-components/display-entity-array/display-entity-array.component.spec.ts b/src/app/core/entity-components/entity-utils/view-components/display-entity-array/display-entity-array.component.spec.ts index db9b184dec..677de7870e 100644 --- a/src/app/core/entity-components/entity-utils/view-components/display-entity-array/display-entity-array.component.spec.ts +++ b/src/app/core/entity-components/entity-utils/view-components/display-entity-array/display-entity-array.component.spec.ts @@ -4,6 +4,8 @@ import { DisplayEntityArrayComponent } from "./display-entity-array.component"; import { EntityMapperService } from "../../../../entity/entity-mapper.service"; import { Child } from "../../../../../child-dev-project/children/model/child"; import { Note } from "../../../../../child-dev-project/notes/model/note"; +import { EntitySchemaService } from "../../../../entity/schema/entity-schema.service"; +import { DynamicEntityService } from "../../../../entity/dynamic-entity.service"; describe("DisplayEntityArrayComponent", () => { let component: DisplayEntityArrayComponent; @@ -15,7 +17,11 @@ describe("DisplayEntityArrayComponent", () => { mockEntityMapper.load.and.resolveTo(new Child()); await TestBed.configureTestingModule({ declarations: [DisplayEntityArrayComponent], - providers: [{ provide: EntityMapperService, useValue: mockEntityMapper }], + providers: [ + { provide: EntityMapperService, useValue: mockEntityMapper }, + EntitySchemaService, + DynamicEntityService, + ], }).compileComponents(); }); diff --git a/src/app/core/entity-components/entity-utils/view-components/display-entity-array/display-entity-array.component.ts b/src/app/core/entity-components/entity-utils/view-components/display-entity-array/display-entity-array.component.ts index 706fd772e7..e066cd99b3 100644 --- a/src/app/core/entity-components/entity-utils/view-components/display-entity-array/display-entity-array.component.ts +++ b/src/app/core/entity-components/entity-utils/view-components/display-entity-array/display-entity-array.component.ts @@ -3,7 +3,7 @@ import { Entity } from "../../../../entity/model/entity"; import { EntityMapperService } from "../../../../entity/entity-mapper.service"; import { ViewComponent } from "../view-component"; import { ViewPropertyConfig } from "../../../entity-list/EntityListConfig"; -import { ENTITY_MAP } from "../../../entity-details/entity-details.component"; +import { DynamicEntityService } from "../../../../entity/dynamic-entity.service"; @Component({ selector: "app-display-entity-array", @@ -13,7 +13,10 @@ import { ENTITY_MAP } from "../../../entity-details/entity-details.component"; export class DisplayEntityArrayComponent extends ViewComponent { readonly aggregationThreshold = 5; entities: Entity[]; - constructor(private entityMapper: EntityMapperService) { + constructor( + private entityMapper: EntityMapperService, + private dynamicEntityService: DynamicEntityService + ) { super(); } @@ -22,10 +25,9 @@ export class DisplayEntityArrayComponent extends ViewComponent { const entityIds: string[] = this.entity[this.property] || []; if (entityIds.length < this.aggregationThreshold) { const entityType = this.entity.getSchema().get(this.property).additional; - const entityConstructor = ENTITY_MAP.get(entityType); - if (!entityConstructor) { - throw new Error(`Could not find type ${entityType} in ENTITY_MAP`); - } + const entityConstructor = this.dynamicEntityService.getEntityConstructor( + entityType + ); const entityPromises = entityIds.map((entityId) => this.entityMapper.load(entityConstructor, entityId) ); diff --git a/src/app/core/entity-components/entity-utils/view-components/display-entity/display-entity.component.spec.ts b/src/app/core/entity-components/entity-utils/view-components/display-entity/display-entity.component.spec.ts index 70434fcbbc..5497a56c05 100644 --- a/src/app/core/entity-components/entity-utils/view-components/display-entity/display-entity.component.spec.ts +++ b/src/app/core/entity-components/entity-utils/view-components/display-entity/display-entity.component.spec.ts @@ -10,6 +10,8 @@ import { EntityMapperService } from "../../../../entity/entity-mapper.service"; import { Child } from "../../../../../child-dev-project/children/model/child"; import { ChildSchoolRelation } from "../../../../../child-dev-project/children/model/childSchoolRelation"; import { School } from "../../../../../child-dev-project/schools/model/school"; +import { EntitySchemaService } from "../../../../entity/schema/entity-schema.service"; +import { DynamicEntityService } from "../../../../entity/dynamic-entity.service"; describe("DisplayEntityComponent", () => { let component: DisplayEntityComponent; @@ -21,7 +23,11 @@ describe("DisplayEntityComponent", () => { mockEntityMapper.load.and.resolveTo(new Child()); await TestBed.configureTestingModule({ declarations: [DisplayEntityComponent], - providers: [{ provide: EntityMapperService, useValue: mockEntityMapper }], + providers: [ + { provide: EntityMapperService, useValue: mockEntityMapper }, + EntitySchemaService, + DynamicEntityService, + ], }).compileComponents(); }); diff --git a/src/app/core/entity-components/entity-utils/view-components/display-entity/display-entity.component.ts b/src/app/core/entity-components/entity-utils/view-components/display-entity/display-entity.component.ts index b57e7bbe74..890aa324bc 100644 --- a/src/app/core/entity-components/entity-utils/view-components/display-entity/display-entity.component.ts +++ b/src/app/core/entity-components/entity-utils/view-components/display-entity/display-entity.component.ts @@ -1,9 +1,8 @@ import { Component, Input, OnInit } from "@angular/core"; import { Entity } from "../../../../entity/model/entity"; import { ViewPropertyConfig } from "../../../entity-list/EntityListConfig"; -import { EntityMapperService } from "../../../../entity/entity-mapper.service"; -import { ENTITY_MAP } from "../../../entity-details/entity-details.component"; import { ViewComponent } from "../view-component"; +import { DynamicEntityService } from "../../../../entity/dynamic-entity.service"; @Component({ selector: "app-display-entity", @@ -14,7 +13,7 @@ export class DisplayEntityComponent extends ViewComponent implements OnInit { @Input() entityToDisplay: Entity; @Input() linkDisabled = false; entityBlockComponent: string; - constructor(private entityMapper: EntityMapperService) { + constructor(private dynamicEntityService: DynamicEntityService) { super(); } @@ -31,12 +30,8 @@ export class DisplayEntityComponent extends ViewComponent implements OnInit { if (this.entity[this.property]) { const type = config.config || this.entity.getSchema().get(this.property).additional; - const entityConstructor = ENTITY_MAP.get(type); - if (!entityConstructor) { - throw new Error(`Could not find type ${type} in ENTITY_MAP`); - } - this.entityToDisplay = await this.entityMapper - .load(entityConstructor, this.entity[this.property]) + this.entityToDisplay = await this.dynamicEntityService + .loadEntity(type, this.entity[this.property]) .catch(() => undefined); this.ngOnInit(); } diff --git a/src/app/core/entity/database-entity.decorator.ts b/src/app/core/entity/database-entity.decorator.ts index 526e131491..249c97d1a0 100644 --- a/src/app/core/entity/database-entity.decorator.ts +++ b/src/app/core/entity/database-entity.decorator.ts @@ -5,8 +5,11 @@ * * @param entityType The string key for this Entity Type, used as id prefix. */ +import { DynamicEntityService } from "./dynamic-entity.service"; + export function DatabaseEntity(entityType: string) { return (constructor) => { + DynamicEntityService.registerNewEntity(entityType, constructor); constructor.ENTITY_TYPE = entityType; // append parent schema definitions diff --git a/src/app/core/entity/dynamic-entity.service.spec.ts b/src/app/core/entity/dynamic-entity.service.spec.ts new file mode 100644 index 0000000000..7f02ae7a66 --- /dev/null +++ b/src/app/core/entity/dynamic-entity.service.spec.ts @@ -0,0 +1,158 @@ +import { EntityMapperService } from "./entity-mapper.service"; +import { TestBed } from "@angular/core/testing"; +import { EntitySchemaService } from "./schema/entity-schema.service"; +import { DynamicEntityService } from "./dynamic-entity.service"; +import { DatabaseEntity } from "./database-entity.decorator"; +import { Entity, EntityConstructor } from "./model/entity"; +import { DatabaseField } from "./database-field.decorator"; +import { + mockEntityMapper, + MockEntityMapperService, +} from "./mock-entity-mapper-service"; + +describe("DynamicEntityService", () => { + let service: DynamicEntityService; + let mockedEntityMapper: MockEntityMapperService; + + beforeEach(() => { + mockedEntityMapper = mockEntityMapper(); + TestBed.configureTestingModule({ + providers: [ + { provide: EntityMapperService, useValue: mockedEntityMapper }, + EntitySchemaService, + ], + }); + service = TestBed.inject(DynamicEntityService); + }); + + it("should be created", () => { + expect(service).toBeTruthy(); + }); + + it("contains the new entity-definition when registered", () => { + const mockEntityName = "Mock"; + DynamicEntityService.registerNewEntity( + mockEntityName, + EntityWithoutDecorator + ); + expect(service.isRegisteredEntity(mockEntityName)).toBeTrue(); + }); + + it("contains entity-definitions when they have the 'DatabaseEntity' decorator", () => { + expect(service.isRegisteredEntity(ENTITY_WITH_DECORATOR_TYPE)).toBeTrue(); + }); + + it("throws when trying to define a duplicate entity", () => { + const registerNewEntity = () => { + DynamicEntityService.registerNewEntity( + ENTITY_WITH_DECORATOR_TYPE, + EntityWithDecorator + ); + }; + expect(registerNewEntity).toThrowError(); + }); + + it("throws when trying to set a constructor that is not an entity-constructor", () => { + const registerNonEntity = () => { + DynamicEntityService.registerNewEntity( + NON_ENTITY_CLASS_TYPE, + NonEntityClass as EntityConstructor + ); + }; + expect(registerNonEntity).toThrowError(); + }); + + it("returns the entity-constructor of a defined entity", () => { + const ctor = service.getEntityConstructor(ENTITY_WITH_DECORATOR_TYPE); + expect(ctor).toEqual(EntityWithDecorator); + }); + + it("throws when trying to get an entity-constructor that doesn't exist", () => { + expect(() => service.getEntityConstructor("IDoNotExist")).toThrowError(); + }); + + it("Instantiates an entity", () => { + const instance = service.instantiateEntity(ENTITY_WITH_PARAMETERS_TYPE); + expect(instance.getType()).toEqual(ENTITY_WITH_PARAMETERS_TYPE); + expect(instance).toBeInstanceOf(Entity); + expect(instance).toBeInstanceOf(EntityWithParameters); + }); + + it("Instantiates an entity with given id", () => { + const idName = "id"; + const instance = service.instantiateEntity( + ENTITY_WITH_PARAMETERS_TYPE, + idName + ); + expect(instance.getId()).toEqual(idName); + expect(instance.getType()).toEqual(ENTITY_WITH_PARAMETERS_TYPE); + }); + + it("instantiates an entity with given initial raw parameters", () => { + const idName = "id"; + const params = { + x: "Hello, World!", + y: 42, + }; + const instance = service.instantiateEntity( + ENTITY_WITH_PARAMETERS_TYPE, + idName, + params + ); + expect(instance.x).toEqual(params.x); + expect(instance.y).toEqual(params.y); + }); + + it("returns true when any entity is registered from a set of given types", () => { + expect( + service.hasAnyRegisteredEntity(ENTITY_WITH_DECORATOR_TYPE, "IDoNotExist") + ).toBeTrue(); + }); + + it("returns false when no entity is registered from a set of given types", () => { + expect( + service.hasAnyRegisteredEntity("IDoNotExist", "MeNeither") + ).toBeFalse(); + }); + + it("loads an entity by it's type", () => { + const entityId = "id"; + const mockEntity = new EntityWithParameters(entityId); + mockEntity.x = "Hello, World!"; + mockEntity.y = 42; + mockedEntityMapper.add(mockEntity); + const entity = service.loadEntity(ENTITY_WITH_PARAMETERS_TYPE, entityId); + return expectAsync(entity).toBeResolvedTo(mockEntity); + }); + + it("loads an array of entities by their type", () => { + const mockEntities = ["id1", "id2"].map( + (id) => new EntityWithParameters(id) + ); + mockedEntityMapper.addAll(mockEntities); + const entities = service.loadType(ENTITY_WITH_PARAMETERS_TYPE); + return expectAsync(entities).toBeResolvedTo(mockEntities); + }); +}); + +const ENTITY_WITH_DECORATOR_TYPE = "EntityWithDecorator"; + +@DatabaseEntity(ENTITY_WITH_DECORATOR_TYPE) +class EntityWithDecorator extends Entity {} + +const ENTITY_WITH_PARAMETERS_TYPE = "EntityWithParameters"; + +@DatabaseEntity(ENTITY_WITH_PARAMETERS_TYPE) +class EntityWithParameters extends Entity { + @DatabaseField() + x?: string; + + @DatabaseField() + y?: number; +} + +class EntityWithoutDecorator extends Entity {} + +const NON_ENTITY_CLASS_TYPE = "NonEntityClass"; + +class NonEntityClass {} diff --git a/src/app/core/entity/dynamic-entity.service.ts b/src/app/core/entity/dynamic-entity.service.ts new file mode 100644 index 0000000000..decd24abf8 --- /dev/null +++ b/src/app/core/entity/dynamic-entity.service.ts @@ -0,0 +1,128 @@ +import { Entity, EntityConstructor } from "./model/entity"; +import { Injectable } from "@angular/core"; +import { EntityMapperService } from "./entity-mapper.service"; +import { EntitySchemaService } from "./schema/entity-schema.service"; + +/** + * A service that can be used to get the entity-constructors (see {@link EntityConstructor}) + * from their string-types. + * This also contains utility methods that deal with creating, loading, + * checking on the existence and instantiating entities based on the string-types + */ +@Injectable({ + providedIn: "root", +}) +export class DynamicEntityService { + private static ENTITY_MAP = new Map>(); + + /** + * Registers a new entity so that it can be used using this service. + * This method should generally never be used other than from the + * {@link DatabaseEntity}-Decorator + * + * @param type The entity-type as string + * @param constructor The constructor of the entity + */ + static registerNewEntity( + type: string, + constructor: EntityConstructor + ) { + if (!(new constructor() instanceof Entity)) { + throw Error( + `Tried to register an entity-type that is not a subclass of Entity\n` + + `type: ${type}; constructor: ${constructor}` + ); + } + if (this.ENTITY_MAP.has(type)) { + throw Error( + `Duplicate entity definition: ${type} is already registered with constructor ${this.ENTITY_MAP.get( + type + )}` + ); + } + this.ENTITY_MAP.set(type, constructor); + } + + constructor( + private entityMapper: EntityMapperService, + private entitySchemaService: EntitySchemaService + ) {} + + /** + * returns the entity-constructor for a given string-name (i.e. the type) + * of the entity. If the name is not registered, this method throws an error + * @param entityType The type to get the entity from + */ + getEntityConstructor( + entityType: string + ): EntityConstructor { + const ctor = DynamicEntityService.ENTITY_MAP.get(entityType); + if (!ctor) { + throw new Error(`Entity-type ${entityType} does not exist!`); + } + return ctor as EntityConstructor; + } + + /** + * Utility method to instantiate an entity using initial, raw parameters as they + * would appear in the database + * @param entityType The type to instantiate an entity by + * @param id The id that the entity should have + * @param initialParameters The initial parameters as they would appear in the database + */ + instantiateEntity( + entityType: string, + id?: string, + initialParameters?: object + ): E { + const ctor = this.getEntityConstructor(entityType); + const entity = id ? new ctor(id) : new ctor(); + if (initialParameters) { + this.entitySchemaService.loadDataIntoEntity(entity, initialParameters); + } + return entity; + } + + /** + * returns {@code true}, when the entity is registered and could thus be + * instantiated. Use this method when you don't want to use the throwing + * {@link getEntityConstructor} or similar throwing methods + * @param entityType The type to look up + */ + isRegisteredEntity(entityType: string): boolean { + return DynamicEntityService.ENTITY_MAP.has(entityType); + } + + /** + * returns {@code true}, when any of the given entities are registered + * and could be instantiated + * @param entityTypes The types of entities to look up + */ + hasAnyRegisteredEntity(...entityTypes: string[]): boolean { + return entityTypes.some((type) => this.isRegisteredEntity(type)); + } + + /** + * Load an entity dynamically using the name instead of the constructor + * like one would in the {@link EntityMapperService} + * @param entityType The type of the entity to load + * @param entityId The id of the entity to load + */ + async loadEntity( + entityType: string, + entityId: string + ): Promise { + const ctor = this.getEntityConstructor(entityType); + return this.entityMapper.load(ctor, entityId); + } + + /** + * Load all entities of a certain type using the name of the entity-type + * instead of the constructor + * @param entityType The entity-type to load + */ + async loadType(entityType: string): Promise { + const ctor = this.getEntityConstructor(entityType); + return this.entityMapper.loadType(ctor); + } +} diff --git a/src/app/core/entity/entity-config.service.spec.ts b/src/app/core/entity/entity-config.service.spec.ts index a2012c1e1e..4845baf9e8 100644 --- a/src/app/core/entity/entity-config.service.spec.ts +++ b/src/app/core/entity/entity-config.service.spec.ts @@ -5,21 +5,63 @@ import { DatabaseEntity } from "./database-entity.decorator"; import { DatabaseField } from "./database-field.decorator"; import { Entity } from "./model/entity"; import { ConfigService } from "../config/config.service"; +import { DynamicEntityService } from "./dynamic-entity.service"; +import { LoggingService } from "../logging/logging.service"; +import { EntitySchemaService } from "./schema/entity-schema.service"; +import { EntityMapperService } from "./entity-mapper.service"; +import { mockEntityMapper } from "./mock-entity-mapper-service"; + +declare global { + namespace jasmine { + interface Matchers { + toContainKey(key: any); + } + } +} describe("EntityConfigService", () => { let service: EntityConfigService; - const mockConfigService: jasmine.SpyObj = jasmine.createSpyObj( - "mockConfigService", - ["getConfig"] - ); + let mockConfigService: jasmine.SpyObj; + let mockLogger: jasmine.SpyObj; const testConfig: EntityConfig = { attributes: [{ name: "testAttribute", schema: { dataType: "string" } }], }; + beforeAll(() => { + jasmine.addMatchers({ + toContainKey: () => { + return { + compare: (actual: Map, expected: T) => { + if (actual.has(expected)) { + return { + pass: true, + }; + } else { + return { + pass: false, + message: `Expected Map ${[...actual].join( + "," + )} to contain key '${expected}'`, + }; + } + }, + }; + }, + }); + }); + beforeEach(() => { + mockConfigService = jasmine.createSpyObj(["getConfig", "getAllConfigs"]); + mockLogger = jasmine.createSpyObj(["error"]); mockConfigService.getConfig.and.returnValue(testConfig); TestBed.configureTestingModule({ - providers: [{ provide: ConfigService, useValue: mockConfigService }], + providers: [ + { provide: ConfigService, useValue: mockConfigService }, + { provide: LoggingService, useValue: mockLogger }, + { provide: EntityMapperService, useValue: mockEntityMapper() }, + DynamicEntityService, + EntitySchemaService, + ], }); service = TestBed.inject(EntityConfigService); }); @@ -31,8 +73,8 @@ describe("EntityConfigService", () => { it("should add attributes to a entity class schema", () => { expect(Test.schema.has("name")).toBeTrue(); service.addConfigAttributes(Test); - expect(Test.schema.has("testAttribute")).toBeTrue(); - expect(Test.schema.has("name")).toBeTrue(); + expect(Test.schema).toContainKey("testAttribute"); + expect(Test.schema).toContainKey("name"); }); it("should assign the correct schema", () => { @@ -47,9 +89,58 @@ describe("EntityConfigService", () => { expect(mockConfigService.getConfig).toHaveBeenCalledWith("entity:Test"); expect(result).toBe(config); }); + + it("warns when trying to setting the entities up from config and they are not registered", () => { + const configWithInvalidEntities: (EntityConfig & { _id: string })[] = [ + { + _id: "entity:IDoNotExist", + permissions: {}, + }, + ]; + mockConfigService.getAllConfigs.and.returnValue(configWithInvalidEntities); + service.setupEntitiesFromConfig(); + expect(mockLogger.error).toHaveBeenCalled(); + }); + + it("appends custom definitions for each entity from the config", () => { + const ATTRIBUTE_1_NAME = "test1Attribute"; + const ATTRIBUTE_2_NAME = "test2Attribute"; + const mockEntityConfigs: (EntityConfig & { _id: string })[] = [ + { + _id: "entity:Test", + attributes: [ + { + name: ATTRIBUTE_1_NAME, + schema: { + dataType: "string", + }, + }, + ], + }, + { + _id: "entity:Test2", + attributes: [ + { + name: ATTRIBUTE_2_NAME, + schema: { + dataType: "number", + }, + }, + ], + }, + ]; + mockConfigService.getAllConfigs.and.returnValue(mockEntityConfigs); + service.setupEntitiesFromConfig(); + expect(mockLogger.error).not.toHaveBeenCalled(); + expect(Test.schema).toContainKey(ATTRIBUTE_1_NAME); + expect(Test2.schema).toContainKey(ATTRIBUTE_2_NAME); + }); }); @DatabaseEntity("Test") class Test extends Entity { @DatabaseField() name: string; } + +@DatabaseEntity("Test2") +class Test2 extends Entity {} diff --git a/src/app/core/entity/entity-config.service.ts b/src/app/core/entity/entity-config.service.ts index 64987d8f62..5bbbdc1197 100644 --- a/src/app/core/entity/entity-config.service.ts +++ b/src/app/core/entity/entity-config.service.ts @@ -1,20 +1,44 @@ import { Injectable } from "@angular/core"; -import { Entity, EntityConstructor } from "./model/entity"; +import { + Entity, + ENTITY_CONFIG_PREFIX, + EntityConstructor, +} from "./model/entity"; import { ConfigService } from "../config/config.service"; import { EntitySchemaField } from "./schema/entity-schema-field"; import { addPropertySchema } from "./database-field.decorator"; import { OperationType } from "../permissions/entity-permissions.service"; +import { DynamicEntityService } from "./dynamic-entity.service"; +import { LoggingService } from "../logging/logging.service"; +/** + * A service that allows to work with configuration-objects + * related to entities such as assigning dynamic attributes + * and their schemas to existing entities + */ @Injectable({ providedIn: "root", }) export class EntityConfigService { static readonly PREFIX_ENTITY_CONFIG = "entity:"; - constructor(private configService: ConfigService) {} + constructor( + private configService: ConfigService, + private dynamicEntityService: DynamicEntityService, + private loggingService: LoggingService + ) {} - public addConfigAttributes(entityType: typeof Entity) { - const entityConfig = this.getEntityConfig(entityType); + /** + * Appends the given (dynamic) attributes to the schema of the provided Entity. + * If no arguments are provided, they will be loaded from the config + * @param entityType The type to add the attributes to + * @param configAttributes The attributes to add + */ + public addConfigAttributes( + entityType: EntityConstructor, + configAttributes?: EntityConfig + ) { + const entityConfig = configAttributes || this.getEntityConfig(entityType); if (entityConfig?.attributes) { entityConfig.attributes.forEach((attribute) => addPropertySchema( @@ -26,14 +50,66 @@ export class EntityConfigService { } } - public getEntityConfig(entityType: EntityConstructor): EntityConfig { + /** + * Returns the `EntityConfig` from the config service that contains additional + * fields for a certain entity + * @param entityType The type to get the config for + */ + public getEntityConfig(entityType: EntityConstructor): EntityConfig { const configName = EntityConfigService.PREFIX_ENTITY_CONFIG + entityType.ENTITY_TYPE; return this.configService.getConfig(configName); } + + /** + * Assigns additional schema-fields to all entities that are + * defined inside the config. Entities that are not registered + * using the {@link DatabaseEntity}-Decorator won't work and will + * trigger an error message + */ + setupEntitiesFromConfig() { + const entitiesNotFound: string[] = []; + for (const config of this.configService.getAllConfigs< + EntityConfig & { _id: string } + >(ENTITY_CONFIG_PREFIX)) { + const id = config._id.substring(ENTITY_CONFIG_PREFIX.length); + try { + const ctor = this.dynamicEntityService.getEntityConstructor(id); + this.addConfigAttributes(ctor, config); + } catch (err) { + entitiesNotFound.push(id); + } + } + if (entitiesNotFound.length > 0) { + this.loggingService.error( + `The following entities were defined in the config but are not registered properly: ${entitiesNotFound.join( + ", " + )}.\n` + + `Make sure they exist as a class and are properly registered (see the how-to guides for more info on this topic)` + ); + } + } } +/** + * Dynamic configuration for a entity. + * This allows to change entity metadata based on the configuration. + */ export interface EntityConfig { permissions?: { [key in OperationType]?: string[] }; - attributes?: { name: string; schema: EntitySchemaField }[]; + + /** + * A list of attributes that will be dynamically added/overwritten to the entity. + */ + attributes?: { + /** + * The name of the attribute (class variable) to be added/overwritten. + */ + name: string; + + /** + * The (new) schema configuration for this attribute. + */ + schema: EntitySchemaField; + }[]; } diff --git a/src/app/core/entity/mock-entity-mapper-service.ts b/src/app/core/entity/mock-entity-mapper-service.ts index a39ff937fd..5973ab7488 100644 --- a/src/app/core/entity/mock-entity-mapper-service.ts +++ b/src/app/core/entity/mock-entity-mapper-service.ts @@ -58,7 +58,11 @@ export class MockEntityMapperService extends EntityMapperService { * @param id */ public get(entityType: string, id: string): Entity { - return this.data.get(entityType)?.get(id); + const result = this.data.get(entityType)?.get(id); + if (!result) { + throw { status: 404 }; + } + return result; } /** diff --git a/src/app/core/entity/model/entity.ts b/src/app/core/entity/model/entity.ts index 6a7c10cc4b..37c6bcbb3d 100644 --- a/src/app/core/entity/model/entity.ts +++ b/src/app/core/entity/model/entity.ts @@ -26,7 +26,9 @@ import { getWarningLevelColor, WarningLevel } from "./warning-level"; * It can also be used to check the ENTITY_TYPE of a class * For example usage check the {@link EntityMapperService}. */ -export type EntityConstructor = (new (id?: string) => T) & +export type EntityConstructor = (new ( + id?: string +) => T) & typeof Entity; export const ENTITY_CONFIG_PREFIX = "entity:"; diff --git a/src/app/core/form-dialog/form-dialog-wrapper/form-dialog-wrapper.component.spec.ts b/src/app/core/form-dialog/form-dialog-wrapper/form-dialog-wrapper.component.spec.ts index 78c77dbb58..e87a5e8473 100644 --- a/src/app/core/form-dialog/form-dialog-wrapper/form-dialog-wrapper.component.spec.ts +++ b/src/app/core/form-dialog/form-dialog-wrapper/form-dialog-wrapper.component.spec.ts @@ -9,6 +9,8 @@ import { MatDialogRef } from "@angular/material/dialog"; import { Subject } from "rxjs"; import { MatSnackBarModule } from "@angular/material/snack-bar"; import { MockSessionModule } from "../../session/mock-session.module"; +import { DynamicEntityService } from "../../entity/dynamic-entity.service"; +import { EntitySchemaService } from "../../entity/schema/entity-schema.service"; describe("FormDialogWrapperComponent", () => { let component: FormDialogWrapperComponent; @@ -26,7 +28,11 @@ describe("FormDialogWrapperComponent", () => { MatSnackBarModule, MockSessionModule.withState(), ], - providers: [{ provide: MatDialogRef, useValue: {} }], + providers: [ + { provide: MatDialogRef, useValue: {} }, + DynamicEntityService, + EntitySchemaService, + ], }).compileComponents(); saveEntitySpy = spyOn(TestBed.inject(EntityMapperService), "save"); diff --git a/src/app/core/session/login/login.component.html b/src/app/core/session/login/login.component.html index 23fd388477..8e507ed2c7 100644 --- a/src/app/core/session/login/login.component.html +++ b/src/app/core/session/login/login.component.html @@ -27,6 +27,7 @@