From eac0d9fb265f07be12028fabecdcdddf7be1c164 Mon Sep 17 00:00:00 2001 From: qntm Date: Sun, 21 Aug 2022 20:39:58 +0100 Subject: [PATCH] Stop using Enzyme (#151) * Stop doing snapshot testing * Stop using shallow * A lot better * Scrap Enzyme * Eliminate e2e class names * The last testing fixes? * Removals --- .dotfile | 1 - babel.config.json | 10 +- config/{jestSetup.js => jestSetup.ts} | 5 +- cypress/e2e/play.cy.js | 30 +- jest.config.js | 5 +- package-lock.json | 1493 ++++----- package.json | 10 +- src/components/Game/Game.css | 94 +- src/components/Game/Game.spec.tsx | 926 ++---- src/components/Game/Game.tsx | 147 +- src/components/Well/Well.css | 20 +- src/components/Well/Well.spec.tsx | 20 +- src/components/Well/Well.tsx | 6 + .../Well/__snapshots__/Well.spec.tsx.snap | 2662 ----------------- src/enemy-ais/hatetris-ai.spec.tsx | 21 +- src/enemy-ais/lovetris-ai.spec.tsx | 23 +- tsconfig.json | 5 +- 17 files changed, 1031 insertions(+), 4447 deletions(-) delete mode 100644 .dotfile rename config/{jestSetup.js => jestSetup.ts} (55%) delete mode 100644 src/components/Well/__snapshots__/Well.spec.tsx.snap diff --git a/.dotfile b/.dotfile deleted file mode 100644 index 8b13789..0000000 --- a/.dotfile +++ /dev/null @@ -1 +0,0 @@ - diff --git a/babel.config.json b/babel.config.json index 18f3008..81ed6f9 100644 --- a/babel.config.json +++ b/babel.config.json @@ -1,3 +1,11 @@ { - "presets": ["@babel/preset-env", "@babel/preset-react", "@babel/preset-typescript"] + "presets": [ + ["@babel/preset-env", { + "targets": { + "node": "current" + } + }], + "@babel/preset-react", + "@babel/preset-typescript" + ] } diff --git a/config/jestSetup.js b/config/jestSetup.ts similarity index 55% rename from config/jestSetup.js rename to config/jestSetup.ts index e61ca67..fba4eb0 100644 --- a/config/jestSetup.js +++ b/config/jestSetup.ts @@ -1,7 +1,4 @@ -const Enzyme = require('enzyme') -const EnzymeAdapterReact16 = require('enzyme-adapter-react-16') - -Enzyme.configure({ adapter: new EnzymeAdapterReact16() }) +import '@testing-library/jest-dom' // Need for clipboard testing navigator.clipboard = { diff --git a/cypress/e2e/play.cy.js b/cypress/e2e/play.cy.js index edac729..a9984f8 100644 --- a/cypress/e2e/play.cy.js +++ b/cypress/e2e/play.cy.js @@ -6,14 +6,14 @@ describe('HATETRIS', () => { cy.contains('you\'re playing HATETRIS by qntm') cy.get('button').contains('start new game').click() - cy.get('.e2e__score').contains('0') + cy.get('[data-testid="score"]').contains('0') for (let i = 0; i < 90; i++) { cy.get('body').trigger('keydown', { key: 'Down' }) } - cy.get('.e2e__score').contains('0') - cy.get('.e2e__replay-out').contains('௨ටໃݹ௨ටໃݹठ') + cy.get('[data-testid="score"]').contains('0') + cy.get('[data-testid="replay-out"]').contains('௨ටໃݹ௨ටໃݹठ') }) it('plays an easy game', () => { @@ -23,30 +23,30 @@ describe('HATETRIS', () => { cy.get('button').contains('select AI').click() cy.get('button').contains('all 4x1 pieces').click() cy.get('button').contains('start new game').click() - cy.get('.e2e__score').contains('0') + cy.get('[data-testid="score"]').contains('0') for (let i = -5; i <= 4; i++) { // Rotate until vertical - cy.get('.e2e__up').click() + cy.get('button').contains('⟳').click() // Move to correct column if (i < 0) { for (let j = 0; j > i; j--) { - cy.get('.e2e__left').click() + cy.get('button').contains('←').click() } } else { for (let j = 0; j < i; j++) { - cy.get('.e2e__right').click() + cy.get('button').contains('→').click() } } // Drop for (let j = 0; j < 17; j++) { - cy.get('.e2e__down').click() + cy.get('button').contains('↓').click() } } - cy.get('.e2e__score').contains('4') + cy.get('[data-testid="score"]').contains('4') // no replay }) @@ -63,21 +63,21 @@ describe('HATETRIS', () => { // Move to correct column if (i < 0) { for (let j = 0; j > i; j--) { - cy.get('.e2e__left').click() + cy.get('button').contains('←').click() } } else { for (let j = 0; j < i; j++) { - cy.get('.e2e__right').click() + cy.get('button').contains('→').click() } } // Drop for (let j = 0; j < 18; j++) { - cy.get('.e2e__down').click() + cy.get('button').contains('↓').click() } } - cy.get('.e2e__score').contains('2') + cy.get('[data-testid="score"]').contains('2') }) it('plays a Base2048 replay', () => { @@ -92,9 +92,9 @@ describe('HATETRIS', () => { cy.get('button').contains('show a replay').click() for (let score = 0; score <= 11; score++) { - cy.get('.e2e__score').contains(String(score), { timeout: 20000 }) + cy.get('[data-testid="score"]').contains(String(score), { timeout: 20000 }) } - cy.get('.e2e__replay-out').contains(replay) + cy.get('[data-testid="replay-out"]').contains(replay) }) }) diff --git a/jest.config.js b/jest.config.js index 0acd529..a10faf4 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,9 +1,6 @@ module.exports = { collectCoverage: true, - setupFilesAfterEnv: ['/config/jestSetup.js'], - snapshotSerializers: [ - 'enzyme-to-json/serializer' - ], + setupFilesAfterEnv: ['/config/jestSetup.ts'], moduleNameMapper: { '\\.css$': '/__mocks__/styleMock.js' }, diff --git a/package-lock.json b/package-lock.json index b9c33c0..9fe98d1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,9 @@ "@babel/preset-env": "^7.15.0", "@babel/preset-react": "^7.14.5", "@babel/preset-typescript": "^7.14.5", - "@types/enzyme": "^3.10.9", + "@testing-library/jest-dom": "^5.16.5", + "@testing-library/react": "^12.1.5", + "@testing-library/user-event": "^14.4.3", "@types/jest": "^27.0.1", "@types/react": "^18.0.8", "@types/react-dom": "^18.0.3", @@ -26,9 +28,6 @@ "classnames": "^2.2.6", "css-loader": "^6.2.0", "cypress": "^10.3.0", - "enzyme": "^3.11.0", - "enzyme-adapter-react-16": "^1.15.4", - "enzyme-to-json": "^3.5.0", "eslint": "^8.16.0", "eslint-config-standard": "^17.0.0", "eslint-plugin-import": "^2.24.2", @@ -54,6 +53,12 @@ "webpack-hot-middleware": "^2.25.0" } }, + "node_modules/@adobe/css-tools": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.0.1.tgz", + "integrity": "sha512-+u76oB43nOHrF4DDWRLWDCtci7f3QJoEBigemIdIeTi1ODqjx6Tad9NCVnPRwewWlKkVab5PlK8DCtPTyX7S8g==", + "dev": true + }, "node_modules/@babel/code-frame": { "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz", @@ -2916,6 +2921,235 @@ "@sinonjs/commons": "^1.7.0" } }, + "node_modules/@testing-library/dom": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-8.17.1.tgz", + "integrity": "sha512-KnH2MnJUzmFNPW6RIKfd+zf2Wue8mEKX0M3cpX6aKl5ZXrJM1/c/Pc8c2xDNYQCnJO48Sm5ITbMXgqTr3h4jxQ==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^4.2.0", + "aria-query": "^5.0.0", + "chalk": "^4.1.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.4.4", + "pretty-format": "^27.0.2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@testing-library/dom/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@testing-library/dom/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@testing-library/dom/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@testing-library/dom/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/@testing-library/dom/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@testing-library/dom/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@testing-library/jest-dom": { + "version": "5.16.5", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-5.16.5.tgz", + "integrity": "sha512-N5ixQ2qKpi5OLYfwQmUb/5mSV9LneAcaUfp32pn4yCnpb8r/Yz0pXFPck21dIicKmi+ta5WRAknkZCfA8refMA==", + "dev": true, + "dependencies": { + "@adobe/css-tools": "^4.0.1", + "@babel/runtime": "^7.9.2", + "@types/testing-library__jest-dom": "^5.9.1", + "aria-query": "^5.0.0", + "chalk": "^3.0.0", + "css.escape": "^1.5.1", + "dom-accessibility-api": "^0.5.6", + "lodash": "^4.17.15", + "redent": "^3.0.0" + }, + "engines": { + "node": ">=8", + "npm": ">=6", + "yarn": ">=1" + } + }, + "node_modules/@testing-library/jest-dom/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@testing-library/jest-dom/node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@testing-library/jest-dom/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@testing-library/jest-dom/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/@testing-library/jest-dom/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@testing-library/jest-dom/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@testing-library/react": { + "version": "12.1.5", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-12.1.5.tgz", + "integrity": "sha512-OfTXCJUFgjd/digLUuPxa0+/3ZxsQmE7ub9kcbW/wi96Bh3o/p5vrETcBGfP17NWPGqeYYl5LTRpwyGoMC4ysg==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.12.5", + "@testing-library/dom": "^8.0.0", + "@types/react-dom": "<18.0.0" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "react": "<18.0.0", + "react-dom": "<18.0.0" + } + }, + "node_modules/@testing-library/react/node_modules/@types/react": { + "version": "17.0.48", + "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.48.tgz", + "integrity": "sha512-zJ6IYlJ8cYYxiJfUaZOQee4lh99mFihBoqkOSEGV+dFi9leROW6+PgstzQ+w3gWTnUfskALtQPGHK6dYmPj+2A==", + "dev": true, + "dependencies": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@testing-library/react/node_modules/@types/react-dom": { + "version": "17.0.17", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.17.tgz", + "integrity": "sha512-VjnqEmqGnasQKV0CWLevqMTXBYG9GbwuE6x3VetERLh0cq2LTptFE73MrQi2S7GkKXCf2GgwItB/melLnxfnsg==", + "dev": true, + "dependencies": { + "@types/react": "^17" + } + }, + "node_modules/@testing-library/user-event": { + "version": "14.4.3", + "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.4.3.tgz", + "integrity": "sha512-kCUc5MEwaEMakkO5x7aoD+DLi02ehmEM2QCGWvNqAS1dV/fAvORWEjnjsEIvml59M7Y5kCkWN6fCCyPOe8OL6Q==", + "dev": true, + "engines": { + "node": ">=12", + "npm": ">=6" + }, + "peerDependencies": { + "@testing-library/dom": ">=7.21.4" + } + }, "node_modules/@tootallnate/once": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", @@ -2925,6 +3159,12 @@ "node": ">= 10" } }, + "node_modules/@types/aria-query": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-4.2.2.tgz", + "integrity": "sha512-HnYpAE1Y6kRyKM/XkEuiRQhTHvkzMBurTHnpFLYLBGPIylZNPs9jJcuOOYWxPLJCSEtmZT0Y8rHDokKN7rRTig==", + "dev": true + }, "node_modules/@types/babel__core": { "version": "7.1.19", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.19.tgz", @@ -2966,25 +3206,6 @@ "@babel/types": "^7.3.0" } }, - "node_modules/@types/cheerio": { - "version": "0.22.30", - "resolved": "https://registry.npmjs.org/@types/cheerio/-/cheerio-0.22.30.tgz", - "integrity": "sha512-t7ZVArWZlq3dFa9Yt33qFBQIK4CQd1Q3UJp0V+UhP6vgLWLM6Qug7vZuRSGXg45zXeB1Fm5X2vmBkEX58LV2Tw==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/enzyme": { - "version": "3.10.11", - "resolved": "https://registry.npmjs.org/@types/enzyme/-/enzyme-3.10.11.tgz", - "integrity": "sha512-LEtC7zXsQlbGXWGcnnmOI7rTyP+i1QzQv4Va91RKXDEukLDaNyxu0rXlfMiGEhJwfgTPCTb0R+Pnlj//oM9e/w==", - "dev": true, - "dependencies": { - "@types/cheerio": "*", - "@types/react": "*" - } - }, "node_modules/@types/eslint": { "version": "8.4.1", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.1.tgz", @@ -3157,6 +3378,15 @@ "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", "dev": true }, + "node_modules/@types/testing-library__jest-dom": { + "version": "5.14.5", + "resolved": "https://registry.npmjs.org/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.14.5.tgz", + "integrity": "sha512-SBwbxYoyPIvxHbeHxTZX2Pe/74F/tX2/D3mMvzabdeJ25bBojfW0TyB8BHrbq/9zaaKICJZjLP+8r6AeZMFCuQ==", + "dev": true, + "dependencies": { + "@types/jest": "*" + } + }, "node_modules/@types/tough-cookie": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.2.tgz", @@ -3719,35 +3949,6 @@ "node": ">=8" } }, - "node_modules/airbnb-prop-types": { - "version": "2.16.0", - "resolved": "https://registry.npmjs.org/airbnb-prop-types/-/airbnb-prop-types-2.16.0.tgz", - "integrity": "sha512-7WHOFolP/6cS96PhKNrslCLMYAI8yB1Pp6u6XmxozQOiZbsI5ycglZr5cHhBFfuRcQQjzCMith5ZPZdYiJCxUg==", - "dev": true, - "dependencies": { - "array.prototype.find": "^2.1.1", - "function.prototype.name": "^1.1.2", - "is-regex": "^1.1.0", - "object-is": "^1.1.2", - "object.assign": "^4.1.0", - "object.entries": "^1.1.2", - "prop-types": "^15.7.2", - "prop-types-exact": "^1.2.0", - "react-is": "^16.13.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - }, - "peerDependencies": { - "react": "^0.14 || ^15.0.0 || ^16.0.0-alpha" - } - }, - "node_modules/airbnb-prop-types/node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "dev": true - }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -3911,6 +4112,15 @@ "sprintf-js": "~1.0.2" } }, + "node_modules/aria-query": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.0.0.tgz", + "integrity": "sha512-V+SM7AbUwJ+EBnB8+DXs0hPZHO0W6pqBcc0dW90OwtVG02PswOu/teuARoLQjdDOH+t9pJgGnW5/Qmouf3gPJg==", + "dev": true, + "engines": { + "node": ">=6.0" + } + }, "node_modules/array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", @@ -3945,39 +4155,6 @@ "node": ">=8" } }, - "node_modules/array.prototype.filter": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/array.prototype.filter/-/array.prototype.filter-1.0.1.tgz", - "integrity": "sha512-Dk3Ty7N42Odk7PjU/Ci3zT4pLj20YvuVnneG/58ICM6bt4Ij5kZaJTVQ9TSaWaIECX2sFyz4KItkVZqHNnciqw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.0", - "es-array-method-boxes-properly": "^1.0.0", - "is-string": "^1.0.7" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array.prototype.find": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/array.prototype.find/-/array.prototype.find-2.1.2.tgz", - "integrity": "sha512-00S1O4ewO95OmmJW7EesWfQlrCrLEL8kZ40w3+GkLX2yTt0m2ggcePPa2uHPJ9KUmJvwRq+lCV9bD8Yim23x/Q==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/array.prototype.flat": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.5.tgz", @@ -4777,49 +4954,6 @@ "node": ">= 0.8.0" } }, - "node_modules/cheerio": { - "version": "1.0.0-rc.10", - "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.10.tgz", - "integrity": "sha512-g0J0q/O6mW8z5zxQ3A8E8J1hUgp4SMOvEoW/x84OwyHKe/Zccz83PVT4y5Crcr530FV6NgmKI1qvGTKVl9XXVw==", - "dev": true, - "dependencies": { - "cheerio-select": "^1.5.0", - "dom-serializer": "^1.3.2", - "domhandler": "^4.2.0", - "htmlparser2": "^6.1.0", - "parse5": "^6.0.1", - "parse5-htmlparser2-tree-adapter": "^6.0.1", - "tslib": "^2.2.0" - }, - "engines": { - "node": ">= 6" - }, - "funding": { - "url": "https://github.com/cheeriojs/cheerio?sponsor=1" - } - }, - "node_modules/cheerio-select": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-1.5.0.tgz", - "integrity": "sha512-qocaHPv5ypefh6YNxvnbABM07KMxExbtbfuJoIie3iZXX1ERwYmJcIiRrr9H05ucQP1k28dav8rpdDgjQd8drg==", - "dev": true, - "dependencies": { - "css-select": "^4.1.3", - "css-what": "^5.0.1", - "domelementtype": "^2.2.0", - "domhandler": "^4.2.0", - "domutils": "^2.7.0" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/cheerio/node_modules/tslib": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", - "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", - "dev": true - }, "node_modules/chokidar": { "version": "3.5.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", @@ -5265,6 +5399,12 @@ "url": "https://github.com/sponsors/fb55" } }, + "node_modules/css.escape": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", + "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", + "dev": true + }, "node_modules/cssesc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", @@ -5675,12 +5815,6 @@ "node": ">=8" } }, - "node_modules/discontinuous-range": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/discontinuous-range/-/discontinuous-range-1.0.0.tgz", - "integrity": "sha1-44Mx8IRLukm5qctxx3FYWqsbxlo=", - "dev": true - }, "node_modules/doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -5693,6 +5827,12 @@ "node": ">=6.0.0" } }, + "node_modules/dom-accessibility-api": { + "version": "0.5.14", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.14.tgz", + "integrity": "sha512-NMt+m9zFMPZe0JcY9gN224Qvk6qLIdqex29clBvc/y75ZBX9YA9wNK3frsYvu2DI1xcCIwxwnX+TlsJ2DSOADg==", + "dev": true + }, "node_modules/dom-converter": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz", @@ -5904,145 +6044,6 @@ "node": ">=4" } }, - "node_modules/enzyme": { - "version": "3.11.0", - "resolved": "https://registry.npmjs.org/enzyme/-/enzyme-3.11.0.tgz", - "integrity": "sha512-Dw8/Gs4vRjxY6/6i9wU0V+utmQO9kvh9XLnz3LIudviOnVYDEe2ec+0k+NQoMamn1VrjKgCUOWj5jG/5M5M0Qw==", - "dev": true, - "dependencies": { - "array.prototype.flat": "^1.2.3", - "cheerio": "^1.0.0-rc.3", - "enzyme-shallow-equal": "^1.0.1", - "function.prototype.name": "^1.1.2", - "has": "^1.0.3", - "html-element-map": "^1.2.0", - "is-boolean-object": "^1.0.1", - "is-callable": "^1.1.5", - "is-number-object": "^1.0.4", - "is-regex": "^1.0.5", - "is-string": "^1.0.5", - "is-subset": "^0.1.1", - "lodash.escape": "^4.0.1", - "lodash.isequal": "^4.5.0", - "object-inspect": "^1.7.0", - "object-is": "^1.0.2", - "object.assign": "^4.1.0", - "object.entries": "^1.1.1", - "object.values": "^1.1.1", - "raf": "^3.4.1", - "rst-selector-parser": "^2.2.3", - "string.prototype.trim": "^1.2.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/enzyme-adapter-react-16": { - "version": "1.15.6", - "resolved": "https://registry.npmjs.org/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.15.6.tgz", - "integrity": "sha512-yFlVJCXh8T+mcQo8M6my9sPgeGzj85HSHi6Apgf1Cvq/7EL/J9+1JoJmJsRxZgyTvPMAqOEpRSu/Ii/ZpyOk0g==", - "dev": true, - "dependencies": { - "enzyme-adapter-utils": "^1.14.0", - "enzyme-shallow-equal": "^1.0.4", - "has": "^1.0.3", - "object.assign": "^4.1.2", - "object.values": "^1.1.2", - "prop-types": "^15.7.2", - "react-is": "^16.13.1", - "react-test-renderer": "^16.0.0-0", - "semver": "^5.7.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - }, - "peerDependencies": { - "enzyme": "^3.0.0", - "react": "^16.0.0-0", - "react-dom": "^16.0.0-0" - } - }, - "node_modules/enzyme-adapter-react-16/node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "dev": true - }, - "node_modules/enzyme-adapter-react-16/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true, - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/enzyme-adapter-utils": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/enzyme-adapter-utils/-/enzyme-adapter-utils-1.14.0.tgz", - "integrity": "sha512-F/z/7SeLt+reKFcb7597IThpDp0bmzcH1E9Oabqv+o01cID2/YInlqHbFl7HzWBl4h3OdZYedtwNDOmSKkk0bg==", - "dev": true, - "dependencies": { - "airbnb-prop-types": "^2.16.0", - "function.prototype.name": "^1.1.3", - "has": "^1.0.3", - "object.assign": "^4.1.2", - "object.fromentries": "^2.0.3", - "prop-types": "^15.7.2", - "semver": "^5.7.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - }, - "peerDependencies": { - "react": "0.13.x || 0.14.x || ^15.0.0-0 || ^16.0.0-0" - } - }, - "node_modules/enzyme-adapter-utils/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true, - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/enzyme-shallow-equal": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/enzyme-shallow-equal/-/enzyme-shallow-equal-1.0.4.tgz", - "integrity": "sha512-MttIwB8kKxypwHvRynuC3ahyNc+cFbR8mjVIltnmzQ0uKGqmsfO4bfBuLxb0beLNPhjblUEYvEbsg+VSygvF1Q==", - "dev": true, - "dependencies": { - "has": "^1.0.3", - "object-is": "^1.1.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/enzyme-to-json": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/enzyme-to-json/-/enzyme-to-json-3.6.2.tgz", - "integrity": "sha512-Ynm6Z6R6iwQ0g2g1YToz6DWhxVnt8Dy1ijR2zynRKxTyBGA8rCDXU3rs2Qc4OKvUvc2Qoe1bcFK6bnPs20TrTg==", - "dev": true, - "dependencies": { - "@types/cheerio": "^0.22.22", - "lodash": "^4.17.21", - "react-is": "^16.12.0" - }, - "engines": { - "node": ">=6.0.0" - }, - "peerDependencies": { - "enzyme": "^3.4.0" - } - }, - "node_modules/enzyme-to-json/node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "dev": true - }, "node_modules/error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -6086,12 +6087,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/es-array-method-boxes-properly": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz", - "integrity": "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==", - "dev": true - }, "node_modules/es-module-lexer": { "version": "0.9.3", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz", @@ -7735,39 +7730,12 @@ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", "dev": true }, - "node_modules/function.prototype.name": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", - "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.0", - "functions-have-names": "^1.2.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/functional-red-black-tree": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", "dev": true }, - "node_modules/functions-have-names": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.2.tgz", - "integrity": "sha512-bLgc3asbWdwPbx2mNk2S49kmJCuQeu0nfmaOgbs8WIyzzkw3r4htszdIi9Q9EMezDPTYuJx2wvjZ/EwgAthpnA==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -8083,19 +8051,6 @@ "node": ">=0.10.0" } }, - "node_modules/html-element-map": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/html-element-map/-/html-element-map-1.3.1.tgz", - "integrity": "sha512-6XMlxrAFX4UEEGxctfFnmrFaaZFNf9i5fNuV5wZ3WWQ4FVaNP1aX1LkX9j2mfEx1NpjeE/rL3nmgEn23GdFmrg==", - "dev": true, - "dependencies": { - "array.prototype.filter": "^1.0.0", - "call-bind": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/html-encoding-sniffer": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", @@ -8824,12 +8779,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-subset": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-subset/-/is-subset-0.1.1.tgz", - "integrity": "sha1-ilkRfZMt4d4A8kX83TnOQ/HpOaY=", - "dev": true - }, "node_modules/is-symbol": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", @@ -11511,24 +11460,6 @@ "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=", "dev": true }, - "node_modules/lodash.escape": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-4.0.1.tgz", - "integrity": "sha1-yQRGkMIeBClL6qUXcS/e0fqI3pg=", - "dev": true - }, - "node_modules/lodash.flattendeep": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", - "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=", - "dev": true - }, - "node_modules/lodash.isequal": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", - "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=", - "dev": true - }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -11748,6 +11679,15 @@ "node": ">=10" } }, + "node_modules/lz-string": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.4.4.tgz", + "integrity": "sha512-0ckx7ZHRPqb0oUm8zNr+90mtf9DQB60H1wMCjBtfi62Kl3a7JbHob6gA2bC+xRvZoOL+1hzUK8jeuEIQE8svEQ==", + "dev": true, + "bin": { + "lz-string": "bin/bin.js" + } + }, "node_modules/make-dir": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", @@ -11891,6 +11831,15 @@ "node": ">=6" } }, + "node_modules/min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/mini-css-extract-plugin": { "version": "2.5.3", "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.5.3.tgz", @@ -11978,13 +11927,7 @@ "node_modules/minimist": { "version": "1.2.6", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", - "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", - "dev": true - }, - "node_modules/moo": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/moo/-/moo-0.5.1.tgz", - "integrity": "sha512-I1mnb5xn4fO80BH9BLcF0yLypy2UKl+Cb01Fu0hJRkJjlCRtxZMWkTdAtDd5ZqCOxtCkhmRwyI57vWT+1iZ67w==", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", "dev": true }, "node_modules/ms": { @@ -12011,34 +11954,6 @@ "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", "dev": true }, - "node_modules/nearley": { - "version": "2.20.1", - "resolved": "https://registry.npmjs.org/nearley/-/nearley-2.20.1.tgz", - "integrity": "sha512-+Mc8UaAebFzgV+KpI5n7DasuuQCHA89dmwm7JXw3TV43ukfNQ9DnBH3Mdb2g/I4Fdxc26pwimBWvjIw0UAILSQ==", - "dev": true, - "dependencies": { - "commander": "^2.19.0", - "moo": "^0.5.0", - "railroad-diagrams": "^1.0.0", - "randexp": "0.4.6" - }, - "bin": { - "nearley-railroad": "bin/nearley-railroad.js", - "nearley-test": "bin/nearley-test.js", - "nearley-unparse": "bin/nearley-unparse.js", - "nearleyc": "bin/nearleyc.js" - }, - "funding": { - "type": "individual", - "url": "https://nearley.js.org/#give-to-nearley" - } - }, - "node_modules/nearley/node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true - }, "node_modules/negotiator": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", @@ -12158,22 +12073,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/object-is": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", - "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/object-keys": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", @@ -12467,15 +12366,6 @@ "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", "dev": true }, - "node_modules/parse5-htmlparser2-tree-adapter": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz", - "integrity": "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==", - "dev": true, - "dependencies": { - "parse5": "^6.0.1" - } - }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -12807,17 +12697,6 @@ "react-is": "^16.13.1" } }, - "node_modules/prop-types-exact": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/prop-types-exact/-/prop-types-exact-1.2.0.tgz", - "integrity": "sha512-K+Tk3Kd9V0odiXFP9fwDHUYRyvK3Nun3GVyPapSIs5OBkITAm15W0CPFD/YKTkMUAbc0b9CUwRQp2ybiBIq+eA==", - "dev": true, - "dependencies": { - "has": "^1.0.3", - "object.assign": "^4.1.0", - "reflect.ownkeys": "^0.2.0" - } - }, "node_modules/prop-types/node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", @@ -12922,34 +12801,6 @@ } ] }, - "node_modules/raf": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", - "integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==", - "dev": true, - "dependencies": { - "performance-now": "^2.1.0" - } - }, - "node_modules/railroad-diagrams": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz", - "integrity": "sha1-635iZ1SN3t+4mcG5Dlc3RVnN234=", - "dev": true - }, - "node_modules/randexp": { - "version": "0.4.6", - "resolved": "https://registry.npmjs.org/randexp/-/randexp-0.4.6.tgz", - "integrity": "sha512-80WNmd9DA0tmZrw9qQa62GPPWfuXJknrmVmLcxvq4uZBdYqb1wYoKTmnlGUchvVWe0XiLupYkBoXVOxz3C8DYQ==", - "dev": true, - "dependencies": { - "discontinuous-range": "1.0.0", - "ret": "~0.1.10" - }, - "engines": { - "node": ">=0.12" - } - }, "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -13220,27 +13071,6 @@ "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", "dev": true }, - "node_modules/react-test-renderer": { - "version": "16.14.0", - "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-16.14.0.tgz", - "integrity": "sha512-L8yPjqPE5CZO6rKsKXRO/rVPiaCOy0tQQJbC+UjPNlobl5mad59lvPjwFsQHTvL03caVDIVr9x9/OSgDe6I5Eg==", - "dev": true, - "dependencies": { - "object-assign": "^4.1.1", - "prop-types": "^15.6.2", - "react-is": "^16.8.6", - "scheduler": "^0.19.1" - }, - "peerDependencies": { - "react": "^16.14.0" - } - }, - "node_modules/react-test-renderer/node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "dev": true - }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -13277,11 +13107,18 @@ "node": ">=0.10.0" } }, - "node_modules/reflect.ownkeys": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/reflect.ownkeys/-/reflect.ownkeys-0.2.0.tgz", - "integrity": "sha1-dJrO7H8/34tj+SegSAnpDFwLNGA=", - "dev": true + "node_modules/redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "dev": true, + "dependencies": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + }, + "engines": { + "node": ">=8" + } }, "node_modules/regenerate": { "version": "1.4.2", @@ -13506,15 +13343,6 @@ "node": ">=8" } }, - "node_modules/ret": { - "version": "0.1.15", - "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", - "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", - "dev": true, - "engines": { - "node": ">=0.12" - } - }, "node_modules/reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", @@ -13546,16 +13374,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/rst-selector-parser": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/rst-selector-parser/-/rst-selector-parser-2.2.3.tgz", - "integrity": "sha1-gbIw6i/MYGbInjRy3nlChdmwPZE=", - "dev": true, - "dependencies": { - "lodash.flattendeep": "^4.4.0", - "nearley": "^2.7.10" - } - }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -14100,23 +13918,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/string.prototype.trim": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.5.tgz", - "integrity": "sha512-Lnh17webJVsD6ECeovpVN17RlAKjmz4rF9S+8Y45CkMc/ufVpTkU3vZIyIC7sllQ1FCvObZnnCdNs/HXTUOTlg==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/string.prototype.trimend": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz", @@ -14173,6 +13974,18 @@ "node": ">=6" } }, + "node_modules/strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dev": true, + "dependencies": { + "min-indent": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -15492,6 +15305,12 @@ } }, "dependencies": { + "@adobe/css-tools": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.0.1.tgz", + "integrity": "sha512-+u76oB43nOHrF4DDWRLWDCtci7f3QJoEBigemIdIeTi1ODqjx6Tad9NCVnPRwewWlKkVab5PlK8DCtPTyX7S8g==", + "dev": true + }, "@babel/code-frame": { "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz", @@ -17573,12 +17392,193 @@ "@sinonjs/commons": "^1.7.0" } }, + "@testing-library/dom": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-8.17.1.tgz", + "integrity": "sha512-KnH2MnJUzmFNPW6RIKfd+zf2Wue8mEKX0M3cpX6aKl5ZXrJM1/c/Pc8c2xDNYQCnJO48Sm5ITbMXgqTr3h4jxQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^4.2.0", + "aria-query": "^5.0.0", + "chalk": "^4.1.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.4.4", + "pretty-format": "^27.0.2" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "@testing-library/jest-dom": { + "version": "5.16.5", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-5.16.5.tgz", + "integrity": "sha512-N5ixQ2qKpi5OLYfwQmUb/5mSV9LneAcaUfp32pn4yCnpb8r/Yz0pXFPck21dIicKmi+ta5WRAknkZCfA8refMA==", + "dev": true, + "requires": { + "@adobe/css-tools": "^4.0.1", + "@babel/runtime": "^7.9.2", + "@types/testing-library__jest-dom": "^5.9.1", + "aria-query": "^5.0.0", + "chalk": "^3.0.0", + "css.escape": "^1.5.1", + "dom-accessibility-api": "^0.5.6", + "lodash": "^4.17.15", + "redent": "^3.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "@testing-library/react": { + "version": "12.1.5", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-12.1.5.tgz", + "integrity": "sha512-OfTXCJUFgjd/digLUuPxa0+/3ZxsQmE7ub9kcbW/wi96Bh3o/p5vrETcBGfP17NWPGqeYYl5LTRpwyGoMC4ysg==", + "dev": true, + "requires": { + "@babel/runtime": "^7.12.5", + "@testing-library/dom": "^8.0.0", + "@types/react-dom": "<18.0.0" + }, + "dependencies": { + "@types/react": { + "version": "17.0.48", + "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.48.tgz", + "integrity": "sha512-zJ6IYlJ8cYYxiJfUaZOQee4lh99mFihBoqkOSEGV+dFi9leROW6+PgstzQ+w3gWTnUfskALtQPGHK6dYmPj+2A==", + "dev": true, + "requires": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + }, + "@types/react-dom": { + "version": "17.0.17", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.17.tgz", + "integrity": "sha512-VjnqEmqGnasQKV0CWLevqMTXBYG9GbwuE6x3VetERLh0cq2LTptFE73MrQi2S7GkKXCf2GgwItB/melLnxfnsg==", + "dev": true, + "requires": { + "@types/react": "^17" + } + } + } + }, + "@testing-library/user-event": { + "version": "14.4.3", + "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.4.3.tgz", + "integrity": "sha512-kCUc5MEwaEMakkO5x7aoD+DLi02ehmEM2QCGWvNqAS1dV/fAvORWEjnjsEIvml59M7Y5kCkWN6fCCyPOe8OL6Q==", + "dev": true, + "requires": {} + }, "@tootallnate/once": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", "dev": true }, + "@types/aria-query": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-4.2.2.tgz", + "integrity": "sha512-HnYpAE1Y6kRyKM/XkEuiRQhTHvkzMBurTHnpFLYLBGPIylZNPs9jJcuOOYWxPLJCSEtmZT0Y8rHDokKN7rRTig==", + "dev": true + }, "@types/babel__core": { "version": "7.1.19", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.19.tgz", @@ -17607,36 +17607,17 @@ "integrity": "sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==", "dev": true, "requires": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "@types/babel__traverse": { - "version": "7.17.1", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.17.1.tgz", - "integrity": "sha512-kVzjari1s2YVi77D3w1yuvohV2idweYXMCDzqBiVNN63TcDWrIlTVOYpqVrvbbyOE/IyzBoTKF0fdnLPEORFxA==", - "dev": true, - "requires": { - "@babel/types": "^7.3.0" - } - }, - "@types/cheerio": { - "version": "0.22.30", - "resolved": "https://registry.npmjs.org/@types/cheerio/-/cheerio-0.22.30.tgz", - "integrity": "sha512-t7ZVArWZlq3dFa9Yt33qFBQIK4CQd1Q3UJp0V+UhP6vgLWLM6Qug7vZuRSGXg45zXeB1Fm5X2vmBkEX58LV2Tw==", - "dev": true, - "requires": { - "@types/node": "*" + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" } }, - "@types/enzyme": { - "version": "3.10.11", - "resolved": "https://registry.npmjs.org/@types/enzyme/-/enzyme-3.10.11.tgz", - "integrity": "sha512-LEtC7zXsQlbGXWGcnnmOI7rTyP+i1QzQv4Va91RKXDEukLDaNyxu0rXlfMiGEhJwfgTPCTb0R+Pnlj//oM9e/w==", + "@types/babel__traverse": { + "version": "7.17.1", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.17.1.tgz", + "integrity": "sha512-kVzjari1s2YVi77D3w1yuvohV2idweYXMCDzqBiVNN63TcDWrIlTVOYpqVrvbbyOE/IyzBoTKF0fdnLPEORFxA==", "dev": true, "requires": { - "@types/cheerio": "*", - "@types/react": "*" + "@babel/types": "^7.3.0" } }, "@types/eslint": { @@ -17811,6 +17792,15 @@ "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", "dev": true }, + "@types/testing-library__jest-dom": { + "version": "5.14.5", + "resolved": "https://registry.npmjs.org/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.14.5.tgz", + "integrity": "sha512-SBwbxYoyPIvxHbeHxTZX2Pe/74F/tX2/D3mMvzabdeJ25bBojfW0TyB8BHrbq/9zaaKICJZjLP+8r6AeZMFCuQ==", + "dev": true, + "requires": { + "@types/jest": "*" + } + }, "@types/tough-cookie": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.2.tgz", @@ -18233,31 +18223,6 @@ "indent-string": "^4.0.0" } }, - "airbnb-prop-types": { - "version": "2.16.0", - "resolved": "https://registry.npmjs.org/airbnb-prop-types/-/airbnb-prop-types-2.16.0.tgz", - "integrity": "sha512-7WHOFolP/6cS96PhKNrslCLMYAI8yB1Pp6u6XmxozQOiZbsI5ycglZr5cHhBFfuRcQQjzCMith5ZPZdYiJCxUg==", - "dev": true, - "requires": { - "array.prototype.find": "^2.1.1", - "function.prototype.name": "^1.1.2", - "is-regex": "^1.1.0", - "object-is": "^1.1.2", - "object.assign": "^4.1.0", - "object.entries": "^1.1.2", - "prop-types": "^15.7.2", - "prop-types-exact": "^1.2.0", - "react-is": "^16.13.1" - }, - "dependencies": { - "react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "dev": true - } - } - }, "ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -18367,6 +18332,12 @@ "sprintf-js": "~1.0.2" } }, + "aria-query": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.0.0.tgz", + "integrity": "sha512-V+SM7AbUwJ+EBnB8+DXs0hPZHO0W6pqBcc0dW90OwtVG02PswOu/teuARoLQjdDOH+t9pJgGnW5/Qmouf3gPJg==", + "dev": true + }, "array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", @@ -18392,30 +18363,6 @@ "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", "dev": true }, - "array.prototype.filter": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/array.prototype.filter/-/array.prototype.filter-1.0.1.tgz", - "integrity": "sha512-Dk3Ty7N42Odk7PjU/Ci3zT4pLj20YvuVnneG/58ICM6bt4Ij5kZaJTVQ9TSaWaIECX2sFyz4KItkVZqHNnciqw==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.0", - "es-array-method-boxes-properly": "^1.0.0", - "is-string": "^1.0.7" - } - }, - "array.prototype.find": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/array.prototype.find/-/array.prototype.find-2.1.2.tgz", - "integrity": "sha512-00S1O4ewO95OmmJW7EesWfQlrCrLEL8kZ40w3+GkLX2yTt0m2ggcePPa2uHPJ9KUmJvwRq+lCV9bD8Yim23x/Q==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.0" - } - }, "array.prototype.flat": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.5.tgz", @@ -19017,42 +18964,6 @@ "integrity": "sha1-FCD/sQ/URNz8ebQ4kbv//TKoRgA=", "dev": true }, - "cheerio": { - "version": "1.0.0-rc.10", - "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.10.tgz", - "integrity": "sha512-g0J0q/O6mW8z5zxQ3A8E8J1hUgp4SMOvEoW/x84OwyHKe/Zccz83PVT4y5Crcr530FV6NgmKI1qvGTKVl9XXVw==", - "dev": true, - "requires": { - "cheerio-select": "^1.5.0", - "dom-serializer": "^1.3.2", - "domhandler": "^4.2.0", - "htmlparser2": "^6.1.0", - "parse5": "^6.0.1", - "parse5-htmlparser2-tree-adapter": "^6.0.1", - "tslib": "^2.2.0" - }, - "dependencies": { - "tslib": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", - "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", - "dev": true - } - } - }, - "cheerio-select": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-1.5.0.tgz", - "integrity": "sha512-qocaHPv5ypefh6YNxvnbABM07KMxExbtbfuJoIie3iZXX1ERwYmJcIiRrr9H05ucQP1k28dav8rpdDgjQd8drg==", - "dev": true, - "requires": { - "css-select": "^4.1.3", - "css-what": "^5.0.1", - "domelementtype": "^2.2.0", - "domhandler": "^4.2.0", - "domutils": "^2.7.0" - } - }, "chokidar": { "version": "3.5.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", @@ -19384,6 +19295,12 @@ "integrity": "sha512-arSMRWIIFY0hV8pIxZMEfmMI47Wj3R/aWpZDDxWYCPEiOMv6tfOrnpDtgxBYPEQD4V0Y/958+1TdC3iWTFcUPw==", "dev": true }, + "css.escape": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", + "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", + "dev": true + }, "cssesc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", @@ -19704,12 +19621,6 @@ "path-type": "^4.0.0" } }, - "discontinuous-range": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/discontinuous-range/-/discontinuous-range-1.0.0.tgz", - "integrity": "sha1-44Mx8IRLukm5qctxx3FYWqsbxlo=", - "dev": true - }, "doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -19719,6 +19630,12 @@ "esutils": "^2.0.2" } }, + "dom-accessibility-api": { + "version": "0.5.14", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.14.tgz", + "integrity": "sha512-NMt+m9zFMPZe0JcY9gN224Qvk6qLIdqex29clBvc/y75ZBX9YA9wNK3frsYvu2DI1xcCIwxwnX+TlsJ2DSOADg==", + "dev": true + }, "dom-converter": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz", @@ -19884,119 +19801,6 @@ "integrity": "sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw==", "dev": true }, - "enzyme": { - "version": "3.11.0", - "resolved": "https://registry.npmjs.org/enzyme/-/enzyme-3.11.0.tgz", - "integrity": "sha512-Dw8/Gs4vRjxY6/6i9wU0V+utmQO9kvh9XLnz3LIudviOnVYDEe2ec+0k+NQoMamn1VrjKgCUOWj5jG/5M5M0Qw==", - "dev": true, - "requires": { - "array.prototype.flat": "^1.2.3", - "cheerio": "^1.0.0-rc.3", - "enzyme-shallow-equal": "^1.0.1", - "function.prototype.name": "^1.1.2", - "has": "^1.0.3", - "html-element-map": "^1.2.0", - "is-boolean-object": "^1.0.1", - "is-callable": "^1.1.5", - "is-number-object": "^1.0.4", - "is-regex": "^1.0.5", - "is-string": "^1.0.5", - "is-subset": "^0.1.1", - "lodash.escape": "^4.0.1", - "lodash.isequal": "^4.5.0", - "object-inspect": "^1.7.0", - "object-is": "^1.0.2", - "object.assign": "^4.1.0", - "object.entries": "^1.1.1", - "object.values": "^1.1.1", - "raf": "^3.4.1", - "rst-selector-parser": "^2.2.3", - "string.prototype.trim": "^1.2.1" - } - }, - "enzyme-adapter-react-16": { - "version": "1.15.6", - "resolved": "https://registry.npmjs.org/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.15.6.tgz", - "integrity": "sha512-yFlVJCXh8T+mcQo8M6my9sPgeGzj85HSHi6Apgf1Cvq/7EL/J9+1JoJmJsRxZgyTvPMAqOEpRSu/Ii/ZpyOk0g==", - "dev": true, - "requires": { - "enzyme-adapter-utils": "^1.14.0", - "enzyme-shallow-equal": "^1.0.4", - "has": "^1.0.3", - "object.assign": "^4.1.2", - "object.values": "^1.1.2", - "prop-types": "^15.7.2", - "react-is": "^16.13.1", - "react-test-renderer": "^16.0.0-0", - "semver": "^5.7.0" - }, - "dependencies": { - "react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "dev": true - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - } - } - }, - "enzyme-adapter-utils": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/enzyme-adapter-utils/-/enzyme-adapter-utils-1.14.0.tgz", - "integrity": "sha512-F/z/7SeLt+reKFcb7597IThpDp0bmzcH1E9Oabqv+o01cID2/YInlqHbFl7HzWBl4h3OdZYedtwNDOmSKkk0bg==", - "dev": true, - "requires": { - "airbnb-prop-types": "^2.16.0", - "function.prototype.name": "^1.1.3", - "has": "^1.0.3", - "object.assign": "^4.1.2", - "object.fromentries": "^2.0.3", - "prop-types": "^15.7.2", - "semver": "^5.7.1" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - } - } - }, - "enzyme-shallow-equal": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/enzyme-shallow-equal/-/enzyme-shallow-equal-1.0.4.tgz", - "integrity": "sha512-MttIwB8kKxypwHvRynuC3ahyNc+cFbR8mjVIltnmzQ0uKGqmsfO4bfBuLxb0beLNPhjblUEYvEbsg+VSygvF1Q==", - "dev": true, - "requires": { - "has": "^1.0.3", - "object-is": "^1.1.2" - } - }, - "enzyme-to-json": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/enzyme-to-json/-/enzyme-to-json-3.6.2.tgz", - "integrity": "sha512-Ynm6Z6R6iwQ0g2g1YToz6DWhxVnt8Dy1ijR2zynRKxTyBGA8rCDXU3rs2Qc4OKvUvc2Qoe1bcFK6bnPs20TrTg==", - "dev": true, - "requires": { - "@types/cheerio": "^0.22.22", - "lodash": "^4.17.21", - "react-is": "^16.12.0" - }, - "dependencies": { - "react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "dev": true - } - } - }, "error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -20034,12 +19838,6 @@ "unbox-primitive": "^1.0.1" } }, - "es-array-method-boxes-properly": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz", - "integrity": "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==", - "dev": true - }, "es-module-lexer": { "version": "0.9.3", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz", @@ -21243,30 +21041,12 @@ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", "dev": true }, - "function.prototype.name": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", - "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.0", - "functions-have-names": "^1.2.2" - } - }, "functional-red-black-tree": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", "dev": true }, - "functions-have-names": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.2.tgz", - "integrity": "sha512-bLgc3asbWdwPbx2mNk2S49kmJCuQeu0nfmaOgbs8WIyzzkw3r4htszdIi9Q9EMezDPTYuJx2wvjZ/EwgAthpnA==", - "dev": true - }, "gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -21494,16 +21274,6 @@ "parse-passwd": "^1.0.0" } }, - "html-element-map": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/html-element-map/-/html-element-map-1.3.1.tgz", - "integrity": "sha512-6XMlxrAFX4UEEGxctfFnmrFaaZFNf9i5fNuV5wZ3WWQ4FVaNP1aX1LkX9j2mfEx1NpjeE/rL3nmgEn23GdFmrg==", - "dev": true, - "requires": { - "array.prototype.filter": "^1.0.0", - "call-bind": "^1.0.2" - } - }, "html-encoding-sniffer": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", @@ -21996,12 +21766,6 @@ "has-tostringtag": "^1.0.0" } }, - "is-subset": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-subset/-/is-subset-0.1.1.tgz", - "integrity": "sha1-ilkRfZMt4d4A8kX83TnOQ/HpOaY=", - "dev": true - }, "is-symbol": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", @@ -24013,24 +23777,6 @@ "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=", "dev": true }, - "lodash.escape": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-4.0.1.tgz", - "integrity": "sha1-yQRGkMIeBClL6qUXcS/e0fqI3pg=", - "dev": true - }, - "lodash.flattendeep": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", - "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=", - "dev": true - }, - "lodash.isequal": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", - "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=", - "dev": true - }, "lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -24199,6 +23945,12 @@ "yallist": "^4.0.0" } }, + "lz-string": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.4.4.tgz", + "integrity": "sha512-0ckx7ZHRPqb0oUm8zNr+90mtf9DQB60H1wMCjBtfi62Kl3a7JbHob6gA2bC+xRvZoOL+1hzUK8jeuEIQE8svEQ==", + "dev": true + }, "make-dir": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", @@ -24308,6 +24060,12 @@ "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", "dev": true }, + "min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "dev": true + }, "mini-css-extract-plugin": { "version": "2.5.3", "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.5.3.tgz", @@ -24373,12 +24131,6 @@ "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", "dev": true }, - "moo": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/moo/-/moo-0.5.1.tgz", - "integrity": "sha512-I1mnb5xn4fO80BH9BLcF0yLypy2UKl+Cb01Fu0hJRkJjlCRtxZMWkTdAtDd5ZqCOxtCkhmRwyI57vWT+1iZ67w==", - "dev": true - }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -24397,26 +24149,6 @@ "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", "dev": true }, - "nearley": { - "version": "2.20.1", - "resolved": "https://registry.npmjs.org/nearley/-/nearley-2.20.1.tgz", - "integrity": "sha512-+Mc8UaAebFzgV+KpI5n7DasuuQCHA89dmwm7JXw3TV43ukfNQ9DnBH3Mdb2g/I4Fdxc26pwimBWvjIw0UAILSQ==", - "dev": true, - "requires": { - "commander": "^2.19.0", - "moo": "^0.5.0", - "railroad-diagrams": "^1.0.0", - "randexp": "0.4.6" - }, - "dependencies": { - "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true - } - } - }, "negotiator": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", @@ -24519,16 +24251,6 @@ "integrity": "sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g==", "dev": true }, - "object-is": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", - "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - } - }, "object-keys": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", @@ -24743,15 +24465,6 @@ "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", "dev": true }, - "parse5-htmlparser2-tree-adapter": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz", - "integrity": "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==", - "dev": true, - "requires": { - "parse5": "^6.0.1" - } - }, "parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -25008,17 +24721,6 @@ } } }, - "prop-types-exact": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/prop-types-exact/-/prop-types-exact-1.2.0.tgz", - "integrity": "sha512-K+Tk3Kd9V0odiXFP9fwDHUYRyvK3Nun3GVyPapSIs5OBkITAm15W0CPFD/YKTkMUAbc0b9CUwRQp2ybiBIq+eA==", - "dev": true, - "requires": { - "has": "^1.0.3", - "object.assign": "^4.1.0", - "reflect.ownkeys": "^0.2.0" - } - }, "proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -25084,31 +24786,6 @@ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", "dev": true }, - "raf": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", - "integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==", - "dev": true, - "requires": { - "performance-now": "^2.1.0" - } - }, - "railroad-diagrams": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz", - "integrity": "sha1-635iZ1SN3t+4mcG5Dlc3RVnN234=", - "dev": true - }, - "randexp": { - "version": "0.4.6", - "resolved": "https://registry.npmjs.org/randexp/-/randexp-0.4.6.tgz", - "integrity": "sha512-80WNmd9DA0tmZrw9qQa62GPPWfuXJknrmVmLcxvq4uZBdYqb1wYoKTmnlGUchvVWe0XiLupYkBoXVOxz3C8DYQ==", - "dev": true, - "requires": { - "discontinuous-range": "1.0.0", - "ret": "~0.1.10" - } - }, "randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -25309,26 +24986,6 @@ "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", "dev": true }, - "react-test-renderer": { - "version": "16.14.0", - "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-16.14.0.tgz", - "integrity": "sha512-L8yPjqPE5CZO6rKsKXRO/rVPiaCOy0tQQJbC+UjPNlobl5mad59lvPjwFsQHTvL03caVDIVr9x9/OSgDe6I5Eg==", - "dev": true, - "requires": { - "object-assign": "^4.1.1", - "prop-types": "^15.6.2", - "react-is": "^16.8.6", - "scheduler": "^0.19.1" - }, - "dependencies": { - "react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "dev": true - } - } - }, "readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -25356,11 +25013,15 @@ "minimatch": "3.0.4" } }, - "reflect.ownkeys": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/reflect.ownkeys/-/reflect.ownkeys-0.2.0.tgz", - "integrity": "sha1-dJrO7H8/34tj+SegSAnpDFwLNGA=", - "dev": true + "redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "dev": true, + "requires": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + } }, "regenerate": { "version": "1.4.2", @@ -25535,12 +25196,6 @@ "signal-exit": "^3.0.2" } }, - "ret": { - "version": "0.1.15", - "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", - "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", - "dev": true - }, "reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", @@ -25562,16 +25217,6 @@ "glob": "^7.1.3" } }, - "rst-selector-parser": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/rst-selector-parser/-/rst-selector-parser-2.2.3.tgz", - "integrity": "sha1-gbIw6i/MYGbInjRy3nlChdmwPZE=", - "dev": true, - "requires": { - "lodash.flattendeep": "^4.4.0", - "nearley": "^2.7.10" - } - }, "run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -26001,17 +25646,6 @@ "side-channel": "^1.0.4" } }, - "string.prototype.trim": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.5.tgz", - "integrity": "sha512-Lnh17webJVsD6ECeovpVN17RlAKjmz4rF9S+8Y45CkMc/ufVpTkU3vZIyIC7sllQ1FCvObZnnCdNs/HXTUOTlg==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1" - } - }, "string.prototype.trimend": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz", @@ -26053,6 +25687,15 @@ "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", "dev": true }, + "strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dev": true, + "requires": { + "min-indent": "^1.0.0" + } + }, "strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", diff --git a/package.json b/package.json index b82d7c5..f0ceab7 100644 --- a/package.json +++ b/package.json @@ -14,10 +14,9 @@ "cypress-built": "start-server-and-test \"npm run start-built\" http://localhost:3000/hatetris.html \"cypress run\"", "eslint": "eslint . --ext .js,.ts,.tsx", "jest": "jest", - "pretest": "npm run eslint", "start": "node scripts/start.js", "start-built": "node scripts/start-built.js", - "test": "npm run jest && npm run build && npm run cypress-built" + "test": "npm run eslint && npm run jest && npm run build && npm run cypress-built" }, "keywords": [ "tetris" @@ -33,7 +32,9 @@ "@babel/preset-env": "^7.15.0", "@babel/preset-react": "^7.14.5", "@babel/preset-typescript": "^7.14.5", - "@types/enzyme": "^3.10.9", + "@testing-library/jest-dom": "^5.16.5", + "@testing-library/react": "^12.1.5", + "@testing-library/user-event": "^14.4.3", "@types/jest": "^27.0.1", "@types/react": "^18.0.8", "@types/react-dom": "^18.0.3", @@ -45,9 +46,6 @@ "classnames": "^2.2.6", "css-loader": "^6.2.0", "cypress": "^10.3.0", - "enzyme": "^3.11.0", - "enzyme-adapter-react-16": "^1.15.4", - "enzyme-to-json": "^3.5.0", "eslint": "^8.16.0", "eslint-config-standard": "^17.0.0", "eslint-plugin-import": "^2.24.2", diff --git a/src/components/Game/Game.css b/src/components/Game/Game.css index 360831f..36b348a 100644 --- a/src/components/Game/Game.css +++ b/src/components/Game/Game.css @@ -1,88 +1,88 @@ .game { - box-sizing: border-box; - width: 100vw; - max-width: 360px; - margin: 0 auto; - font-family: arial; - padding: 10px; - display: flex; - flex-direction: column; - gap: 10px; + box-sizing: border-box; + width: 100vw; + max-width: 360px; + margin: 0 auto; + font-family: arial; + padding: 10px; + display: flex; + flex-direction: column; + gap: 10px; } .game__top { - flex: 0 0 auto; - display: flex; - gap: 10px; - flex-direction: row; + flex: 0 0 auto; + display: flex; + gap: 10px; + flex-direction: row; } .game__topleft { - flex: 0 0 auto; + flex: 0 0 auto; } .game__topright { - flex: 1 1 auto; - display: flex; - flex-direction: column; + flex: 1 1 auto; + display: flex; + flex-direction: column; } .game__paragraph { - /* in a flex column, vertical margins do not collapse! */ - margin-bottom: 10px; - flex: 0 0 auto; + /* in a flex column, vertical margins do not collapse! */ + margin-bottom: 10px; + flex: 0 0 auto; } .game__paragraph:last-child { - margin-bottom: 0; + margin-bottom: 0; } .game__replay-out { - background-color: rgba(189, 189, 189, .3); - width: 100%; - padding: 8px; - line-height: normal; - overflow: hidden; - text-overflow: ellipsis; + background-color: rgba(189, 189, 189, .3); + width: 100%; + padding: 8px; + line-height: normal; + overflow: hidden; + text-overflow: ellipsis; } .game__button { - border: 0; - height: 48px; - width: 100%; - font-size: 1em; /* avoid shrinkage */ - font-family: inherit; - padding: .5rem .75rem; - background-color: rgb(16, 82, 225); - color: white; - cursor: pointer; - line-height: 1rem; + border: 0; + height: 48px; + width: 100%; + font-size: 1em; /* avoid shrinkage */ + font-family: inherit; + padding: .5rem .75rem; + background-color: rgb(16, 82, 225); + color: white; + cursor: pointer; + line-height: 1rem; } .game__button:hover { - background-color: rgb(5, 52, 185); + background-color: rgb(5, 52, 185); } .game__button:active { - background-color: red; + background-color: red; } .game__button[disabled] { - background-color: grey; - cursor: default; + background-color: grey; + cursor: default; } :focus { - outline: 2px solid black; + outline: 2px solid black; } .game__spacer { - flex: 1 1 auto; + flex: 1 1 auto; } .game__bottom { - flex: 1 1 auto; - display: flex; - flex-direction: column; - gap: 10px; + flex: 1 1 auto; + display: flex; + flex-direction: column; + gap: 10px; } diff --git a/src/components/Game/Game.spec.tsx b/src/components/Game/Game.spec.tsx index 4842827..e4fa094 100644 --- a/src/components/Game/Game.spec.tsx +++ b/src/components/Game/Game.spec.tsx @@ -2,21 +2,27 @@ 'use strict' -import { shallow } from 'enzyme' +import userEvent from '@testing-library/user-event' +import { render, screen } from '@testing-library/react' import * as React from 'react' -import Game, { hatetris, lovetris } from './Game' +import Game from './Game' import type { GameProps } from './Game' import hatetrisRotationSystem from '../../rotation-systems/hatetris-rotation-system' jest.useFakeTimers() +// BOY do I need to figure out a faster way to run these unit tests +jest.setTimeout(60000) + +const replayTimeout = 180 + describe('', () => { - const getGame = (props: Partial = {}) => { - return shallow( + const renderGame = (props: Partial = {}) => { + render( ', () => { ) } + let user: ReturnType + + beforeEach(() => { + // RTL's keyboard activity simulation involves asynchronous delays. + // We need to advance time so that those delayed things actually happen + user = userEvent.setup({ + advanceTimers: number => { + jest.advanceTimersByTime(number) + } + }) + }) + it('rejects a rotation system with no pieces', () => { - expect(() => getGame({ + renderGame({ rotationSystem: { placeNewPiece: () => ({ id: '', o: NaN, x: NaN, y: NaN }), rotations: {} } - })).toThrowError() + }) + expect(screen.getByTestId('error-real')).toHaveTextContent('Have to have at least one piece!') + expect(screen.getByTestId('error-interpretation')).toHaveTextContent('Caught this exception while trying to start HATETRIS. Application halted.') }) it('rejects a well depth below the bar', () => { - expect(() => getGame({ bar: 4, wellDepth: 3 })).toThrowError() + renderGame({ bar: 4, wellDepth: 3 }) + expect(screen.getByTestId('error-real')).toHaveTextContent('Can\'t have well with depth 3 less than bar at 4') + expect(screen.getByTestId('error-interpretation')).toHaveTextContent('Caught this exception while trying to start HATETRIS. Application halted.') }) it('rejects a well width less than 4', () => { - expect(() => getGame({ wellWidth: 3 })).toThrowError() + renderGame({ wellWidth: 3 }) + expect(screen.getByTestId('error-real')).toHaveTextContent('Can\'t have well with width 3 less than 4') + expect(screen.getByTestId('error-interpretation')).toHaveTextContent('Caught this exception while trying to start HATETRIS. Application halted.') }) - it('ignores all keystrokes before the game has begun', () => { - const game = getGame() - expect(game.state()).toEqual({ - customAiCode: '', - displayEnemy: false, - enemy: hatetris, - error: null, - mode: 'INITIAL', - wellStateId: -1, - wellStates: [], - replay: [], - replayCopiedTimeoutId: undefined, - replayTimeoutId: undefined - }) + it('ignores all keystrokes before the game has begun', async () => { + const originalWarn = console.warn + console.warn = jest.fn() - const warn = jest.spyOn(console, 'warn') - warn.mockImplementation(() => {}) - game.instance().handleDocumentKeyDown(new window.KeyboardEvent('keydown', { key: 'Left' })) - game.instance().handleDocumentKeyDown(new window.KeyboardEvent('keydown', { key: 'Right' })) - game.instance().handleDocumentKeyDown(new window.KeyboardEvent('keydown', { key: 'Down' })) - game.instance().handleDocumentKeyDown(new window.KeyboardEvent('keydown', { key: 'Up' })) - game.instance().handleDocumentKeyDown(new window.KeyboardEvent('keydown', { key: 'z', ctrlKey: true })) - game.instance().handleDocumentKeyDown(new window.KeyboardEvent('keydown', { key: 'y', ctrlKey: true })) - expect(warn).toHaveBeenCalledTimes(6) - expect(game.state()).toEqual({ - customAiCode: '', - displayEnemy: false, - enemy: hatetris, - error: null, - mode: 'INITIAL', - wellStateId: -1, - wellStates: [], - replay: [], - replayCopiedTimeoutId: undefined, - replayTimeoutId: undefined - }) + renderGame() + expect(screen.getByTestId('start-button')).toHaveTextContent('start new game') + + await user.keyboard('{Left}') + await user.keyboard('{Right}') + await user.keyboard('{Down}') + await user.keyboard('{Up}') + await user.keyboard('{Control>}z{/Control}') + await user.keyboard('{Control>}y{/Control}') + + expect(console.warn).toHaveBeenCalledTimes(6) + expect(screen.getByTestId('start-button')).toHaveTextContent('start new game') - warn.mockRestore() - game.unmount() + console.warn = originalWarn }) - it('lets you play a few moves', () => { - const game = getGame() - expect(game.state()).toEqual(expect.objectContaining({ - mode: 'INITIAL', - wellStateId: -1, - wellStates: [], - replay: [] - })) - - game.find('.e2e__start-button').simulate('click') - expect(game.state()).toEqual(expect.objectContaining({ - mode: 'PLAYING', - wellStateId: 0, - wellStates: [{ - core: { - well: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - score: 0 - }, - ai: new Set([ - JSON.stringify([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) - ]), - piece: { id: 'S', o: 0, x: 3, y: 0 } - }], - replay: [] - })) - - game.instance().handleDocumentKeyDown(new window.KeyboardEvent('keydown', { key: 'ArrowLeft' })) - expect(game.state()).toEqual(expect.objectContaining({ - mode: 'PLAYING', - wellStateId: 1, - wellStates: [{ - core: { - well: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - score: 0 - }, - ai: new Set([ - JSON.stringify([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) - ]), - piece: { id: 'S', o: 0, x: 3, y: 0 } - }, { - core: { - well: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - score: 0 - }, - ai: new Set([ - JSON.stringify([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) - ]), - piece: { id: 'S', o: 0, x: 2, y: 0 } - }], - replay: ['L'] - })) - - game.instance().handleDocumentKeyDown(new window.KeyboardEvent('keydown', { key: 'ArrowRight' })) - expect(game.state()).toEqual(expect.objectContaining({ - mode: 'PLAYING', - wellStateId: 2, - wellStates: [{ - core: { - well: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - score: 0 - }, - ai: new Set([ - JSON.stringify([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) - ]), - piece: { id: 'S', o: 0, x: 3, y: 0 } - }, { - core: { - well: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - score: 0 - }, - ai: new Set([ - JSON.stringify([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) - ]), - piece: { id: 'S', o: 0, x: 2, y: 0 } - }, { - core: { - well: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - score: 0 - }, - ai: new Set([ - JSON.stringify([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) - ]), - piece: { id: 'S', o: 0, x: 3, y: 0 } - }], - replay: ['L', 'R'] - })) - - game.instance().handleDocumentKeyDown(new window.KeyboardEvent('keydown', { key: 'ArrowDown' })) - expect(game.state()).toEqual(expect.objectContaining({ - mode: 'PLAYING', - wellStateId: 3, - wellStates: [{ - core: { - well: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - score: 0 - }, - ai: new Set([ - JSON.stringify([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) - ]), - piece: { id: 'S', o: 0, x: 3, y: 0 } - }, { - core: { - well: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - score: 0 - }, - ai: new Set([ - JSON.stringify([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) - ]), - piece: { id: 'S', o: 0, x: 2, y: 0 } - }, { - core: { - well: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - score: 0 - }, - ai: new Set([ - JSON.stringify([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) - ]), - piece: { id: 'S', o: 0, x: 3, y: 0 } - }, { - core: { - well: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - score: 0 - }, - ai: new Set([ - JSON.stringify([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) - ]), - piece: { id: 'S', o: 0, x: 3, y: 1 } - }], - replay: ['L', 'R', 'D'] - })) - - game.instance().handleDocumentKeyDown(new window.KeyboardEvent('keydown', { key: 'ArrowUp' })) - expect(game.state()).toEqual(expect.objectContaining({ - mode: 'PLAYING', - wellStateId: 4, - wellStates: [{ - core: { - well: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - score: 0 - }, - ai: new Set([ - JSON.stringify([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) - ]), - piece: { id: 'S', o: 0, x: 3, y: 0 } - }, { - core: { - well: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - score: 0 - }, - ai: new Set([ - JSON.stringify([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) - ]), - piece: { id: 'S', o: 0, x: 2, y: 0 } - }, { - core: { - well: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - score: 0 - }, - ai: new Set([ - JSON.stringify([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) - ]), - piece: { id: 'S', o: 0, x: 3, y: 0 } - }, { - core: { - well: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - score: 0 - }, - ai: new Set([ - JSON.stringify([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) - ]), - piece: { id: 'S', o: 0, x: 3, y: 1 } - }, { - core: { - well: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - score: 0 - }, - ai: new Set([ - JSON.stringify([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) - ]), - piece: { id: 'S', o: 1, x: 3, y: 1 } - }], - replay: ['L', 'R', 'D', 'U'] - })) - - game.instance().handleDocumentKeyDown(new window.KeyboardEvent('keydown', { key: 'z', ctrlKey: true })) - expect(game.state()).toEqual(expect.objectContaining({ - mode: 'PLAYING', - wellStateId: 3, - wellStates: [{ - core: { - well: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - score: 0 - }, - ai: new Set([ - JSON.stringify([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) - ]), - piece: { id: 'S', o: 0, x: 3, y: 0 } - }, { - core: { - well: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - score: 0 - }, - ai: new Set([ - JSON.stringify([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) - ]), - piece: { id: 'S', o: 0, x: 2, y: 0 } - }, { - core: { - well: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - score: 0 - }, - ai: new Set([ - JSON.stringify([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) - ]), - piece: { id: 'S', o: 0, x: 3, y: 0 } - }, { - core: { - well: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - score: 0 - }, - ai: new Set([ - JSON.stringify([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) - ]), - piece: { id: 'S', o: 0, x: 3, y: 1 } - }, { - core: { - well: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - score: 0 - }, - ai: new Set([ - JSON.stringify([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) - ]), - piece: { id: 'S', o: 1, x: 3, y: 1 } - }], - replay: ['L', 'R', 'D', 'U'] - })) - - game.instance().handleDocumentKeyDown(new window.KeyboardEvent('keydown', { key: 'y', ctrlKey: true })) - expect(game.state()).toEqual(expect.objectContaining({ - mode: 'PLAYING', - wellStateId: 4, - wellStates: [{ - core: { - well: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - score: 0 - }, - ai: new Set([ - JSON.stringify([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) - ]), - piece: { id: 'S', o: 0, x: 3, y: 0 } - }, { - core: { - well: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - score: 0 - }, - ai: new Set([ - JSON.stringify([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) - ]), - piece: { id: 'S', o: 0, x: 2, y: 0 } - }, { - core: { - well: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - score: 0 - }, - ai: new Set([ - JSON.stringify([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) - ]), - piece: { id: 'S', o: 0, x: 3, y: 0 } - }, { - core: { - well: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - score: 0 - }, - ai: new Set([ - JSON.stringify([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) - ]), - piece: { id: 'S', o: 0, x: 3, y: 1 } - }, { - core: { - well: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - score: 0 - }, - ai: new Set([ - JSON.stringify([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) - ]), - piece: { id: 'S', o: 1, x: 3, y: 1 } - }], - replay: ['L', 'R', 'D', 'U'] - })) + it('lets you play a few moves', async () => { + const originalWarn = console.warn + console.warn = jest.fn() - // Warn on attempted redo at end of history - const warn = jest.spyOn(console, 'warn') - warn.mockImplementation(() => {}) + renderGame() + + await user.click(screen.getByTestId('start-button')) + expect(screen.queryAllByTestId('well__cell well__cell--live')).toHaveLength(4) + + await user.keyboard('{Left}') + expect(screen.queryAllByTestId('well__cell well__cell--live')).toHaveLength(4) + + await user.keyboard('{Right}') + expect(screen.queryAllByTestId('well__cell well__cell--live')).toHaveLength(4) + + await user.keyboard('{Down}') + expect(screen.queryAllByTestId('well__cell well__cell--live')).toHaveLength(4) + + // Rotate, puts part of the piece in contact with the bar below + await user.keyboard('{Up}') + expect(screen.queryAllByTestId('well__cell well__cell--live')).toHaveLength(3) + expect(screen.queryAllByTestId('well__cell well__cell--bar well__cell--live')).toHaveLength(1) - game.instance().handleDocumentKeyDown(new window.KeyboardEvent('keydown', { key: 'y', ctrlKey: true })) - expect(warn).toHaveBeenCalledTimes(1) + // Undo + await user.keyboard('{Control>}z{/Control}') + expect(screen.queryAllByTestId('well__cell well__cell--live')).toHaveLength(4) - warn.mockRestore() - game.unmount() + // Redo + await user.keyboard('{Control>}y{/Control}') + expect(screen.queryAllByTestId('well__cell well__cell--live')).toHaveLength(3) + expect(screen.queryAllByTestId('well__cell well__cell--bar well__cell--live')).toHaveLength(1) + + // Warn on attempted redo at end of history + await user.keyboard('{Control>}y{/Control}') + expect(screen.queryAllByTestId('well__cell well__cell--live')).toHaveLength(3) + expect(screen.queryAllByTestId('well__cell well__cell--bar well__cell--live')).toHaveLength(1) + expect(console.warn).toHaveBeenCalledTimes(1) + + console.warn = originalWarn }) - it('just lets you play if you enter an empty replay', () => { - const game = getGame() + it('just lets you play if you enter an empty replay', async () => { + renderGame() const prompt = jest.spyOn(window, 'prompt') prompt.mockReturnValueOnce('') - game.find('.e2e__replay-button').simulate('click') + await user.click(screen.getByTestId('replay-button')) + expect(prompt.mock.calls).toEqual([ + ['Paste replay string...'] + ]) prompt.mockRestore() - expect(game.state()).toEqual(expect.objectContaining({ - customAiCode: '', - displayEnemy: false, - enemy: hatetris, - mode: 'PLAYING', - replay: [], - replayTimeoutId: undefined, - wellStates: [ - expect.anything() - ], - wellStateId: 0 - })) - - game.unmount() + expect(screen.queryAllByTestId('down')).toHaveLength(1) }) - it('lets you select a different AI and play a full game with it and provides a replay', () => { - const game = getGame() - expect(game.state()).toEqual({ - customAiCode: '', - displayEnemy: false, - enemy: hatetris, - error: null, - mode: 'INITIAL', - wellStateId: -1, - wellStates: [], - replay: [], - replayCopiedTimeoutId: undefined, - replayTimeoutId: undefined - }) + it('lets you select a different AI and play a full game with it and provides a replay', async () => { + renderGame() - game.find('.e2e__select-ai').simulate('click') - expect(game.state()).toEqual(expect.objectContaining({ - displayEnemy: false, - enemy: hatetris, - mode: 'SELECT_AI' - })) - - game.find('.e2e__enemy').at(1).simulate('click') - expect(game.state()).toEqual(expect.objectContaining({ - displayEnemy: true, - enemy: lovetris, - mode: 'INITIAL' - })) - - game.find('.e2e__start-button').simulate('click') - expect(game.find('.e2e__enemy-short').text()).toBe('AI: ❤️') - expect(game.state()).toEqual(expect.objectContaining({ - displayEnemy: true, - enemy: lovetris, - mode: 'PLAYING', - wellStateId: 0, - wellStates: [{ - core: { - well: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - score: 0 - }, - piece: { id: 'I', o: 0, x: 3, y: 0 } // An I piece! - }], - replay: [] - })) + await user.click(screen.getByTestId('select-ai')) + await user.click(screen.queryAllByTestId('enemy').at(1)) + await user.click(screen.getByTestId('start-button')) + expect(screen.getByTestId('enemy-short')).toHaveTextContent('AI: ❤️') for (let i = 0; i < 187; i++) { - expect(game.state().mode).toBe('PLAYING') - game.instance().handleDown() + await user.keyboard('{Down}') } - expect(game.state()).toEqual(expect.objectContaining({ - enemy: lovetris, - mode: 'GAME_OVER', - wellStateId: 187, - wellStates: expect.any(Array) - })) - - expect(game.find('.e2e__replay-out').text()).toBe('௨ටໃݹ௨ටໃݹ௨ටໃݹ௨ටໃݹ௨Đ') + expect(screen.getByTestId('replay-out')).toHaveTextContent('௨ටໃݹ௨ටໃݹ௨ටໃݹ௨ටໃݹ௨Đ') }) - it('lets you use a custom AI', () => { - const game = getGame() + it('lets you use a custom AI', async () => { + renderGame() - game.find('.e2e__select-ai').simulate('click') - game.find('.e2e__custom-enemy').simulate('click') - game.find('.e2e__ai-textarea').simulate('change', { - target: { - value: '() => \'J\'' - } - }) - game.find('.e2e__submit-custom-enemy').simulate('click') - game.find('.e2e__start-button').simulate('click') - - expect(game.find('.e2e__enemy-short').text()).toBe('AI: custom') - expect(game.state()).toEqual({ - customAiCode: '() => \'J\'', - displayEnemy: true, - enemy: expect.objectContaining({ - shortDescription: 'custom' - }), - error: null, - mode: 'PLAYING', - wellStateId: 0, - wellStates: [{ - core: { - well: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - score: 0 - }, - piece: { id: 'J', o: 0, x: 3, y: 0 } // A J piece! - }], - replay: [], - replayCopiedTimeoutId: undefined, - replayTimeoutId: undefined - }) + await user.click(screen.getByTestId('select-ai')) + await user.click(screen.getByTestId('custom-enemy')) + await user.type(screen.getByTestId('ai-textarea'), '() => \'J\'') + await user.click(screen.getByTestId('submit-custom-enemy')) + await user.click(screen.getByTestId('start-button')) + + expect(screen.getByTestId('enemy-short')).toHaveTextContent('AI: custom') + + // current piece is a J... assertion TODO }) - it('supports a custom AI with state', () => { - const game = getGame() + it('supports a custom AI with state', async () => { + renderGame() - game.find('.e2e__select-ai').simulate('click') - game.find('.e2e__custom-enemy').simulate('click') - game.find('.e2e__ai-textarea').simulate('change', { - target: { - // AI state is last piece sent - // (_, aiState) => aiState === 'S' ? ['Z', 'Z'] : ['S', 'S'] - value: '(_, aiState) => aiState === \'S\' ? [\'Z\', \'Z\'] : [\'S\', \'S\']' - } - }) - game.find('.e2e__submit-custom-enemy').simulate('click') - game.find('.e2e__start-button').simulate('click') + await user.click(screen.getByTestId('select-ai')) + await user.click(screen.getByTestId('custom-enemy')) - expect(game.state().wellStates[game.state().wellStateId].piece.id).toBe('S') - for (let i = 0; i < 18; i++) { - game.instance().handleDown() - } - expect(game.state().wellStates[game.state().wellStateId].piece.id).toBe('Z') + // AI state is last piece sent + // (_, aiState) => aiState === 'S' ? ['Z', 'Z'] : ['S', 'S'] + await user.type(screen.getByTestId('ai-textarea'), '(_, aiState) => aiState === \'S\' ? [[\'Z\', \'Z\'] : [[\'S\', \'S\']') + await user.click(screen.getByTestId('submit-custom-enemy')) + await user.click(screen.getByTestId('start-button')) + + // first piece is an S... assertion TODO + // second piece is a Z... assertion TODO }) - it('lets you decide NOT to use a custom AI', () => { - const game = getGame() + it('lets you decide NOT to use a custom AI', async () => { + renderGame() - game.find('.e2e__select-ai').simulate('click') - game.find('.e2e__custom-enemy').simulate('click') - game.find('.e2e__cancel-custom-enemy').simulate('click') - expect(game.state()).toEqual(expect.objectContaining({ - customAiCode: '', - mode: 'SELECT_AI' - })) + await user.click(screen.getByTestId('select-ai')) + await user.click(screen.getByTestId('custom-enemy')) + await user.click(screen.getByTestId('cancel-custom-enemy')) + + expect(screen.queryAllByTestId('enemy')).toHaveLength(4) }) - it('errors out if your custom AI is invalid JavaScript, but you can dismiss it', () => { - const game = getGame() + it('errors out if your custom AI is invalid JavaScript, but you can dismiss it', async () => { + renderGame() - game.find('.e2e__select-ai').simulate('click') - game.find('.e2e__custom-enemy').simulate('click') - game.find('.e2e__ai-textarea').simulate('change', { - target: { - value: '() =>' - } - }) + await user.click(screen.getByTestId('select-ai')) + await user.click(screen.getByTestId('custom-enemy')) + await user.type(screen.getByTestId('ai-textarea'), '() =>') const error = console.error console.error = jest.fn() - game.find('.e2e__submit-custom-enemy').simulate('click') + await user.click(screen.getByTestId('submit-custom-enemy')) console.error = error - expect(game.state()).toEqual(expect.objectContaining({ - error: { - interpretation: 'Caught this exception while trying to evaluate your custom AI JavaScript.', - real: expect.any(String) - } - })) + expect(screen.getByTestId('error-interpretation')).toHaveTextContent('Caught this exception while trying to evaluate your custom AI JavaScript.') - game.find('.e2e__dismiss-error').simulate('click') - expect(game.state()).toEqual(expect.objectContaining({ - error: null - })) + await user.click(screen.getByTestId('dismiss-error')) }) - it('errors out if your custom AI throws an error on the first piece', () => { - const game = getGame() + it('errors out if your custom AI throws an error on the first piece', async () => { + renderGame() - game.find('.e2e__select-ai').simulate('click') - game.find('.e2e__custom-enemy').simulate('click') - game.find('.e2e__ai-textarea').simulate('change', { - target: { - value: '() => { throw Error(\'BANG\') }' - } - }) - game.find('.e2e__submit-custom-enemy').simulate('click') + await user.click(screen.getByTestId('select-ai')) + await user.click(screen.getByTestId('custom-enemy')) + await user.type(screen.getByTestId('ai-textarea'), '() => {{ throw Error(\'BANG\') }') + await user.click(screen.getByTestId('submit-custom-enemy')) // Start a replay instead of starting a new game (coverage) // TODO: deduplicate that code @@ -586,190 +227,102 @@ describe('', () => { console.error = jest.fn() const prompt = jest.spyOn(window, 'prompt') prompt.mockReturnValueOnce('') - game.find('.e2e__replay-button').simulate('click') + await user.click(screen.getByTestId('replay-button')) + expect(prompt.mock.calls).toEqual([ + ['Paste replay string...'] + ]) prompt.mockRestore() console.error = error - expect(game.state()).toEqual(expect.objectContaining({ - error: { - interpretation: 'Caught this exception while trying to generate the first piece using your custom enemy AI. Game abandoned.', - real: 'BANG' - } - })) + expect(screen.getByTestId('error-real')).toHaveTextContent('BANG') + expect(screen.getByTestId('error-interpretation')).toHaveTextContent('Caught this exception while trying to generate the first piece using your custom enemy AI. Game abandoned.') }) - it('errors out if your custom AI returns a bad piece', () => { - const game = getGame() + it('errors out if your custom AI returns a bad piece', async () => { + renderGame() - game.find('.e2e__select-ai').simulate('click') - game.find('.e2e__custom-enemy').simulate('click') - game.find('.e2e__ai-textarea').simulate('change', { - target: { - value: '() => \'K\'' - } - }) - game.find('.e2e__submit-custom-enemy').simulate('click') + await user.click(screen.getByTestId('select-ai')) + await user.click(screen.getByTestId('custom-enemy')) + await user.type(screen.getByTestId('ai-textarea'), '() => \'K\'') + await user.click(screen.getByTestId('submit-custom-enemy')) const error = console.error console.error = jest.fn() - game.find('.e2e__start-button').simulate('click') + await user.click(screen.getByTestId('start-button')) console.error = error - expect(game.state()).toEqual(expect.objectContaining({ - error: { - interpretation: 'Caught this exception while trying to generate the first piece using your custom enemy AI. Game abandoned.', - real: 'Bad piece ID: K' - } - })) + expect(screen.getByTestId('error-real')).toHaveTextContent('Bad piece ID: K') + expect(screen.getByTestId('error-interpretation')).toHaveTextContent('Caught this exception while trying to generate the first piece using your custom enemy AI. Game abandoned.') }) - it('errors out if your custom AI throws an error on a later piece', () => { - const game = getGame() - - game.find('.e2e__select-ai').simulate('click') - game.find('.e2e__custom-enemy').simulate('click') - game.find('.e2e__ai-textarea').simulate('change', { - target: { - value: ` - (() => { - let first = true - return () => { - if (first) { - first = false - return 'I' - } - throw Error('FZAAPP') - } - })() - ` - } - }) - game.find('.e2e__submit-custom-enemy').simulate('click') - game.find('.e2e__start-button').simulate('click') + it('errors out if your custom AI throws an error on a later piece', async () => { + renderGame() + + await user.click(screen.getByTestId('select-ai')) + await user.click(screen.getByTestId('custom-enemy')) + await user.type(screen.getByTestId('ai-textarea'), ` + (() => {{ + let first = true + return () => {{ + if (first) {{ + first = false + return 'I' + } + throw Error('FZAAPP') + } + })() + `) + await user.click(screen.getByTestId('submit-custom-enemy')) + await user.click(screen.getByTestId('start-button')) for (let i = 0; i < 18; i++) { - expect(game.state().error).toBeNull() - game.instance().handleDown() + await user.click(screen.getByTestId('down')) } const error = console.error console.error = jest.fn() - expect(game.state().error).toBeNull() - game.instance().handleDown() + await user.click(screen.getByTestId('down')) console.error = error - expect(game.state()).toEqual(expect.objectContaining({ - error: { - interpretation: 'Caught this exception while trying to generate a new piece using your custom AI. Game halted.', - real: 'FZAAPP' - } - })) + expect(screen.getByTestId('error-real')).toHaveTextContent('FZAAPP') + expect(screen.getByTestId('error-interpretation')).toHaveTextContent('Caught this exception while trying to generate a new piece using your custom AI. Game halted.') }) - it('lets you decide not to replay anything', () => { - const game = getGame() + it('lets you decide not to replay anything', async () => { + renderGame() const prompt = jest.spyOn(window, 'prompt') prompt.mockReturnValueOnce(null) - game.find('.e2e__replay-button').simulate('click') + await user.click(screen.getByTestId('replay-button')) + expect(prompt.mock.calls).toEqual([ + ['Paste replay string...'] + ]) prompt.mockRestore() - - expect(game.state()).toEqual(expect.objectContaining({ - enemy: hatetris, - mode: 'INITIAL', - replay: [], - replayTimeoutId: undefined, - wellStates: [], - wellStateId: -1 - })) - - game.unmount() }) describe('when a replay is in progress', () => { - let game: ReturnType - - beforeEach(() => { - game = getGame() + beforeEach(async () => { + renderGame() const prompt = jest.spyOn(window, 'prompt') prompt.mockReturnValueOnce('AAAA AAAA AAAA AAAA AAAA AAAA AAAA AAAA AAAA AAAA AAAA A2') - game.find('.e2e__replay-button').simulate('click') + await user.click(screen.getByTestId('replay-button')) + expect(prompt.mock.calls).toEqual([ + ['Paste replay string...'] + ]) prompt.mockRestore() - // Play a little of the replay - jest.runOnlyPendingTimers() - jest.runOnlyPendingTimers() - jest.runOnlyPendingTimers() - - expect(game.state()).toEqual(expect.objectContaining({ - enemy: hatetris, - mode: 'REPLAYING', - wellStates: [ - expect.anything(), - expect.anything(), - expect.anything(), - expect.anything() - ], - wellStateId: 3, - replayTimeoutId: expect.any(Number) - })) - }) - - afterEach(() => { - game.unmount() + // Play three moves of the replay + jest.advanceTimersByTime(replayTimeout * 3.5) }) - it('lets you start a new game', () => { - // TODO: this is no longer provided in the UI... - game.instance().handleClickStart() - expect(game.state()).toEqual(expect.objectContaining({ - enemy: hatetris, - mode: 'PLAYING', - wellStates: [ - expect.anything() - ], - wellStateId: 0, - replayTimeoutId: undefined // trashed - })) - }) - - it('lets you start a new replay', () => { - // TODO: this is no longer provided in the UI... - const prompt = jest.spyOn(window, 'prompt') - prompt.mockReturnValueOnce('AAAA 1234 BCDE 2345 CDEF 3456') - game.instance().handleClickReplay() - prompt.mockRestore() - - expect(game.state()).toEqual(expect.objectContaining({ - enemy: hatetris, - mode: 'REPLAYING', - wellStates: [ - expect.anything() - ], - wellStateId: 0, - replayTimeoutId: expect.any(Number) - })) - }) - - it('lets you undo and stops replaying if you do so', () => { - game.instance().handleDocumentKeyDown(new window.KeyboardEvent('keydown', { key: 'z', ctrlKey: true })) - expect(game.state()).toEqual(expect.objectContaining({ - enemy: hatetris, - mode: 'PLAYING', // no longer replaying - wellStates: [ - expect.anything(), - expect.anything(), - expect.anything(), - expect.anything() // well state ID 3 still exists - ], - wellStateId: 2, // down from 3 - replayTimeoutId: undefined - })) + it('lets you undo and stops replaying if you do so', async () => { + await user.keyboard('{Control>}z{/Control}') + // TODO: assert that `wellStateId` is now 2, down from 3 }) }) - describe('check known replays', () => { + describe('check known replays', function () { const runs = [{ name: 'qntm', expectedScore: 0, @@ -879,54 +432,51 @@ describe('', () => { runs.forEach(run => { describe(run.name, () => { Object.entries(run.replays).forEach(([encoding, string]) => { - it(encoding, () => { + it(encoding, async () => { const warn = console.warn console.warn = jest.fn() - const game = getGame() + renderGame() const prompt = jest.spyOn(window, 'prompt') prompt.mockReturnValueOnce(string) - game.find('.e2e__replay-button').simulate('click') + await user.click(screen.getByTestId('replay-button')) + expect(prompt.mock.calls).toEqual([ + ['Paste replay string...'] + ]) prompt.mockRestore() jest.runAllTimers() - const state = game.state() - expect(state.mode).toBe('GAME_OVER') - expect(state.wellStates[state.wellStateId].core.score).toBe(run.expectedScore) + expect(screen.getByTestId('score')).toHaveTextContent(String(run.expectedScore)) if (encoding === 'Base2048') { - expect(game.find('.e2e__replay-out').text()).toBe(string) + expect(screen.getByTestId('replay-out')).toHaveTextContent(string) } else { // Other encodings have differing amounts of padding so result in slightly // different output Base2048 } // Copy the replay - return game.instance().handleClickCopyReplay() - .then(() => navigator.clipboard.readText()) - .then(contents => { - if (encoding === 'Base2048') { - expect(contents).toBe(string) - } else { - // Other encodings have differing amounts of padding so result in slightly - // different output Base2048 - } - - // "copied!" disappears after a while - expect(game.state().replayCopiedTimeoutId).toEqual(expect.any(Number)) - expect(game.find('.e2e__copy-replay').text()).toBe('copied!') - - jest.runAllTimers() - expect(game.state().replayCopiedTimeoutId).toBeUndefined() - - game.find('.e2e__done').simulate('click') - expect(game.state().mode).toBe('INITIAL') - game.unmount() - - // TODO: maybe some assertions about how many trailing moves were ignored - console.warn = warn - }) + await user.click(screen.getByTestId('copy-replay')) + const contents = await navigator.clipboard.readText() + if (encoding === 'Base2048') { + expect(contents).toBe(string) + } else { + // Other encodings have differing amounts of padding so result in slightly + // different output Base2048 + } + + // "copied!" disappears after a while + expect(screen.getByTestId('copy-replay')).toHaveTextContent('copied!') + + jest.runAllTimers() + + expect(screen.getByTestId('copy-replay')).toHaveTextContent('copy replay') + + await user.click(screen.getByTestId('done')) + + // TODO: maybe some assertions about how many trailing moves were ignored + console.warn = warn }) }) }) @@ -934,28 +484,30 @@ describe('', () => { }) describe('Brzustowski algorithm', () => { - it('works?', () => { + it('works?', async () => { const replay = 'ౚටฅٽ௨෨ଈݚСචƘݷౚ೯ບߢ௨චÐݺɷගÐݚɷ౾ܖࠆಛقຽঅ௩قଭݪ௧ڠଭɟ௨ගɑݸ౻ටПݹ౻ඪܖࢭࢶටະऔ௨ชІݶಒටତݹ௮੬ໃए௨ඩషݹࢳΟໃঅ௩Ϻසݶ౻ටลɒƐปໄஈб೯ܘމலڄໃѣҳబຽࢭ௧پܖࠇமقܬݶసطДݺஶϺ༠ɖɷڠПݚݫට༨অ௩Ѳໃףذචʃݹ୦Ѹໃɠ௨ಀІݪɷలฅהԥೱ൧ݺذගІܭϟقܯஈলටݕݺɷඪ൧ރ௧ڠໞݶಏكɑקযඤʃܭدڠЖܭИටКђ௬ට༠ݹଛʈໄຍ௩Ϻໄຍஶشܙਨ௩ѸແࡨߛࡆІܭЬقଚԓஶѮໄຍ४ΟฦݶƖقФ୬௧ٴະࡆذ౾ಊݪСඦബঅࢴඥܨІةɤໃקਙชІࢭরइໄЅЗࡄฅݹઽඨƞݷసقʃࢭࠒΟฆݹߝɈฅђߝԚໄŦ௬ॴ༠הభ൝Іࢭஜу༨ঀಇΘuঅఴуଞݺد෪ງڝࢶ੬ПܭɷٯฦݚСಀഫɟஜضÐɝɴฃ༥މభ෨ܐঔਆ' const expectedScore = 55 const warn = console.warn console.warn = jest.fn() - const game = getGame() + renderGame() - game.find('.e2e__select-ai').simulate('click') - game.find('.e2e__enemy').at(2).simulate('click') + await user.click(screen.getByTestId('select-ai')) + await user.click(screen.queryAllByTestId('enemy').at(2)) const prompt = jest.spyOn(window, 'prompt') prompt.mockReturnValueOnce(replay) - game.find('.e2e__replay-button').simulate('click') + await user.click(screen.getByTestId('replay-button')) + expect(prompt.mock.calls).toEqual([ + ['Paste replay string...'] + ]) prompt.mockRestore() - jest.runAllTimers() + // Replay is about 2400 moves long + jest.advanceTimersByTime(replayTimeout * 10000) - const state = game.state() - expect(state.mode).toBe('GAME_OVER') - expect(state.wellStates[state.wellStateId].core.score).toBe(expectedScore) + expect(screen.getByTestId('score')).toHaveTextContent(String(expectedScore)) console.warn = warn }) }) diff --git a/src/components/Game/Game.tsx b/src/components/Game/Game.tsx index 291efb9..9931bca 100644 --- a/src/components/Game/Game.tsx +++ b/src/components/Game/Game.tsx @@ -79,7 +79,8 @@ type GameProps = { type GameState = { error: { interpretation: string, - real: string + real: string, + dismissable: boolean }, displayEnemy: boolean, enemy: Enemy, @@ -145,20 +146,28 @@ class Game extends React.Component { wellWidth } = this.props + let error + if (Object.keys(rotationSystem.rotations).length < 1) { - throw Error('Have to have at least one piece!') + error = Error('Have to have at least one piece!') } if (wellDepth < bar) { - throw Error("Can't have well with depth " + String(wellDepth) + ' less than bar at ' + String(bar)) + error = Error("Can't have well with depth " + String(wellDepth) + ' less than bar at ' + String(bar)) } if (wellWidth < minWidth) { - throw Error("Can't have well with width " + String(wellWidth) + ' less than ' + String(minWidth)) + error = Error("Can't have well with width " + String(wellWidth) + ' less than ' + String(minWidth)) } this.state = { - error: null, + error: error + ? { + interpretation: 'Caught this exception while trying to start HATETRIS. Application halted.', + real: error.message, + dismissable: false + } + : null, displayEnemy: false, // don't show it unless the user selects one manually enemy: hatetris, customAiCode: '', @@ -406,7 +415,8 @@ class Game extends React.Component { this.setState({ error: { interpretation: 'Caught this exception while trying to generate the first piece using your custom enemy AI. Game abandoned.', - real: error.message + real: error.message, + dismissable: true } }) return @@ -460,7 +470,8 @@ class Game extends React.Component { this.setState({ error: { interpretation: 'Caught this exception while trying to generate the first piece using your custom enemy AI. Game abandoned.', - real: error.message + real: error.message, + dismissable: true } }) return @@ -572,7 +583,8 @@ class Game extends React.Component { this.setState({ error: { interpretation: 'Caught this exception while trying to generate a new piece using your custom AI. Game halted.', - real: error.message + real: error.message, + dismissable: true } }) return @@ -764,7 +776,8 @@ class Game extends React.Component { this.setState({ error: { interpretation: 'Caught this exception while trying to evaluate your custom AI JavaScript.', - real: error.message + real: error.message, + dismissable: true } }) return @@ -811,29 +824,41 @@ class Game extends React.Component { return (

Error

-

+

{error.real}

-

+

{error.interpretation}

-

To fix this

-

- Check your browser console for more information. - Use this information to fix your AI code and submit it again. - Or, use one of the preset AIs instead. -

+ {error.dismissable && ( + <> +

To fix this

+

+ Check your browser console for more information. + Use this information to fix your AI code and submit it again. + Or, use one of the preset AIs instead. +

-

- -

+

+ +

+ + )} + + {!error.dismissable && ( + <> +

To fix this

+

Report this problem to qntm.

+ + )}
) } @@ -856,13 +881,19 @@ class Game extends React.Component {

{displayEnemy && ( -

+

AI: {enemy.shortDescription}

)} {score !== null && ( -

+

score: {score}

)} @@ -890,7 +921,8 @@ class Game extends React.Component { {mode === 'INITIAL' && (

@@ -965,14 +1000,16 @@ class Game extends React.Component {