From 76e6785790962692dfe1209ffe0123c0a698bde9 Mon Sep 17 00:00:00 2001 From: bidof Date: Tue, 2 Apr 2024 16:54:15 +0200 Subject: [PATCH 01/79] test del roomquestion --- package-lock.json | 5944 ++++++++++++++++++++++++++---- package.json | 1 + roomservice/RoomQuestions.js | 10 + roomservice/package-lock.json | 135 + roomservice/package.json | 1 + roomservice/room-service.test.js | 94 + 6 files changed, 5557 insertions(+), 628 deletions(-) create mode 100644 roomservice/room-service.test.js diff --git a/package-lock.json b/package-lock.json index 35267d1f..8d6ea5b3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,14 +12,27 @@ "socket.io-client": "^4.7.5" }, "devDependencies": { + "jest": "^29.7.0", "react-router-dom": "^6.22.3" } }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/@babel/code-frame": { "version": "7.24.2", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.2.tgz", "integrity": "sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==", - "peer": true, "dependencies": { "@babel/highlight": "^7.24.2", "picocolors": "^1.0.0" @@ -28,11 +41,120 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/compat-data": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.1.tgz", + "integrity": "sha512-Pc65opHDliVpRHuKfzI+gSA4zcgr65O4cl64fFJIWEEh8JoHIHh0Oez1Eo8Arz8zq/JhgKodQaxEwUPRtZylVA==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.24.3", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.3.tgz", + "integrity": "sha512-5FcvN1JHw2sHJChotgx8Ek0lyuh4kCKelgMTTqhYJJtloNvUfpAFMeNQUtdlIaktwrSV9LtCdqwk48wL2wBacQ==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.24.2", + "@babel/generator": "^7.24.1", + "@babel/helper-compilation-targets": "^7.23.6", + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helpers": "^7.24.1", + "@babel/parser": "^7.24.1", + "@babel/template": "^7.24.0", + "@babel/traverse": "^7.24.1", + "@babel/types": "^7.24.0", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "node_modules/@babel/generator": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.1.tgz", + "integrity": "sha512-DfCRfZsBcrPEHUfuBMgbJ1Ut01Y/itOs+hY2nFLgqsqXd52/iSiVq5TITtUasIUgm+IIKdY2/1I7auiQOEeC9A==", + "dev": true, + "dependencies": { + "@babel/types": "^7.24.0", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz", + "integrity": "sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.23.5", + "@babel/helper-validator-option": "^7.23.5", + "browserslist": "^4.22.2", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-environment-visitor": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", + "dev": true, + "dependencies": { + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-hoist-variables": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/helper-module-imports": { "version": "7.24.3", "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.3.tgz", "integrity": "sha512-viKb0F9f2s0BCS22QSF308z/+1YWKV/76mwt61NBzS5izMzDPwdq1pTrzf+Li3npBWX9KdQbkeCt1jSAM7lZqg==", - "peer": true, "dependencies": { "@babel/types": "^7.24.0" }, @@ -40,11 +162,62 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz", + "integrity": "sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-module-imports": "^7.22.15", + "@babel/helper-simple-access": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/helper-validator-identifier": "^7.22.20" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.0.tgz", + "integrity": "sha512-9cUznXMG0+FxRuJfvL82QlTqIzhVW9sL0KjMPHhAOOvpQGL8QtdxnBKILjBqxlHyliz0yCa1G903ZXI/FuHy2w==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", + "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/helper-string-parser": { "version": "7.24.1", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.1.tgz", "integrity": "sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ==", - "peer": true, "engines": { "node": ">=6.9.0" } @@ -53,7 +226,29 @@ "version": "7.22.20", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", - "peer": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz", + "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.1.tgz", + "integrity": "sha512-BpU09QqEe6ZCHuIHFphEFgvNSrubve1FtyMton26ekZ85gRGi6LrTF7zArARp2YvyFxloeiRmtSCq5sjh1WqIg==", + "dev": true, + "dependencies": { + "@babel/template": "^7.24.0", + "@babel/traverse": "^7.24.1", + "@babel/types": "^7.24.0" + }, "engines": { "node": ">=6.9.0" } @@ -62,7 +257,6 @@ "version": "7.24.2", "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.2.tgz", "integrity": "sha512-Yac1ao4flkTxTteCDZLEvdxg2fZfz1v8M4QpaGypq/WPDqg3ijHYbDfs+LG5hvzSoqaSZ9/Z9lKSP3CjZjv+pA==", - "peer": true, "dependencies": { "@babel/helper-validator-identifier": "^7.22.20", "chalk": "^2.4.2", @@ -73,6 +267,195 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/parser": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.1.tgz", + "integrity": "sha512-Zo9c7N3xdOIQrNip7Lc9wvRPzlRtovHVE4lkz8WEDr7uYh/GMQhSiIgFxGIArRHYdJE5kxtZjAf8rT0xhdLCzg==", + "dev": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.1.tgz", + "integrity": "sha512-2eCtxZXf+kbkMIsXS4poTvT4Yu5rXiRa+9xGVT56raghjmBTKMpFNc9R4IDiB4emao9eO22Ox7CxuJG7BgExqA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.24.1.tgz", + "integrity": "sha512-Yhnmvy5HZEnHUty6i++gcfH1/l68AHnItFHnaCv6hn9dNh0hQvvQJsxpi4BMBFN5DLeHBuucT/0DgzXif/OyRw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/runtime": { "version": "7.24.0", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.0.tgz", @@ -84,11 +467,45 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/template": { + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.0.tgz", + "integrity": "sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.23.5", + "@babel/parser": "^7.24.0", + "@babel/types": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.1.tgz", + "integrity": "sha512-xuU6o9m68KeqZbQuDt2TcKSxUw/mrsvavlEqQ1leZ/B+C9tk6E4sRWy97WaXgvq5E+nU3cXMxv3WKOCanVMCmQ==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.24.1", + "@babel/generator": "^7.24.1", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.24.1", + "@babel/types": "^7.24.0", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/types": { "version": "7.24.0", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.0.tgz", "integrity": "sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==", - "peer": true, "dependencies": { "@babel/helper-string-parser": "^7.23.4", "@babel/helper-validator-identifier": "^7.22.20", @@ -98,6 +515,12 @@ "node": ">=6.9.0" } }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true + }, "node_modules/@chakra-ui/anatomy": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/@chakra-ui/anatomy/-/anatomy-2.2.2.tgz", @@ -401,36 +824,752 @@ "integrity": "sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww==", "peer": true }, - "node_modules/@popperjs/core": { - "version": "2.11.8", - "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", - "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/popperjs" - } - }, - "node_modules/@react-aria/ssr": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.9.2.tgz", - "integrity": "sha512-0gKkgDYdnq1w+ey8KzG9l+H5Z821qh9vVjztk55rUg71vTk/Eaebeir+WtzcLLwTjw3m/asIjx8Y59y1lJZhBw==", + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, "dependencies": { - "@swc/helpers": "^0.5.0" + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" }, "engines": { - "node": ">= 12" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + "node": ">=8" } }, - "node_modules/@remix-run/router": { - "version": "1.15.3", - "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.15.3.tgz", - "integrity": "sha512-Oy8rmScVrVxWZVOpEF57ovlnhpZ8CCPlnIIumVcV9nFdiSIrus99+Lw78ekXyGvVDlIsFJbSfmSovJUhCWYV3w==", + "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", "dev": true, "engines": { - "node": ">=14.0.0" + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", + "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/console/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/@jest/console/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/@jest/console/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/@jest/console/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/@jest/console/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/@jest/console/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/@jest/core": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", + "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", + "dev": true, + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/reporters": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^29.7.0", + "jest-config": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-resolve-dependencies": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "jest-watcher": "^29.7.0", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/core/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/@jest/core/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/@jest/core/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/@jest/core/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/@jest/core/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/@jest/core/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/@jest/environment": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", + "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", + "dev": true, + "dependencies": { + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", + "dev": true, + "dependencies": { + "expect": "^29.7.0", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", + "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", + "dev": true, + "dependencies": { + "jest-get-type": "^29.6.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", + "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/globals": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", + "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/types": "^29.6.3", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/reporters": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", + "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", + "dev": true, + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^6.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/reporters/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/@jest/reporters/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/@jest/reporters/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/@jest/reporters/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/@jest/reporters/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/@jest/reporters/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/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/source-map": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", + "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.18", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-result": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", + "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", + "dev": true, + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-sequencer": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", + "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", + "dev": true, + "dependencies": { + "@jest/test-result": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/transform": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/transform/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/@jest/transform/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/@jest/transform/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/@jest/transform/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/@jest/transform/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "node_modules/@jest/transform/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/@jest/transform/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/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/types/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/@jest/types/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/@jest/types/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/@jest/types/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/@jest/types/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/@jest/types/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/@jridgewell/gen-mapping": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/@react-aria/ssr": { + "version": "3.9.2", + "resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.9.2.tgz", + "integrity": "sha512-0gKkgDYdnq1w+ey8KzG9l+H5Z821qh9vVjztk55rUg71vTk/Eaebeir+WtzcLLwTjw3m/asIjx8Y59y1lJZhBw==", + "dependencies": { + "@swc/helpers": "^0.5.0" + }, + "engines": { + "node": ">= 12" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@remix-run/router": { + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.15.3.tgz", + "integrity": "sha512-Oy8rmScVrVxWZVOpEF57ovlnhpZ8CCPlnIIumVcV9nFdiSIrus99+Lw78ekXyGvVDlIsFJbSfmSovJUhCWYV3w==", + "dev": true, + "engines": { + "node": ">=14.0.0" } }, "node_modules/@restart/hooks": { @@ -438,856 +1577,3545 @@ "resolved": "https://registry.npmjs.org/@restart/hooks/-/hooks-0.4.16.tgz", "integrity": "sha512-f7aCv7c+nU/3mF7NWLtVVr0Ra80RqsO89hO72r+Y/nvQr5+q0UFGkocElTH6MJApvReVh6JHUFYn2cw1WdHF3w==", "dependencies": { - "dequal": "^2.0.3" + "dequal": "^2.0.3" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@restart/ui": { + "version": "1.6.8", + "resolved": "https://registry.npmjs.org/@restart/ui/-/ui-1.6.8.tgz", + "integrity": "sha512-6ndCv3oZ7r9vuP1Ok9KH55TM1/UkdBnP/fSraW0DFDMbPMzWKhVKeFAIEUCRCSdzayjZDcFYK6xbMlipN9dmMA==", + "dependencies": { + "@babel/runtime": "^7.21.0", + "@popperjs/core": "^2.11.6", + "@react-aria/ssr": "^3.5.0", + "@restart/hooks": "^0.4.9", + "@types/warning": "^3.0.0", + "dequal": "^2.0.3", + "dom-helpers": "^5.2.0", + "uncontrollable": "^8.0.1", + "warning": "^4.0.3" + }, + "peerDependencies": { + "react": ">=16.14.0", + "react-dom": ">=16.14.0" + } + }, + "node_modules/@restart/ui/node_modules/uncontrollable": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-8.0.4.tgz", + "integrity": "sha512-ulRWYWHvscPFc0QQXvyJjY6LIXU56f0h8pQFvhxiKk5V1fcI8gp9Ht9leVAhrVjzqMw0BgjspBINx9r6oyJUvQ==", + "peerDependencies": { + "react": ">=16.14.0" + } + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", + "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==" + }, + "node_modules/@swc/helpers": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.7.tgz", + "integrity": "sha512-BVvNZhx362+l2tSwSuyEUV4h7+jk9raNdoTSdLfwTshXJSaGmYKluGRJznziCI3KX02Z19DdsQrdfrpXAU3Hfg==", + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.6.8", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz", + "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.5.tgz", + "integrity": "sha512-WXCyOcRtH37HAUkpXhUduaxdm82b4GSlyTqajXviN4EfiuPgNYR109xMCKvpl6zPIpua0DGlMEDCq+g8EdoheQ==", + "dev": true, + "dependencies": { + "@babel/types": "^7.20.7" + } + }, + "node_modules/@types/graceful-fs": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", + "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/lodash": { + "version": "4.17.0", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.0.tgz", + "integrity": "sha512-t7dhREVv6dbNj0q17X12j7yDG4bD/DHYX7o5/DbDxobP0HnGPgpRz2Ej77aL7TZT3DSw13fqUTj8J4mMnqa7WA==", + "peer": true + }, + "node_modules/@types/lodash.mergewith": { + "version": "4.6.7", + "resolved": "https://registry.npmjs.org/@types/lodash.mergewith/-/lodash.mergewith-4.6.7.tgz", + "integrity": "sha512-3m+lkO5CLRRYU0fhGRp7zbsGi6+BZj0uTVSwvcKU+nSlhjA9/QRNfuSGnD2mX6hQA7ZbmcCkzk5h4ZYGOtk14A==", + "peer": true, + "dependencies": { + "@types/lodash": "*" + } + }, + "node_modules/@types/node": { + "version": "20.12.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.2.tgz", + "integrity": "sha512-zQ0NYO87hyN6Xrclcqp7f8ZbXNbRfoGWNcMvHTPQp9UUrwI0mI7XBz+cu7/W6/VClYo2g63B0cjull/srU7LgQ==", + "dev": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/parse-json": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", + "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==", + "peer": true + }, + "node_modules/@types/prop-types": { + "version": "15.7.11", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.11.tgz", + "integrity": "sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==" + }, + "node_modules/@types/react": { + "version": "18.2.67", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.67.tgz", + "integrity": "sha512-vkIE2vTIMHQ/xL0rgmuoECBCkZFZeHr49HeWSc24AptMbNRo7pwSBvj73rlJJs9fGKj0koS+V7kQB1jHS0uCgw==", + "dependencies": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-transition-group": { + "version": "4.4.10", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.10.tgz", + "integrity": "sha512-hT/+s0VQs2ojCX823m60m5f0sL5idt9SO6Tj6Dg+rdphGPIeJbJ6CxvBYkgkGKrYeDjvIpKTR38UzmtHJOGW3Q==", + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/scheduler": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.8.tgz", + "integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==" + }, + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "dev": true + }, + "node_modules/@types/warning": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/warning/-/warning-3.0.3.tgz", + "integrity": "sha512-D1XC7WK8K+zZEveUPY+cf4+kgauk8N4eHr/XIHXGlGYkHLud6hK9lYfZk1ry1TNh798cZUCgb6MqGEG8DkJt6Q==" + }, + "node_modules/@types/yargs": { + "version": "17.0.32", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", + "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", + "dev": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/axios": { + "version": "1.6.8", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.8.tgz", + "integrity": "sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/babel-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", + "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", + "dev": true, + "dependencies": { + "@jest/transform": "^29.7.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.6.3", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" + } + }, + "node_modules/babel-jest/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/babel-jest/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/babel-jest/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/babel-jest/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/babel-jest/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/babel-jest/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/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-istanbul/node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", + "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", + "dev": true, + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/babel-plugin-macros": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", + "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.12.5", + "cosmiconfig": "^7.0.0", + "resolve": "^1.19.0" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + } + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", + "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", + "dev": true, + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.8.3", + "@babel/plugin-syntax-import-meta": "^7.8.3", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.8.3", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-top-level-await": "^7.8.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/babel-preset-jest": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", + "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", + "dev": true, + "dependencies": { + "babel-plugin-jest-hoist": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/body-parser": { + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", + "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.23.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz", + "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001587", + "electron-to-chromium": "^1.4.668", + "node-releases": "^2.0.14", + "update-browserslist-db": "^1.0.13" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001605", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001605.tgz", + "integrity": "sha512-nXwGlFWo34uliI9z3n6Qc0wZaf7zaZWA1CPZ169La5mV3I/gem7bst0vr5XQH5TJXZIMfDeZyOrZnSlVzKxxHQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chalk/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "engines": { + "node": ">=8" + } + }, + "node_modules/cjs-module-lexer": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz", + "integrity": "sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==", + "dev": true + }, + "node_modules/classnames": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==" + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true, + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", + "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", + "dev": true + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + }, + "node_modules/color2k": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/color2k/-/color2k-2.0.3.tgz", + "integrity": "sha512-zW190nQTIoXcGCaU08DvVNFTmQhUpnJfVuAKfWqUQkflXKpaDdpaYoM0iluLS9lgJNHyBF58KKA2FBEwkD7wog==", + "peer": 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==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "peer": true + }, + "node_modules/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + }, + "node_modules/cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "peer": true, + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/create-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", + "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "prompts": "^2.0.1" + }, + "bin": { + "create-jest": "bin/create-jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/create-jest/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/create-jest/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/create-jest/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/create-jest/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/create-jest/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/create-jest/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/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css-box-model": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/css-box-model/-/css-box-model-1.2.1.tgz", + "integrity": "sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw==", + "peer": true, + "dependencies": { + "tiny-invariant": "^1.0.6" + } + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/dedent": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.1.tgz", + "integrity": "sha512-+LxW+KLWxu3HW3M2w2ympwtqPrqYRzU8fqi6Fhd18fBALe15blJPI/I4+UHveMVG6lJqB4JNd4UG0S5cnVHwIg==", + "dev": true, + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + }, + "node_modules/electron-to-chromium": { + "version": "1.4.723", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.723.tgz", + "integrity": "sha512-rxFVtrMGMFROr4qqU6n95rUi9IlfIm+lIAt+hOToy/9r6CDv0XiEcQdC3VP71y1pE5CFTzKV0RvxOGYCPWWHPw==", + "dev": true + }, + "node_modules/emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/engine.io-client": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.5.3.tgz", + "integrity": "sha512-9Z0qLB0NIisTRt1DZ/8U2k12RJn8yls/nXMZLn+/N8hANT3TcYjKFKcwbw5zFQiN4NTde3TSY9zb79e1ij6j9Q==", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.11.0", + "xmlhttprequest-ssl": "~2.0.0" + } + }, + "node_modules/engine.io-parser": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.2.tgz", + "integrity": "sha512-RcyUFKA93/CXH20l4SoVvzZfrSDMOTUS3bWVpTt2FuFP+XYrL8i8oonHP7WInRyVHXh0n/ORtoeiE1os+8qkSw==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escalade": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", + "dev": true, + "dependencies": { + "@jest/expect-utils": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/express": { + "version": "4.19.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.19.1.tgz", + "integrity": "sha512-K4w1/Bp7y8iSiVObmCrtq8Cs79XjJc/RU2YYkZQ7wpUu5ZyZ7MtPHkqoMz4pf+mgXfNvo2qft8D9OnrH2ABk9w==", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.2", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.6.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.2.0", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.11.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dev": true, + "dependencies": { + "bser": "2.1.1" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==", + "peer": true + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": 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==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/framesync": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/framesync/-/framesync-6.1.2.tgz", + "integrity": "sha512-jBTqhX6KaQVDyus8muwZbBeGGP0XgujBRbQ7gM7BRdS3CadCZIHiawyzYLnafYcvZIh5j8WE7cxZKFn7dXhu9g==", + "peer": true, + "dependencies": { + "tslib": "2.4.0" + } + }, + "node_modules/framesync/node_modules/tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", + "peer": true + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "engines": { + "node": ">=4" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "peer": true, + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "peer": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-local": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", + "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", + "dev": true, + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" + }, + "node_modules/is-core-module": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "dependencies": { + "hasown": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.2.tgz", + "integrity": "sha512-1WUsZ9R1lA0HtBSohTkm39WTPlNKSJ5iFk7UwqXkBLoHQT+hfqPsfsTDVuZdKGaBwn7din9bS7SsnoAr943hvw==", + "dev": true, + "dependencies": { + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-instrument/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-instrument/node_modules/semver": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-instrument/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report/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/istanbul-lib-report/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/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps/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, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/istanbul-reports": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", + "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", + "dev": true, + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", + "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", + "dev": true, + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/types": "^29.6.3", + "import-local": "^3.0.2", + "jest-cli": "^29.7.0" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-changed-files": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", + "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", + "dev": true, + "dependencies": { + "execa": "^5.0.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-circus": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", + "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^1.0.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^29.7.0", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0", + "pretty-format": "^29.7.0", + "pure-rand": "^6.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-circus/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/jest-circus/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/jest-circus/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/jest-circus/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/jest-circus/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/jest-circus/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" }, - "peerDependencies": { - "react": ">=16.8.0" + "engines": { + "node": ">=8" } }, - "node_modules/@restart/ui": { - "version": "1.6.8", - "resolved": "https://registry.npmjs.org/@restart/ui/-/ui-1.6.8.tgz", - "integrity": "sha512-6ndCv3oZ7r9vuP1Ok9KH55TM1/UkdBnP/fSraW0DFDMbPMzWKhVKeFAIEUCRCSdzayjZDcFYK6xbMlipN9dmMA==", + "node_modules/jest-cli": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", + "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", + "dev": true, "dependencies": { - "@babel/runtime": "^7.21.0", - "@popperjs/core": "^2.11.6", - "@react-aria/ssr": "^3.5.0", - "@restart/hooks": "^0.4.9", - "@types/warning": "^3.0.0", - "dequal": "^2.0.3", - "dom-helpers": "^5.2.0", - "uncontrollable": "^8.0.1", - "warning": "^4.0.3" + "@jest/core": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "create-jest": "^29.7.0", + "exit": "^0.1.2", + "import-local": "^3.0.2", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "yargs": "^17.3.1" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" }, "peerDependencies": { - "react": ">=16.14.0", - "react-dom": ">=16.14.0" + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } } }, - "node_modules/@restart/ui/node_modules/uncontrollable": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-8.0.4.tgz", - "integrity": "sha512-ulRWYWHvscPFc0QQXvyJjY6LIXU56f0h8pQFvhxiKk5V1fcI8gp9Ht9leVAhrVjzqMw0BgjspBINx9r6oyJUvQ==", + "node_modules/jest-cli/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/jest-cli/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/jest-cli/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/jest-cli/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/jest-cli/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/jest-cli/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/jest-config": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", + "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-jest": "^29.7.0", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, "peerDependencies": { - "react": ">=16.14.0" + "@types/node": "*", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "ts-node": { + "optional": true + } } }, - "node_modules/@socket.io/component-emitter": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", - "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==" + "node_modules/jest-config/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/@swc/helpers": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.7.tgz", - "integrity": "sha512-BVvNZhx362+l2tSwSuyEUV4h7+jk9raNdoTSdLfwTshXJSaGmYKluGRJznziCI3KX02Z19DdsQrdfrpXAU3Hfg==", + "node_modules/jest-config/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": { - "tslib": "^2.4.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@types/lodash": { - "version": "4.17.0", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.0.tgz", - "integrity": "sha512-t7dhREVv6dbNj0q17X12j7yDG4bD/DHYX7o5/DbDxobP0HnGPgpRz2Ej77aL7TZT3DSw13fqUTj8J4mMnqa7WA==", - "peer": true + "node_modules/jest-config/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/@types/lodash.mergewith": { - "version": "4.6.7", - "resolved": "https://registry.npmjs.org/@types/lodash.mergewith/-/lodash.mergewith-4.6.7.tgz", - "integrity": "sha512-3m+lkO5CLRRYU0fhGRp7zbsGi6+BZj0uTVSwvcKU+nSlhjA9/QRNfuSGnD2mX6hQA7ZbmcCkzk5h4ZYGOtk14A==", - "peer": true, + "node_modules/jest-config/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/jest-config/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/jest-config/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, "dependencies": { - "@types/lodash": "*" + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, - "node_modules/@types/parse-json": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", - "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==", - "peer": true + "node_modules/jest-diff": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } }, - "node_modules/@types/prop-types": { - "version": "15.7.11", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.11.tgz", - "integrity": "sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==" + "node_modules/jest-diff/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/@types/react": { - "version": "18.2.67", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.67.tgz", - "integrity": "sha512-vkIE2vTIMHQ/xL0rgmuoECBCkZFZeHr49HeWSc24AptMbNRo7pwSBvj73rlJJs9fGKj0koS+V7kQB1jHS0uCgw==", + "node_modules/jest-diff/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": { - "@types/prop-types": "*", - "@types/scheduler": "*", - "csstype": "^3.0.2" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@types/react-transition-group": { - "version": "4.4.10", - "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.10.tgz", - "integrity": "sha512-hT/+s0VQs2ojCX823m60m5f0sL5idt9SO6Tj6Dg+rdphGPIeJbJ6CxvBYkgkGKrYeDjvIpKTR38UzmtHJOGW3Q==", + "node_modules/jest-diff/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, "dependencies": { - "@types/react": "*" + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-diff/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/jest-diff/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/jest-diff/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/jest-docblock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", + "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", + "dev": true, + "dependencies": { + "detect-newline": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-each": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", + "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "jest-util": "^29.7.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-each/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/jest-each/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/jest-each/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/jest-each/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/jest-each/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/jest-each/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, - "node_modules/@types/scheduler": { - "version": "0.16.8", - "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.8.tgz", - "integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==" + "node_modules/jest-environment-node": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", + "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } }, - "node_modules/@types/warning": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/warning/-/warning-3.0.3.tgz", - "integrity": "sha512-D1XC7WK8K+zZEveUPY+cf4+kgauk8N4eHr/XIHXGlGYkHLud6hK9lYfZk1ry1TNh798cZUCgb6MqGEG8DkJt6Q==" + "node_modules/jest-get-type": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } }, - "node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "node_modules/jest-haste-map": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", + "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", + "dev": true, "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" + "@jest/types": "^29.6.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" }, "engines": { - "node": ">= 0.6" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" } }, - "node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "peer": true, + "node_modules/jest-leak-detector": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", + "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", + "dev": true, "dependencies": { - "color-convert": "^1.9.0" + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" }, "engines": { - "node": ">=4" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" - }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" - }, - "node_modules/axios": { - "version": "1.6.8", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.8.tgz", - "integrity": "sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==", + "node_modules/jest-matcher-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", + "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", + "dev": true, "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.0", - "proxy-from-env": "^1.1.0" + "chalk": "^4.0.0", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/babel-plugin-macros": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", - "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", - "peer": true, + "node_modules/jest-matcher-utils/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": { - "@babel/runtime": "^7.12.5", - "cosmiconfig": "^7.0.0", - "resolve": "^1.19.0" + "color-convert": "^2.0.1" }, "engines": { - "node": ">=10", - "npm": ">=6" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/body-parser": { - "version": "1.20.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", - "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "node_modules/jest-matcher-utils/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": { - "bytes": "3.1.2", - "content-type": "~1.0.5", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.11.0", - "raw-body": "2.5.2", - "type-is": "~1.6.18", - "unpipe": "1.0.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/body-parser/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "node_modules/jest-matcher-utils/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": { - "ms": "2.0.0" + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" } }, - "node_modules/body-parser/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + "node_modules/jest-matcher-utils/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/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "node_modules/jest-matcher-utils/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": ">= 0.8" + "node": ">=8" } }, - "node_modules/call-bind": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", - "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "node_modules/jest-matcher-utils/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": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.1" + "has-flag": "^4.0.0" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=8" } }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "peer": true, + "node_modules/jest-message-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, "engines": { - "node": ">=6" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "peer": true, + "node_modules/jest-message-util/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": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "color-convert": "^2.0.1" }, "engines": { - "node": ">=4" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/chalk/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "peer": true, + "node_modules/jest-message-util/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": ">=0.8.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/classnames": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", - "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==" - }, - "node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "peer": true, + "node_modules/jest-message-util/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.3" + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" } }, - "node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "peer": true + "node_modules/jest-message-util/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/color2k": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/color2k/-/color2k-2.0.3.tgz", - "integrity": "sha512-zW190nQTIoXcGCaU08DvVNFTmQhUpnJfVuAKfWqUQkflXKpaDdpaYoM0iluLS9lgJNHyBF58KKA2FBEwkD7wog==", - "peer": true + "node_modules/jest-message-util/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/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "node_modules/jest-message-util/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": { - "delayed-stream": "~1.0.0" + "has-flag": "^4.0.0" }, "engines": { - "node": ">= 0.8" + "node": ">=8" } }, - "node_modules/content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "node_modules/jest-mock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", + "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", + "dev": true, "dependencies": { - "safe-buffer": "5.2.1" + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-util": "^29.7.0" }, "engines": { - "node": ">= 0.6" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "dev": true, "engines": { - "node": ">= 0.6" + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } } }, - "node_modules/convert-source-map": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", - "peer": true - }, - "node_modules/cookie": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "node_modules/jest-regex-util": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "dev": true, "engines": { - "node": ">= 0.6" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" - }, - "node_modules/cosmiconfig": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", - "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", - "peer": true, + "node_modules/jest-resolve": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", + "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", + "dev": true, "dependencies": { - "@types/parse-json": "^4.0.0", - "import-fresh": "^3.2.1", - "parse-json": "^5.0.0", - "path-type": "^4.0.0", - "yaml": "^1.10.0" + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "resolve": "^1.20.0", + "resolve.exports": "^2.0.0", + "slash": "^3.0.0" }, "engines": { - "node": ">=10" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/css-box-model": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/css-box-model/-/css-box-model-1.2.1.tgz", - "integrity": "sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw==", - "peer": true, + "node_modules/jest-resolve-dependencies": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", + "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", + "dev": true, "dependencies": { - "tiny-invariant": "^1.0.6" + "jest-regex-util": "^29.6.3", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/csstype": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" - }, - "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "node_modules/jest-resolve/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": { - "ms": "2.1.2" + "color-convert": "^2.0.1" }, "engines": { - "node": ">=6.0" + "node": ">=8" }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/define-data-property": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "node_modules/jest-resolve/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": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": ">= 0.4" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "node_modules/jest-resolve/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": ">=0.4.0" + "node": ">=7.0.0" } }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "node_modules/jest-resolve/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/jest-resolve/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": ">= 0.8" + "node": ">=8" } }, - "node_modules/dequal": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", - "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "node_modules/jest-resolve/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": ">=6" + "node": ">=8" } }, - "node_modules/destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "node_modules/jest-runner": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", + "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", + "dev": true, + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/environment": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-leak-detector": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-resolve": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-util": "^29.7.0", + "jest-watcher": "^29.7.0", + "jest-worker": "^29.7.0", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/dom-helpers": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", - "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "node_modules/jest-runner/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": { - "@babel/runtime": "^7.8.7", - "csstype": "^3.0.2" + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" - }, - "node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "node_modules/jest-runner/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": ">= 0.8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/engine.io-client": { - "version": "6.5.3", - "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.5.3.tgz", - "integrity": "sha512-9Z0qLB0NIisTRt1DZ/8U2k12RJn8yls/nXMZLn+/N8hANT3TcYjKFKcwbw5zFQiN4NTde3TSY9zb79e1ij6j9Q==", + "node_modules/jest-runner/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": { - "@socket.io/component-emitter": "~3.1.0", - "debug": "~4.3.1", - "engine.io-parser": "~5.2.1", - "ws": "~8.11.0", - "xmlhttprequest-ssl": "~2.0.0" + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" } }, - "node_modules/engine.io-parser": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.2.tgz", - "integrity": "sha512-RcyUFKA93/CXH20l4SoVvzZfrSDMOTUS3bWVpTt2FuFP+XYrL8i8oonHP7WInRyVHXh0n/ORtoeiE1os+8qkSw==", + "node_modules/jest-runner/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/jest-runner/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": ">=10.0.0" + "node": ">=8" } }, - "node_modules/error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "peer": true, + "node_modules/jest-runner/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": { - "is-arrayish": "^0.2.1" + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, - "node_modules/es-define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", - "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "node_modules/jest-runtime": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", + "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", + "dev": true, "dependencies": { - "get-intrinsic": "^1.2.4" + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/globals": "^29.7.0", + "@jest/source-map": "^29.6.3", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" }, "engines": { - "node": ">= 0.4" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "node_modules/jest-runtime/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": ">= 0.4" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" - }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "peer": true, + "node_modules/jest-runtime/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/sponsors/sindresorhus" + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "node_modules/jest-runtime/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": ">= 0.6" + "node": ">=7.0.0" } }, - "node_modules/express": { - "version": "4.19.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.19.1.tgz", - "integrity": "sha512-K4w1/Bp7y8iSiVObmCrtq8Cs79XjJc/RU2YYkZQ7wpUu5ZyZ7MtPHkqoMz4pf+mgXfNvo2qft8D9OnrH2ABk9w==", + "node_modules/jest-runtime/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/jest-runtime/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/jest-runtime/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": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.20.2", - "content-disposition": "0.5.4", - "content-type": "~1.0.4", - "cookie": "0.6.0", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "1.2.0", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", - "methods": "~1.1.2", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.7", - "qs": "6.11.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" + "has-flag": "^4.0.0" }, "engines": { - "node": ">= 0.10.0" + "node": ">=8" } }, - "node_modules/express/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "node_modules/jest-snapshot": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", + "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", + "dev": true, "dependencies": { - "ms": "2.0.0" + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-jsx": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "natural-compare": "^1.4.0", + "pretty-format": "^29.7.0", + "semver": "^7.5.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/express/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + "node_modules/jest-snapshot/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/finalhandler": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "node_modules/jest-snapshot/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": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "statuses": "2.0.1", - "unpipe": "~1.0.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": ">= 0.8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/finalhandler/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "node_modules/jest-snapshot/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": { - "ms": "2.0.0" + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" } }, - "node_modules/finalhandler/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "node_modules/find-root": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", - "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==", - "peer": true + "node_modules/jest-snapshot/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/follow-redirects": { - "version": "1.15.6", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", - "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], + "node_modules/jest-snapshot/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": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } + "node": ">=8" } }, - "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==", + "node_modules/jest-snapshot/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" + "yallist": "^4.0.0" }, "engines": { - "node": ">= 6" + "node": ">=10" } }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "node_modules/jest-snapshot/node_modules/semver": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, "engines": { - "node": ">= 0.6" + "node": ">=10" } }, - "node_modules/framesync": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/framesync/-/framesync-6.1.2.tgz", - "integrity": "sha512-jBTqhX6KaQVDyus8muwZbBeGGP0XgujBRbQ7gM7BRdS3CadCZIHiawyzYLnafYcvZIh5j8WE7cxZKFn7dXhu9g==", - "peer": true, + "node_modules/jest-snapshot/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": { - "tslib": "2.4.0" + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, - "node_modules/framesync/node_modules/tslib": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", - "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", - "peer": true - }, - "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "node_modules/jest-snapshot/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, "engines": { - "node": ">= 0.6" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "node_modules/jest-util/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/sponsors/ljharb" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/get-intrinsic": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", - "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "node_modules/jest-util/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": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": ">= 0.4" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "node_modules/jest-util/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": { - "get-intrinsic": "^1.1.3" + "color-name": "~1.1.4" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": ">=7.0.0" } }, - "node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "peer": true, + "node_modules/jest-util/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/jest-util/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": ">=4" + "node": ">=8" } }, - "node_modules/has-property-descriptors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "node_modules/jest-util/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": { - "es-define-property": "^1.0.0" + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-validate": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", + "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "leven": "^3.1.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate/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/sponsors/ljharb" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/has-proto": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", - "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, "engines": { - "node": ">= 0.4" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "node_modules/jest-validate/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": ">= 0.4" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "node_modules/jest-validate/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": { - "function-bind": "^1.1.2" + "color-name": "~1.1.4" }, "engines": { - "node": ">= 0.4" + "node": ">=7.0.0" } }, - "node_modules/hoist-non-react-statics": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", - "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", - "peer": true, + "node_modules/jest-validate/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/jest-validate/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/jest-validate/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": { - "react-is": "^16.7.0" + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, - "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "node_modules/jest-watcher": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", + "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", + "dev": true, "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "jest-util": "^29.7.0", + "string-length": "^4.0.1" }, "engines": { - "node": ">= 0.8" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "node_modules/jest-watcher/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": { - "safer-buffer": ">= 2.1.2 < 3" + "color-convert": "^2.0.1" }, "engines": { - "node": ">=0.10.0" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "peer": true, + "node_modules/jest-watcher/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": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": ">=6" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + "node_modules/jest-watcher/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/invariant": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", - "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "node_modules/jest-watcher/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/jest-watcher/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/jest-watcher/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": { - "loose-envify": "^1.0.0" + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, - "node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "node_modules/jest-worker": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "dev": true, + "dependencies": { + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, "engines": { - "node": ">= 0.10" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "peer": true + "node_modules/jest-worker/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/is-core-module": { - "version": "2.13.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", - "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", - "peer": true, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, "dependencies": { - "hasown": "^2.0.0" + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/chalk/supports-color?sponsor=1" } }, "node_modules/js-tokens": { @@ -1295,17 +5123,82 @@ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, + "node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "peer": true + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "engines": { + "node": ">=6" + } }, "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "peer": true + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } }, "node_modules/lodash.mergewith": { "version": "4.6.2", @@ -1324,6 +5217,72 @@ "loose-envify": "cli.js" } }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-dir/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "dependencies": { + "tmpl": "1.0.5" + } + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -1337,6 +5296,12 @@ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, "node_modules/methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", @@ -1345,6 +5310,19 @@ "node": ">= 0.6" } }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, "node_modules/mime": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", @@ -1375,11 +5353,38 @@ "node": ">= 0.6" } }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, "node_modules/negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", @@ -1388,6 +5393,39 @@ "node": ">= 0.6" } }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true + }, + "node_modules/node-releases": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", + "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", + "dev": true + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -1415,6 +5453,81 @@ "node": ">= 0.8" } }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-locate/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -1431,7 +5544,6 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "peer": true, "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", @@ -1453,11 +5565,37 @@ "node": ">= 0.8" } }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "peer": true + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, "node_modules/path-to-regexp": { "version": "0.1.7", @@ -1476,8 +5614,85 @@ "node_modules/picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "peer": true + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pirates": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", + "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.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/pretty-format/node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } }, "node_modules/prop-types": { "version": "15.8.1", @@ -1518,6 +5733,22 @@ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" }, + "node_modules/pure-rand": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", + "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ] + }, "node_modules/qs": { "version": "6.11.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", @@ -1676,11 +5907,19 @@ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/resolve": { "version": "1.22.8", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", - "peer": true, "dependencies": { "is-core-module": "^2.13.0", "path-parse": "^1.0.7", @@ -1693,6 +5932,27 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-cwd/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -1702,6 +5962,15 @@ "node": ">=4" } }, + "node_modules/resolve.exports": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz", + "integrity": "sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==", + "dev": true, + "engines": { + "node": ">=10" + } + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -1735,6 +6004,15 @@ "loose-envify": "^1.1.0" } }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/send": { "version": "0.18.0", "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", @@ -1811,6 +6089,27 @@ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/side-channel": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", @@ -1828,6 +6127,27 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/socket.io-client": { "version": "4.7.5", "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.7.5.tgz", @@ -1863,6 +6183,52 @@ "node": ">=0.10.0" } }, + "node_modules/source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/source-map-support/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, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/stack-utils/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", @@ -1871,6 +6237,75 @@ "node": ">= 0.8" } }, + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/stylis": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", @@ -1881,7 +6316,6 @@ "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "peer": true, "dependencies": { "has-flag": "^3.0.0" }, @@ -1893,7 +6327,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "peer": true, "engines": { "node": ">= 0.4" }, @@ -1901,21 +6334,52 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/tiny-invariant": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", "peer": true }, + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true + }, "node_modules/to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", - "peer": true, "engines": { "node": ">=4" } }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, "node_modules/toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", @@ -1929,6 +6393,27 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", @@ -1955,6 +6440,12 @@ "react": ">=15.0.0" } }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true + }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -1963,6 +6454,36 @@ "node": ">= 0.8" } }, + "node_modules/update-browserslist-db": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", + "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, "node_modules/utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", @@ -1971,6 +6492,26 @@ "node": ">= 0.4.0" } }, + "node_modules/v8-to-istanbul": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.2.0.tgz", + "integrity": "sha512-/EH/sDgxU2eGxajKdwLCDmQ4FWq+kpi3uCmBGpw1xJtnAxEjlD8j8PEiGWpCIMIs3ciNAgH0d3TTJiUkYzyZjA==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/v8-to-istanbul/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -1979,6 +6520,15 @@ "node": ">= 0.8" } }, + "node_modules/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "dependencies": { + "makeerror": "1.0.12" + } + }, "node_modules/warning": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", @@ -1987,6 +6537,90 @@ "loose-envify": "^1.0.0" } }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/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/wrap-ansi/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/wrap-ansi/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/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "node_modules/write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, "node_modules/ws": { "version": "8.11.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", @@ -2015,6 +6649,21 @@ "node": ">=0.4.0" } }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, "node_modules/yaml": { "version": "1.10.2", "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", @@ -2023,6 +6672,45 @@ "engines": { "node": ">= 6" } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } } } } diff --git a/package.json b/package.json index 05a3a007..b775fab2 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "socket.io-client": "^4.7.5" }, "devDependencies": { + "jest": "^29.7.0", "react-router-dom": "^6.22.3" } } diff --git a/roomservice/RoomQuestions.js b/roomservice/RoomQuestions.js index 55ba3a2f..19885d9f 100644 --- a/roomservice/RoomQuestions.js +++ b/roomservice/RoomQuestions.js @@ -166,5 +166,15 @@ class RoomQuestions{ console.log(`La sala con id ${id} no existe.`); } } + getRoomUsers(id) { + // Check if the room exists + if (this.rooms.has(id)) { + // Return the list of users in the room + return this.rooms.get(id); + } else { + // If the room does not exist, return an empty array + return []; + } + } } module.exports=RoomQuestions; \ No newline at end of file diff --git a/roomservice/package-lock.json b/roomservice/package-lock.json index 85763b70..762050f9 100644 --- a/roomservice/package-lock.json +++ b/roomservice/package-lock.json @@ -13,6 +13,7 @@ "express": "^4.18.2", "mongoose": "^8.0.4", "socket.io": "^4.7.5", + "socket.io-client": "^4.7.5", "xml2js": "^0.4.23" }, "devDependencies": { @@ -2012,6 +2013,39 @@ "node": ">=10.2.0" } }, + "node_modules/engine.io-client": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.5.3.tgz", + "integrity": "sha512-9Z0qLB0NIisTRt1DZ/8U2k12RJn8yls/nXMZLn+/N8hANT3TcYjKFKcwbw5zFQiN4NTde3TSY9zb79e1ij6j9Q==", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.11.0", + "xmlhttprequest-ssl": "~2.0.0" + } + }, + "node_modules/engine.io-client/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/engine.io-client/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, "node_modules/engine.io-parser": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.2.tgz", @@ -4761,6 +4795,41 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, + "node_modules/socket.io-client": { + "version": "4.7.5", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.7.5.tgz", + "integrity": "sha512-sJ/tqHOCe7Z50JCBCXrsY3I2k03iOiUe+tj1OmKeD2lXPiGH/RUCdTZFoqVyN7l1MnpIzPrGtLcijffmeouNlQ==", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.2", + "engine.io-client": "~6.5.2", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-client/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io-client/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, "node_modules/socket.io-parser": { "version": "4.2.4", "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", @@ -5395,6 +5464,14 @@ "node": ">=4.0" } }, + "node_modules/xmlhttprequest-ssl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz", + "integrity": "sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", @@ -7024,6 +7101,33 @@ } } }, + "engine.io-client": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.5.3.tgz", + "integrity": "sha512-9Z0qLB0NIisTRt1DZ/8U2k12RJn8yls/nXMZLn+/N8hANT3TcYjKFKcwbw5zFQiN4NTde3TSY9zb79e1ij6j9Q==", + "requires": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.11.0", + "xmlhttprequest-ssl": "~2.0.0" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, "engine.io-parser": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.2.tgz", @@ -9022,6 +9126,32 @@ } } }, + "socket.io-client": { + "version": "4.7.5", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.7.5.tgz", + "integrity": "sha512-sJ/tqHOCe7Z50JCBCXrsY3I2k03iOiUe+tj1OmKeD2lXPiGH/RUCdTZFoqVyN7l1MnpIzPrGtLcijffmeouNlQ==", + "requires": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.2", + "engine.io-client": "~6.5.2", + "socket.io-parser": "~4.2.4" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, "socket.io-parser": { "version": "4.2.4", "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", @@ -9454,6 +9584,11 @@ "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==" }, + "xmlhttprequest-ssl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz", + "integrity": "sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==" + }, "y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", diff --git a/roomservice/package.json b/roomservice/package.json index 35401c3c..bbdb064e 100644 --- a/roomservice/package.json +++ b/roomservice/package.json @@ -22,6 +22,7 @@ "express": "^4.18.2", "mongoose": "^8.0.4", "socket.io": "^4.7.5", + "socket.io-client": "^4.7.5", "xml2js": "^0.4.23" }, "devDependencies": { diff --git a/roomservice/room-service.test.js b/roomservice/room-service.test.js new file mode 100644 index 00000000..2d64fc5e --- /dev/null +++ b/roomservice/room-service.test.js @@ -0,0 +1,94 @@ +const RoomQuestions = require('./RoomQuestions'); +const { Server } = require('socket.io'); +const http = require('http'); +const axios = require('axios'); + +jest.mock('axios'); + +describe('RoomQuestions', () => { + let roomQuestions; + let io; + let socket; + + beforeEach(() => { + const server = http.createServer(); + server.listen(); + io = new Server(server); + socket = { + emit: jest.fn().mockImplementation((event, message) => { + console.log(`Event: ${event}, Message: ${message}`); + }), + join: jest.fn(), + of: jest.fn().mockReturnThis(), + sockets: { + values: jest.fn().mockReturnThis(), + next: jest.fn().mockReturnThis(), + value: jest.fn() + } + }; + roomQuestions = new RoomQuestions(io); + }); + + afterEach(() => { + io.close(); + }); + + test('should create room', async () => { + const username = 'testUser'; + const id = await roomQuestions.createRoom(username, socket); + expect(roomQuestions.rooms.has(id)).toBe(true); + }); + + test('should join room', async () => { + const username = 'testUser'; + const id = await roomQuestions.createRoom(username, socket); + await roomQuestions.joinRoom(id, 'anotherUser', socket); + expect(roomQuestions.rooms.get(id)).toContain('anotherUser'); + }); + + test('should start game if enough players', async () => { + const username = 'testUser'; + const id = await roomQuestions.createRoom(username, socket); + await roomQuestions.joinRoom(id, 'anotherUser', socket); + + axios.get.mockResolvedValue({ data: 'questions' }); + + await roomQuestions.startGame(id, socket); + expect(socket.emit).toHaveBeenCalledWith('gameStarted', 'questions'); + }); + test('should not start game without enough players', async () => { + const username = 'testUser'; + const id = await roomQuestions.createRoom(username, socket); + + // Try to start game with only one user + await roomQuestions.startGame(id, socket); + + // Check that the game did not start + expect(socket.emit).not.toHaveBeenCalledWith('gameStarted', expect.anything()); + }); + + test('should end game and determine winner', async () => { + const username = 'testUser'; + const anotherUser = 'anotherUser'; + const id = await roomQuestions.createRoom(username, socket); + await roomQuestions.joinRoom(id, anotherUser, socket); + await roomQuestions.joinRoom(id, username, socket); + + // Check that there are only two users in the room + const roomUsers = roomQuestions.getRoomUsers(id); + expect(roomUsers.length).toBe(2); + + // Start game for both users + await roomQuestions.startGame(id, socket); + + // Check that the game started + expect(socket.emit).toHaveBeenCalledWith('gameStarted', expect.anything()); + + // End game for both users + await roomQuestions.endGame(id, { user: username, correctas: 5, incorrectas: 0, tiempoTotal: 10 }, socket); + await roomQuestions.endGame(id, { user: anotherUser, correctas: 1, incorrectas: 4, tiempoTotal: 20 }, socket); + + // Check that the game ended + expect(socket.emit).toHaveBeenCalledWith('gameEnded', expect.any(String)); + }); +}); \ No newline at end of file From 3370b79a64239e53a5261be024665a0b8bcbd18b Mon Sep 17 00:00:00 2001 From: lucia-sonia Date: Tue, 2 Apr 2024 18:56:10 +0200 Subject: [PATCH 02/79] =?UTF-8?q?a=C3=B1adidos=20cambios=20en=20la=20parte?= =?UTF-8?q?=205=20de=20la=20documentacion?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/images/05-BussinesLogic-Level2.png | Bin 104606 -> 0 bytes docs/images/05-Database-Level2.png | Bin 99739 -> 0 bytes docs/images/05-Level1.png | Bin 44619 -> 0 bytes docs/images/05-ScopeAndContext.png | Bin 25814 -> 0 bytes docs/images/05-UserInterface-Level2.png | Bin 103665 -> 0 bytes docs/images/Level2Databases.png | Bin 14805 -> 0 bytes docs/images/Level2Services.png | Bin 20051 -> 0 bytes docs/images/Level2Webapp.png | Bin 20306 -> 0 bytes docs/images/level1.png | Bin 37319 -> 78542 bytes docs/images/level2-History.png | Bin 0 -> 13825 bytes docs/images/level2-Question.png | Bin 0 -> 27024 bytes docs/images/level2-Users.png | Bin 0 -> 14649 bytes docs/src/05_building_block_view.adoc | 44 ++++++++++++------------ 13 files changed, 22 insertions(+), 22 deletions(-) delete mode 100644 docs/images/05-BussinesLogic-Level2.png delete mode 100644 docs/images/05-Database-Level2.png delete mode 100644 docs/images/05-Level1.png delete mode 100644 docs/images/05-ScopeAndContext.png delete mode 100644 docs/images/05-UserInterface-Level2.png delete mode 100644 docs/images/Level2Databases.png delete mode 100644 docs/images/Level2Services.png delete mode 100644 docs/images/Level2Webapp.png create mode 100644 docs/images/level2-History.png create mode 100644 docs/images/level2-Question.png create mode 100644 docs/images/level2-Users.png diff --git a/docs/images/05-BussinesLogic-Level2.png b/docs/images/05-BussinesLogic-Level2.png deleted file mode 100644 index e6defb4acb5f61cf107b7aad05215c335422fcd6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 104606 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

yBBzw;?pEXeN;+XxAE|H~U5yma0D5aeNrT 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 diff --git a/docs/images/05-Database-Level2.png b/docs/images/05-Database-Level2.png deleted file mode 100644 index 51cc686b19b384e571b0938f80594b846d58ca1c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 99739 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 aUl4lLUhBdmwGuJ#W3$VC=e_M-QU3%?AeDgt diff --git a/docs/images/05-ScopeAndContext.png b/docs/images/05-ScopeAndContext.png deleted file mode 100644 index 0a63bda71a8f68553bb2e405cad826583e40e889..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 25814 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

$Qqmu;)=Oyi^37rn!ojnrg>;W#9>G{l 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 QmC&dvYAWQMv-tV{0N-uUZ~y=R diff --git a/docs/images/05-UserInterface-Level2.png b/docs/images/05-UserInterface-Level2.png deleted file mode 100644 index e8bb6923a1bc970a1499e831d8d88bf2c5a3ad17..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 103665 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 R|4$RKf6pP~93!VQ{|E8`YNG%E diff --git a/docs/images/Level2Services.png b/docs/images/Level2Services.png deleted file mode 100644 index 1b910dd183c02db91c8f17482514bb07bca392b2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20051 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≦S6_OkEA@#Zbqs>|b}X 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 diff --git a/docs/images/level2-History.png b/docs/images/level2-History.png new file mode 100644 index 0000000000000000000000000000000000000000..370d521210d5268aa834195b4522994ae9a0108b GIT binary patch literal 13825 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 vNAy2K6Lh-UsqTs(+!jqL$l*kjibMJov=G7h1 literal 0 HcmV?d00001 diff --git a/docs/images/level2-Users.png b/docs/images/level2-Users.png new file mode 100644 index 0000000000000000000000000000000000000000..a7dd874b0a1315a1e3fd0592c8b912c2fe04b034 GIT binary patch literal 14649 zcmeHu2|Sc-+c%1|2xTb>k+Q_trDWfi$WDwV24k7g%*c$gWl2;>wuGz|F-4Lwi0&49 zmWT-1DJJ_8-g6lD-F@FZ_w&5Z^M2p^z2E!%elpi}UgvpS=Xosu|8X3qYsROwnRamP zprN5*($�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 Date: Tue, 2 Apr 2024 22:08:50 +0200 Subject: [PATCH 03/79] solucionar errores y combinar los dos game singlplayer y multiplayer en uno --- questionservice/scheduler.js | 2 +- webapp/src/App.js | 5 +- webapp/src/components/game/Game.js | 29 +++++- webapp/src/components/game/GameMultiplayer.js | 97 ------------------- webapp/src/components/rooms/Room.js | 6 +- 5 files changed, 33 insertions(+), 106 deletions(-) delete mode 100644 webapp/src/components/game/GameMultiplayer.js diff --git a/questionservice/scheduler.js b/questionservice/scheduler.js index b9c6f45a..46eb0d94 100644 --- a/questionservice/scheduler.js +++ b/questionservice/scheduler.js @@ -20,7 +20,7 @@ class Scheduler { } start() { - cron.schedule('*/30 * * * *', async () => { + cron.schedule('*/1 * * * *', async () => { try { await this.generarPregunta(); } diff --git a/webapp/src/App.js b/webapp/src/App.js index 0b23886b..9ed1e029 100644 --- a/webapp/src/App.js +++ b/webapp/src/App.js @@ -19,7 +19,6 @@ import PrincipalView from './components/principalView/PrincipalView'; import Room from './components/rooms/Room'; // Importa el componente de sala import CreateRoomForm from './components/rooms/CreateRoom'; // Importa el componente para crear sala import JoinRoomForm from './components/rooms/JoinRoom'; // Importa el componente para unirse a sala -import GameMultiplayer from './components/game/GameMultiplayer'; // Importa el componente para el juego multijugador const App = () => { @@ -66,8 +65,8 @@ const App = () => { } /> } /> - } /> - } /> + } /> + diff --git a/webapp/src/components/game/Game.js b/webapp/src/components/game/Game.js index f90bedce..66dc85c7 100644 --- a/webapp/src/components/game/Game.js +++ b/webapp/src/components/game/Game.js @@ -11,7 +11,7 @@ recibe el obj gameMode que contieene las preguntas para ese modo de juego recibe questions que son las del servidor si estas en multiplayer si no le pasa contexto se utiliziara el por defecto que es el GameContext */ -function Game({darkMode,questions:multiplayerQuestions,endGame}) { +function Game({darkMode,questions:multiplayerQuestions=null,endGame=null}) { //obtienes las preguntas del contexto o bien de la prop q se le pasa const { startGame, questions: singleplayerQuestions, isLoading } = useContext(GameContext); @@ -78,6 +78,8 @@ function Game({darkMode,questions:multiplayerQuestions,endGame}) { const onClose=()=>{ setIsOpen(false); + //solamente te iras si has cerrado el singleplayer en multiplayer no te vas hasta que no ves el ganador + if( multiplayerQuestions==null) navigate('/home'); } //Colores chakra dark - light @@ -87,6 +89,7 @@ function Game({darkMode,questions:multiplayerQuestions,endGame}) { //#08313A, #107869 return ( + console.log("En game"+darkMode.darkMode), @@ -104,7 +107,29 @@ function Game({darkMode,questions:multiplayerQuestions,endGame}) { )} + + + {multiplayerQuestions ? (

):( @@ -125,7 +150,7 @@ function Game({darkMode,questions:multiplayerQuestions,endGame}) { - + )} ); } diff --git a/webapp/src/components/game/GameMultiplayer.js b/webapp/src/components/game/GameMultiplayer.js deleted file mode 100644 index 2844eb6c..00000000 --- a/webapp/src/components/game/GameMultiplayer.js +++ /dev/null @@ -1,97 +0,0 @@ -import { QuestionArea } from './QuestionArea'; -import { useEffect, useState,useContext } from 'react'; -import { CircularProgress } from "@mui/material"; -import {GameContext} from './GameContext'; -import {useNavigate} from 'react-router-dom'; -import { Box, AlertDialog, AlertDialogBody, AlertDialogFooter, AlertDialogHeader, AlertDialogContent, AlertDialogOverlay, Button,Center } from "@chakra-ui/react"; - -/* -recibe el obj gameMode que contieene las preguntas para ese modo de juego -recibe questions que son las del servidor si estas en multiplayer - si no le pasa contexto se utiliziara el por defecto que es el GameContext -*/ -function Game({questions:multiplayerQuestions,endGame}) { - - //obtienes las preguntas del contexto o bien de la prop q se le pasa - const { startGame, questions: singleplayerQuestions, isLoading } = useContext(GameContext); - const questions = multiplayerQuestions || singleplayerQuestions; - - const [isOpen, setIsOpen] = useState(false);//es el cuadro de dialogo que se abre al finalizar el juego - - //e le pasaran al Question area para que cuando acabe el juego tengan el valor de las respuestas correctas - const [correctAnswers, setCorrectAnswers] = useState(0); - const [incorrectAnswers, setIncorrectAnswers] = useState(0); - const [finished, setFinished] = useState(false); - const navigate = useNavigate(); - - - useEffect(() => { - startGame(); - }, []); - - //se ejecuta al cambiar el num de correctas que solo cambia si se ha terminado el juego - useEffect(()=>{ - if(finished&&localStorage.getItem('username')!=null){//tienes que estar logeado para guardar el historial - const data={ - user:localStorage.getItem('username'), - correctas:correctAnswers, - incorrectas:incorrectAnswers, - tiempoTotal:999 - }; - - - console.log("hola se ha enviado el hisotrial al servidor con los datos" ,data); - // axios.post(`${apiEndpoint}/historyAdd`, data); - - - setIsOpen(true);//hacer que aparzca el cuadro de dialogo - - //comprobar si es mnultiplayer y si lo es se enviara al servidor que se ha finalizado el juego - if(multiplayerQuestions!=null){ - endGame(data); - } - - } - },[setFinished,correctAnswers,incorrectAnswers]) - - const onClose=()=>{ - setIsOpen(false); - //espera a que el reso de usuarios terminen - } - return ( - - {isLoading ? ( - - - ) : ( - - )} - - - - - Juego Terminado - - - - Esperando al resto de jugadores... - - - - - - - - - - ); -} - -export default Game; diff --git a/webapp/src/components/rooms/Room.js b/webapp/src/components/rooms/Room.js index f62b2893..83965cc8 100644 --- a/webapp/src/components/rooms/Room.js +++ b/webapp/src/components/rooms/Room.js @@ -5,9 +5,9 @@ import { AlertDialog, AlertDialogBody, AlertDialogFooter, AlertDialogHeader, Ale import { useNavigate } from 'react-router-dom'; import socket from './socket'; -import Game from '../game/GameMultiplayer'; +import Game from '../game/Game'; -function Room() { +function Room({ darkMode }) { const nagivate = useNavigate(); const { roomId } = useParams(); const location = useLocation(); @@ -94,7 +94,7 @@ function Room() { ))} {isHost && } - {gameStarted && questions.length > 0 && } + {gameStarted && questions.length > 0 && } Date: Wed, 3 Apr 2024 00:17:28 +0200 Subject: [PATCH 04/79] =?UTF-8?q?A=C3=B1adidas=20descripciones=20de=20los?= =?UTF-8?q?=20errores=20del=20questionservice?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gatewayservice/gateway-service.js | 11 +- questionservice/guardarPreguntaBaseDatos.js | 34 ++++-- questionservice/obtenerPreguntasBaseDatos.js | 74 +++++++----- questionservice/obtenerPreguntasWikidata.js | 114 +++++++++++++------ questionservice/question-service.js | 18 +-- questionservice/questionGeneration.js | 15 ++- 6 files changed, 173 insertions(+), 93 deletions(-) diff --git a/gatewayservice/gateway-service.js b/gatewayservice/gateway-service.js index 1bb76af1..b0253ba1 100644 --- a/gatewayservice/gateway-service.js +++ b/gatewayservice/gateway-service.js @@ -56,11 +56,10 @@ app.get('/getQuestion', async (req, res) => { res.json(questionResponse.data); } catch (error) { //Modifico el error - res.status(500).json({ error: 'Error al realizar la solicitud al servicio de preguntas' }); + res.status(500).json({ error: 'Error al realizar la solicitud al servicio de preguntas para obtener una pregunta -> ' + error.response.data.error}); } }); - app.get('/getQuestionModoBasico', async (req, res) => { try { // llamamos al servicio de preguntas @@ -68,19 +67,17 @@ app.get('/getQuestionModoBasico', async (req, res) => { res.json(questionResponse.data); } catch (error) { //Modifico el error - res.status(500).json({ error: 'Error al realizar la solicitud al servicio de preguntas' }); + res.status(500).json({ error: 'Error al realizar la solicitud al servicio de preguntas para obtener las preguntas del modo básico -> ' + error.response.data.error}); } }); app.get('/generateQuestion', async (req, res) => { try { // llamamos al servicio de preguntas - await axios.get(questionServiceUrl+'/generateQuestion', req.body); - + await axios.get(questionServiceUrl+'/generateQuestion', req.body); } catch (error) { //Modifico el error - res.status(500).json({ error: 'Error al realizar la solicitud al servicio de generacion de preguntas' }); - + res.status(500).json({ error: 'Error al realizar la solicitud al servicio de generacion de preguntas -> ' + error.response.data.error}); } }); diff --git a/questionservice/guardarPreguntaBaseDatos.js b/questionservice/guardarPreguntaBaseDatos.js index e1b4da57..bcaafda4 100644 --- a/questionservice/guardarPreguntaBaseDatos.js +++ b/questionservice/guardarPreguntaBaseDatos.js @@ -22,7 +22,7 @@ class GuardarBaseDatos{ this.guardarSegundaIncorrecta(idTipo); this.guardarTerceraIncorrecta(idTipo); }).catch(error => { - console.error("Error al guardar la categoría o el tipo de pregunta:", error); + throw new Error("Error al guardar la pregunta: " + error.message); }); } @@ -43,6 +43,9 @@ class GuardarBaseDatos{ //guardamos el id de la categoria nueva idCategoria = categoriaGuardada._id; resolve(idCategoria); + }) + .catch(error => { + reject(new Error('Error al guardar la nueva categoría en la base de datos: ' + error.message)); }); } @@ -53,8 +56,7 @@ class GuardarBaseDatos{ } }); }) .catch(error => { - console.error("Error al ejecutar la consulta:", error); - reject(error); // Rechazamos la Promesa con el error + reject(new Error('No se ha guardado la categoria en la base de datos ' + error.message)); }); } @@ -91,6 +93,9 @@ class GuardarBaseDatos{ //guardamos el id del tipo idTipo = tipoGuardado._id; resolve(idTipo); + }) + .catch(error => { + reject(new Error('Error al guardar el nuevo tipo en la base de datos: ' + error.message)); }); } else { @@ -101,14 +106,22 @@ class GuardarBaseDatos{ //guardamos el id del tipo idTipo = tipoGuardado._id; resolve(idTipo); + }) + .catch(error => { + reject(new Error('Error al guardar el tipo actualizado en la base de datos: ' + error.message)); }); } - }); + }) + .catch(error => { + reject(new Error('Error al buscar el tipo de pregunta en la base de datos: ' + error.message)); + }) + .catch(error => { + reject(new Error('Error al guardar la nueva pregunta en la base de datos: ' + error.message)); + }); }); } }).catch(error => { - console.error("Error al ejecutar la consulta:", error); - reject(error); // Rechazamos la Promesa con el error + reject(new Error('Error al guardar la pregunta en la base de datos: ' + error.message)); }); }); } @@ -136,6 +149,8 @@ class GuardarBaseDatos{ respuestaExistente.save(); } } + }).catch(error => { + throw new Error('Error al guardar la primera respuesta incorrecta en la base de datos: ' + error.message); }); } @@ -162,7 +177,9 @@ class GuardarBaseDatos{ respuestaExistente.save(); } } - }); + }).catch(error => { + throw new Error('Error al guardar la segunda respuesta incorrecta en la base de datos: ' + error.message); + }); } guardarTerceraIncorrecta(idTipo){ @@ -188,9 +205,10 @@ class GuardarBaseDatos{ respuestaExistente.save(); } } + }).catch(error => { + throw new Error('Error al guardar la tercera respuesta incorrecta en la base de datos: ' + error.message); }); } - } module.exports = GuardarBaseDatos; \ No newline at end of file diff --git a/questionservice/obtenerPreguntasBaseDatos.js b/questionservice/obtenerPreguntasBaseDatos.js index 80bfc3de..d1df439f 100644 --- a/questionservice/obtenerPreguntasBaseDatos.js +++ b/questionservice/obtenerPreguntasBaseDatos.js @@ -7,37 +7,53 @@ const Respuesta = mongoose.model('Respuesta'); class ObtenerPreguntas{ + async obtenerPregunta(numeroPreguntas){ + try{ + var resultado = {}; + var objetoExterno= {}; + //Se cojen las preguntas del numero que se pase por parametro + var preguntas = await Pregunta.aggregate([{ $sample: { size: numeroPreguntas } }]); + for(var i = 0; i < preguntas.length; i++){ + try{ + var tipo = await Tipos.findOne({ idPreguntas: { $in: preguntas[i]._id } }); - async obtenerPregunta(numeroPreguntas){ - var resultado = {}; - var objetoExterno= {}; - //Se cojen las preguntas del numero que se pase por parametro - console.log("Numero" + numeroPreguntas); - var preguntas = await Pregunta.aggregate([{ $sample: { size: numeroPreguntas } }]); - console.log("Preguntas " + preguntas); - for(var i = 0; i < preguntas.length; i++){ - - var tipo = await Tipos.findOne({ idPreguntas: { $in: preguntas[i]._id } }); - console.log("Pregunta" + preguntas[i]); - var respuestas = await Respuesta.aggregate([ - { $match: { tipos: {$in : [tipo._id]}, textoRespuesta: { $ne: [preguntas[i].respuestaCorrecta, "Ninguna de las anteriores" ]} } }, - { $sample: { size: 3 } } - ]); - - resultado = { - pregunta: preguntas[i].textoPregunta, - correcta: preguntas[i].respuestaCorrecta, - respuestasIncorrecta1: respuestas[0].textoRespuesta, - respuestasIncorrecta2: respuestas[1].textoRespuesta, - respuestasIncorrecta3: respuestas[2].textoRespuesta - }; - console.log("Resultado" + resultado); - objetoExterno["resultado" + (i+1)] = resultado; - - } - console.log( "objeto entero " + objetoExterno); - return objetoExterno; + var respuestas = await Respuesta.aggregate([ + { $match: { tipos: {$in : [tipo._id]}, textoRespuesta: { $ne: [preguntas[i].respuestaCorrecta, "Ninguna de las anteriores" ]} } }, + { $sample: { size: 3 } } + ]); + + //comprobamos si hay respuestas + if(respuestas.length < 3){ + throw new Error("No hay suficientes respuestas en la base de datos"); + } + + resultado = { + pregunta: preguntas[i].textoPregunta, + correcta: preguntas[i].respuestaCorrecta, + respuestasIncorrecta1: respuestas[0].textoRespuesta, + respuestasIncorrecta2: respuestas[1].textoRespuesta, + respuestasIncorrecta3: respuestas[2].textoRespuesta + }; + + objetoExterno["resultado" + (i+1)] = resultado; + } + catch(error){ + throw new Error("Error al obtener el tipo o las respuestas de la base de datos"); + } + } + + //comprobamos si hay preguntas + if(preguntas.lenght != numeroPreguntas){ + throw new Error("No se han devuelto el numero de preguntas necesario"); + } + + console.log("Preguntas finales: " + objetoExterno); + return objetoExterno; + } + catch(error){ + throw new Error("Error al obtener las preguntas de la base de datos"); + } } } diff --git a/questionservice/obtenerPreguntasWikidata.js b/questionservice/obtenerPreguntasWikidata.js index 2e9f940e..b1bb1364 100644 --- a/questionservice/obtenerPreguntasWikidata.js +++ b/questionservice/obtenerPreguntasWikidata.js @@ -34,22 +34,40 @@ class ObtenerPreguntaWikiData { fs.readFile('preguntas.xml', 'utf-8', (err, data) => { if (err) { - console.error('Error al leer el archivo:', err); + reject(new Error('Error al leer el archivo:', err)); return; } // Parsear el XML xml2js.parseString(data, (parseErr, result) => { if (parseErr) { - console.error('Error al analizar el XML:', parseErr); + reject(new Error('Error al analizar el XML:', parseErr)); return; } + + if(!result || !result.preguntas || !result.preguntas.pregunta){ + reject(new Error('No hay preguntas disponibles en el archivo')); + return; + } + // Obtener las preguntas disponibles var preguntas = result.preguntas.pregunta; + + //comprobamos si hay preguntas disponibles + if(preguntas.length === 0){ + reject(new Error('No hay preguntas disponibles')); + return; + } // Seleccionar una pregunta aleatoria var pregunta = preguntas[Math.floor(Math.random() * preguntas.length)]; + //comprobamos si tenemos todos los datos disponibles + if(!pregunta.$.question || !pregunta.$.type || !pregunta.$.category){ + reject(new Error('La pregunta no tiene la estructura correcta')); + return; + } + // Obtener la información relativa a la pregunta this.question = pregunta.$.question; this.type = pregunta.$.type; @@ -66,6 +84,12 @@ class ObtenerPreguntaWikiData { this.labels = prueba.map(match => { return match.slice(1); // Elimina el primer carácter "?" y muestra el resto }); + + //comprobamos que tenemos el numero de labels correcto + if(this.labels.length !== 3){ + reject(new Error('La consulta no tiene el formato correcto para las labels')); + return; + } //obtenemos todas las entradas de wikidata para esa query this.obtenerEntidadesConsulta(query) @@ -82,7 +106,7 @@ class ObtenerPreguntaWikiData { */ obtenerEntidadesConsulta(consulta){ return new Promise((resolve, reject) => { - const apiUrl = 'https://query.wikidata.org/sparql'; + const apiUrl = 'https://query.wikidata.org/sparql'; axios.get(apiUrl, { params: { @@ -96,7 +120,8 @@ class ObtenerPreguntaWikiData { .catch(error => reject(error)); }) .catch(error => { - console.error('Error:', error); + reject(new Error('Error al obtener las entidades de wikidata:', error)); + return; }); }); } @@ -164,56 +189,73 @@ class ObtenerPreguntaWikiData { //leemos el archivo fs.readFile('esqueletoPreguntas.xml', 'utf-8', (err, data) => { if (err) { - console.error('Error al leer el esqueleto de las preguntas:', err); - return; + reject(new Error('Error al leer el esqueleto de las preguntas:', err)); + return; } //parseamos el xml xml2js.parseString(data, (parseErr, result) => { if (parseErr) { - console.error('Error al analizar el esqueleto de las preguntas:', parseErr); + reject(new Error('Error al analizar el esqueleto de las preguntas:', parseErr)); return; } - //obtenemos el esqueleto de la pregunta que queremos hacer - var textoPregunta = this.obtenerTextoPregunta(result, this.question, this.type); - - //para comprobar si es un Q - var regex = /^Q\d+/; - //comprobamos que el resultado es valido para hacer la pregunta (que no sea QXXXXX) - var preguntaCorrecta = this.answers.find(entidad => { - return entidad.label !== "Ninguna de las anteriores" && !regex.test(entidad.label); - }); - - if(preguntaCorrecta){ - //rellenamos el esqueleto de la pregunta con los datos de la entidad - var pregunta = preguntaCorrecta.label; - var respuestaCorrecta = preguntaCorrecta.result; - var consulta = textoPregunta.replace('{RELLENAR}', pregunta); - - this.generarPregunta(consulta, respuestaCorrecta) - .then(() => resolve()) - .catch(error => reject(error)); + if(!result || !result.textoPreguntas || !result.textoPreguntas.pregunta){ + reject(new Error('No hay esqueletos de preguntas disponibles')); + return; } - - //si no hay pregunta resolvemos la promesa else{ - resolve(); - } + var preguntas = result.textoPreguntas.pregunta; + + //obtenemos el esqueleto de la pregunta que queremos hacer + var textoPregunta = this.obtenerTextoPregunta(preguntas, this.question, this.type); + + //comprobamos si se ha encontrado el texto de la pregunta + if(textoPregunta === ""){ + reject(new Error('No se ha encontrado el texto de la pregunta')); + return; + } + + //para comprobar si es un Q + var regex = /^Q\d+/; + //comprobamos que el resultado es valido para hacer la pregunta (que no sea QXXXXX) + var preguntaCorrecta = this.answers.find(entidad => { + return entidad.label !== "Ninguna de las anteriores" && !regex.test(entidad.label); + }); + + if(preguntaCorrecta){ + //rellenamos el esqueleto de la pregunta con los datos de la entidad + var pregunta = preguntaCorrecta.label; + var respuestaCorrecta = preguntaCorrecta.result; + var consulta = textoPregunta.replace('{RELLENAR}', pregunta); + + this.generarPregunta(consulta, respuestaCorrecta) + .then(() => resolve()) + .catch(error => reject(error)); + } + + //si no hay pregunta resolvemos la promesa + else{ + reject(new Error('No se ha encontrado una entidad válida para hacer la pregunta')); + return; + } + } }); }); - }); + }); } /* obtenemos el texto de la pregunta que queremos hacer */ - obtenerTextoPregunta(result, question, type) { - var preguntas = result.textoPreguntas.pregunta; + obtenerTextoPregunta(preguntas, question, type) { for (var pregunta of preguntas) { - if (pregunta.$.question === question && pregunta.$.type === type) { - return pregunta._; - } + if(!pregunta.$.question || !pregunta.$.type){ + return ""; + } + if (pregunta.$.question === question && pregunta.$.type === type) { + return pregunta._; + } } return ""; } diff --git a/questionservice/question-service.js b/questionservice/question-service.js index 840de5d4..a2971b12 100644 --- a/questionservice/question-service.js +++ b/questionservice/question-service.js @@ -22,6 +22,8 @@ app.use(express.json()); const mongoUri = process.env.MONGODB_URI || 'mongodb://localhost:27017/questionsdb'; mongoose.connect(mongoUri); +// Endpoints para la obtención de preguntas para los modos de juego + app.get('/getQuestion', async(req,res)=> { try{ //coger pregunta bd @@ -30,10 +32,10 @@ app.get('/getQuestion', async(req,res)=> { res.json(questions); } catch(error) { - res.status(error.response.status).json({ error: error.response.data.error }); + res.status(500).json({ error: error.message }); } - }); + app.get('/getQuestionModoBasico', async(req,res)=> { try{ //coger pregunta bd @@ -42,19 +44,19 @@ app.get('/getQuestionModoBasico', async(req,res)=> { res.json(questions); } catch(error) { - res.status(error.response.status).json({ error: error.response.data.error }); + res.status(500).json({ error: error.message }); } }); +// Endpoints para la generación de preguntas + app.get('/generateQuestion', async(req,res)=> { try{ - const instancia = newquestion.ejecutarOperaciones(); - + await newquestion.ejecutarOperaciones(); } catch(error) { - res.status(error.response.status).json({ error: error.response.data.error }); - } - + res.status(500).json({ error: error.message }); + } }); diff --git a/questionservice/questionGeneration.js b/questionservice/questionGeneration.js index 1d797bd5..642861d1 100644 --- a/questionservice/questionGeneration.js +++ b/questionservice/questionGeneration.js @@ -7,13 +7,18 @@ const guardarPregunta = new GuardarPregunta(); class GenerarPregunta { // Método para ejecutar las operaciones async ejecutarOperaciones() { - await preguntaWiki.leerYSacarConsultas(); + try{ + await preguntaWiki.leerYSacarConsultas(); - //si se ha generado pregunta, guardarla en la base de datos - if (preguntaWiki.obtenerPregunta() !== undefined) { - console.log("Pregunta generada: ", preguntaWiki.obtenerPregunta()); - guardarPregunta.guardarEnBaseDatos(preguntaWiki.obtenerPregunta()); + //si se ha generado pregunta, guardarla en la base de datos + if (preguntaWiki.obtenerPregunta() !== undefined) { + console.log("Pregunta generada: ", preguntaWiki.obtenerPregunta()); + guardarPregunta.guardarEnBaseDatos(preguntaWiki.obtenerPregunta()); + } } + catch(error){ + throw new Error(error.message); + }; } } From 85c91229397ced5b5e59f393c4b1dc0110424da0 Mon Sep 17 00:00:00 2001 From: bidof Date: Wed, 3 Apr 2024 00:50:20 +0200 Subject: [PATCH 05/79] agregar popups indicando que te has unido a la sala o que tienes errores , y nuevo evento que maneja el error de unirse a la sala --- questionservice/scheduler.js | 2 +- roomservice/RoomQuestions.js | 18 ++++------ roomservice/room-service.js | 4 ++- webapp/src/App.js | 4 +-- webapp/src/components/rooms/JoinRoom.js | 45 ++++++++++++++++++++++--- 5 files changed, 53 insertions(+), 20 deletions(-) diff --git a/questionservice/scheduler.js b/questionservice/scheduler.js index 46eb0d94..b9c6f45a 100644 --- a/questionservice/scheduler.js +++ b/questionservice/scheduler.js @@ -20,7 +20,7 @@ class Scheduler { } start() { - cron.schedule('*/1 * * * *', async () => { + cron.schedule('*/30 * * * *', async () => { try { await this.generarPregunta(); } diff --git a/roomservice/RoomQuestions.js b/roomservice/RoomQuestions.js index 19885d9f..900b03ae 100644 --- a/roomservice/RoomQuestions.js +++ b/roomservice/RoomQuestions.js @@ -11,31 +11,27 @@ class RoomQuestions{ } async joinRoom(id, username, socket) { try { - if (id != null && id !== undefined) { + if (id && id.trim() !== '') { // Comprueba que la id no sea null, undefined o un string vacío if (this.rooms.has(id)) { // Verifica si la sala existe let userList = this.rooms.get(id); - // Verifica si el usuario ya existe en la sala + // Verifica si el usuario ya existe en la sala if (!userList.includes(username)) { userList.push(username); this.rooms.set(id, userList); } - // console.log("Rooms after adding: " + JSON.stringify([...this.rooms])); // mostrar los usuarios de la sala - //asociar el socket a la sala + // Asociar el socket a la sala socket.join(id); // Emitir evento 'roomJoined' solo al usuario que se acaba de unir a la sala socket.emit('roomJoined', id); - - - this.emitCurrentUsers(id, socket); + console.log(`Usuario ${username} se ha unido a la sala ${id}`); } else { - throw new Error("la sala no existe "); + throw new Error("la sala no existe"); + } - } else { - throw new Error("ID de sala inválido"); } } catch (error) { - console.log(error); + throw new Error('Error al unirse a la sala:', error); } } diff --git a/roomservice/room-service.js b/roomservice/room-service.js index e449cbd0..c3525965 100644 --- a/roomservice/room-service.js +++ b/roomservice/room-service.js @@ -34,10 +34,12 @@ io.on('connection', (socket) => { try { console.log("microservicio--joinroom , valor id "+id+" username "+username); await roomQuestions.joinRoom(id, username,socket); - console.log(`Usuario ${username} se ha unido a la sala ${id}`); + // console.log(`Usuario ${username} se ha unido a la sala ${id}`); } catch (error) { console.log(`Error al unir al usuario a la sala: ${error.message}`); + socket.emit('roomErrorJoining'); + console.log("error al unir al usuario a la sala lanzado envento RoomErrorJoining"); } }); // Evento personalizado para crear una sala diff --git a/webapp/src/App.js b/webapp/src/App.js index 9ed1e029..12edaa06 100644 --- a/webapp/src/App.js +++ b/webapp/src/App.js @@ -63,8 +63,8 @@ const App = () => { } /> - } /> - } /> + } /> + } /> } /> diff --git a/webapp/src/components/rooms/JoinRoom.js b/webapp/src/components/rooms/JoinRoom.js index 748042bc..5bd6d05f 100644 --- a/webapp/src/components/rooms/JoinRoom.js +++ b/webapp/src/components/rooms/JoinRoom.js @@ -1,8 +1,8 @@ -import React, { useState } from 'react'; +import React, { useState,useEffect } from 'react'; import { useNavigate } from 'react-router-dom'; import { Box, Input, Button, Text, Tooltip, Icon, Center, VStack, HStack } from "@chakra-ui/react"; import { InfoOutlineIcon } from '@chakra-ui/icons'; - +import { useToast } from "@chakra-ui/react"; import socket from './socket'; const JoinRoomForm = () => { const navigate = useNavigate(); @@ -12,15 +12,49 @@ const JoinRoomForm = () => { const apiEndpoint = process.env.REACT_APP_API_URI ||'http://localhost:8000'; const username = localStorage.getItem('username'); + const toast = useToast(); + + useEffect(() => { + socket.on('roomJoined', (roomId) => { + navigate(`/room/${roomId}`); + toast({ + title: "Unido a la sala con éxito.", + description: `Te has unido a la sala ${roomId}.`, + status: "success", + duration: 9000, + isClosable: true, + }); + }); + + socket.on('roomErrorJoining', () => { + console.log("escuchando evento error al unirse a sala"); + toast({ + title: "Error al unirse a la sala.", + description: `No te has podido unir a la sala.`, + status: "error", + duration: 9000, + isClosable: true, + }); + + }); + + + return () => { + // Limpiar los manejadores de eventos cuando el componente se desmonte + socket.off('roomJoined'); + socket.off('roomErrorJoining'); + }; + }, []); + const handleJoinRoom = async () => { + + try { setIsLoading(true); console.log("id de la sala: "+roomId+" username: "+username); socket.emit('joinRoom', {id:roomId, username:username }); - socket.on('roomJoined', (roomId) => { - navigate(`/room/${roomId}`); - }); + } catch (error) { console.error('Error al unirse a la sala:', error); } finally { @@ -28,6 +62,7 @@ const JoinRoomForm = () => { } }; + return ( From 8a68396ba738822fd3e0429c9216149b396224ec Mon Sep 17 00:00:00 2001 From: bidof Date: Wed, 3 Apr 2024 17:10:06 +0200 Subject: [PATCH 06/79] test del roomQuestion completados --- ...service.test.js => room-questions.test.js} | 75 +++++++++++++------ 1 file changed, 52 insertions(+), 23 deletions(-) rename roomservice/{room-service.test.js => room-questions.test.js} (50%) diff --git a/roomservice/room-service.test.js b/roomservice/room-questions.test.js similarity index 50% rename from roomservice/room-service.test.js rename to roomservice/room-questions.test.js index 2d64fc5e..259ac5e0 100644 --- a/roomservice/room-service.test.js +++ b/roomservice/room-questions.test.js @@ -8,13 +8,14 @@ jest.mock('axios'); describe('RoomQuestions', () => { let roomQuestions; let io; - let socket; + let socket1; + let socket2; beforeEach(() => { const server = http.createServer(); server.listen(); io = new Server(server); - socket = { + socket1 = { emit: jest.fn().mockImplementation((event, message) => { console.log(`Event: ${event}, Message: ${message}`); }), @@ -24,8 +25,30 @@ describe('RoomQuestions', () => { values: jest.fn().mockReturnThis(), next: jest.fn().mockReturnThis(), value: jest.fn() - } + }, + //la funcion to simula que lo mandas al resto de jugadores + //en la practica lo manda a la sala de socket.io + to: jest.fn(() => ({ + emit: (event, message) => socket2.emit(event, message) + })) }; + socket2 = { + emit: jest.fn().mockImplementation((event, message) => { + console.log(`Event: ${event}, Message: ${message}`); + }), + join: jest.fn(), + of: jest.fn().mockReturnThis(), + sockets: { + values: jest.fn().mockReturnThis(), + next: jest.fn().mockReturnThis(), + value: jest.fn() + }, + //la funcion to simula que lo mandas al resto de jugadores + //en la practica lo manda a la sala de socket.io + to: jest.fn(() => ({ + emit: (event, message) => socket1.emit(event, message) + })) + }; roomQuestions = new RoomQuestions(io); }); @@ -35,60 +58,66 @@ describe('RoomQuestions', () => { test('should create room', async () => { const username = 'testUser'; - const id = await roomQuestions.createRoom(username, socket); + const id = await roomQuestions.createRoom(username, socket1); expect(roomQuestions.rooms.has(id)).toBe(true); }); test('should join room', async () => { const username = 'testUser'; - const id = await roomQuestions.createRoom(username, socket); - await roomQuestions.joinRoom(id, 'anotherUser', socket); + const id = await roomQuestions.createRoom(username, socket1); + await roomQuestions.joinRoom(id, 'anotherUser', socket2); expect(roomQuestions.rooms.get(id)).toContain('anotherUser'); }); test('should start game if enough players', async () => { const username = 'testUser'; - const id = await roomQuestions.createRoom(username, socket); - await roomQuestions.joinRoom(id, 'anotherUser', socket); + const id = await roomQuestions.createRoom(username, socket1); + await roomQuestions.joinRoom(id, 'anotherUser', socket2); axios.get.mockResolvedValue({ data: 'questions' }); - await roomQuestions.startGame(id, socket); - expect(socket.emit).toHaveBeenCalledWith('gameStarted', 'questions'); + await roomQuestions.startGame(id, socket1); + expect(socket1.emit).toHaveBeenCalledWith('gameStarted', 'questions'); }); + test('should not start game without enough players', async () => { const username = 'testUser'; - const id = await roomQuestions.createRoom(username, socket); + const id = await roomQuestions.createRoom(username, socket1); // Try to start game with only one user - await roomQuestions.startGame(id, socket); + await roomQuestions.startGame(id, socket1); // Check that the game did not start - expect(socket.emit).not.toHaveBeenCalledWith('gameStarted', expect.anything()); + expect(socket1.emit).not.toHaveBeenCalledWith('gameStarted', expect.anything()); }); test('should end game and determine winner', async () => { const username = 'testUser'; const anotherUser = 'anotherUser'; - const id = await roomQuestions.createRoom(username, socket); - await roomQuestions.joinRoom(id, anotherUser, socket); - await roomQuestions.joinRoom(id, username, socket); + const id = await roomQuestions.createRoom(username, socket1); + await roomQuestions.joinRoom(id, anotherUser, socket2); + await roomQuestions.joinRoom(id, username, socket1); // Check that there are only two users in the room const roomUsers = roomQuestions.getRoomUsers(id); expect(roomUsers.length).toBe(2); // Start game for both users - await roomQuestions.startGame(id, socket); + await roomQuestions.startGame(id, socket1); // Check that the game started - expect(socket.emit).toHaveBeenCalledWith('gameStarted', expect.anything()); + expect(socket1.emit).toHaveBeenCalledWith('gameStarted', expect.anything()); + + // End game for first user + await roomQuestions.endGame(id, { user: username, correctas: 5, incorrectas: 0, tiempoTotal: 10 }, socket1); + + // End game for second user + await roomQuestions.endGame(id, { user: anotherUser, correctas: 1, incorrectas: 4, tiempoTotal: 20 }, socket2); - // End game for both users - await roomQuestions.endGame(id, { user: username, correctas: 5, incorrectas: 0, tiempoTotal: 10 }, socket); - await roomQuestions.endGame(id, { user: anotherUser, correctas: 1, incorrectas: 4, tiempoTotal: 20 }, socket); + // Check that the user 2 recived the game ended event + expect(socket2.emit).toHaveBeenCalledWith('gameEnded', 'testUser'); + // Check that the user 1 recived the game ended event + expect(socket1.emit).toHaveBeenCalledWith('gameEnded', 'testUser'); - // Check that the game ended - expect(socket.emit).toHaveBeenCalledWith('gameEnded', expect.any(String)); }); }); \ No newline at end of file From f0eb10dea181ad4d57f8803dfe5b0cd6413a1564 Mon Sep 17 00:00:00 2001 From: bidof Date: Wed, 3 Apr 2024 18:09:04 +0200 Subject: [PATCH 07/79] test creado y modificado el room service par que sea posible testear el servidor --- roomservice/room-service.js | 4 +++- roomservice/room-service.test.js | 36 ++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 roomservice/room-service.test.js diff --git a/roomservice/room-service.js b/roomservice/room-service.js index c3525965..08ed6a8a 100644 --- a/roomservice/room-service.js +++ b/roomservice/room-service.js @@ -79,4 +79,6 @@ io.on('connection', (socket) => { }); // Iniciar el servidor -server.listen(port, () => console.log(`Servidor corriendo en el puerto ${port}`)); \ No newline at end of file +server.listen(port, () => console.log(`Servidor corriendo en el puerto ${port}`)); + +module.exports = server; \ No newline at end of file diff --git a/roomservice/room-service.test.js b/roomservice/room-service.test.js new file mode 100644 index 00000000..c69918a5 --- /dev/null +++ b/roomservice/room-service.test.js @@ -0,0 +1,36 @@ +const io = require('socket.io-client'); +const server = require('./room-service'); +let socket; +let port =8005; +beforeEach((done) => { + // Setup + socket = io.connect('http://localhost:'+'8005', { + 'reconnection delay' : 0 + , 'reopen delay' : 0 + , 'force new connection' : true + }); + + socket.on('connect', () => { + done(); + }); +}); + +afterEach((done) => { + // Cleanup + if(socket.connected) { + socket.disconnect(); + } + done(); +}); + +describe('room-service.js', () => { + it('should create a room correctly', (done) => { + socket.emit('createRoom', { username:'username' }); + socket.on('roomCreated', (data) => { + expect(data).toBeDefined(); + done(); + }); + }); + + // Add more tests for other events +}); \ No newline at end of file From a55f55a419debb3250e976e9430b17955573e37c Mon Sep 17 00:00:00 2001 From: bidof Date: Wed, 3 Apr 2024 18:48:27 +0200 Subject: [PATCH 08/79] agregar test completos del room-service --- roomservice/RoomQuestions.js | 3 + roomservice/room-service.test.js | 110 +++++++++++++++++++++++++++++-- 2 files changed, 108 insertions(+), 5 deletions(-) diff --git a/roomservice/RoomQuestions.js b/roomservice/RoomQuestions.js index 900b03ae..89db5fa6 100644 --- a/roomservice/RoomQuestions.js +++ b/roomservice/RoomQuestions.js @@ -10,6 +10,7 @@ class RoomQuestions{ this.io = io; } async joinRoom(id, username, socket) { + console.log("valores del join room params id "+id+" username "+username+" socket "+socket); try { if (id && id.trim() !== '') { // Comprueba que la id no sea null, undefined o un string vacío if (this.rooms.has(id)) { // Verifica si la sala existe @@ -26,11 +27,13 @@ class RoomQuestions{ socket.emit('roomJoined', id); console.log(`Usuario ${username} se ha unido a la sala ${id}`); } else { + console.log(`La sala con id ${id} no existe.`); throw new Error("la sala no existe"); } } } catch (error) { + console.log(`Error al unirse a la sala: ${error.message}`); throw new Error('Error al unirse a la sala:', error); } } diff --git a/roomservice/room-service.test.js b/roomservice/room-service.test.js index c69918a5..160e1891 100644 --- a/roomservice/room-service.test.js +++ b/roomservice/room-service.test.js @@ -1,14 +1,28 @@ const io = require('socket.io-client'); const server = require('./room-service'); -let socket; +const axios = require('axios'); +let socket,socket2; let port =8005; + +jest.mock('axios'); + +// Antes de tu prueba +axios.get.mockResolvedValue({ data: 'mocked data' }); + beforeEach((done) => { + // Setup socket = io.connect('http://localhost:'+'8005', { 'reconnection delay' : 0 , 'reopen delay' : 0 , 'force new connection' : true }); + socket2 = io.connect('http://localhost:'+'8005', { + 'reconnection delay' : 0 + , 'reopen delay' : 0 + , 'force new connection' : true + }); + socket.on('connect', () => { done(); @@ -20,17 +34,103 @@ afterEach((done) => { if(socket.connected) { socket.disconnect(); } + if(socket2.connected) { + socket.disconnect(); + } done(); }); describe('room-service.js', () => { - it('should create a room correctly', (done) => { - socket.emit('createRoom', { username:'username' }); - socket.on('roomCreated', (data) => { + // Test para crear una sala + it('should create a room correctly', (done) => { + socket.emit('createRoom', { username:'username' }); + socket.on('roomCreated', (data) => { + expect(data).toBeDefined(); + done(); + }); +}); +// Test para unirte a una sala siendo otro usuario +it('should join a room correctly', (done) => { + socket.emit('createRoom', { username:'usernamePrimero' }); + socket.on('roomCreated', (data) => { + let roomId = data.toString(); // Convert the room ID to a string + socket2.emit('joinRoom', { id: roomId, username: 'username0Segundo' }); + socket2.on('roomJoined', (data) => { expect(data).toBeDefined(); done(); }); }); +}); + +it('should show the users once the page is loaded', (done) => { + socket.emit('createRoom', { username:'usernamePrimero' }); + socket.on('roomCreated', (data) => { + let roomId = data.toString(); // Convert the room ID to a string + socket2.emit('joinRoom', { id: roomId, username: 'username0Segundo' }); + socket2.on('roomJoined', (data) => { + socket2.emit('ready', { id: roomId }); + socket2.on('currentUsers', (data) => { + expect(data).toBeDefined(); + done(); + }); + }); + }); +}); + + //test start game + it('should start game', (done) => { + // Mock axios.get to simulate getting questions + axios.get.mockResolvedValue({ data: 'questions' }); + + socket.emit('createRoom', { username:'usernamePrimero' }); + socket.on('roomCreated', (data) => { + let roomId = data.toString(); // Convert the room ID to a string + socket2.emit('joinRoom', { id: roomId, username: 'username0Segundo' }); + socket2.on('roomJoined', (data) => { + expect(data).toBeDefined(); + done(); + }); + + // Start game + socket.emit('startGame', { id: roomId }); + socket.on('gameStarted', (data) => { + expect(data).toEqual('questions'); + done(); + }); + }); + }); + + it('should end a game', (done) => { + // Mock axios.get to simulate getting questions + axios.get.mockResolvedValue({ data: 'questions' }); + + socket.emit('createRoom', { username:'usernamePrimero' }); + socket.on('roomCreated', (data) => { + let roomId = data.toString(); // Convert the room ID to a string + socket2.emit('joinRoom', { id: roomId, username: 'username0Segundo' }); + socket2.on('roomJoined', (data) => { + expect(data).toBeDefined(); + done(); + }); + + // Start game + socket.emit('startGame', { id: roomId }); + socket.on('gameStarted', (data) => { + expect(data).toEqual('questions'); + done(); + }); + + //End game + socket.emit('endGame', { id: roomId, results: { user: 'username1', correctas: 5, incorrectas: 0, tiempoTotal: 60 } }); + socket2.emit('endGame', { id: roomId, results: { user: 'username2', correctas: 4, incorrectas: 1, tiempoTotal: 60 } }); + socket.on('gameEnded', (winner) => { + expect(winner).toBeDefined(); + done(); + }); + }); + }); + + + - // Add more tests for other events }); \ No newline at end of file From 86b0db47ddccda2cb8ab86dd9253e74e2ee3a449 Mon Sep 17 00:00:00 2001 From: bidof Date: Wed, 3 Apr 2024 18:51:48 +0200 Subject: [PATCH 09/79] agregas caso de test que me falto , sala no existente --- roomservice/room-service.test.js | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/roomservice/room-service.test.js b/roomservice/room-service.test.js index 160e1891..5e550a19 100644 --- a/roomservice/room-service.test.js +++ b/roomservice/room-service.test.js @@ -76,7 +76,19 @@ it('should show the users once the page is loaded', (done) => { }); }); }); - + //caso de que la sala no exista + it('should not join a room that dont exist ', (done) => { + let roomId= "1234Noexiste"; + socket2.emit('joinRoom', { id: roomId, username: 'username0Segundo' }); + socket2.on('roomErrorJoining', () => { + + done(); + }); + + + }); + + //test start game it('should start game', (done) => { // Mock axios.get to simulate getting questions From a176e097575c74b7f57270cbbf4bcffaffe9d92b Mon Sep 17 00:00:00 2001 From: UO283535 Date: Wed, 3 Apr 2024 19:04:09 +0200 Subject: [PATCH 10/79] =?UTF-8?q?A=C3=B1ado=20cambios=20en=20el=20punto=20?= =?UTF-8?q?2=20de=20la=20documentacion?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/src/02_architecture_constraints.adoc | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/docs/src/02_architecture_constraints.adoc b/docs/src/02_architecture_constraints.adoc index dadf0adb..0abe6cd9 100644 --- a/docs/src/02_architecture_constraints.adoc +++ b/docs/src/02_architecture_constraints.adoc @@ -1,13 +1,17 @@ [[section-architecture-constraints]] == Architecture Constraints +Each of the following constraints plays a crucial role in the formation of the software architecture and in determining the best practices +for the development team. Next, we will explain the ones that the team must comply with. + 1.Technical Constraints [options = "header", cols = "1,2"] |=== | Constraint | Description | Wikidata | Wikidata is a knowledge base that provides data sources, used to obtain information for the game. In this case, it is mandatory. | Docker | Software that allows automating the deployment of applications. The application will be running on a Docker host. -| GitHub | Platform that allows us to have a repository where to develop the project and perform different actions such as creating issues or tasks. +| GitHub | A cloud storage service for collaborative application development, which facilitates effective communication, +manages the tracking of different project versions, and distributes responsibilities equitably among team members. |=== 2.Organizational Constraints @@ -15,9 +19,10 @@ |=== | Constraint | Description | Team | A team formed by 5 individuals who will need to learn to work and coordinate together. -| Time | We need to learn how to manage time effectively as we must optimize the time between meetings, in-class work, and homework. The lack of experience and the learning curve associated with new technologies can lead to issues. -| New Technologies | The majority of technologies are new to us, and we need to learn how to work with them. -| Communication Difficulties | The lack of familiarity within the team can lead to misunderstandings or a lack of communication and coordination. +| Time | The team will need to present the project with new advancements, adapting to the anticipated delivery dates, +as well as what has been established in the minutes taken during the weekly team meetings. +| Meetings | Every week there will be a meeting among the team members where problems will be discussed, the work that each member +should do for the next meeting will be established, and the work done to date will be reviewed. |=== 3.Convention Constraints @@ -25,6 +30,6 @@ |=== | Constraint | Description | Documentation | Arc42 is a template for architecture documentation. It is the one we should use to generate the documentation. -| Code | The code should follow an order that does not pose any problem when understanding it for another team member. +| Clean Code | he code should follow an order that does not pose any problem for another team member to understand. In addition, good practices such as naming conventions should be followed. | Structure | The project must follow a fixed structure, both the documentation and the code must be done under the same standards. |=== \ No newline at end of file From 98e7a4701f2326354698fee555a0192ec66d4915 Mon Sep 17 00:00:00 2001 From: UO283535 Date: Wed, 3 Apr 2024 19:05:50 +0200 Subject: [PATCH 11/79] Modificaciones punto 8 de la documentacion --- docs/src/08_concepts.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/08_concepts.adoc b/docs/src/08_concepts.adoc index 7c362c6b..3214d6d6 100644 --- a/docs/src/08_concepts.adoc +++ b/docs/src/08_concepts.adoc @@ -30,4 +30,4 @@ In development... === Development concepts - * Test: Realizadas pruebas de testing del microservicio gateway-service y del componente de webapp Game, + * Test: Realizadas pruebas de testing del microservicio gateway-service y del componente de webapp Game. From 231da98413957636a1f1e43e88bd7502a72b24b2 Mon Sep 17 00:00:00 2001 From: UO283535 Date: Wed, 3 Apr 2024 19:08:32 +0200 Subject: [PATCH 12/79] Modificaciones punto 8 de la documentacion --- docs/src/08_concepts.adoc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/src/08_concepts.adoc b/docs/src/08_concepts.adoc index 3214d6d6..d8e899c5 100644 --- a/docs/src/08_concepts.adoc +++ b/docs/src/08_concepts.adoc @@ -30,4 +30,5 @@ In development... === Development concepts - * Test: Realizadas pruebas de testing del microservicio gateway-service y del componente de webapp Game. + * Test: To test the correct operation of the system, we have decided to carry out unit tests for the application components as well as for the services. + Testing has been carried out on the gateway-service microservice and the Game webapp component. \ No newline at end of file From a8e90eb95548978420d4eb76462a52a80308e40e Mon Sep 17 00:00:00 2001 From: UO283535 Date: Wed, 3 Apr 2024 19:36:03 +0200 Subject: [PATCH 13/79] =?UTF-8?q?Arreglo=20tests=20a=20falta=20de=20a?= =?UTF-8?q?=C3=B1adir=20nuevos=20casos=20de=20uso?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gatewayservice/gateway-service.test.js | 229 ++++++++++--------------- 1 file changed, 91 insertions(+), 138 deletions(-) diff --git a/gatewayservice/gateway-service.test.js b/gatewayservice/gateway-service.test.js index 0cafc9c1..ee817ea2 100644 --- a/gatewayservice/gateway-service.test.js +++ b/gatewayservice/gateway-service.test.js @@ -17,97 +17,83 @@ describe('Gateway Service', () => { return Promise.resolve({ data: { userId: 'mockedUserId' } }); } }); - // Test de /health endpoint it('should perform the health request', async () => { const response = await request(app).get('/health').send(); - expect(response.statusCode).toBe(200); }); - // Test /login endpoint - it('should forward login request to auth service', async () => { - const response = await request(app) - .post('/login') - .send({ username: 'testuser', password: 'testpassword' }); - - expect(response.statusCode).toBe(200); - expect(response.body.token).toBe('mockedToken'); -}); - - - -// Test /adduser endpoint -it('should forward add user request to user service', async () => { - const response = await request(app) - .post('/adduser') - .send({ username: 'newuser', password: 'newpassword' }); - +it('should perform the getQuestion request', async () => { + const response = await request(app).get('/getQuestion').send(); expect(response.statusCode).toBe(200); - expect(response.body.userId).toBe('mockedUserId'); -}); - -it('should handle authentication error', async () => { - const authError = new Error('Authentication failed'); - authError.response = { - status: 401, - data: { error: 'Invalid credentials' }, + const data = { + pregunta: '¿Cuál es la capital de Francia?', + respuestas: ['Berlin', 'Paris', 'Londres', 'Madrid'], + correcta: 'Helsinki', }; - - // Simula un error en la llamada al servicio de autenticación - axios.post.mockImplementationOnce(() => Promise.reject(authError)); - - // Realiza la solicitud al endpoint - const response = await request(app).post('/login').send({ /* datos de autenticación */ }); - - // Verifica que la respuesta tenga un código de estado 401 - expect(response.statusCode).toBe(401); - expect(response.body.error).toBe('Invalid credentials'); + axios.get.mockImplementationOnce(() => Promise.resolve({ data })); }); - + // Test /login endpoint + it('should forward login request to auth service', async () => { + const response = await request(app) + .post('/login') + .send({ username: 'testuser', password: 'testpassword' }); + expect(response.statusCode).toBe(200); + expect(response.body.token).toBe('mockedToken'); + }); + + // Test /adduser endpoint + it('should forward add user request to user service', async () => { + const response = await request(app) + .post('/adduser') + .send({ username: 'newuser', password: 'newpassword' }); + expect(response.statusCode).toBe(200); + expect(response.body.userId).toBe('mockedUserId'); + }); - it('should handle authentication error', async () => { - const authError = new Error('Authentication failed'); - authError.response = { - status: 401, - data: { error: 'Invalid credentials' }, - }; - - // Simula un error en la llamada al servicio de autenticación - axios.post.mockImplementationOnce(() => Promise.reject(authError)); - - // Realiza la solicitud al endpoint - const response = await request(app).post('/adduser').send({ /* datos de autenticación */ }); - - // Verifica que la respuesta tenga un código de estado 401 - expect(response.statusCode).toBe(401); - expect(response.body.error).toBe('Invalid credentials'); - }); - //CAso de prueba para un endpoint inexistente - - it('should return 404 for nonexistent endpoint', async()=>{ const response = await request(app) .get('/nonexistent'); - expect(response.statusCode).toBe(404); }); - - -//*********************ENDPOINTS DEL QUESTION SERVICE********************************************* */ - - - + // Test /getQuestion endpoint + axios.get.mockImplementation((url, data) => { + if (url.endsWith("/getQuestion")) { + return Promise.resolve({ + data: [ + { + pregunta: "¿Cuál es la capital de España?", + respuestas: ["Madrid", "Paris", "Londres", "Berlin"], + correcta: "Madrid" + } + ], + }); + } + }); //Verifica si el manejo de errores funciona correctamente cuando la llamada al servicio de preguntas falla. it('should handle error when fetching question', async () => { const questionServiceUrl = 'http://localhost:8003'; const errorMessage = 'Network Error'; axios.get.mockImplementationOnce(() => Promise.reject(new Error(errorMessage))); }); - - + it('should forward get question request to question service', async () => { + const questionServiceUrl = 'http://localhost:8003'; + const expectedQuestion = '¿Cuál es la capital de Francia?'; + const expectedOptions = ['Berlin', 'Paris', 'Londres', 'Madrid']; + const expectedCorrectAnswer = 'Helsinki'; + // Simula una llamada exitosa al servicio de preguntas + axios.get.mockImplementationOnce(() => Promise.resolve({ data })); + // Realiza la solicitud al endpoint + const response = await request(app).get('/getQuestion').send(); + // Verifica que la respuesta tenga un código de estado 200 + expect(response.statusCode).toBe(200); + // Verifica que la pregunta y las opciones sean correctas + expect(response.body.pregunta).toBe(expectedQuestion); + expect(response.body.respuestas).toEqual(expect.arrayContaining(expectedOptions)); + expect(response.body.correcta).toBe(expectedCorrectAnswer); + }); it('should forward get question request to question generate service', async () => { const questionServiceUrl = 'http://localhost:8003/generateQuestions'; const data = { @@ -118,23 +104,50 @@ it('should handle authentication error', async () => { axios.get.mockImplementationOnce(() => Promise.resolve({ data })); }); - //Verifica si el manejo de errores funciona correctamente cuando la llamada al servicio de preguntas falla. it('should handle error when fetching question', async () => { const questionServiceUrl = 'http://localhost:8003/generateQuestions'; const errorMessage = 'Network Error'; axios.get.mockImplementationOnce(() => Promise.reject(new Error(errorMessage))); }); - - //Verifica si el manejo de errores funciona correctamente cuando la llamada al servicio de preguntas modo basico falla. - it('should handle error when fetching question mode basic', async () => { - const questionServiceUrl = 'http://localhost:8003/getQuestionModoBasico'; - const errorMessage = 'Network Error'; - axios.get.mockImplementationOnce(() => Promise.reject(new Error(errorMessage))); + it('should handle authentication error', async () => { + const authError = new Error('Authentication failed'); + authError.response = { + status: 401, + data: { error: 'Invalid credentials' }, + }; + + // Simula un error en la llamada al servicio de autenticación + axios.post.mockImplementationOnce(() => Promise.reject(authError)); + + // Realiza la solicitud al endpoint + const response = await request(app).post('/login').send({ /* datos de autenticación */ }); + + // Verifica que la respuesta tenga un código de estado 401 + expect(response.statusCode).toBe(401); + expect(response.body.error).toBe('Invalid credentials'); }); - -it('should return an error when the question service request fails', async () => { + + + it('should handle authentication error', async () => { + const authError = new Error('Authentication failed'); + authError.response = { + status: 401, + data: { error: 'Invalid credentials' }, + }; + + // Simula un error en la llamada al servicio de autenticación + axios.post.mockImplementationOnce(() => Promise.reject(authError)); + + // Realiza la solicitud al endpoint + const response = await request(app).post('/adduser').send({ /* datos de autenticación */ }); + + // Verifica que la respuesta tenga un código de estado 401 + expect(response.statusCode).toBe(401); + expect(response.body.error).toBe('Invalid credentials'); + }); + it('should return an error when the question service request fails', async () => { // Mock the axios.get method to reject the promise axios.get.mockImplementationOnce(() => Promise.reject(new Error('Error al realizar la solicitud al servicio de preguntas')) @@ -148,65 +161,5 @@ it('should return an error when the question service request fails', async () => expect(response.body.error).toBeDefined(); expect(response.body.error).toEqual('Error al realizar la solicitud al servicio de preguntas'); }); -it('should return an error when the question service request fails', async () => { - // Mock the axios.get method to reject the promise - axios.get.mockImplementationOnce(() => - Promise.reject(new Error('Error al realizar la solicitud al servicio de preguntas')) - ); - - const response = await request(app).get('/getQuestionModoBasico'); - - expect(response.statusCode).toBe(500); - expect(response.body.error).toBeDefined(); - expect(response.body.error).toEqual('Error al realizar la solicitud al servicio de preguntas'); - }); - - it('should return an error when generating questions fails', async () => { - const errorMessage = 'Error al realizar la solicitud al servicio de generacion de preguntas'; - axios.get.mockRejectedValue(new Error(errorMessage)); - - const response = await request(app).get('/generateQuestions'); - - expect(response.statusCode).toBe(500); - expect(response.body.error).toBeDefined(); - expect(response.body.error).toEqual(errorMessage); - }); - - it('should return question data when the request is successful', async () => { - // Mock the axios.get method to resolve the promise - axios.get.mockResolvedValueOnce({ data: { question: 'mockedQuestionData' } }); - - const response = await request(app).get('/getQuestionModoBasico'); - - expect(response.statusCode).toBe(200); - expect(response.body).toEqual({ question: 'mockedQuestionData' }); - }); -//***************************** ENDPOINTS HISTORY-SERVICE*************************************************** */ - -it('should return an error when the history service request fails', async () => { - // Mock the axios.get method to reject the promise - axios.get.mockImplementationOnce(() => - Promise.reject(new Error('Error al realizar la solicitud al servicio de historial')) - ); - - const response = await request(app).get('/getHistoryDetallado'); - - expect(response.statusCode).toBe(500); - expect(response.body.error).toBeDefined(); - expect(response.body.error).toEqual('Error al realizar la solicitud al servicio de historial'); -}); - -it('should return an error when the total history service request fails', async () => { - // Mock the axios.get method to reject the promise - axios.get.mockImplementationOnce(() => - Promise.reject(new Error('Error al realizar la solicitud al servicio de historial')) - ); - - const response = await request(app).get('/getHistoryTotal'); - - expect(response.statusCode).toBe(500); - expect(response.body.error).toBeDefined(); - expect(response.body.error).toEqual('Error al realizar la solicitud al servicio de historial'); -}); - + }); \ No newline at end of file From 372c06f56d6c669f738612a1b0b90d1e449e1b09 Mon Sep 17 00:00:00 2001 From: bidof Date: Wed, 3 Apr 2024 19:42:30 +0200 Subject: [PATCH 14/79] test del JoinRoom --- webapp/package-lock.json | 10 ++++ webapp/package.json | 1 + webapp/src/components/rooms/JoinRoom.test.js | 62 ++++++++++++++++++++ 3 files changed, 73 insertions(+) create mode 100644 webapp/src/components/rooms/JoinRoom.test.js diff --git a/webapp/package-lock.json b/webapp/package-lock.json index 9510b152..2cff6d62 100644 --- a/webapp/package-lock.json +++ b/webapp/package-lock.json @@ -33,6 +33,7 @@ "jest": "^29.3.1", "jest-cucumber": "^3.0.1", "jest-environment-node": "^29.7.0", + "mock-socket": "^9.3.1", "mongodb-memory-server": "^9.1.4", "puppeteer": "^21.7.0", "serve": "^14.2.1", @@ -20480,6 +20481,15 @@ "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", "dev": true }, + "node_modules/mock-socket": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/mock-socket/-/mock-socket-9.3.1.tgz", + "integrity": "sha512-qxBgB7Qa2sEQgHFjj0dSigq7fX4k6Saisd5Nelwp2q8mlbAFh5dHV9JTTlF8viYJLSSWgMCZFUom8PJcMNBoJw==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, "node_modules/mongodb": { "version": "5.9.2", "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-5.9.2.tgz", diff --git a/webapp/package.json b/webapp/package.json index 9691086c..f81360a0 100644 --- a/webapp/package.json +++ b/webapp/package.json @@ -54,6 +54,7 @@ "jest": "^29.3.1", "jest-cucumber": "^3.0.1", "jest-environment-node": "^29.7.0", + "mock-socket": "^9.3.1", "mongodb-memory-server": "^9.1.4", "puppeteer": "^21.7.0", "serve": "^14.2.1", diff --git a/webapp/src/components/rooms/JoinRoom.test.js b/webapp/src/components/rooms/JoinRoom.test.js new file mode 100644 index 00000000..7fff8f4c --- /dev/null +++ b/webapp/src/components/rooms/JoinRoom.test.js @@ -0,0 +1,62 @@ +import { render, screen, act } from '@testing-library/react'; +import { BrowserRouter as Router } from 'react-router-dom'; +import JoinRoom from './JoinRoom'; +import { ChakraProvider } from '@chakra-ui/react'; +import socket from './socket'; + +jest.mock('./socket', () => ({ + on: jest.fn(), + emit: jest.fn(), + off: jest.fn(), +})); + +test('renders popup if dont exists a room ', async () => { + const spy = jest.spyOn(socket, 'on'); + + render( + + + + + + ); + + act(() => { + spy.mock.calls[1][1](); // Llama al segundo manejador de eventos registrado con socket.on + }); + + const toastText = await screen.findByText('Error al unirse a la sala.', {}, { timeout: 5000 }); + expect(toastText).toBeInTheDocument(); +}); + +test('renders JoinRoom component', () => { + render( + + + + + + ); + + const joinRoomElements = screen.getAllByText(/Unirse a Sala/i); + expect(joinRoomElements.length).toBeGreaterThan(0); +}); + +test('renders success toast when roomJoined event is emitted', async () => { + const spy = jest.spyOn(socket, 'on'); + + render( + + + + + + ); + + act(() => { + spy.mock.calls[0][1]('1234'); // Llama al primer manejador de eventos registrado con socket.on + }); + + const toastText = await screen.findByText('Unido a la sala con éxito.', {}, { timeout: 5000 }); + expect(toastText).toBeInTheDocument(); +}); \ No newline at end of file From dfc10bf9991efa66f9e649151a484575a1fd6e4f Mon Sep 17 00:00:00 2001 From: lucia-sonia Date: Thu, 4 Apr 2024 16:34:10 +0200 Subject: [PATCH 15/79] preparando las cosas para la internacionalizacion --- webapp/package-lock.json | 61 +++++++++++++++++++ webapp/package.json | 4 +- webapp/src/index.js | 7 ++- webapp/src/internacionalizacion/i18n.js | 27 ++++++++ .../locales/en/translation.json | 3 + .../locales/es/translation.json | 3 + 6 files changed, 103 insertions(+), 2 deletions(-) create mode 100644 webapp/src/internacionalizacion/i18n.js create mode 100644 webapp/src/internacionalizacion/locales/en/translation.json create mode 100644 webapp/src/internacionalizacion/locales/es/translation.json diff --git a/webapp/package-lock.json b/webapp/package-lock.json index 5027553c..25126040 100644 --- a/webapp/package-lock.json +++ b/webapp/package-lock.json @@ -17,9 +17,11 @@ "@testing-library/user-event": "^14.5.2", "axios": "^1.6.5", "bootstrap": "^5.3.3", + "i18next": "^21.7.1", "react": "^18.2.0", "react-bootstrap": "^2.10.1", "react-dom": "^18.2.0", + "react-i18next": "^11.13.10", "react-icons": "^5.0.1", "react-router-dom": "^6.22.3", "react-scripts": "5.0.1", @@ -13593,6 +13595,14 @@ "node": ">= 12" } }, + "node_modules/html-parse-stringify": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz", + "integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==", + "dependencies": { + "void-elements": "3.1.0" + } + }, "node_modules/html-webpack-plugin": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.6.0.tgz", @@ -13736,6 +13746,28 @@ "node": ">=10.17.0" } }, + "node_modules/i18next": { + "version": "21.10.0", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-21.10.0.tgz", + "integrity": "sha512-YeuIBmFsGjUfO3qBmMOc0rQaun4mIpGKET5WDwvu8lU7gvwpcariZLNtL0Fzj+zazcHUrlXHiptcFhBMFaxzfg==", + "funding": [ + { + "type": "individual", + "url": "https://locize.com" + }, + { + "type": "individual", + "url": "https://locize.com/i18next.html" + }, + { + "type": "individual", + "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project" + } + ], + "dependencies": { + "@babel/runtime": "^7.17.2" + } + }, "node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -23553,6 +23585,27 @@ } } }, + "node_modules/react-i18next": { + "version": "11.18.6", + "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-11.18.6.tgz", + "integrity": "sha512-yHb2F9BiT0lqoQDt8loZ5gWP331GwctHz9tYQ8A2EIEUu+CcEdjBLQWli1USG3RdWQt3W+jqQLg/d4rrQR96LA==", + "dependencies": { + "@babel/runtime": "^7.14.5", + "html-parse-stringify": "^3.0.1" + }, + "peerDependencies": { + "i18next": ">= 19.0.0", + "react": ">= 16.8.0" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, "node_modules/react-icons": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.0.1.tgz", @@ -28284,6 +28337,14 @@ "node": ">= 0.8" } }, + "node_modules/void-elements": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz", + "integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/w3c-hr-time": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", diff --git a/webapp/package.json b/webapp/package.json index bf5957f3..538cbe6a 100644 --- a/webapp/package.json +++ b/webapp/package.json @@ -18,7 +18,9 @@ "react-icons": "^5.0.1", "react-router-dom": "^6.22.3", "react-scripts": "5.0.1", - "web-vitals": "^3.5.1" + "web-vitals": "^3.5.1", + "i18next": "^21.7.1", + "react-i18next": "^11.13.10" }, "scripts": { "start": "react-scripts start", diff --git a/webapp/src/index.js b/webapp/src/index.js index 29d225c0..1ea61022 100644 --- a/webapp/src/index.js +++ b/webapp/src/index.js @@ -5,11 +5,16 @@ import App from './App'; import reportWebVitals from './reportWebVitals'; import { ColorModeScript } from "@chakra-ui/react"; +//para internacionalizacion +import { I18nextProvider } from 'react-i18next'; +import i18n from './internacionalizacion/i18n.js'; ReactDOM.render( - + + + , , document.getElementById('root') diff --git a/webapp/src/internacionalizacion/i18n.js b/webapp/src/internacionalizacion/i18n.js new file mode 100644 index 00000000..21a4d66b --- /dev/null +++ b/webapp/src/internacionalizacion/i18n.js @@ -0,0 +1,27 @@ +//fichero de configuración de internacionalización + +import i18n from 'i18next'; +import { initReactI18next } from 'react-i18next'; + +import translationEN from './locales/en/translation.json'; +import translationES from './locales/es/translation.json'; + +i18n.use(initReactI18next).init({ + resources: { + en: { + translation: translationEN + }, + es: { + translation: translationES + } + }, + + lng: "es", //idioma predeterminado + fallbackLng: "en", //idioma de respaldo por si no funciona el idioma predeterminado + + interpolation: { + escapeValue: false + } +}); + +export default i18n; \ No newline at end of file diff --git a/webapp/src/internacionalizacion/locales/en/translation.json b/webapp/src/internacionalizacion/locales/en/translation.json new file mode 100644 index 00000000..077404aa --- /dev/null +++ b/webapp/src/internacionalizacion/locales/en/translation.json @@ -0,0 +1,3 @@ +{ + +} \ No newline at end of file diff --git a/webapp/src/internacionalizacion/locales/es/translation.json b/webapp/src/internacionalizacion/locales/es/translation.json new file mode 100644 index 00000000..544b7b4d --- /dev/null +++ b/webapp/src/internacionalizacion/locales/es/translation.json @@ -0,0 +1,3 @@ +{ + +} \ No newline at end of file From b200a9e6953352fb8f8b0337d268083be3e50992 Mon Sep 17 00:00:00 2001 From: lucia-sonia Date: Thu, 4 Apr 2024 17:15:00 +0200 Subject: [PATCH 16/79] =?UTF-8?q?a=C3=B1adida=20internacionalizacion=20en?= =?UTF-8?q?=20el=20navbar?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- webapp/package-lock.json | 26 ++++++++ webapp/package.json | 1 + webapp/public/spain_flag.png | Bin 0 -> 509 bytes webapp/public/uk_flag.png | Bin 0 -> 628 bytes webapp/src/components/navbar/NavBar.js | 58 +++++++++++++++--- .../locales/en/translation.json | 4 +- .../locales/es/translation.json | 11 +++- 7 files changed, 91 insertions(+), 9 deletions(-) create mode 100644 webapp/public/spain_flag.png create mode 100644 webapp/public/uk_flag.png diff --git a/webapp/package-lock.json b/webapp/package-lock.json index 25126040..43763ae0 100644 --- a/webapp/package-lock.json +++ b/webapp/package-lock.json @@ -11,6 +11,7 @@ "@chakra-ui/react": "^2.8.2", "@emotion/react": "^11.11.3", "@emotion/styled": "^11.11.0", + "@mui/icons-material": "^5.2.7", "@mui/material": "^5.15.14", "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^14.1.2", @@ -5672,6 +5673,31 @@ "url": "https://opencollective.com/mui-org" } }, + "node_modules/@mui/icons-material": { + "version": "5.15.15", + "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.15.15.tgz", + "integrity": "sha512-kkeU/pe+hABcYDH6Uqy8RmIsr2S/y5bP2rp+Gat4CcRjCcVne6KudS1NrZQhUCRysrTDCAhcbcf9gt+/+pGO2g==", + "dependencies": { + "@babel/runtime": "^7.23.9" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@mui/material": "^5.0.0", + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@mui/material": { "version": "5.15.14", "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.15.14.tgz", diff --git a/webapp/package.json b/webapp/package.json index 538cbe6a..dc086238 100644 --- a/webapp/package.json +++ b/webapp/package.json @@ -6,6 +6,7 @@ "@chakra-ui/react": "^2.8.2", "@emotion/react": "^11.11.3", "@emotion/styled": "^11.11.0", + "@mui/icons-material": "^5.2.7", "@mui/material": "^5.15.14", "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^14.1.2", diff --git a/webapp/public/spain_flag.png b/webapp/public/spain_flag.png new file mode 100644 index 0000000000000000000000000000000000000000..36b30d754af5da2e8fd40ea11d79b59c1945bba2 GIT binary patch literal 509 zcmVA#CW|K6rSdgL zJ>(!YNRg)0l2)j0nws70&M0AbKuAKs2Y#2~<;U~B!w`hg$jFC#Ab>C#)&kGL%Dbsz z^G3BgAH{tT0a1V*Enp2;D2T9DufLX6*jz%N57B^{K*9osX<}Yr-h0*kMSH1y0WeIq zg-zpe<>R_l(YGED03F~kO_v@an_EY;cDsahfnUJSe`b^KeLS#0;+jDL5zVJNG~Tp` zcLNq5%yIdCg#m6}W35FoAWm0^fPmVo0DmgRtOxjyUsAq%huX6MP-(O)Q()499uN+4 zx_O<}+a2=DvqbrM+VyjP->w1?4g?@G=mXXrF`sDUW`VsQGwiM2qV(wzs&Hc1_ofDJ zF9SfMDio)4%$@noway`h;~t-vGHv4`fR{Vzlw9Y^@&;utQn`H!i>1guD>O4yg7B^FZ+6UHz z(yZ@k@qNF7)*4ZYaU{kNM-kiGE%CJe!vFFI5T2|-P$JU$00000NkvXXu0mjfE1=}% literal 0 HcmV?d00001 diff --git a/webapp/public/uk_flag.png b/webapp/public/uk_flag.png new file mode 100644 index 0000000000000000000000000000000000000000..0291e298271ca9ce02fec12fca5809c2719413a0 GIT binary patch literal 628 zcmV-)0*n2LP)AcnX9HbU7QRpLJX%6qmTw8l7z%dfnp>f8I>(+5#c5w;UY9uKR=IkB8(oHLQ}hd7-%-tkHq7kX=s{;0C86ubE(P81@1d-WVqdGGtr2*6p6*i z8F)qlRTi3@1aAQrfITH`c&oY?jMzzf;b-Vt2PQEDF$`4Mf`g=&e%?2>Q&M+-Y3C*; zRgj&VN$aV!uRE#%E+7k7%gRgxV;W!u9BQtN4n$Y|JO2Z`Q|9GKdtW60 O0000 { let barBackgroundColor = darkMode?"#001c17" : "#fef5c6"; let textColor = darkMode?"#FCFAF0" : "#08313A"; + //para la internacionalización + const { t, i18n } = useTranslation(); + const [anchorLanguage, setAnchorLanguage] = useState(null); + + const changeLanguage = (language) => { + i18n.changeLanguage(language); + handleCloseLanguage(); + }; + + const handleClickLanguage = (event) => { + setAnchorLanguage(event.currentTarget); + }; + + const handleCloseLanguage = () => { + setAnchorLanguage(null); + }; + console.log('isLoggedIn', isLoggedIn); return ( + + + + + {i18n.language.toUpperCase()} + + + changeLanguage('es')}> + + {t('spanishFlag')} + + {t('spanish')} + changeLanguage('en')} startIcon={Spanish Flag}> + + {t('englishFlag')} + + {t('english')} + + {isLoggedIn() ? ( <> - + { {username} - Cerrar sesión + {t('logout')} - Historial + {t('history')} ) : ( <> )} + ); diff --git a/webapp/src/internacionalizacion/locales/en/translation.json b/webapp/src/internacionalizacion/locales/en/translation.json index 077404aa..5c48f11c 100644 --- a/webapp/src/internacionalizacion/locales/en/translation.json +++ b/webapp/src/internacionalizacion/locales/en/translation.json @@ -1,3 +1,5 @@ { - + "language": "Language", + "spanish": "Spanish", + "english": "English" } \ No newline at end of file diff --git a/webapp/src/internacionalizacion/locales/es/translation.json b/webapp/src/internacionalizacion/locales/es/translation.json index 544b7b4d..bd572f23 100644 --- a/webapp/src/internacionalizacion/locales/es/translation.json +++ b/webapp/src/internacionalizacion/locales/es/translation.json @@ -1,3 +1,12 @@ { - + "language": "Idioma", + "spanish": "Español", + "spanishFlag": "Bandera de España", + "englishFlag": "Bandera de Inglaterra", + "english": "Inglés", + "home": "Inicio", + "logout": "Cerrar sesión", + "history": "Historial", + "signUp": "Registrarse", + "signIn": "Iniciar sesión" } \ No newline at end of file From cc6aa8c7c56f317dd86fc5cb3a42699e6f2c4942 Mon Sep 17 00:00:00 2001 From: lucia-sonia Date: Thu, 4 Apr 2024 17:22:31 +0200 Subject: [PATCH 17/79] =?UTF-8?q?a=C3=B1adida=20internacionalizacion=20en?= =?UTF-8?q?=20el=20footer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- webapp/src/components/footer/Footer.js | 18 +++++++++++++----- .../locales/en/translation.json | 14 ++++++++++++-- .../locales/es/translation.json | 5 ++++- 3 files changed, 29 insertions(+), 8 deletions(-) diff --git a/webapp/src/components/footer/Footer.js b/webapp/src/components/footer/Footer.js index 34f95a86..2ef59e39 100644 --- a/webapp/src/components/footer/Footer.js +++ b/webapp/src/components/footer/Footer.js @@ -1,23 +1,31 @@ import React from 'react'; import './Footer.css'; // Asegúrate de importar tu archivo de estilos si es necesario -const Footer = ({darkMode}) => ( +import { useTranslation } from 'react-i18next'; + + +const Footer = ({darkMode}) => { + + const {t} = useTranslation(); + + return( -); + ); +}; export default Footer; diff --git a/webapp/src/internacionalizacion/locales/en/translation.json b/webapp/src/internacionalizacion/locales/en/translation.json index 5c48f11c..92ee2d2b 100644 --- a/webapp/src/internacionalizacion/locales/en/translation.json +++ b/webapp/src/internacionalizacion/locales/en/translation.json @@ -1,5 +1,15 @@ { "language": "Language", "spanish": "Spanish", - "english": "English" -} \ No newline at end of file + "spanishFlag": "Spanish Flag", + "englishFlag": "English Flag", + "english": "English", + "home": "Home", + "logout": "Logout", + "history": "History", + "signUp": "Sign Up", + "signIn": "Sign In", + "github": "Project's Github", + "university": "School of Computer Engineering", + "subject": "Software Architecture Assignment" +} diff --git a/webapp/src/internacionalizacion/locales/es/translation.json b/webapp/src/internacionalizacion/locales/es/translation.json index bd572f23..e2debf71 100644 --- a/webapp/src/internacionalizacion/locales/es/translation.json +++ b/webapp/src/internacionalizacion/locales/es/translation.json @@ -8,5 +8,8 @@ "logout": "Cerrar sesión", "history": "Historial", "signUp": "Registrarse", - "signIn": "Iniciar sesión" + "signIn": "Iniciar sesión", + "github": "Github del Proyecto", + "university": "Escuela de Ingeniería Informática", + "subject": "Trabajo de Arquitectura de Software" } \ No newline at end of file From 6b06c179f04336346e9593fa56c39cf7a7cdda8e Mon Sep 17 00:00:00 2001 From: lucia-sonia Date: Thu, 4 Apr 2024 17:33:13 +0200 Subject: [PATCH 18/79] =?UTF-8?q?a=C3=B1adida=20internacionalizacion=20en?= =?UTF-8?q?=20el=20adduser?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- webapp/src/components/adduser/AddUser.js | 22 +++++++++++-------- .../locales/en/translation.json | 10 ++++++++- .../locales/es/translation.json | 10 ++++++++- 3 files changed, 31 insertions(+), 11 deletions(-) diff --git a/webapp/src/components/adduser/AddUser.js b/webapp/src/components/adduser/AddUser.js index 8b61d486..9a77dfdf 100644 --- a/webapp/src/components/adduser/AddUser.js +++ b/webapp/src/components/adduser/AddUser.js @@ -3,6 +3,7 @@ import React, { useState } from 'react'; import axios from 'axios'; import { Container, Typography, TextField, Button, Snackbar } from '@mui/material'; import { useNavigate } from 'react-router-dom'; +import { useTranslation } from 'react-i18next'; const apiEndpoint = process.env.REACT_APP_API_URI ||'http://localhost:8000'; const AddUser = (darkMode) => { @@ -35,6 +36,9 @@ const AddUser = (darkMode) => { let text = darkMode.darkMode ? '#FCFAF0' : '#08313A'; let buttonColor = darkMode.darkMode ? '#107869' : '#FFFFF5'; + //para la internacionalización + const {t} = useTranslation(); + return ( { color: text }}> - Añadir usuario + {t('addUser')} { name="email" margin="normal" fullWidth - label="Email" + label={t('email')} value={email} onChange={(e) => setEmail(e.target.value)} /> @@ -78,7 +82,7 @@ const AddUser = (darkMode) => { name="username" margin="normal" fullWidth - label="Usuario" + label={t('userName')} value={username} onChange={(e) => setUsername(e.target.value)} /> @@ -95,11 +99,11 @@ const AddUser = (darkMode) => { name="password" margin="normal" fullWidth - label="Contraseña" + label={t('password')} type="password" value={password} onChange={(e) => setPassword(e.target.value)} - helperText="La contraseña tiene que tener al menos 12 caracteres, una letra mayúscula, una letra minúscula, un número y un carácter especial" + helperText={t('passwordHelper')} /> { name="passwordConfirm" margin="normal" fullWidth - label="Confirme la contraseña" + label={t('passwordRepeat')} type="password" value={passwordConfirm} onChange={(e) => setPasswordConfirm(e.target.value)} - helperText="Las contraseñas tienen que coincidir" + helperText={t('passwordRepeatHelper')} /> - + {error && ( setError('')} message={`Error: ${error}`} /> )} diff --git a/webapp/src/internacionalizacion/locales/en/translation.json b/webapp/src/internacionalizacion/locales/en/translation.json index 92ee2d2b..4389d836 100644 --- a/webapp/src/internacionalizacion/locales/en/translation.json +++ b/webapp/src/internacionalizacion/locales/en/translation.json @@ -11,5 +11,13 @@ "signIn": "Sign In", "github": "Project's Github", "university": "School of Computer Engineering", - "subject": "Software Architecture Assignment" + "subject": "Software Architecture Assignment", + "addUser": "Add User", + "addUserMessage": "User added successfully", + "email": "Email", + "userName": "Username", + "password": "Password", + "passwordHelper": "Password must be at least 12 characters long, include one uppercase letter, one lowercase letter, one number, and one special character", + "passwordRepeat": "Repeat Password", + "passwordRepeatHelper": "Passwords must match" } diff --git a/webapp/src/internacionalizacion/locales/es/translation.json b/webapp/src/internacionalizacion/locales/es/translation.json index e2debf71..c92f658e 100644 --- a/webapp/src/internacionalizacion/locales/es/translation.json +++ b/webapp/src/internacionalizacion/locales/es/translation.json @@ -11,5 +11,13 @@ "signIn": "Iniciar sesión", "github": "Github del Proyecto", "university": "Escuela de Ingeniería Informática", - "subject": "Trabajo de Arquitectura de Software" + "subject": "Trabajo de Arquitectura de Software", + "addUser": "Añadir Usuario", + "addUserMessage": "Usuario añadido correctamente", + "email": "Correo Electrónico", + "userName": "Nombre de Usuario", + "password": "Contraseña", + "passwordHelper": "La contraseña tiene que tener al menos 12 caracteres, una letra mayúscula, una letra minúscula, un número y un carácter especial", + "passwordRepeat": "Repetir Contraseña", + "passwordRepeatHelper": "Las contraseñas tienen que coincidir" } \ No newline at end of file From 360a1ae72b1c14fc32c06449469cd3e97c5ad1a9 Mon Sep 17 00:00:00 2001 From: lucia-sonia Date: Thu, 4 Apr 2024 17:58:17 +0200 Subject: [PATCH 19/79] =?UTF-8?q?a=C3=B1adida=20internacionalizacion=20en?= =?UTF-8?q?=20el=20history=20primera=20version?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- webapp/src/components/history/StatsBlock.jsx | 19 +++++++++++-------- .../locales/en/translation.json | 10 +++++++++- .../locales/es/translation.json | 10 +++++++++- 3 files changed, 29 insertions(+), 10 deletions(-) diff --git a/webapp/src/components/history/StatsBlock.jsx b/webapp/src/components/history/StatsBlock.jsx index d45bb9c3..f4f0ca10 100644 --- a/webapp/src/components/history/StatsBlock.jsx +++ b/webapp/src/components/history/StatsBlock.jsx @@ -1,5 +1,6 @@ import React from 'react'; import { Heading, Box, Text, CircularProgress, CircularProgressLabel } from '@chakra-ui/react'; +import { useTranslation } from 'react-i18next'; export function StatsBlock({ darkMode,playerStats }){ @@ -10,31 +11,33 @@ export function StatsBlock({ darkMode,playerStats }){ let statBackgroundColor = darkMode ? '#D4F1F4' : '#D4F1F4'; let text = darkMode ? '#FCFAF0' : '#08313A'; let titles = darkMode ? '#90ADC6' : '#00325E'; - + + //para la internacionalización + const {t} = useTranslation(); return ( - Historial de {nombreJugador} + {t('historyMessage')} {nombreJugador} - Resumen + {t('resume')} - Partidas jugadas + {t('gamesPlayed')} {playerStats.numeroJuegos} - Aciertos + {t('questionsCorrect')} {playerStats.preguntas_acertadas}% - Fallos + {t('questionsFailed')} {playerStats.preguntas_falladas}% @@ -42,11 +45,11 @@ export function StatsBlock({ darkMode,playerStats }){ - Tiempo total jugado + {t('totalTime')} {playerStats.tiempoTotal}s - Tiempo medio por partida + {t('averageTime')} {playerStats.tiempoMedio}s diff --git a/webapp/src/internacionalizacion/locales/en/translation.json b/webapp/src/internacionalizacion/locales/en/translation.json index 4389d836..38b61b1d 100644 --- a/webapp/src/internacionalizacion/locales/en/translation.json +++ b/webapp/src/internacionalizacion/locales/en/translation.json @@ -19,5 +19,13 @@ "password": "Password", "passwordHelper": "Password must be at least 12 characters long, include one uppercase letter, one lowercase letter, one number, and one special character", "passwordRepeat": "Repeat Password", - "passwordRepeatHelper": "Passwords must match" + "passwordRepeatHelper": "Passwords must match", + "historyMessage": "History of ", + "resume": "Summary", + "gamesPlayed": "Games played", + "questionsCorrect": "Questions correct", + "questionsFailed": "Questions failed", + "date": "Date", + "totalTime": "Total time played", + "averageTime": "Average time per game" } diff --git a/webapp/src/internacionalizacion/locales/es/translation.json b/webapp/src/internacionalizacion/locales/es/translation.json index c92f658e..708dc041 100644 --- a/webapp/src/internacionalizacion/locales/es/translation.json +++ b/webapp/src/internacionalizacion/locales/es/translation.json @@ -19,5 +19,13 @@ "password": "Contraseña", "passwordHelper": "La contraseña tiene que tener al menos 12 caracteres, una letra mayúscula, una letra minúscula, un número y un carácter especial", "passwordRepeat": "Repetir Contraseña", - "passwordRepeatHelper": "Las contraseñas tienen que coincidir" + "passwordRepeatHelper": "Las contraseñas tienen que coincidir", + "historyMessage": "Historial de ", + "resume": "Resumen", + "gamesPlayed": "Partidas jugadas", + "questionsCorrect": "Preguntas acertadas", + "questionsFailed": "Preguntas falladas", + "date": "Fecha", + "totalTime": "Tiempo total jugado", + "averageTime": "Tiempo medio por partida" } \ No newline at end of file From 75d9d9d187921bc1b2a0fd35d8e6881c93ece31d Mon Sep 17 00:00:00 2001 From: lucia-sonia Date: Thu, 4 Apr 2024 18:44:29 +0200 Subject: [PATCH 20/79] =?UTF-8?q?a=C3=B1adida=20la=20internacionalizacion?= =?UTF-8?q?=20en=20el=20login?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- webapp/src/components/login/Login.js | 33 ++++++++----------- .../locales/en/translation.json | 13 +++++++- .../locales/es/translation.json | 13 +++++++- 3 files changed, 38 insertions(+), 21 deletions(-) diff --git a/webapp/src/components/login/Login.js b/webapp/src/components/login/Login.js index 6a611ee9..98950a7c 100644 --- a/webapp/src/components/login/Login.js +++ b/webapp/src/components/login/Login.js @@ -3,11 +3,12 @@ import React, { useContext, useState } from 'react'; import axios from 'axios'; import { Container, Typography, TextField, Button, Snackbar } from '@mui/material'; - import { AuthContext } from '../authcontext'; import { useNavigate ,Link} from 'react-router-dom'; +import { useTranslation } from 'react-i18next'; + const Login = (darkMode) => { //hacer que el navbar guarde el contexo de si estas loggeado o no //ademas metes en localStorage que es como una cookie , el usuario para poder sacar sus datos en historial etc @@ -16,8 +17,6 @@ const Login = (darkMode) => { const{handleLogin}=useContext(AuthContext); - - const [username, setUsername] = useState(''); const [password, setPassword] = useState(''); const [error, setError] = useState(''); @@ -25,11 +24,10 @@ const Login = (darkMode) => { const [createdAt, setCreatedAt] = useState(''); const [openSnackbar, setOpenSnackbar] = useState(false); + const apiEndpoint = process.env.REACT_APP_API_URI ||'http://localhost:8000'; - const apiEndpoint = process.env.REACT_APP_API_URI ||'http://localhost:8000'; - - - + //para la internacionalización + const {t} = useTranslation(); const loginUser = async () => { try { @@ -38,12 +36,10 @@ const Login = (darkMode) => { // Extract data from the response const { createdAt: userCreatedAt} = response.data; - handleLogin(response.data.token,response.data.username);//pasasr el token que nos da el servidor setCreatedAt(userCreatedAt); - setLoginSuccess(true); - + setLoginSuccess(true); setOpenSnackbar(true); //ir a la url @@ -75,10 +71,10 @@ const Login = (darkMode) => { {loginSuccess ? (
- Hello {username}! + {t('loginWelcomeMessage')} {username} - Your account was created on {new Date(createdAt).toLocaleDateString()}. + {t('loginDate')} {new Date(createdAt).toLocaleDateString()}. @@ -86,7 +82,7 @@ const Login = (darkMode) => { ) : (
- Login + {t('loginMessage')} { }} margin="normal" fullWidth - label="Username" + label={t('loginUsername')} value={username} onChange={(e) => setUsername(e.target.value)} - helperText="Puedes logearte con tu email o tu nombre de usuario" /> { }} margin="normal" fullWidth - label="Password" + label={t('loginPassword')} type="password" value={password} onChange={(e) => setPassword(e.target.value)} /> - ¿No tienes cuenta , registrate aqui ? + {t('loginToAddUser')} - + {error && ( setError('')} message={`Error: ${error}`} /> )} diff --git a/webapp/src/internacionalizacion/locales/en/translation.json b/webapp/src/internacionalizacion/locales/en/translation.json index 38b61b1d..2a980caf 100644 --- a/webapp/src/internacionalizacion/locales/en/translation.json +++ b/webapp/src/internacionalizacion/locales/en/translation.json @@ -9,9 +9,11 @@ "history": "History", "signUp": "Sign Up", "signIn": "Sign In", + "github": "Project's Github", "university": "School of Computer Engineering", "subject": "Software Architecture Assignment", + "addUser": "Add User", "addUserMessage": "User added successfully", "email": "Email", @@ -20,6 +22,7 @@ "passwordHelper": "Password must be at least 12 characters long, include one uppercase letter, one lowercase letter, one number, and one special character", "passwordRepeat": "Repeat Password", "passwordRepeatHelper": "Passwords must match", + "historyMessage": "History of ", "resume": "Summary", "gamesPlayed": "Games played", @@ -27,5 +30,13 @@ "questionsFailed": "Questions failed", "date": "Date", "totalTime": "Total time played", - "averageTime": "Average time per game" + "averageTime": "Average time per game", + + "loginWelcomeMessage": "Welcome ", + "loginDate": "Your account was created on ", + "loginMessage": "Log in", + "loginUsername": "Username or email", + "loginPassword": "Password", + "loginToAddUser": "Don't have an account? Sign up here", + "loginSuccess": "Login successful" } diff --git a/webapp/src/internacionalizacion/locales/es/translation.json b/webapp/src/internacionalizacion/locales/es/translation.json index 708dc041..47fccf1d 100644 --- a/webapp/src/internacionalizacion/locales/es/translation.json +++ b/webapp/src/internacionalizacion/locales/es/translation.json @@ -9,9 +9,11 @@ "history": "Historial", "signUp": "Registrarse", "signIn": "Iniciar sesión", + "github": "Github del Proyecto", "university": "Escuela de Ingeniería Informática", "subject": "Trabajo de Arquitectura de Software", + "addUser": "Añadir Usuario", "addUserMessage": "Usuario añadido correctamente", "email": "Correo Electrónico", @@ -20,6 +22,7 @@ "passwordHelper": "La contraseña tiene que tener al menos 12 caracteres, una letra mayúscula, una letra minúscula, un número y un carácter especial", "passwordRepeat": "Repetir Contraseña", "passwordRepeatHelper": "Las contraseñas tienen que coincidir", + "historyMessage": "Historial de ", "resume": "Resumen", "gamesPlayed": "Partidas jugadas", @@ -27,5 +30,13 @@ "questionsFailed": "Preguntas falladas", "date": "Fecha", "totalTime": "Tiempo total jugado", - "averageTime": "Tiempo medio por partida" + "averageTime": "Tiempo medio por partida", + + "loginWelcomeMessage": "Bienvenido ", + "loginDate": "Tu cuenta fue creada el ", + "loginMessage": "Inicia sesión", + "loginUsername": "Nombre de Usuario o email", + "loginPassword": "Contraseña", + "loginToAddUser": "¿No tienes cuenta? Regístrate aquí", + "loginSuccess": "Inicio de sesión correcto" } \ No newline at end of file From b2ac46a51e0495a7538c06de1e0ebf36271c3358 Mon Sep 17 00:00:00 2001 From: lucia-sonia Date: Thu, 4 Apr 2024 18:59:48 +0200 Subject: [PATCH 21/79] =?UTF-8?q?a=C3=B1adida=20internacionalizacion=20en?= =?UTF-8?q?=20la=20pagina=20principal=20y=20en=20peque=C3=B1os=20sitios?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- webapp/src/components/logout/Logout.js | 8 ++++++-- webapp/src/components/navbar/NavBar.js | 2 +- .../src/components/principalView/PrincipalView.js | 15 +++++++++------ webapp/src/components/startbutton/StartButton.js | 6 +++++- .../locales/en/translation.json | 14 ++++++++++++-- .../locales/es/translation.json | 14 ++++++++++++-- 6 files changed, 45 insertions(+), 14 deletions(-) diff --git a/webapp/src/components/logout/Logout.js b/webapp/src/components/logout/Logout.js index f217d14f..47e5f2d6 100644 --- a/webapp/src/components/logout/Logout.js +++ b/webapp/src/components/logout/Logout.js @@ -2,9 +2,13 @@ import React , { useContext,useEffect }from 'react'; import './Logout.css'; import { AuthContext } from '../authcontext'; +import { useTranslation } from 'react-i18next'; + const Logout = () => { const { logout } = useContext(AuthContext); - + + //para la internacionalización + const {t} = useTranslation(); useEffect(() => { logout(); @@ -13,7 +17,7 @@ const Logout = () => { return (
-

Gracias por jugar. ¡Vuelve pronto!

+

{t('logoutMessage')}

); diff --git a/webapp/src/components/navbar/NavBar.js b/webapp/src/components/navbar/NavBar.js index 53696863..0f5a4dee 100644 --- a/webapp/src/components/navbar/NavBar.js +++ b/webapp/src/components/navbar/NavBar.js @@ -85,7 +85,7 @@ const NavBar = ({ setDarkMode, darkMode}) => { {t('spanishFlag')} {t('spanish')} - changeLanguage('en')} startIcon={Spanish Flag}> + changeLanguage('en')} > {t('englishFlag')} diff --git a/webapp/src/components/principalView/PrincipalView.js b/webapp/src/components/principalView/PrincipalView.js index 6291bcfd..2d440345 100644 --- a/webapp/src/components/principalView/PrincipalView.js +++ b/webapp/src/components/principalView/PrincipalView.js @@ -1,19 +1,22 @@ import { Box, Heading, Text } from '@chakra-ui/react'; - +import { useTranslation } from 'react-i18next'; const PrincipalView = (darkMode) => { let backgroundColor = darkMode.darkMode ? '#08313A' : '#b5b4b1'; let text = darkMode.darkMode ? '#FCFAF0' : '#08313A'; + + //para la internacionalización + const {t} = useTranslation(); return ( - wiq_es05c - Nuestro grupo: como y después el juego - Bienvenido a nuestro juego de preguntas - Somos un equipo de desarrolladores apasionados por los juegos de preguntas. - En este juego, te desafiamos a responder una serie de preguntas aleatorias de diferentes categorías. Ofrecemos varios modos de juego para mantener las cosas interesantes. ¡Buena suerte! + {t('group')} + {t('welcomeMessage')} + {t('welcomeMessage2')} + {t('welcomeMessage3')} + {t('welcomeMessage4')} ); }; diff --git a/webapp/src/components/startbutton/StartButton.js b/webapp/src/components/startbutton/StartButton.js index 68f5f31d..c5fc42c8 100644 --- a/webapp/src/components/startbutton/StartButton.js +++ b/webapp/src/components/startbutton/StartButton.js @@ -2,12 +2,16 @@ import React, { useContext } from 'react'; import { useNavigate } from 'react-router-dom'; import './startButton.css' import { GameContext } from '../game/GameContext'; +import { useTranslation } from 'react-i18next'; + const StartButton = () => { const { startGame} = useContext(GameContext); const navigate = useNavigate(); + //para la internacionalización + const {t} = useTranslation(); const handleClick = () => { startGame();//llamar a la funcion empezar juego @@ -16,7 +20,7 @@ const StartButton = () => { return ( ); }; diff --git a/webapp/src/internacionalizacion/locales/en/translation.json b/webapp/src/internacionalizacion/locales/en/translation.json index 2a980caf..370da7d7 100644 --- a/webapp/src/internacionalizacion/locales/en/translation.json +++ b/webapp/src/internacionalizacion/locales/en/translation.json @@ -31,12 +31,22 @@ "date": "Date", "totalTime": "Total time played", "averageTime": "Average time per game", - + "loginWelcomeMessage": "Welcome ", "loginDate": "Your account was created on ", "loginMessage": "Log in", "loginUsername": "Username or email", "loginPassword": "Password", "loginToAddUser": "Don't have an account? Sign up here", - "loginSuccess": "Login successful" + "loginSuccess": "Login successful", + + "logoutMessage": "Thank you for playing. Come back soon!", + + "group": "WIQ_ES05C", + "welcomeMessage": "Our group: how and then the game", + "welcomeMessage2": "Welcome to our quiz game", + "welcomeMessage3": "We are a team of developers passionate about quiz games.", + "welcomeMessage4": "In this game, we challenge you to answer a series of random questions from various categories. We offer several game modes to keep things interesting. Good luck!", + + "startButton": "Start game" } diff --git a/webapp/src/internacionalizacion/locales/es/translation.json b/webapp/src/internacionalizacion/locales/es/translation.json index 47fccf1d..e57f2173 100644 --- a/webapp/src/internacionalizacion/locales/es/translation.json +++ b/webapp/src/internacionalizacion/locales/es/translation.json @@ -31,12 +31,22 @@ "date": "Fecha", "totalTime": "Tiempo total jugado", "averageTime": "Tiempo medio por partida", - + "loginWelcomeMessage": "Bienvenido ", "loginDate": "Tu cuenta fue creada el ", "loginMessage": "Inicia sesión", "loginUsername": "Nombre de Usuario o email", "loginPassword": "Contraseña", "loginToAddUser": "¿No tienes cuenta? Regístrate aquí", - "loginSuccess": "Inicio de sesión correcto" + "loginSuccess": "Inicio de sesión correcto", + + "logoutMessage": "Gracias por jugar. ¡Vuelve pronto!", + + "group": "WIQ_ES05C", + "welcomeMessage": "Nuestro grupo: como y después el juego", + "welcomeMessage2": "Bienvenido a nuestro juego de preguntas", + "welcomeMessage3": "Somos un equipo de desarrolladores apasionados por los juegos de preguntas.", + "welcomeMessage4": "En este juego, te desafiamos a responder una serie de preguntas aleatorias de diferentes categorías. Ofrecemos varios modos de juego para mantener las cosas interesantes. ¡Buena suerte!", + + "startButton": "Comenzar juego" } \ No newline at end of file From f436d161d9c81a95b77088b957b6d37eb5cf6a0f Mon Sep 17 00:00:00 2001 From: bidof Date: Thu, 4 Apr 2024 19:31:33 +0200 Subject: [PATCH 22/79] test del createRoom --- webapp/src/components/rooms/CreateRoom.js | 29 +++++++++++-------- .../src/components/rooms/CreateRoom.test.js | 27 +++++++++++++++++ 2 files changed, 44 insertions(+), 12 deletions(-) create mode 100644 webapp/src/components/rooms/CreateRoom.test.js diff --git a/webapp/src/components/rooms/CreateRoom.js b/webapp/src/components/rooms/CreateRoom.js index 3130a821..1dfd2c7a 100644 --- a/webapp/src/components/rooms/CreateRoom.js +++ b/webapp/src/components/rooms/CreateRoom.js @@ -1,5 +1,7 @@ import React, { useState,useEffect } from 'react'; import { useNavigate } from 'react-router-dom'; +import { Box, Text, Button, VStack, Center } from "@chakra-ui/react"; + import socket from './socket'; const CreateRoomForm = () => { const navigate = useNavigate(); @@ -27,18 +29,21 @@ const CreateRoomForm = () => { }; return ( -
- setRoomId(e.target.value)} - placeholder="Ingrese el ID de la sala" - /> - -
- ); + + +
+ Crear Sala +
+ + Al hacer clic en "Crear Sala", se creará una nueva sala con un ID único. + Deberás compartir este ID con las personas que quieras que se unan a tu sala. + + +
+
+ ); }; export default CreateRoomForm; diff --git a/webapp/src/components/rooms/CreateRoom.test.js b/webapp/src/components/rooms/CreateRoom.test.js new file mode 100644 index 00000000..4ad2e02e --- /dev/null +++ b/webapp/src/components/rooms/CreateRoom.test.js @@ -0,0 +1,27 @@ +import { render, screen, act } from '@testing-library/react'; +import { BrowserRouter as Router } from 'react-router-dom'; +import { ChakraProvider } from '@chakra-ui/react'; +import CreateRoom from './CreateRoom'; +import socket from './socket'; + +jest.mock('./socket', () => ({ + on: jest.fn(), + emit: jest.fn(), + off: jest.fn(), +})); + +test('renders CreateRoom component and checks for button', () => { + render( + + + + + + ); + + const createRoomButton = screen.getByTestId('createRoom'); + expect(createRoomButton).toBeInTheDocument(); + }); + + + From 9b90454934a304858f08c0e679a8c510eff41f41 Mon Sep 17 00:00:00 2001 From: bidof Date: Thu, 4 Apr 2024 19:34:21 +0200 Subject: [PATCH 23/79] agregar caso que faltaba --- webapp/src/components/rooms/CreateRoom.test.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/webapp/src/components/rooms/CreateRoom.test.js b/webapp/src/components/rooms/CreateRoom.test.js index 4ad2e02e..05456400 100644 --- a/webapp/src/components/rooms/CreateRoom.test.js +++ b/webapp/src/components/rooms/CreateRoom.test.js @@ -2,6 +2,8 @@ import { render, screen, act } from '@testing-library/react'; import { BrowserRouter as Router } from 'react-router-dom'; import { ChakraProvider } from '@chakra-ui/react'; import CreateRoom from './CreateRoom'; +import { fireEvent } from '@testing-library/react'; + import socket from './socket'; jest.mock('./socket', () => ({ @@ -24,4 +26,20 @@ test('renders CreateRoom component and checks for button', () => { }); + test('renders CreateRoom component and simulates button click', () => { + render( + + + + + + ); + + const createRoomButton = screen.getByTestId('createRoom'); + expect(createRoomButton).toBeInTheDocument(); + + fireEvent.click(createRoomButton); + + + }); From 9415e05b58e5126e80d007fdeb215502957631f6 Mon Sep 17 00:00:00 2001 From: lucia-sonia Date: Thu, 4 Apr 2024 19:34:37 +0200 Subject: [PATCH 24/79] =?UTF-8?q?a=C3=B1adida=20internacionalizacion=20a?= =?UTF-8?q?=20los=20ultimos=20cambios?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- webapp/package.json | 2 +- webapp/src/components/history/GameBlock.jsx | 13 ++++++++----- webapp/src/components/history/StatsBlock.jsx | 4 ++-- webapp/src/components/rooms/CreateRoom.js | 9 +++++++-- webapp/src/components/rooms/JoinRoom.js | 11 ++++++++--- webapp/src/components/rooms/Room.js | 15 +++++++++------ .../locales/en/translation.json | 2 ++ .../locales/es/translation.json | 17 ++++++++++++++++- 8 files changed, 53 insertions(+), 20 deletions(-) diff --git a/webapp/package.json b/webapp/package.json index 798f0cf9..53e7353d 100644 --- a/webapp/package.json +++ b/webapp/package.json @@ -21,7 +21,7 @@ "react-router-dom": "^6.22.3", "react-scripts": "5.0.1", "i18next": "^21.7.1", - "react-i18next": "^11.13.10" + "react-i18next": "^11.13.10", "socket.io-client": "^4.7.5", "web-vitals": "^3.5.1" }, diff --git a/webapp/src/components/history/GameBlock.jsx b/webapp/src/components/history/GameBlock.jsx index f51f6144..0cf73d32 100644 --- a/webapp/src/components/history/GameBlock.jsx +++ b/webapp/src/components/history/GameBlock.jsx @@ -1,4 +1,5 @@ import { Box, Text, Heading } from "@chakra-ui/react"; +import { useTranslation } from 'react-i18next'; export function GameBlock( {darkMode, gameInfo} ){ @@ -22,6 +23,8 @@ export function GameBlock( {darkMode, gameInfo} ){ let totalPreguntas = aciertos + fallos; let tiempo = gameInfo.tiempo; + //para la internacionalización + const {t} = useTranslation(); function formatTime(tiempo) { let hours = Math.floor(tiempo / 3600); @@ -67,22 +70,22 @@ export function GameBlock( {darkMode, gameInfo} ){ {numeroJuego} - Fecha + {t('date')} {fechaFormateada} - Número aciertos + {t('questionsCorrect')} {aciertos}/{totalPreguntas} - Número fallos + {t('questionsFailed')} {fallos}/{totalPreguntas} - Duración partida - {tiempo}s + {t('gameTime')} + {tiempo}{t('timeUnit')} ); diff --git a/webapp/src/components/history/StatsBlock.jsx b/webapp/src/components/history/StatsBlock.jsx index 07b1d5fe..3d7412fa 100644 --- a/webapp/src/components/history/StatsBlock.jsx +++ b/webapp/src/components/history/StatsBlock.jsx @@ -61,13 +61,13 @@ export function StatsBlock({ darkMode,playerStats }){ {t('questionsCorrect')} - {playerStats.preguntas_acertadas}% + {playerStats.preguntas_acertadas}{t('questionsPercentage')} {t('questionsFailed')} - {playerStats.preguntas_falladas}% + {playerStats.preguntas_falladas}{t('questionsPercentage')} diff --git a/webapp/src/components/rooms/CreateRoom.js b/webapp/src/components/rooms/CreateRoom.js index 3130a821..14d1b0b3 100644 --- a/webapp/src/components/rooms/CreateRoom.js +++ b/webapp/src/components/rooms/CreateRoom.js @@ -1,12 +1,17 @@ import React, { useState,useEffect } from 'react'; import { useNavigate } from 'react-router-dom'; import socket from './socket'; +import { useTranslation } from 'react-i18next'; + const CreateRoomForm = () => { const navigate = useNavigate(); const [roomId, setRoomId] = useState(''); const [isLoading, setIsLoading] = useState(false); const username = localStorage.getItem('username'); + + //para la internacionalización + const {t} = useTranslation(); useEffect(() => { // Manejar la respuesta, por ejemplo, mostrar un mensaje de éxito @@ -32,10 +37,10 @@ const CreateRoomForm = () => { type="text" value={roomId} onChange={(e) => setRoomId(e.target.value)} - placeholder="Ingrese el ID de la sala" + placeholder={t('roomCreatePlaceholder')} />
); diff --git a/webapp/src/components/rooms/JoinRoom.js b/webapp/src/components/rooms/JoinRoom.js index 5bd6d05f..eef44f13 100644 --- a/webapp/src/components/rooms/JoinRoom.js +++ b/webapp/src/components/rooms/JoinRoom.js @@ -4,6 +4,8 @@ import { Box, Input, Button, Text, Tooltip, Icon, Center, VStack, HStack } from import { InfoOutlineIcon } from '@chakra-ui/icons'; import { useToast } from "@chakra-ui/react"; import socket from './socket'; +import { useTranslation } from 'react-i18next'; + const JoinRoomForm = () => { const navigate = useNavigate(); @@ -14,6 +16,9 @@ const JoinRoomForm = () => { const toast = useToast(); + //para la internacionalización + const {t} = useTranslation(); + useEffect(() => { socket.on('roomJoined', (roomId) => { navigate(`/room/${roomId}`); @@ -67,17 +72,17 @@ const JoinRoomForm = () => {
- Unirse a Sala + {t('roomJoinButton')}
setRoomId(e.target.value)} - placeholder="Ingrese el ID de la sala" + placeholder={t('roomCreatePlaceholder')} size="lg" />
diff --git a/webapp/src/components/rooms/Room.js b/webapp/src/components/rooms/Room.js index 83965cc8..a4aecd8b 100644 --- a/webapp/src/components/rooms/Room.js +++ b/webapp/src/components/rooms/Room.js @@ -6,6 +6,7 @@ import { useNavigate } from 'react-router-dom'; import socket from './socket'; import Game from '../game/Game'; +import { useTranslation } from 'react-i18next'; function Room({ darkMode }) { const nagivate = useNavigate(); @@ -20,6 +21,8 @@ function Room({ darkMode }) { const [winner, setWinner] = useState(null); + //para la internacionalización + const {t} = useTranslation(); //para el mensaje del ganador const [isOpen, setIsOpen] = useState(false); @@ -86,14 +89,14 @@ function Room({ darkMode }) { return (
-

Sala: {roomId}

-

Usuarios:

+

{t('room')}{roomId}

+

{t('roomUsers')}

    {Object.keys(users).map((username, index) => (
  • {username}
  • ))}
- {isHost && } + {isHost && } {gameStarted && questions.length > 0 && } - Juego terminado + {t('roomEndGame')} - El ganador es {winner} + {t('roomWinner')}{winner} diff --git a/webapp/src/internacionalizacion/locales/en/translation.json b/webapp/src/internacionalizacion/locales/en/translation.json index 370da7d7..9570f9f3 100644 --- a/webapp/src/internacionalizacion/locales/en/translation.json +++ b/webapp/src/internacionalizacion/locales/en/translation.json @@ -31,6 +31,8 @@ "date": "Date", "totalTime": "Total time played", "averageTime": "Average time per game", + "gameTime": "Game time", + "timeUnit" : "seconds", "loginWelcomeMessage": "Welcome ", "loginDate": "Your account was created on ", diff --git a/webapp/src/internacionalizacion/locales/es/translation.json b/webapp/src/internacionalizacion/locales/es/translation.json index e57f2173..81abee41 100644 --- a/webapp/src/internacionalizacion/locales/es/translation.json +++ b/webapp/src/internacionalizacion/locales/es/translation.json @@ -31,6 +31,9 @@ "date": "Fecha", "totalTime": "Tiempo total jugado", "averageTime": "Tiempo medio por partida", + "gameTime": "Tiempo de la partida", + "timeUnit" : "segundos", + "questionsPercentage": "%", "loginWelcomeMessage": "Bienvenido ", "loginDate": "Tu cuenta fue creada el ", @@ -48,5 +51,17 @@ "welcomeMessage3": "Somos un equipo de desarrolladores apasionados por los juegos de preguntas.", "welcomeMessage4": "En este juego, te desafiamos a responder una serie de preguntas aleatorias de diferentes categorías. Ofrecemos varios modos de juego para mantener las cosas interesantes. ¡Buena suerte!", - "startButton": "Comenzar juego" + "startButton": "Comenzar juego", + + "roomCreatePlaceholder": "Ingrese el ID de la sala", + "roomCreateButton": "Crear sala", + "roomWaitCreateMessage": "Creando...", + "roomJoinButton": "Unirse a Sala", + "room" : "Sala: ", + "roomUsers" : "Usuarios:", + "roomStartGameButton": "Iniciar Juego", + "roomEndGame": "Juego terminado", + "roomWinner": "El ganador es ", + + "close": "Cerrar" } \ No newline at end of file From ff687496f3cba73736e054961981287e1f9abf85 Mon Sep 17 00:00:00 2001 From: bidof Date: Thu, 4 Apr 2024 19:45:58 +0200 Subject: [PATCH 25/79] primeros test sobre el room test --- webapp/src/components/rooms/Room.test.js | 33 ++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 webapp/src/components/rooms/Room.test.js diff --git a/webapp/src/components/rooms/Room.test.js b/webapp/src/components/rooms/Room.test.js new file mode 100644 index 00000000..fc441318 --- /dev/null +++ b/webapp/src/components/rooms/Room.test.js @@ -0,0 +1,33 @@ +import { render, screen } from '@testing-library/react'; +import { BrowserRouter as Router } from 'react-router-dom'; +import { ChakraProvider } from '@chakra-ui/react'; +import Room from './Room'; + +jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + useParams: () => ({ roomId: '1234' }), + useNavigate: () => jest.fn(), + useLocation: () => ({ state: { isHost: true } }), +})); + +test('renders Room component', () => { + render( + + + + + + ); +}); + +test('renders Room component with correct room ID', () => { + render( + + + + + + ); + + expect(screen.getByText('Sala: 1234')).toBeInTheDocument(); +}); \ No newline at end of file From 76be353f42aa57d376e1338ca560261d5aed92aa Mon Sep 17 00:00:00 2001 From: lucia-sonia Date: Thu, 4 Apr 2024 19:46:21 +0200 Subject: [PATCH 26/79] =?UTF-8?q?a=C3=B1adidos=20los=20mensajes=20para=20i?= =?UTF-8?q?ngles=20del=20ultimo=20commit?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../locales/en/translation.json | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/webapp/src/internacionalizacion/locales/en/translation.json b/webapp/src/internacionalizacion/locales/en/translation.json index 9570f9f3..2e7e177f 100644 --- a/webapp/src/internacionalizacion/locales/en/translation.json +++ b/webapp/src/internacionalizacion/locales/en/translation.json @@ -33,6 +33,7 @@ "averageTime": "Average time per game", "gameTime": "Game time", "timeUnit" : "seconds", + "questionsPercentage": "%", "loginWelcomeMessage": "Welcome ", "loginDate": "Your account was created on ", @@ -50,5 +51,17 @@ "welcomeMessage3": "We are a team of developers passionate about quiz games.", "welcomeMessage4": "In this game, we challenge you to answer a series of random questions from various categories. We offer several game modes to keep things interesting. Good luck!", - "startButton": "Start game" -} + "startButton": "Start game", + + "roomCreatePlaceholder": "Enter the room ID", + "roomCreateButton": "Create room", + "roomWaitCreateMessage": "Creating...", + "roomJoinButton": "Join Room", + "room" : "Room: ", + "roomUsers" : "Users:", + "roomStartGameButton": "Start Game", + "roomEndGame": "Game over", + "roomWinner": "The winner is ", + + "close": "Close" +} \ No newline at end of file From 0761a2d3b4cbda5ecab45883d449306115547623 Mon Sep 17 00:00:00 2001 From: bidof Date: Thu, 4 Apr 2024 19:49:34 +0200 Subject: [PATCH 27/79] test de room completos --- webapp/src/components/rooms/Room.test.js | 49 +++++++++++++++++++++++- 1 file changed, 47 insertions(+), 2 deletions(-) diff --git a/webapp/src/components/rooms/Room.test.js b/webapp/src/components/rooms/Room.test.js index fc441318..2302b278 100644 --- a/webapp/src/components/rooms/Room.test.js +++ b/webapp/src/components/rooms/Room.test.js @@ -2,13 +2,20 @@ import { render, screen } from '@testing-library/react'; import { BrowserRouter as Router } from 'react-router-dom'; import { ChakraProvider } from '@chakra-ui/react'; import Room from './Room'; - +import socket from './socket'; jest.mock('react-router-dom', () => ({ ...jest.requireActual('react-router-dom'), useParams: () => ({ roomId: '1234' }), useNavigate: () => jest.fn(), useLocation: () => ({ state: { isHost: true } }), })); +jest.mock('./socket', () => { + return { + emit: jest.fn(), + on: jest.fn(), + off: jest.fn(), + }; + }); test('renders Room component', () => { render( @@ -30,4 +37,42 @@ test('renders Room component with correct room ID', () => { ); expect(screen.getByText('Sala: 1234')).toBeInTheDocument(); -}); \ No newline at end of file +}); + +test('renders Room component with users', () => { + socket.on.mockImplementation((event, callback) => { + if (event === 'currentUsers') { + callback({ 'user1': true, 'user2': true }); + } + }); + + render( + + + + + + ); + + expect(screen.getByText('user1')).toBeInTheDocument(); + expect(screen.getByText('user2')).toBeInTheDocument(); + }); + + test('renders Room component with users', () => { + socket.on.mockImplementation((event, callback) => { + if (event === 'currentUsers') { + callback({ 'user1': true, 'user2': true }); + } + }); + + render( + + + + + + ); + + expect(screen.getByText('user1')).toBeInTheDocument(); + expect(screen.getByText('user2')).toBeInTheDocument(); + }); \ No newline at end of file From a2054f265c73cc1f230ad0a018fc32b8be94f79e Mon Sep 17 00:00:00 2001 From: bidof Date: Thu, 4 Apr 2024 20:09:19 +0200 Subject: [PATCH 28/79] =?UTF-8?q?cambio=20de=20dise=C3=B1o=20para=20soport?= =?UTF-8?q?ar=20diferentes=20modos=20de=20juegos=20y=20no=20duplicar=20cod?= =?UTF-8?q?igo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- webapp/src/components/game/BasicGame.js | 21 ++++++++++++++++ .../src/components/game/gameModes/GameMode.js | 25 +++++++++++++++++++ .../components/game/gameModes/RoomGameMode.js | 12 --------- .../game/gameModes/basicGameMode.js | 20 --------------- 4 files changed, 46 insertions(+), 32 deletions(-) create mode 100644 webapp/src/components/game/BasicGame.js create mode 100644 webapp/src/components/game/gameModes/GameMode.js delete mode 100644 webapp/src/components/game/gameModes/RoomGameMode.js delete mode 100644 webapp/src/components/game/gameModes/basicGameMode.js diff --git a/webapp/src/components/game/BasicGame.js b/webapp/src/components/game/BasicGame.js new file mode 100644 index 00000000..91b684ca --- /dev/null +++ b/webapp/src/components/game/BasicGame.js @@ -0,0 +1,21 @@ +// Modo de juego básico +class BasicGame extends GameMode { + constructor(context) { + super(); + this.context = context; + } + fetchQuestions() { + // Implementación por defecto para obtener preguntas + return this.context.questions; + } + startGame() { + // Implementación por defecto para iniciar el juego + this.context.startGame(); + } + endGame() { + // Implementación por defecto para terminar el juego + } + sendHistory() { + // Implementación por defecto para enviar historial + } + } \ No newline at end of file diff --git a/webapp/src/components/game/gameModes/GameMode.js b/webapp/src/components/game/gameModes/GameMode.js new file mode 100644 index 00000000..1746cc03 --- /dev/null +++ b/webapp/src/components/game/gameModes/GameMode.js @@ -0,0 +1,25 @@ +// GameMode.js +/* +interfaz con lo necesario para que un modo de juego funcione +*/ + +class GameMode { + constructor() { + this.questions=[]; + this.isLoading=false; + } + fetchQuestions() { + throw new Error("Method 'fetchQuestions' must be implemented."); + } + startGame() { + throw new Error("Method 'startGame' must be implemented."); + } + endGame() { + throw new Error("Method 'endGame' must be implemented."); + } + sendHistory() { + throw new Error("Method 'sendHistory' must be implemented."); + } + } + + export default GameMode; \ No newline at end of file diff --git a/webapp/src/components/game/gameModes/RoomGameMode.js b/webapp/src/components/game/gameModes/RoomGameMode.js deleted file mode 100644 index 1bf99699..00000000 --- a/webapp/src/components/game/gameModes/RoomGameMode.js +++ /dev/null @@ -1,12 +0,0 @@ - -export class RoomGameMode { - constructor(questions) { - this.apiEndpoint = process.env.REACT_APP_API_URI || 'http://localhost:8000'; - this.questions=questions; - } - //usado en el modo de sservidor, simplemente recibe las preguntas del servidor - async fetchQuestions() { - return this.questions; - } - -}; diff --git a/webapp/src/components/game/gameModes/basicGameMode.js b/webapp/src/components/game/gameModes/basicGameMode.js deleted file mode 100644 index 67964390..00000000 --- a/webapp/src/components/game/gameModes/basicGameMode.js +++ /dev/null @@ -1,20 +0,0 @@ - -export class BasicGameMode { - constructor() { - this.apiEndpoint = process.env.REACT_APP_API_URI || 'http://localhost:8000'; - this.questions=[]; - } - - async fetchQuestions() { - try { - const response = await fetch(`${this.apiEndpoint}/getQuestionModoBasico`); - const data = await response.json(); - - this.questions = Object.values(data); - } catch (error) { - console.error('Error fetching question data:', error); - } - return this.questions; - } - -}; From a072f2162ccd70451ec5f89f366ff41a24cf89a4 Mon Sep 17 00:00:00 2001 From: bidof Date: Thu, 4 Apr 2024 20:32:35 +0200 Subject: [PATCH 29/79] implementaciones por defecto de los metodos del game --- webapp/src/components/game/BasicGame.js | 66 +++++++++++++------ .../src/components/game/gameModes/GameMode.js | 18 +++-- 2 files changed, 61 insertions(+), 23 deletions(-) diff --git a/webapp/src/components/game/BasicGame.js b/webapp/src/components/game/BasicGame.js index 91b684ca..cd242888 100644 --- a/webapp/src/components/game/BasicGame.js +++ b/webapp/src/components/game/BasicGame.js @@ -1,21 +1,49 @@ -// Modo de juego básico +// BasicGame.js +/* +implementacion por defecto dle juego , esta implementacion es muy similar siempre +pero en ciertos casos como en el online si no quieres guardar el historial o quieres x cosa +heredas y sobreescribes y list */ + +import GameMode from './GameMode'; + class BasicGame extends GameMode { - constructor(context) { - super(); - this.context = context; + constructor() { + super(); + this.isLoading = true; + } + async fetchQuestions() { + try { + const response = await fetch(`${this.apiEndpoint}/getQuestionModoBasico`); + const data = await response.json(); + + this.questions = Object.values(data); + this.isLoading = false; + + } catch (error) { + console.error('Error fetching question data:', error); } - fetchQuestions() { - // Implementación por defecto para obtener preguntas - return this.context.questions; - } - startGame() { - // Implementación por defecto para iniciar el juego - this.context.startGame(); - } - endGame() { - // Implementación por defecto para terminar el juego - } - sendHistory() { - // Implementación por defecto para enviar historial - } - } \ No newline at end of file + return this.questions; + } + async startGame() { + this.isLoading = true; + this.questionIndex = 0; + await this.fetchQuestions(); + } + endGame() { + this.isGameEnded = true; + this.questionIndex=0; + } + sendHistory() { + // Implementación por defecto para enviar historial + } + nextQuestion() { + if(!this.isGameEnded){ + this.questionIndex++; + //es el json con pregunta {resp1 resp2 resp3 resp4 correcta} + return this.questions[this.questionIndex]; + }else + this.endGame(); + } +} + +export default BasicGame; \ No newline at end of file diff --git a/webapp/src/components/game/gameModes/GameMode.js b/webapp/src/components/game/gameModes/GameMode.js index 1746cc03..66de5014 100644 --- a/webapp/src/components/game/gameModes/GameMode.js +++ b/webapp/src/components/game/gameModes/GameMode.js @@ -7,19 +7,29 @@ class GameMode { constructor() { this.questions=[]; this.isLoading=false; + this.apiEndpoint = process.env.REACT_APP_API_URI || 'http://localhost:8000'; + this.isGameEnded=false; + this.questionIndex=0; } - fetchQuestions() { + async fetchQuestions() { throw new Error("Method 'fetchQuestions' must be implemented."); } - startGame() { + async startGame() { throw new Error("Method 'startGame' must be implemented."); } - endGame() { + async endGame() { throw new Error("Method 'endGame' must be implemented."); } - sendHistory() { + async sendHistory() { throw new Error("Method 'sendHistory' must be implemented."); } + /* + hace lo necesario en la pregunta actual y pasa a la la siguiente pregunta + pasa a la siguiente pregunta + */ + nextQuestion(){ + throw new Error("Method 'nextQuestion' must be implemented."); + } } export default GameMode; \ No newline at end of file From d2eaabb0a74f9dfdffe1cf1815eac18a0c42ccaa Mon Sep 17 00:00:00 2001 From: bidof Date: Thu, 4 Apr 2024 20:47:24 +0200 Subject: [PATCH 30/79] emepzar el refactor del componente que se muestra: game para que funcione el modo singleplayer --- webapp/src/components/game/BasicGame.js | 59 ++++++++++++++++++++----- webapp/src/components/game/Game.js | 16 +++---- 2 files changed, 54 insertions(+), 21 deletions(-) diff --git a/webapp/src/components/game/BasicGame.js b/webapp/src/components/game/BasicGame.js index cd242888..d4f42cc2 100644 --- a/webapp/src/components/game/BasicGame.js +++ b/webapp/src/components/game/BasicGame.js @@ -33,17 +33,56 @@ class BasicGame extends GameMode { this.isGameEnded = true; this.questionIndex=0; } - sendHistory() { - // Implementación por defecto para enviar historial + /* + recibe el objeto que representa los datos asi si quieres no guardar un dato no se lo pasas + */ + async sendHistory(historyData) { + if (localStorage.getItem('username') != null) { + if (!('correctas' in historyData) || !('incorrectas' in historyData) || !('tiempoTotal' in historyData)) { + throw new Error('historyData must have correctas, incorrectas, and tiempoTotal properties'); + } + + const data = { + user: localStorage.getItem('username'), + correctas: historyData.correctas, + incorrectas: historyData.incorrectas, + tiempoTotal: historyData.tiempoTotal + }; + + console.log("Se envian los siguientes datos al historial", data); + fetch(`${this.apiEndpoint}/updateHistory`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(data) + }) + .then(response => { + if (!response.ok) { + throw new Error('Network response was not ok'); + } + return response.json(); + }) + .then(data => { + console.log('Respuesta del servidor:', data); + }) + .catch(error => { + console.error('Error al enviar el historial al servidor:', error); + }); + } + } + async nextQuestion() { + this.isLoading = true; + if(!this.isGameEnded){ + this.questionIndex++; + // es el json con pregunta {resp1 resp2 resp3 resp4 correcta} + const nextQuestion = this.questions[this.questionIndex]; + this.isLoading = false; + return nextQuestion; + } else { + this.endGame(); + } } - nextQuestion() { - if(!this.isGameEnded){ - this.questionIndex++; - //es el json con pregunta {resp1 resp2 resp3 resp4 correcta} - return this.questions[this.questionIndex]; - }else - this.endGame(); - } } export default BasicGame; \ No newline at end of file diff --git a/webapp/src/components/game/Game.js b/webapp/src/components/game/Game.js index 66dc85c7..599f8b8a 100644 --- a/webapp/src/components/game/Game.js +++ b/webapp/src/components/game/Game.js @@ -3,7 +3,7 @@ import { useEffect, useState,useContext } from 'react'; import {GameContext} from './GameContext'; import {useNavigate} from 'react-router-dom'; import { Spinner, Box, AlertDialog, AlertDialogBody, AlertDialogFooter, AlertDialogHeader, AlertDialogContent, AlertDialogOverlay, Button,Center } from "@chakra-ui/react"; - +import BasicGame from './BasicGame'; const apiEndpoint = process.env.REACT_APP_API_URI ||'http://localhost:8000'; /* @@ -11,25 +11,19 @@ recibe el obj gameMode que contieene las preguntas para ese modo de juego recibe questions que son las del servidor si estas en multiplayer si no le pasa contexto se utiliziara el por defecto que es el GameContext */ -function Game({darkMode,questions:multiplayerQuestions=null,endGame=null}) { - - //obtienes las preguntas del contexto o bien de la prop q se le pasa - const { startGame, questions: singleplayerQuestions, isLoading } = useContext(GameContext); - const questions = multiplayerQuestions || singleplayerQuestions; - - const [isOpen, setIsOpen] = useState(false);//es el cuadro de dialogo que se abre al finalizar el juego +function Game({darkMode,gameMode= new BasicMode(),endGame=null}) { - //e le pasaran al Question area para que cuando acabe el juego tengan el valor de las respuestas correctas + const [isOpen, setIsOpen] = useState(false); const [correctAnswers, setCorrectAnswers] = useState(0); const [incorrectAnswers, setIncorrectAnswers] = useState(0); const [totalTime, setTotalTime] = useState(0); const [finished, setFinished] = useState(false); const navigate = useNavigate(); - const timeToAnswer = 20000;//Aquí podemos definir el tiempo para responder + const timeToAnswer = 20000; useEffect(() => { - startGame(); + gameMode.startGame(); }, []); //se ejecuta al cambiar el num de correctas que solo cambia si se ha terminado el juego From 8dcb275c40c7149007fe6f4f8be0001a0195b9a7 Mon Sep 17 00:00:00 2001 From: lucia-sonia Date: Thu, 4 Apr 2024 20:49:59 +0200 Subject: [PATCH 31/79] =?UTF-8?q?a=C3=B1adidas=20comprobaciones=20de=20la?= =?UTF-8?q?=20internacionalizacion?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/history/AllGamesBlock.jsx | 6 ++++-- webapp/src/components/history/GameBlock.jsx | 11 +++++++---- webapp/src/components/history/StatsBlock.jsx | 19 +++++++++++-------- webapp/src/components/navbar/NavBar.js | 4 ++-- .../locales/en/translation.json | 6 ++++-- .../locales/es/translation.json | 6 ++++-- 6 files changed, 32 insertions(+), 20 deletions(-) diff --git a/webapp/src/components/history/AllGamesBlock.jsx b/webapp/src/components/history/AllGamesBlock.jsx index b1f0d1dd..a73753fa 100644 --- a/webapp/src/components/history/AllGamesBlock.jsx +++ b/webapp/src/components/history/AllGamesBlock.jsx @@ -1,7 +1,7 @@ import React from 'react'; import {GameBlock} from './GameBlock'; import { Box, Heading} from "@chakra-ui/react"; - +import { useTranslation } from 'react-i18next'; export function AllGamesBlock({darkMode, games }){ @@ -9,13 +9,15 @@ export function AllGamesBlock({darkMode, games }){ let text = darkMode ? '#FCFAF0' : '#08313A'; let titles = darkMode ? '#90ADC6' : '#00325E'; + //para la internacionalización + const {t} = useTranslation(); console.log("Partidas x: "+games); console.log(games); return ( - Partidas + {t('gamesPlayed')} {games.map((game, index) => ( ))} diff --git a/webapp/src/components/history/GameBlock.jsx b/webapp/src/components/history/GameBlock.jsx index 0cf73d32..0d2747a1 100644 --- a/webapp/src/components/history/GameBlock.jsx +++ b/webapp/src/components/history/GameBlock.jsx @@ -3,6 +3,10 @@ import { useTranslation } from 'react-i18next'; export function GameBlock( {darkMode, gameInfo} ){ + + //para la internacionalización + const {t, i18n} = useTranslation(); + console.log("Partida: " + gameInfo); /* let fecha = gameInfo.fecha; @@ -12,10 +16,11 @@ export function GameBlock( {darkMode, gameInfo} ){ let tiempo = gameInfo.tiempo; */ //Parseo la fecha, la dife de hora es que la pasa a la hora de españa - //Sin parsear 2024-03-20T00:00:00.000Z --- Parseada 20 de marzo de 2024, 01:00 + //Sin parsear 2024-03-20T00:00:00.000Z --- Parseada 20 de marzo de 2024, 01:00 + let fecha = new Date(gameInfo.fecha); let options = { year: 'numeric', month: 'long', day: 'numeric', hour: '2-digit', minute: '2-digit' }; - let fechaFormateada = fecha.toLocaleDateString(undefined, options); + let fechaFormateada = fecha.toLocaleDateString(i18n.language, options); let numeroJuego = gameInfo.numeroJuego; let aciertos = gameInfo.preguntas_acertadas; @@ -23,8 +28,6 @@ export function GameBlock( {darkMode, gameInfo} ){ let totalPreguntas = aciertos + fallos; let tiempo = gameInfo.tiempo; - //para la internacionalización - const {t} = useTranslation(); function formatTime(tiempo) { let hours = Math.floor(tiempo / 3600); diff --git a/webapp/src/components/history/StatsBlock.jsx b/webapp/src/components/history/StatsBlock.jsx index 3d7412fa..629b1d98 100644 --- a/webapp/src/components/history/StatsBlock.jsx +++ b/webapp/src/components/history/StatsBlock.jsx @@ -2,7 +2,10 @@ import React from 'react'; import { Heading, Box, Text, CircularProgress, CircularProgressLabel } from '@chakra-ui/react'; import { useTranslation } from 'react-i18next'; -export function StatsBlock({ darkMode,playerStats }){ +export function StatsBlock({ darkMode,playerStats }){ + + //para la internacionalización + const {t} = useTranslation(); let nombreJugador = playerStats.nombreUsuario; console.log(playerStats); @@ -13,6 +16,8 @@ export function StatsBlock({ darkMode,playerStats }){ let tiempoMedioFormateado = formatTime(playerStats.tiempoMedio); + + function formatTime(tiempo) { let hours = Math.floor(tiempo / 3600); tiempo %= 3600; @@ -23,14 +28,15 @@ export function StatsBlock({ darkMode,playerStats }){ let tiempoFormateado = ''; if (hours > 0) { - tiempoFormateado += `${hours}h `; + tiempoFormateado += `${hours}${t('timeUnitHours')} `; } if (minutes > 0 || hours > 0) { // Si hay horas, se mostrarán los minutos aunque sean 0 - tiempoFormateado += `${minutes}m `; + tiempoFormateado += `${minutes}${t('timeUnitMinutes')}`; } - - tiempoFormateado += `${seconds}s`; + + tiempoFormateado += `${seconds}${t('timeUnit')}`; + return tiempoFormateado; } @@ -39,9 +45,6 @@ export function StatsBlock({ darkMode,playerStats }){ let statBackgroundColor = darkMode ? '#D4F1F4' : '#D4F1F4'; let text = darkMode ? '#FCFAF0' : '#08313A'; let titles = darkMode ? '#90ADC6' : '#00325E'; - - //para la internacionalización - const {t} = useTranslation(); return ( diff --git a/webapp/src/components/navbar/NavBar.js b/webapp/src/components/navbar/NavBar.js index 4811bbea..72b2f4fa 100644 --- a/webapp/src/components/navbar/NavBar.js +++ b/webapp/src/components/navbar/NavBar.js @@ -70,10 +70,10 @@ const NavBar = ({ setDarkMode, darkMode}) => { diff --git a/webapp/src/internacionalizacion/locales/en/translation.json b/webapp/src/internacionalizacion/locales/en/translation.json index 2e7e177f..7ce00c4d 100644 --- a/webapp/src/internacionalizacion/locales/en/translation.json +++ b/webapp/src/internacionalizacion/locales/en/translation.json @@ -32,8 +32,10 @@ "totalTime": "Total time played", "averageTime": "Average time per game", "gameTime": "Game time", - "timeUnit" : "seconds", - "questionsPercentage": "%", + "timeUnit" : " seconds", + "timeUnitMinutes" : " minutes", + "timeUnitHours" : " hours", + "questionsPercentage": " %", "loginWelcomeMessage": "Welcome ", "loginDate": "Your account was created on ", diff --git a/webapp/src/internacionalizacion/locales/es/translation.json b/webapp/src/internacionalizacion/locales/es/translation.json index 81abee41..2b952bbd 100644 --- a/webapp/src/internacionalizacion/locales/es/translation.json +++ b/webapp/src/internacionalizacion/locales/es/translation.json @@ -32,8 +32,10 @@ "totalTime": "Tiempo total jugado", "averageTime": "Tiempo medio por partida", "gameTime": "Tiempo de la partida", - "timeUnit" : "segundos", - "questionsPercentage": "%", + "timeUnit" : " segundos", + "timeUnitMinutes" : " minutos", + "timeUnitHours" : " horas", + "questionsPercentage": " %", "loginWelcomeMessage": "Bienvenido ", "loginDate": "Tu cuenta fue creada el ", From 69ea7fc2d67f745b1e4ed8db58cf1fc26d4905f0 Mon Sep 17 00:00:00 2001 From: bidof Date: Thu, 4 Apr 2024 20:58:09 +0200 Subject: [PATCH 32/79] clase Game simplificada --- webapp/src/components/game/Game.js | 86 +++++++++---------- webapp/src/components/game/QuestionArea.jsx | 2 +- .../src/components/game/gameModes/GameMode.js | 2 + 3 files changed, 45 insertions(+), 45 deletions(-) diff --git a/webapp/src/components/game/Game.js b/webapp/src/components/game/Game.js index 599f8b8a..d6dbd2ac 100644 --- a/webapp/src/components/game/Game.js +++ b/webapp/src/components/game/Game.js @@ -17,58 +17,46 @@ function Game({darkMode,gameMode= new BasicMode(),endGame=null}) { const [correctAnswers, setCorrectAnswers] = useState(0); const [incorrectAnswers, setIncorrectAnswers] = useState(0); const [totalTime, setTotalTime] = useState(0); - const [finished, setFinished] = useState(false); const navigate = useNavigate(); const timeToAnswer = 20000; + const [finished, setFinished] = useState(false); + + + + //cada vez que cambie el estado finished actualizas tb el del game + useEffect(() => { + if (finished) { + gameMode.endGame(); + } + }, [finished]); + + //empiza el juego al cargar el componente useEffect(() => { gameMode.startGame(); }, []); - - //se ejecuta al cambiar el num de correctas que solo cambia si se ha terminado el juego - useEffect(()=>{ - if(finished&&localStorage.getItem('username')!=null && totalTime != 0){//tienes que estar logeado para guardar el historial - const data={ - user:localStorage.getItem('username'), - correctas:correctAnswers, - incorrectas:incorrectAnswers, - tiempoTotal:totalTime + //se encarga de comprobar el estado del juego y si ha terminado llama al metodo history del modo de juego + useEffect(() => { + if (finished && localStorage.getItem('username') != null && totalTime != 0) { + const data = { + correctas: correctAnswers, + incorrectas: incorrectAnswers, + tiempoTotal: totalTime }; - console.log("See envian los siguientes datos al historial" ,data); - fetch(`${apiEndpoint}/updateHistory`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify(data) - }) - .then(response => { - if (!response.ok) { - throw new Error('Network response was not ok'); - } - return response.json(); - }) - .then(data => { - console.log('Respuesta del servidor:', data); - setIsOpen(true); // Hacer que aparezca el cuadro de diálogo - }) - .catch(error => { - console.error('Error al enviar el historial al servidor:', error); - // Manejar el error si es necesario - }); - - - setIsOpen(true);//hacer que aparzca el cuadro de dialogo - - //comprobar si es mnultiplayer y si lo es se enviara al servidor que se ha finalizado el juego - if(multiplayerQuestions!=null){ - endGame(data); - } - + gameMode.sendHistory(data) + .then(() => { + setIsOpen(true); + //enviar el fin del juego para que se reinicie el juego o te quites el socket + endGame(data); + + }) + .catch(error => { + console.error('Error al enviar el historial al servidor:', error); + }); } - },[setFinished,correctAnswers,incorrectAnswers, totalTime]) + }, [finished, correctAnswers, incorrectAnswers, totalTime]); const onClose=()=>{ setIsOpen(false); @@ -98,8 +86,18 @@ function Game({darkMode,gameMode= new BasicMode(),endGame=null}) { />//Para mientras carga ) : ( - + + )} diff --git a/webapp/src/components/game/QuestionArea.jsx b/webapp/src/components/game/QuestionArea.jsx index 94f58623..96dbd021 100644 --- a/webapp/src/components/game/QuestionArea.jsx +++ b/webapp/src/components/game/QuestionArea.jsx @@ -9,7 +9,7 @@ import { GameTimer } from './timers/GameTimer.jsx'; *maneja la logica general del juego reincia el contador del timepo salta de pregunas etc, cuando el juego termina actualiza las respuestas correctas e incorrectas y se las apsas a game con prop Drilling */ -export function QuestionArea({darkMode, questions,setTotalCorrectAnswers, setTotalIncorrectAnswers,setFinished, setTotalTimeFinish, timeToAnswer=30000}){ +export function QuestionArea({darkMode, questions,setTotalCorrectAnswers, setTotalIncorrectAnswers,setFinished, setTotalTimeFinish, timeToAnswer=30000,nextQuestion}){ const [questionIndex, setQuestionIndex] = useState(0); // Nuevo estado para el índice de la pregunta // Estado para almacenar los datos de la pregunta const [questionData, setQuestionData] = useState(null); // Estado para almacenar los datosS de la pregunta diff --git a/webapp/src/components/game/gameModes/GameMode.js b/webapp/src/components/game/gameModes/GameMode.js index 66de5014..612d36b7 100644 --- a/webapp/src/components/game/gameModes/GameMode.js +++ b/webapp/src/components/game/gameModes/GameMode.js @@ -30,6 +30,8 @@ class GameMode { nextQuestion(){ throw new Error("Method 'nextQuestion' must be implemented."); } + + } export default GameMode; \ No newline at end of file From 81cb96d99a0985f3681343a532e852bfff790e67 Mon Sep 17 00:00:00 2001 From: bidof Date: Thu, 4 Apr 2024 21:38:11 +0200 Subject: [PATCH 33/79] nueva funciones agregadas al Modo basico del game , falt que funcione bien el questionArea --- webapp/src/components/game/BasicGame.js | 20 +++++++++++++++++-- webapp/src/components/game/Game.js | 16 ++++++--------- .../src/components/game/gameModes/GameMode.js | 5 +++-- 3 files changed, 27 insertions(+), 14 deletions(-) diff --git a/webapp/src/components/game/BasicGame.js b/webapp/src/components/game/BasicGame.js index d4f42cc2..96ac4d5e 100644 --- a/webapp/src/components/game/BasicGame.js +++ b/webapp/src/components/game/BasicGame.js @@ -10,6 +10,7 @@ class BasicGame extends GameMode { constructor() { super(); this.isLoading = true; + } async fetchQuestions() { try { @@ -76,13 +77,28 @@ class BasicGame extends GameMode { if(!this.isGameEnded){ this.questionIndex++; // es el json con pregunta {resp1 resp2 resp3 resp4 correcta} - const nextQuestion = this.questions[this.questionIndex]; + this.questions[this.questionIndex]; this.isLoading = false; - return nextQuestion; + return this.getCurrentQuestion();//devolver la nueva pregunta } else { this.endGame(); } } + async getCurrentQuestion() { + // Obtener la pregunta actual del array de preguntas + const data = this.questions[this.questionIndex]; + + // Convertir los datos de la pregunta al formato que necesita QuestionArea + const questionData = { + pregunta: data.pregunta, + correcta: data.correcta, + respuestasIncorrecta1: data.respuestasIncorrecta1, + respuestasIncorrecta2: data.respuestasIncorrecta2, + respuestasIncorrecta3: data.respuestasIncorrecta3 + }; + + return questionData; + } } export default BasicGame; \ No newline at end of file diff --git a/webapp/src/components/game/Game.js b/webapp/src/components/game/Game.js index d6dbd2ac..4514c4e4 100644 --- a/webapp/src/components/game/Game.js +++ b/webapp/src/components/game/Game.js @@ -18,24 +18,19 @@ function Game({darkMode,gameMode= new BasicMode(),endGame=null}) { const [incorrectAnswers, setIncorrectAnswers] = useState(0); const [totalTime, setTotalTime] = useState(0); const navigate = useNavigate(); - const timeToAnswer = 20000; - const [finished, setFinished] = useState(false); + const timeToAnswer = gameMode.timeToAnswer; + // Utilizar el estado isGameEnded de gameMode en lugar de mantener un estado separado en Game + const finished = gameMode.isGameEnded; - //cada vez que cambie el estado finished actualizas tb el del game - useEffect(() => { - if (finished) { - gameMode.endGame(); - } - }, [finished]); - //empiza el juego al cargar el componente useEffect(() => { gameMode.startGame(); }, []); + //se encarga de comprobar el estado del juego y si ha terminado llama al metodo history del modo de juego useEffect(() => { if (finished && localStorage.getItem('username') != null && totalTime != 0) { @@ -49,12 +44,13 @@ function Game({darkMode,gameMode= new BasicMode(),endGame=null}) { .then(() => { setIsOpen(true); //enviar el fin del juego para que se reinicie el juego o te quites el socket - endGame(data); + }) .catch(error => { console.error('Error al enviar el historial al servidor:', error); }); + gameMode.endGame();//terminar el juego } }, [finished, correctAnswers, incorrectAnswers, totalTime]); diff --git a/webapp/src/components/game/gameModes/GameMode.js b/webapp/src/components/game/gameModes/GameMode.js index 612d36b7..4270c232 100644 --- a/webapp/src/components/game/gameModes/GameMode.js +++ b/webapp/src/components/game/gameModes/GameMode.js @@ -9,7 +9,8 @@ class GameMode { this.isLoading=false; this.apiEndpoint = process.env.REACT_APP_API_URI || 'http://localhost:8000'; this.isGameEnded=false; - this.questionIndex=0; + this.questionIndex=-1; + this.timeToAnswer=20000; } async fetchQuestions() { throw new Error("Method 'fetchQuestions' must be implemented."); @@ -30,7 +31,7 @@ class GameMode { nextQuestion(){ throw new Error("Method 'nextQuestion' must be implemented."); } - + async getCurrentQuestion(){} } From 93a94c402a4af59d63f3636a430b7c65dfe7c316 Mon Sep 17 00:00:00 2001 From: bidof Date: Thu, 4 Apr 2024 22:00:02 +0200 Subject: [PATCH 34/79] se ha unido y simplificado lo maximo posible y eliminado clases incesarias --- webapp/src/App.js | 6 +- webapp/src/components/game/BasicGame.js | 5 +- webapp/src/components/game/Game.js | 120 ++++++++++-------- webapp/src/components/game/GameContext.js | 30 ----- webapp/src/components/game/QuestionArea.jsx | 112 ++++------------ .../src/components/startbutton/StartButton.js | 5 +- 6 files changed, 99 insertions(+), 179 deletions(-) delete mode 100644 webapp/src/components/game/GameContext.js diff --git a/webapp/src/App.js b/webapp/src/App.js index 12edaa06..cd56624a 100644 --- a/webapp/src/App.js +++ b/webapp/src/App.js @@ -13,8 +13,6 @@ import AuthenticatedLayout from './components/authenticationLayout'; import GuestLayout from './components/GuestLayout'; import Logout from './components/logout/Logout'; import {History} from './components/history/History'; -import {BasicGameMode } from './components/game/gameModes/basicGameMode'; -import {GameProvider} from './components/game/GameContext'; import PrincipalView from './components/principalView/PrincipalView'; import Room from './components/rooms/Room'; // Importa el componente de sala import CreateRoomForm from './components/rooms/CreateRoom'; // Importa el componente para crear sala @@ -39,7 +37,7 @@ const App = () => { - + } /> @@ -68,7 +66,7 @@ const App = () => { } /> - +
diff --git a/webapp/src/components/game/BasicGame.js b/webapp/src/components/game/BasicGame.js index 96ac4d5e..d2053a63 100644 --- a/webapp/src/components/game/BasicGame.js +++ b/webapp/src/components/game/BasicGame.js @@ -4,7 +4,7 @@ implementacion por defecto dle juego , esta implementacion es muy similar siempr pero en ciertos casos como en el online si no quieres guardar el historial o quieres x cosa heredas y sobreescribes y list */ -import GameMode from './GameMode'; +import GameMode from './gameModes/GameMode'; class BasicGame extends GameMode { constructor() { @@ -76,8 +76,7 @@ class BasicGame extends GameMode { this.isLoading = true; if(!this.isGameEnded){ this.questionIndex++; - // es el json con pregunta {resp1 resp2 resp3 resp4 correcta} - this.questions[this.questionIndex]; + this.isLoading = false; return this.getCurrentQuestion();//devolver la nueva pregunta } else { diff --git a/webapp/src/components/game/Game.js b/webapp/src/components/game/Game.js index 4514c4e4..96cd86cc 100644 --- a/webapp/src/components/game/Game.js +++ b/webapp/src/components/game/Game.js @@ -1,6 +1,5 @@ import { QuestionArea } from './QuestionArea'; import { useEffect, useState,useContext } from 'react'; -import {GameContext} from './GameContext'; import {useNavigate} from 'react-router-dom'; import { Spinner, Box, AlertDialog, AlertDialogBody, AlertDialogFooter, AlertDialogHeader, AlertDialogContent, AlertDialogOverlay, Button,Center } from "@chakra-ui/react"; import BasicGame from './BasicGame'; @@ -11,8 +10,7 @@ recibe el obj gameMode que contieene las preguntas para ese modo de juego recibe questions que son las del servidor si estas en multiplayer si no le pasa contexto se utiliziara el por defecto que es el GameContext */ -function Game({darkMode,gameMode= new BasicMode(),endGame=null}) { - +function Game({darkMode,gameMode= new BasicGame()}) { const [isOpen, setIsOpen] = useState(false); const [correctAnswers, setCorrectAnswers] = useState(0); const [incorrectAnswers, setIncorrectAnswers] = useState(0); @@ -23,6 +21,9 @@ function Game({darkMode,gameMode= new BasicMode(),endGame=null}) { // Utilizar el estado isGameEnded de gameMode en lugar de mantener un estado separado en Game const finished = gameMode.isGameEnded; + //se la pasa a questionArea + const [isFinished, setIsFinished] = useState(false); + @@ -57,9 +58,43 @@ function Game({darkMode,gameMode= new BasicMode(),endGame=null}) { const onClose=()=>{ setIsOpen(false); //solamente te iras si has cerrado el singleplayer en multiplayer no te vas hasta que no ves el ganador - if( multiplayerQuestions==null) + navigate('/home'); } + + + /*funcioneess que cuentas las correctas y demas para la interfaaz */ + +// Función para manejar cuando se selecciona una respuesta + const handleAnswerSelect = (isCorrect) => { + if (isCorrect) { + setCorrectAnswers(correctAnswers + 1); + } + else{ + setIncorrectAnswers(incorrectAnswers + 1); + } + Finish(); + }; + + /* + comprueba si terminaste el juego y si no es así, pasa a la siguiente pregunta */ + const Finish = () => { + if(gameMode.questionIndex === gameMode.questions.length - 1) + { + gameMode.setIsGameEnded(true); + } + else + { + gameMode.nextQuestion(); + } + }; + + //Este cuando quedemos sin tiempo (perder) + const handleTimeout = () => { + Finish(); + }; + + //Colores chakra dark - light console.log("En game"+darkMode.darkMode); let backgroundColorFirst= darkMode.darkMode? '#08313A' : '#FFFFF5'; @@ -69,55 +104,33 @@ function Game({darkMode,gameMode= new BasicMode(),endGame=null}) { return ( console.log("En game"+darkMode.darkMode), - {isLoading ? ( - //Para mientras carga - - ) : ( - - - )} - - - {multiplayerQuestions ? (
+ bgGradient={`linear(to-t, ${backgroundColorFirst}, ${backgroundColorSecond})`} + display="flex" justifyContent="center" alignItems="center"> + {gameMode.isLoading ? ( + //Para mientras carga + + ) : ( + + + )} - - - - Juego Terminado - - - - Esperando al resto de jugadores... - - - - - - - - -
):( @@ -138,7 +151,8 @@ function Game({darkMode,gameMode= new BasicMode(),endGame=null}) { - )} + +
); } diff --git a/webapp/src/components/game/GameContext.js b/webapp/src/components/game/GameContext.js deleted file mode 100644 index 795c7484..00000000 --- a/webapp/src/components/game/GameContext.js +++ /dev/null @@ -1,30 +0,0 @@ -import { createContext, useState, useEffect } from 'react'; - -export const GameContext = createContext(); -/* -es el encargado de crear el contexto par aque si te desloguae se peirdan los datos del juego, envolvera todos los componentes -encapsula el modo de jeugo la funcion empezar a jugar y reinicir juego para que esten disponibles tanto para el juego como para los botones que lo empiezan */ -export const GameProvider = ({ children,gameMode }) => { - const [questions, setQuestions] = useState([]); - const [isLoading, setIsLoading] = useState(true); - - const startGame = async () => { - resetGame(); - // Aquí deberías reemplazar `fetchQuestions` con tu propia lógica para obtener las preguntas - const fetchedQuestions = await gameMode.fetchQuestions(); - setQuestions(fetchedQuestions); - setIsLoading(false); - }; - - const resetGame = () => { - setQuestions([]); - setIsLoading(true); - }; - - - return ( - - {children} - - ); -}; \ No newline at end of file diff --git a/webapp/src/components/game/QuestionArea.jsx b/webapp/src/components/game/QuestionArea.jsx index 96dbd021..b59310be 100644 --- a/webapp/src/components/game/QuestionArea.jsx +++ b/webapp/src/components/game/QuestionArea.jsx @@ -1,121 +1,61 @@ -import { useEffect, useState,useRef } from 'react'; +import { useEffect, useState, useRef } from 'react'; import { Box } from "@chakra-ui/react"; import { AnswersBlock } from './AnswersBlock.jsx'; import { EnunciadoBlock } from './EnunciadoBlock.jsx'; import { Timer } from './timers/Timer'; import { GameTimer } from './timers/GameTimer.jsx'; -/* -*maneja la logica general del juego reincia el contador del timepo salta de pregunas etc, -cuando el juego termina actualiza las respuestas correctas e incorrectas y se las apsas a game con prop Drilling */ - -export function QuestionArea({darkMode, questions,setTotalCorrectAnswers, setTotalIncorrectAnswers,setFinished, setTotalTimeFinish, timeToAnswer=30000,nextQuestion}){ - const [questionIndex, setQuestionIndex] = useState(0); // Nuevo estado para el índice de la pregunta - // Estado para almacenar los datos de la pregunta - const [questionData, setQuestionData] = useState(null); // Estado para almacenar los datosS de la pregunta - // Estado para almacenar las respuestas - const [respuestas, setRespuestas] = useState([]); - // Estado que almacena la correcta - const [correcta, setCorrecta] = useState(); - const [open, setOpen] = useState(false); // Nuevo estado para controlar si el diálogo está abierto o cerrado - const [correctAnswers, setCorrectAnswers] = useState(0); // Nuevo estado para llevar la cuenta de las respuestas correctas - const [incorrectAnswers, setIncorrectAnswers] = useState(0); // Nuevo estado para llevar la cuenta de las respuestas incorrectas +export function QuestionArea({darkMode, question, setTotalCorrectAnswers, setTotalIncorrectAnswers, setFinished, setTotalTimeFinish, timeToAnswer=30000, nextQuestion}){ + const [correctAnswers, setCorrectAnswers] = useState(0); + const [incorrectAnswers, setIncorrectAnswers] = useState(0); const [totalTime, setTotalTime] = useState(0); + const [isGameEnded, setIsGameEnded] = useState(false); + const resetTimer = useRef(null); - - const[isGameEnded, setIsGameEnded] = useState(false); // Nuevo estado para controlar si el juego ha terminado o no - const resetTimer = useRef(null); // Ref para almacenar la función resetTimer - - - // Función para obtener los datos de la pregunta const fetchQuestionData = () => { try { - console.log("Array de preguntas en el juego: ", questions); - // Obtener los datos de la pregunta del array de preguntas - const data = questions[questionIndex]; // Usar el índice de la pregunta para obtener la pregunta actual - setQuestionData(data); // Actualizar el estado con los datos de la pregunta obtenidos del array - //Meto la correcta - setCorrecta(data.correcta); - //calcular respuestas + const data = question; const respuestasArray = [data.correcta, data.respuestasIncorrecta1, data.respuestasIncorrecta2, data.respuestasIncorrecta3]; - setRespuestas(respuestasArray); + return respuestasArray; } catch (error) { console.error('Error fetching question data:', error); } }; - // Llamar a la función al cargar el componente y cuando cambie el índice de la pregunta - useEffect(() => { - fetchQuestionData(); - }, [questionIndex]); + const respuestas = fetchQuestionData(); - //se lanza si se acaba el juego - useEffect(()=>{ - //como esto se lanza cada que se actiliza tb cuando se pone a false solamente guardas el historial al ser true + useEffect(() => { if(isGameEnded){ - //alert("Has terminado el juego, has acertado "+correctAnswers+" preguntas y has fallado "+incorrectAnswers+" preguntas"); - setOpen(true); setTotalCorrectAnswers(correctAnswers); setTotalIncorrectAnswers(incorrectAnswers); console.log("El juego ha terminado con un tiempo total de: ", totalTime, " segundos."); setTotalTimeFinish(totalTime); setFinished(true); - } - - },[isGameEnded, totalTime]) - // Función para manejar cuando se selecciona una respuesta + },[isGameEnded, totalTime]); + const handleAnswerSelect = (isCorrect) => { if (isCorrect) { setCorrectAnswers(correctAnswers + 1); - } - else{ + } else { setIncorrectAnswers(incorrectAnswers + 1); } - Finish(); - - }; - /* - comprueba si terminaste el juego y si no es así, pasa a la siguiente pregunta */ - const Finish = () => { - if(questionIndex===questions.length-1) - { - //poner a true el estado de juego terminado y ademas parar el reloj - setIsGameEnded(true); - } - else - { - loadNextQuestion(); - } - }; - - // Función para cargar la siguiente pregunta - const loadNextQuestion = () => { - //poes el indice en la nueva preggunta y actualizas el valor de la pregunta actual - setQuestionIndex(questionIndex+1); - fetchQuestionData();//obtener la siguiente pregunnta + nextQuestion(); }; - - //Este cuando quedemos sin tiempo (perder) const handleTimeout = () => { - Finish(); - }; - - const handleClose = () => { - setOpen(false); + nextQuestion(); }; - return( - - - - - - - - - - ) + return( + + + + + + + + + ) } \ No newline at end of file diff --git a/webapp/src/components/startbutton/StartButton.js b/webapp/src/components/startbutton/StartButton.js index 68f5f31d..6dad87bb 100644 --- a/webapp/src/components/startbutton/StartButton.js +++ b/webapp/src/components/startbutton/StartButton.js @@ -1,16 +1,15 @@ import React, { useContext } from 'react'; import { useNavigate } from 'react-router-dom'; import './startButton.css' -import { GameContext } from '../game/GameContext'; + const StartButton = () => { - const { startGame} = useContext(GameContext); const navigate = useNavigate(); const handleClick = () => { - startGame();//llamar a la funcion empezar juego + navigate("/game"); }; From 57edf790052d481aa5b52a3704390dc06a955bd0 Mon Sep 17 00:00:00 2001 From: bidof Date: Thu, 4 Apr 2024 22:16:27 +0200 Subject: [PATCH 35/79] la preguntas se cargan bien pero no se muestran da un error --- webapp/src/components/game/BasicGame.js | 19 ++++++++++++++----- webapp/src/components/game/Game.js | 15 ++++++++++++--- .../src/components/game/gameModes/GameMode.js | 2 +- 3 files changed, 27 insertions(+), 9 deletions(-) diff --git a/webapp/src/components/game/BasicGame.js b/webapp/src/components/game/BasicGame.js index d2053a63..9f5b69c3 100644 --- a/webapp/src/components/game/BasicGame.js +++ b/webapp/src/components/game/BasicGame.js @@ -9,16 +9,18 @@ import GameMode from './gameModes/GameMode'; class BasicGame extends GameMode { constructor() { super(); - this.isLoading = true; + } async fetchQuestions() { try { const response = await fetch(`${this.apiEndpoint}/getQuestionModoBasico`); const data = await response.json(); + this.questions = Object.values(data); - this.isLoading = false; + // console.log('preguntas', this.questions); + this.isLoading = false; } catch (error) { console.error('Error fetching question data:', error); @@ -29,6 +31,8 @@ class BasicGame extends GameMode { this.isLoading = true; this.questionIndex = 0; await this.fetchQuestions(); + this.isLoading = false; + } endGame() { this.isGameEnded = true; @@ -83,10 +87,15 @@ class BasicGame extends GameMode { this.endGame(); } } - async getCurrentQuestion() { + async getCurrentQuestion() { + // Comprobar si this.questions[this.questionIndex] es undefined + if (this.questions[this.questionIndex] === undefined) { + throw new Error('No question at index ' + this.questionIndex); + } + // Obtener la pregunta actual del array de preguntas const data = this.questions[this.questionIndex]; - + // Convertir los datos de la pregunta al formato que necesita QuestionArea const questionData = { pregunta: data.pregunta, @@ -95,7 +104,7 @@ class BasicGame extends GameMode { respuestasIncorrecta2: data.respuestasIncorrecta2, respuestasIncorrecta3: data.respuestasIncorrecta3 }; - + return questionData; } } diff --git a/webapp/src/components/game/Game.js b/webapp/src/components/game/Game.js index 96cd86cc..f5570d3a 100644 --- a/webapp/src/components/game/Game.js +++ b/webapp/src/components/game/Game.js @@ -24,12 +24,21 @@ function Game({darkMode,gameMode= new BasicGame()}) { //se la pasa a questionArea const [isFinished, setIsFinished] = useState(false); + //ojo no es el mismo que loading de gameMode + const[isLoading,setIsLoading]=useState(true);//para que no cargue el questionArea hasta que tengas las preguntas - //empiza el juego al cargar el componente useEffect(() => { - gameMode.startGame(); + const startGameAsync = async () => { + await gameMode.startGame(); + console.log('preguntas', gameMode.questions); + + // Establecer isLoading a false después de que las preguntas se hayan cargado + setIsLoading(false); + }; + + startGameAsync(); }, []); //se encarga de comprobar el estado del juego y si ha terminado llama al metodo history del modo de juego @@ -106,7 +115,7 @@ function Game({darkMode,gameMode= new BasicGame()}) { - {gameMode.isLoading ? ( + {isLoading ? ( Date: Thu, 4 Apr 2024 22:26:54 +0200 Subject: [PATCH 36/79] =?UTF-8?q?arreglado=20el=20questionservice=20habia?= =?UTF-8?q?=20un=20peque=C3=B1o=20fallo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- questionservice/obtenerPreguntasBaseDatos.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/questionservice/obtenerPreguntasBaseDatos.js b/questionservice/obtenerPreguntasBaseDatos.js index d1df439f..ffc1511b 100644 --- a/questionservice/obtenerPreguntasBaseDatos.js +++ b/questionservice/obtenerPreguntasBaseDatos.js @@ -14,6 +14,12 @@ class ObtenerPreguntas{ //Se cojen las preguntas del numero que se pase por parametro var preguntas = await Pregunta.aggregate([{ $sample: { size: numeroPreguntas } }]); + //comprobamos si hay preguntas + if(preguntas.length != numeroPreguntas){ + console.log("Entra al error"); + throw new Error("No se han devuelto el numero de preguntas necesario"); + } + for(var i = 0; i < preguntas.length; i++){ try{ var tipo = await Tipos.findOne({ idPreguntas: { $in: preguntas[i]._id } }); @@ -43,11 +49,6 @@ class ObtenerPreguntas{ } } - //comprobamos si hay preguntas - if(preguntas.lenght != numeroPreguntas){ - throw new Error("No se han devuelto el numero de preguntas necesario"); - } - console.log("Preguntas finales: " + objetoExterno); return objetoExterno; } From 34539384e109b795821e07da1902b524a1efb135 Mon Sep 17 00:00:00 2001 From: bidof Date: Thu, 4 Apr 2024 23:39:11 +0200 Subject: [PATCH 37/79] problema , no se cambian las preguntas por la sincronia --- .vscode/extensions.json | 5 ++ webapp/src/components/game/BasicGame.js | 32 ++++---- webapp/src/components/game/Game.js | 80 ++++++++++++------- webapp/src/components/game/QuestionArea.jsx | 43 +++++----- .../src/components/game/gameModes/GameMode.js | 4 +- 5 files changed, 94 insertions(+), 70 deletions(-) create mode 100644 .vscode/extensions.json diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 00000000..174a5cca --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,5 @@ +{ + "recommendations": [ + "github.copilot" + ] +} \ No newline at end of file diff --git a/webapp/src/components/game/BasicGame.js b/webapp/src/components/game/BasicGame.js index 9f5b69c3..899eef06 100644 --- a/webapp/src/components/game/BasicGame.js +++ b/webapp/src/components/game/BasicGame.js @@ -9,32 +9,31 @@ import GameMode from './gameModes/GameMode'; class BasicGame extends GameMode { constructor() { super(); - + // Vincular nextQuestion al contexto correcto + this.nextQuestion = this.nextQuestion.bind(this); } async fetchQuestions() { try { const response = await fetch(`${this.apiEndpoint}/getQuestionModoBasico`); const data = await response.json(); - - + this.questions = Object.values(data); - // console.log('preguntas', this.questions); - this.isLoading = false; - + this.isLoading = false; // Mover esta línea aquí + } catch (error) { console.error('Error fetching question data:', error); } return this.questions; } + async startGame() { this.isLoading = true; this.questionIndex = 0; await this.fetchQuestions(); this.isLoading = false; - } - endGame() { + async endGame() { this.isGameEnded = true; this.questionIndex=0; } @@ -76,18 +75,21 @@ class BasicGame extends GameMode { }); } } - async nextQuestion() { + nextQuestion() { + console.log('questions en el next questions ',this.questions); this.isLoading = true; - if(!this.isGameEnded){ + if (this.questionIndex >= this.questions.length - 1) { + this.endGame(); + return null; + } else { this.questionIndex++; - + const currentQuestion = this.getCurrentQuestion(); + this.isLoading = false; - return this.getCurrentQuestion();//devolver la nueva pregunta - } else { - this.endGame(); + return currentQuestion; // devolver la pregunta actual } } - async getCurrentQuestion() { + getCurrentQuestion() { // Comprobar si this.questions[this.questionIndex] es undefined if (this.questions[this.questionIndex] === undefined) { throw new Error('No question at index ' + this.questionIndex); diff --git a/webapp/src/components/game/Game.js b/webapp/src/components/game/Game.js index f5570d3a..8ea03e29 100644 --- a/webapp/src/components/game/Game.js +++ b/webapp/src/components/game/Game.js @@ -17,7 +17,7 @@ function Game({darkMode,gameMode= new BasicGame()}) { const [totalTime, setTotalTime] = useState(0); const navigate = useNavigate(); const timeToAnswer = gameMode.timeToAnswer; - + // Utilizar el estado isGameEnded de gameMode en lugar de mantener un estado separado en Game const finished = gameMode.isGameEnded; @@ -28,21 +28,35 @@ function Game({darkMode,gameMode= new BasicGame()}) { const[isLoading,setIsLoading]=useState(true);//para que no cargue el questionArea hasta que tengas las preguntas - //empiza el juego al cargar el componente + const [currentQuestion, setCurrentQuestion] = useState(null); + useEffect(() => { const startGameAsync = async () => { await gameMode.startGame(); console.log('preguntas', gameMode.questions); - - // Establecer isLoading a false después de que las preguntas se hayan cargado + + const currentQuestion = gameMode.getCurrentQuestion(); + console.log('primera pregunta ', currentQuestion); + + // Establecer la pregunta actual y isLoading a false después de que las preguntas se hayan cargado + setCurrentQuestion(currentQuestion); setIsLoading(false); + }; - + startGameAsync(); }, []); + //se llamad cada vez que hace una pregunta + useEffect(() => { + //carga la siguiente pregunta el !==0 es porque al cargar el componente ya sacas la primera + if (!gameMode.isGameEnded && gameMode.questionIndex>0) { + const nextQuestion = gameMode.nextQuestion(); + setCurrentQuestion(nextQuestion); + } + }, [correctAnswers, incorrectAnswers]); - //se encarga de comprobar el estado del juego y si ha terminado llama al metodo history del modo de juego useEffect(() => { + if (finished && localStorage.getItem('username') != null && totalTime != 0) { const data = { correctas: correctAnswers, @@ -62,7 +76,7 @@ function Game({darkMode,gameMode= new BasicGame()}) { }); gameMode.endGame();//terminar el juego } - }, [finished, correctAnswers, incorrectAnswers, totalTime]); + }, [finished, totalTime,gameMode.isGameEnded]); const onClose=()=>{ setIsOpen(false); @@ -87,14 +101,15 @@ function Game({darkMode,gameMode= new BasicGame()}) { /* comprueba si terminaste el juego y si no es así, pasa a la siguiente pregunta */ - const Finish = () => { - if(gameMode.questionIndex === gameMode.questions.length - 1) - { + const Finish = async () => { + console.log('entra en el finish'); + if(gameMode.questionIndex === gameMode.questions.length - 1) { gameMode.setIsGameEnded(true); - } - else - { - gameMode.nextQuestion(); + } else { + const nextQuestion = await gameMode.nextQuestion(); + + setCurrentQuestion(nextQuestion); + console.log('siguiente pregunta tras hacer click ', nextQuestion); } }; @@ -102,16 +117,19 @@ function Game({darkMode,gameMode= new BasicGame()}) { const handleTimeout = () => { Finish(); }; - + //incrementa el timepo en x + const incrementTime = (x) => { + setTotalTime(totalTime + x); + }; + //Colores chakra dark - light - console.log("En game"+darkMode.darkMode); + let backgroundColorFirst= darkMode.darkMode? '#08313A' : '#FFFFF5'; let backgroundColorSecond= darkMode.darkMode? '#107869' : '#FDF4E3'; //#08313A, #107869 return ( - console.log("En game"+darkMode.darkMode), @@ -124,20 +142,24 @@ function Game({darkMode,gameMode= new BasicGame()}) { size='xl' marginTop='5em' />//Para mientras carga - + ) : ( - + )} diff --git a/webapp/src/components/game/QuestionArea.jsx b/webapp/src/components/game/QuestionArea.jsx index b59310be..0a8b3e64 100644 --- a/webapp/src/components/game/QuestionArea.jsx +++ b/webapp/src/components/game/QuestionArea.jsx @@ -5,18 +5,22 @@ import { EnunciadoBlock } from './EnunciadoBlock.jsx'; import { Timer } from './timers/Timer'; import { GameTimer } from './timers/GameTimer.jsx'; -export function QuestionArea({darkMode, question, setTotalCorrectAnswers, setTotalIncorrectAnswers, setFinished, setTotalTimeFinish, timeToAnswer=30000, nextQuestion}){ - const [correctAnswers, setCorrectAnswers] = useState(0); - const [incorrectAnswers, setIncorrectAnswers] = useState(0); - const [totalTime, setTotalTime] = useState(0); +export function QuestionArea({darkMode, question, setFinished, setTotalTime, timeToAnswer=30000,onAnswerSelect,handleTimeout,incrementTime}){ + + // Eliminar los estados correctAnswers e incorrectAnswers + + const [time, setTime] = useState(0); const [isGameEnded, setIsGameEnded] = useState(false); const resetTimer = useRef(null); const fetchQuestionData = () => { try { const data = question; + const respuestasArray = [data.correcta, data.respuestasIncorrecta1, data.respuestasIncorrecta2, data.respuestasIncorrecta3]; + return respuestasArray; + } catch (error) { console.error('Error fetching question data:', error); } @@ -24,38 +28,27 @@ export function QuestionArea({darkMode, question, setTotalCorrectAnswers, setTot const respuestas = fetchQuestionData(); - useEffect(() => { - if(isGameEnded){ - setTotalCorrectAnswers(correctAnswers); - setTotalIncorrectAnswers(incorrectAnswers); - console.log("El juego ha terminado con un tiempo total de: ", totalTime, " segundos."); - setTotalTimeFinish(totalTime); - setFinished(true); - } - },[isGameEnded, totalTime]); - - const handleAnswerSelect = (isCorrect) => { - if (isCorrect) { - setCorrectAnswers(correctAnswers + 1); - } else { - setIncorrectAnswers(incorrectAnswers + 1); - } - nextQuestion(); + + const handleButtonClick = (isCorrect) => { + // Llamar a onAnswerSelect cuando se selecciona una respuesta + onAnswerSelect(isCorrect); + incrementTime(100); }; - const handleTimeout = () => { - nextQuestion(); + const onTimeout = () => { + handleTimeout(); + }; return( - + - + ) } \ No newline at end of file diff --git a/webapp/src/components/game/gameModes/GameMode.js b/webapp/src/components/game/gameModes/GameMode.js index b0545acf..0f275b33 100644 --- a/webapp/src/components/game/gameModes/GameMode.js +++ b/webapp/src/components/game/gameModes/GameMode.js @@ -31,7 +31,9 @@ class GameMode { nextQuestion(){ throw new Error("Method 'nextQuestion' must be implemented."); } - async getCurrentQuestion(){} + getCurrentQuestion(){ + throw new Error("Method 'nextQuestion' must be implemented."); + } } From 753a08a11dabd363aeac062a785851271261122c Mon Sep 17 00:00:00 2001 From: bidof Date: Fri, 5 Apr 2024 00:06:43 +0200 Subject: [PATCH 38/79] ya se cargan todas las preguntas falta el endgame --- webapp/src/components/game/AnswersBlock.jsx | 3 +- webapp/src/components/game/Game.js | 59 +++++++++++---------- 2 files changed, 32 insertions(+), 30 deletions(-) diff --git a/webapp/src/components/game/AnswersBlock.jsx b/webapp/src/components/game/AnswersBlock.jsx index 6052089a..7310ebab 100644 --- a/webapp/src/components/game/AnswersBlock.jsx +++ b/webapp/src/components/game/AnswersBlock.jsx @@ -9,7 +9,8 @@ export function AnswersBlock({ respuestas, correcta ,onAnswerSelect,isGameEnded, let respuestasCopy = respuestas; //Colores de los botones para que tengan orden random - console.log("En asnblock "+darkMode.darkMode); + //console.log("En asnblock "+darkMode.darkMode); + const colorsArray= darkMode.darkMode? ["#FFD743","#D773A2","#07BB9C","#A06AB4"] : ["#E3C7E0","#BAE5C0","#84C7F2","#E8F7FF"]; //Baraja con algoritmo de Fisher-Yates los colores for (let i = colorsArray.length - 1; i > 0; i--) { diff --git a/webapp/src/components/game/Game.js b/webapp/src/components/game/Game.js index 8ea03e29..6e01003c 100644 --- a/webapp/src/components/game/Game.js +++ b/webapp/src/components/game/Game.js @@ -29,31 +29,45 @@ function Game({darkMode,gameMode= new BasicGame()}) { const [currentQuestion, setCurrentQuestion] = useState(null); - + const [gameModeInstance, setGameModeInstance] = useState(gameMode);//guarda la instancia tras obtener las preguntas useEffect(() => { const startGameAsync = async () => { + setIsLoading(true); // Establecer isLoading a true antes de cargar las preguntas await gameMode.startGame(); console.log('preguntas', gameMode.questions); - + const currentQuestion = gameMode.getCurrentQuestion(); console.log('primera pregunta ', currentQuestion); - + // Establecer la pregunta actual y isLoading a false después de que las preguntas se hayan cargado setCurrentQuestion(currentQuestion); setIsLoading(false); + setGameModeInstance(gameMode); }; - + startGameAsync(); }, []); - //se llamad cada vez que hace una pregunta - useEffect(() => { - //carga la siguiente pregunta el !==0 es porque al cargar el componente ya sacas la primera - if (!gameMode.isGameEnded && gameMode.questionIndex>0) { - const nextQuestion = gameMode.nextQuestion(); - setCurrentQuestion(nextQuestion); - } - }, [correctAnswers, incorrectAnswers]); + + + +// Función para manejar cuando se selecciona una respuesta +const handleAnswerSelect = (isCorrect) => { + if (isCorrect) { + setCorrectAnswers(correctAnswers + 1); + } else { + setIncorrectAnswers(incorrectAnswers + 1); + } +}; + +// useEffect que se dispara cuando correctAnswers o incorrectAnswers cambian +useEffect(() => { + console.log('corectAnswer useEffect el valor de las preguntas es ',gameModeInstance.questions); + if (correctAnswers + incorrectAnswers < gameModeInstance.questions.length) { + const nextQuestion = gameModeInstance.nextQuestion(); + setCurrentQuestion(nextQuestion); + } +}, [correctAnswers, incorrectAnswers]); useEffect(() => { @@ -76,7 +90,7 @@ function Game({darkMode,gameMode= new BasicGame()}) { }); gameMode.endGame();//terminar el juego } - }, [finished, totalTime,gameMode.isGameEnded]); + }, [finished, totalTime,gameModeInstance.isGameEnded]); const onClose=()=>{ setIsOpen(false); @@ -86,27 +100,14 @@ function Game({darkMode,gameMode= new BasicGame()}) { } - /*funcioneess que cuentas las correctas y demas para la interfaaz */ - -// Función para manejar cuando se selecciona una respuesta - const handleAnswerSelect = (isCorrect) => { - if (isCorrect) { - setCorrectAnswers(correctAnswers + 1); - } - else{ - setIncorrectAnswers(incorrectAnswers + 1); - } - Finish(); - }; - /* comprueba si terminaste el juego y si no es así, pasa a la siguiente pregunta */ const Finish = async () => { console.log('entra en el finish'); - if(gameMode.questionIndex === gameMode.questions.length - 1) { - gameMode.setIsGameEnded(true); + if(gameModeInstance.questionIndex === gameModeInstance.questions.length - 1) { + gameModeInstance.setIsGameEnded(true); } else { - const nextQuestion = await gameMode.nextQuestion(); + const nextQuestion = await gameModeInstance.nextQuestion(); setCurrentQuestion(nextQuestion); console.log('siguiente pregunta tras hacer click ', nextQuestion); From 628faae2def71efd6477c0f454ea5c2edc7b4e6d Mon Sep 17 00:00:00 2001 From: bidof Date: Fri, 5 Apr 2024 01:10:44 +0200 Subject: [PATCH 39/79] juego singleplayer arreglado --- webapp/src/components/game/BasicGame.js | 18 +++- webapp/src/components/game/Game.js | 137 ++++++++++-------------- 2 files changed, 71 insertions(+), 84 deletions(-) diff --git a/webapp/src/components/game/BasicGame.js b/webapp/src/components/game/BasicGame.js index 899eef06..fa78b810 100644 --- a/webapp/src/components/game/BasicGame.js +++ b/webapp/src/components/game/BasicGame.js @@ -34,6 +34,7 @@ class BasicGame extends GameMode { this.isLoading = false; } async endGame() { + console.log('endGameeeeeeeeee'); this.isGameEnded = true; this.questionIndex=0; } @@ -75,16 +76,20 @@ class BasicGame extends GameMode { }); } } - nextQuestion() { + nextQuestion() { console.log('questions en el next questions ',this.questions); this.isLoading = true; - if (this.questionIndex >= this.questions.length - 1) { - this.endGame(); + console.log('a ',this.questionIndex); + if (this.questionIndex >=9) { + console.log("fin juego"); + this.finishGame(); return null; } else { + // Incrementar this.questionIndex después de comprobar si has llegado a la última pregunta this.questionIndex++; + console.log('b ',this.questionIndex); const currentQuestion = this.getCurrentQuestion(); - + console.log('c ',currentQuestion); this.isLoading = false; return currentQuestion; // devolver la pregunta actual } @@ -109,6 +114,11 @@ class BasicGame extends GameMode { return questionData; } + + finishGame(){ + this.isGameEnded = true; + this.endGame(); + } } export default BasicGame; \ No newline at end of file diff --git a/webapp/src/components/game/Game.js b/webapp/src/components/game/Game.js index 6e01003c..e9d68769 100644 --- a/webapp/src/components/game/Game.js +++ b/webapp/src/components/game/Game.js @@ -1,15 +1,10 @@ import { QuestionArea } from './QuestionArea'; -import { useEffect, useState,useContext } from 'react'; +import { useEffect, useState, useRef } from 'react'; import {useNavigate} from 'react-router-dom'; import { Spinner, Box, AlertDialog, AlertDialogBody, AlertDialogFooter, AlertDialogHeader, AlertDialogContent, AlertDialogOverlay, Button,Center } from "@chakra-ui/react"; import BasicGame from './BasicGame'; const apiEndpoint = process.env.REACT_APP_API_URI ||'http://localhost:8000'; -/* -recibe el obj gameMode que contieene las preguntas para ese modo de juego -recibe questions que son las del servidor si estas en multiplayer - si no le pasa contexto se utiliziara el por defecto que es el GameContext -*/ function Game({darkMode,gameMode= new BasicGame()}) { const [isOpen, setIsOpen] = useState(false); const [correctAnswers, setCorrectAnswers] = useState(0); @@ -18,60 +13,73 @@ function Game({darkMode,gameMode= new BasicGame()}) { const navigate = useNavigate(); const timeToAnswer = gameMode.timeToAnswer; - // Utilizar el estado isGameEnded de gameMode en lugar de mantener un estado separado en Game - const finished = gameMode.isGameEnded; - - //se la pasa a questionArea const [isFinished, setIsFinished] = useState(false); + const[isLoading,setIsLoading]=useState(true); + const [currentQuestion, setCurrentQuestion] = useState(null); - //ojo no es el mismo que loading de gameMode - const[isLoading,setIsLoading]=useState(true);//para que no cargue el questionArea hasta que tengas las preguntas - + const gameModeRef = useRef(gameMode); - const [currentQuestion, setCurrentQuestion] = useState(null); - const [gameModeInstance, setGameModeInstance] = useState(gameMode);//guarda la instancia tras obtener las preguntas useEffect(() => { const startGameAsync = async () => { - setIsLoading(true); // Establecer isLoading a true antes de cargar las preguntas + setIsLoading(true); await gameMode.startGame(); console.log('preguntas', gameMode.questions); const currentQuestion = gameMode.getCurrentQuestion(); console.log('primera pregunta ', currentQuestion); - // Establecer la pregunta actual y isLoading a false después de que las preguntas se hayan cargado setCurrentQuestion(currentQuestion); setIsLoading(false); - - setGameModeInstance(gameMode); + + gameModeRef.current = gameMode; }; startGameAsync(); }, []); - + useEffect(() => { + const startGameAsync = async () => { + setIsLoading(true); + await gameModeRef.current.startGame(); + console.log('preguntas', gameModeRef.current.questions); -// Función para manejar cuando se selecciona una respuesta -const handleAnswerSelect = (isCorrect) => { - if (isCorrect) { - setCorrectAnswers(correctAnswers + 1); - } else { - setIncorrectAnswers(incorrectAnswers + 1); - } -}; - -// useEffect que se dispara cuando correctAnswers o incorrectAnswers cambian -useEffect(() => { - console.log('corectAnswer useEffect el valor de las preguntas es ',gameModeInstance.questions); - if (correctAnswers + incorrectAnswers < gameModeInstance.questions.length) { - const nextQuestion = gameModeInstance.nextQuestion(); - setCurrentQuestion(nextQuestion); - } -}, [correctAnswers, incorrectAnswers]); + const currentQuestion = gameModeRef.current.getCurrentQuestion(); + console.log('primera pregunta ', currentQuestion); + + setCurrentQuestion(currentQuestion); + setIsLoading(false); + }; + + startGameAsync(); + }, []); + + const handleAnswerSelect = (isCorrect) => { + if (isCorrect) { + setCorrectAnswers(correctAnswers + 1); + } else { + setIncorrectAnswers(incorrectAnswers + 1); + } + }; + + useEffect(() => { + //console.log('corectAnswer useEffect el valor de las preguntas es ',gameModeRef.questions); + console.log("entra en el useEffect de correctAnswer",correctAnswers,incorrectAnswers,gameModeRef.current.questions.length); + + if (correctAnswers + incorrectAnswers < gameModeRef.current.questions.length ) { //para que no entre en el finished nada mas cargar el juegu + console.log("entra en el if del correctAnswer"); + const nextQuestion = gameModeRef.current.nextQuestion(); + console.log('siguiente pregunta',nextQuestion); + setCurrentQuestion(nextQuestion); + } else if(gameModeRef.current.questions.length != 0){ //no deberia entrar cuando se cargue el componente + console.log("use effect finish"); + gameModeRef.current.finishGame(); + } + }, [correctAnswers, incorrectAnswers]); useEffect(() => { - - if (finished && localStorage.getItem('username') != null && totalTime != 0) { + console.log("entra en el useEffect del finished"); + console.log('valores de IsGameEnded y totalTime',gameModeRef.current.isGameEnded,totalTime) + if (gameModeRef.current.isGameEnded &&localStorage.getItem('username') != null && totalTime != 0) { const data = { correctas: correctAnswers, incorrectas: incorrectAnswers, @@ -80,55 +88,30 @@ useEffect(() => { gameMode.sendHistory(data) .then(() => { - setIsOpen(true); - //enviar el fin del juego para que se reinicie el juego o te quites el socket - - + console.log('Historial enviado correctamente'); }) .catch(error => { console.error('Error al enviar el historial al servidor:', error); }); - gameMode.endGame();//terminar el juego + setIsOpen(true); } - }, [finished, totalTime,gameModeInstance.isGameEnded]); + }, [totalTime,gameModeRef.current.isGameEnded]); const onClose=()=>{ setIsOpen(false); - //solamente te iras si has cerrado el singleplayer en multiplayer no te vas hasta que no ves el ganador - navigate('/home'); } - - /* - comprueba si terminaste el juego y si no es así, pasa a la siguiente pregunta */ - const Finish = async () => { - console.log('entra en el finish'); - if(gameModeInstance.questionIndex === gameModeInstance.questions.length - 1) { - gameModeInstance.setIsGameEnded(true); - } else { - const nextQuestion = await gameModeInstance.nextQuestion(); - - setCurrentQuestion(nextQuestion); - console.log('siguiente pregunta tras hacer click ', nextQuestion); - } - }; - - //Este cuando quedemos sin tiempo (perder) const handleTimeout = () => { - Finish(); + handleAnswerSelect(false); }; - //incrementa el timepo en x + const incrementTime = (x) => { setTotalTime(totalTime + x); }; - - //Colores chakra dark - light - let backgroundColorFirst= darkMode.darkMode? '#08313A' : '#FFFFF5'; let backgroundColorSecond= darkMode.darkMode? '#107869' : '#FDF4E3'; - //#08313A, #107869 return ( { color='blue.500' size='xl' marginTop='5em' - />//Para mientras carga - + /> ) : ( - { setFinished={setIsFinished} setTotalTime={setTotalTime} timeToAnswer={timeToAnswer} - onAnswerSelect={handleAnswerSelect} // Pasar handleAnswerSelect como prop - handleTimeout={handleTimeout} // Pasar handleTimeout como prop - incrementTime={incrementTime} // Pasar incrementTime como prop - - + onAnswerSelect={handleAnswerSelect} + handleTimeout={handleTimeout} + incrementTime={incrementTime} /> )} @@ -176,7 +155,6 @@ useEffect(() => { - @@ -184,9 +162,8 @@ useEffect(() => { - ); } -export default Game; +export default Game; \ No newline at end of file From 871f2676fdaa695b53067cc96b2c2df7f28b774e Mon Sep 17 00:00:00 2001 From: bidof Date: Fri, 5 Apr 2024 01:24:31 +0200 Subject: [PATCH 40/79] arreglado problema del historial --- webapp/src/components/game/Game.js | 10 ++++------ webapp/src/components/game/QuestionArea.jsx | 6 +++--- webapp/src/components/game/timers/GameTimer.jsx | 6 +++--- 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/webapp/src/components/game/Game.js b/webapp/src/components/game/Game.js index e9d68769..dfb6551b 100644 --- a/webapp/src/components/game/Game.js +++ b/webapp/src/components/game/Game.js @@ -73,6 +73,7 @@ function Game({darkMode,gameMode= new BasicGame()}) { } else if(gameModeRef.current.questions.length != 0){ //no deberia entrar cuando se cargue el componente console.log("use effect finish"); gameModeRef.current.finishGame(); + setIsFinished(true); } }, [correctAnswers, incorrectAnswers]); @@ -106,10 +107,6 @@ function Game({darkMode,gameMode= new BasicGame()}) { handleAnswerSelect(false); }; - const incrementTime = (x) => { - setTotalTime(totalTime + x); - }; - let backgroundColorFirst= darkMode.darkMode? '#08313A' : '#FFFFF5'; let backgroundColorSecond= darkMode.darkMode? '#107869' : '#FDF4E3'; @@ -133,12 +130,13 @@ function Game({darkMode,gameMode= new BasicGame()}) { question={currentQuestion} setTotalCorrectAnswers={setCorrectAnswers} setTotalIncorrectAnswers={setIncorrectAnswers} - setFinished={setIsFinished} + isFinished={isFinished} setTotalTime={setTotalTime} timeToAnswer={timeToAnswer} onAnswerSelect={handleAnswerSelect} handleTimeout={handleTimeout} - incrementTime={incrementTime} + + /> )} diff --git a/webapp/src/components/game/QuestionArea.jsx b/webapp/src/components/game/QuestionArea.jsx index 0a8b3e64..f955337f 100644 --- a/webapp/src/components/game/QuestionArea.jsx +++ b/webapp/src/components/game/QuestionArea.jsx @@ -5,7 +5,7 @@ import { EnunciadoBlock } from './EnunciadoBlock.jsx'; import { Timer } from './timers/Timer'; import { GameTimer } from './timers/GameTimer.jsx'; -export function QuestionArea({darkMode, question, setFinished, setTotalTime, timeToAnswer=30000,onAnswerSelect,handleTimeout,incrementTime}){ +export function QuestionArea({darkMode, question, isFinished, setTotalTime, timeToAnswer=30000,onAnswerSelect,handleTimeout}){ // Eliminar los estados correctAnswers e incorrectAnswers @@ -32,7 +32,7 @@ export function QuestionArea({darkMode, question, setFinished, setTotalTime, tim const handleButtonClick = (isCorrect) => { // Llamar a onAnswerSelect cuando se selecciona una respuesta onAnswerSelect(isCorrect); - incrementTime(100); + //incrementTime(100); }; const onTimeout = () => { @@ -46,7 +46,7 @@ export function QuestionArea({darkMode, question, setFinished, setTotalTime, tim - + diff --git a/webapp/src/components/game/timers/GameTimer.jsx b/webapp/src/components/game/timers/GameTimer.jsx index c5c77674..fdfd6d33 100644 --- a/webapp/src/components/game/timers/GameTimer.jsx +++ b/webapp/src/components/game/timers/GameTimer.jsx @@ -4,11 +4,11 @@ import { FaRegClock } from 'react-icons/fa'; -export function GameTimer({darkMode, isGameEnded, setTotalTime }) { +export function GameTimer({darkMode, isFinished, setTotalTime }) { const [seconds, setSeconds] = useState(0); useEffect(() => { - if (isGameEnded) { + if (isFinished) { console.log("El juego ha terminado con un tiempo total de: ", seconds, " segundos."); setTotalTime(seconds); return; @@ -19,7 +19,7 @@ export function GameTimer({darkMode, isGameEnded, setTotalTime }) { }, 1000); return () => clearInterval(intervalId); - }, [isGameEnded, seconds, setTotalTime]); + }, [isFinished, seconds, setTotalTime]); let textColor = darkMode.darkMode? "#FCFAF0" : "#08313A"; From 6b2b3f9b55e00f6c6e9c0db41ec130a3e6334a58 Mon Sep 17 00:00:00 2001 From: bidof Date: Fri, 5 Apr 2024 01:30:34 +0200 Subject: [PATCH 41/79] dejar quieto el reloj al terminar la partida --- webapp/src/components/game/QuestionArea.jsx | 2 +- webapp/src/components/game/timers/Timer.jsx | 13 ++++++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/webapp/src/components/game/QuestionArea.jsx b/webapp/src/components/game/QuestionArea.jsx index f955337f..313d615b 100644 --- a/webapp/src/components/game/QuestionArea.jsx +++ b/webapp/src/components/game/QuestionArea.jsx @@ -44,7 +44,7 @@ export function QuestionArea({darkMode, question, isFinished, setTotalTime, time - + diff --git a/webapp/src/components/game/timers/Timer.jsx b/webapp/src/components/game/timers/Timer.jsx index 2d4442bb..79242e9c 100644 --- a/webapp/src/components/game/timers/Timer.jsx +++ b/webapp/src/components/game/timers/Timer.jsx @@ -2,7 +2,7 @@ import React, { useEffect, useState, useRef } from 'react'; import { CircularProgress, CircularProgressLabel, useColorModeValue } from '@chakra-ui/react'; import { Box } from "@chakra-ui/react"; -export const Timer = React.memo(({ onTimeout, timeout = 30000, resetTimer, darkMode }) => { +export const Timer = React.memo(({ onTimeout, timeout = 30000, resetTimer, darkMode,gameFinish }) => { const [progress, setProgress] = useState(100); // Inicia el progreso en 100% const intervalRef = useRef(null); @@ -10,9 +10,16 @@ export const Timer = React.memo(({ onTimeout, timeout = 30000, resetTimer, darkM resetTimer.current = resetTimerFunction; }, []); + + useEffect(() => { const startTime = Date.now(); intervalRef.current = setInterval(() => { + if (gameFinish) { + clearInterval(intervalRef.current); + setProgress(100); + return; + } const elapsedTime = Date.now() - startTime; const remainingTime = timeout - elapsedTime; const remainingProgress = (remainingTime / timeout) * 100; @@ -22,9 +29,9 @@ export const Timer = React.memo(({ onTimeout, timeout = 30000, resetTimer, darkM onTimeout(); } }, 1000); // Actualiza el progreso cada segundo - + return () => clearInterval(intervalRef.current); - }, [timeout, onTimeout]); + }, [timeout, onTimeout, gameFinish]); const resetTimerFunction = () => { clearInterval(intervalRef.current); From 86730f1dbcd63fae76a8e387f124f7d86c5b6980 Mon Sep 17 00:00:00 2001 From: bidof Date: Fri, 5 Apr 2024 02:05:38 +0200 Subject: [PATCH 42/79] juego multiplayer funiconnado falta el final --- webapp/package-lock.json | 10 ++++++ webapp/package.json | 1 + .../src/components/game/gameModes/RoomGame.js | 34 +++++++++++++++++++ webapp/src/components/rooms/Room.js | 33 ++++-------------- 4 files changed, 52 insertions(+), 26 deletions(-) create mode 100644 webapp/src/components/game/gameModes/RoomGame.js diff --git a/webapp/package-lock.json b/webapp/package-lock.json index 2cff6d62..d44e7d2c 100644 --- a/webapp/package-lock.json +++ b/webapp/package-lock.json @@ -25,6 +25,7 @@ "react-router-dom": "^6.22.3", "react-scripts": "5.0.1", "socket.io-client": "^4.7.5", + "sweetalert2": "^11.10.7", "web-vitals": "^3.5.1" }, "devDependencies": { @@ -27438,6 +27439,15 @@ "boolbase": "~1.0.0" } }, + "node_modules/sweetalert2": { + "version": "11.10.7", + "resolved": "https://registry.npmjs.org/sweetalert2/-/sweetalert2-11.10.7.tgz", + "integrity": "sha512-5Jlzrmaitay6KzU+2+LhYu9q+L4v/dZ8oZyEDH14ep0C/QilCnFLHmqAyD/Lhq/lm5DiwsOs6Tr58iv8k3wyGg==", + "funding": { + "type": "individual", + "url": "https://github.com/sponsors/limonte" + } + }, "node_modules/symbol-tree": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", diff --git a/webapp/package.json b/webapp/package.json index f81360a0..079c80a0 100644 --- a/webapp/package.json +++ b/webapp/package.json @@ -20,6 +20,7 @@ "react-router-dom": "^6.22.3", "react-scripts": "5.0.1", "socket.io-client": "^4.7.5", + "sweetalert2": "^11.10.7", "web-vitals": "^3.5.1" }, "scripts": { diff --git a/webapp/src/components/game/gameModes/RoomGame.js b/webapp/src/components/game/gameModes/RoomGame.js new file mode 100644 index 00000000..f41dc333 --- /dev/null +++ b/webapp/src/components/game/gameModes/RoomGame.js @@ -0,0 +1,34 @@ +import Swal from 'sweetalert2'; +import BasicGame from '../BasicGame'; +/* +los sockets estan en room.js le pasas un objjeto con las funciones necesarias para que el juego funcione +que son el fetchquestions y el endgame + +*/ +class RoomGame extends BasicGame { + constructor(room) { + super(); + this.room = room; + this.winner=room.winner; + } + + async fetchQuestions() { + this.questions = this.room.getQuestions(); + } + + async endGame() { + console.log('RoomGame endGame'); + this.isGameEnded = true; + this.questionIndex = 0; + this.room.endGame({ correctas: this.correctAnswers, incorrectas: this.incorrectAnswers, tiempoTotal: this.totalTime }); + + // Muestra el cuadro de diálogo aquí + // Puedes utilizar un paquete como sweetalert2 para mostrar el cuadro de diálogo + Swal.fire({ + title: 'Juego terminado', + text: `El ganador es ${this.room.winner}`, + confirmButtonText: 'Cerrar' + }); + } + } + export default RoomGame; \ No newline at end of file diff --git a/webapp/src/components/rooms/Room.js b/webapp/src/components/rooms/Room.js index 83965cc8..46889d7c 100644 --- a/webapp/src/components/rooms/Room.js +++ b/webapp/src/components/rooms/Room.js @@ -6,7 +6,7 @@ import { useNavigate } from 'react-router-dom'; import socket from './socket'; import Game from '../game/Game'; - +import RoomGame from '../game/gameModes/RoomGame'; function Room({ darkMode }) { const nagivate = useNavigate(); const { roomId } = useParams(); @@ -82,7 +82,11 @@ function Room({ darkMode }) { } - +const room={ + endGame:endGame, + getQuestions:()=>questions, + winner:winner +} return (
@@ -94,30 +98,7 @@ function Room({ darkMode }) { ))} {isHost && } - {gameStarted && questions.length > 0 && } - - - - - Juego terminado - - - - El ganador es {winner} - - - - - - - - + {gameStarted && questions.length > 0 && }
); } From 602818af91782a5bd2e28ca436355ec00178d933 Mon Sep 17 00:00:00 2001 From: bidof Date: Fri, 5 Apr 2024 02:28:26 +0200 Subject: [PATCH 43/79] mis cambios de integracion faltaria el final del game multiplayer --- webapp/src/components/game/AnswersBlock.jsx | 1 + webapp/src/components/game/Game.js | 3 +-- webapp/src/components/game/gameModes/RoomGame.js | 11 ++++++++++- webapp/src/components/rooms/Room.js | 1 + 4 files changed, 13 insertions(+), 3 deletions(-) diff --git a/webapp/src/components/game/AnswersBlock.jsx b/webapp/src/components/game/AnswersBlock.jsx index 7310ebab..0a74ea78 100644 --- a/webapp/src/components/game/AnswersBlock.jsx +++ b/webapp/src/components/game/AnswersBlock.jsx @@ -6,6 +6,7 @@ export function AnswersBlock({ respuestas, correcta ,onAnswerSelect,isGameEnded, const [respuestasAleatorizadas, setRespuestasAleatorizadas] = useState([]); + let respuestasCopy = respuestas; //Colores de los botones para que tengan orden random diff --git a/webapp/src/components/game/Game.js b/webapp/src/components/game/Game.js index dfb6551b..35f07b4d 100644 --- a/webapp/src/components/game/Game.js +++ b/webapp/src/components/game/Game.js @@ -65,10 +65,9 @@ function Game({darkMode,gameMode= new BasicGame()}) { //console.log('corectAnswer useEffect el valor de las preguntas es ',gameModeRef.questions); console.log("entra en el useEffect de correctAnswer",correctAnswers,incorrectAnswers,gameModeRef.current.questions.length); - if (correctAnswers + incorrectAnswers < gameModeRef.current.questions.length ) { //para que no entre en el finished nada mas cargar el juegu + if ( !isFinished && correctAnswers + incorrectAnswers < gameModeRef.current.questions.length ) { //para que no entre en el finished nada mas cargar el juegu console.log("entra en el if del correctAnswer"); const nextQuestion = gameModeRef.current.nextQuestion(); - console.log('siguiente pregunta',nextQuestion); setCurrentQuestion(nextQuestion); } else if(gameModeRef.current.questions.length != 0){ //no deberia entrar cuando se cargue el componente console.log("use effect finish"); diff --git a/webapp/src/components/game/gameModes/RoomGame.js b/webapp/src/components/game/gameModes/RoomGame.js index f41dc333..c6fe42af 100644 --- a/webapp/src/components/game/gameModes/RoomGame.js +++ b/webapp/src/components/game/gameModes/RoomGame.js @@ -15,12 +15,16 @@ class RoomGame extends BasicGame { async fetchQuestions() { this.questions = this.room.getQuestions(); } + async sendHistory(){ + console.log("no se envia el historial"); + return; + } async endGame() { console.log('RoomGame endGame'); this.isGameEnded = true; this.questionIndex = 0; - this.room.endGame({ correctas: this.correctAnswers, incorrectas: this.incorrectAnswers, tiempoTotal: this.totalTime }); + //this.room.endGame({ correctas: this.correctAnswers, incorrectas: this.incorrectAnswers, tiempoTotal: this.totalTime }); // Muestra el cuadro de diálogo aquí // Puedes utilizar un paquete como sweetalert2 para mostrar el cuadro de diálogo @@ -30,5 +34,10 @@ class RoomGame extends BasicGame { confirmButtonText: 'Cerrar' }); } + + finishGame(){ + this.isGameEnded = true; + this.endGame(); + } } export default RoomGame; \ No newline at end of file diff --git a/webapp/src/components/rooms/Room.js b/webapp/src/components/rooms/Room.js index 46889d7c..d29f4674 100644 --- a/webapp/src/components/rooms/Room.js +++ b/webapp/src/components/rooms/Room.js @@ -78,6 +78,7 @@ function Room({ darkMode }) { } //funcion que le pasas a game para gestionar el finaldel juego function endGame(results) { + console.log("emitir endGame socket.io"); socket.emit('endGame', {id:roomId, results:results}); } From 7d5372851723192bff7ce025be5d10c6d954abe2 Mon Sep 17 00:00:00 2001 From: UO283535 Date: Fri, 5 Apr 2024 10:38:26 +0200 Subject: [PATCH 44/79] =?UTF-8?q?Actualizaci=C3=B3n=20tests=20de=20los=20e?= =?UTF-8?q?ndpoints=20de=20question=20y=20history=20del=20gatewayService?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gatewayservice/gateway-service.js | 6 +- gatewayservice/gateway-service.test.js | 255 +++++++++++++++++-------- 2 files changed, 180 insertions(+), 81 deletions(-) diff --git a/gatewayservice/gateway-service.js b/gatewayservice/gateway-service.js index 3dbf6447..09f81a42 100644 --- a/gatewayservice/gateway-service.js +++ b/gatewayservice/gateway-service.js @@ -72,7 +72,7 @@ app.get('/getQuestionModoBasico', async (req, res) => { res.json(questionResponse.data); } catch (error) { //Modifico el error - res.status(500).json({ error: 'Error al realizar la solicitud al servicio de preguntas' }); + res.status(500).json({ error: 'Error al realizar la solicitud al servicio de preguntas modo basico' }); } }); @@ -101,7 +101,7 @@ app.get('/getHistoryDetallado', async (req, res) => { res.json(historyResponse.data); } catch (error) { //Modifico el error - res.status(500).json({ error: 'Error al realizar la solicitud al servicio de historial' }); + res.status(500).json({ error: 'Error al realizar la solicitud al servicio de historial detallado' }); } }); @@ -115,7 +115,7 @@ app.get('/getHistoryTotal', async (req, res) => { res.json(historyResponse.data); } catch (error) { //Modifico el error - res.status(500).json({ error: 'Error al realizar la solicitud al servicio de historial' }); + res.status(500).json({ error: 'Error al realizar la solicitud al servicio de historial total' }); } }); //***************************************************endpoints de las salas */ diff --git a/gatewayservice/gateway-service.test.js b/gatewayservice/gateway-service.test.js index ee817ea2..480870bd 100644 --- a/gatewayservice/gateway-service.test.js +++ b/gatewayservice/gateway-service.test.js @@ -24,49 +24,100 @@ it('should perform the health request', async () => { expect(response.statusCode).toBe(200); }); + // Test /login endpoint + it('should forward login request to auth service', async () => { + const response = await request(app) + .post('/login') + .send({ username: 'testuser', password: 'testpassword' }); + expect(response.statusCode).toBe(200); + expect(response.body.token).toBe('mockedToken'); +}); + +// Test /adduser endpoint +it('should forward add user request to user service', async () => { + const response = await request(app) + .post('/adduser') + .send({ username: 'newuser', password: 'newpassword' }); + expect(response.statusCode).toBe(200); + expect(response.body.userId).toBe('mockedUserId'); +}); +it('should handle authentication error', async () => { + const authError = new Error('Authentication failed'); + authError.response = { + status: 401, + data: { error: 'Invalid credentials' }, + }; + + // Simula un error en la llamada al servicio de autenticación + axios.post.mockImplementationOnce(() => Promise.reject(authError)); + + // Realiza la solicitud al endpoint + const response = await request(app).post('/login').send({ /* datos de autenticación */ }); + + // Verifica que la respuesta tenga un código de estado 401 + expect(response.statusCode).toBe(401); + expect(response.body.error).toBe('Invalid credentials'); +}); + + + it('should handle authentication error', async () => { + const authError = new Error('Authentication failed'); + authError.response = { + status: 401, + data: { error: 'Invalid credentials' }, + }; + + // Simula un error en la llamada al servicio de autenticación + axios.post.mockImplementationOnce(() => Promise.reject(authError)); + + // Realiza la solicitud al endpoint + const response = await request(app).post('/adduser').send({ /* datos de autenticación */ }); + + // Verifica que la respuesta tenga un código de estado 401 + expect(response.statusCode).toBe(401); + expect(response.body.error).toBe('Invalid credentials'); + }); + +//CAso de prueba para un endpoint inexistente +it('should return 404 for nonexistent endpoint', async()=>{ + const response = await request(app) + .get('/nonexistent'); + expect(response.statusCode).toBe(404); +}); + +//*********************ENDPOINTS DEL QUESTION SERVICE********************************************* */ +//Caso positivo para el endpoint /getQuestion it('should perform the getQuestion request', async () => { const response = await request(app).get('/getQuestion').send(); expect(response.statusCode).toBe(200); const data = { pregunta: '¿Cuál es la capital de Francia?', respuestas: ['Berlin', 'Paris', 'Londres', 'Madrid'], - correcta: 'Helsinki', + correcta: 'Paris', }; axios.get.mockImplementationOnce(() => Promise.resolve({ data })); }); - // Test /login endpoint - it('should forward login request to auth service', async () => { - const response = await request(app) - .post('/login') - .send({ username: 'testuser', password: 'testpassword' }); - expect(response.statusCode).toBe(200); - expect(response.body.token).toBe('mockedToken'); - }); - - // Test /adduser endpoint - it('should forward add user request to user service', async () => { - const response = await request(app) - .post('/adduser') - .send({ username: 'newuser', password: 'newpassword' }); - expect(response.statusCode).toBe(200); - expect(response.body.userId).toBe('mockedUserId'); - }); - - //CAso de prueba para un endpoint inexistente - it('should return 404 for nonexistent endpoint', async()=>{ - const response = await request(app) - .get('/nonexistent'); - expect(response.statusCode).toBe(404); - }); +//Caso positivo para el endpoint /getQuestionModoBasico +it('should perform the getQuestion modo basico request', async () => { + const response = await request(app).get('/getQuestionModoBasico').send(); + expect(response.statusCode).toBe(200); + const data = { + pregunta: '¿Cuál es la capital de Francia?', + respuestas: ['Berlin', 'Paris', 'Londres', 'Madrid'], + correcta: 'Paris', + }; + axios.get.mockImplementationOnce(() => Promise.resolve({ data })); +}); + // Test /getQuestion endpoint axios.get.mockImplementation((url, data) => { if (url.endsWith("/getQuestion")) { return Promise.resolve({ data: [ { - pregunta: "¿Cuál es la capital de España?", - respuestas: ["Madrid", "Paris", "Londres", "Berlin"], - correcta: "Madrid" + pregunta: "¿Cuál es la capital de Francia?", + respuestas: ['Berlin', 'Paris', 'Londres', 'Madrid'], + correcta: "Paris" } ], }); @@ -78,11 +129,12 @@ it('should perform the getQuestion request', async () => { const errorMessage = 'Network Error'; axios.get.mockImplementationOnce(() => Promise.reject(new Error(errorMessage))); }); + //Casos de prueba para el endpoint /getQuestion it('should forward get question request to question service', async () => { const questionServiceUrl = 'http://localhost:8003'; const expectedQuestion = '¿Cuál es la capital de Francia?'; const expectedOptions = ['Berlin', 'Paris', 'Londres', 'Madrid']; - const expectedCorrectAnswer = 'Helsinki'; + const expectedCorrectAnswer = 'Paris'; // Simula una llamada exitosa al servicio de preguntas axios.get.mockImplementationOnce(() => Promise.resolve({ data })); // Realiza la solicitud al endpoint @@ -94,12 +146,14 @@ it('should perform the getQuestion request', async () => { expect(response.body.respuestas).toEqual(expect.arrayContaining(expectedOptions)); expect(response.body.correcta).toBe(expectedCorrectAnswer); }); + + //Caso de prueba para el endpoint /generateQuestion it('should forward get question request to question generate service', async () => { const questionServiceUrl = 'http://localhost:8003/generateQuestions'; const data = { pregunta: '¿Cuál es la capital de Francia?', respuestas: ['Berlin', 'Paris', 'Londres', 'Madrid'], - correcta: 'Helsinki', + correcta: 'Paris', }; axios.get.mockImplementationOnce(() => Promise.resolve({ data })); @@ -111,55 +165,100 @@ it('should perform the getQuestion request', async () => { axios.get.mockImplementationOnce(() => Promise.reject(new Error(errorMessage))); }); - it('should handle authentication error', async () => { - const authError = new Error('Authentication failed'); - authError.response = { - status: 401, - data: { error: 'Invalid credentials' }, - }; - - // Simula un error en la llamada al servicio de autenticación - axios.post.mockImplementationOnce(() => Promise.reject(authError)); - - // Realiza la solicitud al endpoint - const response = await request(app).post('/login').send({ /* datos de autenticación */ }); - - // Verifica que la respuesta tenga un código de estado 401 - expect(response.statusCode).toBe(401); - expect(response.body.error).toBe('Invalid credentials'); + //Caso negativo para el endpoint /getQuestion +it('should return an error when the question service request fails', async () => { + // Mock the axios.get method to reject the promise + axios.get.mockImplementationOnce(() => + Promise.reject(new Error('Error al realizar la solicitud al servicio de preguntas')) + ); + const response = await request(app) + .get('/getQuestion') + .send({ id: 'mockedQuestionId' }); + + expect(response.statusCode).toBe(500); + expect(response.body.error).toBeDefined(); + expect(response.body.error).toEqual('Error al realizar la solicitud al servicio de preguntas'); + }); +//Caso negativo para el endpoint /getQuestionModoBasico + it('should return an error when the question modo basico service request fails', async () => { + // Mock the axios.get method to reject the promise + axios.get.mockImplementationOnce(() => + Promise.reject(new Error('Error al realizar la solicitud al servicio de preguntas modo basico')) + ); + const response = await request(app) + .get('/getQuestionModoBasico') + .send({ id: 'mockedQuestionId' }); + + expect(response.statusCode).toBe(500); + expect(response.body.error).toBeDefined(); + expect(response.body.error).toEqual('Error al realizar la solicitud al servicio de preguntas modo basico'); }); + - - it('should handle authentication error', async () => { - const authError = new Error('Authentication failed'); - authError.response = { - status: 401, - data: { error: 'Invalid credentials' }, - }; - - // Simula un error en la llamada al servicio de autenticación - axios.post.mockImplementationOnce(() => Promise.reject(authError)); - - // Realiza la solicitud al endpoint - const response = await request(app).post('/adduser').send({ /* datos de autenticación */ }); - - // Verifica que la respuesta tenga un código de estado 401 - expect(response.statusCode).toBe(401); - expect(response.body.error).toBe('Invalid credentials'); - }); - it('should return an error when the question service request fails', async () => { - // Mock the axios.get method to reject the promise - axios.get.mockImplementationOnce(() => - Promise.reject(new Error('Error al realizar la solicitud al servicio de preguntas')) - ); - - const response = await request(app) - .get('/getQuestion') - .send({ id: 'mockedQuestionId' }); + +//***************************** ENDPOINTS HISTORY-SERVICE*************************************************** */ +//Caso positivo para el endpoint /getHistoryDetallado +it('should perform the getHistoryDetallado request', async () => { + const response = await request(app).get('/getHistoryDetallado').send(); + expect(response.statusCode).toBe(200); + const data = { + usuario: 'testuser', + preguntas: [ + { + pregunta: '¿Cuál es la capital de Francia?', + respuesta: 'Paris', + correcta: true, + }, + ], + }; + axios.get.mockImplementationOnce(() => Promise.resolve({ data })); +}); + +//Caso negativo para el endpoint /getHistoryDetallado +it('should return an error when the history detallado service request fails', async () => { + // Mock the axios.get method to reject the promise + axios.get.mockImplementationOnce(() => + Promise.reject(new Error('Error al realizar la solicitud al servicio de historial')) + ); + const response = await request(app) + .get('/getHistoryDetallado') + .send({ id: 'mockedHistoryId' }); - expect(response.statusCode).toBe(500); - expect(response.body.error).toBeDefined(); - expect(response.body.error).toEqual('Error al realizar la solicitud al servicio de preguntas'); - }); - + expect(response.statusCode).toBe(500); + expect(response.body.error).toBeDefined(); + expect(response.body.error).toEqual('Error al realizar la solicitud al servicio de historial detallado'); + }); + + //Verifica si el manejo de errores funciona correctamente cuando la llamada al servicio de historial falla. + it('should handle error when fetching history', async () => { + const historyServiceUrl = 'http://localhost:8004'; + const errorMessage = 'Network Error'; + axios.get.mockImplementationOnce(() => Promise.reject(new Error(errorMessage))); + }); + + + // Test /getHistoryTotal endpoint + //Caso negativo para el endpoint /getHistoryTotal + it('should return an error when the history total service request fails', async () => { + // Mock the axios.get method to reject the promise + axios.get.mockImplementationOnce(() => + Promise.reject(new Error('Error al realizar la solicitud al servicio de historial')) + ); + const response = await request(app) + .get('/getHistoryTotal') + .send({ id: 'mockedHistoryId' }); + + expect(response.statusCode).toBe(500); + expect(response.body.error).toBeDefined(); + expect(response.body.error).toEqual('Error al realizar la solicitud al servicio de historial total'); + }); + //Verifica si el manejo de errores funciona correctamente cuando la llamada al servicio de historial falla. + it('should handle error when fetching history', async () => { + const historyServiceUrl = 'http://localhost:8004'; + const errorMessage = 'Network Error'; + axios.get.mockImplementationOnce(() => Promise.reject(new Error(errorMessage))); + }); + + // Test /getHistoryTotal endpoint + }); \ No newline at end of file From fdd3146a1eced7df43ff28907e6a41cb09772a83 Mon Sep 17 00:00:00 2001 From: UO283535 Date: Fri, 5 Apr 2024 10:49:19 +0200 Subject: [PATCH 45/79] =?UTF-8?q?A=C3=B1ado=20metodo=20getQuestionDiaria?= =?UTF-8?q?=20al=20questionService=20y=20sus=20tests=20en=20el=20gatewaySe?= =?UTF-8?q?rvice?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gatewayservice/gateway-service.js | 12 ++++++++++++ gatewayservice/gateway-service.test.js | 22 ++++++++++++++++++++-- questionservice/question-service.js | 13 +++++++++++++ 3 files changed, 45 insertions(+), 2 deletions(-) diff --git a/gatewayservice/gateway-service.js b/gatewayservice/gateway-service.js index 09f81a42..9de04dba 100644 --- a/gatewayservice/gateway-service.js +++ b/gatewayservice/gateway-service.js @@ -64,6 +64,18 @@ app.get('/getQuestion', async (req, res) => { } }); +app.get('/getQuestionDiaria', async (req, res) => { + try { + // llamamos al servicio de preguntas + const questionResponse = await axios.get(questionServiceUrl+'/getQuestionDiaria', req.body); + + res.json(questionResponse.data); + } catch (error) { + //Modifico el error + res.status(500).json({ error: 'Error al realizar la solicitud al servicio de preguntas' }); + } +}); + app.get('/getQuestionModoBasico', async (req, res) => { try { diff --git a/gatewayservice/gateway-service.test.js b/gatewayservice/gateway-service.test.js index 480870bd..0a01573e 100644 --- a/gatewayservice/gateway-service.test.js +++ b/gatewayservice/gateway-service.test.js @@ -86,6 +86,8 @@ it('should return 404 for nonexistent endpoint', async()=>{ }); //*********************ENDPOINTS DEL QUESTION SERVICE********************************************* */ + + //Caso positivo para el endpoint /getQuestion it('should perform the getQuestion request', async () => { const response = await request(app).get('/getQuestion').send(); @@ -97,6 +99,20 @@ it('should perform the getQuestion request', async () => { }; axios.get.mockImplementationOnce(() => Promise.resolve({ data })); }); + +//Caso positivo para el endpoint /getQuestionDiario +it('should perform the getQuestionDiario request', async () => { + const response = await request(app).get('/getQuestionDiaria').send(); + expect(response.statusCode).toBe(200); + const data = { + pregunta: '¿Cuál es la capital de Francia?', + respuestas: ['Berlin', 'Paris', 'Londres', 'Madrid'], + correcta: 'Paris', + }; + axios.get.mockImplementationOnce(() => Promise.resolve({ data })); +}); + + //Caso positivo para el endpoint /getQuestionModoBasico it('should perform the getQuestion modo basico request', async () => { const response = await request(app).get('/getQuestionModoBasico').send(); @@ -123,6 +139,8 @@ it('should perform the getQuestion modo basico request', async () => { }); } }); + + //Verifica si el manejo de errores funciona correctamente cuando la llamada al servicio de preguntas falla. it('should handle error when fetching question', async () => { const questionServiceUrl = 'http://localhost:8003'; @@ -158,6 +176,7 @@ it('should perform the getQuestion modo basico request', async () => { axios.get.mockImplementationOnce(() => Promise.resolve({ data })); }); + //Verifica si el manejo de errores funciona correctamente cuando la llamada al servicio de preguntas falla. it('should handle error when fetching question', async () => { const questionServiceUrl = 'http://localhost:8003/generateQuestions'; @@ -179,6 +198,7 @@ it('should return an error when the question service request fails', async () => expect(response.body.error).toBeDefined(); expect(response.body.error).toEqual('Error al realizar la solicitud al servicio de preguntas'); }); + //Caso negativo para el endpoint /getQuestionModoBasico it('should return an error when the question modo basico service request fails', async () => { // Mock the axios.get method to reject the promise @@ -194,7 +214,6 @@ it('should return an error when the question service request fails', async () => expect(response.body.error).toEqual('Error al realizar la solicitud al servicio de preguntas modo basico'); }); - //***************************** ENDPOINTS HISTORY-SERVICE*************************************************** */ //Caso positivo para el endpoint /getHistoryDetallado @@ -259,6 +278,5 @@ it('should return an error when the history detallado service request fails', as axios.get.mockImplementationOnce(() => Promise.reject(new Error(errorMessage))); }); - // Test /getHistoryTotal endpoint }); \ No newline at end of file diff --git a/questionservice/question-service.js b/questionservice/question-service.js index 45555032..4f41c4eb 100644 --- a/questionservice/question-service.js +++ b/questionservice/question-service.js @@ -40,6 +40,19 @@ app.get('/getQuestion', async(req,res)=> { res.status(error.response.status).json({ error: error.response.data.error }); } +}); + +app.get('/getQuestionDiaria', async(req,res)=> { + try{ + //coger pregunta bd + const questions = await question.obtenerPregunta(1); + //para devolver la pregunta + res.json(questions); + + } catch(error) { + res.status(error.response.status).json({ error: error.response.data.error }); + } + }); app.get('/getQuestionModoBasico', async(req,res)=> { try{ From ed234dda1aeb54a7f69c5781f970322d1ad4307a Mon Sep 17 00:00:00 2001 From: UO283535 Date: Fri, 5 Apr 2024 16:48:13 +0200 Subject: [PATCH 46/79] =?UTF-8?q?Actualizaci=C3=B3n=20test=20del=20gateway?= =?UTF-8?q?Service?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gatewayservice/gateway-service.test.js | 32 ++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/gatewayservice/gateway-service.test.js b/gatewayservice/gateway-service.test.js index 0a01573e..a2180c07 100644 --- a/gatewayservice/gateway-service.test.js +++ b/gatewayservice/gateway-service.test.js @@ -176,7 +176,8 @@ it('should perform the getQuestion modo basico request', async () => { axios.get.mockImplementationOnce(() => Promise.resolve({ data })); }); - + + //Verifica si el manejo de errores funciona correctamente cuando la llamada al servicio de preguntas falla. it('should handle error when fetching question', async () => { const questionServiceUrl = 'http://localhost:8003/generateQuestions'; @@ -214,8 +215,18 @@ it('should return an error when the question service request fails', async () => expect(response.body.error).toEqual('Error al realizar la solicitud al servicio de preguntas modo basico'); }); + //Verifica si el manejo de errores funciona correctamente cuando la llamada al servicio de preguntas falla. + it('should handle error when fetching question', async () => { + const questionServiceUrl = 'http://localhost:8003/getQuestionDiaria'; + const errorMessage = 'Network Error'; + axios.get.mockImplementationOnce(() => Promise.reject(new Error(errorMessage))); + + } + ); + //***************************** ENDPOINTS HISTORY-SERVICE*************************************************** */ + //Caso positivo para el endpoint /getHistoryDetallado it('should perform the getHistoryDetallado request', async () => { const response = await request(app).get('/getHistoryDetallado').send(); @@ -257,6 +268,12 @@ it('should return an error when the history detallado service request fails', as // Test /getHistoryTotal endpoint + + //Caso positivo para el endpoint /getHistoryDetallado +it('should perform the getHistoryDetallado request', async () => { + const response = await request(app).get('/getHistoryTotal').send(); + +}); //Caso negativo para el endpoint /getHistoryTotal it('should return an error when the history total service request fails', async () => { // Mock the axios.get method to reject the promise @@ -277,6 +294,17 @@ it('should return an error when the history detallado service request fails', as const errorMessage = 'Network Error'; axios.get.mockImplementationOnce(() => Promise.reject(new Error(errorMessage))); }); - + //Verifica si el manejo de errores funciona correctamente cuando la llamada al servicio de historial total falla. + it('should handle error when fetching history total', async () => { + const historyServiceUrl = 'http://localhost:8004/getHistoryTotal'; + const errorMessage = 'Network Error'; + axios.get.mockImplementationOnce(() => Promise.reject(new Error(errorMessage))); +}); +//Verifica si el manejo de errores funciona correctamente cuando la llamada al servicio de historial detallado falla. +it('should handle error when fetching history detallado', async () => { + const historyServiceUrl = 'http://localhost:8004/getHistoryDetallado'; + const errorMessage = 'Network Error'; + axios.get.mockImplementationOnce(() => Promise.reject(new Error(errorMessage))); + }); }); \ No newline at end of file From 453e68cb798e55b2418e4ad4a19905ec0ceb9746 Mon Sep 17 00:00:00 2001 From: UO283535 Date: Fri, 5 Apr 2024 17:05:58 +0200 Subject: [PATCH 47/79] Actualizo readme con datos de los integrantes del grupo --- README.md | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index cf2aaced..cef6c5f6 100644 --- a/README.md +++ b/README.md @@ -18,16 +18,15 @@ This repo is a basic application composed of several components. Both the user, auth service and questions service share a Mongo database that is accessed with mongoose. ## Members of the group + Nombre | Identificador - Sonia Moro Lauda + Sonia Moro Lauda | UO282189 - Lucía Villanueva Rodríguez + Lucía Villanueva Rodríguez | UO283535 - Pedro Castro Montes + Pedro Castro Montes | UO288120 - Adrián Santamarina Romero - - David González González + David Alvarez Diaz | UO283196 ## Quick start guide From d7034705edba1dd26fb24f7a4a2bc64f740d3095 Mon Sep 17 00:00:00 2001 From: UO283535 Date: Fri, 5 Apr 2024 17:11:04 +0200 Subject: [PATCH 48/79] =?UTF-8?q?arreglo=20tabla=20nombre=20y=20a=C3=B1ado?= =?UTF-8?q?=20paramtro=20bugs=20prueba?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index cef6c5f6..93afa340 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,7 @@ Visit our app : http://20.68.253.187:3000/ [![Deploy on release](https://github.com/Arquisoft/wiq_es05c/actions/workflows/release.yml/badge.svg)](https://github.com/Arquisoft/wiq_es05c/actions/workflows/release.yml) [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=Arquisoft_wiq_es05c&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=Arquisoft_wiq_es05c) [![Coverage](https://sonarcloud.io/api/project_badges/measure?project=Arquisoft_wiq_es05c&metric=coverage)](https://sonarcloud.io/summary/new_code?id=Arquisoft_wiq_es05c) +[![Bugs](https://sonarcloud.io/api/project_badges/measure?project=Arquisoft_wiq_es05c&metric=bugs)](https://sonarcloud.io/summary/new_code?id=Arquisoft_wiq_es05c) This is a base repo for the [Software Architecture course](http://arquisoft.github.io/) in [2023/2024 edition](https://arquisoft.github.io/course2324.html). @@ -18,15 +19,14 @@ This repo is a basic application composed of several components. Both the user, auth service and questions service share a Mongo database that is accessed with mongoose. ## Members of the group - Nombre | Identificador - - Sonia Moro Lauda | UO282189 - - Lucía Villanueva Rodríguez | UO283535 - - Pedro Castro Montes | UO288120 - - David Alvarez Diaz | UO283196 + ## Members of the group + +| Nombre | Identificador | +|-----------------------------|---------------| +| Sonia Moro Lauda | UO282189 | +| Lucía Villanueva Rodríguez | UO283535 | +| Pedro Castro Montes | UO288120 | +| David Alvarez Diaz | UO283196 | ## Quick start guide From 13fa7adb0503e1d7471f661b92454a6c6c2f759b Mon Sep 17 00:00:00 2001 From: UO283535 Date: Fri, 5 Apr 2024 17:13:38 +0200 Subject: [PATCH 49/79] Ultimas modificaciones y actualizaciones del README --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 93afa340..773404fc 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,9 @@ Visit our app : http://20.68.253.187:3000/ [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=Arquisoft_wiq_es05c&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=Arquisoft_wiq_es05c) [![Coverage](https://sonarcloud.io/api/project_badges/measure?project=Arquisoft_wiq_es05c&metric=coverage)](https://sonarcloud.io/summary/new_code?id=Arquisoft_wiq_es05c) [![Bugs](https://sonarcloud.io/api/project_badges/measure?project=Arquisoft_wiq_es05c&metric=bugs)](https://sonarcloud.io/summary/new_code?id=Arquisoft_wiq_es05c) +[![Lines of Code](https://sonarcloud.io/api/project_badges/measure?project=Arquisoft_wiq_es05c&metric=ncloc)](https://sonarcloud.io/summary/new_code?id=Arquisoft_wiq_es05c) +[![Vulnerabilities](https://sonarcloud.io/api/project_badges/measure?project=Arquisoft_wiq_es05c&metric=vulnerabilities)](https://sonarcloud.io/summary/new_code?id=Arquisoft_wiq_es05c) +[![SQALE Rating](https://sonarcloud.io/api/project_badges/measure?project=Arquisoft_wiq_es05c&metric=sqale_rating)](https://sonarcloud.io/summary/new_code?id=Arquisoft_wiq_es05c) This is a base repo for the [Software Architecture course](http://arquisoft.github.io/) in [2023/2024 edition](https://arquisoft.github.io/course2324.html). @@ -19,7 +22,6 @@ This repo is a basic application composed of several components. Both the user, auth service and questions service share a Mongo database that is accessed with mongoose. ## Members of the group - ## Members of the group | Nombre | Identificador | |-----------------------------|---------------| From ac8c18541b229ad23444a87f0dd9c7b34d7bf2e5 Mon Sep 17 00:00:00 2001 From: uo288120 <145131850+Pedro-C-M@users.noreply.github.com> Date: Sat, 6 Apr 2024 13:07:13 +0200 Subject: [PATCH 50/79] =?UTF-8?q?Redondeo=20el=20porcentaje=20a=202=20deci?= =?UTF-8?q?males=20por=20si=20es=20peri=C3=B3dico?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- webapp/src/components/history/StatsBlock.jsx | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/webapp/src/components/history/StatsBlock.jsx b/webapp/src/components/history/StatsBlock.jsx index 629b1d98..83039350 100644 --- a/webapp/src/components/history/StatsBlock.jsx +++ b/webapp/src/components/history/StatsBlock.jsx @@ -16,7 +16,9 @@ export function StatsBlock({ darkMode,playerStats }){ let tiempoMedioFormateado = formatTime(playerStats.tiempoMedio); - + let aciertosRedondeados = parseFloat(playerStats.preguntas_acertadas.toFixed(2)); + + let fallosRedondeados = parseFloat(playerStats.preguntas_falladas.toFixed(2)); function formatTime(tiempo) { let hours = Math.floor(tiempo / 3600); @@ -63,14 +65,14 @@ export function StatsBlock({ darkMode,playerStats }){ {t('questionsCorrect')} - - {playerStats.preguntas_acertadas}{t('questionsPercentage')} + + {aciertosRedondeados}{t('questionsPercentage')} {t('questionsFailed')} - - {playerStats.preguntas_falladas}{t('questionsPercentage')} + + {fallosRedondeados}{t('questionsPercentage')} From 6fcb7f0c6f5368f3a2dfd556e8f0baa484796e39 Mon Sep 17 00:00:00 2001 From: uo288120 <145131850+Pedro-C-M@users.noreply.github.com> Date: Sat, 6 Apr 2024 13:39:45 +0200 Subject: [PATCH 51/79] Si no hay historial cambia la ventana --- .../src/components/history/AllGamesBlock.jsx | 16 ++++++++++++++ webapp/src/components/history/History.js | 16 +++++++------- webapp/src/components/history/StatsBlock.jsx | 21 +++++++++++++++---- 3 files changed, 42 insertions(+), 11 deletions(-) diff --git a/webapp/src/components/history/AllGamesBlock.jsx b/webapp/src/components/history/AllGamesBlock.jsx index a73753fa..93c04962 100644 --- a/webapp/src/components/history/AllGamesBlock.jsx +++ b/webapp/src/components/history/AllGamesBlock.jsx @@ -12,6 +12,22 @@ export function AllGamesBlock({darkMode, games }){ //para la internacionalización const {t} = useTranslation(); + let hasHistory = true;//Por defecto lo pongo a true + + let errorMessage = games[0]; + + if (!games || errorMessage.includes("Error")) { + hasHistory = false; + } + + if (!hasHistory) { + return ( + + {t('noHistoryGamesMessage')} + + ); + } + console.log("Partidas x: "+games); console.log(games); return ( diff --git a/webapp/src/components/history/History.js b/webapp/src/components/history/History.js index 8ee7dc57..d94dfaf6 100644 --- a/webapp/src/components/history/History.js +++ b/webapp/src/components/history/History.js @@ -20,18 +20,19 @@ export function History({darkMode}){ const [stats, setStatistics] = useState([]);//Para las estadisticas completas de todos los juegos const [isLoadingStats, setIsLoadingStats] = useState(true); + useEffect(() => { fetch(gamesEndpoint) .then(response => response.json()) .then(partidas => { - console.log("Partidas: "); - console.log(partidas); + //console.log("Partidas: "); + //console.log(partidas); let gamesArray = Object.values(partidas); setAllGames(gamesArray); setIsLoadingGames(false); }) .catch(error => { - console.error('Error cargando el historial de todas las partidas del usuario:', error); + //console.error('Error cargando el historial de todas las partidas del usuario:', error); }); }, []); @@ -39,19 +40,19 @@ export function History({darkMode}){ fetch(statisticsEndpoint) .then(response => response.json()) .then(estadisticas => { - console.log("Estadísticas: "); - console.log(estadisticas); + //console.log("Estadísticas: "); + //console.log(estadisticas); setStatistics(estadisticas); setIsLoadingStats(false); }) .catch(error => { - console.error('Error cargando las estadísticas del usuario:', error); + //console.error('Error cargando las estadísticas del usuario:', error); }); }, []); - console.log(darkMode); + //console.log(darkMode); let backgroundColor = darkMode ? '#001c17' : '#fef5c6'; let text = darkMode ? '#FCFAF0' : '#08313A'; @@ -68,6 +69,7 @@ export function History({darkMode}){ />//Para mientras carga ) : ( + diff --git a/webapp/src/components/history/StatsBlock.jsx b/webapp/src/components/history/StatsBlock.jsx index 83039350..01836875 100644 --- a/webapp/src/components/history/StatsBlock.jsx +++ b/webapp/src/components/history/StatsBlock.jsx @@ -7,18 +7,31 @@ export function StatsBlock({ darkMode,playerStats }){ //para la internacionalización const {t} = useTranslation(); - let nombreJugador = playerStats.nombreUsuario; - console.log(playerStats); + let hasHistory = true;//Por defecto lo pongo a true + + if (!playerStats || !playerStats.nombreUsuario || playerStats.tiempoMedio == 0) { + hasHistory = false; + } + if (!hasHistory) { + return ( + + {t('noHistoryMessage')} + + ); + } + + let nombreJugador = playerStats.nombreUsuario; + let tiempo = playerStats.tiempoTotal; let tiempoFormateado = formatTime(tiempo); let tiempoMedioFormateado = formatTime(playerStats.tiempoMedio); - let aciertosRedondeados = parseFloat(playerStats.preguntas_acertadas.toFixed(2)); + let aciertosRedondeados = 1//parseFloat(playerStats.preguntas_acertadas.toFixed(2)); - let fallosRedondeados = parseFloat(playerStats.preguntas_falladas.toFixed(2)); + let fallosRedondeados = 1//parseFloat(playerStats.preguntas_falladas.toFixed(2)); function formatTime(tiempo) { let hours = Math.floor(tiempo / 3600); From 625dec1867abe2cf1a47d22dd6cf6d1b2f073a07 Mon Sep 17 00:00:00 2001 From: uo288120 <145131850+Pedro-C-M@users.noreply.github.com> Date: Sat, 6 Apr 2024 13:57:40 +0200 Subject: [PATCH 52/79] =?UTF-8?q?El=20m=C3=A9todo=20includes=20pod=C3=ADa?= =?UTF-8?q?=20recibir=20un=20array=20y=20dar=20excepci=C3=B3n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- webapp/src/components/history/AllGamesBlock.jsx | 11 ++++++++++- .../internacionalizacion/locales/en/translation.json | 2 ++ .../internacionalizacion/locales/es/translation.json | 2 ++ 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/webapp/src/components/history/AllGamesBlock.jsx b/webapp/src/components/history/AllGamesBlock.jsx index 93c04962..d7f7bfdc 100644 --- a/webapp/src/components/history/AllGamesBlock.jsx +++ b/webapp/src/components/history/AllGamesBlock.jsx @@ -16,7 +16,16 @@ export function AllGamesBlock({darkMode, games }){ let errorMessage = games[0]; - if (!games || errorMessage.includes("Error")) { + if(typeof errorMessage === 'string'){ + let errorMessage = games[0]; + //Si tiene el mensaje de error + if (errorMessage.includes("Error")) { + hasHistory = false; + } + } + + //O si no tiene directamente games + if (!games) { hasHistory = false; } diff --git a/webapp/src/internacionalizacion/locales/en/translation.json b/webapp/src/internacionalizacion/locales/en/translation.json index 7ce00c4d..d023038d 100644 --- a/webapp/src/internacionalizacion/locales/en/translation.json +++ b/webapp/src/internacionalizacion/locales/en/translation.json @@ -9,6 +9,8 @@ "history": "History", "signUp": "Sign Up", "signIn": "Sign In", + "noHistoryMessage": "No history available", + "noHistoryGamesMessage": "No games played yet", "github": "Project's Github", "university": "School of Computer Engineering", diff --git a/webapp/src/internacionalizacion/locales/es/translation.json b/webapp/src/internacionalizacion/locales/es/translation.json index 2b952bbd..ff8f646f 100644 --- a/webapp/src/internacionalizacion/locales/es/translation.json +++ b/webapp/src/internacionalizacion/locales/es/translation.json @@ -9,6 +9,8 @@ "history": "Historial", "signUp": "Registrarse", "signIn": "Iniciar sesión", + "noHistoryMessage": "No tiene historial", + "noHistoryGamesMessage": "No jugó partidas todavía", "github": "Github del Proyecto", "university": "Escuela de Ingeniería Informática", From dd9967597f37fd2e31a4f0a468adc3c4829d71cc Mon Sep 17 00:00:00 2001 From: uo288120 <145131850+Pedro-C-M@users.noreply.github.com> Date: Sat, 6 Apr 2024 14:14:23 +0200 Subject: [PATCH 53/79] Colores oscuros para el historial y arreglo de paso de segundos a texto de tiempo --- webapp/src/components/history/AllGamesBlock.jsx | 8 ++++---- webapp/src/components/history/GameBlock.jsx | 6 +++++- webapp/src/components/history/StatsBlock.jsx | 11 ++++++----- .../internacionalizacion/locales/en/translation.json | 6 +++--- .../internacionalizacion/locales/es/translation.json | 6 +++--- 5 files changed, 21 insertions(+), 16 deletions(-) diff --git a/webapp/src/components/history/AllGamesBlock.jsx b/webapp/src/components/history/AllGamesBlock.jsx index d7f7bfdc..c45ad8bd 100644 --- a/webapp/src/components/history/AllGamesBlock.jsx +++ b/webapp/src/components/history/AllGamesBlock.jsx @@ -5,7 +5,7 @@ import { useTranslation } from 'react-i18next'; export function AllGamesBlock({darkMode, games }){ - let backgroundColor = darkMode ? '#D3B1C2' : '#D3B1C2'; + let backgroundColor = darkMode ? '#3F3F3F' : '#D3B1C2'; let text = darkMode ? '#FCFAF0' : '#08313A'; let titles = darkMode ? '#90ADC6' : '#00325E'; @@ -32,13 +32,13 @@ export function AllGamesBlock({darkMode, games }){ if (!hasHistory) { return ( - {t('noHistoryGamesMessage')} + {t('noHistoryGamesMessage')} ); } - console.log("Partidas x: "+games); - console.log(games); + //console.log("Partidas x: "+games); + //console.log(games); return ( diff --git a/webapp/src/components/history/GameBlock.jsx b/webapp/src/components/history/GameBlock.jsx index 0d2747a1..3d2e7d13 100644 --- a/webapp/src/components/history/GameBlock.jsx +++ b/webapp/src/components/history/GameBlock.jsx @@ -57,7 +57,11 @@ export function GameBlock( {darkMode, gameInfo} ){ let hue = porcentaje * 120; // Interpola entre rojo (0) y verde (120) en el espacio de color HSL - return `hsl(${hue}, 50%, 70%)`; // Devuelve un color en formato HSL + if(darkMode){ + return `hsl(${hue}, 50%, 20%)`; // Devuelve un color en formato HSL más oscuro + }else{ + return `hsl(${hue}, 50%, 70%)`; // Devuelve un color en formato HSL + } } let statBackgroundColor = darkMode ? '#D4F1F4' : '#D4F1F4'; diff --git a/webapp/src/components/history/StatsBlock.jsx b/webapp/src/components/history/StatsBlock.jsx index 01836875..5741338b 100644 --- a/webapp/src/components/history/StatsBlock.jsx +++ b/webapp/src/components/history/StatsBlock.jsx @@ -13,10 +13,12 @@ export function StatsBlock({ darkMode,playerStats }){ hasHistory = false; } + let backgroundColor = darkMode ? '#001c17' : '#fef5c6'; + if (!hasHistory) { return ( - {t('noHistoryMessage')} + {t('noHistoryMessage')} ); } @@ -56,8 +58,7 @@ export function StatsBlock({ darkMode,playerStats }){ return tiempoFormateado; } - let backgroundColor = darkMode ? '#001c17' : '#fef5c6'; - let statBackgroundColor = darkMode ? '#D4F1F4' : '#D4F1F4'; + let statBackgroundColor = darkMode ? '#282828' : '#D4F1F4'; let text = darkMode ? '#FCFAF0' : '#08313A'; let titles = darkMode ? '#90ADC6' : '#00325E'; @@ -79,13 +80,13 @@ export function StatsBlock({ darkMode,playerStats }){ {t('questionsCorrect')} - {aciertosRedondeados}{t('questionsPercentage')} + {aciertosRedondeados}{t('questionsPercentage')} {t('questionsFailed')} - {fallosRedondeados}{t('questionsPercentage')} + {fallosRedondeados}{t('questionsPercentage')} diff --git a/webapp/src/internacionalizacion/locales/en/translation.json b/webapp/src/internacionalizacion/locales/en/translation.json index d023038d..01799f20 100644 --- a/webapp/src/internacionalizacion/locales/en/translation.json +++ b/webapp/src/internacionalizacion/locales/en/translation.json @@ -34,9 +34,9 @@ "totalTime": "Total time played", "averageTime": "Average time per game", "gameTime": "Game time", - "timeUnit" : " seconds", - "timeUnitMinutes" : " minutes", - "timeUnitHours" : " hours", + "timeUnit" : " seconds ", + "timeUnitMinutes" : " minutes ", + "timeUnitHours" : " hours ", "questionsPercentage": " %", "loginWelcomeMessage": "Welcome ", diff --git a/webapp/src/internacionalizacion/locales/es/translation.json b/webapp/src/internacionalizacion/locales/es/translation.json index ff8f646f..a83a9e5d 100644 --- a/webapp/src/internacionalizacion/locales/es/translation.json +++ b/webapp/src/internacionalizacion/locales/es/translation.json @@ -34,9 +34,9 @@ "totalTime": "Tiempo total jugado", "averageTime": "Tiempo medio por partida", "gameTime": "Tiempo de la partida", - "timeUnit" : " segundos", - "timeUnitMinutes" : " minutos", - "timeUnitHours" : " horas", + "timeUnit" : " segundos ", + "timeUnitMinutes" : " minutos ", + "timeUnitHours" : " horas ", "questionsPercentage": " %", "loginWelcomeMessage": "Bienvenido ", From f965b6553502efe187efa661f4a58e80d8da7af8 Mon Sep 17 00:00:00 2001 From: uo288120 <145131850+Pedro-C-M@users.noreply.github.com> Date: Sat, 6 Apr 2024 16:21:28 +0200 Subject: [PATCH 54/79] =?UTF-8?q?Menu=20de=20nav=20tiene=20separadores=20l?= =?UTF-8?q?=C3=B3gicos=20y=20enlace=20a=20Ranking?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- webapp/src/App.js | 6 +++++ webapp/src/components/navbar/NavBar.js | 13 ++++++--- webapp/src/components/ranking/Ranking.js | 27 +++++++++++++++++++ .../locales/en/translation.json | 2 ++ .../locales/es/translation.json | 2 ++ 5 files changed, 46 insertions(+), 4 deletions(-) create mode 100644 webapp/src/components/ranking/Ranking.js diff --git a/webapp/src/App.js b/webapp/src/App.js index 12edaa06..36fc5c07 100644 --- a/webapp/src/App.js +++ b/webapp/src/App.js @@ -19,6 +19,7 @@ import PrincipalView from './components/principalView/PrincipalView'; import Room from './components/rooms/Room'; // Importa el componente de sala import CreateRoomForm from './components/rooms/CreateRoom'; // Importa el componente para crear sala import JoinRoomForm from './components/rooms/JoinRoom'; // Importa el componente para unirse a sala +import { Ranking } from './components/ranking/Ranking'; const App = () => { @@ -62,6 +63,11 @@ const App = () => { } /> + + + + } /> } /> } /> diff --git a/webapp/src/components/navbar/NavBar.js b/webapp/src/components/navbar/NavBar.js index 72b2f4fa..dcf37cc5 100644 --- a/webapp/src/components/navbar/NavBar.js +++ b/webapp/src/components/navbar/NavBar.js @@ -1,5 +1,5 @@ import React, { useContext, useState } from 'react'; -import { AppBar, Toolbar, Button, IconButton, Avatar, Menu, MenuItem, Typography,ListItem, ListItemIcon , Switch } from '@mui/material'; +import { Divider, AppBar, Toolbar, Button, IconButton, Avatar, Menu, MenuItem, Typography,ListItem, ListItemIcon , Switch } from '@mui/material'; import { styled } from '@mui/system'; import { Link } from 'react-router-dom'; import { AuthContext } from '../authcontext'; @@ -113,12 +113,17 @@ const NavBar = ({ setDarkMode, darkMode}) => { {username} - - {t('logout')} - + {t('history')} + + {t('ranking')} + + + + {t('logout')} + diff --git a/webapp/src/components/ranking/Ranking.js b/webapp/src/components/ranking/Ranking.js new file mode 100644 index 00000000..f38ad9ce --- /dev/null +++ b/webapp/src/components/ranking/Ranking.js @@ -0,0 +1,27 @@ +import React from 'react'; + +export function Ranking({darkMode}){ + // Aquí puedes agregar la lógica para obtener los datos del ranking de usuarios + const users = [ + { name: 'Usuario 1', dailyQuestions: 10 }, + { name: 'Usuario 2', dailyQuestions: 8 }, + { name: 'Usuario 3', dailyQuestions: 5 }, + // Agrega más usuarios según sea necesario + ]; + + // Ordenar los usuarios por número de preguntas diarias acertadas + users.sort((a, b) => b.dailyQuestions - a.dailyQuestions); + + return ( +
+

Ranking de Usuarios

+
    + {users.map((user, index) => ( +
  1. + {user.name} - {user.dailyQuestions} preguntas acertadas +
  2. + ))} +
+
+ ); +} \ No newline at end of file diff --git a/webapp/src/internacionalizacion/locales/en/translation.json b/webapp/src/internacionalizacion/locales/en/translation.json index 01799f20..9a00913e 100644 --- a/webapp/src/internacionalizacion/locales/en/translation.json +++ b/webapp/src/internacionalizacion/locales/en/translation.json @@ -39,6 +39,8 @@ "timeUnitHours" : " hours ", "questionsPercentage": " %", + "ranking": "Ranking", + "loginWelcomeMessage": "Welcome ", "loginDate": "Your account was created on ", "loginMessage": "Log in", diff --git a/webapp/src/internacionalizacion/locales/es/translation.json b/webapp/src/internacionalizacion/locales/es/translation.json index a83a9e5d..09a0fe34 100644 --- a/webapp/src/internacionalizacion/locales/es/translation.json +++ b/webapp/src/internacionalizacion/locales/es/translation.json @@ -39,6 +39,8 @@ "timeUnitHours" : " horas ", "questionsPercentage": " %", + "ranking": "Ranking", + "loginWelcomeMessage": "Bienvenido ", "loginDate": "Tu cuenta fue creada el ", "loginMessage": "Inicia sesión", From a422871f160639ad7d729a7c1ba3469f7631e68f Mon Sep 17 00:00:00 2001 From: lucia-sonia Date: Sat, 6 Apr 2024 20:05:37 +0200 Subject: [PATCH 55/79] cambios en las consultas a wikidata para obtener tambien resultados para el ingles --- questionservice/preguntas.xml | 325 +++++++++++++++++++++++----------- 1 file changed, 224 insertions(+), 101 deletions(-) diff --git a/questionservice/preguntas.xml b/questionservice/preguntas.xml index 2e444bcf..7d58a0f5 100644 --- a/questionservice/preguntas.xml +++ b/questionservice/preguntas.xml @@ -1,296 +1,419 @@ + - SELECT DISTINCT ?countryLabel (SAMPLE(?nombreCapital) AS ?capitales) + SELECT DISTINCT ?countryLabel_es ?countryLabel_en (SAMPLE(?nombreCapital_es) AS ?capitales_es) (SAMPLE(?nombreCapital_en) AS ?capitales_en) WHERE { ?country wdt:P31 wd:Q3624078. # Instancia de país ?country wdt:P36 ?capital. # Tiene capital - ?country rdfs:label ?countryLabel filter (lang(?countryLabel) = "es"). - ?capital rdfs:label ?nombreCapital filter (lang(?nombreCapital) = "es"). + ?country rdfs:label ?countryLabel_es FILTER(LANG(?countryLabel_es) = "es"). + ?country rdfs:label ?countryLabel_en FILTER(LANG(?countryLabel_en) = "en"). + ?capital rdfs:label ?nombreCapital_es FILTER(LANG(?nombreCapital_es) = "es"). + ?capital rdfs:label ?nombreCapital_en FILTER(LANG(?nombreCapital_en) = "en"). + } - GROUP BY ?countryLabel + GROUP BY ?countryLabel_es ?countryLabel_en - - SELECT ?stateLabel ?state ?capitalLabel + + + SELECT ?stateLabel_es ?stateLabel_en ?state ?capitalLabel_es ?state_en ?capitalLabel_en WHERE { + # Obtener los estados y sus capitales ?state wdt:P31 wd:Q35657 . ?state p:P36 ?statement . ?statement ps:P36 ?capital . - # Fetching the rank of the property - ?statement wikibase:rank ?rank . - SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE],es". } - # Subquery to get the highest ranked capital for each state - { - SELECT ?state (MAX(?rank) AS ?maxRank) - WHERE { - ?state wdt:P31 wd:Q35657 . - ?state p:P36 ?statement . - ?statement wikibase:rank ?rank . + + # Obtener el rango de la propiedad + ?statement wikibase:rank ?rank . + + # Subconsulta para obtener la capital con el rango máximo para cada estado + { + SELECT ?state (MAX(?rank) AS ?maxRank) + WHERE { + ?state wdt:P31 wd:Q35657 . + ?state p:P36 ?statement . + ?statement wikibase:rank ?rank . + } + GROUP BY ?state } - GROUP BY ?state - } - FILTER (?rank = ?maxRank) - } + FILTER (?rank = ?maxRank) + + # Obtener las etiquetas en diferentes idiomas para los estados y capitales + SERVICE wikibase:label { + bd:serviceParam wikibase:language "en" . + ?state rdfs:label ?stateLabel_en . + ?capital rdfs:label ?capitalLabel_en . + } + SERVICE wikibase:label { + bd:serviceParam wikibase:language "es" . + ?state rdfs:label ?stateLabel_es . + ?capital rdfs:label ?capitalLabel_es . + } + } - - + + - SELECT DISTINCT ?nombreLibro (GROUP_CONCAT(DISTINCT ?nombreAuthor; SEPARATOR=", ") AS ?authors) + SELECT DISTINCT ?nombreLibro_es ?nombreLibro_en (GROUP_CONCAT(DISTINCT ?nombreAuthor_es; SEPARATOR=", ") AS ?autores_es) (GROUP_CONCAT(DISTINCT ?nombreAuthor_en; SEPARATOR=", ") AS ?autores_en) WHERE { ?book wdt:P31 wd:Q571. # libro (clase) ?book wdt:P50 ?author. # Relación "autor" - ?book rdfs:label ?nombreLibro filter (lang(?nombreLibro) = "es"). - ?author rdfs:label ?nombreAuthor filter (lang(?nombreAuthor) = "es"). + ?book rdfs:label ?nombreLibro_es filter (lang(?nombreLibro_es) = "es"). + ?book rdfs:label ?nombreLibro_en filter (lang(?nombreLibro_en) = "en"). + ?author rdfs:label ?nombreAuthor_es filter (lang(?nombreAuthor_es) = "es"). + ?author rdfs:label ?nombreAuthor_en filter (lang(?nombreAuthor_en) = "en"). } - GROUP BY ?nombreLibro + GROUP BY ?nombreLibro_es ?nombreLibro_en + - SELECT DISTINCT ?nombreLibro (GROUP_CONCAT(DISTINCT ?nombregenero; SEPARATOR=", ") AS ?generos) + SELECT DISTINCT ?nombreLibro_es ?nombreLibro_en (GROUP_CONCAT(DISTINCT ?nombregenero_es; SEPARATOR=", ") AS ?generos_es) (GROUP_CONCAT(DISTINCT ?nombregenero_en; SEPARATOR=", ") AS ?generos_en) WHERE { ?book wdt:P31 wd:Q571. ?book wdt:P136 ?genero. # Relación "género" - ?book rdfs:label ?nombreLibro filter (lang(?nombreLibro) = "es"). - ?genero rdfs:label ?nombregenero filter (lang(?nombregenero) = "es"). + ?book rdfs:label ?nombreLibro_es filter (lang(?nombreLibro_es) = "es"). + ?book rdfs:label ?nombreLibro_en filter (lang(?nombreLibro_en) = "en"). + ?genero rdfs:label ?nombregenero_es filter (lang(?nombregenero_es) = "es"). + ?genero rdfs:label ?nombregenero_en filter (lang(?nombregenero_en) = "en"). } - GROUP BY ?nombreLibro + GROUP BY ?nombreLibro_es ?nombreLibro_en + - SELECT DISTINCT ?nombreLibro (GROUP_CONCAT(DISTINCT ?nombrePais; SEPARATOR=", ") AS ?paises) + SELECT DISTINCT ?nombreLibro_es ?nombreLibro_en (GROUP_CONCAT(DISTINCT ?nombrePais_es; SEPARATOR=", ") AS ?paises_es) (GROUP_CONCAT(DISTINCT ?nombrePais_en; SEPARATOR=", ") AS ?paises_en) WHERE { ?book wdt:P31 wd:Q571. # libro (clase) ?book wdt:P495 ?country. # Relación "país de origen" - ?book rdfs:label ?nombreLibro filter (lang(?nombreLibro) = "es"). - ?country rdfs:label ?nombrePais filter (lang(?nombrePais) = "es"). + ?book rdfs:label ?nombreLibro_es filter (lang(?nombreLibro_es) = "es"). + ?book rdfs:label ?nombreLibro_en filter (lang(?nombreLibro_en) = "en"). + ?country rdfs:label ?nombrePais_es filter (lang(?nombrePais_es) = "es"). + ?country rdfs:label ?nombrePais_en filter (lang(?nombrePais_en) = "en"). } - GROUP BY ?nombreLibro + GROUP BY ?nombreLibro_es ?nombreLibro_en + - SELECT ?personLabel ?persona ?birthDate - WHERE{ + SELECT ?personLabel_es ?personLabel_en ?person ?birthDate ?person_en ?birthDate_en + WHERE { ?person wdt:P27 wd:Q29 . ?person wdt:P106 wd:Q33999 . - ?person wdt:P569 ?birthDate . + ?person wdt:P569 ?birthDate . + ?person wdt:P569 ?birthDate_en . + SERVICE wikibase:label { + bd:serviceParam wikibase:language "es". + ?person rdfs:label ?personLabel_es . + } SERVICE wikibase:label { - bd:serviceParam wikibase:language "[AUTO_LANGUAGE],es". + bd:serviceParam wikibase:language "en". + ?person rdfs:label ?personLabel_en . } - } + } + - SELECT ?personLabel ?persona ?startDate + SELECT ?personLabel_es ?personLabel_en ?persona ?startDate ?persona_en ?startDate_en WHERE { ?person wdt:P27 wd:Q29 . ?person wdt:P106 wd:Q156839 . ?person wdt:P2031 ?startDate. + ?person wdt:P2031 ?startDate_en + SERVICE wikibase:label { - bd:serviceParam wikibase:language "[AUTO_LANGUAGE],es". - } - } + bd:serviceParam wikibase:language "es". + ?person rdfs:label ?personLabel_es . + } + + SERVICE wikibase:label { + bd:serviceParam wikibase:language "en". + ?person rdfs:label ?personLabel_en . + } + } + - SELECT DISTINCT ?nombreVideojuego (GROUP_CONCAT(DISTINCT ?nombrePlataforma; SEPARATOR=", ") AS ?plataformas) + SELECT DISTINCT ?nombreVideojuego_es ?nombreVideojuego_en (GROUP_CONCAT(DISTINCT ?nombrePlataforma_es; SEPARATOR=", ") AS ?plataformas_es) (GROUP_CONCAT(DISTINCT ?nombrePlataforma_en; SEPARATOR=", ") AS ?plataformas_en) WHERE { ?videojuego wdt:P31 wd:Q7889. # Videojuegos (clase) ?videojuego wdt:P123 ?plataforma. # Relación "plataforma" - ?videojuego rdfs:label ?nombreVideojuego filter (lang(?nombreVideojuego) = "es"). - ?plataforma rdfs:label ?nombrePlataforma filter (lang(?nombrePlataforma) = "es"). + ?videojuego rdfs:label ?nombreVideojuego_es filter (lang(?nombreVideojuego_es) = "es"). + ?videojuego rdfs:label ?nombreVideojuego_en filter (lang(?nombreVideojuego_en) = "en"). + ?plataforma rdfs:label ?nombrePlataforma_es filter (lang(?nombrePlataforma_es) = "es"). + ?plataforma rdfs:label ?nombrePlataforma_en filter (lang(?nombrePlataforma_en) = "en"). } - GROUP BY ?nombreVideojuego + GROUP BY ?nombreVideojuego_es ?nombreVideojuego_en + - SELECT DISTINCT ?nombreVideojuego (GROUP_CONCAT(DISTINCT ?nombreGenero; SEPARATOR=", ") AS ?generos) + SELECT DISTINCT ?nombreVideojuego_es ?nombreVideojuego_en (GROUP_CONCAT(DISTINCT ?nombreGenero_es; SEPARATOR=", ") AS ?generos_es) (GROUP_CONCAT(DISTINCT ?nombreGenero_en; SEPARATOR=", ") AS ?generos_en) WHERE { ?videojuego wdt:P31 wd:Q7889. # Videojuegos (clase) ?videojuego wdt:P136 ?genero. # Relación "género" - ?videojuego rdfs:label ?nombreVideojuego filter (lang(?nombreVideojuego) = "es"). - ?genero rdfs:label ?nombreGenero filter (lang(?nombreGenero) = "es"). + ?videojuego rdfs:label ?nombreVideojuego_es filter (lang(?nombreVideojuego_es) = "es"). + ?videojuego rdfs:label ?nombreVideojuego_en filter (lang(?nombreVideojuego_en) = "en"). + ?genero rdfs:label ?nombreGenero_es filter (lang(?nombreGenero_es) = "es"). + ?genero rdfs:label ?nombreGenero_en filter (lang(?nombreGenero_en) = "en"). } - GROUP BY ?nombreVideojuego + GROUP BY ?nombreVideojuego_es ?nombreVideojuego_en + - SELECT DISTINCT ?clubLabel (GROUP_CONCAT(DISTINCT ?estadioName; SEPARATOR=", ") AS ?estadios) + SELECT DISTINCT ?clubLabel_es ?clubLabel_en (GROUP_CONCAT(DISTINCT ?estadioName_es; SEPARATOR=", ") AS ?estadios_es) (GROUP_CONCAT(DISTINCT ?estadioName_en; SEPARATOR=", ") AS ?estadios_en) WHERE { ?club wdt:P31 wd:Q476028. # club (clase) ?club wdt:P115 ?estadio. # Relación "estadio" - ?club rdfs:label ?clubLabel filter (lang(?clubLabel) = "es"). - ?estadio rdfs:label ?estadioName filter (lang(?estadioName) = "es"). + ?club rdfs:label ?clubLabel_es filter (lang(?clubLabel_es) = "es"). + ?club rdfs:label ?clubLabel_en filter (lang(?clubLabel_en) = "en"). + ?estadio rdfs:label ?estadioName_es filter (lang(?estadioName_es) = "es"). + ?estadio rdfs:label ?estadioName_en filter (lang(?estadioName_en) = "en"). } - GROUP BY ?clubLabel + GROUP BY ?clubLabel_es ?clubLabel_en + - SELECT ?tenistaLabel ?tenista ?lugarNacimientoLabel + SELECT ?tenistaLabel_es ?tenistaLabel_en ?tenista ?lugarNacimientoLabel_es ?tenista_en ?lugarNacimientoLabel_en WHERE { ?tenista wdt:P106 wd:Q10833314. # Instancia de tenista ?tenista wdt:P19 ?lugarNacimiento. # Lugar de nacimiento - SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE],es". } + + SERVICE wikibase:label { + bd:serviceParam wikibase:language "en". + ?tenista rdfs:label ?tenistaLabel_en. + ?lugarNacimiento rdfs:label ?lugarNacimientoLabel_en. + } + + SERVICE wikibase:label { + bd:serviceParam wikibase:language "es". + ?tenista rdfs:label ?tenistaLabel_es. + ?lugarNacimiento rdfs:label ?lugarNacimientoLabel_es. + } } + - SELECT ?personLabel ?person ?birthDate + SELECT ?personLabel_es ?personLabel_en ?person ?birthDate ?person_en ?birthDate_en WHERE { ?person wdt:P27 wd:Q29 . ?person wdt:P106 wd:Q937857 . ?person wdt:P569 ?birthDate . + ?person wdt:P569 ?birthDate_en . SERVICE wikibase:label { - bd:serviceParam wikibase:language "[AUTO_LANGUAGE],es". + bd:serviceParam wikibase:language "es". + ?person rdfs:label ?personLabel_es. } - } + SERVICE wikibase:label { + bd:serviceParam wikibase:language "en". + ?person rdfs:label ?personLabel_en. + } + } + - SELECT ?personLabel ?person ?startDate + SELECT ?personLabel_es ?personLabel_en ?person ?startDate ?person_en ?startDate_en WHERE { ?person wdt:P27 wd:Q29 . ?person wdt:P106 wd:Q937857 . ?person wdt:P2031 ?startDate. - SERVICE wikibase:label { - bd:serviceParam wikibase:language "[AUTO_LANGUAGE],es". + ?person wdt:P2031 ?startDate_en. + SERVICE wikibase:label { + bd:serviceParam wikibase:language "es". + ?person rdfs:label ?personLabel_es. + } + SERVICE wikibase:label { + bd:serviceParam wikibase:language "en". + ?person rdfs:label ?personLabel_en. } } - + - SELECT DISTINCT ?nombreCompleto ?presidente ?fechaNacimiento + SELECT DISTINCT ?nombreCompleto_es ?nombreCompleto_en ?presidente ?fechaNacimiento ?presidente_en ?fechaNacimiento_en WHERE { ?presidente wdt:P31 wd:Q5; # Filtrar por personas wdt:P39 wd:Q844587; # Filtrar por posición: presidente del Gobierno de España wdt:P569 ?fechaNacimiento; # Obtener fecha de nacimiento (opcional) + wdt:P569 ?fechaNacimiento_en; # Obtener fecha de nacimiento (opcional) wdt:P1559 ?nombreCompleto. - SERVICE wikibase:label { bd:serviceParam wikibase:language "es". } + SERVICE wikibase:label { + bd:serviceParam wikibase:language "es". + ?presidente rdfs:label ?nombreCompleto_es. + } + SERVICE wikibase:label { + bd:serviceParam wikibase:language "en". + ?presidente rdfs:label ?nombreCompleto_en. + } } - ORDER BY ?nombreCompleto + ORDER BY ?nombreCompleto_es + - SELECT ?personLabel ?person ?fNacimiento + SELECT ?personLabel_es ?personLabel_en ?person ?fNacimiento ?person_en ?fNacimiento_en WHERE { ?person wdt:P19 wd:Q14317. ?person wdt:P569 ?fNacimiento . - SERVICE wikibase:label { bd:serviceParam wikibase:language "es". } + ?person wdt:P569 ?fNacimiento_en . + SERVICE wikibase:label { + bd:serviceParam wikibase:language "es". + ?person rdfs:label ?personLabel_es. + } + SERVICE wikibase:label { + bd:serviceParam wikibase:language "en". + ?person rdfs:label ?personLabel_en. + } } - - - + + - SELECT DISTINCT ?nombreCientifico (GROUP_CONCAT(DISTINCT ?nombreCampo; SEPARATOR=", ") AS ?campos) + SELECT DISTINCT ?nombreCientifico_es ?nombreCientifico_en (GROUP_CONCAT(DISTINCT ?nombreCampo_es; SEPARATOR=", ") AS ?campos_es) (GROUP_CONCAT(DISTINCT ?nombreCampo_en; SEPARATOR=", ") AS ?campos_en) WHERE { ?scientist wdt:P31 wd:Q5. # humano (clase) ?scientist wdt:P106 wd:Q901. # Relación "ocupación" con valor "científico" ?scientist wdt:P101 ?field. # Relación "campo de trabajo" - ?scientist rdfs:label ?nombreCientifico filter (lang(?nombreCientifico) = "es"). - ?field rdfs:label ?nombreCampo filter (lang(?nombreCampo) = "es"). + ?scientist rdfs:label ?nombreCientifico_es filter (lang(?nombreCientifico_es) = "es"). + ?field rdfs:label ?nombreCampo_es filter (lang(?nombreCampo_es) = "es"). + ?scientist rdfs:label ?nombreCientifico_en filter (lang(?nombreCientifico_en) = "en"). + ?field rdfs:label ?nombreCampo_en filter (lang(?nombreCampo_en) = "en"). } - GROUP BY ?nombreCientifico + GROUP BY ?nombreCientifico_es ?nombreCientifico_en + - SELECT DISTINCT ?nombreCientifico (GROUP_CONCAT(DISTINCT ?nombreInteres; SEPARATOR=", ") AS ?intereses) + SELECT DISTINCT ?nombreCientifico_es ?nombreCientifico_en (GROUP_CONCAT(DISTINCT ?nombreInteres_es; SEPARATOR=", ") AS ?intereses_es) (GROUP_CONCAT(DISTINCT ?nombreInteres_en; SEPARATOR=", ") AS ?intereses_en) WHERE { ?scientist wdt:P31 wd:Q5. # humano (clase) ?scientist wdt:P106 wd:Q901. # Relación "ocupación" con valor "científico" ?scientist wdt:P2650 ?interest. # Relación "área de interés" - ?scientist rdfs:label ?nombreCientifico filter (lang(?nombreCientifico) = "es"). - ?interest rdfs:label ?nombreInteres filter (lang(?nombreInteres) = "es"). + ?scientist rdfs:label ?nombreCientifico_es filter (lang(?nombreCientifico_es) = "es"). + ?interest rdfs:label ?nombreInteres_es filter (lang(?nombreInteres_es) = "es"). + ?scientist rdfs:label ?nombreCientifico_en filter (lang(?nombreCientifico_en) = "en"). + ?interest rdfs:label ?nombreInteres_en filter (lang(?nombreInteres_en) = "en"). } - GROUP BY ?nombreCientifico + GROUP BY ?nombreCientifico_es ?nombreCientifico_en + - SELECT DISTINCT ?nombreElemento ?element ?numeroAtomico + SELECT DISTINCT ?nombreElemento_es ?nombreElemento_en ?element ?numeroAtomico ?element_en ?numeroAtomico_en WHERE { - ?element wdt:P31 wd:Q11344. # elemento químico (clase) - ?element wdt:P1086 ?atomicNumber. # Relación "número atómico" - ?element rdfs:label ?nombreElemento filter (lang(?nombreElemento) = "es"). - BIND(str(?atomicNumber) AS ?numeroAtomico) - } + ?element wdt:P31 wd:Q11344. # Elemento químico (clase) + ?element wdt:P1086 ?atomicNumber. # Relación "número atómico" + ?element rdfs:label ?nombreElemento_es filter (lang(?nombreElemento_es) = "es"). + ?element rdfs:label ?nombreElemento_en filter (lang(?nombreElemento_en) = "en"). + BIND(str(?atomicNumber) AS ?numeroAtomico) + BIND(str(?atomicNumber) AS ?numeroAtomico_en) + } + ORDER BY ?nombreElemento_es - + - SELECT ?personLabel ?person ?startDate + SELECT ?personLabel_es ?personLabel_en ?person ?startDate ?person_en ?startDate_en WHERE { ?person wdt:P27 wd:Q29 . ?person wdt:P106 wd:Q130857 . - ?person wdt:P2031 ?startDate. + ?person wdt:P2031 ?startDate. + ?person wdt:P2031 ?startDate_en. + SERVICE wikibase:label { + bd:serviceParam wikibase:language "es". + ?person rdfs:label ?personLabel_es. + } SERVICE wikibase:label { - bd:serviceParam wikibase:language "[AUTO_LANGUAGE],es". + bd:serviceParam wikibase:language "en". + ?person rdfs:label ?personLabel_en. } - } + } + - SELECT ?personLabel ?person ?startDate + SELECT ?personLabel_es ?personLabel_en ?person ?startDate ?person_en ?startDate_en WHERE { ?person wdt:P27 wd:Q29 . ?person wdt:P106 wd:Q855091 . - ?person wdt:P2031 ?startDate. - SERVICE wikibase:label { - bd:serviceParam wikibase:language "[AUTO_LANGUAGE],es". - } + ?person wdt:P2031 ?startDate. + ?person wdt:P2031 ?startDate_en. + SERVICE wikibase:label { + bd:serviceParam wikibase:language "es". + ?person rdfs:label ?personLabel_es. + } + SERVICE wikibase:label { + bd:serviceParam wikibase:language "en". + ?person rdfs:label ?personLabel_en. + } } + - SELECT ?personLabel ?person ?startDate + SELECT ?personLabel_es ?personLabel_en ?person ?startDate ?person_en ?startDate_en WHERE { ?person wdt:P27 wd:Q29 . ?person wdt:P106 wd:Q177220 . - ?person wdt:P2031 ?startDate. - SERVICE wikibase:label { - bd:serviceParam wikibase:language "[AUTO_LANGUAGE],es". - } + ?person wdt:P2031 ?startDate. + ?person wdt:P2031 ?startDate_en. + SERVICE wikibase:label { + bd:serviceParam wikibase:language "es". + ?person rdfs:label ?personLabel_es. + } + SERVICE wikibase:label { + bd:serviceParam wikibase:language "en". + ?person rdfs:label ?personLabel_en. + } } From 1579efa11c3905b5f251c5f4db8478ee4ac96256 Mon Sep 17 00:00:00 2001 From: lucia-sonia Date: Sat, 6 Apr 2024 20:19:53 +0200 Subject: [PATCH 56/79] =?UTF-8?q?cambiado=20el=20esqueleto=20de=20las=20pr?= =?UTF-8?q?eguntas=20para=20que=20ahora=20tengan=20el=20esqueleto=20en=20e?= =?UTF-8?q?spa=C3=B1ol=20y=20en=20ingles?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- questionservice/esqueletoPreguntas.xml | 66 ++++++++++++++++++-------- 1 file changed, 46 insertions(+), 20 deletions(-) diff --git a/questionservice/esqueletoPreguntas.xml b/questionservice/esqueletoPreguntas.xml index af15efff..6f16c658 100644 --- a/questionservice/esqueletoPreguntas.xml +++ b/questionservice/esqueletoPreguntas.xml @@ -1,82 +1,108 @@ - ¿Cuál es la capital del pais {RELLENAR}? + ¿Cuál es la capital de {RELLENAR}? + What is the capital of {RELLENAR}? - ¿Cuál es la capital del estado de {RELLENAR}? + ¿Cuál es la capital del estado de {RELLENAR}? + What is the capital of the state of {RELLENAR}? - ¿Cuál es el autor/es del libro llamado {RELLENAR}? + ¿Cuál es el autor/es del libro llamado {RELLENAR}? + What is the author(s) of the book called {RELLENAR}? - ¿Cuál es el genero/s del libro llamado {RELLENAR}? + ¿Cuál es el genero/s del libro llamado {RELLENAR}? + What is the genre(s) of the book called {RELLENAR}? + + + + ¿De que país es el libro titulado {RELLENAR}? + What country is the book titled {RELLENAR} from? - ¿Cuál es la fecha de nacimiento del actor español {RELLENAR}? + ¿Cuál es la fecha de nacimiento del actor español {RELLENAR}? + What is the date of birth of the Spanish actor {RELLENAR}? - ¿Cuál es la fecha de nacimiento del chef español {RELLENAR}? + ¿Cuál es la fecha de nacimiento del chef español {RELLENAR}? + What is the date of birth of the Spanish chef {RELLENAR}? - ¿Cuál es la plataforma/s que desarrollo el videojuego llamado {RELLENAR}? + ¿Cuál es la plataforma/s que desarrollo el videojuego llamado {RELLENAR}? + What is the platform/s that developed the video game called {RELLENAR}? - ¿Cuál es el genero/s del videojuego llamado {RELLENAR}? + ¿Cuál es el genero/s del videojuego llamado {RELLENAR}? + What is the genre(s) of the video game called {RELLENAR}? - ¿Cuál es el estadio del club de futbol llamado {RELLENAR}? + ¿Cuál es el estadio del club de futbol llamado {RELLENAR}? + What is the stadium of the soccer club called {RELLENAR}? - ¿Cuál es el lugar de nacimiento del tenista llamado {RELLENAR}? + ¿Cuál es el lugar de nacimiento del tenista llamado {RELLENAR}? + What is the birthplace of the tennis player named {RELLENAR}? - ¿Cuál es la fecha de nacimiento del futbolista llamado {RELLENAR}? + ¿Cuál es la fecha de nacimiento del futbolista llamado {RELLENAR}? + What is the date of birth of the footballer named {RELLENAR}? - ¿Cuál es la fecha en la que comenzo su carrera el futbolista llamado {RELLENAR}? + ¿Cuál es la fecha en la que comenzo su carrera el futbolista llamado {RELLENAR}? + WWhat is the date on which the footballer named {RELLENAR} began his career? - ¿Cuál es la fecha de nacimiento del presidente de España llamado {RELLENAR}? + ¿Cuál es la fecha de nacimiento del presidente de España llamado {RELLENAR}? + What is the date of birth of the president of Spain named {RELLENAR}? - ¿Cuál es la fecha de nacimiento de la personalidad de Oviedo llamado/a {RELLENAR}? + ¿Cuál es la fecha de nacimiento de la personalidad de Oviedo llamado/a {RELLENAR}? + What is the date of birth of the Oviedo personality named {RELLENAR}? - ¿Cual/es son los campos de estudio del cientifico llamado {RELLENAR}? + ¿Cual/es son los campos de estudio del cientifico llamado {RELLENAR}? + What are the fields of study of the scientist named {RELLENAR}? - ¿Cual/es son los intereses de estudio del cientifico llamado {RELLENAR}? + ¿Cual/es son los intereses de estudio del cientifico llamado {RELLENAR}? + What are the study interests of the scientist named {RELLENAR}? - ¿Cual es el numero atomico del elemento llamado {RELLENAR}? + ¿Cual es el numero atomico del elemento llamado {RELLENAR}? + What is the atomic number of the element called {RELLENAR}? - ¿Cual es la fecha en la que comenzo su carrera el dj llamado {RELLENAR}? + ¿Cual es la fecha en la que comenzo su carrera el dj llamado {RELLENAR}? + What is the author(s) of the book called {RELLENAR}? - ¿Cual es la fecha en la que comenzo su carrera el guitarrista llamado {RELLENAR}? + ¿Cual es la fecha en la que comenzo su carrera el guitarrista llamado {RELLENAR}? + What is the date the DJ named {RELLENAR} started his career? - ¿Cual es la fecha en la que comenzo su carrera el/la cantante llamado {RELLENAR}? + ¿Cual es la fecha en la que comenzo su carrera el/la cantante llamado {RELLENAR}? + What is the date on which the singer named {RELLENAR} began his/her career? + \ No newline at end of file From df31519a0f6c80fc29abc0e19b0306c65186d017 Mon Sep 17 00:00:00 2001 From: lucia-sonia Date: Sat, 6 Apr 2024 21:01:25 +0200 Subject: [PATCH 57/79] =?UTF-8?q?questionservice=20genera=20a=20la=20vez?= =?UTF-8?q?=20pregunta=20en=20espa=C3=B1ol=20y=20pregunta=20en=20ingles?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- questionservice/guardarPreguntaBaseDatos.js | 28 +++-- questionservice/obtenerPreguntasWikidata.js | 119 +++++++++++++++----- questionservice/package-lock.json | 2 +- questionservice/question-model.js | 18 ++- 4 files changed, 122 insertions(+), 45 deletions(-) diff --git a/questionservice/guardarPreguntaBaseDatos.js b/questionservice/guardarPreguntaBaseDatos.js index bcaafda4..426e6777 100644 --- a/questionservice/guardarPreguntaBaseDatos.js +++ b/questionservice/guardarPreguntaBaseDatos.js @@ -64,13 +64,18 @@ class GuardarBaseDatos{ return new Promise((resolve, reject) => { var idTipo; // Comprobar si la pregunta ya existe - Pregunta.findOne({ textoPregunta: this.finalQuestion.question }) + Pregunta.findOne({ textoPregunta_es: this.finalQuestion.question_es }) .then(preguntaExistente => { if (!preguntaExistente) { // Si no existe la pregunta, se crea var nuevaPregunta = new Pregunta({ - textoPregunta: this.finalQuestion.question, - respuestaCorrecta: this.finalQuestion.correct, + //en español + textoPregunta_es: this.finalQuestion.question_es, + respuestaCorrecta_es: this.finalQuestion.correct_es, + //en ingles + textoPregunta_en: this.finalQuestion.question_en, + respuestaCorrecta_en: this.finalQuestion.correct_en, + //categoria categoria: idCategoria }); @@ -121,19 +126,20 @@ class GuardarBaseDatos{ }); } }).catch(error => { - reject(new Error('Error al guardar la pregunta en la base de datos: ' + error.message)); + reject(new Error('Error al guardar la pregunta en la base de datos, la pregunta ya existe: ' + error.message)); }); }); } guardarPrimeraIncorrecta(idTipo){ //comprobar si la primera respuesta existe ya en la base de datos - Respuesta.findOne({ textoRespuesta: this.finalQuestion.incorrect1 }) + Respuesta.findOne({ textoRespuesta_es: this.finalQuestion.incorrect1_es }) .then(respuestaExistente => { if (!respuestaExistente) { // Si no existe ya esa pregunta la crea var nuevaRespuesta = new Respuesta({ - textoRespuesta: this.finalQuestion.incorrect1, + textoRespuesta_es: this.finalQuestion.incorrect1_es, + textoRespuesta_en: this.finalQuestion.incorrect1_en, tipos: [idTipo] }); @@ -156,12 +162,13 @@ class GuardarBaseDatos{ guardarSegundaIncorrecta(idTipo){ //comprobar si la segunda respuesta existe ya en la base de datos - Respuesta.findOne({ textoRespuesta: this.finalQuestion.incorrect2 }) + Respuesta.findOne({ textoRespuesta_es: this.finalQuestion.incorrect2_es }) .then(respuestaExistente => { if (!respuestaExistente) { // Si no existe ya esa pregunta la crea var nuevaRespuesta = new Respuesta({ - textoRespuesta: this.finalQuestion.incorrect2, + textoRespuesta_es: this.finalQuestion.incorrect2_es, + textoRespuesta_en: this.finalQuestion.incorrect2_en, tipos: [idTipo] }); @@ -184,12 +191,13 @@ class GuardarBaseDatos{ guardarTerceraIncorrecta(idTipo){ //comprobar si la tercera respuesta existe ya en la base de datos - Respuesta.findOne({ textoRespuesta: this.finalQuestion.incorrect3 }) + Respuesta.findOne({ textoRespuesta_es: this.finalQuestion.incorrect3_es }) .then(respuestaExistente => { if (!respuestaExistente) { // Si no existe ya esa pregunta la crea var nuevaRespuesta = new Respuesta({ - textoRespuesta: this.finalQuestion.incorrect3, + textoRespuesta_es: this.finalQuestion.incorrect3_es, + textoRespuesta_en: this.finalQuestion.incorrect3_en, tipos: [idTipo] }); diff --git a/questionservice/obtenerPreguntasWikidata.js b/questionservice/obtenerPreguntasWikidata.js index b1bb1364..f112a51d 100644 --- a/questionservice/obtenerPreguntasWikidata.js +++ b/questionservice/obtenerPreguntasWikidata.js @@ -4,9 +4,7 @@ const axios = require('axios'); class ObtenerPreguntaWikiData { - constructor(language = 'es') { - this.language = language; - + constructor() { //obtenemos las labels de lo que queremos obtener (lo que esta en la select que queremos buscar) this.labels; @@ -86,7 +84,7 @@ class ObtenerPreguntaWikiData { }); //comprobamos que tenemos el numero de labels correcto - if(this.labels.length !== 3){ + if(this.labels.length !== 6){ reject(new Error('La consulta no tiene el formato correcto para las labels')); return; } @@ -135,10 +133,15 @@ class ObtenerPreguntaWikiData { if(data && data.results && data.results.bindings.length > 0){ var entidades = data.results.bindings.map(binding => { return { - //obtenemos el label de la "pregunta" (ejemplo country) - label: this.obtenerValorPropiedad(binding, this.labels[0]), - //obtenemos el label de la "respuesta" (ejemplo capital) - result: this.obtenerValorPropiedad(binding, this.labels[2]) + //obtenemos el label de la "pregunta" (ejemplo country) en español + label_es: this.obtenerValorPropiedad(binding, this.labels[0]), + //obtenemos el label de la "respuesta" (ejemplo capital) en español + result_es: this.obtenerValorPropiedad(binding, this.labels[3]), + + //obtenemos el label de la "pregunta" (ejemplo country) en ingles + label_en: this.obtenerValorPropiedad(binding, this.labels[1]), + //obtenemos el label de la "respuesta" (ejemplo capital) en ingles + result_en: this.obtenerValorPropiedad(binding, this.labels[5]) }; }); @@ -169,8 +172,8 @@ class ObtenerPreguntaWikiData { if (binding && binding.hasOwnProperty(propertyName)) { //comprobamos si es una fecha if(this.esFormatoISO8601(binding[propertyName].value)){ - //devolvemos la fecha formateada - return this.formatearFecha(binding[propertyName].value); + //devolvemos la fecha formateada dependiendo del idioma + return this.formatearFecha(binding[propertyName].value, propertyName.substring(myString.length - 2) ); } //si no es una fecha devolvemos el valor else{ @@ -208,28 +211,37 @@ class ObtenerPreguntaWikiData { var preguntas = result.textoPreguntas.pregunta; //obtenemos el esqueleto de la pregunta que queremos hacer - var textoPregunta = this.obtenerTextoPregunta(preguntas, this.question, this.type); + var textoPreguntaEspañol = this.obtenerTextoPregunta(preguntas, this.question, this.type, "es"); + var textoPreguntaIngles = this.obtenerTextoPregunta(preguntas, this.question, this.type, "en"); //comprobamos si se ha encontrado el texto de la pregunta - if(textoPregunta === ""){ - reject(new Error('No se ha encontrado el texto de la pregunta')); + if(textoPreguntaEspañol === "" || textoPreguntaIngles === "") { + reject(new Error('No se ha encontrado el texto de la pregunta para español o para ingles')); return; } + + console.log("Texto pregunta español: ", textoPreguntaEspañol); + console.log("Texto pregunta ingles: ", textoPreguntaIngles); //para comprobar si es un Q var regex = /^Q\d+/; //comprobamos que el resultado es valido para hacer la pregunta (que no sea QXXXXX) var preguntaCorrecta = this.answers.find(entidad => { - return entidad.label !== "Ninguna de las anteriores" && !regex.test(entidad.label); + return entidad.label_es !== "Ninguna de las anteriores" && !regex.test(entidad.label_es) && entidad.label_en !== "Ninguna de las anteriores" && !regex.test(entidad.label_en); }); if(preguntaCorrecta){ //rellenamos el esqueleto de la pregunta con los datos de la entidad - var pregunta = preguntaCorrecta.label; - var respuestaCorrecta = preguntaCorrecta.result; - var consulta = textoPregunta.replace('{RELLENAR}', pregunta); + //para generarla en español + var pregunta_es = preguntaCorrecta.label_es; + var respuestaCorrecta_es = preguntaCorrecta.result_es; + var consulta_es = textoPreguntaEspañol.replace('{RELLENAR}', pregunta_es); + //para generarla en inglés + var pregunta_en = preguntaCorrecta.label_en; + var respuestaCorrecta_en = preguntaCorrecta.result_en; + var consulta_en = textoPreguntaIngles.replace('{RELLENAR}', pregunta_en); - this.generarPregunta(consulta, respuestaCorrecta) + this.generarPregunta(consulta_es, respuestaCorrecta_es, consulta_en, respuestaCorrecta_en) .then(() => resolve()) .catch(error => reject(error)); } @@ -248,13 +260,20 @@ class ObtenerPreguntaWikiData { /* obtenemos el texto de la pregunta que queremos hacer */ - obtenerTextoPregunta(preguntas, question, type) { + obtenerTextoPregunta(preguntas, question, type, idioma) { for (var pregunta of preguntas) { + //si no tiene alguno de los datos que necesitamos if(!pregunta.$.question || !pregunta.$.type){ return ""; } + //comprobamos si es la pregunta que queremos if (pregunta.$.question === question && pregunta.$.type === type) { - return pregunta._; + if(idioma === "es"){ + console.log("Pregunta: ", pregunta.es[0]); + return pregunta.es[0]; + } + else if(idioma === "en") + return pregunta.en[0]; } } return ""; @@ -263,25 +282,41 @@ class ObtenerPreguntaWikiData { /* generamos un json con la info necesaria de la pregunta para poder guardarla en la base de datos */ - generarPregunta(consulta, respuestaCorrecta){ + generarPregunta(consulta_es, respuestaCorrecta_es, consulta_en, respuestaCorrecta_en){ return new Promise((resolve, reject) => { var respuestasIncorrectas = []; var num = 0; - //añadimos el resto de respuestas + //añadimos el resto de respuestas en español + for(var i = 0; i < this.answers.length; i++){ + if(this.answers[i].result_es !== respuestaCorrecta_es){ + respuestasIncorrectas[num] = this.answers[i].result_es; + num++; + } + } + //añadimos el resto de respuestas en inglés for(var i = 0; i < this.answers.length; i++){ - if(this.answers[i].result !== respuestaCorrecta){ - respuestasIncorrectas[num] = this.answers[i].result; + if(this.answers[i].result_en !== respuestaCorrecta_en){ + respuestasIncorrectas[num] = this.answers[i].result_en; num++; } } //guardamos la pregunta para añadirla a la base de datos this.finalQuestion = { - question: consulta.trim().replace(/\r?\n|\r/g, ''), - correct: respuestaCorrecta, - incorrect1: respuestasIncorrectas[0], - incorrect2: respuestasIncorrectas[1], - incorrect3: respuestasIncorrectas[2], + //para español + question_es: consulta_es.trim().replace(/\r?\n|\r/g, ''), + correct_es: respuestaCorrecta_es, + incorrect1_es: respuestasIncorrectas[0], + incorrect2_es: respuestasIncorrectas[1], + incorrect3_es: respuestasIncorrectas[2], + + //para ingles + question_en: consulta_en.trim().replace(/\r?\n|\r/g, ''), + correct_en: respuestaCorrecta_en, + incorrect1_en: respuestasIncorrectas[3], + incorrect2_en: respuestasIncorrectas[4], + incorrect3_en: respuestasIncorrectas[5], + category: this.category, type: this.type } @@ -309,7 +344,19 @@ class ObtenerPreguntaWikiData { /* formateamos la fecha a un formato más legible */ - formatearFecha(fechaISO8601) { + formatearFecha(fechaISO8601, idioma) { + if(idioma === "es"){ + return this.formatearFechaEspañol(fechaISO8601); + } + else{ + return this.formatearFechaIngles(fechaISO8601); + } + } + + /* + formateamos la fecha a un formato más legible en español + */ + formatearFechaEspañol(fechaISO8601) { var meses = ["enero", "febrero", "marzo", "abril", "mayo", "junio", "julio", "agosto", "septiembre", "octubre", "noviembre", "diciembre"]; var fecha = new Date(fechaISO8601); var dia = fecha.getDate(); @@ -317,7 +364,17 @@ class ObtenerPreguntaWikiData { var año = fecha.getFullYear(); return dia + " de " + mes + " de " + año; } - + + /* + formateamos la fecha a un formato más legible en inglés + */ + formatearFechaIngles(fechaISO8601) { + var fecha = new Date(fechaISO8601); + var dia = fecha.getDate(); + var mes = fecha.getMonth() + 1; + var año = fecha.getFullYear(); + return mes + "/" + dia + "/" + año; + } } diff --git a/questionservice/package-lock.json b/questionservice/package-lock.json index 61b6a464..d11ef109 100644 --- a/questionservice/package-lock.json +++ b/questionservice/package-lock.json @@ -12,8 +12,8 @@ "axios": "^0.24.0", "express": "^4.18.2", "mongoose": "^8.0.4", - "socket.io": "^4.7.5", "node-cron": "^3.0.0", + "socket.io": "^4.7.5", "xml2js": "^0.4.23" }, "devDependencies": { diff --git a/questionservice/question-model.js b/questionservice/question-model.js index 91aec689..dc86e07c 100644 --- a/questionservice/question-model.js +++ b/questionservice/question-model.js @@ -2,11 +2,19 @@ const mongoose = require('mongoose'); //preguntas const preguntaSchema = new mongoose.Schema({ - textoPregunta: { + textoPregunta_es: { type: String, required: true }, - respuestaCorrecta: { + respuestaCorrecta_es: { + type: String, + required: true + }, + textoPregunta_en: { + type: String, + required: true + }, + respuestaCorrecta_en: { type: String, required: true }, @@ -30,7 +38,11 @@ const Categoria = mongoose.model('Categoria', categoriaSchema); //Respuesta const respuestaSchema = new mongoose.Schema({ - textoRespuesta: { + textoRespuesta_es: { + type: String, + required: true + }, + textoRespuesta_en: { type: String, required: true }, From 7407de5aff0da9c3130a6ea1783364f42698ba2f Mon Sep 17 00:00:00 2001 From: lucia-sonia Date: Sat, 6 Apr 2024 21:33:12 +0200 Subject: [PATCH 58/79] la url ahora se para cuando se guarda la pregunta en la bd --- gatewayservice/gateway-service.js | 1 + questionservice/guardarPreguntaBaseDatos.js | 39 +++++++++++++-------- questionservice/obtenerPreguntasWikidata.js | 8 ++--- questionservice/question-service.js | 3 +- 4 files changed, 32 insertions(+), 19 deletions(-) diff --git a/gatewayservice/gateway-service.js b/gatewayservice/gateway-service.js index 06da0914..b08cdcb9 100644 --- a/gatewayservice/gateway-service.js +++ b/gatewayservice/gateway-service.js @@ -94,6 +94,7 @@ app.get('/generateQuestion', async (req, res) => { try { // llamamos al servicio de preguntas await axios.get(questionServiceUrl+'/generateQuestion', req.body); + res.status(200).send("Pregunta generada y guardada correctamente."); } catch (error) { //Modifico el error res.status(500).json({ error: 'Error al realizar la solicitud al servicio de generacion de preguntas -> ' + error.response.data.error}); diff --git a/questionservice/guardarPreguntaBaseDatos.js b/questionservice/guardarPreguntaBaseDatos.js index 426e6777..96624f1f 100644 --- a/questionservice/guardarPreguntaBaseDatos.js +++ b/questionservice/guardarPreguntaBaseDatos.js @@ -10,20 +10,31 @@ class GuardarBaseDatos{ this.finalQuestion; } - guardarEnBaseDatos(finalQuestion){ - this.finalQuestion = finalQuestion; - //primero deberiamos de guardar la categoria - this.guardarCategoria().then(idCategoria => { - // Guardamos el tipo de pregunta - return this.guardarPreguntaTipo(idCategoria); - }).then(idTipo => { - // Guardamos las respuestas incorrectas - this.guardarPrimeraIncorrecta(idTipo); - this.guardarSegundaIncorrecta(idTipo); - this.guardarTerceraIncorrecta(idTipo); - }).catch(error => { - throw new Error("Error al guardar la pregunta: " + error.message); - }); + guardarEnBaseDatos(finalQuestion){ + return new Promise((resolve, reject) => { + this.finalQuestion = finalQuestion; + + //primero deberiamos de guardar la categoria + this.guardarCategoria().then(idCategoria => { + // Guardamos el tipo de pregunta + return this.guardarPreguntaTipo(idCategoria); + }).then(idTipo => { + Promise.all([ + this.guardarPrimeraIncorrecta(idTipo), + this.guardarSegundaIncorrecta(idTipo), + this.guardarTerceraIncorrecta(idTipo) + ]).then(() => { + console.log("Pregunta guardada correctamente en la base de datos."); + resolve(); //para que se resuelva cuando las tres respuestas incorrectas se hayan guardado + }).catch(error => { + throw new Error("Error al guardar las respuestas incorrectas: " + error.message); + }); + }).catch(error => { + throw new Error("Error al guardar la pregunta: " + error.message); + }); + }).catch(error => { + reject(new Error('Error al guardar la pregunta en la base de datos: ' + error.message)); + }); } guardarCategoria(){ diff --git a/questionservice/obtenerPreguntasWikidata.js b/questionservice/obtenerPreguntasWikidata.js index f112a51d..d284aa07 100644 --- a/questionservice/obtenerPreguntasWikidata.js +++ b/questionservice/obtenerPreguntasWikidata.js @@ -173,7 +173,7 @@ class ObtenerPreguntaWikiData { //comprobamos si es una fecha if(this.esFormatoISO8601(binding[propertyName].value)){ //devolvemos la fecha formateada dependiendo del idioma - return this.formatearFecha(binding[propertyName].value, propertyName.substring(myString.length - 2) ); + return this.formatearFecha(binding[propertyName].value, propertyName.substring(propertyName.length - 2)); } //si no es una fecha devolvemos el valor else{ @@ -345,11 +345,11 @@ class ObtenerPreguntaWikiData { formateamos la fecha a un formato más legible */ formatearFecha(fechaISO8601, idioma) { - if(idioma === "es"){ - return this.formatearFechaEspañol(fechaISO8601); + if(idioma === "en"){ + return this.formatearFechaIngles(fechaISO8601); } else{ - return this.formatearFechaIngles(fechaISO8601); + return this.formatearFechaEspañol(fechaISO8601); } } diff --git a/questionservice/question-service.js b/questionservice/question-service.js index ec401974..0ba43b51 100644 --- a/questionservice/question-service.js +++ b/questionservice/question-service.js @@ -73,7 +73,8 @@ app.get('/getQuestionModoBasico', async(req,res)=> { app.get('/generateQuestion', async(req,res)=> { try{ - await newquestion.ejecutarOperaciones(); + await newquestion.ejecutarOperaciones(); + res.status(200).send("Pregunta generada y guardada correctamente."); } catch(error) { res.status(500).json({ error: error.message }); } From 8c2276466a422b2043fbc8f4be2c2cee56f9dddb Mon Sep 17 00:00:00 2001 From: bidof Date: Sat, 6 Apr 2024 22:20:25 +0200 Subject: [PATCH 59/79] juego funcionando y sincronizados falta el que te salga el ganador como antes --- webapp/src/components/game/BasicGame.js | 3 ++- webapp/src/components/game/Game.js | 2 +- webapp/src/components/game/gameModes/RoomGame.js | 11 +++++++---- webapp/src/components/rooms/Room.js | 4 +++- 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/webapp/src/components/game/BasicGame.js b/webapp/src/components/game/BasicGame.js index fa78b810..c122bf99 100644 --- a/webapp/src/components/game/BasicGame.js +++ b/webapp/src/components/game/BasicGame.js @@ -83,7 +83,8 @@ class BasicGame extends GameMode { if (this.questionIndex >=9) { console.log("fin juego"); this.finishGame(); - return null; + //devuelve las ultima pregunta + return this.questions[this.questions.length-1]; } else { // Incrementar this.questionIndex después de comprobar si has llegado a la última pregunta this.questionIndex++; diff --git a/webapp/src/components/game/Game.js b/webapp/src/components/game/Game.js index 35f07b4d..5961210a 100644 --- a/webapp/src/components/game/Game.js +++ b/webapp/src/components/game/Game.js @@ -69,7 +69,7 @@ function Game({darkMode,gameMode= new BasicGame()}) { console.log("entra en el if del correctAnswer"); const nextQuestion = gameModeRef.current.nextQuestion(); setCurrentQuestion(nextQuestion); - } else if(gameModeRef.current.questions.length != 0){ //no deberia entrar cuando se cargue el componente + } else if(gameModeRef.current.questions.length != 0 && isFinished){ //no deberia entrar cuando se cargue el componente console.log("use effect finish"); gameModeRef.current.finishGame(); setIsFinished(true); diff --git a/webapp/src/components/game/gameModes/RoomGame.js b/webapp/src/components/game/gameModes/RoomGame.js index c6fe42af..e9824c58 100644 --- a/webapp/src/components/game/gameModes/RoomGame.js +++ b/webapp/src/components/game/gameModes/RoomGame.js @@ -6,10 +6,11 @@ que son el fetchquestions y el endgame */ class RoomGame extends BasicGame { - constructor(room) { + constructor(room,navigate) { super(); this.room = room; this.winner=room.winner; + this.navigate=navigate; } async fetchQuestions() { @@ -21,16 +22,18 @@ class RoomGame extends BasicGame { } async endGame() { + //this.navigate('/home'); + console.log('RoomGame endGame'); this.isGameEnded = true; - this.questionIndex = 0; + //this.questionIndex = 0; //this.room.endGame({ correctas: this.correctAnswers, incorrectas: this.incorrectAnswers, tiempoTotal: this.totalTime }); // Muestra el cuadro de diálogo aquí // Puedes utilizar un paquete como sweetalert2 para mostrar el cuadro de diálogo Swal.fire({ - title: 'Juego terminado', - text: `El ganador es ${this.room.winner}`, + title: 'Esperando al resto de jugadores', + text: `Esperando a que el resto de jugaores terminen la partida `, confirmButtonText: 'Cerrar' }); } diff --git a/webapp/src/components/rooms/Room.js b/webapp/src/components/rooms/Room.js index d29f4674..0d042ee7 100644 --- a/webapp/src/components/rooms/Room.js +++ b/webapp/src/components/rooms/Room.js @@ -82,6 +82,8 @@ function Room({ darkMode }) { socket.emit('endGame', {id:roomId, results:results}); } +//indica donde vas al accabar el juego + const room={ endGame:endGame, @@ -99,7 +101,7 @@ const room={ ))} {isHost && } - {gameStarted && questions.length > 0 && } + {gameStarted && questions.length > 0 && }
); } From 1ae9c9433a89fa9168ba4d324c9a9c3daac4a2e6 Mon Sep 17 00:00:00 2001 From: lucia-sonia Date: Sun, 7 Apr 2024 00:21:42 +0200 Subject: [PATCH 60/79] =?UTF-8?q?ahora=20al=20jugar=20el=20juego=20salen?= =?UTF-8?q?=20preguntas=20en=20ingles=20o=20espa=C3=B1ol=20dependiendo=20d?= =?UTF-8?q?el=20idioma=20al=20iniciar=20el=20juego?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gatewayservice/gateway-service.js | 16 ++++-- questionservice/obtenerPreguntasBaseDatos.js | 50 +++++++++++++------ questionservice/question-service.js | 15 ++++-- webapp/src/components/game/GameContext.js | 6 ++- .../game/gameModes/basicGameMode.js | 8 ++- 5 files changed, 68 insertions(+), 27 deletions(-) diff --git a/gatewayservice/gateway-service.js b/gatewayservice/gateway-service.js index b08cdcb9..dbc6e9eb 100644 --- a/gatewayservice/gateway-service.js +++ b/gatewayservice/gateway-service.js @@ -54,8 +54,9 @@ app.post('/adduser', async (req, res) => { app.get('/getQuestion', async (req, res) => { try { + const idioma = req.query.idioma; // llamamos al servicio de preguntas - const questionResponse = await axios.get(questionServiceUrl+'/getQuestion', req.body); + const questionResponse = await axios.get(`${questionServiceUrl}/getQuestion?idioma=${idioma}`, req.body); res.json(questionResponse.data); } catch (error) { @@ -66,8 +67,9 @@ app.get('/getQuestion', async (req, res) => { app.get('/getQuestionDiaria', async (req, res) => { try { + const idioma = req.query.idioma; // llamamos al servicio de preguntas - const questionResponse = await axios.get(questionServiceUrl+'/getQuestionDiaria', req.body); + const questionResponse = await axios.get(`${questionServiceUrl}/getQuestionDiaria?idioma=${idioma}`, req.body); res.json(questionResponse.data); } catch (error) { @@ -78,9 +80,12 @@ app.get('/getQuestionDiaria', async (req, res) => { app.get('/getQuestionModoBasico', async (req, res) => { - try { - // llamamos al servicio de preguntas - const questionResponse = await axios.get(questionServiceUrl+'/getQuestionModoBasico', req.body); + try { + // Obtener el idioma en el que esta la app + const idioma = req.query.idioma; + console.log("Idioma: " + idioma); + // llamamos al servicio de preguntas + const questionResponse = await axios.get(`${questionServiceUrl}/getQuestionModoBasico?idioma=${idioma}`, req.body); res.json(questionResponse.data); } catch (error) { //Modifico el error @@ -131,6 +136,7 @@ app.get('/getHistoryTotal', async (req, res) => { res.status(500).json({ error: 'Error al realizar la solicitud al servicio de historial total' }); } }); + //***************************************************endpoints de las salas */ app.get('/joinroom/:id/:username',async(req,res)=> { try { diff --git a/questionservice/obtenerPreguntasBaseDatos.js b/questionservice/obtenerPreguntasBaseDatos.js index ffc1511b..bff40746 100644 --- a/questionservice/obtenerPreguntasBaseDatos.js +++ b/questionservice/obtenerPreguntasBaseDatos.js @@ -7,8 +7,9 @@ const Respuesta = mongoose.model('Respuesta'); class ObtenerPreguntas{ - async obtenerPregunta(numeroPreguntas){ + async obtenerPregunta(numeroPreguntas, idioma){ try{ + console.log("Numero de preguntas: " + numeroPreguntas); var resultado = {}; var objetoExterno= {}; //Se cojen las preguntas del numero que se pase por parametro @@ -16,7 +17,6 @@ class ObtenerPreguntas{ //comprobamos si hay preguntas if(preguntas.length != numeroPreguntas){ - console.log("Entra al error"); throw new Error("No se han devuelto el numero de preguntas necesario"); } @@ -24,23 +24,45 @@ class ObtenerPreguntas{ try{ var tipo = await Tipos.findOne({ idPreguntas: { $in: preguntas[i]._id } }); - var respuestas = await Respuesta.aggregate([ - { $match: { tipos: {$in : [tipo._id]}, textoRespuesta: { $ne: [preguntas[i].respuestaCorrecta, "Ninguna de las anteriores" ]} } }, - { $sample: { size: 3 } } - ]); + var respuestas; + + if(idioma == "es"){ + respuestas = await Respuesta.aggregate([ + { $match: { tipos: {$in : [tipo._id]}, textoRespuesta_es: { $ne: [preguntas[i].respuestaCorrecta_es, "Ninguna de las anteriores" ]} } }, + { $sample: { size: 3 } } + ]); + } + else{ + respuestas = await Respuesta.aggregate([ + { $match: { tipos: {$in : [tipo._id]}, textoRespuesta_en: { $ne: [preguntas[i].respuestaCorrecta_en, "Ninguna de las anteriores" ]} } }, + { $sample: { size: 3 } } + ]); + } //comprobamos si hay respuestas if(respuestas.length < 3){ throw new Error("No hay suficientes respuestas en la base de datos"); } - - resultado = { - pregunta: preguntas[i].textoPregunta, - correcta: preguntas[i].respuestaCorrecta, - respuestasIncorrecta1: respuestas[0].textoRespuesta, - respuestasIncorrecta2: respuestas[1].textoRespuesta, - respuestasIncorrecta3: respuestas[2].textoRespuesta - }; + + if(idioma == "es"){ + resultado = { + pregunta: preguntas[i].textoPregunta_es, + correcta: preguntas[i].respuestaCorrecta_es, + respuestasIncorrecta1: respuestas[0].textoRespuesta_es, + respuestasIncorrecta2: respuestas[1].textoRespuesta_es, + respuestasIncorrecta3: respuestas[2].textoRespuesta_es + }; + } + + else{ + resultado = { + pregunta: preguntas[i].textoPregunta_en, + correcta: preguntas[i].respuestaCorrecta_en, + respuestasIncorrecta1: respuestas[0].textoRespuesta_en, + respuestasIncorrecta2: respuestas[1].textoRespuesta_en, + respuestasIncorrecta3: respuestas[2].textoRespuesta_en + }; + } objetoExterno["resultado" + (i+1)] = resultado; } diff --git a/questionservice/question-service.js b/questionservice/question-service.js index 0ba43b51..a788113e 100644 --- a/questionservice/question-service.js +++ b/questionservice/question-service.js @@ -32,9 +32,10 @@ mongoose.connect(mongoUri); // Endpoints para la obtención de preguntas para los modos de juego app.get('/getQuestion', async(req,res)=> { - try{ + try{ + const idioma = req.query.idioma; //coger pregunta bd - const questions = await question.obtenerPregunta(1); + const questions = await question.obtenerPregunta(1, idioma); //para devolver la pregunta res.json(questions); @@ -44,7 +45,8 @@ app.get('/getQuestion', async(req,res)=> { }); app.get('/getQuestionDiaria', async(req,res)=> { - try{ + try{ + const idioma = req.query.idioma; //coger pregunta bd const questions = await question.obtenerPregunta(1); //para devolver la pregunta @@ -57,9 +59,12 @@ app.get('/getQuestionDiaria', async(req,res)=> { }); app.get('/getQuestionModoBasico', async(req,res)=> { - try{ + try{ + console.log("idioma del question service",req.query.idioma); + const idioma = req.query.idioma; + console.log("idioma del question service",idioma); //coger pregunta bd - const questions = await question.obtenerPregunta(10); + const questions = await question.obtenerPregunta(10, idioma); //para devolver la pregunta res.json(questions); diff --git a/webapp/src/components/game/GameContext.js b/webapp/src/components/game/GameContext.js index 795c7484..ca713698 100644 --- a/webapp/src/components/game/GameContext.js +++ b/webapp/src/components/game/GameContext.js @@ -1,5 +1,7 @@ import { createContext, useState, useEffect } from 'react'; +import { useTranslation } from 'react-i18next'; + export const GameContext = createContext(); /* es el encargado de crear el contexto par aque si te desloguae se peirdan los datos del juego, envolvera todos los componentes @@ -7,11 +9,13 @@ encapsula el modo de jeugo la funcion empezar a jugar y reinicir juego para que export const GameProvider = ({ children,gameMode }) => { const [questions, setQuestions] = useState([]); const [isLoading, setIsLoading] = useState(true); + const { i18n } = useTranslation(); + const startGame = async () => { resetGame(); // Aquí deberías reemplazar `fetchQuestions` con tu propia lógica para obtener las preguntas - const fetchedQuestions = await gameMode.fetchQuestions(); + const fetchedQuestions = await gameMode.fetchQuestions(i18n.language); setQuestions(fetchedQuestions); setIsLoading(false); }; diff --git a/webapp/src/components/game/gameModes/basicGameMode.js b/webapp/src/components/game/gameModes/basicGameMode.js index 67964390..49e98287 100644 --- a/webapp/src/components/game/gameModes/basicGameMode.js +++ b/webapp/src/components/game/gameModes/basicGameMode.js @@ -1,13 +1,17 @@ +import { useTranslation } from 'react-i18next'; + export class BasicGameMode { constructor() { this.apiEndpoint = process.env.REACT_APP_API_URI || 'http://localhost:8000'; this.questions=[]; } - async fetchQuestions() { + + + async fetchQuestions(idioma) { try { - const response = await fetch(`${this.apiEndpoint}/getQuestionModoBasico`); + const response = await fetch(`${this.apiEndpoint}/getQuestionModoBasico?idioma=${idioma}`); const data = await response.json(); this.questions = Object.values(data); From 852e6b3823e03798099b73a5223246f358f74e00 Mon Sep 17 00:00:00 2001 From: bidof Date: Sun, 7 Apr 2024 00:50:40 +0200 Subject: [PATCH 61/79] problemas con las room y las sincronias , no empiza el juego --- webapp/src/components/game/BasicGame.js | 4 ++ webapp/src/components/game/Game.js | 5 +- .../src/components/game/gameModes/RoomGame.js | 24 +++++++++ webapp/src/components/rooms/Room.js | 51 +++++++++++-------- 4 files changed, 61 insertions(+), 23 deletions(-) diff --git a/webapp/src/components/game/BasicGame.js b/webapp/src/components/game/BasicGame.js index c122bf99..d56ed41e 100644 --- a/webapp/src/components/game/BasicGame.js +++ b/webapp/src/components/game/BasicGame.js @@ -77,6 +77,7 @@ class BasicGame extends GameMode { } } nextQuestion() { + if(this.questions.length == 0){ console.log('questions en el next questions ',this.questions); this.isLoading = true; console.log('a ',this.questionIndex); @@ -94,6 +95,9 @@ class BasicGame extends GameMode { this.isLoading = false; return currentQuestion; // devolver la pregunta actual } + }else{ + console.log("no se tiene seguiente preungta , el array es vaicio"); + } } getCurrentQuestion() { // Comprobar si this.questions[this.questionIndex] es undefined diff --git a/webapp/src/components/game/Game.js b/webapp/src/components/game/Game.js index 5961210a..fc0e159c 100644 --- a/webapp/src/components/game/Game.js +++ b/webapp/src/components/game/Game.js @@ -5,7 +5,7 @@ import { Spinner, Box, AlertDialog, AlertDialogBody, AlertDialogFooter, AlertDia import BasicGame from './BasicGame'; const apiEndpoint = process.env.REACT_APP_API_URI ||'http://localhost:8000'; -function Game({darkMode,gameMode= new BasicGame()}) { +function Game({darkMode,gameMode=new BasicGame()}) { const [isOpen, setIsOpen] = useState(false); const [correctAnswers, setCorrectAnswers] = useState(0); const [incorrectAnswers, setIncorrectAnswers] = useState(0); @@ -23,6 +23,8 @@ function Game({darkMode,gameMode= new BasicGame()}) { const startGameAsync = async () => { setIsLoading(true); await gameMode.startGame(); + gameModeRef.current = gameMode; + console.log('preguntas', gameMode.questions); const currentQuestion = gameMode.getCurrentQuestion(); @@ -31,7 +33,6 @@ function Game({darkMode,gameMode= new BasicGame()}) { setCurrentQuestion(currentQuestion); setIsLoading(false); - gameModeRef.current = gameMode; }; startGameAsync(); diff --git a/webapp/src/components/game/gameModes/RoomGame.js b/webapp/src/components/game/gameModes/RoomGame.js index e9824c58..51ae6caa 100644 --- a/webapp/src/components/game/gameModes/RoomGame.js +++ b/webapp/src/components/game/gameModes/RoomGame.js @@ -7,12 +7,36 @@ que son el fetchquestions y el endgame */ class RoomGame extends BasicGame { constructor(room,navigate) { + console.log('RoomGame constructor', room); super(); this.room = room; + console.log('RoomGame del constructor', this.room); this.winner=room.winner; this.navigate=navigate; } + startGame() { + return new Promise(async (resolve, reject) => { + try { + this.isLoading = true; + this.questionIndex = 0; + console.log('RoomGame startGame', this.room); + + // Asegúrate de que getQuestions es una función que devuelve una promesa + this.questions = await this.room.getQuestions(); + + console.log('questions:', this.questions); // Añade una declaración de registro para depurar + + this.isLoading = false; + + // Resuelve la promesa cuando todo ha terminado + resolve(); + } catch (error) { + // Si algo sale mal, rechaza la promesa con el error + reject(error); + } + }); + } async fetchQuestions() { this.questions = this.room.getQuestions(); } diff --git a/webapp/src/components/rooms/Room.js b/webapp/src/components/rooms/Room.js index 0d042ee7..31487826 100644 --- a/webapp/src/components/rooms/Room.js +++ b/webapp/src/components/rooms/Room.js @@ -20,6 +20,9 @@ function Room({ darkMode }) { const [winner, setWinner] = useState(null); + const [roomGame, setRoomGame] = useState(null); + + //para el mensaje del ganador const [isOpen, setIsOpen] = useState(false); @@ -28,7 +31,7 @@ function Room({ darkMode }) { setIsOpen(false); nagivate('/home'); }; - + useEffect(() => { socket.on('currentUsers', (users) => { @@ -40,14 +43,20 @@ function Room({ darkMode }) { console.log("eres el host "+isHost); - socket.on('gameStarted', (questions) => { - //hace la peticion por la preguntas a la gateway y se las manda los jugadores - console.log('Juego iniciado, preguntas: ', questions); - const formated =Object.values(questions); - setQuestions(formated); + socket.on('gameStarted', (questionsServer) => { + console.log('Juego iniciado, preguntas recibidas : ', questionsServer); + + let room={ + getQuestions:questionsServer, + winner:function (){ + return winner; + }, + endGame:endGame, + } + setRoomGame(new RoomGame(room, nagivate)); + setGameStarted(true); }); - socket.on('gameEnded', ( winner ) => { console.log('Juego terminado, ganador: ', winner); setWinner(winner); @@ -58,38 +67,38 @@ function Room({ darkMode }) { }, [roomId]); + + //se encagr ad e que cuando las preguntas esten cargadas crees el modo de juego + //muestra el ganador useEffect(() => { if (winner) { setIsOpen(true); } }, [winner]); + function startGame (){ - - if(!gameStarted && isHost){ - //setGameStarted(true); + if(!gameStarted && isHost ){ + setGameStarted(true); socket.emit('startGame', { id: roomId }); console.log("se ha iniciado el juego"); } - - - } + + //funcion que le pasas a game para gestionar el finaldel juego function endGame(results) { console.log("emitir endGame socket.io"); socket.emit('endGame', {id:roomId, results:results}); } -//indica donde vas al accabar el juego - + //pasasrlelos datos al juego + + + + -const room={ - endGame:endGame, - getQuestions:()=>questions, - winner:winner -} return (
@@ -101,7 +110,7 @@ const room={ ))} {isHost && } - {gameStarted && questions.length > 0 && } + {gameStarted && roomGame!=null && }
); } From 4d00c55a4cfd18ea2a1544fd9797c8b86ed078a8 Mon Sep 17 00:00:00 2001 From: bidof Date: Sun, 7 Apr 2024 01:07:01 +0200 Subject: [PATCH 62/79] room service funcionando pero no finaliza nunca --- webapp/src/components/game/BasicGame.js | 9 +++++---- webapp/src/components/game/Game.js | 12 +++++------- webapp/src/components/game/gameModes/RoomGame.js | 6 +++++- 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/webapp/src/components/game/BasicGame.js b/webapp/src/components/game/BasicGame.js index d56ed41e..63f10e13 100644 --- a/webapp/src/components/game/BasicGame.js +++ b/webapp/src/components/game/BasicGame.js @@ -78,6 +78,10 @@ class BasicGame extends GameMode { } nextQuestion() { if(this.questions.length == 0){ + console.log("no se tiene seguiente preungta , el array es vaicio"); + return; // Salir del método si no hay preguntas + } + console.log('questions en el next questions ',this.questions); this.isLoading = true; console.log('a ',this.questionIndex); @@ -91,13 +95,10 @@ class BasicGame extends GameMode { this.questionIndex++; console.log('b ',this.questionIndex); const currentQuestion = this.getCurrentQuestion(); - console.log('c ',currentQuestion); + console.log('c ',currentQuestion); this.isLoading = false; return currentQuestion; // devolver la pregunta actual } - }else{ - console.log("no se tiene seguiente preungta , el array es vaicio"); - } } getCurrentQuestion() { // Comprobar si this.questions[this.questionIndex] es undefined diff --git a/webapp/src/components/game/Game.js b/webapp/src/components/game/Game.js index fc0e159c..a1ca3416 100644 --- a/webapp/src/components/game/Game.js +++ b/webapp/src/components/game/Game.js @@ -22,17 +22,15 @@ function Game({darkMode,gameMode=new BasicGame()}) { useEffect(() => { const startGameAsync = async () => { setIsLoading(true); - await gameMode.startGame(); - gameModeRef.current = gameMode; - - console.log('preguntas', gameMode.questions); + await gameModeRef.current.startGame(); + console.log('preguntas', gameModeRef.current.questions); - const currentQuestion = gameMode.getCurrentQuestion(); + console.log('gameMode',gameModeRef.current); + const currentQuestion = gameModeRef.current.getCurrentQuestion(); console.log('primera pregunta ', currentQuestion); setCurrentQuestion(currentQuestion); setIsLoading(false); - }; startGameAsync(); @@ -44,7 +42,7 @@ function Game({darkMode,gameMode=new BasicGame()}) { await gameModeRef.current.startGame(); console.log('preguntas', gameModeRef.current.questions); - const currentQuestion = gameModeRef.current.getCurrentQuestion(); + let currentQuestion = gameModeRef.current.getCurrentQuestion(); console.log('primera pregunta ', currentQuestion); setCurrentQuestion(currentQuestion); diff --git a/webapp/src/components/game/gameModes/RoomGame.js b/webapp/src/components/game/gameModes/RoomGame.js index 51ae6caa..38d5db83 100644 --- a/webapp/src/components/game/gameModes/RoomGame.js +++ b/webapp/src/components/game/gameModes/RoomGame.js @@ -13,6 +13,7 @@ class RoomGame extends BasicGame { console.log('RoomGame del constructor', this.room); this.winner=room.winner; this.navigate=navigate; + this.questions=[]; } startGame() { @@ -23,8 +24,9 @@ class RoomGame extends BasicGame { console.log('RoomGame startGame', this.room); // Asegúrate de que getQuestions es una función que devuelve una promesa - this.questions = await this.room.getQuestions(); + let data = await this.room.getQuestions; + this.questions=Object.values(data); console.log('questions:', this.questions); // Añade una declaración de registro para depurar this.isLoading = false; @@ -66,5 +68,7 @@ class RoomGame extends BasicGame { this.isGameEnded = true; this.endGame(); } + + } export default RoomGame; \ No newline at end of file From 630c695df9a6cc0adb6c65a899b12e6e97145bee Mon Sep 17 00:00:00 2001 From: bidof Date: Sun, 7 Apr 2024 01:11:47 +0200 Subject: [PATCH 63/79] el juego finaliza pero bug de que no redirecciona --- webapp/src/components/game/Game.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/webapp/src/components/game/Game.js b/webapp/src/components/game/Game.js index a1ca3416..279a17ad 100644 --- a/webapp/src/components/game/Game.js +++ b/webapp/src/components/game/Game.js @@ -61,14 +61,13 @@ function Game({darkMode,gameMode=new BasicGame()}) { }; useEffect(() => { - //console.log('corectAnswer useEffect el valor de las preguntas es ',gameModeRef.questions); - console.log("entra en el useEffect de correctAnswer",correctAnswers,incorrectAnswers,gameModeRef.current.questions.length); + console.log("entra en el useEffect de correctAnswer",correctAnswers,incorrectAnswers,gameModeRef.current.questions.length,isFinished); - if ( !isFinished && correctAnswers + incorrectAnswers < gameModeRef.current.questions.length ) { //para que no entre en el finished nada mas cargar el juegu + if (correctAnswers + incorrectAnswers < gameModeRef.current.questions.length ) { console.log("entra en el if del correctAnswer"); const nextQuestion = gameModeRef.current.nextQuestion(); setCurrentQuestion(nextQuestion); - } else if(gameModeRef.current.questions.length != 0 && isFinished){ //no deberia entrar cuando se cargue el componente + } else if(correctAnswers + incorrectAnswers === gameModeRef.current.questions.length){ console.log("use effect finish"); gameModeRef.current.finishGame(); setIsFinished(true); From 7470203873d49c3ef05437630cd4d19e634d7574 Mon Sep 17 00:00:00 2001 From: lucia-sonia Date: Sun, 7 Apr 2024 01:25:51 +0200 Subject: [PATCH 64/79] =?UTF-8?q?se=20obtiene=20una=20pregunta=20diaria=20?= =?UTF-8?q?cada=2024=20horas=20y=20a=C3=B1adido=20el=20obtener=20la=20preg?= =?UTF-8?q?unta=20diaria=20de=20la=20bd=20para=20la=20fecha=20de=20ese=20m?= =?UTF-8?q?ismo=20dia?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gatewayservice/gateway-service.js | 1 - questionservice/obtenerPreguntasBaseDatos.js | 96 ++++++++++++++++++++ questionservice/question-service.js | 4 +- questionservice/scheduler.js | 32 +++++++ 4 files changed, 129 insertions(+), 4 deletions(-) diff --git a/gatewayservice/gateway-service.js b/gatewayservice/gateway-service.js index dbc6e9eb..4236112d 100644 --- a/gatewayservice/gateway-service.js +++ b/gatewayservice/gateway-service.js @@ -83,7 +83,6 @@ app.get('/getQuestionModoBasico', async (req, res) => { try { // Obtener el idioma en el que esta la app const idioma = req.query.idioma; - console.log("Idioma: " + idioma); // llamamos al servicio de preguntas const questionResponse = await axios.get(`${questionServiceUrl}/getQuestionModoBasico?idioma=${idioma}`, req.body); res.json(questionResponse.data); diff --git a/questionservice/obtenerPreguntasBaseDatos.js b/questionservice/obtenerPreguntasBaseDatos.js index bff40746..e5af5ed3 100644 --- a/questionservice/obtenerPreguntasBaseDatos.js +++ b/questionservice/obtenerPreguntasBaseDatos.js @@ -78,6 +78,102 @@ class ObtenerPreguntas{ throw new Error("Error al obtener las preguntas de la base de datos"); } } + + async obtenerPreguntaDiaria(idioma){ + try{ + console.log("entra en obtener pregunta diaria"); + const fecha = new Date(); // Obtenemos la fecha actual + // como nos da tambien la hora y no queremos eso, la eliminamos + const año = fecha.getFullYear(); + const mes = fecha.getMonth() + 1; + const dia = fecha.getDate(); + // Formateamos la fecha como lo tenemos en la bd + const fechaSinHora = `${año}-${mes < 10 ? '0' : ''}${mes}-${dia < 10 ? '0' : ''}${dia}`; + + console.log("Fecha sin hora: " + fechaSinHora); + console.log(typeof(fechaSinHora)); + + var pregunta = await Pregunta.findOne({ diaria: fechaSinHora }); + var resultado; + + if(pregunta != null){ + try{ + var tipo = await Tipos.findOne({ idPreguntas: { $in: pregunta._id } }); + + var respuestas; + + if(idioma == "es"){ + respuestas = await Respuesta.aggregate([ + { $match: { tipos: {$in : [tipo._id]}, textoRespuesta_es: { $ne: [pregunta.respuestaCorrecta_es, "Ninguna de las anteriores" ]} } }, + { $sample: { size: 3 } } + ]); + } + else{ + respuestas = await Respuesta.aggregate([ + { $match: { tipos: {$in : [tipo._id]}, textoRespuesta_en: { $ne: [pregunta.respuestaCorrecta_en, "Ninguna de las anteriores" ]} } }, + { $sample: { size: 3 } } + ]); + } + + //comprobamos si hay respuestas + if(respuestas.length < 3){ + throw new Error("No hay suficientes respuestas en la base de datos"); + } + + if(idioma == "es"){ + resultado = { + pregunta: pregunta.textoPregunta_es, + correcta: pregunta.respuestaCorrecta_es, + respuestasIncorrecta1: respuestas[0].textoRespuesta_es, + respuestasIncorrecta2: respuestas[1].textoRespuesta_es, + respuestasIncorrecta3: respuestas[2].textoRespuesta_es + }; + } + + else{ + resultado = { + pregunta: pregunta.textoPregunta_en, + correcta: pregunta.respuestaCorrecta_en, + respuestasIncorrecta1: respuestas[0].textoRespuesta_en, + respuestasIncorrecta2: respuestas[1].textoRespuesta_en, + respuestasIncorrecta3: respuestas[2].textoRespuesta_en + }; + } + } + catch(error){ + throw new Error("Error al obtener el tipo o las respuestas de la base de datos"); + } + } + else{ + throw new Error("No se ha encontrado ninguna pregunta diaria en la base de datos"); + } + + return resultado; + + } catch (error) { + throw new Error("Error al obtener la pregunta diaria en la base de datos: " + error.message); + } + } + + async generarPreguntaDiaria(fecha){ + try{ + console.log("Fecha: " + fecha); + + var pregunta = await Pregunta.findOneAndUpdate( + { diaria: null }, // Filtro para encontrar una pregunta con 'diaria' igual a null + { $set: { diaria: fecha } }, // Establecer el valor de 'diaria' a la fecha proporcionada + { new: true, upsert: true, strict: false } // Para devolver el documento actualizado y permitir campos no definidos en el esquema + ); + if (pregunta) { + console.log("Pregunta sin atributo 'diaria' encontrada y actualizada:", pregunta); + console.log("Pregunta con atributo 'diaria' guardada en la base de datos."); + } else { + console.log("No se encontraron preguntas sin el atributo 'diaria'."); + } + } catch (error) { + throw new Error("Error al obtener y actualizar la pregunta diaria en la base de datos: " + error.message); + } + } } module.exports = ObtenerPreguntas; \ No newline at end of file diff --git a/questionservice/question-service.js b/questionservice/question-service.js index a788113e..5fe324d2 100644 --- a/questionservice/question-service.js +++ b/questionservice/question-service.js @@ -48,7 +48,7 @@ app.get('/getQuestionDiaria', async(req,res)=> { try{ const idioma = req.query.idioma; //coger pregunta bd - const questions = await question.obtenerPregunta(1); + const questions = await question.obtenerPreguntaDiaria(idioma); //para devolver la pregunta res.json(questions); @@ -60,9 +60,7 @@ app.get('/getQuestionDiaria', async(req,res)=> { app.get('/getQuestionModoBasico', async(req,res)=> { try{ - console.log("idioma del question service",req.query.idioma); const idioma = req.query.idioma; - console.log("idioma del question service",idioma); //coger pregunta bd const questions = await question.obtenerPregunta(10, idioma); //para devolver la pregunta diff --git a/questionservice/scheduler.js b/questionservice/scheduler.js index b9c6f45a..9d386983 100644 --- a/questionservice/scheduler.js +++ b/questionservice/scheduler.js @@ -4,6 +4,9 @@ const cron = require('node-cron'); const QuestionGenerator = require('./questionGeneration'); const questionGenerator = new QuestionGenerator(); +const ObtenerPreguntaDiaria = require('./obtenerPreguntasBaseDatos'); +const obtenerPreguntaDiaria = new ObtenerPreguntaDiaria(); + class Scheduler { @@ -19,6 +22,23 @@ class Scheduler { } } + async generarPreguntaDiaria() { + try { + const fecha = new Date(); // Obtenemos la fecha actual + // como nos da tambien la hora y no queremos eso, la eliminamos + const año = fecha.getFullYear(); + const mes = fecha.getMonth() + 1; + const dia = fecha.getDate(); + // Formateamos la fecha para que sea compatible con la base de datos + const fechaSinHora = `${año}-${mes < 10 ? '0' : ''}${mes}-${dia < 10 ? '0' : ''}${dia}`; + + await obtenerPreguntaDiaria.generarPreguntaDiaria(fechaSinHora); //Generamos la pregunta diaria + } catch (error) { + console.error(error); + } + } + + start() { cron.schedule('*/30 * * * *', async () => { try { @@ -28,6 +48,18 @@ class Scheduler { console.error('Fallo al generar la pregunta:', error); } }); + + //para generar la pregunta diaria + + cron.schedule('0 0 */1 * *', async () => { + try { + await this.generarPreguntaDiaria(); + } + catch (error) { + console.error('Fallo al generar la pregunta diaria:', error); + } + }); + } } From 8108aaccb7b542d05da38ffd97b30168dbc1c7ba Mon Sep 17 00:00:00 2001 From: lucia-sonia Date: Sun, 7 Apr 2024 01:27:57 +0200 Subject: [PATCH 65/79] =?UTF-8?q?a=C3=B1adida=20la=20fecha=20a=20la=20url?= =?UTF-8?q?=20para=20si=20en=20el=20futuro=20se=20quieren=20obtener=20las?= =?UTF-8?q?=20preguntas=20diarias=20de=20otras=20fechas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gatewayservice/gateway-service.js | 4 +++- questionservice/obtenerPreguntasBaseDatos.js | 16 ++-------------- questionservice/question-service.js | 3 ++- 3 files changed, 7 insertions(+), 16 deletions(-) diff --git a/gatewayservice/gateway-service.js b/gatewayservice/gateway-service.js index 4236112d..266b75aa 100644 --- a/gatewayservice/gateway-service.js +++ b/gatewayservice/gateway-service.js @@ -68,8 +68,10 @@ app.get('/getQuestion', async (req, res) => { app.get('/getQuestionDiaria', async (req, res) => { try { const idioma = req.query.idioma; + const fecha = req.query.fecha; + // llamamos al servicio de preguntas - const questionResponse = await axios.get(`${questionServiceUrl}/getQuestionDiaria?idioma=${idioma}`, req.body); + const questionResponse = await axios.get(`${questionServiceUrl}/getQuestionDiaria?idioma=${idioma}?fecha=${fecha}`, req.body); res.json(questionResponse.data); } catch (error) { diff --git a/questionservice/obtenerPreguntasBaseDatos.js b/questionservice/obtenerPreguntasBaseDatos.js index e5af5ed3..4d8133cc 100644 --- a/questionservice/obtenerPreguntasBaseDatos.js +++ b/questionservice/obtenerPreguntasBaseDatos.js @@ -79,21 +79,9 @@ class ObtenerPreguntas{ } } - async obtenerPreguntaDiaria(idioma){ + async obtenerPreguntaDiaria(idioma, fecha){ try{ - console.log("entra en obtener pregunta diaria"); - const fecha = new Date(); // Obtenemos la fecha actual - // como nos da tambien la hora y no queremos eso, la eliminamos - const año = fecha.getFullYear(); - const mes = fecha.getMonth() + 1; - const dia = fecha.getDate(); - // Formateamos la fecha como lo tenemos en la bd - const fechaSinHora = `${año}-${mes < 10 ? '0' : ''}${mes}-${dia < 10 ? '0' : ''}${dia}`; - - console.log("Fecha sin hora: " + fechaSinHora); - console.log(typeof(fechaSinHora)); - - var pregunta = await Pregunta.findOne({ diaria: fechaSinHora }); + var pregunta = await Pregunta.findOne({ diaria: fecha }); var resultado; if(pregunta != null){ diff --git a/questionservice/question-service.js b/questionservice/question-service.js index 5fe324d2..0db120d9 100644 --- a/questionservice/question-service.js +++ b/questionservice/question-service.js @@ -47,8 +47,9 @@ app.get('/getQuestion', async(req,res)=> { app.get('/getQuestionDiaria', async(req,res)=> { try{ const idioma = req.query.idioma; + const fecha = req.query.fecha; //coger pregunta bd - const questions = await question.obtenerPreguntaDiaria(idioma); + const questions = await question.obtenerPreguntaDiaria(idioma, fecha); //para devolver la pregunta res.json(questions); From 2c9fe2c738b81aa04c1a65d46d07006c1f8ffe2e Mon Sep 17 00:00:00 2001 From: bidof Date: Sun, 7 Apr 2024 01:50:51 +0200 Subject: [PATCH 66/79] cambiado el final del juego --- roomservice/RoomQuestions.js | 8 ++++++- webapp/src/components/game/BasicGame.js | 19 ++++++++++++----- webapp/src/components/game/Game.js | 8 ++++++- .../src/components/game/gameModes/RoomGame.js | 21 ++++++++++++------- webapp/src/components/rooms/Room.js | 5 +++-- 5 files changed, 45 insertions(+), 16 deletions(-) diff --git a/roomservice/RoomQuestions.js b/roomservice/RoomQuestions.js index 89db5fa6..2e347769 100644 --- a/roomservice/RoomQuestions.js +++ b/roomservice/RoomQuestions.js @@ -104,7 +104,13 @@ class RoomQuestions{ // Si todos los usuarios han terminado el juego, determinar quién ha ganado let winner = this.determineWinner(roomResults); console.log("el ganador determinado es "+winner); - socket.to(id).emit('gameEnded', winner); // enviar a todos los de sala quien gano + //hacer uhnjson con el ganador y obtener su tiempo y correctas + let data={ + winner:winner, + correctas:roomResults.get(winner).correctas, + tiempoTotal:roomResults.get(winner).tiempoTotal + } + socket.to(id).emit('gameEnded', data); // enviar a todos los de sala quien gano socket.emit('gameEnded', winner); // } diff --git a/webapp/src/components/game/BasicGame.js b/webapp/src/components/game/BasicGame.js index 63f10e13..c4a2b0c7 100644 --- a/webapp/src/components/game/BasicGame.js +++ b/webapp/src/components/game/BasicGame.js @@ -11,6 +11,10 @@ class BasicGame extends GameMode { super(); // Vincular nextQuestion al contexto correcto this.nextQuestion = this.nextQuestion.bind(this); + + this.correctas=0; + this.incorrectas=0; + this.tiempoTotal=null; } async fetchQuestions() { @@ -81,10 +85,7 @@ class BasicGame extends GameMode { console.log("no se tiene seguiente preungta , el array es vaicio"); return; // Salir del método si no hay preguntas } - - console.log('questions en el next questions ',this.questions); this.isLoading = true; - console.log('a ',this.questionIndex); if (this.questionIndex >=9) { console.log("fin juego"); this.finishGame(); @@ -93,9 +94,7 @@ class BasicGame extends GameMode { } else { // Incrementar this.questionIndex después de comprobar si has llegado a la última pregunta this.questionIndex++; - console.log('b ',this.questionIndex); const currentQuestion = this.getCurrentQuestion(); - console.log('c ',currentQuestion); this.isLoading = false; return currentQuestion; // devolver la pregunta actual } @@ -125,6 +124,16 @@ class BasicGame extends GameMode { this.isGameEnded = true; this.endGame(); } + + incrementCorrectas(){ + this.correctas++; + } + incrementIncorrectas(){ + this.incorrectas++; + } + setTiempoTotal(time){ + this.tiempoTotal=time; + } } export default BasicGame; \ No newline at end of file diff --git a/webapp/src/components/game/Game.js b/webapp/src/components/game/Game.js index 279a17ad..e9971aa6 100644 --- a/webapp/src/components/game/Game.js +++ b/webapp/src/components/game/Game.js @@ -55,8 +55,10 @@ function Game({darkMode,gameMode=new BasicGame()}) { const handleAnswerSelect = (isCorrect) => { if (isCorrect) { setCorrectAnswers(correctAnswers + 1); + gameModeRef.current.incrementCorrectas(); } else { setIncorrectAnswers(incorrectAnswers + 1); + gameModeRef.current.incrementIncorrectas(); } }; @@ -69,6 +71,8 @@ function Game({darkMode,gameMode=new BasicGame()}) { setCurrentQuestion(nextQuestion); } else if(correctAnswers + incorrectAnswers === gameModeRef.current.questions.length){ console.log("use effect finish"); + //poner el tiepo que tardo + gameModeRef.current.setTiempoTotal(totalTime); gameModeRef.current.finishGame(); setIsFinished(true); } @@ -83,7 +87,8 @@ function Game({darkMode,gameMode=new BasicGame()}) { incorrectas: incorrectAnswers, tiempoTotal: totalTime }; - + + //lo hace el endGame gameMode.sendHistory(data) .then(() => { console.log('Historial enviado correctamente'); @@ -91,6 +96,7 @@ function Game({darkMode,gameMode=new BasicGame()}) { .catch(error => { console.error('Error al enviar el historial al servidor:', error); }); + setIsOpen(true); } }, [totalTime,gameModeRef.current.isGameEnded]); diff --git a/webapp/src/components/game/gameModes/RoomGame.js b/webapp/src/components/game/gameModes/RoomGame.js index 38d5db83..2322c91b 100644 --- a/webapp/src/components/game/gameModes/RoomGame.js +++ b/webapp/src/components/game/gameModes/RoomGame.js @@ -42,19 +42,26 @@ class RoomGame extends BasicGame { async fetchQuestions() { this.questions = this.room.getQuestions(); } - async sendHistory(){ - console.log("no se envia el historial"); - return; - } + + async sendHistory(historyData) { + console.log("no se envia historial de este modo"); + return ; + } async endGame() { //this.navigate('/home'); console.log('RoomGame endGame'); this.isGameEnded = true; - //this.questionIndex = 0; - //this.room.endGame({ correctas: this.correctAnswers, incorrectas: this.incorrectAnswers, tiempoTotal: this.totalTime }); - + //emitir el evento y la logica relevante de socket.io le pasas los resultados + + let data={ + user:localStorage.getItem('username'), + correctas:this.correctas, + incorrectas:this.incorrectas, + tiempoTotal:this.tiempoTotal + } + this.room.endGame(data); // Muestra el cuadro de diálogo aquí // Puedes utilizar un paquete como sweetalert2 para mostrar el cuadro de diálogo Swal.fire({ diff --git a/webapp/src/components/rooms/Room.js b/webapp/src/components/rooms/Room.js index 31487826..8ea12af5 100644 --- a/webapp/src/components/rooms/Room.js +++ b/webapp/src/components/rooms/Room.js @@ -57,8 +57,8 @@ function Room({ darkMode }) { setGameStarted(true); }); - socket.on('gameEnded', ( winner ) => { - console.log('Juego terminado, ganador: ', winner); + socket.on('gameEnded', ( data ) => { + console.log('Juego terminado, ganador: correctas y tiempo', data.winner,data.correctas,data.tiempoTotal); setWinner(winner); }); //limpiar el evento @@ -89,6 +89,7 @@ function Room({ darkMode }) { //funcion que le pasas a game para gestionar el finaldel juego function endGame(results) { + console.log("emitir endGame socket.io"); socket.emit('endGame', {id:roomId, results:results}); From 4baa2a9e964dd46eeee6238e1078cd94e9f152d1 Mon Sep 17 00:00:00 2001 From: bidof Date: Sun, 7 Apr 2024 02:44:40 +0200 Subject: [PATCH 67/79] =?UTF-8?q?mejorado=20el=20dis=C3=B1eo=20y=20funcion?= =?UTF-8?q?ando=20todo=20junto?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- webapp/src/components/game/BasicGame.js | 12 +++ webapp/src/components/game/Game.js | 76 ++++--------------- .../src/components/game/gameModes/RoomGame.js | 2 - webapp/src/components/rooms/Room.js | 11 ++- 4 files changed, 36 insertions(+), 65 deletions(-) diff --git a/webapp/src/components/game/BasicGame.js b/webapp/src/components/game/BasicGame.js index c4a2b0c7..eef3bb2d 100644 --- a/webapp/src/components/game/BasicGame.js +++ b/webapp/src/components/game/BasicGame.js @@ -5,6 +5,7 @@ pero en ciertos casos como en el online si no quieres guardar el historial o qui heredas y sobreescribes y list */ import GameMode from './gameModes/GameMode'; +import Swal from 'sweetalert2'; class BasicGame extends GameMode { constructor() { @@ -57,6 +58,16 @@ class BasicGame extends GameMode { incorrectas: historyData.incorrectas, tiempoTotal: historyData.tiempoTotal }; + + Swal.fire({ + title: 'Juego terminado, tus resultados son los siguientes:', + html: ` +

Correctas: ${this.correctas}

+

Incorrectas: ${this.incorrectas}

+

Tiempo total: ${this.tiempoTotal}

+ `, + confirmButtonText: 'Cerrar' + }); console.log("Se envian los siguientes datos al historial", data); fetch(`${this.apiEndpoint}/updateHistory`, { @@ -74,6 +85,7 @@ class BasicGame extends GameMode { }) .then(data => { console.log('Respuesta del servidor:', data); + }) .catch(error => { console.error('Error al enviar el historial al servidor:', error); diff --git a/webapp/src/components/game/Game.js b/webapp/src/components/game/Game.js index e9971aa6..c71affcc 100644 --- a/webapp/src/components/game/Game.js +++ b/webapp/src/components/game/Game.js @@ -3,6 +3,7 @@ import { useEffect, useState, useRef } from 'react'; import {useNavigate} from 'react-router-dom'; import { Spinner, Box, AlertDialog, AlertDialogBody, AlertDialogFooter, AlertDialogHeader, AlertDialogContent, AlertDialogOverlay, Button,Center } from "@chakra-ui/react"; import BasicGame from './BasicGame'; + const apiEndpoint = process.env.REACT_APP_API_URI ||'http://localhost:8000'; function Game({darkMode,gameMode=new BasicGame()}) { @@ -36,21 +37,7 @@ function Game({darkMode,gameMode=new BasicGame()}) { startGameAsync(); }, []); - useEffect(() => { - const startGameAsync = async () => { - setIsLoading(true); - await gameModeRef.current.startGame(); - console.log('preguntas', gameModeRef.current.questions); - - let currentQuestion = gameModeRef.current.getCurrentQuestion(); - console.log('primera pregunta ', currentQuestion); - - setCurrentQuestion(currentQuestion); - setIsLoading(false); - }; - - startGameAsync(); - }, []); + const handleAnswerSelect = (isCorrect) => { if (isCorrect) { @@ -60,6 +47,11 @@ function Game({darkMode,gameMode=new BasicGame()}) { setIncorrectAnswers(incorrectAnswers + 1); gameModeRef.current.incrementIncorrectas(); } + + console.log('comprobar si se pone a true el finished:preguntas tamaño,correctas e incorrecas ',gameModeRef.current.questions.length) + + if(correctAnswers+incorrectAnswers==gameModeRef.current.questions.length-1) + setIsFinished(true); }; useEffect(() => { @@ -69,37 +61,19 @@ function Game({darkMode,gameMode=new BasicGame()}) { console.log("entra en el if del correctAnswer"); const nextQuestion = gameModeRef.current.nextQuestion(); setCurrentQuestion(nextQuestion); - } else if(correctAnswers + incorrectAnswers === gameModeRef.current.questions.length){ + } else if (gameModeRef.current.questions.length>0){//comprobar que no sea vacia para que le ljuego no finalize al empezar console.log("use effect finish"); + setIsFinished(true); //poner el tiepo que tardo gameModeRef.current.setTiempoTotal(totalTime); gameModeRef.current.finishGame(); - setIsFinished(true); - } + gameModeRef.current.sendHistory({correctas: correctAnswers, incorrectas: incorrectAnswers, tiempoTotal: totalTime}); + } + + }, [correctAnswers, incorrectAnswers]); - useEffect(() => { - console.log("entra en el useEffect del finished"); - console.log('valores de IsGameEnded y totalTime',gameModeRef.current.isGameEnded,totalTime) - if (gameModeRef.current.isGameEnded &&localStorage.getItem('username') != null && totalTime != 0) { - const data = { - correctas: correctAnswers, - incorrectas: incorrectAnswers, - tiempoTotal: totalTime - }; - - //lo hace el endGame - gameMode.sendHistory(data) - .then(() => { - console.log('Historial enviado correctamente'); - }) - .catch(error => { - console.error('Error al enviar el historial al servidor:', error); - }); - - setIsOpen(true); - } - }, [totalTime,gameModeRef.current.isGameEnded]); + const onClose=()=>{ setIsOpen(false); @@ -142,27 +116,7 @@ function Game({darkMode,gameMode=new BasicGame()}) { /> )} - - - - - Resultados: - - -
- respuestas correctas: {correctAnswers}
- respuestas incorrectas: {incorrectAnswers} -
-
- - - - -
-
-
+ ); } diff --git a/webapp/src/components/game/gameModes/RoomGame.js b/webapp/src/components/game/gameModes/RoomGame.js index 2322c91b..761ad9ed 100644 --- a/webapp/src/components/game/gameModes/RoomGame.js +++ b/webapp/src/components/game/gameModes/RoomGame.js @@ -49,8 +49,6 @@ class RoomGame extends BasicGame { return ; } async endGame() { - //this.navigate('/home'); - console.log('RoomGame endGame'); this.isGameEnded = true; //emitir el evento y la logica relevante de socket.io le pasas los resultados diff --git a/webapp/src/components/rooms/Room.js b/webapp/src/components/rooms/Room.js index 8ea12af5..1b13de36 100644 --- a/webapp/src/components/rooms/Room.js +++ b/webapp/src/components/rooms/Room.js @@ -3,7 +3,7 @@ import { useParams } from 'react-router-dom'; import { useLocation } from 'react-router-dom'; import { AlertDialog, AlertDialogBody, AlertDialogFooter, AlertDialogHeader, AlertDialogContent, AlertDialogOverlay, Button } from "@chakra-ui/react"; import { useNavigate } from 'react-router-dom'; - +import Swal from 'sweetalert2'; import socket from './socket'; import Game from '../game/Game'; import RoomGame from '../game/gameModes/RoomGame'; @@ -59,7 +59,14 @@ function Room({ darkMode }) { }); socket.on('gameEnded', ( data ) => { console.log('Juego terminado, ganador: correctas y tiempo', data.winner,data.correctas,data.tiempoTotal); - setWinner(winner); + setWinner(data.winner); + + //imrpmir popup del ganador + Swal.fire({ + title: 'El ganador es ', + text: data.winner, + confirmButtonText: 'Cerrar' + }); }); //limpiar el evento return () => socket.off('gameEnded'); From d8ddd661cda0caa41f745309f4a554038ad37f93 Mon Sep 17 00:00:00 2001 From: lucia-sonia Date: Mon, 8 Apr 2024 16:33:44 +0200 Subject: [PATCH 68/79] =?UTF-8?q?A=C3=B1adido=20el=20codigo=20para=20que?= =?UTF-8?q?=20pille=20las=20preguntas=20internacionalizadas=20en=20el=20ga?= =?UTF-8?q?me?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gatewayservice/gateway-service.js | 27 ++++++++++--------- questionservice/obtenerPreguntasBaseDatos.js | 2 +- questionservice/question-service.js | 2 ++ roomservice/RoomQuestions.js | 6 +++-- roomservice/room-service.js | 4 +-- webapp/src/components/game/BasicGame.js | 12 ++++++++- webapp/src/components/game/Game.js | 5 ++++ webapp/src/components/rooms/Room.js | 4 +-- .../src/components/startbutton/StartButton.js | 1 - 9 files changed, 42 insertions(+), 21 deletions(-) diff --git a/gatewayservice/gateway-service.js b/gatewayservice/gateway-service.js index 266b75aa..83024e62 100644 --- a/gatewayservice/gateway-service.js +++ b/gatewayservice/gateway-service.js @@ -85,8 +85,11 @@ app.get('/getQuestionModoBasico', async (req, res) => { try { // Obtener el idioma en el que esta la app const idioma = req.query.idioma; + console.log("entro en getQuestionModoBasico " + idioma); // llamamos al servicio de preguntas const questionResponse = await axios.get(`${questionServiceUrl}/getQuestionModoBasico?idioma=${idioma}`, req.body); + + console.log("getQuestionModoBasico response: ", questionResponse.data); res.json(questionResponse.data); } catch (error) { //Modifico el error @@ -138,6 +141,18 @@ app.get('/getHistoryTotal', async (req, res) => { } }); +app.post('/updateHistory', async (req, res) => { + try { + // llamamos al servicio de preguntas + const historyResponse = await axios.post(historyServiceUrl+'/updateHistory', req.body); + + res.json(historyResponse.data); + } catch (error) { + //Modifico el error + res.status(500).json({ error: 'Error al realizar la solicitud al servicio de historial' }); + } +}); + //***************************************************endpoints de las salas */ app.get('/joinroom/:id/:username',async(req,res)=> { try { @@ -176,18 +191,6 @@ app.get('/startgame/:id/:username',async(req,res)=> { } }); -app.post('/updateHistory', async (req, res) => { - try { - // llamamos al servicio de preguntas - const historyResponse = await axios.post(historyServiceUrl+'/updateHistory', req.body); - - res.json(historyResponse.data); - } catch (error) { - //Modifico el error - res.status(500).json({ error: 'Error al realizar la solicitud al servicio de historial' }); - } -}); - // Start the gateway service const server = app.listen(port, () => { console.log(`Gateway Service listening at http://localhost:${port}`); diff --git a/questionservice/obtenerPreguntasBaseDatos.js b/questionservice/obtenerPreguntasBaseDatos.js index 4d8133cc..f0889a33 100644 --- a/questionservice/obtenerPreguntasBaseDatos.js +++ b/questionservice/obtenerPreguntasBaseDatos.js @@ -72,7 +72,7 @@ class ObtenerPreguntas{ } console.log("Preguntas finales: " + objetoExterno); - return objetoExterno; + return objetoExterno; } catch(error){ throw new Error("Error al obtener las preguntas de la base de datos"); diff --git a/questionservice/question-service.js b/questionservice/question-service.js index 0db120d9..7f66d1ed 100644 --- a/questionservice/question-service.js +++ b/questionservice/question-service.js @@ -62,6 +62,8 @@ app.get('/getQuestionDiaria', async(req,res)=> { app.get('/getQuestionModoBasico', async(req,res)=> { try{ const idioma = req.query.idioma; + + console.log("idioma",idioma); //coger pregunta bd const questions = await question.obtenerPregunta(10, idioma); //para devolver la pregunta diff --git a/roomservice/RoomQuestions.js b/roomservice/RoomQuestions.js index 2e347769..78cda534 100644 --- a/roomservice/RoomQuestions.js +++ b/roomservice/RoomQuestions.js @@ -64,13 +64,15 @@ class RoomQuestions{ /** * funcion empieza el juego para todos los usuersz */ - async startGame(id,socket) { + async startGame(id, idioma, socket) { + if(idioma==null) + idioma = 'en'; try { if (this.checkEnoughPlayers(id)) { //crear la zona de reusltado se hace aqui para que en caso de que abandonen la sala no se haya creado ya this.gameResults.set(id,new Map()); - let preguntas =await axios.get(questionServiceUrl+'/getQuestionModoBasico'); + let preguntas =await axios.get(questionServiceUrl+'/getQuestionModoBasico' + '?idioma=' + idioma); console.log("Preguntas: "+preguntas.data); socket.emit('gameStarted', preguntas.data); socket.to(id).emit('gameStarted', preguntas.data); diff --git a/roomservice/room-service.js b/roomservice/room-service.js index 08ed6a8a..85ad2d3c 100644 --- a/roomservice/room-service.js +++ b/roomservice/room-service.js @@ -61,9 +61,9 @@ io.on('connection', (socket) => { }); - socket.on('startGame', async ({id}) => { + socket.on('startGame', async ({id, idioma}) => { console.log("solicitud empezar juego sala:"+id); - roomQuestions.startGame(id,socket); + roomQuestions.startGame(id,idioma, socket); }); diff --git a/webapp/src/components/game/BasicGame.js b/webapp/src/components/game/BasicGame.js index eef3bb2d..b190ee51 100644 --- a/webapp/src/components/game/BasicGame.js +++ b/webapp/src/components/game/BasicGame.js @@ -8,6 +8,7 @@ import GameMode from './gameModes/GameMode'; import Swal from 'sweetalert2'; class BasicGame extends GameMode { + constructor() { super(); // Vincular nextQuestion al contexto correcto @@ -16,11 +17,15 @@ class BasicGame extends GameMode { this.correctas=0; this.incorrectas=0; this.tiempoTotal=null; + this.idioma = null; } + async fetchQuestions() { + if(this.idioma == null) + this.idioma = 'en'; try { - const response = await fetch(`${this.apiEndpoint}/getQuestionModoBasico`); + const response = await fetch(`${this.apiEndpoint}/getQuestionModoBasico?idioma=${this.idioma}`); const data = await response.json(); this.questions = Object.values(data); @@ -38,11 +43,13 @@ class BasicGame extends GameMode { await this.fetchQuestions(); this.isLoading = false; } + async endGame() { console.log('endGameeeeeeeeee'); this.isGameEnded = true; this.questionIndex=0; } + /* recibe el objeto que representa los datos asi si quieres no guardar un dato no se lo pasas */ @@ -92,6 +99,7 @@ class BasicGame extends GameMode { }); } } + nextQuestion() { if(this.questions.length == 0){ console.log("no se tiene seguiente preungta , el array es vaicio"); @@ -140,9 +148,11 @@ class BasicGame extends GameMode { incrementCorrectas(){ this.correctas++; } + incrementIncorrectas(){ this.incorrectas++; } + setTiempoTotal(time){ this.tiempoTotal=time; } diff --git a/webapp/src/components/game/Game.js b/webapp/src/components/game/Game.js index c71affcc..4769333c 100644 --- a/webapp/src/components/game/Game.js +++ b/webapp/src/components/game/Game.js @@ -3,6 +3,7 @@ import { useEffect, useState, useRef } from 'react'; import {useNavigate} from 'react-router-dom'; import { Spinner, Box, AlertDialog, AlertDialogBody, AlertDialogFooter, AlertDialogHeader, AlertDialogContent, AlertDialogOverlay, Button,Center } from "@chakra-ui/react"; import BasicGame from './BasicGame'; +import { useTranslation } from 'react-i18next'; const apiEndpoint = process.env.REACT_APP_API_URI ||'http://localhost:8000'; @@ -20,9 +21,13 @@ function Game({darkMode,gameMode=new BasicGame()}) { const gameModeRef = useRef(gameMode); + //para pasarle el idioma + const { i18n } = useTranslation(); + useEffect(() => { const startGameAsync = async () => { setIsLoading(true); + gameModeRef.current.idioma = i18n.language; await gameModeRef.current.startGame(); console.log('preguntas', gameModeRef.current.questions); diff --git a/webapp/src/components/rooms/Room.js b/webapp/src/components/rooms/Room.js index 9e689718..ee56ed06 100644 --- a/webapp/src/components/rooms/Room.js +++ b/webapp/src/components/rooms/Room.js @@ -23,7 +23,7 @@ function Room({ darkMode }) { const [winner, setWinner] = useState(null); //para la internacionalización - const {t} = useTranslation(); + const {t, i18n} = useTranslation(); const [roomGame, setRoomGame] = useState(null); @@ -91,7 +91,7 @@ function Room({ darkMode }) { function startGame (){ if(!gameStarted && isHost ){ setGameStarted(true); - socket.emit('startGame', { id: roomId }); + socket.emit('startGame', { id: roomId , idioma: i18n.language}); console.log("se ha iniciado el juego"); } diff --git a/webapp/src/components/startbutton/StartButton.js b/webapp/src/components/startbutton/StartButton.js index 731391e8..aa60db0f 100644 --- a/webapp/src/components/startbutton/StartButton.js +++ b/webapp/src/components/startbutton/StartButton.js @@ -1,7 +1,6 @@ import React, { useContext } from 'react'; import { useNavigate } from 'react-router-dom'; import './startButton.css' -import { GameContext } from '../game/GameContext'; import { useTranslation } from 'react-i18next'; const StartButton = () => { From e36c4bdc0177e4a50dc5c30d29e5d9e7e00b3ffc Mon Sep 17 00:00:00 2001 From: bidof Date: Mon, 8 Apr 2024 20:39:34 +0200 Subject: [PATCH 69/79] pasar el navigate pare redireccionar al terminar el juego --- questionservice/scheduler.js | 2 +- webapp/src/components/game/BasicGame.js | 8 +++++++- webapp/src/components/game/Game.js | 1 + webapp/src/components/game/gameModes/RoomGame.js | 2 ++ 4 files changed, 11 insertions(+), 2 deletions(-) diff --git a/questionservice/scheduler.js b/questionservice/scheduler.js index 9d386983..5be1737a 100644 --- a/questionservice/scheduler.js +++ b/questionservice/scheduler.js @@ -40,7 +40,7 @@ class Scheduler { start() { - cron.schedule('*/30 * * * *', async () => { + cron.schedule('*/01 * * * *', async () => { try { await this.generarPregunta(); } diff --git a/webapp/src/components/game/BasicGame.js b/webapp/src/components/game/BasicGame.js index b190ee51..ed5435f4 100644 --- a/webapp/src/components/game/BasicGame.js +++ b/webapp/src/components/game/BasicGame.js @@ -6,7 +6,7 @@ heredas y sobreescribes y list */ import GameMode from './gameModes/GameMode'; import Swal from 'sweetalert2'; - +import { Redirect } from 'react-router-dom'; class BasicGame extends GameMode { constructor() { @@ -48,6 +48,12 @@ class BasicGame extends GameMode { console.log('endGameeeeeeeeee'); this.isGameEnded = true; this.questionIndex=0; + // Imprimir la función navigate para verificar que se ha pasado correctamente + console.log('Función navigate:', this.navigate); + + + //redireccionar al usuario a /home con la prop dinamica que le pasas + this.navigate('/home'); } /* diff --git a/webapp/src/components/game/Game.js b/webapp/src/components/game/Game.js index 4769333c..10e51314 100644 --- a/webapp/src/components/game/Game.js +++ b/webapp/src/components/game/Game.js @@ -28,6 +28,7 @@ function Game({darkMode,gameMode=new BasicGame()}) { const startGameAsync = async () => { setIsLoading(true); gameModeRef.current.idioma = i18n.language; + gameModeRef.current.navigate = navigate;//le das la prop dinamicamente al obj await gameModeRef.current.startGame(); console.log('preguntas', gameModeRef.current.questions); diff --git a/webapp/src/components/game/gameModes/RoomGame.js b/webapp/src/components/game/gameModes/RoomGame.js index 761ad9ed..b7364317 100644 --- a/webapp/src/components/game/gameModes/RoomGame.js +++ b/webapp/src/components/game/gameModes/RoomGame.js @@ -67,6 +67,8 @@ class RoomGame extends BasicGame { text: `Esperando a que el resto de jugaores terminen la partida `, confirmButtonText: 'Cerrar' }); + + this.navigate('/home'); } finishGame(){ From e4d3b111fd4d98a89b75fa7c01f2eddaa3003fe4 Mon Sep 17 00:00:00 2001 From: bidof Date: Mon, 8 Apr 2024 20:46:01 +0200 Subject: [PATCH 70/79] cambiar el ganador por un sistema de ranking para poder hacer un top --- roomservice/RoomQuestions.js | 34 ++++++++++++++++++- .../src/components/game/gameModes/RoomGame.js | 2 +- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/roomservice/RoomQuestions.js b/roomservice/RoomQuestions.js index 78cda534..006f39da 100644 --- a/roomservice/RoomQuestions.js +++ b/roomservice/RoomQuestions.js @@ -107,12 +107,15 @@ class RoomQuestions{ let winner = this.determineWinner(roomResults); console.log("el ganador determinado es "+winner); //hacer uhnjson con el ganador y obtener su tiempo y correctas + /* let data={ winner:winner, correctas:roomResults.get(winner).correctas, tiempoTotal:roomResults.get(winner).tiempoTotal } - socket.to(id).emit('gameEnded', data); // enviar a todos los de sala quien gano + */ + let ranking = this.determineRanking(roomResults); + socket.to(id).emit('gameEnded', ranking); // enviar a todos los de sala quien gano socket.emit('gameEnded', winner); // } @@ -121,6 +124,35 @@ class RoomQuestions{ } } + determineRanking(roomResults) { + // Convertir el Map de resultados en un array de objetos + let resultsArray = Array.from(roomResults.entries()).map(([username, userResults]) => ({ + username, + correctas: userResults.correctas, + tiempoTotal: userResults.tiempoTotal + })); + + // Ordenar el array de resultados por el número de respuestas correctas y el tiempo total + resultsArray.sort((a, b) => { + if (a.correctas > b.correctas) { + return -1; + } else if (a.correctas < b.correctas) { + return 1; + } else { + if (a.tiempoTotal < b.tiempoTotal) { + return -1; + } else if (a.tiempoTotal > b.tiempoTotal) { + return 1; + } else { + return 0; + } + } + }); + + // Devolver el array de resultados ordenado + return resultsArray; + } + determineWinner(roomResults) { let winner = null; let maxCorrectAnswers = 0; diff --git a/webapp/src/components/game/gameModes/RoomGame.js b/webapp/src/components/game/gameModes/RoomGame.js index b7364317..acf19957 100644 --- a/webapp/src/components/game/gameModes/RoomGame.js +++ b/webapp/src/components/game/gameModes/RoomGame.js @@ -68,7 +68,7 @@ class RoomGame extends BasicGame { confirmButtonText: 'Cerrar' }); - this.navigate('/home'); + } finishGame(){ From 76d0b32d9ce2025975150ca2bf777198a8c72a8d Mon Sep 17 00:00:00 2001 From: bidof Date: Mon, 8 Apr 2024 21:19:10 +0200 Subject: [PATCH 71/79] agregar ranking --- questionservice/scheduler.js | 2 +- roomservice/RoomQuestions.js | 13 ++++------- webapp/src/components/rooms/Ranking.js | 32 ++++++++++++++++++++++++++ webapp/src/components/rooms/Room.js | 25 ++++++++------------ 4 files changed, 46 insertions(+), 26 deletions(-) create mode 100644 webapp/src/components/rooms/Ranking.js diff --git a/questionservice/scheduler.js b/questionservice/scheduler.js index 5be1737a..9d386983 100644 --- a/questionservice/scheduler.js +++ b/questionservice/scheduler.js @@ -40,7 +40,7 @@ class Scheduler { start() { - cron.schedule('*/01 * * * *', async () => { + cron.schedule('*/30 * * * *', async () => { try { await this.generarPregunta(); } diff --git a/roomservice/RoomQuestions.js b/roomservice/RoomQuestions.js index 006f39da..3ff18b83 100644 --- a/roomservice/RoomQuestions.js +++ b/roomservice/RoomQuestions.js @@ -107,16 +107,11 @@ class RoomQuestions{ let winner = this.determineWinner(roomResults); console.log("el ganador determinado es "+winner); //hacer uhnjson con el ganador y obtener su tiempo y correctas - /* - let data={ - winner:winner, - correctas:roomResults.get(winner).correctas, - tiempoTotal:roomResults.get(winner).tiempoTotal - } - */ - let ranking = this.determineRanking(roomResults); + + let ranking = this.determineRanking(roomResults); + console.log("ranking",ranking); socket.to(id).emit('gameEnded', ranking); // enviar a todos los de sala quien gano - socket.emit('gameEnded', winner); // + socket.emit('gameEnded', ranking); // } } catch (error) { diff --git a/webapp/src/components/rooms/Ranking.js b/webapp/src/components/rooms/Ranking.js new file mode 100644 index 00000000..644dd91a --- /dev/null +++ b/webapp/src/components/rooms/Ranking.js @@ -0,0 +1,32 @@ +import React from 'react'; +import { useLocation } from 'react-router-dom'; + +function Ranking() { + // Obtener el ranking del estado de la navegación + const location = useLocation(); + const ranking = location.state.ranking; + + // Ordenar el ranking por el número de respuestas correctas y, en caso de empate, por el tiempo total + ranking.sort((a, b) => { + if (b.correctas !== a.correctas) { + return b.correctas - a.correctas; + } else { + return a.tiempoTotal - b.tiempoTotal; + } + }); + + return ( +
+

Ranking

+
    + {ranking.map((player, index) => ( +
  • + Puesto {index + 1}: {player.username} con {player.correctas} respuestas correctas y un tiempo total de {player.tiempoTotal} +
  • + ))} +
+
+ ); +} + +export default Ranking; \ No newline at end of file diff --git a/webapp/src/components/rooms/Room.js b/webapp/src/components/rooms/Room.js index ee56ed06..e57831e7 100644 --- a/webapp/src/components/rooms/Room.js +++ b/webapp/src/components/rooms/Room.js @@ -10,7 +10,7 @@ import { useTranslation } from 'react-i18next'; import RoomGame from '../game/gameModes/RoomGame'; function Room({ darkMode }) { - const nagivate = useNavigate(); + const navigate = useNavigate(); const { roomId } = useParams(); const location = useLocation(); const isHost = location.state?.isHost; @@ -27,13 +27,12 @@ function Room({ darkMode }) { const [roomGame, setRoomGame] = useState(null); - //para el mensaje del ganador const [isOpen, setIsOpen] = useState(false); const cancelRef = useRef(); const onClose = () =>{ setIsOpen(false); - nagivate('/home'); + navigate('/home'); }; useEffect(() => { @@ -57,23 +56,17 @@ function Room({ darkMode }) { }, endGame:endGame, } - setRoomGame(new RoomGame(room, nagivate)); + setRoomGame(new RoomGame(room, navigate)); setGameStarted(true); }); - socket.on('gameEnded', ( data ) => { - console.log('Juego terminado, ganador: correctas y tiempo', data.winner,data.correctas,data.tiempoTotal); - setWinner(data.winner); - - //imrpmir popup del ganador - Swal.fire({ - title: 'El ganador es ', - text: data.winner, - confirmButtonText: 'Cerrar' - }); + socket.on('gameEnded', (ranking) => { + console.log('Juego terminado, ranking:', ranking); + + + // Redirigir a los jugadores a la página de ranking + navigate('/ranking', { state: { ranking } }); }); - //limpiar el evento - return () => socket.off('gameEnded'); }, [roomId]); From fc90e28e8b44436141a87b33d9813c0cd895984b Mon Sep 17 00:00:00 2001 From: bidof Date: Mon, 8 Apr 2024 21:49:05 +0200 Subject: [PATCH 72/79] nuevo ranking de quien gano el jueggo y top 3 --- webapp/src/App.js | 3 +- webapp/src/components/rooms/Ranking.js | 32 ------------ webapp/src/components/rooms/RankingRoom.js | 59 ++++++++++++++++++++++ webapp/src/components/rooms/Room.js | 2 +- 4 files changed, 62 insertions(+), 34 deletions(-) delete mode 100644 webapp/src/components/rooms/Ranking.js create mode 100644 webapp/src/components/rooms/RankingRoom.js diff --git a/webapp/src/App.js b/webapp/src/App.js index aaf669d8..a7586629 100644 --- a/webapp/src/App.js +++ b/webapp/src/App.js @@ -18,6 +18,7 @@ import Room from './components/rooms/Room'; // Importa el componente de sala import CreateRoomForm from './components/rooms/CreateRoom'; // Importa el componente para crear sala import JoinRoomForm from './components/rooms/JoinRoom'; // Importa el componente para unirse a sala import { Ranking } from './components/ranking/Ranking'; +import RankingRoom from './components/rooms/RankingRoom'; // Asegúrate de que la ruta de importación es correcta const App = () => { @@ -70,7 +71,7 @@ const App = () => { } /> } /> } /> - + } /> diff --git a/webapp/src/components/rooms/Ranking.js b/webapp/src/components/rooms/Ranking.js deleted file mode 100644 index 644dd91a..00000000 --- a/webapp/src/components/rooms/Ranking.js +++ /dev/null @@ -1,32 +0,0 @@ -import React from 'react'; -import { useLocation } from 'react-router-dom'; - -function Ranking() { - // Obtener el ranking del estado de la navegación - const location = useLocation(); - const ranking = location.state.ranking; - - // Ordenar el ranking por el número de respuestas correctas y, en caso de empate, por el tiempo total - ranking.sort((a, b) => { - if (b.correctas !== a.correctas) { - return b.correctas - a.correctas; - } else { - return a.tiempoTotal - b.tiempoTotal; - } - }); - - return ( -
-

Ranking

-
    - {ranking.map((player, index) => ( -
  • - Puesto {index + 1}: {player.username} con {player.correctas} respuestas correctas y un tiempo total de {player.tiempoTotal} -
  • - ))} -
-
- ); -} - -export default Ranking; \ No newline at end of file diff --git a/webapp/src/components/rooms/RankingRoom.js b/webapp/src/components/rooms/RankingRoom.js new file mode 100644 index 00000000..df0d7976 --- /dev/null +++ b/webapp/src/components/rooms/RankingRoom.js @@ -0,0 +1,59 @@ +import React from 'react'; +import { useLocation } from 'react-router-dom'; +import { Box, Heading, Text, VStack, HStack, Badge } from "@chakra-ui/react"; +import { ChakraProvider } from '@chakra-ui/react'; + +function RankingRoom({darkMode}) { + // Obtener el ranking del estado de la navegación + const location = useLocation(); + const ranking = location.state.ranking; + + // Ordenar el ranking por el número de respuestas correctas y, en caso de empate, por el tiempo total + ranking.sort((a, b) => { + if (b.correctas !== a.correctas) { + return b.correctas - a.correctas; + } else { + return a.tiempoTotal - b.tiempoTotal; + } + }); + + // Dividir el ranking en los tres primeros jugadores y el resto + const topThree = ranking.slice(0, 3); + const rest = ranking.slice(3); + + return ( + + + Ranking + + {/* Podio */} + + {topThree.map((player, index) => ( + + + Puesto {index + 1} + + {player.username} + {player.correctas} respuestas correctas + Tiempo total: {player.tiempoTotal} + + ))} + + + {/* Resto de jugadores */} + + {rest.map((player, index) => ( + + Puesto {index + 4}: {player.username} + {player.correctas} respuestas correctas + Tiempo total: {player.tiempoTotal} + + ))} + + + + + ); +} + +export default RankingRoom; \ No newline at end of file diff --git a/webapp/src/components/rooms/Room.js b/webapp/src/components/rooms/Room.js index e57831e7..566a4271 100644 --- a/webapp/src/components/rooms/Room.js +++ b/webapp/src/components/rooms/Room.js @@ -65,7 +65,7 @@ function Room({ darkMode }) { // Redirigir a los jugadores a la página de ranking - navigate('/ranking', { state: { ranking } }); + navigate('/rankingroom/'+roomId,{ state: { ranking: ranking } }); }); From ac81a0a20e3c4d72119428adf1d19dfd361690b1 Mon Sep 17 00:00:00 2001 From: bidof Date: Mon, 8 Apr 2024 21:50:43 +0200 Subject: [PATCH 73/79] test del ranking --- .../src/components/rooms/RankingRoom.test.js | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 webapp/src/components/rooms/RankingRoom.test.js diff --git a/webapp/src/components/rooms/RankingRoom.test.js b/webapp/src/components/rooms/RankingRoom.test.js new file mode 100644 index 00000000..dbd58d73 --- /dev/null +++ b/webapp/src/components/rooms/RankingRoom.test.js @@ -0,0 +1,59 @@ +import { render, screen } from '@testing-library/react'; +import { BrowserRouter as Router, useLocation } from 'react-router-dom'; +import { ChakraProvider } from '@chakra-ui/react'; +import RankingRoom from './RankingRoom'; + +jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + useLocation: jest.fn(), +})); + +test('renders RankingRoom component with correct ranking', () => { + const mockRanking = [ + { username: 'user1', correctas: 5, tiempoTotal: 10 }, + { username: 'user2', correctas: 3, tiempoTotal: 15 }, + { username: 'user3', correctas: 3, tiempoTotal: 12 }, + { username: 'user4', correctas: 2, tiempoTotal: 20 }, + ]; + + useLocation.mockReturnValue({ state: { ranking: mockRanking } }); + + render( + + + + + + ); + + // Check if the top three players are rendered correctly + expect(screen.getByText('Puesto 1')).toBeInTheDocument(); + expect(screen.getByText('Puesto 2')).toBeInTheDocument(); + expect(screen.getByText('Puesto 3')).toBeInTheDocument(); + expect(screen.getByText('user1')).toBeInTheDocument(); + expect(screen.getByText('user2')).toBeInTheDocument(); + expect(screen.getByText('user3')).toBeInTheDocument(); + + // Check if the rest of the players are rendered correctly + expect(screen.getByText('Puesto 4: user4')).toBeInTheDocument(); +}); + +test('renders RankingRoom component with empty ranking', () => { + const mockRanking = []; + + useLocation.mockReturnValue({ state: { ranking: mockRanking } }); + + render( + + + + + + ); + + // Check if the "Ranking" heading is rendered + expect(screen.getByText('Ranking')).toBeInTheDocument(); + + // Check if no players are rendered + expect(screen.queryByText('Puesto 1')).toBeNull(); +}); \ No newline at end of file From 78a3dd9916130f765bebe6c8512668385c4ee049 Mon Sep 17 00:00:00 2001 From: bidof Date: Mon, 8 Apr 2024 22:42:06 +0200 Subject: [PATCH 74/79] falta arreglar el tiempo que siempre es 0 en el juego --- webapp/src/components/game/Game.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/webapp/src/components/game/Game.js b/webapp/src/components/game/Game.js index 10e51314..c8251fa1 100644 --- a/webapp/src/components/game/Game.js +++ b/webapp/src/components/game/Game.js @@ -58,6 +58,7 @@ function Game({darkMode,gameMode=new BasicGame()}) { if(correctAnswers+incorrectAnswers==gameModeRef.current.questions.length-1) setIsFinished(true); + setTotalTime(totalTime); }; useEffect(() => { @@ -71,6 +72,7 @@ function Game({darkMode,gameMode=new BasicGame()}) { console.log("use effect finish"); setIsFinished(true); //poner el tiepo que tardo + console.log("tiempo total tardado en acabar ",totalTime); gameModeRef.current.setTiempoTotal(totalTime); gameModeRef.current.finishGame(); gameModeRef.current.sendHistory({correctas: correctAnswers, incorrectas: incorrectAnswers, tiempoTotal: totalTime}); From 2894eb3fce8119f3ea8b7c0731d276df5b887dae Mon Sep 17 00:00:00 2001 From: lucia-sonia Date: Mon, 8 Apr 2024 22:46:29 +0200 Subject: [PATCH 75/79] Prueba para que los tests del gateway pasen --- gatewayservice/gateway-service.js | 2 +- gatewayservice/gateway-service.test.js | 28 ++++++++++++++++++-------- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/gatewayservice/gateway-service.js b/gatewayservice/gateway-service.js index 83024e62..774be4af 100644 --- a/gatewayservice/gateway-service.js +++ b/gatewayservice/gateway-service.js @@ -61,7 +61,7 @@ app.get('/getQuestion', async (req, res) => { res.json(questionResponse.data); } catch (error) { //Modifico el error - res.status(500).json({ error: 'Error al realizar la solicitud al servicio de preguntas para obtener una pregunta -> ' + error.response.data.error}); + res.status(500).json({ error: 'Error al realizar la solicitud al servicio de preguntas para obtener una pregunta -> ' + error.message}); } }); diff --git a/gatewayservice/gateway-service.test.js b/gatewayservice/gateway-service.test.js index a2180c07..702dd6ef 100644 --- a/gatewayservice/gateway-service.test.js +++ b/gatewayservice/gateway-service.test.js @@ -90,14 +90,26 @@ it('should return 404 for nonexistent endpoint', async()=>{ //Caso positivo para el endpoint /getQuestion it('should perform the getQuestion request', async () => { - const response = await request(app).get('/getQuestion').send(); - expect(response.statusCode).toBe(200); - const data = { - pregunta: '¿Cuál es la capital de Francia?', - respuestas: ['Berlin', 'Paris', 'Londres', 'Madrid'], - correcta: 'Paris', + const idioma = 'es'; + //para una pregunta + const mockQuestion = { + "resultado1":{ + "pregunta":"¿Cuál es la capital del pais Argelia?", + "correcta":"Argel", + "respuestasIncorrecta1":"Castries", + "respuestasIncorrecta2":"Mogadiscio", + "respuestasIncorrecta3":"Oslo" + } }; - axios.get.mockImplementationOnce(() => Promise.resolve({ data })); + questionServiceUrl = 'http://localhost:8003'; + + axios.get.mockResolvedValue({ data: mockQuestion }); + + const response = await request(app).get(`/getQuestion?idioma=${idioma}`); + + expect(axios.get).toHaveBeenCalledWith(`${questionServiceUrl}/getQuestion?idioma=${idioma}`, {}); + expect(response.statusCode).toBe(200); + expect(response.body).toEqual(mockQuestion); }); //Caso positivo para el endpoint /getQuestionDiario @@ -197,7 +209,7 @@ it('should return an error when the question service request fails', async () => expect(response.statusCode).toBe(500); expect(response.body.error).toBeDefined(); - expect(response.body.error).toEqual('Error al realizar la solicitud al servicio de preguntas'); + expect(response.body.error).toEqual('Error al realizar la solicitud al servicio de preguntas para obtener una pregunta -> Network Error'); }); //Caso negativo para el endpoint /getQuestionModoBasico From 4c0d6b52fcfee3bbf022e23311f3f3a2508aace5 Mon Sep 17 00:00:00 2001 From: lucia-sonia Date: Mon, 8 Apr 2024 23:06:57 +0200 Subject: [PATCH 76/79] arreglado el test del footer --- webapp/src/components/footer/Footer.test.js | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/webapp/src/components/footer/Footer.test.js b/webapp/src/components/footer/Footer.test.js index 9ee43719..aa85745d 100644 --- a/webapp/src/components/footer/Footer.test.js +++ b/webapp/src/components/footer/Footer.test.js @@ -2,6 +2,22 @@ import React from 'react'; import { render, screen } from '@testing-library/react'; import Footer from './Footer'; +jest.mock('i18next', () => ({ + use: () => {}, + init: () => {}, +})); + + +jest.mock('react-i18next', () => ({ + useTranslation: () => ({ + t: (key) => ({ + 'subject': 'Trabajo de Arquitectura del Software', + 'github': 'Github del Proyecto', + 'university': 'Escuela de Ingeniería Informática', + })[key], + }), +})); + describe('Footer Component', () => { test('renders footer with correct content and links', () => { // Renderizar el componente @@ -11,7 +27,7 @@ describe('Footer Component', () => { expect(screen.getByText('Trabajo de Arquitectura del Software')).toBeInTheDocument(); // Verificar que el enlace al Github del Proyecto esté presente con el atributo target="_blank" - const githubLink = screen.getByRole('link', { name: /Github del Proyecto/i }); + const githubLink = screen.getByRole('link', { name: /Github del Proyecto/i }); expect(githubLink).toBeInTheDocument(); expect(githubLink).toHaveAttribute('href', 'https://github.com/Arquisoft/wiq_es04c'); expect(githubLink).toHaveAttribute('target', '_blank'); From fd775c3d571ed5df4ac60e11a7b039098aad1f63 Mon Sep 17 00:00:00 2001 From: lucia-sonia Date: Mon, 8 Apr 2024 23:25:45 +0200 Subject: [PATCH 77/79] intentando arreglar test del navbar --- webapp/src/components/navbar/NavBar.test.js | 51 +++++++++------------ 1 file changed, 22 insertions(+), 29 deletions(-) diff --git a/webapp/src/components/navbar/NavBar.test.js b/webapp/src/components/navbar/NavBar.test.js index 919404df..6f7a0b2b 100644 --- a/webapp/src/components/navbar/NavBar.test.js +++ b/webapp/src/components/navbar/NavBar.test.js @@ -1,41 +1,34 @@ import React from 'react'; import { render, screen } from '@testing-library/react'; -import Navbar from './NavBar'; -import { AuthProvider } from '../authcontext'; +import NavBar from './NavBar'; +import { AuthContext } from '../authcontext'; import { BrowserRouter as Router} from 'react-router-dom'; +jest.mock('react-i18next', () => ({ + useTranslation: () => ({ + t: (key) => ({ + 'signUp': 'Registrarse', + 'signIn': 'Iniciar sesión', + 'logout': 'Cerrar sesión', + })[key], + i18n: { + language: 'es', + }, + }), +})); + describe('Navbar Component', () => { - test('renders Navbar with correct content and links', () => { - // Renderizar el componente + test('renders login and register buttons when user is not logged in', () => { + const mockIsLoggedIn = jest.fn().mockReturnValue(false); render( - + - + - + ); - // Verificar que el logo esté presente - const logo = screen.getByAltText('Logo'); - expect(logo).toBeInTheDocument(); - expect(logo).toHaveAttribute('src', 'LogoSaberYganar.png'); - - // Verificar que los enlaces de registro e inicio de sesión estén presentes con los íconos correctos - const registerLink = screen.getByRole('link', { name: /Registrarse/i }); - expect(registerLink).toBeInTheDocument(); - expect(registerLink).toHaveAttribute('href', '/addUser'); - - const loginLink = screen.getByRole('link', { name: /Iniciar sesión/i }); - expect(loginLink).toBeInTheDocument(); - expect(loginLink).toHaveAttribute('href', '/login'); - - // Verificar que los íconos estén presentes en los enlaces - const registerIcon = screen.getByRole('link', { name: /Registrarse/i }).querySelector('i'); - expect(registerIcon).toBeInTheDocument(); - expect(registerIcon).toHaveClass('fas fa-sign-in-alt'); - - const loginIcon = screen.getByRole('link', { name: /Iniciar sesión/i }).querySelector('i'); - expect(loginIcon).toBeInTheDocument(); - expect(loginIcon).toHaveClass('fas fa-sign-in-alt'); + expect(screen.getByRole('link', { name: /Iniciar sesión/i })).toBeInTheDocument(); + expect(screen.getByRole('link', { name: /Registrarse/i })).toBeInTheDocument(); }); }); From e52b8d917f0e203b4dbba0036dd6523fac0f9c0f Mon Sep 17 00:00:00 2001 From: lucia-sonia Date: Mon, 8 Apr 2024 23:46:58 +0200 Subject: [PATCH 78/79] intento de arreglar los test de app --- webapp/src/App.js | 4 ---- webapp/src/App.test.js | 23 ++++++++++++++++++++--- webapp/src/components/footer/Footer.js | 2 +- webapp/src/components/navbar/NavBar.js | 2 +- 4 files changed, 22 insertions(+), 9 deletions(-) diff --git a/webapp/src/App.js b/webapp/src/App.js index aaf669d8..f3387a82 100644 --- a/webapp/src/App.js +++ b/webapp/src/App.js @@ -38,8 +38,6 @@ const App = () => { - - } /> } /> @@ -72,8 +70,6 @@ const App = () => { } /> - -
diff --git a/webapp/src/App.test.js b/webapp/src/App.test.js index 5e3b7314..2d43b26f 100644 --- a/webapp/src/App.test.js +++ b/webapp/src/App.test.js @@ -1,8 +1,25 @@ import { render, screen } from '@testing-library/react'; import App from './App'; +import { useTranslation } from 'react-i18next'; -test('renders learn react link', () => { +jest.mock('react-i18next', () => ({ + useTranslation: () => ({ + t: jest.fn(), + i18n: { + changeLanguage: jest.fn(), + language: 'es', + }, + }), +})); + +test('renders Navbar component', () => { render(); - const linkElement = screen.getByText(/Welcome to the 2024 edition of the Software Architecture course/i); - expect(linkElement).toBeInTheDocument(); + const navbarElement = screen.getByTestId('navbar'); + expect(navbarElement).toBeInTheDocument(); }); + +test('renders Footer component', () => { + render(); + const footerElement = screen.getByTestId('footer'); + expect(footerElement).toBeInTheDocument(); +}); \ No newline at end of file diff --git a/webapp/src/components/footer/Footer.js b/webapp/src/components/footer/Footer.js index 2ef59e39..211486b0 100644 --- a/webapp/src/components/footer/Footer.js +++ b/webapp/src/components/footer/Footer.js @@ -10,7 +10,7 @@ const Footer = ({darkMode}) => { const {t} = useTranslation(); return( -
+

{t('subject')}

diff --git a/webapp/src/components/navbar/NavBar.js b/webapp/src/components/navbar/NavBar.js index dcf37cc5..1695ff40 100644 --- a/webapp/src/components/navbar/NavBar.js +++ b/webapp/src/components/navbar/NavBar.js @@ -63,7 +63,7 @@ const NavBar = ({ setDarkMode, darkMode}) => { console.log('isLoggedIn', isLoggedIn); return ( - +