@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
package/src/slots.tsx ADDED
@@ -0,0 +1,9 @@
1
+ import { SlotOperation } from '@openedx/frontend-base';
2
+
3
+ import { learnerDashboardHeaderApp } from './widgets/LearnerDashboardHeader';
4
+
5
+ const slots: SlotOperation[] = [
6
+ ...(learnerDashboardHeaderApp.slots as [])
7
+ ];
8
+
9
+ export default slots;
@@ -0,0 +1,255 @@
1
+ /* eslint-disable */
2
+ import React from 'react';
3
+ import * as redux from 'redux';
4
+ import { Provider } from 'react-redux';
5
+ import {
6
+ render,
7
+ waitFor,
8
+ } from '@testing-library/react';
9
+
10
+ import { IntlProvider } from '@openedx/frontend-base';
11
+
12
+ import { useFormatDate } from 'utils/hooks';
13
+
14
+ import api from 'data/services/lms/api';
15
+ import * as fakeData from 'data/services/lms/fakeData/courses';
16
+ import { RequestKeys, RequestStates } from 'data/constants/requests';
17
+ import reducers from 'data/redux';
18
+ import { selectors } from 'data/redux';
19
+ import { cardId as genCardId } from 'data/redux/app/reducer';
20
+
21
+ import messages from 'i18n';
22
+
23
+ import App from 'App';
24
+ import Inspector from './inspector';
25
+ import appMessages from './messages';
26
+
27
+ jest.unmock('@openedx/paragon');
28
+ jest.unmock('@openedx/paragon/icons');
29
+ jest.unmock('@openedx/frontend-base');
30
+ jest.unmock('react');
31
+ jest.unmock('react-redux');
32
+ jest.unmock('reselect');
33
+ jest.unmock('hooks');
34
+
35
+ jest.mock('slots/WidgetSidebarSlot', () => jest.fn(() => 'widget-sidebar'));
36
+
37
+ jest.mock('@openedx/frontend-base', () => ({
38
+ ...jest.requireActual('@edx/frontend-base'),
39
+ sendTrackEvent: jest.fn(),
40
+ getAuthenticatedHttpClient: jest.fn(),
41
+ getLoginRedirectUrl: jest.fn(),
42
+ useIntl: () => ({
43
+ formatMessage: jest.requireActual('testUtils').formatMessage,
44
+ formatDate: (date) => `Date-${date}`,
45
+ }),
46
+ logError: jest.fn(),
47
+ }));
48
+
49
+ jest.mock('utils/hooks', () => {
50
+ const formatDate = jest.fn(date => `Date-${date}`);
51
+ return {
52
+ formatDate,
53
+ useFormatDate: () => formatDate,
54
+ };
55
+ });
56
+
57
+
58
+ const configureStore = () => redux.createStore(
59
+ reducers,
60
+ );
61
+
62
+ let el;
63
+ let store;
64
+ let state;
65
+ let retryLink;
66
+ let inspector;
67
+
68
+ /**
69
+ * Simple wrapper for updating the top-level state variable, that also returns the new value
70
+ * @return {obj} - current redux store state
71
+ */
72
+ const getState = () => {
73
+ state = store.getState();
74
+ return state;
75
+ };
76
+
77
+ /**
78
+ * Object to be filled with resolve/reject functions for all controlled network comm channels
79
+ */
80
+ const resolveFns = {
81
+ };
82
+ /**
83
+ * Mock the api with jest functions that can be tested against.
84
+ */
85
+ const mockNetworkError = (reject) => () => reject(new Error({
86
+ response: { status: ErrorStatuses.badRequest },
87
+ }));
88
+
89
+ const mockForbiddenError = (reject) => () => reject(new Error({
90
+ response: { status: ErrorStatuses.forbidden },
91
+ }));
92
+
93
+
94
+ const allCourses = [
95
+ ...fakeData.courseRunData,
96
+ ...fakeData.entitlementData,
97
+ ];
98
+
99
+ const { compileCourseRunData, compileEntitlementData } = fakeData;
100
+
101
+ const initCourses = jest.fn(() => []);
102
+
103
+ let initializeApp;
104
+
105
+ const mockApi = () => {
106
+ api.initializeList = jest.fn(() => new Promise(
107
+ (resolve, reject) => {
108
+ resolveFns.init = {
109
+ success: () => {
110
+ const data = {
111
+ courses: initCourses(),
112
+ ...fakeData.globalData,
113
+ };
114
+ resolve({ data });
115
+ },
116
+ };
117
+ }));
118
+ };
119
+
120
+ /**
121
+ * load and configure the store, render the element, and populate the top-level state object
122
+ */
123
+ const renderEl = async () => {
124
+ store = configureStore();
125
+ el = await render(
126
+ <IntlProvider locale='en' messages={messages.en}>
127
+ <Provider store={store}>
128
+ <App />
129
+ </Provider>
130
+ </IntlProvider>,
131
+ );
132
+ getState();
133
+ };
134
+
135
+ const waitForEqual = async (valFn, expected, key) => waitFor(() => {
136
+ expect(valFn(), `${key} is expected to equal ${expected}`).toEqual(expected);
137
+ });
138
+ const waitForRequestStatus = (key, status) => waitForEqual(
139
+ () => getState().requests[key].status,
140
+ status,
141
+ key,
142
+ );
143
+
144
+ const loadApp = async (courses) => {
145
+ initCourses.mockReturnValue(courses.map(compileCourseRunData));
146
+ await renderEl();
147
+ inspector = new Inspector(el);
148
+ await waitForRequestStatus(RequestKeys.initialize, RequestStates.pending);
149
+ resolveFns.init.success();
150
+ await waitForRequestStatus(RequestKeys.initialize, RequestStates.completed);
151
+ }
152
+
153
+ const courseNames = [
154
+ 'course-name-1',
155
+ 'course-name-2',
156
+ 'course-name-3',
157
+ ];
158
+
159
+ describe('ESG app integration tests', () => {
160
+ beforeEach(() => {
161
+ mockApi();
162
+ });
163
+
164
+ test('initialization', async () => {
165
+ await loadApp([{ courseName: courseNames[0] }]);
166
+ });
167
+
168
+ describe('course cards', () => {
169
+ const courseNames = [
170
+ 'course-name-0',
171
+ 'course-name-1',
172
+ 'course-name-2',
173
+ ];
174
+ const testCourse = async (index, tests) => {
175
+ await getState();
176
+ const cards = inspector.get.courseCards;
177
+ const card = cards.at(index);
178
+ const cardId = genCardId(index);
179
+ const cardDetails = inspector.get.card.details(card);
180
+ const courseData = selectors.app.courseCard.course(state, cardId);
181
+ const { courseName } = selectors.app.courseCard.course(state, cardId);
182
+ inspector.verifyText(inspector.get.card.header(card), courseName);
183
+ if (tests.length > index) {
184
+ tests[index]({ cardId, cardDetails });
185
+ }
186
+ }
187
+
188
+ const loadCourse = async (course) => {
189
+ await loadApp([course].map(compileCourseRunData));
190
+ await waitForRequestStatus(RequestKeys.initialize, RequestStates.pending);
191
+ resolveFns.init.success();
192
+ await waitForRequestStatus(RequestKeys.initialize, RequestStates.completed);
193
+ };
194
+
195
+ describe('audit courses', () => {
196
+ test('audit', async () => {
197
+ const courses = [
198
+ { courseName: courseNames[0] }, // audit, course run not started
199
+ {
200
+ courseName: courseNames[1],
201
+ enrollment: {
202
+ coursewareAccess: {
203
+ isTooEarly: true,
204
+ hasUnmetPrerequisites: false,
205
+ isStaff: false,
206
+ },
207
+ },
208
+ }, // audit, course run not started, is too early
209
+ {
210
+ courseName: courseNames[2],
211
+ courseRun: {
212
+ courseRun: { isStarted: true },
213
+ },
214
+ enrollment: {
215
+ accessExpirationDate: fakeData.pastDate,
216
+ canUpgrade: false,
217
+ isAuditAccessExpired: true,
218
+ hasStarted: true,
219
+ },
220
+ }, // audit, course run and learner started, access expired, cannot upgrade
221
+ ];
222
+ const formatDate = useFormatDate();
223
+ await loadApp(courses);
224
+ await testCourse(0, [
225
+ ({ cardId, cardDetails }) => {
226
+ const enrollment = selectors.app.courseCard.enrollment(state, cardId);
227
+ const courseRun = selectors.app.courseCard.courseRun(state, cardId);
228
+ const courseProvider = selectors.app.courseCard.courseProvider(state, cardId);
229
+ const course = selectors.app.courseCard.course(state, cardId);
230
+ expect(enrollment.isAudit).toEqual(true);
231
+ expect(courseRun.isStarted).toEqual(false);
232
+ expect(enrollment.canUpgrade).toEqual(true);
233
+ [
234
+ courseProvider.name,
235
+ course.courseNumber,
236
+ appMessages.withValues.CourseCardDetails.courseStarts({
237
+ startDate: formatDate(new Date(courseRun.startDate)),
238
+ }),
239
+ ].forEach(value => inspector.verifyTextIncludes(cardDetails, value));
240
+ },
241
+ ]);
242
+ await testCourse(1, [
243
+ ({ cardId, cardDetails }) => {
244
+ const enrollment = selectors.app.courseCard.enrollment(state, cardId);
245
+ const courseRun = selectors.app.courseCard.courseRun(state, cardId);
246
+ expect(enrollment.isAudit).toEqual(true);
247
+ expect(courseRun.isStarted).toEqual(false);
248
+ expect(enrollment.coursewareAccess.isTooEarly).toEqual(true);
249
+ expect(enrollment.hasAccess).toEqual(false);
250
+ },
251
+ ]);
252
+ });
253
+ });
254
+ });
255
+ });
@@ -0,0 +1,50 @@
1
+ /* eslint-disable import/no-extraneous-dependencies */
2
+ import { within } from '@testing-library/react';
3
+
4
+ // import fakeData from 'data/services/lms/fakeData';
5
+ // import { gradingStatusTransform } from 'data/redux/grading/selectors/selected';
6
+
7
+ // import appMessages from './messages';
8
+
9
+ /**
10
+ * App inspector class providing methods to return elements from within
11
+ * the virtual DOM
12
+ * @props {Root Node} el - Root app render node.
13
+ */
14
+ class Inspector {
15
+ constructor(el) {
16
+ this.el = el;
17
+ this.getByRole = this.el.getByRole;
18
+ this.getByText = this.el.getByText;
19
+ this.getByLabelText = this.el.getByLabelText;
20
+ this.findByText = this.el.findByText;
21
+ this.findByLabelText = this.el.findByLabelText;
22
+ }
23
+
24
+ get get() {
25
+ return {
26
+ courseCards: this.el.getAllByTestId('CourseCard'),
27
+ card: {
28
+ header: (card) => within(card).getByTestId('CourseCardTitle'),
29
+ details: (card) => within(card).getByTestId('CourseCardDetails'),
30
+ // banners: (card) => within(card).getByTestId('CourseCardBanners'),
31
+ // programsBadge: (card) => within(card).getByTestId('RelatedProgramsBadge'),
32
+ // actions: (card) => within(card).getByTestId('CourseCardActions'),
33
+ },
34
+ };
35
+ }
36
+
37
+ /**
38
+ * Returns promises for attempting to find elements within the DOM
39
+ */
40
+ get find() {
41
+ return {
42
+ };
43
+ }
44
+
45
+ verifyText = (el, text) => within(el).getByText(text);
46
+
47
+ verifyTextIncludes = (el, text) => within(el).getByText(text, { exact: false });
48
+ }
49
+
50
+ export default Inspector;
@@ -0,0 +1,29 @@
1
+ import CourseCardDetails from 'containers/CourseCard/components/CourseCardDetails/messages';
2
+
3
+ const mapMessages = (messages) => Object.keys(messages).reduce(
4
+ (acc, key) => ({ ...acc, [key]: messages[key].defaultMessage }),
5
+ {},
6
+ );
7
+
8
+ const mapMessagesWithValues = (messages) => Object.keys(messages).reduce(
9
+ (acc, key) => ({
10
+ ...acc,
11
+ [key]: (values) => {
12
+ let message = messages[key].defaultMessage;
13
+ if (values) {
14
+ Object.keys(values).forEach(valueKey => {
15
+ message = message.replaceAll(`{${valueKey}}`, values[valueKey]);
16
+ });
17
+ }
18
+ return message;
19
+ },
20
+ }),
21
+ {},
22
+ );
23
+
24
+ export default {
25
+ CourseCardDetails: mapMessages(CourseCardDetails),
26
+ withValues: {
27
+ CourseCardDetails: mapMessagesWithValues(CourseCardDetails),
28
+ },
29
+ };
@@ -0,0 +1,3 @@
1
+ export const mockSuccess = (returnValFn) => (...args) => Promise.resolve(returnValFn(...args));
2
+
3
+ export const mockFailure = (returnValFn) => (...args) => Promise.reject(returnValFn(...args));
@@ -0,0 +1,211 @@
1
+ import react from 'react';
2
+
3
+ import { StrictDict } from 'utils';
4
+
5
+ /**
6
+ * Mocked formatMessage provided by react-intl
7
+ */
8
+ export const formatMessage = (msg, values) => {
9
+ let message = msg.defaultMessage;
10
+ if (values === undefined) {
11
+ return message;
12
+ }
13
+ // check if value is not a primitive type.
14
+ if (Object.values(values).filter(value => Object(value) === value).length) {
15
+ return <format-message-function {...{ message: msg, values }} />;
16
+ }
17
+ Object.keys(values).forEach((key) => {
18
+ message = message.replaceAll(`{${key}}`, values[key]);
19
+ });
20
+ return message;
21
+ };
22
+
23
+ /**
24
+ * Mock a single component, or a nested component so that its children render nicely
25
+ * in snapshots.
26
+ * @param {string} name - parent component name
27
+ * @param {obj} contents - object of child components with intended component
28
+ * render name.
29
+ * @return {func} - mock component with nested children.
30
+ *
31
+ * usage:
32
+ * mockNestedComponent('Card', { Body: 'Card.Body', Form: { Control: { Feedback: 'Form.Control.Feedback' }}... });
33
+ * mockNestedComponent('IconButton', 'IconButton');
34
+ */
35
+ export const mockNestedComponent = (name, contents) => {
36
+ if (typeof contents === 'function') {
37
+ return contents();
38
+ }
39
+ if (typeof contents !== 'object') {
40
+ return contents;
41
+ }
42
+ const fn = () => name;
43
+ Object.defineProperty(fn, 'name', { value: name });
44
+ Object.keys(contents).forEach((nestedName) => {
45
+ const value = contents[nestedName];
46
+ fn[nestedName] = typeof value !== 'object'
47
+ ? value
48
+ : mockNestedComponent(`${name}.${nestedName}`, value);
49
+ });
50
+ return fn;
51
+ };
52
+
53
+ /**
54
+ * Mock a module of components. nested components will be rendered nicely in snapshots.
55
+ * @param {obj} mapping - component module mock config.
56
+ * @return {obj} - module of flat and nested components that will render nicely in snapshots.
57
+ * usage:
58
+ * mockNestedComponents({
59
+ * Card: { Body: 'Card.Body' },
60
+ * IconButton: 'IconButton',
61
+ * })
62
+ */
63
+ export const mockNestedComponents = (mapping) => Object.entries(mapping).reduce(
64
+ (obj, [name, value]) => ({
65
+ ...obj,
66
+ [name]: mockNestedComponent(name, value),
67
+ }),
68
+ {},
69
+ );
70
+
71
+ /**
72
+ * Mock utility for working with useState in a hooks module.
73
+ * Expects/requires an object containing the state object in order to ensure
74
+ * the mock behavior works appropriately.
75
+ *
76
+ * Expected format:
77
+ * hooks = { state: { <key>: (val) => React.createRef(val), ... } }
78
+ *
79
+ * Returns a utility for mocking useState and providing access to specific state values
80
+ * and setState methods, as well as allowing per-test configuration of useState value returns.
81
+ *
82
+ * Example usage:
83
+ * // hooks.js
84
+ * import * as module from './hooks';
85
+ * const state = {
86
+ * isOpen: (val) => React.useState(val),
87
+ * hasDoors: (val) => React.useState(val),
88
+ * selected: (val) => React.useState(val),
89
+ * };
90
+ * ...
91
+ * export const exampleHook = () => {
92
+ * const [isOpen, setIsOpen] = module.state.isOpen(false);
93
+ * if (!isOpen) { return null; }
94
+ * return { isOpen, setIsOpen };
95
+ * }
96
+ * ...
97
+ *
98
+ * // hooks.test.js
99
+ * import * as hooks from './hooks';
100
+ * const state = new MockUseState(hooks)
101
+ * ...
102
+ * describe('state hooks', () => {
103
+ * state.testGetter(state.keys.isOpen);
104
+ * state.testGetter(state.keys.hasDoors);
105
+ * state.testGetter(state.keys.selected);
106
+ * });
107
+ * describe('exampleHook', () => {
108
+ * beforeEach(() => { state.mock(); });
109
+ * it('returns null if isOpen is default value', () => {
110
+ * expect(hooks.exampleHook()).toEqual(null);
111
+ * });
112
+ * it('returns isOpen and setIsOpen if isOpen is not null', () => {
113
+ * state.mockVal(state.keys.isOpen, true);
114
+ * expect(hooks.exampleHook()).toEqual({
115
+ * isOpen: true,
116
+ * setIsOpen: state.setState[state.keys.isOpen],
117
+ * });
118
+ * });
119
+ * afterEach(() => { state.restore(); });
120
+ * });
121
+ *
122
+ * @param {obj} hooks - hooks module containing a 'state' object
123
+ */
124
+ export class MockUseState {
125
+ constructor(hooks) {
126
+ this.hooks = hooks;
127
+ this.oldState = null;
128
+ this.setState = {};
129
+ this.stateVals = {};
130
+
131
+ this.mock = this.mock.bind(this);
132
+ this.restore = this.restore.bind(this);
133
+ this.mockVal = this.mockVal.bind(this);
134
+ this.testGetter = this.testGetter.bind(this);
135
+ }
136
+
137
+ /**
138
+ * @return {object} - StrictDict of state object keys
139
+ */
140
+ get keys() {
141
+ return StrictDict(Object.keys(this.hooks.state).reduce(
142
+ (obj, key) => ({ ...obj, [key]: key }),
143
+ {},
144
+ ));
145
+ }
146
+
147
+ /**
148
+ * Replace the hook module's state object with a mocked version, initialized to default values.
149
+ */
150
+ mock() {
151
+ this.oldState = this.hooks.state;
152
+ Object.keys(this.keys).forEach(key => {
153
+ this.hooks.state[key] = jest.fn(val => {
154
+ this.stateVals[key] = val;
155
+ return [val, this.setState[key]];
156
+ });
157
+ });
158
+ this.setState = Object.keys(this.keys).reduce(
159
+ (obj, key) => ({
160
+ ...obj,
161
+ [key]: jest.fn(val => {
162
+ this.hooks.state[key] = val;
163
+ }),
164
+ }),
165
+ {},
166
+ );
167
+ }
168
+
169
+ expectInitializedWith(key, value) {
170
+ expect(this.hooks.state[key]).toHaveBeenCalledWith(value);
171
+ }
172
+
173
+ expectSetStateCalledWith(key, value) {
174
+ expect(this.setState[key]).toHaveBeenCalledWith(value);
175
+ }
176
+
177
+ /**
178
+ * Restore the hook module's state object to the actual code.
179
+ */
180
+ restore() {
181
+ this.hooks.state = this.oldState;
182
+ }
183
+
184
+ /**
185
+ * Mock the state getter associated with a single key to return a specific value one time.
186
+ * @param {string} key - state key (from this.keys)
187
+ * @param {any} val - new value to be returned by the useState call.
188
+ */
189
+ mockVal(key, val) {
190
+ this.hooks.state[key].mockReturnValueOnce([val, this.setState[key]]);
191
+ }
192
+
193
+ testGetter(key) {
194
+ test(`${key} state getter should return useState passthrough`, () => {
195
+ const testValue = 'some value';
196
+ const useState = (val) => ({ useState: val });
197
+ jest.spyOn(react, 'useState').mockImplementationOnce(useState);
198
+ expect(this.hooks.state[key](testValue)).toEqual(useState(testValue));
199
+ });
200
+ }
201
+
202
+ get values() {
203
+ return StrictDict({ ...this.hooks.state });
204
+ }
205
+ }
206
+
207
+ export const mockLocation = (href) => {
208
+ delete global.window.location;
209
+ global.window = Object.create(window);
210
+ global.window.location = { href };
211
+ };
@@ -0,0 +1,50 @@
1
+ import { StrictDict } from '../utils';
2
+
3
+ export const categories = StrictDict({
4
+ dashboard: 'dashboard',
5
+ userEngagement: 'user-engagement',
6
+ searchButton: 'search_button',
7
+ credit: 'credit',
8
+ filter: 'filter',
9
+ });
10
+
11
+ export const events = StrictDict({
12
+ enterCourseClicked: 'enterCourseClicked',
13
+ courseImageClicked: 'courseImageClicked',
14
+ courseTitleClicked: 'courseTitleClicked',
15
+ courseOptionsDropdownClicked: 'courseOptionsDropdownClicked',
16
+ shareClicked: 'shareClicked',
17
+ userSettingsChanged: 'userSettingsChanged',
18
+ newSession: 'newSession',
19
+ switchSession: 'switchSession',
20
+ leaveSession: 'leaveSession',
21
+ unenrollReason: 'unenrollReason',
22
+ entitlementUnenrollReason: 'entitlementUnenrollReason',
23
+ });
24
+
25
+ export const eventNames = StrictDict({
26
+ enterCourseClicked: 'edx.bi.dashboard.enter_course.clicked',
27
+ courseImageClicked: 'edx.bi.dashboard.course_image.clicked',
28
+ courseTitleClicked: 'edx.bi.dashboard.course_title.clicked',
29
+ courseOptionsDropdownClicked: 'edx.bi.dashboard.course_options_dropdown.clicked',
30
+ shareClicked: 'edx.course.share_clicked',
31
+ userSettingsChanged: 'edx.user.settings.changed',
32
+ newSession: 'course-dashboard.new-session',
33
+ switchSession: 'course-dashboard.switch-session',
34
+ leaveSession: 'course-dashboard.leave-session',
35
+ unenrollReason: 'unenrollment_reason.selected',
36
+ entitlementUnenrollReason: 'entitlement_unenrollment_reason.selected',
37
+ findCoursesClicked: 'edx.bi.dashboard.find_courses_button.clicked',
38
+ purchaseCredit: 'edx.bi.credit.clicked_purchase_credit',
39
+ filterClicked: 'course-dashboard.filter.clicked',
40
+ filterOptionSelected: 'course-dashboard.filter_option.selected',
41
+ });
42
+
43
+ export const linkNames = StrictDict({
44
+ learnerHomeNavExplore: 'learner_home_nav_explore',
45
+ learnerHomeNavDropdownExplore: 'learner_home_nav_dropdown_explore',
46
+ });
47
+
48
+ export const appName = 'learner-home';
49
+
50
+ export default eventNames;
@@ -0,0 +1,17 @@
1
+ import course from './trackers/course';
2
+ import credit from './trackers/credit';
3
+ import engagement from './trackers/engagement';
4
+ import entitlements from './trackers/entitlements';
5
+ import socialShare from './trackers/socialShare';
6
+ import findCourses from './trackers/findCourses';
7
+ import filter from './trackers/filter';
8
+
9
+ export default {
10
+ course,
11
+ credit,
12
+ engagement,
13
+ entitlements,
14
+ socialShare,
15
+ findCourses,
16
+ filter,
17
+ };
@@ -0,0 +1,44 @@
1
+ import { createEventTracker, createLinkTracker } from '../../data/services/segment/utils';
2
+ import { categories, eventNames } from '../constants';
3
+ import * as module from './course';
4
+
5
+ // Utils/Helpers
6
+ /**
7
+ * Generate a segement event tracker for a given course event.
8
+ * @param {string} eventName - segment event name
9
+ * @param {string} courseId - course run identifier
10
+ * @param {[object]} options - optional event data
11
+ */
12
+ export const courseEventTracker = (eventName, courseId, options = {}) => createEventTracker(
13
+ eventName,
14
+ { category: categories.dashboard, label: courseId, ...options },
15
+ );
16
+ /**
17
+ * Generate a hook to allow components to provide a courseId and link href and provide
18
+ * a link tracker with defined event name and options, over a set of default optiosn.
19
+ * @param {string} eventName - event name for the click event
20
+ * @return {callback} - component hook returning a link tracking event callback
21
+ */
22
+ export const courseLinkTracker = (eventName) => (courseId, href) => (
23
+ createLinkTracker(module.courseEventTracker(eventName, courseId), href)
24
+ );
25
+
26
+ // Non-Link events
27
+ export const courseOptionsDropdownClicked = (courseId) => (
28
+ module.courseEventTracker(eventNames.courseOptionsDropdownClicked, courseId)
29
+ );
30
+
31
+ // Link events (track and then change page location)
32
+ export const courseImageClicked = (...args) => (
33
+ module.courseLinkTracker(eventNames.courseImageClicked)(...args));
34
+ export const courseTitleClicked = (...args) => (
35
+ module.courseLinkTracker(eventNames.courseTitleClicked)(...args));
36
+ export const enterCourseClicked = (...args) => (
37
+ module.courseLinkTracker(eventNames.enterCourseClicked)(...args));
38
+
39
+ export default {
40
+ courseImageClicked,
41
+ courseOptionsDropdownClicked,
42
+ courseTitleClicked,
43
+ enterCourseClicked,
44
+ };