mirror of
https://github.com/ansible/awx.git
synced 2026-03-13 23:17:32 -02:30
use constructor bound methods for Login
This commit is contained in:
@@ -3,12 +3,12 @@ import { MemoryRouter } from 'react-router-dom';
|
||||
import { mount, shallow } from 'enzyme';
|
||||
import { I18nProvider } from '@lingui/react';
|
||||
import { asyncFlush } from '../../jest.setup';
|
||||
import AtLogin from '../../src/pages/Login';
|
||||
import AWXLogin from '../../src/pages/Login';
|
||||
import APIClient from '../../src/api';
|
||||
|
||||
describe('<Login />', () => {
|
||||
let loginWrapper;
|
||||
let atLogin;
|
||||
let awxLogin;
|
||||
let loginPage;
|
||||
let loginForm;
|
||||
let usernameInput;
|
||||
@@ -19,7 +19,7 @@ describe('<Login />', () => {
|
||||
const api = new APIClient({});
|
||||
|
||||
const findChildren = () => {
|
||||
atLogin = loginWrapper.find('AtLogin');
|
||||
awxLogin = loginWrapper.find('AWXLogin');
|
||||
loginPage = loginWrapper.find('LoginPage');
|
||||
loginForm = loginWrapper.find('LoginForm');
|
||||
usernameInput = loginWrapper.find('input#pf-login-username-id');
|
||||
@@ -32,7 +32,7 @@ describe('<Login />', () => {
|
||||
loginWrapper = mount(
|
||||
<MemoryRouter>
|
||||
<I18nProvider>
|
||||
<AtLogin api={api} />
|
||||
<AWXLogin api={api} />
|
||||
</I18nProvider>
|
||||
</MemoryRouter>
|
||||
);
|
||||
@@ -51,7 +51,7 @@ describe('<Login />', () => {
|
||||
expect(usernameInput.props().value).toBe('');
|
||||
expect(passwordInput.length).toBe(1);
|
||||
expect(passwordInput.props().value).toBe('');
|
||||
expect(atLogin.state().isValidPassword).toBe(true);
|
||||
expect(awxLogin.state().isInputValid).toBe(true);
|
||||
expect(submitButton.length).toBe(1);
|
||||
expect(submitButton.props().isDisabled).toBe(false);
|
||||
expect(loginHeaderLogo.length).toBe(1);
|
||||
@@ -61,7 +61,7 @@ describe('<Login />', () => {
|
||||
loginWrapper = mount(
|
||||
<MemoryRouter>
|
||||
<I18nProvider>
|
||||
<AtLogin api={api} logo="images/foo.jpg" alt="Foo Application" />
|
||||
<AWXLogin api={api} logo="images/foo.jpg" alt="Foo Application" />
|
||||
</I18nProvider>
|
||||
</MemoryRouter>
|
||||
);
|
||||
@@ -75,7 +75,7 @@ describe('<Login />', () => {
|
||||
loginWrapper = mount(
|
||||
<MemoryRouter>
|
||||
<I18nProvider>
|
||||
<AtLogin api={api} />
|
||||
<AWXLogin api={api} />
|
||||
</I18nProvider>
|
||||
</MemoryRouter>
|
||||
);
|
||||
@@ -86,49 +86,49 @@ describe('<Login />', () => {
|
||||
});
|
||||
|
||||
test('state maps to un/pw input value props', () => {
|
||||
atLogin.setState({ username: 'un', password: 'pw' });
|
||||
expect(atLogin.state().username).toBe('un');
|
||||
expect(atLogin.state().password).toBe('pw');
|
||||
awxLogin.setState({ username: 'un', password: 'pw' });
|
||||
expect(awxLogin.state().username).toBe('un');
|
||||
expect(awxLogin.state().password).toBe('pw');
|
||||
findChildren();
|
||||
expect(usernameInput.props().value).toBe('un');
|
||||
expect(passwordInput.props().value).toBe('pw');
|
||||
});
|
||||
|
||||
test('updating un/pw clears out error', () => {
|
||||
atLogin.setState({ isValidPassword: false });
|
||||
awxLogin.setState({ isInputValid: false });
|
||||
expect(loginWrapper.find('.pf-c-form__helper-text.pf-m-error').length).toBe(1);
|
||||
usernameInput.instance().value = 'uname';
|
||||
usernameInput.simulate('change');
|
||||
expect(atLogin.state().username).toBe('uname');
|
||||
expect(atLogin.state().isValidPassword).toBe(true);
|
||||
expect(awxLogin.state().username).toBe('uname');
|
||||
expect(awxLogin.state().isInputValid).toBe(true);
|
||||
expect(loginWrapper.find('.pf-c-form__helper-text.pf-m-error').length).toBe(0);
|
||||
atLogin.setState({ isValidPassword: false });
|
||||
awxLogin.setState({ isInputValid: 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(awxLogin.state().password).toBe('pword');
|
||||
expect(awxLogin.state().isInputValid).toBe(true);
|
||||
expect(loginWrapper.find('.pf-c-form__helper-text.pf-m-error').length).toBe(0);
|
||||
});
|
||||
|
||||
test('api.login not called when loading', () => {
|
||||
api.login = jest.fn().mockImplementation(() => Promise.resolve({}));
|
||||
expect(atLogin.state().loading).toBe(false);
|
||||
atLogin.setState({ loading: true });
|
||||
expect(awxLogin.state().isLoading).toBe(false);
|
||||
awxLogin.setState({ isLoading: true });
|
||||
submitButton.simulate('click');
|
||||
expect(api.login).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
test('submit calls api.login successfully', async () => {
|
||||
api.login = jest.fn().mockImplementation(() => Promise.resolve({}));
|
||||
expect(atLogin.state().loading).toBe(false);
|
||||
atLogin.setState({ username: 'unamee', password: 'pwordd' });
|
||||
expect(awxLogin.state().isLoading).toBe(false);
|
||||
awxLogin.setState({ username: 'unamee', password: 'pwordd' });
|
||||
submitButton.simulate('click');
|
||||
expect(api.login).toHaveBeenCalledTimes(1);
|
||||
expect(api.login).toHaveBeenCalledWith('unamee', 'pwordd');
|
||||
expect(atLogin.state().loading).toBe(true);
|
||||
expect(awxLogin.state().isLoading).toBe(true);
|
||||
await asyncFlush();
|
||||
expect(atLogin.state().loading).toBe(false);
|
||||
expect(awxLogin.state().isLoading).toBe(false);
|
||||
});
|
||||
|
||||
test('submit calls api.login handles 401 error', async () => {
|
||||
@@ -137,16 +137,16 @@ describe('<Login />', () => {
|
||||
err.response = { status: 401, message: 'problem' };
|
||||
return Promise.reject(err);
|
||||
});
|
||||
expect(atLogin.state().loading).toBe(false);
|
||||
expect(atLogin.state().isValidPassword).toBe(true);
|
||||
atLogin.setState({ username: 'unamee', password: 'pwordd' });
|
||||
expect(awxLogin.state().isLoading).toBe(false);
|
||||
expect(awxLogin.state().isInputValid).toBe(true);
|
||||
awxLogin.setState({ username: 'unamee', password: 'pwordd' });
|
||||
submitButton.simulate('click');
|
||||
expect(api.login).toHaveBeenCalledTimes(1);
|
||||
expect(api.login).toHaveBeenCalledWith('unamee', 'pwordd');
|
||||
expect(atLogin.state().loading).toBe(true);
|
||||
expect(awxLogin.state().isLoading).toBe(true);
|
||||
await asyncFlush();
|
||||
expect(atLogin.state().isValidPassword).toBe(false);
|
||||
expect(atLogin.state().loading).toBe(false);
|
||||
expect(awxLogin.state().isInputValid).toBe(false);
|
||||
expect(awxLogin.state().isLoading).toBe(false);
|
||||
});
|
||||
|
||||
test('submit calls api.login handles non-401 error', async () => {
|
||||
@@ -155,20 +155,20 @@ describe('<Login />', () => {
|
||||
err.response = { status: 500, message: 'problem' };
|
||||
return Promise.reject(err);
|
||||
});
|
||||
expect(atLogin.state().loading).toBe(false);
|
||||
atLogin.setState({ username: 'unamee', password: 'pwordd' });
|
||||
expect(awxLogin.state().isLoading).toBe(false);
|
||||
awxLogin.setState({ username: 'unamee', password: 'pwordd' });
|
||||
submitButton.simulate('click');
|
||||
expect(api.login).toHaveBeenCalledTimes(1);
|
||||
expect(api.login).toHaveBeenCalledWith('unamee', 'pwordd');
|
||||
expect(atLogin.state().loading).toBe(true);
|
||||
expect(awxLogin.state().isLoading).toBe(true);
|
||||
await asyncFlush();
|
||||
expect(atLogin.state().loading).toBe(false);
|
||||
expect(awxLogin.state().isLoading).toBe(false);
|
||||
});
|
||||
|
||||
test('render Redirect to / when already authenticated', () => {
|
||||
api.isAuthenticated = jest.fn();
|
||||
api.isAuthenticated.mockReturnValue(true);
|
||||
loginWrapper = shallow(<AtLogin api={api} />);
|
||||
loginWrapper = shallow(<AWXLogin api={api} />);
|
||||
const redirectElem = loginWrapper.find('Redirect');
|
||||
expect(redirectElem.length).toBe(1);
|
||||
expect(redirectElem.props().to).toBe('/');
|
||||
|
||||
@@ -9,52 +9,56 @@ import {
|
||||
|
||||
import towerLogo from '../../images/tower-logo-header.svg';
|
||||
|
||||
class AtLogin extends Component {
|
||||
class AWXLogin extends Component {
|
||||
constructor (props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
username: '',
|
||||
password: '',
|
||||
isValidPassword: true,
|
||||
loading: false
|
||||
isInputValid: true,
|
||||
isLoading: false
|
||||
};
|
||||
|
||||
this.onChangeUsername = this.onChangeUsername.bind(this);
|
||||
this.onChangePassword = this.onChangePassword.bind(this);
|
||||
this.onLoginButtonClick = this.onLoginButtonClick.bind(this);
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
this.unmounting = true; // todo: state management
|
||||
onChangeUsername (value) {
|
||||
this.setState({ username: value, isInputValid: true });
|
||||
}
|
||||
|
||||
safeSetState = obj => !this.unmounting && this.setState(obj);
|
||||
onChangePassword (value) {
|
||||
this.setState({ password: value, isInputValid: true });
|
||||
}
|
||||
|
||||
handleUsernameChange = value => this.safeSetState({ username: value, isValidPassword: true });
|
||||
|
||||
handlePasswordChange = value => this.safeSetState({ password: value, isValidPassword: true });
|
||||
|
||||
handleSubmit = async event => {
|
||||
const { username, password, loading } = this.state;
|
||||
async onLoginButtonClick (event) {
|
||||
const { username, password, isLoading } = this.state;
|
||||
const { api } = this.props;
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
if (!loading) {
|
||||
this.safeSetState({ loading: true });
|
||||
if (isLoading) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await api.login(username, password);
|
||||
} catch (error) {
|
||||
if (error.response.status === 401) {
|
||||
this.safeSetState({ isValidPassword: false });
|
||||
}
|
||||
} finally {
|
||||
this.safeSetState({ loading: false });
|
||||
this.setState({ isLoading: true });
|
||||
|
||||
try {
|
||||
await api.login(username, password);
|
||||
} catch (error) {
|
||||
if (error.response.status === 401) {
|
||||
this.setState({ isInputValid: false });
|
||||
}
|
||||
} finally {
|
||||
this.setState({ isLoading: false });
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
const { username, password, isValidPassword } = this.state;
|
||||
const { api, alt, logo } = this.props;
|
||||
const { username, password, isInputValid } = this.state;
|
||||
const { api, alt, loginInfo, logo } = this.props;
|
||||
const logoSrc = logo ? `data:image/jpeg;${logo}` : towerLogo;
|
||||
|
||||
if (api.isAuthenticated()) {
|
||||
@@ -68,17 +72,18 @@ class AtLogin extends Component {
|
||||
brandImgSrc={logoSrc}
|
||||
brandImgAlt={alt || 'Ansible Tower'}
|
||||
loginTitle={i18n._(t`Welcome to Ansible Tower! Please Sign In.`)}
|
||||
textContent={loginInfo}
|
||||
>
|
||||
<LoginForm
|
||||
usernameLabel={i18n._(t`Username`)}
|
||||
usernameValue={username}
|
||||
onChangeUsername={this.handleUsernameChange}
|
||||
passwordLabel={i18n._(t`Password`)}
|
||||
passwordValue={password}
|
||||
onChangePassword={this.handlePasswordChange}
|
||||
isValidPassword={isValidPassword}
|
||||
passwordHelperTextInvalid={i18n._(t`Invalid username or password. Please try again.`)}
|
||||
onLoginButtonClick={this.handleSubmit}
|
||||
usernameValue={username}
|
||||
passwordValue={password}
|
||||
isValidPassword={isInputValid}
|
||||
onChangeUsername={this.onChangeUsername}
|
||||
onChangePassword={this.onChangePassword}
|
||||
onLoginButtonClick={this.onLoginButtonClick}
|
||||
/>
|
||||
</LoginPage>
|
||||
)}
|
||||
@@ -87,4 +92,4 @@ class AtLogin extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
export default AtLogin;
|
||||
export default AWXLogin;
|
||||
|
||||
Reference in New Issue
Block a user