diff --git a/package-lock.json b/package-lock.json index 19e3f81c61..43805462bd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,11 +16,11 @@ "@fortawesome/free-brands-svg-icons": "6.5.2", "@fortawesome/free-solid-svg-icons": "6.5.2", "@fortawesome/react-fontawesome": "0.2.0", + "@openedx/frontend-plugin-framework": "^1.4.0", "@openedx/paragon": "^22.1.1", "@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", "algoliasearch": "^4.14.3", "algoliasearch-helper": "^3.14.0", "classnames": "2.5.1", @@ -5072,6 +5072,48 @@ "node": ">=8" } }, + "node_modules/@openedx/frontend-plugin-framework": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@openedx/frontend-plugin-framework/-/frontend-plugin-framework-1.4.1.tgz", + "integrity": "sha512-8lVvq+kqb4CsPtD2CIf5nL+Ded6r+dTM/0DIwxCuoUTh4i5aCBwPY3gnKsfa1OS9IEJjeSgiMBieH8WRqUiixw==", + "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": "7.2.9", + "redux": "4.2.1", + "regenerator-runtime": "0.14.1" + }, + "peerDependencies": { + "@edx/frontend-platform": "^7.0.0 || ^8.0.0", + "@openedx/paragon": "^21.0.0 || ^22.0.0", + "prop-types": "^15.8.0", + "react": "^17.0.0", + "react-dom": "^17.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/redux": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz", + "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.9.2" + } + }, "node_modules/@openedx/paragon": { "version": "22.2.1", "resolved": "https://registry.npmjs.org/@openedx/paragon/-/paragon-22.2.1.tgz", @@ -5733,35 +5775,6 @@ "react-dom": "<18.0.0" } }, - "node_modules/@testing-library/react-hooks": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/@testing-library/react-hooks/-/react-hooks-8.0.1.tgz", - "integrity": "sha512-Aqhl2IVmLt8IovEVarNDFuJDVWVvhnr9/GCU6UUnrYXwgDFF9h2L2o2P9KBni1AST5sT6riAyoukFLyjQUgD/g==", - "dependencies": { - "@babel/runtime": "^7.12.5", - "react-error-boundary": "^3.1.0" - }, - "engines": { - "node": ">=12" - }, - "peerDependencies": { - "@types/react": "^16.9.0 || ^17.0.0", - "react": "^16.9.0 || ^17.0.0", - "react-dom": "^16.9.0 || ^17.0.0", - "react-test-renderer": "^16.9.0 || ^17.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "react-dom": { - "optional": true - }, - "react-test-renderer": { - "optional": true - } - } - }, "node_modules/@tootallnate/once": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", @@ -18340,16 +18353,14 @@ } }, "node_modules/react-error-boundary": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-3.1.4.tgz", - "integrity": "sha512-uM9uPzZJTF6wRQORmSrvOIgt4lJ9MC1sNgEOj2XGsDTRE4kmpWxg7ENK9EWNKJRMAOY9z0MuF4yIfl6gp4sotA==", + "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" }, - "engines": { - "node": ">=10", - "npm": ">=6" - }, "peerDependencies": { "react": ">=16.13.1" } @@ -18673,7 +18684,7 @@ "version": "16.15.0", "resolved": "https://registry.npmjs.org/react-shallow-renderer/-/react-shallow-renderer-16.15.0.tgz", "integrity": "sha512-oScf2FqQ9LFVQgA73vr86xl2NaOIX73rh+YFqcOp68CWj56tSfgtGKrEbyhCj0rSijyG9M1CYprTh39fBi5hzA==", - "devOptional": true, + "dev": true, "dependencies": { "object-assign": "^4.1.1", "react-is": "^16.12.0 || ^17.0.0 || ^18.0.0" @@ -18728,7 +18739,7 @@ "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-17.0.2.tgz", "integrity": "sha512-yaQ9cB89c17PUb0x6UfWRs7kQCorVdHlutU1boVPEsB8IDZH6n9tHxMacc3y0JoXOJUsZb/t/Mb8FUWMKaM7iQ==", - "devOptional": true, + "dev": true, "dependencies": { "object-assign": "^4.1.1", "react-is": "^17.0.2", @@ -18743,7 +18754,7 @@ "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "devOptional": true + "dev": true }, "node_modules/react-transition-group": { "version": "4.4.5", diff --git a/package.json b/package.json index 5b938c2cf6..ab1d7008aa 100644 --- a/package.json +++ b/package.json @@ -39,11 +39,11 @@ "@fortawesome/free-brands-svg-icons": "6.5.2", "@fortawesome/free-solid-svg-icons": "6.5.2", "@fortawesome/react-fontawesome": "0.2.0", + "@openedx/frontend-plugin-framework": "^1.4.0", "@openedx/paragon": "^22.1.1", "@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", "algoliasearch": "^4.14.3", "algoliasearch-helper": "^3.14.0", "classnames": "2.5.1", diff --git a/src/login/LoginPage.jsx b/src/login/LoginPage.jsx index 6281572675..08333b6439 100644 --- a/src/login/LoginPage.jsx +++ b/src/login/LoginPage.jsx @@ -1,5 +1,5 @@ import React, { useEffect, useMemo, useState } from 'react'; -import { connect } from 'react-redux'; +import { connect, useSelector } from 'react-redux'; import { getConfig } from '@edx/frontend-platform'; import { sendPageEvent, sendTrackEvent } from '@edx/frontend-platform/analytics'; @@ -33,7 +33,7 @@ import { thirdPartyAuthContextSelector } from '../common-components/data/selecto import EnterpriseSSO from '../common-components/EnterpriseSSO'; import ThirdPartyAuth from '../common-components/ThirdPartyAuth'; import { - DEFAULT_STATE, PENDING_STATE, RESET_PAGE, + PENDING_STATE, RESET_PAGE, } from '../data/constants'; import { getActivationStatus, @@ -46,11 +46,6 @@ import ResetPasswordSuccess from '../reset-password/ResetPasswordSuccess'; const LoginPage = (props) => { const { - backedUpFormData, - loginErrorCode, - loginErrorContext, - loginResult, - shouldBackupState, thirdPartyAuthContext: { providers, currentProvider, @@ -59,15 +54,32 @@ const LoginPage = (props) => { platformName, errorMessage: thirdPartyErrorMessage, }, - thirdPartyAuthApiStatus, institutionLogin, - showResetPasswordSuccessBanner, - submitState, // Actions backupFormState, handleInstitutionLogin, getTPADataFromBackend, } = props; + const { + backedUpFormData, + loginErrorCode, + loginErrorContext, + loginResult, + shouldBackupState, + showResetPasswordSuccessBanner, + submitState, + 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 { formatMessage } = useIntl(); const activationMsgType = getActivationStatus(); const queryParams = useMemo(() => getAllPossibleQueryParams(), []); @@ -281,27 +293,15 @@ 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, - }; -}; +const mapStateToProps = state => ({ + thirdPartyAuthContext: thirdPartyAuthContextSelector(state), +}); LoginPage.propTypes = { backedUpFormData: PropTypes.shape({ formFields: PropTypes.shape({}), errors: PropTypes.shape({}), }), - loginErrorCode: PropTypes.string, loginErrorContext: PropTypes.shape({ email: PropTypes.string, redirectUrl: PropTypes.string, @@ -311,10 +311,6 @@ LoginPage.propTypes = { 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, @@ -341,13 +337,8 @@ LoginPage.defaultProps = { emailOrUsername: '', password: '', }, }, - loginErrorCode: null, loginErrorContext: {}, loginResult: {}, - shouldBackupState: false, - showResetPasswordSuccessBanner: false, - submitState: DEFAULT_STATE, - thirdPartyAuthApiStatus: PENDING_STATE, thirdPartyAuthContext: { currentProvider: null, errorMessage: null, diff --git a/src/login/tests/LoginPage.test.jsx b/src/login/tests/LoginPage.test.jsx index 9c337bf25e..a337d4b851 100644 --- a/src/login/tests/LoginPage.test.jsx +++ b/src/login/tests/LoginPage.test.jsx @@ -43,6 +43,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 9451aa2745..d93366ea11 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 LoginPageSlot from '../plugin-slots/LoginPage'; 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,7 @@ const Logistration = (props) => { {!institutionLogin && (

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

)} - + ) @@ -124,12 +129,16 @@ const Logistration = (props) => { ) : (!isValidTpaHint() && !hideRegistrationLink && ( - handleOnSelect(tabKey, selectedPage)}> + handleOnSelect(tabKey, selectedPage)} + > ))} - { key && ( + {key && ( )}
@@ -139,7 +148,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 87bf3e705e..80d5b44127 100644 --- a/src/logistration/Logistration.test.jsx +++ b/src/logistration/Logistration.test.jsx @@ -3,16 +3,14 @@ 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'; import Logistration from './Logistration'; import { clearThirdPartyAuthContextErrorMessage } from '../common-components/data/actions'; -import { - COMPLETE_STATE, LOGIN_PAGE, REGISTER_PAGE, -} from '../data/constants'; +import { COMPLETE_STATE, LOGIN_PAGE, REGISTER_PAGE } from '../data/constants'; import { backupLoginForm } from '../login/data/actions'; import { backupRegistrationForm } from '../register/data/actions'; @@ -23,11 +21,20 @@ jest.mock('@edx/frontend-platform/analytics', () => ({ jest.mock('@edx/frontend-platform/auth'); const mockStore = configureStore(); -const IntlLogistration = injectIntl(Logistration); +let store = {}; +const getComponent = (props) => ( + + + + + + + +); + +const renderComponent = (props = {}) => render(getComponent(props)); describe('Logistration', () => { - let store = {}; - const secondaryProviders = { id: 'saml-test', name: 'Test University', @@ -35,14 +42,6 @@ describe('Logistration', () => { registerUrl: '/dummy_auth', }; - const reduxWrapper = children => ( - - - {children} - - - ); - const initialState = { register: { registrationFormData: { @@ -50,16 +49,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 +80,18 @@ describe('Logistration', () => { }, }, login: { - loginResult: { success: false, redirectUrl: '' }, + loginResult: { + success: false, + redirectUrl: '', + }, + loginFormData: { + formFields: { + emailOrUsername: '', password: '', + }, + errors: { + emailOrUsername: '', password: '', + }, + }, }, }; @@ -90,16 +110,22 @@ describe('Logistration', () => { ENVIRONMENT: 'production', LANGUAGE_PREFERENCE_COOKIE_NAME: 'yum', }, - messages: { 'es-419': {}, de: {}, 'en-us': {} }, + messages: { + 'es-419': {}, + de: {}, + 'en-us': {}, + }, }); }); it('should do nothing when user clicks on the same tab (login/register) again', () => { - const { container } = render(reduxWrapper()); + const { container } = renderComponent(); // While staying on the registration form, clicking the register tab again fireEvent.click(container.querySelector('a[data-rb-event-key="/register"]')); - expect(sendTrackEvent).not.toHaveBeenCalledWith('edx.bi.register_form.toggled', { category: 'user-engagement' }); + expect(sendTrackEvent) + .not + .toHaveBeenCalledWith('edx.bi.register_form.toggled', { category: 'user-engagement' }); }); it('should render registration page', () => { @@ -107,16 +133,18 @@ describe('Logistration', () => { ALLOW_PUBLIC_ACCOUNT_CREATION: true, }); - const { container } = render(reduxWrapper()); + const { container } = renderComponent(); - expect(container.querySelector('RegistrationPage')).toBeDefined(); + expect(container.querySelector('RegistrationPage')) + .toBeDefined(); }); it('should render login page', () => { const props = { selectedPage: LOGIN_PAGE }; - const { container } = render(reduxWrapper()); + const { container } = renderComponent(props); - expect(container.querySelector('LoginPage')).toBeDefined(); + expect(container.querySelector('LoginPage')) + .toBeDefined(); }); it('should render login/register headings when show registration links is disabled', () => { @@ -125,18 +153,20 @@ describe('Logistration', () => { }); let props = { selectedPage: LOGIN_PAGE }; - const { rerender } = render(reduxWrapper()); + const { rerender } = renderComponent(props); // verifying sign in heading - expect(screen.getByRole('heading', { level: 3 }).textContent).toEqual('Sign in'); + expect(screen.getByRole('heading', { level: 3 }).textContent) + .toEqual('Sign in'); // register page is still accessible when SHOW_REGISTRATION_LINKS is false // but it needs to be accessed directly props = { selectedPage: REGISTER_PAGE }; - rerender(reduxWrapper()); + rerender(getComponent(props)); // verifying register heading - expect(screen.getByRole('heading', { level: 3 }).textContent).toEqual('Register'); + expect(screen.getByRole('heading', { level: 3 }).textContent) + .toEqual('Register'); }); it('should render only login page when public account creation is disabled', () => { @@ -160,14 +190,16 @@ describe('Logistration', () => { }); const props = { selectedPage: LOGIN_PAGE }; - const { container } = render(reduxWrapper()); + const { container } = renderComponent(props); // verifying sign in heading for institution login false - expect(screen.getByRole('heading', { level: 3 }).textContent).toEqual('Sign in'); + expect(screen.getByRole('heading', { level: 3 }).textContent) + .toEqual('Sign in'); // verifying tabs heading for institution login true fireEvent.click(screen.getByRole('link')); - expect(container.querySelector('#controlled-tab')).toBeDefined(); + expect(container.querySelector('#controlled-tab')) + .toBeDefined(); }); it('should display institution login option when secondary providers are present', () => { @@ -190,12 +222,14 @@ describe('Logistration', () => { }); const props = { selectedPage: LOGIN_PAGE }; - render(reduxWrapper()); - expect(screen.getByText('Institution/campus credentials')).toBeDefined(); + renderComponent(props); + expect(screen.getByText('Institution/campus credentials')) + .toBeDefined(); // on clicking "Institution/campus credentials" button, it should display institution login page fireEvent.click(screen.getByText('Institution/campus credentials')); - expect(screen.getByText('Test University')).toBeDefined(); + expect(screen.getByText('Test University')) + .toBeDefined(); mergeConfig({ DISABLE_ENTERPRISE_LOGIN: '', @@ -221,11 +255,13 @@ describe('Logistration', () => { }); const props = { selectedPage: LOGIN_PAGE }; - render(reduxWrapper()); + renderComponent(props); fireEvent.click(screen.getByText('Institution/campus credentials')); - expect(sendTrackEvent).toHaveBeenCalledWith('edx.bi.institution_login_form.toggled', { category: 'user-engagement' }); - expect(sendPageEvent).toHaveBeenCalledWith('login_and_registration', 'institution_login'); + expect(sendTrackEvent) + .toHaveBeenCalledWith('edx.bi.institution_login_form.toggled', { category: 'user-engagement' }); + expect(sendPageEvent) + .toHaveBeenCalledWith('login_and_registration', 'institution_login'); mergeConfig({ DISABLE_ENTERPRISE_LOGIN: '', @@ -251,11 +287,15 @@ describe('Logistration', () => { }); delete window.location; - window.location = { hostname: getConfig().SITE_NAME, href: getConfig().BASE_URL }; + window.location = { + hostname: getConfig().SITE_NAME, + href: getConfig().BASE_URL, + }; - render(reduxWrapper()); + renderComponent(); fireEvent.click(screen.getByText('Institution/campus credentials')); - expect(screen.getByText('Test University')).toBeDefined(); + expect(screen.getByText('Test University')) + .toBeDefined(); mergeConfig({ DISABLE_ENTERPRISE_LOGIN: '', @@ -264,23 +304,26 @@ describe('Logistration', () => { it('should fire action to backup registration form on tab click', () => { store.dispatch = jest.fn(store.dispatch); - const { container } = render(reduxWrapper()); + const { container } = renderComponent(); fireEvent.click(container.querySelector('a[data-rb-event-key="/login"]')); - expect(store.dispatch).toHaveBeenCalledWith(backupRegistrationForm()); + expect(store.dispatch) + .toHaveBeenCalledWith(backupRegistrationForm()); }); 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()); + const { container } = renderComponent(props); fireEvent.click(container.querySelector('a[data-rb-event-key="/register"]')); - expect(store.dispatch).toHaveBeenCalledWith(backupLoginForm()); + expect(store.dispatch) + .toHaveBeenCalledWith(backupLoginForm()); }); it('should clear tpa context errorMessage tab click', () => { store.dispatch = jest.fn(store.dispatch); - const { container } = render(reduxWrapper()); + const { container } = renderComponent(); fireEvent.click(container.querySelector('a[data-rb-event-key="/login"]')); - expect(store.dispatch).toHaveBeenCalledWith(clearThirdPartyAuthContextErrorMessage()); + expect(store.dispatch) + .toHaveBeenCalledWith(clearThirdPartyAuthContextErrorMessage()); }); }); diff --git a/src/plugin-slots/LoginPage/index.jsx b/src/plugin-slots/LoginPage/index.jsx new file mode 100644 index 0000000000..ac77c37455 --- /dev/null +++ b/src/plugin-slots/LoginPage/index.jsx @@ -0,0 +1,27 @@ +import React from 'react'; + +import { PluginSlot } from '@openedx/frontend-plugin-framework'; +import PropTypes from 'prop-types'; + +import LoginPage from '../../login/LoginPage'; + +const LoginPageSlot = ({ + institutionLogin, + handleInstitutionLogin, +}) => ( + + + +); + +LoginPageSlot.propTypes = { + institutionLogin: PropTypes.bool.isRequired, + handleInstitutionLogin: PropTypes.func.isRequired, +}; + +export default LoginPageSlot; diff --git a/src/recommendations/data/tests/hooks.test.jsx b/src/recommendations/data/tests/hooks.test.jsx index 9e5d6cf22a..43c989889d 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-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(); + 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); }); });