Compare commits

...

245 Commits

Author SHA1 Message Date
Jason Wesson
3663efb283 feat!: remove custom header settings 2025-06-03 18:56:32 +00:00
renovate[bot]
9ef5840700 fix(deps): update dependency @edx/frontend-component-footer to v14.8.0 (#632)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-02 08:44:31 +00:00
renovate[bot]
3c8c92ab92 fix(deps): update dependency @edx/frontend-platform to v8.3.8 (#631)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-02 05:44:31 +00:00
renovate[bot]
7488fe55f0 chore(deps): update dependency @openedx/frontend-build to v14.6.0 (#610)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Jason Wesson <jsnwesson@gmail.com>
2025-05-28 20:40:21 +00:00
renovate[bot]
fe6c726306 fix(deps): update dependency core-js to v3.42.0 (#630)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-26 12:30:22 +00:00
renovate[bot]
8c3d62c12b fix(deps): update dependency @openedx/paragon to v22.18.1 (#629)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-26 05:35:25 +00:00
diana-villalvazo-wgu
66794acf17 feat: add advertised start date on course card (#619) 2025-05-23 15:51:37 -04:00
Maxwell Frank
2f3f3bcd8b fix: email banner bug (#628) 2025-05-23 13:49:01 -04:00
diana-villalvazo-wgu
dcab4f1b75 feat: add NON_BROWSABLE_COURSES to MFE config (#620) 2025-05-21 13:21:31 -04:00
renovate[bot]
66fdd79bdf fix(deps): update dependency @edx/frontend-platform to v8.3.7 (#623)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-19 13:48:56 +00:00
renovate[bot]
0ea9f6d193 fix(deps): update dependency @reduxjs/toolkit to v2.8.2 (#622)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-19 13:33:24 +00:00
renovate[bot]
fee6a26366 fix(deps): update dependency @edx/frontend-component-footer to v14.7.2 (#621)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-19 06:54:58 +00:00
Justin Hynes
1301fddbc9 Audit usage of header elements in Learner Dashboard (#618) 2025-05-16 12:57:24 -04:00
diana-villalvazo-wgu
5dcd596ac9 chore: change heading for confirm email 2025-05-16 09:49:14 -06:00
diana-villalvazo-wgu
a5522faa42 test: update related tests 2025-05-16 09:27:30 -06:00
diana-villalvazo-wgu
3542c38472 fix: incorrect usage of header elements 2025-05-16 09:27:30 -06:00
Victor Navarro
14c03d8461 fix: add missing translation for notices not found (#612) 2025-05-15 11:57:21 -04:00
diana-villalvazo-wgu
5562d8a339 chore: Clean up learnerDashboardHeader files (#614) 2025-05-13 12:23:20 -03:00
diana-villalvazo-wgu
a9194261c8 chore: Replace query-string with URLSearchParams (#613)
Co-authored-by: diana-villalvazo-wgu <dianaximena.villalva@wgu.edu>
2025-05-13 12:12:06 -03:00
renovate[bot]
11a7512fea fix(deps): update dependency @edx/openedx-atlas to ^0.7.0 (#616)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-12 11:15:48 +00:00
renovate[bot]
be620a80fa fix(deps): update dependency @edx/frontend-platform to v8.3.6 (#615)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-12 07:56:10 +00:00
Edward Zarecor
1d9eb08e59 E0d/i18n fixes (#600)
Co-authored-by: I18n Fix Bot <i18n-fix@example.com>
Co-authored-by: Deborah Kaplan <deborahgu@users.noreply.github.com>
2025-05-09 20:14:57 +00:00
renovate[bot]
05e9626e57 fix(deps): update dependency @edx/frontend-component-footer to v14.7.1 (#611)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-05 05:38:36 +00:00
Brian Smith
fe386e31ee feat: import FooterSlot from component package instead of slot package (#604) 2025-04-24 12:25:32 -04:00
Brian Smith
cb1de82f0a feat: standardize slot ids (#608) 2025-04-24 07:47:52 -04:00
renovate[bot]
2337843d54 fix(deps): update dependency @edx/frontend-component-header to v6.4.0 (#607)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-23 20:17:47 +00:00
renovate[bot]
70da0d38ed fix(deps): update dependency @edx/frontend-component-header to v6.3.0 (#606)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-21 10:26:20 +00:00
renovate[bot]
154a2583f6 fix(deps): update dependency @edx/frontend-component-footer to v14.6.0 (#605)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-21 07:27:36 +00:00
renovate[bot]
633050739e fix(deps): update dependency @edx/frontend-component-footer to v14.4.0 (#602)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-14 12:28:07 +00:00
renovate[bot]
61d24d29f1 chore(deps): update dependency @openedx/frontend-build to v14.5.0 (#601)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-14 07:11:40 +00:00
Ivo Branco
a210f23c9f fix: unenroll reasons translation
The unenroll reasons select wasn't being translated.
2025-04-09 10:17:21 -04:00
renovate[bot]
b16908842e fix(deps): update dependency @edx/frontend-platform to v8.3.4 (#596)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Deborah Kaplan <deborahgu@users.noreply.github.com>
2025-04-08 09:47:48 -04:00
renovate[bot]
b80cab7a66 chore(deps): update dependency @openedx/frontend-build to v14.4.2 (#595)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-08 09:42:55 -04:00
Brian Smith
6b8cd1f780 feat: upgrade to react 18 (#593) 2025-04-04 10:24:46 -04:00
Régis Behmo
78c5d73900 chore: remove husky 🪓🐶 (#594) 2025-04-03 09:17:58 -04:00
renovate[bot]
eb3fc9412d fix(deps): update dependency @openedx/paragon to v22.17.0 (#592)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-31 10:52:17 +00:00
renovate[bot]
3caf6fd67a chore(deps): update dependency @openedx/frontend-build to v14.4.1 (#591)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-31 06:45:28 +00:00
Brian Smith
d48cb3d9fc chore(deps): update @openedx dependencies to versions that support React 18 (#590) 2025-03-27 16:16:48 -04:00
Maxwell Frank
d3b4a7fc84 feat: remove upgrade refs course banner (#585) 2025-03-26 09:48:14 -04:00
Maxwell Frank
9e63777c5c fix: CourseBanner slot readme (#589) 2025-03-25 11:36:10 -04:00
renovate[bot]
cf2f3acc51 fix(deps): update dependency @openedx/frontend-slot-footer to v1.1.0 (#588)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-24 06:45:05 +00:00
Maxwell Frank
54f8bc86e3 fix: rename course banner slot (#586) 2025-03-21 11:08:32 -04:00
renovate[bot]
10961010ba fix(deps): update dependency @openedx/frontend-plugin-framework to v1.6.0 (#584)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-17 05:59:50 +00:00
Kira Miller
3c1b749395 fix: changing slot id 2025-03-11 08:39:12 -06:00
Kira Miller
845ee09bf2 fix: PR requests 2025-03-11 08:39:12 -06:00
Kira Miller
1efec09f44 fix: PR requests 2025-03-11 08:39:12 -06:00
Kira Miller
aa1cae5200 fix: pr requests 2025-03-11 08:39:12 -06:00
Kira Miller
77ab48c59f fix: renaming slot 2025-03-11 08:39:12 -06:00
Kira Miller
5d2b33abd3 feat: adding new plugin slot for an enterprise modal 2025-03-11 08:39:12 -06:00
renovate[bot]
dd4f61eec3 fix(deps): update dependency @edx/frontend-enterprise-hotjar to v7.2.0 (#581)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-10 12:27:57 +00:00
renovate[bot]
8f7580ec30 chore(deps): update dependency @openedx/frontend-build to v14.3.2 (#580)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-10 05:34:46 +00:00
Ankush Chudiwal
fbf24e42d3 fix: broken skip link in Learner Dashboard (#522)
Co-authored-by: Deborah Kaplan <deborahgu@users.noreply.github.com>
2025-03-03 14:52:33 -05:00
Jason Wesson
e764e9c502 Revert "feat: adding new plugin slot for an enterprise modal"
This reverts commit f110a0ade8.
2025-03-03 12:40:17 -06:00
Jason Wesson
13721f2770 Revert "fix: renaming slot"
This reverts commit 6a43918b56.
2025-03-03 12:40:17 -06:00
Jason Wesson
960647ce9f Revert "fix: pr requests"
This reverts commit 86fd29309a.
2025-03-03 12:40:17 -06:00
Jason Wesson
44c797854f Revert "fix: PR requests"
This reverts commit 57d09af61d.
2025-03-03 12:40:17 -06:00
Jason Wesson
3ea088e411 Revert "fix: PR requests"
This reverts commit 76783133da.
2025-03-03 12:40:17 -06:00
Jason Wesson
4a18c890c3 Revert "fix: changing slot id"
This reverts commit b26d4632c9.
2025-03-03 12:40:17 -06:00
renovate[bot]
e1c1c51704 chore(deps): update dependency @openedx/frontend-build to v14.3.1 (#578)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-03 10:39:48 +00:00
renovate[bot]
f83f3a1850 fix(deps): update dependency @openedx/paragon to v22.15.3 (#577)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-03 05:37:49 +00:00
Kira Miller
b26d4632c9 fix: changing slot id 2025-02-27 11:28:33 -06:00
Kira Miller
76783133da fix: PR requests 2025-02-27 11:28:33 -06:00
Kira Miller
57d09af61d fix: PR requests 2025-02-27 11:28:33 -06:00
Kira Miller
86fd29309a fix: pr requests 2025-02-27 11:28:33 -06:00
Kira Miller
6a43918b56 fix: renaming slot 2025-02-27 11:28:33 -06:00
Kira Miller
f110a0ade8 feat: adding new plugin slot for an enterprise modal 2025-02-27 11:28:33 -06:00
Feanil Patel
93bd883a01 Update catalog-info file for release data (#570) 2025-02-26 09:07:11 -05:00
salman2013
61375c9e95 chore: update catalog-info-file for release data and remove openedx.yaml file 2025-02-24 15:36:50 -05:00
renovate[bot]
c2f4be5063 fix(deps): update dependency @openedx/paragon to v22.15.2 (#574)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-24 11:48:06 +00:00
renovate[bot]
14bde7fc3f fix(deps): update dependency @edx/frontend-component-header to v5.8.3 (#573)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-24 07:03:02 +00:00
Maxim Beder
b5e2a94480 test: update test snapshots 2025-02-14 11:51:25 -08:00
Maxim Beder
edcf2fd756 fix: course image height on IOS Safari
Course thumbnails on IOS Safari stretch to the full height of the image,
instead of being limited by width and preserving aspect ratio. This
seems to be a IOS Safari specific behavior[1].

Learner dashboard MFE uses a custom implementation of CourseCardImage,
because the one in Paragon currently doesn't allow the image to be
clickable. Because of that, we are fixing this issue in this repo for
now, instead of fixing it in Paragon, until Paragon updates their
implementation and this repo is updated to use a newer version of
Paragon.

1: https://stackoverflow.com/a/44250830
2025-02-14 11:51:25 -08:00
Maxwell Frank
9228d017af feat: course banner slot (#559) 2025-02-12 14:13:31 -05:00
renovate[bot]
1104c58611 fix(deps): update dependency react-router-dom to v6.29.0 (#561)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-03 08:57:13 +00:00
renovate[bot]
3d7366ac1d fix(deps): update dependency @openedx/paragon to v22.15.1 (#560)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-03 05:15:00 +00:00
renovate[bot]
0f19ff9a02 fix(deps): update dependency @reduxjs/toolkit to v2.5.1 (#556)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-27 10:02:08 +00:00
renovate[bot]
a21caead92 fix(deps): update dependency @openedx/paragon to v22.14.0 (#557)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-27 05:56:20 +00:00
renovate[bot]
2b287c6332 chore(deps): update dependency @edx/browserslist-config to v1.5.0 (#552)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-20 09:40:34 +00:00
renovate[bot]
8b67abd304 fix(deps): update dependency react-router-dom to v6.28.2 (#551)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-20 05:03:34 +00:00
Maxwell Frank
abae82b507 fix: remove remaining UpgradeButton definition and tests (#548) 2025-01-14 14:36:38 -05:00
Maxwell Frank
777d3aa45c feat!: remove UpgradeButton (#536) 2025-01-13 13:50:50 -05:00
renovate[bot]
ce595d0e62 fix(deps): update dependency react-router-dom to v6.28.1 (#544)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-13 13:52:09 +00:00
renovate[bot]
0fd242eb74 fix(deps): update dependency core-js to v3.40.0 (#543)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-13 08:36:11 +00:00
renovate[bot]
d2215570da fix(deps): update dependency @edx/frontend-platform to v8.1.5 (#542)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-13 05:04:41 +00:00
paulbert
b6bef24ace refactor!: Remove ZendeskFab component
Deletes the ZendeskFab component and associated mock, removes react-zendesk package, and removes env variables for Zendesk
2025-01-08 14:07:21 -08:00
renovate[bot]
bb5a2aa3fd fix(deps): update dependency @reduxjs/toolkit to v2.5.0 (#540)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-30 09:27:09 +00:00
renovate[bot]
77d1ba93c3 fix(deps): update dependency @openedx/paragon to v22.13.0 (#539)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-30 06:39:30 +00:00
renovate[bot]
4aa786c595 fix(deps): update dependency @openedx/frontend-plugin-framework to v1.4.1 (#538)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-23 10:23:13 +00:00
renovate[bot]
a5ff2eceae chore(deps): update dependency @edx/browserslist-config to v1.4.0 (#537)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-23 06:34:26 +00:00
renovate[bot]
84b281aa51 fix(deps): update dependency @edx/frontend-platform to v8.1.3 (#534)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-16 10:29:28 +00:00
renovate[bot]
dc5c655314 fix(deps): update dependency @edx/frontend-component-header to v5.8.2 (#533)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-16 06:45:43 +00:00
Brian Smith
2140d8821d chore: add dev script to package.json (#530) 2024-12-13 12:29:02 -05:00
Deborah Kaplan
63860e95ce chore: removing send mail on failure gh action
This action frequently fails, and the current maintainers
(@openedx/2U-aperture) don't require it, as we both monitor the success
of the workflows and we have workflow failure email notifications
enabled. After discussion with Axim, we are removing the  action. It can
always be added back (and potentially debugged) later if another
maintainer would benefit from it.

FIXES: APER-3814
2024-12-09 12:57:47 -08:00
renovate[bot]
1474c4c546 chore(deps): update dependency jest-when to v3.7.0 (#527)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-09 11:45:54 +00:00
renovate[bot]
e2e51dc030 chore(deps): update dependency @openedx/frontend-build to v14.2.2 (#526)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-09 08:22:06 +00:00
Adolfo R. Brandes
604298eaca fix: Use browserslist-config
We were installing browserslist-config but not declaring it.  This had
the effect that webpack - and likely others - were not using it.
2024-12-06 11:08:11 -03:00
renovate[bot]
f9d13c4058 fix(deps): update dependency @edx/frontend-component-header to v5.8.1 (#518)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-02 11:07:31 +00:00
renovate[bot]
e1db6807ef fix(deps): update dependency @openedx/frontend-slot-footer to v1.0.7 (#517)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-02 07:43:53 +00:00
renovate[bot]
d8e1f82bdf fix(deps): update dependency @edx/frontend-enterprise-hotjar to v7 2024-11-26 15:49:09 -06:00
dependabot[bot]
c5a78e01f2 build(deps): bump dawidd6/action-send-mail from 3 to 4
Bumps [dawidd6/action-send-mail](https://github.com/dawidd6/action-send-mail) from 3 to 4.
- [Release notes](https://github.com/dawidd6/action-send-mail/releases)
- [Commits](https://github.com/dawidd6/action-send-mail/compare/v3...v4)

---
updated-dependencies:
- dependency-name: dawidd6/action-send-mail
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-26 15:43:43 -06:00
renovate[bot]
22e4b9facc chore(deps): update dependency @openedx/frontend-build to v14.2.0 (#514)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-26 18:36:14 +00:00
renovate[bot]
1ae555eac9 chore(deps): update dependency husky to v9.1.7 (#513)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-25 06:29:05 +00:00
Diana Olarte
a0e5f75f0b fix: apply feedback 2024-11-21 09:10:34 -05:00
Diana Olarte
2e101d5c23 fix: display programs tab only if it is configured 2024-11-21 09:10:34 -05:00
Deborah Kaplan
ce1848a5c3 Revert "fix: display programs only if the url is configured (#479)" (#504) 2024-11-18 14:45:16 -05:00
Deborah Kaplan
ee515ad666 Revert "fix: display programs only if the url is configured (#479)"
This reverts commit e8886c9d9d.
2024-11-18 14:37:40 -05:00
Deborah Kaplan
bc449a3c34 build(deps): bump codecov/codecov-action from 4 to 5 (#502) 2024-11-18 12:57:22 -05:00
dependabot[bot]
3012f64b4b build(deps): bump codecov/codecov-action from 4 to 5
Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 4 to 5.
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codecov/codecov-action/compare/v4...v5)

---
updated-dependencies:
- dependency-name: codecov/codecov-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-18 16:47:36 +00:00
Diana Olarte
e8886c9d9d fix: display programs only if the url is configured (#479)
Removes the link of programs from the Header if the service is not configured.
2024-11-18 11:51:56 -03:00
renovate[bot]
a074459e03 fix(deps): update dependency react-intl to v6.8.9 (#501)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-18 11:02:36 +00:00
renovate[bot]
b87e12d2cb fix(deps): update dependency @edx/frontend-component-header to v5.7.2 (#500)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-18 06:15:08 +00:00
Emad Rad
bf2bc405d0 chore: npm publish action removed (#490) 2024-11-13 15:36:19 -05:00
Juan Carlos Iasenza (Aulasneo)
9fecc65680 chore: remove unused dependencies 2024-11-13 15:29:53 -05:00
Maxwell Frank
486a0232e3 fix: update husky (#493) 2024-11-13 09:25:16 -05:00
renovate[bot]
e68dc88d6c fix(deps): update dependency react-intl to v6.8.7 (#492)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-11 10:35:23 +00:00
renovate[bot]
f777eaabff fix(deps): update dependency @openedx/frontend-slot-footer to v1.0.6 (#491)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-11 07:42:48 +00:00
renovate[bot]
36080e7074 fix(deps): update dependency @edx/frontend-component-header to v5.7.1 2024-11-04 10:42:27 +00:00
renovate[bot]
bdeb7e1381 fix(deps): update dependency react-intl to v6.8.6 2024-11-04 06:09:44 +00:00
Deborah Kaplan
ecf7b56acf fix(deps): update dependency @reduxjs/toolkit to v2 (#482) 2024-11-01 12:29:05 -04:00
renovate[bot]
92a2ec1fb0 fix(deps): update dependency @reduxjs/toolkit to v2 2024-10-31 19:41:35 +00:00
Bilal Qamar
892262a107 test: Remove support for Node 18 (#436)
Co-authored-by: Jason Wesson <jsnwesson@gmail.com>
2024-10-31 15:39:36 -04:00
Deborah Kaplan
0e10a9b34b fix(deps): update dependency filesize to v10 (#484) 2024-10-30 11:58:49 -04:00
renovate[bot]
d872a57160 fix(deps): update dependency filesize to v10 2024-10-28 22:04:14 +00:00
Deborah Kaplan
0d38f107bd fix(deps): update dependency dompurify to v3 (#483) 2024-10-28 18:02:25 -04:00
renovate[bot]
1217e086c0 fix(deps): update dependency dompurify to v3 2024-10-28 12:48:57 +00:00
renovate[bot]
44e3d58e14 fix(deps): update dependency react-intl to v6.8.4 2024-10-28 11:24:24 +00:00
renovate[bot]
8b52cfc4d3 chore(deps): update dependency redux-mock-store to v1.5.5 2024-10-28 06:24:19 +00:00
Diana Olarte
c93d94035a fix: display SUPPORT_URL only if the url is configured 2024-10-25 12:18:49 -03:00
Brian Smith
08d47dd9f1 feat(deps): update header to 5.6.0 (#485) 2024-10-22 19:19:30 -04:00
Jason Wesson
f250efb660 docs: improve the image example for course card slot 2024-10-21 13:01:43 -06:00
Jason Wesson
c144c04aee fix: modify tests for course card and import paths 2024-10-21 13:01:43 -06:00
Jason Wesson
0a52025a99 feat: add plugin slot for course card action 2024-10-21 13:01:43 -06:00
renovate[bot]
e4e02d4da2 fix(deps): update dependency @openedx/paragon to v22.9.0 2024-10-21 04:16:50 +00:00
Maxwell Frank
0408a54372 refactor: plugin slot implementation (#465) 2024-10-15 15:04:52 -04:00
renovate[bot]
134c741cf8 fix(deps): update dependency react-router-dom to v6.27.0 2024-10-14 11:06:00 +00:00
renovate[bot]
756e85f046 fix(deps): update dependency react-intl to v6.8.0 2024-10-14 06:53:38 +00:00
renovate[bot]
8b532aa49a fix(deps): update dependency @edx/frontend-platform to v8.1.2 2024-10-14 04:09:09 +00:00
renovate[bot]
cc544e4591 fix(deps): update dependency @openedx/paragon to v22.8.1 2024-10-07 09:26:22 +00:00
renovate[bot]
1bd6f71ac1 fix(deps): update dependency @edx/frontend-component-header to v5.5.0 2024-10-07 06:22:31 +00:00
renovate[bot]
8914c7f4cc fix(deps): update dependency dompurify to v2.5.7 2024-09-30 10:40:04 +00:00
renovate[bot]
636216c5d3 chore(deps): update dependency @openedx/frontend-build to v14.1.5 2024-09-30 06:25:59 +00:00
renovate[bot]
a174abbc09 fix(deps): update dependency react-router-dom to v6.26.2 2024-09-23 08:10:12 +00:00
renovate[bot]
5134f8f85b chore(deps): update dependency @openedx/frontend-build to v14.1.4 2024-09-23 05:01:59 +00:00
edX requirements bot
1007dc40fb chore: enable github action auto update in dependabot.yml (#457)
Co-authored-by: Maxwell Frank <92897870+MaxFrank13@users.noreply.github.com>
2024-09-17 09:13:17 -04:00
renovate[bot]
767596301a chore(deps): update dependency react-dev-utils to v12 (#443)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-16 13:38:42 -04:00
renovate[bot]
d76d13bcc2 fix(deps): update dependency @openedx/frontend-slot-footer to v1.0.5 2024-09-16 08:10:12 +00:00
renovate[bot]
bd495e98ee chore(deps): update dependency husky to v9.1.6 2024-09-16 04:43:31 +00:00
Jason Wesson
2f8ff3b517 test: update jest snapshots 2024-09-13 08:46:23 -07:00
Jason Wesson
629de04289 feat: remove Zendesk component from App 2024-09-13 08:46:23 -07:00
Justin Hynes
b4b3d0718d build: Switch to ubuntu-latest for builds (#454) 2024-09-09 14:29:47 -04:00
Feanil Patel
ed7a3ffdbc build: Switch to ubuntu-latest for builds
This code does not have any dependencies that are specific to any specific
version of ubuntu.  So instead of testing on a specific version and then needing
to do work to keep the versions up-to-date, we switch to the ubuntu-latest
target which should be sufficient for testing purposes.

This work is being done as a part of https://github.com/openedx/platform-roadmap/issues/377

closes https://github.com/openedx/frontend-app-learner-dashboard/issues/420
2024-09-09 10:00:38 -04:00
renovate[bot]
0cfebb6976 fix(deps): update dependency @openedx/frontend-plugin-framework to v1.3.0 2024-09-09 06:47:17 +00:00
renovate[bot]
48e2c72180 chore(deps): update dependency @openedx/frontend-build to v14.1.2 2024-09-09 05:07:35 +00:00
renovate[bot]
3ce54cfc4a fix(deps): update dependency core-js to v3.38.1 2024-09-02 06:50:10 +00:00
renovate[bot]
8969d011ff chore(deps): update dependency @openedx/frontend-build to v14.1.1 2024-09-02 04:21:13 +00:00
Deborah Kaplan
8fd6f2c7dc chore: removing an unnecessary import (#440) 2024-08-29 09:00:08 -04:00
Deborah Kaplan
a2041bfc11 chore: removing an unnecessary import
removing an unnecessary import

FIXES: APER-3600
2024-08-28 21:38:55 +00:00
Maxwell Frank
f836239ddb feat!: Remove RecommendationsPanel (#437) 2024-08-28 09:35:19 -04:00
renovate[bot]
00129bcee0 fix(deps): update dependency @openedx/frontend-slot-footer to v1.0.4 2024-08-26 08:48:16 +00:00
renovate[bot]
c714abd656 fix(deps): update dependency @edx/openedx-atlas to v0.6.2 2024-08-26 04:27:55 +00:00
Bilal Qamar
e6baa0787c build: Upgrade to Node 20 (#396) 2024-08-23 10:27:17 -04:00
Bilal Qamar
036e798637 test: Add Node 20 to CI matrix (#431)
Co-authored-by: Maxwell Frank <92897870+MaxFrank13@users.noreply.github.com>
2024-08-22 14:33:39 -04:00
renovate[bot]
db25a6c7e9 chore(deps): update dependency husky to v9 (#425)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-21 13:18:14 -04:00
Maxwell Frank
2d091895a8 chore: update merge config (#428) 2024-08-19 14:10:02 -04:00
renovate[bot]
5ea7c6cc0c chore(deps): update dependency @openedx/frontend-build to v14.1.0 2024-08-19 06:51:43 +00:00
renovate[bot]
72aa81f8dc fix(deps): update dependency react-router-dom to v6.26.1 2024-08-19 05:16:37 +00:00
Justin Hynes
afe386566b chore: remove unused dependency -- axios-mock-adapter (#422) 2024-08-15 09:51:09 -04:00
Justin Hynes
d5da8ba62f chore: remove unused dependency -- axios-mock-adapter
Similarly to #421, the `axios-mock-adapter` dependency is not being used. Renovate is trying to update this from v1.x to 2.x, this PR removes the dependency instead.
2024-08-14 20:17:39 +00:00
Justin Hynes
24a3a2de65 chore: remove unused dependency (#421) 2024-08-14 16:13:49 -04:00
Justin Hynes
40f8b0e960 chore: remove unused dependency
Renovate opened a PR to update `fetch-mock` to v11.x from v9.x, when I went to see where it was used... the only reference I found to this package was in the package.json file. It seems like it's not used anymore.
2024-08-14 20:06:47 +00:00
Justin Hynes
d1813d3dcd chore(deps): update dependency copy-webpack-plugin to v12 (#408) 2024-08-14 15:08:49 -04:00
renovate[bot]
d5b02fbbb0 chore(deps): update dependency copy-webpack-plugin to v12 2024-08-14 18:00:28 +00:00
Justin Hynes
b3620a7832 chore(deps): update actions/setup-node action to v4 (#407) 2024-08-14 13:59:17 -04:00
renovate[bot]
5829e25fed chore(deps): update actions/setup-node action to v4 2024-08-14 17:48:07 +00:00
Justin Hynes
aa041245af chore(deps): update actions/checkout action to v4 (#406) 2024-08-14 13:40:06 -04:00
renovate[bot]
17aa646856 chore(deps): update actions/checkout action to v4 2024-08-12 06:56:29 +00:00
Ahtesham Quraish
7ef5d5b034 fix: replace the header with openedx header
Description:
Replace the header with openedx header
van-1914
2024-08-12 11:55:44 +05:00
renovate[bot]
68a46ac023 fix(deps): update dependency @openedx/frontend-plugin-framework to v1.2.3 2024-08-12 06:40:36 +00:00
renovate[bot]
6310cc0452 chore(deps): update dependency @openedx/frontend-build to v14.0.15 2024-08-12 04:12:33 +00:00
renovate[bot]
9f52c61e4f fix(deps): update dependency react-router-dom to v6.26.0 2024-08-05 07:24:44 +00:00
renovate[bot]
6a62301c1c fix(deps): update dependency core-js to v3.38.0 2024-08-05 06:58:40 +00:00
renovate[bot]
e61eaa8264 fix(deps): update dependency @redux-devtools/extension to v3.3.0 2024-08-05 04:46:33 +00:00
Deborah Kaplan
c906ce0d3a fix(deps): replace dependency redux-devtools-extension with @redux-devtools/extension 3.0.0 (#314) 2024-07-30 14:58:34 -04:00
Deborah Kaplan
e9a1e3e40d chore: fixing jest
making mocked method match
2024-07-30 17:56:58 +00:00
Deborah Kaplan
f149f2c8cf chore: packkage and method name change
this renovate update changed the package. See:
https://github.com/reduxjs/redux-devtools/releases/tag/remotedev-redux-devtools-extension%403.0.0
2024-07-30 17:30:29 +00:00
renovate[bot]
3ee7c62119 fix(deps): replace dependency redux-devtools-extension with @redux-devtools/extension 3.0.0 2024-07-30 14:09:41 +00:00
Maxwell Frank
a5d1cb380d fix: remove Optimizely and surrounding components (#386) 2024-07-30 10:09:04 -04:00
renovate[bot]
e3b4e0956a fix(deps): update dependency regenerator-runtime to ^0.14.0 2024-07-29 13:48:49 +00:00
renovate[bot]
eb427f3772 fix(deps): update dependency redux-thunk to v2.4.2 2024-07-29 11:05:55 +00:00
renovate[bot]
a53c167558 fix(deps): update dependency @edx/frontend-platform to v8.1.1 2024-07-29 07:43:07 +00:00
renovate[bot]
61476422bb chore(deps): update dependency @openedx/frontend-build to v14.0.14 2024-07-29 04:30:28 +00:00
renovate[bot]
5b6c4004c7 fix(deps): update dependency redux to v4.2.1 2024-07-22 13:49:47 +00:00
renovate[bot]
d7bd32aae3 fix(deps): update dependency react-router-dom to v6.25.1 2024-07-22 12:00:05 +00:00
renovate[bot]
c14496ade9 fix(deps): update dependency react-intl to v6.6.8 2024-07-22 07:13:24 +00:00
renovate[bot]
4b117c7882 fix(deps): update dependency @openedx/paragon to v22.7.0 2024-07-22 04:58:26 +00:00
Deborah Kaplan
0162a62c56 feat: switching to 4 letter yaml extension (#388) 2024-07-17 08:46:03 -04:00
Deborah Kaplan
79ad701eca feat: switching to 4 letter yaml extension
it looks like the  processes which rely on catalog-info require the 4
letter extension.
2024-07-16 20:47:14 +00:00
renovate[bot]
3150c110a1 fix(deps): update dependency query-string to v7.1.3 2024-07-15 14:22:57 +00:00
renovate[bot]
9c3264c8a2 fix(deps): update dependency history to v5.3.0 2024-07-15 10:19:25 +00:00
renovate[bot]
95a35e17dc fix(deps): update dependency @openedx/paragon to v22.6.1 2024-07-15 06:15:11 +00:00
renovate[bot]
aa7296c3cd fix(deps): update dependency @openedx/frontend-slot-footer to v1.0.3 2024-07-15 04:43:33 +00:00
Jason Wesson
89ae34c874 feat: change course recommendations endpoint (#371) 2024-07-08 11:19:08 -06:00
Jason Wesson
62a9cb0045 Merge branch 'master' into jwesson/update-course-recommendations-endpoint 2024-07-08 11:15:28 -06:00
renovate[bot]
a96c8fc6ab fix(deps): update dependency @edx/frontend-platform to v8.1.0 2024-07-08 14:35:17 +00:00
renovate[bot]
c0ad27077f fix(deps): update dependency prop-types to v15.8.1 2024-07-08 11:10:15 +00:00
renovate[bot]
08ead35644 fix(deps): update dependency dompurify to v2.5.6 2024-07-08 06:58:48 +00:00
renovate[bot]
362bb8b3cf fix(deps): update dependency @openedx/frontend-plugin-framework to v1.2.2 2024-07-08 04:38:24 +00:00
Jason Wesson
20eebf2f28 feat: change course recommendations endpoint 2024-07-03 16:47:53 +00:00
renovate[bot]
7e8dad41ec fix(deps): update dependency dompurify to v2.5.5 2024-07-01 11:24:42 +00:00
renovate[bot]
f6af646b80 chore(deps): update dependency @openedx/frontend-build to v14.0.10 2024-07-01 07:43:02 +00:00
Justin Hynes
3b25d04752 perf: version check workflow for lockfile updated (#361) 2024-06-25 16:02:31 -04:00
Justin Hynes
31eafb30ba Merge branch 'master' into huniafatima/lockfile-workflow-update 2024-06-25 15:59:08 -04:00
Deborah Kaplan
1705926f52 feat: changing ownership for the recommendations widget (#366) 2024-06-20 16:16:32 -04:00
Deborah Kaplan
f1128d63d7 feat: changing ownership for the recommendations widget
* changing ownership to aperture for this widget  (which is DEPRd  from
  the open source code base, and aperture is scheduled to remove it
  at some point).

FIXES: APER-3497
2024-06-20 16:17:48 +00:00
Adolfo R. Brandes
c9866af227 build: Update codecov and use token
Update codecov to the latest version and start using the org-wide token for uploads.

See https://github.com/openedx/wg-frontend/issues/179
2024-06-17 12:16:53 -03:00
renovate[bot]
a2ccda7b30 fix(deps): update dependency @openedx/frontend-plugin-framework to v1.2.1 2024-06-17 10:40:26 +00:00
renovate[bot]
54cf4bb8fd chore(deps): update dependency @openedx/frontend-build to v14.0.9 2024-06-17 06:48:46 +00:00
Hunia Fatima
229436cddf perf: version check workflow for lockfile updated 2024-06-12 17:28:00 +05:00
Adolfo R. Brandes
e94dd56fb1 fix: Remove edX-specific reference
Remove edx-Specific reference from email confirmation banner.
2024-06-10 11:34:48 -03:00
renovate[bot]
3becef3468 fix(deps): update dependency @edx/openedx-atlas to v0.6.1 2024-06-10 10:22:25 +00:00
renovate[bot]
3570ead725 fix(deps): update dependency @edx/frontend-platform to v8.0.4 2024-06-10 07:19:45 +00:00
Bilal Qamar
17c5fd09f9 feat: updated frontend-build & frontend-platform major versions (#256) 2024-05-30 17:14:41 +05:00
Bilal Qamar
506f8f795f Merge branch master into bilalqamar95/jest-v29-upgrade 2024-05-30 17:09:44 +05:00
renovate[bot]
e73880b442 fix(deps): update dependency @fortawesome/react-fontawesome to v0.2.2 2024-05-27 04:46:08 +00:00
Bilal Qamar
08050f458e Merge branch 'master' into bilalqamar95/jest-v29-upgrade 2024-05-20 16:30:16 +05:00
renovate[bot]
75f19a28b7 fix(deps): update dependency core-js to v3.37.1 2024-05-20 11:12:50 +00:00
Bilal Qamar
ecc4c4c2e0 refactor: update package-lock 2024-05-20 16:08:06 +05:00
Bilal Qamar
28da100ca2 Merge branch master into bilalqamar95/jest-v29-upgrade 2024-05-20 16:07:50 +05:00
renovate[bot]
0fbac0715d fix(deps): update dependency @fortawesome/react-fontawesome to v0.2.1 2024-05-20 07:01:59 +00:00
Adolfo R. Brandes
49f9e6e424 feat: use frontend-plugin-framework to provide a FooterSlot (#345) 2024-05-17 13:05:30 -03:00
Brian Smith
17eff1da7b feat: use frontend-plugin-framework to provide a FooterSlot 2024-05-17 10:54:53 -04:00
Muhammad Abdullah Waheed
0fcaa64a6e fix: fixed snapshots 2024-05-17 16:54:08 +05:00
Bilal Qamar
506d3f655c refactor: updated version for frontend-plugin-framework 2024-05-17 16:35:59 +05:00
Bilal Qamar
37bb54d28e refactor: updated package-lock 2024-05-16 16:42:33 +05:00
Bilal Qamar
1d53ef6153 refactor: react-unit-test-util & hotjar major version upgrades, updated snapshots 2024-05-16 16:19:50 +05:00
Bilal Qamar
59d90f5dc8 Merge branch master into bilalqamar95/jest-v29-upgrade 2024-05-16 16:19:13 +05:00
renovate[bot]
61d881b1cd fix(deps): update dependency core-js to v3.37.0 2024-05-13 09:08:02 +00:00
renovate[bot]
77adb60167 fix(deps): update dependency classnames to v2.5.1 2024-05-13 06:08:29 +00:00
renovate[bot]
b2b199e0e2 fix(deps): update dependency @openedx/paragon to v22.4.0 2024-05-13 04:07:20 +00:00
Bilal Qamar
92f712d670 feat: updated build and platform major versions, along with edx packages 2024-04-24 14:10:11 +05:00
Bilal Qamar
11b7e48080 refactor: updated snapshots for failing tests 2024-04-04 16:33:13 +05:00
Bilal Qamar
a051d712dc refactor: updated frontend-build 2024-04-04 16:32:59 +05:00
Bilal Qamar
8c76b5c689 Merge branch 'master' into bilalqamar95/jest-v29-upgrade 2024-04-04 16:28:33 +05:00
Bilal Qamar
7fccd94d6b Merge branch 'master' into bilalqamar95/jest-v29-upgrade 2023-12-22 16:07:09 +05:00
Bilal Qamar
c405bc63ea chore: bumped jest to v29 2023-12-05 16:17:36 +05:00
240 changed files with 9291 additions and 27844 deletions

4
.env
View File

@@ -32,7 +32,6 @@ ENTERPRISE_MARKETING_UTM_SOURCE=''
ENTERPRISE_MARKETING_UTM_CAMPAIGN=''
ENTERPRISE_MARKETING_FOOTER_UTM_MEDIUM=''
LEARNING_BASE_URL=''
ZENDESK_KEY=''
HOTJAR_APP_ID=''
HOTJAR_VERSION='6'
HOTJAR_DEBUG=''
@@ -40,5 +39,6 @@ ACCOUNT_SETTINGS_URL=''
ACCOUNT_PROFILE_URL=''
ENABLE_NOTICES=''
CAREER_LINK_URL=''
OPTIMIZELY_FULL_STACK_SDK_KEY=''
ENABLE_EDX_PERSONAL_DASHBOARD=false
ENABLE_PROGRAMS=false
NON_BROWSABLE_COURSES=false

View File

@@ -8,7 +8,6 @@ LOGOUT_URL='http://localhost:18000/logout'
LOGO_URL=https://edx-cdn.org/v3/default/logo.svg
LOGO_TRADEMARK_URL=https://edx-cdn.org/v3/default/logo-trademark.svg
LOGO_WHITE_URL=https://edx-cdn.org/v3/default/logo-white.svg
LOGO_POWERED_BY_OPEN_EDX_URL_SVG=https://edx-cdn.org/v3/stage/open-edx-tag.svg
FAVICON_URL=https://edx-cdn.org/v3/default/favicon.ico
CSRF_TOKEN_API_PATH='/csrf/api/v1/token'
REFRESH_ACCESS_TOKEN_ENDPOINT='http://localhost:18000/login_refresh'
@@ -21,7 +20,7 @@ LMS_CLIENT_ID='login-service-client-id'
SEGMENT_KEY=''
FEATURE_FLAGS={}
MARKETING_SITE_BASE_URL='http://localhost:18000'
SUPPORT_URL='http://localhost:18000/support'
SUPPORT_URL=''
CONTACT_URL='http://localhost:18000/contact'
OPEN_SOURCE_URL='http://localhost:18000/openedx'
TERMS_OF_SERVICE_URL='http://localhost:18000/terms-of-service'
@@ -39,7 +38,6 @@ ENTERPRISE_MARKETING_UTM_CAMPAIGN='example.com Referral'
ENTERPRISE_MARKETING_FOOTER_UTM_MEDIUM='Footer'
LEARNING_BASE_URL='http://localhost:2000'
SESSION_COOKIE_DOMAIN='localhost'
ZENDESK_KEY=''
HOTJAR_APP_ID=''
HOTJAR_VERSION='6'
HOTJAR_DEBUG=''
@@ -47,5 +45,6 @@ ACCOUNT_SETTINGS_URL='http://localhost:1997'
ACCOUNT_PROFILE_URL='http://localhost:1995'
ENABLE_NOTICES=''
CAREER_LINK_URL=''
OPTIMIZELY_FULL_STACK_SDK_KEY=''
ENABLE_EDX_PERSONAL_DASHBOARD=false
ENABLE_PROGRAMS=false
NON_BROWSABLE_COURSES=false

View File

@@ -8,7 +8,6 @@ LOGOUT_URL='http://localhost:18000/logout'
LOGO_URL=https://edx-cdn.org/v3/default/logo.svg
LOGO_TRADEMARK_URL=https://edx-cdn.org/v3/default/logo-trademark.svg
LOGO_WHITE_URL=https://edx-cdn.org/v3/default/logo-white.svg
LOGO_POWERED_BY_OPEN_EDX_URL_SVG=https://edx-cdn.org/v3/stage/open-edx-tag.svg
FAVICON_URL=https://edx-cdn.org/v3/default/favicon.ico
CSRF_TOKEN_API_PATH='/csrf/api/v1/token'
REFRESH_ACCESS_TOKEN_ENDPOINT='http://localhost:18000/login_refresh'
@@ -21,7 +20,7 @@ LMS_CLIENT_ID='login-service-client-id'
SEGMENT_KEY=''
FEATURE_FLAGS={}
MARKETING_SITE_BASE_URL='http://localhost:18000'
SUPPORT_URL='http://localhost:18000/support'
SUPPORT_URL=''
CONTACT_URL='http://localhost:18000/contact'
OPEN_SOURCE_URL='http://localhost:18000/openedx'
TERMS_OF_SERVICE_URL='http://localhost:18000/terms-of-service'
@@ -38,7 +37,6 @@ ENTERPRISE_MARKETING_UTM_SOURCE='example.com'
ENTERPRISE_MARKETING_UTM_CAMPAIGN='example.com Referral'
ENTERPRISE_MARKETING_FOOTER_UTM_MEDIUM='Footer'
LEARNING_BASE_URL='http://localhost:2000'
ZENDESK_KEY='test-zendesk-key'
HOTJAR_APP_ID='hot-jar-app-id'
HOTJAR_VERSION='6'
HOTJAR_DEBUG=''
@@ -46,5 +44,6 @@ ACCOUNT_SETTINGS_URL='http://account-settings-url.test'
ACCOUNT_PROFILE_URL='http://account-profile-url.test'
ENABLE_NOTICES=''
CAREER_LINK_URL=''
OPTIMIZELY_FULL_STACK_SDK_KEY='SDK Key'
ENABLE_EDX_PERSONAL_DASHBOARD=true
ENABLE_PROGRAMS=false
NON_BROWSABLE_COURSES=false

7
.github/CODEOWNERS vendored
View File

@@ -1,8 +1 @@
# Root app is developed and owned by Aurora
* @openedx/2U-aperture
# WIDGETS and experiments are developed and owned by separate teams below
# Recommendations panel
/src/widgets/RecommendationsPanel @openedx/2U-vanguards
/src/widgets/LookingForChallengeWidget @openedx/2U-vanguards

7
.github/dependabot.yml vendored Normal file
View File

@@ -0,0 +1,7 @@
version: 2
updates:
# Adding new check for github-actions
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"

View File

@@ -10,18 +10,16 @@ on:
jobs:
tests:
runs-on: ubuntu-20.04
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup Nodejs Env
run: echo "NODE_VER=`cat .nvmrc`" >> $GITHUB_ENV
uses: actions/checkout@v4
- name: Setup Nodejs
uses: actions/setup-node@v3
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VER }}
node-version-file: '.nvmrc'
- name: Install dependencies
run: npm ci
@@ -39,21 +37,7 @@ jobs:
run: npm run build
- name: Run Coverage
uses: codecov/codecov-action@v3
- name: Send failure notification
if: ${{ failure() }}
uses: dawidd6/action-send-mail@v3
uses: codecov/codecov-action@v5
with:
server_address: email-smtp.us-east-1.amazonaws.com
server_port: 465
username: ${{ secrets.EDX_SMTP_USERNAME }}
password: ${{ secrets.EDX_SMTP_PASSWORD }}
subject: CI workflow failed in ${{github.repository}}
to: masters-grades@edx.org,aperture@2u-internal.opsgenie.net
from: github-actions <github-actions@edx.org>
nodemailerlog: true
nodemailerdebug: true
body: CI workflow in ${{github.repository}} failed!
For details see "github.com/${{ github.repository }}/actions/runs/${{ github.run_id
}}"
token: ${{ secrets.CODECOV_TOKEN }}
fail_ci_if_error: true

View File

@@ -1,35 +0,0 @@
name: Release CI
on:
push:
tags:
- "*"
jobs:
release:
name: Release
runs-on: ubuntu-20.04
steps:
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Setup Nodejs Env
run: echo "NODE_VER=`cat .nvmrc`" >> $GITHUB_ENV
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: ${{ env.NODE_VER }}
- name: Install dependencies
run: npm ci
- name: Create Build
run: npm run build
- name: Release Package
env:
GITHUB_TOKEN: ${{ secrets.SEMANTIC_RELEASE_GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.SEMANTIC_RELEASE_NPM_TOKEN }}
run: npm semantic-release

View File

@@ -1,4 +0,0 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npm run lint

2
.nvmrc
View File

@@ -1 +1 @@
18.20
20

View File

@@ -1,27 +0,0 @@
{
"branch": "master",
"tagFormat": "v${version}",
"verifyConditions": [
"@semantic-release/npm",
{
"path": "@semantic-release/github",
"assets": {
"path": "dist/*"
}
}
],
"analyzeCommits": "@semantic-release/commit-analyzer",
"generateNotes": "@semantic-release/release-notes-generator",
"prepare": "@semantic-release/npm",
"publish": [
"@semantic-release/npm",
{
"path": "@semantic-release/github",
"assets": {
"path": "dist/*"
}
}
],
"success": [],
"fail": []
}

View File

@@ -30,6 +30,12 @@ To start the MFE and enable the feature in LMS:
From there, simply load the configured address/port. You should be prompted to log into your LMS if you are not
already, and then redirected to your home page.
Plugins
-------
This MFE can be customized using `Frontend Plugin Framework <https://github.com/openedx/frontend-plugin-framework>`_.
The parts of this MFE that can be customized in that manner are documented `here </src/plugin-slots>`_.
Contributing
------------

View File

@@ -17,6 +17,7 @@ metadata:
openedx.org/arch-interest-groups: ""
# This can be multiple comma-separated projects.
openedx.org/add-to-projects: "openedx:23"
openedx.org/release: "master"
spec:
type: 'service'
lifecycle: 'production'

View File

@@ -29,7 +29,6 @@ module.exports = {
LOGO_URL: 'https://edx-cdn.org/v3/default/logo.svg',
LOGO_TRADEMARK_URL: 'https://edx-cdn.org/v3/default/logo-trademark.svg',
LOGO_WHITE_URL: 'https://edx-cdn.org/v3/default/logo-white.svg',
LOGO_POWERED_BY_OPEN_EDX_URL_SVG: 'https://edx-cdn.org/v3/stage/open-edx-tag.svg',
FAVICON_URL: 'https://edx-cdn.org/v3/default/favicon.ico',
CSRF_TOKEN_API_PATH: '/csrf/api/v1/token',
REFRESH_ACCESS_TOKEN_ENDPOINT: 'http://localhost:18000/login_refresh',
@@ -60,7 +59,6 @@ module.exports = {
ENTERPRISE_MARKETING_FOOTER_UTM_MEDIUM: 'Footer',
LEARNING_BASE_URL: 'http://localhost:2000',
SESSION_COOKIE_DOMAIN: 'localhost',
ZENDESK_KEY: '',
HOTJAR_APP_ID: '',
HOTJAR_VERSION: 6,
HOTJAR_DEBUG: '',
@@ -70,6 +68,5 @@ module.exports = {
ACCOUNT_PROFILE_URL: 'http://localhost:1995',
ENABLE_NOTICES: '',
CAREER_LINK_URL: '',
OPTIMIZELY_FULL_STACK_SDK_KEY: '',
EXPERIMENT_08_23_VAN_PAINTED_DOOR: true,
};

View File

@@ -1,9 +0,0 @@
# This file describes this Open edX repo, as described in OEP-2:
# http://open-edx-proposals.readthedocs.io/en/latest/oeps/oep-0002.html#specification
tags:
- frontend-app
- masters
oeps:
oep-2: true # Repository metadata
openedx-release: {ref: master}

29499
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -6,6 +6,9 @@
"type": "git",
"url": "git+https://github.com/edx/frontend-app-learner-dashboard.git"
},
"browserslist": [
"extends @edx/browserslist-config"
],
"scripts": {
"build": "fedx-scripts webpack",
"i18n_extract": "fedx-scripts formatjs extract",
@@ -13,11 +16,11 @@
"lint-fix": "fedx-scripts eslint --fix --ext .jsx,.js src/",
"semantic-release": "semantic-release",
"start": "fedx-scripts webpack-dev-server --progress",
"dev": "PUBLIC_PATH=/learner-dashboard/ MFE_CONFIG_API_URL='http://localhost:8000/api/mfe_config/v1' fedx-scripts webpack-dev-server --progress --host apps.local.openedx.io",
"test": "TZ=GMT fedx-scripts jest --coverage --passWithNoTests",
"quality": "npm run lint-fix && npm run test",
"watch-tests": "jest --watch",
"snapshot": "fedx-scripts jest --updateSnapshot",
"prepare": "husky install"
"snapshot": "fedx-scripts jest --updateSnapshot"
},
"author": "edX",
"license": "AGPL-3.0",
@@ -27,72 +30,57 @@
},
"dependencies": {
"@edx/brand": "npm:@openedx/brand-openedx@^1.2.2",
"@edx/browserslist-config": "^1.1.0",
"@edx/frontend-component-footer": "13.1.0",
"@edx/frontend-enterprise-hotjar": "3.0.0",
"@edx/frontend-platform": "7.1.4",
"@edx/openedx-atlas": "^0.6.0",
"@edx/react-unit-test-utils": "2.0.0",
"@edx/frontend-component-footer": "^14.6.0",
"@edx/frontend-component-header": "^6.2.0",
"@edx/frontend-enterprise-hotjar": "7.2.0",
"@edx/frontend-platform": "^8.3.1",
"@edx/openedx-atlas": "^0.7.0",
"@edx/react-unit-test-utils": "^4.0.0",
"@fortawesome/fontawesome-svg-core": "^1.2.36",
"@fortawesome/free-brands-svg-icons": "^5.15.4",
"@fortawesome/free-solid-svg-icons": "^5.15.4",
"@fortawesome/react-fontawesome": "^0.2.0",
"@openedx/frontend-plugin-framework": "^1.0.2",
"@openedx/paragon": "^22.2.2",
"@optimizely/react-sdk": "^2.9.2",
"@redux-beacon/segment": "^1.1.0",
"@reduxjs/toolkit": "^1.6.1",
"@testing-library/user-event": "^13.5.0",
"axios": "^0.28.0",
"@openedx/frontend-plugin-framework": "^1.7.0",
"@openedx/paragon": "^22.16.0",
"@redux-devtools/extension": "3.3.0",
"@reduxjs/toolkit": "^2.0.0",
"classnames": "^2.3.1",
"core-js": "3.16.2",
"dompurify": "^2.3.1",
"email-prop-type": "^3.0.1",
"file-saver": "^2.0.5",
"filesize": "^8.0.6",
"core-js": "3.42.0",
"filesize": "^10.0.0",
"font-awesome": "4.7.0",
"history": "5.0.1",
"html-react-parser": "^1.3.0",
"jest": "^29.7.0",
"jest-when": "^3.6.0",
"history": "5.3.0",
"lodash": "^4.17.21",
"moment": "^2.29.4",
"prop-types": "15.7.2",
"query-string": "7.0.1",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"prop-types": "15.8.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-helmet": "^6.1.0",
"react-intl": "6.4.7",
"react-pdf": "^7.0.0",
"react-intl": "6.8.9",
"react-redux": "^7.2.4",
"react-router-dom": "6.15.0",
"react-router-dom": "6.29.0",
"react-share": "^4.4.0",
"react-zendesk": "^0.1.13",
"redux": "4.1.1",
"redux-beacon": "^2.1.0",
"redux-devtools-extension": "2.13.9",
"redux": "4.2.1",
"redux-logger": "3.0.6",
"redux-thunk": "2.3.0",
"regenerator-runtime": "^0.13.9",
"redux-thunk": "2.4.2",
"regenerator-runtime": "^0.14.0",
"reselect": "^4.0.0",
"universal-cookie": "^4.0.4",
"util": "^0.12.4",
"whatwg-fetch": "^3.6.2"
"util": "^0.12.4"
},
"devDependencies": {
"@edx/browserslist-config": "^1.3.0",
"@edx/reactifex": "^2.1.1",
"@openedx/frontend-build": "13.1.4",
"@testing-library/jest-dom": "^5.14.1",
"@testing-library/react": "^12.1.0",
"axios-mock-adapter": "^1.20.0",
"copy-webpack-plugin": "^11.0.0",
"fetch-mock": "^9.11.0",
"husky": "^7.0.0",
"@openedx/frontend-build": "^14.3.3",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.2.0",
"copy-webpack-plugin": "^12.0.0",
"identity-obj-proxy": "^3.0.0",
"jest-expect-message": "^1.0.2",
"react-dev-utils": "^11.0.4",
"react-test-renderer": "^17.0.2",
"redux-mock-store": "^1.5.4",
"semantic-release": "^20.1.3"
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0",
"jest-expect-message": "^1.1.3",
"jest-when": "^3.6.0",
"react-dev-utils": "^12.0.0",
"react-test-renderer": "^18.3.1",
"redux-mock-store": "^1.5.4"
}
}

View File

@@ -6,7 +6,7 @@ import { logError } from '@edx/frontend-platform/logging';
import { initializeHotjar } from '@edx/frontend-enterprise-hotjar';
import { ErrorPage, AppContext } from '@edx/frontend-platform/react';
import Footer from '@edx/frontend-component-footer';
import { FooterSlot } from '@edx/frontend-component-footer';
import { Alert } from '@openedx/paragon';
import { RequestKeys } from 'data/constants/requests';
@@ -17,8 +17,6 @@ import {
} from 'data/redux';
import { reduxHooks } from 'hooks';
import Dashboard from 'containers/Dashboard';
import ZendeskFab from 'components/ZendeskFab';
import { ExperimentProvider } from 'ExperimentContext';
import track from 'tracking';
@@ -42,19 +40,6 @@ export const App = () => {
const { supportEmail } = reduxHooks.usePlatformSettingsData();
const loadData = reduxHooks.useLoadData();
const optimizelyScript = () => {
if (getConfig().OPTIMIZELY_URL) {
return <script src={getConfig().OPTIMIZELY_URL} />;
} if (getConfig().OPTIMIZELY_PROJECT_ID) {
return (
<script
src={`${getConfig().MARKETING_SITE_BASE_URL}/optimizelyjs/${getConfig().OPTIMIZELY_PROJECT_ID}.js`}
/>
);
}
return null;
};
React.useEffect(() => {
if (authenticatedUser?.administrator || getConfig().NODE_ENV === 'development') {
window.loadEmptyData = () => {
@@ -91,26 +76,22 @@ export const App = () => {
<Helmet>
<title>{formatMessage(messages.pageTitle)}</title>
<link rel="shortcut icon" href={getConfig().FAVICON_URL} type="image/x-icon" />
{optimizelyScript()}
</Helmet>
<div>
<AppWrapper>
<LearnerDashboardHeader />
<main>
<main id="main">
{hasNetworkFailure
? (
<Alert variant="danger">
<ErrorPage message={formatMessage(messages.errorMessage, { supportEmail })} />
</Alert>
) : (
<ExperimentProvider>
<Dashboard />
</ExperimentProvider>
<Dashboard />
)}
</main>
</AppWrapper>
<Footer logo={getConfig().LOGO_POWERED_BY_OPEN_EDX_URL_SVG} />
<ZendeskFab />
<FooterSlot />
</div>
</>
);

View File

@@ -9,6 +9,7 @@ $fa-font-path: "~font-awesome/fonts";
$input-focus-box-shadow: $input-box-shadow; // hack to get upgrade to paragon 4.0.0 to work
@import "~@edx/frontend-component-header/dist/index";
@import "~@edx/frontend-component-footer/dist/_footer";
.text-ellipsis {

View File

@@ -2,7 +2,6 @@ import React from 'react';
import { Helmet } from 'react-helmet';
import { shallow } from '@edx/react-unit-test-utils';
import Footer from '@edx/frontend-component-footer';
import { useIntl } from '@edx/frontend-platform/i18n';
import { getConfig } from '@edx/frontend-platform';
@@ -11,18 +10,13 @@ import { reduxHooks } from 'hooks';
import Dashboard from 'containers/Dashboard';
import LearnerDashboardHeader from 'containers/LearnerDashboardHeader';
import AppWrapper from 'containers/WidgetContainers/AppWrapper';
import { ExperimentProvider } from 'ExperimentContext';
import { App } from './App';
import messages from './messages';
jest.mock('@edx/frontend-component-footer', () => 'Footer');
jest.mock('@edx/frontend-component-footer', () => ({ FooterSlot: 'FooterSlot' }));
jest.mock('containers/Dashboard', () => 'Dashboard');
jest.mock('containers/LearnerDashboardHeader', () => 'LearnerDashboardHeader');
jest.mock('components/ZendeskFab', () => 'ZendeskFab');
jest.mock('ExperimentContext', () => ({
ExperimentProvider: 'ExperimentProvider',
}));
jest.mock('containers/WidgetContainers/AppWrapper', () => 'AppWrapper');
jest.mock('data/redux', () => ({
selectors: 'redux.selectors',
@@ -38,8 +32,6 @@ jest.mock('hooks', () => ({
}));
jest.mock('data/store', () => 'data/store');
const logo = 'fakeLogo.png';
jest.mock('@edx/frontend-platform', () => ({
getConfig: jest.fn(() => ({})),
}));
@@ -66,9 +58,6 @@ describe('App router component', () => {
it('displays learner dashboard header', () => {
expect(el.instance.findByType(LearnerDashboardHeader).length).toEqual(1);
});
test('Footer logo drawn from env variable', () => {
expect(el.instance.findByType(Footer)[0].props.logo).toEqual(logo);
});
it('wraps the header and main components in an AppWrapper widget container', () => {
const container = el.instance.findByType(AppWrapper)[0];
expect(container.children[0].type).toEqual('LearnerDashboardHeader');
@@ -78,61 +67,52 @@ describe('App router component', () => {
describe('no network failure', () => {
beforeAll(() => {
reduxHooks.useRequestIsFailed.mockReturnValue(false);
getConfig.mockReturnValue({ LOGO_POWERED_BY_OPEN_EDX_URL_SVG: logo });
getConfig.mockReturnValue({});
el = shallow(<App />);
});
runBasicTests();
it('loads dashboard', () => {
const main = el.instance.findByType('main')[0];
expect(main.children.length).toEqual(1);
const expProvider = main.children[0];
expect(expProvider.type).toEqual('ExperimentProvider');
expect(expProvider.children.length).toEqual(1);
expect(
expProvider.matches(shallow(<ExperimentProvider><Dashboard /></ExperimentProvider>)),
).toEqual(true);
const dashboard = main.children[0].el;
expect(dashboard.type).toEqual('Dashboard');
expect(dashboard).toEqual(shallow(<Dashboard />));
});
});
describe('no network failure with optimizely url', () => {
beforeAll(() => {
reduxHooks.useRequestIsFailed.mockReturnValue(false);
getConfig.mockReturnValue({ LOGO_POWERED_BY_OPEN_EDX_URL_SVG: logo, OPTIMIZELY_URL: 'fake.url' });
getConfig.mockReturnValue({ OPTIMIZELY_URL: 'fake.url' });
el = shallow(<App />);
});
runBasicTests();
it('loads dashboard', () => {
const main = el.instance.findByType('main')[0];
expect(main.children.length).toEqual(1);
const expProvider = main.children[0];
expect(expProvider.type).toEqual('ExperimentProvider');
expect(expProvider.children.length).toEqual(1);
expect(
expProvider.matches(shallow(<ExperimentProvider><Dashboard /></ExperimentProvider>)),
).toEqual(true);
const dashboard = main.children[0].el;
expect(dashboard.type).toEqual('Dashboard');
expect(dashboard).toEqual(shallow(<Dashboard />));
});
});
describe('no network failure with optimizely project id', () => {
beforeAll(() => {
reduxHooks.useRequestIsFailed.mockReturnValue(false);
getConfig.mockReturnValue({ LOGO_POWERED_BY_OPEN_EDX_URL_SVG: logo, OPTIMIZELY_PROJECT_ID: 'fakeId' });
getConfig.mockReturnValue({ OPTIMIZELY_PROJECT_ID: 'fakeId' });
el = shallow(<App />);
});
runBasicTests();
it('loads dashboard', () => {
const main = el.instance.findByType('main')[0];
expect(main.children.length).toEqual(1);
const expProvider = main.children[0];
expect(expProvider.type).toEqual('ExperimentProvider');
expect(expProvider.children.length).toEqual(1);
expect(
expProvider.matches(shallow(<ExperimentProvider><Dashboard /></ExperimentProvider>)),
).toEqual(true);
const dashboard = main.children[0].el;
expect(dashboard.type).toEqual('Dashboard');
expect(dashboard).toEqual(shallow(<Dashboard />));
});
});
describe('initialize failure', () => {
beforeAll(() => {
reduxHooks.useRequestIsFailed.mockImplementation((key) => key === RequestKeys.initialize);
getConfig.mockReturnValue({ LOGO_POWERED_BY_OPEN_EDX_URL_SVG: logo });
getConfig.mockReturnValue({});
el = shallow(<App />);
});
runBasicTests();
@@ -150,7 +130,7 @@ describe('App router component', () => {
describe('refresh failure', () => {
beforeAll(() => {
reduxHooks.useRequestIsFailed.mockImplementation((key) => key === RequestKeys.refreshList);
getConfig.mockReturnValue({ LOGO_POWERED_BY_OPEN_EDX_URL_SVG: logo });
getConfig.mockReturnValue({});
el = shallow(<App />);
});
runBasicTests();

View File

@@ -1,64 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import { useWindowSize, breakpoints } from '@openedx/paragon';
import { StrictDict } from 'utils';
import api from 'widgets/ProductRecommendations/api';
import * as module from './ExperimentContext';
export const state = StrictDict({
experiment: (val) => React.useState(val), // eslint-disable-line
countryCode: (val) => React.useState(val), // eslint-disable-line
});
export const useCountryCode = (setCountryCode) => {
React.useEffect(() => {
api
.fetchRecommendationsContext()
.then((response) => {
setCountryCode(response.data.countryCode);
})
.catch(() => {
setCountryCode('');
});
/* eslint-disable */
}, []);
};
export const ExperimentContext = React.createContext();
export const ExperimentProvider = ({ children }) => {
const [countryCode, setCountryCode] = module.state.countryCode(null);
const [experiment, setExperiment] = module.state.experiment({
isExperimentActive: false,
inRecommendationsVariant: true,
});
module.useCountryCode(setCountryCode);
const { width } = useWindowSize();
const isMobile = width < breakpoints.small.minWidth;
const contextValue = React.useMemo(
() => ({
experiment,
countryCode,
setExperiment,
setCountryCode,
isMobile,
}),
[experiment, countryCode, setExperiment, setCountryCode, isMobile]
);
return (
<ExperimentContext.Provider value={contextValue}>
{children}
</ExperimentContext.Provider>
);
};
export const useExperimentContext = () => React.useContext(ExperimentContext);
ExperimentProvider.propTypes = {
children: PropTypes.node.isRequired,
};
export default { useCountryCode, useExperimentContext };

View File

@@ -1,122 +0,0 @@
import React from 'react';
import { waitFor, render } from '@testing-library/react';
import { useWindowSize } from '@openedx/paragon';
import api from 'widgets/ProductRecommendations/api';
import { MockUseState } from 'testUtils';
import * as experiment from 'ExperimentContext';
const state = new MockUseState(experiment);
jest.unmock('react');
jest.spyOn(React, 'useEffect').mockImplementation((cb, prereqs) => ({ useEffect: { cb, prereqs } }));
jest.mock('widgets/ProductRecommendations/api', () => ({
fetchRecommendationsContext: jest.fn(),
}));
describe('experiments context', () => {
beforeEach(() => {
jest.resetAllMocks();
});
describe('useCountryCode', () => {
describe('behaviour', () => {
describe('useEffect call', () => {
let calls;
let cb;
const setCountryCode = jest.fn();
const successfulFetch = { data: { countryCode: 'ZA' } };
beforeEach(() => {
experiment.useCountryCode(setCountryCode);
({ calls } = React.useEffect.mock);
[[cb]] = calls;
});
it('calls useEffect once', () => {
expect(calls.length).toEqual(1);
});
describe('successful fetch', () => {
it('sets the country code', async () => {
let resolveFn;
api.fetchRecommendationsContext.mockReturnValueOnce(
new Promise((resolve) => {
resolveFn = resolve;
}),
);
cb();
expect(api.fetchRecommendationsContext).toHaveBeenCalled();
expect(setCountryCode).not.toHaveBeenCalled();
resolveFn(successfulFetch);
await waitFor(() => {
expect(setCountryCode).toHaveBeenCalledWith(successfulFetch.data.countryCode);
});
});
});
describe('unsuccessful fetch', () => {
it('sets the country code to an empty string', async () => {
let rejectFn;
api.fetchRecommendationsContext.mockReturnValueOnce(
new Promise((resolve, reject) => {
rejectFn = reject;
}),
);
cb();
expect(api.fetchRecommendationsContext).toHaveBeenCalled();
expect(setCountryCode).not.toHaveBeenCalled();
rejectFn();
await waitFor(() => {
expect(setCountryCode).toHaveBeenCalledWith('');
});
});
});
});
});
});
describe('ExperimentProvider', () => {
const { ExperimentProvider } = experiment;
const TestComponent = () => {
const {
experiment: exp,
setExperiment,
countryCode,
setCountryCode,
isMobile,
} = experiment.useExperimentContext();
expect(exp.isExperimentActive).toBeFalsy();
expect(exp.inRecommendationsVariant).toBeTruthy();
expect(countryCode).toBeNull();
expect(isMobile).toBe(false);
expect(setExperiment).toBeDefined();
expect(setCountryCode).toBeDefined();
return (
<div />
);
};
it('allows access to child components with the context stateful values', () => {
const countryCodeSpy = jest.spyOn(experiment, 'useCountryCode').mockImplementationOnce(() => {});
useWindowSize.mockImplementationOnce(() => ({ width: 577, height: 943 }));
state.mock();
render(
<ExperimentProvider>
<TestComponent />
</ExperimentProvider>,
);
expect(countryCodeSpy).toHaveBeenCalledWith(state.setState.countryCode);
state.expectInitializedWith(state.keys.countryCode, null);
state.expectInitializedWith(state.keys.experiment, { isExperimentActive: false, inRecommendationsVariant: true });
});
});
});

View File

@@ -17,7 +17,9 @@ exports[`App router component component initialize failure snapshot 1`] = `
<div>
<AppWrapper>
<LearnerDashboardHeader />
<main>
<main
id="main"
>
<Alert
variant="danger"
>
@@ -27,10 +29,7 @@ exports[`App router component component initialize failure snapshot 1`] = `
</Alert>
</main>
</AppWrapper>
<Footer
logo="fakeLogo.png"
/>
<ZendeskFab />
<FooterSlot />
</div>
</Fragment>
`;
@@ -52,16 +51,13 @@ exports[`App router component component no network failure snapshot 1`] = `
<div>
<AppWrapper>
<LearnerDashboardHeader />
<main>
<ExperimentProvider>
<Dashboard />
</ExperimentProvider>
<main
id="main"
>
<Dashboard />
</main>
</AppWrapper>
<Footer
logo="fakeLogo.png"
/>
<ZendeskFab />
<FooterSlot />
</div>
</Fragment>
`;
@@ -79,23 +75,17 @@ exports[`App router component component no network failure with optimizely proje
rel="shortcut icon"
type="image/x-icon"
/>
<script
src="undefined/optimizelyjs/fakeId.js"
/>
</HelmetWrapper>
<div>
<AppWrapper>
<LearnerDashboardHeader />
<main>
<ExperimentProvider>
<Dashboard />
</ExperimentProvider>
<main
id="main"
>
<Dashboard />
</main>
</AppWrapper>
<Footer
logo="fakeLogo.png"
/>
<ZendeskFab />
<FooterSlot />
</div>
</Fragment>
`;
@@ -113,23 +103,17 @@ exports[`App router component component no network failure with optimizely url s
rel="shortcut icon"
type="image/x-icon"
/>
<script
src="fake.url"
/>
</HelmetWrapper>
<div>
<AppWrapper>
<LearnerDashboardHeader />
<main>
<ExperimentProvider>
<Dashboard />
</ExperimentProvider>
<main
id="main"
>
<Dashboard />
</main>
</AppWrapper>
<Footer
logo="fakeLogo.png"
/>
<ZendeskFab />
<FooterSlot />
</div>
</Fragment>
`;
@@ -151,7 +135,9 @@ exports[`App router component component refresh failure snapshot 1`] = `
<div>
<AppWrapper>
<LearnerDashboardHeader />
<main>
<main
id="main"
>
<Alert
variant="danger"
>
@@ -161,10 +147,7 @@ exports[`App router component component refresh failure snapshot 1`] = `
</Alert>
</main>
</AppWrapper>
<Footer
logo="fakeLogo.png"
/>
<ZendeskFab />
<FooterSlot />
</div>
</Fragment>
`;

View File

@@ -1,40 +1,43 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`app registry subscribe: APP_INIT_ERROR. snapshot: displays an ErrorPage to root element 1`] = `
<ErrorPage
message="test-error-message"
/>
<UNDEFINED>
<ErrorPage
message="test-error-message"
/>
</UNDEFINED>
`;
exports[`app registry subscribe: APP_READY. links App to root element 1`] = `
<AppProvider
store={
Object {
"redux": "store",
<UNDEFINED>
<AppProvider
store={
{
"redux": "store",
}
}
}
wrapWithRouter={true}
>
<NoticesWrapper>
<Routes>
<Route
element={
<PageWrap>
<App />
</PageWrap>
}
path="/"
/>
<Route
element={
<Navigate
replace={true}
to="/"
/>
}
path="*"
/>
</Routes>
</NoticesWrapper>
</AppProvider>
>
<NoticesWrapper>
<Routes>
<Route
element={
<PageWrap>
<App />
</PageWrap>
}
path="/"
/>
<Route
element={
<Navigate
replace={true}
to="/"
/>
}
path="*"
/>
</Routes>
</NoticesWrapper>
</AppProvider>
</UNDEFINED>
`;

View File

@@ -3,16 +3,15 @@ import { getAuthenticatedHttpClient, getAuthenticatedUser } from '@edx/frontend-
import { logError, logInfo } from '@edx/frontend-platform/logging';
export const noticesUrl = `${getConfig().LMS_BASE_URL}/notices/api/v1/unacknowledged`;
export const error404Message = 'This probably happened because the notices plugin is not installed on platform.';
export const getNotices = ({ onLoad }) => {
export const getNotices = ({ onLoad, notFoundMessage }) => {
const authenticatedUser = getAuthenticatedUser();
const handleError = async (e) => {
// Error probably means that notices is not installed, which is fine.
const { customAttributes: { httpErrorStatus } } = e;
if (httpErrorStatus === 404) {
logInfo(`${e}. ${error404Message}`);
logInfo(`${e}. ${notFoundMessage}`);
} else {
logError(e);
}

View File

@@ -1,9 +1,11 @@
import React from 'react';
import { getConfig } from '@edx/frontend-platform';
import { useIntl } from 'react-intl';
import { StrictDict } from 'utils';
import { getNotices } from './api';
import * as module from './hooks';
import messages from './messages';
/**
* This component uses the platform-plugin-notices plugin to function.
@@ -17,6 +19,8 @@ export const state = StrictDict({
export const useNoticesWrapperData = () => {
const [isRedirected, setIsRedirected] = module.state.isRedirected();
const { formatMessage } = useIntl();
React.useEffect(() => {
if (getConfig().ENABLE_NOTICES) {
getNotices({
@@ -26,9 +30,10 @@ export const useNoticesWrapperData = () => {
window.location.replace(`${data.data.results[0]}?next=${window.location.href}`);
}
},
notFoundMessage: formatMessage(messages.error404Message),
});
}
}, [setIsRedirected]);
}, [setIsRedirected, formatMessage]);
return { isRedirected };
};

View File

@@ -8,6 +8,12 @@ import * as hooks from './hooks';
jest.mock('@edx/frontend-platform', () => ({ getConfig: jest.fn() }));
jest.mock('./api', () => ({ getNotices: jest.fn() }));
const mockFormatMessage = jest.fn(message => message.defaultMessage || 'translated-string');
jest.mock('react-intl', () => ({
useIntl: () => ({
formatMessage: mockFormatMessage,
}),
}));
getConfig.mockReturnValue({ ENABLE_NOTICES: true });
const state = new MockUseState(hooks);
@@ -34,7 +40,7 @@ describe('NoticesWrapper hooks', () => {
getConfig.mockReturnValueOnce({ ENABLE_NOTICES: false });
hooks.useNoticesWrapperData();
const [cb, prereqs] = React.useEffect.mock.calls[0];
expect(prereqs).toEqual([state.setState.isRedirected]);
expect(prereqs).toEqual([state.setState.isRedirected, mockFormatMessage]);
cb();
expect(getNotices).not.toHaveBeenCalled();
});
@@ -43,7 +49,7 @@ describe('NoticesWrapper hooks', () => {
hooks.useNoticesWrapperData();
expect(React.useEffect).toHaveBeenCalled();
const [cb, prereqs] = React.useEffect.mock.calls[0];
expect(prereqs).toEqual([state.setState.isRedirected]);
expect(prereqs).toEqual([state.setState.isRedirected, mockFormatMessage]);
cb();
expect(getNotices).toHaveBeenCalled();
const { onLoad } = getNotices.mock.calls[0][0];
@@ -59,7 +65,7 @@ describe('NoticesWrapper hooks', () => {
window.location = { replace: jest.fn(), href: 'test-old-href' };
hooks.useNoticesWrapperData();
const [cb, prereqs] = React.useEffect.mock.calls[0];
expect(prereqs).toEqual([state.setState.isRedirected]);
expect(prereqs).toEqual([state.setState.isRedirected, mockFormatMessage]);
cb();
expect(getNotices).toHaveBeenCalled();
const { onLoad } = getNotices.mock.calls[0][0];

View File

@@ -0,0 +1,11 @@
import { defineMessages } from '@edx/frontend-platform/i18n';
const messages = defineMessages({
error404Message: {
id: 'learner-dash.notices.error404Message',
defaultMessage: 'This probably happened because the notices plugin is not installed on platform.',
description: 'Error message when notices API returns 404',
},
});
export default messages;

View File

@@ -1,65 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ZendeskFab snapshot 1`] = `
<Zendesk
cookies={true}
defer={true}
webWidget={
Object {
"answerBot": Object {
"avatar": Object {
"name": Object {
"*": "edX Support",
},
"url": "https://edx-cdn.org/v3/prod/favicon.ico",
},
"contactOnlyAfterQuery": true,
"suppress": false,
"title": Object {
"*": "edX Support",
},
},
"chat": Object {
"departments": Object {
"enabled": Array [
"account settings",
"billing and payments",
"certificates",
"deadlines",
"errors and technical issues",
"other",
"proctoring",
],
},
"suppress": false,
},
"contactForm": Object {
"attachments": true,
"selectTicketForm": Object {
"*": "Please choose your request type:",
},
"ticketForms": Array [
Object {
"fields": Array [
Object {
"id": "description",
"prefill": Object {
"*": "",
},
},
],
"id": 360003368814,
"subject": false,
},
],
},
"contactOptions": Object {
"enabled": false,
},
"helpCenter": Object {
"originalArticleButton": true,
},
}
}
/>
`;

View File

@@ -1,56 +0,0 @@
import React from 'react';
import { getConfig } from '@edx/frontend-platform';
import { useIntl } from '@edx/frontend-platform/i18n';
import Zendesk from 'react-zendesk';
import messages from './messages';
const ZendeskFab = () => {
const { formatMessage } = useIntl();
const setting = {
cookies: true,
webWidget: {
contactOptions: {
enabled: false,
},
chat: {
suppress: false,
departments: {
enabled: ['account settings', 'billing and payments', 'certificates', 'deadlines', 'errors and technical issues', 'other', 'proctoring'],
},
},
contactForm: {
ticketForms: [
{
id: 360003368814,
subject: false,
fields: [{ id: 'description', prefill: { '*': '' } }],
},
],
selectTicketForm: {
'*': formatMessage(messages.selectTicketForm),
},
attachments: true,
},
helpCenter: {
originalArticleButton: true,
},
answerBot: {
suppress: false,
contactOnlyAfterQuery: true,
title: { '*': formatMessage(messages.supportTitle) },
avatar: {
url: 'https://edx-cdn.org/v3/prod/favicon.ico',
name: { '*': formatMessage(messages.supportTitle) },
},
},
},
};
return (
<Zendesk defer zendeskKey={getConfig().ZENDESK_KEY} {...setting} />
);
};
export default ZendeskFab;

View File

@@ -1,12 +0,0 @@
import { shallow } from '@edx/react-unit-test-utils';
import ZendeskFab from '.';
jest.mock('react-zendesk', () => 'Zendesk');
describe('ZendeskFab', () => {
test('snapshot', () => {
const wrapper = shallow(<ZendeskFab />);
expect(wrapper.snapshot).toMatchSnapshot();
});
});

View File

@@ -1,16 +0,0 @@
import { defineMessages } from '@edx/frontend-platform/i18n';
const messages = defineMessages({
supportTitle: {
id: 'zendesk.supportTitle',
description: 'Title for the support button',
defaultMessage: 'edX Support',
},
selectTicketForm: {
id: 'zendesk.selectTicketForm',
description: 'Select ticket form',
defaultMessage: 'Please choose your request type:',
},
});
export default messages;

View File

@@ -12,12 +12,14 @@ const configuration = {
// ACCESS_TOKEN_COOKIE_NAME: process.env.ACCESS_TOKEN_COOKIE_NAME,
LEARNING_BASE_URL: process.env.LEARNING_BASE_URL,
SESSION_COOKIE_DOMAIN: process.env.SESSION_COOKIE_DOMAIN || '',
ZENDESK_KEY: process.env.ZENDESK_KEY,
SUPPORT_URL: process.env.SUPPORT_URL || null,
ENABLE_NOTICES: process.env.ENABLE_NOTICES || null,
CAREER_LINK_URL: process.env.CAREER_LINK_URL || null,
LOGO_URL: process.env.LOGO_URL,
ENABLE_EDX_PERSONAL_DASHBOARD: process.env.ENABLE_EDX_PERSONAL_DASHBOARD === 'true',
SEARCH_CATALOG_URL: process.env.SEARCH_CATALOG_URL || null,
ENABLE_PROGRAMS: process.env.ENABLE_PROGRAMS === 'true',
NON_BROWSABLE_COURSES: process.env.NON_BROWSABLE_COURSES === 'true',
};
const features = {};

View File

@@ -27,7 +27,7 @@ reduxHooks.useCardCourseRunData.mockReturnValue({ homeUrl });
const execEdPath = (cardId) => `exec-ed-tracking-path=${cardId}`;
reduxHooks.useCardExecEdTrackingParam.mockImplementation(execEdPath);
reduxHooks.useTrackCourseEvent.mockImplementation(
(eventName, cardId, upgradeUrl) => ({ trackCourseEvent: { eventName, cardId, upgradeUrl } }),
(eventName, cardId, url) => ({ trackCourseEvent: { eventName, cardId, url } }),
);
describe('BeginCourseButton', () => {

View File

@@ -26,7 +26,7 @@ reduxHooks.useCardCourseRunData.mockReturnValue({ resumeUrl });
const execEdPath = (cardId) => `exec-ed-tracking-path=${cardId}`;
reduxHooks.useCardExecEdTrackingParam.mockImplementation(execEdPath);
reduxHooks.useTrackCourseEvent.mockImplementation(
(eventName, cardId, upgradeUrl) => ({ trackCourseEvent: { eventName, cardId, upgradeUrl } }),
(eventName, cardId, url) => ({ trackCourseEvent: { eventName, cardId, url } }),
);
let wrapper;

View File

@@ -1,45 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Locked } from '@openedx/paragon/icons';
import { useIntl } from '@edx/frontend-platform/i18n';
import track from 'tracking';
import { reduxHooks } from 'hooks';
import useActionDisabledState from '../hooks';
import ActionButton from './ActionButton';
import messages from './messages';
export const UpgradeButton = ({ cardId }) => {
const { formatMessage } = useIntl();
const { upgradeUrl } = reduxHooks.useCardCourseRunData(cardId);
const { disableUpgradeCourse } = useActionDisabledState(cardId);
const trackUpgradeClick = reduxHooks.useTrackCourseEvent(
track.course.upgradeClicked,
cardId,
upgradeUrl,
);
const enabledProps = {
as: 'a',
href: upgradeUrl,
onClick: trackUpgradeClick,
};
return (
<ActionButton
iconBefore={Locked}
variant="outline-primary"
disabled={disableUpgradeCourse}
{...!disableUpgradeCourse && enabledProps}
>
{formatMessage(messages.upgrade)}
</ActionButton>
);
};
UpgradeButton.propTypes = {
cardId: PropTypes.string.isRequired,
};
export default UpgradeButton;

View File

@@ -1,49 +0,0 @@
import { shallow } from '@edx/react-unit-test-utils';
import track from 'tracking';
import { reduxHooks } from 'hooks';
import useActionDisabledState from '../hooks';
import UpgradeButton from './UpgradeButton';
jest.mock('tracking', () => ({
course: {
upgradeClicked: jest.fn().mockName('segment.trackUpgradeClicked'),
},
}));
jest.mock('hooks', () => ({
reduxHooks: {
useCardCourseRunData: jest.fn(),
useTrackCourseEvent: jest.fn(
(eventName, cardId, upgradeUrl) => ({ trackCourseEvent: { eventName, cardId, upgradeUrl } }),
),
},
}));
jest.mock('../hooks', () => jest.fn(() => ({ disableUpgradeCourse: false })));
jest.mock('./ActionButton', () => 'ActionButton');
describe('UpgradeButton', () => {
const props = {
cardId: 'cardId',
};
const upgradeUrl = 'upgradeUrl';
reduxHooks.useCardCourseRunData.mockReturnValue({ upgradeUrl });
describe('snapshot', () => {
test('can upgrade', () => {
const wrapper = shallow(<UpgradeButton {...props} />);
expect(wrapper.snapshot).toMatchSnapshot();
expect(wrapper.instance.props.disabled).toEqual(false);
expect(wrapper.instance.props.onClick).toEqual(reduxHooks.useTrackCourseEvent(
track.course.upgradeClicked,
props.cardId,
upgradeUrl,
));
});
test('cannot upgrade', () => {
useActionDisabledState.mockReturnValueOnce({ disableUpgradeCourse: true });
const wrapper = shallow(<UpgradeButton {...props} />);
expect(wrapper.snapshot).toMatchSnapshot();
expect(wrapper.instance.props.disabled).toEqual(true);
});
});
});

View File

@@ -15,7 +15,7 @@ jest.mock('hooks', () => ({
reduxHooks: {
useCardCourseRunData: jest.fn(() => ({ homeUrl: 'homeUrl' })),
useTrackCourseEvent: jest.fn(
(eventName, cardId, upgradeUrl) => ({ trackCourseEvent: { eventName, cardId, upgradeUrl } }),
(eventName, cardId, url) => ({ trackCourseEvent: { eventName, cardId, url } }),
),
},
}));

View File

@@ -6,11 +6,11 @@ exports[`BeginCourseButton snapshot disabled snapshot 1`] = `
disabled={true}
href="#"
onClick={
Object {
"trackCourseEvent": Object {
{
"trackCourseEvent": {
"cardId": "cardId",
"eventName": [MockFunction segment.enterCourseClicked],
"upgradeUrl": "home-urlexec-ed-tracking-path=cardId",
"url": "home-urlexec-ed-tracking-path=cardId",
},
}
}
@@ -25,11 +25,11 @@ exports[`BeginCourseButton snapshot enabled snapshot 1`] = `
disabled={false}
href="#"
onClick={
Object {
"trackCourseEvent": Object {
{
"trackCourseEvent": {
"cardId": "cardId",
"eventName": [MockFunction segment.enterCourseClicked],
"upgradeUrl": "home-urlexec-ed-tracking-path=cardId",
"url": "home-urlexec-ed-tracking-path=cardId",
},
}
}

View File

@@ -6,11 +6,11 @@ exports[`ResumeButton snapshot disabled snapshot 1`] = `
disabled={true}
href="#"
onClick={
Object {
"trackCourseEvent": Object {
{
"trackCourseEvent": {
"cardId": "cardId",
"eventName": [MockFunction segment.enterCourseClicked],
"upgradeUrl": "resume-urlexec-ed-tracking-path=cardId",
"url": "resume-urlexec-ed-tracking-path=cardId",
},
}
}
@@ -25,11 +25,11 @@ exports[`ResumeButton snapshot enabled snapshot 1`] = `
disabled={false}
href="#"
onClick={
Object {
"trackCourseEvent": Object {
{
"trackCourseEvent": {
"cardId": "cardId",
"eventName": [MockFunction segment.enterCourseClicked],
"upgradeUrl": "resume-urlexec-ed-tracking-path=cardId",
"url": "resume-urlexec-ed-tracking-path=cardId",
},
}
}

View File

@@ -1,32 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`UpgradeButton snapshot can upgrade 1`] = `
<ActionButton
as="a"
disabled={false}
href="upgradeUrl"
iconBefore={[MockFunction icons.Locked]}
onClick={
Object {
"trackCourseEvent": Object {
"cardId": "cardId",
"eventName": [MockFunction segment.trackUpgradeClicked],
"upgradeUrl": "upgradeUrl",
},
}
}
variant="outline-primary"
>
Upgrade
</ActionButton>
`;
exports[`UpgradeButton snapshot cannot upgrade 1`] = `
<ActionButton
disabled={true}
iconBefore={[MockFunction icons.Locked]}
variant="outline-primary"
>
Upgrade
</ActionButton>
`;

View File

@@ -6,11 +6,11 @@ exports[`ViewCourseButton learner can view course 1`] = `
disabled={false}
href="#"
onClick={
Object {
"trackCourseEvent": Object {
{
"trackCourseEvent": {
"cardId": "cardId",
"eventName": [MockFunction segment.enterCourseClicked],
"upgradeUrl": "homeUrl",
"url": "homeUrl",
},
}
}
@@ -25,11 +25,11 @@ exports[`ViewCourseButton learner cannot view course 1`] = `
disabled={true}
href="#"
onClick={
Object {
"trackCourseEvent": Object {
{
"trackCourseEvent": {
"cardId": "cardId",
"eventName": [MockFunction segment.enterCourseClicked],
"upgradeUrl": "homeUrl",
"url": "homeUrl",
},
}
}

View File

@@ -5,7 +5,7 @@ import { ActionRow } from '@openedx/paragon';
import { reduxHooks } from 'hooks';
import UpgradeButton from './UpgradeButton';
import CourseCardActionSlot from 'plugin-slots/CourseCardActionSlot';
import SelectSessionButton from './SelectSessionButton';
import BeginCourseButton from './BeginCourseButton';
import ResumeButton from './ResumeButton';
@@ -14,15 +14,13 @@ import ViewCourseButton from './ViewCourseButton';
export const CourseCardActions = ({ cardId }) => {
const { isEntitlement, isFulfilled } = reduxHooks.useCardEntitlementData(cardId);
const {
isVerified,
hasStarted,
isExecEd2UCourse,
} = reduxHooks.useCardEnrollmentData(cardId);
const { isArchived } = reduxHooks.useCardCourseRunData(cardId);
return (
<ActionRow data-test-id="CourseCardActions">
{!(isEntitlement || isVerified || isExecEd2UCourse) && <UpgradeButton cardId={cardId} />}
<CourseCardActionSlot cardId={cardId} />
{isEntitlement && (isFulfilled
? <ViewCourseButton cardId={cardId} />
: <SelectSessionButton cardId={cardId} />

View File

@@ -2,7 +2,7 @@ import { shallow } from '@edx/react-unit-test-utils';
import { reduxHooks } from 'hooks';
import UpgradeButton from './UpgradeButton';
import CourseCardActionSlot from 'plugin-slots/CourseCardActionSlot';
import SelectSessionButton from './SelectSessionButton';
import BeginCourseButton from './BeginCourseButton';
import ResumeButton from './ResumeButton';
@@ -19,7 +19,7 @@ jest.mock('hooks', () => ({
},
}));
jest.mock('./UpgradeButton', () => 'UpgradeButton');
jest.mock('plugin-slots/CourseCardActionSlot', () => 'CustomActionButton');
jest.mock('./SelectSessionButton', () => 'SelectSessionButton');
jest.mock('./ViewCourseButton', () => 'ViewCourseButton');
jest.mock('./BeginCourseButton', () => 'BeginCourseButton');
@@ -57,19 +57,7 @@ describe('CourseCardActions', () => {
});
});
describe('output', () => {
describe('Exec Ed course', () => {
it('does not render upgrade button', () => {
mockHooks({ isExecEd2UCourse: true });
render();
expect(el.instance.findByType(UpgradeButton).length).toEqual(0);
});
});
describe('entitlement course', () => {
it('does not render upgrade button', () => {
mockHooks({ isEntitlement: true });
render();
expect(el.instance.findByType(UpgradeButton).length).toEqual(0);
});
it('renders ViewCourseButton if fulfilled', () => {
mockHooks({ isEntitlement: true, isFulfilled: true });
render();
@@ -81,33 +69,26 @@ describe('CourseCardActions', () => {
expect(el.instance.findByType(SelectSessionButton)[0].props.cardId).toEqual(cardId);
});
});
describe('verified course', () => {
it('does not render upgrade button', () => {
mockHooks({ isVerified: true });
render();
expect(el.instance.findByType(UpgradeButton).length).toEqual(0);
});
});
describe('not entielement, verified, or exec ed', () => {
it('renders UpgradeButton and ViewCourseButton for archived courses', () => {
describe('not entitlement, verified, or exec ed', () => {
it('renders CourseCardActionSlot and ViewCourseButton for archived courses', () => {
mockHooks({ isArchived: true });
render();
expect(el.instance.findByType(UpgradeButton)[0].props.cardId).toEqual(cardId);
expect(el.instance.findByType(CourseCardActionSlot)[0].props.cardId).toEqual(cardId);
expect(el.instance.findByType(ViewCourseButton)[0].props.cardId).toEqual(cardId);
});
describe('unstarted courses', () => {
it('renders UpgradeButton and BeginCourseButton', () => {
it('renders CourseCardActionSlot and BeginCourseButton', () => {
mockHooks();
render();
expect(el.instance.findByType(UpgradeButton)[0].props.cardId).toEqual(cardId);
expect(el.instance.findByType(CourseCardActionSlot)[0].props.cardId).toEqual(cardId);
expect(el.instance.findByType(BeginCourseButton)[0].props.cardId).toEqual(cardId);
});
});
describe('active courses (started, and not archived)', () => {
it('renders UpgradeButton and ResumeButton', () => {
it('renders CourseCardActionSlot and ResumeButton', () => {
mockHooks({ hasStarted: true });
render();
expect(el.instance.findByType(UpgradeButton)[0].props.cardId).toEqual(cardId);
expect(el.instance.findByType(CourseCardActionSlot)[0].props.cardId).toEqual(cardId);
expect(el.instance.findByType(ResumeButton)[0].props.cardId).toEqual(cardId);
});
});

View File

@@ -1,11 +1,6 @@
import { defineMessages } from '@edx/frontend-platform/i18n';
const messages = defineMessages({
upgrade: {
id: 'learner-dash.courseCard.actions.upgrade',
description: 'Course card upgrade button text',
defaultMessage: 'Upgrade',
},
beginCourse: {
id: 'learner-dash.courseCard.actions.beginCourse',
description: 'Course card begin-course button text',

View File

@@ -12,7 +12,6 @@ export const CourseBanner = ({ cardId }) => {
const {
isVerified,
isAuditAccessExpired,
canUpgrade,
coursewareAccess = {},
} = reduxHooks.useCardEnrollmentData(cardId);
const courseRun = reduxHooks.useCardCourseRunData(cardId);
@@ -26,13 +25,7 @@ export const CourseBanner = ({ cardId }) => {
return (
<>
{isAuditAccessExpired
&& (canUpgrade ? (
<Banner>
{formatMessage(messages.auditAccessExpired)}
{' '}
{formatMessage(messages.upgradeToAccess)}
</Banner>
) : (
&& (
<Banner>
{formatMessage(messages.auditAccessExpired)}
{' '}
@@ -40,17 +33,7 @@ export const CourseBanner = ({ cardId }) => {
{formatMessage(messages.findAnotherCourse)}
</Hyperlink>
</Banner>
))}
{courseRun.isActive && !canUpgrade && (
<Banner>
{formatMessage(messages.upgradeDeadlinePassed)}
{' '}
<Hyperlink isInline destination={courseRun.marketingUrl || ''}>
{formatMessage(messages.exploreCourseDetails)}
</Hyperlink>
</Banner>
)}
)}
{(!isStaff && isTooEarly && courseRun.startDate) && (
<Banner>
@@ -59,6 +42,7 @@ export const CourseBanner = ({ cardId }) => {
})}
</Banner>
)}
{(!isStaff && hasUnmetPrerequisites) && (
<Banner>{formatMessage(messages.prerequisitesNotMet)}</Banner>
)}

View File

@@ -25,7 +25,6 @@ let el;
const enrollmentData = {
isVerified: false,
canUpgrade: false,
isAuditAccessExpired: false,
coursewareAccess: {
hasUnmetPrerequisites: false,
@@ -65,51 +64,18 @@ describe('CourseBanner', () => {
render({ enrollment: { isVerified: true } });
expect(el.isEmptyRender()).toEqual(true);
});
describe('audit access expired, can upgrade', () => {
beforeEach(() => {
render({ enrollment: { isAuditAccessExpired: true, canUpgrade: true } });
});
test('snapshot: (auditAccessExpired, upgradeToAccess)', () => {
expect(el.snapshot).toMatchSnapshot();
});
test('messages: (auditAccessExpired, upgradeToAccess)', () => {
expect(el.instance.children[0].children[0].el).toContain(messages.auditAccessExpired.defaultMessage);
expect(el.instance.children[0].children[2].el).toContain(messages.upgradeToAccess.defaultMessage);
});
});
describe('audit access expired, cannot upgrade', () => {
describe('audit access expired', () => {
beforeEach(() => {
render({ enrollment: { isAuditAccessExpired: true } });
});
test('snapshot: (auditAccessExpired, findAnotherCourse hyperlink)', () => {
expect(el.snapshot).toMatchSnapshot();
});
test('messages: (auditAccessExpired, upgradeToAccess)', () => {
test('messages: auditAccessExpired', () => {
expect(el.instance.children[0].children[0].el).toContain(messages.auditAccessExpired.defaultMessage);
expect(el.instance.findByType(Hyperlink)[0].children[0].el).toEqual(messages.findAnotherCourse.defaultMessage);
});
});
describe('course run active and cannot upgrade', () => {
beforeEach(() => {
render({ courseRun: { isActive: true } });
});
test('snapshot: (upgradseDeadlinePassed, exploreCourseDetails hyperlink)', () => {
expect(el.snapshot).toMatchSnapshot();
});
test('messages: (upgradseDeadlinePassed, exploreCourseDetails hyperlink)', () => {
expect(el.instance.children[0].children[0].el).toContain(messages.upgradeDeadlinePassed.defaultMessage);
const link = el.instance.findByType(Hyperlink);
expect(link[0].children[0].el).toEqual(messages.exploreCourseDetails.defaultMessage);
expect(link[0].props.destination).toEqual(courseRunData.marketingUrl);
});
});
test('no display if audit access not expired and (course is not active or can upgrade)', () => {
render();
// isEmptyRender() isn't true because the minimal is <Fragment />
expect(el.instance.children).toEqual([]);
render({ enrollment: { canUpgrade: true }, courseRun: { isActive: true } });
expect(el.instance.children).toEqual([]);
});
describe('unmet prerequisites', () => {
beforeEach(() => {
render({ enrollment: { coursewareAccess: { hasUnmetPrerequisites: true } } });

View File

@@ -10,14 +10,14 @@ exports[`CreditBanner component render with error state snapshot 1`] = `
>
<format-message-function
message={
Object {
{
"defaultMessage": "An error occurred with this transaction. For help, contact {supportEmailLink}.",
"description": "",
"id": "learner-dash.courseCard.banners.credit.error",
}
}
values={
Object {
{
"supportEmailLink": <MailtoLink
to="test-support-email"
>

View File

@@ -27,8 +27,8 @@ exports[`CreditContent component render with action snapshot 1`] = `
</ActionRow>
<CreditRequestForm
requestData={
Object {
"parameters": Object {
{
"parameters": {
"key1": "val1",
},
"url": "test-request-data-url",
@@ -48,8 +48,8 @@ exports[`CreditContent component render without action snapshot 1`] = `
</div>
<CreditRequestForm
requestData={
Object {
"parameters": Object {
{
"parameters": {
"key1": "val1",
},
"url": "test-request-data-url",

View File

@@ -13,12 +13,12 @@ exports[`RelatedProgramsBanner render with programs 1`] = `
</span>
<ProgramsList
programs={
Array [
Object {
[
{
"title": "Program 1",
"url": "http://example.com/program1",
},
Object {
{
"title": "Program 2",
"url": "http://example.com/program2",
},

View File

@@ -43,14 +43,14 @@ exports[`CertificateBanner snapshot is restricted and verified with billing emai
<format-message-function
message={
Object {
{
"defaultMessage": "If you would like a refund on your Certificate of Achievement, please contact our billing address {billingEmail}",
"description": "Message to learners to contact billing for certificate refunds",
"id": "learner-dash.courseCard.banners.certificateRefundContactBilling",
}
}
values={
Object {
{
"billingEmail": <MailtoLink
to="billing@email"
>
@@ -68,14 +68,14 @@ exports[`CertificateBanner snapshot is restricted and verified with support and
>
<format-message-function
message={
Object {
{
"defaultMessage": "Your Certificate of Achievement is being held pending confirmation that the issuance of your Certificate is in compliance with strict U.S. embargoes on Iran, Cuba, Syria, and Sudan. If you think our system has mistakenly identified you as being connected with one of those countries, please let us know by contacting {supportEmail}.",
"description": "Restricted certificate warning message",
"id": "learner-dash.courseCard.banners.certificateRestricted",
}
}
values={
Object {
{
"supportEmail": <MailtoLink
to="suport@email"
>
@@ -87,14 +87,14 @@ exports[`CertificateBanner snapshot is restricted and verified with support and
<format-message-function
message={
Object {
{
"defaultMessage": "If you would like a refund on your Certificate of Achievement, please contact our billing address {billingEmail}",
"description": "Message to learners to contact billing for certificate refunds",
"id": "learner-dash.courseCard.banners.certificateRefundContactBilling",
}
}
values={
Object {
{
"billingEmail": <MailtoLink
to="billing@email"
>
@@ -112,14 +112,14 @@ exports[`CertificateBanner snapshot is restricted and verified with support emai
>
<format-message-function
message={
Object {
{
"defaultMessage": "Your Certificate of Achievement is being held pending confirmation that the issuance of your Certificate is in compliance with strict U.S. embargoes on Iran, Cuba, Syria, and Sudan. If you think our system has mistakenly identified you as being connected with one of those countries, please let us know by contacting {supportEmail}.",
"description": "Restricted certificate warning message",
"id": "learner-dash.courseCard.banners.certificateRestricted",
}
}
values={
Object {
{
"supportEmail": <MailtoLink
to="suport@email"
>
@@ -147,14 +147,14 @@ exports[`CertificateBanner snapshot is restricted with support email 1`] = `
>
<format-message-function
message={
Object {
{
"defaultMessage": "Your Certificate of Achievement is being held pending confirmation that the issuance of your Certificate is in compliance with strict U.S. embargoes on Iran, Cuba, Syria, and Sudan. If you think our system has mistakenly identified you as being connected with one of those countries, please let us know by contacting {supportEmail}.",
"description": "Restricted certificate warning message",
"id": "learner-dash.courseCard.banners.certificateRestricted",
}
}
values={
Object {
{
"supportEmail": <MailtoLink
to="suport@email"
>

View File

@@ -1,16 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`CourseBanner audit access expired, can upgrade snapshot: (auditAccessExpired, upgradeToAccess) 1`] = `
<Fragment>
<Banner>
Your audit access to this course has expired.
Upgrade now to access your course again.
</Banner>
</Fragment>
`;
exports[`CourseBanner audit access expired, cannot upgrade snapshot: (auditAccessExpired, findAnotherCourse hyperlink) 1`] = `
exports[`CourseBanner audit access expired snapshot: (auditAccessExpired, findAnotherCourse hyperlink) 1`] = `
<Fragment>
<Banner>
Your audit access to this course has expired.
@@ -25,21 +15,6 @@ exports[`CourseBanner audit access expired, cannot upgrade snapshot: (auditAcces
</Fragment>
`;
exports[`CourseBanner course run active and cannot upgrade snapshot: (upgradseDeadlinePassed, exploreCourseDetails hyperlink) 1`] = `
<Fragment>
<Banner>
Your upgrade deadline for this course has passed. To upgrade, enroll in a session that is farther in the future.
<Hyperlink
destination="marketing-url"
isInline={true}
>
Explore course details.
</Hyperlink>
</Banner>
</Fragment>
`;
exports[`CourseBanner snapshot: stacking banners 1`] = `<Fragment />`;
exports[`CourseBanner staff snapshot: isStaff 1`] = `<Fragment />`;

View File

@@ -4,14 +4,14 @@ exports[`EntitlementBanner snapshot: expiration warning 1`] = `
<Banner>
<format-message-function
message={
Object {
{
"defaultMessage": "You must {selectSessionButton} by {changeDeadline} to access the course.",
"description": "Entitlement course message when the entitlement is expiring soon.",
"id": "learner-dash.courseCard.banners.entitlementExpiringSoon",
}
}
values={
Object {
{
"changeDeadline": "11/11/2022",
"selectSessionButton": <Button
className="m-0 p-0"
@@ -33,14 +33,14 @@ exports[`EntitlementBanner snapshot: no sessions available 1`] = `
>
<format-message-function
message={
Object {
{
"defaultMessage": "There are no sessions available at the moment. The course team will create new sessions soon. If no sessions appear, please contact {emailLink} for information.",
"description": "Entitlement course message when no sessions are available",
"id": "learner-dash.courseCard.banners.entitlementUnavailable",
}
}
values={
Object {
{
"emailLink": <MailtoLink
to="test-support-email"
>

View File

@@ -8,7 +8,7 @@ exports[`CourseCardBanners render with isEnrolled false 1`] = `
<RelatedProgramsBanner
cardId="test-card-id"
/>
<CourseBanner
<CourseBannerSlot
cardId="test-card-id"
/>
<EntitlementBanner
@@ -25,7 +25,7 @@ exports[`CourseCardBanners renders default CourseCardBanners 1`] = `
<RelatedProgramsBanner
cardId="test-card-id"
/>
<CourseBanner
<CourseBannerSlot
cardId="test-card-id"
/>
<EntitlementBanner

View File

@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
import { reduxHooks } from 'hooks';
import CourseBanner from './CourseBanner';
import CourseBannerSlot from 'plugin-slots/CourseBannerSlot';
import CertificateBanner from './CertificateBanner';
import CreditBanner from './CreditBanner';
import EntitlementBanner from './EntitlementBanner';
@@ -14,7 +14,7 @@ export const CourseCardBanners = ({ cardId }) => {
return (
<div className="course-card-banners" data-testid="CourseCardBanners">
<RelatedProgramsBanner cardId={cardId} />
<CourseBanner cardId={cardId} />
<CourseBannerSlot cardId={cardId} />
<EntitlementBanner cardId={cardId} />
{isEnrolled && <CertificateBanner cardId={cardId} />}
{isEnrolled && <CreditBanner cardId={cardId} />}

View File

@@ -6,26 +6,11 @@ const messages = defineMessages({
description: 'Audit access expiration banner message',
defaultMessage: 'Your audit access to this course has expired.',
},
upgradeToAccess: {
id: 'learner-dash.courseCard.banners.upgradeToAccess',
description: 'Upgrade prompt for audit-expired learners that can still upgrade',
defaultMessage: 'Upgrade now to access your course again.',
},
findAnotherCourse: {
id: 'learner-dash.courseCard.banners.findAnotherCourse',
description: 'Action prompt taking learners to course exploration',
defaultMessage: 'Find another course',
},
upgradeDeadlinePassed: {
id: 'learner-dash.courseCard.banners.upgradeDeadlinePassed',
description: 'Audit upgrade deadline passed banner message',
defaultMessage: 'Your upgrade deadline for this course has passed. To upgrade, enroll in a session that is farther in the future.',
},
exploreCourseDetails: {
id: 'learner-dash.courseCard.banners.exploreCourseDetails',
description: 'Action prompt taking learners to course details page',
defaultMessage: 'Explore course details.',
},
certRestricted: {
id: 'learner-dash.courseCard.banners.certificateRestricted',
description: 'Restricted certificate warning message',

View File

@@ -10,8 +10,8 @@ export const useAccessMessage = ({ cardId }) => {
const courseRun = reduxHooks.useCardCourseRunData(cardId);
const formatDate = utilHooks.useFormatDate();
if (!courseRun.isStarted) {
if (!courseRun.startDate) { return null; }
const startDate = formatDate(courseRun.startDate);
if (!courseRun.startDate && !courseRun.advertisedStart) { return null; }
const startDate = courseRun.advertisedStart ? courseRun.advertisedStart : formatDate(courseRun.startDate);
return formatMessage(messages.courseStarts, { startDate });
}
if (enrollment.isEnrolled) {

View File

@@ -20,11 +20,13 @@ export const CourseCardImage = ({ cardId, orientation }) => {
const { isVerified } = reduxHooks.useCardEnrollmentData(cardId);
const { disableCourseTitle } = useActionDisabledState(cardId);
const handleImageClicked = reduxHooks.useTrackCourseEvent(courseImageClicked, cardId, homeUrl);
const wrapperClassName = `pgn__card-wrapper-image-cap overflow-visible ${orientation}`;
const wrapperClassName = `pgn__card-wrapper-image-cap d-inline-block overflow-visible ${orientation}`;
const image = (
<>
<img
className="pgn__card-image-cap show"
// w-100 is necessary for images on Safari, otherwise stretches full height of the image
// https://stackoverflow.com/a/44250830
className="pgn__card-image-cap w-100 show"
src={bannerImgSrc}
alt={formatMessage(messages.bannerAlt)}
/>

View File

@@ -18,8 +18,8 @@ jest.mock('hooks', () => ({
useCardCourseData: jest.fn(() => ({ bannerImgSrc: 'banner-img-src' })),
useCardCourseRunData: jest.fn(() => ({ homeUrl })),
useCardEnrollmentData: jest.fn(() => ({ isVerified: true })),
useTrackCourseEvent: jest.fn((eventName, cardId, upgradeUrl) => ({
trackCourseEvent: { eventName, cardId, upgradeUrl },
useTrackCourseEvent: jest.fn((eventName, cardId, url) => ({
trackCourseEvent: { eventName, cardId, url },
})),
},
}));

View File

@@ -17,7 +17,7 @@ exports[`CourseCardMenu render show dropdown hide unenroll item and disable emai
<SocialShareMenu
cardId="test-card-id"
emailSettings={
Object {
{
"hide": [MockFunction emailSettingHide],
"isVisible": false,
"show": [MockFunction emailSettingShow],
@@ -58,7 +58,7 @@ exports[`CourseCardMenu render show dropdown show unenroll and enable email snap
<SocialShareMenu
cardId="test-card-id"
emailSettings={
Object {
{
"hide": [MockFunction emailSettingHide],
"isVisible": false,
"show": [MockFunction emailSettingShow],

View File

@@ -17,8 +17,8 @@ jest.mock('hooks', () => ({
reduxHooks: {
useCardCourseData: jest.fn(() => ({ courseName: 'course-name' })),
useCardCourseRunData: jest.fn(() => ({ homeUrl })),
useTrackCourseEvent: jest.fn((eventName, cardId, upgradeUrl) => ({
trackCourseEvent: { eventName, cardId, upgradeUrl },
useTrackCourseEvent: jest.fn((eventName, cardId, url) => ({
trackCourseEvent: { eventName, cardId, url },
})),
},
}));

View File

@@ -2,14 +2,14 @@
exports[`CourseCardImage snapshot renders clickable link course Image 1`] = `
<a
className="pgn__card-wrapper-image-cap overflow-visible orientation"
className="pgn__card-wrapper-image-cap d-inline-block overflow-visible orientation"
href="home-url"
onClick={
Object {
"trackCourseEvent": Object {
{
"trackCourseEvent": {
"cardId": "cardId",
"eventName": [MockFunction segment.courseImageClicked],
"upgradeUrl": "home-url",
"url": "home-url",
},
}
}
@@ -18,7 +18,7 @@ exports[`CourseCardImage snapshot renders clickable link course Image 1`] = `
<Fragment>
<img
alt="Course thumbnail"
className="pgn__card-image-cap show"
className="pgn__card-image-cap w-100 show"
src="banner-img-src"
/>
<span
@@ -43,12 +43,12 @@ exports[`CourseCardImage snapshot renders clickable link course Image 1`] = `
exports[`CourseCardImage snapshot renders disabled link 1`] = `
<div
className="pgn__card-wrapper-image-cap overflow-visible orientation"
className="pgn__card-wrapper-image-cap d-inline-block overflow-visible orientation"
>
<Fragment>
<img
alt="Course thumbnail"
className="pgn__card-image-cap show"
className="pgn__card-image-cap w-100 show"
src="banner-img-src"
/>
<span

View File

@@ -7,11 +7,11 @@ exports[`CourseCardTitle snapshot renders clickable link course title 1`] = `
data-testid="CourseCardTitle"
href="home-url"
onClick={
Object {
"trackCourseEvent": Object {
{
"trackCourseEvent": {
"cardId": "cardId",
"eventName": [MockFunction segment.courseTitleClicked],
"upgradeUrl": "home-url",
"url": "home-url",
},
}
}

View File

@@ -3,19 +3,18 @@ import { reduxHooks } from 'hooks';
export const useActionDisabledState = (cardId) => {
const { isMasquerading } = reduxHooks.useMasqueradeData();
const {
canUpgrade, hasAccess, isAudit, isAuditAccessExpired,
hasAccess, isAudit, isAuditAccessExpired,
} = reduxHooks.useCardEnrollmentData(cardId);
const {
isEntitlement, isFulfilled, canChange, hasSessions,
} = reduxHooks.useCardEntitlementData(cardId);
const { resumeUrl, homeUrl, upgradeUrl } = reduxHooks.useCardCourseRunData(cardId);
const { resumeUrl, homeUrl } = reduxHooks.useCardCourseRunData(cardId);
const disableBeginCourse = !homeUrl || (isMasquerading || !hasAccess || (isAudit && isAuditAccessExpired));
const disableResumeCourse = !resumeUrl || (isMasquerading || !hasAccess || (isAudit && isAuditAccessExpired));
const disableViewCourse = !hasAccess || (isAudit && isAuditAccessExpired);
const disableSelectSession = !isEntitlement || isMasquerading || !hasAccess || (!canChange || !hasSessions);
const disableUpgradeCourse = !upgradeUrl || (isMasquerading && !canUpgrade);
const disableCourseTitle = (isEntitlement && !isFulfilled) || disableViewCourse;
@@ -23,7 +22,6 @@ export const useActionDisabledState = (cardId) => {
disableBeginCourse,
disableResumeCourse,
disableViewCourse,
disableUpgradeCourse,
disableSelectSession,
disableCourseTitle,
};

View File

@@ -16,7 +16,6 @@ const cardId = 'my-test-course-number';
describe('useActionDisabledState', () => {
const defaultData = {
isMasquerading: false,
canUpgrade: false,
isEntitlement: false,
isFulfilled: false,
canChange: false,
@@ -26,12 +25,10 @@ describe('useActionDisabledState', () => {
isAuditAccessExpired: false,
resumeUrl: 'resume.url',
homeUrl: 'home.url',
upgradeUrl: 'upgrade.url',
};
const mockHooksData = (args) => {
const {
isMasquerading,
canUpgrade,
isEntitlement,
isFulfilled,
canChange,
@@ -41,11 +38,9 @@ describe('useActionDisabledState', () => {
isAuditAccessExpired,
resumeUrl,
homeUrl,
upgradeUrl,
} = { ...defaultData, ...args };
reduxHooks.useMasqueradeData.mockReturnValueOnce({ isMasquerading });
reduxHooks.useCardEnrollmentData.mockReturnValueOnce({
canUpgrade,
hasAccess,
isAudit,
isAuditAccessExpired,
@@ -59,7 +54,6 @@ describe('useActionDisabledState', () => {
reduxHooks.useCardCourseRunData.mockReturnValueOnce({
resumeUrl,
homeUrl,
upgradeUrl,
});
};
@@ -121,21 +115,6 @@ describe('useActionDisabledState', () => {
testDisabled({ hasAccess: true }, false);
});
});
describe('disableUpgradeCourse', () => {
const testDisabled = (data, expected) => {
mockHooksData(data);
expect(runHook().disableUpgradeCourse).toBe(expected);
};
it('disable when upgradeUrl is invalid', () => {
testDisabled({ upgradeUrl: null }, true);
});
it('disable when isMasquerading is true and canUpgrade is false', () => {
testDisabled({ isMasquerading: true, canUpgrade: false }, true);
});
it('enable when all conditions are met', () => {
testDisabled({ canUpgrade: true }, false);
});
});
describe('disableSelectSession', () => {
const testDisabled = (data, expected) => {
mockHooksData(data);

View File

@@ -28,7 +28,7 @@ exports[`CourseFilterControls is not mobile snapshot 1`] = `
>
<FilterForm
filters={
Array [
[
"test-filter",
]
}
@@ -84,7 +84,7 @@ exports[`CourseFilterControls mobile snapshot 1`] = `
>
<FilterForm
filters={
Array [
[
"test-filter",
]
}
@@ -144,7 +144,7 @@ exports[`CourseFilterControls no courses snapshot 1`] = `
>
<FilterForm
filters={
Array [
[
"test-filter",
]
}

View File

@@ -11,7 +11,7 @@ exports[`FilterForm snapshot renders 1`] = `
name="course-status-filters"
onChange={[MockFunction handleFilterChange]}
value={
Array [
[
"test-filter",
]
}

View File

@@ -9,9 +9,10 @@ import CourseCard from 'containers/CourseCard';
import { useIsCollapsed } from './hooks';
export const CourseList = ({
filterOptions, setPageNumber, numPages, showFilters, visibleList,
}) => {
export const CourseList = ({ courseListData }) => {
const {
filterOptions, setPageNumber, numPages, showFilters, visibleList,
} = courseListData;
const isCollapsed = useIsCollapsed();
return (
<>
@@ -38,14 +39,16 @@ export const CourseList = ({
);
};
CourseList.propTypes = {
export const courseListDataShape = PropTypes.shape({
showFilters: PropTypes.bool.isRequired,
// eslint-disable-next-line react/forbid-prop-types
visibleList: PropTypes.arrayOf(PropTypes.object).isRequired,
// eslint-disable-next-line react/forbid-prop-types
filterOptions: PropTypes.object.isRequired,
visibleList: PropTypes.arrayOf(PropTypes.shape()).isRequired,
filterOptions: PropTypes.shape().isRequired,
numPages: PropTypes.number.isRequired,
setPageNumber: PropTypes.func.isRequired,
});
CourseList.propTypes = {
courseListData: courseListDataShape,
};
export default CourseList;

View File

@@ -23,7 +23,7 @@ describe('CourseList', () => {
useIsCollapsed.mockReturnValue(false);
const createWrapper = (courseListData = defaultCourseListData) => (
shallow(<CourseList {...courseListData} />)
shallow(<CourseList courseListData={courseListData} />)
);
describe('no courses or filters', () => {

View File

@@ -9,9 +9,11 @@ exports[`NoCoursesView snapshot 1`] = `
alt="No Courses view banner"
src="icon/mock/path"
/>
<h1>
<h3
className="h1"
>
Looking for a new challenge?
</h1>
</h3>
<p>
Explore our courses to add them to your dashboard.
</p>

View File

@@ -19,9 +19,9 @@ export const NoCoursesView = () => {
className="d-flex align-items-center justify-content-center mb-4.5"
>
<Image src={emptyCourseSVG} alt={formatMessage(messages.bannerAlt)} />
<h1>
<h3 className="h1">
{formatMessage(messages.lookingForChallengePrompt)}
</h1>
</h3>
<p>
{formatMessage(messages.exploreCoursesPrompt)}
</p>

View File

@@ -18,11 +18,7 @@ exports[`CoursesPanel no courses snapshot 1`] = `
<CourseFilterControls />
</div>
</div>
<PluginSlot
id="no_courses_view"
>
<NoCoursesView />
</PluginSlot>
<NoCoursesViewSlot />
</div>
`;
@@ -44,16 +40,16 @@ exports[`CoursesPanel with courses snapshot 1`] = `
<CourseFilterControls />
</div>
</div>
<PluginSlot
id="course_list"
>
<CourseList
filterOptions={Object {}}
numPages={1}
setPageNumber={[MockFunction setPageNumber]}
showFilters={false}
visibleList={Array []}
/>
</PluginSlot>
<CourseListSlot
courseListData={
{
"filterOptions": {},
"numPages": 1,
"setPageNumber": [MockFunction setPageNumber],
"showFilters": false,
"visibleList": [],
}
}
/>
</div>
`;

View File

@@ -1,7 +1,5 @@
import React from 'react';
import queryString from 'query-string';
import { ListPageSize, SortKeys } from 'data/constants/app';
import { reduxHooks } from 'hooks';
import { StrictDict } from 'utils';
@@ -27,12 +25,13 @@ export const useCourseListData = () => {
const [sortBy, setSortBy] = module.state.sortBy(SortKeys.enrolled);
const querySearch = queryString.parse(window.location.search, { parseNumbers: true });
const querySearch = new URLSearchParams(window.location.search);
const disablePagination = querySearch.get('disable_pagination');
const { numPages, visibleList } = reduxHooks.useCurrentCourseList({
sortBy,
filters,
pageSize: querySearch?.disable_pagination === 1 ? 0 : ListPageSize,
pageSize: Number(disablePagination) === 1 ? 0 : ListPageSize,
});
const handleRemoveFilter = (filter) => () => removeFilter(filter);

View File

@@ -1,5 +1,3 @@
import queryString from 'query-string';
import { MockUseState } from 'testUtils';
import { reduxHooks } from 'hooks';
import { ListPageSize, SortKeys } from 'data/constants/app';
@@ -15,8 +13,10 @@ jest.mock('hooks', () => ({
},
}));
jest.mock('query-string', () => ({
parse: jest.fn(() => ({})),
const mockGet = jest.fn(() => ({}));
global.URLSearchParams = jest.fn().mockImplementation(() => ({
get: mockGet,
}));
const state = new MockUseState(hooks);
@@ -67,7 +67,7 @@ describe('CourseList hooks', () => {
it('loads current course list with page size 0 if/when there is query param disable_pagination=1', () => {
state.mock();
state.mockVal(state.keys.sortBy, testSortBy);
queryString.parse.mockReturnValueOnce({ disable_pagination: 1 });
mockGet.mockReturnValueOnce('1');
out = hooks.useCourseListData();
expect(reduxHooks.useCurrentCourseList).toHaveBeenCalledWith({
sortBy: testSortBy,

View File

@@ -1,15 +1,13 @@
import React from 'react';
import { PluginSlot } from '@openedx/frontend-plugin-framework';
import { useIntl } from '@edx/frontend-platform/i18n';
import { reduxHooks } from 'hooks';
import {
CourseFilterControls,
} from 'containers/CourseFilterControls';
import NoCoursesView from './NoCoursesView';
import CourseList from './CourseList';
import CourseListSlot from 'plugin-slots/CourseListSlot';
import NoCoursesViewSlot from 'plugin-slots/NoCoursesViewSlot';
import { useCourseListData } from './hooks';
@@ -34,19 +32,7 @@ export const CoursesPanel = () => {
<CourseFilterControls {...courseListData.filterOptions} />
</div>
</div>
{hasCourses ? (
<PluginSlot
id="course_list"
>
<CourseList {...courseListData} />
</PluginSlot>
) : (
<PluginSlot
id="no_courses_view"
>
<NoCoursesView />
</PluginSlot>
)}
{hasCourses ? <CourseListSlot courseListData={courseListData} /> : <NoCoursesViewSlot />}
</div>
);
};

View File

@@ -3,7 +3,8 @@ import PropTypes from 'prop-types';
import { Container, Col, Row } from '@openedx/paragon';
import WidgetFooter from 'containers/WidgetContainers/WidgetFooter';
import WidgetSidebarSlot from 'plugin-slots/WidgetSidebarSlot';
import hooks from './hooks';
export const columnConfig = {
@@ -23,11 +24,10 @@ export const columnConfig = {
},
};
export const DashboardLayout = ({ children, sidebar: Sidebar }) => {
export const DashboardLayout = ({ children }) => {
const {
isCollapsed,
sidebarShowing,
setSidebarShowing,
} = hooks.useDashboardLayoutData();
const courseListColumnProps = sidebarShowing
@@ -40,14 +40,8 @@ export const DashboardLayout = ({ children, sidebar: Sidebar }) => {
<Col {...courseListColumnProps} className="course-list-column">
{children}
</Col>
<Col {...columnConfig.sidebar} className="sidebar-column">
{!isCollapsed && (<h2 className="course-list-title">&nbsp;</h2>)}
<Sidebar setSidebarShowing={setSidebarShowing} />
</Col>
</Row>
<Row>
<Col>
<WidgetFooter />
<Col {...columnConfig.sidebar} className={['sidebar-column', !isCollapsed && 'not-collapsed']}>
<WidgetSidebarSlot />
</Col>
</Row>
</Container>
@@ -55,7 +49,6 @@ export const DashboardLayout = ({ children, sidebar: Sidebar }) => {
};
DashboardLayout.propTypes = {
children: PropTypes.node.isRequired,
sidebar: PropTypes.func.isRequired,
};
export default DashboardLayout;

View File

@@ -16,17 +16,13 @@ const hookProps = {
};
hooks.useDashboardLayoutData.mockReturnValue(hookProps);
const props = {
sidebar: jest.fn(() => 'test-sidebar-content'),
};
const children = 'test-children';
let el;
describe('DashboardLayout', () => {
beforeEach(() => {
jest.clearAllMocks();
el = shallow(<DashboardLayout {...props}>{children}</DashboardLayout>);
el = shallow(<DashboardLayout>{children}</DashboardLayout>);
});
const testColumns = () => {
@@ -40,17 +36,13 @@ describe('DashboardLayout', () => {
const columns = el.instance.findByType(Row)[0].findByType(Col);
expect(columns[0].children).not.toHaveLength(0);
});
it('displays sidebar prop in second column', () => {
it('displays WidgetSidebarSlot in second column', () => {
const columns = el.instance.findByType(Row)[0].findByType(Col);
expect(columns[1].findByType(props.sidebar)).toHaveLength(1);
});
it('displays a footer in the second row', () => {
const columns = el.instance.findByType(Row)[1].findByType(Col);
expect(columns[0].children[0].type).toEqual('WidgetFooter');
expect(columns[1].findByType('WidgetSidebarSlot')).toHaveLength(1);
});
};
const testSidebarLayout = () => {
it('displays widthSidebar width for course list column', () => {
it('displays withSidebar width for course list column', () => {
const columns = el.instance.findByType(Row)[0].findByType(Col);
Object.keys(columnConfig.courseList.withSidebar).forEach(size => {
expect(columns[0].props[size]).toEqual(columnConfig.courseList.withSidebar[size]);
@@ -92,10 +84,9 @@ describe('DashboardLayout', () => {
describe('not collapsed', () => {
const testWidgetSpacing = () => {
it('shows a blank (nbsp) h2 spacer component above widget sidebar', () => {
it('shows not-collapsed class on widget sidebar', () => {
const columns = el.instance.findByType(Col);
// nonbreaking space equivalent
expect(columns[1].findByType('h2')[0].children[0].el).toEqual('\xA0');
expect(columns[1].props.className).toContain('not-collapsed');
});
};
describe('sidebar showing', () => {

View File

@@ -9,13 +9,13 @@ exports[`DashboardLayout collapsed sidebar not showing snapshot 1`] = `
<Col
className="course-list-column"
lg={
Object {
{
"offset": 0,
"span": 12,
}
}
xl={
Object {
{
"offset": 0,
"span": 12,
}
@@ -24,28 +24,26 @@ exports[`DashboardLayout collapsed sidebar not showing snapshot 1`] = `
test-children
</Col>
<Col
className="sidebar-column"
className={
[
"sidebar-column",
false,
]
}
lg={
Object {
{
"offset": 0,
"span": 12,
}
}
xl={
Object {
{
"offset": 0,
"span": 4,
}
}
>
<mockConstructor
setSidebarShowing={[MockFunction hooks.setSidebarShowing]}
/>
</Col>
</Row>
<Row>
<Col>
<WidgetFooter />
<WidgetSidebarSlot />
</Col>
</Row>
</Container>
@@ -60,13 +58,13 @@ exports[`DashboardLayout collapsed sidebar showing snapshot 1`] = `
<Col
className="course-list-column"
lg={
Object {
{
"offset": 0,
"span": 12,
}
}
xl={
Object {
{
"offset": 0,
"span": 8,
}
@@ -75,28 +73,26 @@ exports[`DashboardLayout collapsed sidebar showing snapshot 1`] = `
test-children
</Col>
<Col
className="sidebar-column"
className={
[
"sidebar-column",
false,
]
}
lg={
Object {
{
"offset": 0,
"span": 12,
}
}
xl={
Object {
{
"offset": 0,
"span": 4,
}
}
>
<mockConstructor
setSidebarShowing={[MockFunction hooks.setSidebarShowing]}
/>
</Col>
</Row>
<Row>
<Col>
<WidgetFooter />
<WidgetSidebarSlot />
</Col>
</Row>
</Container>
@@ -111,13 +107,13 @@ exports[`DashboardLayout not collapsed sidebar not showing snapshot 1`] = `
<Col
className="course-list-column"
lg={
Object {
{
"offset": 0,
"span": 12,
}
}
xl={
Object {
{
"offset": 0,
"span": 12,
}
@@ -126,33 +122,26 @@ exports[`DashboardLayout not collapsed sidebar not showing snapshot 1`] = `
test-children
</Col>
<Col
className="sidebar-column"
className={
[
"sidebar-column",
"not-collapsed",
]
}
lg={
Object {
{
"offset": 0,
"span": 12,
}
}
xl={
Object {
{
"offset": 0,
"span": 4,
}
}
>
<h2
className="course-list-title"
>
 
</h2>
<mockConstructor
setSidebarShowing={[MockFunction hooks.setSidebarShowing]}
/>
</Col>
</Row>
<Row>
<Col>
<WidgetFooter />
<WidgetSidebarSlot />
</Col>
</Row>
</Container>
@@ -167,13 +156,13 @@ exports[`DashboardLayout not collapsed sidebar showing snapshot 1`] = `
<Col
className="course-list-column"
lg={
Object {
{
"offset": 0,
"span": 12,
}
}
xl={
Object {
{
"offset": 0,
"span": 8,
}
@@ -182,33 +171,26 @@ exports[`DashboardLayout not collapsed sidebar showing snapshot 1`] = `
test-children
</Col>
<Col
className="sidebar-column"
className={
[
"sidebar-column",
"not-collapsed",
]
}
lg={
Object {
{
"offset": 0,
"span": 12,
}
}
xl={
Object {
{
"offset": 0,
"span": 4,
}
}
>
<h2
className="course-list-title"
>
 
</h2>
<mockConstructor
setSidebarShowing={[MockFunction hooks.setSidebarShowing]}
/>
</Col>
</Row>
<Row>
<Col>
<WidgetFooter />
<WidgetSidebarSlot />
</Col>
</Row>
</Container>

View File

@@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Dashboard snapshots courses loaded, show select session modal, no available dashboards snapshot 1`] = `
exports[`Dashboard snapshots courses loaded, show select session modal snapshot 1`] = `
<div
className="d-flex flex-column p-2 pt-0"
id="dashboard-container"
@@ -11,15 +11,14 @@ exports[`Dashboard snapshots courses loaded, show select session modal, no avail
test-page-title
</h1>
<Fragment>
<DashboardModalSlot />
<SelectSessionModal />
</Fragment>
<div
data-testid="dashboard-content"
id="dashboard-content"
>
<DashboardLayout
sidebar="LoadedWidgetSidebar"
>
<DashboardLayout>
<CoursesPanel />
</DashboardLayout>
</div>
@@ -45,7 +44,7 @@ exports[`Dashboard snapshots courses still loading snapshot 1`] = `
</div>
`;
exports[`Dashboard snapshots there are no courses, there ARE available dashboards snapshot 1`] = `
exports[`Dashboard snapshots there are no courses snapshot 1`] = `
<div
className="d-flex flex-column p-2 pt-0"
id="dashboard-container"
@@ -56,15 +55,13 @@ exports[`Dashboard snapshots there are no courses, there ARE available dashboard
test-page-title
</h1>
<Fragment>
<EnterpriseDashboardModal />
<DashboardModalSlot />
</Fragment>
<div
data-testid="dashboard-content"
id="dashboard-content"
>
<DashboardLayout
sidebar="NoCoursesWidgetSidebar"
>
<DashboardLayout>
<CoursesPanel />
</DashboardLayout>
</div>

View File

@@ -26,7 +26,8 @@ export const useDashboardMessages = () => {
export const useDashboardLayoutData = () => {
const { width } = useWindowSize();
const [sidebarShowing, setSidebarShowing] = module.state.sidebarShowing(false);
const [sidebarShowing, setSidebarShowing] = module.state.sidebarShowing(true);
return {
isDashboardCollapsed: width < breakpoints.large.maxWidth,
sidebarShowing,

View File

@@ -40,9 +40,9 @@ describe('CourseCard hooks', () => {
describe('useDashboardLayoutData', () => {
beforeEach(() => { state.mock(); });
describe('behavior', () => {
it('initializes sidebarShowing to default false value', () => {
it('initializes sidebarShowing to default true value', () => {
hooks.useDashboardLayoutData();
state.expectInitializedWith(state.keys.sidebarShowing, false);
state.expectInitializedWith(state.keys.sidebarShowing, true);
});
});
describe('output', () => {

View File

@@ -2,12 +2,9 @@ import React from 'react';
import { reduxHooks } from 'hooks';
import { RequestKeys } from 'data/constants/requests';
import EnterpriseDashboardModal from 'containers/EnterpriseDashboardModal';
import SelectSessionModal from 'containers/SelectSessionModal';
import CoursesPanel from 'containers/CoursesPanel';
import LoadedSidebar from 'containers/WidgetContainers/LoadedSidebar';
import NoCoursesSidebar from 'containers/WidgetContainers/NoCoursesSidebar';
import DashboardModalSlot from 'plugin-slots/DashboardModalSlot';
import LoadingView from './LoadingView';
import DashboardLayout from './DashboardLayout';
@@ -18,7 +15,6 @@ export const Dashboard = () => {
hooks.useInitializeDashboard();
const { pageTitle } = hooks.useDashboardMessages();
const hasCourses = reduxHooks.useHasCourses();
const hasAvailableDashboards = reduxHooks.useHasAvailableDashboards();
const initIsPending = reduxHooks.useRequestIsPending(RequestKeys.initialize);
const showSelectSessionModal = reduxHooks.useShowSelectSessionModal();
@@ -27,7 +23,7 @@ export const Dashboard = () => {
<h1 className="sr-only">{pageTitle}</h1>
{!initIsPending && (
<>
{hasAvailableDashboards && <EnterpriseDashboardModal />}
<DashboardModalSlot />
{(hasCourses && showSelectSessionModal) && <SelectSessionModal />}
</>
)}
@@ -35,7 +31,7 @@ export const Dashboard = () => {
{initIsPending
? (<LoadingView />)
: (
<DashboardLayout sidebar={hasCourses ? LoadedSidebar : NoCoursesSidebar}>
<DashboardLayout>
<CoursesPanel />
</DashboardLayout>
)}

View File

@@ -6,6 +6,14 @@
.sidebar-column {
padding: 0 map-get($spacers, 3) 0 map-get($spacers, 1);
&.not-collapsed {
padding-top: map-get($spacers, 2);
& >:first-child {
margin-top: map-get($spacers, 5\.5);
}
}
}
@include media-breakpoint-down(lg) {

View File

@@ -2,13 +2,9 @@ import { shallow } from '@edx/react-unit-test-utils';
import { reduxHooks } from 'hooks';
import EnterpriseDashboardModal from 'containers/EnterpriseDashboardModal';
import SelectSessionModal from 'containers/SelectSessionModal';
import CoursesPanel from 'containers/CoursesPanel';
import LoadedWidgetSidebar from 'containers/WidgetContainers/LoadedSidebar';
import NoCoursesWidgetSidebar from 'containers/WidgetContainers/NoCoursesSidebar';
import DashboardLayout from './DashboardLayout';
import LoadingView from './LoadingView';
import hooks from './hooks';
@@ -17,16 +13,13 @@ import Dashboard from '.';
jest.mock('hooks', () => ({
reduxHooks: {
useHasCourses: jest.fn(),
useHasAvailableDashboards: jest.fn(),
useShowSelectSessionModal: jest.fn(),
useRequestIsPending: jest.fn(),
},
}));
jest.mock('containers/EnterpriseDashboardModal', () => 'EnterpriseDashboardModal');
jest.mock('plugin-slots/DashboardModalSlot', () => 'DashboardModalSlot');
jest.mock('containers/CoursesPanel', () => 'CoursesPanel');
jest.mock('containers/WidgetContainers/LoadedSidebar', () => 'LoadedWidgetSidebar');
jest.mock('containers/WidgetContainers/NoCoursesSidebar', () => 'NoCoursesWidgetSidebar');
jest.mock('./LoadingView', () => 'LoadingView');
jest.mock('./DashboardLayout', () => 'DashboardLayout');
@@ -43,12 +36,10 @@ describe('Dashboard', () => {
});
const createWrapper = ({
hasCourses,
hasAvailableDashboards,
initIsPending,
showSelectSessionModal,
}) => {
reduxHooks.useHasCourses.mockReturnValueOnce(hasCourses);
reduxHooks.useHasAvailableDashboards.mockReturnValueOnce(hasAvailableDashboards);
reduxHooks.useRequestIsPending.mockReturnValueOnce(initIsPending);
reduxHooks.useShowSelectSessionModal.mockReturnValueOnce(showSelectSessionModal);
return shallow(<Dashboard />);
@@ -76,7 +67,6 @@ describe('Dashboard', () => {
const testView = ({
props,
content: [contentName, contentEl],
showEnterpriseModal,
showSelectSessionModal,
}) => {
beforeEach(() => { wrapper = createWrapper(props); });
@@ -85,10 +75,6 @@ describe('Dashboard', () => {
it(`renders ${contentName}`, () => {
testContent(contentEl);
});
it(`${renderString(showEnterpriseModal)} dashbaord modal`, () => {
expect(wrapper.instance.findByType(EnterpriseDashboardModal).length)
.toEqual(showEnterpriseModal ? 1 : 0);
});
it(`${renderString(showSelectSessionModal)} select session modal`, () => {
expect(wrapper.instance.findByType(SelectSessionModal).length).toEqual(showSelectSessionModal ? 1 : 0);
});
@@ -97,44 +83,38 @@ describe('Dashboard', () => {
testView({
props: {
hasCourses: false,
hasAvailableDashboards: false,
initIsPending: true,
showSelectSessionModal: false,
},
content: ['LoadingView', <LoadingView />],
showEnterpriseModal: false,
showSelectSessionModal: false,
});
});
describe('courses loaded, show select session modal, no available dashboards', () => {
describe('courses loaded, show select session modal', () => {
testView({
props: {
hasCourses: true,
hasAvailableDashboards: false,
initIsPending: false,
showSelectSessionModal: true,
},
content: ['LoadedView', (
<DashboardLayout sidebar={LoadedWidgetSidebar}><CoursesPanel /></DashboardLayout>
<DashboardLayout><CoursesPanel /></DashboardLayout>
)],
showEnterpriseModal: false,
showSelectSessionModal: true,
});
});
describe('there are no courses, there ARE available dashboards', () => {
describe('there are no courses', () => {
testView({
props: {
hasCourses: false,
hasAvailableDashboards: true,
initIsPending: false,
showSelectSessionModal: false,
},
content: ['Dashboard layout with no courses sidebar and content', (
<DashboardLayout sidebar={NoCoursesWidgetSidebar}><CoursesPanel /></DashboardLayout>
<DashboardLayout><CoursesPanel /></DashboardLayout>
)],
showEnterpriseModal: true,
showSelectSessionModal: false,
});
});

View File

@@ -10,7 +10,7 @@ exports[`EmailSettingsModal render snapshot: emails disabled, show: false 1`] =
<div
className="bg-white p-3 rounded shadow"
style={
Object {
{
"textAlign": "start",
}
}
@@ -54,7 +54,7 @@ exports[`EmailSettingsModal render snapshot: emails disabled, show: true 1`] = `
<div
className="bg-white p-3 rounded shadow"
style={
Object {
{
"textAlign": "start",
}
}
@@ -98,7 +98,7 @@ exports[`EmailSettingsModal render snapshot: emails enabled, show: true 1`] = `
<div
className="bg-white p-3 rounded shadow"
style={
Object {
{
"textAlign": "start",
}
}

View File

@@ -1,42 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`EnterpriseDashboard empty snapshot 1`] = `null`;
exports[`EnterpriseDashboard snapshot 1`] = `
<ModalDialog
hasCloseButton={false}
onClose={[MockFunction useEnterpriseDashboardHook.handleEscape]}
title=""
>
<div
className="bg-white p-3 rounded shadow"
style={
Object {
"textAlign": "start",
}
}
>
<h4>
You have access to the edX, Inc. dashboard
</h4>
<p>
To access the courses available to you through edX, Inc., visit the edX, Inc. dashboard now.
</p>
<ActionRow>
<Button
onClick={[MockFunction useEnterpriseDashboardHook.handleClose]}
variant="tertiary"
>
Dismiss
</Button>
<Button
href="/edx-dashboard"
onClick={[MockFunction useEnterpriseDashboardHook.handleCTAClick]}
type="a"
>
Go to dashboard
</Button>
</ActionRow>
</div>
</ModalDialog>
`;

View File

@@ -1,48 +0,0 @@
import React from 'react';
import { StrictDict } from 'utils';
import track from 'tracking';
import { reduxHooks } from 'hooks';
import * as module from './hooks';
export const state = StrictDict({
showModal: (val) => React.useState(val), // eslint-disable-line
});
const { modalOpened, modalClosed, modalCTAClicked } = track.enterpriseDashboard;
export const useEnterpriseDashboardHook = () => {
const [showModal, setShowModal] = module.state.showModal(true);
const dashboard = reduxHooks.useEnterpriseDashboardData();
const trackOpened = modalOpened(dashboard.enterpriseUUID);
const trackClose = modalClosed(dashboard.enterpriseUUID, 'Cancel button');
const trackEscape = modalClosed(dashboard.enterpriseUUID, 'Escape');
const handleCTAClick = modalCTAClicked(dashboard.enterpriseUUID, dashboard.url);
const handleClose = () => {
trackClose();
setShowModal(false);
};
const handleEscape = () => {
trackEscape();
setShowModal(false);
};
React.useEffect(() => {
if (dashboard && dashboard.label) {
trackOpened();
}
}, []); // eslint-disable-line
return {
showModal,
handleCTAClick,
handleClose,
handleEscape,
dashboard,
};
};
export default useEnterpriseDashboardHook;

View File

@@ -1,75 +0,0 @@
import { MockUseState } from 'testUtils';
import { reduxHooks } from 'hooks';
import track from 'tracking';
import * as hooks from './hooks';
jest.mock('hooks', () => ({
reduxHooks: {
useEnterpriseDashboardData: jest.fn(),
},
}));
jest.mock('tracking', () => {
const modalOpenedEvent = jest.fn();
const modalClosedEvent = jest.fn();
const modalCTAClickedEvent = jest.fn();
return {
__esModule: true,
default: {
enterpriseDashboard: {
modalOpenedEvent,
modalClosedEvent,
modalCTAClickedEvent,
modalOpened: jest.fn(() => modalOpenedEvent),
modalClosed: jest.fn(() => modalClosedEvent),
modalCTAClicked: jest.fn(() => modalCTAClickedEvent),
},
},
};
});
const state = new MockUseState(hooks);
const enterpriseDashboardData = { label: 'edX, Inc.', url: '/edx-dashboard' };
describe('EnterpriseDashboard hooks', () => {
reduxHooks.useEnterpriseDashboardData.mockReturnValue({ ...enterpriseDashboardData });
describe('state values', () => {
state.testGetter(state.keys.showModal);
});
describe('behavior', () => {
let out;
beforeEach(() => {
state.mock();
out = hooks.useEnterpriseDashboardHook();
});
afterEach(state.restore);
test('useEnterpriseDashboardHook to return dashboard data from redux hooks', () => {
expect(out.dashboard).toMatchObject(enterpriseDashboardData);
});
test('modal initializes to shown when rendered and closes on click', () => {
state.expectInitializedWith(state.keys.showModal, true);
out.handleClose();
expect(state.values.showModal).toEqual(false);
});
test('modal initializes to shown when rendered and closes on escape', () => {
state.expectInitializedWith(state.keys.showModal, true);
out.handleEscape();
expect(state.values.showModal).toEqual(false);
});
test('CTA click tracks modalCTAClicked', () => {
out.handleCTAClick();
expect(track.enterpriseDashboard.modalCTAClicked).toHaveBeenCalledWith(
enterpriseDashboardData.enterpriseUUID,
enterpriseDashboardData.url,
);
});
});
});

View File

@@ -1,60 +0,0 @@
import React from 'react';
// import PropTypes from 'prop-types';
import { useIntl } from '@edx/frontend-platform/i18n';
import {
ModalDialog, ActionRow, Button,
} from '@openedx/paragon';
import messages from './messages';
import useEnterpriseDashboardHook from './hooks';
export const EnterpriseDashboardModal = () => {
const { formatMessage } = useIntl();
const {
showModal,
handleClose,
handleCTAClick,
handleEscape,
dashboard,
} = useEnterpriseDashboardHook();
if (!dashboard || !dashboard.label) {
return null;
}
return (
<ModalDialog
isOpen={showModal}
onClose={handleEscape}
hasCloseButton={false}
title=""
>
<div
className="bg-white p-3 rounded shadow"
style={{ textAlign: 'start' }}
>
<h4>
{formatMessage(messages.enterpriseDialogHeader, {
label: dashboard.label,
})}
</h4>
<p>
{formatMessage(messages.enterpriseDialogBody, {
label: dashboard.label,
})}
</p>
<ActionRow>
<Button variant="tertiary" onClick={handleClose}>
{formatMessage(messages.enterpriseDialogDismissButton)}
</Button>
<Button type="a" href={dashboard.url} onClick={handleCTAClick}>
{formatMessage(messages.enterpriseDialogConfirmButton)}
</Button>
</ActionRow>
</div>
</ModalDialog>
);
};
EnterpriseDashboardModal.propTypes = {};
export default EnterpriseDashboardModal;

View File

@@ -1,29 +0,0 @@
import { shallow } from '@edx/react-unit-test-utils';
import EnterpriseDashboard from '.';
import useEnterpriseDashboardHook from './hooks';
jest.mock('./hooks', () => ({
__esModule: true,
default: jest.fn(),
}));
describe('EnterpriseDashboard', () => {
test('snapshot', () => {
const hookData = {
dashboard: { label: 'edX, Inc.', url: '/edx-dashboard' },
showDialog: false,
handleClose: jest.fn().mockName('useEnterpriseDashboardHook.handleClose'),
handleCTAClick: jest.fn().mockName('useEnterpriseDashboardHook.handleCTAClick'),
handleEscape: jest.fn().mockName('useEnterpriseDashboardHook.handleEscape'),
};
useEnterpriseDashboardHook.mockReturnValueOnce({ ...hookData });
const el = shallow(<EnterpriseDashboard />);
expect(el.snapshot).toMatchSnapshot();
});
test('empty snapshot', () => {
useEnterpriseDashboardHook.mockReturnValueOnce({});
const el = shallow(<EnterpriseDashboard />);
expect(el.snapshot).toMatchSnapshot();
});
});

View File

@@ -1,26 +0,0 @@
import { defineMessages } from '@edx/frontend-platform/i18n';
const messages = defineMessages({
enterpriseDialogHeader: {
id: 'leanerDashboard.enterpriseDialogHeader',
defaultMessage: 'You have access to the {label} dashboard',
description: 'title for enterpise dashboard dialog',
},
enterpriseDialogBody: {
id: 'leanerDashboard.enterpriseDialogBody',
defaultMessage: 'To access the courses available to you through {label}, visit the {label} dashboard now.',
description: 'Body text for enterpise dashboard dialog',
},
enterpriseDialogDismissButton: {
id: 'leanerDashboard.enterpriseDialogDismissButton',
defaultMessage: 'Dismiss',
description: 'Dismiss button to cancel visiting dashboard',
},
enterpriseDialogConfirmButton: {
id: 'leanerDashboard.enterpriseDialogConfirmButton',
defaultMessage: 'Go to dashboard',
description: 'Confirm button to go to the dashboard url',
},
});
export default messages;

View File

@@ -1,27 +0,0 @@
import React from 'react';
import { useIntl } from '@edx/frontend-platform/i18n';
import { reduxHooks } from 'hooks';
import { getConfig } from '@edx/frontend-platform';
import messages from './messages';
export const BrandLogo = () => {
const { formatMessage } = useIntl();
const dashboard = reduxHooks.useEnterpriseDashboardData();
return (
<a href={dashboard?.url || '/'} className="mx-auto">
<img
className="logo py-3"
src={getConfig().LOGO_URL}
alt={formatMessage(messages.logoAltText)}
/>
</a>
);
};
BrandLogo.propTypes = {};
export default BrandLogo;

View File

@@ -1,28 +0,0 @@
import { shallow } from '@edx/react-unit-test-utils';
import { reduxHooks } from 'hooks';
import BrandLogo from './BrandLogo';
jest.mock('hooks', () => ({
reduxHooks: {
useEnterpriseDashboardData: jest.fn(),
},
}));
describe('BrandLogo', () => {
test('dashboard defined', () => {
reduxHooks.useEnterpriseDashboardData.mockReturnValueOnce({
url: 'url',
});
const wrapper = shallow(<BrandLogo />);
expect(wrapper.snapshot).toMatchSnapshot();
expect(wrapper.instance.findByType('a')[0].props.href).toEqual('url');
});
test('dashboard undefined', () => {
reduxHooks.useEnterpriseDashboardData.mockReturnValueOnce(null);
const wrapper = shallow(<BrandLogo />);
expect(wrapper.snapshot).toMatchSnapshot();
expect(wrapper.instance.findByType('a')[0].props.href).toEqual('/');
});
});

View File

@@ -1,104 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import { getConfig } from '@edx/frontend-platform';
import { useIntl } from '@edx/frontend-platform/i18n';
import { AppContext } from '@edx/frontend-platform/react';
import { Button, Badge } from '@openedx/paragon';
import urls from 'data/services/lms/urls';
import { reduxHooks } from 'hooks';
import { findCoursesNavDropdownClicked } from '../hooks';
import messages from '../messages';
export const CollapseMenuBody = ({ isOpen }) => {
const { formatMessage } = useIntl();
const { authenticatedUser } = React.useContext(AppContext);
const dashboard = reduxHooks.useEnterpriseDashboardData();
const { courseSearchUrl } = reduxHooks.usePlatformSettingsData();
const exploreCoursesClick = findCoursesNavDropdownClicked(
urls.baseAppUrl(courseSearchUrl),
);
if (!isOpen) {
return null;
}
return (
<div className="d-flex flex-column shadow-sm nav-small-menu">
<Button as="a" href="/" variant="inverse-primary">
{formatMessage(messages.course)}
</Button>
<Button as="a" href={urls.programsUrl()} variant="inverse-primary">
{formatMessage(messages.program)}
</Button>
<Button
as="a"
href={urls.baseAppUrl(courseSearchUrl)}
variant="inverse-primary"
onClick={exploreCoursesClick}
>
{formatMessage(messages.discoverNew)}
</Button>
<Button as="a" href={getConfig().SUPPORT_URL} variant="inverse-primary">
{formatMessage(messages.help)}
</Button>
{authenticatedUser && (
<>
{!!dashboard && (
<Button as="a" href={dashboard.url} variant="inverse-primary">
{formatMessage(messages.dashboard)}
</Button>
)}
{!dashboard && getConfig().CAREER_LINK_URL && (
<Button href={`${getConfig().CAREER_LINK_URL}`}>
{formatMessage(messages.career)}
<Badge className="px-2 mx-2" variant="warning">
{formatMessage(messages.newAlert)}
</Badge>
</Button>
)}
<Button
as="a"
href={`${getConfig().LMS_BASE_URL}/u/${authenticatedUser.username}`}
variant="inverse-primary"
>
{formatMessage(messages.profile)}
</Button>
<Button
as="a"
href={`${getConfig().LMS_BASE_URL}/account/settings`}
variant="inverse-primary"
>
{formatMessage(messages.account)}
</Button>
{getConfig().ORDER_HISTORY_URL && (
<Button
as="a"
variant="inverse-primary"
href={getConfig().ORDER_HISTORY_URL}
>
{formatMessage(messages.orderHistory)}
</Button>
)}
<Button
as="a"
href={getConfig().LOGOUT_URL}
variant="inverse-primary"
>
{formatMessage(messages.signOut)}
</Button>
</>
)}
</div>
);
};
CollapseMenuBody.propTypes = {
isOpen: PropTypes.bool.isRequired,
};
export default CollapseMenuBody;

View File

@@ -1,48 +0,0 @@
import { shallow } from '@edx/react-unit-test-utils';
import { AppContext } from '@edx/frontend-platform/react';
import CollapseMenuBody from './CollapseMenuBody';
jest.mock('@edx/frontend-platform/react', () => ({
AppContext: {
authenticatedUser: {
username: 'username',
},
},
}));
jest.mock('hooks', () => ({
reduxHooks: {
useEnterpriseDashboardData: () => ({
url: 'url',
}),
usePlatformSettingsData: () => ({
courseSearchUrl: '/courseSearchUrl',
}),
},
}));
jest.mock('../hooks', () => ({
findCoursesNavDropdownClicked: (url) => jest.fn().mockName(`findCoursesNavDropdownClicked("${url}")`),
}));
describe('CollapseMenuBody', () => {
test('render', () => {
const wrapper = shallow(<CollapseMenuBody isOpen />);
expect(wrapper.snapshot).toMatchSnapshot();
});
test('render empty if not open', () => {
const wrapper = shallow(<CollapseMenuBody isOpen={false} />);
expect(wrapper.snapshot).toMatchSnapshot();
expect(wrapper.isEmptyRender()).toBe(true);
});
test('render unauthenticated', () => {
const { authenticatedUser } = AppContext;
AppContext.authenticatedUser = null;
const wrapper = shallow(<CollapseMenuBody isOpen />);
expect(wrapper.snapshot).toMatchSnapshot();
AppContext.authenticatedUser = authenticatedUser;
});
});

View File

@@ -1,105 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`CollapseMenuBody render 1`] = `
<div
className="d-flex flex-column shadow-sm nav-small-menu"
>
<Button
as="a"
href="/"
variant="inverse-primary"
>
Courses
</Button>
<Button
as="a"
href="http://localhost:18000/dashboard/programs"
variant="inverse-primary"
>
Programs
</Button>
<Button
as="a"
href="http://localhost:18000/courseSearchUrl"
onClick={[MockFunction findCoursesNavDropdownClicked("http://localhost:18000/courseSearchUrl")]}
variant="inverse-primary"
>
Discover New
</Button>
<Button
as="a"
href="http://localhost:18000/support"
variant="inverse-primary"
>
Help
</Button>
<Fragment>
<Button
as="a"
href="url"
variant="inverse-primary"
>
Dashboard
</Button>
<Button
as="a"
href="http://localhost:18000/u/username"
variant="inverse-primary"
>
Profile
</Button>
<Button
as="a"
href="http://localhost:18000/account/settings"
variant="inverse-primary"
>
Account
</Button>
<Button
as="a"
href="http://localhost:18000/logout"
variant="inverse-primary"
>
Sign Out
</Button>
</Fragment>
</div>
`;
exports[`CollapseMenuBody render empty if not open 1`] = `null`;
exports[`CollapseMenuBody render unauthenticated 1`] = `
<div
className="d-flex flex-column shadow-sm nav-small-menu"
>
<Button
as="a"
href="/"
variant="inverse-primary"
>
Courses
</Button>
<Button
as="a"
href="http://localhost:18000/dashboard/programs"
variant="inverse-primary"
>
Programs
</Button>
<Button
as="a"
href="http://localhost:18000/courseSearchUrl"
onClick={[MockFunction findCoursesNavDropdownClicked("http://localhost:18000/courseSearchUrl")]}
variant="inverse-primary"
>
Discover New
</Button>
<Button
as="a"
href="http://localhost:18000/support"
variant="inverse-primary"
>
Help
</Button>
</div>
`;

View File

@@ -1,48 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`CollapsedHeader render nothing if not collapsed 1`] = `false`;
exports[`CollapsedHeader renders 1`] = `
<Fragment>
<header
className="d-flex shadow-sm align-items-center learner-variant-header"
>
<IconButton
alt="Close"
className="p-4"
iconAs="Icon"
invertColors={true}
isActive={true}
onClick={[MockFunction toggleIsOpen]}
variant="primary"
/>
<mockConstructor />
</header>
<mockConstructor
isOpen={false}
/>
</Fragment>
`;
exports[`CollapsedHeader renders with isOpen true 1`] = `
<Fragment>
<header
className="d-flex shadow-sm align-items-center learner-variant-header"
>
<IconButton
alt="Menu"
className="p-4"
iconAs="Icon"
invertColors={true}
isActive={true}
onClick={[MockFunction toggleIsOpen]}
src={[MockFunction icons.Close]}
variant="primary"
/>
<mockConstructor />
</header>
<mockConstructor
isOpen={true}
/>
</Fragment>
`;

View File

@@ -1,47 +0,0 @@
import React from 'react';
import { useIntl } from '@edx/frontend-platform/i18n';
import { MenuIcon, Close } from '@openedx/paragon/icons';
import { IconButton, Icon } from '@openedx/paragon';
import { useLearnerDashboardHeaderData, useIsCollapsed } from '../hooks';
import CollapseMenuBody from './CollapseMenuBody';
import BrandLogo from '../BrandLogo';
import messages from '../messages';
export const CollapsedHeader = () => {
const { formatMessage } = useIntl();
const isCollapsed = useIsCollapsed();
const { isOpen, toggleIsOpen } = useLearnerDashboardHeaderData();
return (
isCollapsed && (
<>
<header className="d-flex shadow-sm align-items-center learner-variant-header">
<IconButton
invertColors
isActive
src={isOpen ? Close : MenuIcon}
iconAs={Icon}
alt={
isOpen
? formatMessage(messages.collapseMenuOpenAltText)
: formatMessage(messages.collapseMenuClosedAltText)
}
onClick={toggleIsOpen}
variant="primary"
className="p-4"
/>
<BrandLogo />
</header>
<CollapseMenuBody isOpen={isOpen} />
</>
)
);
};
CollapsedHeader.propTypes = {};
export default CollapsedHeader;

View File

@@ -1,38 +0,0 @@
import { shallow } from '@edx/react-unit-test-utils';
import CollapsedHeader from '.';
import { useLearnerDashboardHeaderData, useIsCollapsed } from '../hooks';
jest.mock('../BrandLogo', () => jest.fn(() => 'BrandLogo'));
jest.mock('./CollapseMenuBody', () => jest.fn(() => 'CollapseMenuBody'));
jest.mock('../hooks', () => ({
useIsCollapsed: jest.fn(() => true),
useLearnerDashboardHeaderData: jest.fn(() => ({
isOpen: false,
toggleIsOpen: jest.fn().mockName('toggleIsOpen'),
})),
}));
describe('CollapsedHeader', () => {
it('renders', () => {
const wrapper = shallow(<CollapsedHeader />);
expect(wrapper.snapshot).toMatchSnapshot();
});
it('render nothing if not collapsed', () => {
useIsCollapsed.mockReturnValueOnce(false);
const wrapper = shallow(<CollapsedHeader />);
expect(wrapper.snapshot).toMatchSnapshot();
});
it('renders with isOpen true', () => {
useLearnerDashboardHeaderData.mockReturnValueOnce({
isOpen: true,
toggleIsOpen: jest.fn().mockName('toggleIsOpen'),
});
const wrapper = shallow(<CollapsedHeader />);
expect(wrapper.snapshot).toMatchSnapshot();
});
});

View File

@@ -9,14 +9,14 @@ exports[`ConfirmEmailBanner snapshot Show on unverified 1`] = `
>
<format-message-function
message={
Object {
"defaultMessage": "Remember to confirm your email so that you can keep learning on edX! {confirmNowButton}.",
{
"defaultMessage": "Remember to confirm your email so that you can keep learning! {confirmNowButton}.",
"description": "Text for reminding user to confirm email",
"id": "leanerDashboard.confirmEmailTextReminderBanner",
}
}
values={
Object {
{
"confirmNowButton": <Button
className="confirm-email-now-button"
onClick={[MockFunction openConfirmModalButtonClick]}
@@ -55,11 +55,11 @@ exports[`ConfirmEmailBanner snapshot Show on unverified 1`] = `
onClose={[MockFunction closeConfirmModal]}
title=""
>
<h1
className="text-center p-3"
<h2
className="text-center p-3 h1"
>
Confirm your email
</h1>
</h2>
<p
className="text-center"
>

View File

@@ -64,7 +64,7 @@ export const ConfirmEmailBanner = () => {
</Button>
)}
>
<h1 className="text-center p-3">{formatMessage(messages.confirmEmailModalHeader)}</h1>
<h2 className="text-center p-3 h1">{formatMessage(messages.confirmEmailModalHeader)}</h2>
<p className="text-center">{formatMessage(messages.confirmEmailModalBody)}</p>
</MarketingModal>
</>

Some files were not shown because too many files have changed in this diff Show More