-
- {thirdPartyAuthContext.currentProvider
- && (
-
- )}
- {this.props.loginError ?
: null}
- {submitState === DEFAULT_STATE && this.state.isSubmitted ? windowScrollTo({ left: 0, top: 0, behavior: 'smooth' }) : null}
- {activationMsgType &&
}
- {this.props.forgotPassword.status === 'complete' && !this.props.loginError ? (
-
- ) : null}
-
- {intl.formatMessage(messages['first.time.here'])}
-
- {intl.formatMessage(messages['create.an.account'])}.
-
-
-
-
- {intl.formatMessage(messages['sign.in.heading'])}
-
-
- {(providers.length || secondaryProviders.length || thirdPartyAuthApiStatus === PENDING_STATE)
- && !currentProvider ? (
-
-
- {intl.formatMessage(messages['or.sign.in.with'])}
-
- ) : null}
- {this.renderThirdPartyAuth(providers, secondaryProviders, currentProvider, thirdPartyAuthApiStatus, intl)}
+
+ {thirdPartyAuthContext.currentProvider
+ && (
+
+ )}
+ {this.props.loginError ?
: null}
+ {submitState === DEFAULT_STATE && this.state.isSubmitted ? windowScrollTo({ left: 0, top: 0, behavior: 'smooth' }) : null}
+ {activationMsgType &&
}
+ {this.props.forgotPassword.status === 'complete' && !this.props.loginError ? (
+
+ ) : null}
+
+
+
+ {intl.formatMessage(messages['enterprise.login.btn.text'])}
+
+ {this.renderThirdPartyAuth(providers, secondaryProviders, currentProvider, thirdPartyAuthApiStatus, intl)}
+
>
);
diff --git a/src/login/messages.jsx b/src/login/messages.jsx
index d2eb4dfa..3e613c3a 100644
--- a/src/login/messages.jsx
+++ b/src/login/messages.jsx
@@ -6,11 +6,27 @@ const messages = defineMessages({
defaultMessage: 'Login | {siteName}',
description: 'login page title',
},
+ // Login labels
+ 'login.user.identity.label': {
+ id: 'login.user.identity.label',
+ defaultMessage: 'Username or email',
+ description: 'Label for user identity field to enter either username or email to login',
+ },
+ 'login.password.label': {
+ id: 'login.password.label',
+ defaultMessage: 'Password',
+ description: 'Label for password field',
+ },
'sign.in.button': {
id: 'sign.in.button',
defaultMessage: 'Sign in',
description: 'Button label that appears on login page',
},
+ 'sign.in.btn.pending.state': {
+ id: 'sign.in.btn.pending.state',
+ defaultMessage: 'Loading',
+ description: 'Title of icon that appears when button is in pending state',
+ },
'need.help.signing.in.collapsible.menu': {
id: 'need.help.signing.in.collapsible.menu',
defaultMessage: 'Need help signing in?',
@@ -21,6 +37,11 @@ const messages = defineMessages({
defaultMessage: 'Forgot my password',
description: 'Forgot password link',
},
+ 'forgot.password': {
+ id: 'forgot.password',
+ defaultMessage: 'Forgot password',
+ description: 'Button text for forgot password',
+ },
'other.sign.in.issues': {
id: 'other.sign.in.issues',
defaultMessage: 'Other sign in issues',
@@ -56,10 +77,10 @@ const messages = defineMessages({
defaultMessage: 'Create an account',
description: 'Message on button to return to register page',
},
- 'or.sign.in.with': {
- id: 'or.sign.in.with',
- defaultMessage: 'or sign in with',
- description: 'gives hint about other sign in options',
+ 'login.other.options.heading': {
+ id: 'login.other.options.heading',
+ defaultMessage: 'Or sign in with:',
+ description: 'Text that appears above other sign in options like social auth buttons',
},
'non.compliant.password.title': {
id: 'non.compliant.password.title',
@@ -71,19 +92,14 @@ const messages = defineMessages({
defaultMessage: 'First time here?',
description: 'A question that appears before sign up link',
},
- 'email.label': {
- id: 'email.label',
- defaultMessage: 'Email',
- description: 'Label that appears above email field',
- },
'email.help.message': {
id: 'email.help.message',
defaultMessage: 'The email address you used to register with edX.',
description: 'Message that appears below email field on login page',
},
- 'enterprise.login.link.text': {
- id: 'enterprise.login.link.text',
- defaultMessage: 'Sign in with your company or school',
+ 'enterprise.login.btn.text': {
+ id: 'enterprise.login.btn.text',
+ defaultMessage: 'Company or school credentials',
description: 'Company or school login link text.',
},
'email.format.validation.message': {
@@ -106,11 +122,6 @@ const messages = defineMessages({
defaultMessage: 'Please enter your password.',
description: 'Validation message that appears when password is empty',
},
- 'password.label': {
- id: 'password.label',
- defaultMessage: 'Password',
- description: 'Text that appears above password field or as a placeholder',
- },
'register.link': {
id: 'register.link',
defaultMessage: 'Create an account',
diff --git a/src/login/tests/LoginFailure.test.jsx b/src/login/tests/LoginFailure.test.jsx
index fc406320..8743e549 100644
--- a/src/login/tests/LoginFailure.test.jsx
+++ b/src/login/tests/LoginFailure.test.jsx
@@ -102,7 +102,7 @@ describe('LoginFailureMessage', () => {
props = {
loginError: {
errorCode: INVALID_FORM,
- context: { email: 'Please enter your email.', password: 'Please enter your password.' },
+ context: { emailOrUsername: 'Please enter your email.', password: 'Please enter your password.' },
},
};
diff --git a/src/login/tests/LoginPage.test.jsx b/src/login/tests/LoginPage.test.jsx
index bffff36e..061f682b 100644
--- a/src/login/tests/LoginPage.test.jsx
+++ b/src/login/tests/LoginPage.test.jsx
@@ -1,17 +1,20 @@
import React from 'react';
-import { Provider } from 'react-redux';
-import renderer from 'react-test-renderer';
+
import { mount } from 'enzyme';
import configureStore from 'redux-mock-store';
+import { Provider } from 'react-redux';
+import { MemoryRouter } from 'react-router-dom';
+import renderer from 'react-test-renderer';
import CookiePolicyBanner from '@edx/frontend-component-cookie-policy-banner';
import { getConfig, mergeConfig } from '@edx/frontend-platform';
import * as analytics from '@edx/frontend-platform/analytics';
import { IntlProvider, injectIntl } from '@edx/frontend-platform/i18n';
+import { loginRequest, loginRequestFailure } from '../data/actions';
import LoginFailureMessage from '../LoginFailure';
import LoginPage from '../LoginPage';
-import { loginRequest, loginRequestFailure } from '../data/actions';
+
import { RenderInstitutionButton } from '../../common-components';
import { COMPLETE_STATE, PENDING_STATE } from '../../data/constants';
@@ -28,9 +31,18 @@ describe('LoginPage', () => {
mergeConfig({
USER_SURVEY_COOKIE_NAME: process.env.USER_SURVEY_COOKIE_NAME,
});
+ let props = {};
+ let store = {};
+
+ const reduxWrapper = children => (
+
+
+ {children}
+
+
+ );
const initialState = {
- forgotPassword: { status: null },
login: {
loginResult: { success: false, redirectUrl: '' },
},
@@ -45,9 +57,6 @@ describe('LoginPage', () => {
},
};
- let props = {};
- let store = {};
-
const secondaryProviders = {
id: 'saml-test',
name: 'Test University',
@@ -56,7 +65,7 @@ describe('LoginPage', () => {
skipHintedLogin: false,
};
- const appleProvider = {
+ const ssoProvider = {
id: 'oa2-apple-id',
name: 'Apple',
iconClass: null,
@@ -64,12 +73,6 @@ describe('LoginPage', () => {
loginUrl: '/auth/login/apple-id/?auth_entry=login&next=/dashboard',
};
- const reduxWrapper = children => (
-
- {children}
-
- );
-
beforeEach(() => {
store = mockStore(initialState);
props = {
@@ -77,97 +80,43 @@ describe('LoginPage', () => {
};
});
- it('should match default section snapshot', () => {
- const tree = renderer.create(reduxWrapper(
))
- .toJSON();
- expect(tree).toMatchSnapshot();
- });
-
- it('should match pending button state snapshot', () => {
- store = mockStore({
- ...initialState,
- login: {
- ...initialState.login,
- submitState: PENDING_STATE,
- },
- });
-
- const tree = renderer.create(reduxWrapper(
))
- .toJSON();
- expect(tree).toMatchSnapshot();
- });
-
- it('should match forget password alert message snapshot', () => {
- store = mockStore({
- ...initialState,
- forgotPassword: { status: 'complete', email: 'test@example.com' },
- });
-
- const tree = renderer.create(reduxWrapper(
)).toJSON();
- expect(tree).toMatchSnapshot();
- });
-
- it('should match TPA provider snapshot', () => {
- store = mockStore({
- ...initialState,
- commonComponents: {
- ...initialState.commonComponents,
- thirdPartyAuthContext: {
- ...initialState.commonComponents.thirdPartyAuthContext,
- providers: [appleProvider],
- },
- },
- });
-
- const tree = renderer.create(reduxWrapper(
)).toJSON();
- expect(tree).toMatchSnapshot();
- });
-
- it('should show error message', () => {
- store = mockStore({
- ...initialState,
- login: {
- ...initialState.login,
- loginError: { value: 'Email or password is incorrect.' },
- },
- });
-
- const tree = renderer.create(reduxWrapper(
)).toJSON();
- expect(tree).toMatchSnapshot();
- });
-
- it('should show account activation message', () => {
- delete window.location;
- window.location = { href: getConfig().BASE_URL.concat('/login'), search: '?account_activation_status=info' };
-
- const expectedMessage = 'This account has already been activated.';
+ // ******** test login form submission ********
+ it('should submit form for valid input', () => {
+ store.dispatch = jest.fn(store.dispatch);
const loginPage = mount(reduxWrapper(
));
- expect(loginPage.find('#account-activation-message').find('div').text()).toEqual(expectedMessage);
+
+ loginPage.find('input#email').simulate('change', { target: { value: 'test@example.com' } });
+ loginPage.find('input#password').simulate('change', { target: { value: 'password' } });
+ loginPage.find('button.btn-brand').simulate('click');
+
+ expect(store.dispatch).toHaveBeenCalledWith(loginRequest({ email: 'test@example.com', password: 'password' }));
});
- it('should display login help button', () => {
- const root = mount(reduxWrapper(
));
- expect(root.find('button.field-link').first().text()).toEqual('Need help signing in?');
+ it('should not dispatch loginRequest on empty form submission', () => {
+ store.dispatch = jest.fn(store.dispatch);
+ const loginPage = mount(reduxWrapper(
));
+
+ loginPage.find('button.btn-brand').simulate('click');
+ expect(store.dispatch).not.toHaveBeenCalledWith(loginRequest({}));
});
- it('updates the error state for empty email input on form submission', () => {
- const errorState = { email: 'Please enter your email.', password: '' };
+ // ******** test login form validations ********
+
+ it('should match state on empty form submission', () => {
+ const errorState = { emailOrUsername: 'Please enter your email.', password: 'Please enter your password.' };
store.dispatch = jest.fn(store.dispatch);
const loginPage = (mount(reduxWrapper(
))).find('LoginPage');
-
- loginPage.find('input#password').simulate('change', { target: { value: 'test', name: 'password' } });
loginPage.find('button.btn-brand').simulate('click');
+ // Check that loginRequestFailure was dispatched and state is updated
expect(loginPage.state('errors')).toEqual(errorState);
- expect(store.dispatch).toHaveBeenCalledWith(
- loginRequestFailure({ errorCode: 'invalid-form', context: errorState }),
- );
+ expect(store.dispatch).toHaveBeenCalledWith(loginRequestFailure({ errorCode: 'invalid-form', context: errorState }));
});
- it('updates the error state for invalid email; less than 3 characters on form submission', () => {
- const errorState = { email: 'Email must have at least 3 characters.', password: '' };
+ it('should match state for invalid email (less than 3 characters), on form submission', () => {
+ const errorState = { emailOrUsername: 'Email must have at least 3 characters.', password: '' };
store.dispatch = jest.fn(store.dispatch);
const loginPage = (mount(reduxWrapper(
))).find('LoginPage');
@@ -177,13 +126,10 @@ describe('LoginPage', () => {
loginPage.find('button.btn-brand').simulate('click');
expect(loginPage.state('errors')).toEqual(errorState);
- expect(store.dispatch).toHaveBeenCalledWith(
- loginRequestFailure({ errorCode: 'invalid-form', context: errorState }),
- );
});
- it('updates the error state for invalid email format validation on form submission', () => {
- const errorState = { email: 'The email address you\'ve provided isn\'t formatted correctly.', password: '' };
+ it('should match the state for invalid email format on form submission', () => {
+ const errorState = { emailOrUsername: 'The email address you\'ve provided isn\'t formatted correctly.', password: '' };
store.dispatch = jest.fn(store.dispatch);
const loginPage = (mount(reduxWrapper(
))).find('LoginPage');
@@ -195,127 +141,102 @@ describe('LoginPage', () => {
expect(loginPage.state('errors')).toEqual(errorState);
});
- it('updates the error state for invalid password', () => {
- const errorState = { email: '', password: 'Please enter your password.' };
- store.dispatch = jest.fn(store.dispatch);
+ // ******** test form buttons and links ********
- const loginPage = (mount(reduxWrapper(
))).find('LoginPage');
-
- loginPage.find('input#email').simulate('change', { target: { value: 'test@example.com', name: 'email' } });
- loginPage.find('button.btn-brand').simulate('click');
-
- expect(loginPage.state('errors')).toEqual(errorState);
- expect(store.dispatch).toHaveBeenCalledWith(
- loginRequestFailure({ errorCode: 'invalid-form', context: errorState }),
- );
+ it('should match default button state', () => {
+ const loginPage = mount(reduxWrapper(
));
+ expect(loginPage.find('button[type="submit"] span').first().text()).toEqual('Sign in');
});
- it('submits login request for valid email and password values', () => {
- store.dispatch = jest.fn(store.dispatch);
- const loginPage = (mount(reduxWrapper(
))).find('LoginPage');
- loginPage.find('input#email').simulate('change', { target: { value: 'test@example.com' } });
- loginPage.find('input#password').simulate('change', { target: { value: 'password' } });
- loginPage.find('button.btn-brand').simulate('click');
-
- expect(store.dispatch).toHaveBeenCalledWith(
- loginRequest({ email: 'test@example.com', password: 'password' }),
- );
- });
-
- it('should match url after redirection', () => {
- const dasboardUrl = 'http://test.com/testing-dashboard/';
+ it('should match pending button state', () => {
store = mockStore({
...initialState,
login: {
...initialState.login,
- loginResult: {
- success: true,
- redirectUrl: dasboardUrl,
- },
+ submitState: PENDING_STATE,
},
});
- delete window.location;
- window.location = { href: getConfig().BASE_URL };
- renderer.create(reduxWrapper(
));
- expect(window.location.href).toBe(dasboardUrl);
- });
-
- it('should match url after TPA redirection', () => {
- const authCompleteUrl = '/auth/complete/google-oauth2/';
- store = mockStore({
- ...initialState,
- login: {
- ...initialState.login,
- loginResult: {
- success: true,
- redirectUrl: '',
- },
- },
- commonComponents: {
- ...initialState.commonComponents,
- thirdPartyAuthContext: {
- ...initialState.commonComponents.thirdPartyAuthContext,
- finishAuthUrl: authCompleteUrl,
- },
- },
- });
-
- delete window.location;
- window.location = { href: getConfig().BASE_URL };
- renderer.create(reduxWrapper(
));
- expect(window.location.href).toBe(getConfig().LMS_BASE_URL + authCompleteUrl);
- });
-
- it('should redirect to enterprise selection page', () => {
- const authCompleteUrl = '/auth/complete/google-oauth2/';
- const enterpriseSelectionPage = 'http://localhost:18000/enterprise/select/active/?success_url='.concat(authCompleteUrl);
- store = mockStore({
- ...initialState,
- login: {
- ...initialState.login,
- loginResult: {
- success: true,
- redirectUrl: enterpriseSelectionPage,
- },
- },
- commonComponents: {
- ...initialState.commonComponents,
- thirdPartyAuthContext: {
- ...initialState.commonComponents.thirdPartyAuthContext,
- finishAuthUrl: authCompleteUrl,
- },
- },
- });
-
- delete window.location;
- window.location = { href: getConfig().BASE_URL };
- renderer.create(reduxWrapper(
));
- expect(window.location.href).toBe(enterpriseSelectionPage);
- });
-
- it('should redirect to social auth provider url', () => {
- const loginUrl = '/auth/login/apple-id/?auth_entry=login&next=/dashboard';
- store = mockStore({
- ...initialState,
- commonComponents: {
- ...initialState.commonComponents,
- thirdPartyAuthContext: {
- ...initialState.commonComponents.thirdPartyAuthContext,
- providers: [{
- ...appleProvider,
- loginUrl,
- }],
- },
- },
- });
-
- delete window.location;
- window.location = { href: getConfig().BASE_URL };
const loginPage = mount(reduxWrapper(
));
+ const button = loginPage.find('button[type="submit"] span').first();
- loginPage.find('button#oa2-apple-id').simulate('click');
- expect(window.location.href).toBe(getConfig().LMS_BASE_URL + loginUrl);
+ // test pending state icon and that pending state icon has title associated with it
+ expect(button.find('svg').prop('className')).toEqual(expect.stringContaining('fa-spinner'));
+ expect(button.find('svg').find('title').text()).toEqual('Loading');
+ });
+
+ it('should show forgot password link', () => {
+ const loginPage = mount(reduxWrapper(
));
+ expect(loginPage.find('a#forgot-password').text()).toEqual('Forgot password');
+ });
+
+ it('should show single sign on provider button', () => {
+ store = mockStore({
+ ...initialState,
+ commonComponents: {
+ ...initialState.commonComponents,
+ thirdPartyAuthContext: {
+ ...initialState.commonComponents.thirdPartyAuthContext,
+ providers: [ssoProvider],
+ },
+ },
+ });
+
+ const loginPage = mount(reduxWrapper(
));
+ expect(loginPage.find(`button#${ssoProvider.id}`).length).toEqual(1);
+ });
+
+ it('should not display institution login option when no secondary providers are present', () => {
+ const root = mount(reduxWrapper(
));
+ expect(root.text().includes('Use my university info')).toBe(false);
+ });
+
+ it('should display institution login option when secondary providers are present', () => {
+ store = mockStore({
+ ...initialState,
+ commonComponents: {
+ ...initialState.commonComponents,
+ thirdPartyAuthContext: {
+ ...initialState.commonComponents.thirdPartyAuthContext,
+ secondaryProviders: [secondaryProviders],
+ },
+ },
+ });
+
+ const loginPage = mount(reduxWrapper(
));
+ expect(loginPage.text().includes('Use my university info')).toBe(true);
+
+ // on clicking "Use my university info" button, it should display institution login page
+ loginPage.find(RenderInstitutionButton).simulate('click', { institutionLogin: true });
+ expect(loginPage.text().includes('Test University')).toBe(true);
+ });
+
+ // ******** test alert messages ********
+
+ it('should match login error message', () => {
+ const errorMessage = 'Email or password is incorrect.';
+ store = mockStore({
+ ...initialState,
+ login: {
+ ...initialState.login,
+ loginError: { value: errorMessage },
+ },
+ });
+
+ const loginPage = mount(reduxWrapper(
));
+ expect(loginPage.find('#login-failure-alert').first().text()).toEqual(`We couldn't sign you in.${errorMessage}`);
+ });
+
+ it('should match account activation message', () => {
+ const activationMessage = 'Success! You have activated your account.'
+ + 'You will now receive email updates and alerts from us related '
+ + 'to the courses you are enrolled in. Sign in to continue.';
+
+ delete window.location;
+ window.location = { href: getConfig().BASE_URL.concat('/login'), search: '?account_activation_status=success' };
+
+ const loginPage = mount(reduxWrapper(
));
+ expect(loginPage.find('div#account-activation-message').text()).toEqual(activationMessage);
});
it('should match third party auth alert', () => {
@@ -338,154 +259,119 @@ describe('LoginPage', () => {
expect(loginPage.find('#tpa-alert').find('span').text()).toEqual(expectedMessage);
});
- it('should display institution login button', () => {
+ it('should match forget password confirmation message', () => {
store = mockStore({
...initialState,
- commonComponents: {
- ...initialState.commonComponents,
- thirdPartyAuthContext: {
- ...initialState.commonComponents.thirdPartyAuthContext,
- secondaryProviders: [secondaryProviders],
+ forgotPassword: { status: 'complete', email: 'test@example.com' },
+ });
+
+ const confirmationMessage = 'Check your email'
+ + 'You entered test@example.com. If this email address is associated with your edX account, '
+ + 'we will send a message with password recovery instructions to this email address.If you do not '
+ + '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 loginPage = mount(reduxWrapper(
));
+ expect(loginPage.find('#confirmation-alert').first().text()).toEqual(confirmationMessage);
+ });
+
+ // ******** test redirection ********
+
+ it('should redirect to url returned by login endpoint', () => {
+ const dasboardUrl = 'http://localhost:18000/enterprise/select/active/?success_url=/dashboard';
+ store = mockStore({
+ ...initialState,
+ login: {
+ ...initialState.login,
+ loginResult: {
+ success: true,
+ redirectUrl: dasboardUrl,
},
},
});
- const root = mount(reduxWrapper(
));
- expect(root.text().includes('Use my university info')).toBe(true);
+
+ delete window.location;
+ window.location = { href: getConfig().BASE_URL };
+ renderer.create(reduxWrapper(
));
+ expect(window.location.href).toBe(dasboardUrl);
});
- it('should not display institution login button', () => {
- const root = mount(reduxWrapper(
));
- expect(root.text().includes('Use my university info')).toBe(false);
- });
-
- it('should display institution login page', () => {
+ it('should redirect to finishAuthUrl upon successful login via SSO', () => {
+ const authCompleteUrl = '/auth/complete/google-oauth2/';
store = mockStore({
...initialState,
+ login: {
+ ...initialState.login,
+ loginResult: {
+ success: true,
+ redirectUrl: '',
+ },
+ },
commonComponents: {
...initialState.commonComponents,
thirdPartyAuthContext: {
...initialState.commonComponents.thirdPartyAuthContext,
- secondaryProviders: [secondaryProviders],
+ finishAuthUrl: authCompleteUrl,
},
},
});
- const loginPage = mount(reduxWrapper(
));
- loginPage.find(RenderInstitutionButton).simulate('click', { institutionLogin: true });
- expect(loginPage.text().includes('Test University')).toBe(true);
+
+ delete window.location;
+ window.location = { href: getConfig().BASE_URL };
+
+ renderer.create(reduxWrapper(
));
+ expect(window.location.href).toBe(getConfig().LMS_BASE_URL + authCompleteUrl);
});
- it('send tracking event when create account link is clicked', () => {
- const loginPage = mount(reduxWrapper(
));
-
- loginPage.find('a[href*="/register"]').simulate('click');
- loginPage.update();
- expect(analytics.sendTrackEvent).toHaveBeenCalledWith('edx.bi.register_form.toggled', { category: 'user-engagement' });
- });
-
- it('send page event when login page is rendered', () => {
- mount(reduxWrapper(
));
- expect(analytics.sendPageEvent).toHaveBeenCalledWith('login_and_registration', 'login');
- });
-
- it('send tracking and page events when institutional button is clicked', () => {
+ it('should redirect to social auth provider url on SSO button click', () => {
+ const loginUrl = '/auth/login/apple-id/?auth_entry=login&next=/dashboard';
store = mockStore({
...initialState,
commonComponents: {
...initialState.commonComponents,
thirdPartyAuthContext: {
...initialState.commonComponents.thirdPartyAuthContext,
- secondaryProviders: [secondaryProviders],
+ providers: [{
+ ...ssoProvider,
+ loginUrl,
+ }],
},
},
});
- const loginPage = mount(reduxWrapper(
));
- loginPage.find(RenderInstitutionButton).simulate('click', { institutionLogin: true });
- expect(analytics.sendTrackEvent).toHaveBeenCalledWith('edx.bi.institution_login_form.toggled', { category: 'user-engagement' });
- expect(analytics.sendPageEvent).toHaveBeenCalledWith('login_and_registration', 'institution_login');
- });
- it('check cookie rendered', () => {
- const loginPage = mount(reduxWrapper(
));
- expect(loginPage.find(
)).toBeTruthy();
- });
+ delete window.location;
+ window.location = { href: getConfig().BASE_URL };
- it('form only be scrollable on submission', () => {
const loginPage = mount(reduxWrapper(
));
- loginPage.find('input#password').simulate('change', { target: { value: 'test@example.com', name: 'password' } });
- loginPage.find('button.btn-brand').simulate('click');
-
- expect(loginPage.find(
)).toBeTruthy();
- expect(loginPage.find('LoginPage').state('isSubmitted')).toEqual(true);
+ loginPage.find('button#oa2-apple-id').simulate('click');
+ expect(window.location.href).toBe(getConfig().LMS_BASE_URL + loginUrl);
});
- it('should render tpa button for tpa_hint id in primary provider', () => {
- const expectedMessage = `Sign in using ${appleProvider.name}`;
+ // ******** test hinted third party auth ********
+
+ it('should render tpa button for tpa_hint id matching one of the primary providers', () => {
store = mockStore({
...initialState,
commonComponents: {
...initialState.commonComponents,
thirdPartyAuthContext: {
...initialState.commonComponents.thirdPartyAuthContext,
- providers: [appleProvider],
+ providers: [ssoProvider],
},
thirdPartyAuthApiStatus: COMPLETE_STATE,
},
});
delete window.location;
- window.location = { href: getConfig().BASE_URL.concat('/login'), search: `?next=/dashboard&tpa_hint=${appleProvider.id}` };
- appleProvider.iconImage = null;
+ window.location = { href: getConfig().BASE_URL.concat('/login'), search: `?next=/dashboard&tpa_hint=${ssoProvider.id}` };
+ ssoProvider.iconImage = null;
const loginPage = mount(reduxWrapper(
));
- expect(loginPage.find(`button#${appleProvider.id}`).find('span').text()).toEqual(expectedMessage);
+ expect(loginPage.find(`button#${ssoProvider.id}`).find('span').text()).toEqual(`Sign in using ${ssoProvider.name}`);
});
- it('should render regular tpa button for invalid tpa_hint value', () => {
- const expectedMessage = `${appleProvider.name}`;
- store = mockStore({
- ...initialState,
- commonComponents: {
- ...initialState.commonComponents,
- thirdPartyAuthContext: {
- ...initialState.commonComponents.thirdPartyAuthContext,
- providers: [appleProvider],
- },
- thirdPartyAuthApiStatus: COMPLETE_STATE,
- },
- });
-
- delete window.location;
- window.location = { href: getConfig().BASE_URL.concat('/login'), search: '?next=/dashboard&tpa_hint=invalid' };
- appleProvider.iconImage = null;
-
- const loginPage = mount(reduxWrapper(
));
- expect(loginPage.find(`button#${appleProvider.id}`).find('span#provider-name').text()).toEqual(expectedMessage);
- });
-
- it('should render tpa button for tpa_hint id in secondary provider', () => {
- const expectedMessage = `Sign in using ${secondaryProviders.name}`;
- store = mockStore({
- ...initialState,
- commonComponents: {
- ...initialState.commonComponents,
- thirdPartyAuthContext: {
- ...initialState.commonComponents.thirdPartyAuthContext,
- secondaryProviders: [secondaryProviders],
- },
- thirdPartyAuthApiStatus: COMPLETE_STATE,
- },
- });
- delete window.location;
- window.location = { href: getConfig().BASE_URL.concat('/login'), search: `?next=/dashboard&tpa_hint=${secondaryProviders.id}` };
- secondaryProviders.iconImage = null;
-
- const loginPage = mount(reduxWrapper(
));
- expect(loginPage.find(`button#${secondaryProviders.id}`).find('span').text()).toEqual(expectedMessage);
- });
-
- it('should redirect to idp page if skipHinetedLogin is true', () => {
- secondaryProviders.skipHintedLogin = true;
+ it('should render tpa button for tpa_hint id matching one of the secondary providers', () => {
store = mockStore({
...initialState,
commonComponents: {
@@ -506,6 +392,67 @@ describe('LoginPage', () => {
expect(window.location.href).toEqual(getConfig().LMS_BASE_URL + secondaryProviders.loginUrl);
});
+ it('should render regular tpa button for invalid tpa_hint value', () => {
+ store = mockStore({
+ ...initialState,
+ commonComponents: {
+ ...initialState.commonComponents,
+ thirdPartyAuthContext: {
+ ...initialState.commonComponents.thirdPartyAuthContext,
+ providers: [ssoProvider],
+ },
+ thirdPartyAuthApiStatus: COMPLETE_STATE,
+ },
+ });
+
+ delete window.location;
+ window.location = { href: getConfig().BASE_URL.concat('/login'), search: '?next=/dashboard&tpa_hint=invalid' };
+ ssoProvider.iconImage = null;
+
+ const loginPage = mount(reduxWrapper(
));
+ expect(loginPage.find(`button#${ssoProvider.id}`).find('span#provider-name').text()).toEqual(`${ssoProvider.name}`);
+ });
+
+ // ******** miscellaneous tests ********
+
+ it('should render cookie banner', () => {
+ const loginPage = mount(reduxWrapper(
));
+ expect(loginPage.find(
)).toBeTruthy();
+ });
+
+ it('should send page event when login page is rendered', () => {
+ mount(reduxWrapper(
));
+ expect(analytics.sendPageEvent).toHaveBeenCalledWith('login_and_registration', 'login');
+ });
+
+ it('send tracking and page events when institutional button is clicked', () => {
+ store = mockStore({
+ ...initialState,
+ commonComponents: {
+ ...initialState.commonComponents,
+ thirdPartyAuthContext: {
+ ...initialState.commonComponents.thirdPartyAuthContext,
+ secondaryProviders: [secondaryProviders],
+ },
+ },
+ });
+ const loginPage = mount(reduxWrapper(
));
+ loginPage.find(RenderInstitutionButton).simulate('click', { institutionLogin: true });
+
+ expect(analytics.sendTrackEvent).toHaveBeenCalledWith('edx.bi.institution_login_form.toggled', { category: 'user-engagement' });
+ expect(analytics.sendPageEvent).toHaveBeenCalledWith('login_and_registration', 'institution_login');
+ });
+
+ it('tests that form is only scrollable on form submission', () => {
+ const loginPage = mount(reduxWrapper(
));
+
+ loginPage.find('input#password').simulate('change', { target: { value: 'test', name: 'password' } });
+ loginPage.find('button.btn-brand').simulate('click');
+
+ expect(loginPage.find(
)).toBeTruthy();
+ expect(loginPage.find('LoginPage').state('isSubmitted')).toEqual(true);
+ });
+
it('should set login survey cookie', () => {
store = mockStore({
...initialState,
diff --git a/src/login/tests/__snapshots__/LoginPage.test.jsx.snap b/src/login/tests/__snapshots__/LoginPage.test.jsx.snap
deleted file mode 100644
index 226f0a54..00000000
--- a/src/login/tests/__snapshots__/LoginPage.test.jsx.snap
+++ /dev/null
@@ -1,889 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`LoginPage should match TPA provider snapshot 1`] = `
-
-
-
-
- First time here?
-
- Create an account
- .
-
-
-
-
- Sign in
-
-
-
-
- or sign in with
-
-
-
-
-
-
-
- Apple
-
-
- Sign in with Apple
-
-
-
-
-
-
-`;
-
-exports[`LoginPage should match default section snapshot 1`] = `
-
-`;
-
-exports[`LoginPage should match forget password alert message snapshot 1`] = `
-
-
-
-
-
- Check your email
-
-
-
- You entered
-
- test@example.com
-
- . If this email address is associated with your edX account, we will send a message with password recovery instructions to this email address.
-
-
-
- If you do not 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
-
- .
-
-
-
-
- First time here?
-
- Create an account
- .
-
-
-
-
- Sign in
-
-
-
-
-
-`;
-
-exports[`LoginPage should match pending button state snapshot 1`] = `
-
-`;
-
-exports[`LoginPage should show error message 1`] = `
-
-
-
-
-
- We couldn't sign you in.
-
-
-
- Email or password is incorrect.
-
-
-
-
- First time here?
-
- Create an account
- .
-
-
-
-
- Sign in
-
-
-
-
-
-`;
diff --git a/src/register/RegistrationPage.jsx b/src/register/RegistrationPage.jsx
index 496f8bb9..477edb82 100644
--- a/src/register/RegistrationPage.jsx
+++ b/src/register/RegistrationPage.jsx
@@ -401,13 +401,13 @@ class RegistrationPage extends React.Component {
secondaryProviders={this.props.thirdPartyAuthContext.secondaryProviders}
buttonTitle={intl.formatMessage(messages['register.institution.login.button'])}
/>
-
+
>
);
} else if (thirdPartyAuthApiStatus === PENDING_STATE) {
- thirdPartyComponent =
;
+ thirdPartyComponent =
;
}
return thirdPartyComponent;
}
@@ -571,11 +571,7 @@ class RegistrationPage extends React.Component {
pending: '',
}}
icons={{
- pending: (
-
- {intl.formatMessage(messages['create.an.account.btn.pending.state'])}
-
- ),
+ pending:
,
}}
onClick={this.handleSubmit}
onMouseDown={(e) => e.preventDefault()}
diff --git a/src/register/messages.jsx b/src/register/messages.jsx
index e582d67d..cb5bdf1d 100644
--- a/src/register/messages.jsx
+++ b/src/register/messages.jsx
@@ -76,7 +76,7 @@ const messages = defineMessages({
'create.an.account.btn.pending.state': {
id: 'create.an.account.btn.pending.state',
defaultMessage: 'Loading',
- description: 'Message that appears for screen readers only when button is in pending state',
+ description: 'Title of icon that appears when button is in pending state',
},
'register.rate.limit.reached.message': {
id: 'register.rate.limit.reached.message',
diff --git a/src/register/tests/RegistrationPage.test.jsx b/src/register/tests/RegistrationPage.test.jsx
index 41ed8a92..d14c88e1 100644
--- a/src/register/tests/RegistrationPage.test.jsx
+++ b/src/register/tests/RegistrationPage.test.jsx
@@ -387,12 +387,12 @@ describe('RegistrationPage', () => {
const registrationPage = mount(reduxWrapper(
));
const button = registrationPage.find('button[type="submit"] span').first();
- // submit button has no text when it is loading
- expect(button.text()).toEqual('');
+ // test pending state icon and that pending state icon has title associated with it
expect(button.find('svg').prop('className')).toEqual(expect.stringContaining('fa-spinner'));
+ expect(button.find('svg').find('title').text()).toEqual('Loading');
});
- it('should match single sign on provider button', () => {
+ it('should show single sign on provider button', () => {
store = mockStore({
...initialState,
commonComponents: {
@@ -405,7 +405,7 @@ describe('RegistrationPage', () => {
});
const registrationPage = mount(reduxWrapper(
));
- expect(registrationPage.find('button#oa2-apple-id').length).toEqual(1);
+ expect(registrationPage.find(`button#${ssoProvider.id}`).length).toEqual(1);
});
it('should display institution register button', () => {
@@ -547,7 +547,7 @@ describe('RegistrationPage', () => {
// ******** test hinted third party auth ********
- it('should render tpa button for tpa_hint id in primary provider', () => {
+ it('should render tpa button for tpa_hint id matching one of the primary providers', () => {
const expectedMessage = `Sign in using ${ssoProvider.name}`;
store = mockStore({
...initialState,
@@ -569,7 +569,7 @@ describe('RegistrationPage', () => {
expect(registerPage.find(`button#${ssoProvider.id}`).find('span').text()).toEqual(expectedMessage);
});
- it('should render tpa button for tpa_hint id in secondary provider', () => {
+ it('should render tpa button for tpa_hint id matching one of the secondary providers', () => {
store = mockStore({
...initialState,
commonComponents: {
@@ -632,12 +632,12 @@ describe('RegistrationPage', () => {
expect(shouldUpdate).toBe(false);
});
- it('check cookie rendered', () => {
+ it('should render cookie banner', () => {
const registerPage = mount(reduxWrapper(
));
expect(registerPage.find(
)).toBeTruthy();
});
- it('send page event when register page is rendered', () => {
+ it('should send page event when register page is rendered', () => {
mount(reduxWrapper(
));
expect(analytics.sendPageEvent).toHaveBeenCalledWith('login_and_registration', 'register');
});