From 9abd8b26675d6a6b3a27d2b3bf78f0fec20c7762 Mon Sep 17 00:00:00 2001 From: Mikayla Dobson Date: Fri, 5 Aug 2022 14:08:31 -0500 Subject: [PATCH 01/11] preparing to redefine some actions --- package-lock.json | 1425 +++++++++++++++++ package.json | 2 + src/components/Card/Card.tsx | 2 +- .../Player/ActionMethods/actions.test.ts | 62 - .../Player/ActionMethods/buyCard.test.ts | 141 ++ .../Player/ActionMethods/buyCardActions.ts | 45 +- .../Player/ActionMethods/getChips.test.ts | 82 + .../Player/ActionMethods/reserveCard.test.ts | 0 src/util/getTotalBuyingPower.ts | 27 + src/util/testUtils.ts | 4 +- 10 files changed, 1707 insertions(+), 83 deletions(-) delete mode 100644 src/components/Player/ActionMethods/actions.test.ts create mode 100644 src/components/Player/ActionMethods/buyCard.test.ts create mode 100644 src/components/Player/ActionMethods/getChips.test.ts create mode 100644 src/components/Player/ActionMethods/reserveCard.test.ts create mode 100644 src/util/getTotalBuyingPower.ts diff --git a/package-lock.json b/package-lock.json index 6a74ec5..4691977 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,10 +15,12 @@ "uuid": "^8.3.2" }, "devDependencies": { + "@testing-library/react": "^13.3.0", "@types/react": "^18.0.15", "@types/react-dom": "^18.0.6", "@types/uuid": "^8.3.4", "@vitejs/plugin-react": "^2.0.0", + "jsdom": "^20.0.0", "typescript": "^4.6.4", "vite": "^3.0.0", "vitest": "^0.20.2" @@ -487,6 +489,128 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "node_modules/@testing-library/dom": { + "version": "8.16.1", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-8.16.1.tgz", + "integrity": "sha512-XEV2mBxgv6DKjL3+U3WEUzBgT2CjYksoXGlLrrJXYP8OvRfGkBonvelkorazpFlp8tkEecO06r43vN4DIEyegQ==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^4.2.0", + "aria-query": "^5.0.0", + "chalk": "^4.1.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.4.4", + "pretty-format": "^27.0.2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@testing-library/dom/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@testing-library/dom/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@testing-library/dom/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@testing-library/dom/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/@testing-library/dom/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@testing-library/dom/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@testing-library/react": { + "version": "13.3.0", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-13.3.0.tgz", + "integrity": "sha512-DB79aA426+deFgGSjnf5grczDPiL4taK3hFaa+M5q7q20Kcve9eQottOG5kZ74KEr55v0tU2CQormSSDK87zYQ==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.12.5", + "@testing-library/dom": "^8.5.0", + "@types/react-dom": "^18.0.0" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "react": "^18.0.0", + "react-dom": "^18.0.0" + } + }, + "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": "4.2.2", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-4.2.2.tgz", + "integrity": "sha512-HnYpAE1Y6kRyKM/XkEuiRQhTHvkzMBurTHnpFLYLBGPIylZNPs9jJcuOOYWxPLJCSEtmZT0Y8rHDokKN7rRTig==", + "dev": true + }, "node_modules/@types/chai": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.1.tgz", @@ -567,6 +691,76 @@ "vite": "^3.0.0" } }, + "node_modules/abab": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", + "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", + "dev": true + }, + "node_modules/acorn": { + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz", + "integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-globals": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz", + "integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==", + "dev": true, + "dependencies": { + "acorn": "^7.1.1", + "acorn-walk": "^7.1.1" + } + }, + "node_modules/acorn-globals/node_modules/acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", + "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", @@ -579,6 +773,15 @@ "node": ">=4" } }, + "node_modules/aria-query": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.0.0.tgz", + "integrity": "sha512-V+SM7AbUwJ+EBnB8+DXs0hPZHO0W6pqBcc0dW90OwtVG02PswOu/teuARoLQjdDOH+t9pJgGnW5/Qmouf3gPJg==", + "dev": true, + "engines": { + "node": ">=6.0" + } + }, "node_modules/assertion-error": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", @@ -588,6 +791,18 @@ "node": "*" } }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true + }, + "node_modules/browser-process-hrtime": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", + "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", + "dev": true + }, "node_modules/browserslist": { "version": "4.21.3", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.3.tgz", @@ -688,6 +903,18 @@ "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", "dev": true }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/convert-source-map": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", @@ -697,12 +924,50 @@ "safe-buffer": "~5.1.1" } }, + "node_modules/cssom": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz", + "integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==", + "dev": true + }, + "node_modules/cssstyle": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", + "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", + "dev": true, + "dependencies": { + "cssom": "~0.3.6" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cssstyle/node_modules/cssom": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", + "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", + "dev": true + }, "node_modules/csstype": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.0.tgz", "integrity": "sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA==", "dev": true }, + "node_modules/data-urls": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-3.0.2.tgz", + "integrity": "sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==", + "dev": true, + "dependencies": { + "abab": "^2.0.6", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^11.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -720,6 +985,12 @@ } } }, + "node_modules/decimal.js": { + "version": "10.3.1", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.3.1.tgz", + "integrity": "sha512-V0pfhfr8suzyPGOx3nmq4aHqabehUZn6Ch9kyFpV79TGDTWFmHqUqXdabR7QHqxzrYolF4+tVmJhUG4OURg5dQ==", + "dev": true + }, "node_modules/deep-eql": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", @@ -732,12 +1003,57 @@ "node": ">=0.12" } }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/dom-accessibility-api": { + "version": "0.5.14", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.14.tgz", + "integrity": "sha512-NMt+m9zFMPZe0JcY9gN224Qvk6qLIdqex29clBvc/y75ZBX9YA9wNK3frsYvu2DI1xcCIwxwnX+TlsJ2DSOADg==", + "dev": true + }, + "node_modules/domexception": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz", + "integrity": "sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==", + "dev": true, + "dependencies": { + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/electron-to-chromium": { "version": "1.4.206", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.206.tgz", "integrity": "sha512-h+Fadt1gIaQ06JaIiyqPsBjJ08fV5Q7md+V8bUvQW/9OvXfL2LRICTz2EcnnCP7QzrFTS6/27MRV6Bl9Yn97zA==", "dev": true }, + "node_modules/entities": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.3.1.tgz", + "integrity": "sha512-o4q/dYJlmyjP2zfnaWDUC6A3BQFmVTX+tZPezK7k0GLSU9QYCauscf5Y+qcEPzKL+EixVouYDgLQK5H9GrLpkg==", + "dev": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/esbuild": { "version": "0.14.49", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.14.49.tgz", @@ -1111,6 +1427,79 @@ "node": ">=0.8.0" } }, + "node_modules/escodegen": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.0.0.tgz", + "integrity": "sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw==", + "dev": true, + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2", + "optionator": "^0.8.1" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dev": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/fsevents": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", @@ -1187,6 +1576,57 @@ "@babel/runtime": "^7.7.6" } }, + "node_modules/html-encoding-sniffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", + "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", + "dev": true, + "dependencies": { + "whatwg-encoding": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "dev": true, + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-core-module": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.9.0.tgz", @@ -1199,11 +1639,63 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, + "node_modules/jsdom": { + "version": "20.0.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-20.0.0.tgz", + "integrity": "sha512-x4a6CKCgx00uCmP+QakBDFXwjAJ69IkkIWHmtmjd3wvXPcdOS44hfX2vqkOQrVrq8l9DhNNADZRXaCEWvgXtVA==", + "dev": true, + "dependencies": { + "abab": "^2.0.6", + "acorn": "^8.7.1", + "acorn-globals": "^6.0.0", + "cssom": "^0.5.0", + "cssstyle": "^2.3.0", + "data-urls": "^3.0.2", + "decimal.js": "^10.3.1", + "domexception": "^4.0.0", + "escodegen": "^2.0.0", + "form-data": "^4.0.0", + "html-encoding-sniffer": "^3.0.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.1", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.0", + "parse5": "^7.0.0", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^4.0.0", + "w3c-hr-time": "^1.0.2", + "w3c-xmlserializer": "^3.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^2.0.0", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^11.0.0", + "ws": "^8.8.0", + "xml-name-validator": "^4.0.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "canvas": "^2.5.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, "node_modules/jsesc": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", @@ -1228,6 +1720,19 @@ "node": ">=6" } }, + "node_modules/levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", + "dev": true, + "dependencies": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/local-pkg": { "version": "0.4.2", "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.4.2.tgz", @@ -1260,6 +1765,15 @@ "get-func-name": "^2.0.0" } }, + "node_modules/lz-string": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.4.4.tgz", + "integrity": "sha512-0ckx7ZHRPqb0oUm8zNr+90mtf9DQB60H1wMCjBtfi62Kl3a7JbHob6gA2bC+xRvZoOL+1hzUK8jeuEIQE8svEQ==", + "dev": true, + "bin": { + "lz-string": "bin/bin.js" + } + }, "node_modules/magic-string": { "version": "0.26.2", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.26.2.tgz", @@ -1272,6 +1786,27 @@ "node": ">=12" } }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -1296,6 +1831,12 @@ "integrity": "sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==", "dev": true }, + "node_modules/nwsapi": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.1.tgz", + "integrity": "sha512-JYOWTeFoS0Z93587vRJgASD5Ut11fYl5NyihP3KrYBvMe1FRRs6RN7m20SA/16GM4P6hTnZjT+UmDOt38UeXNg==", + "dev": true + }, "node_modules/ometa": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/ometa/-/ometa-0.2.2.tgz", @@ -1304,6 +1845,35 @@ "node": ">= 0.2.0" } }, + "node_modules/optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "dev": true, + "dependencies": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/parse5": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.0.0.tgz", + "integrity": "sha512-y/t8IXSPWTuRZqXc0ajH/UwDj4mnqLEbSttNbThcFhGrZuOyoyvNBO85PBp2jQa55wY9d07PBNjsK8ZP3K5U6g==", + "dev": true, + "dependencies": { + "entities": "^4.3.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, "node_modules/path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", @@ -1349,6 +1919,56 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "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/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/psl": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", + "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", + "dev": true + }, + "node_modules/punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/react": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", @@ -1372,6 +1992,12 @@ "react": "^18.2.0" } }, + "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/react-refresh": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz", @@ -1448,6 +2074,24 @@ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "dev": true }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, "node_modules/scheduler": { "version": "0.23.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", @@ -1476,6 +2120,16 @@ "semver": "bin/semver.js" } }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/source-map-js": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", @@ -1515,6 +2169,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true + }, "node_modules/tinypool": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.2.4.tgz", @@ -1542,6 +2202,44 @@ "node": ">=4" } }, + "node_modules/tough-cookie": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.0.0.tgz", + "integrity": "sha512-tHdtEpQCMrc1YLrMaqXXcj6AxhYi/xgit6mZu1+EDWUn+qhUf8wMQoFIy9NXuq23zAwtcB0t/MjACGR18pcRbg==", + "dev": true, + "dependencies": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.1.2" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tr46": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", + "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", + "dev": true, + "dependencies": { + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", + "dev": true, + "dependencies": { + "prelude-ls": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/type-detect": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", @@ -1564,6 +2262,15 @@ "node": ">=4.2.0" } }, + "node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "engines": { + "node": ">= 4.0.0" + } + }, "node_modules/update-browserslist-db": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.5.tgz", @@ -1692,6 +2399,115 @@ "optional": true } } + }, + "node_modules/w3c-hr-time": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", + "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", + "dev": true, + "dependencies": { + "browser-process-hrtime": "^1.0.0" + } + }, + "node_modules/w3c-xmlserializer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-3.0.0.tgz", + "integrity": "sha512-3WFqGEgSXIyGhOmAFtlicJNMjEps8b1MG31NCA0/vOF9+nKMUW1ckhi9cnNHmf88Rzw5V+dwIwsm2C7X8k9aQg==", + "dev": true, + "dependencies": { + "xml-name-validator": "^4.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-encoding": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", + "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", + "dev": true, + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-mimetype": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", + "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-url": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", + "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", + "dev": true, + "dependencies": { + "tr46": "^3.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ws": { + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.8.1.tgz", + "integrity": "sha512-bGy2JzvzkPowEJV++hF07hAD6niYSr0JzBNo/J29WsB57A2r7Wlc1UFcTR9IzrPvuNVO4B8LGqF8qcpsVOhJCA==", + "dev": true, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xml-name-validator": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", + "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true } }, "dependencies": { @@ -2036,6 +2852,96 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "@testing-library/dom": { + "version": "8.16.1", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-8.16.1.tgz", + "integrity": "sha512-XEV2mBxgv6DKjL3+U3WEUzBgT2CjYksoXGlLrrJXYP8OvRfGkBonvelkorazpFlp8tkEecO06r43vN4DIEyegQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^4.2.0", + "aria-query": "^5.0.0", + "chalk": "^4.1.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.4.4", + "pretty-format": "^27.0.2" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "@testing-library/react": { + "version": "13.3.0", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-13.3.0.tgz", + "integrity": "sha512-DB79aA426+deFgGSjnf5grczDPiL4taK3hFaa+M5q7q20Kcve9eQottOG5kZ74KEr55v0tU2CQormSSDK87zYQ==", + "dev": true, + "requires": { + "@babel/runtime": "^7.12.5", + "@testing-library/dom": "^8.5.0", + "@types/react-dom": "^18.0.0" + } + }, + "@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "dev": true + }, + "@types/aria-query": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-4.2.2.tgz", + "integrity": "sha512-HnYpAE1Y6kRyKM/XkEuiRQhTHvkzMBurTHnpFLYLBGPIylZNPs9jJcuOOYWxPLJCSEtmZT0Y8rHDokKN7rRTig==", + "dev": true + }, "@types/chai": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.1.tgz", @@ -2110,6 +3016,57 @@ "react-refresh": "^0.14.0" } }, + "abab": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", + "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", + "dev": true + }, + "acorn": { + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz", + "integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==", + "dev": true + }, + "acorn-globals": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz", + "integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==", + "dev": true, + "requires": { + "acorn": "^7.1.1", + "acorn-walk": "^7.1.1" + }, + "dependencies": { + "acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true + } + } + }, + "acorn-walk": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", + "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", + "dev": true + }, + "agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "requires": { + "debug": "4" + } + }, + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, "ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", @@ -2119,12 +3076,30 @@ "color-convert": "^1.9.0" } }, + "aria-query": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.0.0.tgz", + "integrity": "sha512-V+SM7AbUwJ+EBnB8+DXs0hPZHO0W6pqBcc0dW90OwtVG02PswOu/teuARoLQjdDOH+t9pJgGnW5/Qmouf3gPJg==", + "dev": true + }, "assertion-error": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", "dev": true }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true + }, + "browser-process-hrtime": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", + "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", + "dev": true + }, "browserslist": { "version": "4.21.3", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.3.tgz", @@ -2190,6 +3165,15 @@ "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", "dev": true }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "requires": { + "delayed-stream": "~1.0.0" + } + }, "convert-source-map": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", @@ -2199,12 +3183,46 @@ "safe-buffer": "~5.1.1" } }, + "cssom": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz", + "integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==", + "dev": true + }, + "cssstyle": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", + "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", + "dev": true, + "requires": { + "cssom": "~0.3.6" + }, + "dependencies": { + "cssom": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", + "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", + "dev": true + } + } + }, "csstype": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.0.tgz", "integrity": "sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA==", "dev": true }, + "data-urls": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-3.0.2.tgz", + "integrity": "sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==", + "dev": true, + "requires": { + "abab": "^2.0.6", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^11.0.0" + } + }, "debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -2214,6 +3232,12 @@ "ms": "2.1.2" } }, + "decimal.js": { + "version": "10.3.1", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.3.1.tgz", + "integrity": "sha512-V0pfhfr8suzyPGOx3nmq4aHqabehUZn6Ch9kyFpV79TGDTWFmHqUqXdabR7QHqxzrYolF4+tVmJhUG4OURg5dQ==", + "dev": true + }, "deep-eql": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", @@ -2223,12 +3247,45 @@ "type-detect": "^4.0.0" } }, + "deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true + }, + "dom-accessibility-api": { + "version": "0.5.14", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.14.tgz", + "integrity": "sha512-NMt+m9zFMPZe0JcY9gN224Qvk6qLIdqex29clBvc/y75ZBX9YA9wNK3frsYvu2DI1xcCIwxwnX+TlsJ2DSOADg==", + "dev": true + }, + "domexception": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz", + "integrity": "sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==", + "dev": true, + "requires": { + "webidl-conversions": "^7.0.0" + } + }, "electron-to-chromium": { "version": "1.4.206", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.206.tgz", "integrity": "sha512-h+Fadt1gIaQ06JaIiyqPsBjJ08fV5Q7md+V8bUvQW/9OvXfL2LRICTz2EcnnCP7QzrFTS6/27MRV6Bl9Yn97zA==", "dev": true }, + "entities": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.3.1.tgz", + "integrity": "sha512-o4q/dYJlmyjP2zfnaWDUC6A3BQFmVTX+tZPezK7k0GLSU9QYCauscf5Y+qcEPzKL+EixVouYDgLQK5H9GrLpkg==", + "dev": true + }, "esbuild": { "version": "0.14.49", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.14.49.tgz", @@ -2409,6 +3466,54 @@ "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", "dev": true }, + "escodegen": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.0.0.tgz", + "integrity": "sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw==", + "dev": true, + "requires": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2", + "optionator": "^0.8.1", + "source-map": "~0.6.1" + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + }, "fsevents": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", @@ -2463,6 +3568,45 @@ "@babel/runtime": "^7.7.6" } }, + "html-encoding-sniffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", + "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", + "dev": true, + "requires": { + "whatwg-encoding": "^2.0.0" + } + }, + "http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "dev": true, + "requires": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + } + }, + "https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, + "requires": { + "agent-base": "6", + "debug": "4" + } + }, + "iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + } + }, "is-core-module": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.9.0.tgz", @@ -2472,11 +3616,52 @@ "has": "^1.0.3" } }, + "is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true + }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, + "jsdom": { + "version": "20.0.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-20.0.0.tgz", + "integrity": "sha512-x4a6CKCgx00uCmP+QakBDFXwjAJ69IkkIWHmtmjd3wvXPcdOS44hfX2vqkOQrVrq8l9DhNNADZRXaCEWvgXtVA==", + "dev": true, + "requires": { + "abab": "^2.0.6", + "acorn": "^8.7.1", + "acorn-globals": "^6.0.0", + "cssom": "^0.5.0", + "cssstyle": "^2.3.0", + "data-urls": "^3.0.2", + "decimal.js": "^10.3.1", + "domexception": "^4.0.0", + "escodegen": "^2.0.0", + "form-data": "^4.0.0", + "html-encoding-sniffer": "^3.0.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.1", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.0", + "parse5": "^7.0.0", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^4.0.0", + "w3c-hr-time": "^1.0.2", + "w3c-xmlserializer": "^3.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^2.0.0", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^11.0.0", + "ws": "^8.8.0", + "xml-name-validator": "^4.0.0" + } + }, "jsesc": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", @@ -2489,6 +3674,16 @@ "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", "dev": true }, + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + } + }, "local-pkg": { "version": "0.4.2", "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.4.2.tgz", @@ -2512,6 +3707,12 @@ "get-func-name": "^2.0.0" } }, + "lz-string": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.4.4.tgz", + "integrity": "sha512-0ckx7ZHRPqb0oUm8zNr+90mtf9DQB60H1wMCjBtfi62Kl3a7JbHob6gA2bC+xRvZoOL+1hzUK8jeuEIQE8svEQ==", + "dev": true + }, "magic-string": { "version": "0.26.2", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.26.2.tgz", @@ -2521,6 +3722,21 @@ "sourcemap-codec": "^1.4.8" } }, + "mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true + }, + "mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "requires": { + "mime-db": "1.52.0" + } + }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -2539,11 +3755,40 @@ "integrity": "sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==", "dev": true }, + "nwsapi": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.1.tgz", + "integrity": "sha512-JYOWTeFoS0Z93587vRJgASD5Ut11fYl5NyihP3KrYBvMe1FRRs6RN7m20SA/16GM4P6hTnZjT+UmDOt38UeXNg==", + "dev": true + }, "ometa": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/ometa/-/ometa-0.2.2.tgz", "integrity": "sha512-LZuoK/yjU3FvrxPjUXUlZ1bavCfBPqauA7fsNdwi+AVhRdyk2IzgP3JRnevvjzQ6fKHdUw8YISshf53FmpHrng==" }, + "optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "dev": true, + "requires": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + } + }, + "parse5": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.0.0.tgz", + "integrity": "sha512-y/t8IXSPWTuRZqXc0ajH/UwDj4mnqLEbSttNbThcFhGrZuOyoyvNBO85PBp2jQa55wY9d07PBNjsK8ZP3K5U6g==", + "dev": true, + "requires": { + "entities": "^4.3.0" + } + }, "path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", @@ -2573,6 +3818,43 @@ "source-map-js": "^1.0.2" } }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", + "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 + } + } + }, + "psl": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", + "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", + "dev": true + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + }, "react": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", @@ -2590,6 +3872,12 @@ "scheduler": "^0.23.0" } }, + "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 + }, "react-refresh": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz", @@ -2644,6 +3932,21 @@ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "dev": true }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "requires": { + "xmlchars": "^2.2.0" + } + }, "scheduler": { "version": "0.23.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", @@ -2666,6 +3969,13 @@ "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", "dev": true }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "optional": true + }, "source-map-js": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", @@ -2693,6 +4003,12 @@ "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", "dev": true }, + "symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true + }, "tinypool": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.2.4.tgz", @@ -2711,6 +4027,35 @@ "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", "dev": true }, + "tough-cookie": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.0.0.tgz", + "integrity": "sha512-tHdtEpQCMrc1YLrMaqXXcj6AxhYi/xgit6mZu1+EDWUn+qhUf8wMQoFIy9NXuq23zAwtcB0t/MjACGR18pcRbg==", + "dev": true, + "requires": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.1.2" + } + }, + "tr46": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", + "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", + "dev": true, + "requires": { + "punycode": "^2.1.1" + } + }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2" + } + }, "type-detect": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", @@ -2723,6 +4068,12 @@ "integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==", "dev": true }, + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true + }, "update-browserslist-db": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.5.tgz", @@ -2767,6 +4118,80 @@ "tinyspy": "^1.0.0", "vite": "^2.9.12 || ^3.0.0-0" } + }, + "w3c-hr-time": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", + "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", + "dev": true, + "requires": { + "browser-process-hrtime": "^1.0.0" + } + }, + "w3c-xmlserializer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-3.0.0.tgz", + "integrity": "sha512-3WFqGEgSXIyGhOmAFtlicJNMjEps8b1MG31NCA0/vOF9+nKMUW1ckhi9cnNHmf88Rzw5V+dwIwsm2C7X8k9aQg==", + "dev": true, + "requires": { + "xml-name-validator": "^4.0.0" + } + }, + "webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true + }, + "whatwg-encoding": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", + "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", + "dev": true, + "requires": { + "iconv-lite": "0.6.3" + } + }, + "whatwg-mimetype": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", + "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", + "dev": true + }, + "whatwg-url": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", + "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", + "dev": true, + "requires": { + "tr46": "^3.0.0", + "webidl-conversions": "^7.0.0" + } + }, + "word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true + }, + "ws": { + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.8.1.tgz", + "integrity": "sha512-bGy2JzvzkPowEJV++hF07hAD6niYSr0JzBNo/J29WsB57A2r7Wlc1UFcTR9IzrPvuNVO4B8LGqF8qcpsVOhJCA==", + "dev": true, + "requires": {} + }, + "xml-name-validator": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", + "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==", + "dev": true + }, + "xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true } } } diff --git a/package.json b/package.json index 0790eef..3e8f2fd 100644 --- a/package.json +++ b/package.json @@ -17,10 +17,12 @@ "uuid": "^8.3.2" }, "devDependencies": { + "@testing-library/react": "^13.3.0", "@types/react": "^18.0.15", "@types/react-dom": "^18.0.6", "@types/uuid": "^8.3.4", "@vitejs/plugin-react": "^2.0.0", + "jsdom": "^20.0.0", "typescript": "^4.6.4", "vite": "^3.0.0", "vitest": "^0.20.2" diff --git a/src/components/Card/Card.tsx b/src/components/Card/Card.tsx index 659d5cd..9f40bf1 100644 --- a/src/components/Card/Card.tsx +++ b/src/components/Card/Card.tsx @@ -23,7 +23,7 @@ export default function Card({ data, state, setState }: CardProps) { } { state.actions.buyCard.active && diff --git a/src/components/Player/ActionMethods/actions.test.ts b/src/components/Player/ActionMethods/actions.test.ts deleted file mode 100644 index a8d1576..0000000 --- a/src/components/Player/ActionMethods/actions.test.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { buyCard, getTotalBuyingPower, tooExpensive, updateResources } from './buyCardActions'; -import { test, expect, describe } from 'vitest'; -import { expensiveCard, midGameCardOne, midGameState, mockPlayerOne, mockPlayerTwo, mockState } from '../../../util/testUtils'; -import { useCurrentPlayer } from '../../../util/useCurrentPlayer'; - -describe('buy cards', () => { - test('detects unaffordable cards', () => { - const result = tooExpensive(expensiveCard, mockState); - expect(result).toBeTruthy(); - }) - - test('calculates total buying power', () => { - let modifiedState = { - ...mockState, - players: [ - { - ...mockPlayerOne, - inventory: { - ruby: 3, - sapphire: 3, - emerald: 3, - onyx: 3, - diamond: 3, - gold: 3 - }, - cards: [expensiveCard] - }, - mockPlayerTwo - ] - } - - const totalBuyingPower = getTotalBuyingPower(modifiedState); - - const expectedValue = { - ruby: 3, - sapphire: 3, - emerald: 3, - onyx: 3, - diamond: 4, - gold: 3 - } - - expect(totalBuyingPower).toStrictEqual(expectedValue); - }) - - test('updateResources', () => { - const currentPlayer = useCurrentPlayer(midGameState); - if (!currentPlayer) return; - - const { newTradingResources, updatedPlayer } = updateResources(midGameState, midGameCardOne); - expect(newTradingResources).toBeDefined(); - expect(updatedPlayer).toBeDefined(); - }) - - test('renders the correct inventory', () => { - const output = 1; - expect(output).toBe(1); - }) -}) - -describe('get chips', () => {}) -describe('reserve card', () => {}) \ No newline at end of file diff --git a/src/components/Player/ActionMethods/buyCard.test.ts b/src/components/Player/ActionMethods/buyCard.test.ts new file mode 100644 index 0000000..1ae9381 --- /dev/null +++ b/src/components/Player/ActionMethods/buyCard.test.ts @@ -0,0 +1,141 @@ +import { expensiveCard, midGameCardOne, midGameCardTwo, midGameState, mockPlayerOne, mockPlayerTwo, mockState, playerTwoMidGame } from '../../../util/testUtils'; +import { buyCard, tooExpensive } from './buyCardActions'; +import getTotalBuyingPower from '../../../util/getTotalBuyingPower'; +import { useCurrentPlayer } from '../../../util/useCurrentPlayer'; +import { AppState, PlayerData } from '../../../util/types'; +import { test, expect, describe } from 'vitest'; +import { renderHook } from "@testing-library/react"; +import { useState } from 'react'; +import { initialState } from '../../../util/stateSetters'; + +describe('buy cards', () => { + test('detects unaffordable cards', () => { + const result = tooExpensive(expensiveCard, mockState); + expect(result).toBeTruthy(); + }) + + test('calculates total buying power', () => { + let modifiedState = { + ...mockState, + players: [ + { + ...mockPlayerOne, + inventory: { + ruby: 3, + sapphire: 3, + emerald: 3, + onyx: 3, + diamond: 3, + gold: 3 + }, + cards: [expensiveCard] + }, + mockPlayerTwo + ] + } + + const totalBuyingPower = getTotalBuyingPower(modifiedState); + + const expectedValue = { + ruby: 3, + sapphire: 3, + emerald: 3, + onyx: 3, + diamond: 4, + gold: 3 + } + + expect(totalBuyingPower).toStrictEqual(expectedValue); + }) + + test('buyCard and updateResources', () => { + const { result } = renderHook(() => { + const [state, setState] = useState(midGameState); + + return { state, setState } + }) + + const { state, setState } = result.current; + + const currentPlayer = useCurrentPlayer(state); + if (!currentPlayer) return; + + /** + * actions in test: + * midGameState :: playerOneMidGame, playerTwoMidGame + * playerOneMidGame => buy midGameCardTwo + * playerTwoMidGame => buy midGameCardOne + */ + + let P1UPDATED = state.players.filter((p: PlayerData) => p.name === "Player One")[0]; + let P2UPDATED = state.players.filter((p: PlayerData) => p.name === "Player Two")[0]; + + // playerOne receives midGameCardTwo and pays three diamonds back to resource pool + P1UPDATED = { + ...P1UPDATED, + cards: [...P1UPDATED.cards, midGameCardTwo], + turnActive: false, + inventory: { + ...P1UPDATED.inventory, + diamond: 0 + } + } + + P2UPDATED = { ...P2UPDATED, turnActive: true } + + const moveOneExpectedState: AppState = { + ...state, + players: [P1UPDATED, P2UPDATED], + gameboard: { + ...state.gameboard, + tradingResources: { + ...state.gameboard.tradingResources, + diamond: 4 + } + } + } + + // first player action + // @ts-ignore + const newState = buyCard(state, setState, midGameCardTwo); + expect(newState).toStrictEqual(moveOneExpectedState); + + // playerTwo receives midGameCardOne and pays four rubies back to resource pool + P2UPDATED = { + ...P2UPDATED, + cards: [...P2UPDATED.cards, midGameCardOne], + turnActive: false, + inventory: { + ...P2UPDATED.inventory, + ruby: 0 + } + } + + P1UPDATED = { ...P1UPDATED, turnActive: true } + + const moveTwoExpectedState: AppState = { + ...moveOneExpectedState, + players: [P1UPDATED, P2UPDATED], + gameboard: { + ...moveOneExpectedState.gameboard, + tradingResources: { + ...moveOneExpectedState.gameboard.tradingResources, + ruby: 4 + } + } + } + + expect(() => { + if (!newState) throw Error(); + }).not.toThrowError(); + + if (newState) { + // @ts-ignore + const finalState = buyCard(newState, setState, midGameCardOne); + expect(finalState).toStrictEqual(moveTwoExpectedState); + } + }) +}) + +// describe('get chips', () => {}) +// describe('reserve card', () => {}) \ No newline at end of file diff --git a/src/components/Player/ActionMethods/buyCardActions.ts b/src/components/Player/ActionMethods/buyCardActions.ts index c917ba3..260cdb8 100644 --- a/src/components/Player/ActionMethods/buyCardActions.ts +++ b/src/components/Player/ActionMethods/buyCardActions.ts @@ -2,31 +2,32 @@ import { initialActions } from "../../../util/stateSetters"; import { turnOrderUtil } from "../../../util/turnOrderUtil"; import { AppState, CardData, ResourceCost, setStateType } from "../../../util/types"; import { useCurrentPlayer } from "../../../util/useCurrentPlayer"; +import getTotalBuyingPower from "../../../util/getTotalBuyingPower"; -export const getTotalBuyingPower = (state: AppState) => { - const currentPlayer = useCurrentPlayer(state); +// export const _getTotalBuyingPower = (state: AppState) => { +// const currentPlayer = useCurrentPlayer(state); - let totalBuyingPower = { - ruby: 0, - sapphire: 0, - emerald: 0, - diamond: 0, - onyx: 0, - gold: 0, - } +// let totalBuyingPower = { +// ruby: 0, +// sapphire: 0, +// emerald: 0, +// diamond: 0, +// onyx: 0, +// gold: 0, +// } - if (!currentPlayer) return totalBuyingPower; +// if (!currentPlayer) return totalBuyingPower; - for (let [key,quantity] of Object.entries(currentPlayer.inventory)) { - totalBuyingPower[key as keyof ResourceCost] += quantity; - } +// for (let [key,quantity] of Object.entries(currentPlayer.inventory)) { +// totalBuyingPower[key as keyof ResourceCost] += quantity; +// } - for (let each of currentPlayer.cards) { - totalBuyingPower[each.gemValue as keyof ResourceCost] += 1; - } +// for (let each of currentPlayer.cards) { +// totalBuyingPower[each.gemValue as keyof ResourceCost] += 1; +// } - return totalBuyingPower; -} +// return totalBuyingPower; +// } export const tooExpensive = (card: CardData, state: AppState): boolean => { const currentPlayer = useCurrentPlayer(state); @@ -44,6 +45,7 @@ export const tooExpensive = (card: CardData, state: AppState): boolean => { } export const updateResources = (state: AppState, card: CardData) => { + console.log('updateResources called'); let currentPlayer = useCurrentPlayer(state); let newTradingResources = state.gameboard.tradingResources; let updatedPlayer = currentPlayer; @@ -64,6 +66,8 @@ export const buyCard = (state: AppState, setState: setStateType, card: CardData) let currentPlayer = useCurrentPlayer(state); if (!currentPlayer) return; const { newPlayers, roundIncrement } = turnOrderUtil(state, currentPlayer); + + console.log('cleared to setstate'); setState((prev: AppState) => { if (!currentPlayer) return prev; @@ -84,4 +88,7 @@ export const buyCard = (state: AppState, setState: setStateType, card: CardData) actions: initialActions } }) + + // for testing? + return state; } \ No newline at end of file diff --git a/src/components/Player/ActionMethods/getChips.test.ts b/src/components/Player/ActionMethods/getChips.test.ts new file mode 100644 index 0000000..bbddf47 --- /dev/null +++ b/src/components/Player/ActionMethods/getChips.test.ts @@ -0,0 +1,82 @@ +import { describe, expect, it, test } from "vitest"; +import { initialActions } from "../../../util/stateSetters"; +import { mockPlayerOne, mockState } from "../../../util/testUtils"; +import { AppState, PlayerData } from "../../../util/types"; +import { hasMaxChips, validateChips } from "./getChipsActions"; + +const getChipsState: AppState = { + ...mockState, + actions: { + ...initialActions, + getChips: { + active: true, + selection: [] + } + } +} + +describe('get chips', () => { + test('hasMaxChips', () => { + const illegalPlayer: PlayerData = { + ...mockPlayerOne, + inventory: { + ruby: 2, + sapphire: 2, + emerald: 2, + diamond: 2, + onyx: 2, + gold: 2 + } + } + + expect(hasMaxChips(illegalPlayer)).toBeTruthy(); + }) + + describe('validateChips', () => { + test('is falsy when chips action is not active', () => { + expect(validateChips(mockState)).toBeFalsy(); + }) + + test('is falsy when more than three chips selected', () => { + const illegalState = getChipsState; + illegalState.actions.getChips.selection = ['ruby', 'diamond', 'onyx', 'sapphire'] + expect(validateChips(illegalState)).toBeFalsy(); + }) + + test('proper handling of duplicates', () => { + const illegalState = getChipsState; + illegalState.actions.getChips.selection = ['ruby', 'ruby', 'ruby'] + + const legalState = getChipsState; + legalState.actions.getChips.selection = ['ruby', 'ruby'] + + expect(validateChips(illegalState)).toBeFalsy(); + expect(validateChips(legalState)).toBeTruthy(); + }) + + test('no pickup of unavailable resources', () => { + const illegalState = { + ...mockState, + gameboard: { + ...getChipsState.gameboard, + tradingResources: { + ruby: 4, + sapphire: 4, + emerald: 1, + diamond: 4, + onyx: 2, + gold: 4 + } + }, + actions: { + ...initialActions, + + } + } + }) + }) + + describe('getChips', () => { + + }) +}) \ No newline at end of file diff --git a/src/components/Player/ActionMethods/reserveCard.test.ts b/src/components/Player/ActionMethods/reserveCard.test.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/util/getTotalBuyingPower.ts b/src/util/getTotalBuyingPower.ts new file mode 100644 index 0000000..eba6519 --- /dev/null +++ b/src/util/getTotalBuyingPower.ts @@ -0,0 +1,27 @@ +import { AppState, CardData, ResourceCost } from "./types"; +import { useCurrentPlayer } from "./useCurrentPlayer"; + +export default function getTotalBuyingPower(state: AppState) { + const currentPlayer = useCurrentPlayer(state); + + let totalBuyingPower = { + ruby: 0, + sapphire: 0, + emerald: 0, + diamond: 0, + onyx: 0, + gold: 0, + } + + if (!currentPlayer) return totalBuyingPower; + + for (let [key,quantity] of Object.entries(currentPlayer.inventory)) { + totalBuyingPower[key as keyof ResourceCost] += quantity; + } + + for (let each of currentPlayer.cards) { + totalBuyingPower[each.gemValue as keyof ResourceCost] += 1; + } + + return totalBuyingPower; +} \ No newline at end of file diff --git a/src/util/testUtils.ts b/src/util/testUtils.ts index 09e81a5..dcdee0c 100644 --- a/src/util/testUtils.ts +++ b/src/util/testUtils.ts @@ -44,6 +44,7 @@ export const mockState: AppState = { } // mock data for midgame +// playerOneMidGame buys high diamond cost card export const playerOneMidGame = { ...mockPlayerOne, cards: [ @@ -83,6 +84,7 @@ export const playerOneMidGame = { } } +// playerTwoMidGame buys high ruby cost card export const playerTwoMidGame = { ...mockPlayerTwo, cards: [ @@ -126,7 +128,7 @@ export const midGameState: AppState = { gameboard: { ...initialState.gameboard, tradingResources: { - ruby: 1, + ruby: 0, sapphire: 1, emerald: 1, onyx: 1, From c7138038fa75a1e8c7ffe0f326547ec14f786469 Mon Sep 17 00:00:00 2001 From: Mikayla Dobson Date: Fri, 5 Aug 2022 18:55:07 -0500 Subject: [PATCH 02/11] to do: rendering problem after buy card, remove purchased card from gameboard --- src/components/Card/Card.tsx | 1 + .../Player/ActionMethods/buyCard.test.ts | 141 ----------------- .../Player/ActionMethods/buyCard.test.tsx | 75 +++++++++ .../Player/ActionMethods/buyCardActions.ts | 144 ++++++++++-------- src/util/initializeBoard.ts | 1 - 5 files changed, 157 insertions(+), 205 deletions(-) delete mode 100644 src/components/Player/ActionMethods/buyCard.test.ts create mode 100644 src/components/Player/ActionMethods/buyCard.test.tsx diff --git a/src/components/Card/Card.tsx b/src/components/Card/Card.tsx index 9f40bf1..e6a37c4 100644 --- a/src/components/Card/Card.tsx +++ b/src/components/Card/Card.tsx @@ -1,3 +1,4 @@ +import { useEffect } from 'react'; import { v4 } from 'uuid'; import { CardProps } from '../../util/propTypes'; import { ResourceCost } from '../../util/types'; diff --git a/src/components/Player/ActionMethods/buyCard.test.ts b/src/components/Player/ActionMethods/buyCard.test.ts deleted file mode 100644 index 1ae9381..0000000 --- a/src/components/Player/ActionMethods/buyCard.test.ts +++ /dev/null @@ -1,141 +0,0 @@ -import { expensiveCard, midGameCardOne, midGameCardTwo, midGameState, mockPlayerOne, mockPlayerTwo, mockState, playerTwoMidGame } from '../../../util/testUtils'; -import { buyCard, tooExpensive } from './buyCardActions'; -import getTotalBuyingPower from '../../../util/getTotalBuyingPower'; -import { useCurrentPlayer } from '../../../util/useCurrentPlayer'; -import { AppState, PlayerData } from '../../../util/types'; -import { test, expect, describe } from 'vitest'; -import { renderHook } from "@testing-library/react"; -import { useState } from 'react'; -import { initialState } from '../../../util/stateSetters'; - -describe('buy cards', () => { - test('detects unaffordable cards', () => { - const result = tooExpensive(expensiveCard, mockState); - expect(result).toBeTruthy(); - }) - - test('calculates total buying power', () => { - let modifiedState = { - ...mockState, - players: [ - { - ...mockPlayerOne, - inventory: { - ruby: 3, - sapphire: 3, - emerald: 3, - onyx: 3, - diamond: 3, - gold: 3 - }, - cards: [expensiveCard] - }, - mockPlayerTwo - ] - } - - const totalBuyingPower = getTotalBuyingPower(modifiedState); - - const expectedValue = { - ruby: 3, - sapphire: 3, - emerald: 3, - onyx: 3, - diamond: 4, - gold: 3 - } - - expect(totalBuyingPower).toStrictEqual(expectedValue); - }) - - test('buyCard and updateResources', () => { - const { result } = renderHook(() => { - const [state, setState] = useState(midGameState); - - return { state, setState } - }) - - const { state, setState } = result.current; - - const currentPlayer = useCurrentPlayer(state); - if (!currentPlayer) return; - - /** - * actions in test: - * midGameState :: playerOneMidGame, playerTwoMidGame - * playerOneMidGame => buy midGameCardTwo - * playerTwoMidGame => buy midGameCardOne - */ - - let P1UPDATED = state.players.filter((p: PlayerData) => p.name === "Player One")[0]; - let P2UPDATED = state.players.filter((p: PlayerData) => p.name === "Player Two")[0]; - - // playerOne receives midGameCardTwo and pays three diamonds back to resource pool - P1UPDATED = { - ...P1UPDATED, - cards: [...P1UPDATED.cards, midGameCardTwo], - turnActive: false, - inventory: { - ...P1UPDATED.inventory, - diamond: 0 - } - } - - P2UPDATED = { ...P2UPDATED, turnActive: true } - - const moveOneExpectedState: AppState = { - ...state, - players: [P1UPDATED, P2UPDATED], - gameboard: { - ...state.gameboard, - tradingResources: { - ...state.gameboard.tradingResources, - diamond: 4 - } - } - } - - // first player action - // @ts-ignore - const newState = buyCard(state, setState, midGameCardTwo); - expect(newState).toStrictEqual(moveOneExpectedState); - - // playerTwo receives midGameCardOne and pays four rubies back to resource pool - P2UPDATED = { - ...P2UPDATED, - cards: [...P2UPDATED.cards, midGameCardOne], - turnActive: false, - inventory: { - ...P2UPDATED.inventory, - ruby: 0 - } - } - - P1UPDATED = { ...P1UPDATED, turnActive: true } - - const moveTwoExpectedState: AppState = { - ...moveOneExpectedState, - players: [P1UPDATED, P2UPDATED], - gameboard: { - ...moveOneExpectedState.gameboard, - tradingResources: { - ...moveOneExpectedState.gameboard.tradingResources, - ruby: 4 - } - } - } - - expect(() => { - if (!newState) throw Error(); - }).not.toThrowError(); - - if (newState) { - // @ts-ignore - const finalState = buyCard(newState, setState, midGameCardOne); - expect(finalState).toStrictEqual(moveTwoExpectedState); - } - }) -}) - -// describe('get chips', () => {}) -// describe('reserve card', () => {}) \ No newline at end of file diff --git a/src/components/Player/ActionMethods/buyCard.test.tsx b/src/components/Player/ActionMethods/buyCard.test.tsx new file mode 100644 index 0000000..ff8ef30 --- /dev/null +++ b/src/components/Player/ActionMethods/buyCard.test.tsx @@ -0,0 +1,75 @@ +import { expensiveCard, midGameCardOne, midGameCardTwo, midGameState, mockPlayerOne, mockPlayerTwo, mockState } from '../../../util/testUtils'; +import { buyCard, tooExpensive } from './buyCardActions'; +import getTotalBuyingPower from '../../../util/getTotalBuyingPower'; +import { useCurrentPlayer } from '../../../util/useCurrentPlayer'; +import { AppState, PlayerData } from '../../../util/types'; +import { test, expect, describe, vi, afterEach } from 'vitest'; +import { renderHook } from "@testing-library/react"; +import React, { useState } from 'react'; + +afterEach(() => { + vi.restoreAllMocks(); +}) + +describe('buy cards', () => { + test('detects unaffordable cards', () => { + const result = tooExpensive(expensiveCard, mockState); + expect(result).toBeTruthy(); + }) + + test('calculates total buying power', () => { + let modifiedState = { + ...mockState, + players: [ + { + ...mockPlayerOne, + inventory: { + ruby: 3, + sapphire: 3, + emerald: 3, + onyx: 3, + diamond: 3, + gold: 3 + }, + cards: [expensiveCard] + }, + mockPlayerTwo + ] + } + + const totalBuyingPower = getTotalBuyingPower(modifiedState); + + const expectedValue = { + ruby: 3, + sapphire: 3, + emerald: 3, + onyx: 3, + diamond: 4, + gold: 3 + } + + expect(totalBuyingPower).toStrictEqual(expectedValue); + }) + + test('use state', () => { + const { result } = renderHook(() => { + const [state, setState] = useState('me'); + setState('you'); + return state; + }) + + expect(result.current).toBe('you'); + }) + + test('buyCard and updateResources', () => { + /** + * actions in test: + * player triggers "buy card" action + * corresponding chips come out of player's hand + * + */ + }) +}) + +// describe('get chips', () => {}) +// describe('reserve card', () => {}) \ No newline at end of file diff --git a/src/components/Player/ActionMethods/buyCardActions.ts b/src/components/Player/ActionMethods/buyCardActions.ts index 260cdb8..42b4d77 100644 --- a/src/components/Player/ActionMethods/buyCardActions.ts +++ b/src/components/Player/ActionMethods/buyCardActions.ts @@ -4,31 +4,6 @@ import { AppState, CardData, ResourceCost, setStateType } from "../../../util/ty import { useCurrentPlayer } from "../../../util/useCurrentPlayer"; import getTotalBuyingPower from "../../../util/getTotalBuyingPower"; -// export const _getTotalBuyingPower = (state: AppState) => { -// const currentPlayer = useCurrentPlayer(state); - -// let totalBuyingPower = { -// ruby: 0, -// sapphire: 0, -// emerald: 0, -// diamond: 0, -// onyx: 0, -// gold: 0, -// } - -// if (!currentPlayer) return totalBuyingPower; - -// for (let [key,quantity] of Object.entries(currentPlayer.inventory)) { -// totalBuyingPower[key as keyof ResourceCost] += quantity; -// } - -// for (let each of currentPlayer.cards) { -// totalBuyingPower[each.gemValue as keyof ResourceCost] += 1; -// } - -// return totalBuyingPower; -// } - export const tooExpensive = (card: CardData, state: AppState): boolean => { const currentPlayer = useCurrentPlayer(state); if (!currentPlayer) return true; @@ -44,51 +19,94 @@ export const tooExpensive = (card: CardData, state: AppState): boolean => { return false; } -export const updateResources = (state: AppState, card: CardData) => { - console.log('updateResources called'); - let currentPlayer = useCurrentPlayer(state); - let newTradingResources = state.gameboard.tradingResources; - let updatedPlayer = currentPlayer; - const totalBuyingPower = getTotalBuyingPower(state); +export const buyCard = (state: AppState, setState: setStateType, card: CardData) => { + const currentPlayer = useCurrentPlayer(state); + if (!currentPlayer) return; - let difference = 0; - for (let [key, value] of Object.entries(card.resourceCost)) { - if (value < 1) continue; - if (value !== totalBuyingPower[key as keyof ResourceCost]) { - difference += Math.abs(totalBuyingPower[key as keyof ResourceCost] - value); + setState(() => { + const { newPlayers, roundIncrement } = turnOrderUtil(state, currentPlayer); + const idx = newPlayers.indexOf(currentPlayer); + const updatedPlayer = newPlayers[idx]; + + const cardCost = card.resourceCost; + const newPlayerInventory = updatedPlayer.inventory; + const newResourcePool = state.gameboard.tradingResources; + + for (let key of Object.keys(cardCost)) { + const typedKey = key as keyof ResourceCost; + + let adjustedCost = cardCost[typedKey]; + let adjustedInventoryValue = newPlayerInventory[typedKey]; + let adjustedResourcePoolValue = newResourcePool[typedKey]; + + if (!adjustedCost || !adjustedInventoryValue || !adjustedResourcePoolValue) continue; + + while (adjustedCost > 0) { + adjustedCost--; + adjustedInventoryValue--; + adjustedResourcePoolValue++; + } + + newPlayerInventory[typedKey] = adjustedInventoryValue; + newResourcePool[typedKey] = adjustedResourcePoolValue; } - } - return { newTradingResources, updatedPlayer } + updatedPlayer.inventory = newPlayerInventory; + newPlayers[idx] = updatedPlayer; + + state.gameboard.tradingResources = newResourcePool; + roundIncrement && (state.round = state.round + 1); + state.players = newPlayers; + + return state; + }) } -export const buyCard = (state: AppState, setState: setStateType, card: CardData) => { - let currentPlayer = useCurrentPlayer(state); - if (!currentPlayer) return; - const { newPlayers, roundIncrement } = turnOrderUtil(state, currentPlayer); +// export const updateResources = (state: AppState, card: CardData) => { +// console.log('updateResources called'); +// let currentPlayer = useCurrentPlayer(state); +// let newTradingResources = state.gameboard.tradingResources; +// let updatedPlayer = currentPlayer; +// const totalBuyingPower = getTotalBuyingPower(state); - console.log('cleared to setstate'); +// let difference = 0; +// for (let [key, value] of Object.entries(card.resourceCost)) { +// if (value < 1) continue; +// if (value !== totalBuyingPower[key as keyof ResourceCost]) { +// difference += Math.abs(totalBuyingPower[key as keyof ResourceCost] - value); +// } +// } + +// return { newTradingResources, updatedPlayer } +// } + +// export const buyCard = (state: AppState, setState: setStateType, card: CardData) => { +// let currentPlayer = useCurrentPlayer(state); +// if (!currentPlayer) return; +// const { newPlayers, roundIncrement } = turnOrderUtil(state, currentPlayer); + +// console.log('cleared to setstate'); - setState((prev: AppState) => { - if (!currentPlayer) return prev; +// setState((prev: AppState) => { +// if (!currentPlayer) return prev; - const { newTradingResources, updatedPlayer } = updateResources(state, card); - const idx = newPlayers.indexOf(currentPlayer); - updatedPlayer && (newPlayers[idx] = updatedPlayer); +// const { newTradingResources, updatedPlayer } = updateResources(state, card); +// const idx = newPlayers.indexOf(currentPlayer); +// updatedPlayer && (newPlayers[idx] = updatedPlayer); - return { - ...prev, - gameboard: { - ...prev.gameboard, - cardRows: prev.gameboard.cardRows, - tradingResources: newTradingResources - }, - round: (roundIncrement ? prev.round + 1 : prev.round), - players: newPlayers, - actions: initialActions - } - }) +// return { +// ...prev, +// gameboard: { +// ...prev.gameboard, +// cardRows: prev.gameboard.cardRows, +// tradingResources: newTradingResources +// }, +// round: (roundIncrement ? prev.round + 1 : prev.round), +// players: newPlayers, +// actions: initialActions +// } +// }) - // for testing? - return state; -} \ No newline at end of file +// // for testing? +// return state; +// } \ No newline at end of file diff --git a/src/util/initializeBoard.ts b/src/util/initializeBoard.ts index 21327f0..43323dd 100644 --- a/src/util/initializeBoard.ts +++ b/src/util/initializeBoard.ts @@ -33,7 +33,6 @@ const setNobles = (state: AppState, setState: setStateType) => { const setResources = (state: AppState) => { let newResources = state.gameboard.tradingResources; - console.log(state.players.length); switch (state.players.length) { case 2: for (let [key, value] of Object.entries(newResources)) { From e0ad965ac2b1883815a6b7200ab25f788838bce1 Mon Sep 17 00:00:00 2001 From: Mikayla Dobson Date: Fri, 5 Aug 2022 19:50:28 -0500 Subject: [PATCH 03/11] cleanup --- src/App.tsx | 6 +- src/components/Card/Card.tsx | 2 +- .../Player/ActionMethods/buyCardActions.ts | 71 ++++--------------- src/components/Player/Player.tsx | 57 ++++++++++----- 4 files changed, 58 insertions(+), 78 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index a3c6355..9e78fa7 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,6 +1,6 @@ import { BrowserRouter, Routes, Route } from 'react-router-dom' import { initialState } from './util/stateSetters'; -import { useState } from 'react' +import { useEffect, useState } from 'react' import Gameboard from './components/Gameboard/Gameboard' import GameConstructor from './components/GameConstructor'; @@ -9,6 +9,10 @@ import './App.css' function App() { const [state, setState] = useState(initialState); + useEffect(() => { + return; + }, [state]); + return (

SPLENDOR

diff --git a/src/components/Card/Card.tsx b/src/components/Card/Card.tsx index e6a37c4..a5f38d7 100644 --- a/src/components/Card/Card.tsx +++ b/src/components/Card/Card.tsx @@ -1,4 +1,4 @@ -import { useEffect } from 'react'; + import { v4 } from 'uuid'; import { CardProps } from '../../util/propTypes'; import { ResourceCost } from '../../util/types'; diff --git a/src/components/Player/ActionMethods/buyCardActions.ts b/src/components/Player/ActionMethods/buyCardActions.ts index 42b4d77..ac4a920 100644 --- a/src/components/Player/ActionMethods/buyCardActions.ts +++ b/src/components/Player/ActionMethods/buyCardActions.ts @@ -1,4 +1,3 @@ -import { initialActions } from "../../../util/stateSetters"; import { turnOrderUtil } from "../../../util/turnOrderUtil"; import { AppState, CardData, ResourceCost, setStateType } from "../../../util/types"; import { useCurrentPlayer } from "../../../util/useCurrentPlayer"; @@ -23,14 +22,14 @@ export const buyCard = (state: AppState, setState: setStateType, card: CardData) const currentPlayer = useCurrentPlayer(state); if (!currentPlayer) return; - setState(() => { - const { newPlayers, roundIncrement } = turnOrderUtil(state, currentPlayer); + setState((prev) => { + const { newPlayers, roundIncrement } = turnOrderUtil(prev, currentPlayer); const idx = newPlayers.indexOf(currentPlayer); const updatedPlayer = newPlayers[idx]; const cardCost = card.resourceCost; const newPlayerInventory = updatedPlayer.inventory; - const newResourcePool = state.gameboard.tradingResources; + const newResourcePool = prev.gameboard.tradingResources; for (let key of Object.keys(cardCost)) { const typedKey = key as keyof ResourceCost; @@ -52,61 +51,17 @@ export const buyCard = (state: AppState, setState: setStateType, card: CardData) } updatedPlayer.inventory = newPlayerInventory; + updatedPlayer.cards = [...updatedPlayer.cards, card]; newPlayers[idx] = updatedPlayer; - state.gameboard.tradingResources = newResourcePool; - roundIncrement && (state.round = state.round + 1); - state.players = newPlayers; - - return state; + return { + ...prev, + players: newPlayers, + round: (roundIncrement ? prev.round + 1 : prev.round), + gameboard: { + ...prev.gameboard, + tradingResources: newResourcePool + } + } }) } - -// export const updateResources = (state: AppState, card: CardData) => { -// console.log('updateResources called'); -// let currentPlayer = useCurrentPlayer(state); -// let newTradingResources = state.gameboard.tradingResources; -// let updatedPlayer = currentPlayer; -// const totalBuyingPower = getTotalBuyingPower(state); - -// let difference = 0; -// for (let [key, value] of Object.entries(card.resourceCost)) { -// if (value < 1) continue; -// if (value !== totalBuyingPower[key as keyof ResourceCost]) { -// difference += Math.abs(totalBuyingPower[key as keyof ResourceCost] - value); -// } -// } - -// return { newTradingResources, updatedPlayer } -// } - -// export const buyCard = (state: AppState, setState: setStateType, card: CardData) => { -// let currentPlayer = useCurrentPlayer(state); -// if (!currentPlayer) return; -// const { newPlayers, roundIncrement } = turnOrderUtil(state, currentPlayer); - -// console.log('cleared to setstate'); - -// setState((prev: AppState) => { -// if (!currentPlayer) return prev; - -// const { newTradingResources, updatedPlayer } = updateResources(state, card); -// const idx = newPlayers.indexOf(currentPlayer); -// updatedPlayer && (newPlayers[idx] = updatedPlayer); - -// return { -// ...prev, -// gameboard: { -// ...prev.gameboard, -// cardRows: prev.gameboard.cardRows, -// tradingResources: newTradingResources -// }, -// round: (roundIncrement ? prev.round + 1 : prev.round), -// players: newPlayers, -// actions: initialActions -// } -// }) - -// // for testing? -// return state; -// } \ No newline at end of file diff --git a/src/components/Player/Player.tsx b/src/components/Player/Player.tsx index bb38d3a..84ec0d6 100644 --- a/src/components/Player/Player.tsx +++ b/src/components/Player/Player.tsx @@ -10,10 +10,47 @@ export default function Player({ player, state, setState }: PlayerProps) { const [dynamic, setDynamic] = useState(); const [prompt, setPrompt] = useState("Your turn! Select an action type below."); + const [cardView, setCardView] = useState(

Cards:

); + const [reservedView, setReservedView] = useState(

Reserved cards:

); + useEffect(() => { setDynamic(state.players.find((element: PlayerData) => element.id === player.id)) }, [state]); + useEffect(() => { + dynamic && setCardView( + <> +

Cards:

+ { + dynamic.cards.map((data: CardData) => { + return ( +
+

{data.gemValue} card

+

{data.points + " points" || null}

+
+ ) + }) + } + + ) + + dynamic && setReservedView( + <> +

Reserved cards:

+ { + dynamic.reservedCards?.map((data: CardData) => { + return ( +
+

{data.gemValue} cards

+

{data.points + " points" || null}

+
+ ) + }) + } + + ) + }, [dynamic, setState]) + const handleClick = (actionSelection: number) => { switch (actionSelection) { case 0: @@ -63,27 +100,11 @@ export default function Player({ player, state, setState }: PlayerProps) {
-

Cards:

- { dynamic && dynamic.cards.length > 0 && dynamic.cards.map((data: CardData) => { - return ( -
-

{data.gemValue} card

-

{data.points + " points" || null}

-
- )}) - } + {dynamic && cardView}
-

Reserved cards:

- { dynamic?.reservedCards && dynamic.reservedCards?.map((data: CardData) => { - return ( -
-

{data.gemValue} cards

-

{data.points + " points" || null}

-
- ) - })} + {dynamic && reservedView}
From 9c067172bf1e1c2681a6411cbc33a46bdb8f17ac Mon Sep 17 00:00:00 2001 From: Mikayla Dobson Date: Fri, 5 Aug 2022 20:28:07 -0500 Subject: [PATCH 04/11] accounts for permanent resources --- .../Player/ActionMethods/buyCard.test.tsx | 24 +++---------------- .../Player/ActionMethods/buyCardActions.ts | 11 +++++++-- src/components/Player/Player.tsx | 5 ++++ src/util/getTotalBuyingPower.ts | 7 ++---- 4 files changed, 19 insertions(+), 28 deletions(-) diff --git a/src/components/Player/ActionMethods/buyCard.test.tsx b/src/components/Player/ActionMethods/buyCard.test.tsx index ff8ef30..d838987 100644 --- a/src/components/Player/ActionMethods/buyCard.test.tsx +++ b/src/components/Player/ActionMethods/buyCard.test.tsx @@ -2,10 +2,11 @@ import { expensiveCard, midGameCardOne, midGameCardTwo, midGameState, mockPlayer import { buyCard, tooExpensive } from './buyCardActions'; import getTotalBuyingPower from '../../../util/getTotalBuyingPower'; import { useCurrentPlayer } from '../../../util/useCurrentPlayer'; -import { AppState, PlayerData } from '../../../util/types'; +import { AppState, CardData, PlayerData, ResourceCost } from '../../../util/types'; import { test, expect, describe, vi, afterEach } from 'vitest'; import { renderHook } from "@testing-library/react"; import React, { useState } from 'react'; +import { turnOrderUtil } from '../../../util/turnOrderUtil'; afterEach(() => { vi.restoreAllMocks(); @@ -37,7 +38,7 @@ describe('buy cards', () => { ] } - const totalBuyingPower = getTotalBuyingPower(modifiedState); + const totalBuyingPower = getTotalBuyingPower(mockPlayerOne); const expectedValue = { ruby: 3, @@ -50,25 +51,6 @@ describe('buy cards', () => { expect(totalBuyingPower).toStrictEqual(expectedValue); }) - - test('use state', () => { - const { result } = renderHook(() => { - const [state, setState] = useState('me'); - setState('you'); - return state; - }) - - expect(result.current).toBe('you'); - }) - - test('buyCard and updateResources', () => { - /** - * actions in test: - * player triggers "buy card" action - * corresponding chips come out of player's hand - * - */ - }) }) // describe('get chips', () => {}) diff --git a/src/components/Player/ActionMethods/buyCardActions.ts b/src/components/Player/ActionMethods/buyCardActions.ts index ac4a920..11f47b5 100644 --- a/src/components/Player/ActionMethods/buyCardActions.ts +++ b/src/components/Player/ActionMethods/buyCardActions.ts @@ -2,12 +2,13 @@ import { turnOrderUtil } from "../../../util/turnOrderUtil"; import { AppState, CardData, ResourceCost, setStateType } from "../../../util/types"; import { useCurrentPlayer } from "../../../util/useCurrentPlayer"; import getTotalBuyingPower from "../../../util/getTotalBuyingPower"; +import { initialActions } from "../../../util/stateSetters"; export const tooExpensive = (card: CardData, state: AppState): boolean => { const currentPlayer = useCurrentPlayer(state); if (!currentPlayer) return true; for (let [gemType, cost] of Object.entries(card.resourceCost)) { - let totalBuyingPower = getTotalBuyingPower(state); + let totalBuyingPower = getTotalBuyingPower(currentPlayer); for (let [heldResource, quantity] of Object.entries(totalBuyingPower)) { if (gemType === heldResource && quantity < cost) { return true; @@ -28,6 +29,7 @@ export const buyCard = (state: AppState, setState: setStateType, card: CardData) const updatedPlayer = newPlayers[idx]; const cardCost = card.resourceCost; + const playerBuyingPower = getTotalBuyingPower(currentPlayer); const newPlayerInventory = updatedPlayer.inventory; const newResourcePool = prev.gameboard.tradingResources; @@ -40,10 +42,14 @@ export const buyCard = (state: AppState, setState: setStateType, card: CardData) if (!adjustedCost || !adjustedInventoryValue || !adjustedResourcePoolValue) continue; + const buyingPowerDifference = playerBuyingPower[typedKey] - adjustedInventoryValue; + adjustedCost -= buyingPowerDifference; + while (adjustedCost > 0) { - adjustedCost--; adjustedInventoryValue--; adjustedResourcePoolValue++; + + adjustedCost--; } newPlayerInventory[typedKey] = adjustedInventoryValue; @@ -58,6 +64,7 @@ export const buyCard = (state: AppState, setState: setStateType, card: CardData) ...prev, players: newPlayers, round: (roundIncrement ? prev.round + 1 : prev.round), + actions: initialActions, gameboard: { ...prev.gameboard, tradingResources: newResourcePool diff --git a/src/components/Player/Player.tsx b/src/components/Player/Player.tsx index 84ec0d6..ccd0629 100644 --- a/src/components/Player/Player.tsx +++ b/src/components/Player/Player.tsx @@ -27,6 +27,11 @@ export default function Player({ player, state, setState }: PlayerProps) {

{data.gemValue} card

{data.points + " points" || null}

+ { + Object.entries(data.resourceCost).map(([key, value]) => { + return value > 0 &&

{key}: {value}

+ }) + }
) }) diff --git a/src/util/getTotalBuyingPower.ts b/src/util/getTotalBuyingPower.ts index eba6519..34575e4 100644 --- a/src/util/getTotalBuyingPower.ts +++ b/src/util/getTotalBuyingPower.ts @@ -1,9 +1,6 @@ -import { AppState, CardData, ResourceCost } from "./types"; -import { useCurrentPlayer } from "./useCurrentPlayer"; +import { PlayerData, ResourceCost } from "./types"; -export default function getTotalBuyingPower(state: AppState) { - const currentPlayer = useCurrentPlayer(state); - +export default function getTotalBuyingPower(currentPlayer: PlayerData) { let totalBuyingPower = { ruby: 0, sapphire: 0, From 381027eb82fc9323bc072adfd53a42c0595f87b7 Mon Sep 17 00:00:00 2001 From: Mikayla Dobson Date: Sat, 6 Aug 2022 11:52:18 -0500 Subject: [PATCH 05/11] purchased cards removed from available face up cards, replaced with new card from deck --- src/components/Gameboard/Gameboard.tsx | 2 -- .../Player/ActionMethods/buyCardActions.ts | 34 ++++++++++++++++--- .../ActionMethods/reserveCardActions.ts | 4 +-- src/components/Player/Player.tsx | 14 +++----- src/components/Resources/SelectionView.tsx | 20 +++++++++-- src/components/Resources/ViewHTML.tsx | 3 ++ 6 files changed, 54 insertions(+), 23 deletions(-) diff --git a/src/components/Gameboard/Gameboard.tsx b/src/components/Gameboard/Gameboard.tsx index f8f5d78..5834987 100644 --- a/src/components/Gameboard/Gameboard.tsx +++ b/src/components/Gameboard/Gameboard.tsx @@ -19,7 +19,6 @@ export default function Gameboard({ state, setState }: StateProps) { // callbacks for lifting state const liftSelection = useCallback((value: keyof ResourceCost) => { if (!state.actions.getChips.active) return; - setState((prev: AppState) => { let newSelection = prev.actions.getChips.selection; newSelection?.push(value); @@ -38,7 +37,6 @@ export default function Gameboard({ state, setState }: StateProps) { const result = validateChips(newState); newState.actions.getChips.valid = result; - return newState; }) }, [state]); diff --git a/src/components/Player/ActionMethods/buyCardActions.ts b/src/components/Player/ActionMethods/buyCardActions.ts index 11f47b5..5c4060a 100644 --- a/src/components/Player/ActionMethods/buyCardActions.ts +++ b/src/components/Player/ActionMethods/buyCardActions.ts @@ -1,5 +1,5 @@ import { turnOrderUtil } from "../../../util/turnOrderUtil"; -import { AppState, CardData, ResourceCost, setStateType } from "../../../util/types"; +import { AppState, CardData, FullDeck, ResourceCost, setStateType } from "../../../util/types"; import { useCurrentPlayer } from "../../../util/useCurrentPlayer"; import getTotalBuyingPower from "../../../util/getTotalBuyingPower"; import { initialActions } from "../../../util/stateSetters"; @@ -24,10 +24,12 @@ export const buyCard = (state: AppState, setState: setStateType, card: CardData) if (!currentPlayer) return; setState((prev) => { + // shift turn order and identify current player in new player state const { newPlayers, roundIncrement } = turnOrderUtil(prev, currentPlayer); const idx = newPlayers.indexOf(currentPlayer); const updatedPlayer = newPlayers[idx]; + // pointers for each value to be modified const cardCost = card.resourceCost; const playerBuyingPower = getTotalBuyingPower(currentPlayer); const newPlayerInventory = updatedPlayer.inventory; @@ -35,31 +37,45 @@ export const buyCard = (state: AppState, setState: setStateType, card: CardData) for (let key of Object.keys(cardCost)) { const typedKey = key as keyof ResourceCost; - let adjustedCost = cardCost[typedKey]; let adjustedInventoryValue = newPlayerInventory[typedKey]; let adjustedResourcePoolValue = newResourcePool[typedKey]; - if (!adjustedCost || !adjustedInventoryValue || !adjustedResourcePoolValue) continue; + // before decrementing player inventory values, account for total buying power const buyingPowerDifference = playerBuyingPower[typedKey] - adjustedInventoryValue; adjustedCost -= buyingPowerDifference; while (adjustedCost > 0) { adjustedInventoryValue--; adjustedResourcePoolValue++; - adjustedCost--; } + // assign modified values to player inventory and resource pool newPlayerInventory[typedKey] = adjustedInventoryValue; newResourcePool[typedKey] = adjustedResourcePoolValue; } + // connect modified player state to updated list of all players updatedPlayer.inventory = newPlayerInventory; updatedPlayer.cards = [...updatedPlayer.cards, card]; newPlayers[idx] = updatedPlayer; + // attempt to queue replacement card from full deck + const typedCardTier = ["tierThree", "tierTwo", "tierOne"][card.tier + 1] as keyof FullDeck; + let newFullDeckTargetTier = prev.gameboard.deck[typedCardTier]; + const replacementCard = newFullDeckTargetTier.shift(); + + // isolate the affected row of face up cards, remove the purchased card + let newTargetCardRow = prev.gameboard.cardRows[typedCardTier]; + newTargetCardRow = newTargetCardRow.filter((data: CardData) => data.resourceCost !== card.resourceCost); + // push replacement card to face up card, if exists + if (replacementCard) newTargetCardRow.push(replacementCard); + + console.log(newTargetCardRow); + console.log(newFullDeckTargetTier); + return { ...prev, players: newPlayers, @@ -67,7 +83,15 @@ export const buyCard = (state: AppState, setState: setStateType, card: CardData) actions: initialActions, gameboard: { ...prev.gameboard, - tradingResources: newResourcePool + tradingResources: newResourcePool, + cardRows: { + ...prev.gameboard.cardRows, + [typedCardTier]: newTargetCardRow + }, + deck: { + ...prev.gameboard.deck, + [typedCardTier]: newFullDeckTargetTier + } } } }) diff --git a/src/components/Player/ActionMethods/reserveCardActions.ts b/src/components/Player/ActionMethods/reserveCardActions.ts index 91f2224..b440d7e 100644 --- a/src/components/Player/ActionMethods/reserveCardActions.ts +++ b/src/components/Player/ActionMethods/reserveCardActions.ts @@ -31,9 +31,7 @@ export const reserveCard = (state: AppState, setState: setStateType, card: CardD const updatedPlayer = { ...currentPlayer, - reservedCards: currentPlayer.reservedCards ? [ - ...currentPlayer.reservedCards, card - ] : [card], + reservedCards: currentPlayer.reservedCards ? [...currentPlayer.reservedCards, card] : [card], inventory: goldAllowable(currentPlayer) ? { ...currentPlayer.inventory, gold: currentPlayer.inventory.gold && currentPlayer.inventory.gold + 1 diff --git a/src/components/Player/Player.tsx b/src/components/Player/Player.tsx index ccd0629..346f053 100644 --- a/src/components/Player/Player.tsx +++ b/src/components/Player/Player.tsx @@ -1,15 +1,13 @@ +import { setStateAwaitAction, setStateBuyCard, setStateGetChips, setStateReserveCard } from "../../util/stateSetters"; +import { useEffect, useState } from "react"; import { PlayerProps } from "../../util/propTypes"; import { CardData, PlayerData } from "../../util/types" -import { useEffect, useState } from "react"; -import { v4 } from "uuid"; import { hasMaxReserved } from "./ActionMethods/reserveCardActions"; import { hasMaxChips } from "./ActionMethods/getChipsActions"; -import { setStateAwaitAction, setStateBuyCard, setStateGetChips, setStateReserveCard } from "../../util/stateSetters"; +import { v4 } from "uuid"; export default function Player({ player, state, setState }: PlayerProps) { const [dynamic, setDynamic] = useState(); - const [prompt, setPrompt] = useState("Your turn! Select an action type below."); - const [cardView, setCardView] = useState(

Cards:

); const [reservedView, setReservedView] = useState(

Reserved cards:

); @@ -60,19 +58,15 @@ export default function Player({ player, state, setState }: PlayerProps) { switch (actionSelection) { case 0: setState((prev) => setStateGetChips(prev)); - setPrompt('Make your selection of up to three chips.'); break; case 1: setState((prev) => setStateBuyCard(prev)); - setPrompt('Choose a card above to purchase.'); break; case 2: setState((prev) => setStateReserveCard(prev)); - setPrompt('Choose a card above to reserve.'); break; default: setState((prev) => setStateAwaitAction(prev)); - setPrompt("Your turn! Select an action type below."); break; } } @@ -88,7 +82,7 @@ export default function Player({ player, state, setState }: PlayerProps) { {/* Dynamic data from state */}

Score: {dynamic?.points}

-

{dynamic?.turnActive ? prompt : '...'}

+

{dynamic?.turnActive ? "It's your turn!" : "..."}

diff --git a/src/components/Resources/SelectionView.tsx b/src/components/Resources/SelectionView.tsx index fba1d91..62e2f06 100644 --- a/src/components/Resources/SelectionView.tsx +++ b/src/components/Resources/SelectionView.tsx @@ -1,8 +1,11 @@ import { useEffect, useState } from "react"; import { StateProps } from "../../util/propTypes"; +import { useCurrentPlayer } from "../../util/useCurrentPlayer"; import { GetChipsHTML, ReserveCardHTML } from "./ViewHTML"; export default function SelectionView({ state, setState }: StateProps) { + const [currentPlayer, setCurrentPlayer] = useState(useCurrentPlayer(state)); + const actionTypes = [ state.actions.getChips, state.actions.buyCard, @@ -16,14 +19,25 @@ export default function SelectionView({ state, setState }: StateProps) { case (actionTypes[0].active): return case (actionTypes[1].active): - return Please make your selection above:; + return ( +
+

{currentPlayer?.name} has elected to purchase a card!

+ Choose a card above to purchase. +
+ ) case (actionTypes[2].active): return ; default: - return <>; + return ( +
+

{currentPlayer ? `It is currently ${currentPlayer.name}'s turn!` : "Loading..."}

+
+ ); } }) - }, [state]) + + setCurrentPlayer(useCurrentPlayer(state)); + }, [state, state.actions, setState]) return view } \ No newline at end of file diff --git a/src/components/Resources/ViewHTML.tsx b/src/components/Resources/ViewHTML.tsx index 3d52bc5..bc29628 100644 --- a/src/components/Resources/ViewHTML.tsx +++ b/src/components/Resources/ViewHTML.tsx @@ -10,6 +10,7 @@ const { getChips } = getChipsActions; export const GetChipsHTML = ({ state, setState }: StateProps) => { const [prompt, setPrompt] = useState(""); + const currentPlayer = useCurrentPlayer(state); useEffect(() => { if (!state.actions.getChips.active) setPrompt(""); @@ -22,6 +23,7 @@ export const GetChipsHTML = ({ state, setState }: StateProps) => { return (
+

{currentPlayer?.name} has elected to collect resources!

{prompt}
{ @@ -49,6 +51,7 @@ export const ReserveCardHTML = ({ state, setState }: StateProps) => { return (
+

{currentPlayer?.name} has elected to reserve a card!

Please make your selection above. { !hasMaxChips(currentPlayer) && (
From 8fba7605b4f8c31f2a5043de847ccba325e600d0 Mon Sep 17 00:00:00 2001 From: Mikayla Dobson Date: Sat, 6 Aug 2022 12:13:12 -0500 Subject: [PATCH 06/11] added counter for cards remaining --- src/components/Card/Card.tsx | 2 +- src/components/Card/CardRow.tsx | 6 ++++++ src/components/Card/MiniCard.tsx | 10 ---------- src/components/Gameboard/Gameboard.tsx | 4 ++++ src/components/Player/Player.tsx | 5 +++++ 5 files changed, 16 insertions(+), 11 deletions(-) delete mode 100644 src/components/Card/MiniCard.tsx diff --git a/src/components/Card/Card.tsx b/src/components/Card/Card.tsx index a5f38d7..a689856 100644 --- a/src/components/Card/Card.tsx +++ b/src/components/Card/Card.tsx @@ -11,7 +11,7 @@ export default function Card({ data, state, setState }: CardProps) { const currentPlayer = useCurrentPlayer(state); return ( -
+

Counts as: {data.gemValue}

Point value: {data.points || 0}

diff --git a/src/components/Card/CardRow.tsx b/src/components/Card/CardRow.tsx index 2b89a07..f998d0e 100644 --- a/src/components/Card/CardRow.tsx +++ b/src/components/Card/CardRow.tsx @@ -2,8 +2,11 @@ import { CardRowProps } from '../../util/propTypes'; import { CardData } from "../../util/types" import Card from "../Card/Card" import { v4 } from 'uuid'; +import cardTierToKey from '../../util/cardTierToKey'; export default function CardRow({tier, state, setState}: CardRowProps) { + const typedTier = cardTierToKey(tier); + let cards: Array switch (tier) { case 1: @@ -24,6 +27,9 @@ export default function CardRow({tier, state, setState}: CardRowProps) {

Tier: {tier}

+
+

Remaining: {state.gameboard.deck[typedTier].length}

+
{ cards && cards.map((cardData: CardData) => { return })} diff --git a/src/components/Card/MiniCard.tsx b/src/components/Card/MiniCard.tsx deleted file mode 100644 index 4c272aa..0000000 --- a/src/components/Card/MiniCard.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import { CardData } from "../../util/types" - -export const MiniCard = ({ data }: {data: CardData}) => { - return ( -
-

{data.gemValue} card

-

{data.points || null}

-
- ) -} \ No newline at end of file diff --git a/src/components/Gameboard/Gameboard.tsx b/src/components/Gameboard/Gameboard.tsx index 5834987..d30334a 100644 --- a/src/components/Gameboard/Gameboard.tsx +++ b/src/components/Gameboard/Gameboard.tsx @@ -50,6 +50,10 @@ export default function Gameboard({ state, setState }: StateProps) { setCardRows(state); }, [state]) + useEffect(() => { + console.log(state) + }, [state]) + // displays state of board if data is populated useEffect(() => { if (!state.players.length) { diff --git a/src/components/Player/Player.tsx b/src/components/Player/Player.tsx index 346f053..fbd40ed 100644 --- a/src/components/Player/Player.tsx +++ b/src/components/Player/Player.tsx @@ -46,6 +46,11 @@ export default function Player({ player, state, setState }: PlayerProps) {

{data.gemValue} cards

{data.points + " points" || null}

+ { + Object.entries(data.resourceCost).map(([key, value]) => { + return value > 0 &&

{key}: {value}

+ }) + }
) }) From bde0b1f66db9c91fe8f28b24cfd4fbabb5a351cb Mon Sep 17 00:00:00 2001 From: Mikayla Dobson Date: Sat, 6 Aug 2022 14:33:48 -0500 Subject: [PATCH 07/11] computer mouse problems --- src/App.tsx | 5 +- src/components/GameConstructor.tsx | 8 +- src/components/Gameboard/Gameboard.tsx | 11 +- src/components/Gameboard/Nobles.test.ts | 144 ++++++++++++++++++ src/components/Gameboard/Nobles.tsx | 38 ++++- .../Player/ActionMethods/oldBuyCard.ts | 87 ----------- src/components/ResumeGame.tsx | 12 ++ 7 files changed, 205 insertions(+), 100 deletions(-) create mode 100644 src/components/Gameboard/Nobles.test.ts delete mode 100644 src/components/Player/ActionMethods/oldBuyCard.ts create mode 100644 src/components/ResumeGame.tsx diff --git a/src/App.tsx b/src/App.tsx index 9e78fa7..d72e35d 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -5,6 +5,7 @@ import { useEffect, useState } from 'react' import Gameboard from './components/Gameboard/Gameboard' import GameConstructor from './components/GameConstructor'; import './App.css' +import ResumeGame from './components/ResumeGame'; function App() { const [state, setState] = useState(initialState); @@ -18,9 +19,11 @@ function App() {

SPLENDOR

- {/* @ts-ignore */}a + {/* @ts-ignore */} } /> {/* @ts-ignore */} + } /> + {/* @ts-ignore */} } /> diff --git a/src/components/GameConstructor.tsx b/src/components/GameConstructor.tsx index 8fe7f7f..1b020b4 100644 --- a/src/components/GameConstructor.tsx +++ b/src/components/GameConstructor.tsx @@ -1,5 +1,5 @@ import { useEffect, useState } from "react" -import { useNavigate } from "react-router-dom" +import { Link, useNavigate } from "react-router-dom" import { CardData, NobleData, PlayerData } from "../util/types"; import { StateProps } from '../util/propTypes'; @@ -103,7 +103,9 @@ export default function GameConstructor({ state, setState }: StateProps) { return (
-

Configure a new game:

+ Start a new game +

OR

+ Enter your previous game data here to pick up where you left off.
@@ -162,7 +164,7 @@ export default function GameConstructor({ state, setState }: StateProps) { handleRadio(4)} checked={starter === 3 && input.playerFour.name.length > 0}> diff --git a/src/components/Gameboard/Gameboard.tsx b/src/components/Gameboard/Gameboard.tsx index d30334a..8038a1b 100644 --- a/src/components/Gameboard/Gameboard.tsx +++ b/src/components/Gameboard/Gameboard.tsx @@ -3,7 +3,7 @@ import { AppState, ResourceCost } from '../../util/types'; import { useCallback, useEffect, useState } from 'react'; import { getChipsActions } from '../Player/ActionMethods'; import { StateProps } from '../../util/propTypes'; -const { validateChips } = getChipsActions; +import { Link } from 'react-router-dom'; // components import Nobles from './Nobles'; @@ -12,6 +12,7 @@ import AvailableChips from '../Resources/AvailableChips'; import AllPlayers from '../Player/AllPlayers'; import CardRow from '../Card/CardRow'; import SelectionView from '../Resources/SelectionView'; +const { validateChips } = getChipsActions; export default function Gameboard({ state, setState }: StateProps) { const [view, setView] = useState(

Loading...

); @@ -50,17 +51,13 @@ export default function Gameboard({ state, setState }: StateProps) { setCardRows(state); }, [state]) - useEffect(() => { - console.log(state) - }, [state]) - - // displays state of board if data is populated + // displays state of board if data is populated, otherwise points to game constructor useEffect(() => { if (!state.players.length) { setView(
Sorry! It appears we've lost track of your game data. -

Please head back to the home page to start a fresh game.

+

Please head back to the home page to start a fresh game.

); } else { diff --git a/src/components/Gameboard/Nobles.test.ts b/src/components/Gameboard/Nobles.test.ts new file mode 100644 index 0000000..fa5a676 --- /dev/null +++ b/src/components/Gameboard/Nobles.test.ts @@ -0,0 +1,144 @@ +import { describe, test } from "vitest"; +import { CardData, NobleData, PlayerData } from "../../util/types"; + +export const firstNoble: NobleData = { + points: 3, + resourceCost: { + ruby: 0, + sapphire: 0, + emerald: 0, + onyx: 0, + diamond: 3 + } + } + + export const secondNoble: NobleData = { + points: 3, + resourceCost: { + ruby: 3, + sapphire: 0, + emerald: 0, + onyx: 0, + diamond: 0 + } + } + + const exampleOneCards: CardData[] = [ + { + gemValue: "diamond", + tier: 3, + points: 0, + resourceCost: { + ruby: 0, + sapphire: 0, + emerald: 0, + onyx: 0, + diamond: 3 + } + }, + { + gemValue: "diamond", + tier: 3, + points: 1, + resourceCost: { + ruby: 0, + sapphire: 0, + emerald: 0, + onyx: 0, + diamond: 4 + } + }, + { + gemValue: "diamond", + tier: 2, + points: 2, + resourceCost: { + ruby: 0, + sapphire: 0, + emerald: 0, + onyx: 0, + diamond: 5 + } + } +] + +export const legalPlayer: PlayerData = { + name: "First example", + id: 1, + starter: true, + turnActive: true, + points: 5, + nobles: [], + cards: exampleOneCards, + reservedCards: [], + inventory: { + ruby: 0, + sapphire: 0, + emerald: 0, + onyx: 0, + diamond: 0 + } +} + +const exampleTwoCards: CardData[] = [ + { + gemValue: "ruby", + tier: 2, + points: 2, + resourceCost: { + ruby: 5, + sapphire: 0, + emerald: 0, + onyx: 0, + diamond: 0 + } + }, + { + gemValue: "ruby", + tier: 3, + points: 1, + resourceCost: { + ruby: 4, + sapphire: 0, + emerald: 0, + onyx: 0, + diamond: 0 + } + }, + { + gemValue: "ruby", + tier: 3, + points: 0, + resourceCost: { + ruby: 3, + sapphire: 0, + emerald: 0, + onyx: 0, + diamond: 0 + } + }, +] + +export const doesNotIncludeInventory: PlayerData = { + name: "second example", + id: 2, + starter: true, + turnActive: true, + points: 5, + nobles: [], + cards: exampleTwoCards, + reservedCards: [], + inventory: { + ruby: 3, + sapphire: 3, + emerald: 3, + onyx: 3, + diamond: 3, + } +} + +describe('canPickUpNoble', () => { + test('detects noble eligibility by card count', () => { + + }) +}) \ No newline at end of file diff --git a/src/components/Gameboard/Nobles.tsx b/src/components/Gameboard/Nobles.tsx index 02945df..589a855 100644 --- a/src/components/Gameboard/Nobles.tsx +++ b/src/components/Gameboard/Nobles.tsx @@ -1,8 +1,10 @@ -import { useEffect } from "react"; import { v4 } from "uuid"; -import { NobleData, ResourceCost } from "../../util/types"; +import { NobleData, PlayerData, ResourceCost } from "../../util/types"; import { StateProps } from "../../util/propTypes"; import "./Nobles.css" +import getTotalBuyingPower from "../../util/getTotalBuyingPower"; +import { useCurrentPlayer } from "../../util/useCurrentPlayer"; +import { useEffect } from "react"; export default function Nobles({ state, setState }: StateProps) { const removeNoble = (noble: NobleData) => { @@ -18,6 +20,38 @@ export default function Nobles({ state, setState }: StateProps) { }) } + const canPickUpNoble = (player: PlayerData, noble: NobleData): boolean => { + const nobleCost = noble.resourceCost; + + const totalBuyingPower = getTotalBuyingPower(player); + const playerInventory = player.inventory; + + + for (let key of Object.keys(totalBuyingPower)) { + const typedKey = key as keyof ResourceCost; + let coinValue = playerInventory[typedKey] || 0; + + if (!noble.resourceCost[typedKey]) continue; + // @ts-ignore + if ((totalBuyingPower[typedKey] - coinValue) >= noble.resourceCost[typedKey]) { + continue; + } else { + return false; + } + } + + return true; + } + + useEffect(() => { + const currentPlayer = useCurrentPlayer(state); + if (!currentPlayer) return; + + for (let each of state.gameboard.nobles) { + console.log(`${currentPlayer.name} can pick up noble ${state.gameboard.nobles.indexOf(each) + 1}? ${canPickUpNoble(currentPlayer, each) ? "yes" : "no"}`) + } + }, [state]) + return (
NOBLES diff --git a/src/components/Player/ActionMethods/oldBuyCard.ts b/src/components/Player/ActionMethods/oldBuyCard.ts deleted file mode 100644 index f9383ad..0000000 --- a/src/components/Player/ActionMethods/oldBuyCard.ts +++ /dev/null @@ -1,87 +0,0 @@ -import cardTierToKey from "../../../util/cardTierToKey"; -import { initialActions } from "../../../util/stateSetters"; -import { turnOrderUtil } from "../../../util/turnOrderUtil"; -import { AppState, CardData, PlayerData, ResourceCost, setStateType } from "../../../util/types"; -import { useCurrentPlayer } from "../../../util/useCurrentPlayer"; -import { getTotalBuyingPower } from "./buyCardActions"; - -export const buyCard = (card: CardData, state: AppState, setState: setStateType) => { - /** - * functionality: adds target card's data to current player's collection of cards, - * removes the card from the active state of the gameboard, replaces it with - * a new card in the correct tier, and runs turn order utility - * - * @param card -> the target card, @param state -> current app state - */ - - let currentPlayer = useCurrentPlayer(state); - - setState((prev: AppState) => { - if (!currentPlayer) return prev; - - const { newPlayers, roundIncrement } = turnOrderUtil(prev, currentPlayer); - let newPlayerInventory = currentPlayer.inventory; - let newResourcePool = prev.gameboard.tradingResources; - const totalBuyingPower = getTotalBuyingPower(state); - - // iterate through cost values of card to purchase - for (let [gem, cost] of Object.entries(card.resourceCost)) { - if (cost < 1) continue; - let inventoryValue = newPlayerInventory[gem as keyof ResourceCost]; - let globalResource = newResourcePool[gem as keyof ResourceCost]; - - if (!inventoryValue || !globalResource) { - continue; - } else { - let i = cost; - - // prevents duplication of resources when purchasing a card using permanent resources from cards - if (totalBuyingPower[gem as keyof ResourceCost] !== inventoryValue) { - console.log('caught'); - } - - while (i > 0) { - inventoryValue -= 1; - globalResource += 1; - i--; - } - - newResourcePool[gem as keyof ResourceCost] = globalResource; - newPlayerInventory[gem as keyof ResourceCost] = inventoryValue; - } - } - - let updatedPlayer: PlayerData = { - ...currentPlayer, - cards: [...currentPlayer.cards, card], - inventory: newPlayerInventory - } - - let newScore = 0; - for (let each of updatedPlayer.cards) { - newScore += each.points || 0; - } - - updatedPlayer.points = newScore; - const idx = newPlayers.findIndex((one: PlayerData) => one.id === currentPlayer?.id); - newPlayers[idx] = updatedPlayer; - let updatedRows = prev.gameboard.cardRows; - - if (card.tier) { - const tierKey = cardTierToKey(card.tier); - updatedRows[tierKey] = prev.gameboard.cardRows[tierKey].filter((found: CardData) => found.resourceCost !== card.resourceCost); - } - - return { - ...prev, - round: (roundIncrement ? prev.round + 1 : prev.round), - players: newPlayers, - gameboard: { - ...prev.gameboard, - tradingResources: prev.gameboard.tradingResources, - cardRows: updatedRows - }, - actions: initialActions - } - }) -} \ No newline at end of file diff --git a/src/components/ResumeGame.tsx b/src/components/ResumeGame.tsx new file mode 100644 index 0000000..b9ce53e --- /dev/null +++ b/src/components/ResumeGame.tsx @@ -0,0 +1,12 @@ +import { Link } from "react-router-dom"; +import { StateProps } from "../util/propTypes"; + +export default function ResumeGame({ state, setState }: StateProps) { + return ( +
+

Congrats! You've found an in-progress feature.

+

Check back in here later to enter a save game token to pick up a game from where you left off.

+

In the meantime, click here to head back home.

+
+ ) +} \ No newline at end of file From 55308bd7329067a617841a18223bf2d7084543f8 Mon Sep 17 00:00:00 2001 From: Mikayla Dobson Date: Sat, 6 Aug 2022 17:46:18 -0500 Subject: [PATCH 08/11] found bug: incorrect conditional, global resource was skipped if value was 0, falsy --- src/components/Gameboard/Nobles.tsx | 14 +++++++------- .../Player/ActionMethods/buyCardActions.ts | 18 +++++++++--------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/components/Gameboard/Nobles.tsx b/src/components/Gameboard/Nobles.tsx index 589a855..49d1341 100644 --- a/src/components/Gameboard/Nobles.tsx +++ b/src/components/Gameboard/Nobles.tsx @@ -43,14 +43,14 @@ export default function Nobles({ state, setState }: StateProps) { return true; } - useEffect(() => { - const currentPlayer = useCurrentPlayer(state); - if (!currentPlayer) return; + // useEffect(() => { + // const currentPlayer = useCurrentPlayer(state); + // if (!currentPlayer) return; - for (let each of state.gameboard.nobles) { - console.log(`${currentPlayer.name} can pick up noble ${state.gameboard.nobles.indexOf(each) + 1}? ${canPickUpNoble(currentPlayer, each) ? "yes" : "no"}`) - } - }, [state]) + // for (let each of state.gameboard.nobles) { + // console.log(`${currentPlayer.name} can pick up noble ${state.gameboard.nobles.indexOf(each) + 1}? ${canPickUpNoble(currentPlayer, each) ? "yes" : "no"}`) + // } + // }, [state]) return (
diff --git a/src/components/Player/ActionMethods/buyCardActions.ts b/src/components/Player/ActionMethods/buyCardActions.ts index 5c4060a..59546e1 100644 --- a/src/components/Player/ActionMethods/buyCardActions.ts +++ b/src/components/Player/ActionMethods/buyCardActions.ts @@ -7,10 +7,10 @@ import { initialActions } from "../../../util/stateSetters"; export const tooExpensive = (card: CardData, state: AppState): boolean => { const currentPlayer = useCurrentPlayer(state); if (!currentPlayer) return true; - for (let [gemType, cost] of Object.entries(card.resourceCost)) { + for (let [cardGemType, cardCost] of Object.entries(card.resourceCost)) { let totalBuyingPower = getTotalBuyingPower(currentPlayer); for (let [heldResource, quantity] of Object.entries(totalBuyingPower)) { - if (gemType === heldResource && quantity < cost) { + if (cardGemType === heldResource && quantity < cardCost) { return true; } } @@ -39,8 +39,8 @@ export const buyCard = (state: AppState, setState: setStateType, card: CardData) const typedKey = key as keyof ResourceCost; let adjustedCost = cardCost[typedKey]; let adjustedInventoryValue = newPlayerInventory[typedKey]; - let adjustedResourcePoolValue = newResourcePool[typedKey]; - if (!adjustedCost || !adjustedInventoryValue || !adjustedResourcePoolValue) continue; + let adjustedResourcePoolValue = newResourcePool[typedKey] || 0; + if (!adjustedCost || !adjustedInventoryValue) continue; // before decrementing player inventory values, account for total buying power const buyingPowerDifference = playerBuyingPower[typedKey] - adjustedInventoryValue; @@ -48,8 +48,8 @@ export const buyCard = (state: AppState, setState: setStateType, card: CardData) while (adjustedCost > 0) { adjustedInventoryValue--; - adjustedResourcePoolValue++; adjustedCost--; + adjustedResourcePoolValue++; } // assign modified values to player inventory and resource pool @@ -57,13 +57,16 @@ export const buyCard = (state: AppState, setState: setStateType, card: CardData) newResourcePool[typedKey] = adjustedResourcePoolValue; } + console.log(newResourcePool); + console.log(newPlayerInventory); + // connect modified player state to updated list of all players updatedPlayer.inventory = newPlayerInventory; updatedPlayer.cards = [...updatedPlayer.cards, card]; newPlayers[idx] = updatedPlayer; // attempt to queue replacement card from full deck - const typedCardTier = ["tierThree", "tierTwo", "tierOne"][card.tier + 1] as keyof FullDeck; + const typedCardTier = ["tierThree", "tierTwo", "tierOne"][2 - (card.tier-1)] as keyof FullDeck; let newFullDeckTargetTier = prev.gameboard.deck[typedCardTier]; const replacementCard = newFullDeckTargetTier.shift(); @@ -73,9 +76,6 @@ export const buyCard = (state: AppState, setState: setStateType, card: CardData) // push replacement card to face up card, if exists if (replacementCard) newTargetCardRow.push(replacementCard); - console.log(newTargetCardRow); - console.log(newFullDeckTargetTier); - return { ...prev, players: newPlayers, From b0c81078aed3697524e69e715a555779abb2889f Mon Sep 17 00:00:00 2001 From: Mikayla Dobson Date: Sat, 6 Aug 2022 18:05:12 -0500 Subject: [PATCH 09/11] score adds to player data when purchasing a card: --- src/components/Card/Card.tsx | 3 ++- src/components/Player/ActionMethods/buyCardActions.ts | 4 +--- src/components/Player/Player.tsx | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/components/Card/Card.tsx b/src/components/Card/Card.tsx index a689856..856a697 100644 --- a/src/components/Card/Card.tsx +++ b/src/components/Card/Card.tsx @@ -1,4 +1,3 @@ - import { v4 } from 'uuid'; import { CardProps } from '../../util/propTypes'; import { ResourceCost } from '../../util/types'; @@ -10,6 +9,8 @@ const { buyCard, tooExpensive } = buyCardActions; export default function Card({ data, state, setState }: CardProps) { const currentPlayer = useCurrentPlayer(state); + if (!data) return
; + return (
diff --git a/src/components/Player/ActionMethods/buyCardActions.ts b/src/components/Player/ActionMethods/buyCardActions.ts index 59546e1..14a55b2 100644 --- a/src/components/Player/ActionMethods/buyCardActions.ts +++ b/src/components/Player/ActionMethods/buyCardActions.ts @@ -57,12 +57,10 @@ export const buyCard = (state: AppState, setState: setStateType, card: CardData) newResourcePool[typedKey] = adjustedResourcePoolValue; } - console.log(newResourcePool); - console.log(newPlayerInventory); - // connect modified player state to updated list of all players updatedPlayer.inventory = newPlayerInventory; updatedPlayer.cards = [...updatedPlayer.cards, card]; + updatedPlayer.points = updatedPlayer.points + (card.points || 0); newPlayers[idx] = updatedPlayer; // attempt to queue replacement card from full deck diff --git a/src/components/Player/Player.tsx b/src/components/Player/Player.tsx index fbd40ed..503c131 100644 --- a/src/components/Player/Player.tsx +++ b/src/components/Player/Player.tsx @@ -86,7 +86,7 @@ export default function Player({ player, state, setState }: PlayerProps) { {/* Dynamic data from state */}
-

Score: {dynamic?.points}

+

Score: {dynamic && dynamic.points}

{dynamic?.turnActive ? "It's your turn!" : "..."}

From adc653abbf8dff20ea66d6349472f15d8e1a452a Mon Sep 17 00:00:00 2001 From: Mikayla Dobson Date: Sat, 6 Aug 2022 19:14:40 -0500 Subject: [PATCH 10/11] defined logic for picking up noble, adding to score --- src/util/canPickUpNoble.ts | 22 ++++++++++++++++++++++ src/util/stateSetters.ts | 27 +++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 src/util/canPickUpNoble.ts diff --git a/src/util/canPickUpNoble.ts b/src/util/canPickUpNoble.ts new file mode 100644 index 0000000..2021bc4 --- /dev/null +++ b/src/util/canPickUpNoble.ts @@ -0,0 +1,22 @@ +import getTotalBuyingPower from "./getTotalBuyingPower"; +import { NobleData, PlayerData, ResourceCost } from "./types"; + +export const canPickUpNoble = (player: PlayerData, noble: NobleData) => { + const totalBuyingPower = getTotalBuyingPower(player); + const playerInventory = player.inventory; + + for (let key of Object.keys(totalBuyingPower)) { + const typedKey = key as keyof ResourceCost; + let coinValue = playerInventory[typedKey] || 0; + + if (!noble.resourceCost[typedKey]) continue; + // @ts-ignore + if ((totalBuyingPower[typedKey] - coinValue) >= noble.resourceCost[typedKey]) { + continue; + } else { + return; + } + } + + return noble; +} \ No newline at end of file diff --git a/src/util/stateSetters.ts b/src/util/stateSetters.ts index c3f8852..bdd05ca 100644 --- a/src/util/stateSetters.ts +++ b/src/util/stateSetters.ts @@ -1,5 +1,6 @@ import { AppState, CardData, NobleData, PlayerData, ResourceCost } from "./types"; import CardDeck from '../data/cards.json'; +import { useCurrentPlayer } from "./useCurrentPlayer"; export const initialActions = { buyCard: { active: false }, @@ -76,4 +77,30 @@ export const setStateReserveCard = (prev: AppState) => { } } } +} + +export const setStateGetNoble = (prev: AppState, noble: NobleData) => { + const currentPlayer = useCurrentPlayer(prev); + if (!currentPlayer) return prev; + + const updatedPlayer = { + ...currentPlayer, + nobles: [...currentPlayer.nobles, noble], + points: currentPlayer.points + 3 + } + + const idx = prev.players.indexOf(currentPlayer); + const newPlayers = prev.players; + newPlayers[idx] = updatedPlayer; + + const newNobles = prev.gameboard.nobles.filter((x: NobleData) => x.resourceCost !== noble.resourceCost); + + return { + ...prev, + players: newPlayers, + gameboard: { + ...prev.gameboard, + nobles: newNobles + } + } } \ No newline at end of file From afc3e9ac478a6680d3bbed61864b70bc416e72dd Mon Sep 17 00:00:00 2001 From: Mikayla Dobson Date: Sat, 6 Aug 2022 19:22:35 -0500 Subject: [PATCH 11/11] to do: bug, points assigned to wrong player in setStateGetNoble. trigger endgame action --- src/App.tsx | 4 -- src/components/Gameboard/Gameboard.tsx | 6 ++ src/components/Gameboard/Nobles.tsx | 57 +++---------------- .../Player/ActionMethods/buyCardActions.ts | 12 +++- 4 files changed, 25 insertions(+), 54 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index d72e35d..0ab9afe 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -10,10 +10,6 @@ import ResumeGame from './components/ResumeGame'; function App() { const [state, setState] = useState(initialState); - useEffect(() => { - return; - }, [state]); - return (

SPLENDOR

diff --git a/src/components/Gameboard/Gameboard.tsx b/src/components/Gameboard/Gameboard.tsx index 8038a1b..d58fb3f 100644 --- a/src/components/Gameboard/Gameboard.tsx +++ b/src/components/Gameboard/Gameboard.tsx @@ -49,6 +49,12 @@ export default function Gameboard({ state, setState }: StateProps) { useEffect(() => { setCardRows(state); + + for (let player of state.players) { + if (player.points >= 15) { + console.log('trigger endgame'); + } + } }, [state]) // displays state of board if data is populated, otherwise points to game constructor diff --git a/src/components/Gameboard/Nobles.tsx b/src/components/Gameboard/Nobles.tsx index 49d1341..488b743 100644 --- a/src/components/Gameboard/Nobles.tsx +++ b/src/components/Gameboard/Nobles.tsx @@ -1,57 +1,18 @@ import { v4 } from "uuid"; -import { NobleData, PlayerData, ResourceCost } from "../../util/types"; +import { NobleData, ResourceCost } from "../../util/types"; import { StateProps } from "../../util/propTypes"; import "./Nobles.css" -import getTotalBuyingPower from "../../util/getTotalBuyingPower"; -import { useCurrentPlayer } from "../../util/useCurrentPlayer"; -import { useEffect } from "react"; -export default function Nobles({ state, setState }: StateProps) { - const removeNoble = (noble: NobleData) => { - console.log(noble); - setState((prev) => { - return { - ...prev, - gameboard: { - ...prev.gameboard, - nobles: prev.gameboard.nobles.filter((each) => each.nobleid !== noble.nobleid) - } - } - }) +export default function Nobles({ state }: StateProps) { + if (!state.gameboard.nobles.length) { + return ( +
+ NOBLES +

All nobles have been acquired!

+
+ ) } - const canPickUpNoble = (player: PlayerData, noble: NobleData): boolean => { - const nobleCost = noble.resourceCost; - - const totalBuyingPower = getTotalBuyingPower(player); - const playerInventory = player.inventory; - - - for (let key of Object.keys(totalBuyingPower)) { - const typedKey = key as keyof ResourceCost; - let coinValue = playerInventory[typedKey] || 0; - - if (!noble.resourceCost[typedKey]) continue; - // @ts-ignore - if ((totalBuyingPower[typedKey] - coinValue) >= noble.resourceCost[typedKey]) { - continue; - } else { - return false; - } - } - - return true; - } - - // useEffect(() => { - // const currentPlayer = useCurrentPlayer(state); - // if (!currentPlayer) return; - - // for (let each of state.gameboard.nobles) { - // console.log(`${currentPlayer.name} can pick up noble ${state.gameboard.nobles.indexOf(each) + 1}? ${canPickUpNoble(currentPlayer, each) ? "yes" : "no"}`) - // } - // }, [state]) - return (
NOBLES diff --git a/src/components/Player/ActionMethods/buyCardActions.ts b/src/components/Player/ActionMethods/buyCardActions.ts index 14a55b2..4b4c480 100644 --- a/src/components/Player/ActionMethods/buyCardActions.ts +++ b/src/components/Player/ActionMethods/buyCardActions.ts @@ -2,7 +2,8 @@ import { turnOrderUtil } from "../../../util/turnOrderUtil"; import { AppState, CardData, FullDeck, ResourceCost, setStateType } from "../../../util/types"; import { useCurrentPlayer } from "../../../util/useCurrentPlayer"; import getTotalBuyingPower from "../../../util/getTotalBuyingPower"; -import { initialActions } from "../../../util/stateSetters"; +import { initialActions, setStateGetNoble } from "../../../util/stateSetters"; +import { canPickUpNoble } from "../../../util/canPickUpNoble"; export const tooExpensive = (card: CardData, state: AppState): boolean => { const currentPlayer = useCurrentPlayer(state); @@ -92,5 +93,12 @@ export const buyCard = (state: AppState, setState: setStateType, card: CardData) } } } - }) + }); + + for (let each of state.gameboard.nobles) { + if (canPickUpNoble(currentPlayer, each)) { + console.log(`${currentPlayer.name} can pick up noble ${state.gameboard.nobles.indexOf(each)}`); + setState((prev) => setStateGetNoble(prev, each)); + } + } }