From 39a7ecce31e530ce036baf511920fb0d44849efc Mon Sep 17 00:00:00 2001 From: "C. Fuhrman" Date: Thu, 23 Jan 2025 22:38:22 -0500 Subject: [PATCH 01/29] Some tests passing (MultipleChoiceQuestionDisplay.test.ts) --- client/package-lock.json | 215 +++++++++++------- client/package.json | 4 +- client/src/Types/QuestionType.tsx | 5 - .../src/__tests__/Types/QuestionType.test.tsx | 21 +- .../components/GiftTemplate/TextType.test.ts | 2 +- .../MultipleChoiceQuestionDisplay.test.tsx} | 32 +-- .../components/Questions/Question.test.tsx | 2 +- .../StudentModeQuiz/StudentModeQuiz.test.tsx | 13 +- .../GiftTemplate/templates/TextType.ts | 2 +- .../MultipleChoiceQuestionDisplay.tsx} | 50 ++-- .../NumericalQuestion/NumericalQuestion.tsx | 35 ++- .../{Question.tsx => QuestionDisplay.tsx} | 214 +++++++++-------- .../StudentModeQuiz/StudentModeQuiz.tsx | 2 +- .../TeacherModeQuiz/TeacherModeQuiz.tsx | 2 +- .../pages/Teacher/ManageRoom/ManageRoom.tsx | 4 +- 15 files changed, 327 insertions(+), 276 deletions(-) delete mode 100644 client/src/Types/QuestionType.tsx rename client/src/__tests__/components/Questions/{MultipleChoiceQuestion/MultipleChoiceQuestion.test.tsx => MultipleChoiceQuestionDisplay/MultipleChoiceQuestionDisplay.test.tsx} (62%) rename client/src/components/Questions/{MultipleChoiceQuestion/MultipleChoiceQuestion.tsx => MultipleChoiceQuestionDisplay/MultipleChoiceQuestionDisplay.tsx} (62%) rename client/src/components/Questions/{Question.tsx => QuestionDisplay.tsx} (69%) diff --git a/client/package-lock.json b/client/package-lock.json index edc30d8..708d145 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -21,7 +21,7 @@ "axios": "^1.6.7", "dompurify": "^3.2.3", "esbuild": "^0.23.1", - "gift-pegjs": "^1.0.2", + "gift-pegjs": "^2.0.0-beta.0", "jest-environment-jsdom": "^29.7.0", "katex": "^0.16.11", "marked": "^14.1.2", @@ -2546,7 +2546,7 @@ "version": "4.4.1", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz", "integrity": "sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "eslint-visitor-keys": "^3.4.3" @@ -2565,7 +2565,7 @@ "version": "4.12.1", "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" @@ -2575,7 +2575,7 @@ "version": "0.19.1", "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.1.tgz", "integrity": "sha512-fo6Mtm5mWyKjA/Chy1BYTdn5mGJoDNjC7C64ug20ADsRDGrA85bN3uK3MaKbeRkRuuIEAR5N33Jr1pbm411/PA==", - "devOptional": true, + "dev": true, "license": "Apache-2.0", "dependencies": { "@eslint/object-schema": "^2.1.5", @@ -2590,7 +2590,7 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -2601,7 +2601,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "devOptional": true, + "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" @@ -2614,7 +2614,7 @@ "version": "0.10.0", "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.10.0.tgz", "integrity": "sha512-gFHJ+xBOo4G3WRlR1e/3G8A6/KZAH6zcE/hkLRCZTi/B9avAG365QhFA8uOGzTMqgTghpn7/fSnscW++dpMSAw==", - "devOptional": true, + "dev": true, "license": "Apache-2.0", "dependencies": { "@types/json-schema": "^7.0.15" @@ -2627,7 +2627,7 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.2.0.tgz", "integrity": "sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "ajv": "^6.12.4", @@ -2651,7 +2651,7 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -2662,7 +2662,7 @@ "version": "14.0.0", "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=18" @@ -2675,7 +2675,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "devOptional": true, + "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" @@ -2688,7 +2688,7 @@ "version": "9.18.0", "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.18.0.tgz", "integrity": "sha512-fK6L7rxcq6/z+AaQMtiFTkvbHkBLNlwyRxHpKawP0x3u9+NC6MQTnFW+AdpwC6gfHTW0051cokQgtTN2FqlxQA==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2698,7 +2698,7 @@ "version": "2.1.5", "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.5.tgz", "integrity": "sha512-o0bhxnL89h5Bae5T318nFoFzGy+YE5i/gGkoPAgkmTVdRKTiv3p8JHevPiPaMwoloKfEiiaHlawCqaZMqRm+XQ==", - "devOptional": true, + "dev": true, "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2708,7 +2708,7 @@ "version": "0.2.5", "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.5.tgz", "integrity": "sha512-lB05FkqEdUg2AA0xEbUz0SnkXT1LcCTa438W4IWTUh4hdOnVbQyOJ81OrDXsJk/LSiJHubgGEFoR5EHq1NsH1A==", - "devOptional": true, + "dev": true, "license": "Apache-2.0", "dependencies": { "@eslint/core": "^0.10.0", @@ -2815,7 +2815,7 @@ "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", - "devOptional": true, + "dev": true, "license": "Apache-2.0", "engines": { "node": ">=18.18.0" @@ -2825,7 +2825,7 @@ "version": "0.16.6", "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", - "devOptional": true, + "dev": true, "license": "Apache-2.0", "dependencies": { "@humanfs/core": "^0.19.1", @@ -2839,7 +2839,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "devOptional": true, + "dev": true, "license": "Apache-2.0", "engines": { "node": ">=12.22" @@ -2853,7 +2853,7 @@ "version": "0.3.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", - "devOptional": true, + "dev": true, "license": "Apache-2.0", "engines": { "node": ">=18.18" @@ -3850,6 +3850,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3863,6 +3864,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3876,6 +3878,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3889,6 +3892,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3902,6 +3906,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3915,6 +3920,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3928,6 +3934,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3941,6 +3948,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3954,6 +3962,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3967,6 +3976,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3980,6 +3990,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3993,6 +4004,7 @@ "cpu": [ "riscv64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4006,6 +4018,7 @@ "cpu": [ "s390x" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4019,6 +4032,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4032,6 +4046,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4045,6 +4060,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4058,6 +4074,7 @@ "cpu": [ "ia32" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4071,6 +4088,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4111,7 +4129,7 @@ "version": "1.7.40", "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.7.40.tgz", "integrity": "sha512-0HIzM5vigVT5IvNum+pPuST9p8xFhN6mhdIKju7qYYeNuZG78lwms/2d8WgjTJJlzp6JlPguXGrMMNzjQw0qNg==", - "devOptional": true, + "dev": true, "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { @@ -4153,6 +4171,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -4169,6 +4188,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -4185,6 +4205,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -4201,6 +4222,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -4217,6 +4239,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -4233,6 +4256,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -4249,6 +4273,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -4265,6 +4290,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -4281,6 +4307,7 @@ "cpu": [ "ia32" ], + "dev": true, "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -4297,6 +4324,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -4310,14 +4338,14 @@ "version": "0.1.3", "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", - "devOptional": true, + "dev": true, "license": "Apache-2.0" }, "node_modules/@swc/types": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.13.tgz", "integrity": "sha512-JL7eeCk6zWCbiYQg2xQSdLXQJl8Qoc9rXmG2cEKvHe3CKwMHwHGpfOb8frzNLmbycOo6I51qxnLnn9ESf4I20Q==", - "devOptional": true, + "dev": true, "license": "Apache-2.0", "dependencies": { "@swc/counter": "^0.1.3" @@ -4511,6 +4539,7 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "dev": true, "license": "MIT" }, "node_modules/@types/graceful-fs": { @@ -4610,7 +4639,7 @@ "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/@types/katex": { @@ -4992,7 +5021,7 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "devOptional": true, + "dev": true, "license": "MIT", "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" @@ -5026,7 +5055,7 @@ "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", @@ -5101,7 +5130,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, + "dev": true, "license": "Python-2.0" }, "node_modules/aria-query": { @@ -5915,7 +5944,7 @@ "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "path-key": "^3.1.0", @@ -6086,7 +6115,7 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/deepmerge": { @@ -6636,7 +6665,7 @@ "version": "9.18.0", "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.18.0.tgz", "integrity": "sha512-+waTfRWQlSbpt3KWE+CjrPPYnbq9kfZIYUqapc0uBXyjTp8aYXZDsUH16m39Ryq3NjAVP4tjuF7KaukeqoCoaA==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", @@ -6794,7 +6823,7 @@ "version": "8.2.0", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.2.0.tgz", "integrity": "sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==", - "devOptional": true, + "dev": true, "license": "BSD-2-Clause", "dependencies": { "esrecurse": "^4.3.0", @@ -6811,7 +6840,7 @@ "version": "3.4.3", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "devOptional": true, + "dev": true, "license": "Apache-2.0", "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -6824,7 +6853,7 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.1.tgz", "integrity": "sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==", - "devOptional": true, + "dev": true, "license": "Apache-2.0", "engines": { "node": ">=18.18" @@ -6838,7 +6867,7 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -6849,7 +6878,7 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", - "devOptional": true, + "dev": true, "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -6862,7 +6891,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "devOptional": true, + "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" @@ -6875,7 +6904,7 @@ "version": "10.3.0", "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", - "devOptional": true, + "dev": true, "license": "BSD-2-Clause", "dependencies": { "acorn": "^8.14.0", @@ -6893,7 +6922,7 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", - "devOptional": true, + "dev": true, "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -6919,7 +6948,7 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", - "devOptional": true, + "dev": true, "license": "BSD-3-Clause", "dependencies": { "estraverse": "^5.1.0" @@ -6932,7 +6961,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "devOptional": true, + "dev": true, "license": "BSD-2-Clause", "dependencies": { "estraverse": "^5.2.0" @@ -7025,7 +7054,7 @@ "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, + "dev": true, "license": "MIT" }, "node_modules/fast-glob": { @@ -7060,14 +7089,14 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/fastq": { @@ -7093,7 +7122,7 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "flat-cache": "^4.0.0" @@ -7147,7 +7176,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "locate-path": "^6.0.0", @@ -7164,7 +7193,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "flatted": "^3.2.9", @@ -7178,7 +7207,7 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", - "devOptional": true, + "dev": true, "license": "ISC" }, "node_modules/follow-redirects": { @@ -7410,10 +7439,9 @@ } }, "node_modules/gift-pegjs": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/gift-pegjs/-/gift-pegjs-1.0.2.tgz", - "integrity": "sha512-S/A2wBDdia2QWKpB5FtASx1gguep1wg5If5glDWJgUMiABICJT7ogArGfsdgozevhBdbdOiHhrykJP86hbgvRw==", - "license": "MIT", + "version": "2.0.0-beta.0", + "resolved": "https://registry.npmjs.org/gift-pegjs/-/gift-pegjs-2.0.0-beta.0.tgz", + "integrity": "sha512-M/U4zue2F9TXGEmIIf+nL9JWwudAR5pK4gLDeF2pFfKDNPF1MoCgCS9Vf37NH0oP+Grc7SJbq8FVUefqPH99NA==", "dependencies": { "pegjs": "^0.10.x" } @@ -7444,7 +7472,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "devOptional": true, + "dev": true, "license": "ISC", "dependencies": { "is-glob": "^4.0.3" @@ -7732,7 +7760,7 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">= 4" @@ -7778,7 +7806,7 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=0.8.19" @@ -8281,7 +8309,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "devOptional": true, + "dev": true, "license": "ISC" }, "node_modules/istanbul-lib-coverage": { @@ -9319,7 +9347,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -9389,7 +9417,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/json-parse-even-better-errors": { @@ -9402,14 +9430,14 @@ "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, + "dev": true, "license": "MIT" }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/json5": { @@ -9482,7 +9510,7 @@ "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "json-buffer": "3.0.1" @@ -9512,7 +9540,7 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "prelude-ls": "^1.2.1", @@ -9532,7 +9560,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "p-locate": "^5.0.0" @@ -9569,7 +9597,7 @@ "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/longest-streak": { @@ -10326,16 +10354,15 @@ "license": "MIT" }, "node_modules/nanoid": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.0.8.tgz", - "integrity": "sha512-TcJPw+9RV9dibz1hHUzlLVy8N4X9TnwirAjrU08Juo6BNKggzVfP2ZJ/3ZUSq15Xl5i85i+Z89XBO90pB2PghQ==", + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.0.9.tgz", + "integrity": "sha512-Aooyr6MXU6HpvvWXKoVoXwKMs/KyVakWwg7xQfv5/S/RIgJMy0Ifa45H9qqYy7pTCszrHzP21Uk4PZq2HpEM8Q==", "funding": [ { "type": "github", "url": "https://github.com/sponsors/ai" } ], - "license": "MIT", "bin": { "nanoid": "bin/nanoid.js" }, @@ -10347,7 +10374,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/node-int64": { @@ -10527,7 +10554,7 @@ "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "deep-is": "^0.1.3", @@ -10563,7 +10590,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "yocto-queue": "^0.1.0" @@ -10579,7 +10606,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "p-limit": "^3.0.2" @@ -10647,7 +10674,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -10810,6 +10837,7 @@ "version": "8.4.47", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==", + "dev": true, "funding": [ { "type": "opencollective", @@ -10838,6 +10866,7 @@ "version": "3.3.8", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", + "dev": true, "funding": [ { "type": "github", @@ -10856,7 +10885,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">= 0.8.0" @@ -11345,6 +11374,7 @@ "version": "4.24.3", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.24.3.tgz", "integrity": "sha512-HBW896xR5HGmoksbi3JBDtmVzWiPAYqp7wip50hjQ67JbDz61nyoMPdqu1DvVW9asYb2M65Z20ZHsyJCMqMyDg==", + "dev": true, "license": "MIT", "dependencies": { "@types/estree": "1.0.6" @@ -11546,7 +11576,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" @@ -11559,7 +11589,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -11705,6 +11735,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -11935,7 +11966,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -12203,7 +12234,7 @@ "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "prelude-ls": "^1.2.1" @@ -12315,6 +12346,7 @@ "version": "5.6.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", + "dev": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -12548,7 +12580,7 @@ "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "devOptional": true, + "dev": true, "license": "BSD-2-Clause", "dependencies": { "punycode": "^2.1.0" @@ -12637,6 +12669,7 @@ "version": "5.4.10", "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.10.tgz", "integrity": "sha512-1hvaPshuPUtxeQ0hsVH3Mud0ZanOLwVTneA1EgbAM5LhaZEqyPWGRQ7BtaMvUrTDeEaC8pxtj6a6jku3x4z6SQ==", + "dev": true, "license": "MIT", "dependencies": { "esbuild": "^0.21.3", @@ -12775,6 +12808,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -12791,6 +12825,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -12807,6 +12842,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -12823,6 +12859,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -12839,6 +12876,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -12855,6 +12893,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -12871,6 +12910,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -12887,6 +12927,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -12903,6 +12944,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -12919,6 +12961,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -12935,6 +12978,7 @@ "cpu": [ "ia32" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -12951,6 +12995,7 @@ "cpu": [ "loong64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -12967,6 +13012,7 @@ "cpu": [ "mips64el" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -12983,6 +13029,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -12999,6 +13046,7 @@ "cpu": [ "riscv64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -13015,6 +13063,7 @@ "cpu": [ "s390x" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -13031,6 +13080,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -13047,6 +13097,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -13063,6 +13114,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -13079,6 +13131,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -13095,6 +13148,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -13111,6 +13165,7 @@ "cpu": [ "ia32" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -13127,6 +13182,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -13140,6 +13196,7 @@ "version": "0.21.5", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, "hasInstallScript": true, "license": "MIT", "bin": { @@ -13349,7 +13406,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "devOptional": true, + "dev": true, "license": "ISC", "dependencies": { "isexe": "^2.0.0" @@ -13453,7 +13510,7 @@ "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -13610,7 +13667,7 @@ "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=10" diff --git a/client/package.json b/client/package.json index 052063d..eb8d3e3 100644 --- a/client/package.json +++ b/client/package.json @@ -25,7 +25,7 @@ "axios": "^1.6.7", "dompurify": "^3.2.3", "esbuild": "^0.23.1", - "gift-pegjs": "^1.0.2", + "gift-pegjs": "file:../GIFT-grammar-PEG.js", "jest-environment-jsdom": "^29.7.0", "katex": "^0.16.11", "marked": "^14.1.2", @@ -69,4 +69,4 @@ "vite": "^5.4.5", "vite-plugin-environment": "^1.1.3" } -} \ No newline at end of file +} diff --git a/client/src/Types/QuestionType.tsx b/client/src/Types/QuestionType.tsx deleted file mode 100644 index c1e7182..0000000 --- a/client/src/Types/QuestionType.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { GIFTQuestion } from 'gift-pegjs'; - -export interface QuestionType { - question: GIFTQuestion; -} diff --git a/client/src/__tests__/Types/QuestionType.test.tsx b/client/src/__tests__/Types/QuestionType.test.tsx index 43c2900..d02d52b 100644 --- a/client/src/__tests__/Types/QuestionType.test.tsx +++ b/client/src/__tests__/Types/QuestionType.test.tsx @@ -1,6 +1,5 @@ //QuestionType.test.tsx -import { GIFTQuestion } from 'gift-pegjs'; -import { QuestionType } from '../../Types/QuestionType'; +import { Question } from 'gift-pegjs'; const sampleStem = 'Sample question stem'; const options = ['Option A', 'Option B']; @@ -8,30 +7,28 @@ const sampleFormat = 'plain'; const sampleType = 'MC'; const sampleTitle = 'Sample Question'; -const mockQuestion: GIFTQuestion = { +const mockQuestion: Question = { id: '1', type: sampleType, - stem: { format: sampleFormat, text: sampleStem }, + formattedStem: { format: sampleFormat, text: sampleStem }, title: sampleTitle, hasEmbeddedAnswers: false, - globalFeedback: null, choices: [ - { text: { format: sampleFormat, text: options[0] }, isCorrect: true, weight: 1, feedback: null }, - { text: { format: sampleFormat, text: options[1] }, isCorrect: false, weight: 0, feedback: null }, + { formattedText: { format: sampleFormat, text: options[0] }, isCorrect: true, weight: 1 }, + { formattedText: { format: sampleFormat, text: options[1] }, isCorrect: false, weight: 0 }, ], }; -const mockQuestionType: QuestionType = { - question: mockQuestion, -}; +const mockQuestionType = mockQuestion; -describe('QuestionType', () => { +// test seems useless (it's broken) now that gift-pegjs has TypeScript types (and its own tests) +describe.skip('QuestionType', () => { test('has the expected structure', () => { expect(mockQuestionType).toEqual(expect.objectContaining({ question: expect.any(Object), })); - expect(mockQuestionType.question).toEqual(expect.objectContaining({ + expect(mockQuestionType).toEqual(expect.objectContaining({ id: expect.any(String), type: expect.any(String), stem: expect.objectContaining({ diff --git a/client/src/__tests__/components/GiftTemplate/TextType.test.ts b/client/src/__tests__/components/GiftTemplate/TextType.test.ts index 9909686..db5ab70 100644 --- a/client/src/__tests__/components/GiftTemplate/TextType.test.ts +++ b/client/src/__tests__/components/GiftTemplate/TextType.test.ts @@ -1,7 +1,7 @@ // TextType.test.ts -import { TextFormat } from "gift-pegjs"; import textType from "src/components/GiftTemplate/templates/TextType"; +import { TextFormat } from "gift-pegjs"; describe('TextType', () => { it('should format text with basic characters correctly', () => { diff --git a/client/src/__tests__/components/Questions/MultipleChoiceQuestion/MultipleChoiceQuestion.test.tsx b/client/src/__tests__/components/Questions/MultipleChoiceQuestionDisplay/MultipleChoiceQuestionDisplay.test.tsx similarity index 62% rename from client/src/__tests__/components/Questions/MultipleChoiceQuestion/MultipleChoiceQuestion.test.tsx rename to client/src/__tests__/components/Questions/MultipleChoiceQuestionDisplay/MultipleChoiceQuestionDisplay.test.tsx index 784ea81..0e68c39 100644 --- a/client/src/__tests__/components/Questions/MultipleChoiceQuestion/MultipleChoiceQuestion.test.tsx +++ b/client/src/__tests__/components/Questions/MultipleChoiceQuestionDisplay/MultipleChoiceQuestionDisplay.test.tsx @@ -1,35 +1,39 @@ import React from 'react'; import { render, screen, fireEvent } from '@testing-library/react'; import '@testing-library/jest-dom'; -import MultipleChoiceQuestion from 'src/components/Questions/MultipleChoiceQuestion/MultipleChoiceQuestion'; +import MultipleChoiceQuestionDisplay from 'src/components/Questions/MultipleChoiceQuestionDisplay/MultipleChoiceQuestionDisplay'; import { act } from 'react'; import { MemoryRouter } from 'react-router-dom'; +import { MultipleChoiceQuestion, parse } from 'gift-pegjs'; -const questionStem = 'Question stem'; -const sampleFeedback = 'Feedback'; +const questions = parse( + `::Sample Question 1:: Question stem + { + =Choice 1 + ~Choice 2 + }`) as MultipleChoiceQuestion[]; -describe('MultipleChoiceQuestion', () => { +const question = questions[0]; + +describe('MultipleChoiceQuestionDisplay', () => { const mockHandleOnSubmitAnswer = jest.fn(); - const choices = [ - { feedback: null, isCorrect: true, text: { format: 'plain', text: 'Choice 1' } }, - { feedback: null, isCorrect: false, text: { format: 'plain', text: 'Choice 2' } } - ]; + const choices = question.choices; beforeEach(() => { render( - + showAnswer={false} + /> ); }); test('renders the question and choices', () => { - expect(screen.getByText(questionStem)).toBeInTheDocument(); + expect(screen.getByText(question.formattedStem.text)).toBeInTheDocument(); choices.forEach((choice) => { - expect(screen.getByText(choice.text.text)).toBeInTheDocument(); + expect(screen.getByText(choice.formattedText.text)).toBeInTheDocument(); }); }); diff --git a/client/src/__tests__/components/Questions/Question.test.tsx b/client/src/__tests__/components/Questions/Question.test.tsx index cd04c8a..465ae7a 100644 --- a/client/src/__tests__/components/Questions/Question.test.tsx +++ b/client/src/__tests__/components/Questions/Question.test.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { render, screen, fireEvent } from '@testing-library/react'; import '@testing-library/jest-dom'; -import Questions from 'src/components/Questions/Question'; +import Questions from 'src/components/Questions/QuestionDisplay'; import { GIFTQuestion } from 'gift-pegjs'; // diff --git a/client/src/__tests__/pages/Student/StudentModeQuiz/StudentModeQuiz.test.tsx b/client/src/__tests__/pages/Student/StudentModeQuiz/StudentModeQuiz.test.tsx index 85d4a7d..f031983 100644 --- a/client/src/__tests__/pages/Student/StudentModeQuiz/StudentModeQuiz.test.tsx +++ b/client/src/__tests__/pages/Student/StudentModeQuiz/StudentModeQuiz.test.tsx @@ -1,21 +1,20 @@ import React from 'react'; import { render, screen, fireEvent, act } from '@testing-library/react'; import '@testing-library/jest-dom'; -import { parse } from 'gift-pegjs'; import { MemoryRouter } from 'react-router-dom'; -import { QuestionType } from '../../../../Types/QuestionType'; +import { Question } from 'gift-pegjs'; import StudentModeQuiz from 'src/components/StudentModeQuiz/StudentModeQuiz'; +import { parse } from 'gift-pegjs'; const mockGiftQuestions = parse( `::Sample Question 1:: Sample Question 1 {=Option A ~Option B} ::Sample Question 2:: Sample Question 2 {T}`); -const mockQuestions: QuestionType[] = mockGiftQuestions.map((question, index) => { - question.id = (index + 1).toString(); - const newMockQuestion: QuestionType = { - question: question, - }; +const mockQuestions: Question[] = mockGiftQuestions.map((question, index) => { + if (question.type !== "Category") + question.id = (index + 1).toString(); + const newMockQuestion = question; return newMockQuestion; }); diff --git a/client/src/components/GiftTemplate/templates/TextType.ts b/client/src/components/GiftTemplate/templates/TextType.ts index 02a7a14..0247d3f 100644 --- a/client/src/components/GiftTemplate/templates/TextType.ts +++ b/client/src/components/GiftTemplate/templates/TextType.ts @@ -28,7 +28,7 @@ export function formatLatex(text: string): string { * @see marked * @see katex */ -export default function textType({ text }: TextTypeOptions) { +export function textType({ text }: TextTypeOptions) { const formatText = formatLatex(text.text.trim()); // latex needs pure "&", ">", etc. Must not be escaped let parsedText = ''; switch (text.format) { diff --git a/client/src/components/Questions/MultipleChoiceQuestion/MultipleChoiceQuestion.tsx b/client/src/components/Questions/MultipleChoiceQuestionDisplay/MultipleChoiceQuestionDisplay.tsx similarity index 62% rename from client/src/components/Questions/MultipleChoiceQuestion/MultipleChoiceQuestion.tsx rename to client/src/components/Questions/MultipleChoiceQuestionDisplay/MultipleChoiceQuestionDisplay.tsx index 9c18605..a2eac59 100644 --- a/client/src/components/Questions/MultipleChoiceQuestion/MultipleChoiceQuestion.tsx +++ b/client/src/components/Questions/MultipleChoiceQuestionDisplay/MultipleChoiceQuestionDisplay.tsx @@ -1,35 +1,25 @@ -// MultipleChoiceQuestion.tsx +// MultipleChoiceQuestionDisplay.tsx import React, { useEffect, useState } from 'react'; import '../questionStyle.css'; import { Button } from '@mui/material'; -import textType, { formatLatex } from '../../GiftTemplate/templates/TextType'; -import { TextFormat } from '../../GiftTemplate/templates/types'; +import { textType } from '../../GiftTemplate/templates/TextType'; +import { MultipleChoiceQuestion } from 'gift-pegjs'; import DOMPurify from 'dompurify'; -// import Latex from 'react-latex'; - -type Choices = { - feedback: { format: string; text: string } | null; - isCorrect: boolean; - text: { format: string; text: string }; - weigth?: number; -}; interface Props { - questionStem: TextFormat; - choices: Choices[]; - globalFeedback?: string | undefined; + question: MultipleChoiceQuestion; handleOnSubmitAnswer?: (answer: string) => void; showAnswer?: boolean; } -const MultipleChoiceQuestion: React.FC = (props) => { +const MultipleChoiceQuestionDisplay: React.FC = (props) => { - const { questionStem: questionContent, choices, showAnswer, handleOnSubmitAnswer, globalFeedback } = props; + const { question, showAnswer, handleOnSubmitAnswer } = props; const [answer, setAnswer] = useState(); useEffect(() => { setAnswer(undefined); - }, [questionContent]); + }, [question]); const handleOnClickAnswer = (choice: string) => { setAnswer(choice); @@ -41,38 +31,40 @@ const MultipleChoiceQuestion: React.FC = (props) => { return (
-
+
- {choices.map((choice, i) => { - const selected = answer === choice.text.text ? 'selected' : ''; + {question.choices.map((choice, i) => { + const selected = answer === choice.formattedText.text ? 'selected' : ''; return ( -
+
- {choice.feedback && showAnswer && ( + {choice.formattedFeedback && showAnswer && (
{choice.isCorrect ? '✅' : '❌'} - {choice.feedback?.text} +
)}
); })}
- {globalFeedback && showAnswer && ( -
{globalFeedback}
+ {question.formattedGlobalFeedback && showAnswer && ( +
+

${textType({ text: question.formattedGlobalFeedback })}

+
)} {!showAnswer && handleOnSubmitAnswer && ( @@ -92,4 +84,4 @@ const MultipleChoiceQuestion: React.FC = (props) => { ); }; -export default MultipleChoiceQuestion; +export default MultipleChoiceQuestionDisplay; diff --git a/client/src/components/Questions/NumericalQuestion/NumericalQuestion.tsx b/client/src/components/Questions/NumericalQuestion/NumericalQuestion.tsx index 7a9cec7..d8dfc77 100644 --- a/client/src/components/Questions/NumericalQuestion/NumericalQuestion.tsx +++ b/client/src/components/Questions/NumericalQuestion/NumericalQuestion.tsx @@ -3,19 +3,19 @@ import React, { useState } from 'react'; import '../questionStyle.css'; import { Button, TextField } from '@mui/material'; import textType from '../../GiftTemplate/templates/TextType'; -import { TextFormat } from '../../GiftTemplate/templates/types'; +import { TextFormat, NumericalAnswer, isHighLowNumericalAnswer, isMultipleNumericalAnswer, isRangeNumericalAnswer, isSimpleNumericalAnswer, SimpleNumericalAnswer, RangeNumericalAnswer, HighLowNumericalAnswer } from 'gift-pegjs'; import DOMPurify from 'dompurify'; -type CorrectAnswer = { - numberHigh?: number; - numberLow?: number; - number?: number; - type: string; -}; +// type CorrectAnswer = { +// numberHigh?: number; +// numberLow?: number; +// number?: number; +// type: string; +// }; interface Props { questionContent: TextFormat; - correctAnswers: CorrectAnswer; + correctAnswers: NumericalAnswer; globalFeedback?: string | undefined; handleOnSubmitAnswer?: (answer: number) => void; showAnswer?: boolean; @@ -27,11 +27,22 @@ const NumericalQuestion: React.FC = (props) => { const [answer, setAnswer] = useState(); - const correctAnswer = - correctAnswers.type === 'high-low' - ? `Entre ${correctAnswers.numberLow} et ${correctAnswers.numberHigh}` - : correctAnswers.number; + let correctAnswer= ''; + if (isSimpleNumericalAnswer(correctAnswers)) { + correctAnswer = `${(correctAnswers as SimpleNumericalAnswer).number}`; + } else if (isRangeNumericalAnswer(correctAnswers)) { + const choice = correctAnswers as RangeNumericalAnswer; + correctAnswer = `Entre ${choice.number - choice.range} et ${choice.number + choice.range}`; + } else if (isHighLowNumericalAnswer(correctAnswers)) { + const choice = correctAnswers as HighLowNumericalAnswer; + correctAnswer = `Entre ${choice.numberLow} et ${choice.numberHigh}`; + } else if (isMultipleNumericalAnswer(correctAnswers)) { + correctAnswer = `MultipleNumericalAnswer is not supported yet`; + } else { + throw new Error('Unknown numerical answer type'); + } + return (
diff --git a/client/src/components/Questions/Question.tsx b/client/src/components/Questions/QuestionDisplay.tsx similarity index 69% rename from client/src/components/Questions/Question.tsx rename to client/src/components/Questions/QuestionDisplay.tsx index b3f21eb..24cd0ef 100644 --- a/client/src/components/Questions/Question.tsx +++ b/client/src/components/Questions/QuestionDisplay.tsx @@ -1,109 +1,105 @@ -// Question;tsx -import React, { useMemo } from 'react'; -import { GIFTQuestion } from 'gift-pegjs'; - -import TrueFalseQuestion from './TrueFalseQuestion/TrueFalseQuestion'; -import MultipleChoiceQuestion from './MultipleChoiceQuestion/MultipleChoiceQuestion'; -import NumericalQuestion from './NumericalQuestion/NumericalQuestion'; -import ShortAnswerQuestion from './ShortAnswerQuestion/ShortAnswerQuestion'; -import useCheckMobileScreen from '../../services/useCheckMobileScreen'; - -interface QuestionProps { - question: GIFTQuestion | undefined; - handleOnSubmitAnswer?: (answer: string | number | boolean) => void; - showAnswer?: boolean; - imageUrl?: string; -} -const Question: React.FC = ({ - question, - handleOnSubmitAnswer, - showAnswer, - imageUrl -}) => { - const isMobile = useCheckMobileScreen(); - const imgWidth = useMemo(() => { - return isMobile ? '100%' : '20%'; - }, [isMobile]); - - let questionTypeComponent = null; - switch (question?.type) { - case 'TF': - questionTypeComponent = ( - - ); - break; - case 'MC': - questionTypeComponent = ( - ({ ...choice, id: index.toString() }))} - handleOnSubmitAnswer={handleOnSubmitAnswer} - showAnswer={showAnswer} - globalFeedback={question.globalFeedback?.text} - /> - ); - break; - case 'Numerical': - if (question.choices) { - if (!Array.isArray(question.choices)) { - questionTypeComponent = ( - - ); - } else { - questionTypeComponent = ( - - ); - } - } - break; - case 'Short': - questionTypeComponent = ( - ({ ...choice, id: index.toString() }))} - handleOnSubmitAnswer={handleOnSubmitAnswer} - showAnswer={showAnswer} - globalFeedback={question.globalFeedback?.text} - /> - ); - break; - } - return ( -
- {questionTypeComponent ? ( - <> - {imageUrl && ( - QuestionImage - )} - {questionTypeComponent} - - ) : ( -
Question de type inconnue
- )} -
- ); -}; - -export default Question; +// Question;tsx +import React, { useMemo } from 'react'; +import { Question } from 'gift-pegjs'; + +import TrueFalseQuestion from './TrueFalseQuestion/TrueFalseQuestion'; +import MultipleChoiceQuestionDisplay from './MultipleChoiceQuestionDisplay/MultipleChoiceQuestionDisplay'; +import NumericalQuestion from './NumericalQuestion/NumericalQuestion'; +import ShortAnswerQuestion from './ShortAnswerQuestion/ShortAnswerQuestion'; +import useCheckMobileScreen from '../../services/useCheckMobileScreen'; + +interface QuestionProps { + question: Question; + handleOnSubmitAnswer?: (answer: string | number | boolean) => void; + showAnswer?: boolean; +} +const QuestionDisplay: React.FC = ({ + question, + handleOnSubmitAnswer, + showAnswer, +}) => { + const isMobile = useCheckMobileScreen(); + const imgWidth = useMemo(() => { + return isMobile ? '100%' : '20%'; + }, [isMobile]); + + let questionTypeComponent = null; + switch (question?.type) { + case 'TF': + questionTypeComponent = ( + + ); + break; + case 'MC': + questionTypeComponent = ( + + ); + break; + case 'Numerical': + if (question.choices) { + if (!Array.isArray(question.choices)) { + questionTypeComponent = ( + + ); + } else { + questionTypeComponent = ( // TODO fix NumericalQuestion (correctAnswers is borked) + + ); + } + } + break; + case 'Short': + questionTypeComponent = ( + ({ ...choice, id: index.toString() }))} + handleOnSubmitAnswer={handleOnSubmitAnswer} + showAnswer={showAnswer} + globalFeedback={question.formattedGlobalFeedback?.text} + /> + ); + break; + } + return ( +
+ {questionTypeComponent ? ( + <> + {imageUrl && ( + QuestionImage + )} + {questionTypeComponent} + + ) : ( +
Question de type inconnue
+ )} +
+ ); +}; + +export default QuestionDisplay; diff --git a/client/src/components/StudentModeQuiz/StudentModeQuiz.tsx b/client/src/components/StudentModeQuiz/StudentModeQuiz.tsx index 15d6fba..435b4a5 100644 --- a/client/src/components/StudentModeQuiz/StudentModeQuiz.tsx +++ b/client/src/components/StudentModeQuiz/StudentModeQuiz.tsx @@ -1,6 +1,6 @@ // StudentModeQuiz.tsx import React, { useEffect, useState } from 'react'; -import QuestionComponent from '../Questions/Question'; +import QuestionComponent from '../Questions/QuestionDisplay'; import '../../pages/Student/JoinRoom/joinRoom.css'; import { QuestionType } from '../../Types/QuestionType'; // import { QuestionService } from '../../services/QuestionService'; diff --git a/client/src/components/TeacherModeQuiz/TeacherModeQuiz.tsx b/client/src/components/TeacherModeQuiz/TeacherModeQuiz.tsx index 1afab71..850c9ef 100644 --- a/client/src/components/TeacherModeQuiz/TeacherModeQuiz.tsx +++ b/client/src/components/TeacherModeQuiz/TeacherModeQuiz.tsx @@ -1,7 +1,7 @@ // TeacherModeQuiz.tsx import React, { useEffect, useState } from 'react'; -import QuestionComponent from '../Questions/Question'; +import QuestionComponent from '../Questions/QuestionDisplay'; import '../../pages/Student/JoinRoom/joinRoom.css'; import { QuestionType } from '../../Types/QuestionType'; diff --git a/client/src/pages/Teacher/ManageRoom/ManageRoom.tsx b/client/src/pages/Teacher/ManageRoom/ManageRoom.tsx index 95e8464..207143c 100644 --- a/client/src/pages/Teacher/ManageRoom/ManageRoom.tsx +++ b/client/src/pages/Teacher/ManageRoom/ManageRoom.tsx @@ -18,7 +18,7 @@ import { Refresh, Error } from '@mui/icons-material'; import StudentWaitPage from 'src/components/StudentWaitPage/StudentWaitPage'; import DisconnectButton from 'src/components/DisconnectButton/DisconnectButton'; //import QuestionNavigation from 'src/components/QuestionNavigation/QuestionNavigation'; -import Question from 'src/components/Questions/Question'; +import QuestionDisplay from 'src/components/Questions/QuestionDisplay'; import ApiService from '../../../services/ApiService'; const ManageRoom: React.FC = () => { @@ -474,7 +474,7 @@ const ManageRoom: React.FC = () => {
{currentQuestion && ( - From d047c787b716e1780978bc3a5e6fa57a11c3a7f3 Mon Sep 17 00:00:00 2001 From: "C. Fuhrman" Date: Thu, 23 Jan 2025 22:43:50 -0500 Subject: [PATCH 02/29] fix broken import --- client/src/__tests__/Types/QuestionType.test.tsx | 1 + client/src/__tests__/components/GiftTemplate/TextType.test.ts | 2 +- .../Questions/NumericalQuestion/NumericalQuestion.tsx | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/client/src/__tests__/Types/QuestionType.test.tsx b/client/src/__tests__/Types/QuestionType.test.tsx index d02d52b..b4fa319 100644 --- a/client/src/__tests__/Types/QuestionType.test.tsx +++ b/client/src/__tests__/Types/QuestionType.test.tsx @@ -1,4 +1,5 @@ //QuestionType.test.tsx +// Superfluous test now that gift-pegjs has TypeScript types (and its own tests) import { Question } from 'gift-pegjs'; const sampleStem = 'Sample question stem'; diff --git a/client/src/__tests__/components/GiftTemplate/TextType.test.ts b/client/src/__tests__/components/GiftTemplate/TextType.test.ts index db5ab70..7a9993f 100644 --- a/client/src/__tests__/components/GiftTemplate/TextType.test.ts +++ b/client/src/__tests__/components/GiftTemplate/TextType.test.ts @@ -1,6 +1,6 @@ // TextType.test.ts -import textType from "src/components/GiftTemplate/templates/TextType"; +import { textType } from "src/components/GiftTemplate/templates/TextType"; import { TextFormat } from "gift-pegjs"; describe('TextType', () => { diff --git a/client/src/components/Questions/NumericalQuestion/NumericalQuestion.tsx b/client/src/components/Questions/NumericalQuestion/NumericalQuestion.tsx index d8dfc77..488fb2a 100644 --- a/client/src/components/Questions/NumericalQuestion/NumericalQuestion.tsx +++ b/client/src/components/Questions/NumericalQuestion/NumericalQuestion.tsx @@ -2,7 +2,7 @@ import React, { useState } from 'react'; import '../questionStyle.css'; import { Button, TextField } from '@mui/material'; -import textType from '../../GiftTemplate/templates/TextType'; +import { textType } from '../../GiftTemplate/templates/TextType'; import { TextFormat, NumericalAnswer, isHighLowNumericalAnswer, isMultipleNumericalAnswer, isRangeNumericalAnswer, isSimpleNumericalAnswer, SimpleNumericalAnswer, RangeNumericalAnswer, HighLowNumericalAnswer } from 'gift-pegjs'; import DOMPurify from 'dompurify'; From 8a7a4447183e95bd3a51ff3bd69102844624b48c Mon Sep 17 00:00:00 2001 From: "C. Fuhrman" Date: Fri, 24 Jan 2025 14:51:58 -0500 Subject: [PATCH 03/29] baby steps (getting tests running) --- .../MultipleChoiceQuestionDisplay.test.tsx | 10 +- .../NumericalQuestionDisplay.test.tsx} | 47 +++++++--- .../components/Questions/Question.test.tsx | 93 +++++++------------ .../ShortAnswerQuestion.test.tsx | 48 ++++------ client/src/__tests__/smoke-test.test.ts | 30 ++++++ .../NumericalQuestionDisplay.tsx} | 61 ++++++------ .../components/Questions/QuestionDisplay.tsx | 41 +++----- .../ShortAnswerQuestionDisplay.tsx} | 41 ++++---- .../TrueFalseQuestion/TrueFalseQuestion.tsx | 4 +- 9 files changed, 181 insertions(+), 194 deletions(-) rename client/src/__tests__/components/Questions/{NumericalQuestion/NumericalQuestion.test.tsx => NumericalQuestionDisplay/NumericalQuestionDisplay.test.tsx} (55%) create mode 100644 client/src/__tests__/smoke-test.test.ts rename client/src/components/Questions/{NumericalQuestion/NumericalQuestion.tsx => NumericalQuestionDisplay/NumericalQuestionDisplay.tsx} (54%) rename client/src/components/Questions/{ShortAnswerQuestion/ShortAnswerQuestion.tsx => ShortAnswerQuestionDisplay/ShortAnswerQuestionDisplay.tsx} (60%) diff --git a/client/src/__tests__/components/Questions/MultipleChoiceQuestionDisplay/MultipleChoiceQuestionDisplay.test.tsx b/client/src/__tests__/components/Questions/MultipleChoiceQuestionDisplay/MultipleChoiceQuestionDisplay.test.tsx index 0e68c39..be6c8e9 100644 --- a/client/src/__tests__/components/Questions/MultipleChoiceQuestionDisplay/MultipleChoiceQuestionDisplay.test.tsx +++ b/client/src/__tests__/components/Questions/MultipleChoiceQuestionDisplay/MultipleChoiceQuestionDisplay.test.tsx @@ -17,15 +17,19 @@ const question = questions[0]; describe('MultipleChoiceQuestionDisplay', () => { const mockHandleOnSubmitAnswer = jest.fn(); + const sampleProps = { + question: question, + handleOnSubmitAnswer: mockHandleOnSubmitAnswer, + showAnswer: false + }; + const choices = question.choices; beforeEach(() => { render( ); }); diff --git a/client/src/__tests__/components/Questions/NumericalQuestion/NumericalQuestion.test.tsx b/client/src/__tests__/components/Questions/NumericalQuestionDisplay/NumericalQuestionDisplay.test.tsx similarity index 55% rename from client/src/__tests__/components/Questions/NumericalQuestion/NumericalQuestion.test.tsx rename to client/src/__tests__/components/Questions/NumericalQuestionDisplay/NumericalQuestionDisplay.test.tsx index e307ed8..50741d8 100644 --- a/client/src/__tests__/components/Questions/NumericalQuestion/NumericalQuestion.test.tsx +++ b/client/src/__tests__/components/Questions/NumericalQuestionDisplay/NumericalQuestionDisplay.test.tsx @@ -2,29 +2,48 @@ import React from 'react'; import { render, screen, fireEvent } from '@testing-library/react'; import '@testing-library/jest-dom'; -import NumericalQuestion from 'src/components/Questions/NumericalQuestion/NumericalQuestion'; +import NumericalQuestionDisplay from 'src/components/Questions/NumericalQuestionDisplay/NumericalQuestionDisplay'; +import { NumericalQuestion, parse, ParsedGIFTQuestion } from 'gift-pegjs'; +import { MemoryRouter } from 'react-router-dom'; + +const questions = parse( + ` + ::Sample Question 1:: Question stem + { + #5..10 + }` +) as ParsedGIFTQuestion[]; + +const question = questions[0] as NumericalQuestion; + +describe('NumericalQuestion parse', () => { + const q = questions[0]; + + it('The question is Numerical', () => { + expect(q.type).toBe('Numerical'); + }); +}); describe('NumericalQuestion Component', () => { - const mockHandleSubmitAnswer = jest.fn(); - const sampleStem = 'Sample question stem'; + const mockHandleOnSubmitAnswer = jest.fn(); const sampleProps = { - questionTitle: 'Sample Question', - correctAnswers: { - numberHigh: 10, - numberLow: 5, - type: 'high-low' - }, - handleOnSubmitAnswer: mockHandleSubmitAnswer, + question: question, + handleOnSubmitAnswer: mockHandleOnSubmitAnswer, showAnswer: false }; beforeEach(() => { - render(); + render( + + + ); }); it('renders correctly', () => { - expect(screen.getByText(sampleStem)).toBeInTheDocument(); + expect(screen.getByText(question.formattedStem.text)).toBeInTheDocument(); expect(screen.getByTestId('number-input')).toBeInTheDocument(); expect(screen.getByText('Répondre')).toBeInTheDocument(); }); @@ -48,7 +67,7 @@ describe('NumericalQuestion Component', () => { fireEvent.click(submitButton); - expect(mockHandleSubmitAnswer).not.toHaveBeenCalled(); + expect(mockHandleOnSubmitAnswer).not.toHaveBeenCalled(); }); it('submits answer correctly', () => { @@ -59,6 +78,6 @@ describe('NumericalQuestion Component', () => { fireEvent.click(submitButton); - expect(mockHandleSubmitAnswer).toHaveBeenCalledWith(7); + expect(mockHandleOnSubmitAnswer).toHaveBeenCalledWith(7); }); }); diff --git a/client/src/__tests__/components/Questions/Question.test.tsx b/client/src/__tests__/components/Questions/Question.test.tsx index 465ae7a..dc88b53 100644 --- a/client/src/__tests__/components/Questions/Question.test.tsx +++ b/client/src/__tests__/components/Questions/Question.test.tsx @@ -1,71 +1,40 @@ // Question.test.tsx import React from 'react'; -import { render, screen, fireEvent } from '@testing-library/react'; +import { render, screen, fireEvent, within } from '@testing-library/react'; import '@testing-library/jest-dom'; -import Questions from 'src/components/Questions/QuestionDisplay'; -import { GIFTQuestion } from 'gift-pegjs'; +import QuestionDisplay from 'src/components/Questions/QuestionDisplay'; +import { parse, Question } from 'gift-pegjs'; -// describe('Questions Component', () => { const mockHandleSubmitAnswer = jest.fn(); - const sampleTrueFalseQuestion: GIFTQuestion = { - type: 'TF', - stem: { format: 'plain', text: 'Sample True/False Question' }, - isTrue: true, - falseFeedback: null, - trueFeedback: null, - title: 'True/False Question', - hasEmbeddedAnswers: false, - globalFeedback: null, + const sampleTrueFalseQuestion = + parse('::Sample True/False Question:: Sample True/False Question {T}')[0]; + + const sampleMultipleChoiceQuestion = + parse('::Sample Multiple Choice Question:: Sample Multiple Choice Question {=Choice 1 ~Choice 2}')[0]; + + const sampleNumericalQuestion = + parse('::Sample Numerical Question:: Sample Numerical Question {#5..10}')[0]; + + const sampleShortAnswerQuestion = + parse('::Sample Short Answer Question:: Sample Short Answer Question {=Correct Answer =Another Answer}')[0]; + + const sampleProps = { + handleOnSubmitAnswer: mockHandleSubmitAnswer, + showAnswer: false }; - const sampleMultipleChoiceQuestion: GIFTQuestion = { - type: 'MC', - stem: { format: 'plain', text: 'Sample Multiple Choice Question' }, - title: 'Multiple Choice Question', - hasEmbeddedAnswers: false, - globalFeedback: null, - choices: [ - { feedback: null, isCorrect: true, text: { format: 'plain', text: 'Choice 1' }, weight: 1 }, - { feedback: null, isCorrect: false, text: { format: 'plain', text: 'Choice 2' }, weight: 0 }, - ], + const renderComponent = (question: Question) => { + render(); }; - const sampleNumericalQuestion: GIFTQuestion = { - type: 'Numerical', - stem: { format: 'plain', text: 'Sample Numerical Question' }, - title: 'Numerical Question', - hasEmbeddedAnswers: false, - globalFeedback: null, - choices: { numberHigh: 10, numberLow: 5, type: 'high-low' }, - }; - - const sampleShortAnswerQuestion: GIFTQuestion = { - type: 'Short', - stem: { format: 'plain', text: 'Sample short answer question' }, - title: 'Short Answer Question Title', - hasEmbeddedAnswers: false, - globalFeedback: null, - choices: [ - { - feedback: { format: 'html', text: 'Correct answer feedback' }, - isCorrect: true, - text: { format: 'html', text: 'Correct Answer' }, - weight: 1, - }, - { - feedback: { format: 'html', text: 'Incorrect answer feedback' }, - isCorrect: false, - text: { format: 'html', text: 'Incorrect Answer' }, - weight: 0, - }, - ], - }; - - const renderComponent = (question: GIFTQuestion) => { - render(); - }; + it('parsed questions correctly', () => { + expect(sampleTrueFalseQuestion.type).toBe('TF'); + expect(sampleMultipleChoiceQuestion.type).toBe('MC'); + expect(sampleNumericalQuestion.type).toBe('Numerical'); + expect(sampleShortAnswerQuestion.type).toBe('Short'); + }); it('renders correctly for True/False question', () => { renderComponent(sampleTrueFalseQuestion); @@ -120,15 +89,19 @@ describe('Questions Component', () => { it('renders correctly for Short Answer question', () => { renderComponent(sampleShortAnswerQuestion); - expect(screen.getByText('Sample short answer question')).toBeInTheDocument(); - expect(screen.getByTestId('text-input')).toBeInTheDocument(); + expect(screen.getByText('Sample Short Answer Question')).toBeInTheDocument(); + const container = screen.getByLabelText('short-answer-input'); + const inputElement = within(container).getByRole('textbox') as HTMLInputElement; + expect(inputElement).toBeInTheDocument(); expect(screen.getByText('Répondre')).toBeInTheDocument(); }); it('handles input and submission for Short Answer question', () => { renderComponent(sampleShortAnswerQuestion); - const inputElement = screen.getByTestId('text-input') as HTMLInputElement; + const container = screen.getByLabelText('short-answer-input'); + const inputElement = within(container).getByRole('textbox') as HTMLInputElement; + fireEvent.change(inputElement, { target: { value: 'User Input' } }); const submitButton = screen.getByText('Répondre'); diff --git a/client/src/__tests__/components/Questions/ShortAnswerQuestion/ShortAnswerQuestion.test.tsx b/client/src/__tests__/components/Questions/ShortAnswerQuestion/ShortAnswerQuestion.test.tsx index 351f732..709e9d5 100644 --- a/client/src/__tests__/components/Questions/ShortAnswerQuestion/ShortAnswerQuestion.test.tsx +++ b/client/src/__tests__/components/Questions/ShortAnswerQuestion/ShortAnswerQuestion.test.tsx @@ -1,54 +1,35 @@ // ShortAnswerQuestion.test.tsx import React from 'react'; -import { render, screen, fireEvent } from '@testing-library/react'; +import { render, screen, fireEvent, within } from '@testing-library/react'; import '@testing-library/jest-dom'; -import ShortAnswerQuestion from 'src/components/Questions/ShortAnswerQuestion/ShortAnswerQuestion'; +import ShortAnswerQuestionDisplay from 'src/components/Questions/ShortAnswerQuestionDisplay/ShortAnswerQuestionDisplay'; +import { parse, ShortAnswerQuestion } from 'gift-pegjs'; describe('ShortAnswerQuestion Component', () => { const mockHandleSubmitAnswer = jest.fn(); - const sampleStem = 'Sample question stem'; + const question = + parse('::Sample Short Answer Question:: Sample Short Answer Question {=Correct Answer ~Incorrect Answer}')[0] as ShortAnswerQuestion; const sampleProps = { - questionTitle: 'Sample Question', - choices: [ - { - id: '1', - feedback: { - format: 'text', - text: 'Correct answer feedback' - }, - isCorrect: true, - text: { - format: 'text', - text: 'Correct Answer' - } - }, - { - id: '2', - feedback: null, - isCorrect: false, - text: { - format: 'text', - text: 'Incorrect Answer' - } - } - ], handleOnSubmitAnswer: mockHandleSubmitAnswer, showAnswer: false }; beforeEach(() => { - render(); + render(); }); it('renders correctly', () => { - expect(screen.getByText(sampleStem)).toBeInTheDocument(); - expect(screen.getByTestId('text-input')).toBeInTheDocument(); + expect(screen.getByText(question.formattedStem.text)).toBeInTheDocument(); + const container = screen.getByLabelText('short-answer-input'); + const inputElement = within(container).getByRole('textbox') as HTMLInputElement; + expect(inputElement).toBeInTheDocument(); expect(screen.getByText('Répondre')).toBeInTheDocument(); }); it('handles input change correctly', () => { - const inputElement = screen.getByTestId('text-input') as HTMLInputElement; + const container = screen.getByLabelText('short-answer-input'); + const inputElement = within(container).getByRole('textbox') as HTMLInputElement; fireEvent.change(inputElement, { target: { value: 'User Input' } }); @@ -70,7 +51,10 @@ describe('ShortAnswerQuestion Component', () => { }); it('submits answer correctly', () => { - const inputElement = screen.getByTestId('text-input') as HTMLInputElement; + const container = screen.getByLabelText('short-answer-input'); + const inputElement = within(container).getByRole('textbox') as HTMLInputElement; + + // const inputElement = screen.getByRole('textbox', { name: 'short-answer-input'}) as HTMLInputElement; const submitButton = screen.getByText('Répondre'); fireEvent.change(inputElement, { target: { value: 'User Input' } }); diff --git a/client/src/__tests__/smoke-test.test.ts b/client/src/__tests__/smoke-test.test.ts new file mode 100644 index 0000000..1331d7e --- /dev/null +++ b/client/src/__tests__/smoke-test.test.ts @@ -0,0 +1,30 @@ +import { parse, NumericalQuestion, SimpleNumericalAnswer, } from "gift-pegjs"; +import { isSimpleNumericalAnswer } from "gift-pegjs/typeGuards"; + +describe('Numerical Question Tests', () => { + // ::Ulysses birthdate::When was Ulysses S. Grant born? {#1822} + it('should produce a valid Question object for a Numerical question with Title', () => { + const input = ` + ::Ulysses birthdate::When was Ulysses S. Grant born? {#1822} + `; + const result = parse(input); + + // Type assertion to ensure result matches the Question interface + const question = result[0]; + + // Example assertions to check specific properties + expect(question).toHaveProperty('type', 'Numerical'); + const numericalQuestion = question as NumericalQuestion; + expect(numericalQuestion.title).toBe('Ulysses birthdate'); + expect(numericalQuestion.formattedStem.text).toBe('When was Ulysses S. Grant born?'); + expect(numericalQuestion.choices).toBeDefined(); + expect(numericalQuestion.choices).toHaveLength(1); + + const choice = numericalQuestion.choices[0]; + expect(isSimpleNumericalAnswer(choice)).toBe(true); + const c = choice as SimpleNumericalAnswer; + expect(c.type).toBe('simple'); + expect(c.number).toBe(1822); + + }); +}); diff --git a/client/src/components/Questions/NumericalQuestion/NumericalQuestion.tsx b/client/src/components/Questions/NumericalQuestionDisplay/NumericalQuestionDisplay.tsx similarity index 54% rename from client/src/components/Questions/NumericalQuestion/NumericalQuestion.tsx rename to client/src/components/Questions/NumericalQuestionDisplay/NumericalQuestionDisplay.tsx index 488fb2a..acf0869 100644 --- a/client/src/components/Questions/NumericalQuestion/NumericalQuestion.tsx +++ b/client/src/components/Questions/NumericalQuestionDisplay/NumericalQuestionDisplay.tsx @@ -3,71 +3,70 @@ import React, { useState } from 'react'; import '../questionStyle.css'; import { Button, TextField } from '@mui/material'; import { textType } from '../../GiftTemplate/templates/TextType'; -import { TextFormat, NumericalAnswer, isHighLowNumericalAnswer, isMultipleNumericalAnswer, isRangeNumericalAnswer, isSimpleNumericalAnswer, SimpleNumericalAnswer, RangeNumericalAnswer, HighLowNumericalAnswer } from 'gift-pegjs'; +import { NumericalQuestion, SimpleNumericalAnswer, RangeNumericalAnswer, HighLowNumericalAnswer } from 'gift-pegjs'; +import { isSimpleNumericalAnswer, isRangeNumericalAnswer, isHighLowNumericalAnswer, isMultipleNumericalAnswer } from 'gift-pegjs/typeGuards'; import DOMPurify from 'dompurify'; -// type CorrectAnswer = { -// numberHigh?: number; -// numberLow?: number; -// number?: number; -// type: string; -// }; - interface Props { - questionContent: TextFormat; - correctAnswers: NumericalAnswer; - globalFeedback?: string | undefined; + question: NumericalQuestion; handleOnSubmitAnswer?: (answer: number) => void; showAnswer?: boolean; } -const NumericalQuestion: React.FC = (props) => { - const { questionContent, correctAnswers, showAnswer, handleOnSubmitAnswer, globalFeedback } = +const NumericalQuestionDisplay: React.FC = (props) => { + const { question, showAnswer, handleOnSubmitAnswer } = props; const [answer, setAnswer] = useState(); - let correctAnswer= ''; + const correctAnswers = question.choices; + let correctAnswer = ''; - if (isSimpleNumericalAnswer(correctAnswers)) { - correctAnswer = `${(correctAnswers as SimpleNumericalAnswer).number}`; - } else if (isRangeNumericalAnswer(correctAnswers)) { - const choice = correctAnswers as RangeNumericalAnswer; + //const isSingleAnswer = correctAnswers.length === 1; + + if (isSimpleNumericalAnswer(correctAnswers[0])) { + correctAnswer = `${(correctAnswers[0] as SimpleNumericalAnswer).number}`; + } else if (isRangeNumericalAnswer(correctAnswers[0])) { + const choice = correctAnswers[0] as RangeNumericalAnswer; correctAnswer = `Entre ${choice.number - choice.range} et ${choice.number + choice.range}`; - } else if (isHighLowNumericalAnswer(correctAnswers)) { - const choice = correctAnswers as HighLowNumericalAnswer; + } else if (isHighLowNumericalAnswer(correctAnswers[0])) { + const choice = correctAnswers[0] as HighLowNumericalAnswer; correctAnswer = `Entre ${choice.numberLow} et ${choice.numberHigh}`; - } else if (isMultipleNumericalAnswer(correctAnswers)) { + } else if (isMultipleNumericalAnswer(correctAnswers[0])) { correctAnswer = `MultipleNumericalAnswer is not supported yet`; - } else { + } else { throw new Error('Unknown numerical answer type'); - } - + } + return (
-
+
{showAnswer ? ( <>
{correctAnswer}
- {globalFeedback &&
{globalFeedback}
} + {question.formattedGlobalFeedback &&
+
+
} ) : ( <>
) => { setAnswer(e.target.valueAsNumber); }} inputProps={{ 'data-testid': 'number-input' }} />
- {globalFeedback && showAnswer && ( -
{globalFeedback}
+ {question.formattedGlobalFeedback && showAnswer && ( +
+
+
)} {handleOnSubmitAnswer && ( {choice.formattedFeedback && showAnswer && (
{choice.isCorrect ? '✅' : '❌'} -
+
)}
@@ -63,8 +63,8 @@ const MultipleChoiceQuestionDisplay: React.FC = (props) => {
{question.formattedGlobalFeedback && showAnswer && (
-

${textType({ text: question.formattedGlobalFeedback })}

-
+
+
)} {!showAnswer && handleOnSubmitAnswer && ( diff --git a/client/src/components/QuestionsDisplay/NumericalQuestionDisplay/NumericalQuestionDisplay.tsx b/client/src/components/QuestionsDisplay/NumericalQuestionDisplay/NumericalQuestionDisplay.tsx index acf0869..b1e8154 100644 --- a/client/src/components/QuestionsDisplay/NumericalQuestionDisplay/NumericalQuestionDisplay.tsx +++ b/client/src/components/QuestionsDisplay/NumericalQuestionDisplay/NumericalQuestionDisplay.tsx @@ -2,7 +2,7 @@ import React, { useState } from 'react'; import '../questionStyle.css'; import { Button, TextField } from '@mui/material'; -import { textType } from '../../GiftTemplate/templates/TextType'; +import { textType } from '../../GiftTemplate/templates/TextTypeTemplate'; import { NumericalQuestion, SimpleNumericalAnswer, RangeNumericalAnswer, HighLowNumericalAnswer } from 'gift-pegjs'; import { isSimpleNumericalAnswer, isRangeNumericalAnswer, isHighLowNumericalAnswer, isMultipleNumericalAnswer } from 'gift-pegjs/typeGuards'; import DOMPurify from 'dompurify'; @@ -41,13 +41,13 @@ const NumericalQuestionDisplay: React.FC = (props) => { return (
-
+
{showAnswer ? ( <>
{correctAnswer}
{question.formattedGlobalFeedback &&
-
+
} ) : ( @@ -65,7 +65,7 @@ const NumericalQuestionDisplay: React.FC = (props) => {
{question.formattedGlobalFeedback && showAnswer && (
-
+
)} {handleOnSubmitAnswer && ( diff --git a/client/src/components/QuestionsDisplay/QuestionDisplay.tsx b/client/src/components/QuestionsDisplay/QuestionDisplay.tsx index 934092f..8dfa1b3 100644 --- a/client/src/components/QuestionsDisplay/QuestionDisplay.tsx +++ b/client/src/components/QuestionsDisplay/QuestionDisplay.tsx @@ -1,8 +1,7 @@ -// Question;tsx import React from 'react'; import { Question } from 'gift-pegjs'; -import TrueFalseQuestion from './TrueFalseQuestion/TrueFalseQuestion'; +import TrueFalseQuestionDisplay from './TrueFalseQuestionDisplay/TrueFalseQuestionDisplay'; import MultipleChoiceQuestionDisplay from './MultipleChoiceQuestionDisplay/MultipleChoiceQuestionDisplay'; import NumericalQuestionDisplay from './NumericalQuestionDisplay/NumericalQuestionDisplay'; import ShortAnswerQuestionDisplay from './ShortAnswerQuestionDisplay/ShortAnswerQuestionDisplay'; @@ -27,12 +26,10 @@ const QuestionDisplay: React.FC = ({ switch (question?.type) { case 'TF': questionTypeComponent = ( - ); break; diff --git a/client/src/components/QuestionsDisplay/ShortAnswerQuestionDisplay/ShortAnswerQuestionDisplay.tsx b/client/src/components/QuestionsDisplay/ShortAnswerQuestionDisplay/ShortAnswerQuestionDisplay.tsx index 5fd729d..55cbba4 100644 --- a/client/src/components/QuestionsDisplay/ShortAnswerQuestionDisplay/ShortAnswerQuestionDisplay.tsx +++ b/client/src/components/QuestionsDisplay/ShortAnswerQuestionDisplay/ShortAnswerQuestionDisplay.tsx @@ -1,7 +1,7 @@ import React, { useState } from 'react'; import '../questionStyle.css'; import { Button, TextField } from '@mui/material'; -import { textType } from '../../GiftTemplate/templates/TextType'; +import { textType } from '../../GiftTemplate/templates/TextTypeTemplate'; import { ShortAnswerQuestion } from 'gift-pegjs'; import DOMPurify from 'dompurify'; @@ -18,7 +18,7 @@ const ShortAnswerQuestionDisplay: React.FC = (props) => { return (
-
+
{showAnswer ? ( <> @@ -30,7 +30,7 @@ const ShortAnswerQuestionDisplay: React.FC = (props) => { ))}
{question.formattedGlobalFeedback &&
-
+
} ) : ( diff --git a/client/src/components/QuestionsDisplay/TrueFalseQuestionDisplay/TrueFalseQuestionDisplay.tsx b/client/src/components/QuestionsDisplay/TrueFalseQuestionDisplay/TrueFalseQuestionDisplay.tsx index dd5b3e8..1c4fcdb 100644 --- a/client/src/components/QuestionsDisplay/TrueFalseQuestionDisplay/TrueFalseQuestionDisplay.tsx +++ b/client/src/components/QuestionsDisplay/TrueFalseQuestionDisplay/TrueFalseQuestionDisplay.tsx @@ -2,33 +2,32 @@ import React, { useState, useEffect } from 'react'; import '../questionStyle.css'; import { Button } from '@mui/material'; -import { textType } from '../../GiftTemplate/templates/TextType'; -import { TextFormat } from 'gift-pegjs'; +import { TrueFalseQuestion } from 'gift-pegjs'; import DOMPurify from 'dompurify'; +import { textType } from 'src/components/GiftTemplate/templates/TextTypeTemplate'; interface Props { - questionContent: TextFormat; - correctAnswer: boolean; - globalFeedback?: string | undefined; + question: TrueFalseQuestion; handleOnSubmitAnswer?: (answer: boolean) => void; showAnswer?: boolean; } -const TrueFalseQuestion: React.FC = (props) => { - const { questionContent, correctAnswer, showAnswer, handleOnSubmitAnswer, globalFeedback } = +const TrueFalseQuestionDisplay: React.FC = (props) => { + const { question, showAnswer, handleOnSubmitAnswer } = props; const [answer, setAnswer] = useState(undefined); useEffect(() => { setAnswer(undefined); - }, [questionContent]); + }, [question]); const selectedTrue = answer ? 'selected' : ''; const selectedFalse = answer !== undefined && !answer ? 'selected' : ''; + const correctAnswer = question.isTrue === answer; return (
-
+
- {globalFeedback && showAnswer && ( -
{globalFeedback}
+ {/* selected TRUE, show True feedback if it exists */} + {showAnswer && answer && question.trueFormattedFeedback && ( +
+
+
+ )} + {/* selected FALSE, show False feedback if it exists */} + {showAnswer && !answer && question.falseFormattedFeedback && ( +
+
+
+ )} + {question.formattedGlobalFeedback && showAnswer && ( +
+
+
)} {!showAnswer && handleOnSubmitAnswer && (
diff --git a/client/src/components/TeacherModeQuiz/TeacherModeQuiz.tsx b/client/src/components/TeacherModeQuiz/TeacherModeQuiz.tsx index 850c9ef..ae2d382 100644 --- a/client/src/components/TeacherModeQuiz/TeacherModeQuiz.tsx +++ b/client/src/components/TeacherModeQuiz/TeacherModeQuiz.tsx @@ -1,13 +1,14 @@ // TeacherModeQuiz.tsx import React, { useEffect, useState } from 'react'; -import QuestionComponent from '../Questions/QuestionDisplay'; +import QuestionComponent from '../QuestionsDisplay/QuestionDisplay'; import '../../pages/Student/JoinRoom/joinRoom.css'; import { QuestionType } from '../../Types/QuestionType'; // import { QuestionService } from '../../services/QuestionService'; import DisconnectButton from 'src/components/DisconnectButton/DisconnectButton'; import { Dialog, DialogTitle, DialogContent, DialogActions, Button } from '@mui/material'; +import { Question } from 'gift-pegjs'; interface TeacherModeQuizProps { questionInfos: QuestionType; @@ -63,7 +64,7 @@ const TeacherModeQuiz: React.FC = ({ ) : ( )} @@ -76,7 +77,7 @@ const TeacherModeQuiz: React.FC = ({ {feedbackMessage} diff --git a/client/src/pages/Teacher/ManageRoom/ManageRoom.tsx b/client/src/pages/Teacher/ManageRoom/ManageRoom.tsx index 207143c..ae9bdfa 100644 --- a/client/src/pages/Teacher/ManageRoom/ManageRoom.tsx +++ b/client/src/pages/Teacher/ManageRoom/ManageRoom.tsx @@ -2,8 +2,8 @@ import React, { useEffect, useState } from 'react'; import { useNavigate, useParams } from 'react-router-dom'; import { Socket } from 'socket.io-client'; -import { GIFTQuestion, parse } from 'gift-pegjs'; -import { QuestionType } from '../../../Types/QuestionType'; +import { ParsedGIFTQuestion, BaseQuestion, parse, Question } from 'gift-pegjs'; +import { isSimpleNumericalAnswer, isRangeNumericalAnswer, isHighLowNumericalAnswer } from "gift-pegjs/typeGuards"; import LiveResultsComponent from 'src/components/LiveResults/LiveResults'; // import { QuestionService } from '../../../services/QuestionService'; import webSocketService, { AnswerReceptionFromBackendType } from '../../../services/WebsocketService'; @@ -18,8 +18,9 @@ import { Refresh, Error } from '@mui/icons-material'; import StudentWaitPage from 'src/components/StudentWaitPage/StudentWaitPage'; import DisconnectButton from 'src/components/DisconnectButton/DisconnectButton'; //import QuestionNavigation from 'src/components/QuestionNavigation/QuestionNavigation'; -import QuestionDisplay from 'src/components/Questions/QuestionDisplay'; +import QuestionDisplay from 'src/components/QuestionsDisplay/QuestionDisplay'; import ApiService from '../../../services/ApiService'; +import { QuestionType } from 'src/Types/QuestionType'; const ManageRoom: React.FC = () => { const navigate = useNavigate(); @@ -277,7 +278,7 @@ const ManageRoom: React.FC = () => { const parsedQuestions = [] as QuestionType[]; quizQuestionArray.forEach((question, index) => { - parsedQuestions.push({ question: parse(question)[0] }); + parsedQuestions.push({ question: parse(question)[0] as BaseQuestion }); parsedQuestions[index].question.id = (index + 1).toString(); }); if (parsedQuestions.length === 0) return null; @@ -347,7 +348,7 @@ const ManageRoom: React.FC = () => { const answerText = answer.toString(); if (questionInfo) { - const question = questionInfo.question as GIFTQuestion; + const question = questionInfo.question as ParsedGIFTQuestion; if (question.type === 'TF') { return ( (question.isTrue && answerText == 'true') || @@ -355,53 +356,39 @@ const ManageRoom: React.FC = () => { ); } else if (question.type === 'MC') { return question.choices.some( - (choice) => choice.isCorrect && choice.text.text === answerText + (choice) => choice.isCorrect && choice.formattedText.text === answerText ); } else if (question.type === 'Numerical') { - if (question.choices && !Array.isArray(question.choices)) { - if ( - question.choices.type === 'high-low' && - question.choices.numberHigh && - question.choices.numberLow - ) { - const answerNumber = parseFloat(answerText); - if (!isNaN(answerNumber)) { - return ( - answerNumber <= question.choices.numberHigh && - answerNumber >= question.choices.numberLow - ); - } + if (isHighLowNumericalAnswer(question.choices[0])) { + const choice = question.choices[0]; + const answerNumber = parseFloat(answerText); + if (!isNaN(answerNumber)) { + return ( + answerNumber <= choice.numberHigh && + answerNumber >= choice.numberLow + ); } } - if (question.choices && Array.isArray(question.choices)) { - if ( - question.choices[0].text.type === 'range' && - question.choices[0].text.number && - question.choices[0].text.range - ) { - const answerNumber = parseFloat(answerText); - const range = question.choices[0].text.range; - const correctAnswer = question.choices[0].text.number; - if (!isNaN(answerNumber)) { - return ( - answerNumber <= correctAnswer + range && - answerNumber >= correctAnswer - range - ); - } + if (isRangeNumericalAnswer(question.choices[0])) { + const answerNumber = parseFloat(answerText); + const range = question.choices[0].range; + const correctAnswer = question.choices[0].number; + if (!isNaN(answerNumber)) { + return ( + answerNumber <= correctAnswer + range && + answerNumber >= correctAnswer - range + ); } - if ( - question.choices[0].text.type === 'simple' && - question.choices[0].text.number - ) { - const answerNumber = parseFloat(answerText); - if (!isNaN(answerNumber)) { - return answerNumber === question.choices[0].text.number; - } + } + if (isSimpleNumericalAnswer(question.choices[0])) { + const answerNumber = parseFloat(answerText); + if (!isNaN(answerNumber)) { + return answerNumber === question.choices[0].number; } } } else if (question.type === 'Short') { return question.choices.some( - (choice) => choice.text.text.toUpperCase() === answerText.toUpperCase() + (choice) => choice.text.toUpperCase() === answerText.toUpperCase() ); } } @@ -476,7 +463,7 @@ const ManageRoom: React.FC = () => { {currentQuestion && ( )} diff --git a/client/tsconfig.json b/client/tsconfig.json index a6b7592..5115a44 100644 --- a/client/tsconfig.json +++ b/client/tsconfig.json @@ -2,14 +2,19 @@ "compilerOptions": { "baseUrl": "./", "paths": { - "src/*": ["src/*"] + "src/*": [ + "src/*" + ] }, "target": "ESNext", "useDefineForClassFields": true, - "lib": ["ES2020", "DOM", "DOM.Iterable"], + "lib": [ + "ES2020", + "DOM", + "DOM.Iterable" + ], "module": "ESNext", "skipLibCheck": true, - /* Bundler mode */ "moduleResolution": "bundler", "allowImportingTsExtensions": true, @@ -17,7 +22,6 @@ "isolatedModules": true, "noEmit": true, "jsx": "react", - /* Linting */ "strict": true, "noUnusedLocals": true, @@ -25,6 +29,16 @@ "noFallthroughCasesInSwitch": true, "esModuleInterop": true }, - "include": ["src"], - "references": [{ "path": "./tsconfig.node.json" }] + // "exclude": [ + // // "src/components/GiftTemplate/**/*", + // // "src/__tests__/components/GiftTemplate/**/*", + // ], + "include": [ + "src" + ], + "references": [ + { + "path": "./tsconfig.node.json" + } + ] } From 82b904ef82bd99f4d81a382f11e82ee435e4a39c Mon Sep 17 00:00:00 2001 From: "C. Fuhrman" Date: Sat, 25 Jan 2025 02:07:38 -0500 Subject: [PATCH 08/29] Some template (snapshot) tests reveal trouble --- .../components/GiftTemplate/templates/TrueFalse.test.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/src/__tests__/components/GiftTemplate/templates/TrueFalse.test.tsx b/client/src/__tests__/components/GiftTemplate/templates/TrueFalse.test.tsx index 8804c4d..f009607 100644 --- a/client/src/__tests__/components/GiftTemplate/templates/TrueFalse.test.tsx +++ b/client/src/__tests__/components/GiftTemplate/templates/TrueFalse.test.tsx @@ -14,7 +14,7 @@ const plainTextMock: TemplateOptions & TrueFalseQuestion = parse(`::Sample True/False Title::Sample Stem {T#Correct!#Incorrect!####Sample Global Feedback}`)[0] as TrueFalseQuestion; const katexMock: TemplateOptions & TrueFalseQuestion = - parse(`::Sample True/False Title::$$\\frac{zzz}{yyy}$$ {T#Correct!#Incorrect!####Sample Global Feedback}`)[0] as TrueFalseQuestion; + parse(`::Sample True/False Title::$$\\frac\\{zzz\\}\\{yyy\\}$$ {T#Correct!#Incorrect!####Sample Global Feedback}`)[0] as TrueFalseQuestion; const moodleMock: TemplateOptions & TrueFalseQuestion = parse(`::Sample True/False Title::[moodle]Sample Stem{TRUE#Correct!#Incorrect!####Sample Global Feedback}`)[0] as TrueFalseQuestion; @@ -22,7 +22,7 @@ const moodleMock: TemplateOptions & TrueFalseQuestion = const imageMock: TemplateOptions & ShortAnswerQuestion = parse(`::Sample Short Answer Title with Image:: [markdown]Sample Stem with Image ![](https\\://example.com/cat.gif) - {=A =B =C####[html]Sample Image}`)[0] as ShortAnswerQuestion; + {=A =B =C####[html]}`)[0] as ShortAnswerQuestion; test('TrueFalse snapshot test with plain text', () => { const { asFragment } = render(); From 7dbbfbfe650b078f74a1e5185387ad9eafd590f1 Mon Sep 17 00:00:00 2001 From: "C. Fuhrman" Date: Sat, 25 Jan 2025 10:36:07 -0500 Subject: [PATCH 09/29] Latest beta of gift, true-false and mc tweaks, stem style --- client/package-lock.json | 12 +++++++----- client/package.json | 2 +- client/src/components/GiftTemplate/styles.css | 5 +++++ .../GiftTemplate/templates/MultipleChoiceTemplate.ts | 2 +- .../GiftTemplate/templates/TitleTemplate.ts | 2 +- .../GiftTemplate/templates/TrueFalseTemplate.ts | 4 +++- server/package.json | 2 +- 7 files changed, 19 insertions(+), 10 deletions(-) diff --git a/client/package-lock.json b/client/package-lock.json index 708d145..fe23159 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -21,7 +21,7 @@ "axios": "^1.6.7", "dompurify": "^3.2.3", "esbuild": "^0.23.1", - "gift-pegjs": "^2.0.0-beta.0", + "gift-pegjs": "^2.0.0-beta.1", "jest-environment-jsdom": "^29.7.0", "katex": "^0.16.11", "marked": "^14.1.2", @@ -66,6 +66,9 @@ "vite-plugin-environment": "^1.1.3" } }, + "../GIFT-grammar-PEG.js": { + "extraneous": true + }, "node_modules/@adobe/css-tools": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.0.tgz", @@ -7439,9 +7442,9 @@ } }, "node_modules/gift-pegjs": { - "version": "2.0.0-beta.0", - "resolved": "https://registry.npmjs.org/gift-pegjs/-/gift-pegjs-2.0.0-beta.0.tgz", - "integrity": "sha512-M/U4zue2F9TXGEmIIf+nL9JWwudAR5pK4gLDeF2pFfKDNPF1MoCgCS9Vf37NH0oP+Grc7SJbq8FVUefqPH99NA==", + "version": "2.0.0-beta.1", + "resolved": "https://registry.npmjs.org/gift-pegjs/-/gift-pegjs-2.0.0-beta.1.tgz", + "integrity": "sha512-NFWSu3KjpjKrfnbIu/eQOyQqjCgOd/ONDe3+bKhtTQCrTgQPVoybme9cm8tqBmJz1YynloocrPlv9f2syQl/LQ==", "dependencies": { "pegjs": "^0.10.x" } @@ -10718,7 +10721,6 @@ "version": "0.10.0", "resolved": "https://registry.npmjs.org/pegjs/-/pegjs-0.10.0.tgz", "integrity": "sha512-qI5+oFNEGi3L5HAxDwN2LA4Gg7irF70Zs25edhjld9QemOgp0CbvMtbFcMvFtEo1OityPrcCzkQFB8JP/hxgow==", - "license": "MIT", "bin": { "pegjs": "bin/pegjs" }, diff --git a/client/package.json b/client/package.json index eb8d3e3..48d6183 100644 --- a/client/package.json +++ b/client/package.json @@ -25,7 +25,7 @@ "axios": "^1.6.7", "dompurify": "^3.2.3", "esbuild": "^0.23.1", - "gift-pegjs": "file:../GIFT-grammar-PEG.js", + "gift-pegjs": "^2.0.0-beta.1", "jest-environment-jsdom": "^29.7.0", "katex": "^0.16.11", "marked": "^14.1.2", diff --git a/client/src/components/GiftTemplate/styles.css b/client/src/components/GiftTemplate/styles.css index cefa560..e017de1 100644 --- a/client/src/components/GiftTemplate/styles.css +++ b/client/src/components/GiftTemplate/styles.css @@ -72,6 +72,11 @@ font-size: 2rem; font-weight: 500; } + +.present-question-stem { + margin-bottom: 2vh; +} + .preview-container { margin-bottom: 2vh; width: 60vw; diff --git a/client/src/components/GiftTemplate/templates/MultipleChoiceTemplate.ts b/client/src/components/GiftTemplate/templates/MultipleChoiceTemplate.ts index aef9ec1..e965d70 100644 --- a/client/src/components/GiftTemplate/templates/MultipleChoiceTemplate.ts +++ b/client/src/components/GiftTemplate/templates/MultipleChoiceTemplate.ts @@ -22,7 +22,7 @@ export default function MultipleChoiceTemplate({ type: 'Choix multiple', title: title }), - `

${textType(formattedStem)}

`, + `

${textType(formattedStem)}

`, MultipleChoiceAnswers({ choices: choices }), formattedGlobalFeedback ? GlobalFeedback(formattedGlobalFeedback) : '' ] diff --git a/client/src/components/GiftTemplate/templates/TitleTemplate.ts b/client/src/components/GiftTemplate/templates/TitleTemplate.ts index da9f4ed..1722161 100644 --- a/client/src/components/GiftTemplate/templates/TitleTemplate.ts +++ b/client/src/components/GiftTemplate/templates/TitleTemplate.ts @@ -44,7 +44,7 @@ export default function Title({ type, title }: TitleOptions): string {
${ - title !== null + title ? `${title}` : `(Sans titre)` } diff --git a/client/src/components/GiftTemplate/templates/TrueFalseTemplate.ts b/client/src/components/GiftTemplate/templates/TrueFalseTemplate.ts index 4effb4f..329987e 100644 --- a/client/src/components/GiftTemplate/templates/TrueFalseTemplate.ts +++ b/client/src/components/GiftTemplate/templates/TrueFalseTemplate.ts @@ -5,6 +5,8 @@ import GlobalFeedback from './GlobalFeedbackTemplate'; import MultipleChoiceAnswersTemplate from './MultipleChoiceAnswersTemplate'; import Title from './TitleTemplate'; import { TextChoice, TrueFalseQuestion } from 'gift-pegjs'; +import { ParagraphStyle } from '../constants'; +import { state } from '.'; import DOMPurify from 'dompurify'; type TrueFalseOptions = TemplateOptions & TrueFalseQuestion; @@ -40,7 +42,7 @@ export default function TrueFalseTemplate({ type: 'Vrai/Faux', title: title }), - `
`, + `

${DOMPurify.sanitize(textType(formattedStem))}

`, MultipleChoiceAnswersTemplate({ choices: choices }), formattedGlobalFeedback ? GlobalFeedback(formattedGlobalFeedback) : `` ] diff --git a/server/package.json b/server/package.json index c659597..da602ee 100644 --- a/server/package.json +++ b/server/package.json @@ -35,7 +35,7 @@ "supertest": "^6.3.4" }, "engines": { - "node": "18.x" + "node": "20.x" }, "jest": { "testEnvironment": "node", From bb5b2b5e610179c0ed5b3a793f7536bbfefca3fe Mon Sep 17 00:00:00 2001 From: "C. Fuhrman" Date: Sat, 25 Jan 2025 14:19:31 -0500 Subject: [PATCH 10/29] Renames, centralize sanitize(), StemTemplate --- .../components/GiftTemplate/TextType.test.ts | 18 ++++++------- .../MultipleChoice.test.tsx.snap | 14 +++++----- .../templates/DescriptionTemplate.ts | 8 ++---- .../GiftTemplate/templates/EssayTemplate.ts | 6 ++--- .../templates/GlobalFeedbackTemplate.ts | 4 +-- .../templates/MatchingTemplate.ts | 7 ++--- .../MultipleChoiceAnswersTemplate.ts | 6 ++--- .../templates/MultipleChoiceTemplate.ts | 6 ++--- .../templates/NumericalTemplate.ts | 4 +-- .../templates/ShortAnswerTemplate.ts | 7 ++--- .../GiftTemplate/templates/StemTemplate.ts | 26 +++++++++++++++++++ .../templates/TextTypeTemplate.ts | 10 +++---- .../templates/TrueFalseTemplate.ts | 7 ++--- .../MultipleChoiceQuestionDisplay.tsx | 11 ++++---- .../NumericalQuestionDisplay.tsx | 9 +++---- .../ShortAnswerQuestionDisplay.tsx | 7 +++-- .../TrueFalseQuestionDisplay.tsx | 10 +++---- 17 files changed, 88 insertions(+), 72 deletions(-) create mode 100644 client/src/components/GiftTemplate/templates/StemTemplate.ts diff --git a/client/src/__tests__/components/GiftTemplate/TextType.test.ts b/client/src/__tests__/components/GiftTemplate/TextType.test.ts index 109852e..40d229d 100644 --- a/client/src/__tests__/components/GiftTemplate/TextType.test.ts +++ b/client/src/__tests__/components/GiftTemplate/TextType.test.ts @@ -1,4 +1,4 @@ -import { textType } from "src/components/GiftTemplate/templates/TextTypeTemplate"; +import { FormatTextTemplate } from "src/components/GiftTemplate/templates/TextTypeTemplate"; import { TextFormat } from "gift-pegjs"; describe('TextType', () => { @@ -8,7 +8,7 @@ describe('TextType', () => { format: 'moodle' }; const expectedOutput = 'Hello, world! 5 > 3, right?'; - expect(textType(input)).toBe(expectedOutput); + expect(FormatTextTemplate(input)).toBe(expectedOutput); }); it('should format text with newlines correctly', () => { @@ -17,7 +17,7 @@ describe('TextType', () => { format: 'plain' }; const expectedOutput = 'Hello,
world!
5 > 3, right?'; - expect(textType(input)).toBe(expectedOutput); + expect(FormatTextTemplate(input)).toBe(expectedOutput); }); it('should format text with LaTeX correctly', () => { @@ -31,7 +31,7 @@ describe('TextType', () => { // by running the test and copying the "Received string:" in jest output // when it fails (assuming the output is correct) const expectedOutput = 'E=mc2E=mc^2'; - expect(textType(input)).toContain(expectedOutput); + expect(FormatTextTemplate(input)).toContain(expectedOutput); }); it('should format text with two equations (inline and separate) correctly', () => { @@ -41,7 +41,7 @@ describe('TextType', () => { }; // hint: katex-display is the class that indicates a separate equation const expectedOutput = 'a+b=ca + b = c ? E=mc2E=mc^2'; - expect(textType(input)).toContain(expectedOutput); + expect(FormatTextTemplate(input)).toContain(expectedOutput); }); it('should format text with a katex matrix correctly', () => { @@ -51,7 +51,7 @@ describe('TextType', () => { format: 'plain' }; const expectedOutput = 'Donnez le déterminant de la matrice suivante.\\begin{pmatrix}
a&b \\\\
c&d
\\end{pmatrix}'; - expect(textType(input)).toContain(expectedOutput); + expect(FormatTextTemplate(input)).toContain(expectedOutput); }); it('should format text with Markdown correctly', () => { @@ -61,7 +61,7 @@ describe('TextType', () => { }; // TODO: investigate why the output has an extra newline const expectedOutput = 'Bold\n'; - expect(textType(input)).toBe(expectedOutput); + expect(FormatTextTemplate(input)).toBe(expectedOutput); }); it('should format text with HTML correctly', () => { @@ -70,7 +70,7 @@ describe('TextType', () => { format: 'html' }; const expectedOutput = 'yes'; - expect(textType(input)).toBe(expectedOutput); + expect(FormatTextTemplate(input)).toBe(expectedOutput); }); it('should format plain text correctly', () => { @@ -79,7 +79,7 @@ describe('TextType', () => { format: 'plain' }; const expectedOutput = 'Just plain text'; - expect(textType(input)).toBe(expectedOutput); + expect(FormatTextTemplate(input)).toBe(expectedOutput); }); // Add more tests for other formats if needed 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 651b5bc..923d220 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 @@ -40,7 +40,7 @@ exports[`MultipleChoice snapshot test 1`] = ` </div> <p style=" color: hsl(0, 0%, 0%); -">Sample Stem</p><span style=" +" class="present-question-stem">Sample Stem</p><span style=" color: hsl(0, 0%, 0%); ">Choisir une réponse:</span> <div class='multiple-choice-answers-container'> @@ -169,7 +169,7 @@ exports[`MultipleChoice snapshot test with 2 images using markdown text format 1 </div> <p style=" color: hsl(0, 0%, 0%); -"><img src="https://via.placeholder.com/150" alt = "Sample Image"/></p><span style=" +" class="present-question-stem"><img src="https://via.placeholder.com/150" alt = "Sample Image"/></p><span style=" color: hsl(0, 0%, 0%); ">Choisir une réponse:</span> <div class='multiple-choice-answers-container'> @@ -337,7 +337,7 @@ exports[`MultipleChoice snapshot test with Moodle text format 1`] = ` </div> <p style=" color: hsl(0, 0%, 0%); -">Sample Stem</p><span style=" +" class="present-question-stem">Sample Stem</p><span style=" color: hsl(0, 0%, 0%); ">Choisir une réponse:</span> <div class='multiple-choice-answers-container'> @@ -466,7 +466,7 @@ exports[`MultipleChoice snapshot test with image 1`] = ` </div> <p style=" color: hsl(0, 0%, 0%); -">Sample Stem with Image</p><span style=" +" class="present-question-stem">Sample Stem with Image</p><span style=" color: hsl(0, 0%, 0%); ">Choisir une réponse:</span> <div class='multiple-choice-answers-container'> @@ -631,7 +631,7 @@ exports[`MultipleChoice snapshot test with image using markdown text format 1`] </div> <p style=" color: hsl(0, 0%, 0%); -">Sample Stem with Image +" class="present-question-stem">Sample Stem with Image </p><span style=" color: hsl(0, 0%, 0%); ">Choisir une réponse:</span> @@ -800,7 +800,7 @@ exports[`MultipleChoice snapshot test with katex 1`] = ` </div> <p style=" color: hsl(0, 0%, 0%); -"><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mfrac><mrow><mi>z</mi><mi>z</mi><mi>z</mi></mrow><mrow><mi>y</mi><mi>y</mi><mi>y</mi></mrow></mfrac></mrow><annotation encoding="application/x-tex">\\frac{zzz}{yyy}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1.988em;vertical-align:-0.8804em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.1076em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.03588em;">yyy</span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.04398em;">zzz</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.8804em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span></span></span></span></span></p><span style=" +" class="present-question-stem"><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mfrac><mrow><mi>z</mi><mi>z</mi><mi>z</mi></mrow><mrow><mi>y</mi><mi>y</mi><mi>y</mi></mrow></mfrac></mrow><annotation encoding="application/x-tex">\\frac{zzz}{yyy}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1.988em;vertical-align:-0.8804em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.1076em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.03588em;">yyy</span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.04398em;">zzz</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.8804em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span></span></span></span></span></p><span style=" color: hsl(0, 0%, 0%); ">Choisir une réponse:</span> <div class='multiple-choice-answers-container'> @@ -929,7 +929,7 @@ exports[`MultipleChoice snapshot test with katex, using html text format 1`] = ` </div> <p style=" color: hsl(0, 0%, 0%); -"><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mfrac><mrow><mi>z</mi><mi>z</mi><mi>z</mi></mrow><mrow><mi>y</mi><mi>y</mi><mi>y</mi></mrow></mfrac></mrow><annotation encoding="application/x-tex">\\frac{zzz}{yyy}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1.988em;vertical-align:-0.8804em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.1076em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.03588em;">yyy</span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.04398em;">zzz</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.8804em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span></span></span></span></span></p><span style=" +" class="present-question-stem"><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mfrac><mrow><mi>z</mi><mi>z</mi><mi>z</mi></mrow><mrow><mi>y</mi><mi>y</mi><mi>y</mi></mrow></mfrac></mrow><annotation encoding="application/x-tex">\\frac{zzz}{yyy}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1.988em;vertical-align:-0.8804em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.1076em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.03588em;">yyy</span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.04398em;">zzz</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.8804em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span></span></span></span></span></p><span style=" color: hsl(0, 0%, 0%); ">Choisir une réponse:</span> <div class='multiple-choice-answers-container'> diff --git a/client/src/components/GiftTemplate/templates/DescriptionTemplate.ts b/client/src/components/GiftTemplate/templates/DescriptionTemplate.ts index fa4b70d..5e934a7 100644 --- a/client/src/components/GiftTemplate/templates/DescriptionTemplate.ts +++ b/client/src/components/GiftTemplate/templates/DescriptionTemplate.ts @@ -1,10 +1,8 @@ import { TemplateOptions } from './types'; import QuestionContainer from './QuestionContainerTemplate'; import Title from './TitleTemplate'; -import { textType } from './TextTypeTemplate'; -import { ParagraphStyle } from '../constants'; -import { state } from '.'; import { Description } from 'gift-pegjs'; +import StemTemplate from './StemTemplate'; type DescriptionOptions = TemplateOptions & Description; @@ -15,9 +13,7 @@ export default function DescriptionTemplate({ title, formattedStem}: Description type: 'Description', title: title }), - `

- ${textType(formattedStem)} -

` + StemTemplate({formattedStem}), ] })}`; } diff --git a/client/src/components/GiftTemplate/templates/EssayTemplate.ts b/client/src/components/GiftTemplate/templates/EssayTemplate.ts index 5697846..ba1ac03 100644 --- a/client/src/components/GiftTemplate/templates/EssayTemplate.ts +++ b/client/src/components/GiftTemplate/templates/EssayTemplate.ts @@ -1,11 +1,11 @@ import { TemplateOptions } from './types'; import QuestionContainer from './QuestionContainerTemplate'; import Title from './TitleTemplate'; -import {textType} from './TextTypeTemplate'; import GlobalFeedbackTemplate from './GlobalFeedbackTemplate'; -import { ParagraphStyle, TextAreaStyle } from '../constants'; +import { TextAreaStyle } from '../constants'; import { state } from '.'; import { EssayQuestion } from 'gift-pegjs'; +import StemTemplate from './StemTemplate'; type EssayOptions = TemplateOptions & EssayQuestion; @@ -16,7 +16,7 @@ export default function EssayTemplate({ title, formattedStem, formattedGlobalFee type: 'Développement', title: title }), - `

${textType(formattedStem)}

`, + StemTemplate({formattedStem}), ``, diff --git a/client/src/components/GiftTemplate/templates/GlobalFeedbackTemplate.ts b/client/src/components/GiftTemplate/templates/GlobalFeedbackTemplate.ts index 2c5b6d4..e5e4e78 100644 --- a/client/src/components/GiftTemplate/templates/GlobalFeedbackTemplate.ts +++ b/client/src/components/GiftTemplate/templates/GlobalFeedbackTemplate.ts @@ -1,5 +1,5 @@ import { TemplateOptions } from './types'; -import {textType} from './TextTypeTemplate'; +import {FormatTextTemplate} from './TextTypeTemplate'; import { state } from '.'; import { theme } from '../constants'; import { TextFormat } from 'gift-pegjs'; @@ -20,7 +20,7 @@ export default function GlobalFeedbackTemplate({ format, text }: GlobalFeedbackO return (format && text) ? `
-

${textType({format: format, text: text})}

+

${FormatTextTemplate({format: format, text: text})}

` : ``; } diff --git a/client/src/components/GiftTemplate/templates/MatchingTemplate.ts b/client/src/components/GiftTemplate/templates/MatchingTemplate.ts index d36b8ad..a661c87 100644 --- a/client/src/components/GiftTemplate/templates/MatchingTemplate.ts +++ b/client/src/components/GiftTemplate/templates/MatchingTemplate.ts @@ -1,11 +1,12 @@ import { TemplateOptions } from './types'; import QuestionContainer from './QuestionContainerTemplate'; import Title from './TitleTemplate'; -import {textType} from './TextTypeTemplate'; +import {FormatTextTemplate} from './TextTypeTemplate'; import GlobalFeedback from './GlobalFeedbackTemplate'; import { ParagraphStyle, SelectStyle } from '../constants'; import { state } from '.'; import { MatchingQuestion } from 'gift-pegjs'; +import StemTemplate from './StemTemplate'; type MatchingOptions = TemplateOptions & MatchingQuestion; @@ -25,7 +26,7 @@ export default function MatchingTemplate({ type: 'Appariement', title: title }), - `

${textType(formattedStem)}

`, + StemTemplate({formattedStem}), MatchAnswers({ choices: matchPairs }), formattedGlobalFeedback ? GlobalFeedback(formattedGlobalFeedback) : '' ] @@ -66,7 +67,7 @@ function MatchAnswers({ choices }: MatchAnswerOptions): string { .map(({ formattedSubquestion }) => { return `
- ${textType(formattedSubquestion)} + ${FormatTextTemplate(formattedSubquestion)}
diff --git a/client/src/components/GiftTemplate/templates/MultipleChoiceAnswersTemplate.ts b/client/src/components/GiftTemplate/templates/MultipleChoiceAnswersTemplate.ts index 2c6e1c9..1ca156a 100644 --- a/client/src/components/GiftTemplate/templates/MultipleChoiceAnswersTemplate.ts +++ b/client/src/components/GiftTemplate/templates/MultipleChoiceAnswersTemplate.ts @@ -1,6 +1,6 @@ import { nanoid } from 'nanoid'; import { TemplateOptions } from './types'; -import {FormatTextTemplate} from './TextTypeTemplate'; +import {FormattedTextTemplate} from './TextTypeTemplate'; import AnswerIcon from './AnswerIconTemplate'; import { state } from '.'; import { ParagraphStyle, theme } from '../constants'; @@ -42,7 +42,7 @@ export default function MultipleChoiceAnswersTemplate({ choices }: MultipleChoic }" id="${inputId}" name="${id}"> ${AnswerWeight({ correct: isCorrectOption, weight: weight })} ${AnswerIcon({ correct: isCorrectOption })} ${AnswerFeedback({ formattedFeedback: formattedFeedback })} @@ -86,5 +86,5 @@ function AnswerFeedback({ formattedFeedback }: AnswerFeedbackOptions): string { color: ${theme(state.theme, 'teal700', 'gray700')}; `; - return formattedFeedback ? `${FormatTextTemplate(formattedFeedback)}` : ``; + return formattedFeedback ? `${FormattedTextTemplate(formattedFeedback)}` : ``; } diff --git a/client/src/components/GiftTemplate/templates/ShortAnswerTemplate.ts b/client/src/components/GiftTemplate/templates/ShortAnswerTemplate.ts index 1fb04d3..6971785 100644 --- a/client/src/components/GiftTemplate/templates/ShortAnswerTemplate.ts +++ b/client/src/components/GiftTemplate/templates/ShortAnswerTemplate.ts @@ -1,7 +1,7 @@ import { TemplateOptions } from './types'; import QuestionContainer from './QuestionContainerTemplate'; import Title from './TitleTemplate'; -import {FormatTextTemplate} from './TextTypeTemplate'; +import {FormattedTextTemplate} from './TextTypeTemplate'; import GlobalFeedback from './GlobalFeedbackTemplate'; import { ParagraphStyle, InputStyle } from '../constants'; import { state } from './index'; @@ -32,7 +32,7 @@ export default function ShortAnswerTemplate({ function Answers({ choices }: AnswerOptions): string { const placeholder = choices - .map(({ text }) => FormatTextTemplate({ format: '', text: text })) + .map(({ text }) => FormattedTextTemplate({ format: '', text: text })) .join(', '); return `
diff --git a/client/src/components/GiftTemplate/templates/StemTemplate.ts b/client/src/components/GiftTemplate/templates/StemTemplate.ts index 016a247..1eb9241 100644 --- a/client/src/components/GiftTemplate/templates/StemTemplate.ts +++ b/client/src/components/GiftTemplate/templates/StemTemplate.ts @@ -2,7 +2,7 @@ import { TemplateOptions } from './types'; import { state } from '.'; import { ParagraphStyle } from '../constants'; import { BaseQuestion } from 'gift-pegjs'; -import { FormatTextTemplate } from './TextTypeTemplate'; +import { FormattedTextTemplate } from './TextTypeTemplate'; // Type is string to allow for custom question type text (e,g, "Multiple Choice") interface StemOptions extends TemplateOptions { @@ -18,7 +18,7 @@ export default function StemTemplate({ formattedStem }: StemOptions): string {

- ${FormatTextTemplate(formattedStem)} + ${FormattedTextTemplate(formattedStem)}

diff --git a/client/src/components/GiftTemplate/templates/TextTypeTemplate.ts b/client/src/components/GiftTemplate/templates/TextTypeTemplate.ts index 3ee4217..8e07345 100644 --- a/client/src/components/GiftTemplate/templates/TextTypeTemplate.ts +++ b/client/src/components/GiftTemplate/templates/TextTypeTemplate.ts @@ -25,22 +25,27 @@ export function formatLatex(text: string): string { * @see marked * @see katex */ -export function FormatTextTemplate(formattedText: TextFormat): string { +export function FormattedTextTemplate(formattedText: TextFormat): string { const formatText = formatLatex(formattedText.text.trim()); // latex needs pure "&", ">", etc. Must not be escaped let parsedText = ''; + let result = ''; switch (formattedText.format) { case '': case 'moodle': case 'plain': // Replace newlines with
tags - return DOMPurify.sanitize(formatText.replace(/(?:\r\n|\r|\n)/g, '
')); + result = formatText.replace(/(?:\r\n|\r|\n)/g, '
'); + break; case 'html': // Strip outer paragraph tags (not a great approach with regex) - return DOMPurify.sanitize(formatText.replace(/(^

)(.*?)(<\/p>)$/gm, '$2')); + result = formatText.replace(/(^

)(.*?)(<\/p>)$/gm, '$2'); + break; case 'markdown': - parsedText = marked.parse(formatText, { breaks: true }) as string; // https://github.com/markedjs/marked/discussions/3219 - return DOMPurify.sanitize(parsedText.replace(/(^

)(.*?)(<\/p>)$/gm, '$2')); + parsedText = marked.parse(formatText, { breaks: true, gfm: true }) as string; //
for newlines + result = parsedText.replace(/(^

)(.*?)(<\/p>)$/gm, '$2'); + break; default: throw new Error(`Unsupported text format: ${formattedText.format}`); } + return DOMPurify.sanitize(result); } diff --git a/client/src/components/QuestionsDisplay/MultipleChoiceQuestionDisplay/MultipleChoiceQuestionDisplay.tsx b/client/src/components/QuestionsDisplay/MultipleChoiceQuestionDisplay/MultipleChoiceQuestionDisplay.tsx index 1e55ee7..f9ade9f 100644 --- a/client/src/components/QuestionsDisplay/MultipleChoiceQuestionDisplay/MultipleChoiceQuestionDisplay.tsx +++ b/client/src/components/QuestionsDisplay/MultipleChoiceQuestionDisplay/MultipleChoiceQuestionDisplay.tsx @@ -2,7 +2,7 @@ import React, { useEffect, useState } from 'react'; import '../questionStyle.css'; import { Button } from '@mui/material'; -import { FormatTextTemplate } from '../../GiftTemplate/templates/TextTypeTemplate'; +import { FormattedTextTemplate } from '../../GiftTemplate/templates/TextTypeTemplate'; import { MultipleChoiceQuestion } from 'gift-pegjs'; interface Props { @@ -30,7 +30,7 @@ const MultipleChoiceQuestionDisplay: React.FC = (props) => { return (

-
+
{question.choices.map((choice, i) => { @@ -47,13 +47,13 @@ const MultipleChoiceQuestionDisplay: React.FC = (props) => { (choice.isCorrect ? '✅' : '❌')}
{alphabet[i]}
-
+
{choice.formattedFeedback && showAnswer && (
{choice.isCorrect ? '✅' : '❌'} -
+
)}
@@ -62,7 +62,7 @@ const MultipleChoiceQuestionDisplay: React.FC = (props) => {
{question.formattedGlobalFeedback && showAnswer && (
-
+
)} diff --git a/client/src/components/QuestionsDisplay/NumericalQuestionDisplay/NumericalQuestionDisplay.tsx b/client/src/components/QuestionsDisplay/NumericalQuestionDisplay/NumericalQuestionDisplay.tsx index 4067f18..ac9c83f 100644 --- a/client/src/components/QuestionsDisplay/NumericalQuestionDisplay/NumericalQuestionDisplay.tsx +++ b/client/src/components/QuestionsDisplay/NumericalQuestionDisplay/NumericalQuestionDisplay.tsx @@ -2,7 +2,7 @@ import React, { useState } from 'react'; import '../questionStyle.css'; import { Button, TextField } from '@mui/material'; -import { FormatTextTemplate } from '../../GiftTemplate/templates/TextTypeTemplate'; +import { FormattedTextTemplate } from '../../GiftTemplate/templates/TextTypeTemplate'; import { NumericalQuestion, SimpleNumericalAnswer, RangeNumericalAnswer, HighLowNumericalAnswer } from 'gift-pegjs'; import { isSimpleNumericalAnswer, isRangeNumericalAnswer, isHighLowNumericalAnswer, isMultipleNumericalAnswer } from 'gift-pegjs/typeGuards'; @@ -40,13 +40,13 @@ const NumericalQuestionDisplay: React.FC = (props) => { return (
-
+
{showAnswer ? ( <>
{correctAnswer}
{question.formattedGlobalFeedback &&
-
+
} ) : ( @@ -64,7 +64,7 @@ const NumericalQuestionDisplay: React.FC = (props) => {
{question.formattedGlobalFeedback && showAnswer && (
-
+
)} {handleOnSubmitAnswer && ( diff --git a/client/src/components/QuestionsDisplay/ShortAnswerQuestionDisplay/ShortAnswerQuestionDisplay.tsx b/client/src/components/QuestionsDisplay/ShortAnswerQuestionDisplay/ShortAnswerQuestionDisplay.tsx index f6f09d0..50c2261 100644 --- a/client/src/components/QuestionsDisplay/ShortAnswerQuestionDisplay/ShortAnswerQuestionDisplay.tsx +++ b/client/src/components/QuestionsDisplay/ShortAnswerQuestionDisplay/ShortAnswerQuestionDisplay.tsx @@ -1,7 +1,7 @@ import React, { useState } from 'react'; import '../questionStyle.css'; import { Button, TextField } from '@mui/material'; -import { FormatTextTemplate } from '../../GiftTemplate/templates/TextTypeTemplate'; +import { FormattedTextTemplate } from '../../GiftTemplate/templates/TextTypeTemplate'; import { ShortAnswerQuestion } from 'gift-pegjs'; interface Props { @@ -17,7 +17,7 @@ const ShortAnswerQuestionDisplay: React.FC = (props) => { return (
-
+
{showAnswer ? ( <> @@ -29,7 +29,7 @@ const ShortAnswerQuestionDisplay: React.FC = (props) => { ))}
{question.formattedGlobalFeedback &&
-
+
} ) : ( diff --git a/client/src/components/QuestionsDisplay/TrueFalseQuestionDisplay/TrueFalseQuestionDisplay.tsx b/client/src/components/QuestionsDisplay/TrueFalseQuestionDisplay/TrueFalseQuestionDisplay.tsx index 2e6110c..6ab1e07 100644 --- a/client/src/components/QuestionsDisplay/TrueFalseQuestionDisplay/TrueFalseQuestionDisplay.tsx +++ b/client/src/components/QuestionsDisplay/TrueFalseQuestionDisplay/TrueFalseQuestionDisplay.tsx @@ -3,7 +3,7 @@ import React, { useState, useEffect } from 'react'; import '../questionStyle.css'; import { Button } from '@mui/material'; import { TrueFalseQuestion } from 'gift-pegjs'; -import { FormatTextTemplate } from 'src/components/GiftTemplate/templates/TextTypeTemplate'; +import { FormattedTextTemplate } from 'src/components/GiftTemplate/templates/TextTypeTemplate'; interface Props { question: TrueFalseQuestion; @@ -26,7 +26,7 @@ const TrueFalseQuestionDisplay: React.FC = (props) => { return (
-
+