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);
});
});