From 77da72184b1396514452992a59e521671b3a4bfe Mon Sep 17 00:00:00 2001 From: Jose Browne Date: Sun, 13 Oct 2024 12:19:50 -0400 Subject: [PATCH] Major Refactor: Query + Completion Logic + Testing Infrastructure (#31) * Checkpoint * Refactor limit logic * WIP: Bug Fix: Limit logic should skip round if needed to get target due/new in other deck * Fix go to next deck from done screen when on done screen currentCardData is undefined. * WIP: Show completed count on finish screen * WIP: Saving current state before pivot * Ignore lottie type error * Finalize refactor to calculate completed count without storing state and persisting distribution accross re-runs * Fix missing card bug while grading * WIP: Fix overlay counts * Update display counters to reflect completed today * Fix useCurrentCardData tests * WIP: Fix card resolution logic * fix timezone test issue * Fix practice click delay by advancing index right away We reset after we fetch the new list * fix types * supress componentWillUpdate warning Here we don't control the react version * Add first overlay test finished state * Fix issues + add more tests * fix lint errors * fix skipping bug done state needed to account for doing so without grading (which recalculates today state) * Bug Fix: missing new cards * Fix cramming missing cards * on close, reset cramming state * fix cramming mode bugs --- .babelrc | 4 - .babelrc.js | 14 + .eslintignore | 2 +- jest.setup.js | 3 - jest.setup.ts | 26 + package-lock.json | 2021 +++++++++++++---- package.json | 21 +- src/app.tsx | 56 +- src/components/SidePanelWidget.test.tsx | 851 +++++++ ...dePandelWidget.tsx => SidePanelWidget.tsx} | 26 +- src/components/overlay/CardBlock.tsx | 2 +- src/components/overlay/Footer.tsx | 192 +- .../overlay/PracticeOverlay.test.tsx | 233 ++ src/components/overlay/PracticeOverlay.tsx | 229 +- src/hooks/useCachedData.ts | 20 +- src/hooks/useCurrentCardData.tsx | 151 +- src/hooks/useCurrentCarddata.test.tsx | 221 +- src/hooks/usePracticeData.tsx | 94 +- src/hooks/useSafeContext.tsx | 7 + src/hooks/useSettings.ts | 8 +- src/hooks/useTags.tsx | 6 +- src/models/practice.ts | 54 + src/queries.test.ts | 195 -- src/queries.ts | 679 ++++-- src/utils/date.test.ts | 2 +- src/utils/object.ts | 3 + src/utils/testUtils.ts | 355 ++- tsconfig.json | 5 +- 28 files changed, 4023 insertions(+), 1457 deletions(-) delete mode 100644 .babelrc create mode 100644 .babelrc.js delete mode 100644 jest.setup.js create mode 100644 jest.setup.ts create mode 100644 src/components/SidePanelWidget.test.tsx rename src/components/{SidePandelWidget.tsx => SidePanelWidget.tsx} (64%) create mode 100644 src/components/overlay/PracticeOverlay.test.tsx create mode 100644 src/hooks/useSafeContext.tsx create mode 100644 src/models/practice.ts delete mode 100644 src/queries.test.ts create mode 100644 src/utils/object.ts diff --git a/.babelrc b/.babelrc deleted file mode 100644 index 5671d07..0000000 --- a/.babelrc +++ /dev/null @@ -1,4 +0,0 @@ -{ - "presets": ["@babel/preset-env", "@babel/preset-react", "@babel/preset-typescript"], - "plugins": ["@emotion", "@babel/proposal-class-properties", "@babel/proposal-object-rest-spread"] -} diff --git a/.babelrc.js b/.babelrc.js new file mode 100644 index 0000000..22b3328 --- /dev/null +++ b/.babelrc.js @@ -0,0 +1,14 @@ +const isTest = String(process.env.NODE_ENV) === 'test' + +module.exports = { + presets: [ + [ + "@babel/preset-react", + { + ...(isTest && { runtime: "automatic" }) + } + ], + "@babel/preset-env", + "@babel/preset-typescript" + ], +} diff --git a/.eslintignore b/.eslintignore index e2aa1e0..897af6b 100644 --- a/.eslintignore +++ b/.eslintignore @@ -3,4 +3,4 @@ tailwind.config.js webpack.config.js changelog-setup.js -jest.setup.js +jest.setup.ts diff --git a/jest.setup.js b/jest.setup.js deleted file mode 100644 index 924680d..0000000 --- a/jest.setup.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = () => { - process.env.TZ = 'UTC'; -}; diff --git a/jest.setup.ts b/jest.setup.ts new file mode 100644 index 0000000..7cf386d --- /dev/null +++ b/jest.setup.ts @@ -0,0 +1,26 @@ +import '@testing-library/jest-dom'; + +const originalConsoleError = console.error; +const originalConsoleWarn = console.warn; + +beforeAll(() => { + // Since we don't have control over the version of react installed on roam + // let's just supress these warnings + console.error = (...args) => { + if (/Invalid prop/.test(args.toString())) { + return; + } + originalConsoleError(...args); + }; + console.warn = (...args) => { + if (/componentWillUpdate has been renamed/.test(args.toString())) { + return; + } + originalConsoleWarn(...args); + }; +}); + +afterAll(() => { + console.error = originalConsoleError; + console.warn = originalConsoleWarn; +}); diff --git a/package-lock.json b/package-lock.json index c5dfd08..e758587 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,7 +15,6 @@ "@emotion/styled": "^11.10.0", "arrive": "^2.4.1", "dayjs": "^1.11.5", - "lottie-react": "^2.4.0", "react": "^17.0.2", "react-dom": "^17.0.2", "react-lottie": "^1.2.3" @@ -29,10 +28,15 @@ "@babel/preset-react": "^7.18.6", "@babel/preset-typescript": "^7.18.6", "@emotion/babel-plugin": "^11.10.0", + "@testing-library/dom": "^10.3.0", + "@testing-library/jest-dom": "^6.4.6", + "@testing-library/react": "^12.1.5", "@testing-library/react-hooks": "^8.0.1", "@types/arrive": "^2.4.1", "@types/jest": "^28.1.7", - "@types/react": "^18.0.21", + "@types/react": "^17.0.2", + "@types/react-dom": "^17.0.2", + "@types/react-lottie": "^1.2.3", "auto-changelog": "^2.4.0", "babel-jest": "^28.1.3", "babel-loader": "^8.2.5", @@ -40,6 +44,7 @@ "eslint-plugin-react": "^7.30.1", "eslint-plugin-react-hooks": "^4.6.0", "jest": "^28.1.3", + "jest-canvas-mock": "^2.5.2", "jest-environment-jsdom": "^28.1.3", "prettier": "2.7.1", "tailwindcss": "^3.4.1", @@ -59,6 +64,12 @@ "node": ">=0.10.0" } }, + "node_modules/@adobe/css-tools": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.0.tgz", + "integrity": "sha512-Ff9+ksdQQB3rMncgqDK78uLznstjyfIf2Arnh22pW8kBpLs6rpKDwgnZT46hin5Hl1WzazzK64DOrhSwYpS7bQ==", + "dev": true + }, "node_modules/@alloc/quick-lru": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", @@ -3222,198 +3233,623 @@ "@sinonjs/commons": "^1.7.0" } }, - "node_modules/@testing-library/react-hooks": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/@testing-library/react-hooks/-/react-hooks-8.0.1.tgz", - "integrity": "sha512-Aqhl2IVmLt8IovEVarNDFuJDVWVvhnr9/GCU6UUnrYXwgDFF9h2L2o2P9KBni1AST5sT6riAyoukFLyjQUgD/g==", + "node_modules/@testing-library/dom": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.3.0.tgz", + "integrity": "sha512-pT/TYB2+IyMYkkB6lqpkzD7VFbsR0JBJtflK3cS68sCNWxmOhWwRm1XvVHlseNEorsNcxkYsb4sRDV3aNIpttg==", "dev": true, "dependencies": { + "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", - "react-error-boundary": "^3.1.0" + "@types/aria-query": "^5.0.1", + "aria-query": "5.3.0", + "chalk": "^4.1.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "pretty-format": "^27.0.2" }, "engines": { - "node": ">=12" - }, - "peerDependencies": { - "@types/react": "^16.9.0 || ^17.0.0", - "react": "^16.9.0 || ^17.0.0", - "react-dom": "^16.9.0 || ^17.0.0", - "react-test-renderer": "^16.9.0 || ^17.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "react-dom": { - "optional": true - }, - "react-test-renderer": { - "optional": true - } + "node": ">=18" } }, - "node_modules/@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==", + "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": ">= 10" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@types/arrive": { - "version": "2.4.4", - "resolved": "https://registry.npmjs.org/@types/arrive/-/arrive-2.4.4.tgz", - "integrity": "sha512-j5RpdCbOgKnEjqsDlmoB3FezwiUPDnVlCxwunqVpNgpmHjjwaUUE/GKyjfOI1sZbJcypsRVgREGIuEbB4pKHFA==", - "dev": true - }, - "node_modules/@types/babel__core": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", - "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "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": { - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@types/babel__generator": { - "version": "7.6.8", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz", - "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==", + "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": { - "@babel/types": "^7.0.0" + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" } }, - "node_modules/@types/babel__template": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", - "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", - "dev": true, - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^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/@types/babel__traverse": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.5.tgz", - "integrity": "sha512-WXCyOcRtH37HAUkpXhUduaxdm82b4GSlyTqajXviN4EfiuPgNYR109xMCKvpl6zPIpua0DGlMEDCq+g8EdoheQ==", + "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, - "dependencies": { - "@babel/types": "^7.20.7" + "engines": { + "node": ">=8" } }, - "node_modules/@types/dom4": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@types/dom4/-/dom4-2.0.4.tgz", - "integrity": "sha512-PD+wqNhrjWFjAlSVd18jvChZvOXB2SOwAILBmuYev5zswBats5qmzs/QFoooLKd2omj9BT05a8MeSeRmXLGY+Q==" - }, - "node_modules/@types/eslint": { - "version": "8.56.2", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.2.tgz", - "integrity": "sha512-uQDwm1wFHmbBbCZCqAlq6Do9LYwByNZHWzXppSnay9SuwJ+VRbjkbLABer54kcPnMSlG6Fdiy2yaFXm/z9Z5gw==", + "node_modules/@testing-library/dom/node_modules/pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", "dev": true, "dependencies": { - "@types/estree": "*", - "@types/json-schema": "*" + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, - "node_modules/@types/eslint-scope": { - "version": "3.7.7", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", - "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", + "node_modules/@testing-library/dom/node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, - "dependencies": { - "@types/eslint": "*", - "@types/estree": "*" + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@types/estree": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", - "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "node_modules/@testing-library/dom/node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", "dev": true }, - "node_modules/@types/graceful-fs": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", - "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", + "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": { - "@types/node": "*" + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, - "node_modules/@types/istanbul-lib-coverage": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", - "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", - "dev": true - }, - "node_modules/@types/istanbul-lib-report": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", - "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "node_modules/@testing-library/jest-dom": { + "version": "6.4.6", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.4.6.tgz", + "integrity": "sha512-8qpnGVincVDLEcQXWaHOf6zmlbwTKc6Us6PPu4CRnPXCzo2OGBS5cwgMMOWdxDpEz1mkbvXHpEy99M5Yvt682w==", "dev": true, "dependencies": { - "@types/istanbul-lib-coverage": "*" + "@adobe/css-tools": "^4.4.0", + "@babel/runtime": "^7.9.2", + "aria-query": "^5.0.0", + "chalk": "^3.0.0", + "css.escape": "^1.5.1", + "dom-accessibility-api": "^0.6.3", + "lodash": "^4.17.21", + "redent": "^3.0.0" + }, + "engines": { + "node": ">=14", + "npm": ">=6", + "yarn": ">=1" + }, + "peerDependencies": { + "@jest/globals": ">= 28", + "@types/bun": "latest", + "@types/jest": ">= 28", + "jest": ">= 28", + "vitest": ">= 0.32" + }, + "peerDependenciesMeta": { + "@jest/globals": { + "optional": true + }, + "@types/bun": { + "optional": true + }, + "@types/jest": { + "optional": true + }, + "jest": { + "optional": true + }, + "vitest": { + "optional": true + } } }, - "node_modules/@types/istanbul-reports": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", - "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "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": { - "@types/istanbul-lib-report": "*" + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@types/jest": { - "version": "28.1.8", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-28.1.8.tgz", - "integrity": "sha512-8TJkV++s7B6XqnDrzR1m/TT0A0h948Pnl/097veySPN67VRAgQ4gZ7n2KfJo2rVq6njQjdxU3GCCyDvAeuHoiw==", + "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": { - "expect": "^28.0.0", - "pretty-format": "^28.0.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" } }, - "node_modules/@types/jsdom": { - "version": "16.2.15", - "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-16.2.15.tgz", - "integrity": "sha512-nwF87yjBKuX/roqGYerZZM0Nv1pZDMAT5YhOHYeM/72Fic+VEqJh4nyoqoapzJnW3pUlfxPY5FhgsJtM+dRnQQ==", + "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": { - "@types/node": "*", - "@types/parse5": "^6.0.3", - "@types/tough-cookie": "*" + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" } }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "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/@types/node": { - "version": "20.11.19", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.19.tgz", - "integrity": "sha512-7xMnVEcZFu0DikYjWOlRq7NTPETrm7teqUT2WkQjrTIkEgUyyGdWsj/Zg8bEJt5TNklzbPD1X3fqfsHw3SpapQ==", + "node_modules/@testing-library/jest-dom/node_modules/dom-accessibility-api": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz", + "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==", + "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, - "dependencies": { - "undici-types": "~5.26.4" + "engines": { + "node": ">=8" } }, - "node_modules/@types/parse-json": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", - "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==" + "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/@types/parse5": { + "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-hooks": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@testing-library/react-hooks/-/react-hooks-8.0.1.tgz", + "integrity": "sha512-Aqhl2IVmLt8IovEVarNDFuJDVWVvhnr9/GCU6UUnrYXwgDFF9h2L2o2P9KBni1AST5sT6riAyoukFLyjQUgD/g==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.12.5", + "react-error-boundary": "^3.1.0" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "@types/react": "^16.9.0 || ^17.0.0", + "react": "^16.9.0 || ^17.0.0", + "react-dom": "^16.9.0 || ^17.0.0", + "react-test-renderer": "^16.9.0 || ^17.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "react-test-renderer": { + "optional": true + } + } + }, + "node_modules/@testing-library/react/node_modules/@testing-library/dom": { + "version": "8.20.1", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-8.20.1.tgz", + "integrity": "sha512-/DiOQ5xBxgdYRC8LNk7U+RWat0S3qRLeIw3ZIkMQ9kkVlRmwD/Eg8k8CqIpD6GW7u20JIUOfMKbxtiLutpjQ4g==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.1.3", + "chalk": "^4.1.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "pretty-format": "^27.0.2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@testing-library/react/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/react/node_modules/aria-query": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.1.3.tgz", + "integrity": "sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==", + "dev": true, + "dependencies": { + "deep-equal": "^2.0.5" + } + }, + "node_modules/@testing-library/react/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/react/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/react/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/react/node_modules/deep-equal": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.3.tgz", + "integrity": "sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.0", + "call-bind": "^1.0.5", + "es-get-iterator": "^1.1.3", + "get-intrinsic": "^1.2.2", + "is-arguments": "^1.1.1", + "is-array-buffer": "^3.0.2", + "is-date-object": "^1.0.5", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "isarray": "^2.0.5", + "object-is": "^1.1.5", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.5.1", + "side-channel": "^1.0.4", + "which-boxed-primitive": "^1.0.2", + "which-collection": "^1.0.1", + "which-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/@testing-library/react/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/react/node_modules/pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@testing-library/react/node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@testing-library/react/node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true + }, + "node_modules/@testing-library/react/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/@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, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@types/aria-query": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", + "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", + "dev": true + }, + "node_modules/@types/arrive": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/@types/arrive/-/arrive-2.4.4.tgz", + "integrity": "sha512-j5RpdCbOgKnEjqsDlmoB3FezwiUPDnVlCxwunqVpNgpmHjjwaUUE/GKyjfOI1sZbJcypsRVgREGIuEbB4pKHFA==", + "dev": true + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.6.8", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz", + "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.5.tgz", + "integrity": "sha512-WXCyOcRtH37HAUkpXhUduaxdm82b4GSlyTqajXviN4EfiuPgNYR109xMCKvpl6zPIpua0DGlMEDCq+g8EdoheQ==", + "dev": true, + "dependencies": { + "@babel/types": "^7.20.7" + } + }, + "node_modules/@types/dom4": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/dom4/-/dom4-2.0.4.tgz", + "integrity": "sha512-PD+wqNhrjWFjAlSVd18jvChZvOXB2SOwAILBmuYev5zswBats5qmzs/QFoooLKd2omj9BT05a8MeSeRmXLGY+Q==" + }, + "node_modules/@types/eslint": { + "version": "8.56.2", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.2.tgz", + "integrity": "sha512-uQDwm1wFHmbBbCZCqAlq6Do9LYwByNZHWzXppSnay9SuwJ+VRbjkbLABer54kcPnMSlG6Fdiy2yaFXm/z9Z5gw==", + "dev": true, + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/eslint-scope": { + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", + "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", + "dev": true, + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "dev": true + }, + "node_modules/@types/graceful-fs": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", + "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/jest": { + "version": "28.1.8", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-28.1.8.tgz", + "integrity": "sha512-8TJkV++s7B6XqnDrzR1m/TT0A0h948Pnl/097veySPN67VRAgQ4gZ7n2KfJo2rVq6njQjdxU3GCCyDvAeuHoiw==", + "dev": true, + "dependencies": { + "expect": "^28.0.0", + "pretty-format": "^28.0.0" + } + }, + "node_modules/@types/jsdom": { + "version": "16.2.15", + "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-16.2.15.tgz", + "integrity": "sha512-nwF87yjBKuX/roqGYerZZM0Nv1pZDMAT5YhOHYeM/72Fic+VEqJh4nyoqoapzJnW3pUlfxPY5FhgsJtM+dRnQQ==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/parse5": "^6.0.3", + "@types/tough-cookie": "*" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true + }, + "node_modules/@types/node": { + "version": "20.11.19", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.19.tgz", + "integrity": "sha512-7xMnVEcZFu0DikYjWOlRq7NTPETrm7teqUT2WkQjrTIkEgUyyGdWsj/Zg8bEJt5TNklzbPD1X3fqfsHw3SpapQ==", + "dev": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/parse-json": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", + "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==" + }, + "node_modules/@types/parse5": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/@types/parse5/-/parse5-6.0.3.tgz", "integrity": "sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g==", @@ -3432,16 +3868,34 @@ "dev": true }, "node_modules/@types/react": { - "version": "18.2.56", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.56.tgz", - "integrity": "sha512-NpwHDMkS/EFZF2dONFQHgkPRwhvgq/OAvIaGQzxGSBmaeR++kTg6njr15Vatz0/2VcCEwJQFi6Jf4Q0qBu0rLA==", + "version": "17.0.80", + "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.80.tgz", + "integrity": "sha512-LrgHIu2lEtIo8M7d1FcI3BdwXWoRQwMoXOZ7+dPTW0lYREjmlHl3P0U1VD0i/9tppOuv8/sam7sOjx34TxSFbA==", "dev": true, "dependencies": { "@types/prop-types": "*", - "@types/scheduler": "*", + "@types/scheduler": "^0.16", "csstype": "^3.0.2" } }, + "node_modules/@types/react-dom": { + "version": "17.0.25", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.25.tgz", + "integrity": "sha512-urx7A7UxkZQmThYA4So0NelOVjx3V4rNFVJwp0WZlbIK5eM4rNJDiN3R/E9ix0MBh6kAEojk/9YL+Te6D9zHNA==", + "dev": true, + "dependencies": { + "@types/react": "^17" + } + }, + "node_modules/@types/react-lottie": { + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/@types/react-lottie/-/react-lottie-1.2.10.tgz", + "integrity": "sha512-rCd1p3US4ELKJlqwVnP0h5b24zt5p9OCvKUoNpYExLqwbFZMWEiJ6EGLMmH7nmq5V7KomBIbWO2X/XRFsL0vCA==", + "dev": true, + "dependencies": { + "@types/react": "*" + } + }, "node_modules/@types/scheduler": { "version": "0.16.8", "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.8.tgz", @@ -4180,6 +4634,15 @@ "sprintf-js": "~1.0.2" } }, + "node_modules/aria-query": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "dev": true, + "dependencies": { + "dequal": "^2.0.3" + } + }, "node_modules/array-buffer-byte-length": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", @@ -5041,6 +5504,12 @@ "node": ">= 8" } }, + "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", @@ -5053,6 +5522,12 @@ "node": ">=4" } }, + "node_modules/cssfontparser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/cssfontparser/-/cssfontparser-1.2.1.tgz", + "integrity": "sha512-6tun4LoZnj7VN6YeegOVb67KBX/7JJsqvj+pv3ZA7F878/eN33AbGa5b/S/wXxS/tcp8nc40xRUrsPlxIyNUPg==", + "dev": true + }, "node_modules/cssom": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz", @@ -5218,6 +5693,15 @@ "node": ">=0.4.0" } }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/detect-newline": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", @@ -5272,6 +5756,12 @@ "node": ">=6.0.0" } }, + "node_modules/dom-accessibility-api": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", + "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", + "dev": true + }, "node_modules/dom-helpers": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-3.4.0.tgz", @@ -5444,6 +5934,26 @@ "node": ">= 0.4" } }, + "node_modules/es-get-iterator": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz", + "integrity": "sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "has-symbols": "^1.0.3", + "is-arguments": "^1.1.1", + "is-map": "^2.0.2", + "is-set": "^2.0.2", + "is-string": "^1.0.7", + "isarray": "^2.0.5", + "stop-iteration-iterator": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/es-iterator-helpers": { "version": "1.0.17", "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.0.17.tgz", @@ -6743,6 +7253,15 @@ "node": ">=0.8.19" } }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -7397,6 +7916,16 @@ } } }, + "node_modules/jest-canvas-mock": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jest-canvas-mock/-/jest-canvas-mock-2.5.2.tgz", + "integrity": "sha512-vgnpPupjOL6+L5oJXzxTxFrlGEIbHdZqFU+LFNdtLxZ3lRDCl17FlTMM7IatoRQkrcyOTMlDinjUguqmQ6bR2A==", + "dev": true, + "dependencies": { + "cssfontparser": "^1.2.1", + "moo-color": "^1.0.2" + } + }, "node_modules/jest-changed-files": { "version": "28.1.3", "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-28.1.3.tgz", @@ -9200,6 +9729,12 @@ "node": ">=8" } }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, "node_modules/lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", @@ -9223,18 +9758,6 @@ "loose-envify": "cli.js" } }, - "node_modules/lottie-react": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/lottie-react/-/lottie-react-2.4.0.tgz", - "integrity": "sha512-pDJGj+AQlnlyHvOHFK7vLdsDcvbuqvwPZdMlJ360wrzGFurXeKPr8SiRCjLf3LrNYKANQtSsh5dz9UYQHuqx4w==", - "dependencies": { - "lottie-web": "^5.10.2" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" - } - }, "node_modules/lottie-web": { "version": "5.12.2", "resolved": "https://registry.npmjs.org/lottie-web/-/lottie-web-5.12.2.tgz", @@ -9249,6 +9772,15 @@ "yallist": "^3.0.2" } }, + "node_modules/lz-string": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", + "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", + "dev": true, + "bin": { + "lz-string": "bin/bin.js" + } + }, "node_modules/make-dir": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", @@ -9331,6 +9863,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/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -9361,6 +9902,21 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/moo-color": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/moo-color/-/moo-color-1.0.3.tgz", + "integrity": "sha512-i/+ZKXMDf6aqYtBhuOcej71YSlbjT3wCO/4H1j8rPvxDJEifdwgg5MaFyu6iYAT8GBZJg2z0dkgK4YMzvURALQ==", + "dev": true, + "dependencies": { + "color-name": "^1.1.4" + } + }, + "node_modules/moo-color/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/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -10302,6 +10858,19 @@ "node": ">= 0.10" } }, + "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/reflect.getprototypeof": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.5.tgz", @@ -10801,6 +11370,18 @@ "node": ">=8" } }, + "node_modules/stop-iteration-iterator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz", + "integrity": "sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==", + "dev": true, + "dependencies": { + "internal-slot": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/string-length": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", @@ -10951,6 +11532,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", @@ -12378,6 +12971,12 @@ "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", "dev": true }, + "@adobe/css-tools": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.0.tgz", + "integrity": "sha512-Ff9+ksdQQB3rMncgqDK78uLznstjyfIf2Arnh22pW8kBpLs6rpKDwgnZT46hin5Hl1WzazzK64DOrhSwYpS7bQ==", + "dev": true + }, "@alloc/quick-lru": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", @@ -14009,53 +14608,281 @@ "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", "dev": true, "requires": { - "ansi-regex": "^6.0.1" + "ansi-regex": "^6.0.1" + } + }, + "wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "requires": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + } + } + } + }, + "@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "requires": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + } + }, + "@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true + }, + "@jest/console": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-28.1.3.tgz", + "integrity": "sha512-QPAkP5EwKdK/bxIr6C1I4Vs0rm2nHiANzj/Z5X2JQkrZo6IqvC4ldZ9K95tF0HdidhA8Bo6egxSzUFPYKcEXLw==", + "dev": true, + "requires": { + "@jest/types": "^28.1.3", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^28.1.3", + "jest-util": "^28.1.3", + "slash": "^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": "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" + } + } + } + }, + "@jest/core": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-28.1.3.tgz", + "integrity": "sha512-CIKBrlaKOzA7YG19BEqCw3SLIsEwjZkeJzf5bdooVnW4bH5cktqe3JX+G2YV1aK5vP8N9na1IGWFzYaTp6k6NA==", + "dev": true, + "requires": { + "@jest/console": "^28.1.3", + "@jest/reporters": "^28.1.3", + "@jest/test-result": "^28.1.3", + "@jest/transform": "^28.1.3", + "@jest/types": "^28.1.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^28.1.3", + "jest-config": "^28.1.3", + "jest-haste-map": "^28.1.3", + "jest-message-util": "^28.1.3", + "jest-regex-util": "^28.0.2", + "jest-resolve": "^28.1.3", + "jest-resolve-dependencies": "^28.1.3", + "jest-runner": "^28.1.3", + "jest-runtime": "^28.1.3", + "jest-snapshot": "^28.1.3", + "jest-util": "^28.1.3", + "jest-validate": "^28.1.3", + "jest-watcher": "^28.1.3", + "micromatch": "^4.0.4", + "pretty-format": "^28.1.3", + "rimraf": "^3.0.0", + "slash": "^3.0.0", + "strip-ansi": "^6.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": "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" } }, - "wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "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": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" + "has-flag": "^4.0.0" } } } }, - "@istanbuljs/load-nyc-config": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", - "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "@jest/environment": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-28.1.3.tgz", + "integrity": "sha512-1bf40cMFTEkKyEf585R9Iz1WayDjHoHqvts0XFYEqyKM3cFWDpeMoqKKTAF9LSYQModPUlh8FKptoM2YcMWAXA==", "dev": true, "requires": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" + "@jest/fake-timers": "^28.1.3", + "@jest/types": "^28.1.3", + "@types/node": "*", + "jest-mock": "^28.1.3" } }, - "@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "dev": true + "@jest/expect": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-28.1.3.tgz", + "integrity": "sha512-lzc8CpUbSoE4dqT0U+g1qODQjBRHPpCPXissXD4mS9+sWQdmmpeJ9zSH1rS1HEkrsMN0fb7nKrJ9giAR1d3wBw==", + "dev": true, + "requires": { + "expect": "^28.1.3", + "jest-snapshot": "^28.1.3" + } }, - "@jest/console": { + "@jest/expect-utils": { "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-28.1.3.tgz", - "integrity": "sha512-QPAkP5EwKdK/bxIr6C1I4Vs0rm2nHiANzj/Z5X2JQkrZo6IqvC4ldZ9K95tF0HdidhA8Bo6egxSzUFPYKcEXLw==", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-28.1.3.tgz", + "integrity": "sha512-wvbi9LUrHJLn3NlDW6wF2hvIMtd4JUl2QNVrjq+IBSHirgfrR3o9RnVtxzdEGO2n9JyIWwHnLfby5KzqBGg2YA==", + "dev": true, + "requires": { + "jest-get-type": "^28.0.2" + } + }, + "@jest/fake-timers": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-28.1.3.tgz", + "integrity": "sha512-D/wOkL2POHv52h+ok5Oj/1gOG9HSywdoPtFsRCUmlCILXNn5eIWmcnd3DIiWlJnpGvQtmajqBP95Ei0EimxfLw==", + "dev": true, + "requires": { + "@jest/types": "^28.1.3", + "@sinonjs/fake-timers": "^9.1.2", + "@types/node": "*", + "jest-message-util": "^28.1.3", + "jest-mock": "^28.1.3", + "jest-util": "^28.1.3" + } + }, + "@jest/globals": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-28.1.3.tgz", + "integrity": "sha512-XFU4P4phyryCXu1pbcqMO0GSQcYe1IsalYCDzRNyhetyeyxMcIxa11qPNDpVNLeretItNqEmYYQn1UYz/5x1NA==", + "dev": true, + "requires": { + "@jest/environment": "^28.1.3", + "@jest/expect": "^28.1.3", + "@jest/types": "^28.1.3" + } + }, + "@jest/reporters": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-28.1.3.tgz", + "integrity": "sha512-JuAy7wkxQZVNU/V6g9xKzCGC5LVXx9FDcABKsSXp5MiKPEE2144a/vXTEDoyzjUpZKfVwp08Wqg5A4WfTMAzjg==", "dev": true, "requires": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^28.1.3", + "@jest/test-result": "^28.1.3", + "@jest/transform": "^28.1.3", "@jest/types": "^28.1.3", + "@jridgewell/trace-mapping": "^0.3.13", "@types/node": "*", "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^5.1.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", "jest-message-util": "^28.1.3", "jest-util": "^28.1.3", - "slash": "^3.0.0" + "jest-worker": "^28.1.3", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "terminal-link": "^2.0.0", + "v8-to-istanbul": "^9.0.1" }, "dependencies": { "ansi-styles": { @@ -14109,41 +14936,142 @@ } } }, - "@jest/core": { + "@jest/schemas": { "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-28.1.3.tgz", - "integrity": "sha512-CIKBrlaKOzA7YG19BEqCw3SLIsEwjZkeJzf5bdooVnW4bH5cktqe3JX+G2YV1aK5vP8N9na1IGWFzYaTp6k6NA==", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz", + "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==", + "dev": true, + "requires": { + "@sinclair/typebox": "^0.24.1" + } + }, + "@jest/source-map": { + "version": "28.1.2", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-28.1.2.tgz", + "integrity": "sha512-cV8Lx3BeStJb8ipPHnqVw/IM2VCMWO3crWZzYodSIkxXnRcXJipCdx1JCK0K5MsJJouZQTH73mzf4vgxRaH9ww==", + "dev": true, + "requires": { + "@jridgewell/trace-mapping": "^0.3.13", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + } + }, + "@jest/test-result": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-28.1.3.tgz", + "integrity": "sha512-kZAkxnSE+FqE8YjW8gNuoVkkC9I7S1qmenl8sGcDOLropASP+BkcGKwhXoyqQuGOGeYY0y/ixjrd/iERpEXHNg==", "dev": true, "requires": { "@jest/console": "^28.1.3", - "@jest/reporters": "^28.1.3", + "@jest/types": "^28.1.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + } + }, + "@jest/test-sequencer": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-28.1.3.tgz", + "integrity": "sha512-NIMPEqqa59MWnDi1kvXXpYbqsfQmSJsIbnd85mdVGkiDfQ9WQQTXOLsvISUfonmnBT+w85WEgneCigEEdHDFxw==", + "dev": true, + "requires": { "@jest/test-result": "^28.1.3", - "@jest/transform": "^28.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^28.1.3", + "slash": "^3.0.0" + } + }, + "@jest/transform": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-28.1.3.tgz", + "integrity": "sha512-u5dT5di+oFI6hfcLOHGTAfmUxFRrjK+vnaP0kkVow9Md/M7V/MxqQMOz/VV25UZO8pzeA9PjfTpOu6BDuwSPQA==", + "dev": true, + "requires": { + "@babel/core": "^7.11.6", "@jest/types": "^28.1.3", - "@types/node": "*", - "ansi-escapes": "^4.2.1", + "@jridgewell/trace-mapping": "^0.3.13", + "babel-plugin-istanbul": "^6.1.1", "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "exit": "^0.1.2", + "convert-source-map": "^1.4.0", + "fast-json-stable-stringify": "^2.0.0", "graceful-fs": "^4.2.9", - "jest-changed-files": "^28.1.3", - "jest-config": "^28.1.3", "jest-haste-map": "^28.1.3", - "jest-message-util": "^28.1.3", "jest-regex-util": "^28.0.2", - "jest-resolve": "^28.1.3", - "jest-resolve-dependencies": "^28.1.3", - "jest-runner": "^28.1.3", - "jest-runtime": "^28.1.3", - "jest-snapshot": "^28.1.3", "jest-util": "^28.1.3", - "jest-validate": "^28.1.3", - "jest-watcher": "^28.1.3", "micromatch": "^4.0.4", - "pretty-format": "^28.1.3", - "rimraf": "^3.0.0", + "pirates": "^4.0.4", "slash": "^3.0.0", - "strip-ansi": "^6.0.0" + "write-file-atomic": "^4.0.1" + }, + "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 + }, + "convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "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" + } + } + } + }, + "@jest/types": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-28.1.3.tgz", + "integrity": "sha512-RyjiyMUZrKz/c+zlMFO1pm70DcIlST8AeWTkoUdZevew44wcNZQHsEVOiCVtgVnlFFD82FPaXycys58cf2muVQ==", + "dev": true, + "requires": { + "@jest/schemas": "^28.1.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" }, "dependencies": { "ansi-styles": { @@ -14197,93 +15125,140 @@ } } }, - "@jest/environment": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-28.1.3.tgz", - "integrity": "sha512-1bf40cMFTEkKyEf585R9Iz1WayDjHoHqvts0XFYEqyKM3cFWDpeMoqKKTAF9LSYQModPUlh8FKptoM2YcMWAXA==", + "@jridgewell/gen-mapping": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", "dev": true, "requires": { - "@jest/fake-timers": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/node": "*", - "jest-mock": "^28.1.3" + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" } }, - "@jest/expect": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-28.1.3.tgz", - "integrity": "sha512-lzc8CpUbSoE4dqT0U+g1qODQjBRHPpCPXissXD4mS9+sWQdmmpeJ9zSH1rS1HEkrsMN0fb7nKrJ9giAR1d3wBw==", + "@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true + }, + "@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "dev": true + }, + "@jridgewell/source-map": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz", + "integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==", "dev": true, "requires": { - "expect": "^28.1.3", - "jest-snapshot": "^28.1.3" + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" } }, - "@jest/expect-utils": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-28.1.3.tgz", - "integrity": "sha512-wvbi9LUrHJLn3NlDW6wF2hvIMtd4JUl2QNVrjq+IBSHirgfrR3o9RnVtxzdEGO2n9JyIWwHnLfby5KzqBGg2YA==", + "@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true + }, + "@jridgewell/trace-mapping": { + "version": "0.3.22", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.22.tgz", + "integrity": "sha512-Wf963MzWtA2sjrNt+g18IAln9lKnlRp+K2eH4jjIoF1wYeq3aMREpG09xhlhdzS0EjwU7qmUJYangWa+151vZw==", "dev": true, "requires": { - "jest-get-type": "^28.0.2" + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "@jest/fake-timers": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-28.1.3.tgz", - "integrity": "sha512-D/wOkL2POHv52h+ok5Oj/1gOG9HSywdoPtFsRCUmlCILXNn5eIWmcnd3DIiWlJnpGvQtmajqBP95Ei0EimxfLw==", + "@juggle/resize-observer": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@juggle/resize-observer/-/resize-observer-3.4.0.tgz", + "integrity": "sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==" + }, + "@nicolo-ribaudo/eslint-scope-5-internals": { + "version": "5.1.1-v1", + "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz", + "integrity": "sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==", "dev": true, "requires": { - "@jest/types": "^28.1.3", - "@sinonjs/fake-timers": "^9.1.2", - "@types/node": "*", - "jest-message-util": "^28.1.3", - "jest-mock": "^28.1.3", - "jest-util": "^28.1.3" + "eslint-scope": "5.1.1" } }, - "@jest/globals": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-28.1.3.tgz", - "integrity": "sha512-XFU4P4phyryCXu1pbcqMO0GSQcYe1IsalYCDzRNyhetyeyxMcIxa11qPNDpVNLeretItNqEmYYQn1UYz/5x1NA==", + "@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", "dev": true, "requires": { - "@jest/environment": "^28.1.3", - "@jest/expect": "^28.1.3", - "@jest/types": "^28.1.3" + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" } }, - "@jest/reporters": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-28.1.3.tgz", - "integrity": "sha512-JuAy7wkxQZVNU/V6g9xKzCGC5LVXx9FDcABKsSXp5MiKPEE2144a/vXTEDoyzjUpZKfVwp08Wqg5A4WfTMAzjg==", + "@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true + }, + "@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", "dev": true, "requires": { - "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^28.1.3", - "@jest/test-result": "^28.1.3", - "@jest/transform": "^28.1.3", - "@jest/types": "^28.1.3", - "@jridgewell/trace-mapping": "^0.3.13", - "@types/node": "*", - "chalk": "^4.0.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^5.1.0", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.1.3", - "jest-message-util": "^28.1.3", - "jest-util": "^28.1.3", - "jest-worker": "^28.1.3", - "slash": "^3.0.0", - "string-length": "^4.0.1", - "strip-ansi": "^6.0.0", - "terminal-link": "^2.0.0", - "v8-to-istanbul": "^9.0.1" + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + } + }, + "@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "optional": true + }, + "@sinclair/typebox": { + "version": "0.24.51", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.51.tgz", + "integrity": "sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA==", + "dev": true + }, + "@sinonjs/commons": { + "version": "1.8.6", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.6.tgz", + "integrity": "sha512-Ky+XkAkqPZSm3NLBeUng77EBQl3cmeJhITaGHdYH8kjVB+aun3S4XBRti2zt17mtt0mIUDiNxYeoJm6drVvBJQ==", + "dev": true, + "requires": { + "type-detect": "4.0.8" + } + }, + "@sinonjs/fake-timers": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-9.1.2.tgz", + "integrity": "sha512-BPS4ynJW/o92PUR4wgriz2Ud5gpST5vz6GQfMixEDK0Z8ZCUv2M7SkBLykH56T++Xs+8ln9zTGbOvNGIe02/jw==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.7.0" + } + }, + "@testing-library/dom": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.3.0.tgz", + "integrity": "sha512-pT/TYB2+IyMYkkB6lqpkzD7VFbsR0JBJtflK3cS68sCNWxmOhWwRm1XvVHlseNEorsNcxkYsb4sRDV3aNIpttg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.3.0", + "chalk": "^4.1.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "pretty-format": "^27.0.2" }, "dependencies": { "ansi-styles": { @@ -14326,82 +15301,56 @@ "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==", + "pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", "dev": true, "requires": { - "has-flag": "^4.0.0" + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "dependencies": { + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true + } } - } - } - }, - "@jest/schemas": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz", - "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==", - "dev": true, - "requires": { - "@sinclair/typebox": "^0.24.1" - } - }, - "@jest/source-map": { - "version": "28.1.2", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-28.1.2.tgz", - "integrity": "sha512-cV8Lx3BeStJb8ipPHnqVw/IM2VCMWO3crWZzYodSIkxXnRcXJipCdx1JCK0K5MsJJouZQTH73mzf4vgxRaH9ww==", - "dev": true, - "requires": { - "@jridgewell/trace-mapping": "^0.3.13", - "callsites": "^3.0.0", - "graceful-fs": "^4.2.9" - } - }, - "@jest/test-result": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-28.1.3.tgz", - "integrity": "sha512-kZAkxnSE+FqE8YjW8gNuoVkkC9I7S1qmenl8sGcDOLropASP+BkcGKwhXoyqQuGOGeYY0y/ixjrd/iERpEXHNg==", - "dev": true, - "requires": { - "@jest/console": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" - } - }, - "@jest/test-sequencer": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-28.1.3.tgz", - "integrity": "sha512-NIMPEqqa59MWnDi1kvXXpYbqsfQmSJsIbnd85mdVGkiDfQ9WQQTXOLsvISUfonmnBT+w85WEgneCigEEdHDFxw==", - "dev": true, - "requires": { - "@jest/test-result": "^28.1.3", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^28.1.3", - "slash": "^3.0.0" - } - }, - "@jest/transform": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-28.1.3.tgz", - "integrity": "sha512-u5dT5di+oFI6hfcLOHGTAfmUxFRrjK+vnaP0kkVow9Md/M7V/MxqQMOz/VV25UZO8pzeA9PjfTpOu6BDuwSPQA==", - "dev": true, - "requires": { - "@babel/core": "^7.11.6", - "@jest/types": "^28.1.3", - "@jridgewell/trace-mapping": "^0.3.13", - "babel-plugin-istanbul": "^6.1.1", - "chalk": "^4.0.0", - "convert-source-map": "^1.4.0", - "fast-json-stable-stringify": "^2.0.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^28.1.3", - "jest-regex-util": "^28.0.2", - "jest-util": "^28.1.3", - "micromatch": "^4.0.4", - "pirates": "^4.0.4", - "slash": "^3.0.0", - "write-file-atomic": "^4.0.1" + }, + "react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "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": "6.4.6", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.4.6.tgz", + "integrity": "sha512-8qpnGVincVDLEcQXWaHOf6zmlbwTKc6Us6PPu4CRnPXCzo2OGBS5cwgMMOWdxDpEz1mkbvXHpEy99M5Yvt682w==", + "dev": true, + "requires": { + "@adobe/css-tools": "^4.4.0", + "@babel/runtime": "^7.9.2", + "aria-query": "^5.0.0", + "chalk": "^3.0.0", + "css.escape": "^1.5.1", + "dom-accessibility-api": "^0.6.3", + "lodash": "^4.17.21", + "redent": "^3.0.0" }, "dependencies": { "ansi-styles": { @@ -14414,9 +15363,9 @@ } }, "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "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", @@ -14438,10 +15387,10 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "convert-source-map": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "dom-accessibility-api": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz", + "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==", "dev": true }, "has-flag": { @@ -14461,20 +15410,33 @@ } } }, - "@jest/types": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-28.1.3.tgz", - "integrity": "sha512-RyjiyMUZrKz/c+zlMFO1pm70DcIlST8AeWTkoUdZevew44wcNZQHsEVOiCVtgVnlFFD82FPaXycys58cf2muVQ==", + "@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": { - "@jest/schemas": "^28.1.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" + "@babel/runtime": "^7.12.5", + "@testing-library/dom": "^8.0.0", + "@types/react-dom": "<18.0.0" }, "dependencies": { + "@testing-library/dom": { + "version": "8.20.1", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-8.20.1.tgz", + "integrity": "sha512-/DiOQ5xBxgdYRC8LNk7U+RWat0S3qRLeIw3ZIkMQ9kkVlRmwD/Eg8k8CqIpD6GW7u20JIUOfMKbxtiLutpjQ4g==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.1.3", + "chalk": "^4.1.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "pretty-format": "^27.0.2" + } + }, "ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -14484,6 +15446,15 @@ "color-convert": "^2.0.1" } }, + "aria-query": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.1.3.tgz", + "integrity": "sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==", + "dev": true, + "requires": { + "deep-equal": "^2.0.5" + } + }, "chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -14509,12 +15480,63 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "deep-equal": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.3.tgz", + "integrity": "sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA==", + "dev": true, + "requires": { + "array-buffer-byte-length": "^1.0.0", + "call-bind": "^1.0.5", + "es-get-iterator": "^1.1.3", + "get-intrinsic": "^1.2.2", + "is-arguments": "^1.1.1", + "is-array-buffer": "^3.0.2", + "is-date-object": "^1.0.5", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "isarray": "^2.0.5", + "object-is": "^1.1.5", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.5.1", + "side-channel": "^1.0.4", + "which-boxed-primitive": "^1.0.2", + "which-collection": "^1.0.1", + "which-typed-array": "^1.1.13" + } + }, "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 }, + "pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "dependencies": { + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true + } + } + }, + "react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true + }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -14526,126 +15548,6 @@ } } }, - "@jridgewell/gen-mapping": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", - "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", - "dev": true, - "requires": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - } - }, - "@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true - }, - "@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", - "dev": true - }, - "@jridgewell/source-map": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz", - "integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==", - "dev": true, - "requires": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" - } - }, - "@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", - "dev": true - }, - "@jridgewell/trace-mapping": { - "version": "0.3.22", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.22.tgz", - "integrity": "sha512-Wf963MzWtA2sjrNt+g18IAln9lKnlRp+K2eH4jjIoF1wYeq3aMREpG09xhlhdzS0EjwU7qmUJYangWa+151vZw==", - "dev": true, - "requires": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "@juggle/resize-observer": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@juggle/resize-observer/-/resize-observer-3.4.0.tgz", - "integrity": "sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==" - }, - "@nicolo-ribaudo/eslint-scope-5-internals": { - "version": "5.1.1-v1", - "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz", - "integrity": "sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==", - "dev": true, - "requires": { - "eslint-scope": "5.1.1" - } - }, - "@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "requires": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - } - }, - "@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true - }, - "@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "requires": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - } - }, - "@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "dev": true, - "optional": true - }, - "@sinclair/typebox": { - "version": "0.24.51", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.51.tgz", - "integrity": "sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA==", - "dev": true - }, - "@sinonjs/commons": { - "version": "1.8.6", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.6.tgz", - "integrity": "sha512-Ky+XkAkqPZSm3NLBeUng77EBQl3cmeJhITaGHdYH8kjVB+aun3S4XBRti2zt17mtt0mIUDiNxYeoJm6drVvBJQ==", - "dev": true, - "requires": { - "type-detect": "4.0.8" - } - }, - "@sinonjs/fake-timers": { - "version": "9.1.2", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-9.1.2.tgz", - "integrity": "sha512-BPS4ynJW/o92PUR4wgriz2Ud5gpST5vz6GQfMixEDK0Z8ZCUv2M7SkBLykH56T++Xs+8ln9zTGbOvNGIe02/jw==", - "dev": true, - "requires": { - "@sinonjs/commons": "^1.7.0" - } - }, "@testing-library/react-hooks": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/@testing-library/react-hooks/-/react-hooks-8.0.1.tgz", @@ -14662,6 +15564,12 @@ "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", "dev": true }, + "@types/aria-query": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", + "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", + "dev": true + }, "@types/arrive": { "version": "2.4.4", "resolved": "https://registry.npmjs.org/@types/arrive/-/arrive-2.4.4.tgz", @@ -14833,16 +15741,34 @@ "dev": true }, "@types/react": { - "version": "18.2.56", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.56.tgz", - "integrity": "sha512-NpwHDMkS/EFZF2dONFQHgkPRwhvgq/OAvIaGQzxGSBmaeR++kTg6njr15Vatz0/2VcCEwJQFi6Jf4Q0qBu0rLA==", + "version": "17.0.80", + "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.80.tgz", + "integrity": "sha512-LrgHIu2lEtIo8M7d1FcI3BdwXWoRQwMoXOZ7+dPTW0lYREjmlHl3P0U1VD0i/9tppOuv8/sam7sOjx34TxSFbA==", "dev": true, "requires": { "@types/prop-types": "*", - "@types/scheduler": "*", + "@types/scheduler": "^0.16", "csstype": "^3.0.2" } }, + "@types/react-dom": { + "version": "17.0.25", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.25.tgz", + "integrity": "sha512-urx7A7UxkZQmThYA4So0NelOVjx3V4rNFVJwp0WZlbIK5eM4rNJDiN3R/E9ix0MBh6kAEojk/9YL+Te6D9zHNA==", + "dev": true, + "requires": { + "@types/react": "^17" + } + }, + "@types/react-lottie": { + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/@types/react-lottie/-/react-lottie-1.2.10.tgz", + "integrity": "sha512-rCd1p3US4ELKJlqwVnP0h5b24zt5p9OCvKUoNpYExLqwbFZMWEiJ6EGLMmH7nmq5V7KomBIbWO2X/XRFsL0vCA==", + "dev": true, + "requires": { + "@types/react": "*" + } + }, "@types/scheduler": { "version": "0.16.8", "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.8.tgz", @@ -15401,6 +16327,15 @@ "sprintf-js": "~1.0.2" } }, + "aria-query": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "dev": true, + "requires": { + "dequal": "^2.0.3" + } + }, "array-buffer-byte-length": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", @@ -16038,12 +16973,24 @@ "which": "^2.0.1" } }, + "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", "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", "dev": true }, + "cssfontparser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/cssfontparser/-/cssfontparser-1.2.1.tgz", + "integrity": "sha512-6tun4LoZnj7VN6YeegOVb67KBX/7JJsqvj+pv3ZA7F878/eN33AbGa5b/S/wXxS/tcp8nc40xRUrsPlxIyNUPg==", + "dev": true + }, "cssom": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz", @@ -16172,6 +17119,12 @@ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", "dev": true }, + "dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true + }, "detect-newline": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", @@ -16214,6 +17167,12 @@ "esutils": "^2.0.2" } }, + "dom-accessibility-api": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", + "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", + "dev": true + }, "dom-helpers": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-3.4.0.tgz", @@ -16352,6 +17311,23 @@ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==" }, + "es-get-iterator": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz", + "integrity": "sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "has-symbols": "^1.0.3", + "is-arguments": "^1.1.1", + "is-map": "^2.0.2", + "is-set": "^2.0.2", + "is-string": "^1.0.7", + "isarray": "^2.0.5", + "stop-iteration-iterator": "^1.0.0" + } + }, "es-iterator-helpers": { "version": "1.0.17", "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.0.17.tgz", @@ -17287,6 +18263,12 @@ "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "dev": true }, + "indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true + }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -17740,6 +18722,16 @@ "jest-cli": "^28.1.3" } }, + "jest-canvas-mock": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jest-canvas-mock/-/jest-canvas-mock-2.5.2.tgz", + "integrity": "sha512-vgnpPupjOL6+L5oJXzxTxFrlGEIbHdZqFU+LFNdtLxZ3lRDCl17FlTMM7IatoRQkrcyOTMlDinjUguqmQ6bR2A==", + "dev": true, + "requires": { + "cssfontparser": "^1.2.1", + "moo-color": "^1.0.2" + } + }, "jest-changed-files": { "version": "28.1.3", "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-28.1.3.tgz", @@ -19088,6 +20080,12 @@ "p-locate": "^4.1.0" } }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, "lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", @@ -19108,14 +20106,6 @@ "js-tokens": "^3.0.0 || ^4.0.0" } }, - "lottie-react": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/lottie-react/-/lottie-react-2.4.0.tgz", - "integrity": "sha512-pDJGj+AQlnlyHvOHFK7vLdsDcvbuqvwPZdMlJ360wrzGFurXeKPr8SiRCjLf3LrNYKANQtSsh5dz9UYQHuqx4w==", - "requires": { - "lottie-web": "^5.10.2" - } - }, "lottie-web": { "version": "5.12.2", "resolved": "https://registry.npmjs.org/lottie-web/-/lottie-web-5.12.2.tgz", @@ -19130,6 +20120,12 @@ "yallist": "^3.0.2" } }, + "lz-string": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", + "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", + "dev": true + }, "make-dir": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", @@ -19191,6 +20187,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 + }, "minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -19212,6 +20214,23 @@ "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==", "dev": true }, + "moo-color": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/moo-color/-/moo-color-1.0.3.tgz", + "integrity": "sha512-i/+ZKXMDf6aqYtBhuOcej71YSlbjT3wCO/4H1j8rPvxDJEifdwgg5MaFyu6iYAT8GBZJg2z0dkgK4YMzvURALQ==", + "dev": true, + "requires": { + "color-name": "^1.1.4" + }, + "dependencies": { + "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 + } + } + }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -19856,6 +20875,16 @@ "resolve": "^1.9.0" } }, + "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" + } + }, "reflect.getprototypeof": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.5.tgz", @@ -20217,6 +21246,15 @@ } } }, + "stop-iteration-iterator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz", + "integrity": "sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==", + "dev": true, + "requires": { + "internal-slot": "^1.0.4" + } + }, "string-length": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", @@ -20329,6 +21367,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 8720f50..6ab8840 100644 --- a/package.json +++ b/package.json @@ -10,8 +10,9 @@ "format": "prettier --write \"./**/*.{js,jsx,json}\"", "lint": "eslint '**/*.{ts,tsx,js,jsx}'", "typecheck": "tsc", - "test": "jest", - "test-dev": "jest --watch", + "test": "TZ=UTC jest", + "test-dev": "TZ=UTC jest --watch --verbose --runInBand", + "test-debug": "TZ=UTC node --inspect-brk node_modules/.bin/jest --runInBand --watch --verbose", "changelog-generate": "auto-changelog --starting-version v16 --handlebars-setup changelog-setup.js --template changelog-template.hbs --commit-limit false", "changelog-data": "npm run changelog-generate -- --template json --output changelog-data.json", "changelog": "npm run changelog-data && npm run changelog-generate && git add CHANGELOG.md" @@ -31,7 +32,12 @@ "^~(.*)$": "/src$1" }, "verbose": false, - "globalSetup": "./jest.setup.js" + "setupFiles": [ + "jest-canvas-mock" + ], + "setupFilesAfterEnv": [ + "/jest.setup.ts" + ] }, "devDependencies": { "@babel/core": "^7.18.10", @@ -42,10 +48,15 @@ "@babel/preset-react": "^7.18.6", "@babel/preset-typescript": "^7.18.6", "@emotion/babel-plugin": "^11.10.0", + "@testing-library/dom": "^10.3.0", + "@testing-library/jest-dom": "^6.4.6", + "@testing-library/react": "^12.1.5", "@testing-library/react-hooks": "^8.0.1", "@types/arrive": "^2.4.1", "@types/jest": "^28.1.7", - "@types/react": "^18.0.21", + "@types/react": "^17.0.2", + "@types/react-dom": "^17.0.2", + "@types/react-lottie": "^1.2.3", "auto-changelog": "^2.4.0", "babel-jest": "^28.1.3", "babel-loader": "^8.2.5", @@ -53,6 +64,7 @@ "eslint-plugin-react": "^7.30.1", "eslint-plugin-react-hooks": "^4.6.0", "jest": "^28.1.3", + "jest-canvas-mock": "^2.5.2", "jest-environment-jsdom": "^28.1.3", "prettier": "2.7.1", "tailwindcss": "^3.4.1", @@ -71,7 +83,6 @@ "@emotion/styled": "^11.10.0", "arrive": "^2.4.1", "dayjs": "^1.11.5", - "lottie-react": "^2.4.0", "react": "^17.0.2", "react-dom": "^17.0.2", "react-lottie": "^1.2.3" diff --git a/src/app.tsx b/src/app.tsx index e662aab..21fa057 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import * as Blueprint from '@blueprintjs/core'; import PracticeOverlay from '~/components/overlay/PracticeOverlay'; -import SidePandelWidget from '~/components/SidePandelWidget'; +import SidePannelWidget from '~/components/SidePanelWidget'; import practice from '~/practice'; import usePracticeData from '~/hooks/usePracticeData'; import useTags from '~/hooks/useTags'; @@ -13,7 +13,7 @@ import useCachedData from '~/hooks/useCachedData'; import useOnVisibilityStateChange from '~/hooks/useOnVisibilityStateChange'; import { IntervalMultiplierType, ReviewModes } from '~/models/session'; -interface handlePracticeProps { +export interface handlePracticeProps { refUid: string; grade: number; reviewMode: ReviewModes; @@ -28,27 +28,14 @@ const App = () => { const { tagsListString, dataPageTitle, dailyLimit, rtlEnabled } = useSettings(); const { selectedTag, setSelectedTag, tagsList } = useTags({ tagsListString }); - const { - data: { lastCompletedDate }, - saveCacheData, - fetchCacheData, - deleteCacheDataKey, - } = useCachedData({ dataPageTitle, selectedTag }); - - const { - practiceCardsUids, - practiceData, - displayCardCounts, - fetchPracticeData, - completedTodayCount, - remainingDueCardsCount, - } = usePracticeData({ + const { fetchCacheData } = useCachedData({ dataPageTitle, selectedTag }); + + const { practiceData, today, fetchPracticeData } = usePracticeData({ tagsList, selectedTag, dataPageTitle, isCramming, dailyLimit, - lastCompletedDate, }); const handlePracticeClick = async ({ @@ -64,9 +51,11 @@ const App = () => { } const cardData = practiceData[refUid]; + const latestSession = cardData[cardData.length - 1]; + try { await practice({ - ...cardData, + ...latestSession, grade, refUid, dataPageTitle, @@ -76,21 +65,22 @@ const App = () => { intervalMultiplier, intervalMultiplierType, }); + + refreshData(); } catch (error) { console.error('Error Saving Practice Data', error); } }; - /** - * Warning: Calling this function while the overlay is open resets the state - * of the current practice session. Causing you to lose your progress. - */ const refreshData = () => { fetchCacheData(); fetchPracticeData(); }; - useOnVisibilityStateChange(refreshData); + useOnVisibilityStateChange(() => { + if (showPracticeOverlay) return; + refreshData(); + }); const onShowPracticeOverlay = () => { refreshData(); @@ -100,6 +90,7 @@ const App = () => { const onClosePracticeOverlayCallback = () => { setShowPracticeOverlay(false); + setIsCramming(false); refreshData(); }; @@ -108,8 +99,7 @@ const App = () => { }; const handleReviewMoreClick = async () => { - await deleteCacheDataKey('lastCompletedDate'); - + // @TODOZ: Handle this case. refreshData(); }; @@ -139,15 +129,10 @@ const App = () => { return ( <> - + {showPracticeOverlay && ( { selectedTag={selectedTag} isCramming={isCramming} setIsCramming={setIsCramming} - saveCacheData={saveCacheData} - lastCompletedDate={lastCompletedDate} - completedTodayCount={completedTodayCount} - dailyLimit={dailyLimit} - remainingDueCardsCount={remainingDueCardsCount} rtlEnabled={rtlEnabled} - displayCardCounts={displayCardCounts} + today={today} /> )} diff --git a/src/components/SidePanelWidget.test.tsx b/src/components/SidePanelWidget.test.tsx new file mode 100644 index 0000000..ccf35ae --- /dev/null +++ b/src/components/SidePanelWidget.test.tsx @@ -0,0 +1,851 @@ +import { render, screen, act, within } from '@testing-library/react'; + +import * as testUtils from '~/utils/testUtils'; +import * as dateUtils from '~/utils/date'; + +import App from '~/app'; + +describe('Side Panel Widget', () => { + const originalLocation = window; + + afterEach(() => { + Object.defineProperty(globalThis, 'window', { + value: originalLocation, + }); + }); + + it('renders initial state without error', async () => { + const mockBuilder = new testUtils.MockDataBuilder(); + mockBuilder.mockQueryResults(); + + await act(async () => { + render(); + }); + + expect(screen.getByText('Review')).toBeInTheDocument(); + }); + + it('renders completed state when nothing due/new', async () => { + const mockBuilder = new testUtils.MockDataBuilder(); + mockBuilder.mockQueryResults(); + + await act(async () => { + render(); + }); + + const sidePanelWrapper = screen.getByTestId('side-panel-wrapper'); + expect(sidePanelWrapper).toBeInTheDocument(); + + const completedIconClass = '.bp3-icon-confirm'; + const completedIcon = sidePanelWrapper.querySelector(completedIconClass); + expect(completedIcon).toBeInTheDocument(); + }); + + it('renders correct count when new cards added', async () => { + const uid = 'id_1'; + const mockBuilder = new testUtils.MockDataBuilder().withCard({ uid }); + mockBuilder.mockQueryResults(); + + await act(async () => { + render(); + }); + + const sidePanelWrapper = screen.getByTestId('side-panel-wrapper'); + const incompleteIconClass = '.bp3-icon-box'; + const incompleteIconElement = sidePanelWrapper.querySelector(incompleteIconClass); + expect(incompleteIconElement).toBeInTheDocument(); + + const newTag = screen.getByTestId('new-tag'); + expect(newTag).toBeInTheDocument(); + expect(newTag).toHaveTextContent('1'); + + const dueTag = screen.queryByTestId('due-tag'); + expect(dueTag).not.toBeInTheDocument(); + }); + + it('renders correct count when new cards added to multile decks', async () => { + const mockBuilder = new testUtils.MockDataBuilder() + .withTag('deck-one') + .withCard({ uid: 'id_1', tag: 'deck-one' }) + .withCard({ uid: 'id_2', tag: 'deck-one' }) + .withTag('deck-two') + .withCard({ uid: 'id_3', tag: 'deck-two' }); + mockBuilder.mockQueryResults(); + + await act(async () => { + render(); + }); + + const sidePanelWrapper = screen.getByTestId('side-panel-wrapper'); + const incompleteIconClass = '.bp3-icon-box'; + const incompleteIconElement = sidePanelWrapper.querySelector(incompleteIconClass); + expect(incompleteIconElement).toBeInTheDocument(); + + const newTag = screen.getByTestId('new-tag'); + expect(newTag).toBeInTheDocument(); + expect(newTag).toHaveTextContent('3'); + + const dueTag = screen.queryByTestId('due-tag'); + expect(dueTag).not.toBeInTheDocument(); + }); + + it('renders correct count when with due cards, unstarted', async () => { + const uid = 'id_1'; + const mockBuilder = new testUtils.MockDataBuilder().withCard({ uid }).withSession(uid, { + nextDueDate: new Date(), + }); + mockBuilder.mockQueryResults(); + + await act(async () => { + render(); + }); + + const sidePanelWrapper = screen.getByTestId('side-panel-wrapper'); + const incompleteIconClass = '.bp3-icon-box'; + const incompleteIconElement = sidePanelWrapper.querySelector(incompleteIconClass); + expect(incompleteIconElement).toBeInTheDocument(); + + const dueTag = screen.getByTestId('due-tag'); + expect(dueTag).toBeInTheDocument(); + expect(dueTag).toHaveTextContent('1'); + + const newTag = screen.queryByTestId('new-tag'); + expect(newTag).not.toBeInTheDocument(); + }); + + it('renders correct count when with due cards, unstarted, multiple decks', async () => { + const uid = 'id_1'; + const mockBuilder = new testUtils.MockDataBuilder() + .withCard({ uid }) + .withSession(uid, { + nextDueDate: new Date(), + }) + .withTag('deck-one') + .withCard({ uid: 'id_2', tag: 'deck-one' }) + .withSession('id_2', { + nextDueDate: new Date(), + }); + + mockBuilder.mockQueryResults(); + + await act(async () => { + render(); + }); + + const sidePanelWrapper = screen.getByTestId('side-panel-wrapper'); + const incompleteIconClass = '.bp3-icon-box'; + const incompleteIconElement = sidePanelWrapper.querySelector(incompleteIconClass); + expect(incompleteIconElement).toBeInTheDocument(); + + const dueTag = screen.getByTestId('due-tag'); + expect(dueTag).toBeInTheDocument(); + expect(dueTag).toHaveTextContent('2'); + + const newTag = screen.queryByTestId('new-tag'); + expect(newTag).not.toBeInTheDocument(); + }); + + it('decrements count when card completed', async () => { + const card_1 = 'id_1'; + const card_2 = 'id_2'; + const mockBuilder = new testUtils.MockDataBuilder() + // Create two cards due today + .withCard({ uid: card_1 }) + .withCard({ uid: card_2 }) + .withSession(card_1, { + dateCreated: dateUtils.subtractDays(new Date(), 1), + nextDueDate: new Date(), + }) + .withSession(card_2, { + dateCreated: dateUtils.subtractDays(new Date(), 1), + nextDueDate: new Date(), + }) + // Complete one today + .withSession(card_1, { + dateCreated: new Date(), + nextDueDate: dateUtils.addDays(new Date(), 1), + }); + + mockBuilder.mockQueryResults(); + await act(async () => { + render(); + }); + + const newTag = screen.queryByTestId('new-tag'); + expect(newTag).not.toBeInTheDocument(); + + const dueTag = screen.getByTestId('due-tag'); + expect(dueTag).toBeInTheDocument(); + expect(dueTag).toHaveTextContent('1'); + }); + + it('can handle case where same card is practiced twice on same day', async () => { + const card_1 = 'id_1'; + const mockBuilder = new testUtils.MockDataBuilder() + .withCard({ uid: card_1 }) + .withSession(card_1, { + dateCreated: dateUtils.subtractDays(new Date(), 1), + nextDueDate: new Date(), + }) + .withSession(card_1, { + dateCreated: new Date(), + nextDueDate: new Date(), + }) + .withSession(card_1, { + dateCreated: new Date(), + nextDueDate: dateUtils.addDays(new Date(), 2), + }); + + mockBuilder.mockQueryResults(); + await act(async () => { + render(); + }); + + const newTag = screen.queryByTestId('new-tag'); + expect(newTag).not.toBeInTheDocument(); + + const dueTag = screen.queryByTestId('due-tag'); + expect(dueTag).not.toBeInTheDocument(); + }); + + it('renders completed state when all new/due cards completed', async () => { + const dueCard1 = 'id_1'; + const dueCard2 = 'id_2'; + const newCard1 = 'id_3'; + const newCard2 = 'id_4'; + + const mockBuilder = new testUtils.MockDataBuilder() + // Create some cards due today + .withCard({ uid: dueCard1 }) + .withSession(dueCard1, { + dateCreated: dateUtils.subtractDays(new Date(), 1), + nextDueDate: new Date(), + }) + .withCard({ uid: dueCard2 }) + .withSession(dueCard2, { + dateCreated: dateUtils.subtractDays(new Date(), 1), + nextDueDate: new Date(), + }) + // Create some new cards + .withCard({ uid: newCard1 }) + .withCard({ uid: newCard2 }) + // Complete all new/due cards + .withSession(dueCard1, { + dateCreated: new Date(), + nextDueDate: dateUtils.addDays(new Date(), 1), + }) + .withSession(dueCard2, { + dateCreated: new Date(), + nextDueDate: dateUtils.addDays(new Date(), 1), + }) + .withSession(newCard1, { + dateCreated: new Date(), + nextDueDate: dateUtils.addDays(new Date(), 1), + }) + .withSession(newCard2, { + dateCreated: new Date(), + nextDueDate: dateUtils.addDays(new Date(), 1), + }); + + mockBuilder.mockQueryResults(); + + await act(async () => { + render(); + }); + + const sidePanelWrapper = screen.getByTestId('side-panel-wrapper'); + expect(sidePanelWrapper).toBeInTheDocument(); + + const completedIconClass = '.bp3-icon-confirm'; + const completedIcon = sidePanelWrapper.querySelector(completedIconClass); + expect(completedIcon).toBeInTheDocument(); + }); + + it('renders completed state when all new/due cards completed, multille decks', async () => { + const dueCard1 = 'id_1'; + const dueCard2 = 'id_2'; + const newCard1 = 'id_3'; + const newCard2 = 'id_4'; + const deckTwo = 'deck-two'; + + const mockBuilder = new testUtils.MockDataBuilder() + // Create some cards due today + .withCard({ uid: dueCard1 }) + .withSession(dueCard1, { + dateCreated: dateUtils.subtractDays(new Date(), 1), + nextDueDate: new Date(), + }) + .withCard({ uid: dueCard2 }) + .withSession(dueCard2, { + dateCreated: dateUtils.subtractDays(new Date(), 1), + nextDueDate: new Date(), + }) + // Create some new cards on another deck + .withTag(deckTwo) + .withCard({ uid: newCard1, tag: deckTwo }) + .withCard({ uid: newCard2, tag: deckTwo }) + // Complete all new/due cards + .withSession(dueCard1, { + dateCreated: new Date(), + nextDueDate: dateUtils.addDays(new Date(), 1), + }) + .withSession(dueCard2, { + dateCreated: new Date(), + nextDueDate: dateUtils.addDays(new Date(), 1), + }) + .withSession(newCard1, { + dateCreated: new Date(), + nextDueDate: dateUtils.addDays(new Date(), 1), + }) + .withSession(newCard2, { + dateCreated: new Date(), + nextDueDate: dateUtils.addDays(new Date(), 1), + }); + + mockBuilder.mockQueryResults(); + + await act(async () => { + render(); + }); + + const sidePanelWrapper = screen.getByTestId('side-panel-wrapper'); + expect(sidePanelWrapper).toBeInTheDocument(); + + const completedIconClass = '.bp3-icon-confirm'; + const completedIcon = sidePanelWrapper.querySelector(completedIconClass); + expect(completedIcon).toBeInTheDocument(); + }); + + describe('Daily Limit Set', () => { + it('Renders correct counts when single deck, with only only new cards', async () => { + const mockBuilder = new testUtils.MockDataBuilder().withSetting({ + dailyLimit: 5, + }); + + for (let i = 0; i < 10; i++) { + mockBuilder.withCard({ uid: `id_${i}` }); + } + + mockBuilder.mockQueryResults(); + + await act(async () => { + render(); + }); + + const sidePanelWrapper = screen.getByTestId('side-panel-wrapper'); + const incompleteIconClass = '.bp3-icon-box'; + const incompleteIconElement = sidePanelWrapper.querySelector(incompleteIconClass); + expect(incompleteIconElement).toBeInTheDocument(); + + const newTag = screen.getByTestId('new-tag'); + const dueTag = screen.queryByTestId('due-tag'); + expect(newTag).toHaveTextContent('5'); + expect(dueTag).not.toBeInTheDocument(); + }); + + it('Due cards should only make up 25% of daily limit when possible', async () => { + const newCardCount = 10; + const dueCardCount = 5; + + const mockBuilder = new testUtils.MockDataBuilder().withSetting({ + dailyLimit: 5, + }); + + // Create some cards new today + for (let i = 0; i < newCardCount; i++) { + mockBuilder.withCard({ uid: `id_${i}` }); + } + + // Create some cards due today + for (let i = newCardCount; i < newCardCount + dueCardCount; i++) { + mockBuilder.withCard({ uid: `id_${i}` }).withSession(`id_${i}`, { + dateCreated: dateUtils.subtractDays(new Date(), 1), + nextDueDate: new Date(), + }); + } + + mockBuilder.mockQueryResults(); + + await act(async () => { + render(); + }); + + const sidePanelWrapper = screen.getByTestId('side-panel-wrapper'); + const incompleteIconClass = '.bp3-icon-box'; + const incompleteIconElement = sidePanelWrapper.querySelector(incompleteIconClass); + expect(incompleteIconElement).toBeInTheDocument(); + + const newTag = screen.getByTestId('new-tag'); + const dueTag = screen.queryByTestId('due-tag'); + expect(newTag).toHaveTextContent('1'); // Limit new cards to 25% of limit + expect(dueTag).toHaveTextContent('4'); + }); + + it('When not enough due cards to make limit, pulls from new if available', async () => { + const newCardCount = 10; + const dueCardCount = 1; + + const mockBuilder = new testUtils.MockDataBuilder().withSetting({ + dailyLimit: 5, + }); + + // Create some cards new today + for (let i = 0; i < newCardCount; i++) { + mockBuilder.withCard({ uid: `id_${i}` }); + } + + // Create some cards due today + for (let i = newCardCount; i < newCardCount + dueCardCount; i++) { + mockBuilder.withCard({ uid: `id_${i}` }).withSession(`id_${i}`, { + dateCreated: dateUtils.subtractDays(new Date(), 1), + nextDueDate: new Date(), + }); + } + + mockBuilder.mockQueryResults(); + + await act(async () => { + render(); + }); + + const sidePanelWrapper = screen.getByTestId('side-panel-wrapper'); + const incompleteIconClass = '.bp3-icon-box'; + const incompleteIconElement = sidePanelWrapper.querySelector(incompleteIconClass); + expect(incompleteIconElement).toBeInTheDocument(); + + const newTag = screen.getByTestId('new-tag'); + const dueTag = screen.queryByTestId('due-tag'); + + expect(newTag).toHaveTextContent('4'); + expect(dueTag).toHaveTextContent('1'); + }); + + it('renders correct count new/due count when daily limit set is split between two decks', async () => { + const dueCard1 = 'id_due_1'; + const dueCard2 = 'id_due_2'; + const newCard1 = 'id_new_3'; + const newCard2 = 'id_new_4'; + const deckTwo = 'deck-two'; + + const mockBuilder = new testUtils.MockDataBuilder().withSetting({ + dailyLimit: 2, + }); + + // Add due cards to memo deck + mockBuilder + .withCard({ uid: dueCard1, tag: 'memo' }) + .withSession(dueCard1, { + dateCreated: dateUtils.subtractDays(new Date(), 1), + nextDueDate: new Date(), + }) + .withCard({ uid: dueCard2 }) + .withSession(dueCard2, { + dateCreated: dateUtils.subtractDays(new Date(), 1), + nextDueDate: new Date(), + }); + + // Create some new cards on another deck + mockBuilder + .withTag(deckTwo) + .withCard({ uid: newCard1, tag: deckTwo }) + .withCard({ uid: newCard2, tag: deckTwo }); + + mockBuilder.mockQueryResults(); + + await act(async () => { + render(); + }); + + const sidePanelWrapper = screen.getByTestId('side-panel-wrapper'); + const incompleteIconClass = '.bp3-icon-box'; + const incompleteIconElement = sidePanelWrapper.querySelector(incompleteIconClass); + expect(incompleteIconElement).toBeInTheDocument(); + + const dueTag = screen.getByTestId('due-tag'); + expect(dueTag).toBeInTheDocument(); + expect(dueTag).toHaveTextContent('1'); + + const newTag = screen.getByTestId('new-tag'); + expect(newTag).toBeInTheDocument(); + expect(newTag).toHaveTextContent('1'); + }); + + it('renders correct count when, limit set, one deck complete, but other deck has due cards', async () => { + const mockBuilder = new testUtils.MockDataBuilder(); + + mockBuilder.withSetting({ + dailyLimit: 5, + }); + + // Add completed deck + mockBuilder.withCard({ uid: 'memo_1' }); + + // Add deck with some new cards + mockBuilder.withTag('deck-two'); + for (let i = 0; i < 10; i++) { + mockBuilder.withCard({ uid: `deck_two_${i}`, tag: 'deck-two' }); + } + + mockBuilder.mockQueryResults(); + + await act(async () => { + render(); + }); + + const sidePanelWrapper = screen.getByTestId('side-panel-wrapper'); + const incompleteIconClass = '.bp3-icon-box'; + const incompleteIconElement = sidePanelWrapper.querySelector(incompleteIconClass); + expect(incompleteIconElement).toBeInTheDocument(); + + const dueTag = screen.queryByTestId('due-tag'); + expect(dueTag).not.toBeInTheDocument(); + + const newTag = screen.queryByTestId('new-tag'); + expect(newTag).toBeInTheDocument(); + expect(newTag).toHaveTextContent('5'); + }); + + it('renders correct count when, limit set, one deck complete, but other deck has due and new cards', async () => { + const mockBuilder = new testUtils.MockDataBuilder(); + + mockBuilder.withSetting({ + dailyLimit: 5, + }); + + // Add completed deck + mockBuilder.withCard({ uid: 'memo_1' }); + + // Add deck with some new cards + mockBuilder.withTag('deck-two'); + for (let i = 0; i < 10; i++) { + mockBuilder.withCard({ uid: `deck_two_${i}`, tag: 'deck-two' }); + } + + // Create some cards due today + for (let i = 0; i < 5; i++) { + mockBuilder + .withCard({ uid: `deck_two_due_${i}`, tag: 'deck-two' }) + .withSession(`deck_two_due_${i}`, { + dateCreated: dateUtils.subtractDays(new Date(), 1), + nextDueDate: new Date(), + }); + } + + mockBuilder.mockQueryResults(); + + await act(async () => { + render(); + }); + + const sidePanelWrapper = screen.getByTestId('side-panel-wrapper'); + const incompleteIconClass = '.bp3-icon-box'; + const incompleteIconElement = sidePanelWrapper.querySelector(incompleteIconClass); + expect(incompleteIconElement).toBeInTheDocument(); + + const dueTag = screen.queryByTestId('due-tag'); + expect(dueTag).toBeInTheDocument(); + expect(dueTag).toHaveTextContent('4'); + + const newTag = screen.queryByTestId('new-tag'); + expect(newTag).toBeInTheDocument(); + expect(newTag).toHaveTextContent('1'); + }); + + it('renders correct count when, limit set, has completed today cards', async () => { + const mockBuilder = new testUtils.MockDataBuilder(); + + mockBuilder.withSetting({ + dailyLimit: 5, + }); + + // Add completed deck + mockBuilder.withCard({ uid: 'memo_1' }); + + // Add deck with some new cards + mockBuilder.withTag('deck-two'); + for (let i = 0; i < 10; i++) { + mockBuilder.withCard({ uid: `deck_two_${i}`, tag: 'deck-two' }); + } + + // Create some cards due today + for (let i = 0; i < 5; i++) { + mockBuilder + .withCard({ uid: `deck_two_due_${i}`, tag: 'deck-two' }) + .withSession(`deck_two_due_${i}`, { + dateCreated: dateUtils.subtractDays(new Date(), 1), + nextDueDate: new Date(), + }); + } + + // Complete one new card (this is the one that was originaly selected) + mockBuilder.withSession(`memo_1`, { + dateCreated: new Date(), + nextDueDate: dateUtils.addDays(new Date(), 1), + }); + + mockBuilder.mockQueryResults(); + + await act(async () => { + render(); + }); + + const sidePanelWrapper = screen.getByTestId('side-panel-wrapper'); + const incompleteIconClass = '.bp3-icon-box'; + const incompleteIconElement = sidePanelWrapper.querySelector(incompleteIconClass); + expect(incompleteIconElement).toBeInTheDocument(); + + const dueTag = screen.queryByTestId('due-tag'); + expect(dueTag).toBeInTheDocument(); + expect(dueTag).toHaveTextContent('4'); + + const newTag = screen.queryByTestId('new-tag'); + expect(newTag).not.toBeInTheDocument(); + }); + + it('renders correct count when limit is 1, prioritizig due cards', async () => { + const mockBuilder = new testUtils.MockDataBuilder(); + + mockBuilder.withSetting({ + dailyLimit: 1, + }); + + // Add deck with some new cards + mockBuilder.withTag('deck-two'); + for (let i = 0; i < 10; i++) { + mockBuilder.withCard({ uid: `deck_two_${i}`, tag: 'deck-two' }); + } + + // Create some cards due today + for (let i = 0; i < 5; i++) { + mockBuilder + .withCard({ uid: `deck_two_due_${i}`, tag: 'deck-two' }) + .withSession(`deck_two_due_${i}`, { + dateCreated: dateUtils.subtractDays(new Date(), 1), + nextDueDate: new Date(), + }); + } + + mockBuilder.mockQueryResults(); + + await act(async () => { + render(); + }); + + const sidePanelWrapper = screen.getByTestId('side-panel-wrapper'); + const incompleteIconClass = '.bp3-icon-box'; + const incompleteIconElement = sidePanelWrapper.querySelector(incompleteIconClass); + expect(incompleteIconElement).toBeInTheDocument(); + + const dueTag = screen.queryByTestId('due-tag'); + expect(dueTag).toBeInTheDocument(); + expect(dueTag).toHaveTextContent('1'); + + const newTag = screen.queryByTestId('new-tag'); + expect(newTag).not.toBeInTheDocument(); + }); + + it('renders correct count when limit is < 4, at least 1 new', async () => { + const mockBuilder = new testUtils.MockDataBuilder(); + + mockBuilder.withSetting({ + dailyLimit: 2, + }); + + // Add completed deck + mockBuilder.withCard({ uid: 'memo_1' }); + + // Add deck with some new cards + mockBuilder.withTag('deck-two'); + for (let i = 0; i < 10; i++) { + mockBuilder.withCard({ uid: `deck_two_${i}`, tag: 'deck-two' }); + } + + // Create some cards due today + for (let i = 0; i < 5; i++) { + mockBuilder + .withCard({ uid: `deck_two_due_${i}`, tag: 'deck-two' }) + .withSession(`deck_two_due_${i}`, { + dateCreated: dateUtils.subtractDays(new Date(), 1), + nextDueDate: new Date(), + }); + } + + mockBuilder.mockQueryResults(); + + await act(async () => { + render(); + }); + + const sidePanelWrapper = screen.getByTestId('side-panel-wrapper'); + const incompleteIconClass = '.bp3-icon-box'; + const incompleteIconElement = sidePanelWrapper.querySelector(incompleteIconClass); + expect(incompleteIconElement).toBeInTheDocument(); + + const dueTag = screen.queryByTestId('due-tag'); + expect(dueTag).toBeInTheDocument(); + expect(dueTag).toHaveTextContent('1'); + + const newTag = screen.queryByTestId('new-tag'); + expect(newTag).toBeInTheDocument(); + expect(newTag).toHaveTextContent('1'); + }); + + it('renders correct count when target new count is less than available new card count', async () => { + const mockBuilder = new testUtils.MockDataBuilder(); + + mockBuilder.withSetting({ + dailyLimit: 20, + }); + + // Add deck with some new cards + for (let i = 0; i < 4; i++) { + mockBuilder.withCard({ uid: `id_new_${i}` }); + } + + // Create some cards due today + for (let i = 0; i < 20; i++) { + mockBuilder.withCard({ uid: `id_due_${i}` }).withSession(`id_due_${i}`, { + dateCreated: dateUtils.subtractDays(new Date(), 1), + nextDueDate: new Date(), + }); + } + + mockBuilder.mockQueryResults(); + + await act(async () => { + render(); + }); + + const sidePanelWrapper = screen.getByTestId('side-panel-wrapper'); + const incompleteIconClass = '.bp3-icon-box'; + const incompleteIconElement = sidePanelWrapper.querySelector(incompleteIconClass); + expect(incompleteIconElement).toBeInTheDocument(); + + const dueTag = screen.queryByTestId('due-tag'); + expect(dueTag).toBeInTheDocument(); + expect(dueTag).toHaveTextContent('16'); + + const newTag = screen.queryByTestId('new-tag'); + expect(newTag).toBeInTheDocument(); + expect(newTag).toHaveTextContent('4'); + }); + + it("renders correct count when target due can't can't be meet in first deck, but is available in second deck", async () => { + const mockBuilder = new testUtils.MockDataBuilder(); + + mockBuilder.withSetting({ + dailyLimit: 1, + }); + + // Add a due card to default deck (this is the one we want to skip) + mockBuilder.withCard({ uid: 'memo_1' }).withSession('memo_1', { + dateCreated: dateUtils.subtractDays(new Date(), 1), + nextDueDate: new Date(), + }); + + // Add a due card to new deck + const newDeckDueCard = 'deck-two-due-1'; + mockBuilder + .withTag('deck-two') + .withCard({ uid: newDeckDueCard, tag: 'deck-two' }) + .withSession(newDeckDueCard, { + dateCreated: dateUtils.subtractDays(new Date(), 1), + nextDueDate: new Date(), + }); + + mockBuilder.mockQueryResults(); + + await act(async () => { + render(); + }); + + const sidePanelWrapper = screen.getByTestId('side-panel-wrapper'); + const incompleteIconClass = '.bp3-icon-box'; + const incompleteIconElement = sidePanelWrapper.querySelector(incompleteIconClass); + expect(incompleteIconElement).toBeInTheDocument(); + + const dueTag = screen.queryByTestId('due-tag'); + expect(dueTag).toBeInTheDocument(); + expect(dueTag).toHaveTextContent('1'); + + const newTag = screen.queryByTestId('new-tag'); + expect(newTag).not.toBeInTheDocument(); + }); + + it('takes completed cards in decks into acount, alocating remaing limit count consistently', async () => { + /** + * This handles the case where we have a limit set between multiple decks, + * we finish one deck, then refetch. we expect the limit to not restart + * but instead remember the original distribution and subtract completed + * cards from that. Essentially, distribution should remain the same. + */ + const mockBuilder = new testUtils.MockDataBuilder(); + + mockBuilder.withSetting({ + dailyLimit: 3, + }); + + // Add some cards to default deck + for (let i = 0; i < 5; i++) { + mockBuilder.withCard({ uid: `1_due_${i}` }).withSession(`1_due_${i}`, { + dateCreated: dateUtils.subtractDays(new Date(), 1), + nextDueDate: new Date(), + }); + mockBuilder.withCard({ uid: `1_new_${i}` }); + } + + // Add some cards to second deck + mockBuilder.withTag('deck-two'); + for (let i = 0; i < 5; i++) { + mockBuilder.withCard({ uid: `2_due_${i}`, tag: 'deck-two' }).withSession(`2_due_${i}`, { + dateCreated: dateUtils.subtractDays(new Date(), 1), + nextDueDate: new Date(), + }); + mockBuilder.withCard({ uid: `2_new_${i}`, tag: 'deck-two' }); + } + + mockBuilder.mockQueryResults(); + + await act(async () => { + render(); + }); + + // Complete all the cards in the first deck + mockBuilder.withSession(`1_due_0`, { + dateCreated: new Date(), + nextDueDate: dateUtils.addDays(new Date(), 1), + }); + mockBuilder.withSession(`1_new_0`, { + dateCreated: new Date(), + nextDueDate: dateUtils.addDays(new Date(), 1), + }); + mockBuilder.mockQueryResults(); + + // Refresh data by launching modal + await act(async () => { + testUtils.actions.launchModal(); + }); + + // Open Tag Selector + await act(async () => { + testUtils.actions.openTagSelector(); + }); + + // Here we expect the first deck to be marked complete, and the second deck retains its 1 due card + const tagListElements = screen.queryAllByTestId('tag-selector-item'); + + const defaultDeck = within(tagListElements[0].parentNode as HTMLElement).getByText('memo'); + const secondDeck = within(tagListElements[0].parentNode as HTMLElement).getByText('deck-two'); + + const defaultDeckDue = within(defaultDeck).queryByTestId('tag-selector-due'); + const defaultDeckNew = within(defaultDeck).queryByTestId('tag-selector-new'); + const secondDeckDue = within(secondDeck).queryByTestId('tag-selector-due'); + const secondDeckNew = within(secondDeck).queryByTestId('tag-selector-new'); + + expect(defaultDeckDue).not.toBeInTheDocument(); + expect(defaultDeckNew).not.toBeInTheDocument(); + expect(secondDeckDue).toHaveTextContent('1'); + expect(secondDeckNew).not.toBeInTheDocument(); + }); + }); +}); diff --git a/src/components/SidePandelWidget.tsx b/src/components/SidePanelWidget.tsx similarity index 64% rename from src/components/SidePandelWidget.tsx rename to src/components/SidePanelWidget.tsx index 11006bc..21fcc44 100644 --- a/src/components/SidePandelWidget.tsx +++ b/src/components/SidePanelWidget.tsx @@ -1,6 +1,7 @@ -import styled from '@emotion/styled'; import * as Blueprint from '@blueprintjs/core'; +import styled from '@emotion/styled'; import Tooltip from '~/components/Tooltip'; +import { CompletionStatus, Today } from '~/models/practice'; const Wrapper = styled.span` display: flex; @@ -14,15 +15,22 @@ const Tag = styled(Blueprint.Tag)` } `; -const SidePandelWidget = ({ onClickCallback, displayCardCounts }) => { - const combinedCounts = displayCardCounts.combined; - const totalCardsCount = combinedCounts.new + combinedCounts.due; - const isDone = totalCardsCount === 0; +interface SidePanelWidgetProps { + onClickCallback: () => void; + today: Today; +} +const SidePandelWidget = ({ onClickCallback, today }: SidePanelWidgetProps) => { + const allDoneToday = today.combinedToday.status === CompletionStatus.Finished; + const combinedCounts = today.combinedToday; - const iconClass = isDone ? 'bp3-icon-confirm' : 'bp3-icon-box'; + const iconClass = allDoneToday ? 'bp3-icon-confirm' : 'bp3-icon-box'; return ( - +
@@ -33,7 +41,7 @@ const SidePandelWidget = ({ onClickCallback, displayCardCounts }) => { {combinedCounts.due > 0 && ( // @ts-ignore - + {combinedCounts.due} @@ -41,7 +49,7 @@ const SidePandelWidget = ({ onClickCallback, displayCardCounts }) => { {combinedCounts.new > 0 && ( // @ts-ignore - + {combinedCounts.new} diff --git a/src/components/overlay/CardBlock.tsx b/src/components/overlay/CardBlock.tsx index 1c62900..2dd9341 100644 --- a/src/components/overlay/CardBlock.tsx +++ b/src/components/overlay/CardBlock.tsx @@ -31,7 +31,7 @@ const CardBlock = ({ // Ensure block is not collapsed (so we can reveal children programatically) const roamBlockElm = ref.current.querySelector('.rm-block'); setRenderedBlockElm(roamBlockElm); - const isCollapsed = roamBlockElm.classList.contains('rm-block--closed'); + const isCollapsed = roamBlockElm?.classList.contains('rm-block--closed'); if (isCollapsed) { // Currently no Roam API to toggle block collapse, so had to find this hacky // way to do it by simulating click diff --git a/src/components/overlay/Footer.tsx b/src/components/overlay/Footer.tsx index 4c260bc..1312638 100644 --- a/src/components/overlay/Footer.tsx +++ b/src/components/overlay/Footer.tsx @@ -8,6 +8,22 @@ import ButtonTags from '~/components/ButtonTags'; import { IntervalMultiplierType, ReviewModes } from '~/models/session'; import { MainContext } from '~/components/overlay/PracticeOverlay'; +interface IntervalEstimate { + reviewMode: string; + grade: number; + repetitions: number; + interval: number; + eFactor: number; + dateCreated: string; + nextDueDate: string; + nextDueDateFromNow: string; +} + +type IntervalEstimates = + | undefined + | { + [key: number]: IntervalEstimate; + }; const Footer = ({ setShowAnswers, showAnswers, @@ -36,7 +52,9 @@ const Footer = ({ }; const showAnswerFn = React.useMemo(() => { - return () => setShowAnswers(true); + return () => { + setShowAnswers(true); + }; }, [setShowAnswers]); const gradeFn = React.useMemo( () => (grade) => { @@ -146,9 +164,13 @@ const Footer = ({ ); const { handleKeyDown, handleKeyUp } = Blueprint.useHotkeys(hotkeys); - const intervalEstimates = React.useMemo(() => { + const intervalEstimates: IntervalEstimates = React.useMemo(() => { if (!currentCardData) return; + if (!reviewMode) { + console.error('Review mode not set'); + return; + } const grades = [0, 1, 2, 3, 4, 5]; const { interval, repetitions, eFactor } = currentCardData; const estimates = {}; @@ -177,7 +199,10 @@ const Footer = ({ onKeyDown={handleKeyDown} onKeyUp={handleKeyUp} > - + {isDone || !hasCards ? ( { - const { reviewMode, setReviewMode } = React.useContext(MainContext); + const { reviewMode, setReviewModeOverride } = React.useContext(MainContext); const toggleReviewMode = () => { - setReviewMode((prev: ReviewModes) => { - return prev === ReviewModes.DefaultSpacedInterval + if (setReviewModeOverride === undefined) return; + + setReviewModeOverride((prev: ReviewModes | undefined) => { + const isOverrideSet = prev !== undefined; + + if (isOverrideSet) { + // If set we clear it + return undefined; + } + + // Toggle Review Mode + return reviewMode === ReviewModes.DefaultSpacedInterval ? ReviewModes.FixedInterval : ReviewModes.DefaultSpacedInterval; }); @@ -311,6 +346,7 @@ const GradingControlsWrapper = ({ onClick={() => { activateButtonFn('space-button', toggleReviewMode); }} + data-testid="review-mode-button" active={activeButtonKey === 'space-button'} outlined > @@ -417,15 +453,24 @@ const FixedIntervalModeControls = ({ isIntervalEditorOpen, toggleIntervalEditorOpen, intervalEstimates, -}) => { +}: { + activeButtonKey: string; + intervalPractice: () => void; + isIntervalEditorOpen: boolean; + toggleIntervalEditorOpen: () => void; + intervalEstimates: IntervalEstimates; +}): JSX.Element | undefined => { const { intervalMultiplier, intervalMultiplierType } = React.useContext(MainContext); const onInteractionhandler = (nextState) => { if (!nextState && isIntervalEditorOpen) toggleIntervalEditorOpen(); }; + if (!intervalEstimates) { + console.error('Interval estimates not set'); + return; + } return ( <> - {/* @ts-expect-error */} intervalPractice()} - tooltipText={`Review ${intervalEstimates[0]?.nextDueDateFromNow}`} + tooltipText={`Review ${intervalEstimates[0].nextDueDateFromNow}`} active={activeButtonKey === 'next-button'} outlined > @@ -464,63 +509,78 @@ const FixedIntervalModeControls = ({ ); }; -const SpacedIntervalModeControls = ({ activeButtonKey, gradeFn, intervalEstimates }) => ( - <> - gradeFn(0)} - active={activeButtonKey === 'forgot-button'} - outlined - > - Forgot{' '} - - F - - - gradeFn(2)} - tooltipText={`Review ${intervalEstimates[2]?.nextDueDateFromNow}`} - active={activeButtonKey === 'hard-button'} - outlined - > - Hard{' '} - - H - - - gradeFn(4)} - tooltipText={`Review ${intervalEstimates[4]?.nextDueDateFromNow}`} - active={activeButtonKey === 'good-button'} - outlined - > - Good{' '} - - G - - - gradeFn(5)} - tooltipText={`Review ${intervalEstimates[5]?.nextDueDateFromNow}`} - active={activeButtonKey === 'perfect-button'} - outlined - > - Perfect{' '} - - SPACE - - - -); +const SpacedIntervalModeControls = ({ + activeButtonKey, + gradeFn, + intervalEstimates, +}: { + activeButtonKey: string; + gradeFn: (grade: number) => void; + intervalEstimates: IntervalEstimates; +}) => { + if (!intervalEstimates) { + console.error('Interval estimates not set'); + return; + } + + return ( + <> + gradeFn(0)} + active={activeButtonKey === 'forgot-button'} + outlined + > + Forgot{' '} + + F + + + gradeFn(2)} + tooltipText={`Review ${intervalEstimates[2]?.nextDueDateFromNow}`} + active={activeButtonKey === 'hard-button'} + outlined + > + Hard{' '} + + H + + + gradeFn(4)} + tooltipText={`Review ${intervalEstimates[4]?.nextDueDateFromNow}`} + active={activeButtonKey === 'good-button'} + outlined + > + Good{' '} + + G + + + gradeFn(5)} + tooltipText={`Review ${intervalEstimates[5]?.nextDueDateFromNow}`} + active={activeButtonKey === 'perfect-button'} + outlined + > + Perfect{' '} + + SPACE + + + + ); +}; const FooterWrapper = styled.div` background-color: #f6f9fd; diff --git a/src/components/overlay/PracticeOverlay.test.tsx b/src/components/overlay/PracticeOverlay.test.tsx new file mode 100644 index 0000000..b30b7ef --- /dev/null +++ b/src/components/overlay/PracticeOverlay.test.tsx @@ -0,0 +1,233 @@ +import { act, render, screen } from '@testing-library/react'; + +import * as testUtils from '~/utils/testUtils'; +import * as dateUtils from '~/utils/date'; + +import App from '~/app'; +import { IntervalMultiplierType, ReviewModes } from '~/models/session'; + +describe('PracticeOverlay', () => { + it("renders done state when there's no practice data", async () => { + new testUtils.MockDataBuilder().mockQueryResults(); + await act(async () => { + render(); + }); + + await act(async () => { + testUtils.actions.launchModal(); + }); + + const practiceOverlayDoneState = document.querySelector( + '[data-testid="practice-overlay-done-state"]' + ); + expect(practiceOverlayDoneState).toBeInTheDocument(); + }); + + it('Renders correctly when 1 new card', async () => { + const mockBuilder = new testUtils.MockDataBuilder(); + + mockBuilder.withCard({ uid: 'id_new_1' }); + mockBuilder.mockQueryResults(); + + await act(async () => { + render(); + }); + + // Renders new tag count in sidepanel + const newTag = screen.queryByTestId('new-tag'); + expect(newTag).toHaveTextContent('1'); + + await act(async () => { + testUtils.actions.launchModal(); + }); + + // Renders "New" status badge + const statusBadge = screen.queryByTestId('status-badge'); + expect(statusBadge).toBeInTheDocument(); + expect(statusBadge).toHaveTextContent('New'); + + // Renders display count 1/1 + const displayCountCurrent = screen.queryByTestId('display-count-current'); + expect(displayCountCurrent).toBeInTheDocument(); + expect(displayCountCurrent).toHaveTextContent('1'); + + const displayCountTotal = screen.queryByTestId('display-count-total'); + expect(displayCountTotal).toBeInTheDocument(); + expect(displayCountTotal).toHaveTextContent('1'); + }); + + it('Entire Flow', async () => { + const mockBuilder = new testUtils.MockDataBuilder(); + + // Add a due card today + const dueCard1 = 'id_due_1'; + mockBuilder.withCard({ uid: dueCard1 }).withSession(dueCard1, { + dateCreated: dateUtils.subtractDays(new Date(), 1), + nextDueDate: new Date(), + }); + + // Add a new + const newCard1 = 'id_new_1'; + mockBuilder.withCard({ uid: newCard1 }); + + mockBuilder.mockQueryResults(); + await act(async () => { + render(); + }); + + await act(async () => { + testUtils.actions.launchModal(); + }); + + // Verify current card is due + let statusBadge = screen.queryByTestId('status-badge'); + expect(statusBadge).toBeInTheDocument(); + expect(statusBadge).toHaveTextContent('Due Today'); + + await act(async () => { + testUtils.actions.clickControlButton('Show Answer'); + }); + + // Grade the card + let result = await testUtils.grade('Perfect', mockBuilder); + expect(result).toMatchObject({ + reviewMode: ReviewModes.DefaultSpacedInterval, + dataPageTitle: testUtils.dataPageTitle, + dateCreated: new Date(), + eFactor: 2.6, + grade: 5, + refUid: 'id_due_1', + nextDueDate: dateUtils.addDays(new Date(), 1), + }); + + statusBadge = screen.queryByTestId('status-badge'); + expect(statusBadge).toHaveTextContent('New'); + + // Grade new card + await act(async () => { + testUtils.actions.clickControlButton('Show Answer'); + }); + result = await testUtils.grade('Good', mockBuilder); + expect(result).toMatchObject({ + reviewMode: ReviewModes.DefaultSpacedInterval, + dataPageTitle: testUtils.dataPageTitle, + dateCreated: new Date(), + eFactor: 2.5, + grade: 4, + refUid: 'id_new_1', + nextDueDate: dateUtils.addDays(new Date(), 1), + }); + + // Verify completed view + // Renders display count 0/0 + let displayCountCurrent = screen.queryByTestId('display-count-current'); + expect(displayCountCurrent).toBeInTheDocument(); + expect(displayCountCurrent).toHaveTextContent('0'); + + let displayCountTotal = screen.queryByTestId('display-count-total'); + expect(displayCountTotal).toBeInTheDocument(); + expect(displayCountTotal).toHaveTextContent('0'); + + let doneState = screen.queryByTestId('practice-overlay-done-state'); + expect(doneState).toBeInTheDocument(); + + // Continue Cramming Mode + await act(async () => { + testUtils.actions.clickControlButton('Continue Cramming'); + }); + + statusBadge = screen.queryByTestId('status-badge'); + expect(statusBadge).toHaveTextContent('Cramming'); + + // Verify count + displayCountCurrent = screen.queryByTestId('display-count-current'); + expect(displayCountCurrent).toBeInTheDocument(); + expect(displayCountCurrent).toHaveTextContent('1'); + + displayCountTotal = screen.queryByTestId('display-count-total'); + expect(displayCountTotal).toBeInTheDocument(); + expect(displayCountTotal).toHaveTextContent('2'); + + // Skip till end + await act(async () => { + testUtils.actions.clickControlButton('Show Answer'); + }); + await act(async () => { + testUtils.actions.clickControlButton('Skip'); + }); + await act(async () => { + testUtils.actions.clickControlButton('Show Answer'); + }); + await act(async () => { + testUtils.actions.clickControlButton('Skip'); + }); + + // Verify completed view + // Renders display count 0/0 + displayCountCurrent = screen.queryByTestId('display-count-current'); + expect(displayCountCurrent).toBeInTheDocument(); + expect(displayCountCurrent).toHaveTextContent('0'); + + displayCountTotal = screen.queryByTestId('display-count-total'); + expect(displayCountTotal).toBeInTheDocument(); + expect(displayCountTotal).toHaveTextContent('0'); + + doneState = screen.queryByTestId('practice-overlay-done-state'); + expect(doneState).toBeInTheDocument(); + }); + + it('Grading works correctly when switching review modes', async () => { + const mockBuilder = new testUtils.MockDataBuilder(); + + // Add a due card today + const dueCard1 = 'id_due_1'; + mockBuilder.withCard({ uid: dueCard1 }).withSession(dueCard1, { + dateCreated: dateUtils.subtractDays(new Date(), 1), + nextDueDate: new Date(), + }); + + // Add a new + const newCard1 = 'id_new_1'; + mockBuilder.withCard({ uid: newCard1 }); + + mockBuilder.mockQueryResults(); + await act(async () => { + render(); + }); + + await act(async () => { + testUtils.actions.launchModal(); + }); + + await act(async () => { + testUtils.actions.clickControlButton('Show Answer'); + }); + + // Switch to fixed interval mode (do it 3 times to very switching back and forth works) + await act(async () => { + testUtils.actions.clickSwitchReviewModeButton(); + }); + await act(async () => { + testUtils.actions.clickSwitchReviewModeButton(); + }); + await act(async () => { + testUtils.actions.clickSwitchReviewModeButton(); + }); + + // Grade the card + const result = await testUtils.grade('Next', mockBuilder); + expect(result).toMatchObject({ + reviewMode: ReviewModes.FixedInterval, + dataPageTitle: testUtils.dataPageTitle, + dateCreated: new Date(), + refUid: 'id_due_1', + intervalMultiplier: 3, + intervalMultiplierType: IntervalMultiplierType.Days, + nextDueDate: dateUtils.addDays(new Date(), 3), + }); + + // Next card should be new + const statusBadge = screen.queryByTestId('status-badge'); + expect(statusBadge).toHaveTextContent('New'); + }); +}); diff --git a/src/components/overlay/PracticeOverlay.tsx b/src/components/overlay/PracticeOverlay.tsx index 28ee48f..3308046 100644 --- a/src/components/overlay/PracticeOverlay.tsx +++ b/src/components/overlay/PracticeOverlay.tsx @@ -14,82 +14,109 @@ import mediaQueries from '~/utils/mediaQueries'; import CardBlock from '~/components/overlay/CardBlock'; import Footer from '~/components/overlay/Footer'; import ButtonTags from '~/components/ButtonTags'; -import { IntervalMultiplierType, ReviewModes } from '~/models/session'; +import { CompleteRecords, IntervalMultiplierType, ReviewModes } from '~/models/session'; import useCurrentCardData from '~/hooks/useCurrentCardData'; import { generateNewSession } from '~/queries'; +import { CompletionStatus, Today } from '~/models/practice'; +import { handlePracticeProps } from '~/app'; +import { useSafeContext } from '~/hooks/useSafeContext'; interface MainContextProps { - reviewMode?: ReviewModes; - setReviewMode?: React.Dispatch>; - intervalMultiplier?: number; - setIntervalMultiplier?: (multiplier: number) => void; - intervalMultiplierType?: IntervalMultiplierType; - setIntervalMultiplierType?: (type: IntervalMultiplierType) => void; - onPracticeClick?: (props: any) => void; + reviewMode: ReviewModes | undefined; + setReviewModeOverride: React.Dispatch>; + intervalMultiplier: number; + setIntervalMultiplier: (multiplier: number) => void; + intervalMultiplierType: IntervalMultiplierType; + setIntervalMultiplierType: (type: IntervalMultiplierType) => void; + onPracticeClick: (props: any) => void; + today: Today; + selectedTag: string; + currentIndex: number; +} + +export const MainContext = React.createContext({} as MainContextProps); + +interface Props { + isOpen: boolean; + tagsList: string[]; + selectedTag: string; + onCloseCallback: () => void; + practiceData: CompleteRecords; + today: Today; + handlePracticeClick: (props: handlePracticeProps) => void; + handleMemoTagChange: (tag: string) => void; + handleReviewMoreClick: () => void; + isCramming: boolean; + setIsCramming: (isCramming: boolean) => void; + rtlEnabled: boolean; } -export const MainContext = React.createContext({}); const PracticeOverlay = ({ - dataPageTitle, isOpen, tagsList, selectedTag, onCloseCallback, - practiceCardUids, practiceData, - displayCardCounts, + today, handlePracticeClick, handleMemoTagChange, handleReviewMoreClick, isCramming, setIsCramming, - saveCacheData, - lastCompletedDate, - dailyLimit, - completedTodayCount, - remainingDueCardsCount, rtlEnabled, -}) => { +}: Props) => { + const todaySelectedTag = today.tags[selectedTag]; + const newCardsUids = todaySelectedTag.newUids; + const dueCardsUids = todaySelectedTag.dueUids; + // Always practice due cards first + // @MAYBE: Make this order configurable? + const practiceCardUids = [...dueCardsUids, ...newCardsUids]; const [currentIndex, setCurrentIndex] = React.useState(0); - const totalCardsCount = practiceCardUids.length; - const hasCards = totalCardsCount > 0; - const isDone = currentIndex > practiceCardUids.length - 1; - const isLastCompleteDateToday = dateUtils.isSameDay(lastCompletedDate, new Date()); - const isFirst = currentIndex === 0; - const reviewCountReset = completedTodayCount && !lastCompletedDate; - const dailyLimitDelta = - dailyLimit && completedTodayCount && !isDone && !reviewCountReset ? completedTodayCount : 0; + const isFirst = currentIndex === 0; + const completedTodayCount = todaySelectedTag.completed; - const currentCardRefUid = practiceCardUids[currentIndex]; - const { currentCardData, reviewMode, setReviewMode } = useCurrentCardData({ - practiceData, - dataPageTitle, + const currentCardRefUid = practiceCardUids[currentIndex] as string | undefined; + const sessions = React.useMemo( + () => (currentCardRefUid ? practiceData[currentCardRefUid] : []), + [currentCardRefUid, practiceData] + ); + const { currentCardData, reviewMode, setReviewModeOverride } = useCurrentCardData({ currentCardRefUid, + sessions, }); + const totalCardsCount = todaySelectedTag.new + todaySelectedTag.due; + const hasCards = totalCardsCount > 0; + const isDone = todaySelectedTag.status === CompletionStatus.Finished || !currentCardData; + const newFixedSessionDefaults = React.useMemo( () => generateNewSession({ reviewMode: ReviewModes.FixedInterval }), [] ); const [intervalMultiplier, setIntervalMultiplier] = React.useState( - currentCardData?.intervalMultiplier || newFixedSessionDefaults.intervalMultiplier + currentCardData?.intervalMultiplier || (newFixedSessionDefaults.intervalMultiplier as number) ); const [intervalMultiplierType, setIntervalMultiplierType] = React.useState( - currentCardData?.intervalMultiplierType || newFixedSessionDefaults.intervalMultiplierType + currentCardData?.intervalMultiplierType || + (newFixedSessionDefaults.intervalMultiplierType as IntervalMultiplierType) ); // When card changes, update multiplier state React.useEffect(() => { + if (!currentCardData) return; + if (currentCardData?.reviewMode === ReviewModes.FixedInterval) { // If card has multiplier, use that - setIntervalMultiplier(currentCardData?.intervalMultiplier); - setIntervalMultiplierType(currentCardData?.intervalMultiplierType); + setIntervalMultiplier(currentCardData.intervalMultiplier as number); + setIntervalMultiplierType(currentCardData.intervalMultiplierType as IntervalMultiplierType); } else { // Otherwise, just reset to default - setIntervalMultiplier(newFixedSessionDefaults.intervalMultiplier); - setIntervalMultiplierType(newFixedSessionDefaults.intervalMultiplierType); + setIntervalMultiplier(newFixedSessionDefaults.intervalMultiplier as number); + setIntervalMultiplierType( + newFixedSessionDefaults.intervalMultiplierType as IntervalMultiplierType + ); } }, [currentCardData, newFixedSessionDefaults]); @@ -113,17 +140,6 @@ const PracticeOverlay = ({ } }, [hasBlockChildren, hasCloze, currentIndex, tagsList]); - // On show "done" screen - React.useEffect(() => { - if (isDone) { - if (isCramming) { - setIsCramming(false); - } else if (!isLastCompleteDateToday) { - saveCacheData({ lastCompletedDate: new Date() }); - } - } - }, [isDone]); - const onTagChange = async (tag) => { setCurrentIndex(0); handleMemoTagChange(tag); @@ -137,6 +153,11 @@ const PracticeOverlay = ({ } }; + // When sessions are updated, reset current index + React.useEffect(() => { + setCurrentIndex(0); + }, [practiceData]); + const onPracticeClick = React.useCallback( (props) => { if (isDone) return; @@ -145,12 +166,12 @@ const PracticeOverlay = ({ setCurrentIndex(currentIndex + 1); }, [ - currentIndex, handlePracticeClick, isDone, reviewMode, intervalMultiplier, intervalMultiplierType, + currentIndex, ] ); @@ -204,12 +225,15 @@ const PracticeOverlay = ({ {/* @ts-ignore */} @@ -222,9 +246,6 @@ const PracticeOverlay = ({
) : ( -
+
- {remainingDueCardsCount ? ( + {/* @TODOZ: Add support for review more*/} + {/* eslint-disable-next-line no-constant-condition */} + {false ? (
- Reviewed {completedTodayCount}{' '} + Reviewed {todaySelectedTag.completed}{' '} {stringUtils.pluralize(completedTodayCount, 'card', 'cards')} today.{' '} Review more
) : ( -
No cards left to review!
+
+ You're all caught up! 🌟{' '} + {todaySelectedTag.completed > 0 + ? `Reviewed ${todaySelectedTag.completed} ${stringUtils.pluralize( + todaySelectedTag.completed, + 'card', + 'cards' + )} today.` + : ''} +
)}
)} @@ -321,7 +351,7 @@ const HeaderWrapper = styled.div` } `; -const TagSelector = ({ tagsList, selectedTag, onTagChange, displayCardCounts }) => { +const TagSelector = ({ tagsList, selectedTag, onTagChange }) => { return ( // @ts-ignore ); }} @@ -345,7 +374,12 @@ const TagSelector = ({ tagsList, selectedTag, onTagChange, displayCardCounts }) }} popoverProps={{ minimal: true }} > - + ); }; @@ -372,20 +406,34 @@ const Tag = styled(Blueprint.Tag)` } `; -const TagSelectorItem = ({ text, onClick, active, tagsList, displayCardCounts }) => { - const dueCount = displayCardCounts[text].due; - const newCount = displayCardCounts[text].new; +const TagSelectorItem = ({ text, onClick, active, tagsList }) => { + const { today } = React.useContext(MainContext); + const dueCount = today.tags[text].due; + const newCount = today.tags[text].new; + const index = tagsList.indexOf(text); const placement = index === tagsList.length - 1 ? 'bottom' : 'top'; return ( - + {text}
{dueCount > 0 && ( // @ts-ignore - + {dueCount} @@ -393,7 +441,13 @@ const TagSelectorItem = ({ text, onClick, active, tagsList, displayCardCounts }) {newCount > 0 && ( // @ts-ignore - + {newCount} @@ -458,10 +512,7 @@ const BreadcrumbTooltipContent = ({ showBreadcrumbs }) => { const Header = ({ tagsList, - selectedTag, - currentIndex, onCloseCallback, - totalCardsCount, onTagChange, className, status, @@ -470,20 +521,21 @@ const Header = ({ showBreadcrumbs, setShowBreadcrumbs, isCramming, - dailyLimitDelta, - displayCardCounts, }) => { + const { selectedTag, today, currentIndex } = useSafeContext(MainContext); + const todaySelectedTag = today.tags[selectedTag]; + const completedTodayCount = todaySelectedTag.completed; + const remainingTodayCount = todaySelectedTag.due + todaySelectedTag.new; + + const currentIndexDelta = isCramming ? 0 : completedTodayCount; + const currentDisplayCount = currentIndexDelta + currentIndex + 1; + return (
- +
@@ -501,17 +553,20 @@ const Header = ({
)} - + + + - - {totalCardsCount === 0 - ? 0 - : isDone - ? currentIndex + dailyLimitDelta - : currentIndex + 1 + dailyLimitDelta} - + {isDone ? 0 : currentDisplayCount} / - {totalCardsCount + dailyLimitDelta} + + {isDone ? 0 : remainingTodayCount} +