diff --git a/.gitignore b/.gitignore index 1ae1622..5c9b7bc 100644 --- a/.gitignore +++ b/.gitignore @@ -41,6 +41,7 @@ build/Release # Dependency directories node_modules/ jspm_packages/ +mongo-backup/ # Snowpack dependency directory (https://snowpack.dev/) web_modules/ diff --git a/README.fr-ca.md b/README.fr-ca.md new file mode 100644 index 0000000..0710768 --- /dev/null +++ b/README.fr-ca.md @@ -0,0 +1,30 @@ +[![CI/CD Pipeline for Frontend](https://github.com/ets-cfuhrman-pfe/EvalueTonSavoir/actions/workflows/frontend-deploy.yml/badge.svg)](https://github.com/ets-cfuhrman-pfe/EvalueTonSavoir/actions/workflows/frontend-deploy.yml) +[![CI/CD Pipeline for Backend](https://github.com/ets-cfuhrman-pfe/EvalueTonSavoir/actions/workflows/backend-deploy.yml/badge.svg)](https://github.com/ets-cfuhrman-pfe/EvalueTonSavoir/actions/workflows/backend-deploy.yml) +[![CI/CD Pipeline for Nginx Router](https://github.com/ets-cfuhrman-pfe/EvalueTonSavoir/actions/workflows/deploy.yml/badge.svg)](https://github.com/ets-cfuhrman-pfe/EvalueTonSavoir/actions/workflows/deploy.yml) + + +[![en](https://img.shields.io/badge/lang-en-red.svg)](https://github.com/ets-cfuhrman-pfe/EvalueTonSavoir/blob/master/README.md) + +# EvalueTonSavoir + +EvalueTonSavoir est une plateforme open source et auto-hébergée qui poursuit le développement du code provenant de https://github.com/ETS-PFE004-Plateforme-sondage-minitest. Cette plateforme minimaliste est conçue comme un outil d'apprentissage et d'enseignement, offrant une solution simple et efficace pour la création de quiz utilisant le format GIFT, similaire à Moodle. + +## Fonctionnalités clés + +* Open Source et Auto-hébergé : Possédez et contrôlez vos données en déployant la plateforme sur votre propre infrastructure. +* Compatibilité GIFT : Créez des quiz facilement en utilisant le format GIFT, permettant une intégration transparente avec d'autres systèmes d'apprentissage. +* Minimaliste et Efficace : Une approche bare bones pour garantir la simplicité et la facilité d'utilisation, mettant l'accent sur l'essentiel de l'apprentissage. + +## Contribution + +Actuellement, il n'y a pas de modèle établi pour les contributions. Si vous constatez quelque chose de manquant ou si vous pensez qu'une amélioration est possible, n'hésitez pas à ouvrir un issue et/ou une PR) + +## Liens utiles + +* [Dépôt d'origine Frontend](https://github.com/ETS-PFE004-Plateforme-sondage-minitest/ETS-PFE004-EvalueTonSavoir-Frontend) +* [Dépôt d'origine Backend](https://github.com/ETS-PFE004-Plateforme-sondage-minitest/ETS-PFE004-EvalueTonSavoir-Backend) +* [Documentation (Wiki)](https://github.com/ets-cfuhrman-pfe/EvalueTonSavoir/wiki) + +## License + +EvalueTonSavoir is open-sourced and licensed under the [MIT License](/LICENSE). diff --git a/README.md b/README.md index b935ea0..bb4f7dc 100644 --- a/README.md +++ b/README.md @@ -2,24 +2,26 @@ [![CI/CD Pipeline for Backend](https://github.com/ets-cfuhrman-pfe/EvalueTonSavoir/actions/workflows/backend-deploy.yml/badge.svg)](https://github.com/ets-cfuhrman-pfe/EvalueTonSavoir/actions/workflows/backend-deploy.yml) [![CI/CD Pipeline for Nginx Router](https://github.com/ets-cfuhrman-pfe/EvalueTonSavoir/actions/workflows/deploy.yml/badge.svg)](https://github.com/ets-cfuhrman-pfe/EvalueTonSavoir/actions/workflows/deploy.yml) -# EvalueTonSavoir +[![fr-ca](https://img.shields.io/badge/lang-fr--ca-green.svg)](https://github.com/ets-cfuhrman-pfe/EvalueTonSavoir/blob/main/README.fr-ca.md) -EvalueTonSavoir est une plateforme open source et auto-hébergée qui poursuit le développement du code provenant de https://github.com/ETS-PFE004-Plateforme-sondage-minitest. Cette plateforme minimaliste est conçue comme un outil d'apprentissage et d'enseignement, offrant une solution simple et efficace pour la création de quiz utilisant le format GIFT, similaire à Moodle. +# EvalueTonSavoir -## Fonctionnalités clés +EvalueTonSavoir is an open-source and self-hosted platform that continues the development of the code from https://github.com/ETS-PFE004-Plateforme-sondage-minitest. This minimalist platform is designed as a learning and teaching tool, offering a simple and effective solution for creating quizzes using the GIFT format, similar to Moodle. -* Open Source et Auto-hébergé : Possédez et contrôlez vos données en déployant la plateforme sur votre propre infrastructure. -* Compatibilité GIFT : Créez des quiz facilement en utilisant le format GIFT, permettant une intégration transparente avec d'autres systèmes d'apprentissage. -* Minimaliste et Efficace : Une approche bare bones pour garantir la simplicité et la facilité d'utilisation, mettant l'accent sur l'essentiel de l'apprentissage. +## Key Features + +* **Open Source and Self-Hosted**: Own and control your data by deploying the platform on your own infrastructure. +* **GIFT Compatibility**: Easily create quizzes using the GIFT format, enabling seamless integration with other learning systems. +* **Minimalist and Efficient**: A bare-bones approach to ensure simplicity and ease of use, focusing on the essentials of learning. ## Contribution -Actuellement, il n'y a pas de modèle établi pour les contributions. Si vous constatez quelque chose de manquant ou si vous pensez qu'une amélioration est possible, n'hésitez pas à ouvrir un issue et/ou une PR) +Currently, there is no established model for contributions. If you notice something missing or think an improvement is possible, feel free to open an issue and/or a PR. -## Liens utiles +## Useful Links -* [Dépôt d'origine Frontend](https://github.com/ETS-PFE004-Plateforme-sondage-minitest/ETS-PFE004-EvalueTonSavoir-Frontend) -* [Dépôt d'origine Backend](https://github.com/ETS-PFE004-Plateforme-sondage-minitest/ETS-PFE004-EvalueTonSavoir-Backend) +* [Original Frontend Repository](https://github.com/ETS-PFE004-Plateforme-sondage-minitest/ETS-PFE004-EvalueTonSavoir-Frontend) +* [Original Backend Repository](https://github.com/ETS-PFE004-Plateforme-sondage-minitest/ETS-PFE004-EvalueTonSavoir-Backend) * [Documentation (Wiki)](https://github.com/ets-cfuhrman-pfe/EvalueTonSavoir/wiki) ## License diff --git a/client/.env.example b/client/.env.example index 944dcbb..fa03bbb 100644 --- a/client/.env.example +++ b/client/.env.example @@ -1,2 +1,2 @@ VITE_BACKEND_URL=http://localhost:4400 -VITE_AZURE_BACKEND_URL=http://localhost:4400 \ No newline at end of file +VITE_AZURE_BACKEND_URL=http://localhost:4400 diff --git a/client/package-lock.json b/client/package-lock.json index 19cd5cd..0d8073a 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -14,19 +14,21 @@ "@fortawesome/fontawesome-svg-core": "^6.7.2", "@fortawesome/free-solid-svg-icons": "^6.7.2", "@fortawesome/react-fontawesome": "^0.2.0", - "@mui/icons-material": "^6.4.6", + "@mui/icons-material": "6.4.7", "@mui/lab": "^5.0.0-alpha.153", - "@mui/material": "^6.4.7", + "@mui/material": "6.4.7", + "@mui/system": "6.4.7", "@types/uuid": "^9.0.7", "axios": "^1.8.1", - "dompurify": "^3.2.3", - "esbuild": "^0.25.0", + "dompurify": "^3.2.5", + "esbuild": "^0.25.2", "gift-pegjs": "^2.0.0-beta.1", "jest-environment-jsdom": "^29.7.0", "jwt-decode": "^4.0.0", "katex": "^0.16.11", - "marked": "^14.1.2", - "nanoid": "^5.1.2", + "marked": "^15.0.8", + "nanoid": "^5.1.5", + "qrcode.react": "^4.2.0", "react": "^18.3.1", "react-dom": "^18.3.1", "react-modal": "^3.16.3", @@ -34,39 +36,40 @@ "remark-math": "^6.0.0", "socket.io-client": "^4.7.2", "ts-node": "^10.9.1", - "uuid": "^9.0.1", - "vite-plugin-checker": "^0.9.0" + "uuid": "^11.1.0", + "vite-plugin-checker": "^0.9.1" }, "devDependencies": { "@babel/preset-env": "^7.26.9", "@babel/preset-react": "^7.26.3", - "@babel/preset-typescript": "^7.23.3", - "@eslint/js": "^9.21.0", + "@babel/preset-typescript": "^7.27.0", + "@eslint/js": "^9.24.0", "@testing-library/dom": "^10.4.0", "@testing-library/jest-dom": "^6.6.3", - "@testing-library/react": "^16.2.0", + "@testing-library/react": "^16.3.0", + "@testing-library/user-event": "^14.6.1", "@types/jest": "^29.5.13", - "@types/node": "^22.13.5", + "@types/node": "^22.14.0", "@types/react": "^18.2.15", "@types/react-dom": "^18.2.7", "@types/react-latex": "^2.0.3", - "@typescript-eslint/eslint-plugin": "^8.25.0", - "@typescript-eslint/parser": "^8.25.0", - "@vitejs/plugin-react-swc": "^3.8.0", + "@typescript-eslint/eslint-plugin": "^8.29.1", + "@typescript-eslint/parser": "^8.29.1", + "@vitejs/plugin-react-swc": "^3.8.1", "cross-env": "^7.0.3", - "eslint": "^9.21.0", + "eslint": "^9.24.0", "eslint-plugin-eslint-comments": "^3.2.0", "eslint-plugin-jest": "^28.11.0", - "eslint-plugin-react": "^7.37.3", + "eslint-plugin-react": "^7.37.5", "eslint-plugin-react-hooks": "^5.1.0-rc-206df66e-20240912", "eslint-plugin-react-refresh": "^0.4.19", "eslint-plugin-unused-imports": "^4.1.4", "globals": "^15.14.0", "identity-obj-proxy": "^3.0.0", "jest": "^29.7.0", - "ts-jest": "^29.2.6", - "typescript": "^5.7.3", - "typescript-eslint": "^8.25.0", + "ts-jest": "^29.3.1", + "typescript": "^5.8.3", + "typescript-eslint": "^8.29.1", "vite": "^6.2.0", "vite-plugin-environment": "^1.1.3" } @@ -155,13 +158,12 @@ "license": "MIT" }, "node_modules/@babel/generator": { - "version": "7.26.9", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.9.tgz", - "integrity": "sha512-kEWdzjOAUMW4hAyrzJ0ZaTOu9OmpyDIQicIh0zg0EEcEkYXZb2TjtBhnHi2ViX7PKwZqF4xwqfAm299/QMP3lg==", - "license": "MIT", + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.0.tgz", + "integrity": "sha512-VybsKvpiN1gU1sdMZIp7FcqphVVKEwcuj02x73uvcHE0PTihx1nlBcowYWhDwjpoAXRv43+gDzyggGnn1XZhVw==", "dependencies": { - "@babel/parser": "^7.26.9", - "@babel/types": "^7.26.9", + "@babel/parser": "^7.27.0", + "@babel/types": "^7.27.0", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^3.0.2" @@ -201,18 +203,17 @@ } }, "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.26.9", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.26.9.tgz", - "integrity": "sha512-ubbUqCofvxPRurw5L8WTsCLSkQiVpov4Qx0WMA+jUN+nXBK8ADPlJO1grkFw5CWKC5+sZSOfuGMdX1aI1iT9Sg==", + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.27.0.tgz", + "integrity": "sha512-vSGCvMecvFCd/BdpGlhpXYNhhC4ccxyvQWpbGL4CWbvfEoLFWUZuSuf7s9Aw70flgQF+6vptvgK2IfOnKlRmBg==", "dev": true, - "license": "MIT", "dependencies": { "@babel/helper-annotate-as-pure": "^7.25.9", "@babel/helper-member-expression-to-functions": "^7.25.9", "@babel/helper-optimise-call-expression": "^7.25.9", "@babel/helper-replace-supers": "^7.26.5", "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", - "@babel/traverse": "^7.26.9", + "@babel/traverse": "^7.27.0", "semver": "^6.3.1" }, "engines": { @@ -419,26 +420,24 @@ } }, "node_modules/@babel/helpers": { - "version": "7.26.9", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.9.tgz", - "integrity": "sha512-Mz/4+y8udxBKdmzt/UjPACs4G3j5SshJJEFFKxlCGPydG4JAHXxjWjAwjd09tf6oINvl1VfMJo+nB7H2YKQ0dA==", + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.0.tgz", + "integrity": "sha512-U5eyP/CTFPuNE3qk+WZMxFkp/4zUzdceQlfzf7DdGdhp+Fezd7HD+i8Y24ZuTMKX3wQBld449jijbGq6OdGNQg==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/template": "^7.26.9", - "@babel/types": "^7.26.9" + "@babel/template": "^7.27.0", + "@babel/types": "^7.27.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.26.9", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.9.tgz", - "integrity": "sha512-81NWa1njQblgZbQHxWHpxxCzNsa3ZwvFqpUg7P+NNUU6f3UU2jBEg4OlF/J6rl8+PQGh1q6/zWScd001YwcA5A==", - "license": "MIT", + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.0.tgz", + "integrity": "sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==", "dependencies": { - "@babel/types": "^7.26.9" + "@babel/types": "^7.27.0" }, "bin": { "parser": "bin/babel-parser.js" @@ -1666,14 +1665,13 @@ } }, "node_modules/@babel/plugin-transform-typescript": { - "version": "7.26.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.26.8.tgz", - "integrity": "sha512-bME5J9AC8ChwA7aEPJ6zym3w7aObZULHhbNLU0bKUhKsAkylkzUdq+0kdymh9rzi8nlNFl2bmldFBCKNJBUpuw==", + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.27.0.tgz", + "integrity": "sha512-fRGGjO2UEGPjvEcyAZXRXAS8AfdaQoq7HnxAbJoAoW10B9xOKesmmndJv+Sym2a+9FHWZ9KbyyLCe9s0Sn5jtg==", "dev": true, - "license": "MIT", "dependencies": { "@babel/helper-annotate-as-pure": "^7.25.9", - "@babel/helper-create-class-features-plugin": "^7.25.9", + "@babel/helper-create-class-features-plugin": "^7.27.0", "@babel/helper-plugin-utils": "^7.26.5", "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", "@babel/plugin-syntax-typescript": "^7.25.9" @@ -1873,17 +1871,16 @@ } }, "node_modules/@babel/preset-typescript": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.26.0.tgz", - "integrity": "sha512-NMk1IGZ5I/oHhoXEElcm+xUnL/szL6xflkFZmoEU9xj1qSJXpiS7rsspYo92B4DRCDvZn2erT5LdsCeXAKNCkg==", + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.27.0.tgz", + "integrity": "sha512-vxaPFfJtHhgeOVXRKuHpHPAOgymmy8V8I65T1q53R7GCZlefKeCaTyDs3zOPHTTbmquvNlQYC5klEvWsBAtrBQ==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-plugin-utils": "^7.26.5", "@babel/helper-validator-option": "^7.25.9", "@babel/plugin-syntax-jsx": "^7.25.9", - "@babel/plugin-transform-modules-commonjs": "^7.25.9", - "@babel/plugin-transform-typescript": "^7.25.9" + "@babel/plugin-transform-modules-commonjs": "^7.26.3", + "@babel/plugin-transform-typescript": "^7.27.0" }, "engines": { "node": ">=6.9.0" @@ -1893,10 +1890,9 @@ } }, "node_modules/@babel/runtime": { - "version": "7.26.9", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.9.tgz", - "integrity": "sha512-aA63XwOkcl4xxQa3HjPMqOP6LiK0ZDv3mUPYEFXkpHbaFjtGggE1A61FjFzJnB+p7/oy2gA8E+rcBNl/zC1tMg==", - "license": "MIT", + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.0.tgz", + "integrity": "sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==", "dependencies": { "regenerator-runtime": "^0.14.0" }, @@ -1905,30 +1901,28 @@ } }, "node_modules/@babel/template": { - "version": "7.26.9", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.26.9.tgz", - "integrity": "sha512-qyRplbeIpNZhmzOysF/wFMuP9sctmh2cFzRAZOn1YapxBsE1i9bJIY586R/WBLfLcmcBlM8ROBiQURnnNy+zfA==", - "license": "MIT", + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.0.tgz", + "integrity": "sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA==", "dependencies": { "@babel/code-frame": "^7.26.2", - "@babel/parser": "^7.26.9", - "@babel/types": "^7.26.9" + "@babel/parser": "^7.27.0", + "@babel/types": "^7.27.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.26.9", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.26.9.tgz", - "integrity": "sha512-ZYW7L+pL8ahU5fXmNbPF+iZFHCv5scFak7MZ9bwaRPLUhHh7QQEMjZUg0HevihoqCM5iSYHN61EyCoZvqC+bxg==", - "license": "MIT", + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.0.tgz", + "integrity": "sha512-19lYZFzYVQkkHkl4Cy4WrAVcqBkgvV2YM2TU3xG6DIwO7O3ecbDPfW3yM3bjAGcqcQHi+CCtjMR3dIEHxsd6bA==", "dependencies": { "@babel/code-frame": "^7.26.2", - "@babel/generator": "^7.26.9", - "@babel/parser": "^7.26.9", - "@babel/template": "^7.26.9", - "@babel/types": "^7.26.9", + "@babel/generator": "^7.27.0", + "@babel/parser": "^7.27.0", + "@babel/template": "^7.27.0", + "@babel/types": "^7.27.0", "debug": "^4.3.1", "globals": "^11.1.0" }, @@ -1946,10 +1940,9 @@ } }, "node_modules/@babel/types": { - "version": "7.26.9", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.9.tgz", - "integrity": "sha512-Y3IR1cRnOxOCDvMmNiym7XpXQ93iGDDPHx+Zj+NM+rg0fBaShfQLkg+hKPaZCEvg5N/LeCo4+Rj/i3FuJsIQaw==", - "license": "MIT", + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.0.tgz", + "integrity": "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==", "dependencies": { "@babel/helper-string-parser": "^7.25.9", "@babel/helper-validator-identifier": "^7.25.9" @@ -2134,13 +2127,12 @@ "license": "MIT" }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.0.tgz", - "integrity": "sha512-O7vun9Sf8DFjH2UtqK8Ku3LkquL9SZL8OLY1T5NZkA34+wG3OQF7cl4Ql8vdNzM6fzBbYfLaiRLIOZ+2FOCgBQ==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.2.tgz", + "integrity": "sha512-wCIboOL2yXZym2cgm6mlA742s9QeJ8DjGVaL39dLN4rRwrOgOyYSnOaFPhKZGLb2ngj4EyfAFjsNJwPXZvseag==", "cpu": [ "ppc64" ], - "license": "MIT", "optional": true, "os": [ "aix" @@ -2150,13 +2142,12 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.0.tgz", - "integrity": "sha512-PTyWCYYiU0+1eJKmw21lWtC+d08JDZPQ5g+kFyxP0V+es6VPPSUhM6zk8iImp2jbV6GwjX4pap0JFbUQN65X1g==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.2.tgz", + "integrity": "sha512-NQhH7jFstVY5x8CKbcfa166GoV0EFkaPkCKBQkdPJFvo5u+nGXLEH/ooniLb3QI8Fk58YAx7nsPLozUWfCBOJA==", "cpu": [ "arm" ], - "license": "MIT", "optional": true, "os": [ "android" @@ -2166,13 +2157,12 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.0.tgz", - "integrity": "sha512-grvv8WncGjDSyUBjN9yHXNt+cq0snxXbDxy5pJtzMKGmmpPxeAmAhWxXI+01lU5rwZomDgD3kJwulEnhTRUd6g==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.2.tgz", + "integrity": "sha512-5ZAX5xOmTligeBaeNEPnPaeEuah53Id2tX4c2CVP3JaROTH+j4fnfHCkr1PjXMd78hMst+TlkfKcW/DlTq0i4w==", "cpu": [ "arm64" ], - "license": "MIT", "optional": true, "os": [ "android" @@ -2182,13 +2172,12 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.0.tgz", - "integrity": "sha512-m/ix7SfKG5buCnxasr52+LI78SQ+wgdENi9CqyCXwjVR2X4Jkz+BpC3le3AoBPYTC9NHklwngVXvbJ9/Akhrfg==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.2.tgz", + "integrity": "sha512-Ffcx+nnma8Sge4jzddPHCZVRvIfQ0kMsUsCMcJRHkGJ1cDmhe4SsrYIjLUKn1xpHZybmOqCWwB0zQvsjdEHtkg==", "cpu": [ "x64" ], - "license": "MIT", "optional": true, "os": [ "android" @@ -2198,13 +2187,12 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.0.tgz", - "integrity": "sha512-mVwdUb5SRkPayVadIOI78K7aAnPamoeFR2bT5nszFUZ9P8UpK4ratOdYbZZXYSqPKMHfS1wdHCJk1P1EZpRdvw==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.2.tgz", + "integrity": "sha512-MpM6LUVTXAzOvN4KbjzU/q5smzryuoNjlriAIx+06RpecwCkL9JpenNzpKd2YMzLJFOdPqBpuub6eVRP5IgiSA==", "cpu": [ "arm64" ], - "license": "MIT", "optional": true, "os": [ "darwin" @@ -2214,13 +2202,12 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.0.tgz", - "integrity": "sha512-DgDaYsPWFTS4S3nWpFcMn/33ZZwAAeAFKNHNa1QN0rI4pUjgqf0f7ONmXf6d22tqTY+H9FNdgeaAa+YIFUn2Rg==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.2.tgz", + "integrity": "sha512-5eRPrTX7wFyuWe8FqEFPG2cU0+butQQVNcT4sVipqjLYQjjh8a8+vUTfgBKM88ObB85ahsnTwF7PSIt6PG+QkA==", "cpu": [ "x64" ], - "license": "MIT", "optional": true, "os": [ "darwin" @@ -2230,13 +2217,12 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.0.tgz", - "integrity": "sha512-VN4ocxy6dxefN1MepBx/iD1dH5K8qNtNe227I0mnTRjry8tj5MRk4zprLEdG8WPyAPb93/e4pSgi1SoHdgOa4w==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.2.tgz", + "integrity": "sha512-mLwm4vXKiQ2UTSX4+ImyiPdiHjiZhIaE9QvC7sw0tZ6HoNMjYAqQpGyui5VRIi5sGd+uWq940gdCbY3VLvsO1w==", "cpu": [ "arm64" ], - "license": "MIT", "optional": true, "os": [ "freebsd" @@ -2246,13 +2232,12 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.0.tgz", - "integrity": "sha512-mrSgt7lCh07FY+hDD1TxiTyIHyttn6vnjesnPoVDNmDfOmggTLXRv8Id5fNZey1gl/V2dyVK1VXXqVsQIiAk+A==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.2.tgz", + "integrity": "sha512-6qyyn6TjayJSwGpm8J9QYYGQcRgc90nmfdUb0O7pp1s4lTY+9D0H9O02v5JqGApUyiHOtkz6+1hZNvNtEhbwRQ==", "cpu": [ "x64" ], - "license": "MIT", "optional": true, "os": [ "freebsd" @@ -2262,13 +2247,12 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.0.tgz", - "integrity": "sha512-vkB3IYj2IDo3g9xX7HqhPYxVkNQe8qTK55fraQyTzTX/fxaDtXiEnavv9geOsonh2Fd2RMB+i5cbhu2zMNWJwg==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.2.tgz", + "integrity": "sha512-UHBRgJcmjJv5oeQF8EpTRZs/1knq6loLxTsjc3nxO9eXAPDLcWW55flrMVc97qFPbmZP31ta1AZVUKQzKTzb0g==", "cpu": [ "arm" ], - "license": "MIT", "optional": true, "os": [ "linux" @@ -2278,13 +2262,12 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.0.tgz", - "integrity": "sha512-9QAQjTWNDM/Vk2bgBl17yWuZxZNQIF0OUUuPZRKoDtqF2k4EtYbpyiG5/Dk7nqeK6kIJWPYldkOcBqjXjrUlmg==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.2.tgz", + "integrity": "sha512-gq/sjLsOyMT19I8obBISvhoYiZIAaGF8JpeXu1u8yPv8BE5HlWYobmlsfijFIZ9hIVGYkbdFhEqC0NvM4kNO0g==", "cpu": [ "arm64" ], - "license": "MIT", "optional": true, "os": [ "linux" @@ -2294,13 +2277,12 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.0.tgz", - "integrity": "sha512-43ET5bHbphBegyeqLb7I1eYn2P/JYGNmzzdidq/w0T8E2SsYL1U6un2NFROFRg1JZLTzdCoRomg8Rvf9M6W6Gg==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.2.tgz", + "integrity": "sha512-bBYCv9obgW2cBP+2ZWfjYTU+f5cxRoGGQ5SeDbYdFCAZpYWrfjjfYwvUpP8MlKbP0nwZ5gyOU/0aUzZ5HWPuvQ==", "cpu": [ "ia32" ], - "license": "MIT", "optional": true, "os": [ "linux" @@ -2310,13 +2292,12 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.0.tgz", - "integrity": "sha512-fC95c/xyNFueMhClxJmeRIj2yrSMdDfmqJnyOY4ZqsALkDrrKJfIg5NTMSzVBr5YW1jf+l7/cndBfP3MSDpoHw==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.2.tgz", + "integrity": "sha512-SHNGiKtvnU2dBlM5D8CXRFdd+6etgZ9dXfaPCeJtz+37PIUlixvlIhI23L5khKXs3DIzAn9V8v+qb1TRKrgT5w==", "cpu": [ "loong64" ], - "license": "MIT", "optional": true, "os": [ "linux" @@ -2326,13 +2307,12 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.0.tgz", - "integrity": "sha512-nkAMFju7KDW73T1DdH7glcyIptm95a7Le8irTQNO/qtkoyypZAnjchQgooFUDQhNAy4iu08N79W4T4pMBwhPwQ==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.2.tgz", + "integrity": "sha512-hDDRlzE6rPeoj+5fsADqdUZl1OzqDYow4TB4Y/3PlKBD0ph1e6uPHzIQcv2Z65u2K0kpeByIyAjCmjn1hJgG0Q==", "cpu": [ "mips64el" ], - "license": "MIT", "optional": true, "os": [ "linux" @@ -2342,13 +2322,12 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.0.tgz", - "integrity": "sha512-NhyOejdhRGS8Iwv+KKR2zTq2PpysF9XqY+Zk77vQHqNbo/PwZCzB5/h7VGuREZm1fixhs4Q/qWRSi5zmAiO4Fw==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.2.tgz", + "integrity": "sha512-tsHu2RRSWzipmUi9UBDEzc0nLc4HtpZEI5Ba+Omms5456x5WaNuiG3u7xh5AO6sipnJ9r4cRWQB2tUjPyIkc6g==", "cpu": [ "ppc64" ], - "license": "MIT", "optional": true, "os": [ "linux" @@ -2358,13 +2337,12 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.0.tgz", - "integrity": "sha512-5S/rbP5OY+GHLC5qXp1y/Mx//e92L1YDqkiBbO9TQOvuFXM+iDqUNG5XopAnXoRH3FjIUDkeGcY1cgNvnXp/kA==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.2.tgz", + "integrity": "sha512-k4LtpgV7NJQOml/10uPU0s4SAXGnowi5qBSjaLWMojNCUICNu7TshqHLAEbkBdAszL5TabfvQ48kK84hyFzjnw==", "cpu": [ "riscv64" ], - "license": "MIT", "optional": true, "os": [ "linux" @@ -2374,13 +2352,12 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.0.tgz", - "integrity": "sha512-XM2BFsEBz0Fw37V0zU4CXfcfuACMrppsMFKdYY2WuTS3yi8O1nFOhil/xhKTmE1nPmVyvQJjJivgDT+xh8pXJA==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.2.tgz", + "integrity": "sha512-GRa4IshOdvKY7M/rDpRR3gkiTNp34M0eLTaC1a08gNrh4u488aPhuZOCpkF6+2wl3zAN7L7XIpOFBhnaE3/Q8Q==", "cpu": [ "s390x" ], - "license": "MIT", "optional": true, "os": [ "linux" @@ -2390,13 +2367,12 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.0.tgz", - "integrity": "sha512-9yl91rHw/cpwMCNytUDxwj2XjFpxML0y9HAOH9pNVQDpQrBxHy01Dx+vaMu0N1CKa/RzBD2hB4u//nfc+Sd3Cw==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.2.tgz", + "integrity": "sha512-QInHERlqpTTZ4FRB0fROQWXcYRD64lAoiegezDunLpalZMjcUcld3YzZmVJ2H/Cp0wJRZ8Xtjtj0cEHhYc/uUg==", "cpu": [ "x64" ], - "license": "MIT", "optional": true, "os": [ "linux" @@ -2406,13 +2382,12 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.0.tgz", - "integrity": "sha512-RuG4PSMPFfrkH6UwCAqBzauBWTygTvb1nxWasEJooGSJ/NwRw7b2HOwyRTQIU97Hq37l3npXoZGYMy3b3xYvPw==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.2.tgz", + "integrity": "sha512-talAIBoY5M8vHc6EeI2WW9d/CkiO9MQJ0IOWX8hrLhxGbro/vBXJvaQXefW2cP0z0nQVTdQ/eNyGFV1GSKrxfw==", "cpu": [ "arm64" ], - "license": "MIT", "optional": true, "os": [ "netbsd" @@ -2422,13 +2397,12 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.0.tgz", - "integrity": "sha512-jl+qisSB5jk01N5f7sPCsBENCOlPiS/xptD5yxOx2oqQfyourJwIKLRA2yqWdifj3owQZCL2sn6o08dBzZGQzA==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.2.tgz", + "integrity": "sha512-voZT9Z+tpOxrvfKFyfDYPc4DO4rk06qamv1a/fkuzHpiVBMOhpjK+vBmWM8J1eiB3OLSMFYNaOaBNLXGChf5tg==", "cpu": [ "x64" ], - "license": "MIT", "optional": true, "os": [ "netbsd" @@ -2438,13 +2412,12 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.0.tgz", - "integrity": "sha512-21sUNbq2r84YE+SJDfaQRvdgznTD8Xc0oc3p3iW/a1EVWeNj/SdUCbm5U0itZPQYRuRTW20fPMWMpcrciH2EJw==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.2.tgz", + "integrity": "sha512-dcXYOC6NXOqcykeDlwId9kB6OkPUxOEqU+rkrYVqJbK2hagWOMrsTGsMr8+rW02M+d5Op5NNlgMmjzecaRf7Tg==", "cpu": [ "arm64" ], - "license": "MIT", "optional": true, "os": [ "openbsd" @@ -2454,13 +2427,12 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.0.tgz", - "integrity": "sha512-2gwwriSMPcCFRlPlKx3zLQhfN/2WjJ2NSlg5TKLQOJdV0mSxIcYNTMhk3H3ulL/cak+Xj0lY1Ym9ysDV1igceg==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.2.tgz", + "integrity": "sha512-t/TkWwahkH0Tsgoq1Ju7QfgGhArkGLkF1uYz8nQS/PPFlXbP5YgRpqQR3ARRiC2iXoLTWFxc6DJMSK10dVXluw==", "cpu": [ "x64" ], - "license": "MIT", "optional": true, "os": [ "openbsd" @@ -2470,13 +2442,12 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.0.tgz", - "integrity": "sha512-bxI7ThgLzPrPz484/S9jLlvUAHYMzy6I0XiU1ZMeAEOBcS0VePBFxh1JjTQt3Xiat5b6Oh4x7UC7IwKQKIJRIg==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.2.tgz", + "integrity": "sha512-cfZH1co2+imVdWCjd+D1gf9NjkchVhhdpgb1q5y6Hcv9TP6Zi9ZG/beI3ig8TvwT9lH9dlxLq5MQBBgwuj4xvA==", "cpu": [ "x64" ], - "license": "MIT", "optional": true, "os": [ "sunos" @@ -2486,13 +2457,12 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.0.tgz", - "integrity": "sha512-ZUAc2YK6JW89xTbXvftxdnYy3m4iHIkDtK3CLce8wg8M2L+YZhIvO1DKpxrd0Yr59AeNNkTiic9YLf6FTtXWMw==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.2.tgz", + "integrity": "sha512-7Loyjh+D/Nx/sOTzV8vfbB3GJuHdOQyrOryFdZvPHLf42Tk9ivBU5Aedi7iyX+x6rbn2Mh68T4qq1SDqJBQO5Q==", "cpu": [ "arm64" ], - "license": "MIT", "optional": true, "os": [ "win32" @@ -2502,13 +2472,12 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.0.tgz", - "integrity": "sha512-eSNxISBu8XweVEWG31/JzjkIGbGIJN/TrRoiSVZwZ6pkC6VX4Im/WV2cz559/TXLcYbcrDN8JtKgd9DJVIo8GA==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.2.tgz", + "integrity": "sha512-WRJgsz9un0nqZJ4MfhabxaD9Ft8KioqU3JMinOTvobbX6MOSUigSBlogP8QB3uxpJDsFS6yN+3FDBdqE5lg9kg==", "cpu": [ "ia32" ], - "license": "MIT", "optional": true, "os": [ "win32" @@ -2518,13 +2487,12 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.0.tgz", - "integrity": "sha512-ZENoHJBxA20C2zFzh6AI4fT6RraMzjYw4xKWemRTRmRVtN9c5DcH9r/f2ihEkMjOW5eGgrwCslG/+Y/3bL+DHQ==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.2.tgz", + "integrity": "sha512-kM3HKb16VIXZyIeVrM1ygYmZBKybX8N4p754bw390wGO3Tf2j4L2/WYL+4suWujpgf6GBYs3jv7TyUivdd05JA==", "cpu": [ "x64" ], - "license": "MIT", "optional": true, "os": [ "win32" @@ -2563,11 +2531,10 @@ } }, "node_modules/@eslint/config-array": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.2.tgz", - "integrity": "sha512-GNKqxfHG2ySmJOBSHg7LxeUx4xpuCoFjacmlCoYWEbaPXLwvfIjixRI12xCQZeULksQb23uiA8F40w5TojpV7w==", + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.0.tgz", + "integrity": "sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ==", "devOptional": true, - "license": "Apache-2.0", "dependencies": { "@eslint/object-schema": "^2.1.6", "debug": "^4.3.1", @@ -2582,7 +2549,6 @@ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "devOptional": true, - "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -2593,7 +2559,6 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "devOptional": true, - "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -2601,6 +2566,15 @@ "node": "*" } }, + "node_modules/@eslint/config-helpers": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.1.tgz", + "integrity": "sha512-RI17tsD2frtDu/3dmI7QRrD4bedNKPM08ziRYaC5AhkGrzIAJelm9kJU1TznK+apx6V+cqRz8tfpEeG3oIyjxw==", + "devOptional": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, "node_modules/@eslint/core": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.12.0.tgz", @@ -2615,11 +2589,10 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.0.tgz", - "integrity": "sha512-yaVPAiNAalnCZedKLdR21GOGILMLKPyqSLWaAjQFvYA2i/ciDi8ArYVr69Anohb6cH2Ukhqti4aFnYyPm8wdwQ==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", + "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", "devOptional": true, - "license": "MIT", "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", @@ -2643,7 +2616,6 @@ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "devOptional": true, - "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -2654,7 +2626,6 @@ "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", "devOptional": true, - "license": "MIT", "engines": { "node": ">=18" }, @@ -2667,7 +2638,6 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "devOptional": true, - "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -2676,11 +2646,10 @@ } }, "node_modules/@eslint/js": { - "version": "9.21.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.21.0.tgz", - "integrity": "sha512-BqStZ3HX8Yz6LvsF5ByXYrtigrV5AXADWLAGc7PH/1SxOb7/FIYYMszZZWiUou/GB9P2lXWk2SV4d+Z8h0nknw==", + "version": "9.24.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.24.0.tgz", + "integrity": "sha512-uIY/y3z0uvOGX8cp1C2fiC4+ZmBhp6yZWkojtHL1YEMnRt1Y63HB9TM17proGEmeG7HeUY+UP36F0aknKYTpYA==", "devOptional": true, - "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } @@ -2690,7 +2659,6 @@ "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", "devOptional": true, - "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } @@ -3397,9 +3365,9 @@ } }, "node_modules/@mui/core-downloads-tracker": { - "version": "6.4.7", - "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-6.4.7.tgz", - "integrity": "sha512-XjJrKFNt9zAKvcnoIIBquXyFyhfrHYuttqMsoDS7lM7VwufYG4fAPw4kINjBFg++fqXM2BNAuWR9J7XVIuKIKg==", + "version": "6.4.11", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-6.4.11.tgz", + "integrity": "sha512-CzAQs9CTzlwbsF9ZYB4o4lLwBv1/qNE264NjuYao+ctAXsmlPtYa8RtER4UsUXSMxNN9Qi+aQdYcKl2sUpnmAw==", "license": "MIT", "funding": { "type": "opencollective", @@ -3407,9 +3375,9 @@ } }, "node_modules/@mui/icons-material": { - "version": "6.4.6", - "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-6.4.6.tgz", - "integrity": "sha512-rGJBvIQQbQAlyKYljHQ8wAQS/K2/uYwvemcpygnAmCizmCI4zSF9HQPuiG8Ql4YLZ6V/uKjA3WHIYmF/8sV+pQ==", + "version": "6.4.7", + "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-6.4.7.tgz", + "integrity": "sha512-Rk8cs9ufQoLBw582Rdqq7fnSXXZTqhYRbpe1Y5SAz9lJKZP3CIdrj0PfG8HJLGw1hrsHFN/rkkm70IDzhJsG1g==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.26.0" @@ -3422,7 +3390,7 @@ "url": "https://opencollective.com/mui-org" }, "peerDependencies": { - "@mui/material": "^6.4.6", + "@mui/material": "^6.4.7", "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react": "^17.0.0 || ^18.0.0 || ^19.0.0" }, @@ -3473,6 +3441,119 @@ } } }, + "node_modules/@mui/lab/node_modules/@mui/private-theming": { + "version": "5.17.1", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.17.1.tgz", + "integrity": "sha512-XMxU0NTYcKqdsG8LRmSoxERPXwMbp16sIXPcLVgLGII/bVNagX0xaheWAwFv8+zDK7tI3ajllkuD3GZZE++ICQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.9", + "@mui/utils": "^5.17.1", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/lab/node_modules/@mui/styled-engine": { + "version": "5.16.14", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.16.14.tgz", + "integrity": "sha512-UAiMPZABZ7p8mUW4akDV6O7N3+4DatStpXMZwPlt+H/dA0lt67qawN021MNND+4QTpjaiMYxbhKZeQcyWCbuKw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.9", + "@emotion/cache": "^11.13.5", + "csstype": "^3.1.3", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.4.1", + "@emotion/styled": "^11.3.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + } + } + }, + "node_modules/@mui/lab/node_modules/@mui/system": { + "version": "5.17.1", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.17.1.tgz", + "integrity": "sha512-aJrmGfQpyF0U4D4xYwA6ueVtQcEMebET43CUmKMP7e7iFh3sMIF3sBR0l8Urb4pqx1CBjHAaWgB0ojpND4Q3Jg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.9", + "@mui/private-theming": "^5.17.1", + "@mui/styled-engine": "^5.16.14", + "@mui/types": "~7.2.15", + "@mui/utils": "^5.17.1", + "clsx": "^2.1.0", + "csstype": "^3.1.3", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/lab/node_modules/@mui/types": { + "version": "7.2.24", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.24.tgz", + "integrity": "sha512-3c8tRt/CbWZ+pEg7QpSwbdxOk36EfmhbKf6AGZsD1EcLDLTSZoxxJ86FVtcjxvjuhdyBiWKSTGZFaXCnidO2kw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@mui/material": { "version": "6.4.7", "resolved": "https://registry.npmjs.org/@mui/material/-/material-6.4.7.tgz", @@ -3522,14 +3603,58 @@ } } }, - "node_modules/@mui/material/node_modules/@mui/private-theming": { - "version": "6.4.6", - "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-6.4.6.tgz", - "integrity": "sha512-T5FxdPzCELuOrhpA2g4Pi6241HAxRwZudzAuL9vBvniuB5YU82HCmrARw32AuCiyTfWzbrYGGpZ4zyeqqp9RvQ==", + "node_modules/@mui/material/node_modules/@mui/types": { + "version": "7.2.24", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.24.tgz", + "integrity": "sha512-3c8tRt/CbWZ+pEg7QpSwbdxOk36EfmhbKf6AGZsD1EcLDLTSZoxxJ86FVtcjxvjuhdyBiWKSTGZFaXCnidO2kw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/material/node_modules/@mui/utils": { + "version": "6.4.9", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-6.4.9.tgz", + "integrity": "sha512-Y12Q9hbK9g+ZY0T3Rxrx9m2m10gaphDuUMgWxyV5kNJevVxXYCLclYUCC9vXaIk1/NdNDTcW2Yfr2OGvNFNmHg==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.26.0", - "@mui/utils": "^6.4.6", + "@mui/types": "~7.2.24", + "@types/prop-types": "^15.7.14", + "clsx": "^2.1.1", + "prop-types": "^15.8.1", + "react-is": "^19.0.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/private-theming": { + "version": "6.4.9", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-6.4.9.tgz", + "integrity": "sha512-LktcVmI5X17/Q5SkwjCcdOLBzt1hXuc14jYa7NPShog0GBDCDvKtcnP0V7a2s6EiVRlv7BzbWEJzH6+l/zaCxw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.26.0", + "@mui/utils": "^6.4.9", "prop-types": "^15.8.1" }, "engines": { @@ -3549,10 +3674,54 @@ } } }, - "node_modules/@mui/material/node_modules/@mui/styled-engine": { - "version": "6.4.6", - "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-6.4.6.tgz", - "integrity": "sha512-vSWYc9ZLX46be5gP+FCzWVn5rvDr4cXC5JBZwSIkYk9xbC7GeV+0kCvB8Q6XLFQJy+a62bbqtmdwS4Ghi9NBlQ==", + "node_modules/@mui/private-theming/node_modules/@mui/types": { + "version": "7.2.24", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.24.tgz", + "integrity": "sha512-3c8tRt/CbWZ+pEg7QpSwbdxOk36EfmhbKf6AGZsD1EcLDLTSZoxxJ86FVtcjxvjuhdyBiWKSTGZFaXCnidO2kw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/private-theming/node_modules/@mui/utils": { + "version": "6.4.9", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-6.4.9.tgz", + "integrity": "sha512-Y12Q9hbK9g+ZY0T3Rxrx9m2m10gaphDuUMgWxyV5kNJevVxXYCLclYUCC9vXaIk1/NdNDTcW2Yfr2OGvNFNmHg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.26.0", + "@mui/types": "~7.2.24", + "@types/prop-types": "^15.7.14", + "clsx": "^2.1.1", + "prop-types": "^15.8.1", + "react-is": "^19.0.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/styled-engine": { + "version": "6.4.11", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-6.4.11.tgz", + "integrity": "sha512-74AUmlHXaGNbyUqdK/+NwDJOZqgRQw6BcNvhoWYLq3LGbLTkE+khaJ7soz6cIabE4CPYqO2/QAIU1Z/HEjjpcw==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.26.0", @@ -3583,7 +3752,7 @@ } } }, - "node_modules/@mui/material/node_modules/@mui/system": { + "node_modules/@mui/system": { "version": "6.4.7", "resolved": "https://registry.npmjs.org/@mui/system/-/system-6.4.7.tgz", "integrity": "sha512-7wwc4++Ak6tGIooEVA9AY7FhH2p9fvBMORT4vNLMAysH3Yus/9B9RYMbrn3ANgsOyvT3Z7nE+SP8/+3FimQmcg==", @@ -3623,14 +3792,28 @@ } } }, - "node_modules/@mui/material/node_modules/@mui/utils": { - "version": "6.4.6", - "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-6.4.6.tgz", - "integrity": "sha512-43nZeE1pJF2anGafNydUcYFPtHwAqiBiauRtaMvurdrZI3YrUjHkAu43RBsxef7OFtJMXGiHFvq43kb7lig0sA==", + "node_modules/@mui/system/node_modules/@mui/types": { + "version": "7.2.24", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.24.tgz", + "integrity": "sha512-3c8tRt/CbWZ+pEg7QpSwbdxOk36EfmhbKf6AGZsD1EcLDLTSZoxxJ86FVtcjxvjuhdyBiWKSTGZFaXCnidO2kw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/system/node_modules/@mui/utils": { + "version": "6.4.9", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-6.4.9.tgz", + "integrity": "sha512-Y12Q9hbK9g+ZY0T3Rxrx9m2m10gaphDuUMgWxyV5kNJevVxXYCLclYUCC9vXaIk1/NdNDTcW2Yfr2OGvNFNmHg==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.26.0", - "@mui/types": "^7.2.21", + "@mui/types": "~7.2.24", "@types/prop-types": "^15.7.14", "clsx": "^2.1.1", "prop-types": "^15.8.1", @@ -3653,110 +3836,13 @@ } } }, - "node_modules/@mui/private-theming": { - "version": "5.16.14", - "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.16.14.tgz", - "integrity": "sha512-12t7NKzvYi819IO5IapW2BcR33wP/KAVrU8d7gLhGHoAmhDxyXlRoKiRij3TOD8+uzk0B6R9wHUNKi4baJcRNg==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.23.9", - "@mui/utils": "^5.16.14", - "prop-types": "^15.8.1" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui-org" - }, - "peerDependencies": { - "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", - "react": "^17.0.0 || ^18.0.0 || ^19.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@mui/styled-engine": { - "version": "5.16.14", - "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.16.14.tgz", - "integrity": "sha512-UAiMPZABZ7p8mUW4akDV6O7N3+4DatStpXMZwPlt+H/dA0lt67qawN021MNND+4QTpjaiMYxbhKZeQcyWCbuKw==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.23.9", - "@emotion/cache": "^11.13.5", - "csstype": "^3.1.3", - "prop-types": "^15.8.1" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui-org" - }, - "peerDependencies": { - "@emotion/react": "^11.4.1", - "@emotion/styled": "^11.3.0", - "react": "^17.0.0 || ^18.0.0 || ^19.0.0" - }, - "peerDependenciesMeta": { - "@emotion/react": { - "optional": true - }, - "@emotion/styled": { - "optional": true - } - } - }, - "node_modules/@mui/system": { - "version": "5.16.14", - "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.16.14.tgz", - "integrity": "sha512-KBxMwCb8mSIABnKvoGbvM33XHyT+sN0BzEBG+rsSc0lLQGzs7127KWkCA6/H8h6LZ00XpBEME5MAj8mZLiQ1tw==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.23.9", - "@mui/private-theming": "^5.16.14", - "@mui/styled-engine": "^5.16.14", - "@mui/types": "^7.2.15", - "@mui/utils": "^5.16.14", - "clsx": "^2.1.0", - "csstype": "^3.1.3", - "prop-types": "^15.8.1" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui-org" - }, - "peerDependencies": { - "@emotion/react": "^11.5.0", - "@emotion/styled": "^11.3.0", - "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", - "react": "^17.0.0 || ^18.0.0 || ^19.0.0" - }, - "peerDependenciesMeta": { - "@emotion/react": { - "optional": true - }, - "@emotion/styled": { - "optional": true - }, - "@types/react": { - "optional": true - } - } - }, "node_modules/@mui/types": { - "version": "7.2.21", - "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.21.tgz", - "integrity": "sha512-6HstngiUxNqLU+/DPqlUJDIPbzUBxIVHb1MmXP0eTWDIROiCR2viugXpEif0PPe2mLqqakPzzRClWAnK+8UJww==", - "license": "MIT", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.4.0.tgz", + "integrity": "sha512-TxJ4ezEeedWHBjOmLtxI203a9DII9l4k83RXmz1PYSAmnyEcK2PglTNmJGxswC/wM5cdl9ap2h8lnXvt2swAGQ==", + "dependencies": { + "@babel/runtime": "^7.26.10" + }, "peerDependencies": { "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0" }, @@ -3767,13 +3853,13 @@ } }, "node_modules/@mui/utils": { - "version": "5.16.14", - "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.16.14.tgz", - "integrity": "sha512-wn1QZkRzSmeXD1IguBVvJJHV3s6rxJrfb6YuC9Kk6Noh9f8Fb54nUs5JRkKm+BOerRhj5fLg05Dhx/H3Ofb8Mg==", + "version": "5.17.1", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.17.1.tgz", + "integrity": "sha512-jEZ8FTqInt2WzxDV8bhImWBqeQRD99c/id/fq83H0ER9tFl+sfZlaAoCdznGvbSQQ9ividMxqSV2c7cC1vBcQg==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.23.9", - "@mui/types": "^7.2.15", + "@mui/types": "~7.2.15", "@types/prop-types": "^15.7.12", "clsx": "^2.1.1", "prop-types": "^15.8.1", @@ -3796,12 +3882,25 @@ } } }, + "node_modules/@mui/utils/node_modules/@mui/types": { + "version": "7.2.24", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.24.tgz", + "integrity": "sha512-3c8tRt/CbWZ+pEg7QpSwbdxOk36EfmhbKf6AGZsD1EcLDLTSZoxxJ86FVtcjxvjuhdyBiWKSTGZFaXCnidO2kw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", "dev": true, - "license": "MIT", "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" @@ -3815,7 +3914,6 @@ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", "dev": true, - "license": "MIT", "engines": { "node": ">= 8" } @@ -3825,7 +3923,6 @@ "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", "dev": true, - "license": "MIT", "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" @@ -4131,15 +4228,14 @@ "license": "MIT" }, "node_modules/@swc/core": { - "version": "1.11.5", - "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.11.5.tgz", - "integrity": "sha512-EVY7zfpehxhTZXOfy508gb3D78ihoGGmvyiTWtlBPjgIaidP1Xw0naHMD78CWiFlZmeDjKXJufGtsEGOnZdmNA==", + "version": "1.11.18", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.11.18.tgz", + "integrity": "sha512-ORZxyCKKiqYt2iHdh1C7pfVR1GBjkuFOdwqZggQzaq0vt22DpGca+2JsUtkUoWQmWcct04v5+ScwgvsHuMObxA==", "devOptional": true, "hasInstallScript": true, - "license": "Apache-2.0", "dependencies": { "@swc/counter": "^0.1.3", - "@swc/types": "^0.1.19" + "@swc/types": "^0.1.21" }, "engines": { "node": ">=10" @@ -4149,16 +4245,16 @@ "url": "https://opencollective.com/swc" }, "optionalDependencies": { - "@swc/core-darwin-arm64": "1.11.5", - "@swc/core-darwin-x64": "1.11.5", - "@swc/core-linux-arm-gnueabihf": "1.11.5", - "@swc/core-linux-arm64-gnu": "1.11.5", - "@swc/core-linux-arm64-musl": "1.11.5", - "@swc/core-linux-x64-gnu": "1.11.5", - "@swc/core-linux-x64-musl": "1.11.5", - "@swc/core-win32-arm64-msvc": "1.11.5", - "@swc/core-win32-ia32-msvc": "1.11.5", - "@swc/core-win32-x64-msvc": "1.11.5" + "@swc/core-darwin-arm64": "1.11.18", + "@swc/core-darwin-x64": "1.11.18", + "@swc/core-linux-arm-gnueabihf": "1.11.18", + "@swc/core-linux-arm64-gnu": "1.11.18", + "@swc/core-linux-arm64-musl": "1.11.18", + "@swc/core-linux-x64-gnu": "1.11.18", + "@swc/core-linux-x64-musl": "1.11.18", + "@swc/core-win32-arm64-msvc": "1.11.18", + "@swc/core-win32-ia32-msvc": "1.11.18", + "@swc/core-win32-x64-msvc": "1.11.18" }, "peerDependencies": { "@swc/helpers": "*" @@ -4170,13 +4266,12 @@ } }, "node_modules/@swc/core-darwin-arm64": { - "version": "1.11.5", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.11.5.tgz", - "integrity": "sha512-GEd1hzEx0mSGkJYMFMGLnrGgjL2rOsOsuYWyjyiA3WLmhD7o+n/EWBDo6mzD/9aeF8dzSPC0TnW216gJbvrNzA==", + "version": "1.11.18", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.11.18.tgz", + "integrity": "sha512-K6AntdUlNMQg8aChqjeXwnVhK6d4WRZ9TgtLSTmdU0Ugll4an7QK49s9NrT7XQU91cEsVvzdr++p1bNImx0hJg==", "cpu": [ "arm64" ], - "license": "Apache-2.0 AND MIT", "optional": true, "os": [ "darwin" @@ -4186,13 +4281,12 @@ } }, "node_modules/@swc/core-darwin-x64": { - "version": "1.11.5", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.11.5.tgz", - "integrity": "sha512-toz04z9wAClVvQSEY3xzrgyyeWBAfMWcKG4K0ugNvO56h/wczi2ZHRlnAXZW1tghKBk3z6MXqa/srfXgNhffKw==", + "version": "1.11.18", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.11.18.tgz", + "integrity": "sha512-RCRvC6Q9M5BArTvj/IzUAAYGrgxYFbTTnAtf6UX7JFq2DAn+hEwYUjmC1m0gFso9HqFU0m5QZUGfZvVmACGWUw==", "cpu": [ "x64" ], - "license": "Apache-2.0 AND MIT", "optional": true, "os": [ "darwin" @@ -4202,13 +4296,12 @@ } }, "node_modules/@swc/core-linux-arm-gnueabihf": { - "version": "1.11.5", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.11.5.tgz", - "integrity": "sha512-5SjmKxXdwbBpsYGTpgeXOXMIjS563/ntRGn8Zc12H/c4VfPrRLGhgbJ/48z2XVFyBLcw7BCHZyFuVX1+ZI3W0Q==", + "version": "1.11.18", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.11.18.tgz", + "integrity": "sha512-wteAKf8YKb3jOnZFm3EzuIMzzCVXMuQOLHsz1IgEOc44/gdgNXKxaYTWAowZuej7t68tf/w0cRNMc7Le414v/g==", "cpu": [ "arm" ], - "license": "Apache-2.0", "optional": true, "os": [ "linux" @@ -4218,13 +4311,12 @@ } }, "node_modules/@swc/core-linux-arm64-gnu": { - "version": "1.11.5", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.11.5.tgz", - "integrity": "sha512-pydIlInHRzRIwB0NHblz3Dx58H/bsi0I5F2deLf9iOmwPNuOGcEEZF1Qatc7YIjP5DFbXK+Dcz+pMUZb2cc2MQ==", + "version": "1.11.18", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.11.18.tgz", + "integrity": "sha512-hY6jJYZ6PKHSBo5OATswfyKsUgsWu9+4nDcN8liYIRRgz3E0G9wk0VUTP4cFPivBFeHWTTAGz687/Nf2aQEIpw==", "cpu": [ "arm64" ], - "license": "Apache-2.0 AND MIT", "optional": true, "os": [ "linux" @@ -4234,13 +4326,12 @@ } }, "node_modules/@swc/core-linux-arm64-musl": { - "version": "1.11.5", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.11.5.tgz", - "integrity": "sha512-LhBHKjkZq5tJF1Lh0NJFpx7ROnCWLckrlIAIdSt9XfOV+zuEXJQOj+NFcM1eNk17GFfFyUMOZyGZxzYq5dveEQ==", + "version": "1.11.18", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.11.18.tgz", + "integrity": "sha512-slu0mlP2nucvQalttnapfpqpD/LlM9NHx9g3ofgsLzjObyMEBiX4ZysQ3y65U8Mjw71RNqtLd/ZmvxI6OmLdiQ==", "cpu": [ "arm64" ], - "license": "Apache-2.0 AND MIT", "optional": true, "os": [ "linux" @@ -4250,13 +4341,12 @@ } }, "node_modules/@swc/core-linux-x64-gnu": { - "version": "1.11.5", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.11.5.tgz", - "integrity": "sha512-dCi4xkxXlsk5sQYb3i413Cfh7+wMJeBYTvBZTD5xh+/DgRtIcIJLYJ2tNjWC4/C2i5fj+Ze9bKNSdd8weRWZ3A==", + "version": "1.11.18", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.11.18.tgz", + "integrity": "sha512-h9a/8PA25arMCQ9t8CE8rA1s0c77z4kCZZ7dUuUkD88yEXIrARMca1IKR7of+S3slfQrf1Zlq3Ac1Fb1HVJziQ==", "cpu": [ "x64" ], - "license": "Apache-2.0 AND MIT", "optional": true, "os": [ "linux" @@ -4266,13 +4356,12 @@ } }, "node_modules/@swc/core-linux-x64-musl": { - "version": "1.11.5", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.11.5.tgz", - "integrity": "sha512-K0AC4TreM5Oo/tXNXnE/Gf5+5y/HwUdd7xvUjOpZddcX/RlsbYOKWLgOtA3fdFIuta7XC+vrGKmIhm5l70DSVQ==", + "version": "1.11.18", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.11.18.tgz", + "integrity": "sha512-0sMDJj5qUGK9QEw4lrxLxkTP/4AoKciqNzXvqbk+J9XuXN2aIv4BsR1Y7z3GwAeMFGsba2lbHLOtJlDsaqIsiA==", "cpu": [ "x64" ], - "license": "Apache-2.0 AND MIT", "optional": true, "os": [ "linux" @@ -4282,13 +4371,12 @@ } }, "node_modules/@swc/core-win32-arm64-msvc": { - "version": "1.11.5", - "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.11.5.tgz", - "integrity": "sha512-wzum8sYUsvPY7kgUfuqVYTgIPYmBC8KPksoNM1fz5UfhudU0ciQuYvUBD47GIGOevaoxhLkjPH4CB95vh1mJ9w==", + "version": "1.11.18", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.11.18.tgz", + "integrity": "sha512-zGv9HnfgBcKyt54MJRWdwRNu9BuYkAFM7bx+tWtKhd37Ef7ZX20QLs9xXl5wWDXCbsOdRxXIZgXs6PEL+Pzmrw==", "cpu": [ "arm64" ], - "license": "Apache-2.0 AND MIT", "optional": true, "os": [ "win32" @@ -4298,13 +4386,12 @@ } }, "node_modules/@swc/core-win32-ia32-msvc": { - "version": "1.11.5", - "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.11.5.tgz", - "integrity": "sha512-lco7mw0TPRTpVPR6NwggJpjdUkAboGRkLrDHjIsUaR+Y5+0m5FMMkHOMxWXAbrBS5c4ph7QErp4Lma4r9Mn5og==", + "version": "1.11.18", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.11.18.tgz", + "integrity": "sha512-uBKj0S1lYv/E2ZhxHZOxSiQwoegYmzbPRpjq6eHBZDv97mu7W3K27/lsnPbvAfQ6b6rnv8BI+EsmJ7VLQBAHBQ==", "cpu": [ "ia32" ], - "license": "Apache-2.0 AND MIT", "optional": true, "os": [ "win32" @@ -4314,13 +4401,12 @@ } }, "node_modules/@swc/core-win32-x64-msvc": { - "version": "1.11.5", - "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.11.5.tgz", - "integrity": "sha512-E+DApLSC6JRK8VkDa4bNsBdD7Qoomx1HvKVZpOXl9v94hUZI5GMExl4vU5isvb+hPWL7rZ0NeI7ITnVLgLJRbA==", + "version": "1.11.18", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.11.18.tgz", + "integrity": "sha512-8USTRcdgeFMNBgvVXl8tz6n4+9s9m+zHsfDeBT4jPgwnq2bnLBlTUlwnPwzDxfg9nUJr6RFD4xeKfWyZZRosZg==", "cpu": [ "x64" ], - "license": "Apache-2.0 AND MIT", "optional": true, "os": [ "win32" @@ -4333,15 +4419,13 @@ "version": "0.1.3", "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", - "devOptional": true, - "license": "Apache-2.0" + "devOptional": true }, "node_modules/@swc/types": { - "version": "0.1.19", - "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.19.tgz", - "integrity": "sha512-WkAZaAfj44kh/UFdAQcrMP1I0nwRqpt27u+08LMBYMqmQfwwMofYoMh/48NGkMMRfC4ynpfwRbJuu8ErfNloeA==", + "version": "0.1.21", + "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.21.tgz", + "integrity": "sha512-2YEtj5HJVbKivud9N4bpPBAyZhj4S2Ipe5LkUG94alTpr7in/GU/EARgPAd3BwU+YOmFVJC2+kjqhGRi3r0ZpQ==", "devOptional": true, - "license": "Apache-2.0", "dependencies": { "@swc/counter": "^0.1.3" } @@ -4371,7 +4455,6 @@ "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.6.3.tgz", "integrity": "sha512-IteBhl4XqYNkM54f4ejhLRJiZNqcSCoXUOG2CPK7qbD322KjQozM4kHQOfkG2oln9b9HTYqs+Sae8vBATubxxA==", "dev": true, - "license": "MIT", "dependencies": { "@adobe/css-tools": "^4.4.0", "aria-query": "^5.0.0", @@ -4409,11 +4492,10 @@ "license": "MIT" }, "node_modules/@testing-library/react": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.2.0.tgz", - "integrity": "sha512-2cSskAvA1QNtKc8Y9VJQRv0tm3hLVgxRGDB+KYhIaPQJ1I+RHbhIXcM+zClKXzMes/wshsMVzf4B9vS4IZpqDQ==", + "version": "16.3.0", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.0.tgz", + "integrity": "sha512-kFSyxiEDwv1WLl2fgsq6pPBbw5aWKrsY2/noi1Id0TK0UParSF62oFQFGHXIyaG4pp2tEub/Zlel+fjjZILDsw==", "dev": true, - "license": "MIT", "dependencies": { "@babel/runtime": "^7.12.5" }, @@ -4436,6 +4518,19 @@ } } }, + "node_modules/@testing-library/user-event": { + "version": "14.6.1", + "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.6.1.tgz", + "integrity": "sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==", + "dev": true, + "engines": { + "node": ">=12", + "npm": ">=6" + }, + "peerDependencies": { + "@testing-library/dom": ">=7.21.4" + } + }, "node_modules/@tootallnate/once": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", @@ -4665,12 +4760,11 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "22.13.5", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.5.tgz", - "integrity": "sha512-+lTU0PxZXn0Dr1NBtC7Y8cR21AJr87dLLU953CWA6pMxxv/UDc7jYAY90upcrie1nRcD6XNG5HOYEDtgW5TxAg==", - "license": "MIT", + "version": "22.14.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.14.0.tgz", + "integrity": "sha512-Kmpl+z84ILoG+3T/zQFyAJsU6EPTmOCj8/2+83fSN6djd6I4o7uOuGIH6vq3PrjY5BGitSbFuMN18j3iknubbA==", "dependencies": { - "undici-types": "~6.20.0" + "undici-types": "~6.21.0" } }, "node_modules/@types/parse-json": { @@ -4771,17 +4865,16 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.25.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.25.0.tgz", - "integrity": "sha512-VM7bpzAe7JO/BFf40pIT1lJqS/z1F8OaSsUB3rpFJucQA4cOSuH2RVVVkFULN+En0Djgr29/jb4EQnedUo95KA==", + "version": "8.29.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.29.1.tgz", + "integrity": "sha512-ba0rr4Wfvg23vERs3eB+P3lfj2E+2g3lhWcCVukUuhtcdUx5lSIFZlGFEBHKr+3zizDa/TvZTptdNHVZWAkSBg==", "dev": true, - "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.25.0", - "@typescript-eslint/type-utils": "8.25.0", - "@typescript-eslint/utils": "8.25.0", - "@typescript-eslint/visitor-keys": "8.25.0", + "@typescript-eslint/scope-manager": "8.29.1", + "@typescript-eslint/type-utils": "8.29.1", + "@typescript-eslint/utils": "8.29.1", + "@typescript-eslint/visitor-keys": "8.29.1", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", @@ -4797,20 +4890,19 @@ "peerDependencies": { "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.8.0" + "typescript": ">=4.8.4 <5.9.0" } }, "node_modules/@typescript-eslint/parser": { - "version": "8.25.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.25.0.tgz", - "integrity": "sha512-4gbs64bnbSzu4FpgMiQ1A+D+urxkoJk/kqlDJ2W//5SygaEiAP2B4GoS7TEdxgwol2el03gckFV9lJ4QOMiiHg==", + "version": "8.29.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.29.1.tgz", + "integrity": "sha512-zczrHVEqEaTwh12gWBIJWj8nx+ayDcCJs06yoNMY0kwjMWDM6+kppljY+BxWI06d2Ja+h4+WdufDcwMnnMEWmg==", "dev": true, - "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.25.0", - "@typescript-eslint/types": "8.25.0", - "@typescript-eslint/typescript-estree": "8.25.0", - "@typescript-eslint/visitor-keys": "8.25.0", + "@typescript-eslint/scope-manager": "8.29.1", + "@typescript-eslint/types": "8.29.1", + "@typescript-eslint/typescript-estree": "8.29.1", + "@typescript-eslint/visitor-keys": "8.29.1", "debug": "^4.3.4" }, "engines": { @@ -4822,18 +4914,17 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.8.0" + "typescript": ">=4.8.4 <5.9.0" } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.25.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.25.0.tgz", - "integrity": "sha512-6PPeiKIGbgStEyt4NNXa2ru5pMzQ8OYKO1hX1z53HMomrmiSB+R5FmChgQAP1ro8jMtNawz+TRQo/cSXrauTpg==", + "version": "8.29.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.29.1.tgz", + "integrity": "sha512-2nggXGX5F3YrsGN08pw4XpMLO1Rgtnn4AzTegC2MDesv6q3QaTU5yU7IbS1tf1IwCR0Hv/1EFygLn9ms6LIpDA==", "dev": true, - "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.25.0", - "@typescript-eslint/visitor-keys": "8.25.0" + "@typescript-eslint/types": "8.29.1", + "@typescript-eslint/visitor-keys": "8.29.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4844,14 +4935,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.25.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.25.0.tgz", - "integrity": "sha512-d77dHgHWnxmXOPJuDWO4FDWADmGQkN5+tt6SFRZz/RtCWl4pHgFl3+WdYCn16+3teG09DY6XtEpf3gGD0a186g==", + "version": "8.29.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.29.1.tgz", + "integrity": "sha512-DkDUSDwZVCYN71xA4wzySqqcZsHKic53A4BLqmrWFFpOpNSoxX233lwGu/2135ymTCR04PoKiEEEvN1gFYg4Tw==", "dev": true, - "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "8.25.0", - "@typescript-eslint/utils": "8.25.0", + "@typescript-eslint/typescript-estree": "8.29.1", + "@typescript-eslint/utils": "8.29.1", "debug": "^4.3.4", "ts-api-utils": "^2.0.1" }, @@ -4864,15 +4954,14 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.8.0" + "typescript": ">=4.8.4 <5.9.0" } }, "node_modules/@typescript-eslint/types": { - "version": "8.25.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.25.0.tgz", - "integrity": "sha512-+vUe0Zb4tkNgznQwicsvLUJgZIRs6ITeWSCclX1q85pR1iOiaj+4uZJIUp//Z27QWu5Cseiw3O3AR8hVpax7Aw==", + "version": "8.29.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.29.1.tgz", + "integrity": "sha512-VT7T1PuJF1hpYC3AGm2rCgJBjHL3nc+A/bhOp9sGMKfi5v0WufsX/sHCFBfNTx2F+zA6qBc/PD0/kLRLjdt8mQ==", "dev": true, - "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, @@ -4882,14 +4971,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.25.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.25.0.tgz", - "integrity": "sha512-ZPaiAKEZ6Blt/TPAx5Ot0EIB/yGtLI2EsGoY6F7XKklfMxYQyvtL+gT/UCqkMzO0BVFHLDlzvFqQzurYahxv9Q==", + "version": "8.29.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.29.1.tgz", + "integrity": "sha512-l1enRoSaUkQxOQnbi0KPUtqeZkSiFlqrx9/3ns2rEDhGKfTa+88RmXqedC1zmVTOWrLc2e6DEJrTA51C9iLH5g==", "dev": true, - "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.25.0", - "@typescript-eslint/visitor-keys": "8.25.0", + "@typescript-eslint/types": "8.29.1", + "@typescript-eslint/visitor-keys": "8.29.1", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -4905,7 +4993,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=4.8.4 <5.8.0" + "typescript": ">=4.8.4 <5.9.0" } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { @@ -4913,7 +5001,6 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", "dev": true, - "license": "ISC", "bin": { "semver": "bin/semver.js" }, @@ -4922,16 +5009,15 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.25.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.25.0.tgz", - "integrity": "sha512-syqRbrEv0J1wywiLsK60XzHnQe/kRViI3zwFALrNEgnntn1l24Ra2KvOAWwWbWZ1lBZxZljPDGOq967dsl6fkA==", + "version": "8.29.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.29.1.tgz", + "integrity": "sha512-QAkFEbytSaB8wnmB+DflhUPz6CLbFWE2SnSCrRMEa+KnXIzDYbpsn++1HGvnfAsUY44doDXmvRkO5shlM/3UfA==", "dev": true, - "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.25.0", - "@typescript-eslint/types": "8.25.0", - "@typescript-eslint/typescript-estree": "8.25.0" + "@typescript-eslint/scope-manager": "8.29.1", + "@typescript-eslint/types": "8.29.1", + "@typescript-eslint/typescript-estree": "8.29.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4942,17 +5028,16 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.8.0" + "typescript": ">=4.8.4 <5.9.0" } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.25.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.25.0.tgz", - "integrity": "sha512-kCYXKAum9CecGVHGij7muybDfTS2sD3t0L4bJsEZLkyrXUImiCTq1M3LG2SRtOhiHFwMR9wAFplpT6XHYjTkwQ==", + "version": "8.29.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.29.1.tgz", + "integrity": "sha512-RGLh5CRaUEf02viP5c1Vh1cMGffQscyHe7HPAzGpfmfflFg1wUz2rYxd+OZqwpeypYvZ8UxSxuIpF++fmOzEcg==", "dev": true, - "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.25.0", + "@typescript-eslint/types": "8.29.1", "eslint-visitor-keys": "^4.2.0" }, "engines": { @@ -4968,7 +5053,6 @@ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", "dev": true, - "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, @@ -4977,13 +5061,12 @@ } }, "node_modules/@vitejs/plugin-react-swc": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react-swc/-/plugin-react-swc-3.8.0.tgz", - "integrity": "sha512-T4sHPvS+DIqDP51ifPqa9XIRAz/kIvIi8oXcnOZZgHmMotgmmdxe/DD5tMFlt5nuIRzT0/QuiwmKlH0503Aapw==", + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react-swc/-/plugin-react-swc-3.8.1.tgz", + "integrity": "sha512-aEUPCckHDcFyxpwFm0AIkbtv6PpUp3xTb9wYGFjtABynXjCYKkWoxX0AOK9NT9XCrdk6mBBUOeHQS+RKdcNO1A==", "dev": true, - "license": "MIT", "dependencies": { - "@swc/core": "^1.10.15" + "@swc/core": "^1.11.11" }, "peerDependencies": { "vite": "^4 || ^5 || ^6" @@ -5023,7 +5106,6 @@ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "devOptional": true, - "license": "MIT", "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } @@ -5057,7 +5139,6 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "devOptional": true, - "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -5147,8 +5228,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "devOptional": true, - "license": "Python-2.0" + "devOptional": true }, "node_modules/aria-query": { "version": "5.3.0", @@ -5336,10 +5416,9 @@ } }, "node_modules/axios": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.1.tgz", - "integrity": "sha512-NN+fvwH/kV01dYUQ3PTOZns4LWtWhOFCAhQ/pHb88WQ1hNe5V/dvFwc4VJcDL11LT9xSX0QtsR8sWUuyOuOq7g==", - "license": "MIT", + "version": "1.8.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.4.tgz", + "integrity": "sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==", "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.0", @@ -5654,14 +5733,13 @@ } }, "node_modules/call-bound": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.3.tgz", - "integrity": "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", "dev": true, - "license": "MIT", "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "get-intrinsic": "^1.2.6" + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" }, "engines": { "node": ">= 0.4" @@ -6291,10 +6369,9 @@ } }, "node_modules/dompurify": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.4.tgz", - "integrity": "sha512-ysFSFEDVduQpyhzAob/kkuJjf5zWkZD8/A9ywSp1byueyuCfHamrCBa14/Oc2iiB0e51B+NpxSl5gmzn+Ms/mg==", - "license": "(MPL-2.0 OR Apache-2.0)", + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.5.tgz", + "integrity": "sha512-mLPd29uoRe9HpvwP2TxClGQBzGXeEC/we/q+bFlmPPmj2p2Ugl3r6ATu/UU1v77DXNcehiBg9zsr1dREyA/dJQ==", "optionalDependencies": { "@types/trusted-types": "^2.0.7" } @@ -6608,11 +6685,10 @@ } }, "node_modules/esbuild": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.0.tgz", - "integrity": "sha512-BXq5mqc8ltbaN34cDqWuYKyNhX8D/Z0J1xdtdQ8UcIIIyJyz+ZMKUt58tF3SrZ85jcfN/PZYhjR5uDQAYNVbuw==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.2.tgz", + "integrity": "sha512-16854zccKPnC+toMywC+uKNeYSv+/eXkevRAfwRD/G9Cleq66m8XFIrigkbvauLLlCfDL45Q2cWegSg53gGBnQ==", "hasInstallScript": true, - "license": "MIT", "bin": { "esbuild": "bin/esbuild" }, @@ -6620,31 +6696,31 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.0", - "@esbuild/android-arm": "0.25.0", - "@esbuild/android-arm64": "0.25.0", - "@esbuild/android-x64": "0.25.0", - "@esbuild/darwin-arm64": "0.25.0", - "@esbuild/darwin-x64": "0.25.0", - "@esbuild/freebsd-arm64": "0.25.0", - "@esbuild/freebsd-x64": "0.25.0", - "@esbuild/linux-arm": "0.25.0", - "@esbuild/linux-arm64": "0.25.0", - "@esbuild/linux-ia32": "0.25.0", - "@esbuild/linux-loong64": "0.25.0", - "@esbuild/linux-mips64el": "0.25.0", - "@esbuild/linux-ppc64": "0.25.0", - "@esbuild/linux-riscv64": "0.25.0", - "@esbuild/linux-s390x": "0.25.0", - "@esbuild/linux-x64": "0.25.0", - "@esbuild/netbsd-arm64": "0.25.0", - "@esbuild/netbsd-x64": "0.25.0", - "@esbuild/openbsd-arm64": "0.25.0", - "@esbuild/openbsd-x64": "0.25.0", - "@esbuild/sunos-x64": "0.25.0", - "@esbuild/win32-arm64": "0.25.0", - "@esbuild/win32-ia32": "0.25.0", - "@esbuild/win32-x64": "0.25.0" + "@esbuild/aix-ppc64": "0.25.2", + "@esbuild/android-arm": "0.25.2", + "@esbuild/android-arm64": "0.25.2", + "@esbuild/android-x64": "0.25.2", + "@esbuild/darwin-arm64": "0.25.2", + "@esbuild/darwin-x64": "0.25.2", + "@esbuild/freebsd-arm64": "0.25.2", + "@esbuild/freebsd-x64": "0.25.2", + "@esbuild/linux-arm": "0.25.2", + "@esbuild/linux-arm64": "0.25.2", + "@esbuild/linux-ia32": "0.25.2", + "@esbuild/linux-loong64": "0.25.2", + "@esbuild/linux-mips64el": "0.25.2", + "@esbuild/linux-ppc64": "0.25.2", + "@esbuild/linux-riscv64": "0.25.2", + "@esbuild/linux-s390x": "0.25.2", + "@esbuild/linux-x64": "0.25.2", + "@esbuild/netbsd-arm64": "0.25.2", + "@esbuild/netbsd-x64": "0.25.2", + "@esbuild/openbsd-arm64": "0.25.2", + "@esbuild/openbsd-x64": "0.25.2", + "@esbuild/sunos-x64": "0.25.2", + "@esbuild/win32-arm64": "0.25.2", + "@esbuild/win32-ia32": "0.25.2", + "@esbuild/win32-x64": "0.25.2" } }, "node_modules/escalade": { @@ -6701,18 +6777,18 @@ } }, "node_modules/eslint": { - "version": "9.21.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.21.0.tgz", - "integrity": "sha512-KjeihdFqTPhOMXTt7StsDxriV4n66ueuF/jfPNC3j/lduHwr/ijDwJMsF+wyMJethgiKi5wniIE243vi07d3pg==", + "version": "9.24.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.24.0.tgz", + "integrity": "sha512-eh/jxIEJyZrvbWRe4XuVclLPDYSYYYgLy5zXGGxD6j8zjSAxFEzI2fL/8xNq6O2yKqVt+eF2YhV+hxjV6UKXwQ==", "devOptional": true, - "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.19.2", + "@eslint/config-array": "^0.20.0", + "@eslint/config-helpers": "^0.2.0", "@eslint/core": "^0.12.0", - "@eslint/eslintrc": "^3.3.0", - "@eslint/js": "9.21.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.24.0", "@eslint/plugin-kit": "^0.2.7", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", @@ -6724,7 +6800,7 @@ "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.2.0", + "eslint-scope": "^8.3.0", "eslint-visitor-keys": "^4.2.0", "espree": "^10.3.0", "esquery": "^1.5.0", @@ -6817,11 +6893,10 @@ } }, "node_modules/eslint-plugin-react": { - "version": "7.37.4", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.4.tgz", - "integrity": "sha512-BGP0jRmfYyvOyvMoRX/uoUeW+GqNj9y16bPQzqAHf3AYII/tDs+jMN0dBVkl88/OZwNGwrVFxE7riHsXVfy/LQ==", + "version": "7.37.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz", + "integrity": "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==", "dev": true, - "license": "MIT", "dependencies": { "array-includes": "^3.1.8", "array.prototype.findlast": "^1.2.5", @@ -6833,7 +6908,7 @@ "hasown": "^2.0.2", "jsx-ast-utils": "^2.4.1 || ^3.0.0", "minimatch": "^3.1.2", - "object.entries": "^1.1.8", + "object.entries": "^1.1.9", "object.fromentries": "^2.0.8", "object.values": "^1.2.1", "prop-types": "^15.8.1", @@ -6931,11 +7006,10 @@ } }, "node_modules/eslint-scope": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.2.0.tgz", - "integrity": "sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.3.0.tgz", + "integrity": "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==", "devOptional": true, - "license": "BSD-2-Clause", "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" @@ -7002,7 +7076,6 @@ "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", "devOptional": true, - "license": "BSD-2-Clause", "dependencies": { "acorn": "^8.14.0", "acorn-jsx": "^5.3.2", @@ -7020,7 +7093,6 @@ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", "devOptional": true, - "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, @@ -7059,7 +7131,6 @@ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "devOptional": true, - "license": "BSD-2-Clause", "dependencies": { "estraverse": "^5.2.0" }, @@ -7151,15 +7222,13 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "devOptional": true, - "license": "MIT" + "devOptional": true }, "node_modules/fast-glob": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", "dev": true, - "license": "MIT", "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", @@ -7176,7 +7245,6 @@ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dev": true, - "license": "ISC", "dependencies": { "is-glob": "^4.0.1" }, @@ -7203,7 +7271,6 @@ "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", "dev": true, - "license": "ISC", "dependencies": { "reusify": "^1.0.4" } @@ -9507,7 +9574,6 @@ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "devOptional": true, - "license": "MIT", "dependencies": { "argparse": "^2.0.1" }, @@ -9589,8 +9655,7 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "devOptional": true, - "license": "MIT" + "devOptional": true }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", @@ -9834,10 +9899,9 @@ } }, "node_modules/marked": { - "version": "14.1.4", - "resolved": "https://registry.npmjs.org/marked/-/marked-14.1.4.tgz", - "integrity": "sha512-vkVZ8ONmUdPnjCKc5uTRvmkRbx4EAi2OkTOXmfTDhZz3OFqMNBM1oTTWwTr4HY4uAEojhzPf+Fy8F1DWa3Sndg==", - "license": "MIT", + "version": "15.0.8", + "resolved": "https://registry.npmjs.org/marked/-/marked-15.0.8.tgz", + "integrity": "sha512-rli4l2LyZqpQuRve5C0rkn6pj3hT8EWPC+zkAxFTAJLxRbENfTAhEQq9itrmf1Y81QtAX5D/MYlGlIomNgj9lA==", "bin": { "marked": "bin/marked.js" }, @@ -9957,7 +10021,6 @@ "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", "dev": true, - "license": "MIT", "engines": { "node": ">= 8" } @@ -10494,7 +10557,6 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dev": true, - "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" }, @@ -10512,16 +10574,15 @@ "license": "MIT" }, "node_modules/nanoid": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.2.tgz", - "integrity": "sha512-b+CiXQCNMUGe0Ri64S9SXFcP9hogjAJ2Rd6GdVxhPLRm7mhGaM7VgOvCAJ1ZshfHbqVDI3uqTI5C8/GaKuLI7g==", + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.5.tgz", + "integrity": "sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw==", "funding": [ { "type": "github", "url": "https://github.com/sponsors/ai" } ], - "license": "MIT", "bin": { "nanoid": "bin/nanoid.js" }, @@ -10633,15 +10694,15 @@ } }, "node_modules/object.entries": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.8.tgz", - "integrity": "sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ==", + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.9.tgz", + "integrity": "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==", "dev": true, - "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" + "es-object-atoms": "^1.1.1" }, "engines": { "node": ">= 0.4" @@ -11161,6 +11222,15 @@ ], "license": "MIT" }, + "node_modules/qrcode.react": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/qrcode.react/-/qrcode.react-4.2.0.tgz", + "integrity": "sha512-QpgqWi8rD9DsS9EP3z7BT+5lY5SFhsqGjpgW5DY/i3mK4M9DTBNz3ErMi8BWYEfI3L0d8GIbGmcdFAS1uIRGjA==", + "license": "ISC", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/querystringify": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", @@ -11185,8 +11255,7 @@ "type": "consulting", "url": "https://feross.org/support" } - ], - "license": "MIT" + ] }, "node_modules/react": { "version": "18.3.1", @@ -11546,7 +11615,6 @@ "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", "dev": true, - "license": "MIT", "engines": { "iojs": ">=1.0.0", "node": ">=0.10.0" @@ -11609,7 +11677,6 @@ "url": "https://feross.org/support" } ], - "license": "MIT", "dependencies": { "queue-microtask": "^1.2.2" } @@ -12359,11 +12426,10 @@ } }, "node_modules/ts-jest": { - "version": "29.2.6", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.2.6.tgz", - "integrity": "sha512-yTNZVZqc8lSixm+QGVFcPe6+yj7+TWZwIesuOWvfcn4B9bz5x4NDzVCQQjOs7Hfouu36aEqfEbo9Qpo+gq8dDg==", + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.3.1.tgz", + "integrity": "sha512-FT2PIRtZABwl6+ZCry8IY7JZ3xMuppsEV9qFVHOVe8jDzggwUZ9TsM4chyJxL9yi6LvkqcZYU3LmapEE454zBQ==", "dev": true, - "license": "MIT", "dependencies": { "bs-logger": "^0.2.6", "ejs": "^3.1.10", @@ -12373,6 +12439,7 @@ "lodash.memoize": "^4.1.2", "make-error": "^1.3.6", "semver": "^7.7.1", + "type-fest": "^4.38.0", "yargs-parser": "^21.1.1" }, "bin": { @@ -12420,6 +12487,18 @@ "node": ">=10" } }, + "node_modules/ts-jest/node_modules/type-fest": { + "version": "4.39.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.39.1.tgz", + "integrity": "sha512-uW9qzd66uyHYxwyVBYiwS4Oi0qZyUqwjU+Oevr6ZogYiXt99EOYtwvzMSLw1c3lYo2HzJsep/NB23iEVEgjG/w==", + "dev": true, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/ts-node": { "version": "10.9.2", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", @@ -12577,10 +12656,9 @@ } }, "node_modules/typescript": { - "version": "5.7.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", - "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", - "license": "Apache-2.0", + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -12590,15 +12668,14 @@ } }, "node_modules/typescript-eslint": { - "version": "8.25.0", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.25.0.tgz", - "integrity": "sha512-TxRdQQLH4g7JkoFlYG3caW5v1S6kEkz8rqt80iQJZUYPq1zD1Ra7HfQBJJ88ABRaMvHAXnwRvRB4V+6sQ9xN5Q==", + "version": "8.29.1", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.29.1.tgz", + "integrity": "sha512-f8cDkvndhbQMPcysk6CUSGBWV+g1utqdn71P5YKwMumVMOG/5k7cHq0KyG4O52nB0oKS4aN2Tp5+wB4APJGC+w==", "dev": true, - "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.25.0", - "@typescript-eslint/parser": "8.25.0", - "@typescript-eslint/utils": "8.25.0" + "@typescript-eslint/eslint-plugin": "8.29.1", + "@typescript-eslint/parser": "8.29.1", + "@typescript-eslint/utils": "8.29.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -12609,7 +12686,7 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.8.0" + "typescript": ">=4.8.4 <5.9.0" } }, "node_modules/unbox-primitive": { @@ -12632,10 +12709,9 @@ } }, "node_modules/undici-types": { - "version": "6.20.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", - "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", - "license": "MIT" + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==" }, "node_modules/unicode-canonical-property-names-ecmascript": { "version": "2.0.1", @@ -12826,7 +12902,6 @@ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "devOptional": true, - "license": "BSD-2-Clause", "dependencies": { "punycode": "^2.1.0" } @@ -12842,16 +12917,15 @@ } }, "node_modules/uuid": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", - "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", + "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", "funding": [ "https://github.com/sponsors/broofa", "https://github.com/sponsors/ctavan" ], - "license": "MIT", "bin": { - "uuid": "dist/bin/uuid" + "uuid": "dist/esm/bin/uuid" } }, "node_modules/v8-compile-cache-lib": { @@ -12911,10 +12985,9 @@ } }, "node_modules/vite": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.2.0.tgz", - "integrity": "sha512-7dPxoo+WsT/64rDcwoOjk76XHj+TqNTIvHKcuMQ1k4/SeHDaQt5GFAeLYzrimZrMpn/O6DtdI03WUjdxuPM0oQ==", - "license": "MIT", + "version": "6.2.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.2.5.tgz", + "integrity": "sha512-j023J/hCAa4pRIUH6J9HemwYfjB5llR2Ps0CWeikOtdR8+pAURAk0DoJC5/mm9kd+UgdnIy7d6HE4EAvlYhPhA==", "dependencies": { "esbuild": "^0.25.0", "postcss": "^8.5.3", @@ -12982,10 +13055,9 @@ } }, "node_modules/vite-plugin-checker": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/vite-plugin-checker/-/vite-plugin-checker-0.9.0.tgz", - "integrity": "sha512-gf/zc0KWX8ATEOgnpgAM1I+IbvWkkO80RB+FxlLtC5cabXSesbJmAUw6E+mMDDMGIT+VHAktmxJZpMTt3lSubQ==", - "license": "MIT", + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/vite-plugin-checker/-/vite-plugin-checker-0.9.1.tgz", + "integrity": "sha512-neH3CSNWdkZ+zi+WPt/0y5+IO2I0UAI0NX6MaXqU/KxN1Lz6np/7IooRB6VVAMBa4nigqm1GRF6qNa4+EL5jDQ==", "dependencies": { "@babel/code-frame": "^7.26.2", "chokidar": "^4.0.3", @@ -13004,7 +13076,7 @@ "@biomejs/biome": ">=1.7", "eslint": ">=7", "meow": "^13.2.0", - "optionator": "^0.9.1", + "optionator": "^0.9.4", "stylelint": ">=16", "typescript": "*", "vite": ">=2.0.0", diff --git a/client/package.json b/client/package.json index 45352e6..b682b8c 100644 --- a/client/package.json +++ b/client/package.json @@ -8,7 +8,7 @@ "build": "tsc && vite build", "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", "preview": "vite preview", - "test": "jest --colors", + "test": "jest --colors --silent", "test:watch": "jest --watch" }, "dependencies": { @@ -18,19 +18,21 @@ "@fortawesome/fontawesome-svg-core": "^6.7.2", "@fortawesome/free-solid-svg-icons": "^6.7.2", "@fortawesome/react-fontawesome": "^0.2.0", - "@mui/icons-material": "^6.4.6", + "@mui/icons-material": "6.4.7", "@mui/lab": "^5.0.0-alpha.153", - "@mui/material": "^6.4.7", + "@mui/material": "6.4.7", + "@mui/system": "6.4.7", "@types/uuid": "^9.0.7", "axios": "^1.8.1", - "dompurify": "^3.2.3", - "esbuild": "^0.25.0", + "dompurify": "^3.2.5", + "esbuild": "^0.25.2", "gift-pegjs": "^2.0.0-beta.1", "jest-environment-jsdom": "^29.7.0", "jwt-decode": "^4.0.0", "katex": "^0.16.11", - "marked": "^14.1.2", - "nanoid": "^5.1.2", + "marked": "^15.0.8", + "nanoid": "^5.1.5", + "qrcode.react": "^4.2.0", "react": "^18.3.1", "react-dom": "^18.3.1", "react-modal": "^3.16.3", @@ -38,39 +40,40 @@ "remark-math": "^6.0.0", "socket.io-client": "^4.7.2", "ts-node": "^10.9.1", - "uuid": "^9.0.1", - "vite-plugin-checker": "^0.9.0" + "uuid": "^11.1.0", + "vite-plugin-checker": "^0.9.1" }, "devDependencies": { "@babel/preset-env": "^7.26.9", "@babel/preset-react": "^7.26.3", - "@babel/preset-typescript": "^7.23.3", - "@eslint/js": "^9.21.0", + "@babel/preset-typescript": "^7.27.0", + "@eslint/js": "^9.24.0", "@testing-library/dom": "^10.4.0", "@testing-library/jest-dom": "^6.6.3", - "@testing-library/react": "^16.2.0", + "@testing-library/react": "^16.3.0", + "@testing-library/user-event": "^14.6.1", "@types/jest": "^29.5.13", - "@types/node": "^22.13.5", + "@types/node": "^22.14.0", "@types/react": "^18.2.15", "@types/react-dom": "^18.2.7", "@types/react-latex": "^2.0.3", - "@typescript-eslint/eslint-plugin": "^8.25.0", - "@typescript-eslint/parser": "^8.25.0", - "@vitejs/plugin-react-swc": "^3.8.0", + "@typescript-eslint/eslint-plugin": "^8.29.1", + "@typescript-eslint/parser": "^8.29.1", + "@vitejs/plugin-react-swc": "^3.8.1", "cross-env": "^7.0.3", - "eslint": "^9.21.0", + "eslint": "^9.24.0", "eslint-plugin-eslint-comments": "^3.2.0", "eslint-plugin-jest": "^28.11.0", - "eslint-plugin-react": "^7.37.3", + "eslint-plugin-react": "^7.37.5", "eslint-plugin-react-hooks": "^5.1.0-rc-206df66e-20240912", "eslint-plugin-react-refresh": "^0.4.19", "eslint-plugin-unused-imports": "^4.1.4", "globals": "^15.14.0", "identity-obj-proxy": "^3.0.0", "jest": "^29.7.0", - "ts-jest": "^29.2.6", - "typescript": "^5.7.3", - "typescript-eslint": "^8.25.0", + "ts-jest": "^29.3.1", + "typescript": "^5.8.3", + "typescript-eslint": "^8.29.1", "vite": "^6.2.0", "vite-plugin-environment": "^1.1.3" } diff --git a/client/src/Types/Images.tsx b/client/src/Types/Images.tsx new file mode 100644 index 0000000..8cfe170 --- /dev/null +++ b/client/src/Types/Images.tsx @@ -0,0 +1,17 @@ +export interface Images { + id: string; + file_content: string; + file_name: string; + mime_type: string; +} + +export interface ImagesResponse { + images: Images[]; + total: number; +} + +export interface ImagesParams { + page: number; + limit: number; + uid?: string; +} \ No newline at end of file diff --git a/client/src/__tests__/components/GiftTemplate/LiveResults/LiveResults.test.tsx b/client/src/__tests__/components/GiftTemplate/LiveResults/LiveResults.test.tsx index 3431b73..b9b6b9f 100644 --- a/client/src/__tests__/components/GiftTemplate/LiveResults/LiveResults.test.tsx +++ b/client/src/__tests__/components/GiftTemplate/LiveResults/LiveResults.test.tsx @@ -19,8 +19,8 @@ const mockQuestions: QuestionType[] = mockGiftQuestions.map((question, index) => }); const mockStudents: StudentType[] = [ - { id: "1", name: 'Student 1', answers: [{ idQuestion: 1, answer: 'Answer 1', isCorrect: true }] }, - { id: "2", name: 'Student 2', answers: [{ idQuestion: 2, answer: 'Answer 2', isCorrect: false }] }, + { id: "1", name: 'Student 1', answers: [{ idQuestion: 1, answer: ['Answer 1'], isCorrect: true }] }, + { id: "2", name: 'Student 2', answers: [{ idQuestion: 2, answer: ['Answer 2'], isCorrect: false }] }, ]; const mockShowSelectedQuestion = jest.fn(); @@ -92,4 +92,4 @@ describe('LiveResults', () => { expect(mockShowSelectedQuestion).toHaveBeenCalled(); }); -}); \ No newline at end of file +}); diff --git a/client/src/__tests__/components/GiftTemplate/LiveResults/LiveResultsTable/LiveResultsTable.test.tsx b/client/src/__tests__/components/GiftTemplate/LiveResults/LiveResultsTable/LiveResultsTable.test.tsx index d6f41d6..fe26173 100644 --- a/client/src/__tests__/components/GiftTemplate/LiveResults/LiveResultsTable/LiveResultsTable.test.tsx +++ b/client/src/__tests__/components/GiftTemplate/LiveResults/LiveResultsTable/LiveResultsTable.test.tsx @@ -20,8 +20,8 @@ const mockQuestions: QuestionType[] = mockGiftQuestions.map((question, index) => const mockStudents: StudentType[] = [ - { id: "1", name: 'Student 1', answers: [{ idQuestion: 1, answer: 'Answer 1', isCorrect: true }] }, - { id: "2", name: 'Student 2', answers: [{ idQuestion: 2, answer: 'Answer 2', isCorrect: false }] }, + { id: "1", name: 'Student 1', answers: [{ idQuestion: 1, answer: ['Answer 1'], isCorrect: true }] }, + { id: "2", name: 'Student 2', answers: [{ idQuestion: 2, answer: ['Answer 2'], isCorrect: false }] }, ]; const mockShowSelectedQuestion = jest.fn(); diff --git a/client/src/__tests__/components/GiftTemplate/LiveResults/LiveResultsTable/TableComponents/LiveResultsTableBody.test.tsx b/client/src/__tests__/components/GiftTemplate/LiveResults/LiveResultsTable/TableComponents/LiveResultsTableBody.test.tsx index 9d4fb5c..ce10e1b 100644 --- a/client/src/__tests__/components/GiftTemplate/LiveResults/LiveResultsTable/TableComponents/LiveResultsTableBody.test.tsx +++ b/client/src/__tests__/components/GiftTemplate/LiveResults/LiveResultsTable/TableComponents/LiveResultsTableBody.test.tsx @@ -20,8 +20,8 @@ const mockQuestions: QuestionType[] = mockGiftQuestions.map((question, index) => }); const mockStudents: StudentType[] = [ - { id: "1", name: 'Student 1', answers: [{ idQuestion: 1, answer: 'Answer 1', isCorrect: true }] }, - { id: "2", name: 'Student 2', answers: [{ idQuestion: 2, answer: 'Answer 2', isCorrect: false }] }, + { id: "1", name: 'Student 1', answers: [{ idQuestion: 1, answer: ['Answer 1'], isCorrect: true }] }, + { id: "2", name: 'Student 2', answers: [{ idQuestion: 2, answer: ['Answer 2'], isCorrect: false }] }, ]; const mockGetStudentGrade = jest.fn((student: StudentType) => { diff --git a/client/src/__tests__/components/GiftTemplate/LiveResults/LiveResultsTable/TableComponents/LiveResultsTableFooter.test.tsx b/client/src/__tests__/components/GiftTemplate/LiveResults/LiveResultsTable/TableComponents/LiveResultsTableFooter.test.tsx index 99a6dc3..dd7b54a 100644 --- a/client/src/__tests__/components/GiftTemplate/LiveResults/LiveResultsTable/TableComponents/LiveResultsTableFooter.test.tsx +++ b/client/src/__tests__/components/GiftTemplate/LiveResults/LiveResultsTable/TableComponents/LiveResultsTableFooter.test.tsx @@ -6,8 +6,8 @@ import LiveResultsTableFooter from 'src/components/LiveResults/LiveResultsTable/ const mockStudents: StudentType[] = [ - { id: "1", name: 'Student 1', answers: [{ idQuestion: 1, answer: 'Answer 1', isCorrect: true }] }, - { id: "2", name: 'Student 2', answers: [{ idQuestion: 2, answer: 'Answer 2', isCorrect: false }] }, + { id: "1", name: 'Student 1', answers: [{ idQuestion: 1, answer: ['Answer 1'], isCorrect: true }] }, + { id: "2", name: 'Student 2', answers: [{ idQuestion: 2, answer: ['Answer 2'], isCorrect: false }] }, ]; const mockGetStudentGrade = jest.fn((student: StudentType) => { @@ -52,4 +52,4 @@ describe('LiveResultsTableFooter', () => { expect(screen.getByText('50 %')).toBeInTheDocument(); }); -}); \ No newline at end of file +}); diff --git a/client/src/__tests__/components/GiftTemplate/templates/MultipleChoice.test.tsx b/client/src/__tests__/components/GiftTemplate/templates/MultipleChoice.test.tsx index bdecb06..2770267 100644 --- a/client/src/__tests__/components/GiftTemplate/templates/MultipleChoice.test.tsx +++ b/client/src/__tests__/components/GiftTemplate/templates/MultipleChoice.test.tsx @@ -29,7 +29,7 @@ const katekMock: TemplateOptions & MultipleChoiceQuestion = { formattedStem: { format: 'plain' , text: '$$\\frac{zzz}{yyy}$$'}, choices: [ { formattedText: { format: 'plain' , text: 'Choice 1'}, isCorrect: true, formattedFeedback: { format: 'plain' , text: 'Correct!'}, weight: 1 }, - { formattedText: { format: 'plain', text: 'Choice 2' }, isCorrect: true, formattedFeedback: { format: 'plain' , text: 'Correct!'}, weight: 1 } + { formattedText: { format: 'plain', text: 'Choice 2' }, isCorrect: false, formattedFeedback: { format: 'plain' , text: 'Correct!'}, weight: 0 } ], formattedGlobalFeedback: { format: 'plain', text: 'Sample Global Feedback' } }; diff --git a/client/src/__tests__/components/GiftTemplate/templates/__snapshots__/MultipleChoice.test.tsx.snap b/client/src/__tests__/components/GiftTemplate/templates/__snapshots__/MultipleChoice.test.tsx.snap index 0a17bf6..d32efd4 100644 --- a/client/src/__tests__/components/GiftTemplate/templates/__snapshots__/MultipleChoice.test.tsx.snap +++ b/client/src/__tests__/components/GiftTemplate/templates/__snapshots__/MultipleChoice.test.tsx.snap @@ -733,7 +733,7 @@ exports[`MultipleChoice snapshot test with katex 1`] = ` <div class='multiple-choice-answers-container'> <input class="gift-input" type="radio" id="idmocked-id" name="idmocked-id"> - <span class="answer-weight-container answer-positive-weight">1%</span> + <label style=" display: inline-block; padding: 0.2em 0 0.2em 0; @@ -742,15 +742,15 @@ exports[`MultipleChoice snapshot test with katex 1`] = ` " for="idmocked-id"> Choice 2 </label> - <svg data-testid="correct-icon" style=" + <svg data-testid="incorrect-icon" style=" vertical-align: text-bottom; display: inline-block; margin-left: 0.1rem; margin-right: 0.2rem; - width: 1em; - color: hsl(120, 39%, 54%); - " role="img" aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z"></path></svg> + width: 0.75em; + color: hsl(2, 64%, 58%); + " role="img" aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 352 512"><path fill="currentColor" d="M242.72 256l100.07-100.07c12.28-12.28 12.28-32.19 0-44.48l-22.24-22.24c-12.28-12.28-32.19-12.28-44.48 0L176 189.28 75.93 89.21c-12.28-12.28-32.19-12.28-44.48 0L9.21 111.45c-12.28 12.28-12.28 32.19 0 44.48L109.28 256 9.21 356.07c-12.28 12.28-12.28 32.19 0 44.48l22.24 22.24c12.28 12.28 32.2 12.28 44.48 0L176 322.72l100.07 100.07c12.28 12.28 32.2 12.28 44.48 0l22.24-22.24c12.28-12.28 12.28-32.19 0-44.48L242.72 256z"></path></svg> <span class="feedback-container">Correct!</span> </input> </div> diff --git a/client/src/__tests__/components/ImageGallery/ImageGallery.test.tsx b/client/src/__tests__/components/ImageGallery/ImageGallery.test.tsx new file mode 100644 index 0000000..52eb3a3 --- /dev/null +++ b/client/src/__tests__/components/ImageGallery/ImageGallery.test.tsx @@ -0,0 +1,147 @@ +import React, { act } from "react"; +import "@testing-library/jest-dom"; +import { render, screen, fireEvent, waitFor } from "@testing-library/react"; +import ImageGallery from "../../../components/ImageGallery/ImageGallery"; +import ApiService from "../../../services/ApiService"; +import { Images } from "../../../Types/Images"; +import userEvent from "@testing-library/user-event"; + +jest.mock("../../../services/ApiService"); + +const mockImages: Images[] = [ + { id: "1", file_name: "image1.jpg", mime_type: "image/jpeg", file_content: "mockBase64Content1" }, + { id: "2", file_name: "image2.jpg", mime_type: "image/jpeg", file_content: "mockBase64Content2" }, + { id: "3", file_name: "image3.jpg", mime_type: "image/jpeg", file_content: "mockBase64Content3" }, +]; + +beforeAll(() => { + global.URL.createObjectURL = jest.fn(() => 'mockedObjectUrl'); + Object.assign(navigator, { + clipboard: { + writeText: jest.fn(), + }, + }); +}); + +describe("ImageGallery", () => { + let mockHandleDelete: jest.Mock; + + beforeEach(async () => { + (ApiService.getImages as jest.Mock).mockResolvedValue({ images: mockImages, total: 3 }); + (ApiService.deleteImage as jest.Mock).mockResolvedValue(true); + (ApiService.uploadImage as jest.Mock).mockResolvedValue('mockImageUrl'); + await act(async () => { + render(); + }); + mockHandleDelete = jest.fn(); + }); + + it("should render images correctly", async () => { + await act(async () => { + await screen.findByText("Galerie"); + }); + + expect(screen.getByAltText("Image image1.jpg")).toBeInTheDocument(); + expect(screen.getByAltText("Image image2.jpg")).toBeInTheDocument(); + }); + + it("should handle copy action", async () => { + const handleCopyMock = jest.fn(); + await act(async () => { + render(); + }); + + const copyButtons = await waitFor(() => screen.findAllByTestId(/gallery-tab-copy-/)); + await act(async () => { + fireEvent.click(copyButtons[0]); + }); + + expect(navigator.clipboard.writeText).toHaveBeenCalled(); + }); + + it("should delete an image and update the gallery", async () => { + const fetchImagesMock = jest.fn().mockResolvedValue({ images: mockImages.filter((image) => image.id !== "1"), total: 2 }); + + (ApiService.getImages as jest.Mock).mockImplementation(fetchImagesMock); + + await act(async () => { + render(); + }); + + await act(async () => { + await screen.findByAltText("Image image1.jpg"); + }); + + const deleteButtons = await waitFor(() => screen.findAllByTestId(/gallery-tab-delete-/)); + fireEvent.click(deleteButtons[0]); + + await waitFor(() => { + expect(screen.getByText("Voulez-vous supprimer cette image?")).toBeInTheDocument(); + }); + + const confirmDeleteButton = screen.getByText("Delete"); + await act(async () => { + fireEvent.click(confirmDeleteButton); + }); + + await waitFor(() => { + expect(ApiService.deleteImage).toHaveBeenCalledWith("1"); + }); + + await waitFor(() => { + expect(screen.queryByAltText("Image image1.jpg")).toBeNull(); + expect(screen.getByText("Image supprimée avec succès!")).toBeInTheDocument(); + }); + }); + + it("should upload an image and display success message", async () => { + const importTab = screen.getByRole("tab", { name: /import/i }); + fireEvent.click(importTab); + + const fileInputs = await screen.findAllByTestId("file-input"); + const fileInput = fileInputs[1]; + + expect(fileInput).toBeInTheDocument(); + + const file = new File(["image"], "image.jpg", { type: "image/jpeg" }); + await userEvent.upload(fileInput, file); + + + await waitFor(() => screen.getByAltText("Preview")); + const previewImage = screen.getByAltText("Preview"); + + expect(previewImage).toBeInTheDocument(); + + const uploadButton = screen.getByRole('button', { name: /téléverser/i }); + fireEvent.click(uploadButton); + const successMessage = await screen.findByText(/téléversée avec succès/i); + expect(successMessage).toBeInTheDocument(); + }); + + it("should close the image preview dialog when close button is clicked", async () => { + const imageCard = screen.getByAltText("Image image1.jpg"); + fireEvent.click(imageCard); + + const dialogImage = await screen.findByAltText("Enlarged view"); + expect(dialogImage).toBeInTheDocument(); + + const closeButton = screen.getByTestId("close-button"); + fireEvent.click(closeButton); + + await waitFor(() => { + expect(screen.queryByAltText("Enlarged view")).not.toBeInTheDocument(); + }); + }); + + it("should show an error message when no file is selected", async () => { + const importTab = screen.getByRole("tab", { name: /import/i }); + fireEvent.click(importTab); + const uploadButton = screen.getByRole('button', { name: /téléverser/i }); + fireEvent.click(uploadButton); + + await waitFor(() => { + expect(screen.getByText("Veuillez choisir une image à téléverser.")).toBeInTheDocument(); + }); + }); + +}); diff --git a/client/src/__tests__/components/ImageGallery/ImageGalleryModal.test.tsx b/client/src/__tests__/components/ImageGallery/ImageGalleryModal.test.tsx new file mode 100644 index 0000000..f5a65a6 --- /dev/null +++ b/client/src/__tests__/components/ImageGallery/ImageGalleryModal.test.tsx @@ -0,0 +1,44 @@ +import React from "react"; +import { render, screen, fireEvent, waitFor } from "@testing-library/react"; +import ImageGalleryModal from "../../../components/ImageGallery/ImageGalleryModal/ImageGalleryModal"; +import "@testing-library/jest-dom"; + +jest.mock("../../../components/ImageGallery/ImageGallery", () => ({ + __esModule: true, + default: jest.fn(() =>
), +})); + +describe("ImageGalleryModal", () => { + + it("renders button correctly", () => { + render(); + + const button = screen.getByLabelText(/images-open/i); + expect(button).toBeInTheDocument(); + }); + + it("opens the modal when button is clicked", () => { + render(); + + const button = screen.getByRole("button", { name: /images/i }); + fireEvent.click(button); + + const dialog = screen.getByRole("dialog"); + expect(dialog).toBeInTheDocument(); + }); + + + it("closes the modal when close button is clicked", async () => { + render(); + + fireEvent.click(screen.getByRole("button", { name: /images/i })); + + const closeButton = screen.getByRole("button", { name: /close/i }); + fireEvent.click(closeButton); + + await waitFor(() => { + expect(screen.queryByRole("dialog")).not.toBeInTheDocument(); + }); + }); + +}); diff --git a/client/src/__tests__/components/LiveResults/LiveResults.test.tsx b/client/src/__tests__/components/LiveResults/LiveResults.test.tsx index ce6244e..269d83b 100644 --- a/client/src/__tests__/components/LiveResults/LiveResults.test.tsx +++ b/client/src/__tests__/components/LiveResults/LiveResults.test.tsx @@ -5,7 +5,7 @@ import LiveResults from 'src/components/LiveResults/LiveResults'; import { QuestionType } from 'src/Types/QuestionType'; import { StudentType } from 'src/Types/StudentType'; import { Socket } from 'socket.io-client'; -import { BaseQuestion,parse } from 'gift-pegjs'; +import { BaseQuestion, parse } from 'gift-pegjs'; const mockSocket: Socket = { on: jest.fn(), @@ -19,19 +19,28 @@ const mockGiftQuestions = parse( `::Sample Question 1:: Question stem { =Choice 1 - ~Choice 2 - }`); + =Choice 2 + ~Choice 3 + ~Choice 4 + } + + ::Sample Question 2:: Question stem {TRUE} + `); const mockQuestions: QuestionType[] = mockGiftQuestions.map((question, index) => { if (question.type !== "Category") question.id = (index + 1).toString(); const newMockQuestion = question; - return {question : newMockQuestion as BaseQuestion}; + return { question: newMockQuestion as BaseQuestion }; }); +console.log(`mockQuestions: ${JSON.stringify(mockQuestions)}`); + +// each student should have a different score for the tests to pass const mockStudents: StudentType[] = [ - { id: '1', name: 'Student 1', answers: [{ idQuestion: 1, answer: 'Choice 1', isCorrect: true }] }, - { id: '2', name: 'Student 2', answers: [{ idQuestion: 1, answer: 'Choice 2', isCorrect: false }] }, + { id: '1', name: 'Student 1', answers: [] }, + { id: '2', name: 'Student 2', answers: [{ idQuestion: 1, answer: ['Choice 3'], isCorrect: false }, { idQuestion: 2, answer: [true], isCorrect: true}] }, + { id: '3', name: 'Student 3', answers: [{ idQuestion: 1, answer: ['Choice 1', 'Choice 2'], isCorrect: true }, { idQuestion: 2, answer: [true], isCorrect: true}] }, ]; describe('LiveResults', () => { @@ -52,7 +61,7 @@ describe('LiveResults', () => { // Toggle the display of usernames back fireEvent.click(toggleUsernamesSwitch); - + // Check if the component renders the students mockStudents.forEach((student) => { expect(screen.getByText(student.name)).toBeInTheDocument(); @@ -82,82 +91,88 @@ describe('LiveResults', () => { }); }); -}); -test('calculates and displays the correct student grades', () => { - render( - - ); + test('calculates and displays the correct student grades', () => { + render( + + ); - // Toggle the display of usernames - const toggleUsernamesSwitch = screen.getByLabelText('Afficher les noms'); + // Toggle the display of usernames + const toggleUsernamesSwitch = screen.getByLabelText('Afficher les noms'); - // Toggle the display of usernames back - fireEvent.click(toggleUsernamesSwitch); - - // Check if the student grades are calculated and displayed correctly - mockStudents.forEach((student) => { - const grade = student.answers.filter(answer => answer.isCorrect).length / mockQuestions.length * 100; - expect(screen.getByText(`${grade.toFixed()} %`)).toBeInTheDocument(); + // Toggle the display of usernames back + fireEvent.click(toggleUsernamesSwitch); + + // Check if the student grades are calculated and displayed correctly + const getByTextInTableCellBody = (text: string) => { + const elements = screen.getAllByText(text); // Get all elements with the specified text + return elements.find((element) => element.closest('.MuiTableCell-body')); // don't get the footer element(s) + }; + mockStudents.forEach((student) => { + const grade = student.answers.filter(answer => answer.isCorrect).length / mockQuestions.length * 100; + const element = getByTextInTableCellBody(`${grade.toFixed()} %`); + expect(element).toBeInTheDocument(); + }); }); -}); -test('calculates and displays the class average', () => { - render( - - ); + test('calculates and displays the class average', () => { + render( + + ); - // Toggle the display of usernames - const toggleUsernamesSwitch = screen.getByLabelText('Afficher les noms'); + // Toggle the display of usernames + const toggleUsernamesSwitch = screen.getByLabelText('Afficher les noms'); - // Toggle the display of usernames back - fireEvent.click(toggleUsernamesSwitch); - - // Calculate the class average - const totalGrades = mockStudents.reduce((total, student) => { - return total + (student.answers.filter(answer => answer.isCorrect).length / mockQuestions.length * 100); - }, 0); - const classAverage = totalGrades / mockStudents.length; + // Toggle the display of usernames back + fireEvent.click(toggleUsernamesSwitch); - // Check if the class average is displayed correctly - const classAverageElements = screen.getAllByText(`${classAverage.toFixed()} %`); - const classAverageElement = classAverageElements.find((element) => { - return element.closest('td')?.classList.contains('MuiTableCell-footer'); - }); - expect(classAverageElement).toBeInTheDocument(); -}); + // Calculate the class average + const totalGrades = mockStudents.reduce((total, student) => { + return total + (student.answers.filter(answer => answer.isCorrect).length / mockQuestions.length * 100); + }, 0); + const classAverage = totalGrades / mockStudents.length; -test('displays the correct answers per question', () => { - render( - - ); - - // Check if the correct answers per question are displayed correctly - mockQuestions.forEach((_, index) => { - const correctAnswers = mockStudents.filter(student => student.answers.some(answer => answer.idQuestion === index + 1 && answer.isCorrect)).length; - const correctAnswersPercentage = (correctAnswers / mockStudents.length) * 100; - const correctAnswersElements = screen.getAllByText(`${correctAnswersPercentage.toFixed()} %`); - const correctAnswersElement = correctAnswersElements.find((element) => { - return element.closest('td')?.classList.contains('MuiTableCell-root'); + // Check if the class average is displayed correctly + const classAverageElements = screen.getAllByText(`${classAverage.toFixed()} %`); + const classAverageElement = classAverageElements.find((element) => { + return element.closest('td')?.classList.contains('MuiTableCell-footer'); }); - expect(correctAnswersElement).toBeInTheDocument(); + expect(classAverageElement).toBeInTheDocument(); }); -}); \ No newline at end of file + + test('displays the correct answers per question', () => { + render( + + ); + + // Check if the correct answers per question are displayed correctly + mockQuestions.forEach((_, index) => { + const correctAnswers = mockStudents.filter(student => student.answers.some(answer => answer.idQuestion === index + 1 && answer.isCorrect)).length; + const correctAnswersPercentage = (correctAnswers / mockStudents.length) * 100; + const correctAnswersElements = screen.getAllByText(`${correctAnswersPercentage.toFixed()} %`); + const correctAnswersElement = correctAnswersElements.find((element) => { + return element.closest('td')?.classList.contains('MuiTableCell-root'); + }); + expect(correctAnswersElement).toBeInTheDocument(); + }); + }); + +}); diff --git a/client/src/__tests__/components/QuestionsDisplay/MultipleChoiceQuestionDisplay/MultipleChoiceQuestionDisplay.test.tsx b/client/src/__tests__/components/QuestionsDisplay/MultipleChoiceQuestionDisplay/MultipleChoiceQuestionDisplay.test.tsx index 8751e8b..45e9b0a 100644 --- a/client/src/__tests__/components/QuestionsDisplay/MultipleChoiceQuestionDisplay/MultipleChoiceQuestionDisplay.test.tsx +++ b/client/src/__tests__/components/QuestionsDisplay/MultipleChoiceQuestionDisplay/MultipleChoiceQuestionDisplay.test.tsx @@ -12,14 +12,23 @@ const questions = parse( { =Choice 1 ~Choice 2 - }`) as MultipleChoiceQuestion[]; + } + + ::Sample Question 2:: Question stem + { + =Choice 1 + =Choice 2 + ~Choice 3 + } + `) as MultipleChoiceQuestion[]; -const question = questions[0]; +const questionWithOneCorrectChoice = questions[0]; +const questionWithMultipleCorrectChoices = questions[1]; describe('MultipleChoiceQuestionDisplay', () => { const mockHandleOnSubmitAnswer = jest.fn(); - const TestWrapper = ({ showAnswer }: { showAnswer: boolean }) => { + const TestWrapper = ({ showAnswer, question }: { showAnswer: boolean; question: MultipleChoiceQuestion }) => { const [showAnswerState, setShowAnswerState] = useState(showAnswer); const handleOnSubmitAnswer = (answer: AnswerType) => { @@ -38,28 +47,51 @@ describe('MultipleChoiceQuestionDisplay', () => { ); }; - const choices = question.choices; + const twoChoices = questionWithOneCorrectChoice.choices; + const threeChoices = questionWithMultipleCorrectChoices.choices; - beforeEach(() => { - render(); - }); + test('renders a question (that has only one correct choice) and its choices', () => { + render(); - test('renders the question and choices', () => { - expect(screen.getByText(question.formattedStem.text)).toBeInTheDocument(); - choices.forEach((choice) => { + expect(screen.getByText(questionWithOneCorrectChoice.formattedStem.text)).toBeInTheDocument(); + twoChoices.forEach((choice) => { expect(screen.getByText(choice.formattedText.text)).toBeInTheDocument(); }); }); + test('only allows one choice to be selected when question only has one correct answer', () => { + render(); + + const choiceButton1 = screen.getByText('Choice 1').closest('button'); + const choiceButton2 = screen.getByText('Choice 2').closest('button'); + + if (!choiceButton1 || !choiceButton2) throw new Error('Choice buttons not found'); + + // Simulate selecting multiple answers + act(() => { + fireEvent.click(choiceButton1); + }); + act(() => { + fireEvent.click(choiceButton2); + }); + + // Verify that only the last answer is selected + expect(choiceButton1.querySelector('.answer-text.selected')).not.toBeInTheDocument(); + expect(choiceButton2.querySelector('.answer-text.selected')).toBeInTheDocument(); + }); + test('does not submit when no answer is selected', () => { + render(); const submitButton = screen.getByText('Répondre'); act(() => { fireEvent.click(submitButton); }); expect(mockHandleOnSubmitAnswer).not.toHaveBeenCalled(); + mockHandleOnSubmitAnswer.mockClear(); }); test('submits the selected answer', () => { + render(); const choiceButton = screen.getByText('Choice 1').closest('button'); if (!choiceButton) throw new Error('Choice button not found'); act(() => { @@ -70,10 +102,68 @@ describe('MultipleChoiceQuestionDisplay', () => { fireEvent.click(submitButton); }); - expect(mockHandleOnSubmitAnswer).toHaveBeenCalledWith('Choice 1'); + expect(mockHandleOnSubmitAnswer).toHaveBeenCalledWith(['Choice 1']); + mockHandleOnSubmitAnswer.mockClear(); + }); + + + test('renders a question (that has multiple correct choices) and its choices', () => { + render(); + expect(screen.getByText(questionWithMultipleCorrectChoices.formattedStem.text)).toBeInTheDocument(); + threeChoices.forEach((choice) => { + expect(screen.getByText(choice.formattedText.text)).toBeInTheDocument(); + }); + }); + + test('allows multiple choices to be selected when question has multiple correct answers', () => { + render(); + const choiceButton1 = screen.getByText('Choice 1').closest('button'); + const choiceButton2 = screen.getByText('Choice 2').closest('button'); + const choiceButton3 = screen.getByText('Choice 3').closest('button'); + + if (!choiceButton1 || !choiceButton2 || !choiceButton3) throw new Error('Choice buttons not found'); + + act(() => { + fireEvent.click(choiceButton1); + }); + act(() => { + fireEvent.click(choiceButton2); + }); + + expect(choiceButton1.querySelector('.answer-text.selected')).toBeInTheDocument(); + expect(choiceButton2.querySelector('.answer-text.selected')).toBeInTheDocument(); + expect(choiceButton3.querySelector('.answer-text.selected')).not.toBeInTheDocument(); // didn't click + + }); + + test('submits multiple selected answers', () => { + render(); + const choiceButton1 = screen.getByText('Choice 1').closest('button'); + const choiceButton2 = screen.getByText('Choice 2').closest('button'); + + if (!choiceButton1 || !choiceButton2) throw new Error('Choice buttons not found'); + + // Simulate selecting multiple answers + act(() => { + fireEvent.click(choiceButton1); + }); + act(() => { + fireEvent.click(choiceButton2); + }); + + // Simulate submitting the answers + const submitButton = screen.getByText('Répondre'); + act(() => { + fireEvent.click(submitButton); + }); + + // Verify that the mockHandleOnSubmitAnswer function is called with both answers + expect(mockHandleOnSubmitAnswer).toHaveBeenCalledWith(['Choice 1', 'Choice 2']); + mockHandleOnSubmitAnswer.mockClear(); }); it('should show ✅ next to the correct answer and ❌ next to the wrong answers when showAnswer is true', async () => { + render(); const choiceButton = screen.getByText('Choice 1').closest('button'); if (!choiceButton) throw new Error('Choice button not found'); @@ -89,16 +179,17 @@ describe('MultipleChoiceQuestionDisplay', () => { }); // Wait for the DOM to update - const correctAnswer = screen.getByText("Choice 1").closest('button'); - expect(correctAnswer).toBeInTheDocument(); - expect(correctAnswer?.textContent).toContain('✅'); + const correctAnswer = screen.getByText("Choice 1").closest('button'); + expect(correctAnswer).toBeInTheDocument(); + expect(correctAnswer?.textContent).toContain('✅'); - const wrongAnswer1 = screen.getByText("Choice 2").closest('button'); - expect(wrongAnswer1).toBeInTheDocument(); - expect(wrongAnswer1?.textContent).toContain('❌'); + const wrongAnswer1 = screen.getByText("Choice 2").closest('button'); + expect(wrongAnswer1).toBeInTheDocument(); + expect(wrongAnswer1?.textContent).toContain('❌'); }); - it('should not show ✅ or ❌ when repondre button is not clicked', async () => { + it('should not show ✅ or ❌ when Répondre button is not clicked', async () => { + render(); const choiceButton = screen.getByText('Choice 1').closest('button'); if (!choiceButton) throw new Error('Choice button not found'); @@ -118,5 +209,5 @@ describe('MultipleChoiceQuestionDisplay', () => { expect(wrongAnswer1?.textContent).not.toContain('❌'); }); - }); +}); diff --git a/client/src/__tests__/components/QuestionsDisplay/NumericalQuestionDisplay/NumericalQuestionDisplay.test.tsx b/client/src/__tests__/components/QuestionsDisplay/NumericalQuestionDisplay/NumericalQuestionDisplay.test.tsx index 639537a..5c32547 100644 --- a/client/src/__tests__/components/QuestionsDisplay/NumericalQuestionDisplay/NumericalQuestionDisplay.test.tsx +++ b/client/src/__tests__/components/QuestionsDisplay/NumericalQuestionDisplay/NumericalQuestionDisplay.test.tsx @@ -67,6 +67,7 @@ describe('NumericalQuestion Component', () => { fireEvent.click(submitButton); expect(mockHandleOnSubmitAnswer).not.toHaveBeenCalled(); + mockHandleOnSubmitAnswer.mockClear(); }); it('submits answer correctly', () => { @@ -77,6 +78,7 @@ describe('NumericalQuestion Component', () => { fireEvent.click(submitButton); - expect(mockHandleOnSubmitAnswer).toHaveBeenCalledWith(7); + expect(mockHandleOnSubmitAnswer).toHaveBeenCalledWith([7]); + mockHandleOnSubmitAnswer.mockClear(); }); }); diff --git a/client/src/__tests__/components/QuestionsDisplay/Question.test.tsx b/client/src/__tests__/components/QuestionsDisplay/Question.test.tsx index 8c7546f..142e563 100644 --- a/client/src/__tests__/components/QuestionsDisplay/Question.test.tsx +++ b/client/src/__tests__/components/QuestionsDisplay/Question.test.tsx @@ -29,23 +29,24 @@ describe('Questions Component', () => { render(); }; - describe('question type parsing', () => { - it('parses true/false question type correctly', () => { - expect(sampleTrueFalseQuestion.type).toBe('TF'); - }); + // describe('question type parsing', () => { + // it('parses true/false question type correctly', () => { + // expect(sampleTrueFalseQuestion.type).toBe('TF'); + // }); - it('parses multiple choice question type correctly', () => { - expect(sampleMultipleChoiceQuestion.type).toBe('MC'); - }); + // it('parses multiple choice question type correctly', () => { + // expect(sampleMultipleChoiceQuestion.type).toBe('MC'); + // }); - it('parses numerical question type correctly', () => { - expect(sampleNumericalQuestion.type).toBe('Numerical'); - }); + // it('parses numerical question type correctly', () => { + // expect(sampleNumericalQuestion.type).toBe('Numerical'); + // }); + + // it('parses short answer question type correctly', () => { + // expect(sampleShortAnswerQuestion.type).toBe('Short'); + // }); + // }); - it('parses short answer question type correctly', () => { - expect(sampleShortAnswerQuestion.type).toBe('Short'); - }); - }); it('renders correctly for True/False question', () => { renderComponent(sampleTrueFalseQuestion); @@ -73,7 +74,8 @@ describe('Questions Component', () => { const submitButton = screen.getByText('Répondre'); fireEvent.click(submitButton); - expect(mockHandleSubmitAnswer).toHaveBeenCalledWith('Choice 1'); + expect(mockHandleSubmitAnswer).toHaveBeenCalledWith(['Choice 1']); + mockHandleSubmitAnswer.mockClear(); }); it('renders correctly for Numerical question', () => { @@ -93,7 +95,8 @@ describe('Questions Component', () => { const submitButton = screen.getByText('Répondre'); fireEvent.click(submitButton); - expect(mockHandleSubmitAnswer).toHaveBeenCalledWith(7); + expect(mockHandleSubmitAnswer).toHaveBeenCalledWith([7]); + mockHandleSubmitAnswer.mockClear(); }); it('renders correctly for Short Answer question', () => { @@ -117,7 +120,7 @@ describe('Questions Component', () => { const submitButton = screen.getByText('Répondre'); fireEvent.click(submitButton); - expect(mockHandleSubmitAnswer).toHaveBeenCalledWith('User Input'); + expect(mockHandleSubmitAnswer).toHaveBeenCalledWith(['User Input']); }); }); diff --git a/client/src/__tests__/components/QuestionsDisplay/ShortAnswerQuestionDisplay/ShortAnswerQuestionDisplay.test.tsx b/client/src/__tests__/components/QuestionsDisplay/ShortAnswerQuestionDisplay/ShortAnswerQuestionDisplay.test.tsx index c4326eb..57e9da5 100644 --- a/client/src/__tests__/components/QuestionsDisplay/ShortAnswerQuestionDisplay/ShortAnswerQuestionDisplay.test.tsx +++ b/client/src/__tests__/components/QuestionsDisplay/ShortAnswerQuestionDisplay/ShortAnswerQuestionDisplay.test.tsx @@ -47,6 +47,7 @@ describe('ShortAnswerQuestion Component', () => { fireEvent.click(submitButton); expect(mockHandleSubmitAnswer).not.toHaveBeenCalled(); + mockHandleSubmitAnswer.mockClear(); }); it('submits answer correctly', () => { @@ -60,6 +61,7 @@ describe('ShortAnswerQuestion Component', () => { fireEvent.click(submitButton); - expect(mockHandleSubmitAnswer).toHaveBeenCalledWith('User Input'); + expect(mockHandleSubmitAnswer).toHaveBeenCalledWith(['User Input']); + mockHandleSubmitAnswer.mockClear(); }); }); diff --git a/client/src/__tests__/components/QuestionsDisplay/TrueFalseQuestionDisplay/TrueFalseQuestionDisplay.test.tsx b/client/src/__tests__/components/QuestionsDisplay/TrueFalseQuestionDisplay/TrueFalseQuestionDisplay.test.tsx index e6910d4..f79d1da 100644 --- a/client/src/__tests__/components/QuestionsDisplay/TrueFalseQuestionDisplay/TrueFalseQuestionDisplay.test.tsx +++ b/client/src/__tests__/components/QuestionsDisplay/TrueFalseQuestionDisplay/TrueFalseQuestionDisplay.test.tsx @@ -56,6 +56,7 @@ describe('TrueFalseQuestion Component', () => { }); expect(mockHandleSubmitAnswer).not.toHaveBeenCalled(); + mockHandleSubmitAnswer.mockClear(); }); it('submits answer correctly for True', () => { @@ -70,7 +71,8 @@ describe('TrueFalseQuestion Component', () => { fireEvent.click(submitButton); }); - expect(mockHandleSubmitAnswer).toHaveBeenCalledWith(true); + expect(mockHandleSubmitAnswer).toHaveBeenCalledWith([true]); + mockHandleSubmitAnswer.mockClear(); }); it('submits answer correctly for False', () => { @@ -83,7 +85,8 @@ describe('TrueFalseQuestion Component', () => { fireEvent.click(submitButton); }); - expect(mockHandleSubmitAnswer).toHaveBeenCalledWith(false); + expect(mockHandleSubmitAnswer).toHaveBeenCalledWith([false]); + mockHandleSubmitAnswer.mockClear(); }); @@ -112,7 +115,7 @@ describe('TrueFalseQuestion Component', () => { expect(wrongAnswer1?.textContent).toContain('❌'); }); - it('should not show ✅ or ❌ when repondre button is not clicked', async () => { + it('should not show ✅ or ❌ when Répondre button is not clicked', async () => { const choiceButton = screen.getByText('Vrai').closest('button'); if (!choiceButton) throw new Error('Choice button not found'); diff --git a/client/src/__tests__/components/ShareQuizModal/ShareQuizModal.test.tsx b/client/src/__tests__/components/ShareQuizModal/ShareQuizModal.test.tsx new file mode 100644 index 0000000..7027eaf --- /dev/null +++ b/client/src/__tests__/components/ShareQuizModal/ShareQuizModal.test.tsx @@ -0,0 +1,81 @@ +import React from 'react'; +import { render, screen, fireEvent, act } from '@testing-library/react'; +import ShareQuizModal from '../../../components/ShareQuizModal/ShareQuizModal.tsx'; +import { QuizType } from '../../../Types/QuizType'; +import '@testing-library/jest-dom'; + +describe('ShareQuizModal', () => { + const mockQuiz: QuizType = { + _id: '123', + folderId: 'folder-123', + folderName: 'Test Folder', + userId: 'user-123', + title: 'Test Quiz', + content: ['Question 1', 'Question 2'], + created_at: new Date(), + updated_at: new Date(), + }; + + beforeAll(() => { + // Properly mock the clipboard API + Object.defineProperty(navigator, 'clipboard', { + value: { + writeText: jest.fn().mockImplementation(() => Promise.resolve()), + }, + writable: true, + }); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('renders the share button', () => { + render(); + expect(screen.getByLabelText('partager quiz')).toBeInTheDocument(); + expect(screen.getByTestId('ShareIcon')).toBeInTheDocument(); + }); + + it('copies the quiz URL to clipboard when share button is clicked', async () => { + render(); + const shareButton = screen.getByLabelText('partager quiz'); + + await act(async () => { + fireEvent.click(shareButton); + }); + + expect(navigator.clipboard.writeText).toHaveBeenCalledWith( + `${window.location.origin}/teacher/share/${mockQuiz._id}` + ); + + // Check for feedback dialog content + expect(screen.getByText(/L'URL de partage pour le quiz/i)).toBeInTheDocument(); + expect(screen.getByText(mockQuiz.title)).toBeInTheDocument(); + expect(screen.getByText(/a été copiée\./i)).toBeInTheDocument(); + }); + + it('shows error message when clipboard write fails', async () => { + // Override the mock to reject + (navigator.clipboard.writeText as jest.Mock).mockRejectedValueOnce(new Error('Clipboard write failed')); + + render(); + const shareButton = screen.getByLabelText('partager quiz'); + + await act(async () => { + fireEvent.click(shareButton); + }); + + expect(screen.getByText(/Une erreur est survenue lors de la copie de l'URL\./i)).toBeInTheDocument(); + }); + + it('displays the quiz title in the success message', async () => { + render(); + const shareButton = screen.getByLabelText('partager quiz'); + + await act(async () => { + fireEvent.click(shareButton); + }); + + expect(screen.getByText(mockQuiz.title)).toBeInTheDocument(); + }); +}); \ No newline at end of file diff --git a/client/src/__tests__/pages/ManageRoom/IsCorrect.test.tsx b/client/src/__tests__/pages/ManageRoom/IsCorrect.test.tsx new file mode 100644 index 0000000..80748f3 --- /dev/null +++ b/client/src/__tests__/pages/ManageRoom/IsCorrect.test.tsx @@ -0,0 +1,359 @@ +import { checkIfIsCorrect } from 'src/pages/Teacher/ManageRoom/useRooms'; +import { HighLowNumericalAnswer, MultipleChoiceQuestion, MultipleNumericalAnswer, NumericalQuestion, RangeNumericalAnswer, ShortAnswerQuestion, SimpleNumericalAnswer, TrueFalseQuestion } from 'gift-pegjs'; +import { AnswerType } from 'src/pages/Student/JoinRoom/JoinRoom'; +import { QuestionType } from 'src/Types/QuestionType'; + +describe('checkIfIsCorrect', () => { + const mockQuestions: QuestionType[] = [ + { + question: { + id: '1', + type: 'MC', + choices: [ + { isCorrect: true, formattedText: { text: 'Answer1' } }, + { isCorrect: true, formattedText: { text: 'Answer2' } }, + { isCorrect: false, formattedText: { text: 'Answer3' } }, + ], + } as MultipleChoiceQuestion, + }, + ]; + + test('returns true when all selected answers are correct', () => { + const answer: AnswerType = ['Answer1', 'Answer2']; + const result = checkIfIsCorrect(answer, 1, mockQuestions); + expect(result).toBe(true); + }); + + test('returns false when some selected answers are incorrect', () => { + const answer: AnswerType = ['Answer1', 'Answer3']; + const result = checkIfIsCorrect(answer, 1, mockQuestions); + expect(result).toBe(false); + }); + + test('returns false when all correct answers are selected, but one incorrect is also selected', () => { + const answer: AnswerType = ['Answer1', 'Answer2', 'Answer3']; + const result = checkIfIsCorrect(answer, 1, mockQuestions); + expect(result).toBe(false); + }); + + test('returns false when no answers are selected', () => { + const answer: AnswerType = []; + const result = checkIfIsCorrect(answer, 1, mockQuestions); + expect(result).toBe(false); + }); + + test('returns false when no correct answers are provided in the question', () => { + const mockQuestionsWithNoCorrectAnswers: QuestionType[] = [ + { + question: { + id: '1', + type: 'MC', + choices: [ + { isCorrect: false, formattedText: { text: 'Answer1' } }, + { isCorrect: false, formattedText: { text: 'Answer2' } }, + ], + } as MultipleChoiceQuestion, + }, + ]; + const answer: AnswerType = ['Answer1']; + const result = checkIfIsCorrect(answer, 1, mockQuestionsWithNoCorrectAnswers); + expect(result).toBe(false); + }); + + test('returns true for a correct true/false answer', () => { + const mockQuestionsTF: QuestionType[] = [ + { + question: { + id: '2', + type: 'TF', + isTrue: true, + } as TrueFalseQuestion, + }, + ]; + const answer: AnswerType = [true]; + const result = checkIfIsCorrect(answer, 2, mockQuestionsTF); + expect(result).toBe(true); + }); + + test('returns false for an incorrect true/false answer', () => { + const mockQuestionsTF: QuestionType[] = [ + { + question: { + id: '2', + type: 'TF', + isTrue: true, + } as TrueFalseQuestion, + }, + ]; + const answer: AnswerType = [false]; + const result = checkIfIsCorrect(answer, 2, mockQuestionsTF); + expect(result).toBe(false); + }); + + test('returns false for a true/false question with no answer', () => { + const mockQuestionsTF: QuestionType[] = [ + { + question: { + id: '2', + type: 'TF', + isTrue: true, + } as TrueFalseQuestion, + }, + ]; + const answer: AnswerType = []; + const result = checkIfIsCorrect(answer, 2, mockQuestionsTF); + expect(result).toBe(false); + }); + + test('returns true for a correct true/false answer when isTrue is false', () => { + const mockQuestionsTF: QuestionType[] = [ + { + question: { + id: '3', + type: 'TF', + isTrue: false, // Correct answer is false + } as TrueFalseQuestion, + }, + ]; + const answer: AnswerType = [false]; + const result = checkIfIsCorrect(answer, 3, mockQuestionsTF); + expect(result).toBe(true); + }); + + test('returns false for an incorrect true/false answer when isTrue is false', () => { + const mockQuestionsTF: QuestionType[] = [ + { + question: { + id: '3', + type: 'TF', + isTrue: false, // Correct answer is false + } as TrueFalseQuestion, + }, + ]; + const answer: AnswerType = [true]; + const result = checkIfIsCorrect(answer, 3, mockQuestionsTF); + expect(result).toBe(false); + }); + + test('returns true for a correct short answer', () => { + const mockQuestionsShort: QuestionType[] = [ + { + question: { + id: '4', + type: 'Short', + choices: [ + { text: 'CorrectAnswer1' }, + { text: 'CorrectAnswer2' }, + ], + } as ShortAnswerQuestion, + }, + ]; + const answer: AnswerType = ['CorrectAnswer1']; + const result = checkIfIsCorrect(answer, 4, mockQuestionsShort); + expect(result).toBe(true); + }); + + test('returns false for an incorrect short answer', () => { + const mockQuestionsShort: QuestionType[] = [ + { + question: { + id: '4', + type: 'Short', + choices: [ + { text: 'CorrectAnswer1' }, + { text: 'CorrectAnswer2' }, + ], + } as ShortAnswerQuestion, + }, + ]; + const answer: AnswerType = ['WrongAnswer']; + const result = checkIfIsCorrect(answer, 4, mockQuestionsShort); + expect(result).toBe(false); + }); + + test('returns true for a correct short answer with case insensitivity', () => { + const mockQuestionsShort: QuestionType[] = [ + { + question: { + id: '4', + type: 'Short', + choices: [ + { text: 'CorrectAnswer1' }, + { text: 'CorrectAnswer2' }, + ], + } as ShortAnswerQuestion, + }, + ]; + const answer: AnswerType = ['correctanswer1']; // Lowercase version of the correct answer + const result = checkIfIsCorrect(answer, 4, mockQuestionsShort); + expect(result).toBe(true); + }); + + test('returns false for a short answer question with no answer', () => { + const mockQuestionsShort: QuestionType[] = [ + { + question: { + id: '4', + type: 'Short', + choices: [ + { text: 'CorrectAnswer1' }, + { text: 'CorrectAnswer2' }, + ], + } as ShortAnswerQuestion, + }, + ]; + const answer: AnswerType = []; + const result = checkIfIsCorrect(answer, 4, mockQuestionsShort); + expect(result).toBe(false); + }); + + + test('returns true for a correct simple numerical answer', () => { + const mockQuestionsNumerical: QuestionType[] = [ + { + question: { + id: '5', + type: 'Numerical', + choices: [ + { type: 'simple', number: 42 } as SimpleNumericalAnswer, + ], + } as NumericalQuestion, + }, + ]; + const answer: AnswerType = [42]; // User's answer + const result = checkIfIsCorrect(answer, 5, mockQuestionsNumerical); + expect(result).toBe(true); + }); + + test('returns false for an incorrect simple numerical answer', () => { + const mockQuestionsNumerical: QuestionType[] = [ + { + question: { + id: '5', + type: 'Numerical', + choices: [ + { type: 'simple', number: 42 } as SimpleNumericalAnswer, + ], + } as NumericalQuestion, + }, + ]; + const answer: AnswerType = [43]; // User's answer + const result = checkIfIsCorrect(answer, 5, mockQuestionsNumerical); + expect(result).toBe(false); + }); + + test('returns true for a correct range numerical answer', () => { + const mockQuestionsNumerical: QuestionType[] = [ + { + question: { + id: '6', + type: 'Numerical', + choices: [ + { type: 'range', number: 50, range: 5 } as RangeNumericalAnswer, + ], + } as NumericalQuestion, + }, + ]; + const answer: AnswerType = [52]; // User's answer within the range (50 ± 5) + const result = checkIfIsCorrect(answer, 6, mockQuestionsNumerical); + expect(result).toBe(true); + }); + + test('returns false for an out-of-range numerical answer', () => { + const mockQuestionsNumerical: QuestionType[] = [ + { + question: { + id: '6', + type: 'Numerical', + choices: [ + { type: 'range', number: 50, range: 5 } as RangeNumericalAnswer, + ], + } as NumericalQuestion, + }, + ]; + const answer: AnswerType = [56]; // User's answer outside the range (50 ± 5) + const result = checkIfIsCorrect(answer, 6, mockQuestionsNumerical); + expect(result).toBe(false); + }); + + test('returns true for a correct high-low numerical answer', () => { + const mockQuestionsNumerical: QuestionType[] = [ + { + question: { + id: '7', + type: 'Numerical', + choices: [ + { type: 'high-low', numberHigh: 100, numberLow: 90 } as HighLowNumericalAnswer, + ], + } as NumericalQuestion, + }, + ]; + const answer: AnswerType = [95]; // User's answer within the range (90 to 100) + const result = checkIfIsCorrect(answer, 7, mockQuestionsNumerical); + expect(result).toBe(true); + }); + + test('returns false for an out-of-range high-low numerical answer', () => { + const mockQuestionsNumerical: QuestionType[] = [ + { + question: { + id: '7', + type: 'Numerical', + choices: [ + { type: 'high-low', numberHigh: 100, numberLow: 90 } as HighLowNumericalAnswer, + ], + } as NumericalQuestion, + }, + ]; + const answer: AnswerType = [105]; // User's answer outside the range (90 to 100) + const result = checkIfIsCorrect(answer, 7, mockQuestionsNumerical); + expect(result).toBe(false); + }); + + test('returns true for a correct multiple numerical answer', () => { + const mockQuestionsNumerical: QuestionType[] = [ + { + question: { + id: '8', + type: 'Numerical', + choices: [ + { + isCorrect: true, + answer: { type: 'simple', number: 42 } as SimpleNumericalAnswer, + } as MultipleNumericalAnswer, + { + isCorrect: false, + answer: { type: 'high-low', numberHigh: 100, numberLow: 90 } as HighLowNumericalAnswer, + formattedFeedback: { text: 'You guessed way too high' }, + } + ], + } as NumericalQuestion, + }, + ]; + const answer: AnswerType = [42]; // User's answer matches the correct multiple numerical answer + const result = checkIfIsCorrect(answer, 8, mockQuestionsNumerical); + expect(result).toBe(true); + }); + + test('returns false for an incorrect multiple numerical answer', () => { + const mockQuestionsNumerical: QuestionType[] = [ + { + question: { + id: '8', + type: 'Numerical', + choices: [ + { + type: 'multiple', + isCorrect: true, + answer: { type: 'simple', number: 42 } as SimpleNumericalAnswer, + } as MultipleNumericalAnswer, + ], + } as NumericalQuestion, + }, + ]; + const answer: AnswerType = [43]; // User's answer does not match the correct multiple numerical answer + const result = checkIfIsCorrect(answer, 8, mockQuestionsNumerical); + expect(result).toBe(false); + }); + +}); diff --git a/client/src/__tests__/pages/ManageRoom/ManageRoom.test.tsx b/client/src/__tests__/pages/ManageRoom/ManageRoom.test.tsx index 142f1f3..aca1d64 100644 --- a/client/src/__tests__/pages/ManageRoom/ManageRoom.test.tsx +++ b/client/src/__tests__/pages/ManageRoom/ManageRoom.test.tsx @@ -19,6 +19,11 @@ jest.mock('react-router-dom', () => ({ })); jest.mock('src/pages/Teacher/ManageRoom/RoomContext'); +jest.mock('qrcode.react', () => ({ + __esModule: true, + QRCodeCanvas: ({ value }: { value: string }) =>
{value}
, +})); + const mockSocket = { on: jest.fn(), off: jest.fn(), @@ -44,7 +49,7 @@ const mockStudents: StudentType[] = [ ]; const mockAnswerData: AnswerReceptionFromBackendType = { - answer: 'Answer1', + answer: ['Answer1'], idQuestion: 1, idUser: '1', username: 'Student 1', @@ -233,7 +238,7 @@ describe('ManageRoom', () => { await act(async () => { const createSuccessCallback = (mockSocket.on as jest.Mock).mock.calls.find(call => call[0] === 'create-success')[1]; - createSuccessCallback('test-room-name'); + createSuccessCallback('Test Room'); }); const launchButton = screen.getByText('Lancer'); @@ -256,6 +261,7 @@ describe('ManageRoom', () => { }); await waitFor(() => { + // console.info(consoleSpy.mock.calls); expect(consoleSpy).toHaveBeenCalledWith( 'Received answer from Student 1 for question 1: Answer1' ); @@ -293,4 +299,84 @@ describe('ManageRoom', () => { expect(screen.queryByText('Student 1')).not.toBeInTheDocument(); }); }); + + test('terminates the quiz and navigates to teacher dashboard when the "Terminer le quiz" button is clicked', async () => { + await act(async () => { + render( + + + + ); + }); + + await act(async () => { + const createSuccessCallback = (mockSocket.on as jest.Mock).mock.calls.find(call => call[0] === 'create-success')[1]; + createSuccessCallback('Test Room'); + }); + + fireEvent.click(screen.getByText('Lancer')); + fireEvent.click(screen.getByText('Rythme du professeur')); + fireEvent.click(screen.getAllByText('Lancer')[1]); + + await waitFor(() => { + expect(screen.getByText('Test Quiz')).toBeInTheDocument(); + }); + + const finishQuizButton = screen.getByText('Terminer le quiz'); + fireEvent.click(finishQuizButton); + + await waitFor(() => { + expect(navigate).toHaveBeenCalledWith('/teacher/dashboard'); + }); + }); + + test("Affiche la modale QR Code lorsqu’on clique sur le bouton", async () => { + render(); + + const button = screen.getByRole('button', { name: /lien de participation/i }); + fireEvent.click(button); + + await waitFor(() => { + expect(screen.getByRole('dialog')).toBeInTheDocument(); + }); + + expect(screen.getByRole('heading', { name: /Rejoindre la salle/i })).toBeInTheDocument(); + expect(screen.getByText(/Scannez ce QR code ou partagez le lien ci-dessous/i)).toBeInTheDocument(); + expect(screen.getByTestId('qr-code')).toBeInTheDocument(); + }); + + test("Ferme la modale QR Code lorsqu’on clique sur le bouton Fermer", async () => { + render(); + + fireEvent.click(screen.getByRole('button', { name: /lien de participation/i })); + + await waitFor(() => { + expect(screen.getByRole('dialog')).toBeInTheDocument(); + }); + + fireEvent.click(screen.getByRole('button', { name: /fermer/i })); + + await waitFor(() => { + expect(screen.queryByRole('dialog')).not.toBeInTheDocument(); + }); + }); + + test('Affiche le bon lien de participation', () => { + render(); + + fireEvent.click(screen.getByRole('button', { name: /lien de participation/i })); + + const roomUrl = `${window.location.origin}/student/join-room?roomName=Test Room`; + expect(screen.getByTestId('qr-code')).toHaveTextContent(roomUrl); + }); + + test('Vérifie que le QR code contient la bonne URL', () => { + render(); + + fireEvent.click(screen.getByRole('button', { name: /lien de participation/i })); + + const roomUrl = `${window.location.origin}/student/join-room?roomName=Test Room`; + expect(screen.getByTestId('qr-code')).toHaveTextContent(roomUrl); + }); }); + diff --git a/client/src/__tests__/pages/Student/StudentModeQuiz/StudentModeQuiz.test.tsx b/client/src/__tests__/pages/Student/StudentModeQuiz/StudentModeQuiz.test.tsx index 1218694..11fe682 100644 --- a/client/src/__tests__/pages/Student/StudentModeQuiz/StudentModeQuiz.test.tsx +++ b/client/src/__tests__/pages/Student/StudentModeQuiz/StudentModeQuiz.test.tsx @@ -8,7 +8,7 @@ import { QuestionType } from 'src/Types/QuestionType'; import { AnswerSubmissionToBackendType } from 'src/services/WebsocketService'; const mockGiftQuestions = parse( - `::Sample Question 1:: Sample Question 1 {=Option A ~Option B} + `::Sample Question 1:: Sample Question 1 {=Option A =Option B ~Option C} ::Sample Question 2:: Sample Question 2 {T}`); @@ -23,9 +23,6 @@ const mockSubmitAnswer = jest.fn(); const mockDisconnectWebSocket = jest.fn(); beforeEach(() => { - // Clear local storage before each test - // localStorage.clear(); - render( { fireEvent.click(screen.getByText('Répondre')); }); - expect(mockSubmitAnswer).toHaveBeenCalledWith('Option A', 1); + expect(mockSubmitAnswer).toHaveBeenCalledWith(['Option A'], 1); }); test('handles shows feedback for an already answered question', async () => { @@ -65,13 +62,13 @@ describe('StudentModeQuiz', () => { act(() => { fireEvent.click(screen.getByText('Répondre')); }); - expect(mockSubmitAnswer).toHaveBeenCalledWith('Option A', 1); + expect(mockSubmitAnswer).toHaveBeenCalledWith(['Option A'], 1); const firstButtonA = screen.getByRole("button", {name: '✅ A Option A'}); expect(firstButtonA).toBeInTheDocument(); expect(firstButtonA.querySelector('.selected')).toBeInTheDocument(); - expect(screen.getByRole("button", {name: '❌ B Option B'})).toBeInTheDocument(); + expect(screen.getByRole("button", {name: '✅ B Option B'})).toBeInTheDocument(); expect(screen.queryByText('Répondre')).not.toBeInTheDocument(); // Navigate to the next question @@ -87,12 +84,12 @@ describe('StudentModeQuiz', () => { }); expect(await screen.findByText('Sample Question 1')).toBeInTheDocument(); - // Since answers are mocked, the it doesn't recognize the question as already answered + // Since answers are mocked, it doesn't recognize the question as already answered // TODO these tests are partially faked, need to be fixed if we can mock the answers // const buttonA = screen.getByRole("button", {name: '✅ A Option A'}); const buttonA = screen.getByRole("button", {name: 'A Option A'}); expect(buttonA).toBeInTheDocument(); - // const buttonB = screen.getByRole("button", {name: '❌ B Option B'}); + // const buttonB = screen.getByRole("button", {name: '✅ B Option B'}); const buttonB = screen.getByRole("button", {name: 'B Option B'}); expect(buttonB).toBeInTheDocument(); // // "Option A" div inside the name of button should have selected class @@ -122,4 +119,30 @@ describe('StudentModeQuiz', () => { expect(screen.getByText('Sample Question 2')).toBeInTheDocument(); expect(screen.getByText('Répondre')).toBeInTheDocument(); }); + + // le test suivant est fait dans MultipleChoiceQuestionDisplay.test.tsx +// test('allows multiple answers to be selected for a question', async () => { +// // Simulate selecting multiple answers +// act(() => { +// fireEvent.click(screen.getByText('Option A')); +// }); +// act(() => { +// fireEvent.click(screen.getByText('Option B')); +// }); + +// // Simulate submitting the answers +// act(() => { +// fireEvent.click(screen.getByText('Répondre')); +// }); + +// // Verify that the mockSubmitAnswer function is called with both answers +// expect(mockSubmitAnswer).toHaveBeenCalledWith(['Option A', 'Option B'], 1); + +// // Verify that the selected answers are displayed as selected +// const buttonA = screen.getByRole('button', { name: '✅ A Option A' }); +// const buttonB = screen.getByRole('button', { name: '✅ B Option B' }); +// expect(buttonA).toBeInTheDocument(); +// expect(buttonB).toBeInTheDocument(); +// }); + }); diff --git a/client/src/__tests__/pages/Student/TeacherModeQuiz/TeacherModeQuiz.test.tsx b/client/src/__tests__/pages/Student/TeacherModeQuiz/TeacherModeQuiz.test.tsx index 6a4ec59..4cd5a8d 100644 --- a/client/src/__tests__/pages/Student/TeacherModeQuiz/TeacherModeQuiz.test.tsx +++ b/client/src/__tests__/pages/Student/TeacherModeQuiz/TeacherModeQuiz.test.tsx @@ -63,7 +63,8 @@ describe('TeacherModeQuiz', () => { act(() => { fireEvent.click(screen.getByText('Répondre')); }); - expect(mockSubmitAnswer).toHaveBeenCalledWith('Option A', 1); + expect(mockSubmitAnswer).toHaveBeenCalledWith(['Option A'], 1); + mockSubmitAnswer.mockClear(); }); test('handles shows feedback for an already answered question', () => { @@ -74,7 +75,8 @@ describe('TeacherModeQuiz', () => { act(() => { fireEvent.click(screen.getByText('Répondre')); }); - expect(mockSubmitAnswer).toHaveBeenCalledWith('Option A', 1); + expect(mockSubmitAnswer).toHaveBeenCalledWith(['Option A'], 1); + mockSubmitAnswer.mockClear(); mockQuestion = mockQuestions[1].question as MultipleChoiceQuestion; // Navigate to the next question by re-rendering with new props act(() => { diff --git a/client/src/__tests__/pages/Teacher/Share/Share.test.tsx b/client/src/__tests__/pages/Teacher/Share/Share.test.tsx new file mode 100644 index 0000000..9cc3203 --- /dev/null +++ b/client/src/__tests__/pages/Teacher/Share/Share.test.tsx @@ -0,0 +1,95 @@ +import React from 'react'; +import { render, screen, fireEvent, waitFor } from '@testing-library/react'; +import { MemoryRouter, Route, Routes, useParams } from 'react-router-dom'; +import Share from '../../../../pages/Teacher/Share/Share.tsx'; +import ApiService from '../../../../services/ApiService'; +import '@testing-library/jest-dom'; + +// Mock the ApiService and react-router-dom +jest.mock('../../../../services/ApiService'); +jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + useParams: jest.fn(), + useNavigate: jest.fn(), +})); + +describe('Share Component', () => { + const mockNavigate = jest.fn(); + const mockUseParams = useParams as jest.Mock; + const mockApiService = ApiService as jest.Mocked; + + beforeEach(() => { + jest.clearAllMocks(); + mockUseParams.mockReturnValue({ id: 'quiz123' }); + require('react-router-dom').useNavigate.mockReturnValue(mockNavigate); + }); + + const renderComponent = (initialEntries = ['/share/quiz123']) => { + return render( + + + } /> + Dashboard
} /> + Login} /> + + + ); + }; + + it('should show loading state initially', () => { + mockApiService.getAllQuizIds.mockResolvedValue([]); + mockApiService.getUserFolders.mockResolvedValue([]); + mockApiService.getSharedQuiz.mockResolvedValue('Test Quiz'); + + renderComponent(); + expect(screen.getByText('Chargement...')).toBeInTheDocument(); + }); + + it('should redirect to login if not authenticated', async () => { + mockApiService.isLoggedIn.mockReturnValue(false); + + renderComponent(); + + await waitFor(() => { + expect(mockNavigate).toHaveBeenCalledWith('/login'); + }); + }); + + it('should show "quiz already exists" message when quiz exists', async () => { + mockApiService.isLoggedIn.mockReturnValue(true); + mockApiService.getAllQuizIds.mockResolvedValue(['quiz123']); + + renderComponent(); + + await waitFor(() => { + expect(screen.getByText('Quiz déjà existant')).toBeInTheDocument(); + expect(screen.getByText(/Le quiz que vous essayez d'importer existe déjà sur votre compte/i)).toBeInTheDocument(); + expect(screen.getByText('Retour au tableau de bord')).toBeInTheDocument(); + }); + }); + + it('should navigate to dashboard when clicking return button in "quiz exists" view', async () => { + mockApiService.isLoggedIn.mockReturnValue(true); + mockApiService.getAllQuizIds.mockResolvedValue(['quiz123']); + + renderComponent(); + + await waitFor(() => { + fireEvent.click(screen.getByText('Retour au tableau de bord')); + expect(mockNavigate).toHaveBeenCalledWith('/teacher/dashboard'); + }); + }); + + it('should show error when no folders exist', async () => { + mockApiService.isLoggedIn.mockReturnValue(true); + mockApiService.getAllQuizIds.mockResolvedValue([]); + mockApiService.getUserFolders.mockResolvedValue([]); + + renderComponent(); + + await waitFor(() => { + expect(mockNavigate).toHaveBeenCalledWith('/teacher/dashboard'); + }); + }); + +}); \ No newline at end of file diff --git a/client/src/__tests__/services/ShareQuizService.test.tsx b/client/src/__tests__/services/ShareQuizService.test.tsx new file mode 100644 index 0000000..78c1f65 --- /dev/null +++ b/client/src/__tests__/services/ShareQuizService.test.tsx @@ -0,0 +1,42 @@ +import axios from 'axios'; +import ApiService from '../../services/ApiService'; +import { ENV_VARIABLES } from '../../constants'; + +jest.mock('axios'); +const mockedAxios = axios as jest.Mocked; + + describe('getSharedQuiz', () => { + it('should call the API to get a shared quiz and return the quiz data on success', async () => { + const quizId = '123'; + const quizData = 'Quiz data'; + const response = { status: 200, data: { data: quizData } }; + mockedAxios.get.mockResolvedValue(response); + + const result = await ApiService.getSharedQuiz(quizId); + + expect(mockedAxios.get).toHaveBeenCalledWith( + `${ENV_VARIABLES.VITE_BACKEND_URL}/api/quiz/getShare/${quizId}`, + { headers: expect.any(Object) } + ); + expect(result).toBe(quizData); + }); + + it('should return an error message if the API call fails', async () => { + const quizId = '123'; + const errorMessage = 'An unexpected error occurred.'; + mockedAxios.get.mockRejectedValue({ response: { data: { error: errorMessage } } }); + + const result = await ApiService.getSharedQuiz(quizId); + + expect(result).toBe(errorMessage); + }); + + it('should return a generic error message if an unexpected error occurs', async () => { + const quizId = '123'; + mockedAxios.get.mockRejectedValue(new Error('Unexpected error')); + + const result = await ApiService.getSharedQuiz(quizId); + + expect(result).toBe('An unexpected error occurred.'); + }); + }); \ No newline at end of file diff --git a/client/src/__tests__/services/getAllQuizIdsService.test.tsx b/client/src/__tests__/services/getAllQuizIdsService.test.tsx new file mode 100644 index 0000000..222e7db --- /dev/null +++ b/client/src/__tests__/services/getAllQuizIdsService.test.tsx @@ -0,0 +1,42 @@ +import axios from 'axios'; +import ApiService from '../../services/ApiService'; +import { FolderType } from '../../Types/FolderType'; +import { QuizType } from '../../Types/QuizType'; + +jest.mock('axios'); +const mockedAxios = axios as jest.Mocked; + +describe('ApiService', () => { + describe('getAllQuizIds', () => { + it('should return all quiz IDs from all folders', async () => { + const folders: FolderType[] = [ + { _id: 'folder1', title: 'Folder 1', userId: 'user1', created_at: new Date().toISOString() }, + { _id: 'folder2', title: 'Folder 2', userId: 'user2', created_at: new Date().toISOString() } + ]; + const quizzesFolder1: QuizType[] = [ + { _id: 'quiz1', title: 'Quiz 1', content: [], folderId: 'folder1', folderName: 'Folder 1', userId: 'user1', created_at: new Date(), updated_at: new Date() }, + { _id: 'quiz2', title: 'Quiz 2', content: [], folderId: 'folder1', folderName: 'Folder 1', userId: 'user1', created_at: new Date(), updated_at: new Date() } + ]; + const quizzesFolder2: QuizType[] = [ + { _id: 'quiz3', title: 'Quiz 3', content: [], folderId: 'folder2', folderName: 'Folder 2', userId: 'user2', created_at: new Date(), updated_at: new Date() } + ]; + + mockedAxios.get + .mockResolvedValueOnce({ status: 200, data: { data: folders } }) + .mockResolvedValueOnce({ status: 200, data: { data: quizzesFolder1 } }) + .mockResolvedValueOnce({ status: 200, data: { data: quizzesFolder2 } }); + + const result = await ApiService.getAllQuizIds(); + + expect(result).toEqual(['quiz1', 'quiz2', 'quiz3']); + }); + + it('should return an empty array if no folders are found', async () => { + mockedAxios.get.mockResolvedValueOnce({ status: 200, data: { data: [] } }); + + const result = await ApiService.getAllQuizIds(); + + expect(result).toEqual([]); + }); + }); +}); \ No newline at end of file diff --git a/client/src/components/GiftTemplate/templates/MultipleChoiceAnswersTemplate.ts b/client/src/components/GiftTemplate/templates/MultipleChoiceAnswersTemplate.ts index 2bc423d..76c151d 100644 --- a/client/src/components/GiftTemplate/templates/MultipleChoiceAnswersTemplate.ts +++ b/client/src/components/GiftTemplate/templates/MultipleChoiceAnswersTemplate.ts @@ -13,14 +13,14 @@ type AnswerFeedbackOptions = TemplateOptions & Pick isCorrect === true).length === 0; + const hasManyCorrectChoices = choices.filter(({ isCorrect }) => isCorrect === true).length > 1; const prompt = `Choisir une réponse${ - isMultipleAnswer ? ` ou plusieurs` : `` + hasManyCorrectChoices ? ` ou plusieurs` : `` }:`; const result = choices .map(({ weight, isCorrect, formattedText, formattedFeedback }) => { @@ -32,12 +32,12 @@ export default function MultipleChoiceAnswersTemplate({ choices }: MultipleChoic const inputId = `id${nanoid(6)}`; const isPositiveWeight = (weight != undefined) && (weight > 0); - const isCorrectOption = isMultipleAnswer ? isPositiveWeight : isCorrect; + const isCorrectOption = hasManyCorrectChoices ? isPositiveWeight || isCorrect : isCorrect; return `
${AnswerWeight({ weight: weight })}