Compare commits

..

3 Commits

Author SHA1 Message Date
sundasnoreen12
f1b3a9006e Merge remote-tracking branch 'origin/2u-main' into INF-rebase-2u-main
-m "fix: fixed conflicts"
2025-07-08 13:27:47 +05:00
sundasnoreen12
ddcd4dbe08 fix: package lock file 2025-07-08 12:50:41 +05:00
eemaanamir
d55ddbeb0f fix: resolved merge conflicts 2025-07-08 12:04:39 +05:00
38 changed files with 4715 additions and 3330 deletions

1
.env
View File

@@ -46,4 +46,3 @@ APP_ID=''
MFE_CONFIG_API_URL=''
# Fallback in local style files
PARAGON_THEME_URLS={}
RECAPTCHA_SITE_KEY_WEB=''

View File

@@ -46,4 +46,3 @@ ZENDESK_KEY=''
ZENDESK_LOGO_URL=''
# Fallback in local style files
PARAGON_THEME_URLS={}
RECAPTCHA_SITE_KEY_WEB=''

View File

@@ -21,4 +21,3 @@ MFE_CONFIG_API_URL=''
COHESION_WRITE_KEY=''
COHESION_SOURCE_KEY=''
PARAGON_THEME_URLS={}
RECAPTCHA_SITE_KEY_WEB=''

2
CODEOWNERS Normal file
View File

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

7011
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -19,6 +19,11 @@
"dev": "PUBLIC_PATH=/authn/ MFE_CONFIG_API_URL='http://localhost:8000/api/mfe_config/v1' fedx-scripts webpack-dev-server --progress --host apps.local.openedx.io",
"test": "fedx-scripts jest --coverage --passWithNoTests"
},
"husky": {
"hooks": {
"pre-commit": "npm run lint"
}
},
"author": "edX",
"license": "AGPL-3.0",
"homepage": "https://github.com/openedx/frontend-app-authn#readme",
@@ -35,8 +40,8 @@
"@fortawesome/fontawesome-svg-core": "6.7.2",
"@fortawesome/free-brands-svg-icons": "6.7.2",
"@fortawesome/free-solid-svg-icons": "6.7.2",
"@fortawesome/react-fontawesome": "0.2.2",
"@openedx/frontend-plugin-framework": "^1.3.0",
"@fortawesome/react-fontawesome": "0.2.6",
"@openedx/paragon": "^23.4.2",
"@optimizely/react-sdk": "^2.9.1",
"@redux-devtools/extension": "3.3.0",
@@ -46,13 +51,12 @@
"classnames": "2.5.1",
"core-js": "3.43.0",
"fastest-levenshtein": "1.0.16",
"form-urlencoded": "6.1.6",
"form-urlencoded": "6.1.5",
"prop-types": "15.8.1",
"query-string": "7.1.3",
"react-error-boundary": "^4.0.13",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-google-recaptcha-v3": "^1.11.0",
"react-error-boundary": "^4.0.13",
"react-helmet": "6.1.0",
"react-loading-skeleton": "3.5.0",
"react-redux": "7.2.9",
@@ -72,13 +76,14 @@
"devDependencies": {
"@edx/browserslist-config": "^1.1.1",
"@edx/reactifex": "1.1.0",
"@openedx/frontend-build": "^14.6.1",
"copy-webpack-plugin": "^11.0.0",
"@openedx/frontend-build": "^14.6.2",
"babel-plugin-formatjs": "10.5.39",
"eslint-plugin-import": "2.32.0",
"glob": "7.2.3",
"history": "5.3.0",
"jest": "30.1.3",
"husky": "9.1.7",
"jest": "30.0.4",
"react-test-renderer": "^18.3.1",
"ts-jest": "^29.4.0"
}

View File

@@ -2,7 +2,6 @@ import React from 'react';
import { getConfig } from '@edx/frontend-platform';
import { AppProvider } from '@edx/frontend-platform/react';
import { GoogleReCaptchaProvider } from 'react-google-recaptcha-v3';
import { Helmet } from 'react-helmet';
import { Navigate, Route, Routes } from 'react-router-dom';
@@ -33,43 +32,34 @@ import './index.scss';
registerIcons();
const MainApp = () => {
const recaptchaKey = getConfig().RECAPTCHA_SITE_KEY_WEB;
return (
<GoogleReCaptchaProvider
reCaptchaKey={recaptchaKey}
useEnterprise
>
<AppProvider store={configureStore()}>
<Helmet>
<link rel="shortcut icon" href={getConfig().FAVICON_URL} type="image/x-icon" />
</Helmet>
<Routes>
<Route path="/" element={<Navigate replace to={updatePathWithQueryParams(REGISTER_PAGE)} />} />
<Route
path={REGISTER_EMBEDDED_PAGE}
element={<EmbeddedRegistrationRoute><RegistrationPage /></EmbeddedRegistrationRoute>}
/>
<Route
path={LOGIN_PAGE}
element={
<UnAuthOnlyRoute><Logistration selectedPage={LOGIN_PAGE} /></UnAuthOnlyRoute>
}
/>
<Route path={REGISTER_PAGE} element={<UnAuthOnlyRoute><Logistration /></UnAuthOnlyRoute>} />
<Route path={RESET_PAGE} element={<UnAuthOnlyRoute><ForgotPasswordPage /></UnAuthOnlyRoute>} />
<Route path={PASSWORD_RESET_CONFIRM} element={<ResetPasswordPage />} />
<Route path={AUTHN_PROGRESSIVE_PROFILING} element={<ProgressiveProfiling />} />
<Route path={RECOMMENDATIONS} element={<RecommendationsPage />} />
<Route path={PAGE_NOT_FOUND} element={<NotFoundPage />} />
<Route path="*" element={<Navigate replace to={PAGE_NOT_FOUND} />} />
</Routes>
<RouteTracker />
<MainAppSlot />
</AppProvider>
</GoogleReCaptchaProvider>
);
};
const MainApp = () => (
<AppProvider store={configureStore()}>
<Helmet>
<link rel="shortcut icon" href={getConfig().FAVICON_URL} type="image/x-icon" />
</Helmet>
<Routes>
<Route path="/" element={<Navigate replace to={updatePathWithQueryParams(REGISTER_PAGE)} />} />
<Route
path={REGISTER_EMBEDDED_PAGE}
element={<EmbeddedRegistrationRoute><RegistrationPage /></EmbeddedRegistrationRoute>}
/>
<Route
path={LOGIN_PAGE}
element={
<UnAuthOnlyRoute><Logistration selectedPage={LOGIN_PAGE} /></UnAuthOnlyRoute>
}
/>
<Route path={REGISTER_PAGE} element={<UnAuthOnlyRoute><Logistration /></UnAuthOnlyRoute>} />
<Route path={RESET_PAGE} element={<UnAuthOnlyRoute><ForgotPasswordPage /></UnAuthOnlyRoute>} />
<Route path={PASSWORD_RESET_CONFIRM} element={<ResetPasswordPage />} />
<Route path={AUTHN_PROGRESSIVE_PROFILING} element={<ProgressiveProfiling />} />
<Route path={RECOMMENDATIONS} element={<RecommendationsPage />} />
<Route path={PAGE_NOT_FOUND} element={<NotFoundPage />} />
<Route path="*" element={<Navigate replace to={PAGE_NOT_FOUND} />} />
</Routes>
<RouteTracker />
<MainAppSlot />
</AppProvider>
);
export default MainApp;

View File

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

View File

@@ -37,7 +37,6 @@ const configuration = {
ALGOLIA_APP_ID: process.env.ALGOLIA_APP_ID || '',
ALGOLIA_SEARCH_API_KEY: process.env.ALGOLIA_SEARCH_API_KEY || '',
AUTO_GENERATED_USERNAME_EXPERIMENT_ID: process.env.AUTO_GENERATED_USERNAME_EXPERIMENT_ID || '',
RECAPTCHA_SITE_KEY_WEB: process.env.RECAPTCHA_SITE_KEY_WEB || '',
};
export default configuration;

View File

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

View File

@@ -1,8 +1,8 @@
import { useEffect, useMemo, useState } from 'react';
import React, { useEffect, useMemo, useState } from 'react';
import { connect, useDispatch } from 'react-redux';
import { getConfig } from '@edx/frontend-platform';
import { useIntl } from '@edx/frontend-platform/i18n';
import { injectIntl, useIntl } from '@edx/frontend-platform/i18n';
import {
Form, StatefulButton,
} from '@openedx/paragon';
@@ -386,4 +386,4 @@ export default connect(
loginRequest,
getTPADataFromBackend: getThirdPartyAuthContext,
},
)(LoginPage);
)(injectIntl(LoginPage));

View File

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

View File

@@ -2,7 +2,7 @@ import React from 'react';
import { Provider } from 'react-redux';
import { getConfig } from '@edx/frontend-platform';
import { IntlProvider } from '@edx/frontend-platform/i18n';
import { injectIntl, IntlProvider } from '@edx/frontend-platform/i18n';
import {
fireEvent, render, screen, waitFor,
} from '@testing-library/react';
@@ -14,6 +14,7 @@ import mockTagular from '../../cohesion/utils';
import { RESET_PAGE } from '../../data/constants';
import ChangePasswordPrompt from '../ChangePasswordPrompt';
const IntlChangePasswordPrompt = injectIntl(ChangePasswordPrompt);
const mockedNavigator = jest.fn();
const mockStore = configureStore();
mockTagular();
@@ -69,7 +70,7 @@ describe('ChangePasswordPromptTests', () => {
<IntlProvider locale="en">
<Provider store={store}>
<MemoryRouter>
<ChangePasswordPrompt {...props} />
<IntlChangePasswordPrompt {...props} />
</MemoryRouter>
</Provider>
</IntlProvider>,
@@ -95,7 +96,7 @@ describe('ChangePasswordPromptTests', () => {
<IntlProvider locale="en">
<Provider store={store}>
<MemoryRouter>
<ChangePasswordPrompt {...props} />
<IntlChangePasswordPrompt {...props} />
</MemoryRouter>
</Provider>
</IntlProvider>,

View File

@@ -64,7 +64,7 @@ describe('LoginFailureMessage', () => {
render(
<IntlProvider locale="en">
<LoginFailureMessage {...props} />
<IntlLoginFailureMessage {...props} />
</IntlProvider>,
);
@@ -92,7 +92,7 @@ describe('LoginFailureMessage', () => {
render(
<IntlProvider locale="en">
<LoginFailureMessage {...props} />
<IntlLoginFailureMessage {...props} />
</IntlProvider>,
);
@@ -122,7 +122,7 @@ describe('LoginFailureMessage', () => {
render(
<IntlProvider locale="en">
<LoginFailureMessage {...props} />
<IntlLoginFailureMessage {...props} />
</IntlProvider>,
);
@@ -148,7 +148,7 @@ describe('LoginFailureMessage', () => {
render(
<IntlProvider locale="en">
<LoginFailureMessage {...props} />
<IntlLoginFailureMessage {...props} />
</IntlProvider>,
);
@@ -168,7 +168,7 @@ describe('LoginFailureMessage', () => {
render(
<IntlProvider locale="en">
<LoginFailureMessage {...props} />
<IntlLoginFailureMessage {...props} />
</IntlProvider>,
);
@@ -192,7 +192,7 @@ describe('LoginFailureMessage', () => {
render(
<IntlProvider locale="en">
<LoginFailureMessage {...props} />
<IntlLoginFailureMessage {...props} />
</IntlProvider>,
);
@@ -212,7 +212,7 @@ describe('LoginFailureMessage', () => {
render(
<IntlProvider locale="en">
<LoginFailureMessage {...props} />
<IntlLoginFailureMessage {...props} />
</IntlProvider>,
);
@@ -232,7 +232,7 @@ describe('LoginFailureMessage', () => {
render(
<IntlProvider locale="en">
<LoginFailureMessage {...props} />
<IntlLoginFailureMessage {...props} />
</IntlProvider>,
);
@@ -252,7 +252,7 @@ describe('LoginFailureMessage', () => {
render(
<IntlProvider locale="en">
<LoginFailureMessage {...props} />
<IntlLoginFailureMessage {...props} />
</IntlProvider>,
);
@@ -271,7 +271,7 @@ describe('LoginFailureMessage', () => {
render(
<IntlProvider locale="en">
<LoginFailureMessage {...props} />
<IntlLoginFailureMessage {...props} />
</IntlProvider>,
);
@@ -291,7 +291,7 @@ describe('LoginFailureMessage', () => {
render(
<IntlProvider locale="en">
<LoginFailureMessage {...props} />
<IntlLoginFailureMessage {...props} />
</IntlProvider>,
);
@@ -391,7 +391,7 @@ describe('LoginFailureMessage', () => {
render(
<IntlProvider locale="en">
<LoginFailureMessage {...props} />
<IntlLoginFailureMessage {...props} />
</IntlProvider>,
);

View File

@@ -1,8 +1,9 @@
import React from 'react';
import { Provider } from 'react-redux';
import { getConfig, mergeConfig } from '@edx/frontend-platform';
import { sendPageEvent, sendTrackEvent } from '@edx/frontend-platform/analytics';
import { IntlProvider } from '@edx/frontend-platform/i18n';
import { injectIntl, IntlProvider } from '@edx/frontend-platform/i18n';
import {
fireEvent, render, screen, waitFor,
} from '@testing-library/react';
@@ -27,6 +28,7 @@ jest.mock('@edx/frontend-platform/auth', () => ({
}));
mockTagular();
const IntlLoginPage = injectIntl(LoginPage);
const mockStore = configureStore();
describe('LoginPage', () => {
@@ -91,7 +93,7 @@ describe('LoginPage', () => {
it('should submit form for valid input', () => {
store.dispatch = jest.fn(store.dispatch);
render(reduxWrapper(<LoginPage {...props} />));
render(reduxWrapper(<IntlLoginPage {...props} />));
fireEvent.change(screen.getByText(
'',
@@ -112,7 +114,7 @@ describe('LoginPage', () => {
it('should not dispatch loginRequest on empty form submission', () => {
store.dispatch = jest.fn(store.dispatch);
render(reduxWrapper(<LoginPage {...props} />));
render(reduxWrapper(<IntlLoginPage {...props} />));
fireEvent.click(screen.getByText(
'',
@@ -131,7 +133,7 @@ describe('LoginPage', () => {
});
store.dispatch = jest.fn(store.dispatch);
render(reduxWrapper(<LoginPage {...props} />));
render(reduxWrapper(<IntlLoginPage {...props} />));
fireEvent.click(screen.getByText(
'',
{ selector: '.btn-brand' },
@@ -145,7 +147,7 @@ describe('LoginPage', () => {
it('should match state for invalid email (less than 2 characters), on form submission', () => {
store.dispatch = jest.fn(store.dispatch);
render(reduxWrapper(<LoginPage {...props} />));
render(reduxWrapper(<IntlLoginPage {...props} />));
fireEvent.change(screen.getByText(
'',
@@ -165,7 +167,7 @@ describe('LoginPage', () => {
});
it('should show error messages for required fields on empty form submission', () => {
const { container } = render(reduxWrapper(<LoginPage {...props} />));
const { container } = render(reduxWrapper(<IntlLoginPage {...props} />));
fireEvent.click(screen.getByText(
'',
{ selector: '.btn-brand' },
@@ -179,7 +181,7 @@ describe('LoginPage', () => {
});
it('should run frontend validations for emailOrUsername field on form submission', () => {
const { container } = render(reduxWrapper(<LoginPage {...props} />));
const { container } = render(reduxWrapper(<IntlLoginPage {...props} />));
fireEvent.change(screen.getByText(
'',
@@ -198,7 +200,7 @@ describe('LoginPage', () => {
it('should reset field related error messages on onFocus event', async () => {
store.dispatch = jest.fn(store.dispatch);
render(reduxWrapper(<LoginPage {...props} />));
render(reduxWrapper(<IntlLoginPage {...props} />));
await act(async () => {
// clicking submit button with empty fields to make the errors appear
@@ -227,7 +229,7 @@ describe('LoginPage', () => {
// ******** test form buttons and links ********
it('should match default button state', () => {
render(reduxWrapper(<LoginPage {...props} />));
render(reduxWrapper(<IntlLoginPage {...props} />));
expect(screen.getByText('Sign in')).toBeDefined();
});
@@ -240,7 +242,7 @@ describe('LoginPage', () => {
},
});
render(reduxWrapper(<LoginPage {...props} />));
render(reduxWrapper(<IntlLoginPage {...props} />));
expect(screen.getByText(
'pending',
@@ -248,7 +250,7 @@ describe('LoginPage', () => {
});
it('should show forgot password link', () => {
render(reduxWrapper(<LoginPage {...props} />));
render(reduxWrapper(<IntlLoginPage {...props} />));
expect(screen.getByText(
'Forgot password',
@@ -268,7 +270,7 @@ describe('LoginPage', () => {
},
});
render(reduxWrapper(<LoginPage {...props} />));
render(reduxWrapper(<IntlLoginPage {...props} />));
expect(screen.getByText(
'',
{ selector: `#${ssoProvider.id}` },
@@ -290,7 +292,7 @@ describe('LoginPage', () => {
},
});
const { queryByText } = render(reduxWrapper(<LoginPage {...props} />));
const { queryByText } = render(reduxWrapper(<IntlLoginPage {...props} />));
expect(queryByText('Company or school credentials')).toBeNull();
expect(queryByText('Or sign in with:')).toBeNull();
expect(queryByText('Institution/campus credentials')).toBeNull();
@@ -310,7 +312,7 @@ describe('LoginPage', () => {
},
});
const { queryByText } = render(reduxWrapper(<LoginPage {...props} />));
const { queryByText } = render(reduxWrapper(<IntlLoginPage {...props} />));
expect(queryByText('Company or school credentials')).toBeNull();
expect(queryByText('Or sign in with:')).toBeNull();
});
@@ -330,7 +332,7 @@ describe('LoginPage', () => {
},
});
const { queryByText } = render(reduxWrapper(<LoginPage {...props} />));
const { queryByText } = render(reduxWrapper(<IntlLoginPage {...props} />));
expect(queryByText('Or sign in with:')).toBeDefined();
expect(queryByText('Company or school credentials')).toBeDefined();
expect(queryByText('Institution/campus credentials')).toBeDefined();
@@ -355,7 +357,7 @@ describe('LoginPage', () => {
},
});
const { queryByText } = render(reduxWrapper(<LoginPage {...props} />));
const { queryByText } = render(reduxWrapper(<IntlLoginPage {...props} />));
expect(queryByText('Or sign in with:')).toBeDefined();
expect(queryByText('Company or school credentials')).toBeNull();
expect(queryByText('Institution/campus credentials')).toBeDefined();
@@ -383,7 +385,7 @@ describe('LoginPage', () => {
},
});
const { queryByText } = render(reduxWrapper(<LoginPage {...props} />));
const { queryByText } = render(reduxWrapper(<IntlLoginPage {...props} />));
expect(queryByText('Or sign in with:')).toBeDefined();
expect(queryByText('Institution/campus credentials')).toBeDefined();
@@ -403,7 +405,7 @@ describe('LoginPage', () => {
},
});
const { queryByText } = render(reduxWrapper(<LoginPage {...props} />));
const { queryByText } = render(reduxWrapper(<IntlLoginPage {...props} />));
expect(queryByText('Or sign in with:')).toBeNull();
expect(queryByText('Institution/campus credentials')).toBeNull();
expect(queryByText('Company or school credentials')).toBeNull();
@@ -421,7 +423,7 @@ describe('LoginPage', () => {
},
});
const { queryByText } = render(reduxWrapper(<LoginPage {...props} />));
const { queryByText } = render(reduxWrapper(<IntlLoginPage {...props} />));
expect(queryByText('Or sign in with:')).toBeDefined();
expect(queryByText('Company or school credentials')).toBeNull();
expect(queryByText('Institution/campus credentials')).toBeDefined();
@@ -444,7 +446,7 @@ describe('LoginPage', () => {
},
});
render(reduxWrapper(<LoginPage {...props} />));
render(reduxWrapper(<IntlLoginPage {...props} />));
expect(screen.getByText(
'',
{ selector: '#login-failure-alert' },
@@ -468,7 +470,7 @@ describe('LoginPage', () => {
+ 'linked '}${ getConfig().SITE_NAME } account. To link your accounts, sign in now using your ${
getConfig().SITE_NAME } password.`;
render(reduxWrapper(<LoginPage {...props} />));
render(reduxWrapper(<IntlLoginPage {...props} />));
expect(screen.getByText(
'',
{ selector: '#tpa-alert' },
@@ -487,7 +489,7 @@ describe('LoginPage', () => {
},
},
});
render(reduxWrapper(<LoginPage {...props} />));
render(reduxWrapper(<IntlLoginPage {...props} />));
expect(screen.getByText(
'',
{ selector: '#login-failure-alert' },
@@ -504,7 +506,7 @@ describe('LoginPage', () => {
},
});
render(reduxWrapper(<LoginPage {...props} />));
render(reduxWrapper(<IntlLoginPage {...props} />));
expect(screen.getByText(
'',
{ selector: '#login-failure-alert' },
@@ -528,7 +530,7 @@ describe('LoginPage', () => {
delete window.location;
window.location = { href: getConfig().BASE_URL };
render(reduxWrapper(<LoginPage {...props} />));
render(reduxWrapper(<IntlLoginPage {...props} />));
await waitFor(() => {
expect(window.location.href).toBe(dashboardURL);
}, { timeout: 1100 });
@@ -557,7 +559,7 @@ describe('LoginPage', () => {
delete window.location;
window.location = { href: getConfig().BASE_URL };
render(reduxWrapper(<LoginPage {...props} />));
render(reduxWrapper(<IntlLoginPage {...props} />));
await waitFor(() => {
expect(window.location.href).toBe(getConfig().LMS_BASE_URL + authCompleteUrl);
}, { timeout: 1100 });
@@ -578,7 +580,7 @@ describe('LoginPage', () => {
delete window.location;
window.location = { href: getConfig().BASE_URL };
render(reduxWrapper(<LoginPage {...props} />));
render(reduxWrapper(<IntlLoginPage {...props} />));
fireEvent.click(screen.getByText(
'',
@@ -609,7 +611,7 @@ describe('LoginPage', () => {
delete window.location;
window.location = { href: getConfig().BASE_URL };
render(reduxWrapper(<LoginPage {...props} />));
render(reduxWrapper(<IntlLoginPage {...props} />));
await waitFor(() => {
expect(window.location.href).toBe(getConfig().LMS_BASE_URL + finishAuthUrl);
}, { timeout: 1100 });
@@ -633,7 +635,7 @@ describe('LoginPage', () => {
delete window.location;
window.location = { href: getConfig().BASE_URL.concat(LOGIN_PAGE), search: `?next=/dashboard&tpa_hint=${ssoProvider.id}` };
render(reduxWrapper(<LoginPage {...props} />));
render(reduxWrapper(<IntlLoginPage {...props} />));
expect(screen.getByText(
'',
{ selector: `#${ssoProvider.id}` },
@@ -660,7 +662,7 @@ describe('LoginPage', () => {
delete window.location;
window.location = { href: getConfig().BASE_URL.concat(LOGIN_PAGE), search: `?next=/dashboard&tpa_hint=${ssoProvider.id}` };
const { container } = render(reduxWrapper(<LoginPage {...props} />));
const { container } = render(reduxWrapper(<IntlLoginPage {...props} />));
expect(container.querySelector('.react-loading-skeleton')).toBeTruthy();
});
@@ -682,7 +684,7 @@ describe('LoginPage', () => {
window.location = { href: getConfig().BASE_URL.concat(LOGIN_PAGE), search: `?next=/dashboard&tpa_hint=${secondaryProviders.id}` };
secondaryProviders.iconImage = null;
render(reduxWrapper(<LoginPage {...props} />));
render(reduxWrapper(<IntlLoginPage {...props} />));
expect(window.location.href).toEqual(getConfig().LMS_BASE_URL + secondaryProviders.loginUrl);
});
@@ -702,7 +704,7 @@ describe('LoginPage', () => {
delete window.location;
window.location = { href: getConfig().BASE_URL.concat(LOGIN_PAGE), search: '?next=/dashboard&tpa_hint=invalid' };
const { container } = render(reduxWrapper(<LoginPage {...props} />));
const { container } = render(reduxWrapper(<IntlLoginPage {...props} />));
expect(container.querySelector(`#${ssoProvider.id}`).querySelector('#provider-name').textContent).toEqual(`${ssoProvider.name}`);
mergeConfig({
@@ -726,7 +728,7 @@ describe('LoginPage', () => {
delete window.location;
window.location = { href: getConfig().BASE_URL.concat(LOGIN_PAGE), search: `?tpa_hint=${ssoProvider.id}` };
render(reduxWrapper(<LoginPage {...props} />));
render(reduxWrapper(<IntlLoginPage {...props} />));
expect(screen.getByText(
'Show me other ways to sign in or register',
).textContent).toBeDefined();
@@ -752,7 +754,7 @@ describe('LoginPage', () => {
delete window.location;
window.location = { href: getConfig().BASE_URL.concat(LOGIN_PAGE), search: `?tpa_hint=${ssoProvider.id}` };
render(reduxWrapper(<LoginPage {...props} />));
render(reduxWrapper(<IntlLoginPage {...props} />));
expect(screen.getByText(
'Show me other ways to sign in',
).textContent).toBeDefined();
@@ -761,7 +763,7 @@ describe('LoginPage', () => {
// ******** miscellaneous tests ********
it('should send page event when login page is rendered', () => {
render(reduxWrapper(<LoginPage {...props} />));
render(reduxWrapper(<IntlLoginPage {...props} />));
expect(sendPageEvent).toHaveBeenCalledWith('login_and_registration', 'login', { app_name: APP_NAME });
});
@@ -775,7 +777,7 @@ describe('LoginPage', () => {
});
store.dispatch = jest.fn(store.dispatch);
render(reduxWrapper(<LoginPage {...props} />));
render(reduxWrapper(<IntlLoginPage {...props} />));
expect(store.dispatch).toHaveBeenCalledWith(backupLoginFormBegin(
{
formFields: {
@@ -789,7 +791,7 @@ describe('LoginPage', () => {
});
it('should send track event when forgot password link is clicked', () => {
render(reduxWrapper(<LoginPage {...props} />));
render(reduxWrapper(<IntlLoginPage {...props} />));
fireEvent.click(screen.getByText(
'Forgot password',
{ selector: '#forgot-password' },
@@ -808,7 +810,7 @@ describe('LoginPage', () => {
});
store.dispatch = jest.fn(store.dispatch);
render(reduxWrapper(<LoginPage {...props} />));
render(reduxWrapper(<IntlLoginPage {...props} />));
expect(store.dispatch).toHaveBeenCalledWith(backupLoginFormBegin(
{
formFields: {
@@ -837,7 +839,7 @@ describe('LoginPage', () => {
},
});
const { container } = render(reduxWrapper(<LoginPage {...props} />));
const { container } = render(reduxWrapper(<IntlLoginPage {...props} />));
expect(container.querySelector('input#emailOrUsername').value).toEqual('john_doe');
expect(container.querySelector('input#password').value).toEqual('test-password');
});

View File

@@ -1,8 +1,9 @@
import React from 'react';
import { Provider } from 'react-redux';
import { getConfig, mergeConfig } from '@edx/frontend-platform';
import { sendPageEvent, sendTrackEvent } from '@edx/frontend-platform/analytics';
import { configure, IntlProvider } from '@edx/frontend-platform/i18n';
import { configure, injectIntl, 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';
@@ -27,6 +28,7 @@ jest.mock('@edx/frontend-platform/auth');
jest.mock('../register/data/optimizelyExperiment/useAutoGeneratedUsernameExperimentVariation', () => jest.fn());
const mockStore = configureStore();
const IntlLogistration = injectIntl(Logistration);
describe('Logistration', () => {
let store = {};
@@ -100,7 +102,7 @@ describe('Logistration', () => {
});
it('should do nothing when user clicks on the same tab (login/register) again', () => {
const { container } = render(reduxWrapper(<Logistration />));
const { container } = render(reduxWrapper(<IntlLogistration />));
// While staying on the registration form, clicking the register tab again
fireEvent.click(container.querySelector('a[data-rb-event-key="/register"]'));
@@ -112,14 +114,14 @@ describe('Logistration', () => {
ALLOW_PUBLIC_ACCOUNT_CREATION: true,
});
const { container } = render(reduxWrapper(<Logistration />));
const { container } = render(reduxWrapper(<IntlLogistration />));
expect(container.querySelector('RegistrationPage')).toBeDefined();
});
it('should render login page', () => {
const props = { selectedPage: LOGIN_PAGE };
const { container } = render(reduxWrapper(<Logistration {...props} />));
const { container } = render(reduxWrapper(<IntlLogistration {...props} />));
expect(container.querySelector('LoginPage')).toBeDefined();
});
@@ -130,7 +132,7 @@ describe('Logistration', () => {
});
let props = { selectedPage: LOGIN_PAGE };
const { rerender } = render(reduxWrapper(<Logistration {...props} />));
const { rerender } = render(reduxWrapper(<IntlLogistration {...props} />));
// verifying sign in heading
expect(screen.getByRole('heading', { level: 3 }).textContent).toEqual('Sign in');
@@ -138,7 +140,7 @@ describe('Logistration', () => {
// register page is still accessible when SHOW_REGISTRATION_LINKS is false
// but it needs to be accessed directly
props = { selectedPage: REGISTER_PAGE };
rerender(reduxWrapper(<Logistration {...props} />));
rerender(reduxWrapper(<IntlLogistration {...props} />));
// verifying register heading
expect(screen.getByRole('heading', { level: 3 }).textContent).toEqual('Register');
@@ -165,7 +167,7 @@ describe('Logistration', () => {
});
const props = { selectedPage: LOGIN_PAGE };
const { container } = render(reduxWrapper(<Logistration {...props} />));
const { container } = render(reduxWrapper(<IntlLogistration {...props} />));
// verifying sign in heading for institution login false
expect(screen.getByRole('heading', { level: 3 }).textContent).toEqual('Sign in');
@@ -195,7 +197,7 @@ describe('Logistration', () => {
});
const props = { selectedPage: LOGIN_PAGE };
render(reduxWrapper(<Logistration {...props} />));
render(reduxWrapper(<IntlLogistration {...props} />));
expect(screen.getByText('Institution/campus credentials')).toBeDefined();
// on clicking "Institution/campus credentials" button, it should display institution login page
@@ -226,7 +228,7 @@ describe('Logistration', () => {
});
const props = { selectedPage: LOGIN_PAGE };
render(reduxWrapper(<Logistration {...props} />));
render(reduxWrapper(<IntlLogistration {...props} />));
fireEvent.click(screen.getByText('Institution/campus credentials'));
expect(sendTrackEvent).toHaveBeenCalledWith('edx.bi.institution_login_form.toggled', { category: 'user-engagement', app_name: APP_NAME });
@@ -258,7 +260,7 @@ describe('Logistration', () => {
delete window.location;
window.location = { hostname: getConfig().SITE_NAME, href: getConfig().BASE_URL };
render(reduxWrapper(<Logistration />));
render(reduxWrapper(<IntlLogistration />));
fireEvent.click(screen.getByText('Institution/campus credentials'));
expect(screen.getByText('Test University')).toBeDefined();
@@ -269,7 +271,7 @@ describe('Logistration', () => {
it('should fire action to backup registration form on tab click', () => {
store.dispatch = jest.fn(store.dispatch);
const { container } = render(reduxWrapper(<Logistration />));
const { container } = render(reduxWrapper(<IntlLogistration />));
fireEvent.click(container.querySelector('a[data-rb-event-key="/login"]'));
expect(store.dispatch).toHaveBeenCalledWith(backupRegistrationForm());
});
@@ -277,14 +279,14 @@ describe('Logistration', () => {
it('should fire action to backup login form on tab click', () => {
store.dispatch = jest.fn(store.dispatch);
const props = { selectedPage: LOGIN_PAGE };
const { container } = render(reduxWrapper(<Logistration {...props} />));
const { container } = render(reduxWrapper(<IntlLogistration {...props} />));
fireEvent.click(container.querySelector('a[data-rb-event-key="/register"]'));
expect(store.dispatch).toHaveBeenCalledWith(backupLoginForm());
});
it('should clear tpa context errorMessage tab click', () => {
store.dispatch = jest.fn(store.dispatch);
const { container } = render(reduxWrapper(<Logistration />));
const { container } = render(reduxWrapper(<IntlLogistration />));
fireEvent.click(container.querySelector('a[data-rb-event-key="/login"]'));
expect(store.dispatch).toHaveBeenCalledWith(clearThirdPartyAuthContextErrorMessage());
});

View File

@@ -1,9 +1,10 @@
import React from 'react';
import { Provider } from 'react-redux';
import { getConfig, mergeConfig } from '@edx/frontend-platform';
import { identifyAuthenticatedUser, sendTrackEvent } from '@edx/frontend-platform/analytics';
import { getAuthenticatedUser } from '@edx/frontend-platform/auth';
import { configure, IntlProvider } from '@edx/frontend-platform/i18n';
import { configure, injectIntl, IntlProvider } from '@edx/frontend-platform/i18n';
import {
fireEvent, render, screen, waitFor,
} from '@testing-library/react';
@@ -23,6 +24,7 @@ import {
import { saveUserProfile } from '../data/actions';
import ProgressiveProfiling from '../ProgressiveProfiling';
const IntlProgressiveProfilingPage = injectIntl(ProgressiveProfiling);
const mockStore = configureStore();
mockTagular();
@@ -122,7 +124,7 @@ describe('ProgressiveProfilingTests', () => {
mergeConfig({
AUTHN_PROGRESSIVE_PROFILING_SUPPORT_LINK: '',
});
const { queryByRole } = render(reduxWrapper(<ProgressiveProfiling />));
const { queryByRole } = render(reduxWrapper(<IntlProgressiveProfilingPage />));
const button = queryByRole('button', { name: /learn more about how we use this information/i });
expect(button).toBeNull();
@@ -133,7 +135,7 @@ describe('ProgressiveProfilingTests', () => {
AUTHN_PROGRESSIVE_PROFILING_SUPPORT_LINK: 'http://localhost:1999/support',
});
const { getByText } = render(reduxWrapper(<ProgressiveProfiling />));
const { getByText } = render(reduxWrapper(<IntlProgressiveProfilingPage />));
const learnMoreButton = getByText('Learn more about how we use this information.');
@@ -143,7 +145,7 @@ describe('ProgressiveProfilingTests', () => {
it('should open modal on pressing skip for now button', () => {
delete window.location;
window.location = { href: getConfig().BASE_URL.concat(AUTHN_PROGRESSIVE_PROFILING) };
const { getByRole } = render(reduxWrapper(<ProgressiveProfiling />));
const { getByRole } = render(reduxWrapper(<IntlProgressiveProfilingPage />));
const skipButton = getByRole('button', { name: /skip for now/i });
fireEvent.click(skipButton);
@@ -159,7 +161,7 @@ describe('ProgressiveProfilingTests', () => {
// ******** test event functionality ********
it('should make identify call to segment on progressive profiling page', () => {
render(reduxWrapper(<ProgressiveProfiling />));
render(reduxWrapper(<IntlProgressiveProfilingPage />));
expect(identifyAuthenticatedUser).toHaveBeenCalledWith(3);
expect(identifyAuthenticatedUser).toHaveBeenCalled();
@@ -169,7 +171,7 @@ describe('ProgressiveProfilingTests', () => {
mergeConfig({
AUTHN_PROGRESSIVE_PROFILING_SUPPORT_LINK: 'http://localhost:1999/support',
});
render(reduxWrapper(<ProgressiveProfiling />));
render(reduxWrapper(<IntlProgressiveProfilingPage />));
const supportLink = screen.getByRole('link', { name: /learn more about how we use this information/i });
fireEvent.click(supportLink);
@@ -188,7 +190,7 @@ describe('ProgressiveProfilingTests', () => {
};
delete window.location;
window.location = { href: getConfig().BASE_URL.concat(AUTHN_PROGRESSIVE_PROFILING) };
render(reduxWrapper(<ProgressiveProfiling />));
render(reduxWrapper(<IntlProgressiveProfilingPage />));
const nextButton = screen.getByText('Next');
fireEvent.click(nextButton);
@@ -204,7 +206,7 @@ describe('ProgressiveProfilingTests', () => {
extended_profile: [{ field_name: 'company', field_value: 'test company' }],
};
store.dispatch = jest.fn(store.dispatch);
const { getByLabelText, getByText } = render(reduxWrapper(<ProgressiveProfiling />));
const { getByLabelText, getByText } = render(reduxWrapper(<IntlProgressiveProfilingPage />));
const genderSelect = getByLabelText('Gender');
const companyInput = getByLabelText('Company');
@@ -226,7 +228,7 @@ describe('ProgressiveProfilingTests', () => {
},
});
const { container } = render(reduxWrapper(<ProgressiveProfiling />));
const { container } = render(reduxWrapper(<IntlProgressiveProfilingPage />));
const errorElement = container.querySelector('#pp-page-errors');
expect(errorElement).toBeTruthy();
@@ -242,7 +244,7 @@ describe('ProgressiveProfilingTests', () => {
href: getConfig().BASE_URL,
};
render(reduxWrapper(<ProgressiveProfiling />));
render(reduxWrapper(<IntlProgressiveProfilingPage />));
expect(window.location.href).toEqual(DASHBOARD_URL);
});
@@ -263,7 +265,7 @@ describe('ProgressiveProfilingTests', () => {
eventData,
},
});
const { container } = render(reduxWrapper(<ProgressiveProfiling />));
const { container } = render(reduxWrapper(<IntlProgressiveProfilingPage />));
const nextButton = container.querySelector('button.btn-brand');
expect(nextButton.textContent).toEqual('Next');
@@ -293,7 +295,7 @@ describe('ProgressiveProfilingTests', () => {
},
});
const { container } = render(reduxWrapper(<ProgressiveProfiling />));
const { container } = render(reduxWrapper(<IntlProgressiveProfilingPage />));
const nextButton = container.querySelector('button.btn-brand');
expect(nextButton.textContent).toEqual('Submit');
@@ -329,7 +331,7 @@ describe('ProgressiveProfilingTests', () => {
href: getConfig().BASE_URL.concat(AUTHN_PROGRESSIVE_PROFILING),
search: `?host=${host}&variant=${EMBEDDED}`,
};
render(reduxWrapper(<ProgressiveProfiling />));
render(reduxWrapper(<IntlProgressiveProfilingPage />));
const skipLinkButton = screen.getByText('Skip for now');
fireEvent.click(skipLinkButton);
@@ -354,7 +356,7 @@ describe('ProgressiveProfilingTests', () => {
},
});
const { container } = render(reduxWrapper(<ProgressiveProfiling />));
const { container } = render(reduxWrapper(<IntlProgressiveProfilingPage />));
const tpaSpinnerElement = container.querySelector('#tpa-spinner');
expect(tpaSpinnerElement).toBeTruthy();
@@ -374,7 +376,7 @@ describe('ProgressiveProfilingTests', () => {
href: getConfig().BASE_URL.concat(AUTHN_PROGRESSIVE_PROFILING),
search: `?host=${host}`,
};
render(reduxWrapper(<ProgressiveProfiling />));
render(reduxWrapper(<IntlProgressiveProfilingPage />));
const submitButton = screen.getByText('Next');
fireEvent.click(submitButton);
@@ -389,7 +391,7 @@ describe('ProgressiveProfilingTests', () => {
search: `?variant=${EMBEDDED}&host=${host}`,
};
const { container } = render(reduxWrapper(<ProgressiveProfiling />));
const { container } = render(reduxWrapper(<IntlProgressiveProfilingPage />));
const genderField = container.querySelector('#gender');
expect(genderField).toBeTruthy();
@@ -410,7 +412,7 @@ describe('ProgressiveProfilingTests', () => {
},
});
render(reduxWrapper(<ProgressiveProfiling />));
render(reduxWrapper(<IntlProgressiveProfilingPage />));
expect(window.location.href).toBe(DASHBOARD_URL);
});
@@ -441,7 +443,7 @@ describe('ProgressiveProfilingTests', () => {
},
});
render(reduxWrapper(<ProgressiveProfiling />));
render(reduxWrapper(<IntlProgressiveProfilingPage />));
const submitButton = screen.getByText('Submit');
fireEvent.click(submitButton);
await waitFor(() => {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -31,7 +31,6 @@ import {
FORM_SUBMISSION_ERROR,
TPA_AUTHENTICATION_FAILURE,
} from './data/constants';
import useRecaptchaSubmission from './data/hooks';
import { AUTO_GENERATED_USERNAME_REGISTRATION_EXP_VARIATION, NOT_INITIALIZED } from './data/optimizelyExperiment/helper';
import useAutoGeneratedUsernameExperimentVariation from './data/optimizelyExperiment/useAutoGeneratedUsernameExperimentVariation';
import getBackendValidations from './data/selectors';
@@ -62,7 +61,6 @@ const RegistrationPage = (props) => {
const { formatMessage } = useIntl();
const dispatch = useDispatch();
const { executeWithFallback } = useRecaptchaSubmission('submit_registration_form');
const registrationEmbedded = isHostAvailableInQueryParams();
const platformName = getConfig().SITE_NAME;
const flags = {
@@ -232,7 +230,7 @@ const RegistrationPage = (props) => {
}
};
const registerUser = async () => {
const registerUser = () => {
const totalRegistrationTime = (Date.now() - formStartTime) / 1000;
let payload = { ...formFields, app_name: APP_NAME };
@@ -252,7 +250,7 @@ const RegistrationPage = (props) => {
fieldDescriptions,
formatMessage,
);
setErrors({ ...fieldErrors, captchaError: '' });
setErrors({ ...fieldErrors });
dispatch(setEmailSuggestionInStore(emailSuggestion));
// returning if not valid
@@ -261,28 +259,15 @@ const RegistrationPage = (props) => {
return;
}
let recaptchaToken = null;
try {
recaptchaToken = await executeWithFallback();
} catch (err) {
setErrors(prev => ({
...prev,
captchaError: err.message,
}));
return;
}
// Preparing payload for submission
payload = prepareRegistrationPayload(
payload,
configurableFormFields,
flags.showMarketingEmailOptInCheckbox,
totalRegistrationTime,
queryParams,
);
if (recaptchaToken) {
payload = { ...payload, captcha_token: recaptchaToken };
}
queryParams);
// making register call
dispatch(registerNewUser(payload));
};
@@ -410,11 +395,6 @@ const RegistrationPage = (props) => {
fieldDescriptions={fieldDescriptions}
countriesCodesList={countriesCodesList}
/>
{errors?.captchaError && (
<div className="mt-3 pgn__form-text-invalid pgn__form-text">
{errors.captchaError}
</div>
)}
<StatefulButton
id="register-user"
name="register-user"

View File

@@ -1,9 +1,10 @@
import React from 'react';
import { Provider } from 'react-redux';
import { getConfig, mergeConfig } from '@edx/frontend-platform';
import { sendPageEvent, sendTrackEvent } from '@edx/frontend-platform/analytics';
import {
configure, getLocale, IntlProvider,
configure, getLocale, injectIntl, IntlProvider,
} from '@edx/frontend-platform/i18n';
import { fireEvent, render, waitFor } from '@testing-library/react';
import { mockNavigate, BrowserRouter as Router } from 'react-router-dom';
@@ -17,7 +18,6 @@ import {
} from './data/actions';
import { INTERNAL_SERVER_ERROR } from './data/constants';
import mockTagular from '../cohesion/utils';
import useRecaptchaSubmission from './data/hooks';
import { NOT_INITIALIZED } from './data/optimizelyExperiment/helper';
import useAutoGeneratedUsernameExperimentVariation
from './data/optimizelyExperiment/useAutoGeneratedUsernameExperimentVariation';
@@ -35,13 +35,8 @@ jest.mock('@edx/frontend-platform/i18n', () => ({
getLocale: jest.fn(),
}));
jest.mock('./data/optimizelyExperiment/useAutoGeneratedUsernameExperimentVariation', () => jest.fn());
jest.mock('./data/hooks', () => ({
__esModule: true,
default: jest.fn(() => ({
executeWithFallback: jest.fn(),
})),
}));
const IntlRegistrationPage = injectIntl(RegistrationPage);
const mockStore = configureStore();
mockTagular();
@@ -141,11 +136,6 @@ describe('RegistrationPage', () => {
};
window.location = { search: '' };
useAutoGeneratedUsernameExperimentVariation.mockReturnValue(NOT_INITIALIZED);
useRecaptchaSubmission.mockReturnValue({
executeWithFallback: jest.fn().mockResolvedValue(null),
isReady: true,
isLoading: false,
});
});
afterEach(() => {
@@ -187,7 +177,7 @@ describe('RegistrationPage', () => {
// ******** test registration form submission ********
it('should submit form for valid input', async () => {
it('should submit form for valid input', () => {
getLocale.mockImplementation(() => ('en-us'));
jest.spyOn(global.Date, 'now').mockImplementation(() => 0);
@@ -207,17 +197,15 @@ describe('RegistrationPage', () => {
};
store.dispatch = jest.fn(store.dispatch);
const { getByLabelText, container } = render(routerWrapper(reduxWrapper(<RegistrationPage {...props} />)));
const { getByLabelText, container } = render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
populateRequiredFields(getByLabelText, payload);
const button = container.querySelector('button.btn-brand');
fireEvent.click(button);
await waitFor(() => {
expect(store.dispatch).toHaveBeenCalledWith(registerNewUser({ ...payload, country: 'PK' }));
});
expect(store.dispatch).toHaveBeenCalledWith(registerNewUser({ ...payload, country: 'PK' }));
});
it('should submit form without password field when current provider is present', async () => {
it('should submit form without password field when current provider is present', () => {
jest.spyOn(global.Date, 'now').mockImplementation(() => 0);
const formPayload = {
@@ -242,14 +230,12 @@ describe('RegistrationPage', () => {
},
});
store.dispatch = jest.fn(store.dispatch);
const { getByLabelText, container } = render(routerWrapper(reduxWrapper(<RegistrationPage {...props} />)));
const { getByLabelText, container } = render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
populateRequiredFields(getByLabelText, formPayload, true);
const button = container.querySelector('button.btn-brand');
fireEvent.click(button);
await waitFor(() => {
expect(store.dispatch).toHaveBeenCalledWith(registerNewUser({ ...formPayload, country: 'PK' }));
});
expect(store.dispatch).toHaveBeenCalledWith(registerNewUser({ ...formPayload, country: 'PK' }));
});
it('should display an error when form is submitted with an invalid email', () => {
@@ -267,7 +253,7 @@ describe('RegistrationPage', () => {
};
store.dispatch = jest.fn(store.dispatch);
const { getByLabelText, container } = render(routerWrapper(reduxWrapper(<RegistrationPage {...props} />)));
const { getByLabelText, container } = render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
populateRequiredFields(getByLabelText, formPayload, true);
const button = container.querySelector('button.btn-brand');
@@ -292,7 +278,7 @@ describe('RegistrationPage', () => {
};
store.dispatch = jest.fn(store.dispatch);
const { getByLabelText, container } = render(routerWrapper(reduxWrapper(<RegistrationPage {...props} />)));
const { getByLabelText, container } = render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
populateRequiredFields(getByLabelText, formPayload, true);
const button = container.querySelector('button.btn-brand');
fireEvent.click(button);
@@ -300,7 +286,7 @@ describe('RegistrationPage', () => {
expect(validationErrors.textContent).toContain(usernameError);
});
it('should submit form with marketing email opt in value', async () => {
it('should submit form with marketing email opt in value', () => {
mergeConfig({
MARKETING_EMAILS_OPT_IN: 'true',
});
@@ -320,20 +306,18 @@ describe('RegistrationPage', () => {
};
store.dispatch = jest.fn(store.dispatch);
const { getByLabelText, container } = render(routerWrapper(reduxWrapper(<RegistrationPage {...props} />)));
const { getByLabelText, container } = render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
populateRequiredFields(getByLabelText, payload);
const button = container.querySelector('button.btn-brand');
fireEvent.click(button);
await waitFor(() => {
expect(store.dispatch).toHaveBeenCalledWith(registerNewUser({ ...payload, country: 'PK' }));
});
expect(store.dispatch).toHaveBeenCalledWith(registerNewUser({ ...payload, country: 'PK' }));
mergeConfig({
MARKETING_EMAILS_OPT_IN: '',
});
});
it('should submit form without UsernameField when autoGeneratedUsernameEnabled is true', async () => {
it('should submit form without UsernameField when autoGeneratedUsernameEnabled is true', () => {
mergeConfig({
ENABLE_AUTO_GENERATED_USERNAME: true,
});
@@ -349,13 +333,11 @@ describe('RegistrationPage', () => {
};
store.dispatch = jest.fn(store.dispatch);
const { getByLabelText, container } = render(routerWrapper(reduxWrapper(<RegistrationPage {...props} />)));
const { getByLabelText, container } = render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
populateRequiredFields(getByLabelText, payload, false, true);
const button = container.querySelector('button.btn-brand');
fireEvent.click(button);
await waitFor(() => {
expect(store.dispatch).toHaveBeenCalledWith(registerNewUser({ ...payload, country: 'PK' }));
});
expect(store.dispatch).toHaveBeenCalledWith(registerNewUser({ ...payload, country: 'PK' }));
mergeConfig({
ENABLE_AUTO_GENERATED_USERNAME: false,
});
@@ -366,7 +348,7 @@ describe('RegistrationPage', () => {
ENABLE_AUTO_GENERATED_USERNAME: true,
});
const { queryByLabelText } = render(routerWrapper(reduxWrapper(<RegistrationPage {...props} />)));
const { queryByLabelText } = render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
expect(queryByLabelText('Username')).toBeNull();
mergeConfig({
@@ -377,7 +359,7 @@ describe('RegistrationPage', () => {
it('should not dispatch registerNewUser on empty form Submission', () => {
store.dispatch = jest.fn(store.dispatch);
const { container } = render(routerWrapper(reduxWrapper(<RegistrationPage {...props} />)));
const { container } = render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
const button = container.querySelector('button.btn-brand');
fireEvent.click(button);
@@ -387,126 +369,8 @@ describe('RegistrationPage', () => {
// ******** test registration form validations ********
it('should submit form with valid reCAPTCHA token', async () => {
getLocale.mockImplementation(() => ('en-us'));
jest.spyOn(global.Date, 'now').mockImplementation(() => 0);
useRecaptchaSubmission.mockReturnValue({
executeWithFallback: jest.fn().mockResolvedValue('mock-recaptcha-token'),
isReady: true,
isLoading: false,
});
const payload = {
name: 'John Doe',
username: 'john_doe',
email: 'john.doe@gmail.com',
password: 'password1',
country: 'Pakistan',
honor_code: true,
total_registration_time: 0,
app_name: APP_NAME,
};
store.dispatch = jest.fn(store.dispatch);
const { getByLabelText, container } = render(routerWrapper(reduxWrapper(<RegistrationPage {...props} />)));
populateRequiredFields(getByLabelText, payload);
const button = container.querySelector('button.btn-brand');
fireEvent.click(button);
await waitFor(() => {
const actions = store.dispatch.mock.calls.map(call => call[0]);
const registerAction = actions.find(a => a.type === registerNewUser().type);
expect(registerAction).toBeTruthy();
expect(registerAction.payload).toMatchObject({
registrationInfo: {
...payload,
country: 'PK',
captcha_token: 'mock-recaptcha-token',
},
});
});
});
it('should display error when reCAPTCHA verification fails', async () => {
getLocale.mockImplementation(() => ('en-us'));
jest.spyOn(global.Date, 'now').mockImplementation(() => 0);
useRecaptchaSubmission.mockReturnValue({
executeWithFallback: jest.fn().mockRejectedValue(new Error('CAPTCHA verification failed.')),
isReady: true,
isLoading: false,
});
const payload = {
name: 'John Doe',
username: 'john_doe',
email: 'john.doe@gmail.com',
password: 'password1',
country: 'Pakistan',
honor_code: true,
total_registration_time: 0,
};
store.dispatch = jest.fn(store.dispatch);
const { getByLabelText, container } = render(routerWrapper(reduxWrapper(<RegistrationPage {...props} />)));
populateRequiredFields(getByLabelText, payload);
const button = container.querySelector('button.btn-brand');
fireEvent.click(button);
await waitFor(() => {
const captchaError = container.querySelector('.pgn__form-text-invalid');
expect(captchaError.textContent).toContain('CAPTCHA verification failed.');
});
expect(store.dispatch).not.toHaveBeenCalledWith(expect.objectContaining({
type: registerNewUser().type,
}));
});
it('should submit without reCAPTCHA token if reCAPTCHA is disabled', async () => {
getLocale.mockImplementation(() => ('en-us'));
jest.spyOn(global.Date, 'now').mockImplementation(() => 0);
useRecaptchaSubmission.mockReturnValue({
executeWithFallback: jest.fn().mockResolvedValue(null),
isReady: true,
isLoading: false,
});
const payload = {
name: 'John Doe',
username: 'john_doe',
email: 'john.doe@gmail.com',
password: 'password1',
country: 'Pakistan',
honor_code: true,
total_registration_time: 0,
app_name: APP_NAME,
};
store.dispatch = jest.fn(store.dispatch);
const { getByLabelText, container } = render(routerWrapper(reduxWrapper(<RegistrationPage {...props} />)));
populateRequiredFields(getByLabelText, payload);
const button = container.querySelector('button.btn-brand');
fireEvent.click(button);
await waitFor(() => {
expect(store.dispatch).toHaveBeenCalledWith(
registerNewUser({
...payload,
country: 'PK',
}),
);
});
});
it('should show error messages for required fields on empty form submission', () => {
const { container } = render(routerWrapper(reduxWrapper(<RegistrationPage {...props} />)));
const { container } = render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
const button = container.querySelector('button.btn-brand');
fireEvent.click(button);
@@ -534,7 +398,7 @@ describe('RegistrationPage', () => {
},
},
});
const { container } = render(routerWrapper(reduxWrapper(<IntlProvider locale="en"><RegistrationPage {...props} /></IntlProvider>)));
const { container } = render(routerWrapper(reduxWrapper(<IntlProvider locale="en"><IntlRegistrationPage {...props} /></IntlProvider>)));
const usernameFeedback = container.querySelector('div[feedback-for="username"]');
const emailFeedback = container.querySelector('div[feedback-for="email"]');
@@ -543,7 +407,7 @@ describe('RegistrationPage', () => {
});
it('should clear error on focus', () => {
const { container } = render(routerWrapper(reduxWrapper(<RegistrationPage {...props} />)));
const { container } = render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
const submitButton = container.querySelector('button.btn-brand');
fireEvent.click(submitButton);
@@ -572,7 +436,7 @@ describe('RegistrationPage', () => {
store.dispatch = jest.fn(store.dispatch);
const { container } = render(routerWrapper(reduxWrapper(
<RegistrationPage {...props} />,
<IntlRegistrationPage {...props} />,
)));
const emailInput = container.querySelector('input#email');
@@ -583,7 +447,7 @@ describe('RegistrationPage', () => {
// ******** test form buttons and fields ********
it('should match default button state', () => {
const { container } = render(routerWrapper(reduxWrapper(<RegistrationPage {...props} />)));
const { container } = render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
const button = container.querySelector('button[type="submit"] span');
expect(button.textContent).toEqual('Create an account for free');
});
@@ -597,7 +461,7 @@ describe('RegistrationPage', () => {
},
});
const { container } = render(routerWrapper(reduxWrapper(<RegistrationPage {...props} />)));
const { container } = render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
const button = container.querySelector('button[type="submit"] span.sr-only');
expect(button.textContent).toEqual('pending');
@@ -608,7 +472,7 @@ describe('RegistrationPage', () => {
MARKETING_EMAILS_OPT_IN: 'true',
});
const { container } = render(routerWrapper(reduxWrapper(<RegistrationPage {...props} />)));
const { container } = render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
const checkboxDivs = container.querySelectorAll('div.form-field--checkbox');
expect(checkboxDivs.length).toEqual(1);
@@ -621,7 +485,7 @@ describe('RegistrationPage', () => {
const buttonLabel = 'Register';
delete window.location;
window.location = { href: getConfig().BASE_URL, search: `?cta=${buttonLabel}` };
const { container } = render(reduxWrapper(<RegistrationPage {...props} />));
const { container } = render(reduxWrapper(<IntlRegistrationPage {...props} />));
const button = container.querySelector('button[type="submit"] span');
const buttonText = button.textContent;
@@ -640,7 +504,7 @@ describe('RegistrationPage', () => {
},
});
render(routerWrapper(reduxWrapper(<RegistrationPage {...props} />)));
render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
expect(document.cookie).toMatch(`${getConfig().USER_RETENTION_COOKIE_NAME}=true`);
});
@@ -658,7 +522,7 @@ describe('RegistrationPage', () => {
});
delete window.location;
window.location = { href: getConfig().BASE_URL };
render(routerWrapper(reduxWrapper(<RegistrationPage {...props} />)));
render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
await waitFor(() => {
expect(window.location.href).toBe(dashboardURL);
}, { timeout: 1100 });
@@ -687,7 +551,7 @@ describe('RegistrationPage', () => {
});
delete window.location;
window.location = { href: getConfig().BASE_URL };
render(routerWrapper(reduxWrapper(<RegistrationPage {...props} />)));
render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
await waitFor(() => {
expect(window.location.href).toBe(dashboardUrl);
}, { timeout: 1100 });
@@ -720,7 +584,7 @@ describe('RegistrationPage', () => {
render(reduxWrapper(
<Router>
<RegistrationPage {...props} />
<IntlRegistrationPage {...props} />
</Router>,
));
expect(mockNavigate).toHaveBeenCalledWith(AUTHN_PROGRESSIVE_PROFILING);
@@ -738,12 +602,12 @@ describe('RegistrationPage', () => {
});
store.dispatch = jest.fn(store.dispatch);
render(routerWrapper(reduxWrapper(<RegistrationPage {...props} />)));
render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
expect(store.dispatch).toHaveBeenCalledWith(backupRegistrationFormBegin({ ...registrationFormData }));
});
it('should send page event when register page is rendered', () => {
render(routerWrapper(reduxWrapper(<RegistrationPage {...props} />)));
render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
expect(sendPageEvent).toHaveBeenCalledWith('login_and_registration', 'register', { app_name: APP_NAME });
});
@@ -761,7 +625,7 @@ describe('RegistrationPage', () => {
delete window.location;
window.location = { href: getConfig().BASE_URL };
render(routerWrapper(reduxWrapper(<RegistrationPage {...props} />)));
render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
expect(sendTrackEvent).toHaveBeenCalledWith('edx.bi.user.account.registered.client', { app_name: APP_NAME });
});
@@ -787,7 +651,7 @@ describe('RegistrationPage', () => {
store.dispatch = jest.fn(store.dispatch);
const { container } = render(reduxWrapper(
<Router>
<RegistrationPage {...props} />
<IntlRegistrationPage {...props} />
</Router>,
));
@@ -810,7 +674,7 @@ describe('RegistrationPage', () => {
},
});
const { container } = render(routerWrapper(reduxWrapper(<RegistrationPage {...props} />)));
const { container } = render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
const validationErrors = container.querySelector('div#validation-errors');
expect(validationErrors.textContent).toContain(
'An error has occurred. Try refreshing the page, or check your internet connection.',
@@ -837,7 +701,7 @@ describe('RegistrationPage', () => {
},
});
const { container } = render(routerWrapper(reduxWrapper(<RegistrationPage {...props} />)));
const { container } = render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
const fullNameInput = container.querySelector('input#name');
const usernameInput = container.querySelector('input#username');
@@ -883,14 +747,14 @@ describe('RegistrationPage', () => {
},
},
});
render(routerWrapper(reduxWrapper(<RegistrationPage {...props} />)));
render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
expect(window.parent.postMessage).toHaveBeenCalledTimes(2);
});
it('should not display validations error on blur event when embedded variant is rendered', () => {
delete window.location;
window.location = { href: getConfig().BASE_URL.concat(REGISTER_PAGE), search: '?host=http://localhost/host-website' };
const { container } = render(reduxWrapper(<RegistrationPage {...props} />));
const { container } = render(reduxWrapper(<IntlRegistrationPage {...props} />));
const usernameInput = container.querySelector('input#username');
fireEvent.blur(usernameInput, { target: { value: '', name: 'username' } });
@@ -918,7 +782,7 @@ describe('RegistrationPage', () => {
},
});
const { container } = render(routerWrapper(reduxWrapper(
<RegistrationPage {...props} />),
<IntlRegistrationPage {...props} />),
));
const usernameFeedback = container.querySelector('div[feedback-for="username"]');
@@ -935,7 +799,7 @@ describe('RegistrationPage', () => {
search: '?host=http://localhost/host-website',
};
const { container } = render(routerWrapper(reduxWrapper(<RegistrationPage {...props} />)));
const { container } = render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
const submitButton = container.querySelector('button.btn-brand');
fireEvent.click(submitButton);
@@ -976,7 +840,7 @@ describe('RegistrationPage', () => {
});
store.dispatch = jest.fn(store.dispatch);
const { container } = render(routerWrapper(reduxWrapper(<RegistrationPage {...props} />)));
const { container } = render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
const spinnerElement = container.querySelector('#tpa-spinner');
const registrationFormElement = container.querySelector('#registration-form');
@@ -984,7 +848,7 @@ describe('RegistrationPage', () => {
expect(registrationFormElement).toBeFalsy();
});
it('should auto register if autoSubmitRegForm is true and pipeline details are loaded', async () => {
it('should auto register if autoSubmitRegForm is true and pipeline details are loaded', () => {
jest.spyOn(global.Date, 'now').mockImplementation(() => 0);
getLocale.mockImplementation(() => ('en-us'));
@@ -1027,18 +891,16 @@ describe('RegistrationPage', () => {
});
store.dispatch = jest.fn(store.dispatch);
render(routerWrapper(reduxWrapper(<RegistrationPage {...props} />)));
await waitFor(() => {
expect(store.dispatch).toHaveBeenCalledWith(registerNewUser({
name: 'John Doe',
username: 'john_doe',
email: 'john.doe@example.com',
country: 'PK',
social_auth_provider: 'Apple',
total_registration_time: 0,
app_name: APP_NAME,
}));
});
render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
expect(store.dispatch).toHaveBeenCalledWith(registerNewUser({
name: 'John Doe',
username: 'john_doe',
email: 'john.doe@example.com',
country: 'PK',
social_auth_provider: 'Apple',
total_registration_time: 0,
app_name: APP_NAME,
}));
});
});
});

View File

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

View File

@@ -1,10 +1,11 @@
import React from 'react';
import { Provider } from 'react-redux';
import { mergeConfig } from '@edx/frontend-platform';
import {
getLocale, IntlProvider,
getLocale, injectIntl, IntlProvider,
} from '@edx/frontend-platform/i18n';
import { fireEvent, render, waitFor } from '@testing-library/react';
import { fireEvent, render } from '@testing-library/react';
import { BrowserRouter as Router } from 'react-router-dom';
import configureStore from 'redux-mock-store';
@@ -27,6 +28,8 @@ jest.mock('@edx/frontend-platform/i18n', () => ({
}));
jest.mock('../../data/optimizelyExperiment/useAutoGeneratedUsernameExperimentVariation', () => jest.fn());
const IntlConfigurableRegistrationForm = injectIntl(ConfigurableRegistrationForm);
const IntlRegistrationPage = injectIntl(RegistrationPage);
const mockStore = configureStore();
jest.mock('react-router-dom', () => {
@@ -162,7 +165,7 @@ describe('ConfigurableRegistrationForm', () => {
};
render(routerWrapper(reduxWrapper(
<ConfigurableRegistrationForm {...props} />,
<IntlConfigurableRegistrationForm {...props} />,
)));
expect(document.querySelector('#profession')).toBeTruthy();
@@ -193,7 +196,7 @@ describe('ConfigurableRegistrationForm', () => {
};
render(routerWrapper(reduxWrapper(
<ConfigurableRegistrationForm {...props} />,
<IntlConfigurableRegistrationForm {...props} />,
)));
expect(props.setFormFields).toHaveBeenCalledTimes(2);
@@ -220,12 +223,12 @@ describe('ConfigurableRegistrationForm', () => {
},
},
});
render(routerWrapper(reduxWrapper(<RegistrationPage {...props} />)));
render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
expect(document.querySelector('#profession')).toBeTruthy();
expect(document.querySelector('#tos')).toBeTruthy();
});
it('should submit form with fields returned by backend in payload', async () => {
it('should submit form with fields returned by backend in payload', () => {
mergeConfig({
SHOW_CONFIGURABLE_EDX_FIELDS: true,
});
@@ -254,7 +257,7 @@ describe('ConfigurableRegistrationForm', () => {
};
store.dispatch = jest.fn(store.dispatch);
const { getByLabelText, container } = render(routerWrapper(reduxWrapper(<RegistrationPage {...props} />)));
const { getByLabelText, container } = render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
populateRequiredFields(getByLabelText, payload);
@@ -265,9 +268,7 @@ describe('ConfigurableRegistrationForm', () => {
fireEvent.click(submitButton);
await waitFor(() => {
expect(store.dispatch).toHaveBeenCalledWith(registerNewUser({ ...payload, country: 'PK', app_name: APP_NAME }));
});
expect(store.dispatch).toHaveBeenCalledWith(registerNewUser({ ...payload, country: 'PK', app_name: APP_NAME }));
});
it('should show error messages for required fields on empty form submission', () => {
@@ -291,7 +292,7 @@ describe('ConfigurableRegistrationForm', () => {
},
});
const { container } = render(routerWrapper(reduxWrapper(<RegistrationPage {...props} />)));
const { container } = render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
const submitButton = container.querySelector('button.btn-brand');
fireEvent.click(submitButton);
@@ -317,7 +318,7 @@ describe('ConfigurableRegistrationForm', () => {
},
},
});
const { container } = render(routerWrapper(reduxWrapper(<RegistrationPage {...props} />)));
const { container } = render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
const countryInput = container.querySelector('input[name="country"]');
fireEvent.change(countryInput, { target: { value: 'Pak', name: 'country' } });
fireEvent.blur(countryInput, { target: { value: 'Pak', name: 'country' } });
@@ -342,7 +343,7 @@ describe('ConfigurableRegistrationForm', () => {
},
},
});
const { getByLabelText, container } = render(routerWrapper(reduxWrapper(<RegistrationPage {...props} />)));
const { getByLabelText, container } = render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
const emailInput = getByLabelText('Email');
const confirmEmailInput = getByLabelText('Confirm Email');
@@ -378,7 +379,7 @@ describe('ConfigurableRegistrationForm', () => {
},
},
});
const { getByLabelText, container } = render(routerWrapper(reduxWrapper(<RegistrationPage {...props} />)));
const { getByLabelText, container } = render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
populateRequiredFields(getByLabelText, formPayload, true);
fireEvent.change(
@@ -413,7 +414,7 @@ describe('ConfigurableRegistrationForm', () => {
});
const { getByLabelText, container } = render(
routerWrapper(reduxWrapper(<RegistrationPage {...props} />)),
routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)),
);
const professionInput = getByLabelText('Profession');

View File

@@ -1,8 +1,9 @@
import React from 'react';
import { Provider } from 'react-redux';
import { mergeConfig } from '@edx/frontend-platform';
import {
configure, getLocale, IntlProvider,
configure, getLocale, injectIntl, IntlProvider,
} from '@edx/frontend-platform/i18n';
import { render, screen } from '@testing-library/react';
import { BrowserRouter as Router } from 'react-router-dom';
@@ -27,6 +28,8 @@ jest.mock('@edx/frontend-platform/i18n', () => ({
}));
jest.mock('../../data/optimizelyExperiment/useAutoGeneratedUsernameExperimentVariation', () => jest.fn());
const IntlRegistrationPage = injectIntl(RegistrationPage);
const IntlRegistrationFailure = injectIntl(RegistrationFailureMessage);
const mockStore = configureStore();
jest.mock('react-router-dom', () => {
@@ -140,7 +143,7 @@ describe('RegistrationFailure', () => {
failureCount: 0,
};
const { container } = render(reduxWrapper(<RegistrationFailureMessage {...props} />));
const { container } = render(reduxWrapper(<IntlRegistrationFailure {...props} />));
const alertHeading = container.querySelectorAll('div.alert-heading');
expect(alertHeading.length).toEqual(1);
@@ -156,7 +159,7 @@ describe('RegistrationFailure', () => {
failureCount: 0,
};
const { container } = render(reduxWrapper(<RegistrationFailureMessage {...props} />));
const { container } = render(reduxWrapper(<IntlRegistrationFailure {...props} />));
const alertHeading = container.querySelectorAll('div.alert-heading');
expect(alertHeading.length).toEqual(1);
@@ -175,7 +178,7 @@ describe('RegistrationFailure', () => {
failureCount: 0,
};
const { container } = render(reduxWrapper(<RegistrationFailureMessage {...props} />));
const { container } = render(reduxWrapper(<IntlRegistrationFailure {...props} />));
const alertHeading = container.querySelectorAll('div.alert-heading');
expect(alertHeading.length).toEqual(1);
@@ -194,7 +197,7 @@ describe('RegistrationFailure', () => {
failureCount: 0,
};
const { container } = render(reduxWrapper(<RegistrationFailureMessage {...props} />));
const { container } = render(reduxWrapper(<IntlRegistrationFailure {...props} />));
const alertHeading = container.querySelectorAll('div.alert-heading');
expect(alertHeading.length).toEqual(1);
@@ -214,7 +217,7 @@ describe('RegistrationFailure', () => {
},
});
render(routerWrapper(reduxWrapper(<RegistrationPage {...props} />)));
render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
const validationError = screen.queryByText('An error has occurred. Try refreshing the page, or check your internet connection.');
expect(validationError).not.toBeNull();

View File

@@ -1,8 +1,9 @@
import React from 'react';
import { Provider } from 'react-redux';
import { getConfig, mergeConfig } from '@edx/frontend-platform';
import {
configure, getLocale, IntlProvider,
configure, getLocale, injectIntl, IntlProvider,
} from '@edx/frontend-platform/i18n';
import { fireEvent, render, waitFor } from '@testing-library/react';
import { BrowserRouter as Router } from 'react-router-dom';
@@ -28,6 +29,7 @@ jest.mock('@edx/frontend-platform/i18n', () => ({
jest.mock('../../data/optimizelyExperiment/useAutoGeneratedUsernameExperimentVariation', () => jest.fn());
mockTagular();
const IntlRegistrationPage = injectIntl(RegistrationPage);
const mockStore = configureStore();
jest.mock('react-router-dom', () => {
@@ -163,7 +165,7 @@ describe('ThirdPartyAuth', () => {
});
const { queryByLabelText } = render(
routerWrapper(reduxWrapper(<RegistrationPage {...props} />, { store })),
routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />, { store })),
);
const passwordField = queryByLabelText('Password');
@@ -188,7 +190,7 @@ describe('ThirdPartyAuth', () => {
window.location = { href: getConfig().BASE_URL.concat(LOGIN_PAGE), search: `?next=/dashboard&tpa_hint=${ssoProvider.id}` };
const { container } = render(
routerWrapper(reduxWrapper(<RegistrationPage {...props} />)),
routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)),
);
const tpaButton = container.querySelector(`button#${ssoProvider.id}`);
@@ -213,7 +215,7 @@ describe('ThirdPartyAuth', () => {
search: `?next=/dashboard&tpa_hint=${ssoProvider.id}`,
};
const { container } = render(routerWrapper(reduxWrapper(<RegistrationPage {...props} />)));
const { container } = render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
const skeletonElement = container.querySelector('.react-loading-skeleton');
expect(skeletonElement).toBeTruthy();
@@ -237,7 +239,7 @@ describe('ThirdPartyAuth', () => {
window.location = { href: getConfig().BASE_URL.concat(REGISTER_PAGE), search: `?next=/dashboard&tpa_hint=${ssoProvider.id}` };
ssoProvider.iconImage = null;
const { container } = render(routerWrapper(reduxWrapper(<RegistrationPage {...props} />)));
const { container } = render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
const iconElement = container.querySelector(`button#${ssoProvider.id} div span.pgn__icon`);
expect(iconElement).toBeTruthy();
@@ -260,7 +262,7 @@ describe('ThirdPartyAuth', () => {
delete window.location;
window.location = { href: getConfig().BASE_URL.concat(REGISTER_PAGE), search: `?next=/dashboard&tpa_hint=${secondaryProviders.id}` };
render(routerWrapper(reduxWrapper(<RegistrationPage {...props} />)));
render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
expect(window.location.href).toEqual(getConfig().LMS_BASE_URL + secondaryProviders.registerUrl);
});
@@ -281,7 +283,7 @@ describe('ThirdPartyAuth', () => {
delete window.location;
window.location = { href: getConfig().BASE_URL.concat(LOGIN_PAGE), search: '?next=/dashboard&tpa_hint=invalid' };
const { container } = render(routerWrapper(reduxWrapper(<RegistrationPage {...props} />)));
const { container } = render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
const providerButton = container.querySelector(`button#${ssoProvider.id} span#provider-name`);
expect(providerButton.textContent).toEqual(expectedMessage);
@@ -300,7 +302,7 @@ describe('ThirdPartyAuth', () => {
});
const { container } = render(
routerWrapper(reduxWrapper(<RegistrationPage {...props} />, { store })),
routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />, { store })),
);
const buttonsWithId = container.querySelectorAll(`button#${ssoProvider.id}`);
@@ -321,7 +323,7 @@ describe('ThirdPartyAuth', () => {
});
const { container } = render(
routerWrapper(reduxWrapper(<RegistrationPage {...props} />)),
routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)),
);
const buttonsWithId = container.querySelectorAll(`button#${ssoProvider.id}`);
@@ -335,7 +337,7 @@ describe('ThirdPartyAuth', () => {
institutionLogin: true,
};
const { getByText } = render(routerWrapper(reduxWrapper(<RegistrationPage {...props} />)));
const { getByText } = render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
const headingElement = getByText('Register with institution/campus credentials');
expect(headingElement).toBeTruthy();
});
@@ -360,7 +362,7 @@ describe('ThirdPartyAuth', () => {
window.location = { href: getConfig().BASE_URL };
const { container } = render(
routerWrapper(reduxWrapper(<RegistrationPage {...props} />)),
routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)),
);
const ssoButton = container.querySelector('button#oa2-apple-id');
@@ -393,7 +395,7 @@ describe('ThirdPartyAuth', () => {
delete window.location;
window.location = { href: getConfig().BASE_URL };
render(routerWrapper(reduxWrapper(<RegistrationPage {...props} />)));
render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
await waitFor(() => {
expect(window.location.href).toBe(getConfig().LMS_BASE_URL + authCompleteUrl);
}, { timeout: 1100 });
@@ -416,7 +418,7 @@ describe('ThirdPartyAuth', () => {
const expectedMessage = `${'You\'ve successfully signed into Apple! We just need a little more information before '
+ 'you start learning with '}${ getConfig().SITE_NAME }.`;
const { container } = render(routerWrapper(reduxWrapper(<RegistrationPage {...props} />)));
const { container } = render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
const tpaAlert = container.querySelector('#tpa-alert p');
expect(tpaAlert.textContent).toEqual(expectedMessage);
});
@@ -447,7 +449,7 @@ describe('ThirdPartyAuth', () => {
store.dispatch = jest.fn(store.dispatch);
const { container } = render(
routerWrapper(reduxWrapper(<RegistrationPage {...props} />)),
routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)),
);
const alertHeading = container.querySelector('div.alert-heading');

View File

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

View File

@@ -1,40 +0,0 @@
import { useCallback } from 'react';
import { getConfig } from '@edx/frontend-platform';
import { useIntl } from '@edx/frontend-platform/i18n';
import { useGoogleReCaptcha } from 'react-google-recaptcha-v3';
import messages from '../messages';
const useRecaptchaSubmission = (actionName = 'submit') => {
const { formatMessage } = useIntl();
const { executeRecaptcha } = useGoogleReCaptcha();
const recaptchaKey = getConfig().RECAPTCHA_SITE_KEY_WEB;
const isReady = !!executeRecaptcha || !recaptchaKey;
const executeWithFallback = useCallback(async () => {
if (executeRecaptcha && recaptchaKey) {
const token = await executeRecaptcha(actionName);
if (!token) {
throw new Error(formatMessage(messages['registration.captcha.verification.label']));
}
return token;
}
// Fallback: no reCAPTCHA or not ready
if (recaptchaKey) {
// eslint-disable-next-line no-console
console.warn(`reCAPTCHA not ready for action: ${actionName}. Proceeding without token.`);
}
return null;
}, [executeRecaptcha, recaptchaKey, actionName, formatMessage]);
return {
executeWithFallback,
isReady,
isLoading: recaptchaKey && !executeRecaptcha,
};
};
export default useRecaptchaSubmission;

View File

@@ -1,90 +0,0 @@
import { getConfig } from '@edx/frontend-platform';
import { IntlProvider } from '@edx/frontend-platform/i18n';
import { renderHook } from '@testing-library/react';
import { useGoogleReCaptcha } from 'react-google-recaptcha-v3';
import useRecaptchaSubmission from './hooks';
jest.mock('@edx/frontend-platform', () => ({
getConfig: jest.fn(),
}));
jest.mock('react-google-recaptcha-v3', () => ({
useGoogleReCaptcha: jest.fn(),
}));
jest.mock('@edx/frontend-platform/i18n', () => ({
...jest.requireActual('@edx/frontend-platform/i18n'),
useIntl: () => ({ formatMessage: (msg) => msg.defaultMessage || msg }),
}));
describe('useRecaptchaSubmission', () => {
beforeEach(() => {
getConfig.mockReturnValue({ RECAPTCHA_SITE_KEY_WEB: 'test-key' });
useGoogleReCaptcha.mockReturnValue({ executeRecaptcha: jest.fn() });
});
it('should throw error if reCAPTCHA returns empty token', async () => {
useGoogleReCaptcha.mockReturnValue({
executeRecaptcha: jest.fn().mockResolvedValue(null),
});
const { result } = renderHook(() => useRecaptchaSubmission('test_action'), {
wrapper: ({ children }) => <IntlProvider locale="en">{children}</IntlProvider>,
});
await expect(result.current.executeWithFallback()).rejects.toThrow(
'CAPTCHA verification failed.',
);
});
it('should warn and return null if reCAPTCHA key exists but executeRecaptcha is not ready', async () => {
const warnSpy = jest.spyOn(console, 'warn').mockImplementation();
useGoogleReCaptcha.mockReturnValue({
executeRecaptcha: undefined,
});
const { result } = renderHook(() => useRecaptchaSubmission('test_action'), {
wrapper: ({ children }) => <IntlProvider locale="en">{children}</IntlProvider>,
});
const token = await result.current.executeWithFallback();
expect(token).toBeNull();
expect(warnSpy).toHaveBeenCalledWith(
'reCAPTCHA not ready for action: test_action. Proceeding without token.',
);
warnSpy.mockRestore();
});
it('should handle undefined RECAPTCHA_SITE_KEY_WEB gracefully', async () => {
const warnSpy = jest.spyOn(console, 'warn').mockImplementation();
getConfig.mockReturnValue({ RECAPTCHA_SITE_KEY_WEB: undefined });
const { result } = renderHook(() => useRecaptchaSubmission('test_action'), {
wrapper: ({ children }) => <IntlProvider locale="en">{children}</IntlProvider>,
});
const token = await result.current.executeWithFallback();
expect(token).toBeNull();
expect(warnSpy).not.toHaveBeenCalled();
warnSpy.mockRestore();
});
it('should return token if reCAPTCHA succeeds', async () => {
useGoogleReCaptcha.mockReturnValue({
executeRecaptcha: jest.fn().mockResolvedValue('valid-token'),
});
const { result } = renderHook(() => useRecaptchaSubmission('test_action'), {
wrapper: ({ children }) => <IntlProvider locale="en">{children}</IntlProvider>,
});
const token = await result.current.executeWithFallback();
expect(token).toBe('valid-token');
});
});

View File

@@ -1,4 +1,3 @@
/* eslint-disable consistent-return */
import { useEffect, useState } from 'react';
import {
@@ -23,7 +22,7 @@ const useAutoGeneratedUsernameExperimentVariation = (
useEffect(() => {
if (initExpVariation || registrationEmbedded || !!tpaHint || !!currentProvider
|| thirdPartyAuthApiStatus !== COMPLETE_STATE) {
return;
return variation;
}
const getVariation = () => {

View File

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

View File

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