Merge remote-tracking branch 'origin/master' into lookup-form-component

This commit is contained in:
kialam
2019-01-07 14:40:35 -05:00
49 changed files with 1971 additions and 1136 deletions

View File

@@ -1,72 +1,124 @@
import React from 'react';
import { HashRouter as Router } from 'react-router-dom';
import { shallow, mount } from 'enzyme';
import App from '../src/App';
import api from '../src/api';
import { API_LOGOUT, API_CONFIG } from '../src/endpoints';
import { MemoryRouter } from 'react-router-dom';
import { I18nProvider } from '@lingui/react';
import Dashboard from '../src/pages/Dashboard';
import Login from '../src/pages/Login';
import { mount, shallow } from 'enzyme';
import { asyncFlush } from '../jest.setup';
import App from '../src/App';
const DEFAULT_ACTIVE_GROUP = 'views_group';
const DEFAULT_ACTIVE_ITEM = 'views_group_dashboard';
describe('<App />', () => {
test('renders without crashing', () => {
const appWrapper = shallow(<App />);
test('expected content is rendered', () => {
const appWrapper = mount(
<MemoryRouter>
<I18nProvider>
<App
routeGroups={[
{
groupTitle: 'Group One',
groupId: 'group_one',
routes: [
{ title: 'Foo', path: '/foo' },
{ title: 'Bar', path: '/bar' },
],
},
{
groupTitle: 'Group Two',
groupId: 'group_two',
routes: [
{ title: 'Fiz', path: '/fiz' },
]
}
]}
render={({ routeGroups }) => (
routeGroups.map(({ groupId }) => (<div key={groupId} id={groupId} />))
)}
/>
</I18nProvider>
</MemoryRouter>
);
// page components
expect(appWrapper.length).toBe(1);
expect(appWrapper.find('PageHeader').length).toBe(1);
expect(appWrapper.find('PageSidebar').length).toBe(1);
// sidebar groups and route links
expect(appWrapper.find('NavExpandableGroup').length).toBe(2);
expect(appWrapper.find('a[href="/#/foo"]').length).toBe(1);
expect(appWrapper.find('a[href="/#/bar"]').length).toBe(1);
expect(appWrapper.find('a[href="/#/fiz"]').length).toBe(1);
// inline render
expect(appWrapper.find('#group_one').length).toBe(1);
expect(appWrapper.find('#group_two').length).toBe(1);
});
test('renders login page when not authenticated', () => {
api.isAuthenticated = jest.fn();
api.isAuthenticated.mockReturnValue(false);
test('opening the about modal renders prefetched config data', async (done) => {
const ansible_version = '111';
const version = '222';
const appWrapper = mount(<Router><App /></Router>);
const getConfig = jest.fn(() => Promise.resolve({ data: { ansible_version, version} }));
const api = { getConfig };
const login = appWrapper.find(Login);
expect(login.length).toBe(1);
const dashboard = appWrapper.find(Dashboard);
expect(dashboard.length).toBe(0);
});
const wrapper = mount(
<MemoryRouter>
<I18nProvider>
<App api={api}/>
</I18nProvider>
</MemoryRouter>
);
test('renders dashboard when authenticated', () => {
api.isAuthenticated = jest.fn();
api.isAuthenticated.mockReturnValue(true);
await asyncFlush();
expect(getConfig).toHaveBeenCalledTimes(1);
const appWrapper = mount(<Router><App /></Router>);
// open about modal
const aboutDropdown = 'Dropdown QuestionCircleIcon';
const aboutButton = 'DropdownItem li button';
const aboutModalContent = 'AboutModalBoxContent';
const aboutModalClose = 'button[aria-label="Close Dialog"]';
const dashboard = appWrapper.find(Dashboard);
expect(dashboard.length).toBe(1);
const login = appWrapper.find(Login);
expect(login.length).toBe(0);
expect(wrapper.find(aboutModalContent)).toHaveLength(0);
wrapper.find(aboutDropdown).simulate('click');
wrapper.find(aboutButton).simulate('click');
wrapper.update();
// check about modal content
const content = wrapper.find(aboutModalContent);
expect(content).toHaveLength(1);
expect(content.find('dd').text()).toContain(ansible_version);
expect(content.find('pre').text()).toContain(`< Tower ${version} >`);
// close about modal
wrapper.find(aboutModalClose).simulate('click');
expect(wrapper.find(aboutModalContent)).toHaveLength(0);
done();
});
test('onNavToggle sets state.isNavOpen to opposite', () => {
const appWrapper = shallow(<App.WrappedComponent />);
expect(appWrapper.state().isNavOpen).toBe(true);
appWrapper.instance().onNavToggle();
expect(appWrapper.state().isNavOpen).toBe(false);
const appWrapper = shallow(<App />);
const { onNavToggle } = appWrapper.instance();
[true, false, true, false, true].forEach(expected => {
expect(appWrapper.state().isNavOpen).toBe(expected);
onNavToggle();
});
});
test('onLogoClick sets selected nav back to defaults', () => {
const appWrapper = shallow(<App.WrappedComponent />);
appWrapper.setState({ activeGroup: 'foo', activeItem: 'bar' });
expect(appWrapper.state().activeItem).toBe('bar');
expect(appWrapper.state().activeGroup).toBe('foo');
appWrapper.instance().onLogoClick();
expect(appWrapper.state().activeGroup).toBe(DEFAULT_ACTIVE_GROUP);
});
test('onLogout makes expected call to api client', async (done) => {
const logout = jest.fn(() => Promise.resolve());
const api = { logout };
test('api.logout called from logout button', async () => {
api.get = jest.fn().mockImplementation(() => Promise.resolve({}));
const appWrapper = shallow(<App.WrappedComponent />);
appWrapper.instance().onDevLogout();
appWrapper.setState({ activeGroup: 'foo', activeItem: 'bar' });
expect(api.get).toHaveBeenCalledWith(API_LOGOUT);
const appWrapper = shallow(<App api={api} />);
appWrapper.instance().onLogout();
await asyncFlush();
expect(appWrapper.state().activeItem).toBe(DEFAULT_ACTIVE_ITEM);
expect(appWrapper.state().activeGroup).toBe(DEFAULT_ACTIVE_GROUP);
expect(api.logout).toHaveBeenCalledTimes(1);
done();
});
test('Componenet makes REST call to API_CONFIG endpoint when mounted', () => {

View File

@@ -1,80 +1,129 @@
import mockAxios from 'axios';
import APIClient from '../src/api';
import * as endpoints from '../src/endpoints';
const CSRF_COOKIE_NAME = 'csrftoken';
const CSRF_HEADER_NAME = 'X-CSRFToken';
const LOGIN_CONTENT_TYPE = 'application/x-www-form-urlencoded';
const invalidCookie = 'invalid';
const validLoggedOutCookie = 'current_user=%7B%22id%22%3A1%2C%22type%22%3A%22user%22%2C%22url%22%3A%22%2Fapi%2Fv2%2Fusers%2F1%2F%22%2C%22related%22%3A%7B%22admin_of_organizations%22%3A%22%2Fapi%2Fv2%2Fusers%2F1%2Fadmin_of_organizations%2F%22%2C%22authorized_tokens%22%3A%22%2Fapi%2Fv2%2Fusers%2F1%2Fauthorized_tokens%2F%22%2C%22roles%22%3A%22%2Fapi%2Fv2%2Fusers%2F1%2Froles%2F%22%2C%22organizations%22%3A%22%2Fapi%2Fv2%2Fusers%2F1%2Forganizations%2F%22%2C%22access_list%22%3A%22%2Fapi%2Fv2%2Fusers%2F1%2Faccess_list%2F%22%2C%22teams%22%3A%22%2Fapi%2Fv2%2Fusers%2F1%2Fteams%2F%22%2C%22tokens%22%3A%22%2Fapi%2Fv2%2Fusers%2F1%2Ftokens%2F%22%2C%22personal_tokens%22%3A%22%2Fapi%2Fv2%2Fusers%2F1%2Fpersonal_tokens%2F%22%2C%22credentials%22%3A%22%2Fapi%2Fv2%2Fusers%2F1%2Fcredentials%2F%22%2C%22activity_stream%22%3A%22%2Fapi%2Fv2%2Fusers%2F1%2Factivity_stream%2F%22%2C%22projects%22%3A%22%2Fapi%2Fv2%2Fusers%2F1%2Fprojects%2F%22%7D%2C%22summary_fields%22%3A%7B%7D%2C%22created%22%3A%222018-10-19T16%3A30%3A59.141963Z%22%2C%22username%22%3A%22admin%22%2C%22first_name%22%3A%22%22%2C%22last_name%22%3A%22%22%2C%22email%22%3A%22%22%2C%22is_superuser%22%3Atrue%2C%22is_system_auditor%22%3Afalse%2C%22ldap_dn%22%3A%22%22%2C%22external_account%22%3Anull%2C%22auth%22%3A%5B%5D%7D; userLoggedIn=false; csrftoken=lhOHpLQUFHlIVqx8CCZmEpdEZAz79GIRBIT3asBzTbPE7HS7wizt7WBsgJClz8Ge';
const validLoggedInCookie = 'current_user=%7B%22id%22%3A1%2C%22type%22%3A%22user%22%2C%22url%22%3A%22%2Fapi%2Fv2%2Fusers%2F1%2F%22%2C%22related%22%3A%7B%22admin_of_organizations%22%3A%22%2Fapi%2Fv2%2Fusers%2F1%2Fadmin_of_organizations%2F%22%2C%22authorized_tokens%22%3A%22%2Fapi%2Fv2%2Fusers%2F1%2Fauthorized_tokens%2F%22%2C%22roles%22%3A%22%2Fapi%2Fv2%2Fusers%2F1%2Froles%2F%22%2C%22organizations%22%3A%22%2Fapi%2Fv2%2Fusers%2F1%2Forganizations%2F%22%2C%22access_list%22%3A%22%2Fapi%2Fv2%2Fusers%2F1%2Faccess_list%2F%22%2C%22teams%22%3A%22%2Fapi%2Fv2%2Fusers%2F1%2Fteams%2F%22%2C%22tokens%22%3A%22%2Fapi%2Fv2%2Fusers%2F1%2Ftokens%2F%22%2C%22personal_tokens%22%3A%22%2Fapi%2Fv2%2Fusers%2F1%2Fpersonal_tokens%2F%22%2C%22credentials%22%3A%22%2Fapi%2Fv2%2Fusers%2F1%2Fcredentials%2F%22%2C%22activity_stream%22%3A%22%2Fapi%2Fv2%2Fusers%2F1%2Factivity_stream%2F%22%2C%22projects%22%3A%22%2Fapi%2Fv2%2Fusers%2F1%2Fprojects%2F%22%7D%2C%22summary_fields%22%3A%7B%7D%2C%22created%22%3A%222018-10-19T16%3A30%3A59.141963Z%22%2C%22username%22%3A%22admin%22%2C%22first_name%22%3A%22%22%2C%22last_name%22%3A%22%22%2C%22email%22%3A%22%22%2C%22is_superuser%22%3Atrue%2C%22is_system_auditor%22%3Afalse%2C%22ldap_dn%22%3A%22%22%2C%22external_account%22%3Anull%2C%22auth%22%3A%5B%5D%7D; userLoggedIn=true; csrftoken=lhOHpLQUFHlIVqx8CCZmEpdEZAz79GIRBIT3asBzTbPE7HS7wizt7WBsgJClz8Ge';
describe('APIClient (api.js)', () => {
afterEach(() => {
mockAxios.customClearMocks();
test('isAuthenticated returns false when cookie is invalid', () => {
APIClient.getCookie = jest.fn(() => invalidCookie);
const api = new APIClient();
expect(api.isAuthenticated()).toBe(false);
});
test('constructor calls axios create', () => {
const csrfObj = {
xsrfCookieName: CSRF_COOKIE_NAME,
xsrfHeaderName: CSRF_HEADER_NAME
};
expect(mockAxios.create).toHaveBeenCalledTimes(1);
expect(mockAxios.create).toHaveBeenCalledWith(csrfObj);
expect(APIClient.http).toHaveProperty('get');
test('isAuthenticated returns false when cookie is unauthenticated', () => {
APIClient.getCookie = jest.fn(() => validLoggedOutCookie);
const api = new APIClient();
expect(api.isAuthenticated()).toBe(false);
});
test('isAuthenticated checks authentication and sets cookie from document', () => {
APIClient.getCookie = jest.fn();
const invalidCookie = 'invalid';
const validLoggedOutCookie = 'current_user=%7B%22id%22%3A1%2C%22type%22%3A%22user%22%2C%22url%22%3A%22%2Fapi%2Fv2%2Fusers%2F1%2F%22%2C%22related%22%3A%7B%22admin_of_organizations%22%3A%22%2Fapi%2Fv2%2Fusers%2F1%2Fadmin_of_organizations%2F%22%2C%22authorized_tokens%22%3A%22%2Fapi%2Fv2%2Fusers%2F1%2Fauthorized_tokens%2F%22%2C%22roles%22%3A%22%2Fapi%2Fv2%2Fusers%2F1%2Froles%2F%22%2C%22organizations%22%3A%22%2Fapi%2Fv2%2Fusers%2F1%2Forganizations%2F%22%2C%22access_list%22%3A%22%2Fapi%2Fv2%2Fusers%2F1%2Faccess_list%2F%22%2C%22teams%22%3A%22%2Fapi%2Fv2%2Fusers%2F1%2Fteams%2F%22%2C%22tokens%22%3A%22%2Fapi%2Fv2%2Fusers%2F1%2Ftokens%2F%22%2C%22personal_tokens%22%3A%22%2Fapi%2Fv2%2Fusers%2F1%2Fpersonal_tokens%2F%22%2C%22credentials%22%3A%22%2Fapi%2Fv2%2Fusers%2F1%2Fcredentials%2F%22%2C%22activity_stream%22%3A%22%2Fapi%2Fv2%2Fusers%2F1%2Factivity_stream%2F%22%2C%22projects%22%3A%22%2Fapi%2Fv2%2Fusers%2F1%2Fprojects%2F%22%7D%2C%22summary_fields%22%3A%7B%7D%2C%22created%22%3A%222018-10-19T16%3A30%3A59.141963Z%22%2C%22username%22%3A%22admin%22%2C%22first_name%22%3A%22%22%2C%22last_name%22%3A%22%22%2C%22email%22%3A%22%22%2C%22is_superuser%22%3Atrue%2C%22is_system_auditor%22%3Afalse%2C%22ldap_dn%22%3A%22%22%2C%22external_account%22%3Anull%2C%22auth%22%3A%5B%5D%7D; userLoggedIn=false; csrftoken=lhOHpLQUFHlIVqx8CCZmEpdEZAz79GIRBIT3asBzTbPE7HS7wizt7WBsgJClz8Ge';
const validLoggedInCookie = 'current_user=%7B%22id%22%3A1%2C%22type%22%3A%22user%22%2C%22url%22%3A%22%2Fapi%2Fv2%2Fusers%2F1%2F%22%2C%22related%22%3A%7B%22admin_of_organizations%22%3A%22%2Fapi%2Fv2%2Fusers%2F1%2Fadmin_of_organizations%2F%22%2C%22authorized_tokens%22%3A%22%2Fapi%2Fv2%2Fusers%2F1%2Fauthorized_tokens%2F%22%2C%22roles%22%3A%22%2Fapi%2Fv2%2Fusers%2F1%2Froles%2F%22%2C%22organizations%22%3A%22%2Fapi%2Fv2%2Fusers%2F1%2Forganizations%2F%22%2C%22access_list%22%3A%22%2Fapi%2Fv2%2Fusers%2F1%2Faccess_list%2F%22%2C%22teams%22%3A%22%2Fapi%2Fv2%2Fusers%2F1%2Fteams%2F%22%2C%22tokens%22%3A%22%2Fapi%2Fv2%2Fusers%2F1%2Ftokens%2F%22%2C%22personal_tokens%22%3A%22%2Fapi%2Fv2%2Fusers%2F1%2Fpersonal_tokens%2F%22%2C%22credentials%22%3A%22%2Fapi%2Fv2%2Fusers%2F1%2Fcredentials%2F%22%2C%22activity_stream%22%3A%22%2Fapi%2Fv2%2Fusers%2F1%2Factivity_stream%2F%22%2C%22projects%22%3A%22%2Fapi%2Fv2%2Fusers%2F1%2Fprojects%2F%22%7D%2C%22summary_fields%22%3A%7B%7D%2C%22created%22%3A%222018-10-19T16%3A30%3A59.141963Z%22%2C%22username%22%3A%22admin%22%2C%22first_name%22%3A%22%22%2C%22last_name%22%3A%22%22%2C%22email%22%3A%22%22%2C%22is_superuser%22%3Atrue%2C%22is_system_auditor%22%3Afalse%2C%22ldap_dn%22%3A%22%22%2C%22external_account%22%3Anull%2C%22auth%22%3A%5B%5D%7D; userLoggedIn=true; csrftoken=lhOHpLQUFHlIVqx8CCZmEpdEZAz79GIRBIT3asBzTbPE7HS7wizt7WBsgJClz8Ge';
APIClient.getCookie.mockReturnValue(invalidCookie);
expect(APIClient.isAuthenticated()).toBe(false);
APIClient.getCookie.mockReturnValue(validLoggedOutCookie);
expect(APIClient.isAuthenticated()).toBe(false);
APIClient.getCookie.mockReturnValue(validLoggedInCookie);
expect(APIClient.isAuthenticated()).toBe(true);
test('isAuthenticated returns true when cookie is valid and authenticated', () => {
APIClient.getCookie = jest.fn(() => validLoggedInCookie);
const api = new APIClient();
expect(api.isAuthenticated()).toBe(true);
});
test('login calls get and post to login route, and sets cookie from document', (done) => {
const un = 'foo';
const pw = 'bar';
const next = 'baz';
const headers = { 'Content-Type': LOGIN_CONTENT_TYPE };
const data = `username=${un}&password=${pw}&next=${next}`;
APIClient.setCookie = jest.fn();
APIClient.login(un, pw, next).then(() => {
expect(mockAxios.get).toHaveBeenCalledTimes(1);
expect(mockAxios.get).toHaveBeenCalledWith(endpoints.API_LOGIN, { headers });
expect(mockAxios.post).toHaveBeenCalledTimes(1);
expect(mockAxios.post).toHaveBeenCalledWith(endpoints.API_LOGIN, data, { headers });
done();
});
test('login calls get and post with expected content headers', async (done) => {
const headers = { 'Content-Type': 'application/x-www-form-urlencoded' };
const createPromise = () => Promise.resolve();
const mockHttp = ({ get: jest.fn(createPromise), post: jest.fn(createPromise) });
const api = new APIClient(mockHttp);
await api.login('username', 'password');
expect(mockHttp.get).toHaveBeenCalledTimes(1);
expect(mockHttp.get.mock.calls[0]).toContainEqual({ headers });
expect(mockHttp.post).toHaveBeenCalledTimes(1);
expect(mockHttp.post.mock.calls[0]).toContainEqual({ headers });
done();
});
test('login encodes uri components for username, password and redirect', (done) => {
const un = '/foo/';
const pw = '/bar/';
const next = '/baz/';
const headers = { 'Content-Type': LOGIN_CONTENT_TYPE };
const data = `username=${encodeURIComponent(un)}&password=${encodeURIComponent(pw)}&next=${encodeURIComponent(next)}`;
APIClient.login(un, pw, next).then(() => {
expect(mockAxios.post).toHaveBeenCalledTimes(1);
expect(mockAxios.post).toHaveBeenCalledWith(endpoints.API_LOGIN, data, { headers });
done();
});
test('login sends expected data', async (done) => {
const createPromise = () => Promise.resolve();
const mockHttp = ({ get: jest.fn(createPromise), post: jest.fn(createPromise) });
const api = new APIClient(mockHttp);
await api.login('foo', 'bar');
await api.login('foo', 'bar', 'baz');
expect(mockHttp.post).toHaveBeenCalledTimes(2);
expect(mockHttp.post.mock.calls[0]).toContainEqual('username=foo&password=bar&next=%2Fapi%2Fv2%2Fconfig%2F');
expect(mockHttp.post.mock.calls[1]).toContainEqual('username=foo&password=bar&next=baz');
done();
});
test('login redirect defaults to config route when not explicitly passed', (done) => {
const un = 'foo';
const pw = 'bar';
const headers = { 'Content-Type': LOGIN_CONTENT_TYPE };
const data = `username=${un}&password=${pw}&next=${encodeURIComponent(endpoints.API_CONFIG)}`;
APIClient.setCookie = jest.fn();
APIClient.login(un, pw).then(() => {
expect(mockAxios.post).toHaveBeenCalledTimes(1);
expect(mockAxios.post).toHaveBeenCalledWith(endpoints.API_LOGIN, data, { headers });
done();
});
test('logout calls expected http method', async (done) => {
const createPromise = () => Promise.resolve();
const mockHttp = ({ get: jest.fn(createPromise) });
const api = new APIClient(mockHttp);
await api.logout();
expect(mockHttp.get).toHaveBeenCalledTimes(1);
done();
});
test('getConfig calls expected http method', async (done) => {
const createPromise = () => Promise.resolve();
const mockHttp = ({ get: jest.fn(createPromise) });
const api = new APIClient(mockHttp);
await api.getConfig();
expect(mockHttp.get).toHaveBeenCalledTimes(1);
done();
});
test('getOrganizations calls http method with expected data', async (done) => {
const createPromise = () => Promise.resolve();
const mockHttp = ({ get: jest.fn(createPromise) });
const api = new APIClient(mockHttp);
const defaultParams = {};
const testParams = { foo: 'bar' };
await api.getOrganizations(testParams);
await api.getOrganizations();
expect(mockHttp.get).toHaveBeenCalledTimes(2);
expect(mockHttp.get.mock.calls[0][1]).toEqual({ params: testParams });
expect(mockHttp.get.mock.calls[1][1]).toEqual({ params: defaultParams });
done();
});
test('createOrganization calls http method with expected data', async (done) => {
const createPromise = () => Promise.resolve();
const mockHttp = ({ post: jest.fn(createPromise) });
const api = new APIClient(mockHttp);
const data = { name: 'test '};
await api.createOrganization(data);
expect(mockHttp.post).toHaveBeenCalledTimes(1);
expect(mockHttp.post.mock.calls[0][1]).toEqual(data);
done();
});
test('getOrganizationDetails calls http method with expected data', async (done) => {
const createPromise = () => Promise.resolve();
const mockHttp = ({ get: jest.fn(createPromise) });
const api = new APIClient(mockHttp);
await api.getOrganizationDetails(99);
expect(mockHttp.get).toHaveBeenCalledTimes(1);
expect(mockHttp.get.mock.calls[0][0]).toContain('99');
done();
});
});

View File

@@ -1,8 +1,6 @@
import React from 'react';
import { mount } from 'enzyme';
import { I18nProvider } from '@lingui/react';
import api from '../../src/api';
import { API_CONFIG } from '../../src/endpoints';
import About from '../../src/components/About';
describe('<About />', () => {
@@ -19,16 +17,16 @@ describe('<About />', () => {
aboutWrapper.unmount();
});
test('close button calls onAboutModalClose', () => {
const onAboutModalClose = jest.fn();
test('close button calls onClose handler', () => {
const onClose = jest.fn();
aboutWrapper = mount(
<I18nProvider>
<About isOpen onAboutModalClose={onAboutModalClose} />
<About isOpen onClose={onClose} />
</I18nProvider>
);
closeButton = aboutWrapper.find('AboutModalBoxCloseButton Button');
closeButton.simulate('click');
expect(onAboutModalClose).toBeCalled();
expect(onClose).toBeCalled();
aboutWrapper.unmount();
});
});

View File

@@ -29,4 +29,16 @@ describe('<AnsibleSelect />', () => {
wrapper.find('select').simulate('change');
expect(spy).toHaveBeenCalled();
});
});
test('content not rendered when data property is falsey', () => {
const wrapper = mount(
<AnsibleSelect
selected="foo"
selectChange={() => { }}
labelName={label}
data={null}
/>
);
expect(wrapper.find('FormGroup')).toHaveLength(0);
expect(wrapper.find('Select')).toHaveLength(0);
});
});

View File

@@ -0,0 +1,14 @@
import React from 'react';
import { mount } from 'enzyme';
import { I18nProvider } from '@lingui/react';
import Background from '../../src/components/Background';
describe('Background', () => {
test('renders the expected content', () => {
const wrapper = mount(<Background><div id="test"/></Background>);
expect(wrapper).toHaveLength(1);
expect(wrapper.find('BackgroundImage')).toHaveLength(1);
expect(wrapper.find('#test')).toHaveLength(1);
});
});

View File

@@ -1,35 +0,0 @@
import React from 'react';
import {
Route,
Redirect
} from 'react-router-dom';
import { shallow } from 'enzyme';
import ConditionalRedirect from '../../src/components/ConditionalRedirect';
describe('<ConditionalRedirect />', () => {
test('renders Redirect when shouldRedirect is passed truthy func', () => {
const truthyFunc = () => true;
const shouldHaveRedirectChild = shallow(
<ConditionalRedirect
shouldRedirect={() => truthyFunc()}
/>
);
const redirectChild = shouldHaveRedirectChild.find(Redirect);
expect(redirectChild.length).toBe(1);
const routeChild = shouldHaveRedirectChild.find(Route);
expect(routeChild.length).toBe(0);
});
test('renders Route when shouldRedirect is passed falsy func', () => {
const falsyFunc = () => false;
const shouldHaveRouteChild = shallow(
<ConditionalRedirect
shouldRedirect={() => falsyFunc()}
/>
);
const routeChild = shouldHaveRouteChild.find(Route);
expect(routeChild.length).toBe(1);
const redirectChild = shouldHaveRouteChild.find(Redirect);
expect(redirectChild.length).toBe(0);
});
});

View File

@@ -4,7 +4,6 @@ import { I18nProvider } from '@lingui/react';
import DataListToolbar from '../../src/components/DataListToolbar';
describe('<DataListToolbar />', () => {
const columns = [{ name: 'Name', key: 'name', isSortable: true }];
let toolbar;
afterEach(() => {
@@ -15,6 +14,8 @@ describe('<DataListToolbar />', () => {
});
test('it triggers the expected callbacks', () => {
const columns = [{ name: 'Name', key: 'name', isSortable: true }];
const search = 'button[aria-label="Search"]';
const searchTextInput = 'input[aria-label="Search text input"]';
const selectAll = 'input[aria-label="Select all"]';
@@ -28,6 +29,7 @@ describe('<DataListToolbar />', () => {
<I18nProvider>
<DataListToolbar
isAllSelected={false}
showExpandCollapse={true}
sortedColumnKey="name"
sortOrder="ascending"
columns={columns}
@@ -55,4 +57,166 @@ describe('<DataListToolbar />', () => {
expect(onSearch).toHaveBeenCalledTimes(1);
expect(onSearch).toBeCalledWith('test-321');
});
test('dropdown items sortable columns work', () => {
const sortDropdownToggleSelector = '.pf-l-toolbar__group.sortDropdownGroup .pf-l-toolbar__item button';
const sortDropdownItemsSelector = '.pf-l-toolbar__group.sortDropdownGroup button.pf-c-dropdown__menu-item';
const searchDropdownToggleSelector = '.pf-c-dropdown.searchKeyDropdown .pf-c-dropdown__toggle';
const searchDropdownItemsSelector = '.pf-c-dropdown.searchKeyDropdown button.pf-c-dropdown__menu-item';
const multipleColumns = [
{ name: 'Foo', key: 'foo', isSortable: true },
{ name: 'Bar', key: 'bar', isSortable: true },
{ name: 'Bakery', key: 'bakery', isSortable: true },
{ name: 'Baz', key: 'baz' }
];
const onSearch = jest.fn();
const onSort = jest.fn();
const onSelectAll = jest.fn();
toolbar = mount(
<I18nProvider>
<DataListToolbar
isAllSelected={false}
sortedColumnKey="foo"
sortOrder="ascending"
columns={multipleColumns}
onSearch={onSearch}
onSort={onSort}
onSelectAll={onSelectAll}
/>
</I18nProvider>
);
const sortDropdownToggle = toolbar.find(sortDropdownToggleSelector);
expect(sortDropdownToggle.length).toBe(2);
sortDropdownToggle.at(1).simulate('click');
sortDropdownToggle.at(0).simulate('click');
toolbar.update();
const sortDropdownItems = toolbar.find(sortDropdownItemsSelector);
expect(sortDropdownItems.length).toBe(2);
const mockedSortEvent = { target: { innerText: 'Bar' } };
sortDropdownItems.at(0).simulate('click', mockedSortEvent);
toolbar = mount(
<I18nProvider>
<DataListToolbar
isAllSelected={false}
sortedColumnKey="foo"
sortOrder="descending"
columns={multipleColumns}
onSearch={onSearch}
onSort={onSort}
onSelectAll={onSelectAll}
/>
</I18nProvider>
);
toolbar.update();
const sortDropdownToggleDescending = toolbar.find(sortDropdownToggleSelector);
expect(sortDropdownToggleDescending.length).toBe(2);
sortDropdownToggleDescending.at(1).simulate('click');
sortDropdownToggleDescending.at(0).simulate('click');
toolbar.update();
const sortDropdownItemsDescending = toolbar.find(sortDropdownItemsSelector);
expect(sortDropdownItemsDescending.length).toBe(2);
const mockedSortEventDescending = { target: { innerText: 'Bar' } };
sortDropdownItems.at(0).simulate('click', mockedSortEventDescending);
toolbar.update();
const searchDropdownToggle = toolbar.find(searchDropdownToggleSelector);
expect(searchDropdownToggle.length).toBe(1);
searchDropdownToggle.at(0).simulate('click');
toolbar.update();
const searchDropdownItems = toolbar.find(searchDropdownItemsSelector);
expect(searchDropdownItems.length).toBe(3);
const mockedSearchEvent = { target: { innerText: 'Bar' } };
searchDropdownItems.at(0).simulate('click', mockedSearchEvent);
});
test('it displays correct sort icon', () => {
const downNumericIconSelector = 'SortNumericDownIcon';
const upNumericIconSelector = 'SortNumericUpIcon';
const downAlphaIconSelector = 'SortAlphaDownIcon';
const upAlphaIconSelector = 'SortAlphaUpIcon';
const numericColumns = [{ name: 'ID', key: 'id', isSortable: true, isNumeric: true }];
const alphaColumns = [{ name: 'Name', key: 'name', isSortable: true, isNumeric: false }];
const onSearch = jest.fn();
const onSort = jest.fn();
const onSelectAll = jest.fn();
toolbar = mount(
<I18nProvider>
<DataListToolbar
isAllSelected={false}
sortedColumnKey="id"
sortOrder="descending"
columns={numericColumns}
onSearch={onSearch}
onSort={onSort}
onSelectAll={onSelectAll}
/>
</I18nProvider>
);
const downNumericIcon = toolbar.find(downNumericIconSelector);
expect(downNumericIcon.length).toBe(1);
toolbar = mount(
<I18nProvider>
<DataListToolbar
isAllSelected={false}
sortedColumnKey="id"
sortOrder="ascending"
columns={numericColumns}
onSearch={onSearch}
onSort={onSort}
onSelectAll={onSelectAll}
/>
</I18nProvider>
);
const upNumericIcon = toolbar.find(upNumericIconSelector);
expect(upNumericIcon.length).toBe(1);
toolbar = mount(
<I18nProvider>
<DataListToolbar
isAllSelected={false}
sortedColumnKey="name"
sortOrder="descending"
columns={alphaColumns}
onSearch={onSearch}
onSort={onSort}
onSelectAll={onSelectAll}
/>
</I18nProvider>
);
const downAlphaIcon = toolbar.find(downAlphaIconSelector);
expect(downAlphaIcon.length).toBe(1);
toolbar = mount(
<I18nProvider>
<DataListToolbar
isAllSelected={false}
sortedColumnKey="name"
sortOrder="ascending"
columns={alphaColumns}
onSearch={onSearch}
onSort={onSort}
onSelectAll={onSelectAll}
/>
</I18nProvider>
);
const upAlphaIcon = toolbar.find(upAlphaIconSelector);
expect(upAlphaIcon.length).toBe(1);
});
});

View File

@@ -1,68 +0,0 @@
import React from 'react';
import { mount } from 'enzyme';
import { I18nProvider } from '@lingui/react';
import HelpDropdown from '../../src/components/HelpDropdown';
let questionCircleIcon;
let dropdownWrapper;
let dropdownComponentInstance;
let dropdownToggle;
let dropdownItems;
let dropdownItem;
beforeEach(() => {
dropdownWrapper = mount(
<I18nProvider>
<HelpDropdown />
</I18nProvider>
);
dropdownComponentInstance = dropdownWrapper.find(HelpDropdown).instance();
});
afterEach(() => {
dropdownWrapper.unmount();
});
describe('<HelpDropdown />', () => {
test('initially renders without crashing', () => {
expect(dropdownWrapper.length).toBe(1);
expect(dropdownComponentInstance.state.isOpen).toEqual(false);
expect(dropdownComponentInstance.state.showAboutModal).toEqual(false);
questionCircleIcon = dropdownWrapper.find('QuestionCircleIcon');
expect(questionCircleIcon.length).toBe(1);
});
test('renders two dropdown items', () => {
dropdownComponentInstance.setState({ isOpen: true });
dropdownWrapper.update();
dropdownItems = dropdownWrapper.find('DropdownItem');
expect(dropdownItems.length).toBe(2);
const dropdownTexts = dropdownItems.map(item => item.text());
expect(dropdownTexts).toEqual(['Help', 'About']);
});
test('onToggle sets state.isOpen to opposite', () => {
dropdownComponentInstance.setState({ isOpen: true });
dropdownWrapper.update();
dropdownToggle = dropdownWrapper.find('DropdownToggle > DropdownToggle');
dropdownToggle.simulate('click');
expect(dropdownComponentInstance.state.isOpen).toEqual(false);
});
test('about dropdown item sets state.showAboutModal to true', () => {
dropdownComponentInstance.setState({ isOpen: true });
dropdownWrapper.update();
dropdownItem = dropdownWrapper.find('DropdownItem a').at(1);
dropdownItem.simulate('click');
expect(dropdownComponentInstance.state.showAboutModal).toEqual(true);
});
test('onAboutModalClose sets state.showAboutModal to false', () => {
dropdownComponentInstance.setState({ showAboutModal: true });
dropdownWrapper.update();
const aboutModal = dropdownWrapper.find('AboutModal');
aboutModal.find('AboutModalBoxCloseButton Button').simulate('click');
expect(dropdownComponentInstance.state.showAboutModal).toEqual(false);
});
});

View File

@@ -1,32 +0,0 @@
import React from 'react';
import { mount } from 'enzyme';
import { I18nProvider } from '@lingui/react';
import LogoutButton from '../../src/components/LogoutButton';
let buttonWrapper;
let buttonElem;
let userIconElem;
const findChildren = () => {
buttonElem = buttonWrapper.find('Button');
userIconElem = buttonWrapper.find('UserIcon');
};
describe('<LogoutButton />', () => {
test('initially renders without crashing', () => {
const onDevLogout = jest.fn();
buttonWrapper = mount(
<I18nProvider>
<LogoutButton onDevLogout={onDevLogout} />
</I18nProvider>
);
findChildren();
expect(buttonWrapper.length).toBe(1);
expect(buttonElem.length).toBe(1);
expect(userIconElem.length).toBe(1);
buttonElem.simulate('keyDown', { keyCode: 40, which: 40 });
expect(onDevLogout).toHaveBeenCalledTimes(0);
buttonElem.simulate('keyDown', { keyCode: 13, which: 13 });
expect(onDevLogout).toHaveBeenCalledTimes(1);
});
});

View File

@@ -12,7 +12,7 @@ describe('NavExpandableGroup', () => {
<Nav aria-label="Test Navigation">
<NavExpandableGroup
groupId="test"
title="Test"
groupTitle="Test"
routes={[
{ path: '/foo', title: 'Foo' },
{ path: '/bar', title: 'Bar' },
@@ -45,7 +45,7 @@ describe('NavExpandableGroup', () => {
<Nav aria-label="Test Navigation">
<NavExpandableGroup
groupId="test"
title="Test"
groupTitle="Test"
routes={[
{ path: '/foo', title: 'Foo' },
{ path: '/bar', title: 'Bar' },

View File

@@ -0,0 +1,48 @@
import React from 'react';
import { MemoryRouter } from 'react-router-dom';
import { mount } from 'enzyme';
import { I18nProvider } from '@lingui/react';
import PageHeaderToolbar from '../../src/components/PageHeaderToolbar';
describe('PageHeaderToolbar', () => {
const pageHelpDropdownSelector = 'Dropdown QuestionCircleIcon';
const pageUserDropdownSelector = 'Dropdown UserIcon';
test('expected content is rendered on initialization', () => {
const wrapper = mount(<I18nProvider><PageHeaderToolbar/></I18nProvider>);
expect(wrapper.find(pageHelpDropdownSelector)).toHaveLength(1);
expect(wrapper.find(pageUserDropdownSelector)).toHaveLength(1);
});
test('dropdowns have expected items and callbacks', () => {
const onAboutClick = jest.fn();
const onLogoutClick = jest.fn();
const wrapper = mount(
<MemoryRouter>
<I18nProvider>
<PageHeaderToolbar
onAboutClick={onAboutClick}
onLogoutClick={onLogoutClick}
/>
</I18nProvider>
</MemoryRouter>
);
expect(wrapper.find('DropdownItem')).toHaveLength(0);
wrapper.find(pageHelpDropdownSelector).simulate('click');
expect(wrapper.find('DropdownItem')).toHaveLength(2);
const about = wrapper.find('DropdownItem li button');
about.simulate('click');
expect(onAboutClick).toHaveBeenCalled();
expect(wrapper.find('DropdownItem')).toHaveLength(0);
wrapper.find(pageUserDropdownSelector).simulate('click');
expect(wrapper.find('DropdownItem')).toHaveLength(2);
const logout = wrapper.find('DropdownItem li button');
logout.simulate('click');
expect(onLogoutClick).toHaveBeenCalled();
});
});

View File

@@ -72,4 +72,97 @@ describe('<Pagination />', () => {
expect(onSetPage).toHaveBeenCalledTimes(2);
expect(onSetPage).toBeCalledWith(1, 5);
});
test('previous button does not work on page 1', () => {
const previous = 'button[aria-label="First"]';
const onSetPage = jest.fn();
pagination = mount(
<I18nProvider>
<Pagination
count={21}
page={1}
pageCount={5}
page_size={5}
pageSizeOptions={[5, 10, 25, 50]}
onSetPage={onSetPage}
/>
</I18nProvider>
);
pagination.find(previous).simulate('click');
expect(onSetPage).toHaveBeenCalledTimes(0);
});
test('changing pageSize works', () => {
const pageSizeDropdownToggleSelector = 'DropdownToggle DropdownToggle[className="togglePageSize"]';
const pageSizeDropdownItemsSelector = 'DropdownItem';
const onSetPage = jest.fn();
pagination = mount(
<I18nProvider>
<Pagination
count={21}
page={1}
pageCount={5}
page_size={5}
pageSizeOptions={[5, 10, 25, 50]}
onSetPage={onSetPage}
/>
</I18nProvider>
);
const pageSizeDropdownToggle = pagination.find(pageSizeDropdownToggleSelector);
expect(pageSizeDropdownToggle.length).toBe(1);
pageSizeDropdownToggle.at(0).simulate('click');
const pageSizeDropdownItems = pagination.find(pageSizeDropdownItemsSelector);
expect(pageSizeDropdownItems.length).toBe(3);
pageSizeDropdownItems.at(1).simulate('click');
});
test('submit a new page by typing in input works', () => {
const textInputSelector = '.pf-l-split__item.pf-m-main .pf-c-form-control';
const submitFormSelector = '.pf-l-split__item.pf-m-main form';
const onSetPage = jest.fn();
pagination = mount(
<I18nProvider>
<Pagination
count={21}
page={1}
pageCount={5}
page_size={5}
pageSizeOptions={[5, 10, 25, 50]}
onSetPage={onSetPage}
/>
</I18nProvider>
);
const textInput = pagination.find(textInputSelector);
expect(textInput.length).toBe(1);
textInput.simulate('change');
pagination.setProps({ page: 2 });
const submitForm = pagination.find(submitFormSelector);
expect(submitForm.length).toBe(1);
submitForm.simulate('submit');
pagination.find('Pagination').instance().setState({ value: 'invalid' });
submitForm.simulate('submit');
});
test('text input page change is disabled when only 1 page', () => {
const onSetPage = jest.fn();
pagination = mount(
<I18nProvider>
<Pagination
count={4}
page={1}
pageCount={1}
page_size={5}
pageSizeOptions={[5, 10, 25, 50]}
onSetPage={onSetPage}
/>
</I18nProvider>
);
});
});

View File

@@ -29,11 +29,10 @@ describe('<TowerLogo />', () => {
});
test('adds navigation to route history on click', () => {
const onLogoClick = jest.fn();
logoWrapper = mount(
<MemoryRouter>
<I18nProvider>
<TowerLogo onClick={onLogoClick} />
<TowerLogo linkTo="/" />
</I18nProvider>
</MemoryRouter>
);
@@ -43,7 +42,7 @@ describe('<TowerLogo />', () => {
expect(towerLogoElem.props().history.length).toBe(2);
});
test('gracefully handles not being passed click handler', () => {
test('linkTo prop is optional', () => {
logoWrapper = mount(
<MemoryRouter>
<I18nProvider>
@@ -62,7 +61,7 @@ describe('<TowerLogo />', () => {
logoWrapper = mount(
<MemoryRouter>
<I18nProvider>
<TowerLogo onClick={onLogoClick} />
<TowerLogo />
</I18nProvider>
</MemoryRouter>
);

View File

@@ -1,22 +1,51 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { mount } from 'enzyme';
import { main, getLanguage } from '../src/index';
import api from '../src/api';
import indexToRender from '../src/index';
const custom_logo = (<div>logo</div>);
const custom_login_info = 'custom login info';
jest.mock('react-dom', () => ({ render: jest.fn() }));
const render = template => mount(template);
const data = { custom_logo: 'foo', custom_login_info: '' }
describe('index.jsx', () => {
test('renders without crashing', async () => {
api.getRoot = jest.fn().mockImplementation(() => Promise
.resolve({ data: { custom_logo, custom_login_info } }));
test('login loads when unauthenticated', async (done) => {
const isAuthenticated = () => false;
const getRoot = jest.fn(() => Promise.resolve({ data }));
await indexToRender();
const api = { getRoot, isAuthenticated };
const wrapper = await main(render, api);
expect(ReactDOM.render).toHaveBeenCalled();
expect(api.getRoot).toHaveBeenCalled();
expect(wrapper.find('App')).toHaveLength(0);
expect(wrapper.find('Login')).toHaveLength(1);
const { src } = wrapper.find('Login Brand img').props();
expect(src).toContain(data.custom_logo);
done();
});
test('app loads when authenticated', async (done) => {
const isAuthenticated = () => true;
const getRoot = jest.fn(() => Promise.resolve({ data }));
const api = { getRoot, isAuthenticated };
const wrapper = await main(render, api);
expect(api.getRoot).toHaveBeenCalled();
expect(wrapper.find('App')).toHaveLength(1);
expect(wrapper.find('Login')).toHaveLength(0);
wrapper.find('header a').simulate('click');
wrapper.update();
expect(wrapper.find('App')).toHaveLength(1);
expect(wrapper.find('Login')).toHaveLength(0);
done();
});
test('getLanguage returns the expected language code', () => {
expect(getLanguage({ languages: ['es-US'] })).toEqual('es');
expect(getLanguage({ languages: ['es-US'], language: 'fr-FR', userLanguage: 'en-US' })).toEqual('es');
expect(getLanguage({ language: 'fr-FR', userLanguage: 'en-US' })).toEqual('fr');
expect(getLanguage({ userLanguage: 'en-US' })).toEqual('en');
});
});

View File

@@ -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 api from '../../src/api';
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;
@@ -16,21 +16,23 @@ describe('<Login />', () => {
let submitButton;
let loginHeaderLogo;
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');
passwordInput = loginWrapper.find('input#pf-login-password-id');
submitButton = loginWrapper.find('Button[type="submit"]');
loginHeaderLogo = loginWrapper.find('LoginHeaderBrand Brand');
loginHeaderLogo = loginPage.find('img');
};
beforeEach(() => {
loginWrapper = mount(
<MemoryRouter>
<I18nProvider>
<AtLogin />
<AWXLogin api={api} />
</I18nProvider>
</MemoryRouter>
);
@@ -49,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);
@@ -59,7 +61,7 @@ describe('<Login />', () => {
loginWrapper = mount(
<MemoryRouter>
<I18nProvider>
<AtLogin logo="images/foo.jpg" alt="Foo Application" />
<AWXLogin api={api} logo="images/foo.jpg" alt="Foo Application" />
</I18nProvider>
</MemoryRouter>
);
@@ -73,7 +75,7 @@ describe('<Login />', () => {
loginWrapper = mount(
<MemoryRouter>
<I18nProvider>
<AtLogin />
<AWXLogin api={api} />
</I18nProvider>
</MemoryRouter>
);
@@ -84,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 () => {
@@ -135,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 () => {
@@ -153,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 />);
loginWrapper = shallow(<AWXLogin api={api} />);
const redirectElem = loginWrapper.find('Redirect');
expect(redirectElem.length).toBe(1);
expect(redirectElem.props().to).toBe('/');

View File

@@ -3,9 +3,8 @@ import getTabName from '../../../src/pages/Organizations/utils';
describe('getTabName', () => {
test('returns tab name', () => {
expect(getTabName('details')).toBe('Details');
expect(getTabName('users')).toBe('Users');
expect(getTabName('access')).toBe('Access');
expect(getTabName('teams')).toBe('Teams');
expect(getTabName('admins')).toBe('Admins');
expect(getTabName('notifications')).toBe('Notifications');
expect(getTabName('unknown')).toBe('');
expect(getTabName()).toBe('');