From 0418a04fff5cf5d5a6d7f7d6983226433e87d66f Mon Sep 17 00:00:00 2001 From: "kshitij.sobti" Date: Thu, 7 Nov 2024 15:58:13 +0530 Subject: [PATCH] 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. --- package-lock.json | 181 ++++++++++------ package.json | 1 + src/login/LoginPage.jsx | 198 +++++++----------- src/login/tests/LoginPage.test.jsx | 8 + src/logistration/Logistration.jsx | 70 +++---- src/logistration/Logistration.test.jsx | 31 ++- src/plugin-slots/LoginComponentSlot/README.md | 47 +++++ .../component_with_prefix.png | Bin 0 -> 13317 bytes .../LoginComponentSlot/default_component.png | Bin 0 -> 7681 bytes src/plugin-slots/LoginComponentSlot/index.jsx | 31 +++ src/recommendations/data/tests/hooks.test.jsx | 41 +++- 11 files changed, 370 insertions(+), 238 deletions(-) create mode 100644 src/plugin-slots/LoginComponentSlot/README.md create mode 100644 src/plugin-slots/LoginComponentSlot/component_with_prefix.png create mode 100644 src/plugin-slots/LoginComponentSlot/default_component.png create mode 100644 src/plugin-slots/LoginComponentSlot/index.jsx diff --git a/package-lock.json b/package-lock.json index 57d45a33..d45f2cae 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ "@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", @@ -286,7 +287,6 @@ "integrity": "sha512-5e3FI4Q3M3Pbr21+5xJwCv6ZT6KmGkI0vw3Tozy5ODAQFTIWe37iT8Cr7Ice2Ntb+M3iSKCEWMB1MBgKrW3whg==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.24.7", @@ -2443,7 +2443,6 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": "^14 || ^16 || >=18" }, @@ -2466,7 +2465,6 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": "^14 || ^16 || >=18" } @@ -3166,7 +3164,6 @@ "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.7.2.tgz", "integrity": "sha512-yxtOBWDrdi5DD5o1pmVdq3WMCvnobT0LU6R8RyyVXPvFRd2o79/0NCuQoCjNTeZz9EzA9xS3JxNWfv54RIHFEA==", "license": "MIT", - "peer": true, "dependencies": { "@fortawesome/fontawesome-common-types": "6.7.2" }, @@ -5978,7 +5975,6 @@ "integrity": "sha512-Iu4/GPq90Xr/MSWnonn2qX8VDhI89HN7KOYBZ0/sxmAQgvXXNc7OYNC7kumvzbYzKueJQTyZoUYS7UjKB/n1WA==", "devOptional": true, "license": "AGPL-3.0", - "peer": true, "dependencies": { "@babel/cli": "7.24.8", "@babel/core": "7.24.9", @@ -6497,7 +6493,6 @@ "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/core": "^29.7.0", "@jest/types": "^29.6.3", @@ -7060,12 +7055,88 @@ "node": ">=10" } }, + "node_modules/@openedx/frontend-plugin-framework": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@openedx/frontend-plugin-framework/-/frontend-plugin-framework-1.7.0.tgz", + "integrity": "sha512-8tGkuHvtzhbqb9dU4sXUtR0K44+Hjh1uGR6DvhZAt9wSKQC1v4RBk34ef8DFzQhoNQa/Jtn6BJuta4Un6MmHmw==", + "license": "AGPL-3.0", + "dependencies": { + "@edx/brand": "npm:@openedx/brand-openedx@^1.2.2", + "classnames": "^2.3.2", + "core-js": "3.37.1", + "react-redux": "8.1.1", + "redux": "4.2.1" + }, + "peerDependencies": { + "@edx/frontend-platform": "^7.0.0 || ^8.0.0", + "@openedx/paragon": "^21.0.0 || ^22.0.0 || ^23.0.0", + "prop-types": "^15.8.0", + "react": "^17.0.0 || ^18.0.0", + "react-dom": "^17.0.0 || ^18.0.0", + "react-error-boundary": "^4.0.11" + } + }, + "node_modules/@openedx/frontend-plugin-framework/node_modules/core-js": { + "version": "3.37.1", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.37.1.tgz", + "integrity": "sha512-Xn6qmxrQZyB0FFY8E3bgRXei3lWDJHhvI+u0q9TKIYM49G8pAr0FgnnrFRAmsbptZL1yxRADVXn+x5AGsbBfyw==", + "hasInstallScript": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/@openedx/frontend-plugin-framework/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "license": "MIT" + }, + "node_modules/@openedx/frontend-plugin-framework/node_modules/react-redux": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.1.1.tgz", + "integrity": "sha512-5W0QaKtEhj+3bC0Nj0NkqkhIv8gLADH/2kYFMTHxCVqQILiWzLv6MaLuV5wJU3BQEdHKzTfcvPN0WMS6SC1oyA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.1", + "@types/hoist-non-react-statics": "^3.3.1", + "@types/use-sync-external-store": "^0.0.3", + "hoist-non-react-statics": "^3.3.2", + "react-is": "^18.0.0", + "use-sync-external-store": "^1.0.0" + }, + "peerDependencies": { + "@types/react": "^16.8 || ^17.0 || ^18.0", + "@types/react-dom": "^16.8 || ^17.0 || ^18.0", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0", + "react-native": ">=0.59", + "redux": "^4 || ^5.0.0-beta.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + }, + "redux": { + "optional": true + } + } + }, "node_modules/@openedx/paragon": { "version": "23.15.0", "resolved": "https://registry.npmjs.org/@openedx/paragon/-/paragon-23.15.0.tgz", "integrity": "sha512-wmsO0tOcLGLsinIHWi2PvGIQ0PGlfB8c789NTPCUiY1AGfkpboOEpZc+7f3m1imCdJ3OvBula5lYV9BpuQ0CSQ==", "license": "Apache-2.0", - "peer": true, "workspaces": [ "example", "component-generator", @@ -7755,7 +7826,6 @@ "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", "license": "MIT", - "peer": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/popperjs" @@ -8063,7 +8133,6 @@ "integrity": "sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/core": "^7.21.3", "@svgr/babel-preset": "8.1.0", @@ -8274,7 +8343,8 @@ "version": "5.0.4", "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@types/babel__core": { "version": "7.20.5", @@ -8636,7 +8706,6 @@ "integrity": "sha512-gUuVEAK4/u6F9wRLznPUU4WGUacSEBDPoC2TrBkw3GAnOLHBL45QdfHOXp1kJ4ypBGLxTOB+t7NJLpKoC3gznQ==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~7.11.0" } @@ -8686,12 +8755,12 @@ "license": "MIT" }, "node_modules/@types/react": { - "version": "19.1.13", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.13.tgz", - "integrity": "sha512-hHkbU/eoO3EG5/MZkuFSKmYqPbSVk5byPFa3e7y/8TybHiLMACgI8seVYlicwk7H5K/rI2px9xrQp/C+AUDTiQ==", + "version": "18.3.26", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.26.tgz", + "integrity": "sha512-RFA/bURkcKzx/X9oumPG9Vp3D3JUgus/d0b67KB0t5S/raciymilkOa66olh78MUI92QLbEJevO7rvqU/kjwKA==", "license": "MIT", - "peer": true, "dependencies": { + "@types/prop-types": "*", "csstype": "^3.0.2" } }, @@ -8787,6 +8856,12 @@ "devOptional": true, "license": "MIT" }, + "node_modules/@types/use-sync-external-store": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz", + "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==", + "license": "MIT" + }, "node_modules/@types/warning": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/warning/-/warning-3.0.3.tgz", @@ -8826,7 +8901,6 @@ "integrity": "sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/regexpp": "^4.4.0", "@typescript-eslint/scope-manager": "5.62.0", @@ -8875,7 +8949,6 @@ "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", "devOptional": true, "license": "BSD-2-Clause", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "5.62.0", "@typescript-eslint/types": "5.62.0", @@ -9613,7 +9686,6 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "devOptional": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -9711,7 +9783,6 @@ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -9780,7 +9851,6 @@ "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-4.25.2.tgz", "integrity": "sha512-lYx98L6kb1VvXypbPI7Z54C4BJB2VT5QvOYthvPq6/POufZj+YdyeZSKjoLBKHJgGmYWQTHOKtcCTdKf98WOCA==", "license": "MIT", - "peer": true, "dependencies": { "@algolia/cache-browser-local-storage": "4.25.2", "@algolia/cache-common": "4.25.2", @@ -10256,7 +10326,6 @@ "resolved": "https://registry.npmjs.org/axios/-/axios-1.12.0.tgz", "integrity": "sha512-oXTDccv8PcfjZmPGlWsPSwtOJCZ/b6W5jAMCNcfwJbCzDckwG0jrYJFaWH1yvivfCXjVzV/SPDEhMB3Q+DSurg==", "license": "MIT", - "peer": true, "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.4", @@ -10778,7 +10847,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.8.2", "caniuse-lite": "^1.0.30001741", @@ -12986,7 +13054,8 @@ "version": "0.5.16", "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/dom-converter": { "version": "0.2.0", @@ -13556,7 +13625,6 @@ "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.4.0", @@ -13614,7 +13682,6 @@ "integrity": "sha512-T75QYQVQX57jiNgpF9r1KegMICE94VYwoFQyMGhrvc+lB8YF2E/M/PYDaQe1AJcWaEgqLE+ErXV1Og/+6Vyzew==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "eslint-config-airbnb-base": "^15.0.0", "object.assign": "^4.1.2", @@ -13657,7 +13724,6 @@ "integrity": "sha512-GPxI5URre6dDpJ0CtcthSZVBAfI+Uw7un5OYNVxP2EYi3H81Jw701yFP7AU+/vCE7xBtFmjge7kfhhk4+RAiig==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "eslint-config-airbnb-base": "^15.0.0" }, @@ -14060,7 +14126,6 @@ "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", @@ -14118,7 +14183,6 @@ "integrity": "sha512-63Bog4iIethyo8smBklORknVjB0T2dwB8Mr/hIC+fBS0uyHdYYpzM/Ed+YC8VxTjlXHEWFOdmgwcDn1U2L9VCA==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/runtime": "^7.20.7", "aria-query": "^5.1.3", @@ -14157,7 +14221,6 @@ "integrity": "sha512-73QQMKALArI8/7xGLNI/3LylrEYrlKZSb5C9+q3OtOewTnMQi5cT+aE9E41sLCmli3I9PGGmD1yiZydyo4FEPw==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "array-includes": "^3.1.6", "array.prototype.flatmap": "^1.3.1", @@ -14189,7 +14252,6 @@ "integrity": "sha512-Ck77j8hF7l9N4S/rzSLOWEKpn994YH6iwUK8fr9mXIaQvGpQYmOnQLbiue1u5kI5T1y+gdgqosnEAO9NCz0DBg==", "devOptional": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10" }, @@ -17822,7 +17884,6 @@ "integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", @@ -22576,7 +22637,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "nanoid": "^3.3.7", "picocolors": "^1.1.1", @@ -23361,6 +23421,7 @@ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", "license": "MIT", + "peer": true, "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", @@ -23375,6 +23436,7 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "license": "MIT", + "peer": true, "engines": { "node": ">=10" }, @@ -23386,7 +23448,8 @@ "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/process": { "version": "0.11.10", @@ -23423,7 +23486,6 @@ "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", "license": "MIT", - "peer": true, "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", @@ -23681,7 +23743,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "license": "MIT", - "peer": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -23840,7 +23901,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "license": "MIT", - "peer": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" @@ -23878,6 +23938,19 @@ "node": ">= 12" } }, + "node_modules/react-error-boundary": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-4.1.2.tgz", + "integrity": "sha512-GQDxZ5Jd+Aq/qUxbCm1UtzmL/s++V7zKgE8yMktJiCQXCCFZnMZh9ng+6/Ne6PjNSXH0L9CjeOEREfRnq6Duag==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.12.5" + }, + "peerDependencies": { + "react": ">=16.13.1" + } + }, "node_modules/react-error-overlay": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.1.0.tgz", @@ -24039,16 +24112,6 @@ "tslib": "2" } }, - "node_modules/react-intl/node_modules/@types/react": { - "version": "18.3.24", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.24.tgz", - "integrity": "sha512-0dLEBsA1kI3OezMBF8nSsb7Nk19ZnsyE1LLhB8r27KbgU5H4pvuqZLdtE+aUkJVoXgTVuA+iLIwmZ0TuK4tx6A==", - "license": "MIT", - "dependencies": { - "@types/prop-types": "*", - "csstype": "^3.0.2" - } - }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", @@ -24116,7 +24179,6 @@ "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.9.tgz", "integrity": "sha512-Gx4L3uM182jEEayZfRbI/G11ZpYdNAnBs70lFVMNdHJI76XYtR+7m0MN+eAs7UHBPhWXcnFPaS+9owSCJQHNpQ==", "license": "MIT", - "peer": true, "dependencies": { "@babel/runtime": "^7.15.4", "@types/react-redux": "^7.1.20", @@ -24149,7 +24211,6 @@ "integrity": "sha512-FPvF2XxTSikpJxcr+bHut2H4gJ17+18Uy20D5/F+SKzFap62R3cM5wH6b8WN3LyGSYeQilLEcJcR1fjBSI2S1A==", "devOptional": true, "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -24239,7 +24300,6 @@ "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.1.tgz", "integrity": "sha512-llKsgOkZdbPU1Eg3zK8lCn+sjD9wMRZZPuzmdWWX5SUs8OFkN5HnFVC0u5KMeMaC9aoancFI/KoLuKPqN+hxHw==", "license": "MIT", - "peer": true, "dependencies": { "@remix-run/router": "1.23.0", "react-router": "6.30.1" @@ -24442,7 +24502,6 @@ "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz", "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==", "license": "MIT", - "peer": true, "dependencies": { "@babel/runtime": "^7.9.2" } @@ -25102,7 +25161,6 @@ "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -26104,7 +26162,6 @@ "integrity": "sha512-+xU0IA1StzqAqFs/QtXkK+XJa7wpS4X5H+JQccRKsRCElgeLGocFU1U/UMvMUylKFw6vwGV+Y/a2wb2pm5rFFQ==", "hasInstallScript": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "@bundled-es-modules/deepmerge": "^4.3.1", "@bundled-es-modules/glob": "^10.4.2", @@ -26622,7 +26679,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "devOptional": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -26755,7 +26811,6 @@ "integrity": "sha512-HO3GyiWn2qvTQA4kTgjDcXiMwYQt68a1Y8+JuLRVpdIzm+UOLSHgl/XqR4c6nzJkq5rOkjc02O2I7P7l/Yof0Q==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "bs-logger": "^0.2.6", "fast-json-stable-stringify": "^2.1.0", @@ -26910,8 +26965,7 @@ "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD", - "peer": true + "license": "0BSD" }, "node_modules/tsutils": { "version": "3.21.0", @@ -26965,7 +27019,6 @@ "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "devOptional": true, "license": "(MIT OR CC0-1.0)", - "peer": true, "engines": { "node": ">=10" }, @@ -27071,7 +27124,6 @@ "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", "devOptional": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -27258,7 +27310,6 @@ "devOptional": true, "hasInstallScript": true, "license": "MIT", - "peer": true, "dependencies": { "napi-postinstall": "^0.3.0" }, @@ -27448,6 +27499,15 @@ } } }, + "node_modules/use-sync-external-store": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/util": { "version": "0.12.5", "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", @@ -27618,7 +27678,6 @@ "integrity": "sha512-7b0dTKR3Ed//AD/6kkx/o7duS8H3f1a4w3BYpIriX4BzIhjkn4teo05cptsxvLesHFKK5KObnadmCHBwGc+51A==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.8", @@ -27727,7 +27786,6 @@ "integrity": "sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "@discoveryjs/json-ext": "^0.5.0", "@webpack-cli/configtest": "^2.1.1", @@ -27808,7 +27866,6 @@ "integrity": "sha512-0XavAZbNJ5sDrCbkpWL8mia0o5WPOd2YGtxrEiZkBK9FjLppIUK2TgxK6qGD2P3hUXTJNNPVibrerKcx5WkR1g==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "@types/bonjour": "^3.5.9", "@types/connect-history-api-fallback": "^1.3.5", diff --git a/package.json b/package.json index 84986d71..358c7a16 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "@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", diff --git a/src/login/LoginPage.jsx b/src/login/LoginPage.jsx index 1efeed47..4469746b 100644 --- a/src/login/LoginPage.jsx +++ b/src/login/LoginPage.jsx @@ -1,26 +1,17 @@ -import { 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 { useIntl } from '@edx/frontend-platform/i18n'; -import { - Form, StatefulButton, -} from '@openedx/paragon'; +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, - }, -)(LoginPage); +export default LoginPage; diff --git a/src/login/tests/LoginPage.test.jsx b/src/login/tests/LoginPage.test.jsx index da65bb9d..ecdc25cb 100644 --- a/src/login/tests/LoginPage.test.jsx +++ b/src/login/tests/LoginPage.test.jsx @@ -41,6 +41,14 @@ describe('LoginPage', () => { const initialState = { login: { loginResult: { success: false, redirectUrl: '' }, + loginFormData: { + formFields: { + emailOrUsername: '', password: '', + }, + errors: { + emailOrUsername: '', password: '', + }, + }, }, commonComponents: { thirdPartyAuthApiStatus: null, diff --git a/src/logistration/Logistration.jsx b/src/logistration/Logistration.jsx index 9451aa27..017a3570 100644 --- a/src/logistration/Logistration.jsx +++ b/src/logistration/Logistration.jsx @@ -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 && (

{formatMessage(messages['logistration.sign.in'])}

)} - + ) @@ -124,12 +132,16 @@ const Logistration = (props) => { ) : (!isValidTpaHint() && !hideRegistrationLink && ( - handleOnSelect(tabKey, selectedPage)}> + handleOnSelect(tabKey, selectedPage)} + > ))} - { key && ( + {key && ( )}
@@ -139,7 +151,12 @@ const Logistration = (props) => { )} {selectedPage === LOGIN_PAGE - ? + ? ( + + ) : ( { 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; diff --git a/src/logistration/Logistration.test.jsx b/src/logistration/Logistration.test.jsx index fa376aca..abdfb678 100644 --- a/src/logistration/Logistration.test.jsx +++ b/src/logistration/Logistration.test.jsx @@ -48,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, @@ -69,7 +79,18 @@ describe('Logistration', () => { }, }, login: { - loginResult: { success: false, redirectUrl: '' }, + loginResult: { + success: false, + redirectUrl: '', + }, + loginFormData: { + formFields: { + emailOrUsername: '', password: '', + }, + errors: { + emailOrUsername: '', password: '', + }, + }, }, }; diff --git a/src/plugin-slots/LoginComponentSlot/README.md b/src/plugin-slots/LoginComponentSlot/README.md new file mode 100644 index 00000000..eb6ac19f --- /dev/null +++ b/src/plugin-slots/LoginComponentSlot/README.md @@ -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: () => ( +

You're logging into TEST Instance.

+ ) + }, + }, + ], + }, + }, +}; + +export default config; + +``` diff --git a/src/plugin-slots/LoginComponentSlot/component_with_prefix.png b/src/plugin-slots/LoginComponentSlot/component_with_prefix.png new file mode 100644 index 0000000000000000000000000000000000000000..394855fa8d0b34f0a123503b92a56f38d16379fb GIT binary patch literal 13317 zcmZvD1yoy2*KV-lZiV7*rMQLSUI;~s7k9S?*FtgkmY~HoP~5F}Xp!P>1%i9AoBr>8 z@BRM&-nC|(%$l4znLYc=mgm`tdJ9s-#iGOl006kkN^+V20J0qbfTWIrj2OwngYg3Z z{A0>;(%Rk&@R^Vu0}ZO~Q{oU(b`mLi050k!GG^GF6n!FHaPV(--;c=jfN*^^0MXFA z)sN~Y>nC~U+yq)V)`z77f7#g6!E2YDr1NbD_oqyo*Af=7n1p~EoI_N=f3;xrNf6-2 ze>5o|(*J1x-1y(F--rSK+5U6>KmGsTBXESlZSVbgN4NRzorS&(2!H4>RxH%$Fn&cA zv+V5EU@*Ud3osL>v8g_@Z}4p~dJL?bJ+K1dK0`Si`vDuaByvt2-QGO4jhUT3zV2I| zyxW?sshT}FF)^spx&L?w22$ZoJC{g zkQAZ9%lZfbv7W{E-uhS1*ypy6PYH`;j4lUCJ~Yoe8rMSZ9*lvxrOVDX>5z%t#+x7J z(3@cxMeyXLoZG#ERGA8gR121Llnh!hi3Ub=GO9{ANx81Jawr>NT4}P1e36U_Gifw( zxeQ4fR$AiSgNoB4{3wu|=6dFy)9z{vJ|=3jE9nQDzf(k8@nrVmn46zhR#xUc{(W?` zvb1!6azfW~9u(sBpn zd%NQnm9w*R@0;D8o)8Q7-OkB2na~W?_FKZ4NlpD z<*tGBIR*Im7&66QzEo?H)z!_Q)7R12$!w4@Ha2cF3Eu)0kfWfWn3|feiQ8 zuV0SEnGgtRYvOHv07f#9*(($ZqU0D@dy1S)fuu_9fjEvZjYiepjIe|c6Zg%z@juiFN z@87n@#xxxwFMWM|iSCDohUm^+&h{{FiTwC*QGdj`GpSfuTNh6%A>|lI@bV@pL@xlv z#7f<~K71&0v$pd9=4j&#=3>*@u(mfq44;y*P1WmbsNxQERK1fIw;$(uZ{>$Go<- zHlzK+!-mqgdV03?FLo-Vvi9Eu1_mA%;MMR)Kj!Cw(w1|93ynCwNmBB{Fk(HGd6&3Z*??-_NAR{CH zAae2iEzMaoGjnm_si8r5hq5Q0L!SDb=u<=leQ3?2Q18u6bKc;uUpm|Sy}hBXY4tfg z*}ZksQWkF*&rK~FQ6v&>E0%^Io%+9YqRHmBZm~Eb5Fh{tCfmA_7MaAj_#AGV} zWFEz_WRW^!d2Wt?vSDdhK*ZQpX_7i@i}tq4WeZkSRTUM5DQ#=g)8-KlUHN>d z$RohpUj_pZCdELD6dXoF&!&PbM;ET5gb5%jl`7ILV?$-9N5#mK3f7cHZKq9Wc&u+* zZ2=B8`k}jC!{_U5cE^IOt*wuaNlv2lwx9H-2X41TXix_~m+Vi`+7i=K)KL@il6BFv((sTCa zX)Hu?bP*Dm^t4fAFI8!%frJ+XVP|O(5T<~Oi;JO;SJ&55{?BGd0B>v~OGyIZ7svU` znZv~z7{cThtd-)tyz~}uCPFB2QQeIjLXlT$gxow*-C`Bpu*9ahxj9>E;6wXQad`g> zeF{^H5?(nHxYZ|zhLBzyutISCG7d5^F%gR4Blo?uyJXi0!WviJ9uYuecg=DI4)DBA;!0^d2Vk>7K4Pu zf;X)BP2b}BRv*l@PLz(3k&#|lCm0eECW=itv--rI!BV-WqphuQ#&xrH zYU13rp%%*xaqtv3mZrBy-$mXqctLeB72RUE_*znbGovCqAZ&6SZw`0JkGl|4aDS4yNfOfr-h}ocYM6P zPb_RIU$=L4HStQSc((}uOhj1mXlM!!qhI?JCI%11B##)F$XIP9ys2^2G-xtGYH0{? zRzb+HN0!XQq8$anU||>^b`J9QC-BYS<>%)Q@o9APDfETou%_GZ_IxLje&vhO4d@d_ z13rkm&`Ljx3*%8LC#q#nvKbu-r(X}d0C;NYW8TubO`s*u8#E2$$XzHZDp~<@8@d_g z>0Zs-??~^~o@We#Dpl=6Vm|t31(*|7(i34VH7rY441yBZ;BYSfuJ6uGvC@LXX*OW+ zuLKD6ir{fnQ#`G>xR?t}7HJntb5PVxb!}1d)eCc7B$+r9WJyp3%W=g3qiJyT!N$kc z93+R4hZ1lHL6R;4NukTbT;-Oc^&nvJNbXIr>1B9S}L+1RNZkFpH90Vahnj zkJ(vrsCv>(y2z*YKnN7^7P5u9W8NiH%*=XemeX;rzmOLvkF?FA=g&$f${36H#=*0b zjmywO-uEUOwP0OSMN%;S1N6?xVC0UFtbtUj#%CG62Jg&VM1G1vwjI>VV4;u=gvZuJ z_yeg>(y0x5K})mlQIQYoW~{BBarzchGMv0We0Zl0Cy75iJp5;G$$$-dK5lMFnL2JV zmseL{r@zZ0AdMD6#mF!Zp8WHY0j%=tb!}X$0^iS`9;A1eLUhr-1r@H|_i^=pfBJ&if&syRKktYbkF^J$7KY09g@?~%V$ntlcxctZdZS$&w> zTkV^j)y>MSjMjmsd}D9vw=2U3Z}3l?gDlL1k%8-TuJDU<1?t+A{hJ^xJ9l?J%hz*y zdQ7OTXIPJk$)2RPFW@9O5hAgBZ<3iKPRC<@a0rrti53~^>1&YRLHSo&f@yoLDw&fR zMwQQEVz9}<=A9=xIwkdZV_|H48NEK#Nwju-tXk-BIfuInt|W}3LC_OT@)K7V;~&ET z(7}=EykI}*z;uwrLHF-Mrp_GG;C9Tdsrp-e#)K~&_~EI4c->y}`FxQeCA!KyWoxZU5ZyX442roS$6J&Usyh&6;2^?rG2^5{cK*aQ*1g6&|5~na2 z3}lwU@uWD=6;azD;vu42WxjiOSY>2jZ2Xwe0UV0Tj>-qzukNn$>#n216W2T=QMyA3 ziO|<*xU$q`q6>CGN);;fn1?fwu&+SUjNH-2T;WK_@A^%~I4Z>f7WM z{=4tIz9$KB5PDi#+SI4VN8{`F1MMDAEmUqL%ZOMg>%vx(*y>AbSy>1J$QkTy%xsqRDOVo?)ZhYTdy9A) zp_CgFN=#h$N!lwYF8+mc?961dQ;+Rz4vh6)hS(r~jza@s50Zcn{h%7wYB}*O9+m(Hb}Oi2Vc+TN%U|A(3HQ(HE>QzTk=F5N zs3b?J(y``Oq}a$L0e0J|^CglptCzoxfeqjDmH3vm+Z~{<ZJJ8)M{3PSMD+2wcI@}5Q59!m5GXn7==BzsOjx$ zXqt_3Y-(y!?1ym`D7ccPk)^eS2T`m)L?&YPwrP0=8>*Ql|j;^n-!Ei^DJ{18{4K9aB4tgDtT7GYI z*{w+tTw_Jl53_?XrSb3ixS{(SO3?{w<~Bc`p~Ym16YDw6#NnEj{6v0wMR4cP97XxF zY+YVTn>VsaJkOd9aD0_GcYAiG$sPmA%E`$gcrX<#3qdC!x+Y(&d^6IS&q9Fw;Jeol zIva*b`m7T>VQpB#o5p7%fR`6PIwuB=&q}cVfQg?)%C(#VRBhIu%GMTD^Fp%t*u5R^ z1W%o0*B}T!rnH7^w&8G?&DMVgyy%C0g6i~|AI{CY;WE##du`&>#V%^k1Hd|()u|BE z-M&8AC01zpd*Q}$PVJ~s`K^8U_jHZ;!BWzZJ{^@-Lemc+h3PS-30y5f$o(7=RwJIF z3CG^Q7?m>O8DG!vTXg%G+wiC>>{zR42jOB7LMcg@&eh(;zwLnX=bF>|M@MX7J0R8| zh@!koLo0IugZbSh zDl3rE(VogyQOQD@x2kIW2eUS_;Ongpv9ulOPGRu(EQQ~i(~ITwf6PNen$ANy-;klVVIAP$rwfts>UPXDE@pFF}wNI z48?!^z|9E3TG|Lb7tF%L2EAN1S$@>rHNNb6Z8{i0DF6D00W{p57=M(2oiVKumG}*% zFg1#H7OzpDq{VOjVBXqmTdrE5YENQB1D|Sc{iCxb;x-?U^vK#ju6k09gUmeM_$q7&y>1y@2s#MnK%s>M#Qu5{9m3=A;P zo`EROyPv56i&k#Ak)#)NUr1{w*IA>ZUBB;I6{Ifl#2AlCuRLj34Y}9ODckW(AQ6H) z<>aye^ zU@XZ>IQH)_HY15mXC@@HDN!u~d~{lzT+~tVQSG~k6ZnG_znRIKcRj!$Atv8H&u*b^ zaMgsnj{64z{t;FQI(|sNj4bX)O8KzXY2gpTgR$`t6S{#_bjuY>?|;Q41wjx` z5Ws)7U@-FkXn+OG|JMG_x7qqH_!owNOLHguI*c`m0l%;Bd~#fx7Uvdk{UzF#j~s;- zkFT2+UrwU}%5e_CZCs<10s$SyNsZ@$UBX|+H&34zAOYkxz8O^2aalOpv@J_e;nDK} zYK?0m`pi5JD`B5OCG;u)p658A5-p=csyKy=QD!+zKy-l~7nxhbIl`+v1V*qSb#I>j z_7rn#K;X6nya1(Sx&IACAE5ucYJJK7i&dBA{*~`+{7l?MfHL10u;9~SoWdlUJF#ve zDJiL+9^RT%yyxCB4O;WFwOP(oWvQ4^0fy8RL!LIzZ^OijmqyIIeS?7rm30c+qMD#u%59Hn@R@T{%z$~4lAJYiNcZ{e*zS3cDNmA; zYKhhv2Y1FB?hIKD%DA)qNgkXmn-*csFXP499P3CfRXQ`Ix=KZwWeoxfufJE zL~hrrOX1IM@%xXV_HX;|9;`nt_2O`*jT~HRD%RcPLZ`4_wV(7_u!iM*x_-28uowDr ze*|tj`+a_U(i(yL)>WJ)LWVwa+Y-OzUP{`E4pppavC6pS+*=G{`I^SNQm5kJl5zp5 z-W>Ydl2vs(;rgy)_4s^<<=qe}l#W}K`P^GPHFaVaMJ8qdS-@R@3U9gNOL3HkrCYpv z{@c7WZ}G>g<7nfW!IwON61nGAY39LIbL@USFG5ddj;{wdPeCQJo28iazd_@PcAH`M zjzV`9yLci6Q3X8lL8nquWO7m=Y<~c0L6x+7QW7Gmd`RmPJq!u!XKq!s)7=jR?P^78mE zDBJsC7pv=BxDukG!!$o;7?+l-xzZ1as-MjM?ujP!d~Z)Vc$()|H1+gYAfyWHZvA%a;K>q4$+~U&G7z;4Gj;r`B zlNTv;C?fhP6mt{$2ZTIE3 zq|?zlEsCUxkxFqoZ$ryzI9HlVk$Fj~SzuL(goMPTL|{-*%&-07Dau-;T=(oGEA7}R z?lvwUR3AquVzl(o z=g!Y~!5jylf4N|gU+Ktz0(j*RPF6ovd9pNbQPJCrGS>oy(i?mk3`DJtffQ-lO>=-J z=jXHJQ7zuJ-Hhn87w)|ZzI>g2mOVA4Wzn7dF8U23?*Gim$?3{D-O|}vUFIP*CKO*; zdJWa48lNU%GfszC=6al6b<4?jqke%V+D-&-Flq0?CR$AO@(G1G~UDI z5dr@yn{myDdn4EH7pJF$ilPgre`Agh!7ZJ%0Ki4i)-jS!p=uRDrmf&IlR?#Vs~ifc z8tYt;XE<%Z$qF+~Fc7I5(t`x>LjJEFfZ*TWI^h59nf;%!k3DPWx1Q3-!HrIvB6tU| zZFirxWibH8`1HZXMV!W@W)=^Ctd(x{x%mC7Z@}CM z8xr{s@4Wy7n%RBpwN8r!$M;uROv)N0yXP%-pN|v)Yk^(Y4%YfYxf5M(&wO`hotq46 zW&;X>B#{AjpPic)%gbBo9%hjMN2vd<6tD&YbVL4LU0DIm#DFEtzhC}y{a3($#Qe`& z{<-lf`R`8r-?{(!guipI5yrhfOO@sC&rj`rV~eiilI=Rb^kr5~-o4%PwEtXuM+o@+ zqP!g8qsFc48=X)(&QHDNG_+?wkmCRloTwXB*Xl9BF~T!H)qA0T#sruF22E}`2;_*^ zl%?pD(PUmFqDeAhhymfhBY>wfPa|ewNr1Zgh52?v3SUx5d#jy2WTRUs9nVj2U*Oza*^iVrEko@ zhHr=c?vo}*+NVA<)U}v#Ypt5uK9@;(u`Jc2>o#zp%F|E;Yq$)3)lV9nXAynUKqTWy zgey%LTz^$QWB>W>CjX&h)1nKlb^Tu&ImM6TH-bKff{~BmkPuFB2a-=uR-}<~L}4TV zBFzwn-!eo*sHhAi1Yd*8hgM4b_65ehyc(UC_h&oRi(Gd6t&8VBdrkPkggN^k9s=a$ zmiiS|eIP(0AVw`vOu3uEIDkQA5q3j`W&%0)s>zPq#I zzW=+%AK}V8vg6|7CMPvn=0JF(6iKR&eDe)j81((SyEbguPEJl;U0rhP7Z)VgZU~ef zK}%mz(a`Lm{*X!yC9q?a3D@Jhd3>}UJ5;#;A+z*;Cl>-C3`KB*Ldu5-*tjg2PXR4O zmL**~=b?~cOEP9eai}HH-eW7NJ2W)3tc)FRa&B%&H9M>nd^WX;BSP+nV2Eo;68*Cy z5TqJv8fGy>27~_DvuEZUO5sP_+XFdGSFwUaKH`0Q`}@%k85gblX z;39yNRC~q{4*vxMIBI%G$03+;BDJH+nrtUKJ1D`S-dq*DtgH+HqgbavC}c4>?h@kT z-#R>-uKp=LWwEsU>C>|>3UQzD7r+11CVCQ@29eFwMrpk|B$0_sbY*9KOIXjA@W5j) z(`_MyqmTA-u_1+u@C(WB+JUbFH&>WLFRoKnJ-xiH_Yv>d6&c}&!*5|kiWyi|`uY@E zw4zqu2@quT+hiks{rssxF~6Upuo%hMAH`E3SPlp{oLPj>UJ{;BM>b%RG)72RI6`Bq zn%f#m!N1qX)NMTZYH#>cQ@ool*#JH8b;NI6F z@+gm=kq#a*6MrGvoGJpD`=$nevwdYs*@)sxgJRC`nAH(XQiZ-nHQssb|lii!kf?E?^5G6SbJs|Gk4I(1rps-^dP?C!`j7l33_!egw zAE+Bs2k);7T+%&i_^@k@({pgZse!kh#Wpo3%VPg!of5(Iu&^Zh5@9t=t=Kv`XcN3I z_|2xv{Z|91yXhsZmxW=8$#rrJLb-$PJ57CWfFZ>+aIJoP{Q{PeN!2q+kJj5!&d+c( zjJee?5xTn+*=PB-%cgpX$JDp@Or3}V8{zR=L z$txb6u6}2x^{DZ$vI7ea^>2XT1Tv8g4TA2mJV?yB()zc1P}kw;+t9{fmo$n*p4dv;8$X#2YOlwO;WBrq0z7(r!hUX2wPd{u*i;C+75 zFxc%r4X?s~A?@%4U1Ac}P!9|FAq7-heFMX1ah&cSVInqq*k zZ;OZaRnUdohIz-F2^;ia)9P6VSj&V=g9p{%fcdF!hZsN(YZsC7RLMK;W>2I}BJ8|4 zO&^aZ;3jEe_q@uQ-KuRFwvMv^o8NX9rAc5fdIFp3l%X1xmf@HZEJ{`!C9^b5ClBX>;|6R)2)J?Q9p0V|u#j(ctn%Ye7L ze+E$j(8l_jg4^w0;;g5~RztH6aJ=_{rXP><-GNmJ`aX~-3F!x{b0l3+>w4+eC>4A? zj9T6Lh_=*8y&pN4XKwD+eC4OMNCUs3pMcfG;><1)6hL?1c6+I#moX)4;3stFI&+?j z2`C_ts1^+}1bVbLRlpwS{CSb2NP0IZ?%V@R9q>!wfxJ(8$iZx3Z5BcfhSeDz!+&Rl zy>vNmM6xZhV+{s>1|#P|7BGXs_N;(~G6>-R9c5mEG5PHRn1<`c*CcbC78^;$78?;C z9V@RF8v~jeo%mPCUoq}~@H~c@UDZOfB$u>7;16o=<$_X5v#}2|26O%OveOOU8Ebq_ z%k|v5oe6y5^=P7K;ku=+^+WNv>yoT_`!b3~G(W0keOMxQ@15d&R(krcA4RF@>Csm2 z$WY(TGvpc17g2ltOlwO_YJLtHD$3zE(5%OYrK_Zh6ofi=k{ zD}mTcST_q&WN+W;x9kFC(;zO2S4Yd7*w|QDz9D7Nbz1WB^FAK^c*WeHiPwKJ$`pLo zY%J<>z83^F?q_Py+}(LT)!=ypf14wr2TFV*{{Vrcu&7~`gUZXx_c?&?QXw21Be3Zg z^r`_4)1WVpKU`SyYS_3h|9E?idvyu{$+`5pYrz8nx$oxpe&uii-)W~p^e4=-J||Y5 zS7pk25rHHZ&X>*FC6+CbtmQnbp0#zoX6n7>kl>88qA{A|A15;>Yu~PXPIcSI^eJjL zcSBL(s1qd7&|b@Qt~_=b^2O`XCGO4Hy^`*2w}O`OTUC+^tq5q4QAe^-&js7M0@SJu_lEhwHNDpw2}9p_8bX?-dR3Xpd$ z5k)8Z_14rtuu+SXrHmgvSgyM%oVU z;qFc;XdiaAH&voA{LOZu$z`H^Y4yCWj*pf$ccsHm*lFRLVUzP>lZ&M9g>8pLNpy4_ z|JWvjR>=z1<-MLTxitvfJH0VYmCBrT`-6AWjHG5bO>91j;^a*m^_Asm&kqPWN3@ED zL?*?F3fwZ2Jk5aM3m&4nQPemyF^XlccS>v=78@Je8E_}|Q!wDq#o3wrqDOX#_*2uQ z`Q!ao8+uBO`gV4(IC!?fS&kbHsViYwAlg)cE{<&64%Z zyXWxb!%GNkbZK@+?!BmUlC3+IDMw}P&dc@i2cm~8QFi5M= zsY-TWb|3YvzE@=8;^MRTj^p;)N=bF*yZ0&M={`aF_>aU%N_e=(bH|l-eJU*m=<}gO z{GoefaSzhbMCJZru*XEyQS{==ppQF?Q3xAV^mwH(_mrZMKu$9hqm=$7ZJT_R=0JlO zIMHMXIMd{z{Z`(#& zjC7wWR{M_o!DE8H^{sw8f&kal&;T!RI(`&}6f1`gtBB3}So{|H^keiT`0*>*oR7tj z-#Cn2t2j(d&Zt|^;a4OsDfdi4By#%aoXzP`kq1^*pqdf+^{Bl<^GsOkvJGo|vC+Zy zww1WjaFm!l4wJAQU--r-5ue~=_PC^i>C15WwRwZHkwFqg;yUN~I{8jqg0>yB@6ThO z^U4%-njgb*>V-7wIIRBKi26?;S!!MPHyj2XlFJn=;7@)gI^a%lr^n^~jCsnJFL%ZB z?0aI2Yb9j!6ciM8R=bCbO=_RgF3M3&Taw}+7y!mKQ7~K&sY;szYF&ZLuAIll`!1>a zeOrORN)@Jr7K{ZfHf2LC4flvMx%DP%F5BNBE2}D z34z7XY6vYG9cDo)hGY3QABK>A2LaF-9ZqP4_wA?uw$c z*&1=y^lca+?6V_;B)!w_3;lEi7MjUAV0YCN9JYG>`pkaE0Tx7pOci zWWEq~c0J6Ojc{GS0XpT72?7&8?qIs2mHgO|$dL%3j1zIquC;vNo?}1N6W5lh`=(ET z>nUI)aJq#kq*k%7vSC1L)kJ3+S<9cq$gzo ztnBTLO{OlbdY!>%En3)XI8CaBgOi_z5CkF3O~!pFRy{WRo_jY1DJ)SbWwEa-cdL?= zkJS*1n13KPB_k`Lun4zzv|ei-k!;4}&;FqI>_H=~&fUaVbIX}(f~K@G83}*MB56i` z>7ZXp()oGxeJ%0Rn9vJKI?J&w3q=%9Ed|5$3gN%(KSNwusJ)J~hU1jzZdsafho;6)% zc@!jc0wb5xQ?!)pC3<3$!f`7p)rcx5Z zXy*!9Y309eJQwvl`zhlxU8><-G&vtHP*SD$j6N{tI1xoNM8Lo z`EX*j-*xd#-AG+KgGyW~#KqvZsA76jCX*u3u+gMp+5#0)iNFg@aCvEIX}cF(e9Ds^ zLaayjP6n!Yye(gR^4W7Q9FKFQcR3C6?d>kPeECacgyTfq-_EXqu$iq-FSw*%fVMxe z>ZYfYmuxE>xB1+75nnddtbQgTC4Fe?0Iu26#czuE1DK>KvE=e_I-;JV3rW3Mz`_*r zMWY8GPmbFg)YoA$5qCsqqT=*OJ?ow+pbr+k@uOMQbv<$Oy7|zyoSQu0X2eEOiDJfkRX&U;{DWa2f>orUgX$0h$5<{@W-Yd4orqg0@P5Ie5Hwar*(bpn)Yt zP_PTB|1OYC(KzL6*dXnn^9(82*84J9u83*WeeU7U>D{FRB`Tnx%kSqvti+;$&1G$F zO$j|B5voa;xEhfryZ!cSa^-zBA4S|$Fu-G9htt51FH;$kJH zO)%gv+o#QYfA^qjpzdb;p;bR{NrKuKZ*mQAf;)=RY*}YWQik|@4?tNSBv&nC8v4Hg DlIysS literal 0 HcmV?d00001 diff --git a/src/plugin-slots/LoginComponentSlot/default_component.png b/src/plugin-slots/LoginComponentSlot/default_component.png new file mode 100644 index 0000000000000000000000000000000000000000..7c00670323553675f18413921a4c17721975323a GIT binary patch literal 7681 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 U36VAa(=MQ)s;%-~$s+VW0EobU-2eap literal 0 HcmV?d00001 diff --git a/src/plugin-slots/LoginComponentSlot/index.jsx b/src/plugin-slots/LoginComponentSlot/index.jsx new file mode 100644 index 00000000..96b7fcf4 --- /dev/null +++ b/src/plugin-slots/LoginComponentSlot/index.jsx @@ -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, +}) => ( + + + +); + +LoginComponentSlot.propTypes = { + institutionLogin: PropTypes.bool, + handleInstitutionLogin: PropTypes.func, +}; + +export default LoginComponentSlot; diff --git a/src/recommendations/data/tests/hooks.test.jsx b/src/recommendations/data/tests/hooks.test.jsx index e8033ac5..43c98988 100644 --- a/src/recommendations/data/tests/hooks.test.jsx +++ b/src/recommendations/data/tests/hooks.test.jsx @@ -1,10 +1,12 @@ -import { renderHook } from '@testing-library/react'; +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(); + 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); }); });