Compare commits

..

179 Commits

Author SHA1 Message Date
renovate[bot]
362a2962af chore(deps): update dependency @openedx/paragon to v23.15.2 (#1596)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-30 05:21:11 +00:00
renovate[bot]
80760103a2 chore(deps): update dependency @openedx/paragon to v23.15.1 (#1594)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-29 03:04:18 +00:00
Feanil Patel
bee0afd611 Merge pull request #1354 from open-craft/kshitij/plugin-slot
feat: Add plugin slot for login page
2025-10-24 09:53:15 -04:00
kshitij.sobti
0418a04fff feat: Add plugin slot for login page
This change adds a plugin slot for the login page allowing it to be customised.

Since there was a dependency conflict between frontend-plugin-framework and the react-hooks testing package, the react-hooks testing package has been removed and a replaced with a simple mechanism for testing hooks.

Since this touched the Login Page those have also been refactored to move away from redux connect.
2025-10-24 15:57:09 +05:30
renovate[bot]
5061391122 chore(deps): update dependency @edx/frontend-platform to v8.5.2 (#1593)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-24 04:10:39 +00:00
renovate[bot]
deb7cef005 chore(deps): update dependency @openedx/paragon to v23.15.0 (#1592)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-23 18:27:36 +00:00
renovate[bot]
9bd9d31599 fix(deps): update dependency redux-saga to v1.4.2 (#1591)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-21 22:53:05 +00:00
renovate[bot]
aec68e7c18 fix(deps): update dependency redux-saga to v1.4.1 (#1590)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-21 08:43:12 +00:00
renovate[bot]
f8868b1e36 chore(deps): update dependency @openedx/paragon to v23.14.9 (#1586)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-15 18:41:27 +00:00
Ihor Romaniuk
ffb8a2d434 fix: username suggestions alignment (#1584)
Co-authored-by: Kyrylo Hudym-Levkovych <kyr.hudym@kyrs-MacBook-Pro.local>
2025-10-15 01:51:39 +05:00
renovate[bot]
a615cba2fa chore(deps): update dependency @openedx/paragon to v23.14.8 (#1583)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-10 21:11:29 +00:00
renovate[bot]
c09d7f4eec chore(deps): update dependency ts-jest to v29.4.5 (#1582)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-10 18:04:03 +00:00
renovate[bot]
a67a08a5fb chore(deps): update dependency @openedx/paragon to v23.14.6 (#1581)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-10 13:10:59 +00:00
renovate[bot]
43ef53b703 chore(deps): update dependency babel-plugin-formatjs to v10.5.41 (#1580)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-09 18:05:55 +00:00
renovate[bot]
1dc39fcce1 chore(deps): update dependency @openedx/paragon to v23.14.5 (#1579)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-08 20:51:02 +00:00
renovate[bot]
0a28ef2fb4 chore(deps): update dependency babel-plugin-formatjs to v10.5.40 (#1578)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-06 01:36:40 +00:00
renovate[bot]
c2fa1fa2df chore(deps): update dependency @openedx/paragon to v23.14.4 (#1577)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-01 21:33:53 +00:00
renovate[bot]
44cf541b06 chore(deps): update dependency jest to v30.2.0 (#1576)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-29 01:01:52 +00:00
Feanil Patel
b5b12d0e87 Merge pull request #1574 from openedx/feanil/remove-reactifex-packages
build: remove unused @edx/reactifex package
2025-09-26 16:14:56 -04:00
Feanil Patel
b2862eeb42 build: remove unused @edx/reactifex package
Remove @edx/reactifex package from devDependencies as it is no longer
needed. Translation extraction functionality has been verified to work
correctly without this dependency.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-26 16:11:41 -04:00
renovate[bot]
92a333cc66 chore(deps): update dependency @openedx/paragon to v23.14.3 (#1575)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-26 16:10:20 -04:00
oleksandr.buhaienko
7a9d9bb300 test: Remove support for Node 20 2025-09-26 10:50:26 -03:00
oleksandr.buhaienko
fc4eb61ec9 build: Upgrade to Node 24 2025-09-26 09:17:49 -03:00
renovate[bot]
b2972929c9 chore(deps): update dependency ts-jest to v29.4.4 (#1573)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-19 18:00:14 +00:00
bydawen
bc251a61b2 test: Add Node 24 to CI matrix (#1564) 2025-09-19 13:56:48 -04:00
renovate[bot]
632e962161 chore(deps): update dependency ts-jest to v29.4.3 (#1572)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-18 10:35:42 +00:00
renovate[bot]
5d913b720e chore(deps): update dependency ts-jest to v29.4.2 (#1570)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-16 10:17:33 +00:00
renovate[bot]
f8a5cb50ed fix(deps): update dependency @edx/frontend-platform to v8.5.1 (#1568)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-15 02:15:50 +00:00
Samuel Allan
b97f777b6f fix: update frontend-build to fix install issues (#1553)
Earlier versions of @openedx/frontend-build used on older version of
'sharp', which caused intermittent installation issues. The version of
'sharp' was updated in @openedx/frontend-build to fix these issues, so
the frontend-build version can be updated here, to fix the issues in
this project too. See
https://github.com/openedx/frontend-build/issues/664 and
https://github.com/openedx/frontend-build/pull/665 for more information.

The frontend-build dependency was updated by:

```
npm install --package-lock-only @openedx/frontend-build
```

Private-ref: https://tasks.opencraft.com/browse/BB-9953
2025-09-08 13:39:50 -06:00
renovate[bot]
b2f7579054 fix(deps): update dependency form-urlencoded to v6.1.6 (#1560)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-04 08:36:57 +00:00
renovate[bot]
24742c1cf5 chore(deps): update dependency jest to v30.1.3 (#1557)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-02 16:30:15 +00:00
renovate[bot]
051383e68a chore(deps): update dependency jest to v30.1.2 (#1555)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-01 08:23:58 +00:00
renovate[bot]
5443ebd01b chore(deps): update dependency @openedx/frontend-build to v14.6.2 (#1554)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-01 07:55:50 +00:00
renovate[bot]
3aa2422735 chore(deps): update dependency jest to v30.1.1 (#1552)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-27 20:30:04 +00:00
renovate[bot]
90a7dfeb15 chore(deps): update dependency jest to v30.1.0 (#1551)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-27 05:27:12 +00:00
renovate[bot]
97c7bd744f fix(deps): update dependency @fortawesome/react-fontawesome to v0.2.6 (#1549)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-22 02:27:19 +00:00
renovate[bot]
55c5f705fb fix(deps): update dependency @fortawesome/react-fontawesome to v0.2.5 (#1548)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-20 03:30:08 +00:00
renovate[bot]
f4e2adc261 fix(deps): update dependency @openedx/paragon to v23.14.2 (#1546)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-13 16:49:41 +00:00
Hassan Raza
58ec90aca6 chore: Handle forbidden username exceptions on registration (#1545) 2025-08-13 07:35:30 +00:00
Diana Villalvazo
76e400f0ad refactor: Replace of injectIntl with useIntl (#1540) 2025-08-12 11:00:33 -04:00
Diana Villalvazo
5bd6926f2f refactor: Replace of injectIntl with useIntl (#1539) 2025-08-12 10:46:01 -04:00
Diana Villalvazo
43a584ebd1 refactor: Replace of injectIntl with useIntl (#1538) 2025-08-12 09:42:57 -04:00
sundasnoreen12
4cf0a64d81 Merge pull request #1541 from WGU-Open-edX/1526/injectIntl-4of4
Replace of injectIntl with useIntl() 4/4
2025-08-11 12:31:50 +05:00
diana-villalvazo-wgu
db3d007c51 refactor: Replace of injectIntl with useIntl 2025-08-07 10:50:11 -05:00
renovate[bot]
55a930840f fix(deps): update dependency @edx/frontend-platform to v8.5.0 (#1543)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-06 21:25:05 +00:00
renovate[bot]
fad82b52ad fix(deps): update dependency @openedx/paragon to v23.14.1 (#1537)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-04 14:50:25 +00:00
renovate[bot]
41450686aa chore(deps): update dependency ts-jest to v29.4.1 (#1536)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-04 02:11:37 +00:00
Kyle McCormick
2fcda640f5 chore: Delete CODEOWNERS (#1535)
See: https://github.com/openedx/axim-engineering/issues/1511
2025-07-31 16:18:27 -04:00
renovate[bot]
82252f9a7c fix(deps): update dependency @fortawesome/react-fontawesome to v0.2.3 (#1531)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-24 15:03:04 +00:00
renovate[bot]
818d0278a5 chore(deps): update dependency jest to v30.0.5 (#1527)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-22 08:44:21 +00:00
renovate[bot]
ff3fce99db fix(deps): update dependency @openedx/paragon to v23.14.0 (#1517)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-03 19:01:47 +00:00
renovate[bot]
157c302384 chore(deps): update dependency jest to v30.0.4 (#1516)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-03 00:16:29 +00:00
renovate[bot]
f2a905d373 fix(deps): update dependency @openedx/paragon to v23.13.0 (#1514)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-26 23:59:51 +00:00
renovate[bot]
e984a0b07b chore(deps): update dependency jest to v30.0.3 (#1513)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-25 03:34:38 +00:00
renovate[bot]
7150d4562a fix(deps): update dependency algoliasearch to v4.25.2 (#1510)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-23 17:06:34 +00:00
renovate[bot]
451056866f chore(deps): update dependency eslint-plugin-import to v2.32.0 (#1509)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-20 23:30:16 +00:00
renovate[bot]
76f0cc54d9 fix(deps): update dependency algoliasearch to v4.25.0 (#1508)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-19 19:51:42 +00:00
renovate[bot]
fb70f7a1c2 chore(deps): update dependency jest to v30.0.2 (#1507)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-19 15:46:32 +00:00
renovate[bot]
b664150b4d chore(deps): update dependency jest to v30.0.1 (#1506)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-18 22:50:33 +00:00
Brian Smith
da5a2e31b6 feat!: add design tokens support (#1504)
BREAKING CHANGE: Pre-design-tokens theming is no longer supported.

Co-authored-by: Diana Olarte <diana.olarte@edunext.co>
2025-06-18 14:29:11 -04:00
renovate[bot]
486d0bfd37 chore(deps): update dependency @openedx/frontend-build to v14.6.1 (#1503)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-18 03:45:28 +00:00
renovate[bot]
9332fc113a fix(deps): update dependency algoliasearch-helper to v3.26.0 (#1501)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-16 17:05:31 +00:00
renovate[bot]
181e837ca4 fix(deps): update dependency @openedx/paragon to v22.20.2 (#1500)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-16 06:13:56 +00:00
renovate[bot]
735a9afc3c chore(deps): update dependency babel-plugin-formatjs to v10.5.39 (#1499)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-16 00:51:57 +00:00
renovate[bot]
319c48f1c8 fix(deps): update dependency @openedx/paragon to v22.20.1 (#1496)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-12 17:38:03 +00:00
renovate[bot]
fbd73bfbfe chore(deps): update dependency jest to v30 (#1495)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-10 05:28:53 +00:00
renovate[bot]
27a63cf406 fix(deps): update dependency @edx/frontend-platform to v8.4.0 (#1494)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-09 16:33:27 +00:00
renovate[bot]
7ea351f6a0 fix(deps): update dependency core-js to v3.43.0 (#1493)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-09 07:34:11 +00:00
renovate[bot]
61e8c254d7 fix(deps): update dependency @edx/frontend-platform to v8.3.9 (#1492)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-06 19:27:33 +00:00
renovate[bot]
3a08e790c3 fix(deps): update dependency @openedx/paragon to v22.20.0 (#1491)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-04 18:02:08 +00:00
renovate[bot]
b4c5171886 fix(deps): update dependency @openedx/paragon to v22.18.2 (#1490)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-03 20:32:20 +00:00
renovate[bot]
7b83c416f8 fix(deps): update dependency @edx/frontend-platform to v8.3.8 (#1489)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-30 21:59:07 +00:00
renovate[bot]
a3c261bb13 fix(deps): update dependency @openedx/paragon to v22.18.1 (#1488)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-22 18:12:22 +00:00
renovate[bot]
98d03aa29f fix(deps): update dependency @openedx/paragon to v22.18.0 (#1487)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-21 22:11:58 +00:00
renovate[bot]
f5d5e2fd02 fix(deps): update react-router monorepo to v6.30.1 (#1486)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-20 20:32:18 +00:00
renovate[bot]
490bf27ed1 fix(deps): update dependency @edx/frontend-platform to v8.3.7 (#1484)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-19 13:30:11 +00:00
renovate[bot]
780acac2fd fix(deps): update dependency @edx/frontend-platform to v8.3.6 (#1482)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-12 11:17:03 +00:00
renovate[bot]
2ea763701d fix(deps): update dependency @edx/frontend-platform to v8.3.5 (#1481)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-05 11:51:58 +00:00
renovate[bot]
e2d9ba5857 chore(deps): update dependency babel-plugin-formatjs to v10.5.38 (#1480)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-05 07:15:18 +00:00
renovate[bot]
747d656f0a fix(deps): update dependency core-js to v3.42.0 (#1479)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-29 19:33:32 +00:00
renovate[bot]
8638ed5cf4 fix(deps): update dependency algoliasearch-helper to v3.25.0 (#1478)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-29 15:06:11 +00:00
renovate[bot]
ca2e7f554a chore(deps): update dependency @openedx/frontend-build to v14.6.0 (#1477)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-28 23:54:00 +00:00
Adolfo R. Brandes
0e94124d74 Merge pull request #1470 from regisb/regisb/no-husky
chore: remove husky 🪓🐶
2025-04-14 14:12:48 -03:00
Régis Behmo
af7edd8a3f chore: remove husky 🪓🐶
We remove husky, which is triggering pre-push git hooks, including
running "npm lint". This is causing failures when building Docker
images, because "npm clean-install --omit=dev" automatically triggers "npm
prepare", which attemps to run "husky". But husky is not listed in the
build dependencies, only in devDependencies. As a consequence, package
installation is failing with the following error:

        14.13 > @edx/frontend-app-ora-grading@0.0.1 prepare
        14.13 > husky install
        14.13
        14.15 sh: 1: husky: not found

Similar to: https://github.com/openedx/frontend-app-learning/pull/1622
2025-04-14 18:53:56 +02:00
renovate[bot]
9323f119c8 chore(deps): update dependency @openedx/frontend-build to v14.5.0 (#1474)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-11 18:08:35 +00:00
Brian Smith
38a1924c6a feat: upgrade to react 18 (#1466) 2025-04-04 10:17:53 -04:00
renovate[bot]
2d7303009f fix(deps): update dependency @edx/frontend-platform to v8.3.4 (#1471)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-02 18:46:51 +00:00
Brian Smith
dfcb94a831 fix: properly set background color for floating labels (#1468) 2025-04-01 12:51:38 -04:00
renovate[bot]
520dd6ed6b chore(deps): update dependency @openedx/frontend-build to v14.4.1 (#1464)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-28 14:49:39 +00:00
renovate[bot]
1b32dbfa19 fix(deps): update dependency @openedx/paragon to v22.16.2 (#1462)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-26 12:28:50 +00:00
renovate[bot]
f7d9bdb5b5 chore(deps): update dependency babel-plugin-formatjs to v10.5.37 (#1461)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-26 06:50:31 +00:00
sarina
063ec80cde docs: Add instructions on using Tutor for development 2025-03-25 14:40:55 -03:00
sarina
3cbb134c3a docs: Update migrated edx.rtd links to docs.openedx.org 2025-03-25 14:40:55 -03:00
Brian Smith
059a60d1c8 chore(deps): update @openedx dependencies to versions that support React 18 (#1458) 2025-03-25 12:22:43 -04:00
renovate[bot]
c88d701271 fix(deps): update dependency @openedx/paragon to v22.16.1 (#1460)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-24 22:21:52 +00:00
renovate[bot]
33b98b356b chore(deps): update dependency @openedx/frontend-build to v14.3.3 (#1459)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-24 20:18:14 +00:00
renovate[bot]
1f81699af4 fix(deps): update dependency algoliasearch-helper to v3.24.3 (#1457)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-24 13:55:06 +00:00
renovate[bot]
13aa77fc70 fix(deps): update dependency @edx/frontend-platform to v8.3.3 (#1456)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-24 07:13:09 +00:00
renovate[bot]
66531831b7 chore(deps): update dependency babel-plugin-formatjs to v10.5.36 (#1455)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-24 03:42:16 +00:00
renovate[bot]
846d3f0662 fix(deps): update dependency @edx/frontend-platform to v8.3.2 (#1454)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-21 19:54:09 +00:00
renovate[bot]
f78e84ee0a fix(deps): update dependency @edx/frontend-platform to v8.3.1 (#1449)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-07 23:16:01 +00:00
renovate[bot]
2d27da2391 fix(deps): update dependency @openedx/paragon to v22.16.0 (#1446)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-05 18:03:14 +00:00
renovate[bot]
78413be34a chore(deps): update dependency @openedx/frontend-build to v14.3.2 (#1444)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-05 01:30:17 +00:00
renovate[bot]
88866a39c1 fix(deps): update dependency algoliasearch-helper to v3.24.2 (#1442)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-04 16:02:35 +00:00
renovate[bot]
dc9699c033 fix(deps): update dependency @edx/frontend-platform to v8.3.0 (#1440)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-04 02:44:20 +00:00
renovate[bot]
00a0e27062 fix(deps): update dependency core-js to v3.41.0 (#1439)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-03 03:32:41 +00:00
renovate[bot]
6839afcf3c fix(deps): update react-router monorepo to v6.30.0 (#1435)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-27 18:41:40 +00:00
renovate[bot]
1cd9c58c1a fix(deps): update dependency @openedx/paragon to v22.15.3 (#1432)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-25 22:14:39 +00:00
renovate[bot]
5d481a93c7 fix(deps): update dependency @edx/frontend-platform to v8.2.1 (#1429)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-21 22:56:35 +00:00
renovate[bot]
438d1fcfa7 fix(deps): update dependency @edx/frontend-platform to v8.2.0 (#1428)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-21 03:16:30 +00:00
renovate[bot]
bc912ce139 chore(deps): update dependency @openedx/frontend-build to v14.3.1 (#1427)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-20 23:02:47 +00:00
renovate[bot]
ab1c2d5379 fix(deps): update dependency @openedx/paragon to v22.15.2 (#1426)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-20 15:27:23 +00:00
renovate[bot]
c109f6e771 chore(deps): update dependency babel-plugin-formatjs to v10.5.35 (#1421)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-10 01:03:12 +00:00
renovate[bot]
8976647190 fix(deps): update dependency @openedx/paragon to v22.15.1 (#1420)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-31 21:51:26 +00:00
renovate[bot]
cb051a83ad fix(deps): update dependency @openedx/paragon to v22.15.0 (#1419)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-31 17:40:38 +00:00
renovate[bot]
1b8aec5709 fix(deps): update react-router monorepo to v6.29.0 (#1417)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-30 17:54:09 +00:00
renovate[bot]
9a68e95fcc fix(deps): update dependency algoliasearch-helper to v3.24.1 (#1414)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-28 12:31:35 +00:00
renovate[bot]
c90980afb0 fix(deps): update dependency @openedx/paragon to v22.14.0 (#1413)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-22 22:06:09 +00:00
renovate[bot]
abb8ae5085 fix(deps): update dependency algoliasearch-helper to v3.23.1 (#1412)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-21 18:52:31 +00:00
renovate[bot]
8bb7462098 chore(deps): update dependency babel-plugin-formatjs to v10.5.34 (#1411)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-20 17:48:45 +00:00
renovate[bot]
b4a5397ba1 chore(deps): update dependency babel-plugin-formatjs to v10.5.33 (#1410)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-20 05:32:02 +00:00
Feanil Patel
a43c620dc4 Merge pull request #1406 from salman2013/salman/update-catalog-info-file
Update catalog-info file for release data
2025-01-17 10:42:49 -05:00
salman2013
93d11b8485 chore: Update catalog-info file for release data and remove openedx.yaml 2025-01-17 10:39:31 -05:00
renovate[bot]
68e13d4daf chore(deps): update dependency babel-plugin-formatjs to v10.5.31 (#1409)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-17 09:05:56 +00:00
renovate[bot]
f6617935e3 fix(deps): update react-router monorepo to v6.28.2 (#1408)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-16 18:07:00 +00:00
renovate[bot]
5f4591c046 chore(deps): update dependency @edx/browserslist-config to v1.5.0 (#1407)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-15 16:22:56 +00:00
renovate[bot]
ae52a8cb65 fix(deps): update dependency algoliasearch-helper to v3.23.0 (#1405)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-14 18:38:26 +00:00
renovate[bot]
b8df66ad23 fix(deps): update dependency core-js to v3.40.0 (#1404)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-07 20:46:54 +00:00
renovate[bot]
923776ab96 fix(deps): update dependency @edx/frontend-platform to v8.1.5 (#1403)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-06 12:10:02 +00:00
renovate[bot]
f170f5e3f0 chore(deps): update dependency babel-plugin-formatjs to v10.5.30 (#1402)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-02 18:14:00 +00:00
renovate[bot]
730875ceb2 fix(deps): update dependency @edx/frontend-platform to v8.1.4 (#1401)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-30 09:15:59 +00:00
renovate[bot]
812350d24a fix(deps): update react-router monorepo to v6.28.1 (#1399)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-20 21:25:00 +00:00
renovate[bot]
6879bacb89 fix(deps): update dependency @openedx/paragon to v22.13.0 (#1398)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-18 17:20:36 +00:00
renovate[bot]
9b2b0f2019 fix(deps): update font awesome to v6.7.2 (#1397)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-16 22:17:55 +00:00
renovate[bot]
87884f2d91 fix(deps): update dependency @openedx/paragon to v22.12.0 (#1395)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-16 01:22:07 +00:00
renovate[bot]
3e20fcae57 fix(deps): update dependency @openedx/paragon to v22.11.2 (#1391)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-10 01:57:22 +00:00
renovate[bot]
173896811d chore(deps): update dependency @edx/browserslist-config to v1.4.0 (#1394)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-09 23:47:31 +00:00
renovate[bot]
7af4a08bd9 fix(deps): update dependency algoliasearch-helper to v3.22.6 (#1393)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-09 18:57:08 +00:00
renovate[bot]
6c12b3b034 fix(deps): update dependency @edx/frontend-platform to v8.1.3 (#1392)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-09 15:21:14 +00:00
renovate[bot]
5304085cd8 chore(deps): update dependency babel-plugin-formatjs to v10.5.29 (#1389)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-09 09:02:19 +00:00
renovate[bot]
3cd9ae130c chore(deps): update dependency @openedx/frontend-build to v14.2.2 (#1390)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-09 05:41:46 +00:00
renovate[bot]
28ad2c2cf6 chore(deps): update dependency @openedx/frontend-build to v14.2.1 (#1388)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-09 00:46:21 +00:00
renovate[bot]
3e889df109 chore(deps): update dependency @edx/browserslist-config to v1.3.0 (#1386)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-06 02:36:06 +00:00
Eemaan Amir
52c6efc34d Merge pull request #1357 from openedx/renovate/universal-cookie-7.x
fix(deps): update dependency universal-cookie to v7
2024-11-27 17:14:02 +05:00
renovate[bot]
584a84a99c fix(deps): update dependency universal-cookie to v7 2024-11-27 09:07:41 +00:00
Eemaan Amir
7e4ab1c74c Merge pull request #1356 from openedx/renovate/reselect-5.x
fix(deps): update dependency reselect to v5
2024-11-27 14:07:16 +05:00
renovate[bot]
13d89cb3a0 fix(deps): update dependency reselect to v5 2024-11-22 06:55:30 +00:00
Eemaan Amir
5a1e2e6c97 Merge pull request #1147 from openedx/renovate/actions-checkout-4.x
chore(deps): update actions/checkout action to v4
2024-11-22 11:54:42 +05:00
renovate[bot]
6f1cf29a60 chore(deps): update actions/checkout action to v4 2024-11-21 10:15:17 +00:00
renovate[bot]
159f1ae30e fix(deps): update font awesome to v6.7.1 (#1374)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-21 08:56:48 +00:00
Eemaan Amir
e2e626552f Merge pull request #1314 from openedx/repo-tools/salman/add-dependabot-file-e2a206c
chore: enable github action auto update in dependabot.yml
2024-11-21 13:53:25 +05:00
edX requirements bot
308d7c62e4 chore: enable github action auto update in dependabot.yml 2024-11-19 19:18:17 +05:00
Eemaan Amir
0bc78da55d Merge pull request #1367 from openedx/renovate/codecov-codecov-action-5.x
chore(deps): update codecov/codecov-action action to v5
2024-11-19 18:28:46 +05:00
renovate[bot]
6527caea54 chore(deps): update codecov/codecov-action action to v5 2024-11-19 10:57:48 +00:00
Eemaan Amir
a52912e35b Merge pull request #1149 from openedx/renovate/husky-9.x
chore(deps): update dependency husky to v9
2024-11-19 15:57:32 +05:00
renovate[bot]
6479382b90 chore(deps): update dependency husky to v9 2024-11-19 00:06:24 +00:00
renovate[bot]
4ce36bb12c fix(deps): update font awesome to v6.7.0 (#1371)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-18 23:17:51 +00:00
renovate[bot]
4cc7723984 chore(deps): update dependency @openedx/frontend-build to v14.2.0 (#1370)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-18 20:24:00 +00:00
renovate[bot]
3c3d359d4e chore(deps): update dependency babel-plugin-formatjs to v10.5.26 (#1369)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-18 09:44:26 +00:00
renovate[bot]
cccbf3a9d1 chore(deps): update dependency babel-plugin-formatjs to v10.5.25 (#1368)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-18 06:28:06 +00:00
renovate[bot]
4a3fd2ee8e fix(deps): update dependency @openedx/paragon to v22.10.0 (#1365)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-13 02:40:50 +00:00
Eemaan Amir
48d7cb386a Merge pull request #1362 from openedx/renovate/algolia-instantsearch-monorepo
fix(deps): update dependency algoliasearch-helper to v3.22.5
2024-11-11 18:52:42 +05:00
renovate[bot]
bdf9cab869 fix(deps): update dependency algoliasearch-helper to v3.22.5 2024-11-11 12:41:03 +00:00
Eemaan Amir
be02dabf40 Merge pull request #1361 from openedx/revert-1313-renovate/algolia-instantsearch-monorepo
Revert "fix(deps): update dependency algoliasearch-helper to v3.22.5"
2024-11-11 17:39:58 +05:00
Eemaan Amir
c535fb9d24 Revert "fix(deps): update dependency algoliasearch-helper to v3.22.5"
This reverts commit 8ab8d09b97.
2024-11-11 17:32:28 +05:00
renovate[bot]
8ab8d09b97 fix(deps): update dependency algoliasearch-helper to v3.22.5 2024-11-11 16:45:38 +05:00
renovate[bot]
286c70d50f fix(deps): update react-router monorepo to v6.28.0 (#1355)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-08 12:00:01 +00:00
renovate[bot]
8939e5b91f chore(deps): update dependency babel-plugin-formatjs to v10.5.24 2024-11-06 10:42:36 +00:00
renovate[bot]
bc9f7b3bce fix(deps): update react-router monorepo to v6.27.0 2024-11-01 16:32:19 +00:00
renovate[bot]
fd0bcb9e5f fix(deps): update dependency core-js to v3.39.0 2024-11-01 13:58:08 +00:00
renovate[bot]
98e0167ef1 fix(deps): update dependency @openedx/paragon to v22.9.0 2024-11-01 11:23:30 +00:00
renovate[bot]
8091085f45 chore(deps): update dependency eslint-plugin-import to v2.31.0 2024-11-01 06:51:18 +00:00
renovate[bot]
cd5abd1d9c fix(deps): update dependency redux-mock-store to v1.5.5 2024-11-01 03:09:49 +00:00
renovate[bot]
2a88f435b9 fix(deps): update dependency @edx/frontend-platform to v8.1.2 2024-11-01 00:54:11 +00:00
renovate[bot]
fe1e9c5629 chore(deps): update dependency @openedx/frontend-build to v14.1.5 2024-10-31 21:45:56 +00:00
renovate[bot]
0e363ca724 chore(deps): update dependency babel-plugin-formatjs to v10.5.22 2024-10-31 19:54:28 +00:00
Bilal Qamar
c874638bd1 test: Remove support for Node 18 (#1312)
Co-authored-by: Brian Smith <112954497+brian-smith-tcril@users.noreply.github.com>
2024-10-31 15:51:22 -04:00
Brian Smith
e5c3b1ed41 Revert "fix(deps): update dependency @testing-library/react to v16" (#1339)
This reverts commit 8a27b8cc37.
2024-10-31 15:25:11 -04:00
renovate[bot]
8a27b8cc37 fix(deps): update dependency @testing-library/react to v16 2024-10-31 15:09:42 -04:00
55 changed files with 18896 additions and 6892 deletions

2
.env
View File

@@ -41,3 +41,5 @@ BANNER_IMAGE_EXTRA_SMALL=''
# ***** Miscellaneous *****
APP_ID=''
MFE_CONFIG_API_URL=''
# Fallback in local style files
PARAGON_THEME_URLS={}

View File

@@ -41,3 +41,5 @@ APP_ID=''
MFE_CONFIG_API_URL=''
ZENDESK_KEY=''
ZENDESK_LOGO_URL=''
# Fallback in local style files
PARAGON_THEME_URLS={}

View File

@@ -18,3 +18,4 @@ SEGMENT_KEY=''
SITE_NAME='Your Platform Name Here'
APP_ID=''
MFE_CONFIG_API_URL=''
PARAGON_THEME_URLS={}

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

@@ -11,17 +11,14 @@ on:
jobs:
tests:
runs-on: ubuntu-latest
strategy:
matrix:
node: [18, 20]
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Setup Nodejs
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
node-version-file: '.nvmrc'
- name: Install Dependencies
run: npm ci
@@ -42,7 +39,7 @@ jobs:
run: npm run build
- name: Run Code Coverage
uses: codecov/codecov-action@v4
uses: codecov/codecov-action@v5
with:
token: ${{ secrets.CODECOV_TOKEN }}
fail_ci_if_error: true

2
.nvmrc
View File

@@ -1 +1 @@
20
24

View File

@@ -1,2 +0,0 @@
# The following users are the owners of all frontend-app-authn files
* @openedx/2u-infinity

View File

@@ -29,7 +29,13 @@ Getting Started
Installation
============
This MFE is bundled with `Devstack <https://github.com/openedx/devstack>`_, see the `Getting Started <https://github.com/openedx/devstack#getting-started>`_ section for setup instructions.
`Tutor`_ is currently recommended as a development environment for your new MFE. Please refer to the `relevant tutor-mfe documentation`_ to get started using it.
.. _Tutor: https://github.com/overhangio/tutor
.. _relevant tutor-mfe documentation: https://github.com/overhangio/tutor-mfe?tab=readme-ov-file#mfe-development
Devstack (Deprecated) instructions
==================================
1. Install Devstack using the `Getting Started <https://github.com/openedx/devstack#getting-started>`_ instructions.
@@ -51,7 +57,7 @@ This MFE is bundled with `Devstack <https://github.com/openedx/devstack>`_, see
Environment Variables/Setup Notes
=================================
This MFE is configured via environment variables supplied at build time. All micro-frontends have a shared set of required environment variables, as documented in the Open edX Developer Guide under `Required Environment Variables <https://edx.readthedocs.io/projects/edx-developer-docs/en/latest/developers_guide/micro_frontends_in_open_edx.html#required-environment-variables>`__.
This MFE is configured via environment variables supplied at build time. All micro-frontends have a shared set of required environment variables, as documented in the Open edX Developer Guide under `Required Environment Variables <https://github.com/overhangio/tutor-mfe?tab=readme-ov-file#mfe-development>`__.
The authentication micro-frontend also requires the following additional variable:
@@ -142,13 +148,13 @@ Furthermore, there are several edX-specific environment variables that enable in
- ``true`` | ``''`` (empty strings are falsy)
For more information see the document: `Micro-frontend applications in Open
edX <https://edx.readthedocs.io/projects/edx-developer-docs/en/latest/developers_guide/micro_frontends_in_open_edx.html#required-environment-variables>`__.
edX <https://github.com/overhangio/tutor-mfe?tab=readme-ov-file#mfe-development>`__.
How To Contribute
=================
Contributions are very welcome, and strongly encouraged! We've
put together `some documentation that describes our contribution process <https://edx.readthedocs.org/projects/edx-developer-guide/en/latest/process/index.html>`_.
put together `some documentation that describes our contribution process <https://docs.openedx.org/en/latest/developers/references/developer_guide/process/index.html>`_.
Even though they were written with edx-platform in mind, the guidelines should be followed for Open edX code in general.

View File

@@ -12,6 +12,7 @@ metadata:
icon: 'Article'
annotations:
openedx.org/arch-interest-groups: ""
openedx.org/release: "master"
spec:
owner: group:2u-infinity
type: 'service'

View File

@@ -3,7 +3,7 @@ Enable Social Auth Locally
Please follow the steps below to enable social auth (SSO) locally.
1. Follow `Enabling Third Party Authentication <https://edx.readthedocs.io/projects/edx-installing-configuring-and-running/en/latest/configuration/tpa/index.html>`_ for backend configuration.
1. Follow `Enabling Third Party Authentication <https://docs.openedx.org/en/latest/site_ops/install_configure_run_guide/configuration/tpa/index.html>`_ for backend configuration.
2. Authn has a component for rendering Social Auth providers at frontend which goes through each provider.

View File

@@ -1,8 +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
nick: Authn MFE
oeps: {}
owner: openedx/2u-infinity
openedx-release:
ref: master

24369
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -13,15 +13,12 @@
"build": "fedx-scripts webpack",
"i18n_extract": "fedx-scripts formatjs extract",
"lint": "fedx-scripts eslint --ext .js --ext .jsx .",
"lint:fix": "fedx-scripts eslint --fix --ext .js --ext .jsx .",
"snapshot": "fedx-scripts jest --updateSnapshot",
"start": "fedx-scripts webpack-dev-server --progress",
"dev": "PUBLIC_PATH=/authn/ MFE_CONFIG_API_URL='http://localhost:8000/api/mfe_config/v1' fedx-scripts webpack-dev-server --progress --host apps.local.openedx.io",
"test": "fedx-scripts jest --coverage --passWithNoTests"
},
"husky": {
"hooks": {
"pre-commit": "npm run lint"
}
},
"author": "edX",
"license": "AGPL-3.0",
"homepage": "https://github.com/openedx/frontend-app-authn#readme",
@@ -33,53 +30,52 @@
},
"dependencies": {
"@edx/brand": "npm:@openedx/brand-openedx@^1.2.2",
"@edx/frontend-platform": "^8.0.0",
"@edx/frontend-platform": "^8.3.1",
"@edx/openedx-atlas": "^0.6.0",
"@fortawesome/fontawesome-svg-core": "6.6.0",
"@fortawesome/free-brands-svg-icons": "6.6.0",
"@fortawesome/free-solid-svg-icons": "6.6.0",
"@fortawesome/react-fontawesome": "0.2.2",
"@openedx/paragon": "^22.1.1",
"@fortawesome/fontawesome-svg-core": "6.7.2",
"@fortawesome/free-brands-svg-icons": "6.7.2",
"@fortawesome/free-solid-svg-icons": "6.7.2",
"@fortawesome/react-fontawesome": "0.2.6",
"@openedx/frontend-plugin-framework": "^1.7.0",
"@openedx/paragon": "^23.4.2",
"@optimizely/react-sdk": "^2.9.1",
"@redux-devtools/extension": "3.3.0",
"@testing-library/react": "^12.1.5",
"@testing-library/react-hooks": "^8.0.1",
"@testing-library/react": "^16.2.0",
"algoliasearch": "^4.14.3",
"algoliasearch-helper": "^3.14.0",
"algoliasearch-helper": "^3.26.0",
"classnames": "2.5.1",
"core-js": "3.38.1",
"core-js": "3.43.0",
"fastest-levenshtein": "1.0.16",
"form-urlencoded": "6.1.5",
"form-urlencoded": "6.1.6",
"prop-types": "15.8.1",
"query-string": "7.1.3",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-helmet": "6.1.0",
"react-loading-skeleton": "3.5.0",
"react-redux": "7.2.9",
"react-responsive": "8.2.0",
"react-router": "6.26.2",
"react-router-dom": "6.26.2",
"react-router": "6.30.1",
"react-router-dom": "6.30.1",
"react-zendesk": "^0.1.13",
"redux": "4.2.1",
"redux-logger": "3.0.6",
"redux-mock-store": "1.5.4",
"redux-saga": "1.3.0",
"redux-mock-store": "1.5.5",
"redux-saga": "1.4.2",
"redux-thunk": "2.4.2",
"regenerator-runtime": "0.14.1",
"reselect": "4.1.8",
"universal-cookie": "4.0.4"
"reselect": "5.1.1",
"universal-cookie": "7.2.2"
},
"devDependencies": {
"@edx/browserslist-config": "^1.1.1",
"@edx/reactifex": "1.1.0",
"@openedx/frontend-build": "^14.0.3",
"babel-plugin-formatjs": "10.5.16",
"eslint-plugin-import": "2.30.0",
"@openedx/frontend-build": "^14.6.2",
"babel-plugin-formatjs": "10.5.41",
"eslint-plugin-import": "2.32.0",
"glob": "7.2.3",
"history": "5.3.0",
"husky": "7.0.4",
"jest": "29.7.0",
"react-test-renderer": "^17.0.2"
"jest": "30.2.0",
"react-test-renderer": "^18.3.1",
"ts-jest": "^29.4.0"
}
}

View File

@@ -1,7 +1,6 @@
import React from 'react';
import { Provider } from 'react-redux';
import { injectIntl, IntlProvider } from '@edx/frontend-platform/i18n';
import { IntlProvider } from '@edx/frontend-platform/i18n';
import { fireEvent, render } from '@testing-library/react';
import { act } from 'react-dom/test-utils';
import { MemoryRouter } from 'react-router-dom';
@@ -37,7 +36,6 @@ describe('FormGroup', () => {
describe('PasswordField', () => {
const mockStore = configureStore();
const IntlPasswordField = injectIntl(PasswordField);
let props = {};
let store = {};
@@ -66,7 +64,7 @@ describe('PasswordField', () => {
});
it('should show/hide password on icon click', () => {
const { getByLabelText } = render(reduxWrapper(<IntlPasswordField {...props} />));
const { getByLabelText } = render(reduxWrapper(<PasswordField {...props} />));
const passwordInput = getByLabelText('Password');
const showPasswordButton = getByLabelText('Show password');
@@ -79,7 +77,7 @@ describe('PasswordField', () => {
});
it('should show password requirement tooltip on focus', async () => {
const { getByLabelText } = render(reduxWrapper(<IntlPasswordField {...props} />));
const { getByLabelText } = render(reduxWrapper(<PasswordField {...props} />));
const passwordInput = getByLabelText('Password');
jest.useFakeTimers();
await act(async () => {
@@ -96,7 +94,7 @@ describe('PasswordField', () => {
...props,
value: '',
};
const { getByLabelText } = render(reduxWrapper(<IntlPasswordField {...props} />));
const { getByLabelText } = render(reduxWrapper(<PasswordField {...props} />));
const passwordInput = getByLabelText('Password');
jest.useFakeTimers();
await act(async () => {
@@ -119,7 +117,7 @@ describe('PasswordField', () => {
});
it('should update password requirement checks', async () => {
const { getByLabelText } = render(reduxWrapper(<IntlPasswordField {...props} />));
const { getByLabelText } = render(reduxWrapper(<PasswordField {...props} />));
const passwordInput = getByLabelText('Password');
jest.useFakeTimers();
await act(async () => {
@@ -142,7 +140,7 @@ describe('PasswordField', () => {
});
it('should not run validations when blur is fired on password icon click', () => {
const { container, getByLabelText } = render(reduxWrapper(<IntlPasswordField {...props} />));
const { container, getByLabelText } = render(reduxWrapper(<PasswordField {...props} />));
const passwordInput = container.querySelector('input[name="password"]');
const passwordIcon = getByLabelText('Show password');
@@ -163,7 +161,7 @@ describe('PasswordField', () => {
...props,
handleBlur: jest.fn(),
};
const { container } = render(reduxWrapper(<IntlPasswordField {...props} />));
const { container } = render(reduxWrapper(<PasswordField {...props} />));
const passwordInput = container.querySelector('input[name="password"]');
fireEvent.blur(passwordInput, {
@@ -181,7 +179,7 @@ describe('PasswordField', () => {
...props,
handleErrorChange: jest.fn(),
};
const { container } = render(reduxWrapper(<IntlPasswordField {...props} />));
const { container } = render(reduxWrapper(<PasswordField {...props} />));
const passwordInput = container.querySelector('input[name="password"]');
fireEvent.blur(passwordInput, {
@@ -204,7 +202,7 @@ describe('PasswordField', () => {
handleErrorChange: jest.fn(),
};
const { getByLabelText } = render(reduxWrapper(<IntlPasswordField {...props} />));
const { getByLabelText } = render(reduxWrapper(<PasswordField {...props} />));
const passwordIcon = getByLabelText('Show password');
@@ -224,7 +222,7 @@ describe('PasswordField', () => {
handleErrorChange: jest.fn(),
};
const { getByLabelText } = render(reduxWrapper(<IntlPasswordField {...props} />));
const { getByLabelText } = render(reduxWrapper(<PasswordField {...props} />));
const passwordIcon = getByLabelText('Show password');
@@ -248,7 +246,7 @@ describe('PasswordField', () => {
...props,
handleErrorChange: jest.fn(),
};
const { getByLabelText } = render(reduxWrapper(<IntlPasswordField {...props} />));
const { getByLabelText } = render(reduxWrapper(<PasswordField {...props} />));
const passwordField = getByLabelText('Password');
fireEvent.blur(passwordField, {
target: {
@@ -268,7 +266,7 @@ describe('PasswordField', () => {
handleErrorChange: jest.fn(),
handleBlur: jest.fn(),
};
const { getByLabelText } = render(reduxWrapper(<IntlPasswordField {...props} />));
const { getByLabelText } = render(reduxWrapper(<PasswordField {...props} />));
const passwordIcon = getByLabelText('Show password');

View File

@@ -1,8 +1,7 @@
import React from 'react';
import { Provider } from 'react-redux';
import { mergeConfig } from '@edx/frontend-platform';
import { configure, injectIntl, IntlProvider } from '@edx/frontend-platform/i18n';
import { configure, IntlProvider } from '@edx/frontend-platform/i18n';
import {
fireEvent, render, screen,
} from '@testing-library/react';
@@ -26,7 +25,6 @@ jest.mock('react-router-dom', () => ({
useNavigate: () => mockedNavigator,
}));
const IntlForgotPasswordPage = injectIntl(ForgotPasswordPage);
const mockStore = configureStore();
const initialState = {
@@ -78,7 +76,7 @@ describe('ForgotPasswordPage', () => {
);
it('not should display need other help signing in button', () => {
const { queryByTestId } = render(reduxWrapper(<IntlForgotPasswordPage {...props} />));
const { queryByTestId } = render(reduxWrapper(<ForgotPasswordPage {...props} />));
const forgotPasswordButton = queryByTestId('forgot-password');
expect(forgotPasswordButton).toBeNull();
});
@@ -87,14 +85,14 @@ describe('ForgotPasswordPage', () => {
mergeConfig({
LOGIN_ISSUE_SUPPORT_LINK: '/support',
});
render(reduxWrapper(<IntlForgotPasswordPage {...props} />));
render(reduxWrapper(<ForgotPasswordPage {...props} />));
const forgotPasswordButton = screen.findByText('Need help signing in?');
expect(forgotPasswordButton).toBeDefined();
});
it('should display email validation error message', async () => {
const validationMessage = 'We were unable to contact you.Enter a valid email address below.';
const { container } = render(reduxWrapper(<IntlForgotPasswordPage {...props} />));
const { container } = render(reduxWrapper(<ForgotPasswordPage {...props} />));
const emailInput = screen.getByLabelText('Email');
@@ -115,7 +113,7 @@ describe('ForgotPasswordPage', () => {
const expectedMessage = 'We were unable to contact you.'
+ 'An error has occurred. Try refreshing the page, or check your internet connection.';
const { container } = render(reduxWrapper(<IntlForgotPasswordPage {...props} />));
const { container } = render(reduxWrapper(<ForgotPasswordPage {...props} />));
const alertElements = container.querySelectorAll('.alert-danger');
const validationErrors = alertElements[0].textContent;
@@ -124,7 +122,7 @@ describe('ForgotPasswordPage', () => {
it('should display empty email validation message', async () => {
const validationMessage = 'We were unable to contact you.Enter your email below.';
const { container } = render(reduxWrapper(<IntlForgotPasswordPage {...props} />));
const { container } = render(reduxWrapper(<ForgotPasswordPage {...props} />));
const submitButton = screen.getByText('Submit');
fireEvent.click(submitButton);
@@ -141,7 +139,7 @@ describe('ForgotPasswordPage', () => {
forgotPassword: { status: 'forbidden' },
});
const { container } = render(reduxWrapper(<IntlForgotPasswordPage {...props} />));
const { container } = render(reduxWrapper(<ForgotPasswordPage {...props} />));
const alertElements = container.querySelectorAll('.alert-danger');
const validationErrors = alertElements[0].textContent;
@@ -149,7 +147,7 @@ describe('ForgotPasswordPage', () => {
});
it('should not display any error message on change event', () => {
render(reduxWrapper(<IntlForgotPasswordPage {...props} />));
render(reduxWrapper(<ForgotPasswordPage {...props} />));
const emailInput = screen.getByLabelText('Email');
@@ -172,7 +170,7 @@ describe('ForgotPasswordPage', () => {
};
store.dispatch = jest.fn(store.dispatch);
render(reduxWrapper(<IntlForgotPasswordPage {...props} />));
render(reduxWrapper(<ForgotPasswordPage {...props} />));
const emailInput = screen.getByLabelText('Email');
fireEvent.blur(emailInput);
@@ -187,7 +185,7 @@ describe('ForgotPasswordPage', () => {
emailValidationError: validationMessage,
email: '',
};
const { container } = render(reduxWrapper(<IntlForgotPasswordPage {...props} />));
const { container } = render(reduxWrapper(<ForgotPasswordPage {...props} />));
const validationElement = container.querySelector('.pgn__form-text-invalid');
expect(validationElement.textContent).toEqual(validationMessage);
});
@@ -205,7 +203,7 @@ describe('ForgotPasswordPage', () => {
store.dispatch = jest.fn(store.dispatch);
render(reduxWrapper(<IntlForgotPasswordPage {...props} />));
render(reduxWrapper(<ForgotPasswordPage {...props} />));
const emailInput = screen.getByLabelText('Email');
fireEvent.focus(emailInput);
@@ -219,7 +217,7 @@ describe('ForgotPasswordPage', () => {
emailValidationError: '',
email: '',
};
render(reduxWrapper(<IntlForgotPasswordPage {...props} />));
render(reduxWrapper(<ForgotPasswordPage {...props} />));
const errorElement = screen.queryByTestId('email-invalid-feedback');
expect(errorElement).toBeNull();
});
@@ -236,7 +234,7 @@ describe('ForgotPasswordPage', () => {
+ 'receive a password reset message after 1 minute, verify that you entered the correct email address,'
+ ' or check your spam folder. If you need further assistance, contact technical support.';
const { container } = render(reduxWrapper(<IntlForgotPasswordPage {...props} />));
const { container } = render(reduxWrapper(<ForgotPasswordPage {...props} />));
const successElement = findByTextContent(container, successMessage);
expect(successElement).toBeDefined();
@@ -254,7 +252,7 @@ describe('ForgotPasswordPage', () => {
+ 'This password reset link is invalid. It may have been used already. '
+ 'Enter your email below to receive a new link.';
const { container } = render(reduxWrapper(<IntlForgotPasswordPage {...props} />));
const { container } = render(reduxWrapper(<ForgotPasswordPage {...props} />));
const successElement = findByTextContent(container, successMessage);
expect(successElement).toBeDefined();
@@ -262,7 +260,7 @@ describe('ForgotPasswordPage', () => {
});
it('should redirect onto login page', async () => {
const { container } = render(reduxWrapper(<IntlForgotPasswordPage {...props} />));
const { container } = render(reduxWrapper(<ForgotPasswordPage {...props} />));
const navElement = container.querySelector('nav');
const anchorElement = navElement.querySelector('a');

View File

@@ -1,27 +1,36 @@
import 'core-js/stable';
import 'regenerator-runtime/runtime';
import React from 'react';
import ReactDOM from 'react-dom';
import React, { StrictMode } from 'react';
import {
APP_INIT_ERROR, APP_READY, initialize, mergeConfig, subscribe,
} from '@edx/frontend-platform';
import { ErrorPage } from '@edx/frontend-platform/react';
import { createRoot } from 'react-dom/client';
import configuration from './config';
import messages from './i18n';
import MainApp from './MainApp';
subscribe(APP_READY, () => {
ReactDOM.render(
<MainApp />,
document.getElementById('root'),
const root = createRoot(document.getElementById('root'));
root.render(
<StrictMode>
<MainApp />
</StrictMode>,
);
});
subscribe(APP_INIT_ERROR, (error) => {
ReactDOM.render(<ErrorPage message={error.message} />, document.getElementById('root'));
const root = createRoot(document.getElementById('root'));
root.render(
<StrictMode>
<ErrorPage message={error.message} />
</StrictMode>,
);
});
initialize({

View File

@@ -1,6 +1,2 @@
@import "~@edx/brand/paragon/fonts";
@import "~@edx/brand/paragon/variables";
@import "~@openedx/paragon/scss/core/core";
@import "~@edx/brand/paragon/overrides";
@use "@openedx/paragon/styles/css/core/custom-media-breakpoints" as paragonCustomMediaBreakpoints;
@import "sass/style";

View File

@@ -1,26 +1,17 @@
import React, { useEffect, useMemo, useState } from 'react';
import { connect } from 'react-redux';
import {
useCallback, useEffect, useMemo, useState,
} from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { getConfig } from '@edx/frontend-platform';
import { sendPageEvent, sendTrackEvent } from '@edx/frontend-platform/analytics';
import { injectIntl, useIntl } from '@edx/frontend-platform/i18n';
import {
Form, StatefulButton,
} from '@openedx/paragon';
import { useIntl } from '@edx/frontend-platform/i18n';
import { Form, StatefulButton } from '@openedx/paragon';
import PropTypes from 'prop-types';
import { Helmet } from 'react-helmet';
import Skeleton from 'react-loading-skeleton';
import { Link } from 'react-router-dom';
import AccountActivationMessage from './AccountActivationMessage';
import {
backupLoginFormBegin,
dismissPasswordResetBanner,
loginRequest,
} from './data/actions';
import { INVALID_FORM, TPA_AUTHENTICATION_FAILURE } from './data/constants';
import LoginFailureMessage from './LoginFailure';
import messages from './messages';
import {
FormGroup,
InstitutionLogistration,
@@ -28,13 +19,12 @@ import {
RedirectLogistration,
ThirdPartyAuthAlert,
} from '../common-components';
import AccountActivationMessage from './AccountActivationMessage';
import { getThirdPartyAuthContext } from '../common-components/data/actions';
import { thirdPartyAuthContextSelector } from '../common-components/data/selectors';
import EnterpriseSSO from '../common-components/EnterpriseSSO';
import ThirdPartyAuth from '../common-components/ThirdPartyAuth';
import {
DEFAULT_STATE, PENDING_STATE, RESET_PAGE,
} from '../data/constants';
import { PENDING_STATE, RESET_PAGE } from '../data/constants';
import {
getActivationStatus,
getAllPossibleQueryParams,
@@ -43,37 +33,57 @@ import {
updatePathWithQueryParams,
} from '../data/utils';
import ResetPasswordSuccess from '../reset-password/ResetPasswordSuccess';
import { backupLoginFormBegin, dismissPasswordResetBanner, loginRequest } from './data/actions';
import { INVALID_FORM, TPA_AUTHENTICATION_FAILURE } from './data/constants';
import LoginFailureMessage from './LoginFailure';
import messages from './messages';
const LoginPage = (props) => {
const LoginPage = ({
institutionLogin,
handleInstitutionLogin,
}) => {
const dispatch = useDispatch();
const backupFormState = useCallback((data) => dispatch(backupLoginFormBegin(data)), [dispatch]);
const getTPADataFromBackend = useCallback(() => dispatch(getThirdPartyAuthContext()), [dispatch]);
const {
backedUpFormData,
loginErrorCode,
loginErrorContext,
loginResult,
shouldBackupState,
thirdPartyAuthContext: {
providers,
currentProvider,
secondaryProviders,
finishAuthUrl,
platformName,
errorMessage: thirdPartyErrorMessage,
},
thirdPartyAuthApiStatus,
institutionLogin,
showResetPasswordSuccessBanner,
submitState,
// Actions
backupFormState,
handleInstitutionLogin,
getTPADataFromBackend,
} = props;
thirdPartyAuthContext,
thirdPartyAuthApiStatus,
} = useSelector((state) => ({
backedUpFormData: state.login.loginFormData,
loginErrorCode: state.login.loginErrorCode,
loginErrorContext: state.login.loginErrorContext,
loginResult: state.login.loginResult,
shouldBackupState: state.login.shouldBackupState,
showResetPasswordSuccessBanner: state.login.showResetPasswordSuccessBanner,
submitState: state.login.submitState,
thirdPartyAuthContext: thirdPartyAuthContextSelector(state),
thirdPartyAuthApiStatus: state.commonComponents.thirdPartyAuthApiStatus,
}));
const {
providers,
currentProvider,
secondaryProviders,
finishAuthUrl,
platformName,
errorMessage: thirdPartyErrorMessage,
} = thirdPartyAuthContext;
const { formatMessage } = useIntl();
const activationMsgType = getActivationStatus();
const queryParams = useMemo(() => getAllPossibleQueryParams(), []);
const [formFields, setFormFields] = useState({ ...backedUpFormData.formFields });
const [errorCode, setErrorCode] = useState({ type: '', count: 0, context: {} });
const [errorCode, setErrorCode] = useState({
type: '',
count: 0,
context: {},
});
const [errors, setErrors] = useState({ ...backedUpFormData.errors });
const tpaHint = getTpaHint();
@@ -87,7 +97,7 @@ const LoginPage = (props) => {
payload.tpa_hint = tpaHint;
}
getTPADataFromBackend(payload);
}, [getTPADataFromBackend, queryParams, tpaHint]);
}, [queryParams, tpaHint, getTPADataFromBackend]);
/**
* Backup the login form in redux when login page is toggled.
*/
@@ -98,7 +108,7 @@ const LoginPage = (props) => {
errors: { ...errors },
});
}
}, [shouldBackupState, formFields, errors, backupFormState]);
}, [backupFormState, shouldBackupState, formFields, errors]);
useEffect(() => {
if (loginErrorCode) {
@@ -123,7 +133,10 @@ const LoginPage = (props) => {
}, [thirdPartyErrorMessage]);
const validateFormFields = (payload) => {
const { emailOrUsername, password } = payload;
const {
emailOrUsername,
password,
} = payload;
const fieldErrors = { ...errors };
if (emailOrUsername === '') {
@@ -141,14 +154,18 @@ const LoginPage = (props) => {
const handleSubmit = (event) => {
event.preventDefault();
if (showResetPasswordSuccessBanner) {
props.dismissPasswordResetBanner();
dispatch(dismissPasswordResetBanner());
}
const formData = { ...formFields };
const validationErrors = validateFormFields(formData);
if (validationErrors.emailOrUsername || validationErrors.password) {
setErrors({ ...validationErrors });
setErrorCode(prevState => ({ type: INVALID_FORM, count: prevState.count + 1, context: {} }));
setErrorCode(prevState => ({
type: INVALID_FORM,
count: prevState.count + 1,
context: {},
}));
return;
}
@@ -158,23 +175,35 @@ const LoginPage = (props) => {
password: formData.password,
...queryParams,
};
props.loginRequest(payload);
dispatch(loginRequest(payload));
};
const handleOnChange = (event) => {
const { name, value } = event.target;
setFormFields(prevState => ({ ...prevState, [name]: value }));
const {
name,
value,
} = event.target;
setFormFields(prevState => ({
...prevState,
[name]: value,
}));
};
const handleOnFocus = (event) => {
const { name } = event.target;
setErrors(prevErrors => ({ ...prevErrors, [name]: '' }));
setErrors(prevErrors => ({
...prevErrors,
[name]: '',
}));
};
const trackForgotPasswordLinkClick = () => {
sendTrackEvent('edx.bi.password-reset_form.toggled', { category: 'user-engagement' });
};
const { provider, skipHintedLogin } = getTpaProvider(tpaHint, providers, secondaryProviders);
const {
provider,
skipHintedLogin,
} = getTpaProvider(tpaHint, providers, secondaryProviders);
if (tpaHint) {
if (thirdPartyAuthApiStatus === PENDING_STATE) {
@@ -281,88 +310,9 @@ const LoginPage = (props) => {
);
};
const mapStateToProps = state => {
const loginPageState = state.login;
return {
backedUpFormData: loginPageState.loginFormData,
loginErrorCode: loginPageState.loginErrorCode,
loginErrorContext: loginPageState.loginErrorContext,
loginResult: loginPageState.loginResult,
shouldBackupState: loginPageState.shouldBackupState,
showResetPasswordSuccessBanner: loginPageState.showResetPasswordSuccessBanner,
submitState: loginPageState.submitState,
thirdPartyAuthContext: thirdPartyAuthContextSelector(state),
thirdPartyAuthApiStatus: state.commonComponents.thirdPartyAuthApiStatus,
};
};
LoginPage.propTypes = {
backedUpFormData: PropTypes.shape({
formFields: PropTypes.shape({}),
errors: PropTypes.shape({}),
}),
loginErrorCode: PropTypes.string,
loginErrorContext: PropTypes.shape({
email: PropTypes.string,
redirectUrl: PropTypes.string,
context: PropTypes.shape({}),
}),
loginResult: PropTypes.shape({
redirectUrl: PropTypes.string,
success: PropTypes.bool,
}),
shouldBackupState: PropTypes.bool,
showResetPasswordSuccessBanner: PropTypes.bool,
submitState: PropTypes.string,
thirdPartyAuthApiStatus: PropTypes.string,
institutionLogin: PropTypes.bool.isRequired,
thirdPartyAuthContext: PropTypes.shape({
currentProvider: PropTypes.string,
errorMessage: PropTypes.string,
platformName: PropTypes.string,
providers: PropTypes.arrayOf(PropTypes.shape({})),
secondaryProviders: PropTypes.arrayOf(PropTypes.shape({})),
finishAuthUrl: PropTypes.string,
}),
// Actions
backupFormState: PropTypes.func.isRequired,
dismissPasswordResetBanner: PropTypes.func.isRequired,
loginRequest: PropTypes.func.isRequired,
getTPADataFromBackend: PropTypes.func.isRequired,
handleInstitutionLogin: PropTypes.func.isRequired,
};
LoginPage.defaultProps = {
backedUpFormData: {
formFields: {
emailOrUsername: '', password: '',
},
errors: {
emailOrUsername: '', password: '',
},
},
loginErrorCode: null,
loginErrorContext: {},
loginResult: {},
shouldBackupState: false,
showResetPasswordSuccessBanner: false,
submitState: DEFAULT_STATE,
thirdPartyAuthApiStatus: PENDING_STATE,
thirdPartyAuthContext: {
currentProvider: null,
errorMessage: null,
finishAuthUrl: null,
providers: [],
secondaryProviders: [],
},
};
export default connect(
mapStateToProps,
{
backupFormState: backupLoginFormBegin,
dismissPasswordResetBanner,
loginRequest,
getTPADataFromBackend: getThirdPartyAuthContext,
},
)(injectIntl(LoginPage));
export default LoginPage;

View File

@@ -1,7 +1,5 @@
import React from 'react';
import { mergeConfig } from '@edx/frontend-platform';
import { injectIntl, IntlProvider } from '@edx/frontend-platform/i18n';
import { IntlProvider } from '@edx/frontend-platform/i18n';
import {
render, screen,
} from '@testing-library/react';
@@ -9,8 +7,6 @@ import {
import AccountActivationMessage from '../AccountActivationMessage';
import { ACCOUNT_ACTIVATION_MESSAGE } from '../data/constants';
const IntlAccountActivationMessage = injectIntl(AccountActivationMessage);
describe('AccountActivationMessage', () => {
beforeEach(() => {
mergeConfig({
@@ -21,7 +17,7 @@ describe('AccountActivationMessage', () => {
it('should match account already activated message', () => {
render(
<IntlProvider locale="en">
<IntlAccountActivationMessage messageType={ACCOUNT_ACTIVATION_MESSAGE.INFO} />
<AccountActivationMessage messageType={ACCOUNT_ACTIVATION_MESSAGE.INFO} />
</IntlProvider>,
);
@@ -36,7 +32,7 @@ describe('AccountActivationMessage', () => {
it('should match account activated success message', () => {
render(
<IntlProvider locale="en">
<IntlAccountActivationMessage messageType={ACCOUNT_ACTIVATION_MESSAGE.SUCCESS} />
<AccountActivationMessage messageType={ACCOUNT_ACTIVATION_MESSAGE.SUCCESS} />
</IntlProvider>,
);
@@ -53,7 +49,7 @@ describe('AccountActivationMessage', () => {
it('should match account activation error message', () => {
render(
<IntlProvider locale="en">
<IntlAccountActivationMessage messageType={ACCOUNT_ACTIVATION_MESSAGE.ERROR} />
<AccountActivationMessage messageType={ACCOUNT_ACTIVATION_MESSAGE.ERROR} />
</IntlProvider>,
);
@@ -69,7 +65,7 @@ describe('AccountActivationMessage', () => {
it('should not display anything for invalid message type', () => {
const { container } = render(
<IntlProvider locale="en">
<IntlAccountActivationMessage messageType="invalid-message" />
<AccountActivationMessage messageType="invalid-message" />
</IntlProvider>,
);
@@ -88,7 +84,7 @@ describe('EmailConfirmationMessage', () => {
it('should match email already confirmed message', () => {
render(
<IntlProvider locale="en">
<IntlAccountActivationMessage messageType={ACCOUNT_ACTIVATION_MESSAGE.INFO} />
<AccountActivationMessage messageType={ACCOUNT_ACTIVATION_MESSAGE.INFO} />
</IntlProvider>,
);
@@ -103,7 +99,7 @@ describe('EmailConfirmationMessage', () => {
it('should match email confirmation success message', () => {
render(
<IntlProvider locale="en">
<IntlAccountActivationMessage messageType={ACCOUNT_ACTIVATION_MESSAGE.SUCCESS} />
<AccountActivationMessage messageType={ACCOUNT_ACTIVATION_MESSAGE.SUCCESS} />
</IntlProvider>,
);
const expectedMessage = 'Success! You have confirmed your email.Sign in to continue.';
@@ -117,7 +113,7 @@ describe('EmailConfirmationMessage', () => {
it('should match email confirmation error message', () => {
render(
<IntlProvider locale="en">
<IntlAccountActivationMessage messageType={ACCOUNT_ACTIVATION_MESSAGE.ERROR} />
<AccountActivationMessage messageType={ACCOUNT_ACTIVATION_MESSAGE.ERROR} />
</IntlProvider>,
);
const expectedMessage = 'Your email could not be confirmed'

View File

@@ -1,7 +1,5 @@
import React from 'react';
import { getConfig } from '@edx/frontend-platform';
import { injectIntl, IntlProvider } from '@edx/frontend-platform/i18n';
import { IntlProvider } from '@edx/frontend-platform/i18n';
import {
fireEvent, render, screen,
} from '@testing-library/react';
@@ -11,7 +9,6 @@ import { MemoryRouter } from 'react-router-dom';
import { RESET_PAGE } from '../../data/constants';
import ChangePasswordPrompt from '../ChangePasswordPrompt';
const IntlChangePasswordPrompt = injectIntl(ChangePasswordPrompt);
const mockedNavigator = jest.fn();
jest.mock('react-router-dom', () => ({
@@ -44,7 +41,7 @@ describe('ChangePasswordPromptTests', () => {
render(
<IntlProvider locale="en">
<MemoryRouter>
<IntlChangePasswordPrompt {...props} />
<ChangePasswordPrompt {...props} />
</MemoryRouter>
</IntlProvider>,
);
@@ -61,7 +58,7 @@ describe('ChangePasswordPromptTests', () => {
render(
<IntlProvider locale="en">
<MemoryRouter>
<IntlChangePasswordPrompt {...props} />
<ChangePasswordPrompt {...props} />
</MemoryRouter>
</IntlProvider>,
);

View File

@@ -1,6 +1,4 @@
import React from 'react';
import { injectIntl, IntlProvider } from '@edx/frontend-platform/i18n';
import { IntlProvider } from '@edx/frontend-platform/i18n';
import {
render, screen,
} from '@testing-library/react';
@@ -26,8 +24,6 @@ jest.mock('@edx/frontend-platform/auth', () => ({
getAuthService: jest.fn(),
}));
const IntlLoginFailureMessage = injectIntl(LoginFailureMessage);
describe('LoginFailureMessage', () => {
let props = {};
@@ -48,7 +44,7 @@ describe('LoginFailureMessage', () => {
render(
<IntlProvider locale="en">
<IntlLoginFailureMessage {...props} />
<LoginFailureMessage {...props} />
</IntlProvider>,
);
@@ -76,7 +72,7 @@ describe('LoginFailureMessage', () => {
render(
<IntlProvider locale="en">
<IntlLoginFailureMessage {...props} />
<LoginFailureMessage {...props} />
</IntlProvider>,
);
@@ -106,7 +102,7 @@ describe('LoginFailureMessage', () => {
render(
<IntlProvider locale="en">
<IntlLoginFailureMessage {...props} />
<LoginFailureMessage {...props} />
</IntlProvider>,
);
@@ -132,7 +128,7 @@ describe('LoginFailureMessage', () => {
render(
<IntlProvider locale="en">
<IntlLoginFailureMessage {...props} />
<LoginFailureMessage {...props} />
</IntlProvider>,
);
@@ -152,7 +148,7 @@ describe('LoginFailureMessage', () => {
render(
<IntlProvider locale="en">
<IntlLoginFailureMessage {...props} />
<LoginFailureMessage {...props} />
</IntlProvider>,
);
@@ -176,7 +172,7 @@ describe('LoginFailureMessage', () => {
render(
<IntlProvider locale="en">
<IntlLoginFailureMessage {...props} />
<LoginFailureMessage {...props} />
</IntlProvider>,
);
@@ -196,7 +192,7 @@ describe('LoginFailureMessage', () => {
render(
<IntlProvider locale="en">
<IntlLoginFailureMessage {...props} />
<LoginFailureMessage {...props} />
</IntlProvider>,
);
@@ -216,7 +212,7 @@ describe('LoginFailureMessage', () => {
render(
<IntlProvider locale="en">
<IntlLoginFailureMessage {...props} />
<LoginFailureMessage {...props} />
</IntlProvider>,
);
@@ -236,7 +232,7 @@ describe('LoginFailureMessage', () => {
render(
<IntlProvider locale="en">
<IntlLoginFailureMessage {...props} />
<LoginFailureMessage {...props} />
</IntlProvider>,
);
@@ -255,7 +251,7 @@ describe('LoginFailureMessage', () => {
render(
<IntlProvider locale="en">
<IntlLoginFailureMessage {...props} />
<LoginFailureMessage {...props} />
</IntlProvider>,
);
@@ -275,7 +271,7 @@ describe('LoginFailureMessage', () => {
render(
<IntlProvider locale="en">
<IntlLoginFailureMessage {...props} />
<LoginFailureMessage {...props} />
</IntlProvider>,
);
@@ -301,7 +297,7 @@ describe('LoginFailureMessage', () => {
render(
<IntlProvider locale="en">
<MemoryRouter>
<IntlLoginFailureMessage {...props} />
<LoginFailureMessage {...props} />
</MemoryRouter>
</IntlProvider>,
);
@@ -327,7 +323,7 @@ describe('LoginFailureMessage', () => {
render(
<IntlProvider locale="en">
<MemoryRouter>
<IntlLoginFailureMessage {...props} />
<LoginFailureMessage {...props} />
</MemoryRouter>
</IntlProvider>,
);
@@ -359,7 +355,7 @@ describe('LoginFailureMessage', () => {
render(
<IntlProvider locale="en">
<IntlLoginFailureMessage {...props} />
<LoginFailureMessage {...props} />
</IntlProvider>,
);

View File

@@ -1,9 +1,8 @@
import React from 'react';
import { Provider } from 'react-redux';
import { getConfig, mergeConfig } from '@edx/frontend-platform';
import { sendPageEvent, sendTrackEvent } from '@edx/frontend-platform/analytics';
import { injectIntl, IntlProvider } from '@edx/frontend-platform/i18n';
import { IntlProvider } from '@edx/frontend-platform/i18n';
import {
fireEvent, render, screen, waitFor,
} from '@testing-library/react';
@@ -24,7 +23,6 @@ jest.mock('@edx/frontend-platform/auth', () => ({
getAuthService: jest.fn(),
}));
const IntlLoginPage = injectIntl(LoginPage);
const mockStore = configureStore();
describe('LoginPage', () => {
@@ -43,6 +41,14 @@ describe('LoginPage', () => {
const initialState = {
login: {
loginResult: { success: false, redirectUrl: '' },
loginFormData: {
formFields: {
emailOrUsername: '', password: '',
},
errors: {
emailOrUsername: '', password: '',
},
},
},
commonComponents: {
thirdPartyAuthApiStatus: null,
@@ -88,7 +94,7 @@ describe('LoginPage', () => {
it('should submit form for valid input', () => {
store.dispatch = jest.fn(store.dispatch);
render(reduxWrapper(<IntlLoginPage {...props} />));
render(reduxWrapper(<LoginPage {...props} />));
fireEvent.change(screen.getByText(
'',
@@ -109,7 +115,7 @@ describe('LoginPage', () => {
it('should not dispatch loginRequest on empty form submission', () => {
store.dispatch = jest.fn(store.dispatch);
render(reduxWrapper(<IntlLoginPage {...props} />));
render(reduxWrapper(<LoginPage {...props} />));
fireEvent.click(screen.getByText(
'',
@@ -128,7 +134,7 @@ describe('LoginPage', () => {
});
store.dispatch = jest.fn(store.dispatch);
render(reduxWrapper(<IntlLoginPage {...props} />));
render(reduxWrapper(<LoginPage {...props} />));
fireEvent.click(screen.getByText(
'',
{ selector: '.btn-brand' },
@@ -142,7 +148,7 @@ describe('LoginPage', () => {
it('should match state for invalid email (less than 2 characters), on form submission', () => {
store.dispatch = jest.fn(store.dispatch);
render(reduxWrapper(<IntlLoginPage {...props} />));
render(reduxWrapper(<LoginPage {...props} />));
fireEvent.change(screen.getByText(
'',
@@ -162,7 +168,7 @@ describe('LoginPage', () => {
});
it('should show error messages for required fields on empty form submission', () => {
const { container } = render(reduxWrapper(<IntlLoginPage {...props} />));
const { container } = render(reduxWrapper(<LoginPage {...props} />));
fireEvent.click(screen.getByText(
'',
{ selector: '.btn-brand' },
@@ -176,7 +182,7 @@ describe('LoginPage', () => {
});
it('should run frontend validations for emailOrUsername field on form submission', () => {
const { container } = render(reduxWrapper(<IntlLoginPage {...props} />));
const { container } = render(reduxWrapper(<LoginPage {...props} />));
fireEvent.change(screen.getByText(
'',
@@ -195,7 +201,7 @@ describe('LoginPage', () => {
it('should reset field related error messages on onFocus event', async () => {
store.dispatch = jest.fn(store.dispatch);
render(reduxWrapper(<IntlLoginPage {...props} />));
render(reduxWrapper(<LoginPage {...props} />));
await act(async () => {
// clicking submit button with empty fields to make the errors appear
@@ -224,7 +230,7 @@ describe('LoginPage', () => {
// ******** test form buttons and links ********
it('should match default button state', () => {
render(reduxWrapper(<IntlLoginPage {...props} />));
render(reduxWrapper(<LoginPage {...props} />));
expect(screen.getByText('Sign in')).toBeDefined();
});
@@ -237,7 +243,7 @@ describe('LoginPage', () => {
},
});
render(reduxWrapper(<IntlLoginPage {...props} />));
render(reduxWrapper(<LoginPage {...props} />));
expect(screen.getByText(
'pending',
@@ -245,7 +251,7 @@ describe('LoginPage', () => {
});
it('should show forgot password link', () => {
render(reduxWrapper(<IntlLoginPage {...props} />));
render(reduxWrapper(<LoginPage {...props} />));
expect(screen.getByText(
'Forgot password',
@@ -265,7 +271,7 @@ describe('LoginPage', () => {
},
});
render(reduxWrapper(<IntlLoginPage {...props} />));
render(reduxWrapper(<LoginPage {...props} />));
expect(screen.getByText(
'',
{ selector: `#${ssoProvider.id}` },
@@ -287,7 +293,7 @@ describe('LoginPage', () => {
},
});
const { queryByText } = render(reduxWrapper(<IntlLoginPage {...props} />));
const { queryByText } = render(reduxWrapper(<LoginPage {...props} />));
expect(queryByText('Company or school credentials')).toBeNull();
expect(queryByText('Or sign in with:')).toBeNull();
expect(queryByText('Institution/campus credentials')).toBeNull();
@@ -307,7 +313,7 @@ describe('LoginPage', () => {
},
});
const { queryByText } = render(reduxWrapper(<IntlLoginPage {...props} />));
const { queryByText } = render(reduxWrapper(<LoginPage {...props} />));
expect(queryByText('Company or school credentials')).toBeNull();
expect(queryByText('Or sign in with:')).toBeNull();
});
@@ -327,7 +333,7 @@ describe('LoginPage', () => {
},
});
const { queryByText } = render(reduxWrapper(<IntlLoginPage {...props} />));
const { queryByText } = render(reduxWrapper(<LoginPage {...props} />));
expect(queryByText('Or sign in with:')).toBeDefined();
expect(queryByText('Company or school credentials')).toBeDefined();
expect(queryByText('Institution/campus credentials')).toBeDefined();
@@ -352,7 +358,7 @@ describe('LoginPage', () => {
},
});
const { queryByText } = render(reduxWrapper(<IntlLoginPage {...props} />));
const { queryByText } = render(reduxWrapper(<LoginPage {...props} />));
expect(queryByText('Or sign in with:')).toBeDefined();
expect(queryByText('Company or school credentials')).toBeNull();
expect(queryByText('Institution/campus credentials')).toBeDefined();
@@ -380,7 +386,7 @@ describe('LoginPage', () => {
},
});
const { queryByText } = render(reduxWrapper(<IntlLoginPage {...props} />));
const { queryByText } = render(reduxWrapper(<LoginPage {...props} />));
expect(queryByText('Or sign in with:')).toBeDefined();
expect(queryByText('Institution/campus credentials')).toBeDefined();
@@ -400,7 +406,7 @@ describe('LoginPage', () => {
},
});
const { queryByText } = render(reduxWrapper(<IntlLoginPage {...props} />));
const { queryByText } = render(reduxWrapper(<LoginPage {...props} />));
expect(queryByText('Or sign in with:')).toBeNull();
expect(queryByText('Institution/campus credentials')).toBeNull();
expect(queryByText('Company or school credentials')).toBeNull();
@@ -418,7 +424,7 @@ describe('LoginPage', () => {
},
});
const { queryByText } = render(reduxWrapper(<IntlLoginPage {...props} />));
const { queryByText } = render(reduxWrapper(<LoginPage {...props} />));
expect(queryByText('Or sign in with:')).toBeDefined();
expect(queryByText('Company or school credentials')).toBeNull();
expect(queryByText('Institution/campus credentials')).toBeDefined();
@@ -441,7 +447,7 @@ describe('LoginPage', () => {
},
});
render(reduxWrapper(<IntlLoginPage {...props} />));
render(reduxWrapper(<LoginPage {...props} />));
expect(screen.getByText(
'',
{ selector: '#login-failure-alert' },
@@ -465,7 +471,7 @@ describe('LoginPage', () => {
+ 'linked '}${ getConfig().SITE_NAME } account. To link your accounts, sign in now using your ${
getConfig().SITE_NAME } password.`;
render(reduxWrapper(<IntlLoginPage {...props} />));
render(reduxWrapper(<LoginPage {...props} />));
expect(screen.getByText(
'',
{ selector: '#tpa-alert' },
@@ -484,7 +490,7 @@ describe('LoginPage', () => {
},
},
});
render(reduxWrapper(<IntlLoginPage {...props} />));
render(reduxWrapper(<LoginPage {...props} />));
expect(screen.getByText(
'',
{ selector: '#login-failure-alert' },
@@ -501,7 +507,7 @@ describe('LoginPage', () => {
},
});
render(reduxWrapper(<IntlLoginPage {...props} />));
render(reduxWrapper(<LoginPage {...props} />));
expect(screen.getByText(
'',
{ selector: '#login-failure-alert' },
@@ -525,7 +531,7 @@ describe('LoginPage', () => {
delete window.location;
window.location = { href: getConfig().BASE_URL };
render(reduxWrapper(<IntlLoginPage {...props} />));
render(reduxWrapper(<LoginPage {...props} />));
expect(window.location.href).toBe(dashboardURL);
});
@@ -552,7 +558,7 @@ describe('LoginPage', () => {
delete window.location;
window.location = { href: getConfig().BASE_URL };
render(reduxWrapper(<IntlLoginPage {...props} />));
render(reduxWrapper(<LoginPage {...props} />));
expect(window.location.href).toBe(getConfig().LMS_BASE_URL + authCompleteUrl);
});
@@ -571,7 +577,7 @@ describe('LoginPage', () => {
delete window.location;
window.location = { href: getConfig().BASE_URL };
render(reduxWrapper(<IntlLoginPage {...props} />));
render(reduxWrapper(<LoginPage {...props} />));
fireEvent.click(screen.getByText(
'',
@@ -600,7 +606,7 @@ describe('LoginPage', () => {
delete window.location;
window.location = { href: getConfig().BASE_URL };
render(reduxWrapper(<IntlLoginPage {...props} />));
render(reduxWrapper(<LoginPage {...props} />));
expect(window.location.href).toBe(getConfig().LMS_BASE_URL + finishAuthUrl);
});
@@ -622,7 +628,7 @@ describe('LoginPage', () => {
delete window.location;
window.location = { href: getConfig().BASE_URL.concat(LOGIN_PAGE), search: `?next=/dashboard&tpa_hint=${ssoProvider.id}` };
render(reduxWrapper(<IntlLoginPage {...props} />));
render(reduxWrapper(<LoginPage {...props} />));
expect(screen.getByText(
'',
{ selector: `#${ssoProvider.id}` },
@@ -649,7 +655,7 @@ describe('LoginPage', () => {
delete window.location;
window.location = { href: getConfig().BASE_URL.concat(LOGIN_PAGE), search: `?next=/dashboard&tpa_hint=${ssoProvider.id}` };
const { container } = render(reduxWrapper(<IntlLoginPage {...props} />));
const { container } = render(reduxWrapper(<LoginPage {...props} />));
expect(container.querySelector('.react-loading-skeleton')).toBeTruthy();
});
@@ -671,7 +677,7 @@ describe('LoginPage', () => {
window.location = { href: getConfig().BASE_URL.concat(LOGIN_PAGE), search: `?next=/dashboard&tpa_hint=${secondaryProviders.id}` };
secondaryProviders.iconImage = null;
render(reduxWrapper(<IntlLoginPage {...props} />));
render(reduxWrapper(<LoginPage {...props} />));
expect(window.location.href).toEqual(getConfig().LMS_BASE_URL + secondaryProviders.loginUrl);
});
@@ -691,7 +697,7 @@ describe('LoginPage', () => {
delete window.location;
window.location = { href: getConfig().BASE_URL.concat(LOGIN_PAGE), search: '?next=/dashboard&tpa_hint=invalid' };
const { container } = render(reduxWrapper(<IntlLoginPage {...props} />));
const { container } = render(reduxWrapper(<LoginPage {...props} />));
expect(container.querySelector(`#${ssoProvider.id}`).querySelector('#provider-name').textContent).toEqual(`${ssoProvider.name}`);
mergeConfig({
@@ -715,7 +721,7 @@ describe('LoginPage', () => {
delete window.location;
window.location = { href: getConfig().BASE_URL.concat(LOGIN_PAGE), search: `?tpa_hint=${ssoProvider.id}` };
render(reduxWrapper(<IntlLoginPage {...props} />));
render(reduxWrapper(<LoginPage {...props} />));
expect(screen.getByText(
'Show me other ways to sign in or register',
).textContent).toBeDefined();
@@ -741,7 +747,7 @@ describe('LoginPage', () => {
delete window.location;
window.location = { href: getConfig().BASE_URL.concat(LOGIN_PAGE), search: `?tpa_hint=${ssoProvider.id}` };
render(reduxWrapper(<IntlLoginPage {...props} />));
render(reduxWrapper(<LoginPage {...props} />));
expect(screen.getByText(
'Show me other ways to sign in',
).textContent).toBeDefined();
@@ -750,7 +756,7 @@ describe('LoginPage', () => {
// ******** miscellaneous tests ********
it('should send page event when login page is rendered', () => {
render(reduxWrapper(<IntlLoginPage {...props} />));
render(reduxWrapper(<LoginPage {...props} />));
expect(sendPageEvent).toHaveBeenCalledWith('login_and_registration', 'login');
});
@@ -764,7 +770,7 @@ describe('LoginPage', () => {
});
store.dispatch = jest.fn(store.dispatch);
render(reduxWrapper(<IntlLoginPage {...props} />));
render(reduxWrapper(<LoginPage {...props} />));
expect(store.dispatch).toHaveBeenCalledWith(backupLoginFormBegin(
{
formFields: {
@@ -778,7 +784,7 @@ describe('LoginPage', () => {
});
it('should send track event when forgot password link is clicked', () => {
render(reduxWrapper(<IntlLoginPage {...props} />));
render(reduxWrapper(<LoginPage {...props} />));
fireEvent.click(screen.getByText(
'Forgot password',
{ selector: '#forgot-password' },
@@ -797,7 +803,7 @@ describe('LoginPage', () => {
});
store.dispatch = jest.fn(store.dispatch);
render(reduxWrapper(<IntlLoginPage {...props} />));
render(reduxWrapper(<LoginPage {...props} />));
expect(store.dispatch).toHaveBeenCalledWith(backupLoginFormBegin(
{
formFields: {
@@ -826,7 +832,7 @@ describe('LoginPage', () => {
},
});
const { container } = render(reduxWrapper(<IntlLoginPage {...props} />));
const { container } = render(reduxWrapper(<LoginPage {...props} />));
expect(container.querySelector('input#emailOrUsername').value).toEqual('john_doe');
expect(container.querySelector('input#password').value).toEqual('test-password');
});

View File

@@ -1,5 +1,5 @@
import React, { useEffect, useState } from 'react';
import { connect } from 'react-redux';
import { useDispatch, useSelector } from 'react-redux';
import { getConfig } from '@edx/frontend-platform';
import { sendPageEvent, sendTrackEvent } from '@edx/frontend-platform/analytics';
@@ -24,16 +24,20 @@ import { LOGIN_PAGE, REGISTER_PAGE } from '../data/constants';
import {
getTpaHint, getTpaProvider, updatePathWithQueryParams,
} from '../data/utils';
import { LoginPage } from '../login';
import { backupLoginForm } from '../login/data/actions';
import LoginComponentSlot from '../plugin-slots/LoginComponentSlot';
import { RegistrationPage } from '../register';
import { backupRegistrationForm } from '../register/data/actions';
const Logistration = (props) => {
const { selectedPage, tpaProviders } = props;
const Logistration = ({
selectedPage,
}) => {
const tpaHint = getTpaHint();
const tpaProviders = useSelector(tpaProvidersSelector);
const dispatch = useDispatch();
const {
providers, secondaryProviders,
providers,
secondaryProviders,
} = tpaProviders;
const { formatMessage } = useIntl();
const [institutionLogin, setInstitutionLogin] = useState(false);
@@ -45,7 +49,8 @@ const Logistration = (props) => {
useEffect(() => {
const authService = getAuthService();
if (authService) {
authService.getCsrfTokenService().getCsrfToken(getConfig().LMS_BASE_URL);
authService.getCsrfTokenService()
.getCsrfToken(getConfig().LMS_BASE_URL);
}
});
@@ -71,11 +76,11 @@ const Logistration = (props) => {
return;
}
sendTrackEvent(`edx.bi.${tabKey.replace('/', '')}_form.toggled`, { category: 'user-engagement' });
props.clearThirdPartyAuthContextErrorMessage();
dispatch(clearThirdPartyAuthContextErrorMessage());
if (tabKey === LOGIN_PAGE) {
props.backupRegistrationForm();
dispatch(backupRegistrationForm());
} else if (tabKey === REGISTER_PAGE) {
props.backupLoginForm();
dispatch(backupLoginForm());
}
setKey(tabKey);
};
@@ -111,7 +116,10 @@ const Logistration = (props) => {
{!institutionLogin && (
<h3 className="mb-4.5">{formatMessage(messages['logistration.sign.in'])}</h3>
)}
<LoginPage institutionLogin={institutionLogin} handleInstitutionLogin={handleInstitutionLogin} />
<LoginComponentSlot
institutionLogin={institutionLogin}
handleInstitutionLogin={handleInstitutionLogin}
/>
</div>
</>
)
@@ -124,12 +132,16 @@ const Logistration = (props) => {
</Tabs>
)
: (!isValidTpaHint() && !hideRegistrationLink && (
<Tabs defaultActiveKey={selectedPage} id="controlled-tab" onSelect={(tabKey) => handleOnSelect(tabKey, selectedPage)}>
<Tabs
defaultActiveKey={selectedPage}
id="controlled-tab"
onSelect={(tabKey) => handleOnSelect(tabKey, selectedPage)}
>
<Tab title={formatMessage(messages['logistration.register'])} eventKey={REGISTER_PAGE} />
<Tab title={formatMessage(messages['logistration.sign.in'])} eventKey={LOGIN_PAGE} />
</Tabs>
))}
{ key && (
{key && (
<Navigate to={updatePathWithQueryParams(key)} replace />
)}
<div id="main-content" className="main-content">
@@ -139,7 +151,12 @@ const Logistration = (props) => {
</h3>
)}
{selectedPage === LOGIN_PAGE
? <LoginPage institutionLogin={institutionLogin} handleInstitutionLogin={handleInstitutionLogin} />
? (
<LoginComponentSlot
institutionLogin={institutionLogin}
handleInstitutionLogin={handleInstitutionLogin}
/>
)
: (
<RegistrationPage
institutionLogin={institutionLogin}
@@ -156,35 +173,10 @@ const Logistration = (props) => {
Logistration.propTypes = {
selectedPage: PropTypes.string,
backupLoginForm: PropTypes.func.isRequired,
backupRegistrationForm: PropTypes.func.isRequired,
clearThirdPartyAuthContextErrorMessage: PropTypes.func.isRequired,
tpaProviders: PropTypes.shape({
providers: PropTypes.arrayOf(PropTypes.shape({})),
secondaryProviders: PropTypes.arrayOf(PropTypes.shape({})),
}),
};
Logistration.defaultProps = {
tpaProviders: {
providers: [],
secondaryProviders: [],
},
};
Logistration.defaultProps = {
selectedPage: REGISTER_PAGE,
};
const mapStateToProps = state => ({
tpaProviders: tpaProvidersSelector(state),
});
export default connect(
mapStateToProps,
{
backupLoginForm,
backupRegistrationForm,
clearThirdPartyAuthContextErrorMessage,
},
)(Logistration);
export default Logistration;

View File

@@ -1,9 +1,8 @@
import React from 'react';
import { Provider } from 'react-redux';
import { getConfig, mergeConfig } from '@edx/frontend-platform';
import { sendPageEvent, sendTrackEvent } from '@edx/frontend-platform/analytics';
import { configure, injectIntl, IntlProvider } from '@edx/frontend-platform/i18n';
import { configure, IntlProvider } from '@edx/frontend-platform/i18n';
import { fireEvent, render, screen } from '@testing-library/react';
import { MemoryRouter } from 'react-router-dom';
import configureStore from 'redux-mock-store';
@@ -23,7 +22,6 @@ jest.mock('@edx/frontend-platform/analytics', () => ({
jest.mock('@edx/frontend-platform/auth');
const mockStore = configureStore();
const IntlLogistration = injectIntl(Logistration);
describe('Logistration', () => {
let store = {};
@@ -50,16 +48,26 @@ describe('Logistration', () => {
marketingEmailsOptIn: true,
},
formFields: {
name: '', email: '', username: '', password: '',
name: '',
email: '',
username: '',
password: '',
},
emailSuggestion: {
suggestion: '', type: '',
suggestion: '',
type: '',
},
errors: {
name: '', email: '', username: '', password: '',
name: '',
email: '',
username: '',
password: '',
},
},
registrationResult: { success: false, redirectUrl: '' },
registrationResult: {
success: false,
redirectUrl: '',
},
registrationError: {},
usernameSuggestions: [],
validationApiRateLimited: false,
@@ -71,7 +79,18 @@ describe('Logistration', () => {
},
},
login: {
loginResult: { success: false, redirectUrl: '' },
loginResult: {
success: false,
redirectUrl: '',
},
loginFormData: {
formFields: {
emailOrUsername: '', password: '',
},
errors: {
emailOrUsername: '', password: '',
},
},
},
};
@@ -95,7 +114,7 @@ describe('Logistration', () => {
});
it('should do nothing when user clicks on the same tab (login/register) again', () => {
const { container } = render(reduxWrapper(<IntlLogistration />));
const { container } = render(reduxWrapper(<Logistration />));
// While staying on the registration form, clicking the register tab again
fireEvent.click(container.querySelector('a[data-rb-event-key="/register"]'));
@@ -107,14 +126,14 @@ describe('Logistration', () => {
ALLOW_PUBLIC_ACCOUNT_CREATION: true,
});
const { container } = render(reduxWrapper(<IntlLogistration />));
const { container } = render(reduxWrapper(<Logistration />));
expect(container.querySelector('RegistrationPage')).toBeDefined();
});
it('should render login page', () => {
const props = { selectedPage: LOGIN_PAGE };
const { container } = render(reduxWrapper(<IntlLogistration {...props} />));
const { container } = render(reduxWrapper(<Logistration {...props} />));
expect(container.querySelector('LoginPage')).toBeDefined();
});
@@ -125,7 +144,7 @@ describe('Logistration', () => {
});
let props = { selectedPage: LOGIN_PAGE };
const { rerender } = render(reduxWrapper(<IntlLogistration {...props} />));
const { rerender } = render(reduxWrapper(<Logistration {...props} />));
// verifying sign in heading
expect(screen.getByRole('heading', { level: 3 }).textContent).toEqual('Sign in');
@@ -133,7 +152,7 @@ describe('Logistration', () => {
// register page is still accessible when SHOW_REGISTRATION_LINKS is false
// but it needs to be accessed directly
props = { selectedPage: REGISTER_PAGE };
rerender(reduxWrapper(<IntlLogistration {...props} />));
rerender(reduxWrapper(<Logistration {...props} />));
// verifying register heading
expect(screen.getByRole('heading', { level: 3 }).textContent).toEqual('Register');
@@ -160,7 +179,7 @@ describe('Logistration', () => {
});
const props = { selectedPage: LOGIN_PAGE };
const { container } = render(reduxWrapper(<IntlLogistration {...props} />));
const { container } = render(reduxWrapper(<Logistration {...props} />));
// verifying sign in heading for institution login false
expect(screen.getByRole('heading', { level: 3 }).textContent).toEqual('Sign in');
@@ -190,7 +209,7 @@ describe('Logistration', () => {
});
const props = { selectedPage: LOGIN_PAGE };
render(reduxWrapper(<IntlLogistration {...props} />));
render(reduxWrapper(<Logistration {...props} />));
expect(screen.getByText('Institution/campus credentials')).toBeDefined();
// on clicking "Institution/campus credentials" button, it should display institution login page
@@ -221,7 +240,7 @@ describe('Logistration', () => {
});
const props = { selectedPage: LOGIN_PAGE };
render(reduxWrapper(<IntlLogistration {...props} />));
render(reduxWrapper(<Logistration {...props} />));
fireEvent.click(screen.getByText('Institution/campus credentials'));
expect(sendTrackEvent).toHaveBeenCalledWith('edx.bi.institution_login_form.toggled', { category: 'user-engagement' });
@@ -253,7 +272,7 @@ describe('Logistration', () => {
delete window.location;
window.location = { hostname: getConfig().SITE_NAME, href: getConfig().BASE_URL };
render(reduxWrapper(<IntlLogistration />));
render(reduxWrapper(<Logistration />));
fireEvent.click(screen.getByText('Institution/campus credentials'));
expect(screen.getByText('Test University')).toBeDefined();
@@ -264,7 +283,7 @@ describe('Logistration', () => {
it('should fire action to backup registration form on tab click', () => {
store.dispatch = jest.fn(store.dispatch);
const { container } = render(reduxWrapper(<IntlLogistration />));
const { container } = render(reduxWrapper(<Logistration />));
fireEvent.click(container.querySelector('a[data-rb-event-key="/login"]'));
expect(store.dispatch).toHaveBeenCalledWith(backupRegistrationForm());
});
@@ -272,14 +291,14 @@ describe('Logistration', () => {
it('should fire action to backup login form on tab click', () => {
store.dispatch = jest.fn(store.dispatch);
const props = { selectedPage: LOGIN_PAGE };
const { container } = render(reduxWrapper(<IntlLogistration {...props} />));
const { container } = render(reduxWrapper(<Logistration {...props} />));
fireEvent.click(container.querySelector('a[data-rb-event-key="/register"]'));
expect(store.dispatch).toHaveBeenCalledWith(backupLoginForm());
});
it('should clear tpa context errorMessage tab click', () => {
store.dispatch = jest.fn(store.dispatch);
const { container } = render(reduxWrapper(<IntlLogistration />));
const { container } = render(reduxWrapper(<Logistration />));
fireEvent.click(container.querySelector('a[data-rb-event-key="/login"]'));
expect(store.dispatch).toHaveBeenCalledWith(clearThirdPartyAuthContextErrorMessage());
});

View File

@@ -0,0 +1,47 @@
# Login Component Slot
### Slot ID: `org.openedx.frontend.authn.login_component.v1`
## Description
This slot is used to replace/modify/hide the login component.
## Example
### Default content
![Default Login Page](./default_component.png)
### With a prepended message
![Login Page with ](./component_with_prefix.png)
The following `env.config.jsx` will add a message before the login component.
```js
import { DIRECT_PLUGIN, PLUGIN_OPERATIONS } from '@openedx/frontend-plugin-framework';
// Load environment variables from .env file
const config = {
...process.env,
pluginSlots: {
'org.openedx.frontend.authn.login_component.v1': {
keepDefault: true,
plugins: [
{
op: PLUGIN_OPERATIONS.Insert,
widget: {
id: 'test_plugin',
type: DIRECT_PLUGIN,
priority: 1,
RenderWidget: () => (
<h2>You're logging into TEST Instance.</h2>
)
},
},
],
},
},
};
export default config;
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

View File

@@ -0,0 +1,31 @@
import React from 'react';
import { PluginSlot } from '@openedx/frontend-plugin-framework';
import PropTypes from 'prop-types';
import LoginPage from '../../login/LoginPage';
const LoginComponentSlot = ({
institutionLogin,
handleInstitutionLogin,
}) => (
<PluginSlot
id="org.openedx.frontend.authn.login_component.v1"
pluginProps={{
isInstitutionLogin: institutionLogin,
setInstitutionLogin: handleInstitutionLogin,
}}
>
<LoginPage
institutionLogin={institutionLogin}
handleInstitutionLogin={handleInstitutionLogin}
/>
</PluginSlot>
);
LoginComponentSlot.propTypes = {
institutionLogin: PropTypes.bool,
handleInstitutionLogin: PropTypes.func,
};
export default LoginComponentSlot;

View File

@@ -1,10 +1,9 @@
import React from 'react';
import { Provider } from 'react-redux';
import { getConfig, mergeConfig } from '@edx/frontend-platform';
import { identifyAuthenticatedUser, sendTrackEvent } from '@edx/frontend-platform/analytics';
import { getAuthenticatedUser } from '@edx/frontend-platform/auth';
import { configure, injectIntl, IntlProvider } from '@edx/frontend-platform/i18n';
import { configure, IntlProvider } from '@edx/frontend-platform/i18n';
import {
fireEvent, render, screen,
} from '@testing-library/react';
@@ -22,7 +21,6 @@ import {
import { saveUserProfile } from '../data/actions';
import ProgressiveProfiling from '../ProgressiveProfiling';
const IntlProgressiveProfilingPage = injectIntl(ProgressiveProfiling);
const mockStore = configureStore();
jest.mock('@edx/frontend-platform/analytics', () => ({
@@ -114,7 +112,7 @@ describe('ProgressiveProfilingTests', () => {
mergeConfig({
AUTHN_PROGRESSIVE_PROFILING_SUPPORT_LINK: '',
});
const { queryByRole } = render(reduxWrapper(<IntlProgressiveProfilingPage />));
const { queryByRole } = render(reduxWrapper(<ProgressiveProfiling />));
const button = queryByRole('button', { name: /learn more about how we use this information/i });
expect(button).toBeNull();
@@ -125,7 +123,7 @@ describe('ProgressiveProfilingTests', () => {
AUTHN_PROGRESSIVE_PROFILING_SUPPORT_LINK: 'http://localhost:1999/support',
});
const { getByText } = render(reduxWrapper(<IntlProgressiveProfilingPage />));
const { getByText } = render(reduxWrapper(<ProgressiveProfiling />));
const learnMoreButton = getByText('Learn more about how we use this information.');
@@ -135,7 +133,7 @@ describe('ProgressiveProfilingTests', () => {
it('should open modal on pressing skip for now button', () => {
delete window.location;
window.location = { href: getConfig().BASE_URL.concat(AUTHN_PROGRESSIVE_PROFILING) };
const { getByRole } = render(reduxWrapper(<IntlProgressiveProfilingPage />));
const { getByRole } = render(reduxWrapper(<ProgressiveProfiling />));
const skipButton = getByRole('button', { name: /skip for now/i });
fireEvent.click(skipButton);
@@ -150,7 +148,7 @@ describe('ProgressiveProfilingTests', () => {
// ******** test event functionality ********
it('should make identify call to segment on progressive profiling page', () => {
render(reduxWrapper(<IntlProgressiveProfilingPage />));
render(reduxWrapper(<ProgressiveProfiling />));
expect(identifyAuthenticatedUser).toHaveBeenCalledWith(3);
expect(identifyAuthenticatedUser).toHaveBeenCalled();
@@ -160,7 +158,7 @@ describe('ProgressiveProfilingTests', () => {
mergeConfig({
AUTHN_PROGRESSIVE_PROFILING_SUPPORT_LINK: 'http://localhost:1999/support',
});
render(reduxWrapper(<IntlProgressiveProfilingPage />));
render(reduxWrapper(<ProgressiveProfiling />));
const supportLink = screen.getByRole('link', { name: /learn more about how we use this information/i });
fireEvent.click(supportLink);
@@ -178,7 +176,7 @@ describe('ProgressiveProfilingTests', () => {
};
delete window.location;
window.location = { href: getConfig().BASE_URL.concat(AUTHN_PROGRESSIVE_PROFILING) };
render(reduxWrapper(<IntlProgressiveProfilingPage />));
render(reduxWrapper(<ProgressiveProfiling />));
const nextButton = screen.getByText('Next');
fireEvent.click(nextButton);
@@ -194,7 +192,7 @@ describe('ProgressiveProfilingTests', () => {
extended_profile: [{ field_name: 'company', field_value: 'test company' }],
};
store.dispatch = jest.fn(store.dispatch);
const { getByLabelText, getByText } = render(reduxWrapper(<IntlProgressiveProfilingPage />));
const { getByLabelText, getByText } = render(reduxWrapper(<ProgressiveProfiling />));
const genderSelect = getByLabelText('Gender');
const companyInput = getByLabelText('Company');
@@ -216,7 +214,7 @@ describe('ProgressiveProfilingTests', () => {
},
});
const { container } = render(reduxWrapper(<IntlProgressiveProfilingPage />));
const { container } = render(reduxWrapper(<ProgressiveProfiling />));
const errorElement = container.querySelector('#pp-page-errors');
expect(errorElement).toBeTruthy();
@@ -232,7 +230,7 @@ describe('ProgressiveProfilingTests', () => {
href: getConfig().BASE_URL,
};
render(reduxWrapper(<IntlProgressiveProfilingPage />));
render(reduxWrapper(<ProgressiveProfiling />));
expect(window.location.href).toEqual(DASHBOARD_URL);
});
@@ -250,7 +248,7 @@ describe('ProgressiveProfilingTests', () => {
success: true,
},
});
const { container } = render(reduxWrapper(<IntlProgressiveProfilingPage />));
const { container } = render(reduxWrapper(<ProgressiveProfiling />));
const nextButton = container.querySelector('button.btn-brand');
expect(nextButton.textContent).toEqual('Next');
@@ -277,7 +275,7 @@ describe('ProgressiveProfilingTests', () => {
},
});
const { container } = render(reduxWrapper(<IntlProgressiveProfilingPage />));
const { container } = render(reduxWrapper(<ProgressiveProfiling />));
const nextButton = container.querySelector('button.btn-brand');
expect(nextButton.textContent).toEqual('Submit');
@@ -311,7 +309,7 @@ describe('ProgressiveProfilingTests', () => {
href: getConfig().BASE_URL.concat(AUTHN_PROGRESSIVE_PROFILING),
search: `?host=${host}&variant=${EMBEDDED}`,
};
render(reduxWrapper(<IntlProgressiveProfilingPage />));
render(reduxWrapper(<ProgressiveProfiling />));
const skipLinkButton = screen.getByText('Skip for now');
fireEvent.click(skipLinkButton);
@@ -336,7 +334,7 @@ describe('ProgressiveProfilingTests', () => {
},
});
const { container } = render(reduxWrapper(<IntlProgressiveProfilingPage />));
const { container } = render(reduxWrapper(<ProgressiveProfiling />));
const tpaSpinnerElement = container.querySelector('#tpa-spinner');
expect(tpaSpinnerElement).toBeTruthy();
@@ -355,7 +353,7 @@ describe('ProgressiveProfilingTests', () => {
href: getConfig().BASE_URL.concat(AUTHN_PROGRESSIVE_PROFILING),
search: `?host=${host}`,
};
render(reduxWrapper(<IntlProgressiveProfilingPage />));
render(reduxWrapper(<ProgressiveProfiling />));
const submitButton = screen.getByText('Next');
fireEvent.click(submitButton);
@@ -370,7 +368,7 @@ describe('ProgressiveProfilingTests', () => {
search: `?variant=${EMBEDDED}&host=${host}`,
};
const { container } = render(reduxWrapper(<IntlProgressiveProfilingPage />));
const { container } = render(reduxWrapper(<ProgressiveProfiling />));
const genderField = container.querySelector('#gender');
expect(genderField).toBeTruthy();
@@ -391,7 +389,7 @@ describe('ProgressiveProfilingTests', () => {
},
});
render(reduxWrapper(<IntlProgressiveProfilingPage />));
render(reduxWrapper(<ProgressiveProfiling />));
expect(window.location.href).toBe(DASHBOARD_URL);
});
@@ -419,7 +417,7 @@ describe('ProgressiveProfilingTests', () => {
},
});
render(reduxWrapper(<IntlProgressiveProfilingPage />));
render(reduxWrapper(<ProgressiveProfiling />));
const submitButton = screen.getByText('Submit');
fireEvent.click(submitButton);
expect(window.location.href).toBe(redirectUrl);

View File

@@ -1,13 +1,9 @@
import React from 'react';
import { injectIntl, IntlProvider } from '@edx/frontend-platform/i18n';
import { IntlProvider } from '@edx/frontend-platform/i18n';
import { render } from '@testing-library/react';
import SmallLayout from './SmallLayout';
import mockedRecommendedProducts from '../data/tests/mockedData';
const IntlRecommendationsSmallLayoutPage = injectIntl(SmallLayout);
jest.mock('react-router-dom', () => ({
...jest.requireActual('react-router-dom'),
useLocation: jest.fn(),
@@ -36,7 +32,7 @@ describe('RecommendationsPageTests', () => {
});
it('should render recommendations when recommendations are not loading', () => {
const { container } = render(reduxWrapper(<IntlRecommendationsSmallLayoutPage {...props} />));
const { container } = render(reduxWrapper(<SmallLayout {...props} />));
const reactLoadingSkeleton = container.querySelector('.react-loading-skeleton');
@@ -48,7 +44,7 @@ describe('RecommendationsPageTests', () => {
...props,
isLoading: true,
};
const { container } = render(reduxWrapper(<IntlRecommendationsSmallLayoutPage {...props} />));
const { container } = render(reduxWrapper(<SmallLayout {...props} />));
const reactLoadingSkeleton = container.querySelector('.react-loading-skeleton');

View File

@@ -1,10 +1,12 @@
import { renderHook } from '@testing-library/react-hooks';
import React from 'react';
import { render } from '@testing-library/react';
import algoliasearchHelper from 'algoliasearch-helper';
import mockedRecommendedProducts from './mockedData';
import CreateAlgoliaSearchHelperMock from './test_utils/test_utils';
import isOneTrustFunctionalCookieEnabled from '../../../data/oneTrust';
import useAlgoliaRecommendations from '../hooks/useAlgoliaRecommendations';
import CreateAlgoliaSearchHelperMock from './test_utils/test_utils';
jest.mock('algoliasearch-helper');
@@ -17,6 +19,23 @@ jest.mock('../../../data/algolia', () => ({
jest.mock('../algoliaResultsParser', () => jest.fn((course) => course));
const renderHook = (hookCallback) => {
const result = {
current: null,
};
const Component = () => {
const val = hookCallback();
React.useEffect(() => {
result.current = val;
});
return null;
};
render(<Component />);
return { result };
};
describe('useAlgoliaRecommendations Tests', () => {
const MockSearchHelperWithData = new CreateAlgoliaSearchHelperMock(mockedRecommendedProducts);
const MockSearchHelperWithoutData = new CreateAlgoliaSearchHelperMock();
@@ -28,8 +47,10 @@ describe('useAlgoliaRecommendations Tests', () => {
() => useAlgoliaRecommendations('PK', 'Introductory'),
);
expect(result.current.recommendations).toEqual(mockedRecommendedProducts);
expect(result.current.isLoading).toBe(false);
expect(result.current.recommendations)
.toEqual(mockedRecommendedProducts);
expect(result.current.isLoading)
.toBe(false);
});
it('should not fetch recommendations if functional cookies are not set', async () => {
@@ -39,8 +60,10 @@ describe('useAlgoliaRecommendations Tests', () => {
() => useAlgoliaRecommendations('PK', 'Introductory'),
);
expect(result.current.recommendations).toEqual([]);
expect(result.current.isLoading).toBe(false);
expect(result.current.recommendations)
.toEqual([]);
expect(result.current.isLoading)
.toBe(false);
});
it('should return empty list if no recommendations returned from Algolia', async () => {
@@ -50,7 +73,9 @@ describe('useAlgoliaRecommendations Tests', () => {
() => useAlgoliaRecommendations('PK', 'Introductory'),
);
expect(result.current.recommendations).toEqual([]);
expect(result.current.isLoading).toBe(false);
expect(result.current.recommendations)
.toEqual([]);
expect(result.current.isLoading)
.toBe(false);
});
});

View File

@@ -1,14 +1,12 @@
import React from 'react';
import { Provider } from 'react-redux';
import { injectIntl, IntlProvider } from '@edx/frontend-platform/i18n';
import { IntlProvider } from '@edx/frontend-platform/i18n';
import { render } from '@testing-library/react';
import configureStore from 'redux-mock-store';
import mockedProductData from './mockedData';
import RecommendationList from '../RecommendationsList';
const IntlRecommendationList = injectIntl(RecommendationList);
const mockStore = configureStore();
describe('RecommendationsListTests', () => {
@@ -25,7 +23,7 @@ describe('RecommendationsListTests', () => {
userId: 1234567,
};
const { container } = render(reduxWrapper(<IntlRecommendationList {...props} />));
const { container } = render(reduxWrapper(<RecommendationList {...props} />));
const recommendationCards = container.querySelectorAll('.recommendation-card');
expect(recommendationCards.length).toEqual(mockedProductData.length);
@@ -37,7 +35,7 @@ describe('RecommendationsListTests', () => {
userId: 1234567,
};
const { getByText } = render(reduxWrapper(<IntlRecommendationList {...props} />));
const { getByText } = render(reduxWrapper(<RecommendationList {...props} />));
const firstFooterContent = getByText('1 Course');
const secondFooterContent = getByText('2 Courses');

View File

@@ -1,9 +1,8 @@
import React from 'react';
import { Provider } from 'react-redux';
import { getConfig } from '@edx/frontend-platform';
import { sendTrackEvent } from '@edx/frontend-platform/analytics';
import { injectIntl, IntlProvider } from '@edx/frontend-platform/i18n';
import { IntlProvider } from '@edx/frontend-platform/i18n';
import { useMediaQuery } from '@openedx/paragon';
import { fireEvent, render } from '@testing-library/react';
import { useLocation } from 'react-router-dom';
@@ -16,7 +15,6 @@ import mockedRecommendedProducts from '../data/tests/mockedData';
import RecommendationsPage from '../RecommendationsPage';
import { eventNames, getProductMapping } from '../track';
const IntlRecommendationsPage = injectIntl(RecommendationsPage);
const mockStore = configureStore();
jest.mock('@edx/frontend-platform/analytics', () => ({
@@ -77,7 +75,7 @@ describe('RecommendationsPageTests', () => {
});
it('should redirect to dashboard if user is not coming from registration workflow', () => {
render(reduxWrapper(<IntlRecommendationsPage />));
render(reduxWrapper(<RecommendationsPage />));
expect(window.location.href).toEqual(dashboardUrl);
});
@@ -86,14 +84,14 @@ describe('RecommendationsPageTests', () => {
recommendations: [],
isLoading: false,
});
render(reduxWrapper(<IntlRecommendationsPage />));
render(reduxWrapper(<RecommendationsPage />));
expect(window.location.href).toEqual(dashboardUrl);
});
it('should redirect user if they click "Skip for now" button', () => {
mockUseLocation();
jest.useFakeTimers();
const { container } = render(reduxWrapper(<IntlRecommendationsPage />));
const { container } = render(reduxWrapper(<RecommendationsPage />));
const skipButton = container.querySelector('.pgn__stateful-btn-state-default');
fireEvent.click(skipButton);
jest.advanceTimersByTime(300);
@@ -103,7 +101,7 @@ describe('RecommendationsPageTests', () => {
it('should display recommendations small layout for small screen', () => {
mockUseLocation();
useMediaQuery.mockReturnValue(true);
const { container } = render(reduxWrapper(<IntlRecommendationsPage />));
const { container } = render(reduxWrapper(<RecommendationsPage />));
const recommendationsSmallLayout = container.querySelector('#recommendations-small-layout');
const reactLoadingSkeleton = container.querySelector('.react-loading-skeleton');
@@ -115,7 +113,7 @@ describe('RecommendationsPageTests', () => {
it('should display recommendations large layout for large screen', () => {
mockUseLocation();
useMediaQuery.mockReturnValue(false);
const { container } = render(reduxWrapper(<IntlRecommendationsPage />));
const { container } = render(reduxWrapper(<RecommendationsPage />));
const pgnCollapsible = container.querySelector('.pgn_collapsible');
const reactLoadingSkeleton = container.querySelector('.react-loading-skeleton');
@@ -131,7 +129,7 @@ describe('RecommendationsPageTests', () => {
recommendations: [],
isLoading: true,
});
const { container } = render(reduxWrapper(<IntlRecommendationsPage />));
const { container } = render(reduxWrapper(<RecommendationsPage />));
const reactLoadingSkeleton = container.querySelector('.react-loading-skeleton');
@@ -145,7 +143,7 @@ describe('RecommendationsPageTests', () => {
recommendations: [],
isLoading: true,
});
const { container } = render(reduxWrapper(<IntlRecommendationsPage />));
const { container } = render(reduxWrapper(<RecommendationsPage />));
const reactLoadingSkeleton = container.querySelector('.react-loading-skeleton');
@@ -160,7 +158,7 @@ describe('RecommendationsPageTests', () => {
});
useMediaQuery.mockReturnValue(false);
render(reduxWrapper(<IntlRecommendationsPage />));
render(reduxWrapper(<RecommendationsPage />));
expect(sendTrackEvent).toBeCalled();
expect(sendTrackEvent).toHaveBeenCalledWith(

View File

@@ -1,8 +1,7 @@
import React from 'react';
import { Provider } from 'react-redux';
import { mergeConfig } from '@edx/frontend-platform';
import { injectIntl, IntlProvider } from '@edx/frontend-platform/i18n';
import { IntlProvider } from '@edx/frontend-platform/i18n';
import { fireEvent, render } from '@testing-library/react';
import { BrowserRouter as Router } from 'react-router-dom';
import configureStore from 'redux-mock-store';
@@ -10,7 +9,6 @@ import configureStore from 'redux-mock-store';
import { COUNTRY_CODE_KEY, COUNTRY_DISPLAY_KEY } from './validator';
import { CountryField } from '../index';
const IntlCountryField = injectIntl(CountryField);
const mockStore = configureStore();
jest.mock('react-router-dom', () => {
@@ -82,7 +80,7 @@ describe('CountryField', () => {
};
it('should run country field validation when onBlur is fired', () => {
const { container } = render(routerWrapper(reduxWrapper(<IntlCountryField {...props} />)));
const { container } = render(routerWrapper(reduxWrapper(<CountryField {...props} />)));
const countryInput = container.querySelector('input[name="country"]');
fireEvent.blur(countryInput, {
@@ -97,7 +95,7 @@ describe('CountryField', () => {
});
it('should run country field validation when country name is invalid', () => {
const { container } = render(routerWrapper(reduxWrapper(<IntlCountryField {...props} />)));
const { container } = render(routerWrapper(reduxWrapper(<CountryField {...props} />)));
const countryInput = container.querySelector('input[name="country"]');
fireEvent.blur(countryInput, {
@@ -112,7 +110,7 @@ describe('CountryField', () => {
});
it('should not run country field validation when onBlur is fired by drop-down arrow icon click', () => {
const { container } = render(routerWrapper(reduxWrapper(<IntlCountryField {...props} />)));
const { container } = render(routerWrapper(reduxWrapper(<CountryField {...props} />)));
const countryInput = container.querySelector('input[name="country"]');
const dropdownArrowIcon = container.querySelector('.btn-icon.pgn__form-autosuggest__icon-button');
@@ -125,7 +123,7 @@ describe('CountryField', () => {
});
it('should update errors for frontend validations', () => {
const { container } = render(routerWrapper(reduxWrapper(<IntlCountryField {...props} />)));
const { container } = render(routerWrapper(reduxWrapper(<CountryField {...props} />)));
const countryInput = container.querySelector('input[name="country"]');
fireEvent.blur(countryInput, { target: { value: '', name: 'country' } });
@@ -135,7 +133,7 @@ describe('CountryField', () => {
});
it('should clear error on focus', () => {
const { container } = render(routerWrapper(reduxWrapper(<IntlCountryField {...props} />)));
const { container } = render(routerWrapper(reduxWrapper(<CountryField {...props} />)));
const countryInput = container.querySelector('input[name="country"]');
fireEvent.focus(countryInput);
@@ -153,7 +151,7 @@ describe('CountryField', () => {
},
});
const { container } = render(routerWrapper(reduxWrapper(<IntlCountryField {...props} />)));
const { container } = render(routerWrapper(reduxWrapper(<CountryField {...props} />)));
container.querySelector('input[name="country"]');
expect(props.onChangeHandler).toHaveBeenCalledTimes(1);
@@ -164,7 +162,7 @@ describe('CountryField', () => {
});
it('should set option on dropdown menu item click', () => {
const { container } = render(routerWrapper(reduxWrapper(<IntlCountryField {...props} />)));
const { container } = render(routerWrapper(reduxWrapper(<CountryField {...props} />)));
const dropdownButton = container.querySelector('.pgn__form-autosuggest__icon-button');
fireEvent.click(dropdownButton);
@@ -181,7 +179,7 @@ describe('CountryField', () => {
it('should set value on change', () => {
const { container } = render(
routerWrapper(reduxWrapper(<IntlCountryField {...props} />)),
routerWrapper(reduxWrapper(<CountryField {...props} />)),
);
const countryInput = container.querySelector('input[name="country"]');
@@ -200,7 +198,7 @@ describe('CountryField', () => {
errorMessage: 'country error message',
};
const { container } = render(routerWrapper(reduxWrapper(<IntlCountryField {...props} />)));
const { container } = render(routerWrapper(reduxWrapper(<CountryField {...props} />)));
const feedbackElement = container.querySelector('div[feedback-for="country"]');
expect(feedbackElement).toBeTruthy();

View File

@@ -1,8 +1,7 @@
import React from 'react';
import { Provider } from 'react-redux';
import { getConfig } from '@edx/frontend-platform';
import { injectIntl, IntlProvider } from '@edx/frontend-platform/i18n';
import { IntlProvider } from '@edx/frontend-platform/i18n';
import { fireEvent, render } from '@testing-library/react';
import { BrowserRouter as Router } from 'react-router-dom';
import configureStore from 'redux-mock-store';
@@ -10,7 +9,6 @@ import configureStore from 'redux-mock-store';
import { clearRegistrationBackendError, fetchRealtimeValidations } from '../../data/actions';
import { EmailField } from '../index';
const IntlEmailField = injectIntl(EmailField);
const mockStore = configureStore();
jest.mock('react-router-dom', () => {
@@ -80,7 +78,7 @@ describe('EmailField', () => {
};
it('should run email field validation when onBlur is fired', () => {
const { container } = render(routerWrapper(reduxWrapper(<IntlEmailField {...props} />)));
const { container } = render(routerWrapper(reduxWrapper(<EmailField {...props} />)));
const emailInput = container.querySelector('input#email');
fireEvent.blur(emailInput, { target: { value: '', name: 'email' } });
@@ -92,7 +90,7 @@ describe('EmailField', () => {
});
it('should update errors for frontend validations', () => {
const { container } = render(routerWrapper(reduxWrapper(<IntlEmailField {...props} />)));
const { container } = render(routerWrapper(reduxWrapper(<EmailField {...props} />)));
const emailInput = container.querySelector('input#email');
fireEvent.blur(emailInput, { target: { value: 'ab', name: 'email' } });
@@ -105,7 +103,7 @@ describe('EmailField', () => {
});
it('should clear error on focus', () => {
const { container } = render(routerWrapper(reduxWrapper(<IntlEmailField {...props} />)));
const { container } = render(routerWrapper(reduxWrapper(<EmailField {...props} />)));
const emailInput = container.querySelector('input#email');
fireEvent.focus(emailInput, { target: { value: '', name: 'email' } });
@@ -119,7 +117,7 @@ describe('EmailField', () => {
it('should call backend validation api on blur event, if frontend validations have passed', () => {
store.dispatch = jest.fn(store.dispatch);
const { container } = render(routerWrapper(reduxWrapper(<IntlEmailField {...props} />)));
const { container } = render(routerWrapper(reduxWrapper(<EmailField {...props} />)));
// Enter a valid email so that frontend validations are passed
const emailInput = container.querySelector('input#email');
@@ -129,7 +127,7 @@ describe('EmailField', () => {
});
it('should give email suggestions for common service provider domain typos', () => {
const { container } = render(routerWrapper(reduxWrapper(<IntlEmailField {...props} />)));
const { container } = render(routerWrapper(reduxWrapper(<EmailField {...props} />)));
const emailInput = container.querySelector('input#email');
fireEvent.blur(emailInput, { target: { value: 'john@yopmail.com', name: 'email' } });
@@ -139,7 +137,7 @@ describe('EmailField', () => {
});
it('should be able to click on email suggestions and set it as value', () => {
const { container } = render(routerWrapper(reduxWrapper(<IntlEmailField {...props} />)));
const { container } = render(routerWrapper(reduxWrapper(<EmailField {...props} />)));
const emailInput = container.querySelector('input#email');
fireEvent.blur(emailInput, { target: { value: 'john@yopmail.com', name: 'email' } });
@@ -154,7 +152,7 @@ describe('EmailField', () => {
});
it('should give error for common top level domain mistakes', () => {
const { container } = render(routerWrapper(reduxWrapper(<IntlEmailField {...props} />)));
const { container } = render(routerWrapper(reduxWrapper(<EmailField {...props} />)));
const emailInput = container.querySelector('input#email');
fireEvent.blur(emailInput, { target: { value: 'john@gmail.mistake', name: 'email' } });
@@ -164,7 +162,7 @@ describe('EmailField', () => {
});
it('should give error and suggestion for invalid email', () => {
const { container } = render(routerWrapper(reduxWrapper(<IntlEmailField {...props} />)));
const { container } = render(routerWrapper(reduxWrapper(<EmailField {...props} />)));
const emailInput = container.querySelector('input#email');
fireEvent.blur(emailInput, { target: { value: 'john@gmail', name: 'email' } });
@@ -194,7 +192,7 @@ describe('EmailField', () => {
store.dispatch = jest.fn(store.dispatch);
const { container } = render(routerWrapper(reduxWrapper(<IntlEmailField {...props} />)));
const { container } = render(routerWrapper(reduxWrapper(<EmailField {...props} />)));
const emailInput = container.querySelector('input#email');
fireEvent.focus(emailInput, { target: { value: 'a@gmail.com', name: 'email' } });
@@ -203,7 +201,7 @@ describe('EmailField', () => {
});
it('should clear email suggestions when close icon is clicked', () => {
const { container } = render(routerWrapper(reduxWrapper(<IntlEmailField {...props} />)));
const { container } = render(routerWrapper(reduxWrapper(<EmailField {...props} />)));
const emailInput = container.querySelector('input#email');
fireEvent.blur(emailInput, { target: { value: 'john@gmail.mistake', name: 'email' } });
@@ -224,7 +222,7 @@ describe('EmailField', () => {
confirmEmailValue: 'confirmEmail@yopmail.com',
};
const { container } = render(routerWrapper(reduxWrapper(<IntlEmailField {...props} />)));
const { container } = render(routerWrapper(reduxWrapper(<EmailField {...props} />)));
const emailInput = container.querySelector('input#email');
fireEvent.blur(emailInput, { target: { value: 'differentEmail@yopmail.com', name: 'email' } });

View File

@@ -1,13 +1,9 @@
import React from 'react';
import { getConfig, mergeConfig } from '@edx/frontend-platform';
import { injectIntl, IntlProvider } from '@edx/frontend-platform/i18n';
import { IntlProvider } from '@edx/frontend-platform/i18n';
import { render } from '@testing-library/react';
import { HonorCode } from '../index';
const IntlHonorCode = injectIntl(HonorCode);
describe('HonorCodeTest', () => {
mergeConfig({
PRIVACY_POLICY: 'http://privacy-policy.com',
@@ -28,7 +24,7 @@ describe('HonorCodeTest', () => {
const errorMessage = `You must agree to the ${getConfig().SITE_NAME} Honor Code`;
const { container } = render(
<IntlProvider locale="en">
<IntlHonorCode
<HonorCode
errorMessage={errorMessage}
onChangeHandler={changeHandler}
/>
@@ -43,7 +39,7 @@ describe('HonorCodeTest', () => {
const expectedMsg = 'I agree to the Your Platform Name Here\u00a0Honor Codein a new tab';
const { container } = render(
<IntlProvider locale="en">
<IntlHonorCode onChangeHandler={changeHandler} />
<HonorCode onChangeHandler={changeHandler} />
</IntlProvider>,
);
@@ -56,7 +52,7 @@ describe('HonorCodeTest', () => {
it('should render Terms of Service and Honor code field', () => {
const { container } = render(
<IntlProvider locale="en">
<IntlHonorCode fieldType="tos_and_honor_code" onChangeHandler={changeHandler} />
<HonorCode fieldType="tos_and_honor_code" onChangeHandler={changeHandler} />
</IntlProvider>,
);
const expectedMsg = 'By creating an account, you agree to the Terms of Service and Honor Code and you '

View File

@@ -1,7 +1,6 @@
import React from 'react';
import { Provider } from 'react-redux';
import { injectIntl, IntlProvider } from '@edx/frontend-platform/i18n';
import { IntlProvider } from '@edx/frontend-platform/i18n';
import { fireEvent, render } from '@testing-library/react';
import { BrowserRouter as Router } from 'react-router-dom';
import configureStore from 'redux-mock-store';
@@ -9,7 +8,6 @@ import configureStore from 'redux-mock-store';
import { clearRegistrationBackendError, fetchRealtimeValidations } from '../../data/actions';
import { NameField } from '../index';
const IntlNameField = injectIntl(NameField);
const mockStore = configureStore();
jest.mock('react-router-dom', () => {
@@ -69,7 +67,7 @@ describe('NameField', () => {
const fieldValidation = { name: 'Enter your full name' };
it('should run name field validation when onBlur is fired', () => {
const { container } = render(routerWrapper(reduxWrapper(<IntlNameField {...props} />)));
const { container } = render(routerWrapper(reduxWrapper(<NameField {...props} />)));
const nameInput = container.querySelector('input#name');
fireEvent.blur(nameInput, { target: { value: '', name: 'name' } });
@@ -82,7 +80,7 @@ describe('NameField', () => {
});
it('should update errors for frontend validations', () => {
const { container } = render(routerWrapper(reduxWrapper(<IntlNameField {...props} />)));
const { container } = render(routerWrapper(reduxWrapper(<NameField {...props} />)));
const nameInput = container.querySelector('input#name');
fireEvent.blur(nameInput, { target: { value: 'https://invalid-name.com', name: 'name' } });
@@ -95,7 +93,7 @@ describe('NameField', () => {
});
it('should clear error on focus', () => {
const { container } = render(routerWrapper(reduxWrapper(<IntlNameField {...props} />)));
const { container } = render(routerWrapper(reduxWrapper(<NameField {...props} />)));
const nameInput = container.querySelector('input#name');
fireEvent.focus(nameInput, { target: { value: '', name: 'name' } });
@@ -113,7 +111,7 @@ describe('NameField', () => {
...props,
shouldFetchUsernameSuggestions: true,
};
const { container } = render(routerWrapper(reduxWrapper(<IntlNameField {...props} />)));
const { container } = render(routerWrapper(reduxWrapper(<NameField {...props} />)));
const nameInput = container.querySelector('input#name');
// Enter a valid name so that frontend validations are passed
@@ -135,7 +133,7 @@ describe('NameField', () => {
});
store.dispatch = jest.fn(store.dispatch);
const { container } = render(routerWrapper(reduxWrapper(<IntlNameField {...props} />)));
const { container } = render(routerWrapper(reduxWrapper(<NameField {...props} />)));
const nameInput = container.querySelector('input#name');

View File

@@ -1,13 +1,9 @@
import React from 'react';
import { getConfig } from '@edx/frontend-platform';
import { injectIntl, IntlProvider } from '@edx/frontend-platform/i18n';
import { IntlProvider } from '@edx/frontend-platform/i18n';
import { fireEvent, render } from '@testing-library/react';
import { TermsOfService } from '../index';
const IntlTermsOfService = injectIntl(TermsOfService);
describe('TermsOfServiceTest', () => {
let value = false;
@@ -23,7 +19,7 @@ describe('TermsOfServiceTest', () => {
const errorMessage = `You must agree to the ${getConfig().SITE_NAME} Terms of Service`;
const { container } = render(
<IntlProvider locale="en">
<IntlTermsOfService errorMessage={errorMessage} onChangeHandler={changeHandler} />
<TermsOfService errorMessage={errorMessage} onChangeHandler={changeHandler} />
</IntlProvider>,
);
const errorElement = container.querySelector('.form-text-size');
@@ -33,7 +29,7 @@ describe('TermsOfServiceTest', () => {
it('should render Terms of Service field', () => {
const { container } = render(
<IntlProvider locale="en">
<IntlTermsOfService onChangeHandler={changeHandler} />
<TermsOfService onChangeHandler={changeHandler} />
</IntlProvider>,
);
@@ -48,7 +44,7 @@ describe('TermsOfServiceTest', () => {
it('should change value when Terms of Service field is checked', () => {
const { container } = render(
<IntlProvider locale="en">
<IntlTermsOfService onChangeHandler={changeHandler} />
<TermsOfService onChangeHandler={changeHandler} />
</IntlProvider>,
);
const field = container.querySelector('input#tos');

View File

@@ -101,7 +101,7 @@ const UsernameField = (props) => {
};
const suggestedUsernames = () => (
<div className={className}>
<div className={className} role="listbox">
<span className="text-gray username-suggestion--label">{formatMessage(messages['registration.username.suggestion.label'])}</span>
<div className="username-scroll-suggested--form-field">
{usernameSuggestions.map((username, index) => (
@@ -112,7 +112,9 @@ const UsernameField = (props) => {
className="username-suggestions--chip data-hj-suppress"
autoComplete={props.autoComplete}
key={`suggestion-${index.toString()}`}
tabIndex={0}
onClick={(e) => handleSuggestionClick(e, username)}
role="option"
>
{username}
</Button>
@@ -123,7 +125,7 @@ const UsernameField = (props) => {
);
if (usernameSuggestions.length > 0 && errorMessage && value === ' ') {
className = 'username-suggestions__error';
className = 'username-suggestions';
iconButton = <IconButton src={Close} iconAs={Icon} alt="Close" onClick={() => handleUsernameSuggestionClose()} variant="black" size="sm" className="username-suggestions__close__button" />;
suggestedUsernameDiv = suggestedUsernames();
} else if (usernameSuggestions.length > 0 && value === ' ') {
@@ -134,14 +136,15 @@ const UsernameField = (props) => {
suggestedUsernameDiv = suggestedUsernames();
}
return (
<FormGroup
{...props}
handleChange={handleOnChange}
handleFocus={handleOnFocus}
handleBlur={handleOnBlur}
>
<div className="username__form-group-wrapper">
{suggestedUsernameDiv}
</FormGroup>
<FormGroup
{...props}
handleChange={handleOnChange}
handleFocus={handleOnFocus}
handleBlur={handleOnBlur}
/>
</div>
);
};

View File

@@ -1,7 +1,6 @@
import React from 'react';
import { Provider } from 'react-redux';
import { injectIntl, IntlProvider } from '@edx/frontend-platform/i18n';
import { IntlProvider } from '@edx/frontend-platform/i18n';
import { fireEvent, render } from '@testing-library/react';
import { BrowserRouter as Router } from 'react-router-dom';
import configureStore from 'redux-mock-store';
@@ -9,7 +8,6 @@ import configureStore from 'redux-mock-store';
import { clearRegistrationBackendError, clearUsernameSuggestions, fetchRealtimeValidations } from '../../data/actions';
import { UsernameField } from '../index';
const IntlUsernameField = injectIntl(UsernameField);
const mockStore = configureStore();
jest.mock('react-router-dom', () => {
@@ -73,7 +71,7 @@ describe('UsernameField', () => {
};
it('should run username field validation when onBlur is fired', () => {
const { container } = render(routerWrapper(reduxWrapper(<IntlUsernameField {...props} />)));
const { container } = render(routerWrapper(reduxWrapper(<UsernameField {...props} />)));
const usernameField = container.querySelector('input#username');
fireEvent.blur(usernameField, { target: { value: '', name: 'username' } });
@@ -86,7 +84,7 @@ describe('UsernameField', () => {
});
it('should update errors for frontend validations', () => {
const { container } = render(routerWrapper(reduxWrapper(<IntlUsernameField {...props} />)));
const { container } = render(routerWrapper(reduxWrapper(<UsernameField {...props} />)));
const usernameField = container.querySelector('input#username');
fireEvent.blur(usernameField, { target: { value: 'user#', name: 'username' } });
@@ -99,7 +97,7 @@ describe('UsernameField', () => {
});
it('should clear error on focus', () => {
const { container } = render(routerWrapper(reduxWrapper(<IntlUsernameField {...props} />)));
const { container } = render(routerWrapper(reduxWrapper(<UsernameField {...props} />)));
const usernameField = container.querySelector('input#username');
fireEvent.focus(usernameField, { target: { value: '', name: 'username' } });
@@ -112,7 +110,7 @@ describe('UsernameField', () => {
});
it('should remove space from field on focus if space exists', () => {
const { container } = render(routerWrapper(reduxWrapper(<IntlUsernameField {...props} />)));
const { container } = render(routerWrapper(reduxWrapper(<UsernameField {...props} />)));
const usernameField = container.querySelector('input#username');
fireEvent.focus(usernameField, { target: { value: ' ', name: 'username' } });
@@ -125,7 +123,7 @@ describe('UsernameField', () => {
it('should call backend validation api on blur event, if frontend validations have passed', () => {
store.dispatch = jest.fn(store.dispatch);
const { container } = render(routerWrapper(reduxWrapper(<IntlUsernameField {...props} />)));
const { container } = render(routerWrapper(reduxWrapper(<UsernameField {...props} />)));
const usernameField = container.querySelector('input#username');
// Enter a valid username so that frontend validations are passed
@@ -135,7 +133,7 @@ describe('UsernameField', () => {
});
it('should remove space from the start of username on change', () => {
const { container } = render(routerWrapper(reduxWrapper(<IntlUsernameField {...props} />)));
const { container } = render(routerWrapper(reduxWrapper(<UsernameField {...props} />)));
const usernameField = container.querySelector('input#username');
fireEvent.change(usernameField, { target: { value: ' test-user', name: 'username' } });
@@ -146,7 +144,7 @@ describe('UsernameField', () => {
});
it('should not set username if it is more than 30 character long', () => {
const { container } = render(routerWrapper(reduxWrapper(<IntlUsernameField {...props} />)));
const { container } = render(routerWrapper(reduxWrapper(<UsernameField {...props} />)));
const usernameField = container.querySelector('input#username');
fireEvent.change(usernameField, { target: { value: 'why_this_is_not_valid_username_', name: 'username' } });
@@ -157,7 +155,7 @@ describe('UsernameField', () => {
it('should clear username suggestions when username field is focused in', () => {
store.dispatch = jest.fn(store.dispatch);
const { container } = render(routerWrapper(reduxWrapper(<IntlUsernameField {...props} />)));
const { container } = render(routerWrapper(reduxWrapper(<UsernameField {...props} />)));
const usernameField = container.querySelector('input#username');
fireEvent.focus(usernameField);
@@ -179,7 +177,7 @@ describe('UsernameField', () => {
errorMessage: 'It looks like this username is already taken',
};
const { container } = render(routerWrapper(reduxWrapper(<IntlUsernameField {...props} />)));
const { container } = render(routerWrapper(reduxWrapper(<UsernameField {...props} />)));
const usernameSuggestions = container.querySelectorAll('button.username-suggestions--chip');
expect(usernameSuggestions.length).toEqual(3);
});
@@ -198,7 +196,7 @@ describe('UsernameField', () => {
value: ' ',
};
const { container } = render(routerWrapper(reduxWrapper(<IntlUsernameField {...props} />)));
const { container } = render(routerWrapper(reduxWrapper(<UsernameField {...props} />)));
const usernameSuggestions = container.querySelectorAll('button.username-suggestions--chip');
expect(usernameSuggestions.length).toEqual(3);
});
@@ -218,7 +216,7 @@ describe('UsernameField', () => {
errorMessage: 'username error',
};
const { container } = render(routerWrapper(reduxWrapper(<IntlUsernameField {...props} />)));
const { container } = render(routerWrapper(reduxWrapper(<UsernameField {...props} />)));
const usernameSuggestions = container.querySelectorAll('button.username-suggestions--chip');
expect(usernameSuggestions.length).toEqual(3);
});
@@ -232,7 +230,7 @@ describe('UsernameField', () => {
},
});
render(routerWrapper(reduxWrapper(<IntlUsernameField {...props} />)));
render(routerWrapper(reduxWrapper(<UsernameField {...props} />)));
expect(props.handleChange).toHaveBeenCalledTimes(1);
expect(props.handleChange).toHaveBeenCalledWith(
{ target: { name: 'username', value: ' ' } },
@@ -253,7 +251,7 @@ describe('UsernameField', () => {
value: ' ',
};
const { container } = render(routerWrapper(reduxWrapper(<IntlUsernameField {...props} />)));
const { container } = render(routerWrapper(reduxWrapper(<UsernameField {...props} />)));
const usernameSuggestion = container.querySelector('.username-suggestions--chip');
fireEvent.click(usernameSuggestion);
expect(props.handleChange).toHaveBeenCalledTimes(1);
@@ -277,7 +275,7 @@ describe('UsernameField', () => {
value: ' ',
};
const { container } = render(routerWrapper(reduxWrapper(<IntlUsernameField {...props} />)));
const { container } = render(routerWrapper(reduxWrapper(<UsernameField {...props} />)));
let closeButton = container.querySelector('button.username-suggestions__close__button');
fireEvent.click(closeButton);
expect(store.dispatch).toHaveBeenCalledWith(clearUsernameSuggestions());
@@ -287,7 +285,7 @@ describe('UsernameField', () => {
errorMessage: 'username error',
};
render(routerWrapper(reduxWrapper(<IntlUsernameField {...props} />)));
render(routerWrapper(reduxWrapper(<UsernameField {...props} />)));
closeButton = container.querySelector('button.username-suggestions__close__button');
fireEvent.click(closeButton);
expect(store.dispatch).toHaveBeenCalledWith(clearUsernameSuggestions());
@@ -309,7 +307,7 @@ describe('UsernameField', () => {
store.dispatch = jest.fn(store.dispatch);
const { container } = render(routerWrapper(reduxWrapper(<IntlUsernameField {...props} />)));
const { container } = render(routerWrapper(reduxWrapper(<UsernameField {...props} />)));
const usernameField = container.querySelector('input#username');
fireEvent.focus(usernameField, { target: { value: 'test', name: 'username' } });

View File

@@ -1,10 +1,9 @@
import React from 'react';
import { Provider } from 'react-redux';
import { getConfig, mergeConfig } from '@edx/frontend-platform';
import { sendPageEvent, sendTrackEvent } from '@edx/frontend-platform/analytics';
import {
configure, getLocale, injectIntl, IntlProvider,
configure, getLocale, IntlProvider,
} from '@edx/frontend-platform/i18n';
import { fireEvent, render } from '@testing-library/react';
import { mockNavigate, BrowserRouter as Router } from 'react-router-dom';
@@ -31,7 +30,6 @@ jest.mock('@edx/frontend-platform/i18n', () => ({
getLocale: jest.fn(),
}));
const IntlRegistrationPage = injectIntl(RegistrationPage);
const mockStore = configureStore();
jest.mock('react-router-dom', () => {
@@ -188,7 +186,7 @@ describe('RegistrationPage', () => {
};
store.dispatch = jest.fn(store.dispatch);
const { getByLabelText, container } = render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
const { getByLabelText, container } = render(routerWrapper(reduxWrapper(<RegistrationPage {...props} />)));
populateRequiredFields(getByLabelText, payload);
const button = container.querySelector('button.btn-brand');
fireEvent.click(button);
@@ -220,7 +218,7 @@ describe('RegistrationPage', () => {
},
});
store.dispatch = jest.fn(store.dispatch);
const { getByLabelText, container } = render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
const { getByLabelText, container } = render(routerWrapper(reduxWrapper(<RegistrationPage {...props} />)));
populateRequiredFields(getByLabelText, formPayload, true);
const button = container.querySelector('button.btn-brand');
@@ -243,7 +241,7 @@ describe('RegistrationPage', () => {
};
store.dispatch = jest.fn(store.dispatch);
const { getByLabelText, container } = render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
const { getByLabelText, container } = render(routerWrapper(reduxWrapper(<RegistrationPage {...props} />)));
populateRequiredFields(getByLabelText, formPayload, true);
const button = container.querySelector('button.btn-brand');
@@ -268,7 +266,7 @@ describe('RegistrationPage', () => {
};
store.dispatch = jest.fn(store.dispatch);
const { getByLabelText, container } = render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
const { getByLabelText, container } = render(routerWrapper(reduxWrapper(<RegistrationPage {...props} />)));
populateRequiredFields(getByLabelText, formPayload, true);
const button = container.querySelector('button.btn-brand');
fireEvent.click(button);
@@ -295,7 +293,7 @@ describe('RegistrationPage', () => {
};
store.dispatch = jest.fn(store.dispatch);
const { getByLabelText, container } = render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
const { getByLabelText, container } = render(routerWrapper(reduxWrapper(<RegistrationPage {...props} />)));
populateRequiredFields(getByLabelText, payload);
const button = container.querySelector('button.btn-brand');
fireEvent.click(button);
@@ -321,7 +319,7 @@ describe('RegistrationPage', () => {
};
store.dispatch = jest.fn(store.dispatch);
const { getByLabelText, container } = render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
const { getByLabelText, container } = render(routerWrapper(reduxWrapper(<RegistrationPage {...props} />)));
populateRequiredFields(getByLabelText, payload, false, true);
const button = container.querySelector('button.btn-brand');
fireEvent.click(button);
@@ -336,7 +334,7 @@ describe('RegistrationPage', () => {
ENABLE_AUTO_GENERATED_USERNAME: true,
});
const { queryByLabelText } = render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
const { queryByLabelText } = render(routerWrapper(reduxWrapper(<RegistrationPage {...props} />)));
expect(queryByLabelText('Username')).toBeNull();
mergeConfig({
@@ -347,7 +345,7 @@ describe('RegistrationPage', () => {
it('should not dispatch registerNewUser on empty form Submission', () => {
store.dispatch = jest.fn(store.dispatch);
const { container } = render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
const { container } = render(routerWrapper(reduxWrapper(<RegistrationPage {...props} />)));
const button = container.querySelector('button.btn-brand');
fireEvent.click(button);
@@ -358,7 +356,7 @@ describe('RegistrationPage', () => {
// ******** test registration form validations ********
it('should show error messages for required fields on empty form submission', () => {
const { container } = render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
const { container } = render(routerWrapper(reduxWrapper(<RegistrationPage {...props} />)));
const button = container.querySelector('button.btn-brand');
fireEvent.click(button);
@@ -386,7 +384,7 @@ describe('RegistrationPage', () => {
},
},
});
const { container } = render(routerWrapper(reduxWrapper(<IntlProvider locale="en"><IntlRegistrationPage {...props} /></IntlProvider>)));
const { container } = render(routerWrapper(reduxWrapper(<IntlProvider locale="en"><RegistrationPage {...props} /></IntlProvider>)));
const usernameFeedback = container.querySelector('div[feedback-for="username"]');
const emailFeedback = container.querySelector('div[feedback-for="email"]');
@@ -395,7 +393,7 @@ describe('RegistrationPage', () => {
});
it('should clear error on focus', () => {
const { container } = render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
const { container } = render(routerWrapper(reduxWrapper(<RegistrationPage {...props} />)));
const submitButton = container.querySelector('button.btn-brand');
fireEvent.click(submitButton);
@@ -424,7 +422,7 @@ describe('RegistrationPage', () => {
store.dispatch = jest.fn(store.dispatch);
const { container } = render(routerWrapper(reduxWrapper(
<IntlRegistrationPage {...props} />,
<RegistrationPage {...props} />,
)));
const emailInput = container.querySelector('input#email');
@@ -435,7 +433,7 @@ describe('RegistrationPage', () => {
// ******** test form buttons and fields ********
it('should match default button state', () => {
const { container } = render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
const { container } = render(routerWrapper(reduxWrapper(<RegistrationPage {...props} />)));
const button = container.querySelector('button[type="submit"] span');
expect(button.textContent).toEqual('Create an account for free');
});
@@ -449,7 +447,7 @@ describe('RegistrationPage', () => {
},
});
const { container } = render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
const { container } = render(routerWrapper(reduxWrapper(<RegistrationPage {...props} />)));
const button = container.querySelector('button[type="submit"] span.sr-only');
expect(button.textContent).toEqual('pending');
@@ -460,7 +458,7 @@ describe('RegistrationPage', () => {
MARKETING_EMAILS_OPT_IN: 'true',
});
const { container } = render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
const { container } = render(routerWrapper(reduxWrapper(<RegistrationPage {...props} />)));
const checkboxDivs = container.querySelectorAll('div.form-field--checkbox');
expect(checkboxDivs.length).toEqual(1);
@@ -473,7 +471,7 @@ describe('RegistrationPage', () => {
const buttonLabel = 'Register';
delete window.location;
window.location = { href: getConfig().BASE_URL, search: `?cta=${buttonLabel}` };
const { container } = render(reduxWrapper(<IntlRegistrationPage {...props} />));
const { container } = render(reduxWrapper(<RegistrationPage {...props} />));
const button = container.querySelector('button[type="submit"] span');
const buttonText = button.textContent;
@@ -492,7 +490,7 @@ describe('RegistrationPage', () => {
},
});
render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
render(routerWrapper(reduxWrapper(<RegistrationPage {...props} />)));
expect(document.cookie).toMatch(`${getConfig().USER_RETENTION_COOKIE_NAME}=true`);
});
@@ -510,7 +508,7 @@ describe('RegistrationPage', () => {
});
delete window.location;
window.location = { href: getConfig().BASE_URL };
render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
render(routerWrapper(reduxWrapper(<RegistrationPage {...props} />)));
expect(window.location.href).toBe(dashboardURL);
});
@@ -537,7 +535,7 @@ describe('RegistrationPage', () => {
});
delete window.location;
window.location = { href: getConfig().BASE_URL };
render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
render(routerWrapper(reduxWrapper(<RegistrationPage {...props} />)));
expect(window.location.href).toBe(dashboardUrl);
});
@@ -568,7 +566,7 @@ describe('RegistrationPage', () => {
render(reduxWrapper(
<Router>
<IntlRegistrationPage {...props} />
<RegistrationPage {...props} />
</Router>,
));
expect(mockNavigate).toHaveBeenCalledWith(AUTHN_PROGRESSIVE_PROFILING);
@@ -586,12 +584,12 @@ describe('RegistrationPage', () => {
});
store.dispatch = jest.fn(store.dispatch);
render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
render(routerWrapper(reduxWrapper(<RegistrationPage {...props} />)));
expect(store.dispatch).toHaveBeenCalledWith(backupRegistrationFormBegin({ ...registrationFormData }));
});
it('should send page event when register page is rendered', () => {
render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
render(routerWrapper(reduxWrapper(<RegistrationPage {...props} />)));
expect(sendPageEvent).toHaveBeenCalledWith('login_and_registration', 'register');
});
@@ -609,7 +607,7 @@ describe('RegistrationPage', () => {
delete window.location;
window.location = { href: getConfig().BASE_URL };
render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
render(routerWrapper(reduxWrapper(<RegistrationPage {...props} />)));
expect(sendTrackEvent).toHaveBeenCalledWith('edx.bi.user.account.registered.client', {});
});
@@ -635,7 +633,7 @@ describe('RegistrationPage', () => {
store.dispatch = jest.fn(store.dispatch);
const { container } = render(reduxWrapper(
<Router>
<IntlRegistrationPage {...props} />
<RegistrationPage {...props} />
</Router>,
));
@@ -658,7 +656,7 @@ describe('RegistrationPage', () => {
},
});
const { container } = render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
const { container } = render(routerWrapper(reduxWrapper(<RegistrationPage {...props} />)));
const validationErrors = container.querySelector('div#validation-errors');
expect(validationErrors.textContent).toContain(
'An error has occurred. Try refreshing the page, or check your internet connection.',
@@ -685,7 +683,7 @@ describe('RegistrationPage', () => {
},
});
const { container } = render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
const { container } = render(routerWrapper(reduxWrapper(<RegistrationPage {...props} />)));
const fullNameInput = container.querySelector('input#name');
const usernameInput = container.querySelector('input#username');
@@ -731,14 +729,14 @@ describe('RegistrationPage', () => {
},
},
});
render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
render(routerWrapper(reduxWrapper(<RegistrationPage {...props} />)));
expect(window.parent.postMessage).toHaveBeenCalledTimes(2);
});
it('should not display validations error on blur event when embedded variant is rendered', () => {
delete window.location;
window.location = { href: getConfig().BASE_URL.concat(REGISTER_PAGE), search: '?host=http://localhost/host-website' };
const { container } = render(reduxWrapper(<IntlRegistrationPage {...props} />));
const { container } = render(reduxWrapper(<RegistrationPage {...props} />));
const usernameInput = container.querySelector('input#username');
fireEvent.blur(usernameInput, { target: { value: '', name: 'username' } });
@@ -766,7 +764,7 @@ describe('RegistrationPage', () => {
},
});
const { container } = render(routerWrapper(reduxWrapper(
<IntlRegistrationPage {...props} />),
<RegistrationPage {...props} />),
));
const usernameFeedback = container.querySelector('div[feedback-for="username"]');
@@ -783,7 +781,7 @@ describe('RegistrationPage', () => {
search: '?host=http://localhost/host-website',
};
const { container } = render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
const { container } = render(routerWrapper(reduxWrapper(<RegistrationPage {...props} />)));
const submitButton = container.querySelector('button.btn-brand');
fireEvent.click(submitButton);
@@ -824,7 +822,7 @@ describe('RegistrationPage', () => {
});
store.dispatch = jest.fn(store.dispatch);
const { container } = render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
const { container } = render(routerWrapper(reduxWrapper(<RegistrationPage {...props} />)));
const spinnerElement = container.querySelector('#tpa-spinner');
const registrationFormElement = container.querySelector('#registration-form');
@@ -875,7 +873,7 @@ describe('RegistrationPage', () => {
});
store.dispatch = jest.fn(store.dispatch);
render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
render(routerWrapper(reduxWrapper(<RegistrationPage {...props} />)));
expect(store.dispatch).toHaveBeenCalledWith(registerNewUser({
name: 'John Doe',
username: 'john_doe',

View File

@@ -9,6 +9,7 @@ import PropTypes from 'prop-types';
import { windowScrollTo } from '../../data/utils';
import {
FORBIDDEN_REQUEST,
FORBIDDEN_USERNAME,
INTERNAL_SERVER_ERROR,
TPA_AUTHENTICATION_FAILURE,
TPA_SESSION_EXPIRED,
@@ -48,6 +49,9 @@ const RegistrationFailureMessage = (props) => {
case TPA_SESSION_EXPIRED:
errorMessage = formatMessage(messages['registration.tpa.session.expired'], { provider: context.provider });
break;
case FORBIDDEN_USERNAME:
errorMessage = formatMessage(messages['registration.forbidden.username']);
break;
default:
errorMessage = formatMessage(messages['registration.empty.form.submission.error']);
break;

View File

@@ -1,9 +1,8 @@
import React from 'react';
import { Provider } from 'react-redux';
import { mergeConfig } from '@edx/frontend-platform';
import {
getLocale, injectIntl, IntlProvider,
getLocale, IntlProvider,
} from '@edx/frontend-platform/i18n';
import { fireEvent, render } from '@testing-library/react';
import { BrowserRouter as Router } from 'react-router-dom';
@@ -23,8 +22,6 @@ jest.mock('@edx/frontend-platform/i18n', () => ({
getLocale: jest.fn(),
}));
const IntlConfigurableRegistrationForm = injectIntl(ConfigurableRegistrationForm);
const IntlRegistrationPage = injectIntl(RegistrationPage);
const mockStore = configureStore();
jest.mock('react-router-dom', () => {
@@ -158,7 +155,7 @@ describe('ConfigurableRegistrationForm', () => {
};
render(routerWrapper(reduxWrapper(
<IntlConfigurableRegistrationForm {...props} />,
<ConfigurableRegistrationForm {...props} />,
)));
expect(document.querySelector('#profession')).toBeTruthy();
@@ -188,7 +185,7 @@ describe('ConfigurableRegistrationForm', () => {
};
render(routerWrapper(reduxWrapper(
<IntlConfigurableRegistrationForm {...props} />,
<ConfigurableRegistrationForm {...props} />,
)));
expect(props.setFormFields).toHaveBeenCalledTimes(2);
@@ -215,7 +212,7 @@ describe('ConfigurableRegistrationForm', () => {
},
},
});
render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
render(routerWrapper(reduxWrapper(<RegistrationPage {...props} />)));
expect(document.querySelector('#profession')).toBeTruthy();
expect(document.querySelector('#tos')).toBeTruthy();
});
@@ -249,7 +246,7 @@ describe('ConfigurableRegistrationForm', () => {
};
store.dispatch = jest.fn(store.dispatch);
const { getByLabelText, container } = render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
const { getByLabelText, container } = render(routerWrapper(reduxWrapper(<RegistrationPage {...props} />)));
populateRequiredFields(getByLabelText, payload);
@@ -284,7 +281,7 @@ describe('ConfigurableRegistrationForm', () => {
},
});
const { container } = render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
const { container } = render(routerWrapper(reduxWrapper(<RegistrationPage {...props} />)));
const submitButton = container.querySelector('button.btn-brand');
fireEvent.click(submitButton);
@@ -310,7 +307,7 @@ describe('ConfigurableRegistrationForm', () => {
},
},
});
const { container } = render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
const { container } = render(routerWrapper(reduxWrapper(<RegistrationPage {...props} />)));
const countryInput = container.querySelector('input[name="country"]');
fireEvent.change(countryInput, { target: { value: 'Pak', name: 'country' } });
fireEvent.blur(countryInput, { target: { value: 'Pak', name: 'country' } });
@@ -335,7 +332,7 @@ describe('ConfigurableRegistrationForm', () => {
},
},
});
const { getByLabelText, container } = render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
const { getByLabelText, container } = render(routerWrapper(reduxWrapper(<RegistrationPage {...props} />)));
const emailInput = getByLabelText('Email');
const confirmEmailInput = getByLabelText('Confirm Email');
@@ -371,7 +368,7 @@ describe('ConfigurableRegistrationForm', () => {
},
},
});
const { getByLabelText, container } = render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
const { getByLabelText, container } = render(routerWrapper(reduxWrapper(<RegistrationPage {...props} />)));
populateRequiredFields(getByLabelText, formPayload, true);
fireEvent.change(
@@ -406,7 +403,7 @@ describe('ConfigurableRegistrationForm', () => {
});
const { getByLabelText, container } = render(
routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)),
routerWrapper(reduxWrapper(<RegistrationPage {...props} />)),
);
const professionInput = getByLabelText('Profession');

View File

@@ -1,9 +1,8 @@
import React from 'react';
import { Provider } from 'react-redux';
import { mergeConfig } from '@edx/frontend-platform';
import {
configure, getLocale, injectIntl, IntlProvider,
configure, getLocale, IntlProvider,
} from '@edx/frontend-platform/i18n';
import { render, screen } from '@testing-library/react';
import { BrowserRouter as Router } from 'react-router-dom';
@@ -24,8 +23,6 @@ jest.mock('@edx/frontend-platform/i18n', () => ({
getLocale: jest.fn(),
}));
const IntlRegistrationPage = injectIntl(RegistrationPage);
const IntlRegistrationFailure = injectIntl(RegistrationFailureMessage);
const mockStore = configureStore();
jest.mock('react-router-dom', () => {
@@ -137,7 +134,7 @@ describe('RegistrationFailure', () => {
failureCount: 0,
};
const { container } = render(reduxWrapper(<IntlRegistrationFailure {...props} />));
const { container } = render(reduxWrapper(<RegistrationFailureMessage {...props} />));
const alertHeading = container.querySelectorAll('div.alert-heading');
expect(alertHeading.length).toEqual(1);
@@ -153,7 +150,7 @@ describe('RegistrationFailure', () => {
failureCount: 0,
};
const { container } = render(reduxWrapper(<IntlRegistrationFailure {...props} />));
const { container } = render(reduxWrapper(<RegistrationFailureMessage {...props} />));
const alertHeading = container.querySelectorAll('div.alert-heading');
expect(alertHeading.length).toEqual(1);
@@ -172,7 +169,7 @@ describe('RegistrationFailure', () => {
failureCount: 0,
};
const { container } = render(reduxWrapper(<IntlRegistrationFailure {...props} />));
const { container } = render(reduxWrapper(<RegistrationFailureMessage {...props} />));
const alertHeading = container.querySelectorAll('div.alert-heading');
expect(alertHeading.length).toEqual(1);
@@ -191,7 +188,7 @@ describe('RegistrationFailure', () => {
failureCount: 0,
};
const { container } = render(reduxWrapper(<IntlRegistrationFailure {...props} />));
const { container } = render(reduxWrapper(<RegistrationFailureMessage {...props} />));
const alertHeading = container.querySelectorAll('div.alert-heading');
expect(alertHeading.length).toEqual(1);
@@ -211,7 +208,7 @@ describe('RegistrationFailure', () => {
},
});
render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
render(routerWrapper(reduxWrapper(<RegistrationPage {...props} />)));
const validationError = screen.queryByText('An error has occurred. Try refreshing the page, or check your internet connection.');
expect(validationError).not.toBeNull();

View File

@@ -1,9 +1,8 @@
import React from 'react';
import { Provider } from 'react-redux';
import { getConfig, mergeConfig } from '@edx/frontend-platform';
import {
configure, getLocale, injectIntl, IntlProvider,
configure, getLocale, IntlProvider,
} from '@edx/frontend-platform/i18n';
import { fireEvent, render } from '@testing-library/react';
import { BrowserRouter as Router } from 'react-router-dom';
@@ -23,7 +22,6 @@ jest.mock('@edx/frontend-platform/i18n', () => ({
getLocale: jest.fn(),
}));
const IntlRegistrationPage = injectIntl(RegistrationPage);
const mockStore = configureStore();
jest.mock('react-router-dom', () => {
@@ -157,7 +155,7 @@ describe('ThirdPartyAuth', () => {
});
const { queryByLabelText } = render(
routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />, { store })),
routerWrapper(reduxWrapper(<RegistrationPage {...props} />, { store })),
);
const passwordField = queryByLabelText('Password');
@@ -182,7 +180,7 @@ describe('ThirdPartyAuth', () => {
window.location = { href: getConfig().BASE_URL.concat(LOGIN_PAGE), search: `?next=/dashboard&tpa_hint=${ssoProvider.id}` };
const { container } = render(
routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)),
routerWrapper(reduxWrapper(<RegistrationPage {...props} />)),
);
const tpaButton = container.querySelector(`button#${ssoProvider.id}`);
@@ -207,7 +205,7 @@ describe('ThirdPartyAuth', () => {
search: `?next=/dashboard&tpa_hint=${ssoProvider.id}`,
};
const { container } = render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
const { container } = render(routerWrapper(reduxWrapper(<RegistrationPage {...props} />)));
const skeletonElement = container.querySelector('.react-loading-skeleton');
expect(skeletonElement).toBeTruthy();
@@ -231,7 +229,7 @@ describe('ThirdPartyAuth', () => {
window.location = { href: getConfig().BASE_URL.concat(REGISTER_PAGE), search: `?next=/dashboard&tpa_hint=${ssoProvider.id}` };
ssoProvider.iconImage = null;
const { container } = render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
const { container } = render(routerWrapper(reduxWrapper(<RegistrationPage {...props} />)));
const iconElement = container.querySelector(`button#${ssoProvider.id} div span.pgn__icon`);
expect(iconElement).toBeTruthy();
@@ -254,7 +252,7 @@ describe('ThirdPartyAuth', () => {
delete window.location;
window.location = { href: getConfig().BASE_URL.concat(REGISTER_PAGE), search: `?next=/dashboard&tpa_hint=${secondaryProviders.id}` };
render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
render(routerWrapper(reduxWrapper(<RegistrationPage {...props} />)));
expect(window.location.href).toEqual(getConfig().LMS_BASE_URL + secondaryProviders.registerUrl);
});
@@ -275,7 +273,7 @@ describe('ThirdPartyAuth', () => {
delete window.location;
window.location = { href: getConfig().BASE_URL.concat(LOGIN_PAGE), search: '?next=/dashboard&tpa_hint=invalid' };
const { container } = render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
const { container } = render(routerWrapper(reduxWrapper(<RegistrationPage {...props} />)));
const providerButton = container.querySelector(`button#${ssoProvider.id} span#provider-name`);
expect(providerButton.textContent).toEqual(expectedMessage);
@@ -294,7 +292,7 @@ describe('ThirdPartyAuth', () => {
});
const { container } = render(
routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />, { store })),
routerWrapper(reduxWrapper(<RegistrationPage {...props} />, { store })),
);
const buttonsWithId = container.querySelectorAll(`button#${ssoProvider.id}`);
@@ -315,7 +313,7 @@ describe('ThirdPartyAuth', () => {
});
const { container } = render(
routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)),
routerWrapper(reduxWrapper(<RegistrationPage {...props} />)),
);
const buttonsWithId = container.querySelectorAll(`button#${ssoProvider.id}`);
@@ -329,7 +327,7 @@ describe('ThirdPartyAuth', () => {
institutionLogin: true,
};
const { getByText } = render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
const { getByText } = render(routerWrapper(reduxWrapper(<RegistrationPage {...props} />)));
const headingElement = getByText('Register with institution/campus credentials');
expect(headingElement).toBeTruthy();
});
@@ -354,7 +352,7 @@ describe('ThirdPartyAuth', () => {
window.location = { href: getConfig().BASE_URL };
const { container } = render(
routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)),
routerWrapper(reduxWrapper(<RegistrationPage {...props} />)),
);
const ssoButton = container.querySelector('button#oa2-apple-id');
@@ -385,7 +383,7 @@ describe('ThirdPartyAuth', () => {
delete window.location;
window.location = { href: getConfig().BASE_URL };
render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
render(routerWrapper(reduxWrapper(<RegistrationPage {...props} />)));
expect(window.location.href).toBe(getConfig().LMS_BASE_URL + authCompleteUrl);
});
@@ -406,7 +404,7 @@ describe('ThirdPartyAuth', () => {
const expectedMessage = `${'You\'ve successfully signed into Apple! We just need a little more information before '
+ 'you start learning with '}${ getConfig().SITE_NAME }.`;
const { container } = render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
const { container } = render(routerWrapper(reduxWrapper(<RegistrationPage {...props} />)));
const tpaAlert = container.querySelector('#tpa-alert p');
expect(tpaAlert.textContent).toEqual(expectedMessage);
});
@@ -437,7 +435,7 @@ describe('ThirdPartyAuth', () => {
store.dispatch = jest.fn(store.dispatch);
const { container } = render(
routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)),
routerWrapper(reduxWrapper(<RegistrationPage {...props} />)),
);
const alertHeading = container.querySelector('div.alert-heading');

View File

@@ -11,3 +11,4 @@ export const FORM_SUBMISSION_ERROR = 'form-submission-error';
export const INTERNAL_SERVER_ERROR = 'internal-server-error';
export const TPA_AUTHENTICATION_FAILURE = 'tpa-authentication-failure';
export const TPA_SESSION_EXPIRED = 'tpa-session-expired';
export const FORBIDDEN_USERNAME = 'forbidden-username';

View File

@@ -1,11 +1,14 @@
import { camelCaseObject } from '@edx/frontend-platform';
import { logError, logInfo } from '@edx/frontend-platform/logging';
import { call, put, takeEvery } from 'redux-saga/effects';
import {
call, put, race, take, takeEvery,
} from 'redux-saga/effects';
import {
fetchRealtimeValidationsBegin,
fetchRealtimeValidationsFailure,
fetchRealtimeValidationsSuccess,
REGISTER_CLEAR_USERNAME_SUGGESTIONS,
REGISTER_FORM_VALIDATIONS,
REGISTER_NEW_USER,
registerNewUserBegin,
@@ -41,9 +44,15 @@ export function* handleNewUserRegistration(action) {
export function* fetchRealtimeValidations(action) {
try {
yield put(fetchRealtimeValidationsBegin());
const { fieldValidations } = yield call(getFieldsValidations, action.payload.formPayload);
yield put(fetchRealtimeValidationsSuccess(camelCaseObject(fieldValidations)));
const { response } = yield race({
response: call(getFieldsValidations, action.payload.formPayload),
cancel: take(REGISTER_CLEAR_USERNAME_SUGGESTIONS),
});
if (response) {
yield put(fetchRealtimeValidationsSuccess(camelCaseObject(response.fieldValidations)));
}
} catch (e) {
if (e.response && e.response.status === 403) {
yield put(fetchRealtimeValidationsFailure());

View File

@@ -162,6 +162,11 @@ const messages = defineMessages({
defaultMessage: 'Registration using {provider} has timed out.',
description: '',
},
'registration.forbidden.username': {
id: 'registration.forbidden.username',
defaultMessage: 'Usernames can\'t include words that could be mistaken for course roles. Please choose a different username.',
description: '',
},
'registration.tpa.authentication.failure': {
id: 'registration.tpa.authentication.failure',
defaultMessage: 'We are sorry, you are not authorized to access {platform_name} via this channel. '

View File

@@ -1,7 +1,6 @@
import React from 'react';
import { Provider } from 'react-redux';
import { configure, injectIntl, IntlProvider } from '@edx/frontend-platform/i18n';
import { configure, IntlProvider } from '@edx/frontend-platform/i18n';
import {
fireEvent, render, screen,
} from '@testing-library/react';
@@ -26,7 +25,6 @@ jest.mock('react-router-dom', () => ({
useParams: jest.fn().mockReturnValue({ token }),
}));
const IntlResetPasswordPage = injectIntl(ResetPasswordPage);
const mockStore = configureStore();
describe('ResetPasswordPage', () => {
@@ -95,7 +93,7 @@ describe('ResetPasswordPage', () => {
}));
store.dispatch = jest.fn(store.dispatch);
render(reduxWrapper(<IntlResetPasswordPage {...props} />));
render(reduxWrapper(<ResetPasswordPage {...props} />));
const newPasswordInput = screen.getByLabelText('New password');
const confirmPasswordInput = screen.getByLabelText('Confirm password');
@@ -120,7 +118,7 @@ describe('ResetPasswordPage', () => {
status: TOKEN_STATE.VALID,
},
});
render(reduxWrapper(<IntlResetPasswordPage {...props} />));
render(reduxWrapper(<ResetPasswordPage {...props} />));
const resetPasswordButton = screen.getByRole('button', { name: /Reset password/i, id: 'submit-new-password' });
fireEvent.click(resetPasswordButton);
@@ -144,7 +142,7 @@ describe('ResetPasswordPage', () => {
status: TOKEN_STATE.VALID,
},
});
render(reduxWrapper(<IntlResetPasswordPage {...props} />));
render(reduxWrapper(<ResetPasswordPage {...props} />));
const confirmPasswordInput = screen.getByLabelText('Confirm password');
fireEvent.change(confirmPasswordInput, { target: { value: 'password-mismatch' } });
@@ -163,7 +161,7 @@ describe('ResetPasswordPage', () => {
},
});
const { container } = render(reduxWrapper(<IntlResetPasswordPage {...props} />));
const { container } = render(reduxWrapper(<ResetPasswordPage {...props} />));
const alertElements = container.querySelectorAll('.alert-danger');
const rateLimitError = alertElements[0].textContent;
@@ -179,7 +177,7 @@ describe('ResetPasswordPage', () => {
},
});
const { container } = render(reduxWrapper(<IntlResetPasswordPage {...props} />));
const { container } = render(reduxWrapper(<ResetPasswordPage {...props} />));
const alertElements = container.querySelectorAll('.alert-danger');
const internalServerError = alertElements[0].textContent;
expect(internalServerError).toBe(validationMessage);
@@ -188,7 +186,7 @@ describe('ResetPasswordPage', () => {
// ******** miscellaneous tests ********
it('should call validation on password field when blur event fires', () => {
const resetPasswordPage = render(reduxWrapper(<IntlResetPasswordPage {...props} />));
const resetPasswordPage = render(reduxWrapper(<ResetPasswordPage {...props} />));
const expectedText = 'Password criteria has not been metPassword must contain at least 8 characters, at least one letter, and at least one number';
const newPasswordInput = resetPasswordPage.container.querySelector('input#newPassword');
newPasswordInput.value = 'test-password';
@@ -207,7 +205,7 @@ describe('ResetPasswordPage', () => {
TOKEN_STATE.PENDING,
};
render(reduxWrapper(<IntlResetPasswordPage {...props} />));
render(reduxWrapper(<ResetPasswordPage {...props} />));
expect(store.dispatch).toHaveBeenCalledWith(validateToken(token));
});
@@ -216,19 +214,19 @@ describe('ResetPasswordPage', () => {
status:
PASSWORD_RESET_ERROR,
};
render(reduxWrapper(<IntlResetPasswordPage {...props} />));
render(reduxWrapper(<ResetPasswordPage {...props} />));
expect(mockedNavigator).toHaveBeenCalledWith(RESET_PAGE);
});
it('should redirect the user to root url of the application ', async () => {
props = {
status: SUCCESS,
};
render(reduxWrapper(<IntlResetPasswordPage {...props} />));
render(reduxWrapper(<ResetPasswordPage {...props} />));
expect(mockedNavigator).toHaveBeenCalledWith(LOGIN_PAGE);
});
it('shows spinner during token validation', () => {
render(reduxWrapper(<IntlResetPasswordPage {...props} />));
render(reduxWrapper(<ResetPasswordPage {...props} />));
const spinnerElement = document.getElementsByClassName('div.spinner-header');
expect(spinnerElement).toBeTruthy();
@@ -237,7 +235,7 @@ describe('ResetPasswordPage', () => {
// ******** redirection tests ********
it('by clicking on sign in tab should redirect onto login page', async () => {
const { getByText } = render(reduxWrapper(<IntlResetPasswordPage {...props} />));
const { getByText } = render(reduxWrapper(<ResetPasswordPage {...props} />));
const signInTab = getByText('Sign in');

View File

@@ -2,19 +2,19 @@
.layout {
display: flex;
@include media-breakpoint-down('lg') {
@media (--pgn-size-breakpoint-max-width-lg) {
flex-direction: column;
justify-content: center;
align-items: center;
}
@include media-breakpoint-up('xl') {
@media (--pgn-size-breakpoint-min-width-xl) {
justify-content: space-between;
}
}
.content {
@include media-breakpoint-up('xl') {
@media (--pgn-size-breakpoint-min-width-xl) {
display: flex;
justify-content: center;
width: 50vw;
@@ -47,7 +47,7 @@
font-weight: 700;
line-height: 1;
@include media-breakpoint-down('xl') {
@media (--pgn-size-breakpoint-max-width-xl) {
font-size: 3.75rem;
}
@@ -60,7 +60,7 @@
margin-bottom: 0.5rem;
font-weight: 700;
@include media-breakpoint-down('xl') {
@media (-pgn-size-breakpoint-max-width-xl) {
font-size: 1.375rem;
line-height: 1.75rem;
}
@@ -72,7 +72,7 @@
}
.large-screen-left-container {
@include media-breakpoint-down('xl') {
@media (-pgn-size-breakpoint-max-width-xl) {
flex: 0 0 25%;
max-width: 25%;
}
@@ -87,43 +87,43 @@
height: 0.25rem;
background-image: linear-gradient(
102.02deg,
$brand-700,
$brand-700 20%,
$brand 20%,
var(--pgn-color-brand-700),
var(--pgn-color-brand-700) 20%,
var(--pgn-color-brand-base) 20%,
);
background-repeat: no-repeat;
}
@include media-breakpoint-only('md') {
@media (--pgn-size-breakpoint-min-width-md) and (--pgn-size-breakpoint-max-width-md) {
.medium-screen-top-stripe {
display: flex;
height: 0.5rem;
background-image: linear-gradient(
102.02deg,
$brand-700,
$brand-700 10%,
$brand 10%,
$brand 90%,
$primary-700 90%,
$primary-700 100%,
var(--pgn-color-brand-700),
var(--pgn-color-brand-700) 10%,
var(--pgn-color-brand-base) 10%,
var(--pgn-color-brand-base) 90%,
var(--pgn-color-primary-700) 90%,
var(--pgn-color-primary-700) 100%,
);
background-repeat: no-repeat;
}
}
@include media-breakpoint-only('lg') {
@media (--pgn-size-breakpoint-min-width-lg) and (--pgn-size-breakpoint-max-width-lg){
.medium-screen-top-stripe {
display: flex;
height: 0.5rem;
background-image: linear-gradient(
102.02deg,
$brand-700 10%,
$brand 10%,
$brand 65%,
$primary-700 65%,
$primary-700 75%,
$accent-a 75%,
$accent-a 75%
var(--pgn-color-brand-700) 10%,
var(--pgn-color-brand-base) 10%,
var(--pgn-color-brand-base) 65%,
var(--pgn-color-primary-700) 65%,
var(--pgn-color-primary-700) 75%,
var(--pgn-color-accent-a) 75%,
var(--pgn-color-accent-a) 75%
);
background-repeat: no-repeat;
}
@@ -131,20 +131,20 @@
.extra-large-screen-top-stripe { display: none; }
@include media-breakpoint-up('xl') {
@media (--pgn-size-breakpoint-min-width-xl) {
.extra-large-screen-top-stripe {
display: flex;
height: 0.5rem;
background-image: linear-gradient(
102.02deg,
$brand-700 10%,
$brand 10%,
$brand 45%,
$primary-700 45%,
$primary-700 55%,
$accent-a 55%,
$accent-a 75%,
$info-200 75%,
var(--pgn-color-brand-700) 10%,
var(--pgn-color-brand-base) 10%,
var(--pgn-color-brand-base) 45%,
var(--pgn-color-primary-700) 45%,
var(--pgn-color-primary-700) 55%,
var(--pgn-color-accent-a) 55%,
var(--pgn-color-accent-a) 75%,
var(--pgn-color-info-200) 75%,
);
background-repeat: no-repeat;
}
@@ -152,24 +152,24 @@
.large-screen-svg-light,
.large-screen-svg-primary {
fill: $light-200;
fill: var(--pgn-color-light-200);
overflow: hidden;
position: absolute;
}
.large-screen-svg-primary {
fill: $primary-400;
fill: var(--pgn-color-primary-400);
}
.medium-screen-svg-light,
.medium-screen-svg-primary {
fill: $light-200;
fill: var(--pgn-color-light-200);
overflow: inherit;
position: absolute;
}
.medium-screen-svg-primary {
fill: $primary-400;
fill: var(--pgn-color-primary-400);
}
[dir=rtl]{
@@ -184,20 +184,20 @@
.small-yellow-line {
width: 80px;
height: 0;
border: 2px solid $accent-b;
border: 2px solid var(--pgn-color-accent-b);
transform: rotate(102.02deg);
}
.medium-yellow-line {
width: 120px;
height: 0;
border: 3px solid $accent-b;
border: 3px solid var(--pgn-color-accent-b);
transform: rotate(102.02deg);
}
.large-yellow-line {
width: 240px;
height: 0;
border: 3px solid $accent-b;
border: 3px solid var(--pgn-color-accent-b);
transform: rotate(102.02deg);
}

View File

@@ -11,7 +11,7 @@
margin-bottom: 0.5rem;
font-weight: 700;
@include media-breakpoint-down('md') {
@media (--pgn-size-breakpoint-max-width-md) {
line-height: 1.5rem;
font-size: 1.125rem;
}

View File

@@ -64,52 +64,52 @@ $header-height: 104px;
}
&.light {
background-color: $white;
background-color: var(--pgn-color-white);
.title {
color: $black;
color: var(--pgn-color-black);
}
.subtitle {
color: $gray-700;
color: var(--pgn-color-gray-700);
}
.badge {
background-color: $light-500;
color: $black;
background-color: var(--pgn-color-light-500);
color: var(--pgn-color-black);
}
.footer-content {
color: $gray-700;
color: var(--pgn-color-gray-700);
}
}
&.dark {
background-color: $primary-500;
background-color: var(--pgn-color-primary-500);
.pgn__card-header-title-md {
color: $white;
color: var(--pgn-color-white);
}
.pgn__card-header-subtitle-md {
color: $light-200;
color: var(--pgn-color-light-200);
}
.title {
color: $white;
color: var(--pgn-color-white);
}
.subtitle {
color: $light-200;
color: var(--pgn-color-light-200);
}
.badge {
background-color: $dark-200;
color: $white;
background-color: var(--pgn-color-dark-200);
color: var(--pgn-color-white);
}
.footer-content {
color: $light-200;
color: var(--pgn-color-light-200);
}
}
}

View File

@@ -3,7 +3,7 @@ $card-gap: 24px;
.recommendations-container__card-list {
gap: $card-gap $card-gap;
@include media-breakpoint-down(sm) {
@media (-pgn-size-breakpoint-max-width-sm) {
margin-bottom: 0 !important;
}
@@ -11,15 +11,15 @@ $card-gap: 24px;
flex: 0 1 100%;
cursor: pointer;
@include media-breakpoint-up(sm) {
@media (--pgn-size-breakpoint-min-width-sm) {
flex: 0 1 calc(50% - #{$card-gap - 12});
}
@include media-breakpoint-up(md) {
@media (--pgn-size-breakpoint-min-width-md) {
flex: 0 1 calc(33.333% - #{$card-gap - 8});
}
@include media-breakpoint-up(lg) {
@media (--pgn-size-breakpoint-min-width-lg) {
flex: 0 1 calc(25% - #{$card-gap - 6});
}
}

View File

@@ -23,21 +23,21 @@
}
.alert-link {
color: $primary !important;
color: var(--pgn-color-primary-base) !important;
&:hover {
text-decoration: underline;
color: $info-700 !important;
color: var(--pgn-color-info-700) !important;
}
}
}
.email-suggestion-alert-warning {
color: $info-500 !important;
color: var(--pgn-color-info-500) !important;
&:hover {
text-decoration: underline;
color: $info-700 !important;
color: var(--pgn-color-info-700) !important;
}
}
@@ -56,7 +56,7 @@
line-height: 24px;
font-size: 12px;
font-weight: normal;
color: $primary-700;
color: var(--pgn-color-primary-700);
}
.username-suggestion--label {
@@ -65,10 +65,15 @@
margin-right: 0.25rem;
}
.username-suggestions {
.username__form-group-wrapper {
position: relative;
margin-top: -2.5rem;
margin-left: 15px;
}
.username-suggestions {
position: absolute;
inset: 0;
padding-left: 15px;
z-index: 100;
}
.username-suggestions__close__button {
@@ -76,13 +81,6 @@
position: absolute;
}
.username-suggestions__error {
position: relative;
margin-top: -13.7%;
margin-bottom: 11%;
margin-left: 15px;
}
.username-scroll-suggested--form-field {
width: 20rem;
white-space: nowrap;
@@ -99,7 +97,7 @@
}
}
@media (max-width: map-get($grid-breakpoints, "sm")) {
@media (--pgn-size-breakpoint-max-width-sm) {
.username-scroll-suggested--form-field {
width: 15rem;
}

View File

@@ -40,7 +40,7 @@ $elevation-level-2-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.15);
}
.main-content {
@extend .pt-4;
padding-top: calc(var(--pgn-spacing-spacer-base) * 1.5) !important;
min-width: 464px !important;
}
@@ -80,15 +80,15 @@ $elevation-level-2-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.15);
.alert-link {
font-weight: normal;
text-decoration: underline;
color: $info-300 !important;
color: var(--pgn-color-info-300) !important;
&:hover {
color: $info-500 !important;
color: var(--pgn-color-info-500) !important;
}
}
.form-control {
background-color: $white !important;
background-color: var(--pgn-color-white) !important;
font-size: 0.875rem;
line-height: 1.5;
height: 2.75rem;
@@ -103,11 +103,11 @@ $elevation-level-2-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.15);
margin-bottom: 1rem;
font-size: 14px;
background-color: $white;
border: 1px solid $primary;
background-color: var(--pgn-color-white);
border: 1px solid var(--pgn-color-primary-base);
width: 224px;
height: 36px;
color: $primary;
color: var(--pgn-color-primary-base);
.btn-tpa__image-icon{
background-color: transparent;
@@ -132,8 +132,8 @@ $elevation-level-2-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.15);
}
.btn-tpa__font-container {
background-color: $primary;
color: $white;
background-color: var(--pgn-color-primary-base);
color: var(--pgn-color-white);
font-size: 11px;
margin-left: -6px;
@@ -143,7 +143,7 @@ $elevation-level-2-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.15);
}
.btn-oa2-facebook {
color: $white;
color: var(--pgn-color-white);
border-color: $facebook-blue;
background-color: $facebook-blue;
@@ -151,12 +151,12 @@ $elevation-level-2-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.15);
&:focus {
background-color: $facebook-focus-blue;
border: 1px solid $facebook-focus-blue;
color: $white;
color: var(--pgn-color-white);
}
}
.btn-oa2-google-oauth2 {
color: $white;
color: var(--pgn-color-white);
border-color: $google-blue;
background-color: $google-blue;
@@ -171,12 +171,12 @@ $elevation-level-2-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.15);
&:focus {
background-color: $google-focus-blue;
border: 1px solid $google-focus-blue;
color: $white;
color: var(--pgn-color-white);
}
}
.btn-oa2-apple-id {
color: $white;
color: var(--pgn-color-white);
border-color: $apple-black;
background-color: $apple-black;
font-size: 16px;
@@ -190,12 +190,12 @@ $elevation-level-2-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.15);
&:focus {
background-color: $apple-focus-black;
border: 1px solid $apple-focus-black;
color: $white;
color: var(--pgn-color-white);
}
}
.btn-oa2-azuread-oauth2 {
color: $white;
color: var(--pgn-color-white);
border-color: $microsoft-black;
background-color: $microsoft-black;
@@ -203,7 +203,7 @@ $elevation-level-2-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.15);
&:focus {
background-color: $microsoft-focus-black;
border: 1px solid $microsoft-focus-black;
color: $white;
color: var(--pgn-color-white);
}
}
@@ -214,9 +214,8 @@ $elevation-level-2-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.15);
}
.institute-icon {
@extend .mr-1;
@extend .text-gray;
margin: calc(var(--pgn-spacing-spacer-base) * 0.25) !important;
color: var(--pgn-color-gray-base) !important;
display: inline-block;
margin-bottom: 0.25rem;
height: 18px;
@@ -232,7 +231,7 @@ $elevation-level-2-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.15);
}
.invalid-feedback {
color: $red;
color: var(--pgn-color-red);
}
.full-vertical-height {
@@ -290,22 +289,22 @@ select.form-control {
#password-requirement-left {
opacity: 1;
@extend .x-small;
font-size: var(--pgn-typography-font-size-xs) !important;
filter: drop-shadow($elevation-level-2-shadow) drop-shadow($elevation-level-2-shadow) !important;
right: 0.2rem !important;
.tooltip-inner {
background: $white;
background: var(--pgn-color-white);
display: block;
color: $gray-500;
color: var(--pgn-color-gray-500);
}
.arrow::before {
border-left-color: $white;
border-left-color: var(--pgn-color-white);
}
}
#password-requirement-top {
@extend .x-small;
filter: drop-shadow($elevation-level-2-shadow) drop-shadow($elevation-level-2-shadow) !important;
font-size: var(--pgn-typography-font-size-xs) !important;
filter: drop-shadow(var(--pgn-elevation-box-shadow-level-2)) drop-shadow(var(--pgn-elevation-box-shadow-level-2)) !important;
opacity: 1;
width: 90%;
bottom: 10px !important;
@@ -314,30 +313,30 @@ select.form-control {
.tooltip-inner {
min-width: 464px !important;
background: $white;
background: var(--pgn-color-white);
display: block;
color: $gray-500;
color: var(--pgn-color-gray-500);
}
.arrow::before {
border-top-color: $white;
border-top-color: var(--pgn-color-white);
}
}
.yellow-border {
border: 2px solid $accent-b;
border: 2px solid var(--pgn-color-accent-b);
}
.institutions__heading {
color: $primary-700;
color: var(--pgn-color-primary-700);
}
.logistration-button {
color: $gray-700;
color: var(--pgn-color-gray-700);
}
.logistration-button:hover{
color: $gray-700;
color: var(--pgn-color-gray-700);
text-decoration: none;
}
@@ -352,7 +351,7 @@ select.form-control {
width: 2.3rem;
}
.has-floating-label {
color: $gray-500;
color: var(--pgn-color-gray-500);
}
.pgn__form-control-floating-label .pgn__form-control-floating-label-content {
@@ -366,7 +365,7 @@ select.form-control {
.form-group__form-field .form-control:focus ~ .pgn__form-control-floating-label .pgn__form-control-floating-label-content {
font-size: 16px;
color: $primary-700;
color: var(--pgn-color-primary-700);
}
.form-group__form-field .form-control:not([value='']):not(:focus) ~
@@ -444,14 +443,14 @@ select.form-control {
}
.table-striped tbody tr:nth-of-type(odd) {
background-color: $light-200;
background-color: var(--pgn-color-light-200);
}
.institutions--provider-link {
font-weight: normal;
font-size: 0.875rem;
line-height: 1.5rem;
color: $primary-700
color: var(--pgn-color-primary-700)
}
.pgn__form-control-decorator-trailing {