@openedx/frontend-app-learner-dashboard 1.0.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (351) hide show
  1. package/LICENSE +661 -0
  2. package/README.rst +86 -0
  3. package/package.json +84 -0
  4. package/src/Main.jsx +20 -0
  5. package/src/__mocks__/file.js +1 -0
  6. package/src/__mocks__/svg.js +1 -0
  7. package/src/__snapshots__/App.test.jsx.snap +83 -0
  8. package/src/__snapshots__/index.test.jsx.snap +43 -0
  9. package/src/app.scss +41 -0
  10. package/src/app.ts +23 -0
  11. package/src/assets/empty-course.svg +49 -0
  12. package/src/assets/more-courses-sidewidget.svg +52 -0
  13. package/src/assets/verified-ribbon.png +0 -0
  14. package/src/components/Banner.jsx +26 -0
  15. package/src/components/Banner.test.jsx +27 -0
  16. package/src/components/__snapshots__/Banner.test.jsx.snap +31 -0
  17. package/src/constants.ts +1 -0
  18. package/src/containers/CourseCard/CourseCard.scss +75 -0
  19. package/src/containers/CourseCard/__snapshots__/index.test.jsx.snap +111 -0
  20. package/src/containers/CourseCard/components/CourseCardActions/ActionButton/__snapshots__/index.test.jsx.snap +14 -0
  21. package/src/containers/CourseCard/components/CourseCardActions/ActionButton/hooks.js +8 -0
  22. package/src/containers/CourseCard/components/CourseCardActions/ActionButton/hooks.test.js +21 -0
  23. package/src/containers/CourseCard/components/CourseCardActions/ActionButton/index.jsx +16 -0
  24. package/src/containers/CourseCard/components/CourseCardActions/ActionButton/index.test.jsx +25 -0
  25. package/src/containers/CourseCard/components/CourseCardActions/BeginCourseButton.jsx +38 -0
  26. package/src/containers/CourseCard/components/CourseCardActions/BeginCourseButton.test.jsx +86 -0
  27. package/src/containers/CourseCard/components/CourseCardActions/ResumeButton.jsx +38 -0
  28. package/src/containers/CourseCard/components/CourseCardActions/ResumeButton.test.jsx +84 -0
  29. package/src/containers/CourseCard/components/CourseCardActions/SelectSessionButton.jsx +28 -0
  30. package/src/containers/CourseCard/components/CourseCardActions/SelectSessionButton.test.jsx +34 -0
  31. package/src/containers/CourseCard/components/CourseCardActions/ViewCourseButton.jsx +37 -0
  32. package/src/containers/CourseCard/components/CourseCardActions/ViewCourseButton.test.jsx +45 -0
  33. package/src/containers/CourseCard/components/CourseCardActions/__snapshots__/BeginCourseButton.test.jsx.snap +39 -0
  34. package/src/containers/CourseCard/components/CourseCardActions/__snapshots__/ResumeButton.test.jsx.snap +39 -0
  35. package/src/containers/CourseCard/components/CourseCardActions/__snapshots__/SelectSessionButton.test.jsx.snap +19 -0
  36. package/src/containers/CourseCard/components/CourseCardActions/__snapshots__/ViewCourseButton.test.jsx.snap +39 -0
  37. package/src/containers/CourseCard/components/CourseCardActions/index.jsx +42 -0
  38. package/src/containers/CourseCard/components/CourseCardActions/index.test.jsx +97 -0
  39. package/src/containers/CourseCard/components/CourseCardActions/messages.js +26 -0
  40. package/src/containers/CourseCard/components/CourseCardBanners/CertificateBanner.jsx +95 -0
  41. package/src/containers/CourseCard/components/CourseCardBanners/CertificateBanner.test.jsx +227 -0
  42. package/src/containers/CourseCard/components/CourseCardBanners/CourseBanner.jsx +57 -0
  43. package/src/containers/CourseCard/components/CourseCardBanners/CourseBanner.test.jsx +131 -0
  44. package/src/containers/CourseCard/components/CourseCardBanners/CreditBanner/__snapshots__/index.test.jsx.snap +58 -0
  45. package/src/containers/CourseCard/components/CourseCardBanners/CreditBanner/hooks.js +42 -0
  46. package/src/containers/CourseCard/components/CourseCardBanners/CreditBanner/hooks.test.js +90 -0
  47. package/src/containers/CourseCard/components/CourseCardBanners/CreditBanner/index.jsx +36 -0
  48. package/src/containers/CourseCard/components/CourseCardBanners/CreditBanner/index.test.jsx +95 -0
  49. package/src/containers/CourseCard/components/CourseCardBanners/CreditBanner/messages.js +16 -0
  50. package/src/containers/CourseCard/components/CourseCardBanners/CreditBanner/views/ApprovedContent.jsx +35 -0
  51. package/src/containers/CourseCard/components/CourseCardBanners/CreditBanner/views/ApprovedContent.test.jsx +64 -0
  52. package/src/containers/CourseCard/components/CourseCardBanners/CreditBanner/views/EligibleContent.jsx +34 -0
  53. package/src/containers/CourseCard/components/CourseCardBanners/CreditBanner/views/EligibleContent.test.jsx +82 -0
  54. package/src/containers/CourseCard/components/CourseCardBanners/CreditBanner/views/MustRequestContent.jsx +36 -0
  55. package/src/containers/CourseCard/components/CourseCardBanners/CreditBanner/views/MustRequestContent.test.jsx +74 -0
  56. package/src/containers/CourseCard/components/CourseCardBanners/CreditBanner/views/PendingContent.jsx +30 -0
  57. package/src/containers/CourseCard/components/CourseCardBanners/CreditBanner/views/PendingContent.test.jsx +63 -0
  58. package/src/containers/CourseCard/components/CourseCardBanners/CreditBanner/views/RejectedContent.jsx +27 -0
  59. package/src/containers/CourseCard/components/CourseCardBanners/CreditBanner/views/RejectedContent.test.jsx +54 -0
  60. package/src/containers/CourseCard/components/CourseCardBanners/CreditBanner/views/components/CreditContent.jsx +51 -0
  61. package/src/containers/CourseCard/components/CourseCardBanners/CreditBanner/views/components/CreditContent.test.jsx +60 -0
  62. package/src/containers/CourseCard/components/CourseCardBanners/CreditBanner/views/components/CreditRequestForm/__snapshots__/index.test.jsx.snap +32 -0
  63. package/src/containers/CourseCard/components/CourseCardBanners/CreditBanner/views/components/CreditRequestForm/hooks.js +13 -0
  64. package/src/containers/CourseCard/components/CourseCardBanners/CreditBanner/views/components/CreditRequestForm/hooks.test.js +45 -0
  65. package/src/containers/CourseCard/components/CourseCardBanners/CreditBanner/views/components/CreditRequestForm/index.jsx +43 -0
  66. package/src/containers/CourseCard/components/CourseCardBanners/CreditBanner/views/components/CreditRequestForm/index.test.jsx +65 -0
  67. package/src/containers/CourseCard/components/CourseCardBanners/CreditBanner/views/components/CreditRequestForm/ref.test.jsx +34 -0
  68. package/src/containers/CourseCard/components/CourseCardBanners/CreditBanner/views/components/ProviderLink.jsx +24 -0
  69. package/src/containers/CourseCard/components/CourseCardBanners/CreditBanner/views/components/ProviderLink.test.jsx +42 -0
  70. package/src/containers/CourseCard/components/CourseCardBanners/CreditBanner/views/components/__snapshots__/CreditContent.test.jsx.snap +60 -0
  71. package/src/containers/CourseCard/components/CourseCardBanners/CreditBanner/views/components/__snapshots__/ProviderLink.test.jsx.snap +11 -0
  72. package/src/containers/CourseCard/components/CourseCardBanners/CreditBanner/views/hooks.js +27 -0
  73. package/src/containers/CourseCard/components/CourseCardBanners/CreditBanner/views/hooks.test.js +56 -0
  74. package/src/containers/CourseCard/components/CourseCardBanners/CreditBanner/views/messages.js +61 -0
  75. package/src/containers/CourseCard/components/CourseCardBanners/EntitlementBanner.jsx +66 -0
  76. package/src/containers/CourseCard/components/CourseCardBanners/EntitlementBanner.test.jsx +63 -0
  77. package/src/containers/CourseCard/components/CourseCardBanners/RelatedProgramsBanner/ProgramsList.jsx +23 -0
  78. package/src/containers/CourseCard/components/CourseCardBanners/RelatedProgramsBanner/ProgramsList.test.jsx +23 -0
  79. package/src/containers/CourseCard/components/CourseCardBanners/RelatedProgramsBanner/__snapshots__/ProgramsList.test.jsx.snap +28 -0
  80. package/src/containers/CourseCard/components/CourseCardBanners/RelatedProgramsBanner/__snapshots__/index.test.jsx.snap +29 -0
  81. package/src/containers/CourseCard/components/CourseCardBanners/RelatedProgramsBanner/index.jsx +38 -0
  82. package/src/containers/CourseCard/components/CourseCardBanners/RelatedProgramsBanner/index.test.jsx +42 -0
  83. package/src/containers/CourseCard/components/CourseCardBanners/RelatedProgramsBanner/messages.js +31 -0
  84. package/src/containers/CourseCard/components/CourseCardBanners/__snapshots__/CertificateBanner.test.jsx.snap +205 -0
  85. package/src/containers/CourseCard/components/CourseCardBanners/__snapshots__/CourseBanner.test.jsx.snap +38 -0
  86. package/src/containers/CourseCard/components/CourseCardBanners/__snapshots__/EntitlementBanner.test.jsx.snap +53 -0
  87. package/src/containers/CourseCard/components/CourseCardBanners/__snapshots__/index.test.jsx.snap +41 -0
  88. package/src/containers/CourseCard/components/CourseCardBanners/index.jsx +28 -0
  89. package/src/containers/CourseCard/components/CourseCardBanners/index.test.jsx +32 -0
  90. package/src/containers/CourseCard/components/CourseCardBanners/messages.js +106 -0
  91. package/src/containers/CourseCard/components/CourseCardDetails/__snapshots__/index.test.jsx.snap +56 -0
  92. package/src/containers/CourseCard/components/CourseCardDetails/hooks.js +67 -0
  93. package/src/containers/CourseCard/components/CourseCardDetails/hooks.test.js +186 -0
  94. package/src/containers/CourseCard/components/CourseCardDetails/index.jsx +45 -0
  95. package/src/containers/CourseCard/components/CourseCardDetails/index.scss +7 -0
  96. package/src/containers/CourseCard/components/CourseCardDetails/index.test.jsx +71 -0
  97. package/src/containers/CourseCard/components/CourseCardDetails/messages.js +41 -0
  98. package/src/containers/CourseCard/components/CourseCardImage.jsx +68 -0
  99. package/src/containers/CourseCard/components/CourseCardImage.test.jsx +66 -0
  100. package/src/containers/CourseCard/components/CourseCardMenu/SocialShareMenu.jsx +83 -0
  101. package/src/containers/CourseCard/components/CourseCardMenu/SocialShareMenu.test.jsx +236 -0
  102. package/src/containers/CourseCard/components/CourseCardMenu/__snapshots__/index.test.jsx.snap +81 -0
  103. package/src/containers/CourseCard/components/CourseCardMenu/hooks.js +58 -0
  104. package/src/containers/CourseCard/components/CourseCardMenu/hooks.test.js +153 -0
  105. package/src/containers/CourseCard/components/CourseCardMenu/index.jsx +85 -0
  106. package/src/containers/CourseCard/components/CourseCardMenu/index.test.jsx +214 -0
  107. package/src/containers/CourseCard/components/CourseCardMenu/messages.js +36 -0
  108. package/src/containers/CourseCard/components/CourseCardTitle.jsx +43 -0
  109. package/src/containers/CourseCard/components/CourseCardTitle.test.jsx +67 -0
  110. package/src/containers/CourseCard/components/RelatedProgramsBadge/__snapshots__/index.test.jsx.snap +25 -0
  111. package/src/containers/CourseCard/components/RelatedProgramsBadge/hooks.jsx +35 -0
  112. package/src/containers/CourseCard/components/RelatedProgramsBadge/hooks.test.js +76 -0
  113. package/src/containers/CourseCard/components/RelatedProgramsBadge/index.jsx +39 -0
  114. package/src/containers/CourseCard/components/RelatedProgramsBadge/index.test.jsx +30 -0
  115. package/src/containers/CourseCard/components/RelatedProgramsBadge/messages.js +13 -0
  116. package/src/containers/CourseCard/components/__snapshots__/CourseCardImage.test.jsx.snap +72 -0
  117. package/src/containers/CourseCard/components/__snapshots__/CourseCardTitle.test.jsx.snap +33 -0
  118. package/src/containers/CourseCard/components/hooks.js +33 -0
  119. package/src/containers/CourseCard/components/hooks.test.js +165 -0
  120. package/src/containers/CourseCard/hooks.js +23 -0
  121. package/src/containers/CourseCard/hooks.test.js +48 -0
  122. package/src/containers/CourseCard/index.jsx +50 -0
  123. package/src/containers/CourseCard/index.test.jsx +29 -0
  124. package/src/containers/CourseCard/messages.js +26 -0
  125. package/src/containers/CourseFilterControls/ActiveCourseFilters.jsx +40 -0
  126. package/src/containers/CourseFilterControls/ActiveCourseFilters.test.jsx +17 -0
  127. package/src/containers/CourseFilterControls/CourseFilterControls.jsx +115 -0
  128. package/src/containers/CourseFilterControls/CourseFilterControls.test.jsx +60 -0
  129. package/src/containers/CourseFilterControls/__snapshots__/ActiveCourseFilters.test.jsx.snap +39 -0
  130. package/src/containers/CourseFilterControls/__snapshots__/CourseFilterControls.test.jsx.snap +169 -0
  131. package/src/containers/CourseFilterControls/components/Checkbox.jsx +21 -0
  132. package/src/containers/CourseFilterControls/components/Checkbox.test.jsx +15 -0
  133. package/src/containers/CourseFilterControls/components/FilterForm.jsx +45 -0
  134. package/src/containers/CourseFilterControls/components/FilterForm.test.jsx +29 -0
  135. package/src/containers/CourseFilterControls/components/SortForm.jsx +39 -0
  136. package/src/containers/CourseFilterControls/components/SortForm.test.jsx +19 -0
  137. package/src/containers/CourseFilterControls/components/__snapshots__/Checkbox.test.jsx.snap +46 -0
  138. package/src/containers/CourseFilterControls/components/__snapshots__/FilterForm.test.jsx.snap +41 -0
  139. package/src/containers/CourseFilterControls/components/__snapshots__/SortForm.test.jsx.snap +29 -0
  140. package/src/containers/CourseFilterControls/hooks.js +60 -0
  141. package/src/containers/CourseFilterControls/hooks.test.js +113 -0
  142. package/src/containers/CourseFilterControls/index.jsx +6 -0
  143. package/src/containers/CourseFilterControls/index.scss +29 -0
  144. package/src/containers/CourseFilterControls/messages.js +61 -0
  145. package/src/containers/CoursesPanel/CourseList/__snapshots__/index.test.jsx.snap +70 -0
  146. package/src/containers/CoursesPanel/CourseList/hooks.js +8 -0
  147. package/src/containers/CoursesPanel/CourseList/index.jsx +54 -0
  148. package/src/containers/CoursesPanel/CourseList/index.test.jsx +64 -0
  149. package/src/containers/CoursesPanel/NoCoursesView/__snapshots__/index.test.jsx.snap +29 -0
  150. package/src/containers/CoursesPanel/NoCoursesView/index.jsx +40 -0
  151. package/src/containers/CoursesPanel/NoCoursesView/index.scss +15 -0
  152. package/src/containers/CoursesPanel/NoCoursesView/index.test.jsx +18 -0
  153. package/src/containers/CoursesPanel/NoCoursesView/messages.js +26 -0
  154. package/src/containers/CoursesPanel/__snapshots__/index.test.jsx.snap +55 -0
  155. package/src/containers/CoursesPanel/hooks.js +54 -0
  156. package/src/containers/CoursesPanel/hooks.test.js +115 -0
  157. package/src/containers/CoursesPanel/index.jsx +40 -0
  158. package/src/containers/CoursesPanel/index.scss +42 -0
  159. package/src/containers/CoursesPanel/index.test.jsx +58 -0
  160. package/src/containers/CoursesPanel/messages.js +11 -0
  161. package/src/containers/Dashboard/DashboardLayout.jsx +54 -0
  162. package/src/containers/Dashboard/DashboardLayout.test.jsx +115 -0
  163. package/src/containers/Dashboard/LoadingView.jsx +20 -0
  164. package/src/containers/Dashboard/LoadingView.test.jsx +23 -0
  165. package/src/containers/Dashboard/__snapshots__/DashboardLayout.test.jsx.snap +197 -0
  166. package/src/containers/Dashboard/__snapshots__/LoadingView.test.jsx.snap +13 -0
  167. package/src/containers/Dashboard/__snapshots__/index.test.jsx.snap +69 -0
  168. package/src/containers/Dashboard/hooks.js +34 -0
  169. package/src/containers/Dashboard/hooks.test.js +88 -0
  170. package/src/containers/Dashboard/index.jsx +43 -0
  171. package/src/containers/Dashboard/index.scss +29 -0
  172. package/src/containers/Dashboard/index.test.jsx +122 -0
  173. package/src/containers/EmailSettingsModal/__snapshots__/index.test.jsx.snap +133 -0
  174. package/src/containers/EmailSettingsModal/hooks.js +32 -0
  175. package/src/containers/EmailSettingsModal/hooks.test.js +71 -0
  176. package/src/containers/EmailSettingsModal/index.jsx +58 -0
  177. package/src/containers/EmailSettingsModal/index.test.jsx +57 -0
  178. package/src/containers/EmailSettingsModal/messages.js +37 -0
  179. package/src/containers/RelatedProgramsModal/__snapshots__/index.test.jsx.snap +169 -0
  180. package/src/containers/RelatedProgramsModal/components/ProgramCard.jsx +66 -0
  181. package/src/containers/RelatedProgramsModal/components/ProgramCard.test.jsx +23 -0
  182. package/src/containers/RelatedProgramsModal/components/__snapshots__/ProgramCard.test.jsx.snap +60 -0
  183. package/src/containers/RelatedProgramsModal/components/index.scss +23 -0
  184. package/src/containers/RelatedProgramsModal/components/messages.js +24 -0
  185. package/src/containers/RelatedProgramsModal/hooks.js +10 -0
  186. package/src/containers/RelatedProgramsModal/index.jsx +62 -0
  187. package/src/containers/RelatedProgramsModal/index.scss +5 -0
  188. package/src/containers/RelatedProgramsModal/index.test.jsx +60 -0
  189. package/src/containers/RelatedProgramsModal/messages.js +16 -0
  190. package/src/containers/SelectSessionModal/__snapshots__/index.test.jsx.snap +176 -0
  191. package/src/containers/SelectSessionModal/constants.js +2 -0
  192. package/src/containers/SelectSessionModal/hooks.js +77 -0
  193. package/src/containers/SelectSessionModal/hooks.test.js +194 -0
  194. package/src/containers/SelectSessionModal/index.jsx +75 -0
  195. package/src/containers/SelectSessionModal/index.test.jsx +53 -0
  196. package/src/containers/SelectSessionModal/messages.js +41 -0
  197. package/src/containers/UnenrollConfirmModal/__snapshots__/index.test.jsx.snap +101 -0
  198. package/src/containers/UnenrollConfirmModal/components/ConfirmPane.jsx +36 -0
  199. package/src/containers/UnenrollConfirmModal/components/ConfirmPane.test.jsx +14 -0
  200. package/src/containers/UnenrollConfirmModal/components/FinishedPane.jsx +35 -0
  201. package/src/containers/UnenrollConfirmModal/components/FinishedPane.test.jsx +21 -0
  202. package/src/containers/UnenrollConfirmModal/components/ReasonPane.jsx +65 -0
  203. package/src/containers/UnenrollConfirmModal/components/ReasonPane.test.jsx +26 -0
  204. package/src/containers/UnenrollConfirmModal/components/__snapshots__/ConfirmPane.test.jsx.snap +22 -0
  205. package/src/containers/UnenrollConfirmModal/components/__snapshots__/FinishedPane.test.jsx.snap +38 -0
  206. package/src/containers/UnenrollConfirmModal/components/__snapshots__/ReasonPane.test.jsx.snap +183 -0
  207. package/src/containers/UnenrollConfirmModal/components/messages.js +56 -0
  208. package/src/containers/UnenrollConfirmModal/constants.js +86 -0
  209. package/src/containers/UnenrollConfirmModal/hooks/index.js +55 -0
  210. package/src/containers/UnenrollConfirmModal/hooks/index.test.js +101 -0
  211. package/src/containers/UnenrollConfirmModal/hooks/reasons.js +79 -0
  212. package/src/containers/UnenrollConfirmModal/hooks/reasons.test.js +192 -0
  213. package/src/containers/UnenrollConfirmModal/index.jsx +59 -0
  214. package/src/containers/UnenrollConfirmModal/index.test.jsx +61 -0
  215. package/src/data/constants/app.js +20 -0
  216. package/src/data/constants/app.test.js +24 -0
  217. package/src/data/constants/course.js +17 -0
  218. package/src/data/constants/credit.js +9 -0
  219. package/src/data/constants/files.js +20 -0
  220. package/src/data/constants/htmlKeys.js +21 -0
  221. package/src/data/constants/requests.js +34 -0
  222. package/src/data/contexts/GlobalDataContext.jsx +15 -0
  223. package/src/data/contexts/GlobalDataProvider.jsx +23 -0
  224. package/src/data/contexts/MasqueradeUserContext.jsx +16 -0
  225. package/src/data/contexts/MasqueradeUserProvider.jsx +31 -0
  226. package/src/data/redux/app/index.js +2 -0
  227. package/src/data/redux/app/reducer.js +81 -0
  228. package/src/data/redux/app/reducer.test.js +124 -0
  229. package/src/data/redux/app/selectors/appSelectors.js +23 -0
  230. package/src/data/redux/app/selectors/appSelectors.test.js +28 -0
  231. package/src/data/redux/app/selectors/courseCard.js +155 -0
  232. package/src/data/redux/app/selectors/courseCard.test.js +398 -0
  233. package/src/data/redux/app/selectors/currentList.js +60 -0
  234. package/src/data/redux/app/selectors/currentList.test.js +185 -0
  235. package/src/data/redux/app/selectors/index.js +13 -0
  236. package/src/data/redux/app/selectors/simpleSelectors.js +38 -0
  237. package/src/data/redux/app/selectors/simpleSelectors.test.js +75 -0
  238. package/src/data/redux/hooks/app.js +106 -0
  239. package/src/data/redux/hooks/index.js +2 -0
  240. package/src/data/redux/hooks/requests.js +47 -0
  241. package/src/data/redux/index.js +37 -0
  242. package/src/data/redux/requests/index.js +2 -0
  243. package/src/data/redux/requests/reducer.js +53 -0
  244. package/src/data/redux/requests/reducer.test.js +62 -0
  245. package/src/data/redux/requests/selectors.js +29 -0
  246. package/src/data/redux/requests/selectors.test.js +110 -0
  247. package/src/data/services/lms/api.js +77 -0
  248. package/src/data/services/lms/api.test.js +156 -0
  249. package/src/data/services/lms/constants.js +18 -0
  250. package/src/data/services/lms/fakeData/courses.js +828 -0
  251. package/src/data/services/lms/fakeData/testUtils.js +40 -0
  252. package/src/data/services/lms/index.js +8 -0
  253. package/src/data/services/lms/urls.js +43 -0
  254. package/src/data/services/lms/urls.test.js +51 -0
  255. package/src/data/services/lms/utils.js +60 -0
  256. package/src/data/services/lms/utils.test.js +40 -0
  257. package/src/data/services/segment/utils.js +17 -0
  258. package/src/data/services/segment/utils.test.js +36 -0
  259. package/src/data/store.js +25 -0
  260. package/src/data/store.test.js +47 -0
  261. package/src/data/utils.js +19 -0
  262. package/src/data/utils.test.js +29 -0
  263. package/src/hooks/api.js +123 -0
  264. package/src/hooks/api.test.js +273 -0
  265. package/src/hooks/index.js +7 -0
  266. package/src/hooks/utils.js +17 -0
  267. package/src/hooks/utils.test.js +16 -0
  268. package/src/i18n/index.js +25 -0
  269. package/src/index.ts +3 -0
  270. package/src/messages.js +16 -0
  271. package/src/providers.ts +11 -0
  272. package/src/routes.jsx +14 -0
  273. package/src/segment.js +85 -0
  274. package/src/setupTest.jsx +251 -0
  275. package/src/slots/CourseBannerSlot/README.md +44 -0
  276. package/src/slots/CourseBannerSlot/images/course_banner_slot_default.png +0 -0
  277. package/src/slots/CourseBannerSlot/images/custom_course_banner.png +0 -0
  278. package/src/slots/CourseBannerSlot/index.jsx +18 -0
  279. package/src/slots/CourseCardActionSlot/README.md +53 -0
  280. package/src/slots/CourseCardActionSlot/images/post_course_card_action.png +0 -0
  281. package/src/slots/CourseCardActionSlot/index.jsx +15 -0
  282. package/src/slots/CourseListSlot/README.md +54 -0
  283. package/src/slots/CourseListSlot/images/course_list_slot.png +0 -0
  284. package/src/slots/CourseListSlot/images/readme_custom_course_list.png +0 -0
  285. package/src/slots/CourseListSlot/index.jsx +17 -0
  286. package/src/slots/DashboardModalSlot/README.md +30 -0
  287. package/src/slots/DashboardModalSlot/images/dashboard_modal_slot.png +0 -0
  288. package/src/slots/DashboardModalSlot/index.jsx +7 -0
  289. package/src/slots/NoCoursesViewSlot/README.md +34 -0
  290. package/src/slots/NoCoursesViewSlot/images/no_course_view_slot.png +0 -0
  291. package/src/slots/NoCoursesViewSlot/images/readme_custom_no_courses.png +0 -0
  292. package/src/slots/NoCoursesViewSlot/index.jsx +11 -0
  293. package/src/slots/README.md +8 -0
  294. package/src/slots/WidgetSidebarSlot/README.md +51 -0
  295. package/src/slots/WidgetSidebarSlot/__snapshots__/index.test.jsx.snap +14 -0
  296. package/src/slots/WidgetSidebarSlot/images/readme_custom_sidebar.png +0 -0
  297. package/src/slots/WidgetSidebarSlot/images/widget_sidebar_slot.png +0 -0
  298. package/src/slots/WidgetSidebarSlot/index.jsx +10 -0
  299. package/src/slots/WidgetSidebarSlot/index.test.jsx +18 -0
  300. package/src/slots.tsx +9 -0
  301. package/src/test/app.test.jsx +255 -0
  302. package/src/test/inspector.js +50 -0
  303. package/src/test/messages.js +29 -0
  304. package/src/test/utils.js +3 -0
  305. package/src/testUtils.js +211 -0
  306. package/src/tracking/constants.js +50 -0
  307. package/src/tracking/index.js +17 -0
  308. package/src/tracking/trackers/course.js +44 -0
  309. package/src/tracking/trackers/course.test.js +80 -0
  310. package/src/tracking/trackers/credit.js +20 -0
  311. package/src/tracking/trackers/credit.test.js +38 -0
  312. package/src/tracking/trackers/engagement.js +23 -0
  313. package/src/tracking/trackers/engagement.test.js +31 -0
  314. package/src/tracking/trackers/entitlements.js +34 -0
  315. package/src/tracking/trackers/entitlements.test.js +34 -0
  316. package/src/tracking/trackers/filter.js +21 -0
  317. package/src/tracking/trackers/filter.test.js +45 -0
  318. package/src/tracking/trackers/findCourses.js +16 -0
  319. package/src/tracking/trackers/findCourses.test.js +45 -0
  320. package/src/tracking/trackers/socialShare.js +11 -0
  321. package/src/tracking/trackers/socialShare.test.js +17 -0
  322. package/src/utils/StrictDict.js +19 -0
  323. package/src/utils/StrictDict.test.js +66 -0
  324. package/src/utils/dateFormatter.js +9 -0
  325. package/src/utils/hooks.js +12 -0
  326. package/src/utils/index.js +5 -0
  327. package/src/utils/keyStore.js +10 -0
  328. package/src/widgets/LearnerDashboardHeader/ConfirmEmailBanner/ConfirmEmailBanner.scss +3 -0
  329. package/src/widgets/LearnerDashboardHeader/ConfirmEmailBanner/assets/confirm-email.svg +76 -0
  330. package/src/widgets/LearnerDashboardHeader/ConfirmEmailBanner/hooks.js +45 -0
  331. package/src/widgets/LearnerDashboardHeader/ConfirmEmailBanner/index.jsx +66 -0
  332. package/src/widgets/LearnerDashboardHeader/ConfirmEmailBanner/messages.js +36 -0
  333. package/src/widgets/LearnerDashboardHeader/CoursesLink.jsx +14 -0
  334. package/src/widgets/LearnerDashboardHeader/DiscoverLinkMenuItem.jsx +30 -0
  335. package/src/widgets/LearnerDashboardHeader/MasqueradeBar/hooks.js +69 -0
  336. package/src/widgets/LearnerDashboardHeader/MasqueradeBar/index.jsx +92 -0
  337. package/src/widgets/LearnerDashboardHeader/MasqueradeBar/index.scss +38 -0
  338. package/src/widgets/LearnerDashboardHeader/MasqueradeBar/messages.js +36 -0
  339. package/src/widgets/LearnerDashboardHeader/OrderHistoryLinkMenuItem.jsx +29 -0
  340. package/src/widgets/LearnerDashboardHeader/ProgramsLinkMenuItem.jsx +26 -0
  341. package/src/widgets/LearnerDashboardHeader/SupportLinkMenuItem.jsx +29 -0
  342. package/src/widgets/LearnerDashboardHeader/app.tsx +93 -0
  343. package/src/widgets/LearnerDashboardHeader/hooks.js +20 -0
  344. package/src/widgets/LearnerDashboardHeader/index.ts +1 -0
  345. package/src/widgets/LearnerDashboardHeader/messages.js +91 -0
  346. package/src/widgets/LookingForChallengeWidget/__snapshots__/index.test.jsx.snap +45 -0
  347. package/src/widgets/LookingForChallengeWidget/index.jsx +47 -0
  348. package/src/widgets/LookingForChallengeWidget/index.scss +6 -0
  349. package/src/widgets/LookingForChallengeWidget/index.test.jsx +24 -0
  350. package/src/widgets/LookingForChallengeWidget/messages.js +16 -0
  351. package/src/widgets/LookingForChallengeWidget/track.js +15 -0
@@ -0,0 +1,236 @@
1
+ import { when } from 'jest-when';
2
+ import * as ReactShare from 'react-share';
3
+
4
+ import { useIntl } from '@openedx/frontend-base';
5
+ import { formatMessage, shallow } from '@edx/react-unit-test-utils';
6
+
7
+ import track from 'tracking';
8
+ import { reduxHooks } from 'hooks';
9
+
10
+ import { useEmailSettings } from './hooks';
11
+ import SocialShareMenu, { testIds } from './SocialShareMenu';
12
+ import messages from './messages';
13
+
14
+ jest.mock('react-share', () => ({
15
+ FacebookShareButton: () => 'FacebookShareButton',
16
+ TwitterShareButton: () => 'TwitterShareButton',
17
+ }));
18
+
19
+ jest.mock('tracking', () => ({
20
+ socialShare: 'test-social-share-key',
21
+ }));
22
+
23
+ jest.mock('@openedx/frontend-base', () => ({
24
+ ...jest.requireActual('@openedx/frontend-base'),
25
+ useIntl: jest.fn().mockReturnValue({
26
+ formatMessage: jest.requireActual('@edx/react-unit-test-utils').formatMessage,
27
+ }),
28
+ }));
29
+
30
+ jest.mock('hooks', () => ({
31
+ reduxHooks: {
32
+ useMasqueradeData: jest.fn(),
33
+ useCardCourseData: jest.fn(),
34
+ useCardEnrollmentData: jest.fn(),
35
+ useCardSocialSettingsData: jest.fn(),
36
+ useTrackCourseEvent: jest.fn((...args) => ({ trackCourseEvent: args })),
37
+ },
38
+ }));
39
+ jest.mock('./hooks', () => ({
40
+ useEmailSettings: jest.fn(),
41
+ }));
42
+
43
+ const props = {
44
+ cardId: 'test-card-id',
45
+ emailSettings: { show: jest.fn() },
46
+ };
47
+
48
+ const mockHook = (fn, returnValue, options = {}) => {
49
+ if (options.isCardHook) {
50
+ when(fn).calledWith(props.cardId).mockReturnValueOnce(returnValue);
51
+ } else {
52
+ when(fn).calledWith().mockReturnValueOnce(returnValue);
53
+ }
54
+ };
55
+
56
+ const courseName = 'test-course-name';
57
+
58
+ const socialShare = {
59
+ facebook: {
60
+ isEnabled: true,
61
+ shareUrl: 'facebook-share-url',
62
+ socialBrand: 'facebook-social-brand',
63
+ },
64
+ twitter: {
65
+ isEnabled: true,
66
+ shareUrl: 'twitter-share-url',
67
+ socialBrand: 'twitter-social-brand',
68
+ },
69
+ };
70
+
71
+ const mockHooks = (returnVals = {}) => {
72
+ mockHook(
73
+ reduxHooks.useCardEnrollmentData,
74
+ {
75
+ isEmailEnabled: !!returnVals.isEmailEnabled,
76
+ isExecEd2UCourse: !!returnVals.isExecEd2UCourse,
77
+ },
78
+ { isCardHook: true },
79
+ );
80
+ mockHook(reduxHooks.useCardCourseData, { courseName }, { isCardHook: true });
81
+ mockHook(reduxHooks.useMasqueradeData, { isMasquerading: !!returnVals.isMasquerading });
82
+ mockHook(
83
+ reduxHooks.useCardSocialSettingsData,
84
+ {
85
+ facebook: { ...socialShare.facebook, isEnabled: !!returnVals.facebook?.isEnabled },
86
+ twitter: { ...socialShare.twitter, isEnabled: !!returnVals.twitter?.isEnabled },
87
+ },
88
+ { isCardHook: true },
89
+ );
90
+ };
91
+
92
+ let el;
93
+ const render = () => {
94
+ el = shallow(<SocialShareMenu {...props} />);
95
+ };
96
+
97
+ describe('SocialShareMenu', () => {
98
+ describe('behavior', () => {
99
+ beforeEach(() => {
100
+ mockHooks();
101
+ render();
102
+ });
103
+ it('initializes intl hook', () => {
104
+ expect(useIntl).toHaveBeenCalledWith();
105
+ });
106
+ it('initializes local hooks', () => {
107
+ when(useEmailSettings).expectCalledWith();
108
+ });
109
+ it('initializes redux hook data ', () => {
110
+ when(reduxHooks.useCardEnrollmentData).expectCalledWith(props.cardId);
111
+ when(reduxHooks.useCardCourseData).expectCalledWith(props.cardId);
112
+ when(reduxHooks.useCardSocialSettingsData).expectCalledWith(props.cardId);
113
+ when(reduxHooks.useMasqueradeData).expectCalledWith();
114
+ when(reduxHooks.useTrackCourseEvent).expectCalledWith(track.socialShare, props.cardId, 'twitter');
115
+ when(reduxHooks.useTrackCourseEvent).expectCalledWith(track.socialShare, props.cardId, 'facebook');
116
+ });
117
+ });
118
+ describe('render', () => {
119
+ it('renders null if exec ed course', () => {
120
+ mockHooks({ isExecEd2UCourse: true });
121
+ render();
122
+ expect(el.isEmptyRender()).toEqual(true);
123
+ });
124
+ const testEmailSettingsDropdown = (isMasquerading = false) => {
125
+ describe('email settings dropdown', () => {
126
+ const loadToggle = () => el.instance.findByTestId(testIds.emailSettingsModalToggle)[0];
127
+ it('renders', () => {
128
+ expect(el.instance.findByTestId(testIds.emailSettingsModalToggle).length).toEqual(1);
129
+ });
130
+ if (isMasquerading) {
131
+ it('is disabled', () => {
132
+ expect(loadToggle().props.disabled).toEqual(true);
133
+ });
134
+ } else {
135
+ it('is enabled', () => {
136
+ expect(loadToggle().props.disabled).toEqual(false);
137
+ });
138
+ }
139
+ test('show email settings modal on click', () => {
140
+ expect(loadToggle().props.onClick).toEqual(props.emailSettings.show);
141
+ });
142
+ });
143
+ };
144
+ const testFacebookShareButton = () => {
145
+ test('renders facebook share button with courseName and brand', () => {
146
+ const button = el.instance.findByType(ReactShare.FacebookShareButton)[0];
147
+ expect(button.props.url).toEqual(socialShare.facebook.shareUrl);
148
+ expect(button.props.onClick).toEqual(
149
+ reduxHooks.useTrackCourseEvent(track.socialShare, props.cardId, 'facebook'),
150
+ );
151
+ expect(button.props.title).toEqual(formatMessage(messages.shareQuote, {
152
+ courseName,
153
+ socialBrand: socialShare.facebook.socialBrand,
154
+ }));
155
+ });
156
+ };
157
+ const testTwitterShareButton = () => {
158
+ test('renders twitter share button with courseName and brand', () => {
159
+ const button = el.instance.findByType(ReactShare.TwitterShareButton)[0];
160
+ expect(button.props.url).toEqual(socialShare.twitter.shareUrl);
161
+ expect(button.props.onClick).toEqual(
162
+ reduxHooks.useTrackCourseEvent(track.socialShare, props.cardId, 'twitter'),
163
+ );
164
+ expect(button.props.title).toEqual(formatMessage(messages.shareQuote, {
165
+ courseName,
166
+ socialBrand: socialShare.twitter.socialBrand,
167
+ }));
168
+ });
169
+ };
170
+ describe('all enabled', () => {
171
+ beforeEach(() => {
172
+ mockHooks({
173
+ facebook: { isEnabled: true },
174
+ twitter: { isEnabled: true },
175
+ isEmailEnabled: true,
176
+ });
177
+ render();
178
+ });
179
+ describe('email settings dropdown', () => {
180
+ const loadToggle = () => el.instance.findByTestId(testIds.emailSettingsModalToggle)[0];
181
+ it('renders', () => {
182
+ expect(el.instance.findByTestId(testIds.emailSettingsModalToggle).length).toEqual(1);
183
+ });
184
+ it('is enabled', () => {
185
+ expect(loadToggle().props.disabled).toEqual(false);
186
+ });
187
+ test('show email settings modal on click', () => {
188
+ expect(loadToggle().props.onClick).toEqual(props.emailSettings.show);
189
+ });
190
+ });
191
+ testEmailSettingsDropdown();
192
+ testFacebookShareButton();
193
+ testTwitterShareButton();
194
+ });
195
+ describe('only email enabled', () => {
196
+ beforeEach(() => {
197
+ mockHooks({ isEmailEnabled: true });
198
+ render();
199
+ });
200
+ testEmailSettingsDropdown();
201
+ it('does not render facebook or twitter controls', () => {
202
+ expect(el.instance.findByType(ReactShare.FacebookShareButton).length).toEqual(0);
203
+ expect(el.instance.findByType(ReactShare.TwitterShareButton).length).toEqual(0);
204
+ });
205
+ describe('masquerading', () => {
206
+ beforeEach(() => {
207
+ mockHooks({ isEmailEnabled: true, isMasquerading: true });
208
+ render();
209
+ });
210
+ testEmailSettingsDropdown(true);
211
+ });
212
+ });
213
+ describe('only facebook enabled', () => {
214
+ beforeEach(() => {
215
+ mockHooks({ facebook: { isEnabled: true } });
216
+ render();
217
+ });
218
+ testFacebookShareButton();
219
+ it('does not render email or twitter controls', () => {
220
+ expect(el.instance.findByTestId(testIds.emailSettingsModalToggle).length).toEqual(0);
221
+ expect(el.instance.findByType(ReactShare.TwitterShareButton).length).toEqual(0);
222
+ });
223
+ });
224
+ describe('only twitter enabled', () => {
225
+ beforeEach(() => {
226
+ mockHooks({ twitter: { isEnabled: true } });
227
+ render();
228
+ });
229
+ testTwitterShareButton();
230
+ it('does not render email or facebook controls', () => {
231
+ expect(el.instance.findByTestId(testIds.emailSettingsModalToggle).length).toEqual(0);
232
+ expect(el.instance.findByType(ReactShare.FacebookShareButton).length).toEqual(0);
233
+ });
234
+ });
235
+ });
236
+ });
@@ -0,0 +1,81 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`CourseCardMenu render show dropdown hide unenroll item and disable email snapshot 1`] = `
4
+ <Fragment>
5
+ <Dropdown
6
+ onToggle={[MockFunction hooks.handleToggleDropdown]}
7
+ >
8
+ <Dropdown.Toggle
9
+ alt="Course actions dropdown"
10
+ as="IconButton"
11
+ iconAs="Icon"
12
+ id="course-actions-dropdown-test-card-id"
13
+ src={[MockFunction icons.MoreVert]}
14
+ variant="primary"
15
+ />
16
+ <Dropdown.Menu>
17
+ <SocialShareMenu
18
+ cardId="test-card-id"
19
+ emailSettings={
20
+ {
21
+ "hide": [MockFunction emailSettingHide],
22
+ "isVisible": false,
23
+ "show": [MockFunction emailSettingShow],
24
+ }
25
+ }
26
+ />
27
+ </Dropdown.Menu>
28
+ </Dropdown>
29
+ <UnenrollConfirmModal
30
+ cardId="test-card-id"
31
+ closeModal={[MockFunction unenrollHide]}
32
+ show={false}
33
+ />
34
+ </Fragment>
35
+ `;
36
+
37
+ exports[`CourseCardMenu render show dropdown show unenroll and enable email snapshot 1`] = `
38
+ <Fragment>
39
+ <Dropdown
40
+ onToggle={[MockFunction hooks.handleToggleDropdown]}
41
+ >
42
+ <Dropdown.Toggle
43
+ alt="Course actions dropdown"
44
+ as="IconButton"
45
+ iconAs="Icon"
46
+ id="course-actions-dropdown-test-card-id"
47
+ src={[MockFunction icons.MoreVert]}
48
+ variant="primary"
49
+ />
50
+ <Dropdown.Menu>
51
+ <Dropdown.Item
52
+ data-testid="unenrollModalToggle"
53
+ disabled={false}
54
+ onClick={[MockFunction unenrollShow]}
55
+ >
56
+ Unenroll
57
+ </Dropdown.Item>
58
+ <SocialShareMenu
59
+ cardId="test-card-id"
60
+ emailSettings={
61
+ {
62
+ "hide": [MockFunction emailSettingHide],
63
+ "isVisible": false,
64
+ "show": [MockFunction emailSettingShow],
65
+ }
66
+ }
67
+ />
68
+ </Dropdown.Menu>
69
+ </Dropdown>
70
+ <UnenrollConfirmModal
71
+ cardId="test-card-id"
72
+ closeModal={[MockFunction unenrollHide]}
73
+ show={false}
74
+ />
75
+ <EmailSettingsModal
76
+ cardId="test-card-id"
77
+ closeModal={[MockFunction emailSettingHide]}
78
+ show={false}
79
+ />
80
+ </Fragment>
81
+ `;
@@ -0,0 +1,58 @@
1
+ import { useKeyedState, StrictDict } from '@edx/react-unit-test-utils';
2
+
3
+ import track from '../../../../tracking';
4
+ import { reduxHooks } from '../../../../hooks';
5
+
6
+ export const stateKeys = StrictDict({
7
+ isUnenrollConfirmVisible: 'isUnenrollConfirmVisible',
8
+ isEmailSettingsVisible: 'isEmailSettingsVisible',
9
+ });
10
+
11
+ export const useUnenrollData = () => {
12
+ const [isVisible, setIsVisible] = useKeyedState(stateKeys.isUnenrollConfirmVisible, false);
13
+ return {
14
+ show: () => setIsVisible(true),
15
+ hide: () => setIsVisible(false),
16
+ isVisible,
17
+ };
18
+ };
19
+
20
+ export const useEmailSettings = () => {
21
+ const [isVisible, setIsVisible] = useKeyedState(stateKeys.isEmailSettingsVisible, false);
22
+ return {
23
+ show: () => setIsVisible(true),
24
+ hide: () => setIsVisible(false),
25
+ isVisible,
26
+ };
27
+ };
28
+
29
+ export const useHandleToggleDropdown = (cardId) => {
30
+ const trackCourseEvent = reduxHooks.useTrackCourseEvent(
31
+ track.course.courseOptionsDropdownClicked,
32
+ cardId,
33
+ );
34
+ return (isOpen) => {
35
+ if (isOpen) {
36
+ trackCourseEvent();
37
+ }
38
+ };
39
+ };
40
+
41
+ export const useOptionVisibility = (cardId) => {
42
+ const { isEnrolled, isEmailEnabled } = reduxHooks.useCardEnrollmentData(cardId);
43
+ const { twitter, facebook } = reduxHooks.useCardSocialSettingsData(cardId);
44
+ const { isEarned } = reduxHooks.useCardCertificateData(cardId);
45
+
46
+ const shouldShowUnenrollItem = isEnrolled && !isEarned;
47
+ const shouldShowDropdown = (
48
+ shouldShowUnenrollItem
49
+ || isEmailEnabled
50
+ || facebook.isEnabled
51
+ || twitter.isEnabled
52
+ );
53
+
54
+ return {
55
+ shouldShowUnenrollItem,
56
+ shouldShowDropdown,
57
+ };
58
+ };
@@ -0,0 +1,153 @@
1
+ import { mockUseKeyedState } from '@edx/react-unit-test-utils';
2
+
3
+ import { reduxHooks } from 'hooks';
4
+ import track from 'tracking';
5
+
6
+ import * as hooks from './hooks';
7
+
8
+ jest.mock('hooks', () => ({
9
+ reduxHooks: {
10
+ useCardCertificateData: jest.fn(),
11
+ useCardEnrollmentData: jest.fn(),
12
+ useCardSocialSettingsData: jest.fn(),
13
+ useTrackCourseEvent: jest.fn(),
14
+ },
15
+ }));
16
+
17
+ const trackCourseEvent = jest.fn();
18
+ reduxHooks.useTrackCourseEvent.mockReturnValue(trackCourseEvent);
19
+ const cardId = 'test-card-id';
20
+ let out;
21
+
22
+ const state = mockUseKeyedState(hooks.stateKeys);
23
+
24
+ describe('CourseCardMenu hooks', () => {
25
+ beforeEach(() => {
26
+ jest.clearAllMocks();
27
+ state.mock();
28
+ });
29
+ describe('useUnenrollData', () => {
30
+ beforeEach(() => {
31
+ state.mockVals({ isUnenrollConfirmVisible: true });
32
+ out = hooks.useUnenrollData();
33
+ });
34
+ describe('behavior', () => {
35
+ it('initializes isUnenrollConfirmVisible state to false', () => {
36
+ state.expectInitializedWith(state.keys.isUnenrollConfirmVisible, false);
37
+ });
38
+ });
39
+ describe('output', () => {
40
+ test('state is loaded from current state value', () => {
41
+ expect(out.isVisible).toEqual(true);
42
+ });
43
+ test('show sets state value to true', () => {
44
+ out.show();
45
+ expect(state.setState.isUnenrollConfirmVisible).toHaveBeenCalledWith(true);
46
+ });
47
+ test('hide sets state value to false', () => {
48
+ out.hide();
49
+ expect(state.setState.isUnenrollConfirmVisible).toHaveBeenCalledWith(false);
50
+ });
51
+ });
52
+ });
53
+
54
+ describe('useEmailSettings', () => {
55
+ beforeEach(() => {
56
+ state.mockVals({ isEmailSettingsVisible: true });
57
+ out = hooks.useEmailSettings();
58
+ });
59
+ describe('behavior', () => {
60
+ it('initializes isEmailSettingsVisible state to false', () => {
61
+ state.expectInitializedWith(state.keys.isEmailSettingsVisible, false);
62
+ });
63
+ });
64
+ describe('output', () => {
65
+ test('state is loaded from current state value', () => {
66
+ expect(out.isVisible).toEqual(state.values.isEmailSettingsVisible);
67
+ });
68
+ test('show sets state value to true', () => {
69
+ out.show();
70
+ expect(state.setState.isEmailSettingsVisible).toHaveBeenCalledWith(true);
71
+ });
72
+ test('hide sets state value to false', () => {
73
+ out.hide();
74
+ expect(state.setState.isEmailSettingsVisible).toHaveBeenCalledWith(false);
75
+ });
76
+ });
77
+ });
78
+
79
+ describe('useHandleToggleDropdown', () => {
80
+ beforeEach(() => { out = hooks.useHandleToggleDropdown(cardId); });
81
+ describe('behavior', () => {
82
+ it('initializes course event tracker with event name and card ID', () => {
83
+ expect(reduxHooks.useTrackCourseEvent).toHaveBeenCalledWith(
84
+ track.course.courseOptionsDropdownClicked,
85
+ cardId,
86
+ );
87
+ });
88
+ });
89
+ describe('returned method', () => {
90
+ it('calls trackCourseEvent iff true is passed', () => {
91
+ out(false);
92
+ expect(trackCourseEvent).not.toHaveBeenCalled();
93
+ out(true);
94
+ expect(trackCourseEvent).toHaveBeenCalled();
95
+ });
96
+ });
97
+ });
98
+
99
+ describe('useOptionVisibility', () => {
100
+ const mockReduxHooks = (returnVals = {}) => {
101
+ reduxHooks.useCardSocialSettingsData.mockReturnValueOnce({
102
+ facebook: { isEnabled: !!returnVals.facebook?.isEnabled },
103
+ twitter: { isEnabled: !!returnVals.twitter?.isEnabled },
104
+ });
105
+ reduxHooks.useCardEnrollmentData.mockReturnValueOnce({
106
+ isEnrolled: !!returnVals.isEnrolled,
107
+ isEmailEnabled: !!returnVals.isEmailEnabled,
108
+ });
109
+ reduxHooks.useCardCertificateData.mockReturnValueOnce({
110
+ isEarned: !!returnVals.isEarned,
111
+ });
112
+ };
113
+ describe('shouldShowUnenrollItem', () => {
114
+ it('returns true if enrolled and not earned', () => {
115
+ mockReduxHooks({ isEnrolled: true });
116
+ expect(hooks.useOptionVisibility(cardId).shouldShowUnenrollItem).toEqual(true);
117
+ });
118
+ it('returns false if not enrolled', () => {
119
+ mockReduxHooks();
120
+ expect(hooks.useOptionVisibility(cardId).shouldShowUnenrollItem).toEqual(false);
121
+ });
122
+ it('returns false if enrolled but also earned', () => {
123
+ mockReduxHooks({ isEarned: true });
124
+ expect(hooks.useOptionVisibility(cardId).shouldShowUnenrollItem).toEqual(false);
125
+ });
126
+ });
127
+
128
+ describe('shouldShowDropdown', () => {
129
+ it('returns false if not enrolled and both email and socials are disabled', () => {
130
+ mockReduxHooks();
131
+ expect(hooks.useOptionVisibility(cardId).shouldShowDropdown).toEqual(false);
132
+ });
133
+ it('returns false if enrolled but already earned, and both email and socials are disabled', () => {
134
+ mockReduxHooks({ isEnrolled: true, isEarned: true });
135
+ expect(hooks.useOptionVisibility(cardId).shouldShowDropdown).toEqual(false);
136
+ });
137
+ it('returns true if either social is enabled', () => {
138
+ mockReduxHooks({ facebook: { isEnabled: true } });
139
+ expect(hooks.useOptionVisibility(cardId).shouldShowDropdown).toEqual(true);
140
+ mockReduxHooks({ twitter: { isEnabled: true } });
141
+ expect(hooks.useOptionVisibility(cardId).shouldShowDropdown).toEqual(true);
142
+ });
143
+ it('returns true if email is enabled', () => {
144
+ mockReduxHooks({ isEmailEnabled: true });
145
+ expect(hooks.useOptionVisibility(cardId).shouldShowDropdown).toEqual(true);
146
+ });
147
+ it('returns true if enrolled and not earned', () => {
148
+ mockReduxHooks({ isEnrolled: true });
149
+ expect(hooks.useOptionVisibility(cardId).shouldShowDropdown).toEqual(true);
150
+ });
151
+ });
152
+ });
153
+ });
@@ -0,0 +1,85 @@
1
+ import { useContext } from 'react';
2
+ import PropTypes from 'prop-types';
3
+
4
+ import { useIntl } from '@openedx/frontend-base';
5
+ import { Dropdown, Icon, IconButton } from '@openedx/paragon';
6
+ import { MoreVert } from '@openedx/paragon/icons';
7
+ import { StrictDict } from '@edx/react-unit-test-utils';
8
+
9
+ import MasqueradeUserContext from '../../../../data/contexts/MasqueradeUserContext';
10
+ import EmailSettingsModal from '../../../../containers/EmailSettingsModal';
11
+ import UnenrollConfirmModal from '../../../../containers/UnenrollConfirmModal';
12
+ import { reduxHooks } from '../../../../hooks';
13
+
14
+ import SocialShareMenu from './SocialShareMenu';
15
+ import {
16
+ useEmailSettings,
17
+ useUnenrollData,
18
+ useHandleToggleDropdown,
19
+ useOptionVisibility,
20
+ } from './hooks';
21
+
22
+ import messages from './messages';
23
+
24
+ export const testIds = StrictDict({
25
+ unenrollModalToggle: 'unenrollModalToggle',
26
+ });
27
+
28
+ export const CourseCardMenu = ({ cardId }) => {
29
+ const { formatMessage } = useIntl();
30
+
31
+ const emailSettings = useEmailSettings();
32
+ const unenrollModal = useUnenrollData();
33
+ const handleToggleDropdown = useHandleToggleDropdown(cardId);
34
+ const { shouldShowUnenrollItem, shouldShowDropdown } = useOptionVisibility(cardId);
35
+ const { isMasquerading } = useContext(MasqueradeUserContext);
36
+ const { isEmailEnabled } = reduxHooks.useCardEnrollmentData(cardId);
37
+
38
+ if (!shouldShowDropdown) {
39
+ return null;
40
+ }
41
+
42
+ return (
43
+ <>
44
+ <Dropdown onToggle={handleToggleDropdown}>
45
+ <Dropdown.Toggle
46
+ id={`course-actions-dropdown-${cardId}`}
47
+ as={IconButton}
48
+ src={MoreVert}
49
+ iconAs={Icon}
50
+ variant="primary"
51
+ alt={formatMessage(messages.dropdownAlt)}
52
+ />
53
+ <Dropdown.Menu>
54
+ {shouldShowUnenrollItem && (
55
+ <Dropdown.Item
56
+ disabled={isMasquerading}
57
+ onClick={unenrollModal.show}
58
+ data-testid={testIds.unenrollModalToggle}
59
+ >
60
+ {formatMessage(messages.unenroll)}
61
+ </Dropdown.Item>
62
+ )}
63
+ <SocialShareMenu cardId={cardId} emailSettings={emailSettings} />
64
+ </Dropdown.Menu>
65
+ </Dropdown>
66
+ <UnenrollConfirmModal
67
+ show={unenrollModal.isVisible}
68
+ closeModal={unenrollModal.hide}
69
+ cardId={cardId}
70
+ />
71
+ {isEmailEnabled && (
72
+ <EmailSettingsModal
73
+ show={emailSettings.isVisible}
74
+ closeModal={emailSettings.hide}
75
+ cardId={cardId}
76
+ />
77
+ )}
78
+ </>
79
+ );
80
+ };
81
+ CourseCardMenu.propTypes = {
82
+ cardId: PropTypes.string.isRequired,
83
+ };
84
+
85
+ export default CourseCardMenu;