diff --git a/.eslintrc.json b/.eslintrc.json index 165e406024..67fea73faf 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -143,5 +143,11 @@ "version": "detect" } }, - "ignorePatterns": ["**/*.css", "**/*.scss", "**/*.less", "**/*.json"] + "ignorePatterns": [ + "**/*.css", + "**/*.scss", + "**/*.less", + "**/*.json", + "**/*.svg" + ] } diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index fb6505324e..f9f0f1d257 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -140,6 +140,7 @@ jobs: for file in ${CHANGED_UNAUTH_FILES}; do echo "$file is unauthorized to change/delete" done + echo "To override this, apply the 'ignore-sensitive-files-pr' label" exit 1 Count-Changed-Files: diff --git a/package-lock.json b/package-lock.json index b4e90c3d69..50e6ca55cc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,6 +23,7 @@ "@mui/x-charts": "^7.22.2", "@mui/x-data-grid": "^7.22.1", "@mui/x-date-pickers": "^7.18.0", + "@pdfme/common": "^5.2.11", "@pdfme/generator": "^5.2.3", "@pdfme/schemas": "^5.1.6", "@reduxjs/toolkit": "^2.3.0", @@ -154,6 +155,141 @@ "node": ">=6.0.0" } }, + "node_modules/@ant-design/colors": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@ant-design/colors/-/colors-7.1.0.tgz", + "integrity": "sha512-MMoDGWn1y9LdQJQSHiCC20x3uZ3CwQnv9QMz6pCmJOrqdgM9YxsoVVY0wtrdXbmfSgnV0KNk6zi09NAhMR2jvg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@ctrl/tinycolor": "^3.6.1" + } + }, + "node_modules/@ant-design/cssinjs": { + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/@ant-design/cssinjs/-/cssinjs-1.22.1.tgz", + "integrity": "sha512-SLuXM4wiEE1blOx94iXrkOgseMZHzdr4ngdFu3VVDq6AOWh7rlwqTkMAtJho3EsBF6x/eUGOtK53VZXGQG7+sQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.11.1", + "@emotion/hash": "^0.8.0", + "@emotion/unitless": "^0.7.5", + "classnames": "^2.3.1", + "csstype": "^3.1.3", + "rc-util": "^5.35.0", + "stylis": "^4.3.4" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, + "node_modules/@ant-design/cssinjs-utils": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@ant-design/cssinjs-utils/-/cssinjs-utils-1.1.3.tgz", + "integrity": "sha512-nOoQMLW1l+xR1Co8NFVYiP8pZp3VjIIzqV6D6ShYF2ljtdwWJn5WSsH+7kvCktXL/yhEtWURKOfH5Xz/gzlwsg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@ant-design/cssinjs": "^1.21.0", + "@babel/runtime": "^7.23.2", + "rc-util": "^5.38.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@ant-design/cssinjs/node_modules/@emotion/hash": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz", + "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==", + "license": "MIT", + "peer": true + }, + "node_modules/@ant-design/cssinjs/node_modules/@emotion/unitless": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz", + "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==", + "license": "MIT", + "peer": true + }, + "node_modules/@ant-design/cssinjs/node_modules/stylis": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.4.tgz", + "integrity": "sha512-osIBl6BGUmSfDkyH2mB7EFvCJntXDrLhKjHTRj/rK6xLH0yuPrHULDRQzKokSOD4VoorhtKpfcfW1GAntu8now==", + "license": "MIT", + "peer": true + }, + "node_modules/@ant-design/fast-color": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@ant-design/fast-color/-/fast-color-2.0.6.tgz", + "integrity": "sha512-y2217gk4NqL35giHl72o6Zzqji9O7vHh9YmhUVkPtAOpoTCH4uWxo/pr4VE8t0+ChEPs0qo4eJRC5Q1eXWo3vA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.24.7" + }, + "engines": { + "node": ">=8.x" + } + }, + "node_modules/@ant-design/icons": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/@ant-design/icons/-/icons-5.5.2.tgz", + "integrity": "sha512-xc53rjVBl9v2BqFxUjZGti/RfdDeA8/6KYglmInM2PNqSXc/WfuGDTifJI/ZsokJK0aeKvOIbXc9y2g8ILAhEA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@ant-design/colors": "^7.0.0", + "@ant-design/icons-svg": "^4.4.0", + "@babel/runtime": "^7.24.8", + "classnames": "^2.2.6", + "rc-util": "^5.31.1" + }, + "engines": { + "node": ">=8" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, + "node_modules/@ant-design/icons-svg": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@ant-design/icons-svg/-/icons-svg-4.4.2.tgz", + "integrity": "sha512-vHbT+zJEVzllwP+CM+ul7reTEfBR0vgxFe7+lREAsAA7YGsYpboiq2sQNeQeRvh09GfQgs/GyFEvZpJ9cLXpXA==", + "license": "MIT", + "peer": true + }, + "node_modules/@ant-design/react-slick": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@ant-design/react-slick/-/react-slick-1.1.2.tgz", + "integrity": "sha512-EzlvzE6xQUBrZuuhSAFTdsr4P2bBBHGZwKFemEfq8gIGyIQCxalYfZW/T2ORbtQx5rU69o+WycP3exY/7T1hGA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.10.4", + "classnames": "^2.2.5", + "json2mq": "^0.2.0", + "resize-observer-polyfill": "^1.5.1", + "throttle-debounce": "^5.0.0" + }, + "peerDependencies": { + "react": ">=16.9.0" + } + }, + "node_modules/@ant-design/react-slick/node_modules/throttle-debounce": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-5.0.2.tgz", + "integrity": "sha512-B71/4oyj61iNH0KeCamLuE2rmKuTO5byTOSVwECM5FA7TiAiAW+UqTKZ9ERueC4qvgSttUhdmq1mXC3kJqGX7A==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12.22" + } + }, "node_modules/@apollo/client": { "version": "3.11.8", "resolved": "https://registry.npmjs.org/@apollo/client/-/client-3.11.8.tgz", @@ -2063,6 +2199,16 @@ "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", "dev": true }, + "node_modules/@ctrl/tinycolor": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz", + "integrity": "sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + } + }, "node_modules/@dicebear/adventurer": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/@dicebear/adventurer/-/adventurer-9.2.2.tgz", @@ -4889,17 +5035,19 @@ } }, "node_modules/@pdfme/common": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@pdfme/common/-/common-1.2.6.tgz", - "integrity": "sha512-ROmQ/iMUdmFS2QXD/kKDdcU5T6H3azDs2b1hE/OXs8531BPZ9ABbu9+1NRZQoNK4U/zP2F+Osb/B8ckr9lAmGg==", - "peer": true, + "version": "5.2.11", + "resolved": "https://registry.npmjs.org/@pdfme/common/-/common-5.2.11.tgz", + "integrity": "sha512-XfVn3UH0LRRzUu9NDJ0rUzxv5Nib+vyTEWiTv3KNUuO/X2553yIpzAAlrn46V+0QhW+491G+zgbr6ark0cJe4w==", + "license": "MIT", "dependencies": { + "@pdfme/pdf-lib": "^1.18.3", + "acorn": "^8.14.0", "buffer": "^6.0.3", - "fontkit": "^2.0.2", "zod": "^3.20.2" }, - "engines": { - "node": ">=14" + "peerDependencies": { + "antd": "^5.11.2", + "form-render": "^2.2.20" } }, "node_modules/@pdfme/generator": { @@ -4983,6 +5131,164 @@ "url": "https://opencollective.com/popperjs" } }, + "node_modules/@rc-component/async-validator": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@rc-component/async-validator/-/async-validator-5.0.4.tgz", + "integrity": "sha512-qgGdcVIF604M9EqjNF0hbUTz42bz/RDtxWdWuU5EQe3hi7M8ob54B6B35rOsvX5eSvIHIzT9iH1R3n+hk3CGfg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.24.4" + }, + "engines": { + "node": ">=14.x" + } + }, + "node_modules/@rc-component/color-picker": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@rc-component/color-picker/-/color-picker-2.0.1.tgz", + "integrity": "sha512-WcZYwAThV/b2GISQ8F+7650r5ZZJ043E57aVBFkQ+kSY4C6wdofXgB0hBx+GPGpIU0Z81eETNoDUJMr7oy/P8Q==", + "license": "MIT", + "peer": true, + "dependencies": { + "@ant-design/fast-color": "^2.0.6", + "@babel/runtime": "^7.23.6", + "classnames": "^2.2.6", + "rc-util": "^5.38.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/context": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@rc-component/context/-/context-1.4.0.tgz", + "integrity": "sha512-kFcNxg9oLRMoL3qki0OMxK+7g5mypjgaaJp/pkOis/6rVxma9nJBF/8kCIuTYHUQNr0ii7MxqE33wirPZLJQ2w==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.10.1", + "rc-util": "^5.27.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/mini-decimal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rc-component/mini-decimal/-/mini-decimal-1.1.0.tgz", + "integrity": "sha512-jS4E7T9Li2GuYwI6PyiVXmxTiM6b07rlD9Ge8uGZSCz3WlzcG5ZK7g5bbuKNeZ9pgUuPK/5guV781ujdVpm4HQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.18.0" + }, + "engines": { + "node": ">=8.x" + } + }, + "node_modules/@rc-component/mutate-observer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rc-component/mutate-observer/-/mutate-observer-1.1.0.tgz", + "integrity": "sha512-QjrOsDXQusNwGZPf4/qRQasg7UFEj06XiCJ8iuiq/Io7CrHrgVi6Uuetw60WAMG1799v+aM8kyc+1L/GBbHSlw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.18.0", + "classnames": "^2.3.2", + "rc-util": "^5.24.4" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/portal": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@rc-component/portal/-/portal-1.1.2.tgz", + "integrity": "sha512-6f813C0IsasTZms08kfA8kPAGxbbkYToa8ALaiDIGGECU4i9hj8Plgbx0sNJDrey3EtHO30hmdaxtT0138xZcg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.18.0", + "classnames": "^2.3.2", + "rc-util": "^5.24.4" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/qrcode": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@rc-component/qrcode/-/qrcode-1.0.0.tgz", + "integrity": "sha512-L+rZ4HXP2sJ1gHMGHjsg9jlYBX/SLN2D6OxP9Zn3qgtpMWtO2vUfxVFwiogHpAIqs54FnALxraUy/BCO1yRIgg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.24.7", + "classnames": "^2.3.2", + "rc-util": "^5.38.0" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/tour": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/@rc-component/tour/-/tour-1.15.1.tgz", + "integrity": "sha512-Tr2t7J1DKZUpfJuDZWHxyxWpfmj8EZrqSgyMZ+BCdvKZ6r1UDsfU46M/iWAAFBy961Ssfom2kv5f3UcjIL2CmQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.18.0", + "@rc-component/portal": "^1.0.0-9", + "@rc-component/trigger": "^2.0.0", + "classnames": "^2.3.2", + "rc-util": "^5.24.4" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/trigger": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/@rc-component/trigger/-/trigger-2.2.6.tgz", + "integrity": "sha512-/9zuTnWwhQ3S3WT1T8BubuFTT46kvnXgaERR9f4BTKyn61/wpf/BvbImzYBubzJibU707FxwbKszLlHjcLiv1Q==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.23.2", + "@rc-component/portal": "^1.1.0", + "classnames": "^2.3.2", + "rc-motion": "^2.0.0", + "rc-resize-observer": "^1.3.1", + "rc-util": "^5.44.0" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, "node_modules/@react-aria/ssr": { "version": "3.9.5", "resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.9.5.tgz", @@ -7139,7 +7445,9 @@ "version": "8.14.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "devOptional": true, + "license": "MIT", "bin": { "acorn": "bin/acorn" @@ -7189,6 +7497,16 @@ "node": ">=0.4.0" } }, + "node_modules/add-dom-event-listener": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/add-dom-event-listener/-/add-dom-event-listener-1.1.0.tgz", + "integrity": "sha512-WCxx1ixHT0GQU9hb0KI/mhgRQhnU+U3GvwY6ZvVjYq8rsihIGoaIOUbY0yMPBxLH5MDtr0kz3fisWGNcbWW7Jw==", + "license": "MIT", + "peer": true, + "dependencies": { + "object-assign": "4.x" + } + }, "node_modules/agent-base": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", @@ -7201,6 +7519,30 @@ "node": ">= 6.0.0" } }, + "node_modules/ahooks": { + "version": "3.8.4", + "resolved": "https://registry.npmjs.org/ahooks/-/ahooks-3.8.4.tgz", + "integrity": "sha512-39wDEw2ZHvypaT14EpMMk4AzosHWt0z9bulY0BeDsvc9PqJEV+Kjh/4TZfftSsotBMq52iYIOFPd3PR56e0ZJg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.21.0", + "dayjs": "^1.9.1", + "intersection-observer": "^0.12.0", + "js-cookie": "^3.0.5", + "lodash": "^4.17.21", + "react-fast-compare": "^3.2.2", + "resize-observer-polyfill": "^1.5.1", + "screenfull": "^5.0.0", + "tslib": "^2.4.1" + }, + "engines": { + "node": ">=8.0.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/air-datepicker": { "version": "3.5.3", "resolved": "https://registry.npmjs.org/air-datepicker/-/air-datepicker-3.5.3.tgz", @@ -7262,6 +7604,82 @@ "node": ">=0.10.0" } }, + "node_modules/antd": { + "version": "5.22.6", + "resolved": "https://registry.npmjs.org/antd/-/antd-5.22.6.tgz", + "integrity": "sha512-ZYURSV3FR8qQgbfpa554thlO07L6PeHwhAM0wmxnobOBogND/HqSnTU+UZTqT2b2y9MxSfAIu5Xn1uEM9UpceQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@ant-design/colors": "^7.1.0", + "@ant-design/cssinjs": "^1.21.1", + "@ant-design/cssinjs-utils": "^1.1.3", + "@ant-design/icons": "^5.5.2", + "@ant-design/react-slick": "~1.1.2", + "@babel/runtime": "^7.25.7", + "@ctrl/tinycolor": "^3.6.1", + "@rc-component/color-picker": "~2.0.1", + "@rc-component/mutate-observer": "^1.1.0", + "@rc-component/qrcode": "~1.0.0", + "@rc-component/tour": "~1.15.1", + "@rc-component/trigger": "^2.2.6", + "classnames": "^2.5.1", + "copy-to-clipboard": "^3.3.3", + "dayjs": "^1.11.11", + "rc-cascader": "~3.30.0", + "rc-checkbox": "~3.3.0", + "rc-collapse": "~3.9.0", + "rc-dialog": "~9.6.0", + "rc-drawer": "~7.2.0", + "rc-dropdown": "~4.2.1", + "rc-field-form": "~2.7.0", + "rc-image": "~7.11.0", + "rc-input": "~1.6.4", + "rc-input-number": "~9.3.0", + "rc-mentions": "~2.17.0", + "rc-menu": "~9.16.0", + "rc-motion": "^2.9.5", + "rc-notification": "~5.6.2", + "rc-pagination": "~5.0.0", + "rc-picker": "~4.8.3", + "rc-progress": "~4.0.0", + "rc-rate": "~2.13.0", + "rc-resize-observer": "^1.4.3", + "rc-segmented": "~2.5.0", + "rc-select": "~14.16.4", + "rc-slider": "~11.1.7", + "rc-steps": "~6.0.1", + "rc-switch": "~4.1.0", + "rc-table": "~7.49.0", + "rc-tabs": "~15.4.0", + "rc-textarea": "~1.8.2", + "rc-tooltip": "~6.2.1", + "rc-tree": "~5.10.1", + "rc-tree-select": "~5.24.5", + "rc-upload": "~4.8.1", + "rc-util": "^5.44.2", + "scroll-into-view-if-needed": "^3.1.0", + "throttle-debounce": "^5.0.2" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/ant-design" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/antd/node_modules/throttle-debounce": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-5.0.2.tgz", + "integrity": "sha512-B71/4oyj61iNH0KeCamLuE2rmKuTO5byTOSVwECM5FA7TiAiAW+UqTKZ9ERueC4qvgSttUhdmq1mXC3kJqGX7A==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12.22" + } + }, "node_modules/anymatch": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", @@ -7481,6 +7899,13 @@ "node": ">=12" } }, + "node_modules/async-validator": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/async-validator/-/async-validator-3.5.2.tgz", + "integrity": "sha512-8eLCg00W9pIRZSB781UUX/H6Oskmm8xloZfr09lz5bikRpBVDlJ3hRVuxxP1SxcwsEYfJ4IU8Q19Y8/893r3rQ==", + "license": "MIT", + "peer": true + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -7871,6 +8296,17 @@ "@babel/core": "^7.0.0" } }, + "node_modules/babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha512-ITKNuq2wKlW1fJg9sSW52eepoYgZBggvOAHC0u/CYu/qxQ9EVzThCgR69BnSXLHjy2f7SY5zaQ4yt7H9ZVxY2g==", + "license": "MIT", + "peer": true, + "dependencies": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -8145,7 +8581,6 @@ "url": "https://feross.org/support" } ], - "peer": true, "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" @@ -8423,9 +8858,10 @@ "dev": true }, "node_modules/classnames": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.2.tgz", - "integrity": "sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==" + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==", + "license": "MIT" }, "node_modules/cli-cursor": { "version": "5.0.0", @@ -8647,6 +9083,29 @@ "node": "^12.20.0 || >=14" } }, + "node_modules/component-classes": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/component-classes/-/component-classes-1.2.6.tgz", + "integrity": "sha512-hPFGULxdwugu1QWW3SvVOCUHLzO34+a2J6Wqy0c5ASQkfi9/8nZcBB0ZohaEbXOQlCflMAEMmEWk7u7BVs4koA==", + "license": "MIT", + "peer": true, + "dependencies": { + "component-indexof": "0.0.3" + } + }, + "node_modules/component-indexof": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/component-indexof/-/component-indexof-0.0.3.tgz", + "integrity": "sha512-puDQKvx/64HZXb4hBwIcvQLaLgux8o1CbWl39s41hrIIZDl1lJiD5jc22gj3RBeGK0ovxALDYpIbyjqDUUl0rw==", + "peer": true + }, + "node_modules/compute-scroll-into-view": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-3.1.0.tgz", + "integrity": "sha512-rj8l8pD4bJ1nx+dAkMhV1xB5RuZEyVysfxJqB1pRchh1KVvwOv9b7CGB8ZfjTImVv2oF+sYMUkMZq6Na5Ftmbg==", + "license": "MIT", + "peer": true + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -8760,6 +9219,25 @@ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" }, + "node_modules/copy-to-clipboard": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz", + "integrity": "sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==", + "license": "MIT", + "peer": true, + "dependencies": { + "toggle-selection": "^1.0.6" + } + }, + "node_modules/core-js": { + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", + "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==", + "deprecated": "core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js.", + "hasInstallScript": true, + "license": "MIT", + "peer": true + }, "node_modules/core-js-compat": { "version": "3.38.1", "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.38.1.tgz", @@ -8832,6 +9310,17 @@ "sha.js": "^2.4.8" } }, + "node_modules/create-react-class": { + "version": "15.7.0", + "resolved": "https://registry.npmjs.org/create-react-class/-/create-react-class-15.7.0.tgz", + "integrity": "sha512-QZv4sFWG9S5RUvkTYWbflxeZX+JG7Cz0Tn33rQBJ+WFQTqTfUTjMjiv9tnfXazjsO5r0KhPs+AqCjyrQX6h2ng==", + "license": "MIT", + "peer": true, + "dependencies": { + "loose-envify": "^1.3.1", + "object-assign": "^4.1.1" + } + }, "node_modules/create-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", @@ -8903,6 +9392,28 @@ "url": "https://github.com/sponsors/ljharb" } }, + + "node_modules/crypto-random-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", + "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/css-animation": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/css-animation/-/css-animation-1.6.1.tgz", + "integrity": "sha512-/48+/BaEaHRY6kNQ2OIPzKf9A6g8WjZYjhiNDNuIVbsm5tXCGIAsHDjB4Xu1C4vXJtUWZo26O68OQkDpNBaPog==", + "license": "MIT", + "peer": true, + "dependencies": { + "babel-runtime": "6.x", + "component-classes": "^1.2.5" + } + }, + "node_modules/css-box-model": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/css-box-model/-/css-box-model-1.2.1.tgz", @@ -9362,6 +9873,13 @@ "dev": true, "peer": true }, + "node_modules/dom-align": { + "version": "1.12.4", + "resolved": "https://registry.npmjs.org/dom-align/-/dom-align-1.12.4.tgz", + "integrity": "sha512-R8LUSEay/68zE5c8/3BDxiTEvgb4xZTF0RKmAHfiEVN3klfIpXfi2/QCoiWPccVQ0J/ZGdz9OjzL4uJEP/MRAw==", + "license": "MIT", + "peer": true + }, "node_modules/dom-helpers": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", @@ -11013,6 +11531,129 @@ "node": ">= 6" } }, + "node_modules/form-render": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/form-render/-/form-render-2.5.1.tgz", + "integrity": "sha512-oNbJ+McqB5h1yuyxYAT3ixJF8itmHlnKvqDgQhJT9Tw1c3yGwfRnVXboRxBV+Myz0dkf47zL6lyY1l74yQsWsg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@ant-design/icons": "^4.0.2", + "ahooks": "^3.7.5", + "async-validator": "^3.5.1", + "classnames": "^2.3.1", + "color": "^3.1.2", + "dayjs": "^1.11.7", + "lodash-es": "^4.17.21", + "rc-color-picker": "^1.2.6", + "virtualizedtableforantd4": "^1.1.2", + "zustand": "^4.1.5" + }, + "peerDependencies": { + "antd": "4.x || 5.x", + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/form-render/node_modules/@ant-design/colors": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@ant-design/colors/-/colors-6.0.0.tgz", + "integrity": "sha512-qAZRvPzfdWHtfameEGP2Qvuf838NhergR35o+EuVyB5XvSA98xod5r4utvi4TJ3ywmevm290g9nsCG5MryrdWQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@ctrl/tinycolor": "^3.4.0" + } + }, + "node_modules/form-render/node_modules/@ant-design/icons": { + "version": "4.8.3", + "resolved": "https://registry.npmjs.org/@ant-design/icons/-/icons-4.8.3.tgz", + "integrity": "sha512-HGlIQZzrEbAhpJR6+IGdzfbPym94Owr6JZkJ2QCCnOkPVIWMO2xgIVcOKnl8YcpijIo39V7l2qQL5fmtw56cMw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@ant-design/colors": "^6.0.0", + "@ant-design/icons-svg": "^4.3.0", + "@babel/runtime": "^7.11.2", + "classnames": "^2.2.6", + "lodash": "^4.17.15", + "rc-util": "^5.9.4" + }, + "engines": { + "node": ">=8" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, + "node_modules/form-render/node_modules/color": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", + "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", + "license": "MIT", + "peer": true, + "dependencies": { + "color-convert": "^1.9.3", + "color-string": "^1.6.0" + } + }, + "node_modules/form-render/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "license": "MIT", + "peer": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/form-render/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "license": "MIT", + "peer": true + }, + "node_modules/form-render/node_modules/rc-color-picker": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/rc-color-picker/-/rc-color-picker-1.2.6.tgz", + "integrity": "sha512-AaC9Pg7qCHSy5M4eVbqDIaNb2FC4SEw82GOHB2C4R/+vF2FVa/r5XA+Igg5+zLPmAvBLhz9tL4MAfkRA8yWNJw==", + "license": "MIT", + "peer": true, + "dependencies": { + "classnames": "^2.2.5", + "prop-types": "^15.5.8", + "rc-trigger": "1.x", + "rc-util": "^4.0.2", + "tinycolor2": "^1.4.1" + }, + "peerDependencies": { + "react": "16.x", + "react-dom": "16.x" + } + }, + "node_modules/form-render/node_modules/rc-color-picker/node_modules/rc-util": { + "version": "4.21.1", + "resolved": "https://registry.npmjs.org/rc-util/-/rc-util-4.21.1.tgz", + "integrity": "sha512-Z+vlkSQVc1l8O2UjR3WQ+XdWlhj5q9BMQNLk2iOBch75CqPfrJyGtcWMcnhRlNuDu0Ndtt4kLVO8JI8BrABobg==", + "license": "MIT", + "peer": true, + "dependencies": { + "add-dom-event-listener": "^1.1.0", + "prop-types": "^15.5.10", + "react-is": "^16.12.0", + "react-lifecycles-compat": "^3.0.4", + "shallowequal": "^1.1.0" + } + }, + "node_modules/form-render/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT", + "peer": true + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -11883,6 +12524,13 @@ "node": ">=12" } }, + "node_modules/intersection-observer": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/intersection-observer/-/intersection-observer-0.12.2.tgz", + "integrity": "sha512-7m1vEcPCxXYI8HqnL8CKI6siDyD+eIWSwgB3DZA+ZTogxk9I4CDnj4wilt9x/+/QbHI4YG5YZNmC6458/e9Ktg==", + "license": "Apache-2.0", + "peer": true + }, "node_modules/invariant": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", @@ -14149,6 +14797,16 @@ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", "dev": true }, + "node_modules/json2mq": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/json2mq/-/json2mq-0.2.0.tgz", + "integrity": "sha512-SzoRg7ux5DWTII9J2qkrZrqV1gt+rTaoufMxEzXbS26Uid0NwaJd123HcoB80TgubEppxxIGdNxCx50fEoEWQA==", + "license": "MIT", + "peer": true, + "dependencies": { + "string-convert": "^0.2.0" + } + }, "node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", @@ -14650,8 +15308,14 @@ "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "node_modules/lodash-es": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", + "license": "MIT", + "peer": true }, "node_modules/lodash._reinterpolate": { "version": "3.0.0", @@ -15992,6 +16656,13 @@ "node": ">=0.12" } }, + "node_modules/performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", + "license": "MIT", + "peer": true + }, "node_modules/picocolors": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", @@ -16424,6 +17095,16 @@ } ] }, + "node_modules/raf": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", + "integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==", + "license": "MIT", + "peer": true, + "dependencies": { + "performance-now": "^2.1.0" + } + }, "node_modules/raf-schd": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/raf-schd/-/raf-schd-4.0.3.tgz", @@ -16467,6 +17148,777 @@ "safe-buffer": "^5.1.0" } }, + + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/rc-align": { + "version": "2.4.5", + "resolved": "https://registry.npmjs.org/rc-align/-/rc-align-2.4.5.tgz", + "integrity": "sha512-nv9wYUYdfyfK+qskThf4BQUSIadeI/dCsfaMZfNEoxm9HwOIioQ+LyqmMK6jWHAZQgOzMLaqawhuBXlF63vgjw==", + "license": "MIT", + "peer": true, + "dependencies": { + "babel-runtime": "^6.26.0", + "dom-align": "^1.7.0", + "prop-types": "^15.5.8", + "rc-util": "^4.0.4" + } + }, + "node_modules/rc-align/node_modules/rc-util": { + "version": "4.21.1", + "resolved": "https://registry.npmjs.org/rc-util/-/rc-util-4.21.1.tgz", + "integrity": "sha512-Z+vlkSQVc1l8O2UjR3WQ+XdWlhj5q9BMQNLk2iOBch75CqPfrJyGtcWMcnhRlNuDu0Ndtt4kLVO8JI8BrABobg==", + "license": "MIT", + "peer": true, + "dependencies": { + "add-dom-event-listener": "^1.1.0", + "prop-types": "^15.5.10", + "react-is": "^16.12.0", + "react-lifecycles-compat": "^3.0.4", + "shallowequal": "^1.1.0" + } + }, + "node_modules/rc-align/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT", + "peer": true + }, + "node_modules/rc-animate": { + "version": "2.11.1", + "resolved": "https://registry.npmjs.org/rc-animate/-/rc-animate-2.11.1.tgz", + "integrity": "sha512-1NyuCGFJG/0Y+9RKh5y/i/AalUCA51opyyS/jO2seELpgymZm2u9QV3xwODwEuzkmeQ1BDPxMLmYLcTJedPlkQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "babel-runtime": "6.x", + "classnames": "^2.2.6", + "css-animation": "^1.3.2", + "prop-types": "15.x", + "raf": "^3.4.0", + "rc-util": "^4.15.3", + "react-lifecycles-compat": "^3.0.4" + } + }, + "node_modules/rc-animate/node_modules/rc-util": { + "version": "4.21.1", + "resolved": "https://registry.npmjs.org/rc-util/-/rc-util-4.21.1.tgz", + "integrity": "sha512-Z+vlkSQVc1l8O2UjR3WQ+XdWlhj5q9BMQNLk2iOBch75CqPfrJyGtcWMcnhRlNuDu0Ndtt4kLVO8JI8BrABobg==", + "license": "MIT", + "peer": true, + "dependencies": { + "add-dom-event-listener": "^1.1.0", + "prop-types": "^15.5.10", + "react-is": "^16.12.0", + "react-lifecycles-compat": "^3.0.4", + "shallowequal": "^1.1.0" + } + }, + "node_modules/rc-animate/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT", + "peer": true + }, + "node_modules/rc-cascader": { + "version": "3.30.0", + "resolved": "https://registry.npmjs.org/rc-cascader/-/rc-cascader-3.30.0.tgz", + "integrity": "sha512-rrzSbk1Bdqbu+pDwiLCLHu72+lwX9BZ28+JKzoi0DWZ4N29QYFeip8Gctl33QVd2Xg3Rf14D3yAOG76ElJw16w==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.25.7", + "classnames": "^2.3.1", + "rc-select": "~14.16.2", + "rc-tree": "~5.10.1", + "rc-util": "^5.43.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-checkbox": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/rc-checkbox/-/rc-checkbox-3.3.0.tgz", + "integrity": "sha512-Ih3ZaAcoAiFKJjifzwsGiT/f/quIkxJoklW4yKGho14Olulwn8gN7hOBve0/WGDg5o/l/5mL0w7ff7/YGvefVw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.3.2", + "rc-util": "^5.25.2" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-collapse": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/rc-collapse/-/rc-collapse-3.9.0.tgz", + "integrity": "sha512-swDdz4QZ4dFTo4RAUMLL50qP0EY62N2kvmk2We5xYdRwcRn8WcYtuetCJpwpaCbUfUt5+huLpVxhvmnK+PHrkA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "2.x", + "rc-motion": "^2.3.4", + "rc-util": "^5.27.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-dialog": { + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/rc-dialog/-/rc-dialog-9.6.0.tgz", + "integrity": "sha512-ApoVi9Z8PaCQg6FsUzS8yvBEQy0ZL2PkuvAgrmohPkN3okps5WZ5WQWPc1RNuiOKaAYv8B97ACdsFU5LizzCqg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.10.1", + "@rc-component/portal": "^1.0.0-8", + "classnames": "^2.2.6", + "rc-motion": "^2.3.0", + "rc-util": "^5.21.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-drawer": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/rc-drawer/-/rc-drawer-7.2.0.tgz", + "integrity": "sha512-9lOQ7kBekEJRdEpScHvtmEtXnAsy+NGDXiRWc2ZVC7QXAazNVbeT4EraQKYwCME8BJLa8Bxqxvs5swwyOepRwg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.23.9", + "@rc-component/portal": "^1.1.1", + "classnames": "^2.2.6", + "rc-motion": "^2.6.1", + "rc-util": "^5.38.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-dropdown": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/rc-dropdown/-/rc-dropdown-4.2.1.tgz", + "integrity": "sha512-YDAlXsPv3I1n42dv1JpdM7wJ+gSUBfeyPK59ZpBD9jQhK9jVuxpjj3NmWQHOBceA1zEPVX84T2wbdb2SD0UjmA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.18.3", + "@rc-component/trigger": "^2.0.0", + "classnames": "^2.2.6", + "rc-util": "^5.44.1" + }, + "peerDependencies": { + "react": ">=16.11.0", + "react-dom": ">=16.11.0" + } + }, + "node_modules/rc-field-form": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/rc-field-form/-/rc-field-form-2.7.0.tgz", + "integrity": "sha512-hgKsCay2taxzVnBPZl+1n4ZondsV78G++XVsMIJCAoioMjlMQR9YwAp7JZDIECzIu2Z66R+f4SFIRrO2DjDNAA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.18.0", + "@rc-component/async-validator": "^5.0.3", + "rc-util": "^5.32.2" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-image": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/rc-image/-/rc-image-7.11.0.tgz", + "integrity": "sha512-aZkTEZXqeqfPZtnSdNUnKQA0N/3MbgR7nUnZ+/4MfSFWPFHZau4p5r5ShaI0KPEMnNjv4kijSCFq/9wtJpwykw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.11.2", + "@rc-component/portal": "^1.0.2", + "classnames": "^2.2.6", + "rc-dialog": "~9.6.0", + "rc-motion": "^2.6.2", + "rc-util": "^5.34.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-input": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/rc-input/-/rc-input-1.6.4.tgz", + "integrity": "sha512-lBZhfRD4NSAUW0zOKLUeI6GJuXkxeZYi0hr8VcJgJpyTNOvHw1ysrKWAHcEOAAHj7guxgmWYSi6xWrEdfrSAsA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.11.1", + "classnames": "^2.2.1", + "rc-util": "^5.18.1" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, + "node_modules/rc-input-number": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/rc-input-number/-/rc-input-number-9.3.0.tgz", + "integrity": "sha512-JQ363ywqRyxwgVxpg2z2kja3CehTpYdqR7emJ/6yJjRdbvo+RvfE83fcpBCIJRq3zLp8SakmEXq60qzWyZ7Usw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.10.1", + "@rc-component/mini-decimal": "^1.0.1", + "classnames": "^2.2.5", + "rc-input": "~1.6.0", + "rc-util": "^5.40.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-mentions": { + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/rc-mentions/-/rc-mentions-2.17.0.tgz", + "integrity": "sha512-sfHy+qLvc+p8jx8GUsujZWXDOIlIimp6YQz7N5ONQ6bHsa2kyG+BLa5k2wuxgebBbH97is33wxiyq5UkiXRpHA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.22.5", + "@rc-component/trigger": "^2.0.0", + "classnames": "^2.2.6", + "rc-input": "~1.6.0", + "rc-menu": "~9.16.0", + "rc-textarea": "~1.8.0", + "rc-util": "^5.34.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-menu": { + "version": "9.16.0", + "resolved": "https://registry.npmjs.org/rc-menu/-/rc-menu-9.16.0.tgz", + "integrity": "sha512-vAL0yqPkmXWk3+YKRkmIR8TYj3RVdEt3ptG2jCJXWNAvQbT0VJJdRyHZ7kG/l1JsZlB+VJq/VcYOo69VR4oD+w==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.10.1", + "@rc-component/trigger": "^2.0.0", + "classnames": "2.x", + "rc-motion": "^2.4.3", + "rc-overflow": "^1.3.1", + "rc-util": "^5.27.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-motion": { + "version": "2.9.5", + "resolved": "https://registry.npmjs.org/rc-motion/-/rc-motion-2.9.5.tgz", + "integrity": "sha512-w+XTUrfh7ArbYEd2582uDrEhmBHwK1ZENJiSJVb7uRxdE7qJSYjbO2eksRXmndqyKqKoYPc9ClpPh5242mV1vA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.11.1", + "classnames": "^2.2.1", + "rc-util": "^5.44.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-notification": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/rc-notification/-/rc-notification-5.6.2.tgz", + "integrity": "sha512-Id4IYMoii3zzrG0lB0gD6dPgJx4Iu95Xu0BQrhHIbp7ZnAZbLqdqQ73aIWH0d0UFcElxwaKjnzNovTjo7kXz7g==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "2.x", + "rc-motion": "^2.9.0", + "rc-util": "^5.20.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-overflow": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/rc-overflow/-/rc-overflow-1.3.2.tgz", + "integrity": "sha512-nsUm78jkYAoPygDAcGZeC2VwIg/IBGSodtOY3pMof4W3M9qRJgqaDYm03ZayHlde3I6ipliAxbN0RUcGf5KOzw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.11.1", + "classnames": "^2.2.1", + "rc-resize-observer": "^1.0.0", + "rc-util": "^5.37.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-pagination": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/rc-pagination/-/rc-pagination-5.0.0.tgz", + "integrity": "sha512-QjrPvbAQwps93iluvFM62AEYglGYhWW2q/nliQqmvkTi4PXP4HHoh00iC1Sa5LLVmtWQHmG73fBi2x6H6vFHRg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.3.2", + "rc-util": "^5.38.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-picker": { + "version": "4.8.3", + "resolved": "https://registry.npmjs.org/rc-picker/-/rc-picker-4.8.3.tgz", + "integrity": "sha512-hJ45qoEs4mfxXPAJdp1n3sKwADul874Cd0/HwnsEOE60H+tgiJUGgbOD62As3EG/rFVNS5AWRfBCDJJfmRqOVQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.24.7", + "@rc-component/trigger": "^2.0.0", + "classnames": "^2.2.1", + "rc-overflow": "^1.3.2", + "rc-resize-observer": "^1.4.0", + "rc-util": "^5.43.0" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "date-fns": ">= 2.x", + "dayjs": ">= 1.x", + "luxon": ">= 3.x", + "moment": ">= 2.x", + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + }, + "peerDependenciesMeta": { + "date-fns": { + "optional": true + }, + "dayjs": { + "optional": true + }, + "luxon": { + "optional": true + }, + "moment": { + "optional": true + } + } + }, + "node_modules/rc-progress": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/rc-progress/-/rc-progress-4.0.0.tgz", + "integrity": "sha512-oofVMMafOCokIUIBnZLNcOZFsABaUw8PPrf1/y0ZBvKZNpOiu5h4AO9vv11Sw0p4Hb3D0yGWuEattcQGtNJ/aw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.6", + "rc-util": "^5.16.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-rate": { + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/rc-rate/-/rc-rate-2.13.0.tgz", + "integrity": "sha512-oxvx1Q5k5wD30sjN5tqAyWTvJfLNNJn7Oq3IeS4HxWfAiC4BOXMITNAsw7u/fzdtO4MS8Ki8uRLOzcnEuoQiAw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.5", + "rc-util": "^5.0.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-resize-observer": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/rc-resize-observer/-/rc-resize-observer-1.4.3.tgz", + "integrity": "sha512-YZLjUbyIWox8E9i9C3Tm7ia+W7euPItNWSPX5sCcQTYbnwDb5uNpnLHQCG1f22oZWUhLw4Mv2tFmeWe68CDQRQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.20.7", + "classnames": "^2.2.1", + "rc-util": "^5.44.1", + "resize-observer-polyfill": "^1.5.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-segmented": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/rc-segmented/-/rc-segmented-2.5.0.tgz", + "integrity": "sha512-B28Fe3J9iUFOhFJET3RoXAPFJ2u47QvLSYcZWC4tFYNGPEjug5LAxEasZlA/PpAxhdOPqGWsGbSj7ftneukJnw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.11.1", + "classnames": "^2.2.1", + "rc-motion": "^2.4.4", + "rc-util": "^5.17.0" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, + "node_modules/rc-select": { + "version": "14.16.4", + "resolved": "https://registry.npmjs.org/rc-select/-/rc-select-14.16.4.tgz", + "integrity": "sha512-jP6qf7+vjnxGvPpfPWbGYfFlSl3h8L2XcD4O7g2GYXmEeBC0mw+nPD7i++OOE8v3YGqP8xtYjRKAWCMLfjgxlw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.10.1", + "@rc-component/trigger": "^2.1.1", + "classnames": "2.x", + "rc-motion": "^2.0.1", + "rc-overflow": "^1.3.1", + "rc-util": "^5.16.1", + "rc-virtual-list": "^3.5.2" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, + "node_modules/rc-slider": { + "version": "11.1.7", + "resolved": "https://registry.npmjs.org/rc-slider/-/rc-slider-11.1.7.tgz", + "integrity": "sha512-ytYbZei81TX7otdC0QvoYD72XSlxvTihNth5OeZ6PMXyEDq/vHdWFulQmfDGyXK1NwKwSlKgpvINOa88uT5g2A==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.5", + "rc-util": "^5.36.0" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-steps": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/rc-steps/-/rc-steps-6.0.1.tgz", + "integrity": "sha512-lKHL+Sny0SeHkQKKDJlAjV5oZ8DwCdS2hFhAkIjuQt1/pB81M0cA0ErVFdHq9+jmPmFw1vJB2F5NBzFXLJxV+g==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.16.7", + "classnames": "^2.2.3", + "rc-util": "^5.16.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-switch": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/rc-switch/-/rc-switch-4.1.0.tgz", + "integrity": "sha512-TI8ufP2Az9oEbvyCeVE4+90PDSljGyuwix3fV58p7HV2o4wBnVToEyomJRVyTaZeqNPAp+vqeo4Wnj5u0ZZQBg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.21.0", + "classnames": "^2.2.1", + "rc-util": "^5.30.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-table": { + "version": "7.49.0", + "resolved": "https://registry.npmjs.org/rc-table/-/rc-table-7.49.0.tgz", + "integrity": "sha512-/FoPLX94muAQOxVpi1jhnpKjOIqUbT81eELQPAzSXOke4ky4oCWYUXOcVpL31ZCO90xScwVSXRd7coqtgtB1Ng==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.10.1", + "@rc-component/context": "^1.4.0", + "classnames": "^2.2.5", + "rc-resize-observer": "^1.1.0", + "rc-util": "^5.41.0", + "rc-virtual-list": "^3.14.2" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-tabs": { + "version": "15.4.0", + "resolved": "https://registry.npmjs.org/rc-tabs/-/rc-tabs-15.4.0.tgz", + "integrity": "sha512-llKuyiAVqmXm2z7OrmhX5cNb2ueZaL8ZyA2P4R+6/72NYYcbEgOXibwHiQCFY2RiN3swXl53SIABi2CumUS02g==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.11.2", + "classnames": "2.x", + "rc-dropdown": "~4.2.0", + "rc-menu": "~9.16.0", + "rc-motion": "^2.6.2", + "rc-resize-observer": "^1.0.0", + "rc-util": "^5.34.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-textarea": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/rc-textarea/-/rc-textarea-1.8.2.tgz", + "integrity": "sha512-UFAezAqltyR00a8Lf0IPAyTd29Jj9ee8wt8DqXyDMal7r/Cg/nDt3e1OOv3Th4W6mKaZijjgwuPXhAfVNTN8sw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.1", + "rc-input": "~1.6.0", + "rc-resize-observer": "^1.0.0", + "rc-util": "^5.27.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-tooltip": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/rc-tooltip/-/rc-tooltip-6.2.1.tgz", + "integrity": "sha512-rws0duD/3sHHsD905Nex7FvoUGy2UBQRhTkKxeEvr2FB+r21HsOxcDJI0TzyO8NHhnAA8ILr8pfbSBg5Jj5KBg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.11.2", + "@rc-component/trigger": "^2.0.0", + "classnames": "^2.3.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-tree": { + "version": "5.10.1", + "resolved": "https://registry.npmjs.org/rc-tree/-/rc-tree-5.10.1.tgz", + "integrity": "sha512-FPXb3tT/u39mgjr6JNlHaUTYfHkVGW56XaGDahDpEFLGsnPxGcVLNTjcqoQb/GNbSCycl7tD7EvIymwOTP0+Yw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "2.x", + "rc-motion": "^2.0.1", + "rc-util": "^5.16.1", + "rc-virtual-list": "^3.5.1" + }, + "engines": { + "node": ">=10.x" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, + "node_modules/rc-tree-select": { + "version": "5.24.5", + "resolved": "https://registry.npmjs.org/rc-tree-select/-/rc-tree-select-5.24.5.tgz", + "integrity": "sha512-PnyR8LZJWaiEFw0SHRqo4MNQWyyZsyMs8eNmo68uXZWjxc7QqeWcjPPoONN0rc90c3HZqGF9z+Roz+GLzY5GXA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.25.7", + "classnames": "2.x", + "rc-select": "~14.16.2", + "rc-tree": "~5.10.1", + "rc-util": "^5.43.0" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, + "node_modules/rc-trigger": { + "version": "1.11.5", + "resolved": "https://registry.npmjs.org/rc-trigger/-/rc-trigger-1.11.5.tgz", + "integrity": "sha512-MBuUPw1nFzA4K7jQOwb7uvFaZFjXGd00EofUYiZ+l/fgKVq8wnLC0lkv36kwqM7vfKyftRo2sh7cWVpdPuNnnw==", + "peer": true, + "dependencies": { + "babel-runtime": "6.x", + "create-react-class": "15.x", + "prop-types": "15.x", + "rc-align": "2.x", + "rc-animate": "2.x", + "rc-util": "4.x" + } + }, + "node_modules/rc-trigger/node_modules/rc-util": { + "version": "4.21.1", + "resolved": "https://registry.npmjs.org/rc-util/-/rc-util-4.21.1.tgz", + "integrity": "sha512-Z+vlkSQVc1l8O2UjR3WQ+XdWlhj5q9BMQNLk2iOBch75CqPfrJyGtcWMcnhRlNuDu0Ndtt4kLVO8JI8BrABobg==", + "license": "MIT", + "peer": true, + "dependencies": { + "add-dom-event-listener": "^1.1.0", + "prop-types": "^15.5.10", + "react-is": "^16.12.0", + "react-lifecycles-compat": "^3.0.4", + "shallowequal": "^1.1.0" + } + }, + "node_modules/rc-trigger/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT", + "peer": true + }, + "node_modules/rc-upload": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/rc-upload/-/rc-upload-4.8.1.tgz", + "integrity": "sha512-toEAhwl4hjLAI1u8/CgKWt30BR06ulPa4iGQSMvSXoHzO88gPCslxqV/mnn4gJU7PDoltGIC9Eh+wkeudqgHyw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.18.3", + "classnames": "^2.2.5", + "rc-util": "^5.2.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-util": { + "version": "5.44.3", + "resolved": "https://registry.npmjs.org/rc-util/-/rc-util-5.44.3.tgz", + "integrity": "sha512-q6KCcOFk3rv/zD3MckhJteZxb0VjAIFuf622B7ElK4vfrZdAzs16XR5p3VTdy3+U5jfJU5ACz4QnhLSuAGe5dA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.18.3", + "react-is": "^18.2.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-virtual-list": { + "version": "3.16.1", + "resolved": "https://registry.npmjs.org/rc-virtual-list/-/rc-virtual-list-3.16.1.tgz", + "integrity": "sha512-algM5UsB7vrlPNr9lsZEH8s9KHkP8XbT/Y0qylyPkiM8mIOlSJLjBNADcmbYPEQCm4zW82mZRJuVHNzqqN0EAQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.20.0", + "classnames": "^2.2.6", + "rc-resize-observer": "^1.0.0", + "rc-util": "^5.36.0" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc/node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", @@ -16620,6 +18072,13 @@ "react": "^18.3.1" } }, + "node_modules/react-fast-compare": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz", + "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==", + "license": "MIT", + "peer": true + }, "node_modules/react-google-recaptcha": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/react-google-recaptcha/-/react-google-recaptcha-3.1.0.tgz", @@ -16878,6 +18337,13 @@ "node": ">=4" } }, + "node_modules/regenerator-runtime": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", + "license": "MIT", + "peer": true + }, "node_modules/regenerator-transform": { "version": "0.15.2", "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.2.tgz", @@ -17022,6 +18488,13 @@ "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz", "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==" }, + "node_modules/resize-observer-polyfill": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", + "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==", + "license": "MIT", + "peer": true + }, "node_modules/resolve": { "version": "1.22.8", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", @@ -17413,6 +18886,29 @@ "loose-envify": "^1.1.0" } }, + "node_modules/screenfull": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/screenfull/-/screenfull-5.2.0.tgz", + "integrity": "sha512-9BakfsO2aUQN2K9Fdbj87RJIEZ82Q9IGim7FqM5OsebfoFC6ZHXgDq/KvniuLTPdeM8wY2o6Dj3WQ7KeQCj3cA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/scroll-into-view-if-needed": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/scroll-into-view-if-needed/-/scroll-into-view-if-needed-3.1.0.tgz", + "integrity": "sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "compute-scroll-into-view": "^3.0.2" + } + }, "node_modules/semver": { "version": "7.6.3", "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", @@ -17485,6 +18981,13 @@ "sha.js": "bin.js" } }, + "node_modules/shallowequal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", + "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==", + "license": "MIT", + "peer": true + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -17825,6 +19328,13 @@ "node": ">=0.6.19" } }, + "node_modules/string-convert": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/string-convert/-/string-convert-0.2.1.tgz", + "integrity": "sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A==", + "license": "MIT", + "peer": true + }, "node_modules/string-hash": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/string-hash/-/string-hash-1.1.3.tgz", @@ -18288,6 +19798,13 @@ "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", "dev": true }, + "node_modules/tinycolor2": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.6.0.tgz", + "integrity": "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==", + "license": "MIT", + "peer": true + }, "node_modules/tinyexec": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.1.tgz", @@ -18373,6 +19890,13 @@ "node": ">=8.0" } }, + "node_modules/toggle-selection": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz", + "integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==", + "license": "MIT", + "peer": true + }, "node_modules/toml": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/toml/-/toml-2.3.6.tgz", @@ -19455,6 +20979,18 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/virtualizedtableforantd4": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/virtualizedtableforantd4/-/virtualizedtableforantd4-1.3.1.tgz", + "integrity": "sha512-rW8KoToI2nt1jNtweXIUIiygi74XMzKLzUrrtZbGsQc7m3v68AaedPuf4CZcte+nosgYuPEWnAgjuI/KR8BVbg==", + "license": "MIT", + "peer": true, + "peerDependencies": { + "antd": "^4.0.0 || ^5.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/vite": { "version": "5.4.8", "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.8.tgz", @@ -20324,11 +21860,39 @@ "version": "3.22.4", "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.4.tgz", "integrity": "sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==", - "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } }, + "node_modules/zustand": { + "version": "4.5.5", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.5.tgz", + "integrity": "sha512-+0PALYNJNgK6hldkgDq2vLrw5f6g/jCInz52n9RTpropGgeAf/ioFUCdtsjCqu4gNhW9D01rUQBROoRjdzyn2Q==", + "license": "MIT", + "peer": true, + "dependencies": { + "use-sync-external-store": "1.2.2" + }, + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "@types/react": ">=16.8", + "immer": ">=9.0.6", + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + } + } + }, "node_modules/zwitch": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", diff --git a/package.json b/package.json index 4d296a1641..82f8ac12bf 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "@mui/x-charts": "^7.22.2", "@mui/x-data-grid": "^7.22.1", "@mui/x-date-pickers": "^7.18.0", + "@pdfme/common": "^5.2.11", "@pdfme/generator": "^5.2.3", "@pdfme/schemas": "^5.1.6", "@reduxjs/toolkit": "^2.3.0", diff --git a/public/images/svg/options-outline.svg b/public/images/svg/options-outline.svg new file mode 100644 index 0000000000..939935eb11 --- /dev/null +++ b/public/images/svg/options-outline.svg @@ -0,0 +1,11 @@ + + + + + + diff --git a/public/images/svg/organization.svg b/public/images/svg/organization.svg new file mode 100644 index 0000000000..9bed28f87e --- /dev/null +++ b/public/images/svg/organization.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json index 5f6d319588..50e45906f6 100644 --- a/public/locales/en/translation.json +++ b/public/locales/en/translation.json @@ -772,6 +772,17 @@ "loading": "Loading...", "noAttendees": "Attendees not Found" }, + "eventRegistrant": { + "sort": "Sort", + "allRegistrants": "All Registrants", + "eventRegistrantsTable": "Event Registrants Table", + "serialNumber": "Serial Number", + "registrant": "Registrant", + "registeredAt": "Registered At", + "createdAt": "Created At", + "addRegistrant": "Add Registrant", + "noRegistrantsFound": "No Registrants Found." + }, "onSpotAttendee": { "title": "On-spot Attendee", "enterFirstName": "Enter First Name", diff --git a/public/locales/fr/translation.json b/public/locales/fr/translation.json index 8dc5fa888a..454abe6de9 100644 --- a/public/locales/fr/translation.json +++ b/public/locales/fr/translation.json @@ -1082,6 +1082,17 @@ "loading": "Chargement...", "noAttendees": "Aucun participant trouvé" }, + "eventRegistrant": { + "sort": "Trier", + "allRegistrants": "Tous les inscrits", + "eventRegistrantsTable": "Table des inscrits à l'événement", + "serialNumber": "Numéro de série", + "registrant": "Inscrit", + "registeredAt": "Enregistré le", + "createdAt": "Créé le", + "addRegistrant": "Ajouter un inscrit", + "noRegistrantsFound": "Aucun inscrit trouvé." + }, "onSpotAttendee": { "title": "Participant sur place", "enterFirstName": "Entrez le prénom", diff --git a/public/locales/hi/translation.json b/public/locales/hi/translation.json index 2865e72e1f..49289fed4f 100644 --- a/public/locales/hi/translation.json +++ b/public/locales/hi/translation.json @@ -1082,6 +1082,17 @@ "loading": "लोड हो रहा है", "noAttendees": "कोई प्रतिभागी नहीं मिला" }, + "eventRegistrant": { + "sort": "छांटें", + "allRegistrants": "सभी पंजीकृत व्यक्ति", + "eventRegistrantsTable": "इवेंट पंजीकृत व्यक्ति तालिका", + "serialNumber": "सिरियल नंबर", + "registrant": "पंजीकृत व्यक्ति", + "registeredAt": "पंजीकरण तिथि", + "createdAt": "निर्माण तिथि", + "addRegistrant": "पंजीकृत व्यक्ति जोड़ें", + "noRegistrantsFound": "कोई पंजीकृत व्यक्ति नहीं मिला" + }, "onSpotAttendee": { "title": "ऑन-स्पॉट प्रतिभागी", "enterFirstName": "प्रथम नाम दर्ज करें", diff --git a/public/locales/sp/translation.json b/public/locales/sp/translation.json index da91efb41d..606e5063fd 100644 --- a/public/locales/sp/translation.json +++ b/public/locales/sp/translation.json @@ -1084,6 +1084,18 @@ "loading": "Cargando...", "noAttendees": "No se encontraron asistentes" }, + + "eventRegistrant": { + "sort": "Ordenar", + "allRegistrants": "Todos los registrados", + "eventRegistrantsTable": "Tabla de registrados del evento", + "serialNumber": "Número de serie", + "registrant": "Registrado", + "registeredAt": "Registrado en", + "createdAt": "Creado en", + "addRegistrant": "Agregar registrado", + "noRegistrantsFound": "No se encontraron registrados" + }, "onSpotAttendee": { "title": "Asistente en el lugar", "enterFirstName": "Ingrese el nombre", diff --git a/public/locales/zh/translation.json b/public/locales/zh/translation.json index 5fbbf4b870..1138f3df39 100644 --- a/public/locales/zh/translation.json +++ b/public/locales/zh/translation.json @@ -1082,6 +1082,17 @@ "loading": "加载中...", "noAttendees": "未找到参与者" }, + "eventRegistrant": { + "sort": "排序", + "allRegistrants": "所有注册者", + "eventRegistrantsTable": "活动注册者表", + "serialNumber": "序列号", + "registrant": "注册者", + "registeredAt": "注册时间", + "createdAt": "创建时间", + "addRegistrant": "添加注册者", + "noRegistrantsFound": "未找到注册者" + }, "onSpotAttendee": { "title": "现场参与者", "enterFirstName": "输入名字", diff --git a/src/GraphQl/Queries/Queries.ts b/src/GraphQl/Queries/Queries.ts index 81442cbad2..9cf1a92755 100644 --- a/src/GraphQl/Queries/Queries.ts +++ b/src/GraphQl/Queries/Queries.ts @@ -328,6 +328,16 @@ export const EVENT_ATTENDEES = gql` } `; +export const EVENT_REGISTRANTS = gql` + query GetEventAttendeesByEventId($eventId: ID!) { + getEventAttendeesByEventId(eventId: $eventId) { + userId + isRegistered + _id + } + } +`; + export const EVENT_CHECKINS = gql` query eventCheckIns($id: ID!) { event(id: $id) { diff --git a/src/components/AddOn/core/AddOnEntry/AddOnEntry.module.css b/src/components/AddOn/core/AddOnEntry/AddOnEntry.module.css deleted file mode 100644 index c5dd86c8d4..0000000000 --- a/src/components/AddOn/core/AddOnEntry/AddOnEntry.module.css +++ /dev/null @@ -1,24 +0,0 @@ -.entrytoggle { - margin: 24px 24px 0 auto; - width: fit-content; -} - -.entryaction { - margin-left: auto; - display: flex !important; - align-items: center; - background-color: transparent; - color: #31bb6b; -} -.card { - border: 4px solid green; -} -.entryaction i { - margin-right: 8px; -} - -.entryaction .spinner-grow { - height: 1rem; - width: 1rem; - margin-right: 8px; -} diff --git a/src/components/AddOn/core/AddOnEntry/AddOnEntry.test.tsx b/src/components/AddOn/core/AddOnEntry/AddOnEntry.test.tsx deleted file mode 100644 index 3d800eb59f..0000000000 --- a/src/components/AddOn/core/AddOnEntry/AddOnEntry.test.tsx +++ /dev/null @@ -1,236 +0,0 @@ -import React from 'react'; -import { render, screen } from '@testing-library/react'; -import { BrowserRouter } from 'react-router-dom'; -import AddOnEntry from './AddOnEntry'; -import { - ApolloClient, - ApolloProvider, - InMemoryCache, - ApolloLink, - HttpLink, -} from '@apollo/client'; - -import type { NormalizedCacheObject } from '@apollo/client'; -import { Provider } from 'react-redux'; -import { store } from 'state/store'; -import { BACKEND_URL } from 'Constant/constant'; -import i18nForTest from 'utils/i18nForTest'; -import { I18nextProvider } from 'react-i18next'; -import userEvent from '@testing-library/user-event'; -import { MockedProvider, wait } from '@apollo/react-testing'; -import { StaticMockLink } from 'utils/StaticMockLink'; -import { ADD_ON_ENTRY_MOCK } from './AddOnEntryMocks'; -import { ToastContainer } from 'react-toastify'; -import useLocalStorage from 'utils/useLocalstorage'; - -const { getItem } = useLocalStorage(); - -const link = new StaticMockLink(ADD_ON_ENTRY_MOCK, true); - -const httpLink = new HttpLink({ - uri: BACKEND_URL, - headers: { - authorization: 'Bearer ' + getItem('token') || '', - }, -}); -console.error = jest.fn(); -const client: ApolloClient = new ApolloClient({ - cache: new InMemoryCache(), - link: ApolloLink.from([httpLink]), -}); -let mockID: string | undefined = '1'; -jest.mock('react-router-dom', () => ({ - ...jest.requireActual('react-router-dom'), - useParams: () => ({ orgId: mockID }), -})); - -describe('Testing AddOnEntry', () => { - const props = { - id: 'string', - enabled: true, - title: 'string', - description: 'string', - createdBy: 'string', - component: 'string', - installed: true, - configurable: true, - modified: true, - isInstalled: true, - getInstalledPlugins: (): { sample: string } => { - return { sample: 'sample' }; - }, - }; - - test('should render modal and take info to add plugin for registered organization', () => { - const { getByTestId } = render( - - - - - {} - - - - , - ); - expect(getByTestId('AddOnEntry')).toBeInTheDocument(); - }); - - test('uses default values for title and description when not provided', () => { - // Render the component with only required parameters - const mockGetInstalledPlugins = jest.fn(); - render( - - - - - - - - - , - ); - - const titleElement = screen.getByText('No title provided'); // This will check for the default empty string in the title - const descriptionElement = screen.getByText('Description not available'); // This will check for the default empty string in the description - expect(titleElement).toBeInTheDocument(); // Ensure the title element with default value exists - expect(descriptionElement).toBeInTheDocument(); // Ensure the description element with default value exists - }); - - it('renders correctly', () => { - const props = { - id: '1', - title: 'Test Addon', - description: 'Test addon description', - createdBy: 'Test User', - component: 'string', - installed: true, - configurable: true, - modified: true, - isInstalled: true, - uninstalledOrgs: [], - enabled: true, - getInstalledPlugins: (): { sample: string } => { - return { sample: 'sample' }; - }, - }; - - const { getByText } = render( - - - - - {} - - - - , - ); - - expect(getByText('Test Addon')).toBeInTheDocument(); - expect(getByText('Test addon description')).toBeInTheDocument(); - expect(getByText('Test User')).toBeInTheDocument(); - }); - - it('Uninstall Button works correctly', async () => { - const props = { - id: '1', - title: 'Test Addon', - description: 'Test addon description', - createdBy: 'Test User', - component: 'string', - installed: true, - configurable: true, - modified: true, - isInstalled: true, - uninstalledOrgs: [], - enabled: true, - getInstalledPlugins: (): { sample: string } => { - return { sample: 'sample' }; - }, - }; - mockID = 'undefined'; - const { findByText, getByTestId } = render( - - - - - - {} - - - - , - ); - await wait(100); - const btn = getByTestId('AddOnEntry_btn_install'); - await userEvent.click(btn); - await wait(100); - expect(btn.innerHTML).toMatch(/Install/i); - expect( - await findByText('This feature is now removed from your organization'), - ).toBeInTheDocument(); - await userEvent.click(btn); - await wait(100); - - expect(btn.innerHTML).toMatch(/Uninstall/i); - expect( - await findByText('This feature is now enabled in your organization'), - ).toBeInTheDocument(); - }); - - it('Check if uninstalled orgs includes current org', async () => { - const props = { - id: '1', - title: 'Test Addon', - description: 'Test addon description', - createdBy: 'Test User', - component: 'string', - installed: true, - configurable: true, - modified: true, - isInstalled: true, - uninstalledOrgs: ['undefined'], - enabled: true, - getInstalledPlugins: (): { sample: string } => { - return { sample: 'sample' }; - }, - }; - - const { getByTestId } = render( - - - - - {} - - - - , - ); - await wait(100); - const btn = getByTestId('AddOnEntry_btn_install'); - expect(btn.innerHTML).toMatch(/install/i); - }); - test('should be redirected to /orglist if orgId is undefined', async () => { - mockID = undefined; - render( - - - - - {} - - - - , - ); - await wait(100); - expect(window.location.pathname).toEqual('/orglist'); - }); -}); diff --git a/src/components/AddOn/core/AddOnEntry/AddOnEntry.tsx b/src/components/AddOn/core/AddOnEntry/AddOnEntry.tsx index 12805568f6..e2971fee14 100644 --- a/src/components/AddOn/core/AddOnEntry/AddOnEntry.tsx +++ b/src/components/AddOn/core/AddOnEntry/AddOnEntry.tsx @@ -1,5 +1,5 @@ import React, { useState } from 'react'; -import styles from './AddOnEntry.module.css'; +import styles from './../../../../style/app.module.css'; import { Button, Card, Spinner } from 'react-bootstrap'; import { UPDATE_INSTALL_STATUS_PLUGIN_MUTATION } from 'GraphQl/Mutations/mutations'; import { useMutation } from '@apollo/client'; diff --git a/src/components/AddOn/core/AddOnStore/AddOnStore.test.tsx b/src/components/AddOn/core/AddOnStore/AddOnStore.spec.tsx similarity index 88% rename from src/components/AddOn/core/AddOnStore/AddOnStore.test.tsx rename to src/components/AddOn/core/AddOnStore/AddOnStore.spec.tsx index abb4a80ce8..9a7eb08e1b 100644 --- a/src/components/AddOn/core/AddOnStore/AddOnStore.test.tsx +++ b/src/components/AddOn/core/AddOnStore/AddOnStore.spec.tsx @@ -1,5 +1,4 @@ import React, { act } from 'react'; -import 'jest-location-mock'; import { fireEvent, render, screen } from '@testing-library/react'; import { ApolloClient, @@ -20,6 +19,7 @@ import { ORGANIZATIONS_LIST, PLUGIN_GET } from 'GraphQl/Queries/Queries'; import userEvent from '@testing-library/user-event'; import useLocalStorage from 'utils/useLocalstorage'; import { MockedProvider } from '@apollo/react-testing'; +import { vi, describe, test, expect } from 'vitest'; const { getItem } = useLocalStorage(); interface InterfacePlugin { @@ -27,10 +27,10 @@ interface InterfacePlugin { pluginName: string; component: string; } -jest.mock('components/AddOn/support/services/Plugin.helper', () => ({ +vi.mock('components/AddOn/support/services/Plugin.helper', () => ({ __esModule: true, - default: jest.fn().mockImplementation(() => ({ - fetchStore: jest.fn().mockResolvedValue([ + default: vi.fn().mockImplementation(() => ({ + fetchStore: vi.fn().mockResolvedValue([ { _id: '1', pluginName: 'Plugin 1', @@ -47,7 +47,7 @@ jest.mock('components/AddOn/support/services/Plugin.helper', () => ({ }, // Add more mock data as needed ]), - fetchInstalled: jest.fn().mockResolvedValue([ + fetchInstalled: vi.fn().mockResolvedValue([ { _id: '1', pluginName: 'Installed Plugin 1', @@ -64,18 +64,16 @@ jest.mock('components/AddOn/support/services/Plugin.helper', () => ({ }, // Add more mock data as needed ]), - generateLinks: jest - .fn() - .mockImplementation((plugins: InterfacePlugin[]) => { - return plugins - .filter((plugin) => plugin.enabled) - .map((installedPlugin) => { - return { - name: installedPlugin.pluginName, - url: `/plugin/${installedPlugin.component.toLowerCase()}`, - }; - }); - }), + generateLinks: vi.fn().mockImplementation((plugins: InterfacePlugin[]) => { + return plugins + .filter((plugin) => plugin.enabled) + .map((installedPlugin) => { + return { + name: installedPlugin.pluginName, + url: `/plugin/${installedPlugin.component.toLowerCase()}`, + }; + }); + }), })), })); @@ -99,11 +97,11 @@ const client: ApolloClient = new ApolloClient({ link: ApolloLink.from([httpLink]), }); -jest.mock('components/AddOn/support/services/Plugin.helper', () => ({ +vi.mock('components/AddOn/support/services/Plugin.helper', () => ({ __esModule: true, - default: jest.fn().mockImplementation(() => ({ - fetchInstalled: jest.fn().mockResolvedValue([]), - fetchStore: jest.fn().mockResolvedValue([]), + default: vi.fn().mockImplementation(() => ({ + fetchInstalled: vi.fn().mockResolvedValue([]), + fetchStore: vi.fn().mockResolvedValue([]), })), })); @@ -168,10 +166,15 @@ const PLUGIN_LOADING_MOCK = { loading: true, }, }; -jest.mock('react-router-dom', () => ({ - ...jest.requireActual('react-router-dom'), - useParams: () => ({ orgId: 'undefined' }), -})); + +vi.mock('react-router-dom', async () => { + const actualModule = await vi.importActual('react-router-dom'); + return { + ...actualModule, + useParams: () => ({ orgId: 'undefined' }), + }; +}); + const ORGANIZATIONS_LIST_MOCK = { request: { query: ORGANIZATIONS_LIST, diff --git a/src/components/AddOn/support/components/Action/Action.test.tsx b/src/components/AddOn/support/components/Action/Action.test.tsx deleted file mode 100644 index ce6cd633b9..0000000000 --- a/src/components/AddOn/support/components/Action/Action.test.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import React from 'react'; -import { render } from '@testing-library/react'; -import { Provider } from 'react-redux'; - -import { store } from 'state/store'; -import Action from './Action'; - -describe('Testing Action Component', () => { - const props = { - children: 'dummy children', - label: 'dummy label', - }; - - test('should render props and text elements test for the page component', () => { - const { getByText } = render( - - - , - ); - - expect(getByText(props.label)).toBeInTheDocument(); - expect(getByText(props.children)).toBeInTheDocument(); - }); -}); diff --git a/src/components/Advertisements/core/AdvertisementEntry/AdvertisementEntry.test.tsx b/src/components/Advertisements/core/AdvertisementEntry/AdvertisementEntry.test.tsx deleted file mode 100644 index dbd6f88cc3..0000000000 --- a/src/components/Advertisements/core/AdvertisementEntry/AdvertisementEntry.test.tsx +++ /dev/null @@ -1,637 +0,0 @@ -import React from 'react'; -import { render, fireEvent, waitFor, screen } from '@testing-library/react'; -import { - ApolloClient, - ApolloProvider, - InMemoryCache, - ApolloLink, - HttpLink, -} from '@apollo/client'; -import type { NormalizedCacheObject } from '@apollo/client'; -import { BrowserRouter } from 'react-router-dom'; -import AdvertisementEntry from './AdvertisementEntry'; -import AdvertisementRegister from '../AdvertisementRegister/AdvertisementRegister'; -import { Provider } from 'react-redux'; -import { store } from 'state/store'; -import { BACKEND_URL } from 'Constant/constant'; -import i18nForTest from 'utils/i18nForTest'; -import { I18nextProvider } from 'react-i18next'; -import dayjs from 'dayjs'; -import useLocalStorage from 'utils/useLocalstorage'; -import { MockedProvider } from '@apollo/client/testing'; -import { ORGANIZATION_ADVERTISEMENT_LIST } from 'GraphQl/Queries/OrganizationQueries'; -import { DELETE_ADVERTISEMENT_BY_ID } from 'GraphQl/Mutations/mutations'; - -const { getItem } = useLocalStorage(); - -const httpLink = new HttpLink({ - uri: BACKEND_URL, - headers: { - authorization: 'Bearer ' + getItem('token') || '', - }, -}); - -const translations = JSON.parse( - JSON.stringify( - i18nForTest.getDataByLanguage('en')?.translation?.advertisement ?? null, - ), -); - -const client: ApolloClient = new ApolloClient({ - cache: new InMemoryCache(), - link: ApolloLink.from([httpLink]), -}); - -const mockUseMutation = jest.fn(); -jest.mock('@apollo/client', () => { - const originalModule = jest.requireActual('@apollo/client'); - return { - ...originalModule, - useMutation: () => mockUseMutation(), - }; -}); -jest.mock('react-router-dom', () => ({ - ...jest.requireActual('react-router-dom'), - useParams: () => ({ orgId: '1' }), -})); - -describe('Testing Advertisement Entry Component', () => { - test('Testing rendering and deleting of advertisement', async () => { - const deleteAdByIdMock = jest.fn(); - mockUseMutation.mockReturnValue([deleteAdByIdMock]); - const { getByTestId, getAllByText } = render( - - - - - - - - - , - ); - - //Testing rendering - expect(getByTestId('AdEntry')).toBeInTheDocument(); - expect(getAllByText('POPUP')[0]).toBeInTheDocument(); - expect(getAllByText('Advert1')[0]).toBeInTheDocument(); - expect(screen.getByTestId('media')).toBeInTheDocument(); - - //Testing successful deletion - fireEvent.click(getByTestId('moreiconbtn')); - fireEvent.click(getByTestId('deletebtn')); - - await waitFor(() => { - expect(screen.getByTestId('delete_title')).toBeInTheDocument(); - expect(screen.getByTestId('delete_body')).toBeInTheDocument(); - }); - - fireEvent.click(getByTestId('delete_yes')); - - await waitFor(() => { - expect(deleteAdByIdMock).toHaveBeenCalledWith({ - variables: { - id: '1', - }, - }); - const deletedMessage = screen.queryByText('Advertisement Deleted'); - expect(deletedMessage).toBeNull(); - }); - - //Testing unsuccessful deletion - deleteAdByIdMock.mockRejectedValueOnce(new Error('Deletion Failed')); - - fireEvent.click(getByTestId('moreiconbtn')); - - fireEvent.click(getByTestId('delete_yes')); - - await waitFor(() => { - expect(deleteAdByIdMock).toHaveBeenCalledWith({ - variables: { - id: '1', - }, - }); - const deletionFailedText = screen.queryByText((content, element) => { - return ( - element?.textContent === 'Deletion Failed' && - element.tagName.toLowerCase() === 'div' - ); - }); - expect(deletionFailedText).toBeNull(); - }); - }); - it('should use default props when none are provided', () => { - render( - , - ): void { - throw new Error('Function not implemented.'); - }} - />, - ); - - //Check if component renders with default ''(empty string) - const elements = screen.getAllByText(''); // This will return an array of matching elements - elements.forEach((element) => expect(element).toBeInTheDocument()); - - // Check that the component renders with default `mediaUrl` (empty string) - const mediaElement = screen.getByTestId('media'); - expect(mediaElement).toHaveAttribute('src', ''); - - // Check that the component renders with default `endDate` - const defaultEndDate = new Date().toDateString(); - expect(screen.getByText(`Ends on ${defaultEndDate}`)).toBeInTheDocument(); - - // Check that the component renders with default `startDate` - const defaultStartDate = new Date().toDateString(); - console.log(screen.getByText); - expect(screen.getByText(`Ends on ${defaultStartDate}`)).toBeInTheDocument(); //fix text "Ends on"? - }); - it('should correctly override default props when values are provided', () => { - const mockName = 'Test Ad'; - const mockType = 'Banner'; - const mockMediaUrl = 'https://example.com/media.png'; - const mockEndDate = new Date(2025, 11, 31); - const mockStartDate = new Date(2024, 0, 1); - const mockOrganizationId = 'org123'; - - const { getByText } = render( - , - ): void { - throw new Error('Function not implemented.'); - }} - />, - ); - - // Check that the component renders with provided values - expect(getByText(mockName)).toBeInTheDocument(); - // Add more checks based on how each prop affects rendering - }); - it('should open and close the dropdown when options button is clicked', () => { - const { getByTestId, queryByText, getAllByText } = render( - - - - - - - - - , - ); - - // Test initial rendering - expect(getByTestId('AdEntry')).toBeInTheDocument(); - expect(getAllByText('POPUP')[0]).toBeInTheDocument(); - expect(getAllByText('Advert1')[0]).toBeInTheDocument(); - - // Test dropdown functionality - const optionsButton = getByTestId('moreiconbtn'); - - // Initially, the dropdown should not be visible - expect(queryByText('Edit')).toBeNull(); - - // Click to open the dropdown - fireEvent.click(optionsButton); - - // After clicking the button, the dropdown should be visible - expect(queryByText('Edit')).toBeInTheDocument(); - - // Click again to close the dropdown - fireEvent.click(optionsButton); - - // After the second click, the dropdown should be hidden again - expect(queryByText('Edit')).toBeNull(); - }); - - test('Updates the advertisement and shows success toast on successful update', async () => { - const updateAdByIdMock = jest.fn().mockResolvedValue({ - data: { - updateAdvertisement: { - advertisement: { - _id: '1', - name: 'Updated Advertisement', - mediaUrl: '', - startDate: dayjs(new Date()).add(1, 'day').format('YYYY-MM-DD'), - endDate: dayjs(new Date()).add(2, 'days').format('YYYY-MM-DD'), - type: 'BANNER', - }, - }, - }, - }); - - mockUseMutation.mockReturnValue([updateAdByIdMock]); - - render( - - - - - - - - - , - ); - - const optionsButton = screen.getByTestId('moreiconbtn'); - fireEvent.click(optionsButton); - fireEvent.click(screen.getByTestId('editBtn')); - - fireEvent.change(screen.getByLabelText('Enter name of Advertisement'), { - target: { value: 'Updated Advertisement' }, - }); - - expect(screen.getByLabelText('Enter name of Advertisement')).toHaveValue( - 'Updated Advertisement', - ); - - fireEvent.change(screen.getByLabelText(translations.Rtype), { - target: { value: 'BANNER' }, - }); - expect(screen.getByLabelText(translations.Rtype)).toHaveValue('BANNER'); - - fireEvent.change(screen.getByLabelText(translations.RstartDate), { - target: { value: dayjs().add(1, 'day').format('YYYY-MM-DD') }, - }); - - fireEvent.change(screen.getByLabelText(translations.RendDate), { - target: { value: dayjs().add(2, 'days').format('YYYY-MM-DD') }, - }); - - fireEvent.click(screen.getByTestId('addonupdate')); - - expect(updateAdByIdMock).toHaveBeenCalledWith({ - variables: { - id: '1', - name: 'Updated Advertisement', - type: 'BANNER', - startDate: dayjs().add(1, 'day').format('YYYY-MM-DD'), - endDate: dayjs().add(2, 'days').format('YYYY-MM-DD'), - }, - }); - }); - - test('Simulating if the mutation doesnt have data variable while updating', async () => { - const updateAdByIdMock = jest.fn().mockResolvedValue({ - updateAdvertisement: { - _id: '1', - name: 'Updated Advertisement', - type: 'BANNER', - }, - }); - - mockUseMutation.mockReturnValue([updateAdByIdMock]); - - render( - - - - - - - - - , - ); - - const optionsButton = screen.getByTestId('moreiconbtn'); - fireEvent.click(optionsButton); - fireEvent.click(screen.getByTestId('editBtn')); - - fireEvent.change(screen.getByLabelText('Enter name of Advertisement'), { - target: { value: 'Updated Advertisement' }, - }); - - expect(screen.getByLabelText('Enter name of Advertisement')).toHaveValue( - 'Updated Advertisement', - ); - - fireEvent.change(screen.getByLabelText(translations.Rtype), { - target: { value: 'BANNER' }, - }); - expect(screen.getByLabelText(translations.Rtype)).toHaveValue('BANNER'); - - fireEvent.click(screen.getByTestId('addonupdate')); - - expect(updateAdByIdMock).toHaveBeenCalledWith({ - variables: { - id: '1', - name: 'Updated Advertisement', - type: 'BANNER', - }, - }); - }); - - test('Simulating if the mutation does not have data variable while registering', async () => { - Object.defineProperty(window, 'location', { - configurable: true, - value: { - reload: jest.fn(), - href: 'https://example.com/page/id=1', - }, - }); - const createAdByIdMock = jest.fn().mockResolvedValue({ - data1: { - createAdvertisement: { - _id: '1', - }, - }, - }); - - mockUseMutation.mockReturnValue([createAdByIdMock]); - - render( - - - - - { - - } - - - - , - ); - - fireEvent.click(screen.getByTestId('createAdvertisement')); - - fireEvent.change(screen.getByLabelText('Enter name of Advertisement'), { - target: { value: 'Updated Advertisement' }, - }); - - expect(screen.getByLabelText('Enter name of Advertisement')).toHaveValue( - 'Updated Advertisement', - ); - - fireEvent.change(screen.getByLabelText(translations.Rtype), { - target: { value: 'BANNER' }, - }); - expect(screen.getByLabelText(translations.Rtype)).toHaveValue('BANNER'); - - fireEvent.change(screen.getByLabelText(translations.RstartDate), { - target: { value: '2023-01-01' }, - }); - expect(screen.getByLabelText(translations.RstartDate)).toHaveValue( - '2023-01-01', - ); - - fireEvent.change(screen.getByLabelText(translations.RendDate), { - target: { value: '2023-02-01' }, - }); - expect(screen.getByLabelText(translations.RendDate)).toHaveValue( - '2023-02-01', - ); - - fireEvent.click(screen.getByTestId('addonregister')); - - expect(createAdByIdMock).toHaveBeenCalledWith({ - variables: { - organizationId: '1', - name: 'Updated Advertisement', - file: '', - type: 'BANNER', - startDate: dayjs(new Date('2023-01-01')).format('YYYY-MM-DD'), - endDate: dayjs(new Date('2023-02-01')).format('YYYY-MM-DD'), - }, - }); - }); - test('delet advertisement', async () => { - const deleteAdByIdMock = jest.fn(); - const mocks = [ - { - request: { - query: ORGANIZATION_ADVERTISEMENT_LIST, - variables: { - id: '1', - first: 2, - after: null, - last: null, - before: null, - }, - }, - result: { - data: { - organizations: [ - { - _id: '1', - advertisements: { - edges: [ - { - node: { - _id: '1', - name: 'Advertisement1', - startDate: '2022-01-01', - endDate: '2023-01-01', - mediaUrl: 'http://example1.com', - }, - cursor: 'cursor1', - }, - { - node: { - _id: '2', - name: 'Advertisement2', - startDate: '2024-02-01', - endDate: '2025-02-01', - mediaUrl: 'http://example2.com', - }, - cursor: 'cursor2', - }, - { - node: { - _id: '3', - name: 'Advertisement1', - startDate: '2022-01-01', - endDate: '2023-01-01', - mediaUrl: 'http://example1.com', - }, - cursor: 'cursor3', - }, - { - node: { - _id: '4', - name: 'Advertisement2', - startDate: '2024-02-01', - endDate: '2025-02-01', - mediaUrl: 'http://example2.com', - }, - cursor: 'cursor4', - }, - { - node: { - _id: '5', - name: 'Advertisement1', - startDate: '2022-01-01', - endDate: '2023-01-01', - mediaUrl: 'http://example1.com', - }, - cursor: 'cursor5', - }, - { - node: { - _id: '6', - name: 'Advertisement2', - startDate: '2024-02-01', - endDate: '2025-02-01', - mediaUrl: 'http://example2.com', - }, - cursor: 'cursor6', - }, - ], - pageInfo: { - startCursor: 'cursor1', - endCursor: 'cursor6', - hasNextPage: true, - hasPreviousPage: false, - }, - totalCount: 8, - }, - }, - ], - }, - }, - }, - { - request: { - query: DELETE_ADVERTISEMENT_BY_ID, - variables: { - id: '1', - }, - }, - result: { - data: { - advertisements: { - _id: null, - }, - }, - }, - }, - ]; - mockUseMutation.mockReturnValue([deleteAdByIdMock]); - const { getByTestId, getAllByText } = render( - - - - - - - - - - - , - ); - - //Testing rendering - expect(getByTestId('AdEntry')).toBeInTheDocument(); - expect(getAllByText('POPUP')[0]).toBeInTheDocument(); - expect(getAllByText('Advert1')[0]).toBeInTheDocument(); - expect(screen.getByTestId('media')).toBeInTheDocument(); - - //Testing successful deletion - fireEvent.click(getByTestId('moreiconbtn')); - fireEvent.click(getByTestId('deletebtn')); - - await waitFor(() => { - expect(screen.getByTestId('delete_title')).toBeInTheDocument(); - expect(screen.getByTestId('delete_body')).toBeInTheDocument(); - }); - - fireEvent.click(getByTestId('delete_yes')); - - await waitFor(() => { - expect(deleteAdByIdMock).toHaveBeenCalledWith({ - variables: { - id: '1', - }, - }); - const deletedMessage = screen.queryByText('Advertisement Deleted'); - expect(deletedMessage).toBeNull(); - }); - - //Testing unsuccessful deletion - deleteAdByIdMock.mockRejectedValueOnce(new Error('Deletion Failed')); - - fireEvent.click(getByTestId('moreiconbtn')); - - fireEvent.click(getByTestId('delete_yes')); - - await waitFor(() => { - expect(deleteAdByIdMock).toHaveBeenCalledWith({ - variables: { - id: '1', - }, - }); - const deletionFailedText = screen.queryByText((content, element) => { - return ( - element?.textContent === 'Deletion Failed' && - element.tagName.toLowerCase() === 'div' - ); - }); - expect(deletionFailedText).toBeNull(); - }); - }); -}); diff --git a/src/components/Advertisements/core/AdvertisementRegister/AdvertisementRegister.test.tsx b/src/components/Advertisements/core/AdvertisementRegister/AdvertisementRegister.spec.tsx similarity index 87% rename from src/components/Advertisements/core/AdvertisementRegister/AdvertisementRegister.test.tsx rename to src/components/Advertisements/core/AdvertisementRegister/AdvertisementRegister.spec.tsx index 0646a94819..80ef45226f 100644 --- a/src/components/Advertisements/core/AdvertisementRegister/AdvertisementRegister.test.tsx +++ b/src/components/Advertisements/core/AdvertisementRegister/AdvertisementRegister.spec.tsx @@ -25,14 +25,24 @@ import { StaticMockLink } from 'utils/StaticMockLink'; import userEvent from '@testing-library/user-event'; import useLocalStorage from 'utils/useLocalstorage'; import { ORGANIZATION_ADVERTISEMENT_LIST } from 'GraphQl/Queries/Queries'; +import { vi } from 'vitest'; const { getItem } = useLocalStorage(); -jest.mock('react-toastify', () => ({ +vi.mock('react-router-dom', async () => { + const actual = await vi.importActual('react-router-dom'); + return { + ...actual, + useParams: () => ({ orgId: '1' }), + useNavigate: vi.fn(), + }; +}); + +vi.mock('react-toastify', () => ({ toast: { - success: jest.fn(), - warn: jest.fn(), - error: jest.fn(), + success: vi.fn(), + warn: vi.fn(), + error: vi.fn(), }, })); @@ -120,11 +130,22 @@ const httpLink = new HttpLink({ }, }); +vi.mock('utils/useLocalstorage', () => ({ + default: () => ({ + getItem: vi.fn().mockReturnValue('token'), + }), +})); + const client: ApolloClient = new ApolloClient({ cache: new InMemoryCache(), link: ApolloLink.from([httpLink]), }); +// const client: ApolloClient = new ApolloClient({ +// cache: new InMemoryCache(), +// link, +// }); + const translations = { ...JSON.parse( JSON.stringify( @@ -135,10 +156,6 @@ const translations = { ...JSON.parse(JSON.stringify(i18n.getDataByLanguage('en')?.errors ?? {})), }; -jest.mock('react-router-dom', () => ({ - ...jest.requireActual('react-router-dom'), - useParams: () => ({ orgId: '1' }), -})); describe('Testing Advertisement Register Component', () => { test('AdvertismentRegister component loads correctly in register mode', async () => { const { getByText } = render( @@ -153,7 +170,7 @@ describe('Testing Advertisement Register Component', () => { nameEdit="Advert1" orgIdEdit="1" advertisementMediaEdit="" - setAfter={jest.fn()} + setAfter={vi.fn()} /> @@ -166,8 +183,7 @@ describe('Testing Advertisement Register Component', () => { }); test('create advertisement', async () => { - jest.useFakeTimers(); - const setTimeoutSpy = jest.spyOn(global, 'setTimeout'); + const setTimeoutSpy = vi.spyOn(global, 'setTimeout'); await act(async () => { render( @@ -182,7 +198,7 @@ describe('Testing Advertisement Register Component', () => { nameEdit="Ad1" orgIdEdit="1" advertisementMediaEdit="" - setAfter={jest.fn()} + setAfter={vi.fn()} /> @@ -254,12 +270,11 @@ describe('Testing Advertisement Register Component', () => { ); expect(setTimeoutSpy).toHaveBeenCalled(); }); - jest.useRealTimers(); + vi.useRealTimers(); }); test('update advertisement', async () => { - jest.useFakeTimers(); - const setTimeoutSpy = jest.spyOn(global, 'setTimeout'); + const setTimeoutSpy = vi.spyOn(global, 'setTimeout'); await act(async () => { render( @@ -274,7 +289,7 @@ describe('Testing Advertisement Register Component', () => { nameEdit="Ad1" orgIdEdit="1" advertisementMediaEdit="" - setAfter={jest.fn()} + setAfter={vi.fn()} formStatus="edit" /> @@ -346,13 +361,12 @@ describe('Testing Advertisement Register Component', () => { expect(setTimeoutSpy).toHaveBeenCalled(); }); - jest.useRealTimers(); + vi.useRealTimers(); }); test('Logs error to the console and shows error toast when advertisement creation fails', async () => { - jest.useFakeTimers(); - const setTimeoutSpy = jest.spyOn(global, 'setTimeout'); - const toastErrorSpy = jest.spyOn(toast, 'error'); + const setTimeoutSpy = vi.spyOn(global, 'setTimeout'); + const toastErrorSpy = vi.spyOn(toast, 'error'); await act(async () => { render( @@ -367,7 +381,7 @@ describe('Testing Advertisement Register Component', () => { nameEdit="Ad1" orgIdEdit="1" advertisementMediaEdit="" - setAfter={jest.fn()} + setAfter={vi.fn()} /> @@ -397,12 +411,11 @@ describe('Testing Advertisement Register Component', () => { }); expect(setTimeoutSpy).toHaveBeenCalled(); - jest.useRealTimers(); + vi.useRealTimers(); }); test('Throws error when the end date is less than the start date', async () => { - jest.useFakeTimers(); - const setTimeoutSpy = jest.spyOn(global, 'setTimeout'); + const setTimeoutSpy = vi.spyOn(global, 'setTimeout'); const { getByText, queryByText, getByLabelText } = render( @@ -415,7 +428,7 @@ describe('Testing Advertisement Register Component', () => { nameEdit="Ad1" orgIdEdit="1" advertisementMediaEdit="" - setAfter={jest.fn()} + setAfter={vi.fn()} /> @@ -469,11 +482,10 @@ describe('Testing Advertisement Register Component', () => { 'End Date should be greater than or equal to Start Date', ); expect(setTimeoutSpy).toHaveBeenCalled(); - jest.useRealTimers(); + vi.useRealTimers(); }); test('AdvertismentRegister component loads correctly in edit mode', async () => { - jest.useFakeTimers(); render( @@ -487,7 +499,7 @@ describe('Testing Advertisement Register Component', () => { orgIdEdit="1" advertisementMediaEdit="google.com" formStatus="edit" - setAfter={jest.fn()} + setAfter={vi.fn()} /> @@ -497,11 +509,10 @@ describe('Testing Advertisement Register Component', () => { await waitFor(() => { expect(screen.getByTestId('editBtn')).toBeInTheDocument(); }); - jest.useRealTimers(); + vi.useRealTimers(); }); test('Opens and closes modals on button click', async () => { - jest.useFakeTimers(); const { getByText, queryByText } = render( @@ -514,7 +525,7 @@ describe('Testing Advertisement Register Component', () => { nameEdit="Advert1" orgIdEdit="1" advertisementMediaEdit="" - setAfter={jest.fn()} + setAfter={vi.fn()} /> @@ -529,11 +540,10 @@ describe('Testing Advertisement Register Component', () => { await waitFor(() => { expect(queryByText(translations.close)).not.toBeInTheDocument(); }); - jest.useRealTimers(); + vi.useRealTimers(); }); test('Throws error when the end date is less than the start date while editing the advertisement', async () => { - jest.useFakeTimers(); const { getByText, getByLabelText, queryByText } = render( @@ -548,7 +558,7 @@ describe('Testing Advertisement Register Component', () => { nameEdit="Advert1" orgIdEdit="1" advertisementMediaEdit="google.com" - setAfter={jest.fn()} + setAfter={vi.fn()} /> } @@ -596,11 +606,10 @@ describe('Testing Advertisement Register Component', () => { 'End Date should be greater than or equal to Start Date', ); }); - jest.useRealTimers(); + vi.useRealTimers(); }); test('Media preview renders correctly', async () => { - jest.useFakeTimers(); render( @@ -613,7 +622,7 @@ describe('Testing Advertisement Register Component', () => { nameEdit="Advert1" orgIdEdit="1" advertisementMediaEdit="test.mp4" - setAfter={jest.fn()} + setAfter={vi.fn()} /> @@ -637,5 +646,47 @@ describe('Testing Advertisement Register Component', () => { fireEvent.click(closeButton); expect(mediaPreview).not.toBeInTheDocument(); }); - jest.useRealTimers(); + vi.useRealTimers(); + + test('shows success toast on successful update', async () => { + const setAfterMock = vi.fn(); + + render( + + + + + + + + + , + ); + + const editButton = screen.getByTestId('editBtn'); + fireEvent.click(editButton); + + const nameInput = screen.getByLabelText('Enter name of Advertisement'); + fireEvent.change(nameInput, { target: { value: 'Updated Ad' } }); + + const saveButton = screen.getByTestId('addonupdate'); + fireEvent.click(saveButton); + + await waitFor(() => { + // Verify success toast was shown + expect(toast.success).toHaveBeenCalledWith( + 'Advertisement created successfully.', + ); + }); + }); }); diff --git a/src/components/AgendaCategory/AgendaCategoryContainer.test.tsx b/src/components/AgendaCategory/AgendaCategoryContainer.spec.tsx similarity index 98% rename from src/components/AgendaCategory/AgendaCategoryContainer.test.tsx rename to src/components/AgendaCategory/AgendaCategoryContainer.spec.tsx index d8e27c3cb2..74880558b4 100644 --- a/src/components/AgendaCategory/AgendaCategoryContainer.test.tsx +++ b/src/components/AgendaCategory/AgendaCategoryContainer.spec.tsx @@ -8,9 +8,7 @@ import { fireEvent, } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import 'jest-localstorage-mock'; import { MockedProvider } from '@apollo/client/testing'; -import 'jest-location-mock'; import { I18nextProvider } from 'react-i18next'; import { Provider } from 'react-redux'; import { BrowserRouter } from 'react-router-dom'; @@ -25,14 +23,15 @@ import { StaticMockLink } from 'utils/StaticMockLink'; import AgendaCategoryContainer from './AgendaCategoryContainer'; import { props, props2 } from './AgendaCategoryContainerProps'; import { MOCKS, MOCKS_ERROR_MUTATIONS } from './AgendaCategoryContainerMocks'; +import { vi, describe, test, expect } from 'vitest'; const link = new StaticMockLink(MOCKS, true); const link2 = new StaticMockLink(MOCKS_ERROR_MUTATIONS, true); -jest.mock('react-toastify', () => ({ +vi.mock('react-toastify', () => ({ toast: { - success: jest.fn(), - error: jest.fn(), + success: vi.fn(), + error: vi.fn(), }, })); diff --git a/src/components/AgendaCategory/AgendaCategoryContainerProps.ts b/src/components/AgendaCategory/AgendaCategoryContainerProps.ts index 5181eec153..3d8128128c 100644 --- a/src/components/AgendaCategory/AgendaCategoryContainerProps.ts +++ b/src/components/AgendaCategory/AgendaCategoryContainerProps.ts @@ -1,4 +1,5 @@ type AgendaCategoryConnectionType = 'Organization'; +import { vi } from 'vitest'; export const props = { agendaCategoryConnection: 'Organization' as AgendaCategoryConnectionType, @@ -24,11 +25,11 @@ export const props = { }, }, ], - agendaCategoryRefetch: jest.fn(), + agendaCategoryRefetch: vi.fn(), }; export const props2 = { agendaCategoryConnection: 'Organization' as AgendaCategoryConnectionType, agendaCategoryData: [], - agendaCategoryRefetch: jest.fn(), + agendaCategoryRefetch: vi.fn(), }; diff --git a/src/components/ChangeLanguageDropdown/ChangeLanguageDropdown.test.tsx b/src/components/ChangeLanguageDropdown/ChangeLanguageDropdown.spec.tsx similarity index 93% rename from src/components/ChangeLanguageDropdown/ChangeLanguageDropdown.test.tsx rename to src/components/ChangeLanguageDropdown/ChangeLanguageDropdown.spec.tsx index dc14f6ce17..cbaa628339 100644 --- a/src/components/ChangeLanguageDropdown/ChangeLanguageDropdown.test.tsx +++ b/src/components/ChangeLanguageDropdown/ChangeLanguageDropdown.spec.tsx @@ -11,6 +11,7 @@ import { MockedProvider } from '@apollo/react-testing'; import { UPDATE_USER_MUTATION } from 'GraphQl/Mutations/mutations'; import { StaticMockLink } from 'utils/StaticMockLink'; import useLocalStorage from 'utils/useLocalstorage'; +import { describe, expect, it } from 'vitest'; // import { Provider } from 'react-redux'; // import { store } from 'state/store'; const { setItem } = useLocalStorage(); @@ -52,7 +53,7 @@ const MOCKS = [ const link = new StaticMockLink(MOCKS, true); describe('Testing Change Language Dropdown', () => { - test('Component Should be rendered properly', async () => { + it('Component Should be rendered properly', async () => { const { getByTestId } = render( @@ -81,7 +82,7 @@ describe('Testing Change Language Dropdown', () => { }); }); - test('Component Should accept props properly', async () => { + it('Component Should accept props properly', async () => { const props = { parentContainerStyle: 'parentContainerStyle', btnStyle: 'btnStyle', @@ -103,7 +104,7 @@ describe('Testing Change Language Dropdown', () => { getByTestId('dropdown-btn-0').className.includes(props.btnTextStyle); }); - test('Testing when language cookie is not set', async () => { + it('Testing when language cookie is not set', async () => { Object.defineProperty(window.document, 'cookie', { writable: true, value: 'i18next=', @@ -121,7 +122,7 @@ describe('Testing Change Language Dropdown', () => { expect(cookies.get('i18next')).toBe(''); }); - test('Testing change language functionality', async () => { + it('Testing change language functionality', async () => { Object.defineProperty(window.document, 'cookie', { writable: true, value: 'i18next=sp', diff --git a/src/components/CheckIn/CheckInWrapper.module.css b/src/components/CheckIn/CheckInWrapper.module.css deleted file mode 100644 index f5f42546c3..0000000000 --- a/src/components/CheckIn/CheckInWrapper.module.css +++ /dev/null @@ -1,13 +0,0 @@ -button .iconWrapper { - width: 32px; - padding-right: 4px; - margin-right: 4px; - transform: translateY(4px); -} - -button .iconWrapperSm { - width: 32px; - display: flex; - justify-content: center; - align-items: center; -} diff --git a/src/components/CheckIn/CheckInWrapper.tsx b/src/components/CheckIn/CheckInWrapper.tsx index 859ae1f869..7b35ce1483 100644 --- a/src/components/CheckIn/CheckInWrapper.tsx +++ b/src/components/CheckIn/CheckInWrapper.tsx @@ -1,8 +1,7 @@ import React, { useState } from 'react'; import { CheckInModal } from './CheckInModal'; import { Button } from 'react-bootstrap'; -import IconComponent from 'components/IconComponent/IconComponent'; -import styles from './CheckInWrapper.module.css'; +import style from '../../style/app.module.css'; type PropType = { eventId: string; @@ -21,19 +20,19 @@ export const CheckInWrapper = ({ eventId }: PropType): JSX.Element => { return ( <> {showModal && ( diff --git a/src/components/CheckIn/tagTemplate.ts b/src/components/CheckIn/tagTemplate.ts index 4aa4475e02..a10dbca083 100644 --- a/src/components/CheckIn/tagTemplate.ts +++ b/src/components/CheckIn/tagTemplate.ts @@ -2,8 +2,9 @@ import { Template } from '@pdfme/common'; export const tagTemplate: Template = { schemas: [ - { - name: { + [ + { + name: 'name', type: 'text', position: { x: 14.91, y: 27.03 }, width: 58.55, @@ -14,9 +15,9 @@ export const tagTemplate: Template = { lineHeight: 1, fontName: 'Roboto', fontColor: '#08780b', - }, - }, - ], + } , + ], + ] as any, basePdf: 'data:application/pdf;base64,JVBERi0xLjQKJfbk/N8KMSAwIG9iago8PAovVHlwZSAvQ2F0YWxvZwovVmVyc2lvbiAvMS40Ci9QYWdlcyAyIDAgUgovU3RydWN0VHJlZVJvb3QgMyAwIFIKL01hcmtJbmZvIDQgMCBSCi9MYW5nIChlbikKL1ZpZXdlclByZWZlcmVuY2VzIDUgMCBSCj4+CmVuZG9iago2IDAgb2JqCjw8Ci9DcmVhdG9yIChDYW52YSkKL1Byb2R1Y2VyIChDYW52YSkKL0NyZWF0aW9uRGF0ZSAoRDoyMDIzMDYyMDA3MjgxMyswMCcwMCcpCi9Nb2REYXRlIChEOjIwMjMwNjIwMDcyODEzKzAwJzAwJykKL0tleXdvcmRzIChEQUZjMjhYSXViTSxCQUUycS01WEdhaykKL0F1dGhvciAoRXNoYWFuIEFnZ2Fyd2FsKQovVGl0bGUgKEJsYW5rIE5hbWUgVGFnIGluIEVtZXJhbGQgTWludCBHcmVlbiBBc3BpcmF0aW9uYWwgRWxlZ2FuY2UgU3R5bGUpCj4+CmVuZG9iagoyIDAgb2JqCjw8Ci9UeXBlIC9QYWdlcwovS2lkcyBbNyAwIFJdCi9Db3VudCAxCj4+CmVuZG9iagozIDAgb2JqCjw8Ci9UeXBlIC9TdHJ1Y3RUcmVlUm9vdAovUGFyZW50VHJlZSA4IDAgUgovUGFyZW50VHJlZU5leHRLZXkgMQovSyBbOSAwIFJdCi9JRFRyZWUgMTAgMCBSCj4+CmVuZG9iago0IDAgb2JqCjw8Ci9NYXJrZWQgdHJ1ZQovU3VzcGVjdHMgZmFsc2UKPj4KZW5kb2JqCjUgMCBvYmoKPDwKL0Rpc3BsYXlEb2NUaXRsZSB0cnVlCj4+CmVuZG9iago3IDAgb2JqCjw8Ci9UeXBlIC9QYWdlCi9SZXNvdXJjZXMgMTEgMCBSCi9NZWRpYUJveCBbMC4wIDcuOTIwMDAyNSAyNTIuMCAxNTEuOTJdCi9Db250ZW50cyAxMiAwIFIKL1N0cnVjdFBhcmVudHMgMAovUGFyZW50IDIgMCBSCi9UYWJzIC9TCi9CbGVlZEJveCBbMC4wIDcuOTIwMDAyNSAyNTIuMCAxNTEuOTJdCi9UcmltQm94IFswLjAgNy45MjAwMDI1IDI1Mi4wIDE1MS45Ml0KL0Nyb3BCb3ggWzAuMCA3LjkyMDAwMjUgMjUyLjAgMTUxLjkyXQovUm90YXRlIDAKL0Fubm90cyBbXQo+PgplbmRvYmoKOCAwIG9iago8PAovTGltaXRzIFswIDBdCi9OdW1zIFswIFsxMyAwIFIgMTQgMCBSIDE1IDAgUiAxNiAwIFIgMTcgMCBSIDE4IDAgUiAxOSAwIFJdCl0KPj4KZW5kb2JqCjkgMCBvYmoKPDwKL1R5cGUgL1N0cnVjdEVsZW0KL1MgL0RvY3VtZW50Ci9MYW5nIChlbikKL1AgMyAwIFIKL0sgWzIwIDAgUl0KL0lEIChub2RlMDAwMDE3MzgpCj4+CmVuZG9iagoxMCAwIG9iago8PAovTmFtZXMgWyhub2RlMDAwMDE3MzgpIDkgMCBSIChub2RlMDAwMDE3MzkpIDEzIDAgUiAobm9kZTAwMDAxNzQwKSAyMCAwIFIgKG5vZGUwMDAwMTc0MSkgMjEgMCBSIChub2RlMDAwMDE3NDIpIDIyIDAgUgoobm9kZTAwMDAxNzQzKSAyMyAwIFIgKG5vZGUwMDAwMTc0NCkgMjQgMCBSIChub2RlMDAwMDE3NDUpIDI1IDAgUiAobm9kZTAwMDAxNzQ2KSAyNiAwIFIgKG5vZGUwMDAwMTc0NykgMjcgMCBSCihub2RlMDAwMDE3NjEpIDI4IDAgUiAobm9kZTAwMDAxNzYyKSAyOSAwIFIgKG5vZGUwMDAwMTc2MykgMzAgMCBSIChub2RlMDAwMDE3NjQpIDMxIDAgUiAobm9kZTAwMDAxNzY1KSAzMiAwIFIKKG5vZGUwMDAwMTc2NikgMzMgMCBSIChub2RlMDAwMDE3NjcpIDE0IDAgUiAobm9kZTAwMDAxNzY4KSAzNCAwIFIgKG5vZGUwMDAwMTc2OSkgMzUgMCBSIChub2RlMDAwMDE3NzApIDM2IDAgUgoobm9kZTAwMDAxNzcxKSAxNSAwIFIgKG5vZGUwMDAwMTc3MikgMzcgMCBSIChub2RlMDAwMDE3NzMpIDM4IDAgUiAobm9kZTAwMDAxNzc0KSAzOSAwIFIgKG5vZGUwMDAwMTc3NSkgMTYgMCBSCihub2RlMDAwMDE3NzYpIDE3IDAgUiAobm9kZTAwMDAxNzc3KSA0MCAwIFIgKG5vZGUwMDAwMTc3OCkgMTggMCBSIChub2Rl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Tm9uU3RydWN0Ci9QIDQwIDAgUgovSyBbNTMgMCBSXQovSUQgKG5vZGUwMDAwMTc3OCkKPj4KZW5kb2JqCjE5IDAgb2JqCjw8Ci9UeXBlIC9TdHJ1Y3RFbGVtCi9TIC9Ob25TdHJ1Y3QKL1AgNDQgMCBSCi9LIFs1NCAwIFJdCi9JRCAobm9kZTAwMDAxNzgzKQo+PgplbmRvYmoKMjAgMCBvYmoKPDwKL1R5cGUgL1N0cnVjdEVsZW0KL1MgL0RpdgovUCA5IDAgUgovSyBbMjEgMCBSXQovSUQgKG5vZGUwMDAwMTc0MCkKPj4KZW5kb2JqCjIxIDAgb2JqCjw8Ci9UeXBlIC9TdHJ1Y3RFbGVtCi9TIC9EaXYKL1AgMjAgMCBSCi9LIFsyMiAwIFJdCi9JRCAobm9kZTAwMDAxNzQxKQo+PgplbmRvYmoKMjIgMCBvYmoKPDwKL1R5cGUgL1N0cnVjdEVsZW0KL1MgL0RpdgovUCAyMSAwIFIKL0sgWzIzIDAgUl0KL0lEIChub2RlMDAwMDE3NDIpCj4+CmVuZG9iagoyMyAwIG9iago8PAovVHlwZSAvU3RydWN0RWxlbQovUyAvRGl2Ci9QIDIyIDAgUgovSyBbMjQgMCBSXQovSUQgKG5vZGUwMDAwMTc0MykKPj4KZW5kb2JqCjI0IDAgb2JqCjw8Ci9UeXBlIC9TdHJ1Y3RFbGVtCi9TIC9EaXYKL1AgMjMgMCBSCi9LIFsyNSAwIFJdCi9JRCAobm9kZTAwMDAxNzQ0KQo+PgplbmRvYmoKMjUgMCBvYmoKPDwKL1R5cGUgL1N0cnVjdEVsZW0KL1MgL0RpdgovUCAyNCAwIFIKL0sgWzI2IDAgUl0KL0lEIChub2RlMDAwMDE3NDUpCj4+CmVuZG9iagoyNiAwIG9iago8PAovVHlwZSAvU3RydWN0RWxlbQovUyAvRGl2Ci9QIDI1IDAgUgovSyBbMjcgMCBSXQovSUQgKG5vZGUwMDAwMTc0NikKPj4KZW5kb2JqCjI3IDAgb2JqCjw8Ci9UeXBlIC9TdHJ1Y3RFbGVtCi9TIC9EaXYKL1AgMjYgMCBSCi9LIFsyOCAwIFIgMzEgMCBSIDM0IDAgUiAzNyAwIFIgNDEgMCBSXQovSUQgKG5vZGUwMDAwMTc0NykKPj4KZW5kb2JqCjI4IDAgb2JqCjw8Ci9UeXBlIC9TdHJ1Y3RFbGVtCi9TIC9EaXYKL1AgMjcgMCBSCi9LIFsyOSAwIFJdCi9JRCAobm9kZTAwMDAxNzYxKQo+PgplbmRvYmoKMjkgMCBvYmoKPDwKL1R5cGUgL1N0cnVjdEVsZW0KL1MgL0RpdgovUCAyOCAwIFIKL0sgWzMwIDAgUl0KL0lEIChub2RlMDAwMDE3NjIpCj4+CmVuZG9iagozMCAwIG9iago8PAovVHlwZSAvU3RydWN0RWxlbQovUyAvRGl2Ci9QIDI5IDAgUgovSyBbMTMgMCBSXQovSUQgKG5vZGUwMDAwMTc2MykKPj4KZW5kb2JqCjMxIDAgb2JqCjw8Ci9UeXBlIC9TdHJ1Y3RFbGVtCi9TIC9EaXYKL1AgMjcgMCBSCi9LIFszMiAwIFJdCi9JRCAobm9kZTAwMDAxNzY0KQo+PgplbmRvYmoKMzIgMCBvYmoKPDwKL1R5cGUgL1N0cnVjdEVsZW0KL1MgL0RpdgovUCAzMSAwIFIKL0sgWzMzIDAgUl0KL0lEIChub2RlMDAwMDE3NjUpCj4+CmVuZG9iagozMyAwIG9iago8PAovVHlwZSAvU3RydWN0RWxlbQovUyAvUAovUCAzMiAwIFIKL0sgWzE0IDAgUl0KL0lEIChub2RlMDAwMDE3NjYpCj4+CmVuZG9iagozNCAwIG9iago8PAovVHlwZSAvU3RydWN0RWxlbQovUyAvRGl2Ci9QIDI3IDAgUgovSyBbMzUgMCBSXQovSUQgKG5vZGUwMDAwMTc2OCkKPj4KZW5kb2JqCjM1IDAgb2JqCjw8Ci9UeXBlIC9TdHJ1Y3RFbGVtCi9TIC9EaXYKL1AgMzQgMCBSCi9LIFszNiAwIFJdCi9JRCAobm9kZTAwMDAxNzY5KQo+PgplbmRvYmoKMzYgMCBvYmoKPDwKL1R5cGUgL1N0cnVjdEVsZW0KL1MgL1AKL1AgMzUgMCBSCi9LIFsxNSAwIFJdCi9JRCAobm9kZTAwMDAxNzcwKQo+PgplbmRvYmoKMzcgMCBvYmoKPDwKL1R5cGUgL1N0cnVjdEVsZW0KL1MgL0RpdgovUCAyNyAwIFIKL0sgWzM4IDAgUl0KL0lEIChub2RlMDAwMDE3NzIpCj4+CmVuZG9iagozOCAwIG9iago8PAovVHlwZSAvU3RydWN0RWxlbQovUyAvRGl2Ci9QIDM3IDAgUgovSyBbMzkgMCBSIDQwIDAgUl0KL0lEIChub2RlMDAwMDE3NzMpCj4+CmVuZG9iagozOSAwIG9iago8PAovVHlwZSAvU3RydWN0RWxlbQovUyAvUAovUCAzOCAwIFIKL0sgWzE2IDAgUiAxNyAwIFJdCi9JRCAobm9kZTAwMDAxNzc0KQo+PgplbmRvYmoKNDAgMCBvYmoKPDwKL1R5cGUgL1N0cnVjdEVsZW0KL1MgL1AKL1AgMzggMCBSCi9LIFsxOCAwIFJdCi9JRCAobm9kZTAwMDAxNzc3KQo+PgplbmRvYmoKNDEgMCBvYmoKPDwKL1R5cGUgL1N0cnVjdEVsZW0KL1MgL0RpdgovUCAyNyAwIFIKL0sgWzQyIDAgUl0KL0lEIChub2RlMDAwMDE3NzkpCj4+CmVuZG9iago0MiAwIG9iago8PAovVHlwZSAvU3RydWN0RWxlbQovUyAvRGl2Ci9QIDQxIDAgUgovSyBbNDMgMCBSXQovSUQgKG5vZGUwMDAwMTc4MCkKPj4KZW5kb2JqCjQzIDAgb2JqCjw8Ci9UeXBlIC9TdHJ1Y3RFbGVtCi9TIC9EaXYKL1AgNDIgMCBSCi9LIFs0NCAwIFJdCi9JRCAobm9kZTAwMDAxNzgxKQo+PgplbmRvYmoKNDQgMCBvYmoKPDwKL1R5cGUgL1N0cnVjdEVsZW0KL1MgL1AKL1AgNDMgMCBSCi9LIFsxOSAwIFJdCi9JRCAobm9kZTAwMDAxNzgyKQo+PgplbmRvYmoKNDUgMCBvYmoKPDwKL0czIDU1IDAgUgovRzQgNTYgMCBSCj4+CmVuZG9iago0NiAwIG9iago8PAovTGVuZ3RoIDMwMTg4Ci9UeXBlIC9YT2JqZWN0Ci9TdWJ0eXBlIC9JbWFnZQovV2lkdGggMzQ3Ci9IZWlnaHQgMjMzCi9Db2xvclNwYWNlIC9EZXZpY2VSR0IKL1NNYXNrIDU3IDAgUgovQml0c1BlckNvbXBvbmVudCA4Ci9GaWx0ZXIgL0ZsYXRlRGVjb2RlCj4+CnN0cmVhbQ0KeJzsfQl4FNeVbgmpN6GuajDYY1tIIpPJm8dbJhkyb5LJTIaZJCaYRRvCwTEOjmNi42DAZjGLpNbGamOMgVgJGGMMxo3QvqKltQsBXuKQxFlmPEPGkziLd0BSd6vfuefcul29SOqWhBa7zldff6VWdS237vnv2Y8k6aSTTjrppJNOOumkk0466aSTTjrppJNOOumkk0466aSTTjrppJNOOumkk0466aSTTjrppJNOOumkk0466aSTTp9Y8sI2T/KulrzFkrdL8l6SvM2St0ryOvCzGb+B79+UvG9JXu94365OOul0Ywi42/uO5H0RMcEueZ3I+7Q51E1848Tjf4ew8I6ODDrp9Ikixt20XWbM7noqpq/U5GoyulqNrgajq9roLjW6qozueqO7xeRymnpeiWEHX8Lj6VeO8X4GnXTSaZSI0MDzG6P7kqmvxfJeia2n2NpXJ/fUTu2ps/bWxPXWwKe1p8rqqlP6auT3q5XfOqTeCwb3a0bPRQMTHi7rcoJOOn1CqP/fTe7XpvbWTXO1Wl1Oub9N6b+g9F9S+i+qn2Lngq3/kq2/W3E1w2b1tFh7GhV3t4VByhUdFnTSaRITCQZuQIMm2dMoA5szfm9VPM2Kp0XxtA68tSj9zUp/u+xlEKG4Wmye8xYmJ8AJ3x7vp9JJJ52GRbSgey6Y+hoVb6sMUOB2Iho4ZU9zOJviaVI8nSA82NxVcn9nDLMqvDneT6WTTjoNi7ydzBjoaTN5Wm2w0LtBSAA2B5WhWRly8ziVfic7mO20yG62Y2Tehzd0rUEnnSYlkTOxv8rEVICLCkGBh/E7Y3MPlxbwM2BzqgcLQGiyepqM3B2pA4JOOk1C4oBQbfK2MlOAp0nmS3+YEoIfIMTpgKCTTpOaSGXwtpu87QgIPgkhHEDg6OFpZsYHTyPs6CqDTjpNYmKOQq/U/4aJWRQv+Hg8HEDoV82PbL9N9oDK0GqiwGaddNJpMhKJ9/1vmdwtSv/5yADBI+SEFuZ/dDWC7mCi3AeddNJpMhK5HfsvmNxNtv7OSFUG1fbYIgMgMJWhycQUEB0QdNJpchLjX7vUX2nyAhR0RGhUVCUETytICIqn0drfpEsIOuk0ickHCCj2e5rURT98QGhGQAAwAZWh3Ui5kDrppNNkJKEyuJqm9bfKPAIBDYaRAUKn0tcgM7ejLiHopNOkJWFUZF4Gpw8NIpUQvOcVb1Ocp9ZIdVR00kmnyUi8msEFs9dpY+EELb74wwgAoU3p71Lc9db+ciNFOumkk06TkRgg2BEQmqZzQAjbrugHCN2K+5zcr0sIOuk0mcknIbTI/hJCJF6GNsXbrXgb5P5G3Yagk06TmHjZtLdUlaE1cgkBAYFlStbH9bfqXgaddJrExCun/ZvJ23gTS3hsJc9jJIDQJHswD8JdbfXogKCTTpOZOCD8yvRRXRyTENowUwlDESKUEGyuBpnnMuiAoJNOk5Oo6JmnxOQttjIoaCcbghyB25EkhIuKq07xtOmAoNMkI2ZUPyR5a/VyoIy82diF4Uist3o6Uxk6IjcqIiB4LyneBmu/LiHoNKnIbpecdsQEKgd65dPeUMBL2w7F65jpBwgRSggMEKrlfqee/qzTZCIABNiuPGfoqzG5f2pkmEAdyuo/3bBQfou37hZWej2ShEc/G8JFpa9S8TSYWIGUtvF+Hp10Goq8KBj85ED0mw7LB0WyqyXuWvX0vs5Y9wUjb1VWi+3JPpWw4C23eStuo4DDYbodL9l6q6a56xAQnOP9PDrpNBQxu4Fdcr9kuFZtcXUrni7F3a30OpXrTpuryeJujiHdgZUU+/TZFrxnzaz2civWSGkapoTQW2F11eqAoNMNI+BK5zzpcoZUu17qvF9yZLBP2Idv4PsIedZ7AuuNlxi9Ttn7uq2/nenLzMvWzVjgWp2lp8ncWxfDbQvvsm4jnx5Y8J7CUqutsg8QIpUQLio9JYqrxqgDgk6jTMCGl+dIV+LZ5kW1H5Z2QAPavGgFoGMOrWYb/BnOWcvYRO2vNbGcvg7F3SwzaxgLxUFLWhdIC/Ifz8VeazP3UrcR76eoejCDwWNSf7uVAULjMCMVr5Va+ioMDBBqx/t5dPokERm+Ge9LMU1fNJ9bbC2+V675lnImTa5dbi1ZGVeTHOf4mqlhLjsG0GCeUxpKXGCsfQ4XQYfR7aTwG3UyEyw0M2nZ3YGwcDb2Wqe5902DryHyJx0WGCBUS5FWYg8AhKtnY3tLsOtr2Xg/j06fDEIoiP3tLUrrV5Tuv5Pr/kU6Nm/G0W/En1yUWJw66/TChNKM20+n3vbcHbfsveOW08nTX7x7ak6miiCDnpiaBaCs0e+09rdQ1j9Xlj3UfqgFZ3i34mpVepvl3xfHfdwd2/OWUQsLn1Rk8J5GTGiz+QAhIhsCGh8+LLa6ivX0Z51GiViIALMM3NL6pcSz82eVpyaWpc4qTU0oSU4sT02qTEusTEuqTE8sT0soSUkoTYH/JlakxjuSb87fZnl0L1coBiAqFMY2Z5IvEs8ZBAsUstuCybytcm+78k7D1Gvdlt6fmPp+GfMJhgXvYclbKHk7sHlT+BKCaNTSong7FW/9NE8lGmGOjPfz6PQJILQGxFUsSHrpm0nV6bMbliXVpCdWpAEaJJSlJpSmJpbSZ0oCg4I0+C87pm7prOLFclHaEIBAaOBlQXRuvgLK/aJDmTMAFpAp2lCJaFGutVqvVVh6z1tc/6YqEZ+scCb2RKfY+Hg6ZB8gNIdRIMXJG0HCiHnbZW+FKobpNgSdRk6184GjlfI7QQAAfmcgALIBsD+AQBmAgNgQE1BIgAOSapfCAfLJ5QwNjtw/0Lm1gOCb8MT7TtFwxCczwKe7Gc1rHYqnQ3G3KT0t1utV5r43zK7/MnzCwpk4ygEgtCkRAoKipkGxvOnLDvVUl8f7kXT6BFDnl0BfUM59fVYJcj1jfPwsSyHxQN3ovyn0L9AjZpWlysfuYxrHvvUDndsPEJwaCSFAMHaqqrFaQIwpEQALXQowi7tdBmmhp1rpbTe7umIonOkdp/RWt+SczI42zsUOf5VhKEDwa+yIlVU+wSqVTuNAAAggIZz7WlLxksSKVGYrQEMBSQgcHEBrYPsp6r9SEqvTAR+UiADBX0IIYSjDZoW+JmUUrg+/6lQ8rYrrPAtn+qDC1FNvcmE4E6DBK07pslNyTE5pQUgI3nZbBIDQrKgF29mRbvh81eh16ICg0yjRkSUMEF5ckliyGAEhlVsO6TNgI0AA5aImPZEBwjKmMuy7Z6Bz+wDBKZHZcMicPnUF1MACOuhZgnCr7OqUe5vkD2pN7zab3m1nsABoUPuS5DwVZljEBCIOCKzxawRGxQAJwQ079XrrZ51Gj/bNl+zzlKMLE4qSmS2xUgWEsoEBoTSF2RCYyrAMfjsYIPi8DPOwcqCs0X/DUpNFLWIGCy0Y4thO4UzW90tMvz9n+l0TUyIADS5PtsRqXom91ejtikRC0AJCKwCCVe8Fr9NoEgKCfGThrKL0xMqhAKFMBYS6pfGlqfILKxgrHlo90LnFRO1voN7lspB4hzSdaSe/sK6LcCYP6BGd8vUm69ullv8os/zHWZ/YPFlYg4/MFRPgW3+3iFSMEBBgQJpNei94nUaNVJUh4cwiBAQWbzCEyoCAwLwMZ1ewMgeOgQEB5vwvMZeh0+RuUnihMCd3MkaACaFggZ3hvOJpswIs/PHsjGutsb1v+EU5TnAGEYDg7bBpchnCBAQ8slVxN9n0XvA6jSZhIIFyJjUJcKAqLbEqdTBA8EkIGYmlqUpNGgtqcg4YhwDkbUADQqMJhFtmHiT3otMXkDB8WCD3ZTMur23Kdaf8h6qpPW/Euv7LNClggXdzu2TyACB0hRupqCpTiKjtICEong69XJJOo0cICPKZtFlnkhOq0gEThrQhwGfSuYxZpclKzXwEhHmDnJ4BwiUGCK4mW/8lHpfYL2Ah7LphPl4IgAVyRmBTM3er7bpz2tVzM3p+OrX3v9Tg505pYob18n6vNSZvq6ZAypDWFfHgLQQIMIx662edRo8IEF5Ove1McmJ1elLV4CpDKnoZUpPqMxLKlsi1YQACBhF56ox9LWg6a1fcFKvMZ7if5TASw4J6vBoR7W7EkmJdirtN/rh52rv1067/NLbvpzzInyVdVk4sWOApHsdNzO3Yyc2JQ0sIzbIPEDoUF+zU6W2bdBo9IkBwpCS9dGdidRpsLFJxAEBIUN2OSQ0ZCRVpyrlvMDRo+8ogp+f86JDcFyy9jYrnVYWp/60YctAk+9Y7WhnDExU0m8odKrO4McrR26H0tSu9rdPer1auV1t6qwxUlIl9dk+UugFcQqg2AYIxp6oKCINjgp+EAIDQZPVU6plNOo0ecQkhZVbRnUk16bBxCSG0ypCqAsKyhPJk5dwd0qW5gwOCRDMf9WXXr83Xm2RXl+w5zwyMzMZIoUfq0h+RYWFA80ITwkInO7+708YSq2usvfUWj9PAbsOpbuPNQdyGcBFVhtawHbJaQOhUXI2yu8KkA4JOo0ZcQki9HYQEAIRqFRBKgy2KfoDAjIphSAhEwr53/Wemt0psrH5al83TqVZPapQDzAJCNh4JLLD9DmZvdHUprma512l1tVtcXQZetO0lnls0XuTrCN9iFdrTkGYEX2BGC6vE2Ntk85wzUWlKncaZqNQYC9Vbz3zxAyf9TWhSbQjxjuSkmqUaQBhCQgARIhyjoiCOCZek668YP74w9Q/OmVebbH0XERa6MBAxABacIp45Qljwj3JkP29j52clF9qUDxusvU6Lq8XADHH28ewTQdf1dJtcTiEeDN2rxQcIVImxAVQwEztV5zg8gk5+RAVCqMI4sNXl1dxONLmIAOF0SuLphUl1SxNVQEgYREIoTUmqB0BYooRhVBTE0OBtrJqIsNBTavrorPzWsaRrTVbXBZu7g8MCRtqosgFxSiSeCA0s+BZcBgtUtK2TfJTW9+uUvhqzu0yt5XiZx1CNJVFHeAYIjYomXmsIrcFnVGzF1s+N1n4dEMadEAri3r51Wt18W3Ga8vx98okVlvP3ISDMC6OS0EQiBATrqZT4k4sAEJKqlw6qMqhGxfplCRWpSu03wwcEIg4Lbbxpy7UGyzvOWIdD+lN9nKtbBljwnkdPRKMaY9CsMTyGLTAIngoRztTCFlbMo7Rec1r6zptdDTE84WJs4xa4hHDe5G2y8o7wYaQzaFUG3gu+xURlq3UaN3r7VmB528Uvfq5iMYvuq0iLL02+pfVuufFu0+t3qF15wqkxNgHoCKurHHfsrpufW5hUm5FYszSheEAJIUGoDAAIZelK7YJIAYHI19YN83/fPhH9X2eM16tir7bG9jKrAqtC7O1A86CTxyD1DwcWfNVXNJoIQg1WZ+rvsPW2Wa+2x7razO5XY8Y4nIn3e/13U18DcLcWEAYznqjWElQZLiieevhTlxDGm978nHRprq39H+JLUma3fCupKj3p3FJWWKwsVTqwwFb9j9affJajAQjVYRcoHh/at55VTPrhvbeevJM9xaCA4AtdBkAoSZdrIpYQtMRl9cu8d0PfcUPvWXNPeez7lcrVVpbbCBMe/XHcARECFsIIXRAA4v9zLDfUirDQqbja5WudFtebJvfvVFioxbu6kaZ7upAbAMFpI40mLEBo1gDCRcXdYPXogDDu1PCvwAVy1YKEUqwdhCWGYCcRtsrUpMr0mYfm2dq/YLn0N7xAMWDCsZUTFBbQBiLvfyD++KKk+owkFRBCqwxq6HLiuYxZ5YuVyoUMDQD0RkY8I1Ldek6brnbEveVM+rhN7muVPZdYSB4lL3DfgQYWtGVVwuAjDbtRgaYmrF183ubpYLnV19rMrl+Y3a8YeBtKCme6MXELQkLodU5jEgI8aVM4NgQNIFxS3OdkT53uZRhvKsOEoNJkpm5Xp6uaNcJC3VJWK6A6HZSI6S8usB3PiH0hnXEN8F35IunU8gkHCwQI+x5IOnanFhBCSwgCEOAxy1MUHAc2GqNEHBMO8Z6w19+I7btgudZmdYESccnGEp9bQ8FCGN66AFVCiyQsnKkJw5mYJ1R2nZd7GhV3ucXjMPBYpjbJ2zH6sMDjELqYDaG/RXSEH8Lf6g8INned4inT4xDGmyghqIhVIGSAQNXGqOwYC/MDaYFhAqjks84uAWlBKULDwqW5wHqxFzJmXp4zgWwLBAhPPnBb4eKkcxlDuR199RASy9KUs+mDF1kdHvFApivcwuDujulrt3zslHtZMVJYylU9wuempPjnCGAhhB4BbIjVmVgUcZfMSi7UWV0NFlcjOiidmBMxqlGO5GXwAiBUogek3WcwjEBCqLN6akx66PI4kwoIiSgh+AqLkc0NYKEYa5jXsFwhUMwTziy8zb44tmIxzIA52AlFevf/MHyYCISAEPfEqoTDCxPrMhKrBgtMStBUTEpggDBE1eWREC+zjD5KYEP3azF9nZZrTda+TtnTbaPQAp7CwEFAE7oQZlpEMCyo4Uwsoum84uq0fVQb19dqdnWqUY5vjlqzOZIQvB1mVke9VdMRPiJAqJc9jXpy03iTkBBKUgkQqFA5igopgncYLFSkAosB+8CSGu9IltYskLsWSd4MhgZvpkpvrRy2RW40nwUA4XjqjKNLQEJIDFNCqE6fVbZErl/OOz/eGOI+SgELlyT3pRhXi6WvydbbZvN0y9RIvb+Zkv5Uy0CkudU+cNDAQjPK8B2sB6Wr3fpRQ5yr3eLuNIhmc6PzdF7J8xuLt3q6hxIew8iADgAEV4PiadPTn8ebCBDOgoSQKiQEXoOUqpdrYQGUiMpU1u6kbmlCzdLEM0tmtC6K+9lSJi8CGrzyBdYtcRyjHFF/sVZ/hakM9aAy8GzHhKBcBqqzymGhKj0eAKEbQy+uDFhkdVSIw8LbvDYjq67QZnDVxYKm34OWQIpCBHZ2+7KltD6FMGFB5ElpYKGFRT5jOJPN1a70NFg9l7EGy3ujAAscEH5tvnpuhqfN5gOEsCUE76s2b51aIEUHhHEkFRASKqjcKO9iEGB588FCCbJSBcICrMJVabMAFpozYkv+gbdTJKvCuBgW8LrmVxM+e2DBbMCrQdKfS3n5ZXjYhIq0xOK75NYHGSBcHgszKWOft3DzqumTPzP0XbZ8XDW1t1NmegQFNXFY8GUO8ijoiCwMTk00FMFCKwZSorTAete+PoWElpE+EQDCr4ze2ljWADq8GikBRsW+aviJUa+YNM6k9TLUaGwIWkxQo3xFvRGSH5gdEmChfllCeWpiMbPPxxb+nencV8YtnElc0T4vAcBtyJqKHPpSZpWkKGcxxMIxdn4THrrQiRsylOuioafJ8ufq6VfbWQ84TxfAgs3XONK32gpYCMOv5wwqwwI4Q+FMryjuBplJ6YVMbR+JbZ8HPPzK4C2bynSfrrDqrPoCk1oU70XFWx/bT0VW20ZvlD9lNIVRNGxB30cDwf+GPgXGIShVCxJL0N6uBYQAUYF3N1DdEFytYH8mVaUngrRQnppQjOFMFf8U9+pfcd68nMFqFY6Ng1IDQayEWsXAZdiFZoTqw2fKM2xnM3gex5iTX1ShXeppMv+70wL3cq1tGsj2LL36vMJtjKLgGPGRMDUMvRAHwUITxQfKPedkz1nDCJ19/OZ/afRWx3nbrQwQwqizKlyTHBDq4rydWO1Bb9s0LIqKYvxuNptjjOZog9lgNBvMZoMB9i0GE6NoQ8zQZwEWvjRXKU5NAr5uWObrgsp9Df4MJdqi8eZHXGygXyXVYDgTRjnesvcOm/NvLb/8EpviYxbOpKZoWU/PZ2HYZQIQBsxswt4NqbPLlistmLgB+s44kbAqUGrSv70e89tXjH1vxr7fpoAS0d+FxUub1TJrzYGhC2EYFvinXwYBsGGDMvJwIA4Ir5u8Fdb+TllTZzU8lYHlMti8VVbv61gs7p3RG9ZPE0VNmQLsH2MyASAYTLDDMIF9mhhEwE6MJSZqSCEB44vidn09sSidAQLjo5QgWFBje/yaH6X6jqE/CRZqmXk/qZLlREw/sOCmohVxFSsonGla4apbb2g4E5cQ7ErVfUkVqQkaSSDY58g7PCIgxDsybL/43xMkXyMgDcHVbehxWv7cGtfXinmU57FHTKsfLPT7DAVDw4K24TKVJeEBwyMofs7vtjXW62BZXSwNMxwbglZlOG/z1imeV4zkotVpeBTN2d8EsGAQn0aTgcAhxgSqwxCnAPacx9yF049+IaF4yeymZUz+r2B90gNgwQ8TtOZ6rXkBYAGDA5MwDGB21dJZRSmfKfz6zBNLb345ZS6GM8m1829UOBNxtCNDqV8+C/u6DigkiCCEClaI9bPV357++t9OEEAg8oom0Rhh2Ndk6Gm2vN+i9LbYPN1Us90HC8JsiOAwpJlR8ZV/bFXcjTYGCKPhgvSeUbwlNp6XHRYg+FQG0DKuNVpcTqOe7TgSMhrNKB6YDCgkcBxAQGDfG8zB5oXQlM14YVbnVz5zOjWhMpUFLVdhI/UKxuB+0kKgG0IVGPiyq+rmxUwax4QIjHJ0LPpMYUa8Y+msM0t5ONM7fyeNOizA2Sr/gZVEOJnM+r0KvWDgVEfWu6E0+baKxX/R9Y8TChCIGHdUYhqCgzkC+s4Ze+stV9tjsfCCIso09QtnRBj1FvxqLLTILvjt+dHpqOgtnuZ1TPOGLyEIAymTVeSPquNcTTogjIhizKQsmDga8I0rEQazYWgJgcgr3fr2rbCCJ1Z/M74sefbZNJClGSyABlGdzrqmamAhMdi2wGEhlfQIn3mhmC3BTIOArS4joTgVWG96953MsMBhYfgJhqHpBDZvenlhQgkZDNVezwGwIGynAAhlICEsmHFiwCZu406MR+qxdzya/lznYq43mf+7wvpxi811UQZY8Hbxoo5D2vH6hetBbbEKmCDEg5ECwsszvGUzvB1yJIDADZ5w/yBdeH5q0lWGYVPUlOgYISGoaICWBBPZE4xG45ToMBwNSIAGt3Z8GdgTVvCZpffMrFg+u2wJW/FB+AdYgLWewUJyor+0kBhCWkj1My+UYDgT67SYhkmUGbB2w1XMv7yDhzMBJrBKLKNkWHAwhFGKvjmrlEVcD6QyqCJNMrV+BkVm+o+/Nzo3cMNI1HwmaeH6ecPVdgtLr262ui8png5rvy8kKTwDI+oO3lbFW/3ZUQIE2fvCLcz+eSG8Xi0+z6nsbVa8dbdM8H40E5wAD2KMlhiuL2hMB6rKEM0AITwJgcg5b2blnTLl/Tntcafvn3l6OeuCVJ7CygtUpTHLALMtJPvceX65DymDwgKch+kgLO2oKn12aTILIjr/VTIDotVRkkaOCljTQK6Zn1icOgggJGoBoTT55hfvmXCZmwMQs/79BA2AWH7heuvUq07r78struY47IYWaVcIBITLoyQhnGDeCta8KUxA0IRTepqt3mr1NnS347AImJ1kAxUKTCQhxKg70SZTVESAQARoULlaOrGe/PJxr35hxsW/jXd8Ob6CBSokVS+lCiqUC5mgcn0CKeYqFISwN6p+TBbFBLBQv4xFOZanSBkZsfWLzTV3MjRwjjicqe0rDBDqvhFPHeFFElNooyJJCKBTJA/3emNEAa6HP3fEvNdg7OuO/dipsHzqizx9IFxfg9MfEEZLQnhW8h6TvOFLCCIwu0VxOxVfHQkdEIZFHBCMZEPQmBFIZQCZwWweDiAQUfBA7XxiT/nnn5lW87+BZ3FVTUtAmwBz4pekiAilxDLh6RNRTEJUUBMKSlVYKEtNqmJRjqBNJFWz1GPbi/OsVZ9Tw5nm8MILkVL130uX5soV/zqrbAlLuygfIDapVJUQakAVSoZ7G+Yo3XgKgILrb5n/6LQ47VJP+7TeVsVzAaGgRVMSIfxuCLg0u52yttn0iO7zJSy16udlGLLfK7dtssaOp1Tbpg4IwyIGCGbB/kFGRbMZAAHUihFdwz8I2bT3priDSTcd/7vbSpNnYWukJAxQ9IkKIlBBa3gMggUezoSHsQYKzEyRDiBzywt33HT+7+Lems1LQDvnRSoqTHPMlQrn3lI0LxE9CAMHK6qAgLaRWwq2T7SqkTywuVat1uiV+n4bc/0Ny7v10z5qU3pbZE83VkGhgmxqTbawko61bscWlkrZ32UUze5HdMNeqooQUQNo0QteLYYwGW0IMTESCwyOnhI13PV3NGhKTEx0MCAYNTKDwTJSQBBE7JLN96f+9MvTm++8+dSSpCqWzpCI6QwJ5WmJpQGhCxr9PXQ4kxrlWMuqM4HMMKsy7TOOjBmVC6e2f4mLCpGEE3+mcO7cwrlJJ+YlnMWasUNJCKxqXHnq9KP3s6fKzh6dsRoZ8dSnX6qpT3ap76Kx5w3L1bLYD1usfSzHQfZ2YXEkWuU1AcmqpS4cfYFxoheb2nvaMQ7hF6MBCBkgIUzznidPaFh2DB4OATv1ppHj0rgQk8PNzGRnkSwxUYbxggWQEIwWCzcgqM5HNV4RvzTERk0ZJUAQBEzjoMYu9tiXvnfTyeXAv2zBrUqbfS5DLMpqDzU1OEHVIPzCAEp9Fj8BC0lYnSm+PCXh5MJpjfN40ZKwMeEzJ/+eAcLL/5pUvARONaSEwEq+lKcozz6IcDfOgMCToz9Uw5gdkqsmpqfU8kGtfK3V5upkhde8uPiKmgl+XafDKJugDWCmcCDWUREkBLRVjvTmvWwUPR2a5KYwJYQ2BDcnAsJrkw8QgGIMJlqLDcB4kjlmyjjAAgMEozFGmBONYgc/DSbJaJSGbUMYhOB9vTVPcvJsoJkn7rnl+ZXxjozZuOAmkZuyMo07HLVpEVpYID2izN+8QDVRa1j8Q1LDsttPL7a8eDfvJxWePWFaOTMq3lLyjYSzzGCIcUehEh5FU4ZzGbPL05Ti9PHKbCIKLJ/ildw/iemtN79XO9XVZuvrUtytQeVTtFWVwvQpaEsqYbSz9xWbu0HubzeSejLSR/BK/S1GT2e46c++XK12AASrp2USpzrGaE36bN/EpIWxhQWW0chjkFQhAWWDGNXSKMEWZqTiMIisjsiqc7BU0fTi74L+PuvskqRaVlWJuSmx/7IPFijKUeOJCG1eoOBnVkc9nZd6hu3U8rDuqnwR8zKULI7ngBBaQvA1ZTjHHKnK8/exS+y7sdVRQhJTxb4jeUt8BdZcP4/pvWS5Vg/agdyHEUdYkUxt5SagIOyqCKF7OjThOt4pe502rvuPrLQpr7N6yeShom0RuB3Z8UxCaJvExRCMRnNMgKcPdHmLBWDBMMUYPSawAICAqY4m/2BFDhHwKUlmKWq0VYYAsttnHlodu2cj7My9tAomltz0beDiBAYLzEfJYAG7MGuVCE2S9cBxC4AJjctmnVkkn7ibOQ5e+lZY93NyuVS4Sn45PaF4cRLVSBlcQqjPSAAJ4SWUQ47cqPppAxEzEdyPmIC6M4OCFssHNXJvu+y5YGPVk0QJVuKgCAuk+IqraOqvsnbS7QwN3N3KtTrZc9nEUyxHFohxGc0d71SbWMWkNrWVWzhehmYm/DDJp3USAwKPDTaafII6Z0lQ6o1WyWqIMt7oe5gSExMjAMGoCVb0AUKsJI2JxGK333rqsbjyLQgLc2FmmC98dUb1v9xeDPL/sgRYqavSqSi6WtMsVIpBqS/SOAGLMrHijWdBYFgCEkhiSXihAiogxJ9ZTFJKyKJJCVpAqESV4YZVWA1JIgKHzIZ/+HF030XLh+dsve1WgAIP9ZdvpnVWVGOOoNMT/cTHcc28/hKTN84r7k6l77xytT62z2niNswRV2AGQLAjIHhbmZHQJ5aECQiNVgYIzskNCDFGnlXEJQRt9rHROE2aFg0LdFTUDboHAASDxaIigJ+jgaQXPOpGXT0EMVhYHvfGIqpZBJ+W179wU/WihKK0xOqlSRj/zFZtNdDR55oMZlVWqQCrQJcs+dyp5LmFqz7/XHiAgCqDUrw4oShZCwiBZRU1jR2TQGUoGSNACAwq6Lb89pQZ+Oijljh3i9VzkZnXAvo1eLRMHYnNsF9TMcndpHZYBijoUj5umQrP+scz2MNllDrDXkaV4Z0LJm8LiyvQ3nCwMKOt2cL2QVxxWie1ysCrEGiseWrWoYkXJcAQYoAFCQsX3AhYiAJAiI0lYYC7GIzBgDDmxBwQSdJbNh5L4JXiXv3bmxq/fnPZktm1rHfk7PoMzqeh8qkTtYBQlT6rNPWvixbMLZw71/H1sK5OoctVdySdWZRUl87CG0JVYvdJCA3LEqpuuIQQUPyEBRW8Zbx6Me73dbdcb7X1AWtcULztLL4odP/HoUqlCaYL0eXNyasNeLqUvnaltysO7ue5LYauk1P4XY1SyDa3IVwwuZttvvYxquWTB0s089JPQvHp9wGC7OlEQHh1dO5njEm145mEbKAJHjZplYhoLFMgMf6dEhU9qrAQHS0ZjVqVwQ8QTOMECERezptS/depRsGMkoWzyhcmViyZXZEOzM4CmHnxpcDcZBUQUrFYQfJfV/0LqCFz68Nq/TATL3pr5R23nV2SyPIvREf4AVSGhmWJ5WmzEBDibwAgcL3gCg/acTuj+/7D2Psz84cN1p52ua/Z6qFWsGr64TBawYaukNas9oHFCmzurmk9F62uN6Y2vGB87uloFvM12h5/0n08lSAhyNoC8oPfOQ+H6MadTqzTMgkBgVvzzGROxE9EBmJDkhBImxDqfDRGCklxUpQhevjhxAEUAhBMPs8jsyGMH7GEiDulE/eQO2/quXm3nFkY7/jy7MpkWLVZeECZWLhDRgiogFCS8j9q5gMgfL75q+Fcdo5z3jznvL+suCP+pWRmH/ABQggJYRYCwqzKpf+rJD3DkfGlUQUEJo0f4151FlFwdkpfueF6Rew1p+XjNsWNZj1KQHBzXtZ0bgofCoJ7v6qd4gFqPN3K9Talt326+2Ks+1WT+8+s/StAgf0GFLmkNIT+QkNfg0yeEXezWh62KdQmule3KZ5Xld6GaZ5zMUxC2DPKNzY2BPo7MGM0ZRrSxm2MPtlArNrcD0gVjQAWLJYog2EUYAHOYDJF+xsV1fRn9udoPGjkRIJB7XxMmZw39WTGTSeWxR+Zn1CRPhsdkcCnvnghTbRSSJUhoTj1czXJcxwZn28Oy4bAAaH8joSTC9mFBgUEsiEkVqf9r5KlowgIvpaOqob+UUM07L9bF3e9U+ZQ0EF9mlSNQM0CjqSro4oAamASQUE/9Um5IF9vUa7AFWtj3eVGbbbCDQr7IUDwHIxhO53Y3r2TVXtj1lF0l1APKd5Jqg3vswX3X1WuN1tFXJN31Q25vbGgKVMAFkwgAMQYYzQWRYPP0iiKm6k+CC0smM1TYgzhVjQKSRgwiVCgjUZQ91kiw9jGSlH2NEABYsLUigU3lS6+/UXqK53BEh9q07Eys9aoGKwv+ACB9VQqTv6rsoy5l1b9n6q7w7kFrjKUfyP+1BItIAxUQg3jEAB2RkdloAKhvNEh8uDv66OddunDWqvrguzuwH6LHWji0zZu1kYbRgAFfl3b2A6ty91KX6vt7erYj1vN1zuwPZMdEyIuj0UEILtWkcFTZb7WYL3WYu1pll1diuei0ndB6T2PWzfb91y0uWG/RelplT9okvt+bfa8F81GLHsi5ZMMi6YgLDDuNpi47qBGJqixxCYhMPAUAw4LFtgAT9jPw6mXHkRReF1NRRQ/3SEm0mIIIyFgw450qW0R1VeZUXnnjKoFs04vSChPo5ZqrDVkGUKBqiCEcjv6lH3udqxbmng2OTK3I8KRXLwg4cUF2EdmAAlBvQcWqViWOnKjIg8rels1GnilPzSaCwulq43WXoz/ISggadkn3kfaqS3YckgnbGc2SWCxvg7lvXNTr7Wae0QDx2LJe2rULIdhDQXaTl0dhr7XjB9dtLzqiPtjjfU/HbG/Pi3D9quX5CtFyp8qrD89NRUO/vmPjH0XDJMxf2EImhINWoAUGxttMBiNfrVKYoy+GEKNR4BbGGB/itGIeBJ2uTOVoqawSKgYEYlk8gcEgynqxoUpCiJbAUkFl1bNrEmNL1+cVLQYGJ8WaBAMMEs62Rd0VBpsPQhqG1ecnFiWltR0V7xj0Ywz86XCuTOPh+dlwOousmNR/MlFQ0sICAgJFcm2EQQm+YUaFrLP939uvuyYc80p93QwmyEJzIFQEJ4bcXAoYCFGWFMRVuHeTuX9RqWn2dLXpkLBaLd4DndAnNhx/hIHxo9+FfNui+H9WuO7tcY/1ZnerTW9X2t6r9z4k93RXI66NGo9ZyccRXODYTRK7CTMq0yqyvMmkxYfaHGHDWAhNjYWYCGCq7EoRIvBoLUhmAQgRN9oQKD0ZNVWEFefPLM2jVVdYwpCOoYNo1QQCAWBreISAqCghDWWBWVhNiuslDaz6avsKl2fA0wI665woZdPpSS+sDjRDxAGSG6qWzq7Kl05PpzQZa72Onyhhh++brhUIb3vtPa1sUoFnjZZQEFAo8bwbQX9wVDQrEJBO0CBrbfL9pEzrqfC4mowcM/mOEGBb2SAx1+XvL9Th4ju6pK6OXnqFgOEo1gu8pPdzDEqirohxMSYozUFzVQ7g1/YQIxPVOCwIFmtYWYkRUUZYqVYg3oSjUCCfg3jCKqjDE5e1auIYQZTW746ozxldgmroQr8xQKVy9MSygKruAfXZfVrDKc2k02qwjyIyvTZdWmfKfy69TWszPxWUrj3Rq0qTybfDipDgFGRF2FIETnaBAhwOeVHD0Vqec/Olhyqpe7qCcNHZ8y/rp7+ETA+izoWrRN8VYxUT1xE7gNuXtAUP5E9FHjcqXg6ZNd5W0+bta/b4u6KEVYLEg8mAnE0IKNKG4s4orgjtl8reU+g6XVyFK4bFYqizkpGo9FXBplzrp9z0F+DMJO7MCqMQKaoaIM0darG7SiiILjhYpRtCP7FUsxFX48rnj+jZlG8g/kHkzAKUe2FNEjldl4SQS28lsIPACjAKqwMEKrSbzu1eEbXXOtvEiKuqIaAYD2ZnHTsTupFK3IoQkoIIMnEl6fIkQOChBO+97+NH/3U8ssK6/VWhVcKalEDb3yWw+F5ElUQUFUMpnFQtGGb7OqwXW9R+qqnultNXEqBzwtjZDmMlALiMz+BFoOwCYUFbnWkVdvXVsmHBmY/wwJ8WgxDd1yS0MsQF6f1YmhTHUfTywBcpimnpvzir6dX/5Nkn5dUifXVa7GDG2f8wJZPfjwY1F+eL9YVqbPr0PZYlfrZ6gW3tvyz/PO/Hma72M4vwd1aWu/8i+fQqFjJBZWBNhYMWbFUPhtu+jNN5uefMFT/yHTxdOyfW6Zfbba62hQvJiDwHs2+pJ4IcpF8UNAcAAVqiNEFJnW4OuRrLZa+V2Ndv4h1Nxu4B+HKxIICr+gyc0UNygrYqBjUEdRuJtKdjxlFRcdEYfGiGBZKZOF1kk08hkFT28QUweJOigmTQMxaM6YqexhH5NOUJC6rg2pw4h5KQ57a/qUZXV9MOLkw8RzLCUJ5Oz1Ry18qp/unOYeAAl7FvSyV2QqwksmMsiU31S6Ydu6OETWP5s3o7TfVf4tlYVejo7OcGTa1Gyk1eN2UhKI0uWYBV4KGHBKczz2vG/7zpNLTgVLBBRuvO8T1gsCui2GZC/wNBb6OzyRvdCu9bXJvh3Lt/PS+16b2/cLU9x8xIvB4okndTFwRIKBqVR2l0f/dZfzP9tj/bjf+ui1aJFzzA66MNPl68hLry2wwGgxmjUfALOIWRFhj+Op/tCiRZDT7ZAw4c3TYbZuCiaAANtVWMPVH37vp5LeYYl6eloSpi0wwKPOVQPHFD/jZCkJBQUkKcSUZHOC/SSUp8uuf96ujOOwlA354hdk54167M75oUWIVq/o+u2EZsyfUZWD9lqWsDnxDxmfqM5i5ozTt5rZ/iv3t7eFclDHgO3ye97XJ/a9SdVBVHtDkFYbvRBgk2pDtoyfxY6f1zXOxVy9Z3L82TnDBmw1RJWfzvl/F9Lxm+lk5s5O/dUxq3z2Dtt/uY+/5d+XGay+bei/EcNyonCimj7GjKIkUAYPBQB2XuItBWwMN2TkiQMB+rzwIQZPZxMBheIBw66q5t56cK5IRLHXfuIkF7Sxh62zNUibY1y3FWojJiZqKqaEs+X6KA9kMORQAY9ayiKB4R8a02m/ya1EFxZHH1IIwg3LC1LpvzDizNKFoYcLpxYmnk28/vfi2U4tuP7VwVtGShJcXzzq7GLSe2M47wscf5k1zSp4ug7vJ5n3V5g7owhyelWAwcwHaDHm0YQfLUP64SfmPOuX9S+arr8WIxKgJiANEPFrbKfV1GXs6LR+ckz9okD9qlK+1WXs6bB+3Kh+3sQ1EnZ525cNztndr5T9VWK93xPZRlVfnpwgTyMAYw6IDfNnKfrVNfBELppiYCBZ3o9kXlSREBQqRitiGULhq2irm7p97aRVs07v+5abqhfFFrH8TkwcqEBCCoaA0NRAN/AMRmeWwGKGgMpVKJgIUzC5bcvvpu2c4lknYAVZ67fNcIBkVEk4Q57wZ5QtmVS6OL02dWZJqPZ1ifTllJmzFi2Wn2joq7Osy8/glydNscjVit7IIfQeBQQVOf8thI2JLO8YVXFCuN8nvVCsfNcZeazHyRirvYOGCCYsGqgrjKre8X830qb5u2dXGDClerLLo7WYFIb3YqZZKx7tbWS52T5fybs20PqdFSBefbCJ/QbTRIhKgeByjNgfBKAwIuGMwhONlIIoxmUJKCMZIIxVhYT2+AjBhbuGqvyxc8lf1y28/m0aGAtbxpDotoZQav6rFTEIEFfgaKfqHGKUmUo0U1hI6NfFsWnxJ+qyz6VR4Da4o3Yge8XDCYyspRgIuNOeyWqwVtzn4fUTNJdmEfwUlhEaT28kiD0W1gUgTEPqdmoAEKlbQjCdsAyayXW+RP3DK1xstvU4Dr6BymasqE5aE6OJusrhaprOwTGpE24qhmE0o+QQlN1HaBYuxvAiDYHXXmz/ZugMxNRPpKarZPwFKKyHw4AT60xxZUpKmQoumbRNWaIkYEBwZN5+8e5ZjOVvNqRULlkvlfO2reKZpxeIvFfhBAYYkMSsBOSWr0gAKZhQtm/HyXXMofOjI/TMPrb6xPdSOrbzNkfEXlal/UbvE1PotqfYe2ExnU01lS0wvfEH60V9JYRdaFlYyj8PohkneruoI4fVN02KCHxQ0YUlSFm0o97Rb/3BOvt5lYdG8lxAKankMzwQnGhxPufF6jc3dImOQNlV+UyWokOPQjI8Po3Ge5Ttcq7N6qjG26pUJjX4RU1RMFFYy5IHK3GZo0oQR+ho081Ai1VFIlRMiqqZiMMb6AYIIaTCG7XZkSnS26ecPTW/ZmFCMQQXYZ423QisRMQNcC/CVSBVSgX+NRCqUivFFaUk1GUllqX9ZfMfM09+KO/4dJg+w0OKMmTe6QpEmemoeJWCyftN2LiTQnxJWlQ9TXxCmPEnC1S3c0iXq5Bd+B+yXhIsmxhcpbtCpO+X368zXy8y99QZuZKvCEL4JDwVE7J4PRWMNdhsoU+5GbTVFmUtEvvQNRYyDQEgvpX01WLjf4e3xfqRRoSnRUdFGSbKqTZnNIlhIxCIaNPqC3wExGI8UeW2laJPFIFId1URL2EwmE8t+GpKEEu31Midg010sAaE8JTHIkxhY4CgYBAgcSpKZD4L599MTqtISTi6c2T3X+urf8PrJhx9iiskYVC+kVlNnvnjrc3fcVpR+W8VCpXGZUrdcqfuWXLoENtvBf7b++PMRWBQvq52JnPNczVZWJYwnLIeWEPwMBWpYQr8IMaLahu2gQct/rrNcP2jqy1ajDTt5rcXJQrwSQo7B2xqLNdV9TRk8KhRobSzaTjGi1RSr8HbB5qqXPS0xFOE8eSkg+kjrTFQFAJPIjNboC4yLmW0B2TYKwCQq4pxHtTUDnMdo4D0a8E+TWZLlqHAyI7AFfOxPvpVQkTzbeRfvphQUaqhRCgK1A020YQpPbKxg3888lHHz83cr1TycicU13WgFgQhlgNjuv1fq/qdkl0BPSUSTJgg/CY7kBJZ4lTyrNDmhePH/KFsyo3ZBbP03wrkrHnvvZQzr67ruDCEhaHqlcSgQ0YZuJ3oQLjAo6OuyfVQd13PZ3FceQx2wvft4IYXJRRSB3F9qcrdZmfrj11RuCD+Lz1HbyrozuBrjPO1GliP25qTUGoCFKYGIxycLU4DRV6UkoK6aQZUTosmbEB0DaBK+CTGAGBBFR/vCGqdO5TcWExNFfRmGpOKvSJfmKiXfSCzGEsQlQVDg1+c9yFDAO7CkMEUDzY+zK5KTjs27qW2Btewhv7iCsXm/cLnKOwEQbilJSypPY4pPVXrSuWWsmOq5DN/WsIz5O2CrTIGbjN2/bsjQZd6YGNOcfZ1MB1WQ1TmvxhW0ygAFvW2Kq8P2ntPW67C4ynnmL6sDMG/ChRiFSczQ4WC2Vlb25JIQD8L0vcpqaDf2gG6R+1osIiVqEhHjYh5UYIn2K2koSh9oDAW++GSNYzHaMBbpyUPSS/8q2ecpZxYkYt8lTYdWf6mA91gJijakuAKWRIxexWMrp7+yiGIFJaf9htTqGoTIs+CcZzu7MOHlJUl1GcwuSu0jsY1UQinbEtl+Kk+kqlsKzzX9ue/xvjMD6zIDAIJPQvBVLtKkJfpBQYf8YZO13jHtvaOxrlNGXiBokrY31RAvr9pt9uBjqibTcKs/cQ0CBgrVqHcd00RI9mQhnsxoRvncz2UgnAU+m6GvMjMmRMdgoaTRhYIpqLNEYfJ1VPSUKEOUFH4W9RFWQEA5lZyAZc8TS9S2CP7lCwIjk0tSyOTIpAJ0Q8xxZNxU/T3kmQzJO398FjxUSSzHl848NI+JAVXprKFbYM1GTdBUCWo6LLw5Le70t3hthwHIDxCcGkDwgwKhI6hQ0KawcsfnGRR0OuSflFp+VopBOBKO0+QvECQJF8NPYnnuVRgdqENoDTBcHYq3e5q3fJrrJLaBPjLeDxY2AftFo6pu0HoKhIQQkH4oqrKz4kijDAXR0TGG6BgLohPcUjSWeYyxxEjTpChDeBYJyhc+y8IMGCCotkEhGPg1VBLRhhXYgbEGjk+Od2TMbMAV9tIq6d2HJO9qNtfHhY7cz1Idn7+LJWBWpmtLLnPxpkykP4uyiqwWa2JZmuL4F8za+MJA59baENxNWpHA31zgRBs7VQvsUFzdyseN1t8Ux/2pwvxBJXMmwjhdXo01VcZyZG48eVrgqdFvItAyXD+sLACh/6LSW2H1lCMglI33I0VCIoNAE22odmTQdF8VKY0IBcOpkzYIgVQAFzLyVClfpQV0Z1hYgHQ4VkoChCKQDRggiE7NmpaLwpMIUMC18oTajFklKXOcGX9R9SgwCYsrgPndtohhwjhSJwME5dzdidT0Qe0Fn1CWGignECzQTmXarPLU6UX/LIFE45w5EKPyyGGsm8rYvwXbIzbLflCAjZY8naxKUt8F5VqD/Mfa2I/qzVcbDVRo8XKh5Dw1plrUmJGn2QobxmtFVgFGKyEwxapqmrsKu8tNHpVBwvhAtewAlwqEeCCqoIgoo2G4D8IhkFOA69U0SV/0sugdYwvnLCogJBAgCDahNu4ECxRtSHEFdRkJmI5080srbj6xkqINYy9snElphuNIlNbktSvFd4uu9KGysAUmpBL6kYo0vXpBbPX/5fkUIU8PaHAOrWcVRrczjrcvdPqggDFCF8YVdMtXW5U/VViu1Zl762N4iJGDyxifVPIBgmgaNTxAKJvmrjNPurwG0exVLW5g1kgIaEMwq3EFN4wAENTAJxMXDGiHCy2WaNAjhsQioTKU+SQEFRYoKkk1FFQtjS9OTTi5cIYjI+7Fu1mIkd0u71t/w6MNwyQKPOj6spSRAeoMNZ4eKOVKZGJyiagi7TPli+TGr7HR6PzSgFdoUEOXm1EjCIg27FRcnfL1NvnjqqnX2829HTG8UNikCjEaNjE0gK1DuFfCVhm0gAAqQ+U0V5V58kkIwmbIw4E0NZFimUXvBrVvCyD/pgwmgVFcYUG35hCnwKqkSilWPlRtCAkcClIw+yA9sWJpQnHqjKNLpv/4O1OPfofnDe3aLD3x2ISAAiIEhKnNf/XZAwtm16Vjy+nkxGBlQSMhCNcqHJxUkW4rShu89jKrYHxJ6q82MmXhFZsbK6SxNRFDjHo7p/2uOu76T82unxq47+CVT0W2Djcqvh7rblF8cQjDkxAu2noqZHeZcdLZEIxCGDCqeQqwxcZKLFhoStSUMWqxKsyV2mAnjUnTMjQgNPwrq1tevYCxRu1S5pUjgxvZDCtYPM8tL9wxrWRB7Ol07kYEkeDYygkEBUQECBcTk47NS6TSjoNLCL5CDfCw6QmOZOXMUICA9cz7Wwxe51TWb+VVNKq3W3s75fdKbNfeiO19W40rmGAljG4o0ZN6us2s8Up3pFVkZZ/bsV3xdsves7G9pdGTy8sAZNA0RIg2mqU4I9VRv6E6Qojb0ICAgWdTmgRKRFvCyIAuTmWBSaWps8pTk+qXJZSRdz6D9V6sTJtbOPf2Y2nWgw9QPVUefjzRoIAIAcH4i1ukwrmsVlJNmk9CCGlDEA6UUszlLFoo16DnsXPAYuyiE1PvK7F/qJA/7LB90CT/1hH/cafc+xvTBC9dcuOIxVo7pP52k6fNCjJ/mI3ghYvBI9rWs+RH21vHVOPt5FEZoqJZc0emKRiM0tSpUUaDFD2mOCDIyMOVTaLskrbQitEURsIjph4rRRkgM7N27dgNOak8Ff4z3bFkRsV9HAEoP2giz3MGCMywqXTPT6rGRvAigiJkEILWqFiWfNupxRbnP7KJeGX+YBehEmpvx1x7xXT9p6aPz0/9+Jexn1ooIGKlEQEQSkyeVhsLTGoSneiHDknyuW7xG3cTiFsG7tCZREZFVQzASonjGW1owMwm4ezwdWwxoucRhIQhbw/5Pbb+jptb/vH2H92Z6Fg0oyI59oUFPBBn7KMNR0LoZZAvLEwo5y4SFRMCFYcErduxgkUj3PQcuh0ZqoRbWVFbwuhTiAOCeC7DWaO3DZO21LrxohXF4JjgEw+6AEzi+puxKfwbn+ohHR6BeqJCgdnXaVottwKbOfzCy6SAN3zFUjePZwe/s3oiVvAcnGrXs9zqsu/cfnYJq+9UqRESNBaDBIqvUGOzMbw5WXmB1ZGWSsKqnsQQ4K2JXsJozIhrUo3R3goWnsSyOBvVIpNqsrMv81GTAulXOw7tD95zACkG6v2kU6REKVHMm2DwpUuIhMpoozk6nPRnIvK/izpCr3whzDpCE4vQATr1xWXxxxfxxo5nk0NbFDUx2JjBkRbb/g32yMcm4VNPACIZ6eKLBnc7qxHd30olEfyMh0GfaugyhnB4LyiuJlt/UywTD96cVPrChCKWxcDCk6J55jVPhcb6aWGjARFF8gMUjGJtwzEmNblp+lP/NLuSmUaTWKhVMqv8VozJTXxLmVWMgZcVrNzrrLJUXOQk6Z2ZkxIGJwCRAtW0n4mjPc44z0WM02iU3Wq1NFZASS2AAF/S9yywEzvV9rN0D5vXEc+Dwz8Z1VHGj6IoCTo62vc5ts6OCURY78V85LOwm1SzjEVXVrHoSpbrVLdU3eDPZSwtujotqWrpbRWLLW//zdglaH9CifTLxsemOO3StQ6bG4M2Yd0HZmcs38qym/tb8LNVIRDov6B4OxR3p+zpsr1bOM1VFCsa0umk06gR+R8b5yhnv8p6ylSmsrTHihRWvqmClY1lhV4r0pOqMpJKkyX7vKldmtYwOg2XeDaoXXq/Ofq9C6Y/V8f1dLD+171tcl8z2zwtstupuJtlV7PcC9+0Ktc65Q9bFXbkaxbXW0ZRt1knnUaTfF2f7HHtaUrZN6TCuTOOfiXpWArgA2zTX1wQv+/LM0q/GVv/FWPD/8N5PLE9qpOERJwG69LyX4arrxr/u1D6sMH685PmK5Vx1xrld6qtH9TL79RM/cVLtg8rrL9/4ZaPz8X2XDDozhqdbiyR9xDDEiS73XggedqRf779+fkJJxbd9uKdtx79ulK/jE1f4WfUl6VRooCQjA87Y/6YZXj3qPE3zxreKjNeKTde7TR++Evjh2eN7z0be+2QhccgXdbRQKcbT2rzqRBxFNSi5fIcyalrCqNPfrBAoOvFWFe7//fZvFuTjsc66fQpIWL8y7A52MaFAccnpFqUTjrppJNOOumkk0466aSTTjrppJNOOumkk0466aSTTjrppJNOOumkk0466aSTTjrppJNOOumkk0466aSTTjrppJNOOumkk0466aSTTjrppJNOOumkk0466aSTTmNAdqTNmzdnZ2fvQoKd48ePO51Or17m8sZQRkbG/fffv2/fPhp82Dl06JDD4Rjv+9Lp004wCcW0XLVqVSES7MCfgAZXrlwZHBPoh+uR5s+fP2+e3udoCILhAjSQcOTtGoI/Dx8+/MQTT9gnSyNdnT6J5ECCSbhhw4bMzMwcpG3bth08eBC+9yKF/CH85LHHHtuyZQvNZ5jkNJNXrFgBkDK2DzGZCDCTRmzNI4/Yc3O3ZWZuz8zMzc3dvXs3jBv9a7zvUadPIxGz19bV2XNygJ0BDUBryMrK2r59O+xs3Ljxx0eOACZcvnw5GBbgT1AoSKiAz3Xr1uXl5cGXsA+z+tKlS7quEUwwyHPmzIEdGDQY57Vr127avn3j1q2PP/44jDmNIehrAMg6LOg09kQmguePH7fn5RUUFOzYuXMnTMfdu+FjJ37m5OYyUfbMGTgMYEH7W4KIg4cObd6yZeXKlSAqrFmzBub5gQMHjh07Nohc8Wmm1atXAybce++9MKowtgU7duxEi81O3PLz8+E1wOeWrVtJ+hrv+9Xp00WkKRw6fBgm505k/9y8PLGBNAuwkJOff+LFF+HIzs5O+hUtXmfOnHnhxAkm6RYU5Iif5OTAJyxwJ06cgAP0ZU5LgJAAlTQmuTjU2gGHQcvLzweIyLTbNz/+OEDrI488Mt63rNOni7gxC40GAAhZdnu2xsYFMu0OQIn8/EM//CHZGfx+hbN67xNPwDSGM4gNvnwCvszL215QoAOClrKzswETGAjv20djle0/4DB2MJgACOsffXQV0njfsk6fLvIBQl4e6AvZuO/b7Hb4MgAQtFZxAJB84noxt/FXDCLs9jUHDmzDHR0TiGgAMxyOHYcO2cVQqzvZCAgwnoDDOiDoNC4kAMGuBQTxZShAAC3YjhELAgd8x6u/pbnNtGD0oK0+dGi8H3RCELlsHAgI2aEAARQHAARdQtBpvCg0IIhZGgoQQO7NyMiAT/hv/o4d2oN9sJCTwySH/Pwtu3bBgrh+377xftAJQWRohWE8OCQgPPaYDgg6jT0NAxBo5+DBg1w10OgXAXM7Nzf3u+vXw3fz779/vB90QpAOCDpNcBoGIFBMwvPPP79161YfDvirDDS9t2zZAuqwhHE44/2gE4J0QNBpgtMwAAGmNOkLD61ebc/N3b1nT47WYJ7D3Ax79u6Fz5dVlXm8n3KikA4IOk1wGgYgSOqKv3LlSjiAohco/EDEIezYsSMvL6+5uVkPT9JSBEZFHRB0Gg8aHiAQUajz2rVrQXeAOZyDgUzwzbZt2+6//374PIPxjTogCBJux8d1QJjk5A1FIY+E1RMkaqZow3LgcFzOziZWykYa9XtgAjxtly9T2CHMt3lOpxQeG44EEOinwPgbNm5cl529eufO9fv2bd6xYwsS/TfMR8vGUQp4rsv4RCMZOtJuiMQb8WJ0kNPpFJmGsDMqWd502stI/EJ4LfgTbgAmBkAo/HnjAIEuQU/E00/oHvBPuo0RTsKA59W+JroiXUK8TQdOzjBn40BX8XuJKtEbHBXOGgZRjJkfG/rfA7wFuEP60o5ZwwQZXkz2oW/gX3PmzInUwqZ99pCAsAL4FHeEeU9CfAAacp6PBBC0PAXzEOZ5hiaZl7hs8EfTDlrIR9MOHRxGuVThjBhc/QgS/TbgjRSeOqUdLvrvlSefhI2m3JCXCDmM9IK018pbsUI8CBy2Z8+egydOwGGP79gxWoAgHoHmlfaJgE499phXHVsxkhlII7H0ap9X+5qA7sNwa+1LhD/ur609hLFYw76KpH2J8Fzl5QHTIx6JEsfGgGDJKyoqampqamxshE/Yh28kzSwV90YGt2eeeQb+Ow855cknnzx0+LBdDVKV1OzXIS/qVVcc8exlZWXsHpqa4PsXT56kS9BVth07BleBSzx08ODqgwfFBCAAH+gSIwEEejWZ+/atfOSRH2zf/kB+/g/27t2Ql7dp06Z9+/apPBGaED8Yz9JNFhYWwoRpbm6uqakR85tuD77Pysr64Q9/6Id4Az/O6tWrCQdordyGlAVr1pkzJ+Gl4HDR+MM7opdy8NChcrgNVccffMQGHEOkZwsLX3Y4zhQVlT35JGM6vNUDeKFV3/8+TW/4fvv27SMHBLs/LV+58r5Vqx5++OGcnJxn4IRwgJplXfPMM3Ddl19+mRZTMYag2UVq8vX6C1elpaWnT5+GdwRz79jzz9Mj05xvRnqmsbGgtlZb9iHMCwFrz58/n34ibvoAspUdXyK9QcKZ+9GvTSUmaO2O6KHCJLuKunfccQdl+MLUglcJn3bM+d26bRstRnTbP/rRjw7/8Ie0nwdvFqiggMxrsJuJkXsPPvQQ3Hw4gyNkMDbNnn2W/P50D6CnZ+XksMw4ODOcv6BgZ0FBLv4J38N/6cy79+yhCjyDgPNIAAFGPufwYXgNGzZsYIrD9u0bcHDgT3g1hw8fHkhCILYl+bmysvKHOGi5ubnMQZGfrx23HEz5saMHE54CRniQcRNPCmfeuWvXvqee2rR585o1a7bhC8uhESsooC0fz89GLCuLXs3RDRvq9+yprKoafMT8CGDn8mVY/uwYxZ2F92kvKICHwYcoyMdrwaPZMYR7586dcC9wS4899pjdPiJA0EIBXHdbTs4q3M8loqxJ9Ulhg8eHqUvHw0A/jOGm8KTHjx8Pv3KFdk5m5+ZmYQENO+a85O3YAZeFt0bvLh//JEZYv349jAY8u12tCjX42Aq+g/e46fHH7XgedhUWX5+XQ3NDfYMiTxwOnp+Ssnrz5sFXopEQXei73/0uPNGuPXvgPkR6GrBJAb7co0ePCiaFHXhw+JedHcKO4Rv+kP+qoAB4YRu+mkHqZYl1Cl7WU089RZfgA56fD/dAQe++S+BVWHAgev0KMHmZBpbga6BrDQ8Q6H3dd999NMnzEI7orogT+Nt/6qmBLA9A+/fvpzJNcOfwAxrbAv+HYoFPNOD44HRX69avJ6E35IhRSQc4ENgc5gycMA/PDD9kM0k7YnR+dIsU4D3AT3ZgMnI4iA00B5QdXBFgtDcCo8Gjw4PgUOzQXAuukosTAx4B7m3Ltm1bRyYhwPcEKQzN7PZNOPGA5RjjIx4EPCkNrx2zKdn7gtmYmblr505Yx2GCAbCHqZvQLAJZq/Do0a3w0lXepJysgLGFK2bDmOMLhVsCqISBpdWTloOBLkRyzbJly+5dufJxu72AxhDvP2DOF4gJT1MoNxd+uf/554fUVYdHm3FiPJ6Ts2X79r379tELZagLo7prFwzFdqTHMVOV3jhNLbhDGGS2ZKifQLnIwvmIbFuBj/bsGWQlIrEH5E8YtxycyXBlOnk+rjjk9KdViV8Fv6FJRcfn4fyE26MXQSHHARcaHiCQkPPoo4/CVfbs3cvWQno0dQe+zEX+pesGv26YyTBDGBTAjMKcX1pQstVn4U8EoiIlBePYws1sz8rasHEjxU6L+xGq5XPPPw/ozTI34WXBQKkjlqMdMbiEeC/4mHRYHi3qO3fCGQjeB9GyvaoRiY0DLgo7xIMAI/i/GiZK4aSlmbNVZDgOCxDsWCcB/vX4li2wQOch7+fipQvEk4rphzt2NUSE7pBzUEGBkHUJXgbhBYGQBbt3F2BG/I7du+luaRkKeHE0G0VCN3wSzMJSuB7xnDJigi9EY84YCtcWwOc8VVykIQqcHmpZCfb64NHgMA0N8kTDoEf2719VWLgRFzu4MbifLHVSCX7Znp29ceNGMZ3smsEP2LLxBdEQ5WGc/y5cxAey+wHLbN26NQ9ZhkY+W53DIc+vDSGm4eLxADjNs7EgUvBbGB4gHMEySiC6+JKm1RvLxkm4E2v+wCepkwGP9v3vfx+AlOs7iBtZATzi/zh2fHA7RkrDw8Bvt+Tnww3sOn6cTkhoUFJWtgVmLCJAHuIh3Uz4I0bnJygjZhxIIRX2w4fXrMnFaxFfCJAJ+fZJSoHNjiIiyS0RAYJ4iczVm5lZgFIBv7S6IgzymGIkc1GNBSZl0wxhYXAOIhjfkpm5avNmWqZJXB/sxeEViV9yNKvh3Llz77nnnpAz365W4cPzsbHKU6e9feCrZGsm/E6Uz28QIGwuLARA2Lp3LwwaqAyUvkePmYOB+rCzAdd68Ub8Bl99BX7PgpSHyz0NV0iV52tf+xqBJCkg2QNMs2Dwyfa/B/gdrY8gVd6DVXpGBRBoH8QbuEOCtYB7ACjIw7cDhwlAsOOaC4oGfJ+DhhW7yiaBUwgZOeTQ7d6zB3ipYN8+eDVPPvecpKLBuYYGtlziEgwjxn8+AFMM9CfZ3OAkOeqdkA0t4O0QysEn6EQ5GglEy9qBN6+ZukzpAylCPHjYgAB/rlixgmnWGzdu27wZRp5UngEfNuDBg8aZjAygm2dr5mowI5AsBJ8gbQoZOPCiSFkDIHC2mgC7AxEMpgEMYMDYCkHFjtU5aFQzgx5NiHl2YWzUvD42G3NySCUH7WwYXD8I7cLiw7l798KSRIBAV6dHg5FhEL1rFwP8gBEI+jN4bhOGwFU2btpElme6KE3voqKiHeiZgqsEsIyYbH7Mqz2//z78vACVHQlxPkAMHiEgkBgQ/PbhSxihw4cPi18J/YiAFAYtW/uuxZ37a/r5lDylEQ5pUYOXAq9mF1rDtOIBvZdgkPEbE3UF8WNJ/xHbgahChtxgR+ScOXPgfa1Zs8ZOOVyoI4RYCAQvBDwpjkK2fwWJIQGBSUS7dsE3a9euZWrg7t0kNAZOg4DnDZp42suRUJSHFm9isWAtCZ4UhHwSh/IQDTgEBZ1QKPhcbxJPrV6dRAVmUgOlcs8eO2qUdoQgO/IvPB2Z4wjVQ9w2Mg4V+qML0TItBkHYf+BUox6cQNXIQR0GuNECgrhPGsyAFyHi+UO8I827gE+m+hUUbH78ca2JjGRU7anE8XwqwcqLoyF0VWIisuNpz0/7WSIn0W5fvHix5J9q5LuxGwwIZMfIQ7mInTOId7IRJWg/F+UHkkhz8HlpnHMwdYLZtPPyGCDs2iUAoam5mdCDL+4aZOY2HzQ6kWyZg4O/A0fM752qn2ThBLH82WefDVjIaPRA6N20aVM+6q2ZAW9fO4Zq1qe4mewcf+YNW0I4fvw4DCBofyAIseqLgAZajlMfJFtVTHaqdRpp9LJDPimOMJle4bSUiablI2F6evKppzLxtfoeULPD7WYUvo7eIv5l0FvOQgEMDtuyZcsmKqmhAoKgPC3GaqYHWc9oepDlNlud/GJRBpyki5Kx9IYAwhNPBAKCRgbI1twwDQJs8NTbYdKSxyHoFYg/yeYA8w04S2j39BQAksxJpBUJxIPDqKoDtXHjxkc3b358O1wti2Y+2TMDLsqWPLQ7Pfzww3b/VcD3Wm8wICxfvpzwPxtvhvOshhdIeSevJbV4gB2QITOxPjmVfISZAG8cHpZWEzhGUtX5g4cOZeJsEX4oO/kvCGTs9ky0ZMIP123cCGPL0AYkjQFGzK4+AgkJWkcJadMPrF6dQ3qfBn79ABzN4wAYZNbj6q3mbfrmg2bcBgIEOBYGk2Hg7t3ZGoQPYEz+okkbhetqFgWyq/hNQu28RYKhpvgN8bDkVoDTbkMzmlZr1s75XPTeAkLSPcOLIxenT5nSXDETbxJUFbjQ0qVLtfFUcM8kHgQsvtmqeYfeIE0PJgTAHzg9+PIKgwwqQy7Ij3tpxMYIEIK4xq7qRyTnwDRet27dNpz9QqoJ/mEWspsd0YxOQteFYYHxhOG1o+vcruIMHQNj8MQTTzy5bx98nnrppRdPnXr6wIEtW7eC3pRFBU6D7jMbL5QbytgyNoAg3MqkJNKM1V6lAJVZZgTAJeZRlQBG7r///oceeghGIwflrny0jYO4DmcDhVpA6GYsY04eN3peWhZhTOC0e2HEnnwSPk+eOgUbSH0gdcMiRTZev/VIffwdQuKy21euXEkPTtJC4fPPryKb7c6dWQFDTXyNt2FHZzFI+HAhclvQ/WtZOBwJAS5ai6EO8MbJNZMj1CJ/vub1WnNz4dVQQiVsMD3sGs71SSma9YIWXxhkbbo6TMvDGFBHwmeeWitPe8Pk1bXj2vQwEixt8OIo8ioLV7FgGCHxD44Rb5Bqz5JNgLRp7bsgiRHGcN++fQDRcJ+b1qx5dO3au5Ytu+vb3/72PfcwUyQ8GnqN4Xh4vzQ/xwgQgiQ9/qIRqR588MG77rrrm9/85ne+853HccqFxgTNrCNtVCzcpDXQygiiGk0hWv6ANWDkRRieNnCU+aEef5yE4eyg2SJUm+eee05rxhwbQKCQs7vvvpvxID5IwEiSp57e43HVd6BFLRgNeDpgzB9s2rRl+3YY5AA9C+YIzC4YaoBK8bDMMbdxI3BE8IjRn8yESHqxeDvqDucd/ELwCBnGD/3oRyS0+InEGqVgB54Qrg6aMqxWMIfh5rciAS8XhJQbBwYEh8iRRCgIEEvEJ81AAL6KigpvUGA2q5afmUmYYNeKZ/5M+sADD4ipCPgD8z8fjt+2jXTzAFU0H9FgE1JAZAjJUfesWMHq9AZNSJpX8NvVKLICp9ANwygRN/mOVPEKZhSwCR0WwOnM8/Lww/uffhqekWFadjaPlxjtvPvBJAT/mUPwtT0zkyJyxX3Cfw8cOJCjzhB70FsQpgbxE0ld8shTn4nXgdGA5xW2R5CXKFCfMlbgexAbYPEFZF6PFYpyApY8vFA2NmMKUIrHBhBglYc7f2TtWlgUKJgk+PZy0T4AA75//36HJtuIDKF0m089/fQBeOnbtgW8KWLzw88+uxllTuA7GHCaw/RGHGpqA40Y7ACuEs6QJJYrzP7qqxHjACBsVzuvUZg6PFc+uTMEDmg+SbvPxllN0ZiinQ2ubGvIeZQdMAgDAwJ5NIA94bkoviLL/zVlq+Y+upwddRyHJnWL9rdu374N2VM7e8V5SCODK9LIAJ06dYo9LEgayJLBiyBx7tNPPw2fMKQBgEA71HQmWAWgy8FfMCVAPKDJT4DgezT1YDIkrtiwgZ4FDlu0aBGdfw4Sadzw7C+99NK2oOkxWjS4DUGgAcyNbXjPZOaC5/rSl74UHx8PyyKwDNwkC3tGOSGAcegkJAB8b8UKeNKH775bXB3m6tHnnnvm4MFs9bXSxBDpG5TaBghA7AbSGoXpciEh4FZxg6n48ssv29FyTicZG0CgO38WeBlGTAsI6vH5GEkFepbQarzYwkB0ObSrbmv6b8CbIkB4yeEA/TQzi5FdjYnSBsWRVxGmLozYaiRAUZhdeRiw4Sf8I8HLhfGkuEeyVxCGEIIVBA1XttptYQPqsHY1tA/uHC5E54HLwWuiCDduhhpKQqBHKHjsMXguwYYBkxBm1+atWx+Fk6uVrrXjY8eYarjudrQ10ZTL9mc6EjzgMFC+4DuCU+FcCFjLslAXBmyHYSEH4qFjxzYXFtKWvX9/+g9+8CD6Jojrd2Lv4ACBxI7a3NGjRx1qUMdjmZlsTPwlBG5AQLfUnn37iCvhoQC4RPwtTQ+BgaMMBCoNbkPwaQrw3vfvh8/y8vKAsHCyAzPdFuO1tJNNnIesZHtycwtXrTpcUBAwyelYigAHAgah1syk1dJ/M1QqxMjGXLS/+c0ZdRWmKerVVDEaG0BwqGlx+9av95ng/FccHpsBWo/dDuIQ9ZyF/68rLSUDOy21gyQ82jVEIwbjD2wICyvsLF++XFKTT2m4aIehEJp//ZyVSHBL8O4yUfATOUHwsthChoXrA8xlgo+yBjHXoJ4COBNsuxjEqMhuW5IKtIZBMYw0D9EeS8f/vwULggfnxRdftGNykJ2mh4g90Ly4HMy5oMlDUc2g7DCf++7duQGGKUIh1R4CUFOIQTvUJ3gVvjj4pNB08hMF3DZ9AtRQ2tqp4mIW85OXR36lAB6xI9LCs7OIoN27t2zdSrzv8O+NGzzmo0uDA0IWBcXBC1q5cjumVAS7OWg1X7p0Kf0khBUCeScL2Arn237NGURaGc1e0TdcWCDFs999zz333X//GiTQW3O10qzmWiSWUzDAGAMCPEtdRwcc9dQzz2wTBg3/FSoH5fadu3eTsA1/wmInYSIknKG2tjbcVG6V30FCI9mJRozS02jE7rn33tU/+AGsvKDFwCIVGhBAeMPodOBQEnJo6MjC5rOL+gMCPRpJsMGB4iDUrV27FrTI3egsCBDhQgICnSTz4YfhPik/IuA+KYaHW6Ht9vT09JCDQ6MH0EoqWyBvqpZnYEbKQIHntSP6kRFAO3X5Iq7CEUsmBX0kP3/LE0/Qlrlr18Zdu7bB6KGXgRJ8QkhTOFaU8frD55+Hz63orQhph6QdcpYBRO/C5LV9KDDYVfwfdaNBAA1uVMxSjf/AhvvQIT7Q/cDMBEmMchmCPVzChRrSc6qNmWevCW2M+cKtbLcztXTLlrVIsNjBzWTjpPW7kEZCCEDRsQEE+MmiFStgCfjWffdlorM4O8iuRTdCo0SARm6RzUePPolIKJTxwd+adsQyMBOTWbxxUd6Iti/4/sENGzYiwXD9AAAhpMqAjLYFVlVsgQ0Esw4+d2IrK66J+z+1MGaShhsc5EM3BiehiIhwAIEszJQ1QwmMgWIqNoeFp4M5Ro64gYYFRq+oqChHBa5gxwoFnBMgkA+Rkh1ytV5OjQGBeTazsrZha2DgkeBtm6q+BS8Bdk2gAil3dtRrgNn94kOCpgdFleRipqodPctkoBOy6ODTYyQUjoQAGywHK/fs8WIlnJDnoag2wsngWUfeHMo4CwAEkbMDhz362GNwD3Y17oLQgF5NXj4n2glh/dZICOMFCHNVKxCwVY5qOQmWISkPiAIOYZ6zvg/43skLbx849UPyL8oBJ2D56dgylUIOBIxrR4wGzS+nIAgQnlZN1iSh0aP5PbVmhsO/Hn74YUkVDoNnAvk7fEbmoQCBZGMQ3YFTaFi0QESSFUuY2rr10UcftaMJdKD57Ouwoy5DAVen+MAjmKtFc4NnTPvfp/Z5qS8w+xx4o+CZAOnLrrrCyey2Z88eii3ZAjoantmXMZSjKpgCFtCmsRNDy3LQiAcSF8hdEwcQ9lRWSlgtKuR5KIQmfwC5lHr5ERQLeKdZfRkn/5njxxnGosMoD1OidiKn0LCE3gLQQAMIAX3GxwwQJKxpA6/+gQce2JqV5bNah5xvxMaoZO1Qg8PtWHFCsHzAIGvRwHHmzN69eyllAC7ERkxNfgx3xEIBgl1Du4Jlflph8/Ie/sEP7KH0BUmVIR2RVEyiy4nI7cAgEzs3b8LOI+vWkWo50HymmENaSnZqu3Rp3hrcP3X6FnPDz00s3pe6GIWz+X4eMLVQ46YQVtBQKEZlbV7eRjWgOlsMd5CokE0RaGqgO6VGAqqAeHPjav6HAwhwMwQI3oEB4a677oIXMZCEwP3dmjo2kjq9K06dss+bl4eTZAdO6UDeUV+NdrMHXWKCAILI1CvEOifEoVkhX7rmuejPnZgGC5erxKEO1h1ItG5sbATsZdFNNGLa2LyIRmxwQID70QKCelpiKLv/CGuJzCkZkVRd5vMfg3xCYBcSPOx6u/0ujM8ZpM4AjAzDoi1b7KEkHIoghUlCtbLp/tfv25eP8QkBKCRuOMwtkNTLwcmfQg2FLAmrDx2Cwdm4axeIQ6QfZfn/yg9VNHIyT/1G8elujatudCl8QKBZOpAou2zZMkCtfLII+fOyHSUE0nDtGgkBdugbQgM6xm9k1KHwg+5gztL8Oe6AIGm8h8dfeIHFJKjB2DmUaRg0ONrRJk0fTkt1k6gmgHacOXeo2fEDjZh2RoUYsfAkhJAqA3tqoW6HAgQuwAwLEHKCR0a9EDzsZrRiDQ4I5PzdiYkkPglHHYFsNeyZamXbKaNwzZoCbfhxUOyE767C3nLUCHOmF6jKC8UaARqsRsstBevmY0aGTy4K+bJUolkHCAODtnz58jCrFEZEoyUhrFixYs2aNXkDqwzZaqrXI4884lCjj+DPPDSviRT7bP9pIBJJgiU0u90fDSYMIEia0kagC2zC97hDzSD2Za4FzFXN48CZWT0ENKCRtEmnnTt3rh0zi0Mkn6pnyMWabGJaDjZigwNCqKe2hyEhjAQQAnlB8wk/2bp164r77htSZeBPESAhaN4aDAyFK9tFEAIlLvmPT7aYgRhiGtFGBax279kDMwSElm/fc4+WecUYr0ZTTBZqlyS6aHPEtNNDO1soGV8UixtdTAjfhnDfE09IAxsVAQ3gmAF9W2heI0A4cOAAMcsja9fateH0/tyRq1YVo7TBTIqVoZmDu34JuRMMECRtKjQmNLESkRibtAPnHlmkA8sFqJ8U+0Fl3h3ojiT8XIrR7D4/TtCIsX73ZKXUFNuhUCUy1gXLJ6GNipix4ntqcRVMmhaTNuRUFCpD+GXY+WtFo71dpG75Dwi8o43bt69DC+pDW7cONJ+pztV6tD3u0NoQ1FES9lK7alSkUmD2IJWBDNcsOjcrC7ZtIOKGt21HlwQbc5zACxcuDL5Pu4buvfdeitfNx9DTHCxO6HNKhoLHgdJ2Rk5huh03bdqUhhWYgwGBVoSDBw+SYusLVtTeP4ZgkYIAl4PjT58+TZWOcjHeOHBuUzA81szZsGHDgjVrnlq3jo7fu2cPAK9dG5g0IQFB+9LhkXdjMS5yo9txegunkt/rph2su8h83xiOsgMVEGBSlsBIcf5B85yqeDExbOtWnnS/fTt8Se/3mWeeobp/IbE6ABAc6HaksjAhvAxo5YBFLS0tLaRpi7xIME/WUMJR2ICQpRoVg2OZKA90S2bm47m5qwoLSXcISeT4IDNOsJfBrqo8O1HuIkmGXZcgTnOr3JyIQ82KOmZnb9m2bcOmTesefXToDRMVN23evD0z8/sPPjjQrdpVqyy8IJgJe/fuZXXFMcyS0tZ8Jscg6U7r3KdXNiiXR0BDSghUHfS7mzZJGve3lkhfBr6gFS0wq87OhU/hZQAWgyuSjuznrBdzG50sWbjPEkUffJCKhwvmYvv4TieyhCAuTYGXdOc70CsNrApzOweDgnwOVu0ObnDyzTt2ZGBDCgfmQ9HS5rPZatAgHxN7gQW2q/VXxXCRVDyI8DaI2zFgHhKbUFmhB3GqhwxM+ue77wa2/d4jj4QpIdDLBdSieNeASEUBCIwxyX2/f3/I0aabESldOWKt0ZyKCat5eT9Gt2NRURHV7KJyoIGwnMPrzHzve9+jwVwVIdGvBvcILFq0CNRtOpKFuT71FIzARszqzUcxQGvtzFbHMFuNzX7yyScdoXLlhk1DSAiU2pyfD5D7OLJzSECg7wu0ISX+L4K0nq3bt1O6Iuxvx4hu7r31f/tUhVibmUus7cWIPtjfvXevX6ToBAMEr9qFR3xDsEBH0jx5aPVqeLmZKAkEuN0F14D4CKydceQIYMJ9J0/uxtjaEOFwmJWThawN6hiFdosR82JlqvABgdrQrMbCC4EZ3Oqv8lFIWLtunQNTurSTwatmMUvYMYEMGtqhCwkI5D0BaRCWDFYcM1QAFSvehWxLCcshEZhaMMDq7BsoQer0gJ3teXmHMf2NCkHAVTMx8TlYVWFSHEbaE9Tcc8894aPBI488sllTHUU7RAHR6XQMJUfTDF+bm7s+Jwd+LuoQBsMyTwfAXLlRrIowRPqzyi9U298epK3Qs3znO99h+/4JR9p6PiyDJj9/C0IKTGyR8xWcpkohNDAxdqBQR0WoKMxeZJTbMcZ+oFyGiQAIXrV5UEBlHhAVYLkRt5SHWbfcX+DPNSxWbc+ezZmZD2P0/hoc/OyAcDhNBg1AAV0ahgg4a/78+dlqr7FDmDDrZ+/VnCEAEKhB3gM4LWmR9R2s7jCbp2r8IYlCPCOxNpxnw8MPU0BmgDtvIECA38Jtw7qzQy2/EyAFMSEBi0XvVkt5B89kMhXCGag+iT3ILEAhzaCEHsbkAppOmSB4YMWSABsLT27asQMump6ebh8g7iKYKHeVJOeBgknsqivKq0nSJ2Fy6+HDGbhwABesW7eOxL+AtCkhMpHWuWtgHSpSCifbkUw6sMQ8+NBDJP+I905mK7h5Cs8O5FAVZllRKZRwampqvCIrXJ1vAhBIzIALbcVS4ZTrJG5VuPiJtSeshNDY2AjP+0Ncg0KOOYwhyckOLKuyQ63pEQgIu3Zt2Lp1zbp1qzCnmB4iWL8WUlYeZjeLi4qVaOOmTStXrgxTQoCfwVReqQJvYLClupNH6c94S4Q8QiiCb44ePUrxtyGLyIVMbiJjYD7alLRPp2UB+hVPtTt0CEQRsk7DdeHnFOcJZ4CplR+QbYo7rFA2Rv1JmiQaO6oqoO2S9yEAbMmgIfq/wPFXrlxxaKrukzRIQ0ddBUVxJIea7Bbw9gF8nnvuORBOfvzjH4t3JPo5Smh+eaakZD82l8kNzndQ5/kujGSgZdo+enbFcAqk0DcFWDceMIFqgIj7/z5278pDV0vIOl1UJDYfDVxaQKDn8gMEtaYcSQjwHUgIlHwqKomRPj5QCaBxBAS6CIVVs/xBHEmQ+r74xS/SCWFwao8cOZKdDRvFstqxTtEOrYSgjgOFa4IuuX7DBmAZGHCQAexqiJf2BeVpogLsmMJMvAn8QkPNKpCr0cvB5p1AQNAMVwgzghoWQoVK4OX9WMU9+ITJ+cCqVauxz5rf5fzF3ZCAQJaxPMxgylOrVQdIJnmoRlFmIg24mITEgE8//TRBSp5/YoLYpywkQjLiRBoi9nco5yyVj4azbdy4kcpriytmaxqhSpp1f8WKFd974IEfYCTnCy+88POf/1wcQwbPe++9F2QhuMk1a9YceOYZgAXxIAAL8NZgKC5cuMAtPxpHpHbuCZVhrAHBHxOoTJwdRYLTp0/v379/ldpsIk9UtAiaA8Tjz+GiU15eDiMAvLAdXYd5GnePmKsUewBswrVaJBgfGIFHH32U4rt4TI52wow3IFCCKtbK3UUGakrQg5tfsmTJd7/7XXrFDixmQnf02GOPbQtQGdShyMERAMFgGw4UCYfZWL0zgFOyNQlHRBnqJZ599lm6fz5iQSw2CCCwF4QvIhhD6ACW1ABSB1qSnzl4kLCdlapAq4jWaxaQAx4SEMhFm7djx8NbtpCNPfgxs1WlHj5grh45evT0yy/D+g7D8tijj/KVgvxcQfOQ/GVwWiqhRmcVWgPMZF4GOXgCa4r7AZyXlJTA8rR27doHH3yQfg5z8v+3d229UR1JeH4PPKJ9QULaN14s5WUDQiaYW1g2sTdcYhvDnDk947ksAzYwHoyNbTARl4C0SZTwAgYpL0j7wAP/aLa6vlPlPufMzcbGklOfrIlj5pzurq6uvlV9RY8jygB3bfAnrHGAHlJwEmANHCc6dCIEH+5KZqHdpob8T4yA2rr5u3dpy1DN7KHUIHC0F1R9yL3MDhoE7Rqsb8HEC5JAXISFiV3CmuMujEZNRxJEksSoRJwh1MMukE9QjXlyuTt3/I3n9DQVhL1qhRU7KU4ULKzqHhoE0gqwxyBwNdlnsbWscJIv6rVWozHn3BzrLWzpbHiNGLSCpFoslUja9M4W39+Ba7fRgztORxAJlirgHQbqdb945n+qBPyEmeb3Mgg3ikXPescukfmTB8frOiQ4q/ERE1QCHIybAs+t23sZBCe4zg4nXbcb2ljk+nTM5ICrqypf4iMQxuUeDBf/d5jWI8OmRcqJjUZoAMOjLWTGwX6EupLWdUrQAZpcUNrWJAETVWNufh45DdfX1zsc7UWg5fQ0Z4FxWHLz+YDneahW6T3t+/dX1tbqnLKQ6ka7xLKeOedEQV3TYt68QreY0503COlxnfzC23xwpCNJDdyrNr8WTAfoBWjLOkeCoxdILKQDJCgy1A0NsA0+I54HYRZKzG+jKZBqgUdZ9op2rw0CGQFcKIPgwoHxGCk/eMdUY7ZAJF2tcE7GarjxCaWNI6MbN2h+oXe+efOG3r+yulrOUCPq9McXDTXOGYQx4n1iJRNrSmLphnc1CAVlXb5wIfGKzK8Q+LMkiT6h2Anpqz8b2pxBNj1y+64QtJs8ZfSlS8huFlZYX1KSZQA8MGfZk9DxJmXzxCO3tKhzggMq69atW500vV5yktBuF5V7LSclf5UDnUR6Jr6Lh0ImWbR4UaQjAs6HVDlqDi0hCuxT/Yop+kmfoQmaXAB2rMbL74ipsyt8/aSU7BlrgJmUhskSD15Yj103CEE1ssFcksnRBakDU0ZMvoblQTk9Qg8dOkSfZGMda76niOmmbDCziWHnz5rk10tqleO8+lyDkD4e3KpB0OBuh9lchFaSpIcJJ4a8AWcyqeleutuP4mp1mlfjpL2fPn2CY5KfDXleTrkiyFOzclCAgEdITK/eYicslOnm9zII9Dk6OuqTSMItpGsvMzS9WirPEf+CxUk5fU7YxyAgdoMGUTJfZ3I/BaXHkjEQM+ymQ3i5HKfr6a0lz8W0cx9nImscBegQcGITSAEQJBJn0q8EjcXI1QWDJtzZPMZ0iQGBefRkVlevFiQzYLPZTAyCTKNYWieWhFPHqg5UtftCa8AzMmiLsO+mveqXMAgqeTEILsxlk/nJ6AlPjlVZ7ahOaqGYgMi+RXJInrIqwe9KIe6C5HclIcNPjevAIOD6SdVb31nuEeMPd9yMQYBJz4bJBAYBpFtKfotLZyTlyXw/lsR8WtU4nT1Tv1xC+oBG43IUzfD5AzahqBXmqSyhsaoK/ss2FqfTpeDvtbzE3KZBaOUofJG56cfJSayEQe2Vf7yXPoB+n2rrD/DZS1NXKb0MQiHIdDbLi0N0fRQWFMgQv8bpiNH8F6p81kGd8iMfzyJPXGYU4HQRPgBVJFzu/U7NO+yCdLcpSyuzoe+FiYkr4+MFuUahhTG8CzICjNPqoUYvo+ElXjz4FMyl0g8cCd4rn+wuGQRYV79057AsOId0NwXpv8xqsuNAabVQJ5ewiw8eOOGoj3I3YqrM4fWTk+TsRdbPcM7FDwxCJ8+pyCYhtUII+i6/Qgjj5vJXnLHkZrqfDgtS8jFs27NldZUYXsjAXOMd9pjP1jsmCUk1tQjundjXd7fPQbRsIjH+DrZ4/gwq5C/lHxw+LOQMAvjZHKcsr0gGmeQ4LhBCvjlJLjMOypuamqK2NCQD10CDUJDreGp7cX4e3la10EWtlwnKCFOyr0Ke3hpMTqrLUH4UdCTYvME3O7Oym4vYkXjIQrXomH0bbjabVHdaCU9yzI5mfCYLGecYnrPq0e3vkcRAeRZNSYW2g8eJAwyCdC5VgDAxMVESygKXCbEXPYwlj97mZrl3/AWOXjc2NpDZ3C+BeH+RxON0fTmL0TvY1Ouga0sUXlASt2cMzyzrshN3jnT0Osm5QQ2vVuckaB1PIbIe/tg+t1rwFFpKf/RkXHxfhlB3GAd/3S99FxJBZCaROF0N7CCQWgj2B+MiFJc3ocvLl9mygQ4llr1bSouCN+MuoMYphJIpXiYjfMe78VQqc9KKsI/0qOrixYu4y6sFKZiT4vQzbAh/k3Rm5vr1iIsoM52abqBAmHP5ypWuI5Ra/dWff9LK/tzqqvfHRqeLWchYvHyTsajGjRjhknPT7NzVyxqokqD7bszPI5lOMqnJNWWvpUjyd4kjgxXC9o0aDj4r6P/IyIhjCjVPP8tBviFBSq8WoYOxOcIk+0++qTya5oTfKQwV/sycirSljbgrlfBtU6t5x4rNFLauJNJILndcjyUNNJx6nH4H+6hnGmQ9RzXw5rJQzGEBSQKhVR/uXOh/aVTWJbsfdu7gwO/k8zLQC7mz6nw1qD81TCKVyr8vXXJBggxw7zhZV2SeShpbqdxkr2w89UrCutEuBNWCPlG5jBL9Qbt4M6jseQRaMOO+FddwGXFhFpvh8/9ZGemQTNgX5ZCUjxcANE9VeRNND3iJaVt410NfQORp3v2PNg4Q3eP1dS9wHptojvYRCkUzcQNVZDN78uTJ02fOXCsW68x/1Qi6iX7obfSvvQbpATLmfAfn7YZjJwHufUheGeHCJif6I3Wr8Ykcveo8+8nkmSXy8AdB7Lx9lQnVE09L9CCTpKV6MND8quRmxakmZiU/lXiP2lkdAqqHS0tLOPXFStIrYUY9pFFhi3Dj//r16+WPH+E/NmBsbwtDhj/T2g8rFu+Kr5k9JeI+sW1M34f2Pnr0qL81KAQaDjNIApyFJx5mPd0O8PDxZ1Ms5KdPn3748KHDmSDK7MPsJCqtKjHF79+/zziFqr31MVNgPJZL9gonPKWv/ePYsbDCWI+BxM+Pbp7r4SgCbbkRTs1p911q/n9/+YVqiMxrFUlE6Jzk/pAppgyd0ag6PibqRa6rZf36228vXrzwOZJYMtpflaA79HB1YWEB/mB+McZxIk741cu12rUoOsWe513ZNvAq7O/i339Xbdf6qxpD+ZFpkazHS0l5TKMet6soFJ9l1u0CnzB3H6es8Ais+PWPP56/fEkaqHe4uirKCDN5ebmMLEtwfhuohyGUvYQ2SseYBAx9XU6v57VcjTGPYhCeeZ9GknOr1aJZ6d27d3l5QpgfP37EOhbGLNWEoBOTPuX0Qxc49QkUmzaQhb7s3NvG8AQp58+fpyb867vvzpw9i4SqNDyv8bW3d+ycmChHES1mzqyvx+yHOQx7cKhvNIjAIaOeujigc+y6sNBuLy0vP+KpH/KkauPUHbX1s0+jgQyYvcqKeZOITZB2brvdjjkXUs+n2LEwfKohCwacc+afQiWRly1mh38sNkhiNIqRdwmk4npShyQUA+XmAh/4UGL09+koIoFc5/Bn7+7Saj18+BCX4B2OOVpdW4uCg0Gq/8PV1Z9fvYo412EfqAaeXl8vC1/BFNtJKvEauz1c4IBrWAO0Ai79Bw8epLFFyjODxbMsiclQDLw9DxtLRp4mmpWVFWogyQoaEgk8VQIv8BxTsNLihCx5OOP0LyhTqJMtPy107927R/YZpLLeT4zdHpB07xorJ30HnC1Ii3P27NnR0VEXeFHmhamgryFZZIFDtpELz6sHqwqpPfQ/ZubnI0eOFIRuouubdwTDGwRkj0K0UYFpXhYXF++1WvTzoN1++dNPjvuowMqzpTrjy9p3x48fHxsbo30rFUGzG3XHxsZGJx0P0knn9QNeDUpqk/maon9tuz7Vv6zEjItvP/1OK72fGTROqZfRrmfPnkHBoIFYGg2Um0pAJXbixImx06fJUE9OTVEHURHqtxxG0HSV2LA9xQbh71Li2uPHi0tLt27fnpqeps/WwsKypNLoGtQDe5jB4ELTwwctooF/9OjRU6dOkYZgpFDpz54/pyUZ3ICdZBj//IGDl+CdNB5p1KPvqNBiFFEFinx5AU8DLRqRTQOps0M3sMOHD5OVIyNAMxpESi16+/YtaciWxPX52AKFGtOw00IIlj+UlXYWCQLhctuoiTY8zJ2tL8/nM8po1zwjv/XOFEFfwDfDQdE/PUr+qWHKAiCornYsb9Y+X2JfB8leh5EY4oOGHzX6rEYBaIlqnfINOXDgAA1hZOTcdnvzLQpfFVpIqtJOee51ghgovDPfZbD8yDm41aLxVOblmU7ctnpsD9smWVVZDRxQ24AKajdevieA6BCdtxvtIolh0K2urn4BiaH3qawvVmIGqiEQZhgtuHvAEk4XhxqVvyPl4lUq0i/Tojx2inXZYDDsA+wU67LBYNgH2JJB6JO5yWAw7AP8hw1CZQiDMMYrhK/NIBgM+xfx3bsXlpejRqM/DTsMQsEMgsGwrzHNbvMzzFrmo/gljDTx62OeyVk2CH9rNv194i6npzcYDHsIXKD4xDRkEJrNCjKtS7Jyx4FyNd4yHPjmG/9AjobdYDDsG8AgnPv22ymm7k8FoXAcis8LVqmMnjy51zU1GAy7DvWSqt+8ubK2FjEvB4II6Bf6vOxc4/btc5xNwGAw/BWgeRa+Hx+fYUSlkqfFaDbzXDoGg2F/g6zB3NxcQZLhqvs0EimaQTAY/oKgvcPIyMg0w3F+qMXFxSdPnpg1MBgMBoPBYDAYDAaDwWAwGAwGg8FgMBgMBoPBYDAYDAaDwWAwGAwGg8FgMBgMW8L/AUtJlu4NCmVuZHN0cmVhbQplbmRvYmoKNDcgMCBvYmoKPDwKL0Y3IDU4IDAgUgo+PgplbmRvYmoKNDggMCBvYmoKPDwKL1R5cGUgL01DUgovUGcgNyAwIFIKL01DSUQgMAo+PgplbmRvYmoKNDkgMCBvYmoKPDwKL1R5cGUgL01DUgovUGcgNyAwIFIKL01DSUQgMQo+PgplbmRvYmoKNTAgMCBvYmoKPDwKL1R5cGUgL01DUgovUGcgNyAwIFIKL01DSUQgMgo+PgplbmRvYmoKNTEgMCBvYmoKPDwKL1R5cGUgL01DUgovUGcgNyAwIFIKL01DSUQgMwo+PgplbmRvYmoKNTIgMCBvYmoKPDwKL1R5cGUgL01DUgovUGcgNyAwIFIKL01DSUQgNAo+PgplbmRvYmoKNTMgMCBvYmoKPDwKL1R5cGUgL01DUgovUGcgNyAwIFIKL01DSUQgNQo+PgplbmRvYmoKNTQgMCBvYmoKPDwKL1R5cGUgL01DUgovUGcgNyAwIFIKL01DSUQgNgo+PgplbmRvYmoKNTUgMCBvYmoKPDwKL2NhIDEKL0JNIC9Ob3JtYWwKPj4KZW5kb2JqCjU2IDAgb2JqCjw8Ci9DQSAxCi9jYSAxCi9MQyAwCi9MSiAwCi9MVyAxCi9NTCA0Ci9TQSB0cnVlCi9CTSAvTm9ybWFsCj4+CmVuZG9iago1NyAwIG9iago8PAovTGVuZ3RoIDEyMjAwCi9UeXBlIC9YT2JqZWN0Ci9TdWJ0eXBlIC9JbWFnZQovV2lkdGggMzQ3Ci9IZWlnaHQgMjMzCi9Db2xvclNwYWNlIC9EZXZpY2VHcmF5Ci9CaXRzUGVyQ29tcG9uZW50IDgKL0ZpbHRlciAvRmxhdGVEZWNvZGUKPj4Kc3RyZWFtDQp4nO1dB3wUxfef3b27NBJChxBKQBBpCkFAEQvyExUQgZ+CiqiIPyuKgAUbSLUgCIpgQ7GAIKIiKCgiWCgqTRAQCL1JCyWX3G2b/7yZ2bvdvZJLLgH+YR98ILnb3Zn57ps377158x5CDjnkkEMOOeSQQw455JBDDjnkkEMOOXTOkyC63G63x+Mh/7rEs92bMkSSx2X+VXB7pLPVlTJGgGN6wzYdu/To3r1Lh0vrJ/HPHIqfmo5euCFn3+FjJ06cOPbvvu3r5j5dHzlSoSRo4EkNW0k7NBhJwtnu1/93ElBLjFVV1YJEfsO+25D7bHft/ztJaCD2Y93Ms7qu+/AU5Cr8ZoeikYSGYBnQNBHWZfw2cjkSIT6S0IMArYVrAdpJDrTxkoTuDQOtgsc5AiFeElEfrITh2jHOMhYviejWsNAOd6CNl0TULQy0Cn4Wec521/6/k4iux2oYaIc4XBsviagD1ojyZYFWU/FAB9p4SUDtAFpsh/ZhB9p4SUCtsaZjG9dq2v8cWRsvCeiScNAq/Rxo4yURNQFJa4NW993pQBsvCaiBEgKtquf1dmRtvCSgugW6ZocWn+zhQBsvCahmXhhoj3d1oI2XBFT1OA6F9sj1DrTxkoAqHbaZYwDtwQ4OtPGSgNL2hYF2X/vzBVpRKr0d1nI7wkC7q+15Ai3gKpVWaEDKP2Ggzck+X1zhDa7MRMhdOuAmb7B5FQHarc3OC2hFlPm7b/nTdREqlYChpNUh0Cp4c6PzAloPeo7wEV77fC3yc8nL3KTlYaDdUL8MQSuIoiRJohi6j+pCc7Hf78d4w7AMIhZKeqM1cVkYaNfVKivQCuYwILs2IKFfQKmXFYy3j6yBSjocK2GRbUsXoF1dowxF1CVmNLioaeOGtVIQKJsmIuy8Dvx+WFc0jPc+X9X2fbzkmRcG2lWVyga0Aqrx7NKdJ7zefK/31J5fX2lm+VYUE7eCegTgAsL7h1ZDJYmu+/Mw0C4vXyagFVHd37GZ8ruZxyUKydup5gnDp+Due6ImMHMJNe/6xBr0pQO0vySX2PPPJkmoD85XCGiUNNWHfw0HrY5pOBbWVB3vHlynpIwIwTXNFk8H0C71lAloRXQ3lnXMYtrI0DT8i3mpCnAtHTVcocGC9mT9kjEiBOktG9fCXvmPRMLH/+yzThLqjWWNsSX8VfH3NmhzgqYoA1clqtimZxuQNShuFUkQJ4RAK+NFZSN0WUI9KbTGyDT8rVUgsGXMNHTys+LDeOOLF8RvoQnC2DBc+03ZCFR0oa4WaFX8lVlfF0XXRqsDhbG3TDh381gQC3FNXRENDwPtnLICbSesWKD93AwtWU9Wh0ZhUHBljHPGEPM3nskromfCQPtZ2TDGJHStplqg/cQyMAkts4dlcamgKyoxIkaDhVZscEU0OAy0H5UNd62ErvapqhnaaRZoXehzrOm2HW0DXPJO8MFh1UFmFq91AQ0IgVbF75UVaNvnaRZorWc03GgEqFtwQThwNQLugWczimtEiOg+qzVGufatsgLt5bm6BdoJNoHQwgvrHEMyVORSI2L/0NrFMyJEdFcYrp1QNoJnJNTmSNCvB9C+bF1EJDTKjzW/GgZcysoEXIVYaEOJtuAqMrgiug2g1a3QvlJWoG110Arti7bpKKC+v8hY9TFwrdiajIhtz11YdCNCAK3axrUaHlVWoL1kjxXaZ+2qj4jS757nw1o0cMGI2PRCo6IaEQK6Ccxs3Qrti2UF2mY5QcaBYLbBdmgFwsXluk33GuDa0WXgygTcLaMaFs2IgIj7UGifOReghY2X+J4goYs2W6F9JFRhFwm4ie0mnsDhFzSThbZ9bFZRjAiIuFfs0GpDzgVoKcVlakqowV9maDXtvnC2ELxB6aJRhzC5gLpu7aGbzIggatrulzJjNyIg4t4Ora48evahFVDl+fNvRHGBK6F6qy3Qqn3Cm5kCTI8aT+0gV6m6HglcMCIOjM2I1YgQ0KV2aDXd/+DZh9aNniTjXHp9Qhy2pojqrLRCe2tECx7eYIUHNxL0VC0iuGBEHBwR404ERNyrmg3agnvPPrQu9AkVfos7lQvdio2RRFTz56DWDsZQt2jOEdJI4p2/5RGVQNVDpK4BLlnsDr1Qm0mR6CSgpqodWpzX5+xDK6EF5CWrRMB9e2MaQbo44Iqo+hIrtDdE3/ODNar7vGNkRVPCcq5hROx/nhgRUiF6roAuLFBVG7Snep0L0P4IkS26ImP85U3liJZUdHBFVHmhBVr5msK2U8Houv6jAxj75VBwjW0eoi3sfJboue6o4Iqo3mnNDm3uzecKtDA2PwH3i1uSkegpqswVUYVvTNDqWG5X6E61AHi1m5CDsU/WQxXdoBGR82KT6BaagGqbPBgc2mOdz757xoCWcomC5a9uT0BSEXslotQvzFyL/a1iCAIQ3KSZi4dvjMq5AO720Y2jhTMJKPPfEGiPdDx3oOVTUMP5i+4gYBXJjhdQ8mcBaOEt+ZvHpMsJLtJM/QF/EASVSJyrU859pWHkkBABZey1BH0BtIeuPBeg/YFtrnBwCQN5l/bmKmiMJKCET7DPJBD8F8WoJgvgRax++8+Q80jnm+1W3V9ny8CeMYmRwBVQjR0h0B44o0HhgigA2bRXCX3DD2bzSQlO64JfewhFMCIEwfVBkGvJH18RMphBuEDajYvUoBVhA5cbEW9GwkpA1baEQLu3xZndG4MgTfurd6GPuUAIcAko7MqvXWI3IsgLe9cELdEqaxfFuAO7wHXFnDxuAIdyLigweH+DCPJbQFXWh0C7u+kZhNZ9zbB3Pvn04zceudAys9zoRewPDIiDSwOzfro+hfFUoUSwmRzwRsPATlYrYrQctJL94XGIq9HsKxoTuVrB/RHUKWKq/2lx2EIPdjY8Y9F0AupvtHz4MkuQAGpxiqwiAWcU31Kh5tD3MRoRBNqJFq49VrHIgYgwQRpP2kPEghwKLt03eD2iplpxeQi0OVlnDlppKs6TCfl9stXfJqD7thEBYBoQl3jggpp/c/kYnKdkyRsHrB/g2kNpxYjxBKsra+Q62G/QQrfQILYhkgKW/pNlcwx6sLXmmYNWeIk0T9vNt2UKEFDDF9YS5dJvB5caEV/3IuAWZkRIaAz2BaDV8d7kYnUS9OmMQUsJuD4N2wSughdH9OGWX2iDVsVbqpxBaEeCegSUf5dtZpEB1XmU6D9yKLg+BWsL7irUQnNBbJCJa3ckFLObEulZ5b5faxjbxa2Cf3VHEvypX4dw7ab0M5ZlVUTPsYVGx3m97GqMiwyoWp8FVjlnGBEqln+42xXdiHBBbFAAWsIzxR8WcG6FrpP3WiOZ4KHLEyK5GFPm2HbLVbwh5YyF14roSQPa02EyBYCcS79hFvM3YT2oLoARoeGCn/sK0XynLvREYHsKeOaveLrqIS+67Z/YaroStJZ7InFtkslg4dCuTzhj4bUCetSA9uSN4ZRv8IsmtX7fS13UXBzw9QyMCN/K26TIRoQLDbBAuzqejiLUcd5hv10iROPaxGkh0P4hnkFoHzCgzY3guYCOi43fOAZdM4LjDXCJJiavuNkTaU/FTZ6umATCiuL2kjzddcMiL4fTKmt/kSKh5ZkaAu2KOCOXhdh3BUTUz4D2WOTj7PC4WqP3Y7a7YgKXGRG/dY1gRLjRvYEoUIB2abGGA6pt+Vt+05hVFqIhfBspYlZwTQyBdln8QeGxBklBvkwO7ZE2UWxA4JtqT29WIDyTAYuN/1TCustuKh9OLLjRnRZoFxVdraVsktFvDaYBYOH02ukRnQjSqyHQLo4zcjnr1pqxnrSAfJky3RfBh6N7LgTyZflHfs0nmm4wGEMPGBHf96wQqjG6Ue/Azh9A+01RoRVg1+GCx/+G/bLwrlsNj45kjQnCqBBov43LhSAI4/AfD9WOLY5HQDczY5CYSo0L0aZFMoSkvl+dIosXDd3kk5OGtxAj4ru2Ife70H9VNXDGBqLdi7SG0B2Hi4cTo9Cv2N0zhg9BPdk7IrTohRBov4rPO1PuS/KY1QNjOq4tohvZGq7hgxcUaqiI5IHumz44BuDqAecCs9D8+LcQLciFuvs1E7Qzi+T4AmCvmLDb3JgNWKzm4+3pkRQEAT0VAu3s+Ny1qfOxl3DRmqcyC7fzRXStAe3+2jHYgCJ5oNRuwiFDLJikgvrvdfZ+u1EXr26C9oPYrUwqCjp9cIABGy4uQaebH8d6RwltGBQC7SdxQruAoAXHtdcPrUz00qjiTURXMvVIw/tiO3MN4KKLR+4hUkA1GImOVJMH2KemG91wEpugnRoztLB43TTnCIiC0C0cY5dBxXmTW0SeCCLRqn2WAFuNvNy4BAKB1s89nH8PLR/dcS2gtnQNJ63uqRzr7gossvUGb8U0HWQAXBUPC7GUUcdjZmhfjxFa8IHfuuQUvL0Qjg0oJhr2TmkmRemziO7HQRMD7lPJy42La1P4NirVVrY+mRotSEpA2WwN1/DO8jFLQrrbU63/xiA/UQ3z1VBor/7XcPQDtC/HAi10NuXu1X62LxYWWOo1Pj31oui7SSK6h3owglUZVPxGfNC63sT5GhsO/JczKD3yroCAmrPzRyreXqQz13T7seturJmgnRgKbbv9ZmhHFB6GAF2o9MgWHDb2ywRs7jsXokK26UR0OwHCdDfpYnxHGUTUgSzhKtsgoBtaOQOqRjIiRNRIpuqRhje5inCMiLoW2r+TaxIIMN9DoG2z2wztM4VAS99X5lO7KFOEBRZYj6wih99uggpNnSCiS1UiqwkRDZD8S5Yf7Y74uFZAjaZuD24QgBdl68O1whsRIqrv1Ri0f9k3dSMTOMQqdp2Pmf82AO1LdpZwoewcM7SDo0IrEhkuNh1xMLhDHkaPVYig2D+1aWwxzOITp7CZTr4d+xgjPBGhhqOIaajwHQ84abFxQFY4I0JAdVj0job/iHWjFgIwMvsuAUPBWMaYIHs2lGubbzFD+0gUaMGr7mo/4UhArQu1vFjQzK43WsQeeX/Z2HkrNuTs2rUrZ+PKb1+7MbabohE45WsP/DkILnRp/ZAwx7UFVPsIhzZGpxD4x+sPXAGbD4Hhsw1Jb/9Qrm28MQitjO+LCC0A6+46jQiYsGcaTMCOu6QIh3HguioXZrdt26ZVo2piydTIcxH+qdF3obH7gnU4abFuaB17kJSAMtlCo+GlsTQMz200fI0BrMlk8OH9re1cK6EGa41NVeDavpFCBkinPL3nnDSAteJqWF5k7u19pWXRTjm5TQap6CmhwBm643HTbI3vvrDj2n89n2HVxARUcxeFVsU/FK5Ng4xtPukfy0avseeA8fSQB0go6w8ztLeGXdJBT06+d4mXWMuRgYVNjf0jmzJxVBQSJBcUJXW5SjIxJoCbfNX7hF2ZY08H82nTsErm8QmoxlYO7YLCuBZEcevpBwDYIMfSgYM2tGNA+ZAlW0KZKwJFv0gz3cJAC+pW2qNr/Ny6s8pY3Yj3Iuva4RENUWHG5ZkjUJHczSeepE5k6DRoZB+lmgYooGobuUD4MjrXwj1XzTnFogcNtxfzhpOH7ny8chhlSELVlxFoOan4hhBoYQpVfmYLhDwZTnYzsPQ32sCR4Vm2hGxnm6gKXnvU3kBX/fiw2fknoKrskIyGZ0fzhIPp2WmhbLaQuP4Ov297PC2sySehKsuwD14rgYfIjOvCcG2tMXup2aoHNbkgsDoH9ugoojwWe5ubhgsW895CHkyU0KGbfXRji8xduZMF2sosekeNkoQBpFSF7suwOWiQS0BwnvzzRGoE9Z28kQ+oasYyJvnq2S4TUIVxxwNTKoway3JOHBidiYqZW00QJa5Skp9Kg+fB89l3F6R1AAOiq4k9BVRxGfU4QBKG8Ge6ILgr8+7fuSJv7IyxpYWobX8NTo+8X05UhBm7juYTc8h3Yt/v/w15trSYLQQBN48NWPDf7YSjYsVkWLp0CYmpqalJ8FO8xzdDCI5qJrR+eg/lObLidLFAm/49hzZsEgYaoH3BgDXsUIwlyoOqHOueqMg8YREIfGRd+g98YvCDt17qsjOeC3VTQiMQTQ2Qte2fV4iMLe5+Ful7g+sHvPTujBkz3n91cOdGqGQjQKkyfv27p3iHNdnb0aIKpM3n0IZ4APixgubD/mbHCozVm0EM4/59ULXCDCNTdE1IoI0bva6r4YBlDRDVZsOI+nGktiVW/MsbTQ/eNrl5CWILjuvEnl+c4pFoWPfi/dkWaMt9waF9OQRaALatcRjGyrIw7hUP14hl3C4Po1DWc6GvYCaFAxaDr+DvYXGl+xLQ5VvhQT5OZJLt6FZSZ5sA2OR7fvByK4fN4ZEey3KZPINCq4WkgYAX0OH9fdZTRnpg3CvvrxFnmjOB5q8NK2RV0s9NQxrElQFQQHX3YK+sGc/XNbkAH7q8RPgWtrfTHlthKOMsYmB2pySryEuajgt0jYD+fAjXdpl71HrwkK308Jjf76oa8348GEKS5ApZnwVRWIOtJz1ZA+Cr2/ZYVrx5K4WJmMbbGJ5w0FHAMIpbT6DK+HMbVboEY7p7gfGX1yTYtRj3ZMzcxY9ZoRUECPRS7Gos9a6vu7NCrAd3hQg/w6+i629TdFxAjyUd3T6gVvzreY1T4OQ3TTcQe0c7xXsEB0ZR26SMQ3+1r6+UQnbKBNTkq51HTxzd+pY1GN6FuuoBR38wiA7ez5o7yhUh+5bY8uHxH8/5bMozXVLt3whJ2wLQBoAl/+x8tEpxM1CZno66B5Lj8ncHEjF0A69oBLkbmkzIDSjj4JjPn98eRdC7PVWrV7JziBu9hm2BHPR8k7q2b2LsaqaE2i4NjG3Pg9bbzElWMQ/PI2y2nSjKxc6bZm56VJjqz378SZwSIaHNlHxqO1HXBlkTTizoGGlrl9gEcGzMJt1daKZxuIkzFDi3/H/2SywCP7lQu4NENMMGigoi+wXLOwlCa5h25JctTxayBx0rCegzcI7YsJXx97HvroYhqcPHCvUlGm7OY192jHxkRrL9b/y2gC8xfNx+FfuX9/MURQKKqNw8nBcYGGHJlpGyLsObI3Ni7dCKxTcQ7PRTSB1dgHZlLJEsEUhENwc84ESd1fGRWddHPeCe2aFbj67tbOnJJbSYiyoGrIyVJf2TqGUXM0noqtOyGtitJrPoLcv5KUPW6lwtXDGkaryZVc20OkwuTBn/1SiOvQbPO/iUAayKc6f9h20ORKJef5wgFx+Z19oyUfjxZ7a2QFmKhXeXQ0LRzu1DsXu/aftMw6ssvmLRtYEdVqWm3fLHYrJAYqf19gyu1B2/OZ4iOCzEQ6dz2Pt2BwFJ0U62t1KoNCQLvyUuLXhonzLUd33Siz5uEY0wBV0BtHssB8ck9CtWuQXyy30xB67GSuvDcC2BNh5jlwUm6XCMY/IVLrrxFJEENBKfVsGZKvssaVkMrqXBs9/2qFCMmUre6SRz0BX54YhF0EGhFhWD+rLqzurFSZsYncJz7aZ4TulSaEl//W9nuwpdFBK+YNsARKEcbYV2EfQMmOpH0EiLkXxGQAnvmbmWQHvUIujc6BmaOuiP26sUO3VQFFoVdhlbG+m4dCyUOh8XED32gxahBkIoJS3mm1cqnmRWpl1oNjPFlnaJ9bSznQSU+L4N2mOW8GgRVd9NTLvbowROFZ8EtDDsMvZLPEVwkmZg/eTHTVFMjvmkH1nAvT3UzE00bqJo/NLZXWw1U0Dut2wC4Wg9y7gE1GDQjUklo8faSULv2fVayrXfJBbfzhPRnSc+bB5rfxPnM5vFntpVRI12n1jSrSiZJUJIQC9bljEdHwwXaFo6B7kkNNBeE4taY1Pj0pulmJ0nRAGaiL1UHsi+R2y+zIaXxTlRRTI6fyBMFDSEDSEujGIlEIut8TYUWnPoMmxpPxSXD0GI3RsnoE5gX/iJSbDPlipAKDxLWSEkoc7Yr5lMBmvxgNKm5LWBGAijBzLe1iC+LhSFE1zP/0tf6abb7eJditfiFFHN35nLlBIxubufQWhF1Avny9hMxDIZcebS+hD02vZ7+rknbos1mVFRyIX6YFzg88uKTGaGgueWxnoVmYQJWPP5FZXFQSh+H8YfJ53BLgSyS5UKPwl37Q1y7dTKJVsGrxASkWvQQaqyqyr1POPcYXG5vYpMgsslSVLp1LKFvfJ+UxauWv3H0k+fbhOyW17KRFBs/NDnm0/ShSxv+7wnGpeWOnI2CHRId2p6hQppRH094+MCh3pypTpNW7dr27xu5ZSS8LCfSxRQ38KVey11ska8lh2WPUdIEESxtALqHHLIIYcccsghhxxyyCGHHHLIIYcccsghhxxyyCGHHHLIobAkQhocd0mdgykSQQ4eV0lHh587ZMQ4Wk6uiYFsGKVJxuG1kjuFc46Rp9mV13a4vL7pExeMVYh2jKdECNKLXtSyxYXppdzOWSIBdfx12/5DB/dsmp1hxBGR/xIrV0kuZvqXmElCLees37wjZ9OaSTXLUABRgEQ02wh/6xWICu391fqtWzcsuKd0m3ahV42m+5/94pYlTwRa3QehfX58B4NWRP15lKr2eGkykyCh17FXUVXZhx8om9B+Rk+UYxXfxrlW3IplVdNUGe8tblGrWIhAO57WPNRUfF/ZhHYWO7hiQCuiel4jJ4K/USmyLYF2ggHt/84LaCXUKt/IiugPrWpRcnQ+QtvSyw7QYOyPkqo/bjr/oBVQtVP0PKmuqqerlKL+df5BSz5ZRI8/+WU8tzQ12/MQWgE1XsaUr4VZDrRxUCi0hKpd0//xgf2uqFiqLZ+X0IrBL0uRzlloLelvBRq7DxH8ka8l38Eldn9WWK4V4fiTK+Q4LX9OhIYE1gsEjUT0mtFr6KWgi0SDlvYVLhUiDYs3yq+KdHhB4E1GRMd+vfl/Og6BeT1DxyTwIzGC4Re1VoEIB23wlZkfxG4TXcyxazvdYpSXFI0XEjYXNesKO7AqSlGgFW1Pi/Cu+GUSH1mYI+DsCoE/J4YE2QJyV6+TVSfDTX6CmzOufHj05InD7squaD/ECHkWUblal942ZMwbb417cUD3i6sLyJr+IQzXSlVrZWXVqmo2F6BTVVv0evLlyW+MGtC5USWLtKAJq9LqtL3jyZfefOvVYQ93bVIVhSmKQPpS6ZI+z70+ecwDrUlPE4KGrhVacOMmZ17W5+mXJr/16nP3dbwgPWxWQBhbSt1r739xwpTXXvhfh6ykkBOccLIzvVHnx0a98dbLT9xySXUphmIvFV9eu/vg7vWT68C5+6YvGklblaX9ylkeT56V3OqhWTmmE8GrX+ngMl0TTkPIGL98+4EDOStezzSj0uq5FYHDxad+eqappZW0NgPn7g62kr9yRLuQtyyh6o8sVfgVS+8qj9DEsNCS2zJu+dDU5yPfPt4EhYgM0mq122ceCFy1/5NbK1kZl/xct/+C48YFytrxVxV2etaDeigsieZ9BLqBe2jSVvIHKiJ+19ySGVzqOfMoPeTt9/ngL1RC8L1rKj0YCi1Ux+bW2HPBha3SyMOQC5Umh/VDAplDA4LyI+nOuVB8RvFDJ8hfyER68nWrE5ZIkBtXsfSy5BLS03ktiUDwhULrRin3rgw+zQdZivG+l2rZsCUg3bmcpmr2sT6RqxZ3QZbRo7vWQa423mlIM/lOuejQulGPo7KsyF78EKr/OSSc5UlbNX8B3nN5YEQCqjFDIR/6lWBSV1X2KXhLMGtcKLQSGqHkQ/0jrzqKXyag+j9jPdAMeYo/H5/IMri84df05VlbUfGfDU3TjyD7gIwL/IGe+vH+DiPDcK0HNZlHTGyf+WlwRnzttZZZIKLyb+vYR6sVsqsUnx/Lz7lN+o3nTfIgv2p0WoOhR65fyKHtfpIm/sU3V/0dKywDEkvZqufj3aa88p8Su4o3rvN0/QSFPLwl0+hBOGhfwCqdQHiY8QYqrMIFChsA8CQ1g48ahblTFmCfMQA9UMNC8eJVpixfEroL4wLduIQA6sNHNvGklSZoPejyjYRhTYlO6I9yAT5hTSdfYRG/TKdvlT6yAOORgTPXLjTJuEKR/TK9xC+PKQzaHiexBgmMJy+j2Z0objxXYT6eHxhPFa9CAdEIj6uqKvN0ytiL3zCuCQftcPaJgl80TN93ABW4iKW+VYF/T3aneBDjTWOwk+dDnkrZqM5YgEcGWEhCrU9DXieWUFOjHClDyVla3yAIrRtlb8N+nqqdPFCWVd7nfJx7WRAWQfwG++jdqspeOc3LIiu4P2/TjfpAbmTduIImcQybIT0MtDrkgYaWYcC6aiSk9+NbjZl6AU3GotpYAHKzK434y40BWgm1zGfvjbSz4bsFP+8DHLVTBrStaSFONVifTtd4csbj1XgrAkr9DufzRKOM6TWazlnXLdBKqOpvRu5A43maZmC7sqrBkRIayy8jsG1bNH+Vj3OWH+c2ptiKYuJaNgzynD0/zF/8j0YLgMQGLeCk0VsZ4/PqPypeb4wny0fL1eD9nw28qf3lHe+Z5cds1H48hL/cGKB1ozdYAk8FL2ydkpSUUrn9WLJ+53Xm0GZjheK6e/ojna9o1/H+eZi3ohIWcvFWemIf6yHMr9xjGqvBybL/BKF1M1WXAeJbOmHo8A+3YyPVfj4ezKGVUNs8NlwV//yf1OTklKzhXpausQC/SUfhQbeept1QcE6v8klJyeWbDVxJsBgbk0Awcjb7v+nRqN4Vk70Gtrp2Hb8/FdLMnJ7/3/LGndccYR1Q8HdRZK0VWqI0/Umzp8t4dVXjQan35GypyZ2QWadBIfi8c4rxZc98ymnkkTOMgSTT9GI0z6L80RXVqlz66inMK2qYoHWhqw5zVtPxF03onYl991Dugf7s5ZvOLrSIvWw//tDI+tT5NHu+WlAfLvKg12j+RlU7fZXRL6nDohNdojv3TdCS9g7dygeUxxP7BgqFCOhtfGTmpYgaS2ASutG9DFoN70yMFVoRVdlLofXhQYbGQN5LYobRnZSZ+OC0poFWiGo5mKVpU/FqbiqiFhxIDRf0Z3c1X8FqJZugJS/xTc7cMn4VGSZzs+08/asf30nnmhtdeZyOX8Y/p/IuedAwNjQZP0guIubE50ROwT1z0wRuipF/gpp6YdCSB23PRpIkCJIHDeCpPDW8mdnLAqo08HLIGx/UgZIO8BvzanIruVBoJZS1n97kw48EBZV5sa496BJz8kkRZZ7WWUHZfelM8KHnONPqeDRKJP0hbzntW5pl0wSthJpuZdf58RcegTeRiLp6GRvJ+Es3g3EKq5Qi4+uNjghC+UNcAHwtUhwXcGhnpwRSdRUeEBSUtSo+2MoYruD+y8ghdrxpkO1tEVWL2TVYb8KEbSzQZjJo/XhJOgpUsrA6EaytVFjJMm7hI7V4K0uMJWV7CncLeFDaYipXzdDewThZxbmdUGA3OYGgpLK7/61EASrHsgD78a81Te6UWbza154UagR/TaFV8MErkNsdVIhigpY8RjsdLHAjokfZK9d1by+OtxAo3kcmF03mM4unVMQtY+VaAZIlU1ZX8KcXQM0Re//EYCsCrciQ/i3NqKvjozwNZ4WjmEvfUYH+elCdtQwMDi25mSVpJQ3NNxVOdKHevB6PhtuSDrtRh4N8Gr2ZmJgAsYBujzvJ9TyfGWoDcLKgaayCuIzX/AeqX8a2fxqA1ofnJJpeW3PMChNo2rOWnH6i2+Ny8cnwrgFtdqxcS4a2kNdXUfCGZxrD/AjXT4mMj7eS8hWH9thFTFq3MhK8qy2DjJMAKRhNJoOAqv7EKmNDze7gAIhRedxYWO4hz/Ogh1UoSkaMjrvNHehuzIyrSRseNFRTuRg5PBlWsigpvsNC+2maCdoqO+jDSc/eDApF7m1EYmqVatVqVPmUtQ9cGyu0bvSAyjVVouVvnXF7WmiWXKMVKa1q9epVshZxgcCSrEroFmNG7zGxAkTP+C3QNtjL53Rud6v6uc5YoqCetBuNo4u/puCx13S/iVO3G8awRlTck1zkQhcfwBrPBooPLXqsbky5iE3QzipnstPTlrARgdJj9Ay+zew+fPr3qzdv3bZt+7ZTPA16EbiWSIStRop2sO7y1r1Uzya1oJV6vUZ9sngNtLJtu5drhgxaF0vSyWp1Bp3BdmhF1MKoTL37Ekv+djTPUKzfJpLWhWZgVv9I9x7PNejEcb7Yqbg3NOpG07HCFGLI86zkfHRlDMVfTdDONEOb9Bmbh2qwbqOA0u774ZDXYo+xP7FDS/65AVM+Mcpp4MMTq5raJepI1UeXHcnHtlaC0I5kT5Txh8FXErKBI6KrwWyHrvyTaUZBQu8bPfqM7it8y8SoNQ+w0bDK4gJFVH0HXcioyQwehLx5rQpD1gztZ2ZoPe+wVQCqjXLF1nVPDjMWIRcekF50roWLHjCqPIIZC4JuT0+TJEp67AD1k9haCUI73oD29ajQdjUUhL8rWqGdYPRoLmySJC02VCHNQizmRwnEqmTv51Y+XAtmqX9EYQnzIkHresOA9hvOtWkfs+It5DMoj64oSrGgJW30yIEM7Zoe7OcjvA0BVf+GelxYK5Q0G7RTDGhfiwptN6aNqXhDecEC7StGj74ApS9tqQGt0Z6ZVHyNMbQLf9KgZglnCSjo82Eh25uRoE2YZgiEL5h1455FyzjpVNoUXyDQDzOm7APHM3drkcvlHtyHkL4Is7pxiq2VILSTDGjfiQrtTQa0m6tZuXay0aMZsE1GrGbFcCyEoZ+S+H3EKBu8QYVa6xo2luHxMfoQbLK23DwDWlptVEJDqNkIXcLH/lq24PMZn32aw4tOFBFa6FCzcWugLA7XMGS8oRJVe9EYyFnLWjm8bun8z2fMnLUPaxauHRvkumjQXmcIhJwG1mXsU+P+N8gyJroXMq7V8KLXp06x0FtvTXk+GE0liqjSoB9zeSF7eot8VVRsTdB+nmqCtuIGQ/maQPQ6ooz9q3OPwZFJnbOYdfN20fVa3k0y+Iy7Z+3FXK/SFe1J2kqDApn7qva/3Kk2m3BfGnotV76eNNr4PTgKAu0kG7StWd1qDR9sb1a+BLSMaQgyfhpJ5L4vySugdu5NYfExLbDkKUldJ/+F2XQwLUOFQzs9VQiuJg1l5tvUlEFk0EQdZXVVNf3gDYgdJHJLHxh6bVGh5dVf2ow6yGqXkMn1NR3nk+xlaTinDaL7224hfb4FWsgYzPXaoxWCA3cTk8EK7UXHdaZ85d9jMRmS9xsOu/9Sa+xtplb58SCU6HHbyFoDBaqIogYP/wnaLXUC76kbTQULQOvHy8oFZpiI+rIB6ZiYv24yoE+pE4P0oD9yeaAUFvlsWnG5ln4FNm475iAgX/9Ba4V8ZxhB3VGCW6LxFOWs0IqoPVvWyFu/NrgrJ7g+ZWUmAyZDrfXMVlXxRBPXSqi9j00UXb+QXOWBHSadctbrMUQWiGCG1X6PyRoNH+8azRke9HzJxCYPuHOEZcbCeaQOGQ4tUgmSS8utFdyZLRa0pjgdlwtdvoe2Qyv3uJG4GTNO25cQuCz1Gwu0AsrIM9T5dwIaNywFVI0NQltuFpsRCv7NpH15yCrG7ayNKfT325lnzY+XRzm6Y/IfSW6U/h17jbplQkSClknyIchwD3cOOBWhYBJ58lruZfu7hnGnC71XPK4NgitJLlbTVMFbmpOuJO7irfwRlGGJX1mgJe2uYX3T8eHaXOUW0d2YiwnD8yWip8GpAJ/o/VBiYLT1DjB54MfjoB8u1DiHvU6/pbI1HY5oVlyDP7rRQ9RfoesF/WOAljE4HkpHLqDMHYF9kzHkEtJ3Vv9T0f/l75YwSvN9xdEQLmoSRFcSE2dxaP9qQNpJ2MnmBt7Je0wavjJXN2sIpJGJgU2GOSAK4FH3aWzRNTsVO/oVvl5B6RpazU+CRVFlJaXwFYieFUQ/BLxa1Ym8Z85puBgFDHAB1bg0wWgKbnmUVuPQ8KlesUDLsZ1el/TLfekGXi+MvN2LoQEJLWGfyLAPSYh8dtleDn8RoBVR+Z148oUwsUXyEBG13csFwk+p8Iz1xhLVAblE2kqnXGMPJABtRw4j+ftBFSiCVPNNTLfkrVxbaRGv9OLHv13ER1v1I8wsFR9exCpxetBAVuCTYLsww8yfSU2fX9YLfhFgpVkI9Vh5hXhDIOBDraOpCAGuZUY9Pv7+oKdmKUbZRIX7QNzoXfa2Vbwrm95Xrt/pQJW62KF1o3bHiAE+5aoq9G27WyxlHAj6PwRUzDGWsQ0X0FYqDJKx1RojlLbRcMFreNe4Bx995zDthWZxhZMxP4T5hr6CD4+5sn6deq2HbMfMOUimf2feZ6HC9sAM3fHQBZUSJcmTUjnr2qE/kbe1pBwdRPkV5MXN71mT7tiJmZPYg4m4TogWsWgWCGAEsZAJlZdl9FN/JVzVS+WbT/jI6M6XdXz4R/C4aiwUoCjQ/ieXRjv8Oemxvn0GfngcbAM6tbqR7yR0f2Cu733mxsuvG7gcsxgJM7Qi+l9wr5yZawrlCmwJ8RBR+mJcwK4D8/T4btg3ZcgSu2Sqm3OnCz1mOGjAGlo8bfxrU2f+cghQyNdXlqeDqPI7hhLYO6Y/1a/PA+O3ci+YQkztaLauWSDAmCGiy89+18kIpiM+R5J3Gu9WpwEVcLFhlkSDdpgN2o7HCX/5DStWY1Pbj+eD11ZANY8pqjFK2opfZ+CboBVQxVUGtlj1+WnsGeH73HwL15J/rz7J1WYyKkBP8Sks3sOLVwfLW7nQAh4wglU5EJok+2XyxlZxaFeRYcuBAECF+9L314q6iWPi2lO0KjV7Mg+E2JQRCDG4mxr30L5PVhW/XyejyWd1YoLQzjagvT0817pQu38pc8kQjuf3saJePryvGfPmokHYyycxtOKTwUzyylaBIKFrvLoPBypeAzMW4G+GwMaOOXrGA/FLPsxjD1RFYd5srObjbc1M/n2U+SeLnoHveEAdnb2y/msaHVb530CWk1dDY/KYz4EgfU90n63JZJg9gnIJ5j0mPdiVbQqnewf7OPJQDRjCw5YOwCxOrYWxNzYD84OkvQxon7fEfJFZ+i8u0Pjb47PZj49cx0YqoMS52GtpRcVzn2YRHMEqeeQ9y1T94UGQUC9xUeqNeQCtZjr+7EZ35VGT30waufGXJuaZLKIGS1kZVuuFSh6eBVobsXC/xl7V4Dn2Lv0YP1OIN9xk6H6UcG8eVpmzT1NId1dZTtElvQn+KJVGhGngmJpd6TKcB6FUuLXBtfNwvkI+KMB3GdCOpZ8o+fgl7vh8jO4vQG0Z8B6qEE75W3bQ215xBm2FCiQNvnxX6olPy7JfP9Ys4JYU0O0nwQdFr6KPGJGG+nv9PsWfj02VqyR02TIaoMWa0zUaRFYwKt0qIyVU9aUCAqWsUO8wLXpDK9kfuYoOzIWu2A28pLJ62BqdAPv7FLala4J2dhq69IfgW8sdXtV8s4DE2zaZXuqWvinoSj4dGxhcO9oQKO3ZrRK607j+bq4PC+1nerGZNg2oHHz9Ikq4b6fpy9X/TUS92Y+Hq5vscNR6vumqJR3Jh1cfoz/Lt5jmuogq9ltlZcbcqdmCXWUizbd5N9fGtf5fhjQMtFf/5X2WL4+Ob1RoVLjZqUg0veQus/fKuq4c/H6gfc+K/FKpx/RNp8lb9e2Y1asSzJT/DH91/KtPtw1ck9jpyZfHvzb6oUuCu9PtHhv72mtjB14RHI07q9/0dcdAgfUd+HncdRWt8fYIVbtt5lY4NZ2/9eNuRNYJSV1Hjhv/6uAWVtvI1WHKRtIX7N38XqdEGnFx6VPjxo97/hrb8QqUetkL3+2AXuvy4dXT7syUwkQQgOpeu/fUlQd8VM7nbvnulV4NkszNoYpdJ/x6wA87D8fWvn9HLVR44RarKxy4J7lRdnaDNP5AC1ENN6N5qxZ1odXiltigt0kVG7XMviSLqu3WB9Hf3JkXt7qkdkKYPpifImY0a9WitjvKVcbDEzMaZ2e3qJ8uokgXM0MrtU5zct0F/OSF6SUxI61cvUuyWzasGLwhKtl3GQKPCxt4EyhKIxinWURI2mO6QBJt55HYJ5ZzAeZzO+GqFIZvxd4fU4EcfjVrO+Rx5ticKMV0BPOtQsiF5q9jK60YZgMn9LG2JkqkmH305wgxZVmK7Sp2pRjLYS8h+nU0wC/m2Rppb8yhuMmBttTIgbbUyIG21MiBttTI2MCxxyE4FDd5UM9cTSW2c4EDbQkTgZZ5mhQ814G2RElEGXMPHD15Knd/Tl8H2ZIlAmeTG2/pffNVpZsd5rykQIhkbIcfHCoCCS63x+MpnerYDjnkkEMOOeSQQw455JBDDjnkkEMOOXTm6P8Af29k4A0KZW5kc3RyZWFtCmVuZG9iago1OCAwIG9iago8PAovVHlwZSAvRm9udAovU3VidHlwZSAvVHlwZTAKL0Jhc2VGb250IC9BQUFBQUErQ3JpbXNvblByby1SZWd1bGFyCi9FbmNvZGluZyAvSWRlbnRpdHktSAovRGVzY2VuZGFudEZvbnRzIFs1OSAwIFJdCi9Ub1VuaWNvZGUgNjAgMCBSCj4+CmVuZG9iago1OSAwIG9iago8PAovVHlwZSAvRm9udAovRm9udERlc2NyaXB0b3IgNjEgMCBSCi9CYXNlRm9udCAvQUFBQUFBK0NyaW1zb25Qcm8tUmVndWxhcgovU3VidHlwZSAvQ0lERm9udFR5cGUyCi9DSURUb0dJRE1hcCAvSWRlbnRpdHkKL0NJRFN5c3RlbUluZm8gNjIgMCBSCi9XIFswIFs1MDAgNTY4LjM1OTM4XQogMzAgWzU4OS44NDM3NV0KIDY5IFs2MzIuODEyNV0KIDc2IFs2NTYuMjVdCiA4MSBbMzAyLjczNDM4XQoyMTUgWzkxOC45NDUzMV0KIDIzNyBbNDYyLjg5MDYzXQogMjY1IFs1MTMuNjcxODggNDE1LjAzOTA2XQogMjczIFs1MjYuMzY3MTldCiAyODAgWzQzOS40NTMxM10KMzA1IFs0ODMuMzk4NDRdCiAzMTcgWzI2MS43MTg3NV0KIDM0MCBbMjYyLjY5NTMxXQogMzQ5IFs4MDMuNzEwOTQgMCA1MzcuMTA5MzhdCiAzNjIgWzQ5Ni4wOTM3NV0KMzk3IFs1MjQuNDE0MDYgMCAwIDM1NS40Njg3NV0KIDQyMCBbMzMxLjA1NDY5XQogNDI4IFs1MzAuMjczNDRdCiA0NTIgWzczNS4zNTE1Nl0KIDQ1OCBbNDYwLjkzNzVdCjU4MiBbMjU5Ljc2NTYzXQogNjI1IFsxODcuNV0KXQovRFcgMAo+PgplbmRvYmoKNjAgMCBvYmoKPDwKL0xlbmd0aCAzNTMKL0ZpbHRlciAvRmxhdGVEZWNvZGUKPj4Kc3RyZWFtDQp4nF2S3WqEMBCF732KXLYXi0lWzS6IILoLXvSH2j6Aq+NWqDFE98K3b5zJbqEBhY85ZziZSVhUZaWHhYXvdmprWFg/6M7CPN1sC+wC10EHQrJuaBdP+G/HxgShM9frvMBY6X4K0pSx8MNV58Wu7Cnvpgs8B+Gb7cAO+sqevoracX0z5gdG0AvjQZaxDnrX6aUxr80ILETbrupcfVjWnfP8KT5XA0wiC0rTTh3MpmnBNvoKQcrdyVh6dicLQHf/6nJPtkvffjcW5cLJOY9EtpE4Ee2RophIERVEB6TY+45IpUKKSXkqkRLsKQQpk4joQBQj7X1NEXkf9hRxRFQQ+VpJdCY6ISU50RnpQErFkY6cSCLl1FNRlpxupChL4WuUpaCeCrPIKEGSeCOpKLXkOGI/S3Gf7H0Tgh83meA+nfRqqm+72d7QY/HtzVq3c3xouOxtzYOGx1s0k9lc2/cL9Ma5Qw0KZW5kc3RyZWFtCmVuZG9iago2MSAwIG9iago8PAovVHlwZSAvRm9udERlc2NyaXB0b3IKL0ZvbnROYW1lIC9BQUFBQUErQ3JpbXNvblByby1SZWd1bGFyCi9GbGFncyA0Ci9Bc2NlbnQgODk2LjQ4NDM4Ci9EZXNjZW50IC0yMTQuODQzNzUKL1N0ZW1WIDEzNy42OTUzMTMKL0NhcEhlaWdodCA1NzMuMjQyMTkKL0l0YWxpY0FuZ2xlIDAKL0ZvbnRCQm94IFstMTA0LjQ5MjE4OCAtMjc2LjM2NzE5IDExMzEuODM1OTQgOTYwLjkzNzVdCi9Gb250RmlsZTIgNjMgMCBSCj4+CmVuZG9iago2MiAwIG9iago8PAovUmVnaXN0cnkgKEFkb2JlKQovT3JkZXJpbmcgKElkZW50aXR5KQovU3VwcGxlbWVudCAwCj4+CmVuZG9iago2MyAwIG9iago8PAovTGVuZ3RoIDMyMTEKL0xlbmd0aDEgODkxMgovRmlsdGVyIC9GbGF0ZURlY29kZQo+PgpzdHJlYW0NCnic7VkJcFvVFX2LLMmLFmuXJWvX/5atxdbiL9uybFleEid27NhxSBonEcGJHbwRO4RAoeyknQbaAdLSdKBQ2oEWGIaBNNAMk7KUgbKUbmGAaadTSqdrhjIBOk2s3vclr8RAKKFlyLv5+W/799xz3333vy8jjBBSoCsRRT1r+kLhjT1Tv0IIPw+9W/v6U/1v3P+8Edr7oN26bSwzqXlK81eEiB/ah3ZkpibhrobrDbjkO0b3bleMlP4CIcPXECpJDg9lLrC8XHEAnu+B8dph6JA9IimDNnveMzw2fUmMUClCxZvgCo9ObMtUBrkXECooQUjaPZa5ZJIKksdh7t/gcoxnxoaCuv5ewAc8wk1OTE1n70Bh0FfCxid3DU0OS796HdRfh0t2GGeve1CyH4YRzmaRCu6IcpLVSIbGUQFrLSgS8ADNVbPfZjpPU9jzB7IemPzd7K2nfie5YYkORKbEHoyuufaZf3VvUSVOyKlEHHml8Mil4v0W/vfZW2f+KLmBvgJNKSJzujGSiy2d+L8HkT07hqeZupHpzCgugnEJ+FpExBSDh8FeaOHCWduQhLD+ArDjW2Q7tLtyd7wd+GTfz2W+tE+syKCjyIH25vjRf2DegfDN4sR7ySrmXdE7RERlNorYcJegu+BuBcsoKkFOlEJptBKtR0NoBxpBk2gX2o32oL3ZrKiDjXbAaEYcHYXR6dnR7NHsazDnSrieQywGbXB14V7AsYpGlok4SNSDEJf3+EWkHbWh1agX/R0X4iJcik24Hw/iLXgn3o/vxveJ04rRV5jFEuarh9Bf8nWMDICUqxOkRD/N1ylqRtfn6xJA2pqvF4AN7fm6FFbIlauDI0pAU65O5/XAyhTBSK7Oagg8Mw3cR4H7NrQWTaAxaI2L3hoBjwzDaBp8MgL9UzA6jnqgNYECMJf5czc8mYGeAWjtghkj4hwHqkFBVA0SXvK0I/+8Y8nz8/MbgM8EWgG9Dcsgt6FLwKpdMGP1nI2LtZ0eMw3XJNorjuWecgBeNWDHodYPPUNwX87e3H0nzNkmPpkCtGl4ZkLk7UAVosZpQJhC9SgEwuKJzdiNzgd220TfhkR242J/BkamQN849J6OqS+/Feo+tkx8ovLIQsHWOVlxTubk8jOQlz6eEC3pWCRfXyRPn6nQ6mUkQbedgdy4VCQEZPVp5bJPRJ5jUiD7iBJcIlNnKPcskNc/nyKVn5PPsNjFw1IETidLzohnv5BNKPppY37UQvahyLJj34E395KCa+F9fa6cK0sKvR++4s5Cwa/CSfhTLESJCL537vT5/nELnHWXKfgJVHl2rFqAcRy+6c5kPnwJseus2KIQf2v4wEIFZGZ3wiHTR9Z7cnn/f9qFmJFxto5Hc1w+qYL7kOYDse0f7t//ppC3kOR0/fiV7MmziXuu/H8VXATf5Z+VcvR/bcDnqBxH7NAM52XSTgaRlr15pnhBK2iNMjcv8DwVqExmlPExgyESrhX0HOd2SSmpebH+1TB9jjQ2FI8Zh4tjjcSqbakOpTWutN2W4pRE6j51TMlxylvsWK2Y+YPtViX3aAwTgmMYs99do9l3STPpRy6E2lwcF4smSSRsMAKSiCDV63KAglEmleLJ9TcN+Ndd2u3b5JEFbNpk+aotgYahRnvM5A9a3YOF6evOb5pYV61WfVmyRaFIjabWXZxQKjcXXFFqAawIYF0AWKEPwZLFohzPCxGjTKeDPiMAX7zx5oHAusvWVA66pP5ybbOlKxNsHE5Z2ywmQlrJk8RPMD9WmL4W8AcA/xrJBmUJw9+TUOviI86woriCKyngLO7qsrJyRNlpm0yRVciCBNSGUIYXkiQGuHlgJdEza8A257IjdNFa4NtII6EYU7gdo0kiliQ99ROog6sx1H89P6NL2y0IqzSG9nj9Ck4pcSarj4STTspWijqT4SPVSaeENQo8Sf+RQJN36QhWN7BFbAB17Pds8CiWi2xY1Cxjbw3gipY0kgWWLI8B8SjLnqL3gN5ipjlF3VRUqXVrtXM18U7xnx6qejlMnqSRu/V33PtSmPyMNt6lu72DHqaRY/jwTBspA+2KmVX4e6dOzNUfZoin3mSR35B9Dz+NDyE/rAdaL0YHBB2jAb6VyaRitAgQLTIICKMegtHt4vmIVMrDCsRkYvA8VZF0V0W+aQvbzRpCPGFbtWBZuRETSptrlCa5vbBY5WwsMPo0kyZ9QO4IW5z+0kjE7DPp7ZVyvanUZTdIuprv0XgLS+waAOC93ga9YcQmLdOZfWAjARvfJIeQAwUQ2lMbjYKbgyQGfjYqicxYWwvRbNDrmGmcAGbVirbrDYY1zO+dZL+WrRk1dfrDfdH0ZGvVugMNXZisidvq9PqwxRUNW+9zd6+MFRU6fMrK/vFe7Kqs+UKqY6xRZ9/Q19Nlsb6tLIXV9oEdd4KvnHOemkUGYJ43cqI3WGjC1nk3vjnesD3lT9h1mlKDJuKp6KzubeQEs8U6II9k0qnhpMGjU+tLNJGiQmdvc8+A1W6MRtiaVGRPQAwfQh5UAzsWspDIbG7fAvcQYRD63JqIKyGuF6DjL6kwLW92122MeJp6qiwNOhZ63eRmWBlcgJv/bY2UaevLmzrWFCtSuNJVO9gYO6/JpVRp6ptqCpVOnzI53fRmZKVK7eTMjmMu62Bnz3rgXgnc78A/YvlqlrsRqOudsQhLG2IamSf/Xv3mWvjXfd7MC28VR4LWqKO32SOUmYF6NNPSMVyH8dBmjV+hr2typiMlazdZHCJ3CXJmrfi3+BE4HddBflgporGQNM76FsBCZGGf0ZhbfLp4SViA5gyDOV6IlT9vGq1NcE5Pc11zb3XdhSpPNBpUaDVm41VtKxLb47G+YPXaSKQnFOr1+6pjVb6ogPV7+NvfSIRd9Ta5Llxe4Y/7XU5JoYS3WqI6nUJfpNRShSweDXZW4a1ca2V1Z1VFR8DfUVmRCNelm6LR9LPyCjXKZsVvkxPkoJTDSajJqDe7ESHxb0HQjx9m5++5/MHzMZGZSE1mIWnIHBKavlFFKSVp4lVKdH6+pbJVo3R6lfZExRXBhFzpZdrYSfptiJuyeW1z2yQXKzIrSREMu4J0kpu0rEKhw6ssDqwWLvf2rIjIi2ALVPVtX3FnwxYl00rY9wAth6iPoJ5ZvYKY5cDMuc1nFLW7xVE+nzxyBObzOOvj8/NzYw6SBFOAHmm+vrSA2ls87pYqTGmCPkkFZp3Fb7YJekog3+6kEDTEFjC664xa9xp/ZWsFJUniUckMlZ5uvZ8vAX+oazHv4robd3rqilUej0pu4izjVVGTzXEgON8z5o+aXG5vdaBide1F4kzmPfh2IyrgGV7AcjFJ/QKSsUUk7TkiBVTk4W6t8KZ8lCbps6Qeuu1BkzMOLJIUrDVVurqcLWzx1DHMubxd9ROe+qJZ00YDMaPDdiAUZ0YRMeP8EmxSofLZd/eiAJ+PcAPGqUxtw1AiuSka31LjCls76mtT7qQ8PpRqvSAubGtp2pEI+VaGBi7csCo+UM1W1pj14OOg3ct+u1vPC9GFpCOL0iqf46kFSEivs/m1XKRN8rStaV5YV5PYkTJF9BSvJSpfwo2xq55zxAza2OseVYHJF/e+a2sSyQser7C1MT3SWChfxcUUCg5L+c4obKFy6z9V7G/OEHd4A+wNfpn1yKdd2Coyo42wSKNJFjPMGqWE2hqtRrvTrHGqNBqXipmJ10O0FJgDjm5nStw8/bjeZzIYy9RKaYnfPmiOBYoV7O2rAa9vIAVwGoR9OSTyjwnumBCRyXIZjsYierxB2+JJrKHtV2kJDviMvPqZHyed3s7O4/rdU+9Ew1ol9oTDbFdCNpcCDzj5DMELdDadL9zqebWx2W0llepYsOFnmq5m2bzRnd5ISes+lsLrA5a4Hsis3A+cCtrVToeiCfucfX0DlloL86u9nRtPxqyuaUMVr2QsrVVV1g4hDbwk2ZPkLfwARBOcKtryJwlZ/iThnGsbKX7vtgg5TKLT5j1RcpCEfjjz+F0h8iiJfNF2reUbEnjHQLjO/BzjGrFy1XxHfXLmELyvT4pZ/AEkY389X5/XDEdqKstxZ2cMmSAA9GsPhugTJLnvRRr6QY+hxmFymMyDwYniq5NkHmcb/H/w+0HOpFWVya3h32gYMIvg9uw76CnyGCpi0bKHcrmYBZjFW2WXkyhNarVJSZxajbW01KrRlKs9h/BjZkdpqcM8035UbS3VWNRqi6bMzXLBceLFI+QgnL9QSjQ3F284UObxlJW5XMTrMpvcbpMZXofoP5F+JXQNCmVuZHN0cmVhbQplbmRvYmoKeHJlZgowIDY0CjAwMDAwMDAwMDAgNjU1MzUgZg0KMDAwMDAwMDAxNSAwMDAwMCBuDQowMDAwMDAwNDE5IDAwMDAwIG4NCjAwMDAwMDA0NzYgMDAwMDAgbg0KMDAwMDAwMDU4NCAwMDAwMCBuDQowMDAwMDAwNjM0IDAwMDAwIG4NCjAwMDAwMDAxNTIgMDAwMDAgbg0KMDAwMDAwMDY3NyAwMDAwMCBuDQowMDAwMDAwOTYwIDAwMDAwIG4NCjAwMDAwMDEwNTcgMDAwMDAgbg0KMDAwMDAwMTE2MCAwMDAwMCBuDQowMDAwMDAxOTE2IDAwMDAwIG4NCjAwMDAwMDIwNDEgMDAwMDAgbg0KMDAwMDAwMzA5NCAwMDAwMCBuDQowMDAwMDAzMTg2IDAwMDAwIG4NCjAwMDAwMDMyODEgMDAwMDAgbg0KMDAwMDAwMzM3NiAwMDAwMCBuDQowMDAwMDAzNDcxIDAwMDAwIG4NCjAwMDAwMDM1NjYgMDAwMDAgbg0KMDAwMDAwMzY2MSAwMDAwMCBuDQowMDAwMDAzNzU2IDAwMDAwIG4NCjAwMDAwMDM4NDQgMDAwMDAgbg0KMDAwMDAwMzkzMyAwMDAwMCBuDQowMDAwMDA0MDIyIDAwMDAwIG4NCjAwMDAwMDQxMTEgMDAwMDAgbg0KMDAwMDAwNDIwMCAwMDAwMCBuDQowMDAwMDA0Mjg5IDAwMDAwIG4NCjAwMDAwMDQzNzggMDAwMDAgbg0KMDAwMDAwNDQ5NSAwMDAwMCBuDQowMDAwMDA0NTg0IDAwMDAwIG4NCjAwMDAwMDQ2NzMgMDAwMDAgbg0KMDAwMDAwNDc2MiAwMDAwMCBuDQowMDAwMDA0ODUxIDAwMDAwIG4NCjAwMDAwMDQ5NDAgMDAwMDAgbg0KMDAwMDAwNTAyNyAwMDAwMCBuDQowMDAwMDA1MTE2IDAwMDAwIG4NCjAwMDAwMDUyMDUgMDAwMDAgbg0KMDAwMDAwNTI5MiAwMDAwMCBuDQowMDAwMDA1MzgxIDAwMDAwIG4NCjAwMDAwMDU0NzcgMDAwMDAgbg0KMDAwMDAwNTU3MSAwMDAwMCBuDQowMDAwMDA1NjU4IDAwMDAwIG4NCjAwMDAwMDU3NDcgMDAwMDAgbg0KMDAwMDAwNTgzNiAwMDAwMCBuDQowMDAwMDA1OTI1IDAwMDAwIG4NCjAwMDAwMDYwMTIgMDAwMDAgbg0KMDAwMDAwNjA1NiAwMDAwMCBuDQowMDAwMDM2NDMyIDAwMDAwIG4NCjAwMDAwMzY0NjUgMDAwMDAgbg0KMDAwMDAzNjUxNiAwMDAwMCBuDQowMDAwMDM2NTY3IDAwMDAwIG4NCjAwMDAwMzY2MTggMDAwMDAgbg0KMDAwMDAzNjY2OSAwMDAwMCBuDQowMDAwMDM2NzIwIDAwMDAwIG4NCjAwMDAwMzY3NzEgMDAwMDAgbg0KMDAwMDAzNjgyMiAwMDAwMCBuDQowMDAwMDM2ODYyIDAwMDAwIG4NCjAwMDAwMzY5NDEgMDAwMDAgbg0KMDAwMDA0OTMxNiAwMDAwMCBuDQowMDAwMDQ5NDY5IDAwMDAwIG4NCjAwMDAwNTAwMzcgMDAwMDAgbg0KMDAwMDA1MDQ2NSAwMDAwMCBuDQowMDAwMDUwNzIwIDAwMDAwIG4NCjAwMDAwNTA3OTUgMDAwMDAgbg0KdHJhaWxlcgo8PAovUm9vdCAxIDAgUgovSW5mbyA2IDAgUgovSUQgWzwwQ0M3NzdBRDZENEFBMThFRkFGQ0ExODQ3QjI1QkMxQz4gPDBDQzc3N0FENkQ0QUExOEVGQUZDQTE4NDdCMjVCQzFDPl0KL1NpemUgNjQKPj4Kc3RhcnR4cmVmCjU0MDk2CiUlRU9GCg==', }; diff --git a/src/components/CollapsibleDropdown/CollapsibleDropdown.test.tsx b/src/components/CollapsibleDropdown/CollapsibleDropdown.spec.tsx similarity index 87% rename from src/components/CollapsibleDropdown/CollapsibleDropdown.test.tsx rename to src/components/CollapsibleDropdown/CollapsibleDropdown.spec.tsx index efee248ffb..b288fb9de5 100644 --- a/src/components/CollapsibleDropdown/CollapsibleDropdown.test.tsx +++ b/src/components/CollapsibleDropdown/CollapsibleDropdown.spec.tsx @@ -8,21 +8,33 @@ import { store } from 'state/store'; import { Provider } from 'react-redux'; import { I18nextProvider } from 'react-i18next'; import i18nForTest from 'utils/i18nForTest'; +import { describe, expect, test, vi, afterEach } from 'vitest'; +import type { Location } from '@remix-run/router'; -jest.mock('react-router-dom', () => ({ - ...jest.requireActual('react-router-dom'), - useLocation: () => ({ - pathname: '/orgstore', - state: {}, - key: '', - search: '', - hash: '', - }), -})); +afterEach(() => { + vi.resetModules(); +}); + +const currentLocation: Location = { + pathname: '/orgstore', + state: {}, + key: '', + search: '', + hash: '', +}; + +vi.mock('react-router-dom', async (importOriginal) => { + const mod = (await importOriginal()) as object; + + return { + ...mod, + useLocation: () => currentLocation, + }; +}); const props: InterfaceCollapsibleDropdown = { showDropdown: true, - setShowDropdown: jest.fn(), + setShowDropdown: vi.fn(), target: { name: 'DropDown Category', url: undefined, diff --git a/src/components/EditCustomFieldDropDown/EditCustomFieldDropDown.test.tsx b/src/components/EditCustomFieldDropDown/EditCustomFieldDropDown.spec.tsx similarity index 90% rename from src/components/EditCustomFieldDropDown/EditCustomFieldDropDown.test.tsx rename to src/components/EditCustomFieldDropDown/EditCustomFieldDropDown.spec.tsx index 19d2249a43..4119800dd1 100644 --- a/src/components/EditCustomFieldDropDown/EditCustomFieldDropDown.test.tsx +++ b/src/components/EditCustomFieldDropDown/EditCustomFieldDropDown.spec.tsx @@ -8,6 +8,7 @@ import availableFieldTypes from 'utils/fieldTypes'; import { I18nextProvider } from 'react-i18next'; import i18nForTest from 'utils/i18nForTest'; import type { InterfaceCustomFieldData } from 'utils/interfaces'; +import { describe, it, expect } from 'vitest'; async function wait(ms = 100): Promise { await act(() => { @@ -18,7 +19,7 @@ async function wait(ms = 100): Promise { } describe('Testing Custom Field Dropdown', () => { - test('Component Should be rendered properly', async () => { + it('Component Should be rendered properly', async () => { const customFieldData = { type: 'Number', name: 'Age', @@ -26,11 +27,10 @@ describe('Testing Custom Field Dropdown', () => { const setCustomFieldData: Dispatch< SetStateAction - > = (val) => { - { - val; - } + > = () => { + // Intentionally left blank for testing purposes }; + const props = { customFieldData: customFieldData as InterfaceCustomFieldData, setCustomFieldData: setCustomFieldData, diff --git a/src/components/EventCalendar/EventCalendar.test.tsx b/src/components/EventCalendar/EventCalendar.spec.tsx similarity index 99% rename from src/components/EventCalendar/EventCalendar.test.tsx rename to src/components/EventCalendar/EventCalendar.spec.tsx index 8e2395968a..4dfd0548d6 100644 --- a/src/components/EventCalendar/EventCalendar.test.tsx +++ b/src/components/EventCalendar/EventCalendar.spec.tsx @@ -13,6 +13,7 @@ import i18nForTest from 'utils/i18nForTest'; import { StaticMockLink } from 'utils/StaticMockLink'; import { weekdays, months } from './constants'; import { BrowserRouter as Router } from 'react-router-dom'; +import { vi } from 'vitest'; const eventData = [ { @@ -122,7 +123,7 @@ describe('Calendar', () => { }); afterEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); }); it('should render the current month and year', () => { diff --git a/src/components/EventCalendar/EventHeader.test.tsx b/src/components/EventCalendar/EventHeader.spec.tsx similarity index 92% rename from src/components/EventCalendar/EventHeader.test.tsx rename to src/components/EventCalendar/EventHeader.spec.tsx index e18d066306..be1ba4bd78 100644 --- a/src/components/EventCalendar/EventHeader.test.tsx +++ b/src/components/EventCalendar/EventHeader.spec.tsx @@ -4,11 +4,20 @@ import EventHeader from './EventHeader'; import { ViewType } from '../../screens/OrganizationEvents/OrganizationEvents'; import { I18nextProvider } from 'react-i18next'; import i18nForTest from 'utils/i18nForTest'; +import { vi } from 'vitest'; describe('EventHeader Component', () => { const viewType = ViewType.MONTH; - const handleChangeView = jest.fn(); - const showInviteModal = jest.fn(); + + /** + * Mock function to handle view type changes. + */ + const handleChangeView = vi.fn(); + + /** + * Mock function to handle the display of the invite modal. + */ + const showInviteModal = vi.fn(); it('renders correctly', () => { const { getByTestId } = render( diff --git a/src/components/EventListCard/EventListCard.test.tsx b/src/components/EventListCard/EventListCard.spec.tsx similarity index 92% rename from src/components/EventListCard/EventListCard.test.tsx rename to src/components/EventListCard/EventListCard.spec.tsx index afe81f436e..e0c6cc825f 100644 --- a/src/components/EventListCard/EventListCard.test.tsx +++ b/src/components/EventListCard/EventListCard.spec.tsx @@ -17,16 +17,17 @@ import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; import useLocalStorage from 'utils/useLocalstorage'; import { props } from './EventListCardProps'; import { ERROR_MOCKS, MOCKS } from './EventListCardMocks'; +import { vi, beforeAll, afterAll, expect, it } from 'vitest'; const { setItem } = useLocalStorage(); const link = new StaticMockLink(MOCKS, true); const link2 = new StaticMockLink(ERROR_MOCKS, true); -jest.mock('react-toastify', () => ({ +vi.mock('react-toastify', () => ({ toast: { - success: jest.fn(), - error: jest.fn(), + success: vi.fn(), + error: vi.fn(), }, })); @@ -101,18 +102,17 @@ describe('Testing Event List Card', () => { }; beforeAll(() => { - jest.mock('react-router-dom', () => ({ - ...jest.requireActual('react-router-dom'), - useParams: () => ({ orgId: 'orgId' }), + vi.mock('react-router-dom', async () => ({ + ...(await vi.importActual('react-router-dom')), })); }); afterAll(() => { localStorage.clear(); - jest.clearAllMocks(); + vi.clearAllMocks(); }); - test('Testing for event modal', async () => { + it('Testing for event modal', async () => { renderEventListCard(props[1]); userEvent.click(screen.getByTestId('card')); @@ -129,7 +129,7 @@ describe('Testing Event List Card', () => { }); }); - test('Should navigate to "/" if orgId is not defined', async () => { + it('Should navigate to "/" if orgId is not defined', async () => { render( @@ -163,7 +163,7 @@ describe('Testing Event List Card', () => { }); }); - test('Should render default text if event details are null', async () => { + it('Should render default text if event details are null', async () => { renderEventListCard(props[0]); await waitFor(() => { @@ -171,7 +171,7 @@ describe('Testing Event List Card', () => { }); }); - test('should render props and text elements test for the screen', async () => { + it('should render props and text elements test for the screen', async () => { renderEventListCard(props[1]); expect(screen.getByText(props[1].eventName)).toBeInTheDocument(); @@ -198,7 +198,7 @@ describe('Testing Event List Card', () => { }); }); - test('Should render truncated event name when length is more than 100', async () => { + it('Should render truncated event name when length is more than 100', async () => { const longEventName = 'a'.repeat(101); renderEventListCard({ ...props[1], eventName: longEventName }); @@ -221,7 +221,7 @@ describe('Testing Event List Card', () => { }); }); - test('Should render full event name when length is less than or equal to 100', async () => { + it('Should render full event name when length is less than or equal to 100', async () => { const shortEventName = 'a'.repeat(100); renderEventListCard({ ...props[1], eventName: shortEventName }); @@ -242,7 +242,7 @@ describe('Testing Event List Card', () => { }); }); - test('Should render truncated event description when length is more than 256', async () => { + it('Should render truncated event description when length is more than 256', async () => { const longEventDescription = 'a'.repeat(257); renderEventListCard({ @@ -268,7 +268,7 @@ describe('Testing Event List Card', () => { }); }); - test('Should render full event description when length is less than or equal to 256', async () => { + it('Should render full event description when length is less than or equal to 256', async () => { const shortEventDescription = 'a'.repeat(256); renderEventListCard({ @@ -294,7 +294,7 @@ describe('Testing Event List Card', () => { }); }); - test('Should navigate to event dashboard when clicked (For Admin)', async () => { + it('Should navigate to event dashboard when clicked (For Admin)', async () => { renderEventListCard(props[1]); userEvent.click(screen.getByTestId('card')); @@ -311,7 +311,7 @@ describe('Testing Event List Card', () => { }); }); - test('Should navigate to event dashboard when clicked (For User)', async () => { + it('Should navigate to event dashboard when clicked (For User)', async () => { setItem('userId', '123'); renderEventListCard(props[2]); @@ -329,7 +329,7 @@ describe('Testing Event List Card', () => { }); }); - test('Should update a non-recurring event', async () => { + it('Should update a non-recurring event', async () => { renderEventListCard(props[1]); userEvent.click(screen.getByTestId('card')); @@ -372,7 +372,7 @@ describe('Testing Event List Card', () => { }); }); - test('Should update a non all day non-recurring event', async () => { + it('Should update a non all day non-recurring event', async () => { renderEventListCard(props[1]); userEvent.click(screen.getByTestId('card')); @@ -425,7 +425,7 @@ describe('Testing Event List Card', () => { }); }); - test('should update a single event to be recurring', async () => { + it('should update a single event to be recurring', async () => { renderEventListCard(props[1]); userEvent.click(screen.getByTestId('card')); @@ -469,7 +469,7 @@ describe('Testing Event List Card', () => { }); }); - test('should show different update options for a recurring event based on different conditions', async () => { + it('should show different update options for a recurring event based on different conditions', async () => { renderEventListCard(props[5]); userEvent.click(screen.getByTestId('card')); @@ -595,7 +595,7 @@ describe('Testing Event List Card', () => { }); }); - test('should show recurrenceRule as changed if the recurrence weekdays have changed', async () => { + it('should show recurrenceRule as changed if the recurrence weekdays have changed', async () => { renderEventListCard(props[4]); userEvent.click(screen.getByTestId('card')); @@ -656,7 +656,7 @@ describe('Testing Event List Card', () => { }); }); - test('should update all instances of a recurring event', async () => { + it('should update all instances of a recurring event', async () => { renderEventListCard(props[6]); userEvent.click(screen.getByTestId('card')); @@ -706,7 +706,7 @@ describe('Testing Event List Card', () => { }); }); - test('should update thisAndFollowingInstances of a recurring event', async () => { + it('should update thisAndFollowingInstances of a recurring event', async () => { renderEventListCard(props[5]); userEvent.click(screen.getByTestId('card')); @@ -772,7 +772,7 @@ describe('Testing Event List Card', () => { }); }); - test('should render the delete modal', async () => { + it('should render the delete modal', async () => { renderEventListCard(props[1]); userEvent.click(screen.getByTestId('card')); @@ -807,7 +807,7 @@ describe('Testing Event List Card', () => { }); }); - test('should call the delete event mutation when the "Yes" button is clicked', async () => { + it('should call the delete event mutation when the "Yes" button is clicked', async () => { renderEventListCard(props[1]); userEvent.click(screen.getByTestId('card')); @@ -833,7 +833,7 @@ describe('Testing Event List Card', () => { }); }); - test('select different delete options on recurring events & then delete the recurring event', async () => { + it('select different delete options on recurring events & then delete the recurring event', async () => { renderEventListCard(props[4]); await wait(); @@ -873,7 +873,7 @@ describe('Testing Event List Card', () => { }); }); - test('should show an error toast when the delete event mutation fails', async () => { + it('should show an error toast when the delete event mutation fails', async () => { // Destructure key from props[1] and pass it separately to avoid spreading it const { key, ...otherProps } = props[1]; render( @@ -908,7 +908,7 @@ describe('Testing Event List Card', () => { }); }); - test('handle register should work properly', async () => { + it('handle register should work properly', async () => { setItem('userId', '456'); renderEventListCard(props[2]); @@ -933,7 +933,7 @@ describe('Testing Event List Card', () => { }); }); - test('should show already registered text when the user is registered for an event', async () => { + it('should show already registered text when the user is registered for an event', async () => { renderEventListCard(props[3]); userEvent.click(screen.getByTestId('card')); diff --git a/src/components/EventListCard/EventListCardModals.tsx b/src/components/EventListCard/EventListCardModals.tsx index 193890941c..7755da2a5a 100644 --- a/src/components/EventListCard/EventListCardModals.tsx +++ b/src/components/EventListCard/EventListCardModals.tsx @@ -292,7 +292,6 @@ function EventListCardModals({ } } } catch (error: unknown) { - /* istanbul ignore next */ errorHandler(t, error); } }; @@ -362,7 +361,6 @@ function EventListCardModals({ hideViewModal(); } } catch (error: unknown) { - /* istanbul ignore next */ errorHandler(t, error); } } @@ -491,9 +489,7 @@ function EventListCardModals({ recurrenceStartDate: date?.toDate(), weekDays: [Days[date?.toDate().getDay()]], weekDayOccurenceInMonth: weekDayOccurenceInMonth - ? /* istanbul ignore next */ getWeekDayOccurenceInMonth( - date?.toDate(), - ) + ? getWeekDayOccurenceInMonth(date?.toDate()) : undefined, }); } @@ -531,8 +527,7 @@ function EventListCardModals({ endTime: timeToDayJs(formState.endTime) < time ? time?.format('HH:mm:ss') - : /* istanbul ignore next */ - formState.endTime, + : formState.endTime, }); } }} diff --git a/src/components/EventManagement/Dashboard/EventDashboard.test.tsx b/src/components/EventManagement/Dashboard/EventDashboard.spec.tsx similarity index 88% rename from src/components/EventManagement/Dashboard/EventDashboard.test.tsx rename to src/components/EventManagement/Dashboard/EventDashboard.spec.tsx index dc605a1604..672282ff4a 100644 --- a/src/components/EventManagement/Dashboard/EventDashboard.test.tsx +++ b/src/components/EventManagement/Dashboard/EventDashboard.spec.tsx @@ -13,6 +13,7 @@ import type { ApolloLink, DefaultOptions } from '@apollo/client'; import { MOCKS_WITHOUT_TIME, MOCKS_WITH_TIME } from './EventDashboard.mocks'; import { StaticMockLink } from 'utils/StaticMockLink'; +import { vi, expect, it, describe } from 'vitest'; const mockWithTime = new StaticMockLink(MOCKS_WITH_TIME, true); const mockWithoutTime = new StaticMockLink(MOCKS_WITHOUT_TIME, true); @@ -38,9 +39,8 @@ async function wait(ms = 500): Promise { } const mockID = 'event123'; -jest.mock('react-router-dom', () => ({ - ...jest.requireActual('react-router-dom'), - useParams: () => ({ eventId: mockID }), +vi.mock('react-router-dom', async () => ({ + ...(await vi.importActual('react-router-dom')), })); const renderEventDashboard = (mockLink: ApolloLink): RenderResult => { @@ -63,7 +63,7 @@ const renderEventDashboard = (mockLink: ApolloLink): RenderResult => { }; describe('Testing Event Dashboard Screen', () => { - test('The page should display event details correctly and also show the time if provided', async () => { + it('The page should display event details correctly and also show the time if provided', async () => { const { getByTestId } = renderEventDashboard(mockWithTime); await wait(); @@ -84,7 +84,7 @@ describe('Testing Event Dashboard Screen', () => { fireEvent.click(closeButton); }); - test('The page should display event details correctly and should not show the time if it is null', async () => { + it('The page should display event details correctly and should not show the time if it is null', async () => { const { getByTestId } = renderEventDashboard(mockWithoutTime); await wait(); @@ -92,7 +92,7 @@ describe('Testing Event Dashboard Screen', () => { expect(getByTestId('event-time')).toBeInTheDocument(); }); - test('Should show loader while data is being fetched', async () => { + it('Should show loader while data is being fetched', async () => { const { getByTestId, queryByTestId } = renderEventDashboard(mockWithTime); expect(getByTestId('spinner')).toBeInTheDocument(); // Wait for loading to complete diff --git a/src/components/EventManagement/EventAgendaItems/EventAgendaItems.test.tsx b/src/components/EventManagement/EventAgendaItems/EventAgendaItems.spec.tsx similarity index 87% rename from src/components/EventManagement/EventAgendaItems/EventAgendaItems.test.tsx rename to src/components/EventManagement/EventAgendaItems/EventAgendaItems.spec.tsx index 3bce7ad11e..fabd3312dd 100644 --- a/src/components/EventManagement/EventAgendaItems/EventAgendaItems.test.tsx +++ b/src/components/EventManagement/EventAgendaItems/EventAgendaItems.spec.tsx @@ -7,9 +7,7 @@ import { waitForElementToBeRemoved, } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import 'jest-localstorage-mock'; import { MockedProvider } from '@apollo/client/testing'; -import 'jest-location-mock'; import { I18nextProvider } from 'react-i18next'; import { Provider } from 'react-redux'; import { BrowserRouter } from 'react-router-dom'; @@ -17,11 +15,10 @@ import i18n from 'utils/i18nForTest'; // import { toast } from 'react-toastify'; import { LocalizationProvider } from '@mui/x-date-pickers'; import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; - import { store } from 'state/store'; import { StaticMockLink } from 'utils/StaticMockLink'; - import EventAgendaItems from './EventAgendaItems'; +import { vi, describe, expect, it, beforeEach } from 'vitest'; import { MOCKS, @@ -29,21 +26,20 @@ import { // MOCKS_ERROR_MUTATION, } from './EventAgendaItemsMocks'; -jest.mock('react-toastify', () => ({ +vi.mock('react-toastify', () => ({ toast: { - success: jest.fn(), - error: jest.fn(), + success: vi.fn(), + error: vi.fn(), }, })); -jest.mock('react-router-dom', () => ({ - ...jest.requireActual('react-router-dom'), - useParams: () => ({ eventId: '123' }), +vi.mock('react-router-dom', async () => ({ + ...(await vi.importActual('react-router-dom')), })); //temporarily fixes react-beautiful-dnd droppable method's depreciation error //needs to be fixed in React 19 -jest.spyOn(console, 'error').mockImplementation((message) => { +vi.spyOn(console, 'error').mockImplementation((message) => { if (message.includes('Support for defaultProps will be removed')) { return; } @@ -78,8 +74,18 @@ describe('Testing Agenda Items Components', () => { attachments: [], urls: [], }; - test('Component loads correctly', async () => { - window.location.assign('/event/111/123'); + + beforeEach(() => { + Object.defineProperty(window, 'location', { + configurable: true, + value: { + reload: vi.fn(), + href: 'https://localhost:4321/event/111/123', + }, + }); + }); + + it('Component loads correctly', async () => { const { getByText } = render( @@ -99,8 +105,7 @@ describe('Testing Agenda Items Components', () => { }); }); - test('render error component on unsuccessful agenda item query', async () => { - window.location.assign('/event/111/123'); + it('render error component on unsuccessful agenda item query', async () => { const { queryByText } = render( @@ -122,8 +127,7 @@ describe('Testing Agenda Items Components', () => { }); }); - test('opens and closes the create agenda item modal', async () => { - window.location.assign('/event/111/123'); + it('opens and closes the create agenda item modal', async () => { render( @@ -156,8 +160,7 @@ describe('Testing Agenda Items Components', () => { screen.queryByTestId('createAgendaItemModalCloseBtn'), ); }); - test('creates new agenda item', async () => { - window.location.assign('/event/111/123'); + it('creates new agenda item', async () => { render( diff --git a/src/components/EventManagement/EventAttendance/AttendedEventList.test.tsx b/src/components/EventManagement/EventAttendance/AttendedEventList.spec.tsx similarity index 92% rename from src/components/EventManagement/EventAttendance/AttendedEventList.test.tsx rename to src/components/EventManagement/EventAttendance/AttendedEventList.spec.tsx index 2d60081acf..b365ba89ff 100644 --- a/src/components/EventManagement/EventAttendance/AttendedEventList.test.tsx +++ b/src/components/EventManagement/EventAttendance/AttendedEventList.spec.tsx @@ -7,6 +7,7 @@ import { BrowserRouter } from 'react-router-dom'; import { I18nextProvider } from 'react-i18next'; import i18nForTest from 'utils/i18nForTest'; import { formatDate } from 'utils/dateFormatter'; +import { describe, expect, it } from 'vitest'; const mockEvent = { _id: 'event123', @@ -51,7 +52,7 @@ describe('Testing AttendedEventList', () => { eventId: 'event123', }; - test('Component renders and displays event details correctly', async () => { + it('Component renders and displays event details correctly', async () => { const { queryByText, queryByTitle } = render( @@ -71,7 +72,7 @@ describe('Testing AttendedEventList', () => { }); }); - test('Component handles error state gracefully', async () => { + it('Component handles error state gracefully', async () => { const errorMock = [ { request: { @@ -99,7 +100,7 @@ describe('Testing AttendedEventList', () => { }); }); - test('Component renders link with correct URL', async () => { + it('Component renders link with correct URL', async () => { const { container } = render( diff --git a/src/components/EventManagement/EventAttendance/EventAttendance.test.tsx b/src/components/EventManagement/EventAttendance/EventAttendance.spec.tsx similarity index 85% rename from src/components/EventManagement/EventAttendance/EventAttendance.test.tsx rename to src/components/EventManagement/EventAttendance/EventAttendance.spec.tsx index db44357d07..bff1553cc0 100644 --- a/src/components/EventManagement/EventAttendance/EventAttendance.test.tsx +++ b/src/components/EventManagement/EventAttendance/EventAttendance.spec.tsx @@ -17,6 +17,7 @@ import userEvent from '@testing-library/user-event'; import { StaticMockLink } from 'utils/StaticMockLink'; import i18n from 'utils/i18nForTest'; import { MOCKS } from './Attendance.mocks'; +import { vi, describe, beforeEach, afterEach, expect, it } from 'vitest'; const link = new StaticMockLink(MOCKS, true); @@ -25,7 +26,7 @@ async function wait(): Promise { return Promise.resolve(); }); } -jest.mock('react-chartjs-2', () => ({ +vi.mock('react-chartjs-2', () => ({ Line: () => null, Bar: () => null, Pie: () => null, @@ -47,18 +48,17 @@ const renderEventAttendance = (): RenderResult => { describe('Event Attendance Component', () => { beforeEach(() => { - jest.mock('react-router-dom', () => ({ - ...jest.requireActual('react-router-dom'), - useParams: () => ({ eventId: 'event123', orgId: 'org123' }), + vi.mock('react-router-dom', async () => ({ + ...(await vi.importActual('react-router-dom')), })); }); afterEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); cleanup(); }); - test('Component loads correctly with table headers', async () => { + it('Component loads correctly with table headers', async () => { renderEventAttendance(); await wait(); @@ -70,7 +70,7 @@ describe('Event Attendance Component', () => { }); }); - test('Renders attendee data correctly', async () => { + it('Renders attendee data correctly', async () => { renderEventAttendance(); await wait(); @@ -83,7 +83,7 @@ describe('Event Attendance Component', () => { }); }); - test('Search filters attendees by name correctly', async () => { + it('Search filters attendees by name correctly', async () => { renderEventAttendance(); await wait(); @@ -97,7 +97,7 @@ describe('Event Attendance Component', () => { }); }); - test('Sort functionality changes attendee order', async () => { + it('Sort functionality changes attendee order', async () => { renderEventAttendance(); await wait(); @@ -112,7 +112,7 @@ describe('Event Attendance Component', () => { }); }); - test('Date filter shows correct number of attendees', async () => { + it('Date filter shows correct number of attendees', async () => { renderEventAttendance(); await wait(); @@ -124,7 +124,7 @@ describe('Event Attendance Component', () => { expect(screen.getByText('Attendees not Found')).toBeInTheDocument(); }); }); - test('Statistics modal opens and closes correctly', async () => { + it('Statistics modal opens and closes correctly', async () => { renderEventAttendance(); await wait(); diff --git a/src/components/EventManagement/EventAttendance/EventStatistics.test.tsx b/src/components/EventManagement/EventAttendance/EventStatistics.spec.tsx similarity index 88% rename from src/components/EventManagement/EventAttendance/EventStatistics.test.tsx rename to src/components/EventManagement/EventAttendance/EventStatistics.spec.tsx index 03f4671a5e..38379b54fb 100644 --- a/src/components/EventManagement/EventAttendance/EventStatistics.test.tsx +++ b/src/components/EventManagement/EventAttendance/EventStatistics.spec.tsx @@ -5,21 +5,26 @@ import { MockedProvider } from '@apollo/client/testing'; import { EVENT_DETAILS, RECURRING_EVENTS } from 'GraphQl/Queries/Queries'; import userEvent from '@testing-library/user-event'; import { exportToCSV } from 'utils/chartToPdf'; +import { vi, describe, expect, it } from 'vitest'; +import type { Mock } from 'vitest'; // Mock chart.js to avoid canvas errors -jest.mock('react-chartjs-2', () => ({ +vi.mock('react-chartjs-2', async () => ({ + ...(await vi.importActual('react-chartjs-2')), Line: () => null, Bar: () => null, })); // Mock react-router-dom -jest.mock('react-router-dom', () => ({ +vi.mock('react-router-dom', async () => ({ + ...(await vi.importActual('react-router-dom')), useParams: () => ({ orgId: 'org123', eventId: 'event123', }), })); -jest.mock('utils/chartToPdf', () => ({ - exportToCSV: jest.fn(), +vi.mock('utils/chartToPdf', async () => ({ + ...(await vi.importActual('utils/chartToPdf')), + exportToCSV: vi.fn(), })); const mocks = [ { @@ -139,7 +144,7 @@ const mockStatistics = { }; describe('AttendanceStatisticsModal', () => { - test('renders modal with correct initial state', async () => { + it('renders modal with correct initial state', async () => { render( { }); }); - test('switches between gender and age demographics', async () => { + it('switches between gender and age demographics', async () => { render( { }); }); - test('handles data demographics export functionality', async () => { - const mockExportToCSV = jest.fn(); - (exportToCSV as jest.Mock).mockImplementation(mockExportToCSV); + it('handles data demographics export functionality', async () => { + const mockExportToCSV = vi.fn(); + (exportToCSV as Mock).mockImplementation(mockExportToCSV); render( @@ -220,9 +225,9 @@ describe('AttendanceStatisticsModal', () => { expect(mockExportToCSV).toHaveBeenCalled(); }); - test('handles data trends export functionality', async () => { - const mockExportToCSV = jest.fn(); - (exportToCSV as jest.Mock).mockImplementation(mockExportToCSV); + it('handles data trends export functionality', async () => { + const mockExportToCSV = vi.fn(); + (exportToCSV as Mock).mockImplementation(mockExportToCSV); render( @@ -255,7 +260,7 @@ describe('AttendanceStatisticsModal', () => { expect(mockExportToCSV).toHaveBeenCalled(); }); - test('displays recurring event data correctly', async () => { + it('displays recurring event data correctly', async () => { render( { expect(screen.getByTestId('today-button')).toBeInTheDocument(); }); }); - test('handles pagination and today button correctly', async () => { + it('handles pagination and today button correctly', async () => { render( { expect(screen.getByTestId('today-button')).toBeInTheDocument(); }); - test('handles pagination in recurring events view', async () => { + it('handles pagination in recurring events view', async () => { render( { }); }); - test('closes modal correctly', async () => { - const handleClose = jest.fn(); + it('closes modal correctly', async () => { + const handleClose = vi.fn(); render( { + await waitFor(() => { + return Promise.resolve(); + }); +} + +const renderEventRegistrants = (): RenderResult => { + return render( + + + + + + + + + , + ); +}; + +describe('Event Registrants Component', () => { + beforeEach(() => { + vi.mock('react-router-dom', async () => { + const actual = await vi.importActual('react-router-dom'); + return { + ...actual, + useParams: () => ({ eventId: 'event123', orgId: 'org123' }), + useNavigate: vi.fn(), + }; + }); + }); + + afterEach(() => { + vi.clearAllMocks(); + cleanup(); + }); + + test('Component loads correctly with table headers', async () => { + renderEventRegistrants(); + + await wait(); + + await waitFor(() => { + expect(screen.getByTestId('table-header-serial')).toBeInTheDocument(); + expect(screen.getByTestId('table-header-registrant')).toBeInTheDocument(); + expect(screen.getByTestId('table-header-created-at')).toBeInTheDocument(); + expect( + screen.getByTestId('table-header-add-registrant'), + ).toBeInTheDocument(); + }); + }); + + test('Renders registrants button correctly', async () => { + renderEventRegistrants(); + + await waitFor(() => { + expect(screen.getByTestId('stats-modal')).toBeInTheDocument(); + expect(screen.getByTestId('filter-button')).toBeInTheDocument(); + }); + }); + + test('Handles empty registrants list', async () => { + const emptyMocks = [ + { + request: { + query: EVENT_REGISTRANTS, + variables: { eventId: '660fdf7d2c1ef6c7db1649ad' }, + }, + result: { + data: { + getEventAttendeesByEventId: [], + }, + }, + }, + { + request: { + query: EVENT_ATTENDEES, + variables: { id: '660fdf7d2c1ef6c7db1649ad' }, + }, + result: { + data: { + event: { + attendees: [], + }, + }, + }, + }, + ]; + + const customLink = new StaticMockLink(emptyMocks, true); + render( + + + + + + + + + , + ); + + await waitFor(() => { + expect(screen.getByTestId('no-registrants')).toBeInTheDocument(); + }); + }); + + test('Successfully combines and displays registrant and attendee data', async () => { + const mockData = [ + { + request: { + query: EVENT_REGISTRANTS, + variables: { eventId: 'event123' }, + }, + result: { + data: { + getEventAttendeesByEventId: [ + { + _id: '1', + userId: 'user1', + isRegistered: true, + __typename: 'EventAttendee', + }, + ], + }, + }, + }, + { + request: { + query: EVENT_ATTENDEES, + variables: { id: 'event123' }, + }, + result: { + data: { + event: { + attendees: [ + { + _id: 'user1', + firstName: 'John', + lastName: 'Doe', + createdAt: '2023-09-25T10:00:00.000Z', + __typename: 'User', + }, + ], + }, + }, + }, + }, + ]; + + const customLink = new StaticMockLink(mockData, true); + render( + + + + + + + + + , + ); + + await waitFor(() => { + expect(screen.getByTestId('registrant-row-0')).toBeInTheDocument(); + }); + + // Validate mapped data + expect(screen.getByTestId('attendee-name-0')).toHaveTextContent('John Doe'); + expect(screen.getByTestId('registrant-registered-at-0')).toHaveTextContent( + '2023-09-25', + ); + expect(screen.getByTestId('registrant-created-at-0')).toHaveTextContent( + '10:00:00', + ); + }); + + test('Handles missing attendee data with fallback values', async () => { + const mocksWithMissingFields = [ + { + request: { + query: EVENT_REGISTRANTS, + variables: { eventId: 'event123' }, + }, + result: { + data: { + getEventAttendeesByEventId: [ + { + _id: '1', + userId: 'user1', + isRegistered: true, + __typename: 'EventAttendee', + }, + ], + }, + }, + }, + { + request: { + query: EVENT_ATTENDEES, + variables: { id: 'event123' }, + }, + result: { + data: { + event: { + attendees: [ + { + _id: 'user1', + firstName: 'Jane', + lastName: 'Doe', + createdAt: null, + __typename: 'User', + }, + ], + }, + }, + }, + }, + ]; + + const customLink = new StaticMockLink(mocksWithMissingFields, true); + render( + + + + + + + + + , + ); + + await waitFor(() => { + expect(screen.getByTestId('registrant-row-0')).toBeInTheDocument(); + }); + + // Validate fallback values + expect(screen.getByTestId('attendee-name-0')).toHaveTextContent('Jane Doe'); + expect(screen.getByTestId('registrant-created-at-0')).toHaveTextContent( + 'N/A', + ); + }); +}); diff --git a/src/components/EventManagement/EventRegistrant/EventRegistrants.tsx b/src/components/EventManagement/EventRegistrant/EventRegistrants.tsx new file mode 100644 index 0000000000..efcc2e91f7 --- /dev/null +++ b/src/components/EventManagement/EventRegistrant/EventRegistrants.tsx @@ -0,0 +1,227 @@ +import React, { useEffect, useState, useCallback } from 'react'; +import { + Paper, + TableCell, + TableContainer, + TableHead, + TableRow, + TableBody, +} from '@mui/material'; +import { Button, Table } from 'react-bootstrap'; +import { useTranslation } from 'react-i18next'; +import style from '../../../style/app.module.css'; +import { useLazyQuery } from '@apollo/client'; +import { EVENT_ATTENDEES, EVENT_REGISTRANTS } from 'GraphQl/Queries/Queries'; +import { useParams } from 'react-router-dom'; +import type { InterfaceMember } from '../EventAttendance/InterfaceEvents'; +import { EventRegistrantsWrapper } from 'components/EventRegistrantsModal/EventRegistrantsWrapper'; +import { CheckInWrapper } from 'components/CheckIn/CheckInWrapper'; +/** + * Interface for user data + */ +interface InterfaceUser { + _id: string; + userId: string; + isRegistered: boolean; + __typename: string; + time: string; +} +/** + * Component to manage and display event registrant information + * Includes adding new registrants and check-in functionality for registrants + * @returns JSX element containing the event attendance interface + */ +function EventRegistrants(): JSX.Element { + const { t } = useTranslation('translation', { + keyPrefix: 'eventRegistrant', + }); + const { orgId, eventId } = useParams<{ orgId: string; eventId: string }>(); + const [registrants, setRegistrants] = useState([]); + const [attendees, setAttendees] = useState([]); + const [combinedData, setCombinedData] = useState< + (InterfaceUser & Partial)[] + >([]); + // Fetch registrants + const [getEventRegistrants] = useLazyQuery(EVENT_REGISTRANTS, { + variables: { eventId }, + fetchPolicy: 'cache-and-network', + onCompleted: (data) => { + if (data?.getEventAttendeesByEventId) { + const mappedData = data.getEventAttendeesByEventId.map( + (attendee: InterfaceUser) => ({ + _id: attendee._id, + userId: attendee.userId, + isRegistered: attendee.isRegistered, + __typename: attendee.__typename, + }), + ); + setRegistrants(mappedData); + } + }, + }); + // Fetch attendees + const [getEventAttendees] = useLazyQuery(EVENT_ATTENDEES, { + variables: { id: eventId }, + fetchPolicy: 'cache-and-network', + onCompleted: (data) => { + if (data?.event?.attendees) { + setAttendees(data.event.attendees); + } + }, + }); + // callback function to refresh the data + const refreshData = useCallback(() => { + getEventRegistrants(); + getEventAttendees(); + }, [getEventRegistrants, getEventAttendees]); + useEffect(() => { + refreshData(); + }, [refreshData]); + // Combine registrants and attendees data + useEffect(() => { + if (registrants.length > 0 && attendees.length > 0) { + const mergedData = registrants.map((registrant) => { + const matchedAttendee = attendees.find( + (attendee) => attendee._id === registrant.userId, + ); + const [date, timeWithMilliseconds] = matchedAttendee?.createdAt + ? matchedAttendee.createdAt.split('T') + : ['N/A', 'N/A']; + const [time] = + timeWithMilliseconds !== 'N/A' + ? timeWithMilliseconds.split('.') + : ['N/A']; + return { + ...registrant, + firstName: matchedAttendee?.firstName || 'N/A', + lastName: matchedAttendee?.lastName || 'N/A', + createdAt: date, + time: time, + }; + }); + setCombinedData(mergedData); + } + }, [registrants, attendees]); + return ( +
+
+ {eventId ? ( + + ) : ( + + )} + +
+ + + + + + {t('serialNumber')} + + + {t('registrant')} + + + {t('registeredAt')} + + + {t('createdAt')} + + + {t('addRegistrant')} + + + + + {combinedData.length === 0 ? ( + + + {t('noRegistrantsFound')} + + + ) : ( + combinedData.map((data, index) => ( + + + {index + 1} + + + {data.firstName} {data.lastName} + + + {data.createdAt} + + + {data.time} + + + {eventId && orgId && ( + + )} + + + )) + )} + +
+
+
+ ); +} +export default EventRegistrants; diff --git a/src/components/EventManagement/EventRegistrant/Registrations.mocks.ts b/src/components/EventManagement/EventRegistrant/Registrations.mocks.ts new file mode 100644 index 0000000000..d71714459e --- /dev/null +++ b/src/components/EventManagement/EventRegistrant/Registrations.mocks.ts @@ -0,0 +1,67 @@ +import { EVENT_REGISTRANTS, EVENT_ATTENDEES } from 'GraphQl/Queries/Queries'; + +export const REGISTRANTS_MOCKS = [ + { + request: { + query: EVENT_REGISTRANTS, + variables: { eventId: '660fdf7d2c1ef6c7db1649ad' }, + }, + result: { + data: { + getEventAttendeesByEventId: [ + { + _id: '6589386a2caa9d8d69087484', + userId: '6589386a2caa9d8d69087484', + isRegistered: true, + __typename: 'EventAttendee', + }, + { + _id: '6589386a2caa9d8d69087485', + userId: '6589386a2caa9d8d69087485', + isRegistered: true, + __typename: 'EventAttendee', + }, + ], + }, + }, + }, + { + request: { + query: EVENT_ATTENDEES, + variables: { id: '660fdf7d2c1ef6c7db1649ad' }, + }, + result: { + data: { + event: { + attendees: [ + { + _id: '6589386a2caa9d8d69087484', + firstName: 'Bruce', + lastName: 'Garza', + createdAt: '2023-04-13T10:23:17.742Z', + __typename: 'User', + }, + { + _id: '6589386a2caa9d8d69087485', + firstName: 'Jane', + lastName: 'Smith', + createdAt: '2023-04-13T10:23:17.742Z', + __typename: 'User', + }, + ], + }, + }, + }, + }, +]; + +export const REGISTRANTS_MOCKS_ERROR = [ + { + // Error mock for EVENT_REGISTRANTS query + request: { + query: EVENT_REGISTRANTS, + variables: { eventId: 'event123' }, + }, + error: new Error('An error occurred while fetching registrants'), + }, +]; diff --git a/src/components/EventRegistrantsModal/AddOnSpotAttendee.test.tsx b/src/components/EventRegistrantsModal/AddOnSpotAttendee.spec.tsx similarity index 92% rename from src/components/EventRegistrantsModal/AddOnSpotAttendee.test.tsx rename to src/components/EventRegistrantsModal/AddOnSpotAttendee.spec.tsx index c0dc20d200..66f0dda38b 100644 --- a/src/components/EventRegistrantsModal/AddOnSpotAttendee.test.tsx +++ b/src/components/EventRegistrantsModal/AddOnSpotAttendee.spec.tsx @@ -11,23 +11,27 @@ import { Provider } from 'react-redux'; import { I18nextProvider } from 'react-i18next'; import { store } from 'state/store'; import i18nForTest from '../../utils/i18nForTest'; +import { describe, expect, vi } from 'vitest'; -jest.mock('react-toastify', () => ({ +vi.mock('react-toastify', () => ({ toast: { - success: jest.fn(), - error: jest.fn(), + success: vi.fn(), + error: vi.fn(), }, })); const mockProps = { show: true, - handleClose: jest.fn(), - reloadMembers: jest.fn(), + handleClose: vi.fn(), + reloadMembers: vi.fn(), }; -jest.mock('react-router-dom', () => ({ - ...jest.requireActual('react-router-dom'), - useParams: () => ({ eventId: '123', orgId: '123' }), -})); +vi.mock('react-router-dom', async () => { + const actual = await vi.importActual('react-router-dom'); + return { + ...actual, + useParams: () => ({ eventId: '123', orgId: '123' }), + }; +}); const MOCKS = [ { @@ -80,7 +84,7 @@ const renderAddOnSpotAttendee = (): RenderResult => { describe('AddOnSpotAttendee Component', () => { beforeEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); }); it('renders the component with all form fields', async () => { diff --git a/src/components/EventRegistrantsModal/EventRegistrantsModal.test.tsx b/src/components/EventRegistrantsModal/EventRegistrantsModal.spec.tsx similarity index 99% rename from src/components/EventRegistrantsModal/EventRegistrantsModal.test.tsx rename to src/components/EventRegistrantsModal/EventRegistrantsModal.spec.tsx index 8ca76393cd..4f422ceb7f 100644 --- a/src/components/EventRegistrantsModal/EventRegistrantsModal.test.tsx +++ b/src/components/EventRegistrantsModal/EventRegistrantsModal.spec.tsx @@ -15,6 +15,7 @@ import i18nForTest from 'utils/i18nForTest'; import { ToastContainer } from 'react-toastify'; import { LocalizationProvider } from '@mui/x-date-pickers'; import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; +import { describe, test, expect, vi } from 'vitest'; const queryMockWithoutRegistrant = [ { @@ -160,7 +161,7 @@ describe('Testing Event Registrants Modal', () => { show: true, eventId: 'event123', orgId: 'org123', - handleClose: jest.fn(), + handleClose: vi.fn(), }; test('The modal should be rendered, correct text must be displayed when there are no attendees and add attendee mutation must function properly', async () => { diff --git a/src/components/EventRegistrantsModal/EventRegistrantsWrapper.module.css b/src/components/EventRegistrantsModal/EventRegistrantsWrapper.module.css deleted file mode 100644 index 59b31333af..0000000000 --- a/src/components/EventRegistrantsModal/EventRegistrantsWrapper.module.css +++ /dev/null @@ -1,13 +0,0 @@ -button .iconWrapper { - width: 36px; - padding-right: 4px; - margin-right: 4px; - transform: translateY(4px); -} - -button .iconWrapperSm { - width: 36px; - display: flex; - justify-content: center; - align-items: center; -} diff --git a/src/components/EventRegistrantsModal/EventRegistrantsWrapper.test.tsx b/src/components/EventRegistrantsModal/EventRegistrantsWrapper.spec.tsx similarity index 95% rename from src/components/EventRegistrantsModal/EventRegistrantsWrapper.test.tsx rename to src/components/EventRegistrantsModal/EventRegistrantsWrapper.spec.tsx index d1707e8520..97a0d1f00f 100644 --- a/src/components/EventRegistrantsModal/EventRegistrantsWrapper.test.tsx +++ b/src/components/EventRegistrantsModal/EventRegistrantsWrapper.spec.tsx @@ -11,6 +11,7 @@ import i18nForTest from 'utils/i18nForTest'; import { ToastContainer } from 'react-toastify'; import { LocalizationProvider } from '@mui/x-date-pickers'; import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; +import { describe, test, expect } from 'vitest'; const queryMock = [ { @@ -77,7 +78,7 @@ describe('Testing Event Registrants Wrapper', () => { ); // Open the modal - fireEvent.click(queryByText('Show Registrants') as Element); + fireEvent.click(queryByText('Add Registrants') as Element); await waitFor(() => expect(queryByText('Event Registrants')).toBeInTheDocument(), diff --git a/src/components/EventRegistrantsModal/EventRegistrantsWrapper.tsx b/src/components/EventRegistrantsModal/EventRegistrantsWrapper.tsx index b198fcdd6d..36b6679a9f 100644 --- a/src/components/EventRegistrantsModal/EventRegistrantsWrapper.tsx +++ b/src/components/EventRegistrantsModal/EventRegistrantsWrapper.tsx @@ -1,13 +1,13 @@ import React, { useState } from 'react'; import { EventRegistrantsModal } from './EventRegistrantsModal'; import { Button } from 'react-bootstrap'; -import IconComponent from 'components/IconComponent/IconComponent'; -import styles from './EventRegistrantsWrapper.module.css'; +import style from '../../style/app.module.css'; // Props for the EventRegistrantsWrapper component type PropType = { eventId: string; orgId: string; + onUpdate?: () => void; }; /** @@ -20,37 +20,37 @@ type PropType = { export const EventRegistrantsWrapper = ({ eventId, orgId, + onUpdate, }: PropType): JSX.Element => { // State to control the visibility of the modal const [showModal, setShowModal] = useState(false); + const handleClose = (): void => { + setShowModal(false); + // Call onUpdate after modal is closed + if (onUpdate) { + onUpdate(); + } + }; return ( <> {/* Button to open the event registrants modal */} {/* Render the EventRegistrantsModal if showModal is true */} {showModal && ( { - setShowModal(false); // Hide the modal when closed - }} + handleClose={handleClose} eventId={eventId} orgId={orgId} /> diff --git a/src/components/IconComponent/IconComponent.test.tsx b/src/components/IconComponent/IconComponent.spec.tsx similarity index 94% rename from src/components/IconComponent/IconComponent.test.tsx rename to src/components/IconComponent/IconComponent.spec.tsx index 3ba6ccd84d..b398fe7986 100644 --- a/src/components/IconComponent/IconComponent.test.tsx +++ b/src/components/IconComponent/IconComponent.spec.tsx @@ -1,6 +1,7 @@ import React from 'react'; import { render, screen } from '@testing-library/react'; import IconComponent from './IconComponent'; +import { describe, it, expect } from 'vitest'; const screenTestIdMap: Record> = { MyOrganizations: { @@ -83,6 +84,10 @@ const screenTestIdMap: Record> = { name: 'My Pledges', testId: 'Icon-Component-My-Pledges', }, + LeaveOrganization: { + name: 'Leave Organization', + testId: 'Icon-Component-Leave-Organization', + }, Volunteer: { name: 'Volunteer', testId: 'Icon-Component-Volunteer', diff --git a/src/components/LeftDrawerOrg/LeftDrawerOrg.test.tsx b/src/components/LeftDrawerOrg/LeftDrawerOrg.spec.tsx similarity index 98% rename from src/components/LeftDrawerOrg/LeftDrawerOrg.test.tsx rename to src/components/LeftDrawerOrg/LeftDrawerOrg.spec.tsx index 71f3593499..3fa6c0205e 100644 --- a/src/components/LeftDrawerOrg/LeftDrawerOrg.test.tsx +++ b/src/components/LeftDrawerOrg/LeftDrawerOrg.spec.tsx @@ -1,7 +1,7 @@ +import '@testing-library/jest-dom'; import React, { act } from 'react'; import { fireEvent, render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import 'jest-localstorage-mock'; import { I18nextProvider } from 'react-i18next'; import { BrowserRouter, MemoryRouter } from 'react-router-dom'; @@ -15,6 +15,7 @@ import { ORGANIZATIONS_LIST } from 'GraphQl/Queries/Queries'; import { StaticMockLink } from 'utils/StaticMockLink'; import { REVOKE_REFRESH_TOKEN } from 'GraphQl/Mutations/mutations'; import useLocalStorage from 'utils/useLocalstorage'; +import { vi, describe, test, expect } from 'vitest'; const { setItem } = useLocalStorage(); @@ -65,7 +66,7 @@ const props: InterfaceLeftDrawerProps = { }, ], hideDrawer: false, - setHideDrawer: jest.fn(), + setHideDrawer: vi.fn(), }; const MOCKS = [ @@ -244,11 +245,11 @@ const defaultScreens = [ 'All Organizations', ]; -jest.mock('react-toastify', () => ({ +vi.mock('react-toastify', () => ({ toast: { - success: jest.fn(), - warn: jest.fn(), - error: jest.fn(), + success: vi.fn(), + warn: vi.fn(), + error: vi.fn(), }, })); @@ -275,7 +276,7 @@ beforeEach(() => { }); afterEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); localStorage.clear(); }); diff --git a/src/components/MemberDetail/EventsAttendedByMember.test.tsx b/src/components/MemberDetail/EventsAttendedByMember.spec.tsx similarity index 100% rename from src/components/MemberDetail/EventsAttendedByMember.test.tsx rename to src/components/MemberDetail/EventsAttendedByMember.spec.tsx diff --git a/src/components/MemberDetail/EventsAttendedCardItem.test.tsx b/src/components/MemberDetail/EventsAttendedCardItem.spec.tsx similarity index 96% rename from src/components/MemberDetail/EventsAttendedCardItem.test.tsx rename to src/components/MemberDetail/EventsAttendedCardItem.spec.tsx index afbb19eeea..8694426d58 100644 --- a/src/components/MemberDetail/EventsAttendedCardItem.test.tsx +++ b/src/components/MemberDetail/EventsAttendedCardItem.spec.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { render, screen } from '@testing-library/react'; import { BrowserRouter } from 'react-router-dom'; import EventAttendedCard from './EventsAttendedCardItem'; +import { vi } from 'vitest'; interface InterfaceEventAttendedCardProps { type: 'Event'; @@ -33,7 +34,7 @@ describe('EventAttendedCard', () => { }; beforeEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); }); it('renders event details correctly', () => { diff --git a/src/components/MemberDetail/EventsAttendedMemberModal.test.tsx b/src/components/MemberDetail/EventsAttendedMemberModal.spec.tsx similarity index 91% rename from src/components/MemberDetail/EventsAttendedMemberModal.test.tsx rename to src/components/MemberDetail/EventsAttendedMemberModal.spec.tsx index ebdc3fff4c..fdec64e5f0 100644 --- a/src/components/MemberDetail/EventsAttendedMemberModal.test.tsx +++ b/src/components/MemberDetail/EventsAttendedMemberModal.spec.tsx @@ -3,15 +3,24 @@ import { render, screen, fireEvent } from '@testing-library/react'; import { MockedProvider } from '@apollo/client/testing'; import { BrowserRouter } from 'react-router-dom'; import EventsAttendedMemberModal from './EventsAttendedMemberModal'; +import { vi } from 'vitest'; -jest.mock('react-i18next', () => ({ +/** + * Mock the `react-i18next` module to provide translation functionality. + */ + +vi.mock('react-i18next', () => ({ useTranslation: () => ({ t: (key: string) => key, i18n: { changeLanguage: () => Promise.resolve() }, }), })); -jest.mock('./customTableCell', () => ({ +/** + * Mock the `CustomTableCell` component for testing. + */ + +vi.mock('./customTableCell', () => ({ CustomTableCell: ({ eventId }: { eventId: string }) => ( {`Event ${eventId}`} @@ -33,12 +42,12 @@ const mockEvents = Array.from({ length: 6 }, (_, index) => ({ describe('EventsAttendedMemberModal', () => { const defaultProps = { eventsAttended: mockEvents, - setShow: jest.fn(), + setShow: vi.fn(), show: true, }; beforeEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); }); test('renders modal with correct title when show is true', () => { @@ -95,7 +104,7 @@ describe('EventsAttendedMemberModal', () => { }); test('closes modal when close button is clicked', () => { - const mockSetShow = jest.fn(); + const mockSetShow = vi.fn(); render( diff --git a/src/components/NotFound/NotFound.test.tsx b/src/components/NotFound/NotFound.spec.tsx similarity index 94% rename from src/components/NotFound/NotFound.test.tsx rename to src/components/NotFound/NotFound.spec.tsx index 54c0bcfe4a..a70e355f7a 100644 --- a/src/components/NotFound/NotFound.test.tsx +++ b/src/components/NotFound/NotFound.spec.tsx @@ -1,9 +1,9 @@ import React from 'react'; import { I18nextProvider } from 'react-i18next'; import i18nForTest from 'utils/i18nForTest'; - import { render, screen } from '@testing-library/react'; import NotFound from './NotFound'; +import { expect, it, describe } from 'vitest'; describe('Tesing the NotFound Component', () => { it('renders the component with the correct title for posts', () => { diff --git a/src/components/OrgAdminListCard/OrgAdminListCard.test.tsx b/src/components/OrgAdminListCard/OrgAdminListCard.spec.tsx similarity index 82% rename from src/components/OrgAdminListCard/OrgAdminListCard.test.tsx rename to src/components/OrgAdminListCard/OrgAdminListCard.spec.tsx index 7baea946d2..a68b253c0a 100644 --- a/src/components/OrgAdminListCard/OrgAdminListCard.test.tsx +++ b/src/components/OrgAdminListCard/OrgAdminListCard.spec.tsx @@ -9,6 +9,7 @@ import OrgAdminListCard from './OrgAdminListCard'; import i18nForTest from 'utils/i18nForTest'; import { MemoryRouter, Route, Routes } from 'react-router-dom'; import { StaticMockLink } from 'utils/StaticMockLink'; +import { vi, beforeEach, afterEach, expect, it, describe } from 'vitest'; const MOCKS = [ { @@ -57,27 +58,28 @@ const renderOrgAdminListCard = (props: { , ); }; -jest.mock('i18next-browser-languagedetector', () => ({ - init: jest.fn(), +vi.mock('i18next-browser-languagedetector', async () => ({ + ...(await vi.importActual('i18next-browser-languagedetector')), + init: vi.fn(), type: 'languageDetector', - detect: jest.fn(() => 'en'), - cacheUserLanguage: jest.fn(), + detect: vi.fn(() => 'en'), + cacheUserLanguage: vi.fn(), })); describe('Testing Organization Admin List Card', () => { - global.alert = jest.fn(); + global.alert = vi.fn(); beforeEach(() => { Object.defineProperty(window, 'location', { writable: true, - value: { reload: jest.fn() }, + value: { reload: vi.fn() }, }); }); afterEach(() => { - jest.restoreAllMocks(); + vi.restoreAllMocks(); }); - test('should render props and text elements test for the page component', async () => { + it('should render props and text elements test for the page component', async () => { const props = { toggleRemoveModal: () => true, id: '456', @@ -92,7 +94,7 @@ describe('Testing Organization Admin List Card', () => { await wait(2000); }); - test('Should not render text elements when props value is not passed', async () => { + it('Should not render text elements when props value is not passed', async () => { const props = { toggleRemoveModal: () => true, id: undefined, diff --git a/src/components/OrgContriCards/OrgContriCards.test.tsx b/src/components/OrgContriCards/OrgContriCards.spec.tsx similarity index 97% rename from src/components/OrgContriCards/OrgContriCards.test.tsx rename to src/components/OrgContriCards/OrgContriCards.spec.tsx index 4f202cd355..57a85dc451 100644 --- a/src/components/OrgContriCards/OrgContriCards.test.tsx +++ b/src/components/OrgContriCards/OrgContriCards.spec.tsx @@ -7,7 +7,7 @@ import { I18nextProvider } from 'react-i18next'; import OrgContriCards from './OrgContriCards'; import i18nForTest from 'utils/i18nForTest'; import { BACKEND_URL } from 'Constant/constant'; - +import { describe, expect } from 'vitest'; const client: ApolloClient = new ApolloClient({ cache: new InMemoryCache(), uri: BACKEND_URL, diff --git a/src/components/OrgDelete/OrgDelete.test.tsx b/src/components/OrgDelete/OrgDelete.spec.tsx similarity index 86% rename from src/components/OrgDelete/OrgDelete.test.tsx rename to src/components/OrgDelete/OrgDelete.spec.tsx index b9b9ca2572..23f8dcdde5 100644 --- a/src/components/OrgDelete/OrgDelete.test.tsx +++ b/src/components/OrgDelete/OrgDelete.spec.tsx @@ -3,7 +3,7 @@ import { render, screen } from '@testing-library/react'; import type { NormalizedCacheObject } from '@apollo/client'; import { ApolloClient, ApolloProvider, InMemoryCache } from '@apollo/client'; import { I18nextProvider } from 'react-i18next'; - +import { describe, it, expect } from 'vitest'; import OrgDelete from './OrgDelete'; import i18nForTest from 'utils/i18nForTest'; import { BACKEND_URL } from 'Constant/constant'; @@ -14,7 +14,7 @@ const client: ApolloClient = new ApolloClient({ }); describe('Testing Organization People List Card', () => { - test('should render props and text elements test for the page component', () => { + it('should render props and text elements test for the page component', () => { render( diff --git a/src/components/OrgPeopleListCard/OrgPeopleListCard.test.tsx b/src/components/OrgPeopleListCard/OrgPeopleListCard.spec.tsx similarity index 96% rename from src/components/OrgPeopleListCard/OrgPeopleListCard.test.tsx rename to src/components/OrgPeopleListCard/OrgPeopleListCard.spec.tsx index 7cee31107f..0fb2c39599 100644 --- a/src/components/OrgPeopleListCard/OrgPeopleListCard.test.tsx +++ b/src/components/OrgPeopleListCard/OrgPeopleListCard.spec.tsx @@ -9,7 +9,7 @@ import { REMOVE_MEMBER_MUTATION } from 'GraphQl/Mutations/mutations'; import i18nForTest from 'utils/i18nForTest'; import { BrowserRouter } from 'react-router-dom'; import { StaticMockLink } from 'utils/StaticMockLink'; - +import { describe, test, expect, vi } from 'vitest'; const MOCKS = [ { request: { @@ -41,7 +41,7 @@ describe('Testing Organization People List Card', () => { toggleRemoveModal: () => true, id: '1', }; - global.alert = jest.fn(); + global.alert = vi.fn(); test('should render props and text elements test for the page component', async () => { global.confirm = (): boolean => true; diff --git a/src/components/OrgPostCard/OrgPostCard.test.tsx b/src/components/OrgPostCard/OrgPostCard.spec.tsx similarity index 86% rename from src/components/OrgPostCard/OrgPostCard.test.tsx rename to src/components/OrgPostCard/OrgPostCard.spec.tsx index 7105e5e8f2..5364766aee 100644 --- a/src/components/OrgPostCard/OrgPostCard.test.tsx +++ b/src/components/OrgPostCard/OrgPostCard.spec.tsx @@ -10,7 +10,6 @@ import { MockedProvider } from '@apollo/react-testing'; import OrgPostCard from './OrgPostCard'; import { I18nextProvider } from 'react-i18next'; import userEvent from '@testing-library/user-event'; -import 'jest-localstorage-mock'; import { DELETE_POST_MUTATION, UPDATE_POST_MUTATION, @@ -21,6 +20,36 @@ import { StaticMockLink } from 'utils/StaticMockLink'; import convertToBase64 from 'utils/convertToBase64'; import { BrowserRouter } from 'react-router-dom'; import useLocalStorage from 'utils/useLocalstorage'; +import { vi } from 'vitest'; + +/** + * Unit tests for the OrgPostCard component, which displays organization posts with various interactions. + * + * These tests verify: + * - Basic rendering and display functionality: + * - Proper rendering of post content (title, text, images, videos) + * - "Read more" toggle button behavior + * - Image and video display handling + * - Fallback behavior when media is missing + * + * - Modal interactions: + * - Opening/closing primary modal on post click + * - Secondary modal functionality for edit/delete operations + * - Form validation in edit modal + * - Media upload handling in edit modal + * + * - Post management operations: + * - Creating and updating posts + * - Deleting posts + * - Pinning/unpinning posts + * - Error handling for failed operations + * + * - Media handling: + * - Image upload and preview + * - Video upload and preview + * - Auto-play behavior on hover + * - Clearing uploaded media + */ const { setItem } = useLocalStorage(); @@ -71,19 +100,23 @@ const MOCKS = [ }, }, ]; -jest.mock('react-toastify', () => ({ +vi.mock('react-toastify', () => ({ toast: { - success: jest.fn(), - warn: jest.fn(), - error: jest.fn(), + success: vi.fn(), + warn: vi.fn(), + error: vi.fn(), }, })); -jest.mock('i18next-browser-languagedetector', () => ({ - init: jest.fn(), - type: 'languageDetector', - detect: jest.fn(() => 'en'), - cacheUserLanguage: jest.fn(), -})); +vi.mock('i18next-browser-languagedetector', () => { + return { + default: { + init: vi.fn(), + type: 'languageDetector', + detect: vi.fn(() => 'en'), + cacheUserLanguage: vi.fn(), + }, + }; +}); const link = new StaticMockLink(MOCKS, true); async function wait(ms = 100): Promise { await act(() => { @@ -99,7 +132,7 @@ describe('Testing Organization Post Card', () => { Object.defineProperty(window, 'location', { configurable: true, value: { - reload: jest.fn(), + reload: vi.fn(), }, }); }); @@ -122,20 +155,16 @@ describe('Testing Organization Post Card', () => { pinned: false, }; - jest.mock('react-toastify', () => ({ + vi.mock('react-toastify', () => ({ toast: { - success: jest.fn(), - warn: jest.fn(), - error: jest.fn(), + success: vi.fn(), + warn: vi.fn(), + error: vi.fn(), }, })); - jest.mock('react', () => ({ - ...jest.requireActual('react'), - useRef: jest.fn(), - })); - global.alert = jest.fn(); + global.alert = vi.fn(); - test('Opens post on image click', () => { + it('Opens post on image click', () => { const { getByTestId, getByAltText } = render( @@ -149,7 +178,7 @@ describe('Testing Organization Post Card', () => { expect(getByTestId('card-title')).toBeInTheDocument(); expect(getByAltText('image')).toBeInTheDocument(); }); - test('renders with default props', () => { + it('renders with default props', () => { const { getByAltText, getByTestId } = render( @@ -161,7 +190,7 @@ describe('Testing Organization Post Card', () => { expect(getByTestId('card-title')).toBeInTheDocument(); expect(getByAltText('image')).toBeInTheDocument(); }); - test('toggles "Read more" button', () => { + it('toggles "Read more" button', () => { const { getByTestId } = render( @@ -176,7 +205,7 @@ describe('Testing Organization Post Card', () => { fireEvent.click(toggleButton); expect(toggleButton).toHaveTextContent('Read more'); }); - test('opens and closes edit modal', async () => { + it('opens and closes edit modal', async () => { setItem('id', '123'); render( @@ -196,7 +225,7 @@ describe('Testing Organization Post Card', () => { userEvent.click(createOrgBtn); userEvent.click(screen.getByTestId('closeOrganizationModal')); }); - test('Should render text elements when props value is not passed', async () => { + it('Should render text elements when props value is not passed', async () => { global.confirm = (): boolean => false; render( @@ -209,7 +238,7 @@ describe('Testing Organization Post Card', () => { userEvent.click(screen.getByAltText('image')); expect(screen.getByAltText('Post Image')).toBeInTheDocument(); }); - test('Testing post updating after post is updated', async () => { + it('Testing post updating after post is updated', async () => { const { getByTestId } = render( @@ -265,7 +294,7 @@ describe('Testing Organization Post Card', () => { await waitFor(() => { convertToBase64(file); // Replace with the expected base64-encoded image }); - document.getElementById = jest.fn(() => input); + document.getElementById = vi.fn(() => input); const clearImageButton = getByTestId('closeimage'); fireEvent.click(clearImageButton); } @@ -278,7 +307,7 @@ describe('Testing Organization Post Card', () => { { timeout: 2500 }, ); }); - test('Testing post updating functionality fail case', async () => { + it('Testing post updating functionality fail case', async () => { const props2 = { id: '', postID: '123', @@ -343,7 +372,7 @@ describe('Testing Organization Post Card', () => { await waitFor(() => { convertToBase64(file); // Replace with the expected base64-encoded image }); - document.getElementById = jest.fn(() => input); + document.getElementById = vi.fn(() => input); const clearImageButton = getByTestId('closeimage'); fireEvent.click(clearImageButton); } @@ -356,7 +385,7 @@ describe('Testing Organization Post Card', () => { { timeout: 2500 }, ); }); - test('Testing pin post functionality', async () => { + it('Testing pin post functionality', async () => { render( @@ -378,7 +407,7 @@ describe('Testing Organization Post Card', () => { { timeout: 3000 }, ); }); - test('Testing pin post functionality fail case', async () => { + it('Testing pin post functionality fail case', async () => { const props2 = { id: '', postID: '123', @@ -403,7 +432,7 @@ describe('Testing Organization Post Card', () => { userEvent.click(screen.getByTestId('moreiconbtn')); userEvent.click(screen.getByTestId('pinpostBtn')); }); - test('Testing post delete functionality', async () => { + it('Testing post delete functionality', async () => { render( @@ -429,7 +458,7 @@ describe('Testing Organization Post Card', () => { { timeout: 3000 }, ); }); - test('Testing post delete functionality fail case', async () => { + it('Testing post delete functionality fail case', async () => { const props2 = { id: '', postID: '123', @@ -458,7 +487,7 @@ describe('Testing Organization Post Card', () => { userEvent.click(screen.getByTestId('deletePostModalBtn')); fireEvent.click(screen.getByTestId('deletePostBtn')); }); - test('Testing close functionality of primary modal', async () => { + it('Testing close functionality of primary modal', async () => { render( @@ -475,7 +504,7 @@ describe('Testing Organization Post Card', () => { //Primary Modal is closed expect(screen.queryByTestId('moreiconbtn')).not.toBeInTheDocument(); }); - test('Testing close functionality of secondary modal', async () => { + it('Testing close functionality of secondary modal', async () => { render( @@ -496,7 +525,7 @@ describe('Testing Organization Post Card', () => { expect(screen.queryByTestId('pinpostBtn')).not.toBeInTheDocument(); expect(screen.queryByTestId('closebtn')).not.toBeInTheDocument(); }); - test('renders without "Read more" button when postInfo length is less than or equal to 43', () => { + it('renders without "Read more" button when postInfo length is less than or equal to 43', () => { render( @@ -506,7 +535,7 @@ describe('Testing Organization Post Card', () => { ); expect(screen.queryByTestId('toggleBtn')).not.toBeInTheDocument(); }); - test('renders with "Read more" button when postInfo length is more than 43', () => { + it('renders with "Read more" button when postInfo length is more than 43', () => { const props2 = { id: '12', postID: '123', @@ -529,7 +558,7 @@ describe('Testing Organization Post Card', () => { expect(screen.getByTestId('toggleBtn')).toBeInTheDocument(); }); - test('updates state variables correctly when handleEditModal is called', () => { + it('updates state variables correctly when handleEditModal is called', () => { const link2 = new StaticMockLink(MOCKS, true); render( @@ -555,7 +584,7 @@ describe('Testing Organization Post Card', () => { expect(screen.queryByTestId('pinpostBtn')).not.toBeInTheDocument(); expect(screen.queryByTestId('closebtn')).not.toBeInTheDocument(); }); - test('updates state variables correctly when handleDeleteModal is called', () => { + it('updates state variables correctly when handleDeleteModal is called', () => { const link2 = new StaticMockLink(MOCKS, true); render( @@ -581,7 +610,7 @@ describe('Testing Organization Post Card', () => { expect(screen.queryByTestId('pinpostBtn')).not.toBeInTheDocument(); expect(screen.queryByTestId('closebtn')).not.toBeInTheDocument(); }); - test('clears postvideo state and resets file input value', async () => { + it('clears postvideo state and resets file input value', async () => { const { getByTestId } = render( @@ -615,7 +644,7 @@ describe('Testing Organization Post Card', () => { userEvent.click(screen.getByTestId('closePreview')); } }); - test('clears postimage state and resets file input value', async () => { + it('clears postimage state and resets file input value', async () => { const { getByTestId } = render( @@ -647,12 +676,12 @@ describe('Testing Organization Post Card', () => { await waitFor(() => { convertToBase64(file); // Replace with the expected base64-encoded image }); - document.getElementById = jest.fn(() => input); + document.getElementById = vi.fn(() => input); const clearImageButton = getByTestId('closeimage'); fireEvent.click(clearImageButton); } }); - test('clears postitle state and resets file input value', async () => { + it('clears postitle state and resets file input value', async () => { const { getByTestId } = render( @@ -677,7 +706,7 @@ describe('Testing Organization Post Card', () => { expect(screen.getByTestId('closeOrganizationModal')).toBeInTheDocument(); expect(screen.getByTestId('updatePostBtn')).toBeInTheDocument(); }); - test('clears postinfo state and resets file input value', async () => { + it('clears postinfo state and resets file input value', async () => { const { getByTestId } = render( @@ -702,7 +731,7 @@ describe('Testing Organization Post Card', () => { expect(screen.getByTestId('closeOrganizationModal')).toBeInTheDocument(); expect(screen.getByTestId('updatePostBtn')).toBeInTheDocument(); }); - test('Testing create organization modal', async () => { + it('Testing create organization modal', async () => { setItem('id', '123'); render( @@ -724,7 +753,7 @@ describe('Testing Organization Post Card', () => { userEvent.click(createOrgBtn); userEvent.click(screen.getByTestId('closeOrganizationModal')); }); - test('should toggle post pin when pin button is clicked', async () => { + it('should toggle post pin when pin button is clicked', async () => { const { getByTestId } = render( @@ -744,6 +773,12 @@ describe('Testing Organization Post Card', () => { }); }); test('testing video play and pause on mouse enter and leave events', async () => { + const playMock = vi.fn(); + const pauseMock = vi.fn(); + + HTMLMediaElement.prototype.play = playMock; + HTMLMediaElement.prototype.pause = pauseMock; + const { getByTestId } = render( @@ -754,16 +789,14 @@ describe('Testing Organization Post Card', () => { const card = getByTestId('cardVid'); - HTMLVideoElement.prototype.play = jest.fn(); - HTMLVideoElement.prototype.pause = jest.fn(); - fireEvent.mouseEnter(card); - expect(HTMLVideoElement.prototype.play).toHaveBeenCalled(); + expect(playMock).toHaveBeenCalledTimes(1); fireEvent.mouseLeave(card); - expect(HTMLVideoElement.prototype.pause).toHaveBeenCalled(); + expect(pauseMock).toHaveBeenCalledTimes(1); }); - test('for rendering when no image and no video is available', async () => { + + it('for rendering when no image and no video is available', async () => { const props2 = { id: '', postID: '123', diff --git a/src/components/OrgSettings/ActionItemCategories/CategoryModal.test.tsx b/src/components/OrgSettings/ActionItemCategories/CategoryModal.spec.tsx similarity index 85% rename from src/components/OrgSettings/ActionItemCategories/CategoryModal.test.tsx rename to src/components/OrgSettings/ActionItemCategories/CategoryModal.spec.tsx index 39d4884e8b..e4b5663788 100644 --- a/src/components/OrgSettings/ActionItemCategories/CategoryModal.test.tsx +++ b/src/components/OrgSettings/ActionItemCategories/CategoryModal.spec.tsx @@ -14,11 +14,23 @@ import { MOCKS, MOCKS_ERROR } from './OrgActionItemCategoryMocks'; import type { InterfaceActionItemCategoryModal } from './CategoryModal'; import CategoryModal from './CategoryModal'; import { toast } from 'react-toastify'; - -jest.mock('react-toastify', () => ({ +import { vi } from 'vitest'; +/** + * This file contains unit tests for the `CategoryModal` component. + * + * The tests cover: + * - Proper rendering of the component in various scenarios, including `create` and `edit` modes, mock data, and error states. + * - Handling user interactions with form fields, such as updating the category name and toggling the `isDisabled` switch. + * - Ensuring form submissions trigger appropriate callbacks (e.g., `refetchCategories` and `hide`) and display correct toast notifications. + * - Simulating GraphQL query and mutation operations with mocked data to validate behavior in success and error cases. + * - Testing edge cases, such as submitting without changes, invalid inputs, and handling API errors gracefully. + * - Verifying proper integration of internationalization, Redux state, routing, and toast notifications for success and error feedback. + */ + +vi.mock('react-toastify', () => ({ toast: { - success: jest.fn(), - error: jest.fn(), + success: vi.fn(), + error: vi.fn(), }, })); @@ -37,8 +49,8 @@ const translations = { const categoryProps: InterfaceActionItemCategoryModal[] = [ { isOpen: true, - hide: jest.fn(), - refetchCategories: jest.fn(), + hide: vi.fn(), + refetchCategories: vi.fn(), orgId: 'orgId', mode: 'create', category: { @@ -51,8 +63,8 @@ const categoryProps: InterfaceActionItemCategoryModal[] = [ }, { isOpen: true, - hide: jest.fn(), - refetchCategories: jest.fn(), + hide: vi.fn(), + refetchCategories: vi.fn(), orgId: 'orgId', mode: 'edit', category: { diff --git a/src/components/OrgSettings/ActionItemCategories/OrgActionItemCategories.test.tsx b/src/components/OrgSettings/ActionItemCategories/OrgActionItemCategories.spec.tsx similarity index 83% rename from src/components/OrgSettings/ActionItemCategories/OrgActionItemCategories.test.tsx rename to src/components/OrgSettings/ActionItemCategories/OrgActionItemCategories.spec.tsx index 784d69325f..27eec94851 100644 --- a/src/components/OrgSettings/ActionItemCategories/OrgActionItemCategories.test.tsx +++ b/src/components/OrgSettings/ActionItemCategories/OrgActionItemCategories.spec.tsx @@ -12,19 +12,36 @@ import i18n from 'utils/i18nForTest'; import type { ApolloLink } from '@apollo/client'; import { MOCKS, MOCKS_EMPTY, MOCKS_ERROR } from './OrgActionItemCategoryMocks'; import OrgActionItemCategories from './OrgActionItemCategories'; - -jest.mock('react-toastify', () => ({ +import { vi } from 'vitest'; + +/** + * This file contains unit tests for the `OrgActionItemCategories` component. + * + * The tests cover: + * - Proper rendering of the component under different conditions, including scenarios with populated categories, empty categories, and API errors. + * - User interactions such as searching, filtering, sorting categories, and opening/closing modals for creating or editing categories. + * - Verification of GraphQL query and mutation behaviors using mock data, ensuring correct functionality in both success and error cases. + * - Handling edge cases like no input, invalid input, and form resets. + * - Integration tests for Redux state, routing, internationalization, and toast notifications. + * - Ensuring sorting functionality reflects the `createdAt` property both in ascending and descending order. + * - Testing the modal interactions for creating and editing categories, ensuring proper lifecycle (open/close) and state updates. + * - Checking the rendering of error messages and placeholders when no data is available or an error occurs. + * - Validation of search functionality for categories by name, including clearing the search input and using keyboard shortcuts like `Enter`. + */ + +vi.mock('react-toastify', () => ({ toast: { - success: jest.fn(), - error: jest.fn(), + success: vi.fn(), + error: vi.fn(), }, })); -jest.mock('@mui/x-date-pickers/DateTimePicker', () => { +vi.mock('@mui/x-date-pickers/DateTimePicker', async () => { + const dateTimePickerModule = await vi.importActual( + '@mui/x-date-pickers/DesktopDateTimePicker', + ); return { - DateTimePicker: jest.requireActual( - '@mui/x-date-pickers/DesktopDateTimePicker', - ).DesktopDateTimePicker, + DateTimePicker: dateTimePickerModule.DesktopDateTimePicker, }; }); diff --git a/src/components/OrgSettings/AgendaItemCategories/AgendaCategoryCreateModal.test.tsx b/src/components/OrgSettings/AgendaItemCategories/AgendaCategoryCreateModal.spec.tsx similarity index 76% rename from src/components/OrgSettings/AgendaItemCategories/AgendaCategoryCreateModal.test.tsx rename to src/components/OrgSettings/AgendaItemCategories/AgendaCategoryCreateModal.spec.tsx index da92dfd201..9b69a0e331 100644 --- a/src/components/OrgSettings/AgendaItemCategories/AgendaCategoryCreateModal.test.tsx +++ b/src/components/OrgSettings/AgendaItemCategories/AgendaCategoryCreateModal.spec.tsx @@ -1,30 +1,40 @@ import React from 'react'; import { render, screen, fireEvent } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; import { MockedProvider } from '@apollo/react-testing'; import { I18nextProvider } from 'react-i18next'; import { Provider } from 'react-redux'; import { BrowserRouter } from 'react-router-dom'; import { store } from 'state/store'; import i18nForTest from 'utils/i18nForTest'; - import { LocalizationProvider } from '@mui/x-date-pickers'; import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; - import AgendaCategoryCreateModal from './AgendaCategoryCreateModal'; +import { vi } from 'vitest'; +/** + * This file contains unit tests for the `AgendaCategoryCreateModal` component. + * + * The tests cover: + * - Rendering of the modal, ensuring all elements such as form fields, buttons, and labels are displayed correctly. + * - Behavior of form inputs, including updating the `formState` when the `name` and `description` fields are changed. + * - Proper invocation of the `createAgendaCategoryHandler` when the form is submitted. + * - Integration of Redux state, routing, localization (i18n), and date-picker utilities to ensure compatibility and proper rendering. + * - Validations for form controls to check user interactions, including typing and submitting the form. + * - Mock function verifications for `setFormState`, `hideCreateModal`, and other handlers to ensure state changes and actions are triggered appropriately. + * - Handling edge cases, such as empty fields or invalid data, ensuring graceful degradation of functionality. + */ const mockFormState = { name: 'Test Name', description: 'Test Description', createdBy: 'Test User', }; -const mockHideCreateModal = jest.fn(); -const mockSetFormState = jest.fn(); -const mockCreateAgendaCategoryHandler = jest.fn(); +const mockHideCreateModal = vi.fn(); +const mockSetFormState = vi.fn(); +const mockCreateAgendaCategoryHandler = vi.fn(); const mockT = (key: string): string => key; describe('AgendaCategoryCreateModal', () => { - test('renders modal correctly', () => { + it('renders modal correctly', () => { render( @@ -54,7 +64,7 @@ describe('AgendaCategoryCreateModal', () => { screen.getByTestId('createAgendaCategoryModalCloseBtn'), ).toBeInTheDocument(); }); - test('tests the condition for formState.name and formState.description', () => { + it('tests the condition for formState.name and formState.description', () => { const mockFormState = { name: 'Test Name', description: 'Test Description', @@ -97,7 +107,7 @@ describe('AgendaCategoryCreateModal', () => { description: 'New description', }); }); - test('calls createAgendaCategoryHandler when form is submitted', () => { + it('calls createAgendaCategoryHandler when form is submitted', () => { render( diff --git a/src/components/OrgSettings/AgendaItemCategories/AgendaCategoryUpdateModal.test.tsx b/src/components/OrgSettings/AgendaItemCategories/AgendaCategoryUpdateModal.spec.tsx similarity index 82% rename from src/components/OrgSettings/AgendaItemCategories/AgendaCategoryUpdateModal.test.tsx rename to src/components/OrgSettings/AgendaItemCategories/AgendaCategoryUpdateModal.spec.tsx index 168b97abd3..8be982271c 100644 --- a/src/components/OrgSettings/AgendaItemCategories/AgendaCategoryUpdateModal.test.tsx +++ b/src/components/OrgSettings/AgendaItemCategories/AgendaCategoryUpdateModal.spec.tsx @@ -7,24 +7,36 @@ import { Provider } from 'react-redux'; import { BrowserRouter } from 'react-router-dom'; import { store } from 'state/store'; import i18nForTest from 'utils/i18nForTest'; - import { LocalizationProvider } from '@mui/x-date-pickers'; import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; - import AgendaCategoryUpdateModal from './AgendaCategoryUpdateModal'; +import { vi } from 'vitest'; + +/** + * Unit tests for `AgendaCategoryUpdateModal`: + * + * - **Rendering**: Verifies key elements (e.g., text, buttons) render correctly. + * - **Close Button**: Ensures `hideUpdateModal` is called on close. + * - **Form Inputs**: Confirms `setFormState` updates with new `name` and `description`. + * - **Submission**: Checks `updateAgendaCategoryHandler` triggers on submit. + * - **Integration**: Validates compatibility with Redux, routing, i18n, and MUI date-picker. + * - **Mocks**: Ensures handlers (`setFormState`, `hideUpdateModal`, `updateAgendaCategoryHandler`) are called with correct arguments. + * + * This suite ensures component reliability and behavior consistency. + */ const mockFormState = { name: 'Test Name', description: 'Test Description', createdBy: 'Test User', }; -const mockHideUpdateModal = jest.fn(); -const mockSetFormState = jest.fn(); -const mockUpdateAgendaCategoryHandler = jest.fn(); +const mockHideUpdateModal = vi.fn(); +const mockSetFormState = vi.fn(); +const mockUpdateAgendaCategoryHandler = vi.fn(); const mockT = (key: string): string => key; describe('AgendaCategoryUpdateModal', () => { - test('renders modal correctly', () => { + it('renders modal correctly', () => { render( @@ -53,7 +65,7 @@ describe('AgendaCategoryUpdateModal', () => { ).toBeInTheDocument(); }); - test('calls hideUpdateModal when close button is clicked', () => { + it('calls hideUpdateModal when close button is clicked', () => { render( @@ -79,7 +91,7 @@ describe('AgendaCategoryUpdateModal', () => { expect(mockHideUpdateModal).toHaveBeenCalledTimes(1); }); - test('tests the condition for formState.name and formState.description', () => { + it('tests the condition for formState.name and formState.description', () => { const mockFormState = { name: 'Test Name', description: 'Test Description', @@ -123,7 +135,7 @@ describe('AgendaCategoryUpdateModal', () => { }); }); - test('calls updateAgendaCategoryHandler when form is submitted', () => { + it('calls updateAgendaCategoryHandler when form is submitted', () => { render( diff --git a/src/components/OrgSettings/AgendaItemCategories/OrganizationAgendaCategory.test.tsx b/src/components/OrgSettings/AgendaItemCategories/OrganizationAgendaCategory.spec.tsx similarity index 82% rename from src/components/OrgSettings/AgendaItemCategories/OrganizationAgendaCategory.test.tsx rename to src/components/OrgSettings/AgendaItemCategories/OrganizationAgendaCategory.spec.tsx index 56cb450647..a2bd6d0130 100644 --- a/src/components/OrgSettings/AgendaItemCategories/OrganizationAgendaCategory.test.tsx +++ b/src/components/OrgSettings/AgendaItemCategories/OrganizationAgendaCategory.spec.tsx @@ -7,9 +7,7 @@ import { waitForElementToBeRemoved, } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import 'jest-localstorage-mock'; import { MockedProvider } from '@apollo/client/testing'; -import 'jest-location-mock'; import { I18nextProvider } from 'react-i18next'; import { Provider } from 'react-redux'; import { BrowserRouter } from 'react-router-dom'; @@ -22,23 +20,38 @@ import { store } from 'state/store'; import { StaticMockLink } from 'utils/StaticMockLink'; import OrganizationAgendaCategory from './OrganizationAgendaCategory'; -import { - MOCKS_ERROR_AGENDA_ITEM_CATEGORY_LIST_QUERY, - MOCKS_ERROR_MUTATION, -} from './OrganizationAgendaCategoryErrorMocks'; +import { MOCKS_ERROR_AGENDA_ITEM_CATEGORY_LIST_QUERY } from './OrganizationAgendaCategoryErrorMocks'; import { MOCKS } from './OrganizationAgendaCategoryMocks'; - -jest.mock('react-toastify', () => ({ +import { vi } from 'vitest'; + +/** + * Unit Tests for `OrganizationAgendaCategory` Component + * + * - **Load Component**: Verifies successful rendering of key elements like `createAgendaCategory`. + * - **Error Handling**: Confirms error view appears when agenda category list query fails. + * - **Modal Functionality**: + * - Opens and closes the create agenda category modal. + * - Ensures `createAgendaCategoryModalCloseBtn` disappears on close. + * - **Create Agenda Category**: + * - Simulates filling the form and submission. + * - Verifies success toast on successful creation (`agendaCategoryCreated`). + * - **Integration**: Validates compatibility with Redux, Apollo, i18n, and MUI date-picker. + */ + +vi.mock('react-toastify', () => ({ toast: { - success: jest.fn(), - error: jest.fn(), + success: vi.fn(), + error: vi.fn(), }, })); -jest.mock('react-router-dom', () => ({ - ...jest.requireActual('react-router-dom'), - useParams: () => ({ orgId: '123' }), -})); +vi.mock('react-router-dom', async () => { + const actual = await vi.importActual('react-router-dom'); + return { + ...actual, + useParams: () => ({ orgId: '123' }), + }; +}); async function wait(ms = 100): Promise { await act(() => { @@ -53,8 +66,6 @@ const link2 = new StaticMockLink( MOCKS_ERROR_AGENDA_ITEM_CATEGORY_LIST_QUERY, true, ); -const link3 = new StaticMockLink(MOCKS_ERROR_MUTATION, true); - const translations = { ...JSON.parse( JSON.stringify( @@ -70,7 +81,7 @@ describe('Testing Agenda Categories Component', () => { description: 'Test Description', createdBy: 'Test User', }; - test('Component loads correctly', async () => { + it('Component loads correctly', async () => { const { getByText } = render( @@ -90,7 +101,7 @@ describe('Testing Agenda Categories Component', () => { }); }); - test('render error component on unsuccessful agenda category list query', async () => { + it('render error component on unsuccessful agenda category list query', async () => { const { queryByText } = render( @@ -112,7 +123,7 @@ describe('Testing Agenda Categories Component', () => { }); }); - test('opens and closes the create agenda category modal', async () => { + it('opens and closes the create agenda category modal', async () => { render( @@ -145,7 +156,7 @@ describe('Testing Agenda Categories Component', () => { screen.queryByTestId('createAgendaCategoryModalCloseBtn'), ); }); - test('creates new agenda cagtegory', async () => { + it('creates new agenda cagtegory', async () => { render( diff --git a/src/components/OrgSettings/General/DeleteOrg/DeleteOrg.test.tsx b/src/components/OrgSettings/General/DeleteOrg/DeleteOrg.spec.tsx similarity index 80% rename from src/components/OrgSettings/General/DeleteOrg/DeleteOrg.test.tsx rename to src/components/OrgSettings/General/DeleteOrg/DeleteOrg.spec.tsx index 77ffe65c08..e911d195dc 100644 --- a/src/components/OrgSettings/General/DeleteOrg/DeleteOrg.test.tsx +++ b/src/components/OrgSettings/General/DeleteOrg/DeleteOrg.spec.tsx @@ -1,11 +1,9 @@ import React, { act } from 'react'; import { MockedProvider } from '@apollo/react-testing'; import { render, screen, waitFor } from '@testing-library/react'; -import 'jest-location-mock'; import { I18nextProvider } from 'react-i18next'; import { Provider } from 'react-redux'; import { BrowserRouter } from 'react-router-dom'; - import { DELETE_ORGANIZATION_MUTATION, REMOVE_SAMPLE_ORGANIZATION_MUTATION, @@ -17,6 +15,24 @@ import DeleteOrg from './DeleteOrg'; import { ToastContainer, toast } from 'react-toastify'; import { IS_SAMPLE_ORGANIZATION_QUERY } from 'GraphQl/Queries/Queries'; import useLocalStorage from 'utils/useLocalstorage'; +import { vi } from 'vitest'; + +/** + * Unit Tests for `DeleteOrg` Component + * + * - **Toggle Modal**: Verifies the ability to open and close the delete organization modal for both sample and non-sample organizations. + * - **Delete Organization**: + * - Simulates deleting a non-sample organization and ensures the correct GraphQL mutation is triggered. + * - Confirms navigation occurs after a sample organization is deleted. + * - **Error Handling**: + * - Handles errors from `DELETE_ORGANIZATION_MUTATION` and `IS_SAMPLE_ORGANIZATION_QUERY`. + * - Verifies `toast.error` is called with appropriate error messages when mutations fail. + * - **Mocks**: + * - Mocks GraphQL queries and mutations using `StaticMockLink` for different success and error scenarios. + * - Uses `useParams` to simulate URL parameters (`orgId`). + * - Mocks `useNavigate` to check navigation after successful deletion. + * - **Toast Notifications**: Ensures `toast.success` or `toast.error` is triggered based on success or failure of actions. + */ const { setItem } = useLocalStorage(); @@ -98,13 +114,16 @@ const MOCKS_WITH_ERROR = [ }, ]; -const mockNavgatePush = jest.fn(); +const mockNavgatePush = vi.fn(); let mockURL = '123'; -jest.mock('react-router-dom', () => ({ - ...jest.requireActual('react-router-dom'), - useParams: () => ({ orgId: mockURL }), - useNavigate: () => mockNavgatePush, -})); +vi.mock('react-router-dom', async () => { + const actual = await vi.importActual('react-router-dom'); + return { + ...actual, + useParams: () => ({ orgId: mockURL }), + useNavigate: () => mockNavgatePush, + }; +}); const link = new StaticMockLink(MOCKS, true); const link2 = new StaticMockLink(MOCKS_WITH_ERROR, true); @@ -114,7 +133,7 @@ afterEach(() => { }); describe('Delete Organization Component', () => { - test('should be able to Toggle Delete Organization Modal', async () => { + it('should be able to Toggle Delete Organization Modal', async () => { mockURL = '456'; setItem('SuperAdmin', true); await act(async () => { @@ -143,7 +162,7 @@ describe('Delete Organization Component', () => { }); }); - test('should be able to Toggle Delete Organization Modal When Organization is Sample Organization', async () => { + it('should be able to Toggle Delete Organization Modal When Organization is Sample Organization', async () => { mockURL = '123'; setItem('SuperAdmin', true); await act(async () => { @@ -173,7 +192,7 @@ describe('Delete Organization Component', () => { }); }); - test('Delete organization functionality should work properly', async () => { + it('Delete organization functionality should work properly', async () => { mockURL = '456'; setItem('SuperAdmin', true); await act(async () => { @@ -201,7 +220,7 @@ describe('Delete Organization Component', () => { }); }); - test('Delete organization functionality should work properly for sample org', async () => { + it('Delete organization functionality should work properly for sample org', async () => { mockURL = '123'; setItem('SuperAdmin', true); await act(async () => { @@ -234,10 +253,10 @@ describe('Delete Organization Component', () => { expect(mockNavgatePush).toHaveBeenCalledWith('/orglist'); }); - test('Error handling for IS_SAMPLE_ORGANIZATION_QUERY mock', async () => { + it('Error handling for IS_SAMPLE_ORGANIZATION_QUERY mock', async () => { mockURL = '123'; setItem('SuperAdmin', true); - jest.spyOn(toast, 'error'); + vi.spyOn(toast, 'error'); await act(async () => { render( @@ -270,10 +289,10 @@ describe('Delete Organization Component', () => { }); }); - test('Error handling for DELETE_ORGANIZATION_MUTATION mock', async () => { + it('Error handling for DELETE_ORGANIZATION_MUTATION mock', async () => { mockURL = '456'; setItem('SuperAdmin', true); - jest.spyOn(toast, 'error'); + vi.spyOn(toast, 'error'); await act(async () => { render( diff --git a/src/components/OrgSettings/General/OrgProfileFieldSettings/OrgProfileFieldSettings.test.tsx b/src/components/OrgSettings/General/OrgProfileFieldSettings/OrgProfileFieldSettings.spec.tsx similarity index 86% rename from src/components/OrgSettings/General/OrgProfileFieldSettings/OrgProfileFieldSettings.test.tsx rename to src/components/OrgSettings/General/OrgProfileFieldSettings/OrgProfileFieldSettings.spec.tsx index 8db8773381..45bdfbc122 100644 --- a/src/components/OrgSettings/General/OrgProfileFieldSettings/OrgProfileFieldSettings.test.tsx +++ b/src/components/OrgSettings/General/OrgProfileFieldSettings/OrgProfileFieldSettings.spec.tsx @@ -12,6 +12,18 @@ import { } from 'GraphQl/Mutations/mutations'; import { ORGANIZATION_CUSTOM_FIELDS } from 'GraphQl/Queries/Queries'; import { ToastContainer, toast } from 'react-toastify'; +import { vi } from 'vitest'; + +/** + * Unit Tests for `OrgProfileFieldSettings` Component + * + * - Saving Custom Field: Verifies success and failure of adding a custom field. + * - Typing Custom Field Name: Ensures input updates correctly. + * - Handling No Custom Fields: Displays message when no custom fields exist. + * - Removing Custom Field: Verifies success and failure of removing a custom field. + * - Error Handling: Ensures error messages for GraphQL mutations are displayed. + * - Mock GraphQL Responses: Mocks GraphQL queries and mutations for different scenarios. + */ const MOCKS = [ { @@ -161,7 +173,7 @@ async function wait(ms = 100): Promise { } describe('Testing Save Button', () => { - test('Testing Failure Case For Fetching Custom field', async () => { + it('Testing Failure Case For Fetching Custom field', async () => { render( { screen.queryByText('Failed to fetch custom field'), ).toBeInTheDocument(); }); - test('Saving Organization Custom Field', async () => { + it('Saving Organization Custom Field', async () => { render( @@ -195,7 +207,7 @@ describe('Testing Save Button', () => { expect(screen.queryByText('Field added successfully')).toBeInTheDocument(); }); - test('Testing Failure Case For Saving Custom Field', async () => { + it('Testing Failure Case For Saving Custom Field', async () => { render( @@ -218,7 +230,7 @@ describe('Testing Save Button', () => { ).toBeInTheDocument(); }); - test('Testing Typing Organization Custom Field Name', async () => { + it('Testing Typing Organization Custom Field Name', async () => { const { getByTestId } = render( @@ -232,7 +244,7 @@ describe('Testing Save Button', () => { const fieldNameInput = getByTestId('customFieldInput'); userEvent.type(fieldNameInput, 'Age'); }); - test('When No Custom Data is Present', async () => { + it('When No Custom Data is Present', async () => { const { getByText } = render( @@ -244,7 +256,7 @@ describe('Testing Save Button', () => { await wait(); expect(getByText('No custom fields available')).toBeInTheDocument(); }); - test('Testing Remove Custom Field Button', async () => { + it('Testing Remove Custom Field Button', async () => { render( @@ -262,8 +274,8 @@ describe('Testing Save Button', () => { ).toBeInTheDocument(); }); - test('Testing Failure Case For Removing Custom Field', async () => { - const toastSpy = jest.spyOn(toast, 'error'); + it('Testing Failure Case For Removing Custom Field', async () => { + const toastSpy = vi.spyOn(toast, 'error'); render( diff --git a/src/components/OrgSettings/General/OrgUpdate/OrgUpdate.test.tsx b/src/components/OrgSettings/General/OrgUpdate/OrgUpdate.spec.tsx similarity index 90% rename from src/components/OrgSettings/General/OrgUpdate/OrgUpdate.test.tsx rename to src/components/OrgSettings/General/OrgUpdate/OrgUpdate.spec.tsx index 6304bb3ec9..2a6496d69a 100644 --- a/src/components/OrgSettings/General/OrgUpdate/OrgUpdate.test.tsx +++ b/src/components/OrgSettings/General/OrgUpdate/OrgUpdate.spec.tsx @@ -11,6 +11,18 @@ import { MOCKS_ERROR_ORGLIST, MOCKS_ERROR_UPDATE_ORGLIST, } from './OrgUpdateMocks'; +import { vi } from 'vitest'; + +/** + * Unit Tests for `OrgUpdate` Component + * + * - Rendering Component with Props: Verifies if labels and input fields are correctly rendered based on mock data. + * - Updating Organization: Ensures the form updates with new data and saves changes correctly. + * - Error Handling: Verifies error messages when organization cannot be found or updated. + * - Toast on Error: Verifies that an error toast is shown when the update fails. + * - Form Field Values: Ensures form values are correctly displayed and updated. + * - GraphQL Mock Responses: Mocks GraphQL responses for success and error scenarios. + */ const link = new StaticMockLink(MOCKS, true); @@ -45,9 +57,9 @@ describe('Testing Organization Update', () => { isVisible: true, }; - global.alert = jest.fn(); + global.alert = vi.fn(); - test('should render props and text elements test for the page component along with mock data', async () => { + it('should render props and text elements test for the page component along with mock data', async () => { act(() => { render( @@ -95,7 +107,7 @@ describe('Testing Organization Update', () => { expect(isVisible).not.toBeChecked(); }); - test('Should Update organization properly', async () => { + it('Should Update organization properly', async () => { await act(async () => { render( @@ -168,7 +180,7 @@ describe('Testing Organization Update', () => { expect(isVisible).toBeChecked(); }); - test('Should render error occured text when Organization Could not be found', async () => { + it('Should render error occured text when Organization Could not be found', async () => { act(() => { render( @@ -182,7 +194,7 @@ describe('Testing Organization Update', () => { expect(screen.getByText(/Mock Graphql Error/i)).toBeInTheDocument(); }); - test('Should show error occured toast when Organization could not be updated', async () => { + it('Should show error occured toast when Organization could not be updated', async () => { await act(async () => { render( diff --git a/src/components/OrganizationCardStart/OrganizationCardStart.test.tsx b/src/components/OrganizationCardStart/OrganizationCardStart.spec.tsx similarity index 76% rename from src/components/OrganizationCardStart/OrganizationCardStart.test.tsx rename to src/components/OrganizationCardStart/OrganizationCardStart.spec.tsx index dd65c8649e..71263fb65b 100644 --- a/src/components/OrganizationCardStart/OrganizationCardStart.test.tsx +++ b/src/components/OrganizationCardStart/OrganizationCardStart.spec.tsx @@ -1,9 +1,10 @@ import React from 'react'; import { render, screen } from '@testing-library/react'; import OrganizationCardStart from './OrganizationCardStart'; +import { describe, expect } from 'vitest'; describe('Testing the Organization Cards', () => { - test('should render props and text elements test for the page component', () => { + it('should render props and text elements test for the page component', () => { const props = { id: '123', image: 'https://via.placeholder.com/80', @@ -15,7 +16,7 @@ describe('Testing the Organization Cards', () => { expect(screen.getByText(props.name)).toBeInTheDocument(); }); - test('Should render text elements when props value is not passed', () => { + it('Should render text elements when props value is not passed', () => { const props = { id: '123', image: '', diff --git a/src/components/Pagination/Pagination.test.tsx b/src/components/Pagination/Pagination.spec.tsx similarity index 89% rename from src/components/Pagination/Pagination.test.tsx rename to src/components/Pagination/Pagination.spec.tsx index 40ac2ed19a..873f790565 100644 --- a/src/components/Pagination/Pagination.test.tsx +++ b/src/components/Pagination/Pagination.spec.tsx @@ -6,6 +6,7 @@ import { createTheme, ThemeProvider } from '@mui/material/styles'; import Pagination from './Pagination'; import { store } from 'state/store'; import userEvent from '@testing-library/user-event'; +import { describe, it } from 'vitest'; describe('Testing Pagination component', () => { const props = { @@ -17,7 +18,7 @@ describe('Testing Pagination component', () => { }, }; - test('Component should be rendered properly on rtl', async () => { + it('Component should be rendered properly on rtl', async () => { render( @@ -42,7 +43,7 @@ const props = { theme: { direction: 'rtl' }, }; -test('Component should be rendered properly', async () => { +it('Component should be rendered properly', async () => { const theme = createTheme({ direction: 'rtl', }); diff --git a/src/components/ProfileDropdown/ProfileDropdown.test.tsx b/src/components/ProfileDropdown/ProfileDropdown.spec.tsx similarity index 52% rename from src/components/ProfileDropdown/ProfileDropdown.test.tsx rename to src/components/ProfileDropdown/ProfileDropdown.spec.tsx index 785f33ee92..06883768e6 100644 --- a/src/components/ProfileDropdown/ProfileDropdown.test.tsx +++ b/src/components/ProfileDropdown/ProfileDropdown.spec.tsx @@ -1,17 +1,29 @@ import React, { act } from 'react'; -import { render, screen } from '@testing-library/react'; +import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { BrowserRouter } from 'react-router-dom'; import ProfileDropdown from './ProfileDropdown'; -import 'jest-localstorage-mock'; import { MockedProvider } from '@apollo/react-testing'; import { REVOKE_REFRESH_TOKEN } from 'GraphQl/Mutations/mutations'; import useLocalStorage from 'utils/useLocalstorage'; import { I18nextProvider } from 'react-i18next'; import i18nForTest from 'utils/i18nForTest'; import { GET_COMMUNITY_SESSION_TIMEOUT_DATA } from 'GraphQl/Queries/Queries'; +import { vi } from 'vitest'; const { setItem } = useLocalStorage(); + +const mockNavigate = vi.fn(); + +// Mock useNavigate hook +vi.mock('react-router-dom', async () => { + const actual = await vi.importActual('react-router-dom'); + return { + ...actual, + useNavigate: () => mockNavigate, + }; +}); + const MOCKS = [ { request: { @@ -38,13 +50,13 @@ const MOCKS = [ }, ]; -jest.mock('react-toastify', () => ({ +vi.mock('react-toastify', () => ({ toast: { - success: jest.fn(), - warn: jest.fn(), - error: jest.fn(), + success: vi.fn(), + warn: vi.fn(), + error: vi.fn(), }, - clear: jest.fn(), + clear: vi.fn(), })); beforeEach(() => { @@ -60,11 +72,11 @@ beforeEach(() => { }); afterEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); localStorage.clear(); }); afterEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); localStorage.clear(); }); @@ -130,19 +142,124 @@ describe('ProfileDropdown Component', () => { describe('Member screen routing testing', () => { test('member screen', async () => { + setItem('SuperAdmin', false); + setItem('AdminFor', []); + render( - + + + , ); + await act(async () => { userEvent.click(screen.getByTestId('togDrop')); }); + await act(async () => { + userEvent.click(screen.getByTestId('profileBtn')); + }); + + expect(mockNavigate).toHaveBeenCalledWith('/user/settings'); + }); + }); + + test('handles error when revoking refresh token during logout', async () => { + // Mock the revokeRefreshToken mutation to throw an error + const MOCKS_WITH_ERROR = [ + { + request: { + query: REVOKE_REFRESH_TOKEN, + }, + error: new Error('Failed to revoke refresh token'), + }, + ]; + + const consoleErrorMock = vi + .spyOn(console, 'error') + .mockImplementation(() => {}); + + render( + + + + + , + ); + + // Open the dropdown + await act(async () => { + userEvent.click(screen.getByTestId('togDrop')); + }); + + // Click the logout button + await act(async () => { + userEvent.click(screen.getByTestId('logoutBtn')); + }); + + // Wait for any pending promises + await waitFor(() => { + // Assert that console.error was called + expect(consoleErrorMock).toHaveBeenCalledWith( + 'Error revoking refresh token:', + expect.any(Error), + ); + }); + + // Cleanup mock + consoleErrorMock.mockRestore(); + }); + + test('navigates to /user/settings for a user', async () => { + setItem('SuperAdmin', false); + setItem('AdminFor', []); + + render( + + + + + + + , + ); + + await act(async () => { + userEvent.click(screen.getByTestId('togDrop')); + }); + + await act(async () => { userEvent.click(screen.getByTestId('profileBtn')); - expect(global.window.location.pathname).toBe('/user/settings'); }); + + expect(mockNavigate).toHaveBeenCalledWith('/user/settings'); + }); + + test('navigates to /member/:userID for non-user roles', async () => { + setItem('SuperAdmin', true); // Set as admin + setItem('id', '123'); + + render( + + + + + + + , + ); + + await act(async () => { + userEvent.click(screen.getByTestId('togDrop')); + }); + + await act(async () => { + userEvent.click(screen.getByTestId('profileBtn')); + }); + + expect(mockNavigate).toHaveBeenCalledWith('/member/123'); }); }); diff --git a/src/components/RecurrenceOptions/CustomRecurrence.test.tsx b/src/components/RecurrenceOptions/CustomRecurrence.spec.tsx similarity index 98% rename from src/components/RecurrenceOptions/CustomRecurrence.test.tsx rename to src/components/RecurrenceOptions/CustomRecurrence.spec.tsx index fc0cacf5c4..236e34e855 100644 --- a/src/components/RecurrenceOptions/CustomRecurrence.test.tsx +++ b/src/components/RecurrenceOptions/CustomRecurrence.spec.tsx @@ -9,7 +9,6 @@ import { } from '@testing-library/react'; import { Provider } from 'react-redux'; import { BrowserRouter } from 'react-router-dom'; -import 'jest-location-mock'; import { I18nextProvider } from 'react-i18next'; import OrganizationEvents from '../../screens/OrganizationEvents/OrganizationEvents'; @@ -23,6 +22,7 @@ import { ThemeProvider } from 'react-bootstrap'; import { LocalizationProvider } from '@mui/x-date-pickers'; import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; import { MOCKS } from '../../screens/OrganizationEvents/OrganizationEventsMocks'; +import { describe, test, expect, vi } from 'vitest'; const theme = createTheme({ palette: { @@ -48,19 +48,20 @@ const translations = JSON.parse( ), ); -jest.mock('@mui/x-date-pickers/DateTimePicker', () => { +vi.mock('@mui/x-date-pickers/DateTimePicker', async () => { + const actual = await vi.importActual( + '@mui/x-date-pickers/DesktopDateTimePicker', + ); return { - DateTimePicker: jest.requireActual( - '@mui/x-date-pickers/DesktopDateTimePicker', - ).DesktopDateTimePicker, + DateTimePicker: actual.DesktopDateTimePicker, }; }); -jest.mock('react-toastify', () => ({ +vi.mock('react-toastify', () => ({ toast: { - success: jest.fn(), - warning: jest.fn(), - error: jest.fn(), + success: vi.fn(), + warning: vi.fn(), + error: vi.fn(), }, })); diff --git a/src/components/RecurrenceOptions/RecurrenceOptions.test.tsx b/src/components/RecurrenceOptions/RecurrenceOptions.spec.tsx similarity index 97% rename from src/components/RecurrenceOptions/RecurrenceOptions.test.tsx rename to src/components/RecurrenceOptions/RecurrenceOptions.spec.tsx index 2d283460da..07adf7bd16 100644 --- a/src/components/RecurrenceOptions/RecurrenceOptions.test.tsx +++ b/src/components/RecurrenceOptions/RecurrenceOptions.spec.tsx @@ -9,7 +9,6 @@ import { } from '@testing-library/react'; import { Provider } from 'react-redux'; import { BrowserRouter } from 'react-router-dom'; -import 'jest-location-mock'; import { I18nextProvider } from 'react-i18next'; import OrganizationEvents from '../../screens/OrganizationEvents/OrganizationEvents'; @@ -23,6 +22,7 @@ import { ThemeProvider } from 'react-bootstrap'; import { LocalizationProvider } from '@mui/x-date-pickers'; import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; import { MOCKS } from '../../screens/OrganizationEvents/OrganizationEventsMocks'; +import { describe, test, expect, vi } from 'vitest'; const theme = createTheme({ palette: { @@ -52,19 +52,20 @@ const translations = { ...JSON.parse(JSON.stringify(i18n.getDataByLanguage('en')?.errors ?? {})), }; -jest.mock('@mui/x-date-pickers/DateTimePicker', () => { +vi.mock('@mui/x-date-pickers/DateTimePicker', async () => { + const actual = await vi.importActual( + '@mui/x-date-pickers/DesktopDateTimePicker', + ); return { - DateTimePicker: jest.requireActual( - '@mui/x-date-pickers/DesktopDateTimePicker', - ).DesktopDateTimePicker, + DateTimePicker: actual.DesktopDateTimePicker, }; }); -jest.mock('react-toastify', () => ({ +vi.mock('react-toastify', () => ({ toast: { - success: jest.fn(), - warning: jest.fn(), - error: jest.fn(), + success: vi.fn(), + warning: vi.fn(), + error: vi.fn(), }, })); diff --git a/src/components/RequestsTableItem/RequestsTableItem.test.tsx b/src/components/RequestsTableItem/RequestsTableItem.spec.tsx similarity index 62% rename from src/components/RequestsTableItem/RequestsTableItem.test.tsx rename to src/components/RequestsTableItem/RequestsTableItem.spec.tsx index bbd895300b..b5194fcdce 100644 --- a/src/components/RequestsTableItem/RequestsTableItem.test.tsx +++ b/src/components/RequestsTableItem/RequestsTableItem.spec.tsx @@ -11,6 +11,7 @@ import { BrowserRouter } from 'react-router-dom'; const link = new StaticMockLink(MOCKS, true); import useLocalStorage from 'utils/useLocalstorage'; import userEvent from '@testing-library/user-event'; +import { vi } from 'vitest'; const { setItem } = useLocalStorage(); @@ -21,13 +22,13 @@ async function wait(ms = 100): Promise { }); }); } -const resetAndRefetchMock = jest.fn(); +const resetAndRefetchMock = vi.fn(); -jest.mock('react-toastify', () => ({ +vi.mock('react-toastify', () => ({ toast: { - success: jest.fn(), - error: jest.fn(), - warning: jest.fn(), + success: vi.fn(), + error: vi.fn(), + warning: vi.fn(), }, })); @@ -37,17 +38,17 @@ beforeEach(() => { afterEach(() => { localStorage.clear(); - jest.clearAllMocks(); + vi.clearAllMocks(); }); describe('Testing User Table Item', () => { - console.error = jest.fn((message) => { + console.error = vi.fn((message) => { if (message.includes('validateDOMNesting')) { return; } console.warn(message); }); - test('Should render props and text elements test for the page component', async () => { + it('Should render props and text elements it for the page component', async () => { const props: { request: InterfaceRequestsListItem; index: number; @@ -81,7 +82,7 @@ describe('Testing User Table Item', () => { expect(screen.getByText(/john@example.com/i)).toBeInTheDocument(); }); - test('Accept MembershipRequest Button works properly', async () => { + it('Accept MembershipRequest Button works properly', async () => { const props: { request: InterfaceRequestsListItem; index: number; @@ -113,7 +114,39 @@ describe('Testing User Table Item', () => { userEvent.click(screen.getByTestId('acceptMembershipRequestBtn123')); }); - test('Reject MembershipRequest Button works properly', async () => { + it('Accept MembershipRequest handles error', async () => { + const props: { + request: InterfaceRequestsListItem; + index: number; + resetAndRefetch: () => void; + } = { + request: { + _id: '1', + user: { + firstName: 'John', + lastName: 'Doe', + email: 'john@example.com', + }, + }, + index: 1, + resetAndRefetch: resetAndRefetchMock, + }; + + render( + + + + + + + , + ); + + await wait(); + userEvent.click(screen.getByTestId('acceptMembershipRequestBtn1')); + }); + + it('Reject MembershipRequest Button works properly', async () => { const props: { request: InterfaceRequestsListItem; index: number; @@ -144,4 +177,36 @@ describe('Testing User Table Item', () => { await wait(); userEvent.click(screen.getByTestId('rejectMembershipRequestBtn123')); }); + + it('Reject MembershipRequest handles error', async () => { + const props: { + request: InterfaceRequestsListItem; + index: number; + resetAndRefetch: () => void; + } = { + request: { + _id: '1', + user: { + firstName: 'John', + lastName: 'Doe', + email: 'john@example.com', + }, + }, + index: 1, + resetAndRefetch: resetAndRefetchMock, + }; + + render( + + + + + + + , + ); + + await wait(); + userEvent.click(screen.getByTestId('rejectMembershipRequestBtn1')); + }); }); diff --git a/src/components/RequestsTableItem/RequestsTableItemMocks.ts b/src/components/RequestsTableItem/RequestsTableItemMocks.ts index 22ea245d3a..22bd61d0ac 100644 --- a/src/components/RequestsTableItem/RequestsTableItemMocks.ts +++ b/src/components/RequestsTableItem/RequestsTableItemMocks.ts @@ -8,13 +8,13 @@ export const MOCKS = [ request: { query: ACCEPT_ORGANIZATION_REQUEST_MUTATION, variables: { - id: '1', + id: '123', }, }, result: { data: { acceptMembershipRequest: { - _id: '1', + _id: '123', }, }, }, @@ -23,13 +23,13 @@ export const MOCKS = [ request: { query: REJECT_ORGANIZATION_REQUEST_MUTATION, variables: { - id: '1', + id: '123', }, }, result: { data: { rejectMembershipRequest: { - _id: '1', + _id: '123', }, }, }, diff --git a/src/components/SuperAdminScreen/SuperAdminScreen.test.tsx b/src/components/SuperAdminScreen/SuperAdminScreen.spec.tsx similarity index 97% rename from src/components/SuperAdminScreen/SuperAdminScreen.test.tsx rename to src/components/SuperAdminScreen/SuperAdminScreen.spec.tsx index 84b740ab12..53804a54db 100644 --- a/src/components/SuperAdminScreen/SuperAdminScreen.test.tsx +++ b/src/components/SuperAdminScreen/SuperAdminScreen.spec.tsx @@ -1,13 +1,13 @@ import React from 'react'; import { MockedProvider } from '@apollo/react-testing'; import { fireEvent, render, screen } from '@testing-library/react'; -import 'jest-location-mock'; import { I18nextProvider } from 'react-i18next'; import { Provider } from 'react-redux'; import { BrowserRouter } from 'react-router-dom'; import { store } from 'state/store'; import i18nForTest from 'utils/i18nForTest'; import SuperAdminScreen from './SuperAdminScreen'; +import { describe, test, expect } from 'vitest'; const resizeWindow = (width: number): void => { window.innerWidth = width; diff --git a/src/components/UpdateSession/UpdateSession.test.tsx b/src/components/UpdateSession/UpdateSession.spec.tsx similarity index 81% rename from src/components/UpdateSession/UpdateSession.test.tsx rename to src/components/UpdateSession/UpdateSession.spec.tsx index ce0a868820..3453c990c2 100644 --- a/src/components/UpdateSession/UpdateSession.test.tsx +++ b/src/components/UpdateSession/UpdateSession.spec.tsx @@ -1,16 +1,7 @@ import React from 'react'; import { MockedProvider } from '@apollo/client/testing'; -import { - render, - screen, - act, - within, - fireEvent, - waitFor, -} from '@testing-library/react'; +import { render, screen, act, within } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import 'jest-localstorage-mock'; -import 'jest-location-mock'; import { I18nextProvider } from 'react-i18next'; import { BrowserRouter } from 'react-router-dom'; import { toast } from 'react-toastify'; @@ -19,6 +10,18 @@ import i18n from 'utils/i18nForTest'; import { GET_COMMUNITY_SESSION_TIMEOUT_DATA } from 'GraphQl/Queries/Queries'; import { UPDATE_SESSION_TIMEOUT } from 'GraphQl/Mutations/mutations'; import { errorHandler } from 'utils/errorHandler'; +import { vi } from 'vitest'; +/** + * This file contains unit tests for the `UpdateSession` component. + * + * The tests cover: + * - Proper rendering of the component with different scenarios, including mock data, null values, and error states. + * - Handling user interactions with the slider, such as setting minimum, maximum, and intermediate values. + * - Ensuring callbacks (e.g., `onValueChange`) are triggered correctly based on user input. + * - Simulating GraphQL query and mutation operations using mocked data to verify correct behavior in successful and error cases. + * - Testing edge cases, including null community data, invalid input values, and API errors, ensuring the component handles them gracefully. + * - Verifying proper integration of internationalization, routing, and toast notifications for success or error messages. + */ const MOCKS = [ { @@ -66,31 +69,29 @@ async function wait(ms = 100): Promise { }); } -jest.mock('react-toastify', () => ({ +vi.mock('react-toastify', () => ({ toast: { - success: jest.fn(), - warn: jest.fn(), - error: jest.fn(), + success: vi.fn(), + warn: vi.fn(), + error: vi.fn(), }, })); -jest.mock('utils/errorHandler', () => ({ - errorHandler: jest.fn(), +vi.mock('utils/errorHandler', () => ({ + errorHandler: vi.fn(), })); describe('Testing UpdateTimeout Component', () => { - let consoleWarnSpy: jest.SpyInstance; - beforeEach(() => { - consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation(); + vi.spyOn(console, 'warn').mockImplementation(() => {}); }); afterEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); }); - test('Should handle minimum slider value correctly', async () => { - const mockOnValueChange = jest.fn(); + it('Should handle minimum slider value correctly', async () => { + const mockOnValueChange = vi.fn(); render( @@ -109,8 +110,8 @@ describe('Testing UpdateTimeout Component', () => { expect(mockOnValueChange).toHaveBeenCalledWith(15); // Adjust based on slider min value }); - test('Should handle maximum slider value correctly', async () => { - const mockOnValueChange = jest.fn(); + it('Should handle maximum slider value correctly', async () => { + const mockOnValueChange = vi.fn(); render( @@ -129,8 +130,8 @@ describe('Testing UpdateTimeout Component', () => { expect(mockOnValueChange).toHaveBeenCalledWith(60); // Adjust based on slider max value }); - test('Should not update value if an invalid value is passed', async () => { - const mockOnValueChange = jest.fn(); + it('Should not update value if an invalid value is passed', async () => { + const mockOnValueChange = vi.fn(); render( @@ -150,8 +151,8 @@ describe('Testing UpdateTimeout Component', () => { expect(mockOnValueChange).not.toHaveBeenCalled(); }); - test('Should update slider value on user interaction', async () => { - const mockOnValueChange = jest.fn(); + it('Should update slider value on user interaction', async () => { + const mockOnValueChange = vi.fn(); render( @@ -169,7 +170,7 @@ describe('Testing UpdateTimeout Component', () => { expect(mockOnValueChange).toHaveBeenCalledWith(expect.any(Number)); // Adjust as needed }); - test('Components should render properly', async () => { + it('Components should render properly', async () => { render( @@ -203,7 +204,7 @@ describe('Testing UpdateTimeout Component', () => { expect(screen.getByRole('button', { name: /Update/i })).toBeInTheDocument(); }); - test('Should update session timeout', async () => { + it('Should update session timeout', async () => { render( @@ -228,7 +229,7 @@ describe('Testing UpdateTimeout Component', () => { ); }); - test('Should handle query errors', async () => { + it('Should handle query errors', async () => { const errorMocks = [ { request: { @@ -253,7 +254,7 @@ describe('Testing UpdateTimeout Component', () => { expect(errorHandler).toHaveBeenCalled(); }); - test('Should handle update errors', async () => { + it('Should handle update errors', async () => { const errorMocks = [ { request: { @@ -306,7 +307,7 @@ describe('Testing UpdateTimeout Component', () => { expect(errorHandler).toHaveBeenCalled(); }); - test('Should handle null community object gracefully', async () => { + it('Should handle null community object gracefully', async () => { render( diff --git a/src/components/UserListCard/UserListCard.test.tsx b/src/components/UserListCard/UserListCard.spec.tsx similarity index 77% rename from src/components/UserListCard/UserListCard.test.tsx rename to src/components/UserListCard/UserListCard.spec.tsx index e2ad552507..6a3b2d42d8 100644 --- a/src/components/UserListCard/UserListCard.test.tsx +++ b/src/components/UserListCard/UserListCard.spec.tsx @@ -1,5 +1,5 @@ -import React, { act } from 'react'; -import { render, screen } from '@testing-library/react'; +import React from 'react'; +import { render, screen, act } from '@testing-library/react'; import { MockedProvider } from '@apollo/react-testing'; import userEvent from '@testing-library/user-event'; import { I18nextProvider } from 'react-i18next'; @@ -9,6 +9,7 @@ import { ADD_ADMIN_MUTATION } from 'GraphQl/Mutations/mutations'; import i18nForTest from 'utils/i18nForTest'; import { BrowserRouter } from 'react-router-dom'; import { StaticMockLink } from 'utils/StaticMockLink'; +import { vi, describe, it, beforeEach } from 'vitest'; const MOCKS = [ { @@ -30,17 +31,15 @@ const MOCKS = [ const link = new StaticMockLink(MOCKS, true); async function wait(ms = 100): Promise { - await act(() => { - return new Promise((resolve) => { - setTimeout(resolve, ms); - }); - }); + await act(() => new Promise((resolve) => setTimeout(resolve, ms))); } describe('Testing User List Card', () => { - global.alert = jest.fn(); + beforeEach(() => { + vi.spyOn(global, 'alert').mockImplementation(() => {}); + }); - test('Should render props and text elements test for the page component', async () => { + it('Should render props and text elements test for the page component', async () => { const props = { id: '456', }; @@ -56,11 +55,10 @@ describe('Testing User List Card', () => { ); await wait(); - userEvent.click(screen.getByText(/Add Admin/i)); }); - test('Should render text elements when props value is not passed', async () => { + it('Should render text elements when props value is not passed', async () => { const props = { id: '456', }; diff --git a/src/components/UserPasswordUpdate/UserPasswordUpdate.test.tsx b/src/components/UserPasswordUpdate/UserPasswordUpdate.spec.tsx similarity index 58% rename from src/components/UserPasswordUpdate/UserPasswordUpdate.test.tsx rename to src/components/UserPasswordUpdate/UserPasswordUpdate.spec.tsx index 65f5e40f76..0d704eaed8 100644 --- a/src/components/UserPasswordUpdate/UserPasswordUpdate.test.tsx +++ b/src/components/UserPasswordUpdate/UserPasswordUpdate.spec.tsx @@ -1,43 +1,22 @@ import React, { act } from 'react'; -import { render, screen } from '@testing-library/react'; +import { render, screen, waitFor } from '@testing-library/react'; import { MockedProvider } from '@apollo/react-testing'; import userEvent from '@testing-library/user-event'; import { I18nextProvider } from 'react-i18next'; -import { UPDATE_USER_PASSWORD_MUTATION } from 'GraphQl/Mutations/mutations'; import i18nForTest from 'utils/i18nForTest'; import UserPasswordUpdate from './UserPasswordUpdate'; import { StaticMockLink } from 'utils/StaticMockLink'; import { toast as mockToast } from 'react-toastify'; +import { MOCKS } from './UserPasswordUpdateMocks'; +import { vi } from 'vitest'; -jest.mock('react-toastify', () => ({ +vi.mock('react-toastify', () => ({ toast: { - error: jest.fn(), - success: jest.fn(), + error: vi.fn(), + success: vi.fn(), }, })); -const MOCKS = [ - { - request: { - query: UPDATE_USER_PASSWORD_MUTATION, - variables: { - previousPassword: 'anshgoyal', - newPassword: 'anshgoyalansh', - confirmNewPassword: 'anshgoyalansh', - }, - }, - result: { - data: { - users: [ - { - _id: '1', - }, - ], - }, - }, - }, -]; - const link = new StaticMockLink(MOCKS, true); async function wait(ms = 5): Promise { @@ -56,9 +35,9 @@ describe('Testing User Password Update', () => { confirmNewPassword: 'ThePalisadoesFoundation', }; - global.alert = jest.fn(); + global.alert = vi.fn(); - test('should render props and text elements test for the page component', async () => { + it('should render props and text elements it for the page component', async () => { render( @@ -93,7 +72,7 @@ describe('Testing User Password Update', () => { ).toBeInTheDocument(); }); - test('displays an error when the password field is empty', async () => { + it('displays an error when the password field is empty', async () => { render( @@ -108,7 +87,7 @@ describe('Testing User Password Update', () => { expect(mockToast.error).toHaveBeenCalledWith(`Password can't be empty`); }); - test('displays an error when new and confirm password field does not match', async () => { + it('displays an error when new and confirm password field does not match', async () => { render( @@ -140,4 +119,67 @@ describe('Testing User Password Update', () => { 'New and Confirm password do not match.', ); }); + + it('Successfully update old password', async () => { + render( + + + + + , + ); + + await wait(); + + userEvent.type( + screen.getByPlaceholderText(/Previous Password/i), + formData.previousPassword, + ); + userEvent.type( + screen.getAllByPlaceholderText(/New Password/i)[0], + formData.newPassword, + ); + userEvent.type( + screen.getByPlaceholderText(/Confirm New Password/i), + formData.confirmNewPassword, + ); + + userEvent.click(screen.getByText(/Save Changes/i)); + expect(mockToast.success).toHaveBeenCalledWith( + 'Password updated Successfully', + ); + }); + + it('wrong old password', async () => { + render( + + + + + , + ); + + // await wait(); + + userEvent.type( + screen.getByPlaceholderText(/Previous Password/i), + formData.wrongPassword, + ); + userEvent.type( + screen.getAllByPlaceholderText(/New Password/i)[0], + formData.newPassword, + ); + userEvent.type( + screen.getByPlaceholderText(/Confirm New Password/i), + formData.confirmNewPassword, + ); + + userEvent.click(screen.getByText(/Save Changes/i)); + + await waitFor(() => + expect(mockToast.error).toHaveBeenCalledWith( + 'ApolloError: Invalid previous password', + ), + ); + }); }); diff --git a/src/components/UserPasswordUpdate/UserPasswordUpdateMocks.ts b/src/components/UserPasswordUpdate/UserPasswordUpdateMocks.ts new file mode 100644 index 0000000000..634286f590 --- /dev/null +++ b/src/components/UserPasswordUpdate/UserPasswordUpdateMocks.ts @@ -0,0 +1,40 @@ +import { UPDATE_USER_PASSWORD_MUTATION } from 'GraphQl/Mutations/mutations'; + +export const MOCKS = [ + { + request: { + query: UPDATE_USER_PASSWORD_MUTATION, + variables: { + previousPassword: 'Palisadoes', + newPassword: 'ThePalisadoesFoundation', + confirmNewPassword: 'ThePalisadoesFoundation', + }, + }, + result: { + data: { + users: [ + { + _id: '1', + }, + ], + }, + }, + }, + { + request: { + query: UPDATE_USER_PASSWORD_MUTATION, + variables: { + previousPassword: 'This is wrong password', + newPassword: 'ThePalisadoesFoundation', + confirmNewPassword: 'ThePalisadoesFoundation', + }, + }, + result: { + errors: [ + { + message: 'Invalid previous password', + }, + ], + }, + }, +]; diff --git a/src/components/UserProfileSettings/DeleteUser.test.tsx b/src/components/UserProfileSettings/DeleteUser.spec.tsx similarity index 90% rename from src/components/UserProfileSettings/DeleteUser.test.tsx rename to src/components/UserProfileSettings/DeleteUser.spec.tsx index 34ab44fbe5..d089d82607 100644 --- a/src/components/UserProfileSettings/DeleteUser.test.tsx +++ b/src/components/UserProfileSettings/DeleteUser.spec.tsx @@ -5,9 +5,10 @@ import { BrowserRouter } from 'react-router-dom'; import { I18nextProvider } from 'react-i18next'; import i18nForTest from 'utils/i18nForTest'; import DeleteUser from './DeleteUser'; +import { describe, it, expect } from 'vitest'; describe('Delete User component', () => { - test('renders delete user correctly', () => { + it('renders delete user correctly', () => { const { getByText, getAllByText } = render( diff --git a/src/components/UserProfileSettings/OtherSettings.test.tsx b/src/components/UserProfileSettings/OtherSettings.spec.tsx similarity index 89% rename from src/components/UserProfileSettings/OtherSettings.test.tsx rename to src/components/UserProfileSettings/OtherSettings.spec.tsx index 990a430931..490ad2322c 100644 --- a/src/components/UserProfileSettings/OtherSettings.test.tsx +++ b/src/components/UserProfileSettings/OtherSettings.spec.tsx @@ -5,9 +5,10 @@ import { BrowserRouter } from 'react-router-dom'; import { I18nextProvider } from 'react-i18next'; import i18nForTest from 'utils/i18nForTest'; import OtherSettings from './OtherSettings'; +import { describe, it, expect } from 'vitest'; describe('Delete User component', () => { - test('renders delete user correctly', () => { + it('renders delete user correctly', () => { const { getByText } = render( diff --git a/src/components/UserProfileSettings/UserProfile.test.tsx b/src/components/UserProfileSettings/UserProfile.spec.tsx similarity index 92% rename from src/components/UserProfileSettings/UserProfile.test.tsx rename to src/components/UserProfileSettings/UserProfile.spec.tsx index 82caad5d81..dbf7987789 100644 --- a/src/components/UserProfileSettings/UserProfile.test.tsx +++ b/src/components/UserProfileSettings/UserProfile.spec.tsx @@ -5,9 +5,10 @@ import { MockedProvider } from '@apollo/react-testing'; import { BrowserRouter } from 'react-router-dom'; import { I18nextProvider } from 'react-i18next'; import i18nForTest from 'utils/i18nForTest'; +import { describe, it, expect } from 'vitest'; describe('UserProfile component', () => { - test('renders user profile details correctly', () => { + it('renders user profile details correctly', () => { const userDetails = { firstName: 'Christopher', lastName: 'Doe', diff --git a/src/components/UsersTableItem/UserTableItem.test.tsx b/src/components/UsersTableItem/UserTableItem.spec.tsx similarity index 98% rename from src/components/UsersTableItem/UserTableItem.test.tsx rename to src/components/UsersTableItem/UserTableItem.spec.tsx index 687165b78d..a0b0c39c86 100644 --- a/src/components/UsersTableItem/UserTableItem.test.tsx +++ b/src/components/UsersTableItem/UserTableItem.spec.tsx @@ -13,11 +13,9 @@ const link = new StaticMockLink(MOCKS, true); const link2 = new StaticMockLink(MOCKS2, true); const link3 = new StaticMockLink(MOCKS_UPDATE, true); import useLocalStorage from 'utils/useLocalstorage'; -import { - REMOVE_ADMIN_MUTATION, - REMOVE_MEMBER_MUTATION, -} from 'GraphQl/Mutations/mutations'; import userEvent from '@testing-library/user-event'; +import { vi } from 'vitest'; +import type * as RouterTypes from 'react-router-dom'; const { setItem } = useLocalStorage(); @@ -28,29 +26,34 @@ async function wait(ms = 100): Promise { }); }); } -const resetAndRefetchMock = jest.fn(); +const resetAndRefetchMock = vi.fn(); -jest.mock('react-toastify', () => ({ +vi.mock('react-toastify', () => ({ toast: { - success: jest.fn(), - error: jest.fn(), - warning: jest.fn(), + success: vi.fn(), + error: vi.fn(), + warning: vi.fn(), }, })); Object.defineProperty(window, 'location', { value: { - replace: jest.fn(), + replace: vi.fn(), }, writable: true, }); -const mockNavgatePush = jest.fn(); +const mockNavgatePush = vi.fn(); -jest.mock('react-router-dom', () => ({ - ...jest.requireActual('react-router-dom'), - useNavigate: () => mockNavgatePush, -})); +vi.mock('react-router-dom', async () => { + const actual = (await vi.importActual( + 'react-router-dom', + )) as typeof RouterTypes; + return { + ...actual, + useNavigate: () => mockNavgatePush, + }; +}); beforeEach(() => { setItem('SuperAdmin', true); @@ -59,11 +62,11 @@ beforeEach(() => { afterEach(() => { localStorage.clear(); - jest.clearAllMocks(); + vi.clearAllMocks(); }); describe('Testing User Table Item', () => { - console.error = jest.fn((message) => { + console.error = vi.fn((message) => { if (message.includes('validateDOMNesting')) { return; } @@ -1183,25 +1186,6 @@ describe('Testing User Table Item', () => { resetAndRefetch: resetAndRefetchMock, }; - const mocks = [ - { - request: { - query: REMOVE_MEMBER_MUTATION, - variables: { - userId: '123', - orgId: 'xyz', - }, - }, - result: { - errors: [ - { - message: 'User does not exist', - }, - ], - }, - }, - ]; - render( diff --git a/src/components/Venues/VenueModal.test.tsx b/src/components/Venues/VenueModal.spec.tsx similarity index 93% rename from src/components/Venues/VenueModal.test.tsx rename to src/components/Venues/VenueModal.spec.tsx index b299c8ff20..45560c40cb 100644 --- a/src/components/Venues/VenueModal.test.tsx +++ b/src/components/Venues/VenueModal.spec.tsx @@ -4,7 +4,6 @@ import type { RenderResult } from '@testing-library/react'; import { render, screen, fireEvent } from '@testing-library/react'; import { Provider } from 'react-redux'; import { BrowserRouter } from 'react-router-dom'; -import 'jest-location-mock'; import { I18nextProvider } from 'react-i18next'; import type { InterfaceVenueModalProps } from './VenueModal'; @@ -19,6 +18,8 @@ import { UPDATE_VENUE_MUTATION, } from 'GraphQl/Mutations/mutations'; import type { ApolloLink } from '@apollo/client'; +import { vi } from 'vitest'; +import type * as RouterTypes from 'react-router-dom'; const MOCKS = [ { @@ -65,10 +66,16 @@ const MOCKS = [ const link = new StaticMockLink(MOCKS, true); const mockId = 'orgId'; -jest.mock('react-router-dom', () => ({ - ...jest.requireActual('react-router-dom'), - useParams: () => ({ orgId: mockId }), -})); + +vi.mock('react-router-dom', async () => { + const actual = (await vi.importActual( + 'react-router-dom', + )) as typeof RouterTypes; + return { + ...actual, + useParams: () => ({ orgId: mockId }), + }; +}); async function wait(ms = 100): Promise { await act(() => { @@ -78,26 +85,26 @@ async function wait(ms = 100): Promise { }); } -jest.mock('react-toastify', () => ({ +vi.mock('react-toastify', () => ({ toast: { - success: jest.fn(), - warning: jest.fn(), - error: jest.fn(), + success: vi.fn(), + warning: vi.fn(), + error: vi.fn(), }, })); const props: InterfaceVenueModalProps[] = [ { show: true, - onHide: jest.fn(), + onHide: vi.fn(), edit: false, venueData: null, - refetchVenues: jest.fn(), + refetchVenues: vi.fn(), orgId: 'orgId', }, { show: true, - onHide: jest.fn(), + onHide: vi.fn(), edit: true, venueData: { _id: 'venue1', @@ -106,7 +113,7 @@ const props: InterfaceVenueModalProps[] = [ image: 'image1', capacity: '100', }, - refetchVenues: jest.fn(), + refetchVenues: vi.fn(), orgId: 'orgId', }, ]; @@ -129,7 +136,7 @@ const renderVenueModal = ( }; describe('VenueModal', () => { - global.alert = jest.fn(); + global.alert = vi.fn(); test('renders correctly when show is true', async () => { renderVenueModal(props[0], link); diff --git a/src/screens/EventManagement/EventManagement.tsx b/src/screens/EventManagement/EventManagement.tsx index 2e5cdbd419..e355ac1acc 100644 --- a/src/screens/EventManagement/EventManagement.tsx +++ b/src/screens/EventManagement/EventManagement.tsx @@ -17,6 +17,7 @@ import VolunteerContainer from 'screens/EventVolunteers/VolunteerContainer'; import EventAgendaItems from 'components/EventManagement/EventAgendaItems/EventAgendaItems'; import useLocalStorage from 'utils/useLocalstorage'; import EventAttendance from 'components/EventManagement/EventAttendance/EventAttendance'; +import EventRegistrants from 'components/EventManagement/EventRegistrant/EventRegistrants'; /** * List of tabs for the event dashboard. * @@ -231,7 +232,9 @@ const EventManagement = (): JSX.Element => { ); case 'registrants': return ( -
Event Registrants
+
+ +
); case 'attendance': return ( diff --git a/src/screens/EventVolunteers/VolunteerContainer.test.tsx b/src/screens/EventVolunteers/VolunteerContainer.spec.tsx similarity index 71% rename from src/screens/EventVolunteers/VolunteerContainer.test.tsx rename to src/screens/EventVolunteers/VolunteerContainer.spec.tsx index 928d04195c..292dcd9b34 100644 --- a/src/screens/EventVolunteers/VolunteerContainer.test.tsx +++ b/src/screens/EventVolunteers/VolunteerContainer.spec.tsx @@ -12,9 +12,28 @@ import userEvent from '@testing-library/user-event'; import { MOCKS } from './Volunteers/Volunteers.mocks'; import { StaticMockLink } from 'utils/StaticMockLink'; import { LocalizationProvider } from '@mui/x-date-pickers'; +import { describe, it, beforeEach, expect, vi } from 'vitest'; + +/** + * Unit tests for the `VolunteerContainer` component. + * + * The tests ensure the `VolunteerContainer` component renders correctly with various routes and URL parameters. + * Mocked dependencies are used to isolate the component and verify its behavior. + * All tests are covered. + */ const link1 = new StaticMockLink(MOCKS); +const mockedUseParams = vi.fn(); + +vi.mock('react-router-dom', async () => { + const actual = await vi.importActual('react-router-dom'); + return { + ...actual, + useParams: () => mockedUseParams(), + }; +}); + const renderVolunteerContainer = (): RenderResult => { return render( @@ -41,18 +60,21 @@ const renderVolunteerContainer = (): RenderResult => { }; describe('Testing Volunteer Container', () => { - beforeAll(() => { - jest.mock('react-router-dom', () => ({ - ...jest.requireActual('react-router-dom'), - useParams: () => ({ orgId: 'orgId', eventId: 'eventId' }), - })); + beforeEach(() => { + vi.clearAllMocks(); }); - afterAll(() => { - jest.clearAllMocks(); + it('should redirect to fallback URL if URL params are undefined', async () => { + mockedUseParams.mockReturnValue({}); + + renderVolunteerContainer(); + + await waitFor(() => { + expect(screen.getByTestId('paramsError')).toBeInTheDocument(); + }); }); - it('should redirect to fallback URL if URL params are undefined', async () => { + it('Testing Volunteer Container Screen -> Toggle screens', async () => { render( @@ -71,24 +93,26 @@ describe('Testing Volunteer Container', () => { , ); - await waitFor(() => { - expect(screen.getByTestId('paramsError')).toBeInTheDocument(); - }); - }); + mockedUseParams.mockReturnValue({ orgId: 'orgId', eventId: 'eventId' }); - test('Testing Volunteer Container Screen -> Toggle screens', async () => { renderVolunteerContainer(); const groupRadio = await screen.findByTestId('groupsRadio'); - expect(groupRadio).toBeInTheDocument(); - userEvent.click(groupRadio); - const requestsRadio = await screen.findByTestId('requestsRadio'); - expect(requestsRadio).toBeInTheDocument(); - userEvent.click(requestsRadio); - const individualRadio = await screen.findByTestId('individualRadio'); + + expect(groupRadio).toBeInTheDocument(); + expect(requestsRadio).toBeInTheDocument(); expect(individualRadio).toBeInTheDocument(); - userEvent.click(individualRadio); + + await waitFor(async () => { + await userEvent.click(groupRadio); + await userEvent.click(requestsRadio); + await userEvent.click(individualRadio); + }); + + await waitFor(() => { + expect(screen.getByTestId('paramsError')).toBeInTheDocument(); + }); }); }); diff --git a/src/screens/EventVolunteers/VolunteerContainer.tsx b/src/screens/EventVolunteers/VolunteerContainer.tsx index e026c6f7c8..7e28124b43 100644 --- a/src/screens/EventVolunteers/VolunteerContainer.tsx +++ b/src/screens/EventVolunteers/VolunteerContainer.tsx @@ -34,7 +34,7 @@ function volunteerContainer(): JSX.Element { return (
- + {t( `${dataType === 'group' ? 'volunteerGroups' : dataType === 'individual' ? 'volunteers' : 'requests'}`, )} diff --git a/src/screens/ForgotPassword/ForgotPassword.module.css b/src/screens/ForgotPassword/ForgotPassword.module.css deleted file mode 100644 index 74e09aecc6..0000000000 --- a/src/screens/ForgotPassword/ForgotPassword.module.css +++ /dev/null @@ -1,71 +0,0 @@ -.pageWrapper { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - height: 100vh; -} - -.cardBody { - padding: 2rem; - background-color: #fff; - border-radius: 0.8rem; - border: 1px solid var(--bs-gray-200); -} - -.keyWrapper { - height: 72px; - width: 72px; - transform: rotate(180deg); - position: relative; - overflow: hidden; - display: block; - display: flex; - justify-content: center; - align-items: center; - border-radius: 50%; - margin: 1rem auto; -} - -.keyWrapper .themeOverlay { - position: absolute; - background-color: var(--bs-primary); - height: 100%; - width: 100%; - opacity: 0.15; -} - -.keyWrapper .keyLogo { - height: 42px; - width: 42px; - -webkit-animation: zoomIn 0.3s ease-in-out; - animation: zoomIn 0.3s ease-in-out; -} - -@-webkit-keyframes zoomIn { - 0% { - opacity: 0; - -webkit-transform: scale(0.5); - transform: scale(0.5); - } - - 100% { - opacity: 1; - -webkit-transform: scale(1); - transform: scale(1); - } -} - -@keyframes zoomIn { - 0% { - opacity: 0; - -webkit-transform: scale(0.5); - transform: scale(0.5); - } - - 100% { - opacity: 1; - -webkit-transform: scale(1); - transform: scale(1); - } -} diff --git a/src/screens/ForgotPassword/ForgotPassword.tsx b/src/screens/ForgotPassword/ForgotPassword.tsx index 663960572b..83f20b4375 100644 --- a/src/screens/ForgotPassword/ForgotPassword.tsx +++ b/src/screens/ForgotPassword/ForgotPassword.tsx @@ -16,7 +16,7 @@ import { Form } from 'react-bootstrap'; import Button from 'react-bootstrap/Button'; import { useTranslation } from 'react-i18next'; import { errorHandler } from 'utils/errorHandler'; -import styles from './ForgotPassword.module.css'; +import styles from 'style/app.module.css'; import useLocalStorage from 'utils/useLocalstorage'; /** @@ -162,7 +162,7 @@ const ForgotPassword = (): JSX.Element => {
-
+
diff --git a/src/screens/LoginPage/LoginPage.module.css b/src/screens/LoginPage/LoginPage.module.css deleted file mode 100644 index 6f58138c34..0000000000 --- a/src/screens/LoginPage/LoginPage.module.css +++ /dev/null @@ -1,269 +0,0 @@ -.login_background { - min-height: 100vh; -} - -.communityLogo { - object-fit: contain; -} - -.row .left_portion { - display: flex; - justify-content: center; - align-items: center; - flex-direction: column; - height: 100vh; -} - -.selectOrgText input { - outline: none !important; -} - -.row .left_portion .inner .palisadoes_logo { - width: 600px; - height: auto; -} - -.row .right_portion { - min-height: 100vh; - position: relative; - overflow-y: scroll; - display: flex; - flex-direction: column; - justify-content: center; - padding: 1rem 2.5rem; - background: var(--bs-white); -} - -.row .right_portion::-webkit-scrollbar { - display: none; -} - -.row .right_portion .langChangeBtn { - margin: 0; - position: absolute; - top: 1rem; - left: 1rem; -} - -.langChangeBtnStyle { - width: 7.5rem; - height: 2.2rem; - padding: 0; -} - -.row .right_portion .talawa_logo { - height: 5rem; - width: 5rem; - display: block; - margin: 1.5rem auto 1rem; - -webkit-animation: zoomIn 0.3s ease-in-out; - animation: zoomIn 0.3s ease-in-out; -} - -.row .orText { - display: block; - position: absolute; - top: 0; - left: calc(50% - 2.6rem); - margin: 0 auto; - padding: 0.35rem 2rem; - z-index: 100; - background: var(--bs-white); - color: var(--bs-secondary); -} - -.email_button { - position: absolute; - z-index: 10; - bottom: 0; - right: 0; - height: 100%; - display: flex; - background-color: var(--search-button-bg); - border-color: var(--search-button-border); - justify-content: center; - align-items: center; -} - -.login_btn { - background-color: var(--search-button-bg); - border-color: var(--search-button-border); - margin-top: 1rem; - /* mt-3: Bootstrap margin spacing utility (3 = 1rem) */ - margin-bottom: 1rem; - /* mb-3: Bootstrap margin spacing utility (3 = 1rem) */ - width: 100%; -} - -.reg_btn { - background-color: var(--dropdown-border-color); - border-color: var(--dropdown-border-color); - margin-top: 1rem; - color: white; - /* mt-3: Bootstrap margin spacing utility (3 = 1rem) */ - margin-bottom: 1rem; - /* mb-3: Bootstrap margin spacing utility (3 = 1rem) */ - width: 100%; -} - -@media (max-width: 992px) { - .row .left_portion { - padding: 0 2rem; - } - - .row .left_portion .inner .palisadoes_logo { - width: 100%; - } -} - -@media (max-width: 769px) { - .row { - flex-direction: column-reverse; - } - - .row .right_portion, - .row .left_portion { - height: unset; - } - - .row .right_portion { - min-height: 100vh; - overflow-y: unset; - } - - .row .left_portion .inner { - display: flex; - justify-content: center; - } - - .row .left_portion .inner .palisadoes_logo { - height: 70px; - width: unset; - position: absolute; - margin: 0.5rem; - top: 0; - right: 0; - z-index: 100; - } - - .row .left_portion .inner p { - margin-bottom: 0; - padding: 1rem; - } - - .socialIcons { - margin-bottom: 1rem; - } -} - -@media (max-width: 577px) { - .row .right_portion { - padding: 1rem 1rem 0 1rem; - } - - .row .right_portion .langChangeBtn { - position: absolute; - margin: 1rem; - left: 0; - top: 0; - } - - .marginTopForReg { - margin-top: 4rem !important; - } - - .row .right_portion .talawa_logo { - height: 120px; - margin: 0 auto 2rem auto; - } - - .socialIcons { - margin-bottom: 1rem; - } -} - -.active_tab { - -webkit-animation: fadeIn 0.3s ease-in-out; - animation: fadeIn 0.3s ease-in-out; -} - -@-webkit-keyframes zoomIn { - 0% { - opacity: 0; - -webkit-transform: scale(0.5); - transform: scale(0.5); - } - - 100% { - opacity: 1; - -webkit-transform: scale(1); - transform: scale(1); - } -} - -@keyframes zoomIn { - 0% { - opacity: 0; - -webkit-transform: scale(0.5); - transform: scale(0.5); - } - - 100% { - opacity: 1; - -webkit-transform: scale(1); - transform: scale(1); - } -} - -@-webkit-keyframes fadeIn { - 0% { - opacity: 0; - -webkit-transform: translateY(2rem); - transform: translateY(2rem); - } - - 100% { - opacity: 1; - -webkit-transform: translateY(0); - transform: translateY(0); - } -} - -@keyframes fadeIn { - 0% { - opacity: 0; - -webkit-transform: translateY(2rem); - transform: translateY(2rem); - } - - 100% { - opacity: 1; - -webkit-transform: translateY(0); - transform: translateY(0); - } -} - -.socialIcons { - display: flex; - gap: 16px; - justify-content: center; -} - -.password_checks { - display: flex; - justify-content: space-between; - align-items: flex-start; - flex-direction: column; -} - -.password_check_element { - margin-top: -10px; -} - -.password_check_element_top { - margin-top: 18px; -} - -.password_check_element_bottom { - margin-bottom: -20px; -} diff --git a/src/screens/LoginPage/LoginPage.tsx b/src/screens/LoginPage/LoginPage.tsx index 180009926c..605811c834 100644 --- a/src/screens/LoginPage/LoginPage.tsx +++ b/src/screens/LoginPage/LoginPage.tsx @@ -30,7 +30,7 @@ import LoginPortalToggle from 'components/LoginPortalToggle/LoginPortalToggle'; import { errorHandler } from 'utils/errorHandler'; import useLocalStorage from 'utils/useLocalstorage'; import { socialMediaLinks } from '../../constants'; -import styles from './LoginPage.module.css'; +import styles from 'style/app.module.css'; import type { InterfaceQueryOrganizationListObject } from 'utils/interfaces'; import { Autocomplete, TextField } from '@mui/material'; import useSession from 'utils/useSession'; diff --git a/src/screens/MemberDetail/MemberDetail.tsx b/src/screens/MemberDetail/MemberDetail.tsx index cd552c80a0..cc528a8de3 100644 --- a/src/screens/MemberDetail/MemberDetail.tsx +++ b/src/screens/MemberDetail/MemberDetail.tsx @@ -3,7 +3,7 @@ import { useMutation, useQuery } from '@apollo/client'; import Button from 'react-bootstrap/Button'; import { useTranslation } from 'react-i18next'; import { useLocation, useNavigate, useParams } from 'react-router-dom'; -import styles from './MemberDetail.module.css'; +import styles from '../../style/app.module.css'; import { languages } from 'utils/languages'; import { UPDATE_USER_MUTATION } from 'GraphQl/Mutations/mutations'; import { USER_DETAILS } from 'GraphQl/Queries/Queries'; @@ -433,7 +433,7 @@ const MemberDetail: React.FC = ({ id }): JSX.Element => { {t('birthDate')} { afterEach(() => { localStorage.clear(); cleanup(); + jest.clearAllMocks(); }); describe('Organisations Page testing as SuperAdmin', () => { setItem('id', '123'); - const link = new StaticMockLink(MOCKS, true); const link2 = new StaticMockLink(MOCKS_EMPTY, true); const link3 = new StaticMockLink(MOCKS_WITH_ERROR, true); @@ -475,7 +475,6 @@ describe('Organisations Page testing as SuperAdmin', () => { describe('Organisations Page testing as Admin', () => { const link = new StaticMockLink(MOCKS_ADMIN, true); - test('Create organization modal should not be present in the page for Admin', async () => { setItem('id', '123'); setItem('SuperAdmin', false); @@ -501,35 +500,47 @@ describe('Organisations Page testing as Admin', () => { setItem('SuperAdmin', false); setItem('AdminFor', [{ name: 'adi', _id: 'a0', image: '' }]); - await act(async () => { - render( - - - - - - - - - , - ); - - await wait(); - }); - const sortDropdown = await waitFor(() => screen.getByTestId('sort')); + render( + + + + + + + + + , + ); + + await wait(); + + const sortDropdown = screen.getByTestId('sort'); expect(sortDropdown).toBeInTheDocument(); const sortToggle = screen.getByTestId('sortOrgs'); - fireEvent.click(sortToggle); - const latestOption = await waitFor(() => screen.getByTestId('latest')); + await act(async () => { + fireEvent.click(sortToggle); + }); + + const latestOption = screen.getByTestId('latest'); - fireEvent.click(latestOption); + await act(async () => { + fireEvent.click(latestOption); + }); expect(sortDropdown).toBeInTheDocument(); - fireEvent.click(sortToggle); + + await act(async () => { + fireEvent.click(sortToggle); + }); + const oldestOption = await waitFor(() => screen.getByTestId('oldest')); - fireEvent.click(oldestOption); + + await act(async () => { + fireEvent.click(oldestOption); + }); + expect(sortDropdown).toBeInTheDocument(); }); }); diff --git a/src/screens/OrgList/OrgList.tsx b/src/screens/OrgList/OrgList.tsx index 37a4276982..f30bb8f1ea 100644 --- a/src/screens/OrgList/OrgList.tsx +++ b/src/screens/OrgList/OrgList.tsx @@ -233,6 +233,8 @@ function orgList(): JSX.Element { }; if (errorList || errorUser) { + errorHandler(t, errorList || errorUser); + localStorage.clear(); window.location.assign('/'); } diff --git a/src/screens/OrgList/OrganizationModal.tsx b/src/screens/OrgList/OrganizationModal.tsx index fb3589d1e5..20b131eeb9 100644 --- a/src/screens/OrgList/OrganizationModal.tsx +++ b/src/screens/OrgList/OrganizationModal.tsx @@ -301,7 +301,7 @@ const OrganizationModal: React.FC = ({
{tCommon('OR')}
- {(adminFor.length > 0 || superAdmin) && ( + {((adminFor && adminFor.length > 0) || superAdmin) && (
-
+
@@ -426,7 +426,9 @@ const orgFundCampaign = (): JSX.Element => { ), }} sx={dataGridStyle} - getRowClassName={() => `${styles.rowBackground}`} + getRowClassName={() => + `${styles.rowBackgroundOrganizationFundCampaign}` + } autoHeight rowHeight={65} rows={campaigns.map((campaign, index) => ({ diff --git a/src/screens/OrganizationFundCampaign/OrganizationFundCampaign.module.css b/src/screens/OrganizationFundCampaign/OrganizationFundCampaign.module.css deleted file mode 100644 index 55202baef9..0000000000 --- a/src/screens/OrganizationFundCampaign/OrganizationFundCampaign.module.css +++ /dev/null @@ -1,202 +0,0 @@ -.organizationFundCampaignContainer { - margin: 0.5rem 0; -} -.goalButton { - border: 1px solid rgba(49, 187, 107, 1) !important; - color: rgba(49, 187, 107, 1) !important; - width: 75%; - padding: 10px; - border-radius: 8px; - display: block; - margin: auto; - box-shadow: 5px 5px 4px 0px rgba(49, 187, 107, 0.12); -} -.rowBackground { - background-color: var(--bs-white); - max-height: 120px; -} -.container { - min-height: 100vh; -} -.campaignModal { - max-width: 80vw; - margin-top: 2vh; - margin-left: 13vw; -} -.titlemodal { - color: #707070; - font-weight: 600; - font-size: 32px; - width: 65%; - margin-bottom: 0px; -} -.noOutline input { - outline: none; -} -.modalCloseBtn { - width: 40px; - height: 40px; - padding: 1rem; - display: flex; - justify-content: center; - align-items: center; -} -.greenregbtn { - margin: 1rem 0 0; - margin-top: 15px; - border: 1px solid #e8e5e5; - box-shadow: 0 2px 2px #e8e5e5; - padding: 10px 10px; - border-radius: 5px; - background-color: #31bb6b; - width: 100%; - font-size: 16px; - color: white; - outline: none; - font-weight: 600; - cursor: pointer; - transition: - transform 0.2s, - box-shadow 0.2s; - width: 100%; - flex: 1; -} - -.redregbtn { - margin: 1rem 0 0; - margin-top: 15px; - border: 1px solid #e8e5e5; - box-shadow: 0 2px 2px #e8e5e5; - padding: 10px 10px; - border-radius: 5px; - width: 100%; - font-size: 16px; - color: white; - outline: none; - font-weight: 600; - cursor: pointer; - transition: - transform 0.2s, - box-shadow 0.2s; - width: 100%; - flex: 1; -} -.campaignNameInfo { - font-size: medium; - cursor: pointer; -} -.campaignNameInfo:hover { - color: blue; - transform: translateY(-2px); -} -.message { - margin-top: 25%; - display: flex; - justify-content: center; - align-items: center; - flex-direction: column; -} - -.inputField { - background-color: white; - box-shadow: 0 1px 1px #31bb6b; -} - -.dropdown { - background-color: white; - border: 1px solid #31bb6b; - position: relative; - display: inline-block; - color: #31bb6b; -} - -.btnsContainer { - display: flex; - margin: 2rem 0 2rem 0; - gap: 0.8rem; -} - -.btnsContainer .btnsBlock { - display: flex; - gap: 0.8rem; -} - -.btnsContainer .btnsBlock div button { - display: flex; - margin-left: 1rem; - justify-content: center; - align-items: center; -} - -.btnsContainer .input { - flex: 1; - position: relative; -} - -.btnsContainer input { - outline: 1px solid var(--bs-gray-400); -} - -.btnsContainer .input button { - width: 52px; -} - -.mainpageright > hr { - margin-top: 20px; - width: 100%; - margin-left: -15px; - margin-right: -15px; - margin-bottom: 20px; -} - -.tableHeader { - background-color: var(--bs-primary); - color: var(--bs-white); - font-size: 1rem; -} - -@media (max-width: 1020px) { - .btnsContainer { - flex-direction: column; - margin: 1.5rem 0; - } - - .btnsContainer .btnsBlock { - margin: 1.5rem 0 0 0; - justify-content: space-between; - } - - .btnsContainer .btnsBlock div button { - margin: 0; - } - - .createFundBtn { - margin-top: 0; - } -} - -@media screen and (max-width: 575.5px) { - .mainpageright { - width: 98%; - } -} - -/* For mobile devices */ - -@media (max-width: 520px) { - .btnsContainer { - margin-bottom: 0; - } - - .btnsContainer .btnsBlock { - display: block; - margin-top: 1rem; - margin-right: 0; - } - - .btnsContainer .btnsBlock div button { - margin-bottom: 1rem; - margin-right: 0; - width: 100%; - } -} diff --git a/src/screens/UserPortal/Settings/Settings.spec.tsx b/src/screens/UserPortal/Settings/Settings.spec.tsx index 39d853862f..184789ab04 100644 --- a/src/screens/UserPortal/Settings/Settings.spec.tsx +++ b/src/screens/UserPortal/Settings/Settings.spec.tsx @@ -410,3 +410,36 @@ describe('Testing Settings Screen [User Portal]', () => { }); }); }); + +it('prevents selecting future dates for birth date', async () => { + await act(async () => { + render( + + + + + + + + + , + ); + }); + + const birthDateInput = screen.getByLabelText( + 'Birth Date', + ) as HTMLInputElement; + const today = new Date().toISOString().split('T')[0]; + const futureDate = new Date(); + futureDate.setFullYear(futureDate.getFullYear() + 100); + const futureDateString = futureDate.toISOString().split('T')[0]; + + // Trying future date + fireEvent.change(birthDateInput, { target: { value: futureDateString } }); + // Checking if value is not updated to future date + expect(birthDateInput.value).not.toBe(futureDateString); + + // Checking if value set correctly + fireEvent.change(birthDateInput, { target: { value: today } }); + expect(birthDateInput.value).toBe(today); +}); diff --git a/src/screens/UserPortal/Settings/Settings.tsx b/src/screens/UserPortal/Settings/Settings.tsx index cc222468fc..385c3d639e 100644 --- a/src/screens/UserPortal/Settings/Settings.tsx +++ b/src/screens/UserPortal/Settings/Settings.tsx @@ -126,6 +126,19 @@ export default function settings(): JSX.Element { * @param value - The new value for the field. */ const handleFieldChange = (fieldName: string, value: string): void => { + // If the field is 'birthDate', validate the date + if (fieldName === 'birthDate') { + const today = new Date(); + const selectedDate = new Date(value); + + // Prevent updating the state if the selected date is in the future + if (selectedDate > today) { + console.error('Future dates are not allowed for the birth date.'); + return; // Exit without updating the state + } + } + + // Update state if the value passes validation setisUpdated(true); setUserDetails((prevState) => ({ ...prevState, @@ -449,6 +462,7 @@ export default function settings(): JSX.Element { handleFieldChange('birthDate', e.target.value) } className={`${styles.cardControl}`} + max={new Date().toISOString().split('T')[0]} /> diff --git a/src/setup/askForCustomPort/askForCustomPort.test.ts b/src/setup/askForCustomPort/askForCustomPort.spec.ts similarity index 69% rename from src/setup/askForCustomPort/askForCustomPort.test.ts rename to src/setup/askForCustomPort/askForCustomPort.spec.ts index c46f7b3f91..08e67cec00 100644 --- a/src/setup/askForCustomPort/askForCustomPort.test.ts +++ b/src/setup/askForCustomPort/askForCustomPort.spec.ts @@ -1,33 +1,34 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; import inquirer from 'inquirer'; import { askForCustomPort, validatePort } from './askForCustomPort'; -jest.mock('inquirer'); +vi.mock('inquirer'); describe('askForCustomPort', () => { beforeEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); }); describe('basic port validation', () => { - test('should return default port if user provides no input', async () => { - jest - .spyOn(inquirer, 'prompt') - .mockResolvedValueOnce({ customPort: '4321' }); + it('should return default port if user provides no input', async () => { + vi.spyOn(inquirer, 'prompt').mockResolvedValueOnce({ + customPort: '4321', + }); const result = await askForCustomPort(); expect(result).toBe(4321); }); - test('should return user-provided port', async () => { - jest - .spyOn(inquirer, 'prompt') - .mockResolvedValueOnce({ customPort: '8080' }); + it('should return user-provided port', async () => { + vi.spyOn(inquirer, 'prompt').mockResolvedValueOnce({ + customPort: '8080', + }); const result = await askForCustomPort(); expect(result).toBe(8080); }); - test('should return validation error if port not between 1 and 65535', () => { + it('should return validation error if port not between 1 and 65535', () => { expect(validatePort('abcd')).toBe( 'Please enter a valid port number between 1 and 65535.', ); @@ -41,9 +42,8 @@ describe('askForCustomPort', () => { }); describe('retry mechanism', () => { - test('should handle invalid port input and prompt again', async () => { - jest - .spyOn(inquirer, 'prompt') + it('should handle invalid port input and prompt again', async () => { + vi.spyOn(inquirer, 'prompt') .mockResolvedValueOnce({ customPort: 'abcd' }) .mockResolvedValueOnce({ customPort: '8080' }); @@ -51,9 +51,8 @@ describe('askForCustomPort', () => { expect(result).toBe(8080); }); - test('should return default port after maximum retry attempts', async () => { - jest - .spyOn(inquirer, 'prompt') + it('should return default port after maximum retry attempts', async () => { + vi.spyOn(inquirer, 'prompt') .mockResolvedValueOnce({ customPort: 'invalid-port-attempt1' }) .mockResolvedValueOnce({ customPort: 'invalid-port-attempt2' }) .mockResolvedValueOnce({ customPort: 'invalid-port-attempt3' }) @@ -67,9 +66,8 @@ describe('askForCustomPort', () => { }); describe('reserved ports', () => { - test('should return user-provided port after confirming reserved port', async () => { - jest - .spyOn(inquirer, 'prompt') + it('should return user-provided port after confirming reserved port', async () => { + vi.spyOn(inquirer, 'prompt') .mockResolvedValueOnce({ customPort: '80' }) .mockResolvedValueOnce({ confirmPort: true }); @@ -77,9 +75,8 @@ describe('askForCustomPort', () => { expect(result).toBe(80); }); - test('should re-prompt user for port if reserved port confirmation is denied', async () => { - jest - .spyOn(inquirer, 'prompt') + it('should re-prompt user for port if reserved port confirmation is denied', async () => { + vi.spyOn(inquirer, 'prompt') .mockResolvedValueOnce({ customPort: '80' }) .mockResolvedValueOnce({ confirmPort: false }) .mockResolvedValueOnce({ customPort: '8080' }); @@ -88,9 +85,8 @@ describe('askForCustomPort', () => { expect(result).toBe(8080); }); - test('should return default port if reserved port confirmation is denied after maximum retry attempts', async () => { - jest - .spyOn(inquirer, 'prompt') + it('should return default port if reserved port confirmation is denied after maximum retry attempts', async () => { + vi.spyOn(inquirer, 'prompt') .mockResolvedValueOnce({ customPort: '80' }) .mockResolvedValueOnce({ confirmPort: false }) .mockResolvedValueOnce({ customPort: '80' }) diff --git a/src/state/action-creators/index.test.ts b/src/state/action-creators/index.spec.ts similarity index 90% rename from src/state/action-creators/index.test.ts rename to src/state/action-creators/index.spec.ts index 33aa642b8a..141eb679d9 100644 --- a/src/state/action-creators/index.test.ts +++ b/src/state/action-creators/index.spec.ts @@ -1,3 +1,4 @@ +import { vi } from 'vitest'; import { updateInstalled, installPlugin, @@ -10,7 +11,7 @@ describe('Testing rc/state/action-creators/index.ts', () => { const temp = updateInstalled('testPlug'); expect(typeof temp).toBe('function'); //stubbing the childfunction to check execution - const childFunction = jest.fn(); + const childFunction = vi.fn(); temp(childFunction); expect(childFunction).toHaveBeenCalled(); }); @@ -20,7 +21,7 @@ describe('Testing rc/state/action-creators/index.ts', () => { const temp = installPlugin('testPlug'); expect(typeof temp).toBe('function'); //stubbing the childfunction to check execution - const childFunction = jest.fn(); + const childFunction = vi.fn(); temp(childFunction); expect(childFunction).toHaveBeenCalled(); }); @@ -30,7 +31,7 @@ describe('Testing rc/state/action-creators/index.ts', () => { const temp = removePlugin('testPlug'); expect(typeof temp).toBe('function'); //stubbing the childfunction to check execution - const childFunction = jest.fn(); + const childFunction = vi.fn(); temp(childFunction); expect(childFunction).toHaveBeenCalled(); }); @@ -40,7 +41,7 @@ describe('Testing rc/state/action-creators/index.ts', () => { const temp = updatePluginLinks('testPlug'); expect(typeof temp).toBe('function'); //stubbing the childfunction to check execution - const childFunction = jest.fn(); + const childFunction = vi.fn(); temp(childFunction); expect(childFunction).toHaveBeenCalled(); }); diff --git a/src/state/helpers/Action.test.ts b/src/state/helpers/Action.spec.ts similarity index 53% rename from src/state/helpers/Action.test.ts rename to src/state/helpers/Action.spec.ts index a971c6c160..cc81153617 100644 --- a/src/state/helpers/Action.test.ts +++ b/src/state/helpers/Action.spec.ts @@ -1,8 +1,11 @@ import type { InterfaceAction } from './Action'; test('Testing Reducer Action Interface', () => { - ({ + const action = { type: 'STRING_ACTION_TYPE', payload: 'ANY_PAYLOAD', - }) as InterfaceAction; + } as InterfaceAction; + + expect(action.type).toBe('STRING_ACTION_TYPE'); + expect(action.payload).toBe('ANY_PAYLOAD'); }); diff --git a/src/state/reducers/pluginReducer.test.ts b/src/state/reducers/pluginReducer.spec.ts similarity index 100% rename from src/state/reducers/pluginReducer.test.ts rename to src/state/reducers/pluginReducer.spec.ts diff --git a/src/state/reducers/routesReducer.test.ts b/src/state/reducers/routesReducer.spec.ts similarity index 100% rename from src/state/reducers/routesReducer.test.ts rename to src/state/reducers/routesReducer.spec.ts diff --git a/src/state/reducers/userRoutersReducer.test.ts b/src/state/reducers/userRoutersReducer.spec.ts similarity index 100% rename from src/state/reducers/userRoutersReducer.test.ts rename to src/state/reducers/userRoutersReducer.spec.ts diff --git a/src/state/store.test.tsx b/src/state/store.spec.tsx similarity index 100% rename from src/state/store.test.tsx rename to src/state/store.spec.tsx diff --git a/src/style/app.module.css b/src/style/app.module.css index 5bfdef1e66..5105146238 100644 --- a/src/style/app.module.css +++ b/src/style/app.module.css @@ -992,6 +992,180 @@ hr { background: #afffe8 !important; } +.organizationFundCampaignContainer { + margin: 0.5rem 0; +} + +.rowBackgroundOrganizationFundCampaign { + background-color: var(--bs-white); + max-height: 120px; +} + +.campaignModal { + max-width: 80vw; + margin-top: 2vh; + margin-left: 13vw; +} + +.greenregbtnOrganizationFundCampaign { + margin: 1rem 0 0; + margin-top: 15px; + border: 1px solid var(--bs-gray-300); + box-shadow: 0 2px 2px #e8e5e5; + padding: 10px 10px; + border-radius: 5px; + background-color: var(--bs-primary); + width: 100%; + font-size: 16px; + color: white; + outline: none; + font-weight: 600; + cursor: pointer; + transition: + transform 0.2s, + box-shadow 0.2s; + flex: 1; +} + +.goalButtonOrganizationFundCampaign { + border: 1px solid #e8e5e5 !important; + color: #707070 !important; + width: 75%; + padding: 10px; + border-radius: 8px; + display: block; + margin: auto; + box-shadow: 5px 5px 4px 0px rgba(49, 187, 107, 0.12); +} + +.redregbtn { + margin: 1rem 0 0; + margin-top: 15px; + border: 1px solid #e8e5e5; + box-shadow: 0 2px 2px #e8e5e5; + padding: 10px 10px; + border-radius: 5px; + width: 100%; + font-size: 16px; + color: white; + outline: none; + font-weight: 600; + cursor: pointer; + transition: + transform 0.2s, + box-shadow 0.2s; + width: 100%; + flex: 1; +} + +.campaignNameInfo { + font-size: medium; + cursor: pointer; +} +.campaignNameInfo:hover { + color: blue; + transform: translateY(-2px); +} + +.inputFieldOrganizationFundCampaign { + background-color: white; + box-shadow: 0 1px 1px var(--search-button-bg); +} + +.dropdownOrganizationFundCampaign { + background-color: white; + border: 1px solid #e8e5e5; + position: relative; + display: inline-block; + color: #707070; +} + +.btnsContainerOrganizationFundCampaign { + display: flex; + margin: 2rem 0 2rem 0; + gap: 0.8rem; +} + +.btnsContainerOrganizationFundCampaign .btnsBlockOrganizationFundCampaign { + display: flex; + gap: 0.8rem; +} + +.btnsContainerOrganizationFundCampaign + .btnsBlockOrganizationFundCampaign + div + button { + display: flex; + margin-left: 1rem; + justify-content: center; + align-items: center; +} + +.btnsContainerOrganizationFundCampaign .inputOrganizationFundCampaign { + flex: 1; + position: relative; +} + +.btnsContainerOrganizationFundCampaign input { + outline: 1px solid var(--bs-gray-400); +} + +.btnsContainerOrganizationFundCampaign .inputOrganizationFundCampaign button { + width: 52px; +} + +@media (max-width: 1020px) { + .btnsContainerOrganizationFundCampaign { + flex-direction: column; + margin: 1.5rem 0; + } + + .btnsContainerOrganizationFundCampaign .btnsBlockOrganizationFundCampaign { + margin: 1.5rem 0 0 0; + justify-content: space-between; + } + + .btnsContainerOrganizationFundCampaign + .btnsBlockOrganizationFundCampaign + div + button { + margin: 0; + } + + .createFundBtn { + margin-top: 0; + } +} + +@media screen and (max-width: 575.5px) { + .mainpageright { + width: 98%; + } +} + +/* For mobile devices */ + +@media (max-width: 520px) { + .btnsContainerOrganizationFundCampaign { + margin-bottom: 0; + } + + .btnsContainerOrganizationFundCampaign .btnsBlockOrganizationFundCampaign { + display: block; + margin-top: 1rem; + margin-right: 0; + } + + .btnsContainerOrganizationFundCampaign + .btnsBlockOrganizationFundCampaign + div + button { + margin-bottom: 1rem; + margin-right: 0; + width: 100%; + } +} + @media (max-width: 1024px) { .pageNotFound h1.head { font-size: 200px; @@ -1555,7 +1729,6 @@ input[type='radio']:checked + label:hover { .listBoxOrgList .itemCardOrgList { width: 50%; } - .notFound { flex: 1; display: flex; @@ -1564,106 +1737,646 @@ input[type='radio']:checked + label:hover { flex-direction: column; } -@media (max-width: 1440px) { - .contractOrgList { - padding-left: calc(250px + 2rem + 1.5rem); - } - - .listBoxOrgList .itemCardOrgList { - width: 100%; - } +.mainpage { + display: flex; + flex-direction: row; } -@media (max-width: 1020px) { - .btnsContainerOrgList { - flex-direction: column; - margin: 1.5rem 0; - } +.editIcon { + position: absolute; + top: 10px; + left: 20px; + cursor: pointer; +} - .btnsContainerOrgList .btnsBlockOrgList { - margin: 1.5rem 0 0 0; - justify-content: space-between; - } +.selectWrapper { + position: relative; +} - .btnsContainerOrgList .btnsBlockOrgList button { - margin: 0; - } +.selectWithChevron { + appearance: none; + padding-right: 30px; +} - .btnsContainerOrgList .btnsBlockOrgList div button { - margin-right: 1.5rem; - } +.selectWrapper::after { + content: '\25BC'; + position: absolute; + top: 50%; + right: 10px; + transform: translateY(-50%); + pointer-events: none; } -/* For mobile devices */ +.sidebarstickyMemberDetail { + padding: 0 2rem; + text-overflow: ellipsis; + /* overflow-x: hidden; */ +} -@media (max-width: 520px) { - .btnsContainerOrgList { - margin-bottom: 0; - } +.sidebarstickyMemberDetail > p { + margin-top: -10px; +} - .btnsContainerOrgList .btnsBlockOrgList { - display: block; - margin-top: 1rem; - margin-right: 0; - } +.navitem { + padding-left: 27%; + padding-top: 12px; + padding-bottom: 12px; + cursor: pointer; +} - .btnsContainerOrgList .btnsBlockOrgList div { - flex: 1; - } +.searchtitleMemberDetail { + color: #707070; + font-weight: 600; + font-size: 18px; + margin-top: 60px; + margin-bottom: 20px; + padding-bottom: 5px; + border-bottom: 3px solid #eaebef; + width: 60%; +} - .btnsContainerOrgList .btnsBlockOrgList div[title='Sort organizations'] { - margin-right: 0.5rem; - } +.contact { + width: 100%; +} - .btnsContainerOrgList .btnsBlockOrgList button { - margin-bottom: 1rem; - margin-right: 0; - width: 100%; - } +.sidebarstickyMemberDetail > input { + text-decoration: none; + margin-bottom: 50px; + border-color: #e8e5e5; + width: 80%; + border-radius: 7px; + padding-top: 5px; + padding-bottom: 5px; + padding-right: 10px; + padding-left: 10px; + box-shadow: none; } -/* Loading OrgList CSS */ +.logintitleMemberDetail { + color: #707070; + font-weight: 600; + font-size: 20px; + margin-bottom: 30px; + padding-bottom: 5px; + border-bottom: 3px solid #eaebef; + width: 30%; +} -.itemCardOrgList .loadingWrapper { - background-color: var(--bs-white); - margin: 0.5rem; - height: calc(120px + 2rem); - padding: 1rem; - border-radius: 8px; - outline: 1px solid var(--bs-gray-200); - position: relative; +.logintitleadmin { + color: #707070; + font-weight: 600; + font-size: 20px; + margin-top: 50px; + margin-bottom: 40px; + padding-bottom: 5px; + border-bottom: 3px solid #eaebef; + width: 60%; } -.itemCardOrgList .loadingWrapper .innerContainer { - display: flex; +.cardBodyMemberDetail { + height: 35vh; + overflow-y: scroll; } -.itemCardOrgList .loadingWrapper .innerContainer .orgImgContainer { - width: 120px; - height: 120px; - border-radius: 4px; +.justifyspMemberDetail { + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: flex-start; + /* gap : 2px; */ } -.itemCardOrgList .loadingWrapper .innerContainer .content { - flex: 1; +.flexclm { display: flex; flex-direction: column; - margin-left: 1rem; } -.titlemodaldialog { - color: #707070; - font-size: 20px; - margin-bottom: 20px; - padding-bottom: 5px; +.btngroup { + display: flex; + gap: 2rem; + margin-bottom: 2rem; } -form label { - font-weight: bold; - padding-bottom: 1px; - font-size: 14px; - color: #707070; -} +@media screen and (max-width: 1200px) { + .justifyspMemberDetail { + padding-left: 55px; + display: flex; + justify-content: space-evenly; + } + + .mainpageright { + width: 100%; + } + + .invitebtn { + position: relative; + right: 15px; + } +} + +.invitebtn { + border: 1px solid #e8e5e5; + box-shadow: 0 2px 2px #e8e5e5; + border-radius: 5px; + font-size: 16px; + height: 60%; + color: white; + outline: none; + font-weight: 600; + cursor: pointer; + transition: + transform 0.2s, + box-shadow 0.2s; + background-color: #eaebef; + margin-right: 13px; +} + +.flexdir { + display: flex; + flex-direction: row; + justify-content: space-between; + border: none; +} + +.form_wrapper { + margin-top: 27px; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + position: absolute; + display: flex; + flex-direction: column; + width: 30%; + padding: 40px 30px; + background: #ffffff; + border-color: #e8e5e5; + border-width: 5px; + border-radius: 10px; + max-height: 86vh; + overflow: auto; +} + +.form_wrapper form { + display: flex; + align-items: left; + justify-content: left; + flex-direction: column; +} + +.titlemodalMemberDetail { + color: #707070; + font-weight: 600; + font-size: 20px; + margin-bottom: 20px; + padding-bottom: 5px; + border-bottom: 3px solid #eaebef; + width: 65%; +} + +.checkboxdiv > label { + margin-right: 50px; +} + +.checkboxdiv > label > input { + margin-left: 10px; +} + +.orgphoto { + margin-top: 5px; +} + +.orgphoto > input { + margin-top: 10px; + cursor: pointer; + margin-bottom: 5px; +} + +.cancel > i { + margin-top: 5px; + transform: scale(1.2); + cursor: pointer; + color: #707070; +} + +.greenregbtnMemberDetail { + margin: 1rem 0 0; + margin-top: 10px; + border: 1px solid #e8e5e5; + box-shadow: 0 2px 2px #e8e5e5; + padding: 10px 10px; + border-radius: 5px; + background-color: #eaebef; + font-size: 16px; + color: white; + outline: none; + font-weight: 600; + cursor: pointer; + transition: + transform 0.2s, + box-shadow 0.2s; +} + +.whiteregbtn { + margin: 1rem 0 0; + margin-right: 2px; + margin-top: 10px; + border: 1px solid #eaebef; + padding: 10px 10px; + border-radius: 5px; + background-color: white; + font-size: 16px; + color: #707070; + outline: none; + font-weight: 600; + cursor: pointer; + transition: + transform 0.2s, + box-shadow 0.2s; +} + +@-webkit-keyframes load8 { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + + 100% { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); + } +} + +@keyframes load8 { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + + 100% { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); + } +} + +.list_boxMemberDetail { + height: 70vh; + overflow-y: auto; + width: auto; + padding-right: 50px; +} + +.dispflex { + display: flex; +} + +.dispflex > input { + width: 20%; + border: none; + box-shadow: none; + margin-top: 5px; +} + +.checkboxdivMemberDetail { + display: flex; +} + +.checkboxdivMemberDetail > div { + width: 50%; +} + +@media only screen and (max-width: 600px) { + .sidebar { + position: relative; + bottom: 18px; + } + + .invitebtn { + width: 135px; + position: relative; + right: 10px; + } + + .form_wrapper { + width: 90%; + } + + .searchtitleMemberDetail { + margin-top: 30px; + } +} + +/* User page */ + +.memberfontcreatedbtn { + border-radius: 7px; + border-color: #e8e5e5; + background-color: #eaebef; + color: white; + box-shadow: none; + height: 2.5rem; + width: max-content; + display: flex; + justify-content: center; + align-items: center; +} + +.userImage { + width: 180px; + height: 180px; + object-fit: cover; + border-radius: 8px; +} + +@media only screen and (max-width: 1200px) { + .userImage { + width: 100px; + height: 100px; + } +} + +.activeBtn { + width: 100%; + display: flex; + color: #fff; + border: 1px solid #000; + background-color: #eaebef; + transition: 0.5s; +} + +.activeBtn:hover { + color: #fff; + background: #23864c; + transition: 0.5s; +} + +.inactiveBtn { + width: 100%; + display: flex; + color: #707070; + border: 1px solid #31bb6a60; + background-color: #fff; + transition: 0.5s; +} + +.inactiveBtn:hover { + color: #fff; + background: #eaebef; + transition: 0.5s; +} + +.sidebarstickyMemberDetail > button { + display: flex; + align-items: center; + text-align: start; + padding: 0 1.5rem; + height: 3.25rem; + margin: 0 0 1.5rem 0; + font-weight: bold; + border-radius: 50px; +} + +.bgFill { + height: 2rem; + width: 2rem; + border-radius: 50%; + margin-right: 1rem; + display: flex; + justify-content: center; + align-items: center; +} + +.activeBtn .bgFill { + background-color: #fff; +} + +.activeBtn i { + color: #707070; +} + +.inactiveBtn .bgFill { + background-color: #eaebef; +} + +.inactiveBtn:hover .bgFill { + background-color: #fff; +} + +.inactiveBtn i { + color: #fff; +} + +.inactiveBtn:hover i { + color: #707070; +} + +.topRadius { + border-top-left-radius: 16px; + border-top-right-radius: 16px; +} + +.titlenameMemberDetail { + font-weight: 600; + font-size: 25px; + padding: 15px; + padding-bottom: 0px; + width: 50%; +} + +.toporglocMemberDetail { + font-size: 16px; + padding: 15px; + padding-bottom: 0px; +} + +.toporglocMemberDetail span { + color: #737373; +} + +.inputColor { + background: #f1f3f6; +} + +.cardHeaderMemberDetail { + display: flex; + justify-content: space-between; + align-items: center; +} + +.width60 { + width: 60%; +} + +.maxWidth40 { + max-width: 40%; +} +.maxWidth50 { + max-width: 50%; +} + +.allRound { + border-radius: 16px; +} + +.WidthFit { + width: fit-content; +} + +.dateboxMemberDetail { + border-radius: 7px; + border-color: #e8e5e5; + outline: none; + box-shadow: none; + padding-top: 2px; + padding-bottom: 2px; + padding-right: 5px; + padding-left: 5px; + margin-right: 5px; + margin-left: 5px; +} + +.dateboxMemberDetail > div > input { + padding: 0.5rem 0 0.5rem 0.5rem !important; /* top, right, bottom, left */ + background-color: #f1f3f6; + border-radius: var(--bs-border-radius) !important; + border: none !important; +} + +.dateboxMemberDetail > div > div { + margin-left: 0px !important; +} + +.dateboxMemberDetail > div > fieldset { + border: none !important; + /* background-color: #f1f3f6; */ + border-radius: var(--bs-border-radius) !important; +} + +.dateboxMemberDetail > div { + margin: 0.5rem !important; + background-color: #f1f3f6; +} + +input::file-selector-button { + background-color: black; + color: white; +} + +.Outline { + outline: 1px solid var(--bs-gray-400); +} + +.tagLink { + font-weight: 600; + color: var(--bs-gray-700); + cursor: pointer; +} + +.tagLink:hover { + font-weight: 800; + color: var(--bs-blue); + text-decoration: underline; +} + +@media (max-width: 1440px) { + .contractOrgList { + padding-left: calc(250px + 2rem + 1.5rem); + } + + .listBoxOrgList .itemCardOrgList { + width: 100%; + } +} + +@media (max-width: 1020px) { + .btnsContainerOrgList { + flex-direction: column; + margin: 1.5rem 0; + } + + .btnsContainerOrgList .btnsBlockOrgList { + margin: 1.5rem 0 0 0; + justify-content: space-between; + } + + .btnsContainerOrgList .btnsBlockOrgList button { + margin: 0; + } + + .btnsContainerOrgList .btnsBlockOrgList div button { + margin-right: 1.5rem; + } +} + +/* For mobile devices */ + +@media (max-width: 520px) { + .btnsContainerOrgList { + margin-bottom: 0; + } + + .btnsContainerOrgList .btnsBlockOrgList { + display: block; + margin-top: 1rem; + margin-right: 0; + } + + .btnsContainerOrgList .btnsBlockOrgList div { + flex: 1; + } + + .btnsContainerOrgList .btnsBlockOrgList div[title='Sort organizations'] { + margin-right: 0.5rem; + } + + .btnsContainerOrgList .btnsBlockOrgList button { + margin-bottom: 1rem; + margin-right: 0; + width: 100%; + } +} + +/* Loading OrgList CSS */ + +.itemCardOrgList .loadingWrapper { + background-color: var(--bs-white); + margin: 0.5rem; + height: calc(120px + 2rem); + padding: 1rem; + border-radius: 8px; + outline: 1px solid var(--bs-gray-200); + position: relative; +} + +.itemCardOrgList .loadingWrapper .innerContainer { + display: flex; +} + +.itemCardOrgList .loadingWrapper .innerContainer .orgImgContainer { + width: 120px; + height: 120px; + border-radius: 4px; +} + +.itemCardOrgList .loadingWrapper .innerContainer .content { + flex: 1; + display: flex; + flex-direction: column; + margin-left: 1rem; +} + +.titlemodaldialog { + color: #707070; + font-size: 20px; + margin-bottom: 20px; + padding-bottom: 5px; +} + +form label { + font-weight: bold; + padding-bottom: 1px; + font-size: 14px; + color: #707070; +} form > input { display: block; @@ -1718,6 +2431,157 @@ form > input { z-index: 1; } +.login_background { + min-height: 100vh; +} + +.communityLogo { + object-fit: contain; +} + +.row .left_portion { + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; + height: 100vh; +} + +.selectOrgText input { + outline: none !important; +} + +.row .left_portion .inner .palisadoes_logo { + width: 600px; + height: auto; +} + +.row .right_portion { + min-height: 100vh; + position: relative; + overflow-y: scroll; + display: flex; + flex-direction: column; + justify-content: center; + padding: 1rem 2.5rem; + background: var(--bs-white); +} + +.row .right_portion::-webkit-scrollbar { + width: 8px; +} + +.row .right_portion::-webkit-scrollbar-track { + background: transparent; +} + +.row .right_portion::-webkit-scrollbar-thumb { + background-color: rgba(0, 0, 0, 0.2); + border-radius: 4px; +} + +.row .right_portion .langChangeBtn { + margin: 0; + position: absolute; + top: 1rem; + left: 1rem; +} + +.langChangeBtnStyle { + width: 7.5rem; + height: 2.2rem; + padding: 0; +} + +.talawa_logo { + height: clamp(3rem, 8vw, 5rem); + width: auto; + aspect-ratio: 1; + display: block; + margin: 1.5rem auto 1rem; + @media (prefers-reduced-motion: no-preference) { + -webkit-animation: zoomIn 0.3s ease-in-out; + animation: zoomIn 0.3s ease-in-out; + } +} + +.row .orText { + display: block; + position: absolute; + top: 0; + left: calc(50% - 2.6rem); + margin: 0 auto; + padding: 0.35rem 2rem; + z-index: 100; + background: var(--bs-white); + color: var(--bs-secondary); +} + +.email_button { + position: absolute; + z-index: 10; + bottom: 0; + right: 0; + height: 100%; + display: flex; + background-color: var(--search-button-bg); + border-color: var(--search-button-border); + justify-content: center; + align-items: center; +} + +.login_btn { + background-color: var(--search-button-bg); + border-color: var(--search-button-border); + margin-top: 1rem; + margin-bottom: 1rem; + width: 100%; + transition: background-color 0.2s ease; + cursor: pointer; +} + +.reg_btn { + background-color: var(--dropdown-border-color); + border-color: var(--dropdown-border-color); + margin-top: 1rem; + color: white; + margin-bottom: 1rem; + width: 100%; + transition: background-color 0.2s ease; + cursor: pointer; +} + +.active_tab { + -webkit-animation: fadeIn 0.3s ease-in-out; + animation: fadeIn 0.3s ease-in-out; +} + +.socialIcons { + display: flex; + gap: 16px; + justify-content: center; +} + +.password_checks { + display: flex; + justify-content: space-between; + align-items: flex-start; + flex-direction: column; + gap: var(--spacing-md, 1rem); +} + +.password_check_element { + padding: var(--spacing-sm, 0.5rem) 0; +} + +.password_check_element_top { + margin-top: var(--spacing-lg, 1.125rem); +} + +.password_check_element_bottom { + margin-bottom: var(--spacing-lg, 1.25rem); +} + @media (max-width: 450px) { .itemCardOrgList .loadingWrapper { height: unset; @@ -1823,6 +2687,50 @@ form > input { margin-bottom: 20px; } +.pageWrapper { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + height: 100vh; +} + +.cardTemplate { + padding: 2rem; + background-color: #fff; + border-radius: 0.8rem; + border: 1px solid var(--bs-gray-200); +} + +.keyWrapper { + height: 72px; + width: 72px; + transform: rotate(180deg); + transform-origin: center; + position: relative; + overflow: hidden; + display: flex; + justify-content: center; + align-items: center; + border-radius: 50%; + margin: 1rem auto; +} + +.keyWrapper .themeOverlay { + position: absolute; + background-color: var(--bs-primary); + height: 100%; + width: 100%; + opacity: var(--theme-overlay-opacity, 0.15); +} + +.keyWrapper .keyLogo { + height: 42px; + width: 42px; + -webkit-animation: zoomIn 0.3s ease-in-out; + animation: zoomIn 0.3s ease-in-out; +} + @media (max-width: 1020px) { .btnsContainerOrgPost { flex-direction: column; @@ -1843,6 +2751,133 @@ form > input { } } +@media (max-width: 992px) { + .row .left_portion { + padding: 0 2rem; + } + .row .left_portion .inner .palisadoes_logo { + width: 100%; + } +} + +@media (max-width: 769px) { + .row { + flex-direction: column-reverse; + } + .row .right_portion, + .row .left_portion { + height: unset; + } + .row .right_portion { + min-height: 100vh; + overflow-y: unset; + } + .row .left_portion .inner { + display: flex; + justify-content: center; + } + .row .left_portion .inner .palisadoes_logo { + height: 70px; + width: unset; + position: absolute; + margin: 0.5rem; + top: 0; + right: 0; + z-index: 100; + } + .row .left_portion .inner p { + margin-bottom: 0; + padding: 1rem; + } + .socialIcons { + margin-bottom: 1rem; + } +} + +@media (max-width: 577px) { + .row .right_portion { + padding: 1rem 1rem 0 1rem; + } + .row .right_portion .langChangeBtn { + position: absolute; + margin: 1rem; + left: 0; + top: 0; + } + .marginTopForReg { + margin-top: 4rem !important; + } + .row .right_portion .talawa_logo { + height: 120px; + margin: 0 auto 2rem auto; + } + .socialIcons { + margin-bottom: 1rem; + } +} + +@media (prefers-reduced-motion: reduce) { + .talawa_logo { + animation: none; + } + + .active_tab { + animation: none; + } +} + +@-webkit-keyframes zoomIn { + 0% { + opacity: 0; + -webkit-transform: scale(0.5); + transform: scale(0.5); + } + 100% { + opacity: 1; + -webkit-transform: scale(1); + transform: scale(1); + } +} + +@keyframes zoomIn { + 0% { + opacity: 0; + -webkit-transform: scale(0.5); + transform: scale(0.5); + } + 100% { + opacity: 1; + -webkit-transform: scale(1); + transform: scale(1); + } +} + +@-webkit-keyframes fadeIn { + 0% { + opacity: 0; + -webkit-transform: translateY(2rem); + transform: translateY(2rem); + } + 100% { + opacity: 1; + -webkit-transform: translateY(0); + transform: translateY(0); + } +} + +@keyframes fadeIn { + 0% { + opacity: 0; + -webkit-transform: translateY(2rem); + transform: translateY(2rem); + } + 100% { + opacity: 1; + -webkit-transform: translateY(0); + transform: translateY(0); + } +} + /* For mobile devices */ @media (max-width: 520px) { @@ -1952,3 +2987,58 @@ button[data-testid='createPostBtn'] { top: 45%; } } + +@-webkit-keyframes zoomIn { + 0% { + opacity: 0; + -webkit-transform: scale(0.5); + transform: scale(0.5); + } + + 100% { + opacity: 1; + -webkit-transform: scale(1); + transform: scale(1); + } +} + +@keyframes zoomIn { + 0% { + opacity: 0; + -webkit-transform: scale(0.5); + transform: scale(0.5); + } + + 100% { + opacity: 1; + -webkit-transform: scale(1); + transform: scale(1); + } +} + +/* AddOnEntry.tsx */ + +.entrytoggle { + margin: 24px 24px 0 auto; + width: fit-content; +} + +.entryaction { + margin-left: auto; + display: flex !important; + align-items: center; + background-color: transparent; + color: #31bb6b; +} +.cardAddOnEntry { + border: 4px solid green; +} +.entryaction i { + margin-right: 8px; +} + +.entryaction .spinner-grow { + height: 1rem; + width: 1rem; + margin-right: 8px; +} diff --git a/src/utils/chartToPdf.spec.ts b/src/utils/chartToPdf.spec.ts new file mode 100644 index 0000000000..e1ab67fdb0 --- /dev/null +++ b/src/utils/chartToPdf.spec.ts @@ -0,0 +1,153 @@ +import { expect, describe, test, beforeEach, afterEach, vi } from 'vitest'; +import type { Mock } from 'vitest'; +import { + exportToCSV, + exportTrendsToCSV, + exportDemographicsToCSV, +} from './chartToPdf'; + +describe('CSV Export Functions', () => { + // Define more specific types for our mocks + let mockCreateElement: Mock; + let mockAppendChild: Mock; + let mockRemoveChild: Mock; + let mockClick: Mock; + let mockSetAttribute: Mock; + let mockLink: HTMLAnchorElement; + + beforeEach(() => { + // Mock URL methods with specific types + (global.URL.createObjectURL as unknown) = vi.fn(() => 'mock-url'); + (global.URL.revokeObjectURL as unknown) = vi.fn(); + + // Mock DOM methods + mockSetAttribute = vi.fn(); + mockClick = vi.fn(); + mockLink = { + setAttribute: mockSetAttribute, + click: mockClick, + parentNode: document.body, + } as unknown as HTMLAnchorElement; + + // Mock createElement with proper type assertion + mockCreateElement = vi.fn().mockReturnValue(mockLink); + document.createElement = + mockCreateElement as unknown as typeof document.createElement; + + // Mock appendChild and removeChild with proper type assertions + mockAppendChild = vi.fn().mockReturnValue(mockLink); + mockRemoveChild = vi.fn().mockReturnValue(mockLink); + + document.body.appendChild = + mockAppendChild as unknown as typeof document.body.appendChild; + document.body.removeChild = + mockRemoveChild as unknown as typeof document.body.removeChild; + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + + test('exports data to CSV with proper formatting', () => { + const data: string[][] = [ + ['Header1', 'Header2'], + ['Value1', 'Value2'], + ['Value with, comma', 'Value with "quotes"'], + ]; + + exportToCSV(data, 'test.csv'); + + expect(mockCreateElement).toHaveBeenCalledWith('a'); + expect(mockSetAttribute).toHaveBeenCalledWith('href', 'mock-url'); + expect(mockSetAttribute).toHaveBeenCalledWith('download', 'test.csv'); + expect(mockAppendChild).toHaveBeenCalled(); + expect(mockClick).toHaveBeenCalled(); + expect(mockRemoveChild).toHaveBeenCalled(); + expect(URL.revokeObjectURL).toHaveBeenCalledWith('mock-url'); + }); + + test('throws error if data is empty', () => { + expect(() => exportToCSV([], 'test.csv')).toThrow('Data cannot be empty'); + }); + + test('throws error if filename is empty', () => { + expect(() => exportToCSV([['data']], '')).toThrow('Filename is required'); + }); + + test('adds .csv extension if missing', () => { + const data = [['test']]; + exportToCSV(data, 'filename'); + expect(mockSetAttribute).toHaveBeenCalledWith('download', 'filename.csv'); + }); + + describe('exportTrendsToCSV', () => { + test('exports attendance trends data correctly', () => { + const eventLabels: string[] = ['Event1', 'Event2']; + const attendeeCounts: number[] = [10, 20]; + const maleCounts: number[] = [5, 10]; + const femaleCounts: number[] = [4, 8]; + const otherCounts: number[] = [1, 2]; + + exportTrendsToCSV( + eventLabels, + attendeeCounts, + maleCounts, + femaleCounts, + otherCounts, + ); + + expect(mockCreateElement).toHaveBeenCalledWith('a'); + expect(mockSetAttribute).toHaveBeenCalledWith( + 'download', + 'attendance_trends.csv', + ); + expect(mockClick).toHaveBeenCalled(); + }); + }); + + describe('exportDemographicsToCSV', () => { + test('exports demographics data correctly', () => { + const selectedCategory = 'Age Groups'; + const categoryLabels: string[] = ['0-18', '19-30', '31+']; + const categoryData: number[] = [10, 20, 15]; + + exportDemographicsToCSV(selectedCategory, categoryLabels, categoryData); + + expect(mockCreateElement).toHaveBeenCalledWith('a'); + expect(mockClick).toHaveBeenCalled(); + expect(mockSetAttribute).toHaveBeenCalledWith('href', 'mock-url'); + }); + + test('throws error if selected category is empty', () => { + expect(() => exportDemographicsToCSV('', ['label'], [1])).toThrow( + 'Selected category is required', + ); + }); + + test('throws error if labels and data arrays have different lengths', () => { + expect(() => + exportDemographicsToCSV('Category', ['label1', 'label2'], [1]), + ).toThrow('Labels and data arrays must have the same length'); + }); + + test('creates safe filename with timestamp', () => { + vi.useFakeTimers(); + const mockDate = new Date('2023-01-01T00:00:00.000Z'); + vi.setSystemTime(mockDate); + + const selectedCategory = 'Age & Demographics!'; + const categoryLabels = ['Group1']; + const categoryData = [10]; + + exportDemographicsToCSV(selectedCategory, categoryLabels, categoryData); + + const expectedFilename = + 'age___demographics__demographics_2023-01-01T00-00-00.000Z.csv'; + const downloadCalls = mockSetAttribute.mock.calls.filter( + (call) => call[0] === 'download', + ); + expect(downloadCalls[0][1]).toBe(expectedFilename); + vi.useRealTimers(); + }); + }); +}); diff --git a/src/utils/chartToPdf.test.ts b/src/utils/chartToPdf.test.ts deleted file mode 100644 index b3094fff02..0000000000 --- a/src/utils/chartToPdf.test.ts +++ /dev/null @@ -1,168 +0,0 @@ -import { - exportToCSV, - exportTrendsToCSV, - exportDemographicsToCSV, -} from './chartToPdf'; - -describe('CSV Export Functions', () => { - let mockCreateElement: jest.SpyInstance; - let mockClick: jest.SpyInstance; - let mockSetAttribute: jest.SpyInstance; - - beforeEach(() => { - // Mock URL methods - global.URL.createObjectURL = jest.fn(() => 'mock-url'); - global.URL.revokeObjectURL = jest.fn(); - - // Mock DOM methods - mockSetAttribute = jest.fn(); - mockClick = jest.fn(); - const mockLink = { - setAttribute: mockSetAttribute, - click: mockClick, - } as unknown as HTMLAnchorElement; - - mockCreateElement = jest - .spyOn(document, 'createElement') - .mockReturnValue(mockLink as HTMLAnchorElement); - }); - - afterEach(() => { - jest.clearAllMocks(); - }); - - describe('CSV Export Functions', () => { - let mockCreateElement: jest.SpyInstance; - let mockAppendChild: jest.SpyInstance; - let mockRemoveChild: jest.SpyInstance; - let mockClick: jest.SpyInstance; - let mockSetAttribute: jest.SpyInstance; - - beforeEach(() => { - // Mock URL methods - global.URL.createObjectURL = jest.fn(() => 'mock-url'); - global.URL.revokeObjectURL = jest.fn(); - - // Mock DOM methods - mockSetAttribute = jest.fn(); - mockClick = jest.fn(); - const mockLink = { - setAttribute: mockSetAttribute, - click: mockClick, - parentNode: document.body, // Add this to trigger removeChild - } as unknown as HTMLAnchorElement; - - mockCreateElement = jest - .spyOn(document, 'createElement') - .mockReturnValue(mockLink as HTMLAnchorElement); - mockAppendChild = jest - .spyOn(document.body, 'appendChild') - .mockImplementation(() => mockLink as HTMLAnchorElement); - mockRemoveChild = jest - .spyOn(document.body, 'removeChild') - .mockImplementation(() => mockLink as HTMLAnchorElement); - }); - - test('exports data to CSV with proper formatting', () => { - const data = [ - ['Header1', 'Header2'], - ['Value1', 'Value2'], - ['Value with, comma', 'Value with "quotes"'], - ]; - - exportToCSV(data, 'test.csv'); - - expect(mockCreateElement).toHaveBeenCalledWith('a'); - expect(mockSetAttribute).toHaveBeenCalledWith('href', 'mock-url'); - expect(mockSetAttribute).toHaveBeenCalledWith('download', 'test.csv'); - expect(mockAppendChild).toHaveBeenCalled(); - expect(mockClick).toHaveBeenCalled(); - expect(mockRemoveChild).toHaveBeenCalled(); - expect(URL.revokeObjectURL).toHaveBeenCalledWith('mock-url'); - }); - test('throws error if data is empty', () => { - expect(() => exportToCSV([], 'test.csv')).toThrow('Data cannot be empty'); - }); - - test('throws error if filename is empty', () => { - expect(() => exportToCSV([['data']], '')).toThrow('Filename is required'); - }); - - test('adds .csv extension if missing', () => { - const data = [['test']]; - exportToCSV(data, 'filename'); - expect(mockSetAttribute).toHaveBeenCalledWith('download', 'filename.csv'); - }); - }); - - describe('exportTrendsToCSV', () => { - test('exports attendance trends data correctly', () => { - const eventLabels = ['Event1', 'Event2']; - const attendeeCounts = [10, 20]; - const maleCounts = [5, 10]; - const femaleCounts = [4, 8]; - const otherCounts = [1, 2]; - - exportTrendsToCSV( - eventLabels, - attendeeCounts, - maleCounts, - femaleCounts, - otherCounts, - ); - - expect(mockCreateElement).toHaveBeenCalledWith('a'); - expect(mockSetAttribute).toHaveBeenCalledWith( - 'download', - 'attendance_trends.csv', - ); - expect(mockClick).toHaveBeenCalled(); - }); - }); - - describe('exportDemographicsToCSV', () => { - test('exports demographics data correctly', () => { - const selectedCategory = 'Age Groups'; - const categoryLabels = ['0-18', '19-30', '31+']; - const categoryData = [10, 20, 15]; - - exportDemographicsToCSV(selectedCategory, categoryLabels, categoryData); - - expect(mockCreateElement).toHaveBeenCalledWith('a'); - expect(mockClick).toHaveBeenCalled(); - expect(mockSetAttribute).toHaveBeenCalledWith('href', 'mock-url'); - }); - - test('throws error if selected category is empty', () => { - expect(() => exportDemographicsToCSV('', ['label'], [1])).toThrow( - 'Selected category is required', - ); - }); - - test('throws error if labels and data arrays have different lengths', () => { - expect(() => - exportDemographicsToCSV('Category', ['label1', 'label2'], [1]), - ).toThrow('Labels and data arrays must have the same length'); - }); - - test('creates safe filename with timestamp', () => { - jest.useFakeTimers(); - const mockDate = new Date('2023-01-01T00:00:00.000Z'); - jest.setSystemTime(mockDate); - - const selectedCategory = 'Age & Demographics!'; - const categoryLabels = ['Group1']; - const categoryData = [10]; - - exportDemographicsToCSV(selectedCategory, categoryLabels, categoryData); - - const expectedFilename = - 'age___demographics__demographics_2023-01-01T00-00-00.000Z.csv'; - const downloadCalls = mockSetAttribute.mock.calls.filter( - (call) => call[0] === 'download', - ); - expect(downloadCalls[0][1]).toBe(expectedFilename); - jest.useRealTimers(); - }); - }); -}); diff --git a/src/utils/errorHandler.test.tsx b/src/utils/errorHandler.spec.tsx similarity index 96% rename from src/utils/errorHandler.test.tsx rename to src/utils/errorHandler.spec.tsx index f229e8d5fa..96c35e2a7f 100644 --- a/src/utils/errorHandler.test.tsx +++ b/src/utils/errorHandler.spec.tsx @@ -2,10 +2,11 @@ type TFunction = (key: string, options?: Record) => string; import { errorHandler } from './errorHandler'; import { toast } from 'react-toastify'; +import { describe, it, expect, vi } from 'vitest'; -jest.mock('react-toastify', () => ({ +vi.mock('react-toastify', () => ({ toast: { - error: jest.fn(), + error: vi.fn(), }, })); @@ -22,7 +23,7 @@ describe('Test if errorHandler is working properly', () => { }; beforeEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); }); it('should call toast.error with the correct message if error message is "Failed to fetch"', async () => { diff --git a/src/utils/getRefreshToken.spec.ts b/src/utils/getRefreshToken.spec.ts new file mode 100644 index 0000000000..54ed1b57e8 --- /dev/null +++ b/src/utils/getRefreshToken.spec.ts @@ -0,0 +1,87 @@ +// SKIP_LOCALSTORAGE_CHECK +import { describe, it, expect, beforeEach, vi } from 'vitest'; +import { refreshToken } from './getRefreshToken'; + +const mockApolloClient = { + mutate: vi.fn(() => + Promise.resolve({ + data: { + refreshToken: { + accessToken: 'newAccessToken', + refreshToken: 'newRefreshToken', + }, + }, + }), + ), +}; + +vi.mock('@apollo/client', async () => { + const actual = await vi.importActual('@apollo/client'); + return { + ...actual, + ApolloClient: vi.fn(() => mockApolloClient), + }; +}); + +describe('refreshToken', () => { + const { location } = window; + + interface TestInterfacePartialWindow { + location?: Partial; + } + + delete (window as TestInterfacePartialWindow).location; + global.window.location = { ...location, reload: vi.fn() }; + + // Create storage mock + const localStorageMock = { + getItem: vi.fn(), + setItem: vi.fn(), + clear: vi.fn(), + removeItem: vi.fn(), + length: 0, + key: vi.fn(), + }; + + beforeEach(() => { + vi.clearAllMocks(); + Object.defineProperty(window, 'localStorage', { + value: localStorageMock, + writable: true, + }); + }); + + it('returns true when the token is refreshed successfully', async () => { + const result = await refreshToken(); + + expect(localStorage.setItem).toHaveBeenCalledWith( + 'Talawa-admin_token', + JSON.stringify('newAccessToken'), + ); + expect(localStorage.setItem).toHaveBeenCalledWith( + 'Talawa-admin_refreshToken', + JSON.stringify('newRefreshToken'), + ); + expect(result).toBe(true); + expect(window.location.reload).toHaveBeenCalled(); + }); + + it('returns false and logs error when token refresh fails', async () => { + const consoleErrorSpy = vi + .spyOn(console, 'error') + .mockImplementation(() => {}); + + const errorMock = new Error('Failed to refresh token'); + mockApolloClient.mutate.mockRejectedValueOnce(errorMock); + + const result = await refreshToken(); + + expect(result).toBe(false); + expect(consoleErrorSpy).toHaveBeenCalledWith( + 'Failed to refresh token', + errorMock, + ); + + consoleErrorSpy.mockRestore(); + }); +}); diff --git a/src/utils/getRefreshToken.test.ts b/src/utils/getRefreshToken.test.ts deleted file mode 100644 index 58de898a66..0000000000 --- a/src/utils/getRefreshToken.test.ts +++ /dev/null @@ -1,54 +0,0 @@ -// SKIP_LOCALSTORAGE_CHECK -import { refreshToken } from './getRefreshToken'; - -jest.mock('@apollo/client', () => { - const originalModule = jest.requireActual('@apollo/client'); - - return { - __esModule: true, - ...originalModule, - ApolloClient: jest.fn(() => ({ - mutate: jest.fn(() => - Promise.resolve({ - data: { - refreshToken: { - accessToken: 'newAccessToken', - refreshToken: 'newRefreshToken', - }, - }, - }), - ), - })), - }; -}); - -describe('refreshToken', () => { - // Mock window.location.reload() - const { location } = window; - delete (global.window as any).location; - global.window.location = { ...location, reload: jest.fn() }; - - // Mock localStorage.setItem() and localStorage.clear() - - Storage.prototype.setItem = jest.fn(); - Storage.prototype.clear = jest.fn(); - - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('returns true when the token is refreshed successfully', async () => { - const result = await refreshToken(); - - expect(localStorage.setItem).toHaveBeenCalledWith( - 'Talawa-admin_token', - JSON.stringify('newAccessToken'), - ); - expect(localStorage.setItem).toHaveBeenCalledWith( - 'Talawa-admin_refreshToken', - JSON.stringify('newRefreshToken'), - ); - expect(result).toBe(true); - expect(window.location.reload).toHaveBeenCalled(); - }); -});