From e8ceffab3f285dc5a6b26b5eaeb794ba27b55022 Mon Sep 17 00:00:00 2001 From: Lael Birch Date: Thu, 11 Mar 2021 09:57:01 -0500 Subject: [PATCH] Adds an enroll column to bulk enroll table (#497) * Add enroll column Moves custom table cells to their own file * Replace loader with a skeleton Skeleton approximates the table * Update enroll button text To be in line with the mock * Use config variable in test --- package-lock.json | 206 +++++++++++++++--- package.json | 1 + .../BulkEnrollmentPage/BulkEnrollment.scss | 3 + .../CourseSearchResults.jsx | 114 ++++++---- .../CourseSearchResults.test.jsx | 60 ++--- .../CourseSearchResultsCells.jsx | 26 +++ .../CourseSearchResultsCells.test.jsx | 31 +++ 7 files changed, 335 insertions(+), 106 deletions(-) create mode 100644 src/components/BulkEnrollmentPage/CourseSearchResultsCells.jsx create mode 100644 src/components/BulkEnrollmentPage/CourseSearchResultsCells.test.jsx diff --git a/package-lock.json b/package-lock.json index eb1df35330..bb06731b03 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1870,6 +1870,94 @@ "uncontrollable": "7.2.1" } }, + "@emotion/cache": { + "version": "10.0.29", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-10.0.29.tgz", + "integrity": "sha512-fU2VtSVlHiF27empSbxi1O2JFdNWZO+2NFHfwO0pxgTep6Xa3uGb+3pVKfLww2l/IBGLNEZl5Xf/++A4wAYDYQ==", + "requires": { + "@emotion/sheet": "0.9.4", + "@emotion/stylis": "0.8.5", + "@emotion/utils": "0.11.3", + "@emotion/weak-memoize": "0.2.5" + } + }, + "@emotion/core": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/@emotion/core/-/core-10.1.1.tgz", + "integrity": "sha512-ZMLG6qpXR8x031NXD8HJqugy/AZSkAuMxxqB46pmAR7ze47MhNJ56cdoX243QPZdGctrdfo+s08yZTiwaUcRKA==", + "requires": { + "@babel/runtime": "^7.5.5", + "@emotion/cache": "^10.0.27", + "@emotion/css": "^10.0.27", + "@emotion/serialize": "^0.11.15", + "@emotion/sheet": "0.9.4", + "@emotion/utils": "0.11.3" + } + }, + "@emotion/css": { + "version": "10.0.27", + "resolved": "https://registry.npmjs.org/@emotion/css/-/css-10.0.27.tgz", + "integrity": "sha512-6wZjsvYeBhyZQYNrGoR5yPMYbMBNEnanDrqmsqS1mzDm1cOTu12shvl2j4QHNS36UaTE0USIJawCH9C8oW34Zw==", + "requires": { + "@emotion/serialize": "^0.11.15", + "@emotion/utils": "0.11.3", + "babel-plugin-emotion": "^10.0.27" + } + }, + "@emotion/hash": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz", + "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==" + }, + "@emotion/memoize": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz", + "integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==" + }, + "@emotion/serialize": { + "version": "0.11.16", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-0.11.16.tgz", + "integrity": "sha512-G3J4o8by0VRrO+PFeSc3js2myYNOXVJ3Ya+RGVxnshRYgsvErfAOglKAiy1Eo1vhzxqtUvjCyS5gtewzkmvSSg==", + "requires": { + "@emotion/hash": "0.8.0", + "@emotion/memoize": "0.7.4", + "@emotion/unitless": "0.7.5", + "@emotion/utils": "0.11.3", + "csstype": "^2.5.7" + }, + "dependencies": { + "csstype": { + "version": "2.6.16", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.16.tgz", + "integrity": "sha512-61FBWoDHp/gRtsoDkq/B1nWrCUG/ok1E3tUrcNbZjsE9Cxd9yzUirjS3+nAATB8U4cTtaQmAHbNndoFz5L6C9Q==" + } + } + }, + "@emotion/sheet": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-0.9.4.tgz", + "integrity": "sha512-zM9PFmgVSqBw4zL101Q0HrBVTGmpAxFZH/pYx/cjJT5advXguvcgjHFTCaIO3enL/xr89vK2bh0Mfyj9aa0ANA==" + }, + "@emotion/stylis": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.8.5.tgz", + "integrity": "sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ==" + }, + "@emotion/unitless": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz", + "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==" + }, + "@emotion/utils": { + "version": "0.11.3", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-0.11.3.tgz", + "integrity": "sha512-0o4l6pZC+hI88+bzuaX/6BgOvQVhbt2PfmxauVaYOGgbsAw14wdKyvMCZXnsnsHys94iadcF+RG/wZyx6+ZZBw==" + }, + "@emotion/weak-memoize": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz", + "integrity": "sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA==" + }, "@formatjs/ecma402-abstract": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-1.5.0.tgz", @@ -2624,9 +2712,9 @@ } }, "@popperjs/core": { - "version": "2.8.6", - "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.8.6.tgz", - "integrity": "sha512-1oXH2bAFXz9SttE1v/0Jp+2ZVePsPEAPGIuPKrmljWZcS3FPBEn2Q4WcANozZC0YiCjTWOF55k0g6rbSZS39ew==" + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.9.1.tgz", + "integrity": "sha512-DvJbbn3dUgMxDnJLH+RZQPnXak1h4ZVYQ7CWiFWjQwBFkVajT4rfw2PdpHLTSTwxrYfnoEXkuBiwkDm6tPMQeA==" }, "@redux-beacon/segment": { "version": "1.1.0", @@ -3242,8 +3330,7 @@ "@types/parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", - "dev": true + "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==" }, "@types/prettier": { "version": "2.2.1", @@ -4555,6 +4642,30 @@ "object.assign": "^4.1.0" } }, + "babel-plugin-emotion": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/babel-plugin-emotion/-/babel-plugin-emotion-10.2.2.tgz", + "integrity": "sha512-SMSkGoqTbTyUTDeuVuPIWifPdUGkTk1Kf9BWRiXIOIcuyMfsdp2EjeiiFvOzX8NOBvEh/ypKYvUh2rkgAJMCLA==", + "requires": { + "@babel/helper-module-imports": "^7.0.0", + "@emotion/hash": "0.8.0", + "@emotion/memoize": "0.7.4", + "@emotion/serialize": "^0.11.16", + "babel-plugin-macros": "^2.0.0", + "babel-plugin-syntax-jsx": "^6.18.0", + "convert-source-map": "^1.5.0", + "escape-string-regexp": "^1.0.5", + "find-root": "^1.1.0", + "source-map": "^0.5.7" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" + } + } + }, "babel-plugin-istanbul": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.0.0.tgz", @@ -4580,6 +4691,30 @@ "@types/babel__traverse": "^7.0.6" } }, + "babel-plugin-macros": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-2.8.0.tgz", + "integrity": "sha512-SEP5kJpfGYqYKpBrj5XU3ahw5p5GOHJ0U5ssOSQ/WBVdwkD2Dzlce95exQTs3jOVWPPKLBN2rlEWkCK7dSmLvg==", + "requires": { + "@babel/runtime": "^7.7.2", + "cosmiconfig": "^6.0.0", + "resolve": "^1.12.0" + }, + "dependencies": { + "cosmiconfig": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz", + "integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==", + "requires": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.1.0", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.7.2" + } + } + } + }, "babel-plugin-react-intl": { "version": "7.9.2", "resolved": "https://registry.npmjs.org/babel-plugin-react-intl/-/babel-plugin-react-intl-7.9.2.tgz", @@ -4609,6 +4744,11 @@ } } }, + "babel-plugin-syntax-jsx": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz", + "integrity": "sha1-CvMqmm4Tyno/1QaeYtew9Y0NiUY=" + }, "babel-plugin-transform-imports": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/babel-plugin-transform-imports/-/babel-plugin-transform-imports-2.0.0.tgz", @@ -5552,8 +5692,7 @@ "callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==" }, "camel-case": { "version": "4.1.2", @@ -6373,7 +6512,6 @@ "version": "1.7.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", - "dev": true, "requires": { "safe-buffer": "~5.1.1" }, @@ -6381,8 +6519,7 @@ "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" } } }, @@ -7827,7 +7964,6 @@ "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, "requires": { "is-arrayish": "^0.2.1" } @@ -8979,6 +9115,11 @@ "pkg-dir": "^3.0.0" } }, + "find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==" + }, "find-up": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", @@ -10698,7 +10839,6 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, "requires": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" @@ -11010,8 +11150,7 @@ "is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", - "dev": true + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" }, "is-binary-path": { "version": "1.0.1", @@ -12985,8 +13124,7 @@ "json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" }, "json-schema": { "version": "0.2.3", @@ -13141,8 +13279,7 @@ "lines-and-columns": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", - "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=", - "dev": true + "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=" }, "load-json-file": { "version": "2.0.0", @@ -15066,7 +15203,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, "requires": { "callsites": "^3.0.0" } @@ -15088,7 +15224,6 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dev": true, "requires": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", @@ -15210,8 +15345,7 @@ "path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==" }, "pbkdf2": { "version": "3.1.1", @@ -16443,9 +16577,9 @@ } }, "react-bootstrap": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-1.5.0.tgz", - "integrity": "sha512-t1o4kP3coj2HIaBepJxGAc7HZ1fWGria/s0Yr9JUmNkCilyrnXtK209qn28vuZ4muhnA0uR0GMMXAMrLsLyiIw==", + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-1.5.1.tgz", + "integrity": "sha512-jbJNGx9n4JvKgxlvT8DLKSeF3VcqnPJXS9LFdzoZusiZCCGoYecZ9qSCBH5n2A+kjmuura9JkvxI9l7HD+bIdQ==", "requires": { "@babel/runtime": "^7.4.2", "@restart/context": "^2.1.4", @@ -16461,7 +16595,7 @@ "invariant": "^2.2.4", "prop-types": "^15.7.2", "prop-types-extra": "^1.1.0", - "react-overlays": "^4.1.1", + "react-overlays": "^5.0.0", "react-transition-group": "^4.4.1", "uncontrollable": "^7.0.0", "warning": "^4.0.3" @@ -16991,10 +17125,18 @@ "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" }, + "react-loading-skeleton": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/react-loading-skeleton/-/react-loading-skeleton-2.2.0.tgz", + "integrity": "sha512-HH37uj9aobrUJSqFglHqO9KQt5zGQe+Svutv8LIq7Iq6gpJqCwIzJOsEVfkQy7ReirbI2uLhCtKloBGQIDP0BQ==", + "requires": { + "@emotion/core": "^10.0.22" + } + }, "react-overlays": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/react-overlays/-/react-overlays-4.1.1.tgz", - "integrity": "sha512-WtJifh081e6M24KnvTQoNjQEpz7HoLxqt8TwZM7LOYIkYJ8i/Ly1Xi7RVte87ZVnmqQ4PFaFiNHZhSINPSpdBQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/react-overlays/-/react-overlays-5.0.0.tgz", + "integrity": "sha512-TKbqfAv23TFtCJ2lzISdx76p97G/DP8Rp4TOFdqM9n8GTruVYgE3jX7Zgb8+w7YJ18slTVcDTQ1/tFzdCqjVhA==", "requires": { "@babel/runtime": "^7.12.1", "@popperjs/core": "^2.5.3", @@ -17880,8 +18022,7 @@ "resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==" }, "resolve-pathname": { "version": "3.0.0", @@ -22065,8 +22206,7 @@ "yaml": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.0.tgz", - "integrity": "sha512-yr2icI4glYaNG+KWONODapy2/jDdMSDnrONSjblABjD9B4Z5LgiircSt8m8sRZFNi08kG9Sm0uSHtEmP3zaEGg==", - "dev": true + "integrity": "sha512-yr2icI4glYaNG+KWONODapy2/jDdMSDnrONSjblABjD9B4Z5LgiircSt8m8sRZFNi08kG9Sm0uSHtEmP3zaEGg==" }, "yargs": { "version": "15.4.1", diff --git a/package.json b/package.json index 86a3e45d46..703b74af3e 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,7 @@ "react-dom": "16.13.1", "react-helmet": "5.2.1", "react-instantsearch-dom": "6.8.2", + "react-loading-skeleton": "2.2.0", "react-redux": "5.1.2", "react-responsive": "6.1.2", "react-router": "5.2.0", diff --git a/src/components/BulkEnrollmentPage/BulkEnrollment.scss b/src/components/BulkEnrollmentPage/BulkEnrollment.scss index 4174e80da0..a4b00125f8 100644 --- a/src/components/BulkEnrollmentPage/BulkEnrollment.scss +++ b/src/components/BulkEnrollmentPage/BulkEnrollment.scss @@ -6,4 +6,7 @@ .alert { margin-top: 2rem; } + .enroll-button { + padding-left: 0; + } } diff --git a/src/components/BulkEnrollmentPage/CourseSearchResults.jsx b/src/components/BulkEnrollmentPage/CourseSearchResults.jsx index 372ded6e35..63096dfd97 100644 --- a/src/components/BulkEnrollmentPage/CourseSearchResults.jsx +++ b/src/components/BulkEnrollmentPage/CourseSearchResults.jsx @@ -4,43 +4,51 @@ import React, { } from 'react'; import PropTypes from 'prop-types'; import { connectStateResults } from 'react-instantsearch-dom'; +import Skeleton from 'react-loading-skeleton'; import { - DataTable, Toast, + DataTable, Toast, Button, } from '@edx/paragon'; import { SearchContext, SearchPagination } from '@edx/frontend-enterprise'; -import moment from 'moment'; import BulkEnrollmentModal from '../../containers/BulkEnrollmentModal'; import StatusAlert from '../StatusAlert'; -import LoadingMessage from '../LoadingMessage'; -import { configuration } from '../../config'; +import { CourseNameCell, FormattedDateCell } from './CourseSearchResultsCells'; const ERROR_MESSAGE = 'An error occured while retrieving data'; export const NO_DATA_MESSAGE = 'There are no course results'; - +export const ENROLL_TEXT = 'Enroll learners'; export const TABLE_HEADERS = { courseName: 'Course name', courseStartDate: 'Course start date', + enroll: '', }; -export const CourseNameCell = ({ value, row, enterpriseSlug }) => ( - {value} -); +export const EnrollButton = ({ row, setSelectedCourseRuns, setModalOpen }) => { + const handleClick = useMemo(() => () => { + setSelectedCourseRuns([row.original?.advertised_course_run?.key]); + setModalOpen(true); + }, [setSelectedCourseRuns, setModalOpen, row.original?.advertised_course_run?.key]); + return ( + + ); +}; -CourseNameCell.propTypes = { - value: PropTypes.string.isRequired, +EnrollButton.propTypes = { row: PropTypes.shape({ original: PropTypes.shape({ - key: PropTypes.string.isRequired, + advertised_course_run: PropTypes.shape({ + key: PropTypes.string, + }), }), }).isRequired, - enterpriseSlug: PropTypes.string.isRequired, -}; - -export const FormattedDateCell = ({ value }) => {moment(value).format('MMM D, YYYY')}; - -FormattedDateCell.propTypes = { - value: PropTypes.string.isRequired, + setSelectedCourseRuns: PropTypes.func.isRequired, + setModalOpen: PropTypes.func.isRequired, }; export const BaseCourseSearchResults = ({ @@ -81,8 +89,16 @@ export const BaseCourseSearchResults = ({ const [selectedCourseRuns, setSelectedCourseRuns] = useState([]); const [showToast, setShowToast] = useState(false); + const handleBulkEnrollClick = useMemo(() => (selectedRows) => { + const courseRunKeys = selectedRows?.map( + (selectedRow) => selectedRow.original?.advertised_course_run?.key, + ) || []; + setSelectedCourseRuns(courseRunKeys); + setModalOpen(true); + }, [setModalOpen, setSelectedCourseRuns]); + if (isSearchStalled) { - return (); + return (); } if (!isSearchStalled && error) { @@ -122,33 +138,39 @@ export const BaseCourseSearchResults = ({ > Your learners have been enrolled. -
- { - const courseRunKeys = selectedRows?.map( - (selectedRow) => selectedRow.original?.advertised_course_run?.key, - ) || []; - setSelectedCourseRuns(courseRunKeys); - setModalOpen(true); - }, - }]} - > - - - - - - - -
+ ( + + ), + }, + ]} + > + + + + + + + ); }; diff --git a/src/components/BulkEnrollmentPage/CourseSearchResults.test.jsx b/src/components/BulkEnrollmentPage/CourseSearchResults.test.jsx index a0cd6a9af1..438cb8cbea 100644 --- a/src/components/BulkEnrollmentPage/CourseSearchResults.test.jsx +++ b/src/components/BulkEnrollmentPage/CourseSearchResults.test.jsx @@ -5,11 +5,12 @@ import { mount } from 'enzyme'; import configureMockStore from 'redux-mock-store'; import thunk from 'redux-thunk'; import { SearchContext } from '@edx/frontend-enterprise'; +import { Button } from '@edx/paragon'; +import Skeleton from 'react-loading-skeleton'; import StatusAlert from '../StatusAlert'; import { - BaseCourseSearchResults, CourseNameCell, FormattedDateCell, NO_DATA_MESSAGE, TABLE_HEADERS, + BaseCourseSearchResults, EnrollButton, NO_DATA_MESSAGE, TABLE_HEADERS, ENROLL_TEXT, } from './CourseSearchResults'; -import LoadingMessage from '../LoadingMessage'; const mockStore = configureMockStore([thunk]); @@ -66,26 +67,29 @@ const CourseSearchWrapper = ({ value = { refinementsFromQueryParams }, props = d ); -describe('CourseNameCell', () => { - const row = { - original: { - key: testCourseRunKey, - }, - }; - const slug = 'sluggy'; - const wrapper = mount(); - it('correctly formats a link', () => { - expect(wrapper.find('a').props().href).toEqual(`http://localhost:8734/${slug}/course/${row.original.key}`); +describe('', () => { + const key = 'bears+101'; + const row = { original: { advertised_course_run: { key } } }; + it('displays a button', () => { + const wrapper = mount( {}} setSelectedCourseRuns={() => {}} />); + expect(wrapper.find(Button)).toHaveLength(1); + expect(wrapper.text()).toContain(ENROLL_TEXT); }); - it('displays the course name', () => { - expect(wrapper.text()).toEqual(testCourseName); + it('opens the modal', () => { + const openSpy = jest.fn(); + const wrapper = mount( {}} />); + const button = wrapper.find(Button); + button.simulate('click'); + expect(openSpy).toHaveBeenCalledTimes(1); + expect(openSpy).toHaveBeenCalledWith(true); }); -}); - -describe('', () => { - it('renders a formatted date', () => { - const wrapper = mount(); - expect(wrapper.text()).toEqual('Sep 10, 2020'); + it('sets the selectedCourseRuns', () => { + const setCourseRunSpy = jest.fn(); + const wrapper = mount( {}} setSelectedCourseRuns={setCourseRunSpy} />); + const button = wrapper.find(Button); + button.simulate('click'); + expect(setCourseRunSpy).toHaveBeenCalledTimes(1); + expect(setCourseRunSpy).toHaveBeenCalledWith([key]); }); }); @@ -93,17 +97,19 @@ describe('', () => { it('renders search results', () => { const wrapper = mount(); - // Three header columns, one for sorting, one for Course Name, one for Course Run - const tableHeadercells = wrapper.find('TableHeaderCell'); - expect(tableHeadercells.length).toBe(3); - expect(tableHeadercells.at(1).prop('Header')).toBe(TABLE_HEADERS.courseName); - expect(tableHeadercells.at(2).prop('Header')).toBe(TABLE_HEADERS.courseStartDate); + // Four header columns, one for sorting, one for Course Name, one for Course Run, one for the enroll column + const tableHeaderCells = wrapper.find('TableHeaderCell'); + expect(tableHeaderCells.length).toBe(4); + expect(tableHeaderCells.at(1).prop('Header')).toBe(TABLE_HEADERS.courseName); + expect(tableHeaderCells.at(2).prop('Header')).toBe(TABLE_HEADERS.courseStartDate); + expect(tableHeaderCells.at(3).prop('Header')).toBe(''); // Three table cells, one for sorting, one title, one course run const tableCells = wrapper.find('TableCell'); - expect(tableCells.length).toBe(3); + expect(tableCells.length).toBe(4); expect(tableCells.at(1).text()).toBe(testCourseName); expect(tableCells.at(2).text()).toBe('Sep 10, 2020'); + expect(tableCells.at(3).find(EnrollButton)).toHaveLength(1); }); it('displays search pagination', () => { const wrapper = mount(); @@ -140,7 +146,7 @@ describe('', () => { }); it('renders a loading state when loading algolia results', () => { const wrapper = mount(); - expect(wrapper.find(LoadingMessage)).toHaveLength(1); + expect(wrapper.find(Skeleton)).toHaveLength(1); }); it('renders a message when there are no results', () => { const wrapper = mount( ( + {value} +); + +CourseNameCell.propTypes = { + value: PropTypes.string.isRequired, + row: PropTypes.shape({ + original: PropTypes.shape({ + key: PropTypes.string.isRequired, + }), + }).isRequired, + enterpriseSlug: PropTypes.string.isRequired, +}; + +export const FormattedDateCell = ({ value }) => {moment(value).format('MMM D, YYYY')}; + +FormattedDateCell.propTypes = { + value: PropTypes.string.isRequired, +}; diff --git a/src/components/BulkEnrollmentPage/CourseSearchResultsCells.test.jsx b/src/components/BulkEnrollmentPage/CourseSearchResultsCells.test.jsx new file mode 100644 index 0000000000..3820d4d201 --- /dev/null +++ b/src/components/BulkEnrollmentPage/CourseSearchResultsCells.test.jsx @@ -0,0 +1,31 @@ +import React from 'react'; +import { mount } from 'enzyme'; +import { CourseNameCell, FormattedDateCell } from './CourseSearchResultsCells'; +import { configuration } from '../../config'; + +const testCourseName = 'TestCourseName'; +const testCourseRunKey = 'TestCourseRun'; +const testStartDate = '2020-09-10T04:00:00Z'; + +describe('CourseNameCell', () => { + const row = { + original: { + key: testCourseRunKey, + }, + }; + const slug = 'sluggy'; + const wrapper = mount(); + it('correctly formats a link', () => { + expect(wrapper.find('a').props().href).toEqual(`${configuration.ENTERPRISE_LEARNER_PORTAL_URL}/${slug}/course/${row.original.key}`); + }); + it('displays the course name', () => { + expect(wrapper.text()).toEqual(testCourseName); + }); +}); + +describe('', () => { + it('renders a formatted date', () => { + const wrapper = mount(); + expect(wrapper.text()).toEqual('Sep 10, 2020'); + }); +});