From a9f5594a4637f04bbd17d7fe612a2c689a04c8ca Mon Sep 17 00:00:00 2001 From: Abdirizak Obsiye Date: Wed, 9 Feb 2022 13:12:59 +0000 Subject: [PATCH] AAC-329 Setup accessibility testing and linting in Cypress (#874) * Add cypress-axe and axe-core Add cypress-axe as a yarn devDependency Add axe-core as a yarn peerDependency of the cypress package Add import 'cypress-axe' to the support file as it includes commands to be used in testing * Added basic usage of checkA11y Cypress test for sign in spec checks for accessibility issues on page visit To do: Refactor so that we don't have to reproduce long task code on every spec * Fix js linting issues * Add Cypress command to log results to terminal Add a terminal log command that logs cypress test results to the terminal. Refactor code to move the function terminalLog into the above command. Add initial rules to ignore the govuk-phase-banner accessiblity issue because it is Technial Debt * Add accessiblity cypress checks on sign in Add accessibility checks to the page load and dynamic changes on the page during the sign in cypress tests * Added accessibility tests to cookie tests Added axe a11y checks to cookie_settings and cookie_banner Refactored runtime a11y checks into function composed of: checkA11y and injectAxe for ease of use * Added eslint for Javascript Created config for eslint Added Cypress eslint plugin Linted all JS files in repo Cleaned up test specs * Changed validate:js command to ESLint Ran linter on via command on app and cypress folder * Update comment to ESLint from standardjs * Upgrade ruby to 2.7.5 Upgrade ruby to 2.7.5 from 2.7.2 Upgrade circleci ruby docker imgae to 2.7.5 which comes with node version 16.13.0 This is so that the circleci docker image has the node of at least (^12.22.0, ^14.17.0, or >=16.0.0) as described by ESlint. This will allow for ESlint to be installed and work as our javascript linter * Disable eslint error for cypress plugin/index.js * Upgrade ruby to 2.7.5 in Dockerfile * Fix bundle gems issue in docker This is an issue with our docker alpine image and an older docker host. Keeping to apline3.13 fixes the below error Fix for : While executing gem ... (Gem::FilePermissionError) You don't have write permissions for/usr/local/bundle Solution: https://github.com/docker-library/ruby/issues/351 * Configure Cypress Axe impact Disabled checks for minor and moderate issues (only check for serious or critical) Modified custom a11yCheck command to handle config Applied injectAxe to all a11yChecks to avoid losing the instantiation (calling the function multiple times has no impact) Co-authored-by: ivanELEC --- .circleci/config.yml | 2 +- .eslintrc.json | 29 ++ .git-hooks/pre-commit | 2 +- .ruby-version | 2 +- Dockerfile | 2 +- Gemfile | 2 +- Gemfile.lock | 2 +- app/webpack/packs/application.js | 9 +- app/webpack/packs/unlink.js | 45 +-- cypress/.eslintrc | 8 + .../integration/index/cookie_banner.spec.js | 256 ++++++++------- .../integration/index/cookie_settings.spec.js | 106 ++++--- cypress/integration/index/sign_in.spec.js | 162 +++++----- cypress/plugins/index.js | 18 +- cypress/support/commands.js | 66 ++-- cypress/support/index.js | 3 +- docker/Dockerfile | 2 +- package.json | 8 +- yarn.lock | 300 ++++++++++++++---- 19 files changed, 656 insertions(+), 368 deletions(-) create mode 100644 .eslintrc.json create mode 100644 cypress/.eslintrc diff --git a/.circleci/config.yml b/.circleci/config.yml index dbbbe9c6b..18f975760 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -67,7 +67,7 @@ executors: test-executor: working_directory: ~/repo docker: - - image: circleci/ruby:2.7.2-node-browsers + - image: circleci/ruby:2.7.5-node-browsers environment: RAILS_ENV: test DATABASE_URL: postgres://postgres:circleci@127.0.0.1:5432/laa_court_data_ui_test diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 000000000..35f1e8500 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,29 @@ +{ + "env": { + "browser": true, + "es2021": true + }, + "extends": "eslint:recommended", + "parserOptions": { + "ecmaVersion": "latest", + "sourceType": "module" + }, + "rules": { + "indent": [ + "error", + "tab" + ], + "linebreak-style": [ + "error", + "unix" + ], + "quotes": [ + "error", + "double" + ], + "semi": [ + "error", + "always" + ] + } +} diff --git a/.git-hooks/pre-commit b/.git-hooks/pre-commit index 64dda31a7..6867c1dcf 100755 --- a/.git-hooks/pre-commit +++ b/.git-hooks/pre-commit @@ -25,7 +25,7 @@ fi ################################################################################ -# JavaScript linting using standardJS +# JavaScript linting using ESlint # printf '\nLinting JavaScript files...\n' diff --git a/.ruby-version b/.ruby-version index 2eb2fe97a..849c0c47f 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -ruby-2.7.2 +ruby-2.7.5 diff --git a/Dockerfile b/Dockerfile index d8375841f..57ad25ee0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM ruby:2.7.2-alpine3.11 +FROM ruby:2.7.5-alpine3.13 LABEL Ministry of Justice, LAA Get Paid # fail early and print all commands diff --git a/Gemfile b/Gemfile index eda7be4aa..d966606f7 100644 --- a/Gemfile +++ b/Gemfile @@ -3,7 +3,7 @@ source 'https://rubygems.org' git_source(:github) { |repo| "https://github.com/#{repo}.git" } -ruby '2.7.2' +ruby '2.7.5' # Reduces boot times through caching; required in config/boot.rb gem 'bootsnap', '>= 1.7.0', require: false diff --git a/Gemfile.lock b/Gemfile.lock index cf19228a5..aa97fc716 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -527,7 +527,7 @@ DEPENDENCIES webpacker (~> 5.4) RUBY VERSION - ruby 2.7.2p137 + ruby 2.7.5p203 BUNDLED WITH 2.2.15 diff --git a/app/webpack/packs/application.js b/app/webpack/packs/application.js index 6122997cd..08776b13d 100644 --- a/app/webpack/packs/application.js +++ b/app/webpack/packs/application.js @@ -1,12 +1,13 @@ +/* eslint-disable no-undef */ // This file is automatically compiled by Webpack, along with any other files // present in this directory. You're encouraged to place your actual application logic in // a relevant structure within app/javascript and only use these pack files to reference // that code so it'll be compiled. -import '../stylesheets/application.scss' -require('@rails/ujs').start() -require('govuk-frontend').initAll() -require.context('govuk-frontend/govuk/assets') +import "../stylesheets/application.scss"; +require("@rails/ujs").start(); +require("govuk-frontend").initAll(); +require.context("govuk-frontend/govuk/assets"); // require("@rails/activestorage").start(); // require("channels"); diff --git a/app/webpack/packs/unlink.js b/app/webpack/packs/unlink.js index f42632460..0666d07da 100644 --- a/app/webpack/packs/unlink.js +++ b/app/webpack/packs/unlink.js @@ -1,28 +1,29 @@ function Unlinking () { - // rely on `name`` because id changes in error state - // - const unlinkReasonCode = document.getElementsByName('unlink_attempt[reason_code]')[0] - const unlinkOtherReasonText = document.getElementsByName('unlink_attempt[other_reason_text]')[0] - const otherReasonCode = '7' + // rely on `name`` because id changes in error state + // + const unlinkReasonCode = document.getElementsByName("unlink_attempt[reason_code]")[0]; + const unlinkOtherReasonText = document.getElementsByName("unlink_attempt[other_reason_text]")[0]; + const otherReasonCode = "7"; - if (!unlinkReasonCode) { return false } - if (!unlinkReasonCode.value) { hide() } - if (unlinkReasonCode.value === otherReasonCode) { show() } + if (!unlinkReasonCode) { return false; } + if (!unlinkReasonCode.value) { hide(); } + if (unlinkReasonCode.value === otherReasonCode) { show(); } - // TODO: should really rely on a data-text-required flag ir similar set by backend - // - unlinkReasonCode.onchange = function () { - (this.value === otherReasonCode) ? show() : hide() - } + // TODO: should really rely on a data-text-required flag ir similar set by backend + // + unlinkReasonCode.onchange = function () { + (this.value === otherReasonCode) ? show() : hide(); + }; - function show () { - unlinkOtherReasonText.parentElement.classList.remove('govuk-select__conditional--hidden') - unlinkOtherReasonText.parentElement.removeAttribute('aria-hidden') - } + function show () { + unlinkOtherReasonText.parentElement.classList.remove("govuk-select__conditional--hidden"); + unlinkOtherReasonText.parentElement.removeAttribute("aria-hidden"); + } - function hide () { - unlinkOtherReasonText.parentElement.classList.add('govuk-select__conditional--hidden') - unlinkOtherReasonText.parentElement.setAttribute('aria-hidden', 'false') - } + function hide () { + unlinkOtherReasonText.parentElement.classList.add("govuk-select__conditional--hidden"); + unlinkOtherReasonText.parentElement.setAttribute("aria-hidden", "false"); + } } -Unlinking() + +Unlinking(); diff --git a/cypress/.eslintrc b/cypress/.eslintrc new file mode 100644 index 000000000..96a492f68 --- /dev/null +++ b/cypress/.eslintrc @@ -0,0 +1,8 @@ +{ + "plugins": [ + "cypress" + ], + "extends": [ + "plugin:cypress/recommended" + ] +} \ No newline at end of file diff --git a/cypress/integration/index/cookie_banner.spec.js b/cypress/integration/index/cookie_banner.spec.js index 39583523c..66e8ecac1 100644 --- a/cypress/integration/index/cookie_banner.spec.js +++ b/cypress/integration/index/cookie_banner.spec.js @@ -1,119 +1,137 @@ -describe('Cookie banner', () => { - before(() => { - cy.visit('/') - cy.checkEnvBanner() - }) - - const bannerMessageElement = '.govuk-cookie-banner__content > p' - - context('not logged in and javascript enabled', () => { - beforeEach(() => { - cy.visit('/') - }) - - const changeCookingSettings = 'Change your cookie settings' - const rejectedAdditionalCookies = "You've rejected additional cookies" - const hideMessageText = 'Hide this message' - - it('can reject cookie preferences', () => { - cy.getCookie('cookies_preferences_set').should('not.exist') - cy.checkCookieValue('analytics_cookies_set', 'false') - cy.get("[data-cy='reject_cookies']") - .should('contain', 'Reject analytics cookies') - .and('have.attr', 'href').and('include', 'analytics_cookies_set=false&show_confirm_banner=true') - - cy.get("[data-cy='reject_cookies']").click() - cy.get('.govuk-cookie-banner__content > p').should( - 'contain', - rejectedAdditionalCookies - ) - cy.checkCookieValue('cookies_preferences_set', 'true') - cy.checkCookieValue('analytics_cookies_set', 'false') - }) - - it('can accept cookie preferences', () => { - cy.getCookie('cookies_preferences_set').should('not.exist') - cy.checkCookieValue('analytics_cookies_set', 'false') - cy.get("[data-cy='accept_cookies']") - .should('contain', 'Accept analytics cookies') - .and('have.attr', 'href').and('include', 'analytics_cookies_set=true&show_confirm_banner=true') - - cy.get("[data-cy='accept_cookies']").click() - cy.get('.govuk-cookie-banner__content > p').should( - 'contain', - "You've accepted additional cookies." - ) - cy.checkCookieValue('cookies_preferences_set', 'true') - cy.checkCookieValue('analytics_cookies_set', 'true') - }) - - context('cookies accepted', () => { - beforeEach(() => { - cy.get("[data-cy='accept_cookies']") - .should('contain', 'Accept analytics cookies') - .and('have.attr', 'href').and('include', 'analytics_cookies_set=true&show_confirm_banner=true') - cy.get("[data-cy='accept_cookies']").click() - }) - - it('can hide confirmation message', () => { - cy.get(bannerMessageElement) - .should('contain', "You've accepted additional cookies") - cy.get("[data-cy='hide_message']") - .should('contain', hideMessageText) - .and('have.attr', 'href').and('include', '?') - - cy.get("[data-cy='hide_message']").click() - cy.get("[data-cy='hide_message']").should('not.exist') - }) - - it('can go to cookie settings from cookie banner', () => { - cy.get(`${bannerMessageElement} > a`) - .contains(changeCookingSettings, { matchCase: false }) - .and('have.attr', 'href').and('include', '/cookies/settings') - cy.get(`${bannerMessageElement} > a`).click() - cy.get('.govuk-heading-xl').should('contain', changeCookingSettings) - }) - }) - - context('cookies rejected', () => { - beforeEach(() => { - cy.get("[data-cy='reject_cookies']") - .should('contain', 'Reject analytics cookies') - .and('have.attr', 'href').and('include', 'analytics_cookies_set=false&show_confirm_banner=true') - - cy.get("[data-cy='reject_cookies']").click() - }) - - it('can hide confirmation message', () => { - cy.get(bannerMessageElement) - .should('contain', rejectedAdditionalCookies) - cy.get("[data-cy='hide_message']") - .should('contain', hideMessageText) - .and('have.attr', 'href').and('include', '?') - - cy.get("[data-cy='hide_message']").click() - cy.get("[data-cy='hide_message']").should('not.exist') - }) - - it('can go to cookie settings from cookie banner', () => { - cy.get(`${bannerMessageElement} > a`) - .contains(changeCookingSettings, { matchCase: false }) - .and('have.attr', 'href').and('include', '/cookies/settings') - cy.get(`${bannerMessageElement} > a`).click() - cy.get('.govuk-heading-xl').should('contain', changeCookingSettings) - }) - }) - - it('can go to cookie settings', () => { - cy.get("[data-cy='cookie_settings']") - .should('contain', 'Cookie settings') - .and('have.attr', 'href').and('include', '/cookies/settings') - - cy.get("[data-cy='cookie_settings']").click() - cy.get('.govuk-heading-xl').should( - 'contain', - changeCookingSettings - ) - }) - }) -}) +describe("Cookie banner", () => { + before(() => { + cy.visit("/"); + cy.checkEnvBanner(); + }); + + const bannerMessageElement = ".govuk-cookie-banner__content > p"; + + context("not logged in and javascript enabled", () => { + beforeEach(() => { + cy.visit("/"); + }); + + const changeCookingSettings = "Change your cookie settings"; + const rejectedAdditionalCookies = "You've rejected additional cookies"; + const hideMessageText = "Hide this message"; + + it("can reject cookie preferences", () => { + cy.getCookie("cookies_preferences_set").should("not.exist"); + cy.checkCookieValue("analytics_cookies_set", "false"); + cy.get("[data-cy='reject_cookies']") + .should("contain", "Reject analytics cookies") + .and("have.attr", "href") + .and("include", "analytics_cookies_set=false&show_confirm_banner=true"); + cy.get("[data-cy='reject_cookies']").click(); + cy.customA11yCheck(null, cy.a11yLog); + cy.get(".govuk-cookie-banner__content > p").should( + "contain", + rejectedAdditionalCookies + ); + cy.checkCookieValue("cookies_preferences_set", "true"); + cy.checkCookieValue("analytics_cookies_set", "false"); + }); + + it("can accept cookie preferences", () => { + cy.getCookie("cookies_preferences_set").should("not.exist"); + cy.checkCookieValue("analytics_cookies_set", "false"); + cy.get("[data-cy='accept_cookies']") + .should("contain", "Accept analytics cookies") + .and("have.attr", "href") + .and("include", "analytics_cookies_set=true&show_confirm_banner=true"); + cy.get("[data-cy='accept_cookies']").click(); + cy.customA11yCheck(null, cy.a11yLog); + cy.get(".govuk-cookie-banner__content > p").should( + "contain", + "You've accepted additional cookies." + ); + cy.checkCookieValue("cookies_preferences_set", "true"); + cy.checkCookieValue("analytics_cookies_set", "true"); + }); + + context("cookies accepted", () => { + beforeEach(() => { + cy.get("[data-cy='accept_cookies']") + .should("contain", "Accept analytics cookies") + .and("have.attr", "href") + .and( + "include", + "analytics_cookies_set=true&show_confirm_banner=true" + ); + cy.get("[data-cy='accept_cookies']").click(); + }); + + it("can hide confirmation message", () => { + cy.get(bannerMessageElement).should( + "contain", + "You've accepted additional cookies" + ); + cy.get("[data-cy='hide_message']") + .should("contain", hideMessageText) + .and("have.attr", "href") + .and("include", "?"); + + cy.get("[data-cy='hide_message']").click(); + cy.customA11yCheck(null, cy.a11yLog); + cy.get("[data-cy='hide_message']").should("not.exist"); + }); + + it("can go to cookie settings from cookie banner", () => { + cy.get(`${bannerMessageElement} > a`) + .contains(changeCookingSettings, { matchCase: false }) + .and("have.attr", "href") + .and("include", "/cookies/settings"); + cy.get(`${bannerMessageElement} > a`).click(); + cy.get(".govuk-heading-xl").should("contain", changeCookingSettings); + }); + }); + + context("cookies rejected", () => { + beforeEach(() => { + cy.get("[data-cy='reject_cookies']") + .should("contain", "Reject analytics cookies") + .and("have.attr", "href") + .and( + "include", + "analytics_cookies_set=false&show_confirm_banner=true" + ); + + cy.get("[data-cy='reject_cookies']").click(); + }); + + it("can hide confirmation message", () => { + cy.get(bannerMessageElement).should( + "contain", + rejectedAdditionalCookies + ); + cy.get("[data-cy='hide_message']") + .should("contain", hideMessageText) + .and("have.attr", "href") + .and("include", "?"); + + cy.get("[data-cy='hide_message']").click(); + cy.customA11yCheck(null, cy.a11yLog); + cy.get("[data-cy='hide_message']").should("not.exist"); + }); + + it("can go to cookie settings from cookie banner", () => { + cy.get(`${bannerMessageElement} > a`) + .contains(changeCookingSettings, { matchCase: false }) + .and("have.attr", "href") + .and("include", "/cookies/settings"); + cy.get(`${bannerMessageElement} > a`).click(); + cy.get(".govuk-heading-xl").should("contain", changeCookingSettings); + }); + }); + + it("can go to cookie settings", () => { + cy.get("[data-cy='cookie_settings']") + .should("contain", "Cookie settings") + .and("have.attr", "href") + .and("include", "/cookies/settings"); + + cy.get("[data-cy='cookie_settings']").click(); + cy.get(".govuk-heading-xl").should("contain", changeCookingSettings); + }); + }); +}); diff --git a/cypress/integration/index/cookie_settings.spec.js b/cypress/integration/index/cookie_settings.spec.js index 526a57237..aa7c3b267 100644 --- a/cypress/integration/index/cookie_settings.spec.js +++ b/cypress/integration/index/cookie_settings.spec.js @@ -1,51 +1,67 @@ +describe("Cookie settings page", () => { + const successfullySetCookies = "You've set your cookie preferences."; -describe('Cookie settings page', () => { - const successfullySetCookies = "You've set your cookie preferences." + context("not logged in and javascript enabled", () => { + beforeEach(() => { + cy.visit("/"); + cy.get("[data-cy='cookie_settings']") + .should("contain", "Cookie settings") + .should("have.attr", "href") + .and("include", "/cookies/settings"); + cy.get("[data-cy='cookie_settings']").click(); + }); - context('not logged in and javascript enabled', () => { - beforeEach(() => { - cy.visit('/') - cy.get("[data-cy='cookie_settings']") - .should('contain', 'Cookie settings') - .should('have.attr', 'href').and('include', '/cookies/settings') + it("has no detectable a11y violations on load", () => { + cy.customA11yCheck(null, cy.a11yLog); + }); - cy.get("[data-cy='cookie_settings']").click() - }) + context("Cookies storing is set as false", () => { + beforeEach(() => { + cy.getCookie("cookies_preferences_set").should("not.exist"); + cy.checkCookieValue("analytics_cookies_set", "false"); + cy.get("[data-cy='cookie_settings']").click(); + }); - context('Cookies storing is set as false', () => { - beforeEach(() => { - cy.getCookie('cookies_preferences_set').should('not.exist') - cy.checkCookieValue('analytics_cookies_set', 'false') + it("can change cookie settings", () => { + cy.get("input#cookie-analytics-true-field").should( + "have.value", + "true" + ); + cy.get("input#cookie-analytics-true-field").check(); + cy.get("[data-cy='submit-cookies']").click(); + cy.customA11yCheck(null, cy.a11yLog); + cy.get(".govuk-notification-banner__heading").should( + "contain", + successfullySetCookies + ); + cy.checkCookieValue("cookies_preferences_set", "true"); + cy.checkCookieValue("analytics_cookies_set", "true"); + }); + }); - cy.get("[data-cy='cookie_settings']").click() - }) + context("Cookies storing is set as true", () => { + beforeEach(() => { + cy.visit("/"); + cy.get("[data-cy='cookie_settings']").click(); + cy.setCookie("cookies_preferences_set", "true"); + cy.setCookie("analytics_cookies_set", "true"); + }); - it('can change cookie settings', () => { - cy.get('input#cookie-analytics-true-field').should('have.value', 'true') - cy.get('input#cookie-analytics-true-field').check() - cy.get("[data-cy='submit-cookies']").click() - cy.get('.govuk-notification-banner__heading').should('contain', successfullySetCookies) - cy.checkCookieValue('cookies_preferences_set', 'true') - cy.checkCookieValue('analytics_cookies_set', 'true') - }) - }) - - context('Cookies storing is set as true', () => { - beforeEach(() => { - cy.visit('/') - cy.get("[data-cy='cookie_settings']").click() - cy.setCookie('cookies_preferences_set', 'true') - cy.setCookie('analytics_cookies_set', 'true') - }) - - it('can change cookie settings', () => { - cy.get('input#cookie-analytics-false-field').should('have.value', 'false') - cy.get('input#cookie-analytics-false-field').check() - cy.get("[data-cy='submit-cookies']").click() - cy.get('.govuk-notification-banner__heading').should('contain', successfullySetCookies) - cy.checkCookieValue('cookies_preferences_set', 'true') - cy.checkCookieValue('analytics_cookies_set', 'false') - }) - }) - }) -}) + it("can change cookie settings", () => { + cy.get("input#cookie-analytics-false-field").should( + "have.value", + "false" + ); + cy.get("input#cookie-analytics-false-field").check(); + cy.get("[data-cy='submit-cookies']").click(); + cy.customA11yCheck(null, cy.a11yLog); + cy.get(".govuk-notification-banner__heading").should( + "contain", + successfullySetCookies + ); + cy.checkCookieValue("cookies_preferences_set", "true"); + cy.checkCookieValue("analytics_cookies_set", "false"); + }); + }); + }); +}); diff --git a/cypress/integration/index/sign_in.spec.js b/cypress/integration/index/sign_in.spec.js index bf53f73a6..ba35d66f4 100644 --- a/cypress/integration/index/sign_in.spec.js +++ b/cypress/integration/index/sign_in.spec.js @@ -1,86 +1,96 @@ -describe('User Login Page', () => { - before(() => { - cy.visit('/') - cy.checkEnvBanner() - }) +describe("User Login Page", () => { + before(() => { + cy.visit("/"); + cy.checkEnvBanner(); + }); - beforeEach(() => { - cy.visit('/') - }) + beforeEach(() => { + cy.visit("/"); + }); - it('displays the login page', () => { - cy.get('.govuk-heading-xl').should('have.text', 'Sign in') - cy.get('#new_user') - .should('contain', 'Username or email') - .and('contain', 'Password') - cy.get('input#user-login-field').should('exist') - cy.get('input#user-password-field').should('exist') - }) + it("has no detectable a11y violations on load", () => { + cy.customA11yCheck(null, cy.a11yLog); + }); - it('can log in with correct credentials', () => { - cy.fixture('users').then((users) => { - cy.login(users[0].username, users[0].password) - cy.get('.govuk-error-summary__title').should( - 'contain', - 'Signed in successfully.' - ) - }) - }) + it("displays the login page", () => { + cy.get(".govuk-heading-xl").should("have.text", "Sign in"); + cy.get("#new_user") + .should("contain", "Username or email") + .and("contain", "Password"); + cy.get("input#user-login-field").should("exist"); + cy.get("input#user-password-field").should("exist"); + }); - it('cannot log in with incorrect credentials', () => { - cy.login('someone', 'some-password') - cy.get('.govuk-error-summary__title').should( - 'contain', - 'Invalid username or password.' - ) - }) + it("can log in with correct credentials", () => { + cy.fixture("users").then((users) => { + cy.login(users[0].username, users[0].password); + cy.get(".govuk-error-summary__title").should( + "contain", + "Signed in successfully." + ); + cy.customA11yCheck(null, cy.a11yLog); + }); + }); - it('can log in with valid credentials after an invalid attempt', () => { - cy.login('invalid-username', 'invalid-password') - cy.get('.govuk-error-summary__title').should( - 'contain', - 'Invalid username or password.' - ) - cy.fixture('users').then((users) => { - cy.login(users[0].username, users[0].password) - cy.get('.govuk-error-summary__title').should( - 'contain', - 'Signed in successfully.' - ) - }) - }) + it("cannot log in with incorrect credentials", () => { + cy.login("someone", "some-password"); + cy.get(".govuk-error-summary__title").should( + "contain", + "Invalid username or password." + ); + cy.customA11yCheck(null, cy.a11yLog); + }); - context('logged in', () => { - beforeEach(() => { - cy.visit('/') - cy.fixture('users').then((users) => { - cy.login(users[0].username, users[0].password) - }) - }) + it("can log in with valid credentials after an invalid attempt", () => { + cy.login("invalid-username", "invalid-password"); + cy.get(".govuk-error-summary__title").should( + "contain", + "Invalid username or password." + ); + cy.fixture("users").then((users) => { + cy.login(users[0].username, users[0].password); + cy.get(".govuk-error-summary__title").should( + "contain", + "Signed in successfully." + ); + cy.customA11yCheck(null, cy.a11yLog); + }); + }); - it('displays search filters page', () => { - cy.get('.govuk-fieldset__legend').should( - 'contain', - 'Search for' - ) - }) + context("logged in", () => { + beforeEach(() => { + cy.visit("/"); + cy.fixture("users").then((users) => { + cy.login(users[0].username, users[0].password); + }); + }); - it('can log out', () => { - cy.get("[data-method='delete']") - .should('have.text', 'Sign out') - .should('have.attr', 'href').and('include', '/users/sign_out') + it("has no detectable a11y violations on load", () => { + cy.customA11yCheck(null, cy.a11yLog); + }); - cy.get("[data-method='delete']").click() - cy.get('.govuk-error-summary__title').should('contain', 'Signed out successfully.') - }) - }) + it("displays search filters page", () => { + cy.get(".govuk-fieldset__legend").should("contain", "Search for"); + }); - context('logged out', () => { - it('can not see search filters', () => { - cy.get('#main-content').not( - 'contain', - 'Search for' - ) - }) - }) -}) + it("can log out", () => { + cy.get("[data-method='delete']") + .should("have.text", "Sign out") + .should("have.attr", "href") + .and("include", "/users/sign_out"); + + cy.get("[data-method='delete']").click(); + cy.get(".govuk-error-summary__title").should( + "contain", + "Signed out successfully." + ); + cy.customA11yCheck(null, cy.a11yLog); + }); + }); + + context("logged out", () => { + it("can not see search filters", () => { + cy.get("#main-content").not("contain", "Search for"); + }); + }); +}); diff --git a/cypress/plugins/index.js b/cypress/plugins/index.js index 59b2bab6e..d59accc49 100644 --- a/cypress/plugins/index.js +++ b/cypress/plugins/index.js @@ -15,8 +15,18 @@ /** * @type {Cypress.PluginConfig} */ -// eslint-disable-next-line no-unused-vars +// eslint-disable-next-line no-undef, no-unused-vars module.exports = (on, config) => { - // `on` is used to hook into various events Cypress emits - // `config` is the resolved Cypress config -} + on("task", { + log (message) { + console.log(message); + + return null; + }, + table (message) { + console.table(message); + + return null; + } + }); +}; diff --git a/cypress/support/commands.js b/cypress/support/commands.js index c0162620d..c1bc89607 100644 --- a/cypress/support/commands.js +++ b/cypress/support/commands.js @@ -1,22 +1,52 @@ // attempts to login to vcd with given username (string) and password (strings) -Cypress.Commands.add('login', (username, password) => { - cy.get('input#user-login-field') - .clear() - .type(username) - cy.get('input#user-password-field') - .clear() - .type(password) - cy.get('*[data-cy="login-submit"]') - .click() -}) +Cypress.Commands.add("login", (username, password) => { + cy.get("input#user-login-field").clear().type(username); + cy.get("input#user-password-field").clear().type(password); + cy.get("*[data-cy=\"login-submit\"]").click(); +}); // checks whether appropriate banner is at the top of the site -Cypress.Commands.add('checkEnvBanner', () => { - it('displays the dev banner', () => { - cy.get('.govuk-phase-banner__content').should('contain', Cypress.env('environment')) - }) -}) +Cypress.Commands.add("checkEnvBanner", () => { + it("displays the dev banner", () => { + cy.get(".govuk-phase-banner__content").should( + "contain", + Cypress.env("environment") + ); + }); +}); -Cypress.Commands.add('checkCookieValue', (name, value) => { - cy.getCookie(name).should('have.property', 'value', value) -}) +Cypress.Commands.add("checkCookieValue", (name, value) => { + cy.getCookie(name).should("have.property", "value", value); +}); + +Cypress.Commands.add("customA11yCheck", (selector, logCallback) => { + const options = { + includedImpacts: ["critical","serious"] + }; + cy.injectAxe(); + cy.checkA11y(selector, options, logCallback); +}); + +Cypress.Commands.add("a11yLog", (violations) => { + cy.task( + "log", + `${violations.length} accessibility violation${ + violations.length === 1 ? "" : "s" + } ${violations.length === 1 ? "was" : "were"} detected` + ); + // pluck specific keys to keep the table readable + const violationData = violations.map( + ({ id, impact, description, nodes }) => ({ + id, + impact, + description, + nodes: nodes.length + }) + ); + + cy.task("table", violationData); +}); + +Cypress.Commands.add("axeConfig",() => { + +}); diff --git a/cypress/support/index.js b/cypress/support/index.js index d68db96df..9c2c87197 100644 --- a/cypress/support/index.js +++ b/cypress/support/index.js @@ -14,7 +14,8 @@ // *********************************************************** // Import commands.js using ES2015 syntax: -import './commands' +import "./commands"; +import "cypress-axe"; // Alternatively you can use CommonJS syntax: // require('./commands') diff --git a/docker/Dockerfile b/docker/Dockerfile index cb326aa14..aba4a10e1 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,4 +1,4 @@ -FROM ruby:2.7.2-alpine +FROM ruby:2.7.5-alpine3.13 LABEL Organisation="Ministry of Justice" LABEL Team="LAA Get Paid" LABEL Contact="" diff --git a/package.json b/package.json index 03d0a2cc3..6993fca54 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "private": true, "version": "1.21.1", "scripts": { - "validate:js": "standard", + "validate:js": "eslint app cypress", "validate:scss": "stylelint **/*.scss", "cypress:open": "cypress open", "cypress:run": "cypress run", @@ -19,7 +19,11 @@ }, "devDependencies": { "@webpack-cli/serve": "^1.6.0", - "cypress": "^8.7.0", + "axe-core": "^4.4.0", + "cypress": "^9.4.1", + "cypress-axe": "^0.14.0", + "eslint": "^8.8.0", + "eslint-plugin-cypress": "^2.12.1", "standard": "^16.0.4", "stylelint": "^13.13.1", "stylelint-config-gds": "^0.1.0", diff --git a/yarn.lock b/yarn.lock index a88226da4..26a9778b1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1151,10 +1151,10 @@ resolved "https://registry.yarnpkg.com/@csstools/convert-colors/-/convert-colors-1.4.0.tgz#ad495dc41b12e75d588c6db8b9834f08fa131eb7" integrity sha512-5a6wqoJV/xEdbRNKVo6I4hO3VjyDq//8q2f9I6PBAvMesJHFauXDorcNCsr9RzvsZnaWi5NYCcfyqP1QeFHFbw== -"@cypress/request@^2.88.6": - version "2.88.6" - resolved "https://registry.yarnpkg.com/@cypress/request/-/request-2.88.6.tgz#a970dd675befc6bdf8a8921576c01f51cc5798e9" - integrity sha512-z0UxBE/+qaESAHY9p9sM2h8Y4XqtsbDCt0/DPOrqA/RZgKi4PkxdpXyK4wCCnSk1xHqWHZZAE+gV6aDAR6+caQ== +"@cypress/request@^2.88.10": + version "2.88.10" + resolved "https://registry.yarnpkg.com/@cypress/request/-/request-2.88.10.tgz#b66d76b07f860d3a4b8d7a0604d020c662752cce" + integrity sha512-Zp7F+R93N0yZyG34GutyTNr+okam7s/Fzc1+i3kcqOP8vk6OuajuE9qZJ6Rs+10/1JFtXFYMdyarnU1rZuJesg== dependencies: aws-sign2 "~0.7.0" aws4 "^1.8.0" @@ -1163,8 +1163,7 @@ extend "~3.0.2" forever-agent "~0.6.1" form-data "~2.3.2" - har-validator "~5.1.3" - http-signature "~1.2.0" + http-signature "~1.3.6" is-typedarray "~1.0.0" isstream "~0.1.2" json-stringify-safe "~5.0.1" @@ -1205,6 +1204,21 @@ minimatch "^3.0.4" strip-json-comments "^3.1.1" +"@eslint/eslintrc@^1.0.5": + version "1.0.5" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.0.5.tgz#33f1b838dbf1f923bfa517e008362b78ddbbf318" + integrity sha512-BLxsnmK3KyPunz5wmCCpqy0YelEoxxGmH73Is+Z74oOTMtExcjkr3dDR6quwrjh1YspA8DH9gnX1o069KiS9AQ== + dependencies: + ajv "^6.12.4" + debug "^4.3.2" + espree "^9.2.0" + globals "^13.9.0" + ignore "^4.0.6" + import-fresh "^3.2.1" + js-yaml "^4.1.0" + minimatch "^3.0.4" + strip-json-comments "^3.1.1" + "@hapi/hoek@^9.0.0": version "9.2.1" resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.2.1.tgz#9551142a1980503752536b5050fd99f4a7f13b17" @@ -1217,6 +1231,20 @@ dependencies: "@hapi/hoek" "^9.0.0" +"@humanwhocodes/config-array@^0.9.2": + version "0.9.3" + resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.9.3.tgz#f2564c744b387775b436418491f15fce6601f63e" + integrity sha512-3xSMlXHh03hCcCmFc0rbKp3Ivt2PFEJnQUJDDMTJQ2wkECZWdq4GePs2ctc5H8zV+cHPaq8k2vU8mrQjA6iHdQ== + dependencies: + "@humanwhocodes/object-schema" "^1.2.1" + debug "^4.1.1" + minimatch "^3.0.4" + +"@humanwhocodes/object-schema@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" + integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== + "@ministryofjustice/frontend@^0.2.6": version "0.2.6" resolved "https://registry.yarnpkg.com/@ministryofjustice/frontend/-/frontend-0.2.6.tgz#95ff647e2cd23db291650447fdea33d89fc4d84a" @@ -1397,10 +1425,10 @@ resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.1.tgz#d8f1c0d0dc23afad6dc16a9e993a0865774b4065" integrity sha512-xoDlM2S4ortawSWORYqsdU+2rxdh4LRW9ytc3zmT37RIKQh6IHyKwwtKhKis9ah8ol07DCkZxPt8BBvPjC6v4g== -"@types/sinonjs__fake-timers@^6.0.2": - version "6.0.3" - resolved "https://registry.yarnpkg.com/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-6.0.3.tgz#79df6f358ae8f79e628fe35a63608a0ea8e7cf08" - integrity sha512-E1dU4fzC9wN2QK2Cr1MLCfyHM8BoNnRFvuf45LYMPNDA+WqbNzC45S4UzPxvp1fFJ1rvSGU0bPvdd35VLmXG8g== +"@types/sinonjs__fake-timers@8.1.1": + version "8.1.1" + resolved "https://registry.yarnpkg.com/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.1.tgz#b49c2c70150141a15e0fa7e79cf1f92a72934ce3" + integrity sha512-0kSuKjAS0TrGLJ0M/+8MaFkGsQhZpB6pxOmvS3K8FYI72K//YmdfoW9X2qPsAKh1mkwxGD5zib9s1FIFed6E8g== "@types/sizzle@^2.3.2": version "2.3.3" @@ -1614,6 +1642,11 @@ acorn@^7.4.0: resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== +acorn@^8.7.0: + version "8.7.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.0.tgz#90951fde0f8f09df93549481e5fc141445b791cf" + integrity sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ== + aggregate-error@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a" @@ -1632,7 +1665,7 @@ ajv-keywords@^3.1.0, ajv-keywords@^3.4.1, ajv-keywords@^3.5.2: resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d" integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ== -ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.3, ajv@^6.12.4, ajv@^6.12.5: +ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.4, ajv@^6.12.5: version "6.12.6" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== @@ -1731,6 +1764,11 @@ argparse@^1.0.7: dependencies: sprintf-js "~1.0.2" +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + arr-diff@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" @@ -1906,6 +1944,11 @@ aws4@^1.8.0: resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.11.0.tgz#d61f46d83b2519250e2784daf5b09479a8b41c59" integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA== +axe-core@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.4.0.tgz#f93be7f81017eb8bedeb1859cc8092cc918d2dc8" + integrity sha512-btWy2rze3NnxSSxb7LtNhPYYFrRoFBfjiGzmSc/5Hu47wApO2KNXjP/w7Nv2Uz/Fyr/pfEiwOkcXhDxu0jz5FA== + axios@^0.21.1: version "0.21.4" resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.4.tgz#c67b90dc0568e5c1cf2b0b858c43ba28e2eda575" @@ -1978,7 +2021,7 @@ balanced-match@^2.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-2.0.0.tgz#dc70f920d78db8b858535795867bf48f820633d9" integrity sha512-1ugUSr8BHXRnK23KfuYS+gVMC3LB8QGH9W1iGtDPsNWoQbgtXSExkBu2aDR4epiGWZOjZsj6lDl/N/AqqTC3UA== -base64-js@^1.0.2: +base64-js@^1.0.2, base64-js@^1.3.1: version "1.5.1" resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== @@ -2231,6 +2274,14 @@ buffer@^4.3.0: ieee754 "^1.1.4" isarray "^1.0.0" +buffer@^5.6.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" + integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.1.13" + builtin-status-codes@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8" @@ -2512,15 +2563,14 @@ cli-cursor@^3.1.0: dependencies: restore-cursor "^3.1.0" -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== +cli-table3@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.6.1.tgz#36ce9b7af4847f288d3cdd081fbd09bf7bd237b8" + integrity sha512-w0q/enDHhPLq44ovMGdQeeDLvwxwavsJX7oQGYt/LrBlYsyaxyDnp6z3QzFut/6kLLKnlcUVJLrpB7KBfgG/RA== dependencies: - object-assign "^4.1.0" string-width "^4.2.0" optionalDependencies: - colors "^1.1.2" + colors "1.4.0" cli-truncate@^2.1.0: version "2.1.0" @@ -2613,7 +2663,7 @@ colorette@^2.0.10: resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.12.tgz#7938ab254e7bb1bba29b0fd1b4cc168889ca4d74" integrity sha512-lHID0PU+NtFzeNCwTL6JzUKdb6kDpyEjrwTD1H0cDZswTbsjLh2wTV2Eo2sNZLc0oSg0a5W1AI4Nj7bX4iIdjA== -colors@^1.1.2: +colors@1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78" integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA== @@ -3062,24 +3112,30 @@ cyclist@^1.0.1: resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9" integrity sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk= -cypress@^8.7.0: - version "8.7.0" - resolved "https://registry.yarnpkg.com/cypress/-/cypress-8.7.0.tgz#2ee371f383d8f233d3425b6cc26ddeec2668b6da" - integrity sha512-b1bMC3VQydC6sXzBMFnSqcvwc9dTZMgcaOzT0vpSD+Gq1yFc+72JDWi55sfUK5eIeNLAtWOGy1NNb6UlhMvB+Q== +cypress-axe@^0.14.0: + version "0.14.0" + resolved "https://registry.yarnpkg.com/cypress-axe/-/cypress-axe-0.14.0.tgz#5f5e70fb36b8cb3ba73a8ba01e9262ff1268d5e2" + integrity sha512-7Rdjnko0MjggCmndc1wECAkvQBIhuy+DRtjF7bd5YPZRFvubfMNvrxfqD8PWQmxm7MZE0ffS4Xr43V6ZmvLopg== + +cypress@^9.4.1: + version "9.4.1" + resolved "https://registry.yarnpkg.com/cypress/-/cypress-9.4.1.tgz#1a4ba706435829c24b7edf350c2b059e05da9084" + integrity sha512-+JgMG9uT+QFx97JU9kOHE3jO3+0UdkQ9H1oCBiC7A74qme7Jkdy2sYDBCPjjGczutnWnGUTMRlwiNMP/Uq6LrQ== dependencies: - "@cypress/request" "^2.88.6" + "@cypress/request" "^2.88.10" "@cypress/xvfb" "^1.2.4" "@types/node" "^14.14.31" - "@types/sinonjs__fake-timers" "^6.0.2" + "@types/sinonjs__fake-timers" "8.1.1" "@types/sizzle" "^2.3.2" arch "^2.2.0" blob-util "^2.0.2" bluebird "^3.7.2" + buffer "^5.6.0" cachedir "^2.3.0" chalk "^4.1.0" check-more-types "^2.24.0" cli-cursor "^3.1.0" - cli-table3 "~0.6.0" + cli-table3 "~0.6.1" commander "^5.1.0" common-tags "^1.8.0" dayjs "^1.10.4" @@ -3102,12 +3158,11 @@ cypress@^8.7.0: ospath "^1.2.2" pretty-bytes "^5.6.0" proxy-from-env "1.0.0" - ramda "~0.27.1" request-progress "^3.0.0" + semver "^7.3.2" supports-color "^8.1.1" tmp "~0.2.1" untildify "^4.0.0" - url "^0.11.0" yauzl "^2.10.0" dashdash@^1.12.0: @@ -3561,6 +3616,11 @@ escape-string-regexp@^1.0.5: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= +escape-string-regexp@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + eslint-config-standard-jsx@10.0.0: version "10.0.0" resolved "https://registry.yarnpkg.com/eslint-config-standard-jsx/-/eslint-config-standard-jsx-10.0.0.tgz#dc24992661325a2e480e2c3091d669f19034e18d" @@ -3587,6 +3647,13 @@ eslint-module-utils@^2.6.2: debug "^3.2.7" pkg-dir "^2.0.0" +eslint-plugin-cypress@^2.12.1: + version "2.12.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-cypress/-/eslint-plugin-cypress-2.12.1.tgz#9aeee700708ca8c058e00cdafe215199918c2632" + integrity sha512-c2W/uPADl5kospNDihgiLc7n87t5XhUbFDoTl6CfVkmG+kDAb5Ux10V9PoLPu9N+r7znpc+iQlcmAqT1A/89HA== + dependencies: + globals "^11.12.0" + eslint-plugin-es@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/eslint-plugin-es/-/eslint-plugin-es-3.0.1.tgz#75a7cdfdccddc0589934aeeb384175f221c57893" @@ -3668,6 +3735,14 @@ eslint-scope@^5.1.1: esrecurse "^4.3.0" estraverse "^4.1.1" +eslint-scope@^7.1.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.1.0.tgz#c1f6ea30ac583031f203d65c73e723b01298f153" + integrity sha512-aWwkhnS0qAXqNOgKOK0dJ2nvzEbhEvpy8OlJ9kZ0FeZnA6zpjv1/Vei+puGFFX7zkPCkHHXb7IDX3A+7yPrRWg== + dependencies: + esrecurse "^4.3.0" + estraverse "^5.2.0" + eslint-utils@^2.0.0, eslint-utils@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-2.1.0.tgz#d2de5e03424e707dc10c74068ddedae708741b27" @@ -3675,6 +3750,13 @@ eslint-utils@^2.0.0, eslint-utils@^2.1.0: dependencies: eslint-visitor-keys "^1.1.0" +eslint-utils@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-3.0.0.tgz#8aebaface7345bb33559db0a1f13a1d2d48c3672" + integrity sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA== + dependencies: + eslint-visitor-keys "^2.0.0" + eslint-visitor-keys@^1.1.0, eslint-visitor-keys@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e" @@ -3685,6 +3767,52 @@ eslint-visitor-keys@^2.0.0: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz#21fdc8fbcd9c795cc0321f0563702095751511a8" integrity sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ== +eslint-visitor-keys@^3.1.0, eslint-visitor-keys@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.2.0.tgz#6fbb166a6798ee5991358bc2daa1ba76cc1254a1" + integrity sha512-IOzT0X126zn7ALX0dwFiUQEdsfzrm4+ISsQS8nukaJXwEyYKRSnEIIDULYg1mCtGp7UUXgfGl7BIolXREQK+XQ== + +eslint@^8.8.0: + version "8.8.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.8.0.tgz#9762b49abad0cb4952539ffdb0a046392e571a2d" + integrity sha512-H3KXAzQGBH1plhYS3okDix2ZthuYJlQQEGE5k0IKuEqUSiyu4AmxxlJ2MtTYeJ3xB4jDhcYCwGOg2TXYdnDXlQ== + dependencies: + "@eslint/eslintrc" "^1.0.5" + "@humanwhocodes/config-array" "^0.9.2" + ajv "^6.10.0" + chalk "^4.0.0" + cross-spawn "^7.0.2" + debug "^4.3.2" + doctrine "^3.0.0" + escape-string-regexp "^4.0.0" + eslint-scope "^7.1.0" + eslint-utils "^3.0.0" + eslint-visitor-keys "^3.2.0" + espree "^9.3.0" + esquery "^1.4.0" + esutils "^2.0.2" + fast-deep-equal "^3.1.3" + file-entry-cache "^6.0.1" + functional-red-black-tree "^1.0.1" + glob-parent "^6.0.1" + globals "^13.6.0" + ignore "^5.2.0" + import-fresh "^3.0.0" + imurmurhash "^0.1.4" + is-glob "^4.0.0" + js-yaml "^4.1.0" + json-stable-stringify-without-jsonify "^1.0.1" + levn "^0.4.1" + lodash.merge "^4.6.2" + minimatch "^3.0.4" + natural-compare "^1.4.0" + optionator "^0.9.1" + regexpp "^3.2.0" + strip-ansi "^6.0.1" + strip-json-comments "^3.1.0" + text-table "^0.2.0" + v8-compile-cache "^2.0.3" + eslint@~7.18.0: version "7.18.0" resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.18.0.tgz#7fdcd2f3715a41fe6295a16234bd69aed2c75e67" @@ -3737,6 +3865,15 @@ espree@^7.3.0, espree@^7.3.1: acorn-jsx "^5.3.1" eslint-visitor-keys "^1.3.0" +espree@^9.2.0, espree@^9.3.0: + version "9.3.0" + resolved "https://registry.yarnpkg.com/espree/-/espree-9.3.0.tgz#c1240d79183b72aaee6ccfa5a90bc9111df085a8" + integrity sha512-d/5nCsb0JcqsSEeQzFZ8DH1RmxPcglRWh24EFTlUEmCKoehXGdpsx0RkHDubqUI8LSAIKMQp4r9SzQ3n+sm4HQ== + dependencies: + acorn "^8.7.0" + acorn-jsx "^5.3.1" + eslint-visitor-keys "^3.1.0" + esprima@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" @@ -3749,6 +3886,13 @@ esquery@^1.2.0: dependencies: estraverse "^5.1.0" +esquery@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.4.0.tgz#2148ffc38b82e8c7057dfed48425b3e61f0f24a5" + integrity sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w== + dependencies: + estraverse "^5.1.0" + esrecurse@^4.1.0, esrecurse@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" @@ -3947,7 +4091,7 @@ extsprintf@^1.2.0: resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8= -fast-deep-equal@^3.1.1: +fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== @@ -4314,7 +4458,7 @@ getpass@^0.1.1: dependencies: assert-plus "^1.0.0" -glob-parent@^3.1.0, glob-parent@^5.0.0, glob-parent@^5.1.0, glob-parent@^5.1.2, glob-parent@~5.1.2: +glob-parent@^3.1.0, glob-parent@^5.0.0, glob-parent@^5.1.0, glob-parent@^5.1.2, glob-parent@^6.0.1, glob-parent@~5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== @@ -4356,7 +4500,7 @@ global-prefix@^3.0.0: kind-of "^6.0.2" which "^1.3.1" -globals@^11.1.0: +globals@^11.1.0, globals@^11.12.0: version "11.12.0" resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== @@ -4368,6 +4512,13 @@ globals@^12.1.0: dependencies: type-fest "^0.8.1" +globals@^13.6.0, globals@^13.9.0: + version "13.12.1" + resolved "https://registry.yarnpkg.com/globals/-/globals-13.12.1.tgz#ec206be932e6c77236677127577aa8e50bf1c5cb" + integrity sha512-317dFlgY2pdJZ9rspXDks7073GpDmXdfbM3vYYp0HAMKGDh1FfWPleI2ljVNLQX5M5lXcAslTcPTrOrMEFOjyw== + dependencies: + type-fest "^0.20.2" + globby@^11.0.1: version "11.0.4" resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.4.tgz#2cbaff77c2f2a62e71e9b2813a67b97a3a3001a5" @@ -4424,19 +4575,6 @@ handle-thing@^2.0.0: resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-2.0.1.tgz#857f79ce359580c340d43081cc648970d0bb234e" integrity sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg== -har-schema@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" - integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI= - -har-validator@~5.1.3: - version "5.1.5" - resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.5.tgz#1f0803b9f8cb20c0fa13822df1ecddb36bde1efd" - integrity sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w== - dependencies: - ajv "^6.12.3" - har-schema "^2.0.0" - hard-rejection@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/hard-rejection/-/hard-rejection-2.1.0.tgz#1c6eda5c1685c63942766d79bb40ae773cecd883" @@ -4654,14 +4792,14 @@ http-proxy@^1.18.1: follow-redirects "^1.0.0" requires-port "^1.0.0" -http-signature@~1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" - integrity sha1-muzZJRFHcvPZW2WmCruPfBj7rOE= +http-signature@~1.3.6: + version "1.3.6" + resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.3.6.tgz#cb6fbfdf86d1c974f343be94e87f7fc128662cf9" + integrity sha512-3adrsD6zqo4GsTqtO7FyrejHNv+NgiIfAfv68+jVlFmSr9OGy7zrxONceFRLKvnnZA5jbxQBX1u9PpB6Wi32Gw== dependencies: assert-plus "^1.0.0" - jsprim "^1.2.2" - sshpk "^1.7.0" + jsprim "^2.0.2" + sshpk "^1.14.1" https-browserify@^1.0.0: version "1.0.0" @@ -4692,7 +4830,7 @@ icss-utils@^4.0.0, icss-utils@^4.1.1: dependencies: postcss "^7.0.14" -ieee754@^1.1.4: +ieee754@^1.1.13, ieee754@^1.1.4: version "1.2.1" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== @@ -4712,6 +4850,11 @@ ignore@^5.1.1, ignore@^5.1.4, ignore@^5.1.8: resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.8.tgz#f150a8b50a34289b33e22f5889abd4d8016f0e57" integrity sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw== +ignore@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a" + integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ== + import-cwd@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/import-cwd/-/import-cwd-2.1.0.tgz#aa6cf36e722761285cb371ec6519f53e2435b0a9" @@ -5314,6 +5457,13 @@ js-yaml@^3.13.1, js-yaml@^3.14.1: argparse "^1.0.7" esprima "^4.0.0" +js-yaml@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + dependencies: + argparse "^2.0.1" + jsbn@~0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" @@ -5349,10 +5499,10 @@ json-schema-traverse@^1.0.0: resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== -json-schema@0.2.3: - version "0.2.3" - resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" - integrity sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM= +json-schema@0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.4.0.tgz#f7de4cf6efab838ebaeb3236474cbba5a1930ab5" + integrity sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA== json-stable-stringify-without-jsonify@^1.0.1: version "1.0.1" @@ -5387,14 +5537,14 @@ jsonfile@^6.0.1: optionalDependencies: graceful-fs "^4.1.6" -jsprim@^1.2.2: - version "1.4.1" - resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" - integrity sha1-MT5mvB5cwG5Di8G3SZwuXFastqI= +jsprim@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-2.0.2.tgz#77ca23dbcd4135cd364800d22ff82c2185803d4d" + integrity sha512-gqXddjPqQ6G40VdnI6T6yObEC+pDNvyP95wdQhkWkg7crHH3km5qP1FsOXEkzEQwnz6gz5qGTn1c2Y52wP3OyQ== dependencies: assert-plus "1.0.0" extsprintf "1.3.0" - json-schema "0.2.3" + json-schema "0.4.0" verror "1.10.0" "jsx-ast-utils@^2.4.1 || ^3.0.0": @@ -5561,6 +5711,11 @@ lodash.memoize@^4.1.2: resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4= +lodash.merge@^4.6.2: + version "4.6.2" + 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" @@ -6204,7 +6359,7 @@ num2fraction@^1.2.2: resolved "https://registry.yarnpkg.com/num2fraction/-/num2fraction-1.2.2.tgz#6f682b6a027a4e9ddfa4564cd2589d1d4e669ede" integrity sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4= -object-assign@^4.1.0, object-assign@^4.1.1: +object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= @@ -7541,11 +7696,6 @@ quick-lru@^4.0.1: resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-4.0.1.tgz#5b8878f113a58217848c6482026c73e1ba57727f" integrity sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g== -ramda@~0.27.1: - version "0.27.1" - resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.27.1.tgz#66fc2df3ef873874ffc2da6aa8984658abacf5c9" - integrity sha512-PgIdVpn5y5Yns8vqb8FzBUEYn98V3xcPgawAkkgj0YJ0qDsnHCiNmZYfOGMgOvoB0eWFLpYbhxUR3mxfDIMvpw== - randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5, randombytes@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" @@ -7730,6 +7880,11 @@ regexpp@^3.0.0, regexpp@^3.1.0: resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.1.0.tgz#206d0ad0a5648cffbdb8ae46438f3dc51c9f78e2" integrity sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q== +regexpp@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2" + integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg== + regexpu-core@^4.7.1: version "4.7.1" resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-4.7.1.tgz#2dea5a9a07233298fbf0db91fa9abc4c6e0f8ad6" @@ -8339,10 +8494,10 @@ sprintf-js@~1.0.2: resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= -sshpk@^1.7.0: - version "1.16.1" - resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877" - integrity sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg== +sshpk@^1.14.1: + version "1.17.0" + resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.17.0.tgz#578082d92d4fe612b13007496e543fa0fbcbe4c5" + integrity sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ== dependencies: asn1 "~0.2.3" assert-plus "^1.0.0" @@ -8980,6 +9135,11 @@ type-fest@^0.18.0: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.18.1.tgz#db4bc151a4a2cf4eebf9add5db75508db6cc841f" integrity sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw== +type-fest@^0.20.2: + version "0.20.2" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" + integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== + type-fest@^0.21.3: version "0.21.3" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37"