mirror of
https://github.com/ansible/awx.git
synced 2026-03-03 09:48:51 -03:30
Fixes login page after pf-react added LoginPage and LoginForm components
This commit is contained in:
@@ -2,33 +2,31 @@ import React from 'react';
|
|||||||
import { MemoryRouter } from 'react-router-dom';
|
import { MemoryRouter } from 'react-router-dom';
|
||||||
import { mount, shallow } from 'enzyme';
|
import { mount, shallow } from 'enzyme';
|
||||||
import { asyncFlush } from '../../jest.setup';
|
import { asyncFlush } from '../../jest.setup';
|
||||||
import LoginPage from '../../src/pages/Login';
|
import AtLogin from '../../src/pages/Login';
|
||||||
import api from '../../src/api';
|
import api from '../../src/api';
|
||||||
|
|
||||||
const LOGIN_ERROR_MESSAGE = 'Invalid username or password. Please try again.';
|
describe('<Login />', () => {
|
||||||
|
|
||||||
describe('<LoginPage />', () => {
|
|
||||||
let loginWrapper;
|
let loginWrapper;
|
||||||
|
let atLogin;
|
||||||
let loginPage;
|
let loginPage;
|
||||||
let loginForm;
|
let loginForm;
|
||||||
let usernameInput;
|
let usernameInput;
|
||||||
let passwordInput;
|
let passwordInput;
|
||||||
let errorTextArea;
|
|
||||||
let submitButton;
|
let submitButton;
|
||||||
let defaultLogo;
|
let loginHeaderLogo;
|
||||||
|
|
||||||
const findChildren = () => {
|
const findChildren = () => {
|
||||||
|
atLogin = loginWrapper.find('AtLogin');
|
||||||
loginPage = loginWrapper.find('LoginPage');
|
loginPage = loginWrapper.find('LoginPage');
|
||||||
loginForm = loginWrapper.find('form.pf-c-form');
|
loginForm = loginWrapper.find('LoginForm');
|
||||||
usernameInput = loginWrapper.find('.pf-c-form__group#username TextInput');
|
usernameInput = loginWrapper.find('input#pf-login-username-id');
|
||||||
passwordInput = loginWrapper.find('.pf-c-form__group#password TextInput');
|
passwordInput = loginWrapper.find('input#pf-login-password-id');
|
||||||
errorTextArea = loginWrapper.find('.pf-c-form__helper-text.pf-m-error');
|
|
||||||
submitButton = loginWrapper.find('Button[type="submit"]');
|
submitButton = loginWrapper.find('Button[type="submit"]');
|
||||||
defaultLogo = loginWrapper.find('TowerLogo');
|
loginHeaderLogo = loginWrapper.find('LoginHeaderBrand Brand');
|
||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
loginWrapper = mount(<MemoryRouter><LoginPage /></MemoryRouter>);
|
loginWrapper = mount(<MemoryRouter><AtLogin /></MemoryRouter>);
|
||||||
findChildren();
|
findChildren();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -38,62 +36,78 @@ describe('<LoginPage />', () => {
|
|||||||
|
|
||||||
test('initially renders without crashing', () => {
|
test('initially renders without crashing', () => {
|
||||||
expect(loginWrapper.length).toBe(1);
|
expect(loginWrapper.length).toBe(1);
|
||||||
|
expect(loginPage.length).toBe(1);
|
||||||
expect(loginForm.length).toBe(1);
|
expect(loginForm.length).toBe(1);
|
||||||
expect(usernameInput.length).toBe(1);
|
expect(usernameInput.length).toBe(1);
|
||||||
expect(usernameInput.props().value).toBe('');
|
expect(usernameInput.props().value).toBe('');
|
||||||
expect(passwordInput.length).toBe(1);
|
expect(passwordInput.length).toBe(1);
|
||||||
expect(passwordInput.props().value).toBe('');
|
expect(passwordInput.props().value).toBe('');
|
||||||
expect(errorTextArea.length).toBe(1);
|
expect(atLogin.state().isValidPassword).toBe(true);
|
||||||
expect(loginPage.state().error).toBe('');
|
|
||||||
expect(submitButton.length).toBe(1);
|
expect(submitButton.length).toBe(1);
|
||||||
expect(submitButton.props().isDisabled).toBe(false);
|
expect(submitButton.props().isDisabled).toBe(false);
|
||||||
expect(defaultLogo.length).toBe(1);
|
expect(loginHeaderLogo.length).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('custom logo renders Brand component', () => {
|
test('custom logo renders Brand component with correct src and alt', () => {
|
||||||
loginWrapper = mount(<MemoryRouter><LoginPage logo="hey" /></MemoryRouter>);
|
loginWrapper = mount(<MemoryRouter><AtLogin logo="images/foo.jpg" alt="Foo Application" /></MemoryRouter>);
|
||||||
findChildren();
|
findChildren();
|
||||||
expect(defaultLogo.length).toBe(0);
|
expect(loginHeaderLogo.length).toBe(1);
|
||||||
|
expect(loginHeaderLogo.props().src).toBe('data:image/jpeg;images/foo.jpg');
|
||||||
|
expect(loginHeaderLogo.props().alt).toBe('Foo Application');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('default logo renders Brand component with correct src and alt', () => {
|
||||||
|
loginWrapper = mount(<MemoryRouter><AtLogin /></MemoryRouter>);
|
||||||
|
findChildren();
|
||||||
|
expect(loginHeaderLogo.length).toBe(1);
|
||||||
|
expect(loginHeaderLogo.props().src).toBe('tower-logo-header.svg');
|
||||||
|
expect(loginHeaderLogo.props().alt).toBe('Ansible Tower');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('state maps to un/pw input value props', () => {
|
test('state maps to un/pw input value props', () => {
|
||||||
loginPage.setState({ username: 'un', password: 'pw' });
|
atLogin.setState({ username: 'un', password: 'pw' });
|
||||||
expect(loginPage.state().username).toBe('un');
|
expect(atLogin.state().username).toBe('un');
|
||||||
expect(loginPage.state().password).toBe('pw');
|
expect(atLogin.state().password).toBe('pw');
|
||||||
findChildren();
|
findChildren();
|
||||||
expect(usernameInput.props().value).toBe('un');
|
expect(usernameInput.props().value).toBe('un');
|
||||||
expect(passwordInput.props().value).toBe('pw');
|
expect(passwordInput.props().value).toBe('pw');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('updating un/pw clears out error', () => {
|
test('updating un/pw clears out error', () => {
|
||||||
loginPage.setState({ error: 'error!' });
|
atLogin.setState({ isValidPassword: false });
|
||||||
usernameInput.instance().props.onChange('uname');
|
expect(loginWrapper.find('.pf-c-form__helper-text.pf-m-error').length).toBe(1);
|
||||||
expect(loginPage.state().username).toBe('uname');
|
usernameInput.instance().value = 'uname';
|
||||||
expect(loginPage.state().error).toBe('');
|
usernameInput.simulate('change');
|
||||||
loginPage.setState({ error: 'error!' });
|
expect(atLogin.state().username).toBe('uname');
|
||||||
passwordInput.instance().props.onChange('pword');
|
expect(atLogin.state().isValidPassword).toBe(true);
|
||||||
expect(loginPage.state().password).toBe('pword');
|
expect(loginWrapper.find('.pf-c-form__helper-text.pf-m-error').length).toBe(0);
|
||||||
expect(loginPage.state().error).toBe('');
|
atLogin.setState({ isValidPassword: false });
|
||||||
|
expect(loginWrapper.find('.pf-c-form__helper-text.pf-m-error').length).toBe(1);
|
||||||
|
passwordInput.instance().value = 'pword';
|
||||||
|
passwordInput.simulate('change');
|
||||||
|
expect(atLogin.state().password).toBe('pword');
|
||||||
|
expect(atLogin.state().isValidPassword).toBe(true);
|
||||||
|
expect(loginWrapper.find('.pf-c-form__helper-text.pf-m-error').length).toBe(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('api.login not called when loading', () => {
|
test('api.login not called when loading', () => {
|
||||||
api.login = jest.fn().mockImplementation(() => Promise.resolve({}));
|
api.login = jest.fn().mockImplementation(() => Promise.resolve({}));
|
||||||
expect(loginPage.state().loading).toBe(false);
|
expect(atLogin.state().loading).toBe(false);
|
||||||
loginPage.setState({ loading: true });
|
atLogin.setState({ loading: true });
|
||||||
submitButton.simulate('submit');
|
submitButton.simulate('click');
|
||||||
expect(api.login).toHaveBeenCalledTimes(0);
|
expect(api.login).toHaveBeenCalledTimes(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('submit calls api.login successfully', async () => {
|
test('submit calls api.login successfully', async () => {
|
||||||
api.login = jest.fn().mockImplementation(() => Promise.resolve({}));
|
api.login = jest.fn().mockImplementation(() => Promise.resolve({}));
|
||||||
expect(loginPage.state().loading).toBe(false);
|
expect(atLogin.state().loading).toBe(false);
|
||||||
loginPage.setState({ username: 'unamee', password: 'pwordd' });
|
atLogin.setState({ username: 'unamee', password: 'pwordd' });
|
||||||
submitButton.simulate('submit');
|
submitButton.simulate('click');
|
||||||
expect(api.login).toHaveBeenCalledTimes(1);
|
expect(api.login).toHaveBeenCalledTimes(1);
|
||||||
expect(api.login).toHaveBeenCalledWith('unamee', 'pwordd');
|
expect(api.login).toHaveBeenCalledWith('unamee', 'pwordd');
|
||||||
expect(loginPage.state().loading).toBe(true);
|
expect(atLogin.state().loading).toBe(true);
|
||||||
await asyncFlush();
|
await asyncFlush();
|
||||||
expect(loginPage.state().loading).toBe(false);
|
expect(atLogin.state().loading).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('submit calls api.login handles 401 error', async () => {
|
test('submit calls api.login handles 401 error', async () => {
|
||||||
@@ -102,15 +116,16 @@ describe('<LoginPage />', () => {
|
|||||||
err.response = { status: 401, message: 'problem' };
|
err.response = { status: 401, message: 'problem' };
|
||||||
return Promise.reject(err);
|
return Promise.reject(err);
|
||||||
});
|
});
|
||||||
expect(loginPage.state().loading).toBe(false);
|
expect(atLogin.state().loading).toBe(false);
|
||||||
loginPage.setState({ username: 'unamee', password: 'pwordd' });
|
expect(atLogin.state().isValidPassword).toBe(true);
|
||||||
submitButton.simulate('submit');
|
atLogin.setState({ username: 'unamee', password: 'pwordd' });
|
||||||
|
submitButton.simulate('click');
|
||||||
expect(api.login).toHaveBeenCalledTimes(1);
|
expect(api.login).toHaveBeenCalledTimes(1);
|
||||||
expect(api.login).toHaveBeenCalledWith('unamee', 'pwordd');
|
expect(api.login).toHaveBeenCalledWith('unamee', 'pwordd');
|
||||||
expect(loginPage.state().loading).toBe(true);
|
expect(atLogin.state().loading).toBe(true);
|
||||||
await asyncFlush();
|
await asyncFlush();
|
||||||
expect(loginPage.state().error).toBe(LOGIN_ERROR_MESSAGE);
|
expect(atLogin.state().isValidPassword).toBe(false);
|
||||||
expect(loginPage.state().loading).toBe(false);
|
expect(atLogin.state().loading).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('submit calls api.login handles non-401 error', async () => {
|
test('submit calls api.login handles non-401 error', async () => {
|
||||||
@@ -119,21 +134,20 @@ describe('<LoginPage />', () => {
|
|||||||
err.response = { status: 500, message: 'problem' };
|
err.response = { status: 500, message: 'problem' };
|
||||||
return Promise.reject(err);
|
return Promise.reject(err);
|
||||||
});
|
});
|
||||||
expect(loginPage.state().loading).toBe(false);
|
expect(atLogin.state().loading).toBe(false);
|
||||||
loginPage.setState({ username: 'unamee', password: 'pwordd' });
|
atLogin.setState({ username: 'unamee', password: 'pwordd' });
|
||||||
submitButton.simulate('submit');
|
submitButton.simulate('click');
|
||||||
expect(api.login).toHaveBeenCalledTimes(1);
|
expect(api.login).toHaveBeenCalledTimes(1);
|
||||||
expect(api.login).toHaveBeenCalledWith('unamee', 'pwordd');
|
expect(api.login).toHaveBeenCalledWith('unamee', 'pwordd');
|
||||||
expect(loginPage.state().loading).toBe(true);
|
expect(atLogin.state().loading).toBe(true);
|
||||||
await asyncFlush();
|
await asyncFlush();
|
||||||
expect(loginPage.state().error).toBe('');
|
expect(atLogin.state().loading).toBe(false);
|
||||||
expect(loginPage.state().loading).toBe(false);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('render Redirect to / when already authenticated', () => {
|
test('render Redirect to / when already authenticated', () => {
|
||||||
api.isAuthenticated = jest.fn();
|
api.isAuthenticated = jest.fn();
|
||||||
api.isAuthenticated.mockReturnValue(true);
|
api.isAuthenticated.mockReturnValue(true);
|
||||||
loginWrapper = shallow(<LoginPage />);
|
loginWrapper = shallow(<AtLogin />);
|
||||||
const redirectElem = loginWrapper.find('Redirect');
|
const redirectElem = loginWrapper.find('Redirect');
|
||||||
expect(redirectElem.length).toBe(1);
|
expect(redirectElem.length).toBe(1);
|
||||||
expect(redirectElem.props().to).toBe('/');
|
expect(redirectElem.props().to).toBe('/');
|
||||||
|
|||||||
@@ -1,33 +1,22 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { Redirect } from 'react-router-dom';
|
import { Redirect } from 'react-router-dom';
|
||||||
import {
|
import {
|
||||||
Brand,
|
LoginForm,
|
||||||
Button,
|
LoginPage,
|
||||||
Level,
|
|
||||||
LevelItem,
|
|
||||||
Login,
|
|
||||||
LoginBox,
|
|
||||||
LoginBoxHeader,
|
|
||||||
LoginBoxBody,
|
|
||||||
LoginFooter,
|
|
||||||
LoginHeaderBrand,
|
|
||||||
TextInput,
|
|
||||||
} from '@patternfly/react-core';
|
} from '@patternfly/react-core';
|
||||||
|
|
||||||
import TowerLogo from '../components/TowerLogo';
|
import towerLogo from '../../images/tower-logo-header.svg';
|
||||||
import api from '../api';
|
import api from '../api';
|
||||||
|
|
||||||
const LOGIN_ERROR_MESSAGE = 'Invalid username or password. Please try again.';
|
class AtLogin extends Component {
|
||||||
|
|
||||||
class LoginPage extends Component {
|
|
||||||
constructor (props) {
|
constructor (props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
username: '',
|
username: '',
|
||||||
password: '',
|
password: '',
|
||||||
error: '',
|
isValidPassword: true,
|
||||||
loading: false,
|
loading: false
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,9 +26,9 @@ class LoginPage extends Component {
|
|||||||
|
|
||||||
safeSetState = obj => !this.unmounting && this.setState(obj);
|
safeSetState = obj => !this.unmounting && this.setState(obj);
|
||||||
|
|
||||||
handleUsernameChange = value => this.safeSetState({ username: value, error: '' });
|
handleUsernameChange = value => this.safeSetState({ username: value, isValidPassword: true });
|
||||||
|
|
||||||
handlePasswordChange = value => this.safeSetState({ password: value, error: '' });
|
handlePasswordChange = value => this.safeSetState({ password: value, isValidPassword: true });
|
||||||
|
|
||||||
handleSubmit = async event => {
|
handleSubmit = async event => {
|
||||||
const { username, password, loading } = this.state;
|
const { username, password, loading } = this.state;
|
||||||
@@ -53,7 +42,7 @@ class LoginPage extends Component {
|
|||||||
await api.login(username, password);
|
await api.login(username, password);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error.response.status === 401) {
|
if (error.response.status === 401) {
|
||||||
this.safeSetState({ error: LOGIN_ERROR_MESSAGE });
|
this.safeSetState({ isValidPassword: false });
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
this.safeSetState({ loading: false });
|
this.safeSetState({ loading: false });
|
||||||
@@ -62,75 +51,39 @@ class LoginPage extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { username, password, loading, error } = this.state;
|
const { username, password, isValidPassword } = this.state;
|
||||||
const { logo, loginInfo } = this.props;
|
const { logo, alt } = this.props;
|
||||||
|
const logoSrc = logo ? `data:image/jpeg;${logo}` : towerLogo;
|
||||||
|
const logoAlt = alt || 'Ansible Tower';
|
||||||
|
const LOGIN_ERROR_MESSAGE = 'Invalid username or password. Please try again.';
|
||||||
|
const LOGIN_TITLE = 'Welcome to Ansible Tower! Please Sign In.';
|
||||||
|
const LOGIN_USERNAME = 'Username';
|
||||||
|
const LOGIN_PASSWORD = 'Password';
|
||||||
|
|
||||||
if (api.isAuthenticated()) {
|
if (api.isAuthenticated()) {
|
||||||
return (<Redirect to="/" />);
|
return (<Redirect to="/" />);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Login
|
<LoginPage
|
||||||
header={(
|
mainBrandImgSrc={logoSrc}
|
||||||
<LoginHeaderBrand>
|
mainBrandImgAlt={logoAlt}
|
||||||
{logo ? <Brand src={`data:image/jpeg;${logo}`} alt="logo brand" /> : <TowerLogo />}
|
loginTitle={LOGIN_TITLE}
|
||||||
</LoginHeaderBrand>
|
|
||||||
)}
|
|
||||||
footer={<LoginFooter>{ loginInfo }</LoginFooter>}
|
|
||||||
>
|
>
|
||||||
<LoginBox>
|
<LoginForm
|
||||||
<LoginBoxHeader>
|
usernameLabel={LOGIN_USERNAME}
|
||||||
Welcome to Ansible Tower! Please Sign In.
|
usernameValue={username}
|
||||||
</LoginBoxHeader>
|
onChangeUsername={this.handleUsernameChange}
|
||||||
<LoginBoxBody>
|
passwordLabel={LOGIN_PASSWORD}
|
||||||
<form className="pf-c-form" onSubmit={this.handleSubmit}>
|
passwordValue={password}
|
||||||
<div className="pf-c-form__group" id="username">
|
onChangePassword={this.handlePasswordChange}
|
||||||
<label className="pf-c-form__label" htmlFor="username">
|
isValidPassword={isValidPassword}
|
||||||
Username
|
passwordHelperTextInvalid={LOGIN_ERROR_MESSAGE}
|
||||||
<span className="pf-c-form__label__required" aria-hidden="true">*</span>
|
onLoginButtonClick={this.handleSubmit}
|
||||||
</label>
|
/>
|
||||||
<TextInput
|
</LoginPage>
|
||||||
autoComplete="off"
|
|
||||||
aria-label="Username"
|
|
||||||
name="username"
|
|
||||||
type="text"
|
|
||||||
isDisabled={loading}
|
|
||||||
value={username}
|
|
||||||
onChange={this.handleUsernameChange}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="pf-c-form__group" id="password">
|
|
||||||
<label className="pf-c-form__label" htmlFor="pw">
|
|
||||||
Password
|
|
||||||
<span className="pf-c-form__label__required" aria-hidden="true">*</span>
|
|
||||||
</label>
|
|
||||||
<TextInput
|
|
||||||
aria-label="Password"
|
|
||||||
name="password"
|
|
||||||
type="password"
|
|
||||||
isDisabled={loading}
|
|
||||||
value={password}
|
|
||||||
onChange={this.handlePasswordChange}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<Level>
|
|
||||||
<LevelItem>
|
|
||||||
<p className="pf-c-form__helper-text pf-m-error" aria-live="polite">
|
|
||||||
{ error }
|
|
||||||
</p>
|
|
||||||
</LevelItem>
|
|
||||||
<LevelItem>
|
|
||||||
<Button type="submit" isDisabled={loading}>
|
|
||||||
Sign In
|
|
||||||
</Button>
|
|
||||||
</LevelItem>
|
|
||||||
</Level>
|
|
||||||
</form>
|
|
||||||
</LoginBoxBody>
|
|
||||||
</LoginBox>
|
|
||||||
</Login>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default LoginPage;
|
export default AtLogin;
|
||||||
|
|||||||
Reference in New Issue
Block a user