diff --git a/__mocks__/fileMock.js b/__mocks__/fileMock.js new file mode 100644 index 00000000..86059f36 --- /dev/null +++ b/__mocks__/fileMock.js @@ -0,0 +1 @@ +module.exports = 'test-file-stub'; diff --git a/__mocks__/stylesMock.js b/__mocks__/stylesMock.js new file mode 100644 index 00000000..f053ebf7 --- /dev/null +++ b/__mocks__/stylesMock.js @@ -0,0 +1 @@ +module.exports = {}; diff --git a/jest.config.js b/jest.config.js index 27d61e0c..6d08c022 100644 --- a/jest.config.js +++ b/jest.config.js @@ -9,4 +9,9 @@ module.exports = createConfig('jest', { 'src/setupTest.js', 'src/i18n', ], + moduleNameMapper: { + '\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': + '/__mocks__/fileMock.js', + '\\.(css|less)$': '/__mocks__/styleMock.js', + }, }); diff --git a/package-lock.json b/package-lock.json index a1259b7b..510cc69f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,10 +17,12 @@ "@fortawesome/free-solid-svg-icons": "^6.4.2", "@fortawesome/react-fontawesome": "^0.2.0", "core-js": "3.31.0", + "jest-transform-css": "^6.0.1", "prop-types": "15.8.1", "react": "16.14.0", "react-dom": "16.14.0", "react-intl": "^5.25.1", + "react-paragon-topaz": "^1.0.2", "react-router": "5.2.1", "react-router-dom": "5.3.0", "regenerator-runtime": "0.13.11" @@ -3365,6 +3367,128 @@ "follow-redirects": "^1.14.0" } }, + "node_modules/@emotion/babel-plugin": { + "version": "11.11.0", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.11.0.tgz", + "integrity": "sha512-m4HEDZleaaCH+XgDDsPF15Ht6wTLsgDTeR3WYj9Q/k76JtWhrJjcP4+/XlG8LGT/Rol9qUfOIztXeA84ATpqPQ==", + "dependencies": { + "@babel/helper-module-imports": "^7.16.7", + "@babel/runtime": "^7.18.3", + "@emotion/hash": "^0.9.1", + "@emotion/memoize": "^0.8.1", + "@emotion/serialize": "^1.1.2", + "babel-plugin-macros": "^3.1.0", + "convert-source-map": "^1.5.0", + "escape-string-regexp": "^4.0.0", + "find-root": "^1.1.0", + "source-map": "^0.5.7", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/babel-plugin/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@emotion/babel-plugin/node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@emotion/cache": { + "version": "11.11.0", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.11.0.tgz", + "integrity": "sha512-P34z9ssTCBi3e9EI1ZsWpNHcfY1r09ZO0rZbRO2ob3ZQMnFI35jB536qoXbkdesr5EUhYi22anuEJuyxifaqAQ==", + "dependencies": { + "@emotion/memoize": "^0.8.1", + "@emotion/sheet": "^1.2.2", + "@emotion/utils": "^1.2.1", + "@emotion/weak-memoize": "^0.3.1", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/hash": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.1.tgz", + "integrity": "sha512-gJB6HLm5rYwSLI6PQa+X1t5CFGrv1J1TWG+sOyMCeKz2ojaj6Fnl/rZEspogG+cvqbt4AE/2eIyD2QfLKTBNlQ==" + }, + "node_modules/@emotion/memoize": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz", + "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==" + }, + "node_modules/@emotion/react": { + "version": "11.11.1", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.11.1.tgz", + "integrity": "sha512-5mlW1DquU5HaxjLkfkGN1GA/fvVGdyHURRiX/0FHl2cfIfRxSOfmxEH5YS43edp0OldZrZ+dkBKbngxcNCdZvA==", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.11.0", + "@emotion/cache": "^11.11.0", + "@emotion/serialize": "^1.1.2", + "@emotion/use-insertion-effect-with-fallbacks": "^1.0.1", + "@emotion/utils": "^1.2.1", + "@emotion/weak-memoize": "^0.3.1", + "hoist-non-react-statics": "^3.3.1" + }, + "peerDependencies": { + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/serialize": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.1.2.tgz", + "integrity": "sha512-zR6a/fkFP4EAcCMQtLOhIgpprZOwNmCldtpaISpvz348+DP4Mz8ZoKaGGCQpbzepNIUWbq4w6hNZkwDyKoS+HA==", + "dependencies": { + "@emotion/hash": "^0.9.1", + "@emotion/memoize": "^0.8.1", + "@emotion/unitless": "^0.8.1", + "@emotion/utils": "^1.2.1", + "csstype": "^3.0.2" + } + }, + "node_modules/@emotion/sheet": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.2.2.tgz", + "integrity": "sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA==" + }, + "node_modules/@emotion/unitless": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz", + "integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==" + }, + "node_modules/@emotion/use-insertion-effect-with-fallbacks": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.1.tgz", + "integrity": "sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw==", + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@emotion/utils": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.2.1.tgz", + "integrity": "sha512-Y2tGf3I+XVnajdItskUCn6LX+VUDmP6lTL4fcqsXAv43dnlbZiuW4MWQW38rW/BVWSE7Q/7+XQocmpnRYILUmg==" + }, + "node_modules/@emotion/weak-memoize": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz", + "integrity": "sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww==" + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", @@ -3469,6 +3593,28 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@floating-ui/core": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.5.0.tgz", + "integrity": "sha512-kK1h4m36DQ0UHGj5Ah4db7R0rHemTqqO0QLvUqi1/mUUp3LuAWbWxdxSIf/XsnH9VS6rRVPLJCncjRzUvyCLXg==", + "dependencies": { + "@floating-ui/utils": "^0.1.3" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.5.3.tgz", + "integrity": "sha512-ClAbQnEqJAKCJOEbbLo5IUlZHkNszqhuxS4fHAVxRPXPya6Ysf2G8KypnYcOTpx6I8xcgF9bbHb6g/2KpbV8qA==", + "dependencies": { + "@floating-ui/core": "^1.4.2", + "@floating-ui/utils": "^0.1.3" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.1.6.tgz", + "integrity": "sha512-OfX7E2oUDYxtBvsuS4e/jSn4Q9Qb6DzgeYtsAdkPZ47znpoNsMgZw0+tVijiv3uGNR6dgNlty6r9rzIzHjtd/A==" + }, "node_modules/@formatjs/ecma402-abstract": { "version": "1.11.4", "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-1.11.4.tgz", @@ -7428,6 +7574,35 @@ "node": ">= 10.14.2" } }, + "node_modules/babel-plugin-macros": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", + "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", + "dependencies": { + "@babel/runtime": "^7.12.5", + "cosmiconfig": "^7.0.0", + "resolve": "^1.19.0" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + } + }, + "node_modules/babel-plugin-macros/node_modules/cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/babel-plugin-polyfill-corejs2": { "version": "0.4.3", "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.3.tgz", @@ -8384,6 +8559,14 @@ "resolved": "https://registry.npmjs.org/common-path-prefix/-/common-path-prefix-3.0.0.tgz", "integrity": "sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==" }, + "node_modules/common-tags": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.2.tgz", + "integrity": "sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==", + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/commondir": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", @@ -10874,6 +11057,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==" + }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -11289,6 +11477,22 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/generic-names": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/generic-names/-/generic-names-4.0.0.tgz", + "integrity": "sha512-ySFolZQfw9FoDb3ed9d80Cm9f0+r7qj+HJkWjeD9RBfpxEVTlVhol+gvaQB/78WbwYfbnNh8nWHHBSlg072y6A==", + "dependencies": { + "loader-utils": "^3.2.0" + } + }, + "node_modules/generic-names/node_modules/loader-utils": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-3.2.1.tgz", + "integrity": "sha512-ZvFw1KWS3GVyYBYb7qkmRM/WwL2TQQBxgCK62rlvm4WpVQ23Nb4tYjApUlfjrEGvOs7KHEsmyUn75OHZrJMWPw==", + "engines": { + "node": ">= 12.13.0" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -11954,6 +12158,11 @@ "node": ">=0.10.0" } }, + "node_modules/icss-replace-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz", + "integrity": "sha512-chIaY3Vh2mh2Q3RGXttaDIzeiPvaVXJ+C4DAh/w3c37SKZ/U6PGMmuicR2EQQp9bKG8zLMCl7I+PtIoOOPp8Gg==" + }, "node_modules/icss-utils": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", @@ -16176,6 +16385,21 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true }, + "node_modules/jest-transform-css": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/jest-transform-css/-/jest-transform-css-6.0.1.tgz", + "integrity": "sha512-i78Pi2MW6vcdsUFSRx1kPbjbEIO0pBWwh1Y+PcDrLwTv/6e5p7fzsV/gxFW/SYMHS8DUvMdRVTwVCkA/y+t0iQ==", + "dependencies": { + "common-tags": "1.8.2", + "cross-spawn": "7.0.3", + "postcss-load-config": "4.0.1", + "postcss-modules": "4.3.1", + "style-inject": "0.3.0" + }, + "peerDependencies": { + "postcss": "^8.4.12" + } + }, "node_modules/jest-util": { "version": "26.6.2", "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-26.6.2.tgz", @@ -17193,6 +17417,11 @@ "node": ">= 4.0.0" } }, + "node_modules/memoize-one": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz", + "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==" + }, "node_modules/merge-descriptors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", @@ -18487,6 +18716,42 @@ "postcss": "^8.2.15" } }, + "node_modules/postcss-load-config": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.1.tgz", + "integrity": "sha512-vEJIc8RdiBRu3oRAI0ymerOn+7rPuMvRXslTvZUKZonDHFIczxztIyJ1urxM1x9JXEikvpWWTUUqal5j/8QgvA==", + "dependencies": { + "lilconfig": "^2.0.5", + "yaml": "^2.1.1" + }, + "engines": { + "node": ">= 14" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": ">=8.0.9", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "postcss": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/postcss-load-config/node_modules/yaml": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.4.tgz", + "integrity": "sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==", + "engines": { + "node": ">= 14" + } + }, "node_modules/postcss-loader": { "version": "7.3.3", "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-7.3.3.tgz", @@ -18630,6 +18895,24 @@ "postcss": "^8.2.15" } }, + "node_modules/postcss-modules": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/postcss-modules/-/postcss-modules-4.3.1.tgz", + "integrity": "sha512-ItUhSUxBBdNamkT3KzIZwYNNRFKmkJrofvC2nWab3CPKhYBQ1f27XXh1PAPE27Psx58jeelPsxWB/+og+KEH0Q==", + "dependencies": { + "generic-names": "^4.0.0", + "icss-replace-symbols": "^1.1.0", + "lodash.camelcase": "^4.3.0", + "postcss-modules-extract-imports": "^3.0.0", + "postcss-modules-local-by-default": "^4.0.0", + "postcss-modules-scope": "^3.0.0", + "postcss-modules-values": "^4.0.0", + "string-hash": "^1.1.1" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, "node_modules/postcss-modules-extract-imports": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz", @@ -19643,6 +19926,20 @@ "react-dom": ">=16.3.0" } }, + "node_modules/react-paragon-topaz": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/react-paragon-topaz/-/react-paragon-topaz-1.0.2.tgz", + "integrity": "sha512-JVXcEDX1wiMX2jcTeS3IPwmo3KJKTIxi4I1IApF/OHsroGlcMxul7yGhtpPI2qoHTtuxzK6MLx0/qLpc9bA3pQ==", + "dependencies": { + "@edx/paragon": "^20.45.0", + "@fortawesome/free-solid-svg-icons": "^6.4.2", + "@fortawesome/react-fontawesome": "^0.2.0", + "react": "16.14.0", + "react-dom": "16.14.0", + "react-intl": "^5.25.1", + "react-select": "^5.8.0" + } + }, "node_modules/react-popper": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/react-popper/-/react-popper-2.3.0.tgz", @@ -19803,6 +20100,26 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, + "node_modules/react-select": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/react-select/-/react-select-5.8.0.tgz", + "integrity": "sha512-TfjLDo58XrhP6VG5M/Mi56Us0Yt8X7xD6cDybC7yoRMUNm7BGO7qk8J0TLQOua/prb8vUOtsfnXZwfm30HGsAA==", + "dependencies": { + "@babel/runtime": "^7.12.0", + "@emotion/cache": "^11.4.0", + "@emotion/react": "^11.8.1", + "@floating-ui/dom": "^1.0.1", + "@types/react-transition-group": "^4.4.0", + "memoize-one": "^6.0.0", + "prop-types": "^15.6.0", + "react-transition-group": "^4.3.0", + "use-isomorphic-layout-effect": "^1.1.2" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/react-style-singleton": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.1.tgz", @@ -21706,6 +22023,11 @@ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, + "node_modules/string-hash": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/string-hash/-/string-hash-1.1.3.tgz", + "integrity": "sha512-kJUvRUFK49aub+a7T1nNE66EJbZBMnBgoC1UbCZ5n6bsZKBRga4KgBRTMn/pFkeCZSYtNeSyMxPDM0AXWELk2A==" + }, "node_modules/string-length": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", @@ -21854,6 +22176,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/style-inject": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/style-inject/-/style-inject-0.3.0.tgz", + "integrity": "sha512-IezA2qp+vcdlhJaVm5SOdPPTUu0FCEqfNSli2vRuSIBbu5Nq5UvygTk/VzeCqfLz2Atj3dVII5QBKGZRZ0edzw==" + }, "node_modules/style-loader": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.3.tgz", @@ -21884,6 +22211,11 @@ "postcss": "^8.2.15" } }, + "node_modules/stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==" + }, "node_modules/superagent": { "version": "3.8.3", "resolved": "https://registry.npmjs.org/superagent/-/superagent-3.8.3.tgz", @@ -22788,6 +23120,19 @@ } } }, + "node_modules/use-isomorphic-layout-effect": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz", + "integrity": "sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/use-sidecar": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.2.tgz", diff --git a/package.json b/package.json index a7c82eac..f08bfa80 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ "react": "16.14.0", "react-dom": "16.14.0", "react-intl": "^5.25.1", + "react-paragon-topaz": "^1.0.2", "react-router": "5.2.1", "react-router-dom": "5.3.0", "regenerator-runtime": "0.13.11" diff --git a/src/features/Instructors/AddInstructors/_test_/index.test.jsx b/src/features/Instructors/AddInstructors/_test_/index.test.jsx index 3cace2a7..823f08d9 100644 --- a/src/features/Instructors/AddInstructors/_test_/index.test.jsx +++ b/src/features/Instructors/AddInstructors/_test_/index.test.jsx @@ -41,5 +41,11 @@ describe('Add instructor component', () => { const ccxSelect = getByText('Select Class Name'); expect(instructorInfoInput).toBeInTheDocument(); expect(ccxSelect).toBeInTheDocument(); + + fireEvent.change(instructorInfoInput, { target: { value: 'Name' } }); + const submitButton = getByText('Add'); + await act(async () => { + fireEvent.click(submitButton); + }); }); }); diff --git a/src/features/Instructors/AddInstructors/index.jsx b/src/features/Instructors/AddInstructors/index.jsx index 939ed3e9..208172d6 100644 --- a/src/features/Instructors/AddInstructors/index.jsx +++ b/src/features/Instructors/AddInstructors/index.jsx @@ -2,7 +2,7 @@ import React, { useState, useReducer, useEffect } from 'react'; import { Button, FormGroup, ModalDialog, useToggle, Form, } from '@edx/paragon'; -import { getCCXList } from 'features/Instructors/data/api'; +import { getCCXList, handleInstructorsEnrollment } from 'features/Instructors/data/api'; import reducer from 'features/Instructors/AddInstructors/reducer'; import { logError } from '@edx/frontend-platform/logging'; import { camelCaseObject } from '@edx/frontend-platform'; @@ -16,16 +16,11 @@ const initialState = { error: null, }; -const addInstructorState = { - instructorInfo: '', - ccxId: '', - ccxName: '', -}; - const AddInstructors = () => { const [state, dispatch] = useReducer(reducer, initialState); const [isOpen, open, close] = useToggle(false); - const [addInstructorInfo, setAddInstructorInfo] = useState(addInstructorState); + const [isNoUser, setIsNoUser] = useState(false); + const enrollmentData = new FormData(); const fetchData = async () => { dispatch({ type: FETCH_CCX_LIST_REQUEST }); @@ -39,20 +34,26 @@ const AddInstructors = () => { } }; - const handleInstructorInput = (e) => { - setAddInstructorInfo({ - ...addInstructorInfo, - instructorInfo: e.target.value.trim(), - }); - }; - - const handleCcxSelect = (e) => { - const select = e.target; - setAddInstructorInfo({ - ...addInstructorInfo, - ccxId: select.value, - ccxName: select.options[select.selectedIndex].text, - }); + const handleAddInstructors = async (e) => { + e.preventDefault(); + const form = e.target; + const formData = new FormData(form); + const formJson = Object.fromEntries(formData.entries()); + try { + enrollmentData.append('unique_student_identifier', formJson.instructorInfo); + enrollmentData.append('rolename', 'staff'); + enrollmentData.append('action', 'allow'); + const response = await handleInstructorsEnrollment(enrollmentData, formJson.ccxId); + if (response.data?.userDoesNotExist) { + setIsNoUser(true); + } else { + fetchData(); + close(); + setIsNoUser(false); + } + } catch (error) { + logError(error); + } }; useEffect(() => { @@ -76,28 +77,37 @@ const AddInstructors = () => { - - - {state.data.map((ccx) => )} - - +
+ + + + {state.data.map((ccx) => )} + + + + + {isNoUser && ( + + User does not exist + + )} +
- +
- +
+
diff --git a/src/features/Instructors/data/_test_/api.test.js b/src/features/Instructors/data/_test_/api.test.js index 9e4f38c1..70825fdf 100644 --- a/src/features/Instructors/data/_test_/api.test.js +++ b/src/features/Instructors/data/_test_/api.test.js @@ -1,5 +1,5 @@ import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; -import { getInstructorData } from 'features/Instructors/data/api'; +import { getInstructorData, handleInstructorsEnrollment } from 'features/Instructors/data/api'; jest.mock('@edx/frontend-platform/auth', () => ({ getAuthenticatedHttpClient: jest.fn(), @@ -34,3 +34,27 @@ describe('getInstructorData', () => { ); }); }); + +describe('handleInstructorsEnrollment', () => { + test('should call getAuthenticatedHttpClient with the correct parameters', () => { + const httpClientMock = { + post: jest.fn(), + }; + + const courseId = 'course123'; + const data = new FormData(); + + getAuthenticatedHttpClient.mockReturnValue(httpClientMock); + + handleInstructorsEnrollment(data, courseId); + + expect(getAuthenticatedHttpClient).toHaveBeenCalledTimes(2); + expect(getAuthenticatedHttpClient).toHaveBeenCalledWith(); + + expect(httpClientMock.post).toHaveBeenCalledTimes(1); + expect(httpClientMock.post).toHaveBeenCalledWith( + 'http://localhost:18000/courses/course123/instructor/api/modify_access', + data, + ); + }); +}); diff --git a/src/features/Instructors/data/api.js b/src/features/Instructors/data/api.js index c87e5bb0..e90ae436 100644 --- a/src/features/Instructors/data/api.js +++ b/src/features/Instructors/data/api.js @@ -17,11 +17,22 @@ function getInstructorData(page, filters) { function getCCXList() { const apiV2BaseUrl = getConfig().COURSE_OPERATIONS_API_V2_BASE_URL; return getAuthenticatedHttpClient().get( - `${apiV2BaseUrl}/classes?limit=false`, + `${apiV2BaseUrl}/classes/?limit=false`, + ); +} + +function handleInstructorsEnrollment(data, courseId) { + const INSTRUCTOR_API_URL = `${getConfig().LMS_BASE_URL}/courses/course_id/instructor/api`; + const courseIdSearchPattern = /course_id/; + + return getAuthenticatedHttpClient().post( + `${INSTRUCTOR_API_URL.replace(courseIdSearchPattern, courseId)}/modify_access`, + data, ); } export { getInstructorData, getCCXList, + handleInstructorsEnrollment, }; diff --git a/src/features/Students/StudentsPage/_test_/index.test.jsx b/src/features/Students/StudentsPage/_test_/index.test.jsx index 08c28e90..dbf74317 100644 --- a/src/features/Students/StudentsPage/_test_/index.test.jsx +++ b/src/features/Students/StudentsPage/_test_/index.test.jsx @@ -3,8 +3,6 @@ import axios from 'axios'; import StudentsPage from 'features/Students/StudentsPage'; import { render, - screen, - fireEvent, waitFor, } from '@testing-library/react'; import '@testing-library/jest-dom/extend-expect'; @@ -21,16 +19,30 @@ const mockResponse = { { learnerName: 'Student 1', learnerEmail: 'student1@example.com', - ccxName: 'CCX 1', + courseId: '1', + courseName: 'course 1', + classId: '1', + className: 'class 1', instructors: ['Instructor 1'], created: 'Fri, 25 Aug 2023 19:01:22 GMT', + firstAccess: 'Fri, 25 Aug 2023 19:01:23 GMT', + lastAccess: 'Fri, 25 Aug 2023 20:20:22 GMT', + status: 'active', + examReady: 'yes', }, { learnerName: 'Student 2', learnerEmail: 'student2@example.com', - ccxName: 'CCX 2', + courseId: '2', + courseName: 'course 2', + classId: '2', + className: 'class 2', instructors: ['Instructor 2'], created: 'Sat, 26 Aug 2023 19:01:22 GMT', + firstAccess: 'Sat, 26 Aug 2023 19:01:24 GMT', + lastAccess: 'Sat, 26 Aug 2023 21:22:22 GMT', + status: 'pending', + examReady: 'no', }, ], count: 2, @@ -48,34 +60,14 @@ describe('StudentsPage', () => { waitFor(() => { expect(component.container).toHaveTextContent('Student 1'); expect(component.container).toHaveTextContent('Student 2'); - expect(component.container).toHaveTextContent('student1@example.com'); - expect(component.container).toHaveTextContent('student2@example.com'); - expect(component.container).toHaveTextContent('CCX 1'); - expect(component.container).toHaveTextContent('CCX 2'); + expect(component.container).toHaveTextContent('course 1'); + expect(component.container).toHaveTextContent('course 2'); + expect(component.container).toHaveTextContent('class 1'); + expect(component.container).toHaveTextContent('class 2'); expect(component.container).toHaveTextContent('Instructor 1'); expect(component.container).toHaveTextContent('Instructor 2'); - expect(component.container).toHaveTextContent('Fri, 25 Aug 2023 19:01:22 GMT'); - expect(component.container).toHaveTextContent('Sat, 26 Aug 2023 19:01:22 GMT'); - }); - }); - - it('filters students data', async () => { - axios.get.mockResolvedValue(mockResponse); - - const component = render(); - - fireEvent.click(screen.getByRole('button', { name: /Filters/i })); - - const nameInput = screen.getByPlaceholderText('Enter Student Name'); - const emailInput = screen.getByPlaceholderText('Enter Student Email'); - fireEvent.change(nameInput, { target: { value: 'Student 1' } }); - fireEvent.change(emailInput, { target: { value: 'student1@example.com' } }); - - fireEvent.click(screen.getByText('Apply Filters')); - - waitFor(() => { - expect(component.container).toHaveTextContent('Student 1'); - expect(screen.queryByText('Student 2')).toBeNull(); + expect(component.container).toHaveTextContent('active'); + expect(component.container).toHaveTextContent('pending'); }); }); }); diff --git a/src/features/Students/StudentsPage/_test_/reducer.test.jsx b/src/features/Students/StudentsPage/_test_/reducer.test.jsx new file mode 100644 index 00000000..dc2e73ea --- /dev/null +++ b/src/features/Students/StudentsPage/_test_/reducer.test.jsx @@ -0,0 +1,99 @@ +import { + FETCH_STUDENTS_DATA_REQUEST, + FETCH_STUDENTS_DATA_SUCCESS, + FETCH_STUDENTS_DATA_FAILURE, + UPDATE_CURRENT_PAGE, + OPEN_MODAL, + CLOSE_MODAL, +} from 'features/Students/actionTypes'; +import { RequestStatus } from 'features/constants'; +import reducer from 'features/Instructors/InstructorsPage/reducer'; + +describe('Instructor page reducers', () => { + const initialState = { + data: [], + error: null, + currentPage: 1, + numPages: 0, + }; + + test('should handle FETCH_STUDENTS_DATA_REQUEST', () => { + const state = { + ...initialState, + status: RequestStatus.LOADING, + }; + const action = { + type: FETCH_STUDENTS_DATA_REQUEST, + }; + expect(reducer(state, action)).toEqual(state); + }); + + test('should handle FETCH_STUDENTS_DATA_SUCCESS', () => { + const state = { + ...initialState, + status: RequestStatus.SUCCESS, + count: 0, + }; + const action = { + type: FETCH_STUDENTS_DATA_SUCCESS, + payload: { + results: [], + count: 0, + numPages: 0, + }, + }; + expect(reducer(state, action)).toEqual(state); + }); + + test('should handle FETCH_STUDENTS_DATA_FAILURE', () => { + const state = { + ...initialState, + status: RequestStatus.ERROR, + error: '', + }; + const action = { + type: FETCH_STUDENTS_DATA_FAILURE, + payload: '', + }; + expect(reducer(state, action)).toEqual(state); + }); + + test('should handle UPDATE_CURRENT_PAGE', () => { + const state = { + ...initialState, + currentPage: 1, + }; + const action = { + type: UPDATE_CURRENT_PAGE, + payload: 1, + }; + expect(reducer(state, action)).toEqual(state); + }); + + test('should handle OPEN_MODAL', () => { + const state = { + ...initialState, + filters: { + isOpenFilters: true, + }, + }; + const action = { + type: OPEN_MODAL, + }; + expect(reducer(state, action)).toEqual(state); + }); + + test('should handle CLOSE_MODAL', () => { + const state = { + ...initialState, + filters: { + isOpenFilters: false, + errors: {}, + }, + }; + const action = { + type: CLOSE_MODAL, + }; + expect(reducer(state, action)).toEqual(state); + }); +}); diff --git a/src/features/Students/StudentsPage/index.jsx b/src/features/Students/StudentsPage/index.jsx index 685ff51e..9fd289ce 100644 --- a/src/features/Students/StudentsPage/index.jsx +++ b/src/features/Students/StudentsPage/index.jsx @@ -2,6 +2,7 @@ import { getStudentbyInstitutionAdmin } from 'features/Students/data/api'; import { StudentsTable } from 'features/Students/StudentsTable/index'; import { StudentsFilters } from 'features/Students/StudentsFilters'; import { RequestStatus } from 'features/constants'; +import reducer from 'features/Students/StudentsPage/reducer'; import { logError } from '@edx/frontend-platform/logging'; import Container from '@edx/paragon/dist/Container'; @@ -20,6 +21,14 @@ import { Pagination, Modal, } from '@edx/paragon'; +import { + FETCH_STUDENTS_DATA_REQUEST, + FETCH_STUDENTS_DATA_SUCCESS, + FETCH_STUDENTS_DATA_FAILURE, + UPDATE_CURRENT_PAGE, + OPEN_MODAL, + CLOSE_MODAL, +} from 'features/Students/actionTypes'; const initialFilterFormValues = { learnerName: '', @@ -40,66 +49,19 @@ const initialState = { }, }; -const reducer = (state, action) => { - switch (action.type) { - case 'FETCH_REQUEST': - return { ...state, status: RequestStatus.LOADING }; - case 'FETCH_SUCCESS': { - const { results, count, numPages } = action.payload; - return { - ...state, - status: RequestStatus.SUCCESS, - data: results, - numPages, - count, - }; - } - case 'FETCH_FAILURE': - return { - ...state, - status: RequestStatus.ERROR, - error: action.payload, - }; - case 'UPDATE_CURRENT_PAGE': - return { - ...state, - currentPage: action.payload, - }; - case 'OPEN_MODAL': - return { - ...state, - filters: { - ...state.filters, - isOpenFilters: true, - }, - }; - case 'CLOSE_MODAL': - return { - ...state, - filters: { - ...state.filters, - isOpenFilters: false, - errors: {}, - }, - }; - default: - return state; - } -}; - const StudentsPage = () => { const [state, dispatch] = useReducer(reducer, initialState); const [currentPage, setCurrentPage] = useState(1); const [filters, setFilters] = useState(initialFilterFormValues); const fetchData = async () => { - dispatch({ type: 'FETCH_REQUEST' }); + dispatch({ type: FETCH_STUDENTS_DATA_REQUEST }); try { const response = camelCaseObject(await getStudentbyInstitutionAdmin(currentPage, filters)); - dispatch({ type: 'FETCH_SUCCESS', payload: response.data }); + dispatch({ type: FETCH_STUDENTS_DATA_SUCCESS, payload: response.data }); } catch (error) { - dispatch({ type: 'FETCH_FAILURE', payload: error }); + dispatch({ type: FETCH_STUDENTS_DATA_FAILURE, payload: error }); logError(error); } }; @@ -109,11 +71,11 @@ const StudentsPage = () => { }, [currentPage, filters]); const handleOpenFiltersModal = () => { - dispatch({ type: 'OPEN_MODAL' }); + dispatch({ type: OPEN_MODAL }); }; const handleCloseFiltersModal = () => { - dispatch({ type: 'CLOSE_MODAL' }); + dispatch({ type: CLOSE_MODAL }); }; const handleApplyFilters = async () => { @@ -129,7 +91,7 @@ const StudentsPage = () => { const handlePagination = (targetPage) => { setCurrentPage(targetPage); - dispatch({ type: 'UPDATE_CURRENT_PAGE', payload: targetPage }); + dispatch({ type: UPDATE_CURRENT_PAGE, payload: targetPage }); fetchData(); }; diff --git a/src/features/Students/StudentsPage/reducer.jsx b/src/features/Students/StudentsPage/reducer.jsx new file mode 100644 index 00000000..1c6b20c5 --- /dev/null +++ b/src/features/Students/StudentsPage/reducer.jsx @@ -0,0 +1,58 @@ +import { + FETCH_STUDENTS_DATA_REQUEST, + FETCH_STUDENTS_DATA_SUCCESS, + FETCH_STUDENTS_DATA_FAILURE, + UPDATE_CURRENT_PAGE, + OPEN_MODAL, + CLOSE_MODAL, +} from 'features/Students/actionTypes'; +import { RequestStatus } from 'features/constants'; + +const reducer = (state, action) => { + switch (action.type) { + case FETCH_STUDENTS_DATA_REQUEST: + return { ...state, status: RequestStatus.LOADING }; + case FETCH_STUDENTS_DATA_SUCCESS: { + const { results, count, numPages } = action.payload; + return { + ...state, + status: RequestStatus.SUCCESS, + data: results, + numPages, + count, + }; + } + case FETCH_STUDENTS_DATA_FAILURE: + return { + ...state, + status: RequestStatus.ERROR, + error: action.payload, + }; + case UPDATE_CURRENT_PAGE: + return { + ...state, + currentPage: action.payload, + }; + case OPEN_MODAL: + return { + ...state, + filters: { + ...state.filters, + isOpenFilters: true, + }, + }; + case CLOSE_MODAL: + return { + ...state, + filters: { + ...state.filters, + isOpenFilters: false, + errors: {}, + }, + }; + default: + return state; + } +}; + +export default reducer; diff --git a/src/features/Students/StudentsTable/_test_/columns.test.jsx b/src/features/Students/StudentsTable/_test_/columns.test.jsx index 39d0434f..8855e4be 100644 --- a/src/features/Students/StudentsTable/_test_/columns.test.jsx +++ b/src/features/Students/StudentsTable/_test_/columns.test.jsx @@ -5,49 +5,41 @@ describe('getColumns', () => { const columns = getColumns(); expect(columns).toBeInstanceOf(Array); - expect(columns).toHaveLength(10); + expect(columns).toHaveLength(8); const [ nameColumn, - emailColumn, courseNameColumn, + courseIdColumn, + classNameColumn, ClassIdColumn, instructorsColumn, - createdColumn, - firstAccessColumn, - lastAccessColumn, - gradeColumn, - ActionColumn, + statusColumn, + examReadyColumn, ] = columns; - expect(nameColumn).toHaveProperty('Header', 'Name'); + expect(nameColumn).toHaveProperty('Header', 'Student'); expect(nameColumn).toHaveProperty('accessor', 'learnerName'); - expect(emailColumn).toHaveProperty('Header', 'Email'); - expect(emailColumn).toHaveProperty('accessor', 'learnerEmail'); + expect(courseNameColumn).toHaveProperty('Header', 'Course'); + expect(courseNameColumn).toHaveProperty('accessor', 'courseName'); - expect(courseNameColumn).toHaveProperty('Header', 'Class Name'); - expect(courseNameColumn).toHaveProperty('accessor', 'ccxName'); + expect(courseIdColumn).toHaveProperty('Header', 'Course Id'); + expect(courseIdColumn).toHaveProperty('accessor', 'courseId'); + + expect(classNameColumn).toHaveProperty('Header', 'Class Name'); + expect(classNameColumn).toHaveProperty('accessor', 'className'); expect(ClassIdColumn).toHaveProperty('Header', 'Class Id'); - expect(ClassIdColumn).toHaveProperty('accessor', 'ccxId'); + expect(ClassIdColumn).toHaveProperty('accessor', 'classId'); - expect(instructorsColumn).toHaveProperty('Header', 'Instructors'); + expect(instructorsColumn).toHaveProperty('Header', 'Instructor'); expect(instructorsColumn).toHaveProperty('accessor', 'instructors'); - expect(createdColumn).toHaveProperty('Header', 'Created'); - expect(createdColumn).toHaveProperty('accessor', 'created'); - - expect(firstAccessColumn).toHaveProperty('Header', 'First Access'); - expect(firstAccessColumn).toHaveProperty('accessor', 'firstAccess'); - - expect(lastAccessColumn).toHaveProperty('Header', 'Last Access'); - expect(lastAccessColumn).toHaveProperty('accessor', 'lastAccess'); - - expect(gradeColumn).toHaveProperty('Header', 'Grade'); - expect(gradeColumn).toHaveProperty('accessor', 'grade'); + expect(statusColumn).toHaveProperty('Header', 'Status'); + expect(statusColumn).toHaveProperty('accessor', 'status'); - expect(ActionColumn).toHaveProperty('Header', 'Action'); - expect(ActionColumn).toHaveProperty('accessor', 'status'); + expect(examReadyColumn).toHaveProperty('Header', 'Exam Ready'); + expect(examReadyColumn).toHaveProperty('accessor', 'examReady'); }); }); diff --git a/src/features/Students/StudentsTable/_test_/index.test.jsx b/src/features/Students/StudentsTable/_test_/index.test.jsx index 66b89e29..c98d9065 100644 --- a/src/features/Students/StudentsTable/_test_/index.test.jsx +++ b/src/features/Students/StudentsTable/_test_/index.test.jsx @@ -16,22 +16,30 @@ describe('Student Table', () => { { learnerName: 'Student 1', learnerEmail: 'student1@example.com', - ccxName: 'CCX 1', + courseId: '1', + courseName: 'course 1', + classId: '1', + className: 'class 1', instructors: ['Instructor 1'], created: 'Fri, 25 Aug 2023 19:01:22 GMT', firstAccess: 'Fri, 25 Aug 2023 19:01:23 GMT', lastAccess: 'Fri, 25 Aug 2023 20:20:22 GMT', - grade: true, + status: 'active', + examReady: 'yes', }, { learnerName: 'Student 2', learnerEmail: 'student2@example.com', - ccxName: 'CCX 2', + courseId: '2', + courseName: 'course 2', + classId: '2', + className: 'class 2', instructors: ['Instructor 2'], created: 'Sat, 26 Aug 2023 19:01:22 GMT', firstAccess: 'Sat, 26 Aug 2023 19:01:24 GMT', lastAccess: 'Sat, 26 Aug 2023 21:22:22 GMT', - grade: false, + status: 'pending', + examReady: 'no', }, ]; @@ -47,33 +55,25 @@ describe('Student Table', () => { expect(component.container).toHaveTextContent('Student 1'); expect(component.container).toHaveTextContent('Student 2'); - // Check student emails - expect(component.container).toHaveTextContent('student1@example.com'); - expect(component.container).toHaveTextContent('student2@example.com'); + // Check course names + expect(component.container).toHaveTextContent('course 1'); + expect(component.container).toHaveTextContent('course 2'); - // Check ccx names - expect(component.container).toHaveTextContent('CCX 1'); - expect(component.container).toHaveTextContent('CCX 2'); + // Check class names + expect(component.container).toHaveTextContent('class 1'); + expect(component.container).toHaveTextContent('class 2'); // Check instructors names expect(component.container).toHaveTextContent('Instructor 1'); expect(component.container).toHaveTextContent('Instructor 2'); - // Check datetime for created column is formatted - expect(component.container).toHaveTextContent('Fri, 25 Aug 2023 19:01:22 GMT'); - expect(component.container).toHaveTextContent('Sat, 26 Aug 2023 19:01:22 GMT'); + // Check status + expect(component.container).toHaveTextContent('Active'); + expect(component.container).toHaveTextContent('Pending'); - // Check datetime for first access column is formatted - expect(component.container).toHaveTextContent('Fri, 25 Aug 2023 19:01:23 GMT'); - expect(component.container).toHaveTextContent('Sat, 26 Aug 2023 19:01:24 GMT'); - - // Check datetime for last access column is formatted - expect(component.container).toHaveTextContent('Fri, 25 Aug 2023 20:20:22 GMT'); - expect(component.container).toHaveTextContent('Sat, 26 Aug 2023 21:22:22 GMT'); - - // Check grade - expect(component.container).toHaveTextContent('pass'); - expect(component.container).toHaveTextContent('fail'); + // Check exam ready + expect(component.container).toHaveTextContent('yes'); + expect(component.container).toHaveTextContent('no'); // Ensure "No students found." is not present expect(screen.queryByText('No students found.')).toBeNull(); diff --git a/src/features/Students/StudentsTable/columns.jsx b/src/features/Students/StudentsTable/columns.jsx index 940d974d..15845f06 100644 --- a/src/features/Students/StudentsTable/columns.jsx +++ b/src/features/Students/StudentsTable/columns.jsx @@ -1,27 +1,30 @@ /* eslint-disable react/prop-types */ import React from 'react'; -import { Badge, Button } from '@edx/paragon'; +import { Badge } from 'react-paragon-topaz'; -const getColumns = props => [ +const getColumns = () => [ { - Header: 'Name', + Header: 'Student', accessor: 'learnerName', }, { - Header: 'Email', - accessor: 'learnerEmail', + Header: 'Course', + accessor: 'courseName', + }, + { + Header: 'Course Id', + accessor: 'courseId', }, { Header: 'Class Name', - accessor: 'ccxName', + accessor: 'className', }, { Header: 'Class Id', - accessor: 'ccxId', - disableSortBy: true, + accessor: 'classId', }, { - Header: 'Instructors', + Header: 'Instructor', accessor: 'instructors', Cell: ({ row }) => (
    @@ -30,54 +33,30 @@ const getColumns = props => [ ), }, { - Header: 'Created', - accessor: 'created', - Cell: ({ row }) => ( - row.values.created - ? new Date(row.values.created).toUTCString() - : '' - ), - }, - { - Header: 'First Access', - accessor: 'firstAccess', - Cell: ({ row }) => ( - row.values.firstAccess - ? new Date(row.values.firstAccess).toUTCString() - : '' - ), - }, - { - Header: 'Last Access', - accessor: 'lastAccess', - Cell: ({ row }) => ( - row.values.lastAccess - ? new Date(row.values.lastAccess).toUTCString() - : '' - ), - }, - { - Header: 'Grade', - accessor: 'grade', - Cell: ({ row }) => {row.values.grade ? 'pass' : 'fail'}, - }, - { - Header: 'Action', + Header: 'Status', accessor: 'status', - disableSortBy: true, Cell: ({ row }) => { - const value = row.values; - - if (value.status !== 'Pending') { - return null; + switch (row.values.status) { + case 'active': + return Active; + case 'inactive': + return Inactive; + case 'expired': + return Expired; + case 'pending': + return Pending; + default: + return null; } - - return ; }, }, + { + Header: 'Exam Ready', + accessor: 'examReady', + }, ]; // We don't need to show ccxId column but we need it to use handleStudentsActions. -const hideColumns = { hiddenColumns: ['ccxId'] }; +const hideColumns = { hiddenColumns: ['classId', 'courseId'] }; export { hideColumns, getColumns }; diff --git a/src/features/Students/actionTypes.js b/src/features/Students/actionTypes.js new file mode 100644 index 00000000..9a281a7e --- /dev/null +++ b/src/features/Students/actionTypes.js @@ -0,0 +1,7 @@ +export const FETCH_STUDENTS_DATA_REQUEST = 'FETCH_STUDENTS_DATA_REQUEST'; +export const FETCH_STUDENTS_DATA_SUCCESS = 'FETCH_STUDENTS_DATA_SUCCESS'; +export const FETCH_STUDENTS_DATA_FAILURE = 'FETCH_STUDENTS_DATA_FAILURE'; +export const UPDATE_CURRENT_PAGE = 'UPDATE_CURRENT_PAGE'; + +export const OPEN_MODAL = 'OPEN_MODAL'; +export const CLOSE_MODAL = 'CLOSE_MODAL'; diff --git a/src/index.scss b/src/index.scss index 0bb1f9ee..d39ebb78 100644 --- a/src/index.scss +++ b/src/index.scss @@ -1,4 +1,6 @@ +@import "react-paragon-topaz/src/variables.scss"; @import "@edx/brand/paragon/fonts.scss"; @import "@edx/brand/paragon/variables.scss"; @import "@edx/paragon/scss/core/core.scss"; @import "@edx/brand/paragon/overrides.scss"; +@import "react-paragon-topaz/src/DataTable/DataTable.scss"; \ No newline at end of file