diff --git a/.drone.yml b/.drone.yml index f0355b7fa..da6575adb 100644 --- a/.drone.yml +++ b/.drone.yml @@ -1,5 +1,5 @@ kind: pipeline -name: testing +name: build trigger: branch: @@ -10,6 +10,13 @@ trigger: - push - pull_request +services: + - name: api + image: vikunja/api + environment: + VIKUNJA_SERVICE_TESTINGTOKEN: averyLongSecretToSe33dtheDB + VIKUNJA_LOG_LEVEL: DEBUG + steps: - name: restore-cache image: meltwater/drone-cache:dev @@ -30,11 +37,11 @@ steps: - '.cache' - name: dependencies - image: node:13 + image: node:12 pull: true - group: build-static environment: - YARN_CACHE_FOLDER: .cache + YARN_CACHE_FOLDER: .cache/yarn/ + CYPRESS_CACHE_FOLDER: .cache/cypress/ commands: - yarn --frozen-lockfile --network-timeout 100000 depends_on: @@ -61,26 +68,62 @@ steps: - dependencies - name: build - image: node:13 + image: node:12 pull: true - group: build-static environment: - YARN_CACHE_FOLDER: .cache + YARN_CACHE_FOLDER: .cache/yarn/ + CYPRESS_CACHE_FOLDER: .cache/cypress/ commands: - yarn run lint - yarn run build depends_on: - dependencies - - name: test - image: node:13 + - name: test-unit + image: node:12 pull: true - group: build-static commands: - - yarn test + - yarn test:unit depends_on: - dependencies + - name: test-frontend + image: cypress/browsers:node12.18.3-chrome87-ff82 + pull: true + environment: + CYPRESS_API_URL: http://api:3456/api/v1 + CYPRESS_TEST_SECRET: averyLongSecretToSe33dtheDB + YARN_CACHE_FOLDER: .cache/yarn/ + CYPRESS_CACHE_FOLDER: .cache/cypress/ + commands: + - sed -i 's/localhost/api/g' public/index.html + - yarn serve & npx wait-on http://localhost:8080 + - yarn test:frontend --browser chrome + depends_on: + - dependencies + + - name: upload-test-results + image: plugins/s3:1 + pull: true + settings: + bucket: drone-test-results + access_key: + from_secret: test_results_aws_access_key_id + secret_key: + from_secret: test_results_aws_secret_access_key + endpoint: https://s3.fr-par.scw.cloud + region: fr-par + path_style: true + source: cypress/screenshots/**/**/* + strip_prefix: cypress/screenshots/ + target: /${DRONE_REPO}/${DRONE_PULL_REQUEST}_${DRONE_BRANCH}/${DRONE_BUILD_NUMBER}/ + depends_on: + - test-frontend + when: + status: + - failure + - success + --- kind: pipeline name: release-latest @@ -116,7 +159,7 @@ steps: - '.cache' - name: build - image: node:13 + image: node:12 pull: true group: build-static environment: @@ -186,7 +229,7 @@ steps: - '.cache' - name: build - image: node:13 + image: node:12 pull: true group: build-static environment: diff --git a/.gitignore b/.gitignore index 59b5c1cf2..aad7d2856 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,7 @@ yarn-error.log* *.njsproj *.sln *.sw* + +# Test files +cypress/screenshots +cypress/videos diff --git a/cypress.json b/cypress.json new file mode 100644 index 000000000..9497a81ea --- /dev/null +++ b/cypress.json @@ -0,0 +1,8 @@ +{ + "baseUrl": "http://localhost:8080", + "env": { + "API_URL": "http://localhost:3456/api/v1", + "TEST_SECRET": "testingS3cr3et" + }, + "video": false +} diff --git a/cypress/README.md b/cypress/README.md new file mode 100644 index 000000000..97c6469f1 --- /dev/null +++ b/cypress/README.md @@ -0,0 +1,48 @@ +# Frontend Testing With Cypress + +## Setup + +* Enable the [seeder api endpoint](https://vikunja.io/docs/config-options/#testingtoken). You'll then need to add the testingtoken in `cypress.json` or set the `CYPRESS_TEST_SECRET` environment variable. +* Basic configuration happens in the `cypress.json` file +* Overridable with [env](https://docs.cypress.io/guides/guides/environment-variables.html#Option-3-CYPRESS) +* Override base url with `CYPRESS_BASE_URL` + +## Fixtures + +We're using the [test endpoint](https://vikunja.io/docs/config-options/#testingtoken) of the vikunja api to +seed the database with test data before running the tests. +This ensures better reproducability of tests. + +## Running The Tests Locally + +### Using Docker + +The easiest way to run all frontend tests locally is by using the `docker-compose` file in this repository. +It uses the same configuration as the CI. + +To use it, run + +``` +docker-compose up -d +``` + +Then, once all containers are started, run + +``` +docker-composer run cypress bash +``` + +to get a shell inside the cypress container. +In that shell you can then execute the tests with + +``` +yarn test:frontend +``` + +### Using The Cypress Dashboard + +To open the Cypress Dashboard and run tests from there, run + +``` +yarn cypress:open +``` diff --git a/cypress/docker-compose.yml b/cypress/docker-compose.yml new file mode 100644 index 000000000..1c496de49 --- /dev/null +++ b/cypress/docker-compose.yml @@ -0,0 +1,18 @@ +version: '3' + +services: + api: + image: vikunja/api + environment: + VIKUNJA_LOG_LEVEL: DEBUG + VIKUNJA_SERVICE_TESTINGTOKEN: averyLongSecretToSe33dtheDB + cypress: + image: cypress/browsers:node12.18.3-chrome87-ff82 + volumes: + - ..:/project + - $HOME/.cache:/home/node/.cache/ + user: node + working_dir: /project + environment: + CYPRESS_API_URL: http://api:3456/api/v1 + CYPRESS_TEST_SECRET: averyLongSecretToSe33dtheDB diff --git a/cypress/factories/bucket.js b/cypress/factories/bucket.js new file mode 100644 index 000000000..be90cca99 --- /dev/null +++ b/cypress/factories/bucket.js @@ -0,0 +1,20 @@ +import faker from 'faker' +import {Factory} from '../support/factory' +import {formatISO} from 'date-fns' + +export class BucketFactory extends Factory { + static table = 'buckets' + + static factory() { + const now = new Date() + + return { + id: '{increment}', + title: faker.lorem.words(3), + list_id: 1, + created_by_id: 1, + created: formatISO(now), + updated: formatISO(now) + } + } +} diff --git a/cypress/factories/link_sharing.js b/cypress/factories/link_sharing.js new file mode 100644 index 000000000..66102b1b9 --- /dev/null +++ b/cypress/factories/link_sharing.js @@ -0,0 +1,22 @@ +import {Factory} from '../support/factory' +import {formatISO} from "date-fns" +import faker from 'faker' + +export class LinkShareFactory extends Factory { + static table = 'link_sharing' + + static factory() { + const now = new Date() + + return { + id: '{increment}', + hash: faker.random.word(32), + list_id: 1, + right: 0, + sharing_type: 0, + shared_by_id: 1, + created: formatISO(now), + updated: formatISO(now) + } + } +} diff --git a/cypress/factories/list.js b/cypress/factories/list.js new file mode 100644 index 000000000..784d63b50 --- /dev/null +++ b/cypress/factories/list.js @@ -0,0 +1,20 @@ +import {Factory} from '../support/factory' +import {formatISO} from "date-fns" +import faker from 'faker' + +export class ListFactory extends Factory { + static table = 'list' + + static factory() { + const now = new Date() + + return { + id: '{increment}', + title: faker.lorem.words(3), + owner_id: 1, + namespace_id: 1, + created: formatISO(now), + updated: formatISO(now) + } + } +} \ No newline at end of file diff --git a/cypress/factories/namespace.js b/cypress/factories/namespace.js new file mode 100644 index 000000000..89096d2dd --- /dev/null +++ b/cypress/factories/namespace.js @@ -0,0 +1,19 @@ +import faker from 'faker' +import {Factory} from '../support/factory' +import {formatISO} from 'date-fns' + +export class NamespaceFactory extends Factory { + static table = 'namespaces' + + static factory() { + const now = new Date() + + return { + id: '{increment}', + title: faker.lorem.words(3), + owner_id: 1, + created: formatISO(now), + updated: formatISO(now) + } + } +} diff --git a/cypress/factories/task.js b/cypress/factories/task.js new file mode 100644 index 000000000..8e0d7b59d --- /dev/null +++ b/cypress/factories/task.js @@ -0,0 +1,23 @@ +import faker from 'faker' +import {Factory} from '../support/factory' +import {formatISO} from 'date-fns' + +export class TaskFactory extends Factory { + static table = 'tasks' + + static factory() { + const now = new Date() + + return { + id: '{increment}', + title: faker.lorem.words(3), + done: false, + list_id: 1, + created_by_id: 1, + is_favorite: false, + index: '{increment}', + created: formatISO(now), + updated: formatISO(now) + } + } +} diff --git a/cypress/factories/task_comment.js b/cypress/factories/task_comment.js new file mode 100644 index 000000000..b0b200d00 --- /dev/null +++ b/cypress/factories/task_comment.js @@ -0,0 +1,19 @@ +import faker from 'faker' + +import {Factory} from '../support/factory' +import {formatISO} from "date-fns" + +export class TaskCommentFactory extends Factory { + static table = 'task_comments' + + static factory() { + return { + id: '{increment}', + comment: faker.lorem.text(3), + author_id: 1, + task_id: 1, + created: formatISO(now), + updated: formatISO(now) + } + } +} diff --git a/cypress/factories/team.js b/cypress/factories/team.js new file mode 100644 index 000000000..928b8ce42 --- /dev/null +++ b/cypress/factories/team.js @@ -0,0 +1,18 @@ +import faker from 'faker' +import {Factory} from '../support/factory' +import {formatISO} from 'date-fns' + +export class TeamFactory extends Factory { + static table = 'teams' + + static factory() { + const now = new Date() + + return { + name: faker.lorem.words(3), + created_by_id: 1, + created: formatISO(now), + updated: formatISO(now) + } + } +} diff --git a/cypress/factories/team_member.js b/cypress/factories/team_member.js new file mode 100644 index 000000000..08da679d3 --- /dev/null +++ b/cypress/factories/team_member.js @@ -0,0 +1,15 @@ +import {Factory} from '../support/factory' +import {formatISO} from 'date-fns' + +export class TeamMemberFactory extends Factory { + static table = 'team_members' + + static factory() { + return { + team_id: 1, + user_id: 1, + admin: false, + created: formatISO(new Date()), + } + } +} \ No newline at end of file diff --git a/cypress/factories/user.js b/cypress/factories/user.js new file mode 100644 index 000000000..ca8bb896e --- /dev/null +++ b/cypress/factories/user.js @@ -0,0 +1,21 @@ +import faker from 'faker' + +import {Factory} from '../support/factory' +import {formatISO} from "date-fns" + +export class UserFactory extends Factory { + static table = 'users' + + static factory() { + const now = new Date() + + return { + id: '{increment}', + username: faker.lorem.word(10) + faker.random.uuid(), + password: '$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.', // 1234 + is_active: true, + created: formatISO(now), + updated: formatISO(now) + } + } +} \ No newline at end of file diff --git a/cypress/factories/users_list.js b/cypress/factories/users_list.js new file mode 100644 index 000000000..e583cbed7 --- /dev/null +++ b/cypress/factories/users_list.js @@ -0,0 +1,19 @@ +import {Factory} from '../support/factory' +import {formatISO} from "date-fns" + +export class UserListFactory extends Factory { + static table = 'users_list' + + static factory() { + const now = new Date() + + return { + id: '{increment}', + list_id: 1, + user_id: 1, + right: 0, + created: formatISO(now), + updated: formatISO(now) + } + } +} \ No newline at end of file diff --git a/cypress/fixtures/image.jpg b/cypress/fixtures/image.jpg new file mode 100644 index 000000000..93910582d Binary files /dev/null and b/cypress/fixtures/image.jpg differ diff --git a/cypress/integration/list/list.spec.js b/cypress/integration/list/list.spec.js new file mode 100644 index 000000000..79c882477 --- /dev/null +++ b/cypress/integration/list/list.spec.js @@ -0,0 +1,370 @@ +import {formatISO, format} from 'date-fns' + +import {TaskFactory} from '../../factories/task' +import {ListFactory} from '../../factories/list' +import {UserListFactory} from '../../factories/users_list' +import {UserFactory} from '../../factories/user' +import {NamespaceFactory} from '../../factories/namespace' +import {BucketFactory} from '../../factories/bucket' + +import '../../support/authenticateUser' + +describe('Lists', () => { + beforeEach(() => { + UserFactory.create(1) + NamespaceFactory.create(1) + const lists = ListFactory.create(1, { + title: 'First List' + }) + TaskFactory.truncate() + }) + + it('Should create a new list', () => { + cy.visit('/') + cy.get('a.nsettings[href="/namespaces/1/list"]') + .click() + cy.url() + .should('contain', '/namespaces/1/list') + cy.get('h3') + .contains('Create a new list') + cy.get('input.input') + .type('New List') + cy.get('button.is-success') + .contains('Add') + .click() + + cy.wait(3000) // Waiting until the request to create the new list is done + cy.get('.global-notification') + .should('contain', 'Success') + cy.url() + .should('contain', '/lists/') + cy.get('.list-title h1') + .should('contain', 'New List') + }) + + it('Should redirect to a specific list view after visited', () => { + cy.visit('/lists/1/kanban') + cy.url() + .should('contain', '/lists/1/kanban') + cy.visit('/lists/1') + cy.url() + .should('contain', '/lists/1/kanban') + }) + + describe('List View', () => { + it('Should be an empty list', () => { + cy.visit('/lists/1') + cy.url() + .should('contain', '/lists/1/list') + cy.get('.list-title h1') + .should('contain', 'First List') + cy.get('.list-title a.icon') + .should('have.attr', 'href') + .and('include', '/lists/1/edit') + cy.get('.list-is-empty-notice') + .should('contain', 'This list is currently empty.') + }) + + it('Should navigate to the task when the title is clicked', () => { + const tasks = TaskFactory.create(5, { + id: '{increment}', + list_id: 1, + }) + cy.visit('/lists/1/list') + + cy.get('.tasks .task .tasktext') + .contains(tasks[0].title) + .first() + .click() + + cy.url() + .should('contain', `/tasks/${tasks[0].id}`) + }) + + it('Should not see any elements for a list which is shared read only', () => { + UserFactory.create(2) + UserListFactory.create(1, { + list_id: 2, + user_id: 1, + right: 0, + }) + const lists = ListFactory.create(2, { + owner_id: '{increment}', + namespace_id: '{increment}', + }) + cy.visit(`/lists/${lists[1].id}/`) + + cy.get('.list-title a.icon') + .should('not.exist') + cy.get('input.input[placeholder="Add a new task..."') + .should('not.exist') + }) + }) + + describe('Table View', () => { + it('Should show a table with tasks', () => { + const tasks = TaskFactory.create(1) + cy.visit('/lists/1/table') + + cy.get('.table-view table.table') + .should('exist') + cy.get('.table-view table.table') + .should('contain', tasks[0].title) + }) + + it('Should have working column switches', () => { + TaskFactory.create(1) + cy.visit('/lists/1/table') + + cy.get('.table-view .filter-container .items .button') + .contains('Columns') + .click() + cy.get('.table-view .filter-container .card .card-content .fancycheckbox .check') + .contains('Priority') + .click() + cy.get('.table-view .filter-container .card .card-content .fancycheckbox .check') + .contains('Done') + .click() + + cy.get('.table-view table.table th') + .contains('Priority') + .should('exist') + cy.get('.table-view table.table th') + .contains('Done') + .should('not.exist') + }) + + it('Should navigate to the task when the title is clicked', () => { + const tasks = TaskFactory.create(5, { + id: '{increment}', + list_id: 1, + }) + cy.visit('/lists/1/table') + + cy.get('.table-view table.table a') + .contains(tasks[0].title) + .first() + .click() + + cy.url() + .should('contain', `/tasks/${tasks[0].id}`) + }) + }) + + describe('Gantt View', () => { + it('Hides tasks with no dates', () => { + TaskFactory.create(1) + cy.visit('/lists/1/gantt') + + cy.get('.gantt-chart-container .gantt-chart.box .tasks') + .should('be.empty') + }) + + it('Shows tasks from the current and next month', () => { + const now = new Date() + cy.visit('/lists/1/gantt') + + cy.get('.gantt-chart-container .gantt-chart.box .months') + .should('contain', format(now, 'MMMM')) + .should('contain', format(now.setMonth(now.getMonth() + 1), 'MMMM')) + }) + + it('Shows tasks with dates', () => { + const now = new Date() + const tasks = TaskFactory.create(1, { + start_date: formatISO(now), + end_date: formatISO(now.setDate(now.getDate() + 4)) + }) + cy.visit('/lists/1/gantt') + + cy.get('.gantt-chart-container .gantt-chart.box .tasks') + .should('not.be.empty') + cy.get('.gantt-chart-container .gantt-chart.box .tasks') + .should('contain', tasks[0].title) + }) + + it('Shows tasks with no dates after enabling them', () => { + TaskFactory.create(1, { + start_date: null, + end_date: null, + }) + cy.visit('/lists/1/gantt') + + cy.get('.gantt-chart-container .gantt-options .fancycheckbox') + .contains('Show tasks which don\'t have dates set') + .click() + + cy.get('.gantt-chart-container .gantt-chart.box .tasks') + .should('not.be.empty') + cy.get('.gantt-chart-container .gantt-chart.box .tasks .task.nodate') + .should('exist') + }) + + it('Drags a task around', () => { + const now = new Date() + TaskFactory.create(1, { + start_date: formatISO(now), + end_date: formatISO(now.setDate(now.getDate() + 4)) + }) + cy.visit('/lists/1/gantt') + + cy.get('.gantt-chart-container .gantt-chart.box .tasks .task') + .first() + .trigger('mousedown', {which: 1}) + .trigger('mousemove', {clientX: 500, clientY: 0}) + .trigger('mouseup', {force: true}) + }) + }) + + describe('Kanban', () => { + let buckets + + beforeEach(() => { + buckets = BucketFactory.create(2) + }) + + it('Shows all buckets with their tasks', () => { + const data = TaskFactory.create(10, { + list_id: 1, + bucket_id: 1, + }) + cy.visit('/lists/1/kanban') + + cy.get('.kanban .bucket .title') + .contains(buckets[0].title) + .should('exist') + cy.get('.kanban .bucket .title') + .contains(buckets[1].title) + .should('exist') + cy.get('.kanban .bucket') + .first() + .should('contain', data[0].title) + }) + + it('Can add a new task to a bucket', () => { + const data = TaskFactory.create(2, { + list_id: 1, + bucket_id: 1, + }) + cy.visit('/lists/1/kanban') + + cy.get('.kanban .bucket') + .contains(buckets[0].title) + .get('.bucket-footer .button') + .contains('Add another task') + .click() + cy.get('.kanban .bucket') + .contains(buckets[0].title) + .get('.bucket-footer .field .control input.input') + .type('New Task{enter}') + + cy.get('.kanban .bucket') + .first() + .should('contain', 'New Task') + }) + + it('Can create a new bucket', () => { + cy.visit('/lists/1/kanban') + + cy.get('.kanban .bucket.new-bucket .button') + .click() + cy.get('.kanban .bucket.new-bucket input.input') + .type('New Bucket{enter}') + + cy.wait(1000) // Wait for the request to finish + cy.get('.kanban .bucket .title') + .contains('New Bucket') + .should('exist') + }) + + it('Can set a bucket limit', () => { + cy.visit('/lists/1/kanban') + + cy.get('.kanban .bucket .bucket-header .dropdown.options .dropdown-trigger') + .first() + .click() + cy.get('.kanban .bucket .bucket-header .dropdown.options .dropdown-menu .dropdown-item') + .contains('Limit: Not set') + .click() + cy.get('.kanban .bucket .bucket-header .dropdown.options .dropdown-menu .dropdown-item .field input.input') + .first() + .type(3) + cy.get('.kanban .bucket .bucket-header .dropdown.options .dropdown-menu .dropdown-item .field a.button.is-primary') + .first() + .click() + + cy.get('.kanban .bucket .bucket-header span.limit') + .contains('0/3') + .should('exist') + }) + + it('Can rename a bucket', () => { + cy.visit('/lists/1/kanban') + + cy.get('.kanban .bucket .bucket-header .title') + .first() + .type('{selectall}New Bucket Title{enter}') + cy.get('.kanban .bucket .bucket-header .title') + .first() + .should('contain', 'New Bucket Title') + }) + + it('Can delete a bucket', () => { + cy.visit('/lists/1/kanban') + + cy.get('.kanban .bucket .bucket-header .dropdown.options .dropdown-trigger') + .first() + .click() + cy.get('.kanban .bucket .bucket-header .dropdown.options .dropdown-menu .dropdown-item') + .contains('Delete') + .click() + cy.get('.modal-mask .modal-container .modal-content .header') + .should('contain', 'Delete the bucket') + cy.get('.modal-mask .modal-container .modal-content .actions .button') + .contains('Do it!') + .click() + + cy.get('.kanban .bucket .title') + .contains(buckets[0].title) + .should('not.exist') + }) + + + // The following test does not work. It seems like vue-smooth-dnd does not use either mousemove or dragstart + // (not sure why this actually works at all?) and as I'm planning to swap that out for vuedraggable/sortable.js + // anyway, I figured it wouldn't be worth the hassle right now. + +// it('Can drag tasks around', () => { +// const tasks = TaskFactory.create(2, { +// list_id: 1, +// bucket_id: 1, +// }) +// cy.visit('/lists/1/kanban') +// +// cy.get('.kanban .bucket .tasks .task') +// .contains(tasks[0].title) +// .first() +// .drag('.kanban .bucket:nth-child(2) .tasks .smooth-dnd-container.vertical') +// .trigger('mousedown', {which: 1}) +// .trigger('mousemove', {clientX: 500, clientY: 0}) +// .trigger('mouseup', {force: true}) +// }) + + it('Should navigate to the task when the task card is clicked', () => { + const tasks = TaskFactory.create(5, { + id: '{increment}', + list_id: 1, + bucket_id: 1, + }) + cy.visit('/lists/1/kanban') + + cy.get('.kanban .bucket .tasks .task') + .contains(tasks[0].title) + .first() + .click() + + cy.url() + .should('contain', `/tasks/${tasks[0].id}`) + }) + }) +}) diff --git a/cypress/integration/list/namespaces.spec.js b/cypress/integration/list/namespaces.spec.js new file mode 100644 index 000000000..9735166b9 --- /dev/null +++ b/cypress/integration/list/namespaces.spec.js @@ -0,0 +1,39 @@ +import {UserFactory} from '../../factories/user' + +import '../../support/authenticateUser' +import {ListFactory} from '../../factories/list' +import {NamespaceFactory} from '../../factories/namespace' + +describe('Namepaces', () => { + let namespaces + + beforeEach(() => { + UserFactory.create(1) + namespaces = NamespaceFactory.create(1) + ListFactory.create(1) + }) + + it('Should be all there', () => { + cy.visit('/namespaces') + cy.get('.namespace h1 span') + .should('contain', namespaces[0].title) + }) + + it('Should create a new Namespace', () => { + cy.visit('/namespaces') + cy.get('a.button') + .contains('Create new namespace') + .click() + cy.url() + .should('contain', '/namespaces/new') + cy.get('h3') + .should('contain', 'Create a new namespace') + cy.get('input.input') + .type('New Namespace') + cy.get('button.is-success') + .contains('Add') + .click() + cy.url() + .should('contain', '/namespaces') + }) +}) diff --git a/cypress/integration/misc/editor.spec.js b/cypress/integration/misc/editor.spec.js new file mode 100644 index 000000000..253e64dcc --- /dev/null +++ b/cypress/integration/misc/editor.spec.js @@ -0,0 +1,37 @@ +import {TaskFactory} from '../../factories/task' +import {ListFactory} from '../../factories/list' +import {NamespaceFactory} from '../../factories/namespace' +import {UserListFactory} from '../../factories/users_list' + +import '../../support/authenticateUser' + +describe('Editor', () => { + beforeEach(() => { + NamespaceFactory.create(1) + const lists = ListFactory.create(1) + TaskFactory.truncate() + UserListFactory.truncate() + }) + + it('Has a preview with checkable checkboxes', () => { + const tasks = TaskFactory.create(1, { + description: `# Test Heading +* Bullet 1 +* Bullet 2 + +* [ ] Checklist +* [x] Checklist checked +`, + }) + + cy.visit(`/tasks/${tasks[0].id}`) + cy.get('input[type=checkbox][data-checkbox-num=0]') + .click() + + cy.get('.task-view .details.content.description h3 span.is-small.has-text-success') + .contains('Saved!') + .should('exist') + cy.get('.preview.content') + .should('contain', 'Test Heading') + }) +}) \ No newline at end of file diff --git a/cypress/integration/misc/menu.spec.js b/cypress/integration/misc/menu.spec.js new file mode 100644 index 000000000..98e228f2f --- /dev/null +++ b/cypress/integration/misc/menu.spec.js @@ -0,0 +1,29 @@ +import '../../support/authenticateUser' + +describe('The Menu', () => { + it('Is visible by default on desktop', () => { + cy.get('.namespace-container') + .should('have.class', 'is-active') + }) + + it('Can be hidden on desktop', () => { + cy.get('a.menu-show-button:visible') + .click() + cy.get('.namespace-container') + .should('not.have.class', 'is-active') + }) + + it('Is hidden by default on mobile', () => { + cy.viewport('iphone-8') + cy.get('.namespace-container') + .should('not.have.class', 'is-active') + }) + + it('Is can be shown on mobile', () => { + cy.viewport('iphone-8') + cy.get('a.menu-show-button:visible') + .click() + cy.get('.namespace-container') + .should('have.class', 'is-active') + }) +}) diff --git a/cypress/integration/sharing/linkShare.spec.js b/cypress/integration/sharing/linkShare.spec.js new file mode 100644 index 000000000..bc616cc6f --- /dev/null +++ b/cypress/integration/sharing/linkShare.spec.js @@ -0,0 +1,25 @@ +import {LinkShareFactory} from '../../factories/link_sharing' +import {ListFactory} from '../../factories/list' +import {TaskFactory} from '../../factories/task' + +describe('Link shares', () => { + it('Can view a link share', () => { + const lists = ListFactory.create(1) + const tasks = TaskFactory.create(10, { + list_id: lists[0].id + }) + const linkShares = LinkShareFactory.create(1, { + list_id: lists[0].id, + right: 0, + }) + + cy.visit(`/share/${linkShares[0].hash}/auth`) + + cy.get('h1.title') + .should('contain', lists[0].title) + cy.get('input.input[placeholder="Add a new task..."') + .should('not.exist') + cy.get('.tasks') + .should('contain', tasks[0].title) + }) +}) diff --git a/cypress/integration/sharing/team.spec.js b/cypress/integration/sharing/team.spec.js new file mode 100644 index 000000000..1ec4b11f3 --- /dev/null +++ b/cypress/integration/sharing/team.spec.js @@ -0,0 +1,91 @@ +import {TeamFactory} from '../../factories/team' +import {TeamMemberFactory} from '../../factories/team_member' +import '../../support/authenticateUser' + +describe('Team', () => { + it('Creates a new team', () => { + TeamFactory.truncate() + cy.visit('/teams') + + cy.get('a.button') + .contains('New Team') + .click() + cy.url() + .should('contain', '/teams/new') + cy.get('h3') + .contains('Create a new team') + cy.get('input.input') + .type('New Team') + cy.get('button.is-success') + .contains('Add') + .click() + + cy.get('.fullpage') + .should('not.exist') + cy.url() + .should('contain', '/edit') + cy.get('.card-header .card-header-title') + .first() + .should('contain', 'Edit Team') + }) + + it('Shows all teams', () => { + TeamMemberFactory.create(10, { + team_id: '{increment}', + }) + const teams = TeamFactory.create(10, { + id: '{increment}', + }) + + cy.visit('/teams') + + cy.get('.teams.box') + .should('not.be.empty') + teams.forEach(t => { + cy.get('.teams.box') + .should('contain', t.name) + }) + }) + + it('Allows an admin to edit the team', () => { + TeamMemberFactory.create(1, { + team_id: 1, + admin: true, + }) + const teams = TeamFactory.create(1, { + id: 1, + }) + + cy.visit('/teams/1/edit') + cy.get('.card input.input') + .first() + .type('{selectall}New Team Name') + + cy.get('.card .button') + .contains('Save') + .click() + + cy.get('table.table td') + .contains('Admin') + .should('exist') + cy.get('.global-notification') + .should('contain', 'Success') + }) + + it('Does not allow a normal user to edit the team', () => { + TeamMemberFactory.create(1, { + team_id: 1, + admin: false, + }) + const teams = TeamFactory.create(1, { + id: 1, + }) + + cy.visit('/teams/1/edit') + cy.get('.card input.input') + .should('not.exist') + cy.get('table.table td') + .contains('Member') + .should('exist') + }) +}) diff --git a/cypress/integration/task/task.spec.js b/cypress/integration/task/task.spec.js new file mode 100644 index 000000000..8f8dbbe64 --- /dev/null +++ b/cypress/integration/task/task.spec.js @@ -0,0 +1,237 @@ +import {formatISO} from 'date-fns' + +import {TaskFactory} from '../../factories/task' +import {ListFactory} from '../../factories/list' +import {TaskCommentFactory} from '../../factories/task_comment' +import {UserFactory} from '../../factories/user' +import {NamespaceFactory} from '../../factories/namespace' +import {UserListFactory} from '../../factories/users_list' + +import '../../support/authenticateUser' + +describe('Task', () => { + let namespaces + let lists + + beforeEach(() => { + UserFactory.create(1) + namespaces = NamespaceFactory.create(1) + lists = ListFactory.create(1) + TaskFactory.truncate() + UserListFactory.truncate() + }) + + it('Should be created new', () => { + cy.visit('/lists/1/list') + cy.get('input.input[placeholder="Add a new task..."') + .type('New Task') + cy.get('button.button.is-success') + .contains('Add') + .click() + cy.get('.tasks .task .tasktext') + .first() + .should('contain', 'New Task') + }) + + it('Inserts new tasks at the top of the list', () => { + TaskFactory.create(1) + + cy.visit('/lists/1/list') + cy.get('.list-is-empty-notice') + .should('not.exist') + cy.get('input.input[placeholder="Add a new task..."') + .type('New Task') + cy.get('button.button.is-success') + .contains('Add') + .click() + + cy.wait(1000) // Wait for the request + cy.get('.tasks .task .tasktext') + .first() + .should('contain', 'New Task') + }) + + it('Marks a task as done', () => { + TaskFactory.create(1) + + cy.visit('/lists/1/list') + cy.get('.tasks .task .fancycheckbox label.check') + .first() + .click() + cy.get('.global-notification') + .should('contain', 'Success') + }) + + it('Can add a task to favorites', () => { + TaskFactory.create(1) + + cy.visit('/lists/1/list') + cy.get('.tasks .task .favorite') + .first() + .click() + cy.get('.menu.namespaces-lists') + .should('contain', 'Favorites') + }) + + describe('Task Detail View', () => { + beforeEach(() => { + TaskCommentFactory.truncate() + }) + + it('Shows all task details', () => { + const tasks = TaskFactory.create(1, { + id: 1, + index: 1, + description: 'Lorem ipsum dolor sit amet.' + }) + cy.visit(`/tasks/${tasks[0].id}`) + + cy.get('.task-view h1.title.input') + .should('contain', tasks[0].title) + cy.get('.task-view h1.title.task-id') + .should('contain', '#1') + cy.get('.task-view h6.subtitle') + .should('contain', namespaces[0].title) + .should('contain', lists[0].title) + cy.get('.task-view .details.content.description') + .should('contain', tasks[0].description) + cy.get('.task-view .action-buttons p.created') + .should('contain', 'Created') + }) + + it('Shows a done label for done tasks', () => { + const tasks = TaskFactory.create(1, { + id: 1, + index: 1, + done: true, + done_at: formatISO(new Date()) + }) + cy.visit(`/tasks/${tasks[0].id}`) + + cy.get('.task-view .heading .is-done') + .should('exist') + .should('contain', 'Done') + cy.get('.task-view .action-buttons p.created') + .should('contain', 'Done') + }) + + it('Can mark a task as done', () => { + const tasks = TaskFactory.create(1, { + id: 1, + done: false, + }) + cy.visit(`/tasks/${tasks[0].id}`) + + cy.get('.task-view .action-buttons .button') + .contains('Done!') + .click() + + cy.get('.task-view .heading .is-done') + .should('exist') + .should('contain', 'Done') + cy.get('.global-notification') + .should('contain', 'Success') + cy.get('.task-view .action-buttons .button') + .should('contain', 'Mark as undone') + }) + + it('Shows a task identifier since the list has one', () => { + const lists = ListFactory.create(1, { + id: 1, + identifier: 'TEST', + }) + const tasks = TaskFactory.create(1, { + id: 1, + list_id: lists[0].id, + index: 1, + }) + + cy.visit(`/tasks/${tasks[0].id}`) + + cy.get('.task-view h1.title.task-id') + .should('contain', `${lists[0].identifier}-${tasks[0].index}`) + }) + + it('Can edit the description', () => { + const tasks = TaskFactory.create(1, { + id: 1, + description: 'Lorem ipsum dolor sit amet.' + }) + cy.visit(`/tasks/${tasks[0].id}`) + + cy.get('.task-view .details.content.description .editor a') + .contains('Edit') + .click() + cy.get('.task-view .details.content.description .editor .vue-easymde .EasyMDEContainer .CodeMirror-scroll') + .type('{selectall}New Description') + cy.get('.task-view .details.content.description .editor a') + .contains('Preview') + .click() + + cy.get('.task-view .details.content.description h3 span.is-small.has-text-success') + .contains('Saved!') + .should('exist') + }) + + it('Can add a new comment', () => { + const tasks = TaskFactory.create(1, { + id: 1, + }) + cy.visit(`/tasks/${tasks[0].id}`) + + cy.get('.task-view .comments .media.comment .editor .vue-easymde .EasyMDEContainer .CodeMirror-scroll') + .type('{selectall}New Comment') + cy.get('.task-view .comments .media.comment .button.is-primary') + .contains('Comment') + .click() + + cy.get('.task-view .comments .media.comment .editor') + .should('contain', 'New Comment') + cy.get('.global-notification') + .should('contain', 'Success') + }) + + it('Can move a task to another list', () => { + const lists = ListFactory.create(2) + const tasks = TaskFactory.create(1, { + id: 1, + list_id: lists[0].id, + }) + cy.visit(`/tasks/${tasks[0].id}`) + + cy.get('.task-view .action-buttons .button') + .contains('Move task') + .click() + cy.get('.task-view .content.details .field .multiselect.control .multiselect__tags .multiselect__input') + .type(`${lists[1].title}{enter}`) + + cy.get('.task-view h6.subtitle') + .should('contain', namespaces[0].title) + .should('contain', lists[1].title) + cy.get('.global-notification') + .should('contain', 'Success') + }) + + it('Can delete a task', () => { + const tasks = TaskFactory.create(1, { + id: 1, + list_id: 1, + }) + cy.visit(`/tasks/${tasks[0].id}`) + + cy.get('.task-view .action-buttons .button') + .contains('Delete task') + .click() + cy.get('.modal-mask .modal-container .modal-content .header') + .should('contain', 'Delete this task') + cy.get('.modal-mask .modal-container .modal-content .actions .button') + .contains('Do it!') + .click() + + cy.get('.global-notification') + .should('contain', 'Success') + cy.url() + .should('contain', `/lists/${tasks[0].list_id}/`) + }) + }) +}) diff --git a/cypress/integration/user/login.spec.js b/cypress/integration/user/login.spec.js new file mode 100644 index 000000000..f89aa6cc6 --- /dev/null +++ b/cypress/integration/user/login.spec.js @@ -0,0 +1,57 @@ +import {UserFactory} from '../../factories/user' + +const testAndAssertFailed = fixture => { + cy.visit('/login') + cy.get('input[id=username]').type(fixture.username) + cy.get('input[id=password]').type(fixture.password) + cy.get('button').contains('Login').click() + + cy.wait(5000) // It can take waaaayy too long to log the user in + cy.url().should('include', '/') + cy.get('div.notification.is-danger').contains('Wrong username or password.') +} + +context('Login', () => { + beforeEach(() => { + UserFactory.create(1, { + username: 'test', + }) + cy.visit('/', { + onBeforeLoad(win) { + win.localStorage.removeItem('token') + }, + }) + }) + + it('Should log in with the right credentials', () => { + const fixture = { + username: 'test', + password: '1234', + } + + cy.visit('/login') + cy.get('input[id=username]').type(fixture.username) + cy.get('input[id=password]').type(fixture.password) + cy.get('button').contains('Login').click() + cy.url().should('include', '/') + cy.get('h2').should('contain', `Hi ${fixture.username}!`) + }) + + it('Should fail with a bad password', () => { + const fixture = { + username: 'test', + password: '123456', + } + + testAndAssertFailed(fixture) + }) + + it('Should fail with a bad username', () => { + const fixture = { + username: 'loremipsum', + password: '1234', + } + + testAndAssertFailed(fixture) + }) +}) diff --git a/cypress/integration/user/logout.spec.js b/cypress/integration/user/logout.spec.js new file mode 100644 index 000000000..fbbc7088c --- /dev/null +++ b/cypress/integration/user/logout.spec.js @@ -0,0 +1,16 @@ +import '../../support/authenticateUser' + +describe('Log out', () => { + it('Logs the user out', () => { + cy.visit('/') + + cy.get('.navbar .user .username') + .click() + cy.get('.navbar .user .dropdown-menu a.dropdown-item') + .contains('Logout') + .click() + + cy.url() + .should('contain', '/login') + }) +}) diff --git a/cypress/integration/user/registration.spec.js b/cypress/integration/user/registration.spec.js new file mode 100644 index 000000000..b61b93c5e --- /dev/null +++ b/cypress/integration/user/registration.spec.js @@ -0,0 +1,49 @@ +// This test assumes no mailer is set up and all users are activated immediately. + +import {UserFactory} from '../../factories/user' + +context('Registration', () => { + beforeEach(() => { + UserFactory.create(1, { + username: 'test', + }) + cy.visit('/', { + onBeforeLoad(win) { + win.localStorage.removeItem('token') + }, + }) + }) + + it('Should work without issues', () => { + const fixture = { + username: 'testuser', + password: '123456', + email: 'testuser@example.com', + } + + cy.visit('/register') + cy.get('#username').type(fixture.username) + cy.get('#email').type(fixture.email) + cy.get('#password1').type(fixture.password) + cy.get('#password2').type(fixture.password) + cy.get('button#register-submit').click() + cy.url().should('include', '/') + cy.get('h2').should('contain', `Hi ${fixture.username}!`) + }) + + it('Should fail', () => { + const fixture = { + username: 'test', + password: '123456', + email: 'testuser@example.com', + } + + cy.visit('/register') + cy.get('#username').type(fixture.username) + cy.get('#email').type(fixture.email) + cy.get('#password1').type(fixture.password) + cy.get('#password2').type(fixture.password) + cy.get('button#register-submit').click() + cy.get('div.notification.is-danger').contains('A user with this username already exists.') + }) +}) \ No newline at end of file diff --git a/cypress/integration/user/settings.spec.js b/cypress/integration/user/settings.spec.js new file mode 100644 index 000000000..bcc5cd9ae --- /dev/null +++ b/cypress/integration/user/settings.spec.js @@ -0,0 +1,43 @@ +import {UserFactory} from '../../factories/user' + +import '../../support/authenticateUser' + +describe('User Settings', () => { + beforeEach(() => { + UserFactory.create(1) + }) + + it('Changes the user avatar', () => { + cy.visit('/user/settings') + + cy.get('input[name=avatarProvider][value=upload]') + .click() + cy.get('input[type=file]') + .attachFile('image.jpg') + cy.get('.vue-handler-wrapper.vue-handler-wrapper--south .vue-simple-handler.vue-simple-handler--south') + .trigger('mousedown', {which: 1}) + .trigger('mousemove', {clientY: 100}) + .trigger('mouseup') + cy.get('a.button.is-primary') + .contains('Upload Avatar') + .click() + + cy.get('.global-notification') + .should('contain', 'Success') + }) + + it('Updates the name', () => { + cy.visit('/user/settings') + + cy.get('input#newName') + .type('Lorem Ipsum') + cy.get('.card.update-name button.button.is-primary') + .contains('Save') + .click() + + cy.get('.global-notification') + .should('contain', 'Success') + cy.get('.navbar .user .username') + .should('contain', 'Lorem Ipsum') + }) +}) diff --git a/cypress/plugins/index.js b/cypress/plugins/index.js new file mode 100644 index 000000000..aa9918d21 --- /dev/null +++ b/cypress/plugins/index.js @@ -0,0 +1,21 @@ +/// +// *********************************************************** +// This example plugins/index.js can be used to load plugins +// +// You can change the location of this file or turn off loading +// the plugins file with the 'pluginsFile' configuration option. +// +// You can read more here: +// https://on.cypress.io/plugins-guide +// *********************************************************** + +// This function is called when a project is opened or re-opened (e.g. due to +// the project's config changing) + +/** + * @type {Cypress.PluginConfig} + */ +module.exports = (on, config) => { + // `on` is used to hook into various events Cypress emits + // `config` is the resolved Cypress config +} diff --git a/cypress/support/authenticateUser.js b/cypress/support/authenticateUser.js new file mode 100644 index 000000000..5f2423a46 --- /dev/null +++ b/cypress/support/authenticateUser.js @@ -0,0 +1,29 @@ + +// This authenticates a user and puts the token in local storage which allows us to perform authenticated requests. +// Built after https://github.com/cypress-io/cypress-example-recipes/tree/bd2d6ffb33214884cab343d38e7f9e6ebffb323f/examples/logging-in__jwt + +import {UserFactory} from '../factories/user' + +let token + +before(() => { + const users = UserFactory.create(1) + + cy.request('POST', `${Cypress.env('API_URL')}/login`, { + username: users[0].username, + password: '1234', + }) + .its('body') + .then(r => { + token = r.token + }) +}) + +beforeEach(() => { + cy.log(`Using token ${token} to make authenticated requests`) + cy.visit('/', { + onBeforeLoad(win) { + win.localStorage.setItem('token', token) + }, + }) +}) diff --git a/cypress/support/factory.js b/cypress/support/factory.js new file mode 100644 index 000000000..7dc1f7189 --- /dev/null +++ b/cypress/support/factory.js @@ -0,0 +1,46 @@ +import {seed} from './seed' +import merge from 'lodash/merge' + +/** + * A factory makes it easy to seed the database with data. + */ +export class Factory { + static table = null + + static factory() { + return {} + } + + /** + * Seeds a bunch of fake data into the database. + * + * Takes an override object as its single argument which will override the data from the factory. + * If the value of one of the override fields is `{increment}` that value will be replaced with an incrementing + * number through all created entities. + * + * @param override + * @returns {[]} + */ + static create(count = 1, override = {}) { + const data = [] + + for (let i = 1; i <= count; i++) { + const entry = merge(this.factory(), override) + for (const e in entry) { + if (entry[e] === '{increment}') { + entry[e] = i + } + } + data.push(entry) + } + + seed(this.table, data) + + return data + } + + static truncate() { + seed(this.table, null) + } +} + diff --git a/cypress/support/index.js b/cypress/support/index.js new file mode 100644 index 000000000..a51369df0 --- /dev/null +++ b/cypress/support/index.js @@ -0,0 +1,2 @@ + +import 'cypress-file-upload' diff --git a/cypress/support/seed.js b/cypress/support/seed.js new file mode 100644 index 000000000..acb1b2df4 --- /dev/null +++ b/cypress/support/seed.js @@ -0,0 +1,24 @@ +/** + * Seeds a db table with data. If a data object is provided as the second argument, it will load the fixtures + * file for the table and merge the data from it with the passed data. This allows you to override specific + * fields of the fixtures without having to redeclare the whole fixture. + * + * Passing null as the second argument empties the table. + * + * @param table + * @param data + */ +export function seed(table, data = {}) { + if(data === null) { + data = [] + } + + cy.request({ + method: 'PATCH', + url: `${Cypress.env('API_URL')}/test/${table}`, + headers: { + 'Authorization': Cypress.env('TEST_SECRET'), + }, + body: data, + }) +} diff --git a/package.json b/package.json index f916f7a45..022ba1d51 100644 --- a/package.json +++ b/package.json @@ -4,9 +4,12 @@ "private": true, "scripts": { "serve": "vue-cli-service serve", + "serve:dist": "node scripts/serve-dist.js", "build": "vue-cli-service build --modern", "lint": "vue-cli-service lint --ignore-pattern '*.test.*'", - "test": "jest" + "cypress:open": "cypress open", + "test:unit": "jest", + "test:frontend": "cypress run" }, "dependencies": { "bulma": "0.9.1", @@ -39,8 +42,11 @@ "@vue/cli-service": "4.5.9", "axios": "0.21.0", "babel-eslint": "10.1.0", + "cypress": "6.0.0", + "cypress-file-upload": "^4.1.1", "eslint": "7.15.0", "eslint-plugin-vue": "7.2.0", + "faker": "5.1.0", "jest": "26.6.3", "node-sass": "5.0.0", "sass-loader": "10.1.0", @@ -48,7 +54,8 @@ "vue-multiselect": "2.1.6", "vue-notification": "1.3.20", "vue-router": "3.4.9", - "vue-template-compiler": "2.6.12" + "vue-template-compiler": "2.6.12", + "wait-on": "^5.2.0" }, "eslintConfig": { "root": true, @@ -62,7 +69,11 @@ "rules": {}, "parserOptions": { "parser": "babel-eslint" - } + }, + "ignorePatterns": [ + "*.test.js", + "cypress/*" + ] }, "postcss": { "plugins": { @@ -74,5 +85,8 @@ "last 2 versions", "not ie <= 8" ], - "license": "LGPL-3.0-or-later" + "license": "LGPL-3.0-or-later", + "jest": { + "testPathIgnorePatterns": ["cypress"] + } } diff --git a/scripts/serve-dist.js b/scripts/serve-dist.js new file mode 100644 index 000000000..842f634f1 --- /dev/null +++ b/scripts/serve-dist.js @@ -0,0 +1,16 @@ +const path = require('path') +const express = require('express') +const app = express() + +const p = path.join(__dirname, '..', 'dist') +const port = 8080 + +app.use(express.static(p)) +// Handle urls set by the frontend +app.get('*', (request, response, next) => { + response.sendFile(`${p}/index.html`) +}) +app.listen(port, '127.0.0.1', () => { + console.log(`Serving files from ${p}`) + console.log(`Server started on port ${port}`) +}) diff --git a/src/components/home/navigation.vue b/src/components/home/navigation.vue index 314c194f6..d94d27bb6 100644 --- a/src/components/home/navigation.vue +++ b/src/components/home/navigation.vue @@ -151,10 +151,7 @@ export default { this.$store.dispatch('namespaces/loadNamespaces') }, created() { - // Hide the menu by default on mobile - if (window.innerWidth < 770) { - this.$store.commit(MENU_ACTIVE, false) - } + window.addEventListener('resize', this.resize) }, methods: { toggleFavoriteList(list) { @@ -166,6 +163,14 @@ export default { this.$store.dispatch('lists/toggleListFavorite', list) .catch(e => this.error(e, this)) }, + resize() { + // Hide the menu by default on mobile + if (window.innerWidth < 770) { + this.$store.commit(MENU_ACTIVE, false) + } else { + this.$store.commit(MENU_ACTIVE, true) + } + }, }, } diff --git a/src/components/misc/notification.vue b/src/components/misc/notification.vue index 1b000cc9c..4ad3f2bcc 100644 --- a/src/components/misc/notification.vue +++ b/src/components/misc/notification.vue @@ -1,5 +1,5 @@ - + { this.success({message: 'The task has been deleted successfully.'}, this) - router.back() + this.$router.push({name: 'list.index', params: {listId: this.task.listId}}) }) .catch(e => { this.error(e, this) diff --git a/src/views/user/Login.vue b/src/views/user/Login.vue index a5e35f9a9..c0f4de23b 100644 --- a/src/views/user/Login.vue +++ b/src/views/user/Login.vue @@ -72,7 +72,7 @@ - + Log in with {{ p.name }} diff --git a/src/views/user/Register.vue b/src/views/user/Register.vue index b455ef4fe..e3f32b250 100644 --- a/src/views/user/Register.vue +++ b/src/views/user/Register.vue @@ -62,7 +62,7 @@ - + Register Login diff --git a/src/views/user/Settings.vue b/src/views/user/Settings.vue index a67e680af..5fabdd950 100644 --- a/src/views/user/Settings.vue +++ b/src/views/user/Settings.vue @@ -107,7 +107,7 @@ - + Update your name @@ -116,12 +116,12 @@ - Name + Name diff --git a/yarn.lock b/yarn.lock index 0dca7ee02..a9ea8724d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1489,6 +1489,50 @@ exec-sh "^0.3.2" minimist "^1.2.0" +"@cypress/listr-verbose-renderer@^0.4.1": + version "0.4.1" + resolved "https://registry.yarnpkg.com/@cypress/listr-verbose-renderer/-/listr-verbose-renderer-0.4.1.tgz#a77492f4b11dcc7c446a34b3e28721afd33c642a" + integrity sha1-p3SS9LEdzHxEajSz4ochr9M8ZCo= + dependencies: + chalk "^1.1.3" + cli-cursor "^1.0.2" + date-fns "^1.27.2" + figures "^1.7.0" + +"@cypress/request@^2.88.5": + version "2.88.5" + resolved "https://registry.yarnpkg.com/@cypress/request/-/request-2.88.5.tgz#8d7ecd17b53a849cfd5ab06d5abe7d84976375d7" + integrity sha512-TzEC1XMi1hJkywWpRfD2clreTa/Z+lOrXDCxxBTBPEcY5azdPi56A6Xw+O4tWJnaJH3iIE7G5aDXZC6JgRZLcA== + 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" + oauth-sign "~0.9.0" + 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 "^3.3.2" + +"@cypress/xvfb@^1.2.4": + version "1.2.4" + resolved "https://registry.yarnpkg.com/@cypress/xvfb/-/xvfb-1.2.4.tgz#2daf42e8275b39f4aa53c14214e557bd14e7748a" + integrity sha512-skbBzPggOVYCbnGgV+0dmBdW/s77ZkAOXIC1knS8NagwDjBrNC1LuXtQJeiN6l+m7lzmHtaoUw/ctJKdqkG57Q== + dependencies: + debug "^3.1.0" + lodash.once "^4.1.1" + "@endemolshinegroup/cosmiconfig-typescript-loader@^1.0.0": version "1.0.1" resolved "https://registry.yarnpkg.com/@endemolshinegroup/cosmiconfig-typescript-loader/-/cosmiconfig-typescript-loader-1.0.1.tgz#484ee6f4e9209ffde5d3edbdacf03e0bc5ee0c67" @@ -1561,6 +1605,11 @@ resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-8.5.1.tgz#fde96064ca446dec8c55a8c2f130957b070c6e06" integrity sha512-yN7kbciD87WzLGc5539Tn0sApjyiGHAJgKvG9W8C7O+6c7qmoQMfVs0W4bX17eqz6C78QJqqFrtgdK5EWf6Qow== +"@hapi/hoek@^9.0.0": + version "9.1.0" + resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.1.0.tgz#6c9eafc78c1529248f8f4d92b0799a712b6052c6" + integrity sha512-i9YbZPN3QgfighY/1X1Pu118VUz2Fmmhd6b2n0/O8YVgGGfw0FbUYoA97k7FkpGJ+pLCFEDLUmAPPV4D1kpeFw== + "@hapi/joi@^15.0.0", "@hapi/joi@^15.0.1": version "15.1.1" resolved "https://registry.yarnpkg.com/@hapi/joi/-/joi-15.1.1.tgz#c675b8a71296f02833f8d6d243b34c57b8ce19d7" @@ -1578,6 +1627,13 @@ dependencies: "@hapi/hoek" "^8.3.0" +"@hapi/topo@^5.0.0": + version "5.0.0" + resolved "https://registry.yarnpkg.com/@hapi/topo/-/topo-5.0.0.tgz#c19af8577fa393a06e9c77b60995af959be721e7" + integrity sha512-tFJlT47db0kMqVm3H4nQYgn6Pwg10GTZHb1pwmSiv1K4ks6drQOtfEF5ZnPjkvC+y4/bUPHK+bc87QvLcL+WMw== + dependencies: + "@hapi/hoek" "^9.0.0" + "@intervolga/optimize-cssnano-plugin@^1.0.5": version "1.0.6" resolved "https://registry.yarnpkg.com/@intervolga/optimize-cssnano-plugin/-/optimize-cssnano-plugin-1.0.6.tgz#be7c7846128b88f6a9b1d1261a0ad06eb5c0fdf8" @@ -2010,6 +2066,23 @@ dependencies: any-observable "^0.3.0" +"@sideway/address@^4.1.0": + version "4.1.0" + resolved "https://registry.yarnpkg.com/@sideway/address/-/address-4.1.0.tgz#0b301ada10ac4e0e3fa525c90615e0b61a72b78d" + integrity sha512-wAH/JYRXeIFQRsxerIuLjgUu2Xszam+O5xKeatJ4oudShOOirfmsQ1D6LL54XOU2tizpCYku+s1wmU0SYdpoSA== + dependencies: + "@hapi/hoek" "^9.0.0" + +"@sideway/formula@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@sideway/formula/-/formula-3.0.0.tgz#fe158aee32e6bd5de85044be615bc08478a0a13c" + integrity sha512-vHe7wZ4NOXVfkoRb8T5otiENVlT7a3IAiw7H5M2+GO+9CDgcVUUsX1zalAztCmwyOr2RUTGJdgB+ZvSVqmdHmg== + +"@sideway/pinpoint@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@sideway/pinpoint/-/pinpoint-2.0.0.tgz#cff8ffadc372ad29fd3f78277aeb29e632cc70df" + integrity sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ== + "@sindresorhus/is@^0.7.0": version "0.7.0" resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.7.0.tgz#9a06f4f137ee84d7df0460c1fdb1135ffa6c50fd" @@ -2382,6 +2455,16 @@ "@types/express-serve-static-core" "*" "@types/mime" "*" +"@types/sinonjs__fake-timers@^6.0.1": + version "6.0.2" + resolved "https://registry.yarnpkg.com/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-6.0.2.tgz#3a84cf5ec3249439015e14049bd3161419bf9eae" + integrity sha512-dIPoZ3g5gcx9zZEszaxLSVTvMReD3xxyyDnQUjA6IYDG9Ba2AV0otMPs+77sG9ojB4Qr2N2Vk5RnKeuA0X/0bg== + +"@types/sizzle@^2.3.2": + version "2.3.2" + resolved "https://registry.yarnpkg.com/@types/sizzle/-/sizzle-2.3.2.tgz#a811b8c18e2babab7d542b3365887ae2e4d9de47" + integrity sha512-7EJYyKTL7tFR8+gDbB6Wwz/arpGa0Mywk1TJbNzKzHtzbwVmY4HR9WqS5VV7dsBUKQmPNr192jHr/VpBluj/hg== + "@types/source-list-map@*": version "0.1.2" resolved "https://registry.yarnpkg.com/@types/source-list-map/-/source-list-map-0.1.2.tgz#0078836063ffaf17412349bba364087e0ac02ec9" @@ -3866,6 +3949,11 @@ arch@^2.1.1: resolved "https://registry.yarnpkg.com/arch/-/arch-2.1.1.tgz#8f5c2731aa35a30929221bb0640eed65175ec84e" integrity sha512-BLM56aPo9vLLFVa8+/+pJLnrZ7QGGTVHWsCwieAWT9o9K8UeGaQbzZbGoabWLOo2ksBCztoXdqBZBplqLDDCSg== +arch@^2.1.2: + version "2.2.0" + resolved "https://registry.yarnpkg.com/arch/-/arch-2.2.0.tgz#1bc47818f305764f23ab3306b0bfc086c5a29d11" + integrity sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ== + archive-type@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/archive-type/-/archive-type-4.0.0.tgz#f92e72233056dfc6969472749c267bdb046b1d70" @@ -4028,11 +4116,21 @@ async@^2.6.2: dependencies: lodash "^4.17.14" +async@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/async/-/async-3.2.0.tgz#b3a2685c5ebb641d3de02d161002c60fc9f85720" + integrity sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw== + asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= +at-least-node@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2" + integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg== + atob@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" @@ -4073,6 +4171,13 @@ axios@0.21.0: dependencies: follow-redirects "^1.10.0" +axios@^0.19.2: + version "0.19.2" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.19.2.tgz#3ea36c5d8818d0d5f8a8a97a6d36b86cdc00cb27" + integrity sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA== + dependencies: + follow-redirects "1.5.10" + babel-core@^7.0.0-bridge.0: version "7.0.0-bridge.0" resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-7.0.0-bridge.0.tgz#95a492ddd90f9b4e9a4a1da14eb335b87b634ece" @@ -4291,7 +4396,12 @@ bl@^1.0.0: readable-stream "^2.3.5" safe-buffer "^5.1.1" -bluebird@^3.1.1, bluebird@^3.5.5: +blob-util@2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/blob-util/-/blob-util-2.0.2.tgz#3b4e3c281111bb7f11128518006cdc60b403a1eb" + integrity sha512-T7JQa+zsXXEa6/8ZhHcQEW1UFfVM49Ts65uBkFL6fz2QmrElqmbajIDJvuA0tEhRe5eIjpV9ZF+0RfZR9voJFQ== + +bluebird@^3.1.1, bluebird@^3.5.5, bluebird@^3.7.2: version "3.7.2" resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== @@ -4679,6 +4789,11 @@ cacheable-request@^2.1.1: normalize-url "2.0.1" responselike "1.0.2" +cachedir@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/cachedir/-/cachedir-2.3.0.tgz#0c75892a052198f0b21c7c1804d8331edfcae0e8" + integrity sha512-A+Fezp4zxnit6FanDmv9EqXNAi3vt9DWp51/71UEhXukb7QUuvtv9344h91dyAxuTLoSYJFU299qzR3tzwPAhw== + call-me-maybe@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/call-me-maybe/-/call-me-maybe-1.0.1.tgz#26d208ea89e37b5cbde60250a15f031c16a4d66b" @@ -4885,6 +5000,11 @@ chardet@^0.7.0: resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== +check-more-types@^2.24.0: + version "2.24.0" + resolved "https://registry.yarnpkg.com/check-more-types/-/check-more-types-2.24.0.tgz#1420ffb10fd444dcfc79b43891bbfffd32a84600" + integrity sha1-FCD/sQ/URNz8ebQ4kbv//TKoRgA= + check-types@^8.0.3: version "8.0.3" resolved "https://registry.yarnpkg.com/check-types/-/check-types-8.0.3.tgz#3356cca19c889544f2d7a95ed49ce508a0ecf552" @@ -4991,6 +5111,13 @@ cli-boxes@^2.2.0: resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-2.2.0.tgz#538ecae8f9c6ca508e3c3c95b453fe93cb4c168d" integrity sha512-gpaBrMAizVEANOpfZp/EEUixTXDyGt7DFzdK5hU+UbWt/J0lB0w20ncZj59Z9a93xHb9u12zF5BS6i9RKbtg4w== +cli-cursor@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-1.0.2.tgz#64da3f7d56a54412e59794bd62dc35295e8f2987" + integrity sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc= + dependencies: + restore-cursor "^1.0.1" + cli-cursor@^2.0.0, cli-cursor@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5" @@ -5030,6 +5157,16 @@ cli-spinners@^2.0.0: resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.3.0.tgz#0632239a4b5aa4c958610142c34bb7a651fc8df5" integrity sha512-Xs2Hf2nzrvJMFKimOR7YR0QwZ8fc0u98kdtwN1eNAZzNQgH3vK2pXzff6GJtKh7S5hoJ87ECiAiZFS2fb5Ii2w== +cli-table3@~0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.6.0.tgz#b7b1bc65ca8e7b5cef9124e13dc2b21e2ce4faee" + integrity sha512-gnB85c3MGC7Nm9I/FkiasNBOKjOiO1RNuXXarQms37q4QMpWdlbBgD/VnOStA2faG1dpXMv31RFApjX1/QdgWQ== + dependencies: + object-assign "^4.1.0" + string-width "^4.2.0" + optionalDependencies: + colors "^1.1.2" + cli-truncate@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-0.2.1.tgz#9f15cfbb0705005369216c626ac7d05ab90dd574" @@ -5289,6 +5426,11 @@ commander@^2.18.0, commander@^2.20.0, commander@~2.20.3: resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== +commander@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-5.1.0.tgz#46abbd1652f8e059bddaef99bbdcb2ad9cf179ae" + integrity sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg== + commander@~2.19.0: version "2.19.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.19.0.tgz#f6198aa84e5b83c46054b94ddedbfed5ee9ff12a" @@ -5341,7 +5483,7 @@ concat-map@0.0.1: resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= -concat-stream@^1.5.0: +concat-stream@^1.5.0, concat-stream@^1.6.2: version "1.6.2" resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== @@ -5841,6 +5983,57 @@ cyclist@^1.0.1: resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9" integrity sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk= +cypress-file-upload@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/cypress-file-upload/-/cypress-file-upload-4.1.1.tgz#952713c8104ab7008de99c65bd63f74b244fe4df" + integrity sha512-tX6UhuJ63rNgjdzxglpX+ZYf/bM6PDhFMtt1qCBljLtAgdearqyfD1AHqyh59rOHCjfM+bf6FA3o9b/mdaX6pw== + dependencies: + mime "^2.4.4" + +cypress@6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/cypress/-/cypress-6.0.0.tgz#57050773c61e8fe1e5c9871cc034c616fcacded9" + integrity sha512-A/w9S15xGxX5UVeAQZacKBqaA0Uqlae9e5WMrehehAdFiLOZj08IgSVZOV8YqA9OH9Z0iBOnmsEkK3NNj43VrA== + dependencies: + "@cypress/listr-verbose-renderer" "^0.4.1" + "@cypress/request" "^2.88.5" + "@cypress/xvfb" "^1.2.4" + "@types/sinonjs__fake-timers" "^6.0.1" + "@types/sizzle" "^2.3.2" + arch "^2.1.2" + blob-util "2.0.2" + bluebird "^3.7.2" + cachedir "^2.3.0" + chalk "^4.1.0" + check-more-types "^2.24.0" + cli-table3 "~0.6.0" + commander "^5.1.0" + common-tags "^1.8.0" + debug "^4.1.1" + eventemitter2 "^6.4.2" + execa "^4.0.2" + executable "^4.1.1" + extract-zip "^1.7.0" + fs-extra "^9.0.1" + getos "^3.2.1" + is-ci "^2.0.0" + is-installed-globally "^0.3.2" + lazy-ass "^1.6.0" + listr "^0.14.3" + lodash "^4.17.19" + log-symbols "^4.0.0" + minimist "^1.2.5" + moment "^2.27.0" + ospath "^1.2.2" + pretty-bytes "^5.4.1" + ramda "~0.26.1" + request-progress "^3.0.0" + supports-color "^7.2.0" + tmp "~0.2.1" + untildify "^4.0.0" + url "^0.11.0" + yauzl "^2.10.0" + dashdash@^1.12.0: version "1.14.1" resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" @@ -5877,13 +6070,20 @@ debounce@^1.2.0: resolved "https://registry.yarnpkg.com/debounce/-/debounce-1.2.0.tgz#44a540abc0ea9943018dc0eaa95cce87f65cd131" integrity sha512-mYtLl1xfZLi1m4RtQYlZgJUNQjl4ZxVnHzIR8nLLgi4q1YT8o/WM+MK/f8yfcc9s5Ir5zRaPZyZU6xs1Syoocg== -debug@2.6.9, debug@^2.2.0, debug@^2.3.3: +debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.9: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== dependencies: ms "2.0.0" +debug@=3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" + integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g== + dependencies: + ms "2.0.0" + debug@^3.1.0, debug@^3.1.1, debug@^3.2.5, debug@^3.2.6: version "3.2.6" resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" @@ -6768,6 +6968,11 @@ event-pubsub@4.3.0: resolved "https://registry.yarnpkg.com/event-pubsub/-/event-pubsub-4.3.0.tgz#f68d816bc29f1ec02c539dc58c8dd40ce72cb36e" integrity sha512-z7IyloorXvKbFx9Bpie2+vMJKKx1fH1EN5yiTfp8CiLOTptSYy1g8H4yDpGlEdshL1PBiFtBHepF2cNsqeEeFQ== +eventemitter2@^6.4.2: + version "6.4.3" + resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-6.4.3.tgz#35c563619b13f3681e7eb05cbdaf50f56ba58820" + integrity sha512-t0A2msp6BzOf+QAcI6z9XMktLj52OjGQg+8SJH6v5+3uxNpWYRR3wQmfA+6xtMU9kOC59qk9licus5dYcrYkMQ== + eventemitter3@^3.1.0: version "3.1.2" resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-3.1.2.tgz#2d3d48f9c346698fce83a85d7d664e98535df6e7" @@ -6878,7 +7083,7 @@ execa@^3.2.0, execa@^3.3.0: signal-exit "^3.0.2" strip-final-newline "^2.0.0" -execa@^4.0.0: +execa@^4.0.0, execa@^4.0.2: version "4.1.0" resolved "https://registry.yarnpkg.com/execa/-/execa-4.1.0.tgz#4e5491ad1572f2f17a77d388c6c857135b22847a" integrity sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA== @@ -6893,6 +7098,18 @@ execa@^4.0.0: signal-exit "^3.0.2" strip-final-newline "^2.0.0" +executable@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/executable/-/executable-4.1.1.tgz#41532bff361d3e57af4d763b70582db18f5d133c" + integrity sha512-8iA79xD3uAch729dUG8xaaBBFGaEa0wdD2VkYLFHwlqosEj/jT66AzcreRDSgV7ehnNLBW2WR5jIXwGKjVdTLg== + dependencies: + pify "^2.2.0" + +exit-hook@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/exit-hook/-/exit-hook-1.1.1.tgz#f05ca233b48c05d54fff07765df8507e95c02ff8" + integrity sha1-8FyiM7SMBdVP/wd2XfhQfpXAL/g= + exit@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" @@ -7039,6 +7256,16 @@ extract-stack@^1.0.0: resolved "https://registry.yarnpkg.com/extract-stack/-/extract-stack-1.0.0.tgz#b97acaf9441eea2332529624b732fc5a1c8165fa" integrity sha1-uXrK+UQe6iMyUpYktzL8WhyBZfo= +extract-zip@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-1.7.0.tgz#556cc3ae9df7f452c493a0cfb51cc30277940927" + integrity sha512-xoh5G1W/PB0/27lXgMQyIhP5DSY/LhoCsOyZgb+6iMmRtCwVBo55uKaMoEYrDCKQhWvqEip5ZPKAc6eFNyf/MA== + dependencies: + concat-stream "^1.6.2" + debug "^2.6.9" + mkdirp "^0.5.4" + yauzl "^2.10.0" + extsprintf@1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" @@ -7049,6 +7276,11 @@ extsprintf@^1.2.0: resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8= +faker@5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/faker/-/faker-5.1.0.tgz#e10fa1dec4502551aee0eb771617a7e7b94692e8" + integrity sha512-RrWKFSSA/aNLP0g3o2WW1Zez7/MnMr7xkiZmoCfAGZmdkDQZ6l2KtuXHN5XjdvpRjDl8+3vf+Rrtl06Z352+Mw== + fast-deep-equal@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49" @@ -7358,6 +7590,13 @@ flush-write-stream@^1.0.0: inherits "^2.0.3" readable-stream "^2.3.6" +follow-redirects@1.5.10: + version "1.5.10" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.10.tgz#7b7a9f9aea2fdff36786a94ff643ed07f4ff5e2a" + integrity sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ== + dependencies: + debug "=3.1.0" + follow-redirects@^1.0.0, follow-redirects@^1.10.0: version "1.13.0" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.0.tgz#b42e8d93a2a7eea5ed88633676d6597bc8e384db" @@ -7467,6 +7706,16 @@ fs-extra@^8.1.0: jsonfile "^4.0.0" universalify "^0.1.0" +fs-extra@^9.0.1: + version "9.0.1" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.0.1.tgz#910da0062437ba4c39fedd863f1675ccfefcb9fc" + integrity sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ== + dependencies: + at-least-node "^1.0.0" + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^1.0.0" + fs-minipass@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.0.0.tgz#a6415edab02fae4b9e9230bc87ee2e4472003cd1" @@ -7621,6 +7870,13 @@ get-value@^2.0.3, get-value@^2.0.6: resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" integrity sha1-3BXKHGcjh8p2vTesCjlbogQqLCg= +getos@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/getos/-/getos-3.2.1.tgz#0134d1f4e00eb46144c5a9c0ac4dc087cbb27dc5" + integrity sha512-U56CfOK17OKgTVqozZjUKNdkfEv6jk5WISBJ8SHoagjE6L69zOwl3Z+O8myjY9MEW3i2HPWQBt/LTbCgcC973Q== + dependencies: + async "^3.2.0" + getpass@^0.1.1: version "0.1.7" resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" @@ -7718,6 +7974,13 @@ global-dirs@^0.1.0: dependencies: ini "^1.3.4" +global-dirs@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-2.0.1.tgz#acdf3bb6685bcd55cb35e8a052266569e9469201" + integrity sha512-5HqUqdhkEovj2Of/ms3IeS/EekcO54ytHRLV4PEY2rhRwrHXLQjeVEES0Lhka0xwNDtGYn58wyC4s5+MHsOO6A== + dependencies: + ini "^1.3.5" + globals@^11.1.0: version "11.12.0" resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" @@ -8786,6 +9049,14 @@ is-installed-globally@^0.1.0: global-dirs "^0.1.0" is-path-inside "^1.0.0" +is-installed-globally@^0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.3.2.tgz#fd3efa79ee670d1187233182d5b0a1dd00313141" + integrity sha512-wZ8x1js7Ia0kecP/CHM/3ABkAmujX7WPvQk6uu3Fly/Mk44pySulQpnHG46OMjHGXApINnV4QhY3SWnECO2z5g== + dependencies: + global-dirs "^2.0.1" + is-path-inside "^3.0.1" + is-lower-case@^1.1.0: version "1.1.3" resolved "https://registry.yarnpkg.com/is-lower-case/-/is-lower-case-1.1.3.tgz#7e147be4768dc466db3bfb21cc60b31e6ad69393" @@ -8858,6 +9129,11 @@ is-path-inside@^2.1.0: dependencies: path-is-inside "^1.0.2" +is-path-inside@^3.0.1: + version "3.0.2" + resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.2.tgz#f5220fc82a3e233757291dddc9c5877f2a1f3017" + integrity sha512-/2UGPSgmtqwo1ktx8NDHjuPwZWmHhO+gj0f93EkhLB5RgW9RZevWYYlIkS6zePc6U2WpOdQYIwHe9YC4DWEBVg== + is-plain-obj@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" @@ -9457,6 +9733,17 @@ jest@26.6.3: import-local "^3.0.2" jest-cli "^26.6.3" +joi@^17.1.1: + version "17.3.0" + resolved "https://registry.yarnpkg.com/joi/-/joi-17.3.0.tgz#f1be4a6ce29bc1716665819ac361dfa139fff5d2" + integrity sha512-Qh5gdU6niuYbUIUV5ejbsMiiFmBdw8Kcp8Buj2JntszCkCfxJ9Cz76OtHxOZMPXrt5810iDIXs+n1nNVoquHgg== + dependencies: + "@hapi/hoek" "^9.0.0" + "@hapi/topo" "^5.0.0" + "@sideway/address" "^4.1.0" + "@sideway/formula" "^3.0.0" + "@sideway/pinpoint" "^2.0.0" + js-base64@^2.1.8: version "2.5.1" resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.5.1.tgz#1efa39ef2c5f7980bb1784ade4a8af2de3291121" @@ -9627,6 +9914,15 @@ jsonfile@^4.0.0: optionalDependencies: graceful-fs "^4.1.6" +jsonfile@^6.0.1: + version "6.1.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae" + integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ== + dependencies: + universalify "^2.0.0" + optionalDependencies: + graceful-fs "^4.1.6" + jsonify@~0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" @@ -9710,6 +10006,11 @@ launch-editor@^2.2.1: chalk "^2.3.0" shell-quote "^1.6.1" +lazy-ass@^1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/lazy-ass/-/lazy-ass-1.6.0.tgz#7999655e8646c17f089fdd187d150d3324d54513" + integrity sha1-eZllXoZGwX8In90YfRUNMyTVRRM= + leven@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" @@ -9772,7 +10073,7 @@ listr-verbose-renderer@^0.5.0: date-fns "^1.27.2" figures "^2.0.0" -listr@0.14.3: +listr@0.14.3, listr@^0.14.3: version "0.14.3" resolved "https://registry.yarnpkg.com/listr/-/listr-0.14.3.tgz#2fea909604e434be464c50bddba0d496928fa586" integrity sha512-RmAl7su35BFd/xoMamRjpIE4j3v+L28o8CT5YhAXQJm1fD+1l9ngXY8JAQRJ+tFK2i5njvi0iRUKV09vPwA0iA== @@ -9937,6 +10238,11 @@ lodash.merge@^4.6.1: resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== +lodash.once@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" + integrity sha1-DdOXEhPHxW34gJd9UEyI+0cal6w= + lodash.pickby@4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/lodash.pickby/-/lodash.pickby-4.6.0.tgz#7dea21d8c18d7703a27c704c15d3b84a67e33aff" @@ -10006,6 +10312,13 @@ log-symbols@^2.2.0: dependencies: chalk "^2.0.1" +log-symbols@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.0.0.tgz#69b3cc46d20f448eccdb75ea1fa733d9e821c920" + integrity sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA== + dependencies: + chalk "^4.0.0" + log-update@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/log-update/-/log-update-2.3.0.tgz#88328fd7d1ce7938b29283746f0b1bc126b24708" @@ -10461,7 +10774,7 @@ mkdirp@0.5.1, mkdirp@~0.5.1: dependencies: minimist "0.0.8" -mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@~0.5.0: +mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@^0.5.4, mkdirp@~0.5.0: version "0.5.5" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== @@ -10478,6 +10791,11 @@ moment@2.24.0, moment@^2.22.1, moment@^2.24.0: resolved "https://registry.yarnpkg.com/moment/-/moment-2.24.0.tgz#0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b" integrity sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg== +moment@^2.27.0: + version "2.29.1" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3" + integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ== + move-concurrently@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/move-concurrently/-/move-concurrently-1.0.1.tgz#be2c005fda32e0b29af1f05d7c4b33214c701f92" @@ -11018,6 +11336,11 @@ once@^1.3.0, once@^1.3.1, once@^1.4.0: dependencies: wrappy "1" +onetime@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-1.1.0.tgz#a1f7838f8314c516f05ecefcbc4ccfe04b4ed789" + integrity sha1-ofeDj4MUxRbwXs78vEzP4EtO14k= + onetime@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/onetime/-/onetime-2.0.1.tgz#067428230fd67443b2794b22bba528b6867962d4" @@ -11119,6 +11442,11 @@ os-tmpdir@~1.0.2: resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= +ospath@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/ospath/-/ospath-1.2.2.tgz#1276639774a3f8ef2572f7fe4280e0ea4550c07b" + integrity sha1-EnZjl3Sj+O8lcvf+QoDg6kVQwHs= + p-cancelable@^0.4.0: version "0.4.1" resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-0.4.1.tgz#35f363d67d52081c8d9585e37bcceb7e0bbcb2a0" @@ -11983,6 +12311,11 @@ pretty-bytes@^5.1.0: resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.3.0.tgz#f2849e27db79fb4d6cfe24764fc4134f165989f2" integrity sha512-hjGrh+P926p4R4WbaB6OckyRtO0F0/lQBiT+0gnxjV+5kjPBrfVBFCsCLbMqVQeydvIoouYTCmmEURiH3R1Bdg== +pretty-bytes@^5.4.1: + version "5.4.1" + resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.4.1.tgz#cd89f79bbcef21e3d21eb0da68ffe93f803e884b" + integrity sha512-s1Iam6Gwz3JI5Hweaz4GoCD1WUNUIyzePFy5+Js2hjwGVt2Z79wNN+ZKOZ2vB6C+Xs6njyB84Z1IthQg8d9LxA== + pretty-error@^2.0.2: version "2.1.1" resolved "https://registry.yarnpkg.com/pretty-error/-/pretty-error-2.1.1.tgz#5f4f87c8f91e5ae3f3ba87ab4cf5e03b1a17f1a3" @@ -12193,6 +12526,11 @@ querystringify@^2.1.1: resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.1.1.tgz#60e5a5fd64a7f8bfa4d2ab2ed6fdf4c85bad154e" integrity sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA== +ramda@~0.26.1: + version "0.26.1" + resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.26.1.tgz#8d41351eb8111c55353617fc3bbffad8e4d35d06" + integrity sha512-hLWjpy7EnsDBb0p+Z3B7rPi3GDeRG5ZtiI33kJhTt+ORCd38AbAIjB/9zRIUoeTbE/AVX5ZkU7m6bznsvrf8eQ== + randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5: version "2.1.0" resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" @@ -12507,6 +12845,13 @@ repeating@^2.0.0: dependencies: is-finite "^1.0.0" +request-progress@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/request-progress/-/request-progress-3.0.0.tgz#4ca754081c7fec63f505e4faa825aa06cd669dbe" + integrity sha1-TKdUCBx/7GP1BeT6qCWqBs1mnb4= + dependencies: + throttleit "^1.0.0" + request-promise-core@1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/request-promise-core/-/request-promise-core-1.1.4.tgz#3eedd4223208d419867b78ce815167d10593a22f" @@ -12667,6 +13012,14 @@ responselike@1.0.2: dependencies: lowercase-keys "^1.0.0" +restore-cursor@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-1.0.1.tgz#34661f46886327fed2991479152252df92daa541" + integrity sha1-NGYfRohjJ/7SmRR5FSJS35LapUE= + dependencies: + exit-hook "^1.0.0" + onetime "^1.0.0" + restore-cursor@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf" @@ -12783,6 +13136,13 @@ rxjs@^6.4.0, rxjs@^6.6.0: dependencies: tslib "^1.9.0" +rxjs@^6.5.5: + version "6.6.3" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.3.tgz#8ca84635c4daa900c0d3967a6ee7ac60271ee552" + integrity sha512-trsQc+xYYXZ3urjOiJOuCOa5N3jAZ3eiSpQB5hIT8zGlL2QfnHLJ2r7GMkBGuIausdJN1OneaI6gQlsqNHHmZQ== + dependencies: + tslib "^1.9.0" + safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.2" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" @@ -13745,6 +14105,13 @@ supports-color@^7.0.0, supports-color@^7.1.0: dependencies: has-flag "^4.0.0" +supports-color@^7.2.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + supports-hyperlinks@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/supports-hyperlinks/-/supports-hyperlinks-1.0.1.tgz#71daedf36cc1060ac5100c351bb3da48c29c0ef7" @@ -13977,6 +14344,11 @@ throat@^5.0.0: resolved "https://registry.yarnpkg.com/throat/-/throat-5.0.0.tgz#c5199235803aad18754a667d659b5e72ce16764b" integrity sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA== +throttleit@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/throttleit/-/throttleit-1.0.0.tgz#9e785836daf46743145a5984b6268d828528ac6c" + integrity sha1-nnhYNtr0Z0MUWlmEtiaNgoUorGw= + through2@^2.0.0, through2@^2.0.2, through2@^2.0.3: version "2.0.5" resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd" @@ -14032,6 +14404,13 @@ tmp@^0.0.33: dependencies: os-tmpdir "~1.0.2" +tmp@~0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.1.tgz#8457fc3037dcf4719c251367a1af6500ee1ccf14" + integrity sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ== + dependencies: + rimraf "^3.0.0" + tmpl@1.0.x: version "1.0.4" resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.4.tgz#23640dd7b42d00433911140820e5cf440e521dd1" @@ -14398,6 +14777,16 @@ universalify@^0.1.0: resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== +universalify@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-1.0.0.tgz#b61a1da173e8435b2fe3c67d29b9adf8594bd16d" + integrity sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug== + +universalify@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717" + integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ== + unpipe@1.0.0, unpipe@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" @@ -14416,6 +14805,11 @@ unset-value@^1.0.0: has-value "^0.3.1" isobject "^3.0.0" +untildify@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/untildify/-/untildify-4.0.0.tgz#2bc947b953652487e4600949fb091e3ae8cd919b" + integrity sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw== + unzip-response@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/unzip-response/-/unzip-response-2.0.1.tgz#d2f0f737d16b0615e72a6935ed04214572d56f97" @@ -14875,6 +15269,17 @@ w3c-xmlserializer@^2.0.0: dependencies: xml-name-validator "^3.0.0" +wait-on@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/wait-on/-/wait-on-5.2.0.tgz#6711e74422523279714a36d52cf49fb47c9d9597" + integrity sha512-U1D9PBgGw2XFc6iZqn45VBubw02VsLwnZWteQ1au4hUVHasTZuFSKRzlTB2dqgLhji16YVI8fgpEpwUdCr8B6g== + dependencies: + axios "^0.19.2" + joi "^17.1.1" + lodash "^4.17.19" + minimist "^1.2.5" + rxjs "^6.5.5" + walker@^1.0.7, walker@~1.0.5: version "1.0.7" resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.7.tgz#2f7f9b8fd10d677262b18a884e28d19618e028fb" @@ -15504,7 +15909,7 @@ yarn@^1.21.1: resolved "https://registry.yarnpkg.com/yarn/-/yarn-1.22.4.tgz#01c1197ca5b27f21edc8bc472cd4c8ce0e5a470e" integrity sha512-oYM7hi/lIWm9bCoDMEWgffW8aiNZXCWeZ1/tGy0DWrN6vmzjCXIKu2Y21o8DYVBUtiktwKcNoxyGl/2iKLUNGA== -yauzl@^2.4.2: +yauzl@^2.10.0, yauzl@^2.4.2: version "2.10.0" resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9" integrity sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=
Update your name @@ -116,12 +116,12 @@