diff --git a/__tests__/tests/LoginPage.test.jsx b/__tests__/tests/LoginPage.test.jsx
index af79bcdd88..26af4924de 100644
--- a/__tests__/tests/LoginPage.test.jsx
+++ b/__tests__/tests/LoginPage.test.jsx
@@ -1,37 +1,185 @@
import React from 'react';
import { MemoryRouter } from 'react-router-dom';
-import { shallow, mount } from 'enzyme';
+import { mount, shallow } from 'enzyme';
import LoginPage from '../../src/pages/Login';
import api from '../../src/api';
-import Dashboard from '../../src/pages/Dashboard';
+
+const LOGIN_ERROR_MESSAGE = 'Invalid username or password. Please try again.';
describe('', () => {
- let loginWrapper, usernameInput, passwordInput, errorTextArea, submitButton;
+ let loginWrapper;
+ let loginPage;
+ let loginForm;
+ let usernameInput;
+ let passwordInput;
+ let errorTextArea;
+ let submitButton;
+ let defaultLogo;
- beforeEach(() => {
- loginWrapper = mount();
+ const findChildren = () => {
+ loginPage = loginWrapper.find('LoginPage');
+ loginForm = loginWrapper.find('form.pf-c-form');
usernameInput = loginWrapper.find('.pf-c-form__group#username TextInput');
passwordInput = loginWrapper.find('.pf-c-form__group#password TextInput');
errorTextArea = loginWrapper.find('.pf-c-form__helper-text.pf-m-error');
submitButton = loginWrapper.find('Button[type="submit"]');
+ defaultLogo = loginWrapper.find('TowerLogo');
+ };
+
+ beforeEach(() => {
+ loginWrapper = mount();
+ findChildren();
+ });
+
+ afterEach(() => {
+ loginWrapper.unmount();
});
test('initially renders without crashing', () => {
expect(loginWrapper.length).toBe(1);
+ expect(loginForm.length).toBe(1);
expect(usernameInput.length).toBe(1);
+ expect(usernameInput.props().value).toBe('');
expect(passwordInput.length).toBe(1);
+ expect(passwordInput.props().value).toBe('');
expect(errorTextArea.length).toBe(1);
+ expect(loginPage.state().error).toBe('');
expect(submitButton.length).toBe(1);
+ expect(submitButton.props().isDisabled).toBe(false);
+ expect(defaultLogo.length).toBe(1);
});
- // initially renders empty username and password fields, sets empty error message and makes submit button not disabled
+ test('custom logo renders Brand component', () => {
+ loginWrapper = mount();
+ findChildren();
+ expect(defaultLogo.length).toBe(0);
+ });
- // typing into username and password fields (if the component is not unmounting) will clear out any error message
+ test('state maps to un/pw input value props', () => {
+ loginPage.setState({ username: 'un', password: 'pw' });
+ expect(loginPage.state().username).toBe('un');
+ expect(loginPage.state().password).toBe('pw');
+ findChildren();
+ expect(usernameInput.props().value).toBe('un');
+ expect(passwordInput.props().value).toBe('pw');
+ });
- // when the submit Button is clicked, as long as it is not disabled state.loading is set to true
- // api.login is called with param 1 username and param 2 password
- // if api.login returns an error, the state.error should be set to LOGIN_ERROR_MESSAGE, if the error object returned has response.status set to 401.
- // regardless of error or not, after api.login returns, state.loading should be set to false
+ test('updating un/pw clears out error', () => {
+ loginPage.setState({ error: 'error!' });
+ usernameInput.instance().props.onChange('uname');
+ expect(loginPage.state().username).toBe('uname');
+ expect(loginPage.state().error).toBe('');
+ loginPage.setState({ error: 'error!' });
+ passwordInput.instance().props.onChange('pword');
+ expect(loginPage.state().password).toBe('pword');
+ expect(loginPage.state().error).toBe('');
+ });
- // if api.isAuthenticated mock returns true, Redirect to / should be rendered
-});
\ No newline at end of file
+ test('submit calls api.login successfully', (done) => {
+ api.login = jest.fn().mockImplementation(() => {
+ const loginPromise = Promise.resolve({});
+
+ // wrapped in timeout so this .then happens after state.loading is set to
+ // false in the actual .then handler
+ //
+ // would be improved by using .finally but that's not available for
+ // some reason
+ setTimeout(() => {
+ loginPromise.then(() => {
+ expect(loginPage.state().loading).toBe(false);
+ done();
+ });
+ }, 50);
+
+ return loginPromise;
+ });
+ expect(loginPage.state().loading).toBe(false);
+ loginPage.setState({ username: 'unamee', password: 'pwordd', loading: true });
+ submitButton.simulate('submit');
+ expect(api.login).toHaveBeenCalledTimes(0);
+ loginPage.setState({ loading: false });
+ submitButton.simulate('submit');
+ expect(api.login).toHaveBeenCalledTimes(1);
+ expect(api.login).toHaveBeenCalledWith('unamee', 'pwordd');
+ expect(loginPage.state().loading).toBe(true);
+ });
+
+ test('submit calls api.login handles 401 error', (done) => {
+ api.login = jest.fn().mockImplementation(() => {
+ const err = {
+ response: { status: 401, message: 'problem' },
+ };
+
+ const loginPromise = Promise.reject(err);
+
+ // wrapped in timeout so this .then happens after state.loading is set to
+ // false in the actual .then handler
+ //
+ // would be improved by using .finally but that's not available for
+ // some reason
+ setTimeout(() => {
+ loginPromise.catch(() => {
+ expect(loginPage.state().loading).toBe(false);
+ expect(loginPage.state().error).toBe(LOGIN_ERROR_MESSAGE);
+ done();
+ });
+ }, 50);
+
+ return loginPromise;
+ });
+ expect(loginPage.state().loading).toBe(false);
+ loginPage.setState({ username: 'unamee', password: 'pwordd', loading: true });
+ submitButton.simulate('submit');
+ expect(api.login).toHaveBeenCalledTimes(0);
+ loginPage.setState({ loading: false });
+ expect(loginPage.state().error).toBe('');
+ submitButton.simulate('submit');
+ expect(api.login).toHaveBeenCalledTimes(1);
+ expect(api.login).toHaveBeenCalledWith('unamee', 'pwordd');
+ expect(loginPage.state().loading).toBe(true);
+ });
+
+ test('submit calls api.login handles 401 error', (done) => {
+ api.login = jest.fn().mockImplementation(() => {
+ const err = {
+ response: { status: 500, message: 'problem' },
+ };
+
+ const loginPromise = Promise.reject(err);
+
+ // wrapped in timeout so this .then happens after state.loading is set to
+ // false in the actual .then handler
+ //
+ // would be improved by using .finally but that's not available for
+ // some reason
+ setTimeout(() => {
+ loginPromise.catch(() => {
+ expect(loginPage.state().loading).toBe(false);
+ expect(loginPage.state().error).toBe('');
+ done();
+ });
+ }, 50);
+
+ return loginPromise;
+ });
+ expect(loginPage.state().loading).toBe(false);
+ loginPage.setState({ username: 'unamee', password: 'pwordd', loading: true });
+ submitButton.simulate('submit');
+ expect(api.login).toHaveBeenCalledTimes(0);
+ loginPage.setState({ loading: false });
+ expect(loginPage.state().error).toBe('');
+ submitButton.simulate('submit');
+ expect(api.login).toHaveBeenCalledTimes(1);
+ expect(api.login).toHaveBeenCalledWith('unamee', 'pwordd');
+ expect(loginPage.state().loading).toBe(true);
+ });
+
+ test('render Redirect to / when already authenticated', () => {
+ api.isAuthenticated = jest.fn();
+ api.isAuthenticated.mockReturnValue(true);
+ loginWrapper = shallow();
+ const redirectElem = loginWrapper.find('Redirect');
+ expect(redirectElem.length).toBe(1);
+ expect(redirectElem.props().to).toBe('/');
+ });
+});
diff --git a/src/pages/Login.jsx b/src/pages/Login.jsx
index 2a735dd70c..0ad82ef756 100644
--- a/src/pages/Login.jsx
+++ b/src/pages/Login.jsx
@@ -42,21 +42,24 @@ class LoginPage extends Component {
handlePasswordChange = value => this.safeSetState({ password: value, error: '' });
handleSubmit = event => {
- const { username, password } = this.state;
+ const { username, password, loading } = this.state;
event.preventDefault();
- this.safeSetState({ loading: true });
+ if (!loading) {
+ this.safeSetState({ loading: true });
- api.login(username, password)
- .catch(error => {
- if (error.response.status === 401) {
- this.safeSetState({ error: LOGIN_ERROR_MESSAGE });
- }
- })
- .finally(() => {
- this.safeSetState({ loading: false });
- });
+ api.login(username, password)
+ .then(() => {
+ this.safeSetState({ loading: false });
+ })
+ .catch(error => {
+ this.safeSetState({ loading: false });
+ if (error.response.status === 401) {
+ this.safeSetState({ error: LOGIN_ERROR_MESSAGE });
+ }
+ });
+ }
}
render () {