Reorganize file locations/directory structure (#270)

Reorganize file locations
This commit is contained in:
Michael Abashian
2019-06-19 11:41:14 -04:00
committed by GitHub
parent e3cb8d0447
commit ee56e9ccfb
229 changed files with 478 additions and 317 deletions

118
src/App.test.jsx Normal file
View File

@@ -0,0 +1,118 @@
import React from 'react';
import { mountWithContexts, waitForElement } from '../testUtils/enzymeHelpers';
import { asyncFlush } from '../jest.setup';
import App from './App';
import { ConfigAPI, MeAPI, RootAPI } from './api';
jest.mock('./api');
describe('<App />', () => {
const ansible_version = '111';
const custom_virtualenvs = [];
const version = '222';
beforeEach(() => {
ConfigAPI.read = () => Promise.resolve({
data: {
ansible_version,
custom_virtualenvs,
version
}
});
MeAPI.read = () => Promise.resolve({ data: { results: [{}] } });
});
afterEach(() => {
jest.clearAllMocks();
});
test('expected content is rendered', () => {
const appWrapper = mountWithContexts(
<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} />))
)}
/>,
);
// 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('opening the about modal renders prefetched config data', async (done) => {
const wrapper = mountWithContexts(<App />);
wrapper.update();
// open about modal
const aboutDropdown = 'Dropdown QuestionCircleIcon';
const aboutButton = 'DropdownItem li button';
const aboutModalContent = 'AboutModalBoxContent';
const aboutModalClose = 'button[aria-label="Close Dialog"]';
await waitForElement(wrapper, aboutDropdown);
wrapper.find(aboutDropdown).simulate('click');
const button = await waitForElement(wrapper, aboutButton, el => !el.props().disabled);
button.simulate('click');
// check about modal content
const content = await waitForElement(wrapper, aboutModalContent);
expect(content.find('dd').text()).toContain(ansible_version);
expect(content.find('pre').text()).toContain(`< AWX ${version} >`);
// close about modal
wrapper.find(aboutModalClose).simulate('click');
expect(wrapper.find(aboutModalContent)).toHaveLength(0);
done();
});
test('handleNavToggle sets state.isNavOpen to opposite', () => {
const appWrapper = mountWithContexts(<App />).find('App');
const { handleNavToggle } = appWrapper.instance();
[true, false, true, false, true].forEach(expected => {
expect(appWrapper.state().isNavOpen).toBe(expected);
handleNavToggle();
});
});
test('onLogout makes expected call to api client', async (done) => {
const appWrapper = mountWithContexts(<App />).find('App');
appWrapper.instance().handleLogout();
await asyncFlush();
expect(RootAPI.logout).toHaveBeenCalledTimes(1);
done();
});
});

10
src/RootProvider.test.jsx Normal file
View File

@@ -0,0 +1,10 @@
import { getLanguage } from './RootProvider';
describe('RootProvider.jsx', () => {
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');
});
});

97
src/api/Base.test.jsx Normal file
View File

@@ -0,0 +1,97 @@
import Base from './Base';
describe('Base', () => {
const createPromise = () => Promise.resolve();
const mockBaseURL = '/api/v2/organizations/';
const mockHttp = ({
delete: jest.fn(createPromise),
get: jest.fn(createPromise),
options: jest.fn(createPromise),
patch: jest.fn(createPromise),
post: jest.fn(createPromise),
put: jest.fn(createPromise)
});
const BaseAPI = new Base(mockHttp, mockBaseURL);
afterEach(() => {
jest.clearAllMocks();
});
test('create calls http method with expected data', async (done) => {
const data = { name: 'test ' };
await BaseAPI.create(data);
expect(mockHttp.post).toHaveBeenCalledTimes(1);
expect(mockHttp.post.mock.calls[0][1]).toEqual(data);
done();
});
test('destroy calls http method with expected data', async (done) => {
const resourceId = 1;
await BaseAPI.destroy(resourceId);
expect(mockHttp.delete).toHaveBeenCalledTimes(1);
expect(mockHttp.delete.mock.calls[0][0]).toEqual(`${mockBaseURL}${resourceId}/`);
done();
});
test('read calls http method with expected data', async (done) => {
const defaultParams = {};
const testParams = { foo: 'bar' };
await BaseAPI.read(testParams);
await BaseAPI.read();
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('readDetail calls http method with expected data', async (done) => {
const resourceId = 1;
await BaseAPI.readDetail(resourceId);
expect(mockHttp.get).toHaveBeenCalledTimes(1);
expect(mockHttp.get.mock.calls[0][0]).toEqual(`${mockBaseURL}${resourceId}/`);
done();
});
test('readOptions calls http method with expected data', async (done) => {
await BaseAPI.readOptions();
expect(mockHttp.options).toHaveBeenCalledTimes(1);
expect(mockHttp.options.mock.calls[0][0]).toEqual(`${mockBaseURL}`);
done();
});
test('replace calls http method with expected data', async (done) => {
const resourceId = 1;
const data = { name: 'test ' };
await BaseAPI.replace(resourceId, data);
expect(mockHttp.put).toHaveBeenCalledTimes(1);
expect(mockHttp.put.mock.calls[0][0]).toEqual(`${mockBaseURL}${resourceId}/`);
expect(mockHttp.put.mock.calls[0][1]).toEqual(data);
done();
});
test('update calls http method with expected data', async (done) => {
const resourceId = 1;
const data = { name: 'test ' };
await BaseAPI.update(resourceId, data);
expect(mockHttp.patch).toHaveBeenCalledTimes(1);
expect(mockHttp.patch.mock.calls[0][0]).toEqual(`${mockBaseURL}${resourceId}/`);
expect(mockHttp.patch.mock.calls[0][1]).toEqual(data);
done();
});
});

View File

@@ -0,0 +1,39 @@
import Organizations from './Organizations';
import { describeNotificationMixin } from '../../../testUtils/apiReusable';
describe('OrganizationsAPI', () => {
const orgId = 1;
const searchParams = { foo: 'bar' };
const createPromise = () => Promise.resolve();
const mockHttp = ({ get: jest.fn(createPromise) });
const OrganizationsAPI = new Organizations(mockHttp);
afterEach(() => {
jest.clearAllMocks();
});
test('read access list calls get with expected params', async (done) => {
await OrganizationsAPI.readAccessList(orgId);
await OrganizationsAPI.readAccessList(orgId, searchParams);
expect(mockHttp.get).toHaveBeenCalledTimes(2);
expect(mockHttp.get.mock.calls[0]).toContainEqual(`/api/v2/organizations/${orgId}/access_list/`, { params: {} });
expect(mockHttp.get.mock.calls[1]).toContainEqual(`/api/v2/organizations/${orgId}/access_list/`, { params: searchParams });
done();
});
test('read teams calls get with expected params', async (done) => {
await OrganizationsAPI.readTeams(orgId);
await OrganizationsAPI.readTeams(orgId, searchParams);
expect(mockHttp.get).toHaveBeenCalledTimes(2);
expect(mockHttp.get.mock.calls[0]).toContainEqual(`/api/v2/organizations/${orgId}/teams/`, { params: {} });
expect(mockHttp.get.mock.calls[1]).toContainEqual(`/api/v2/organizations/${orgId}/teams/`, { params: searchParams });
done();
});
});
describeNotificationMixin(Organizations, 'Organizations[NotificationsMixin]');

View File

@@ -0,0 +1,45 @@
import Root from './Root';
describe('RootAPI', () => {
const createPromise = () => Promise.resolve();
const mockHttp = ({ get: jest.fn(createPromise), post: jest.fn(createPromise) });
const RootAPI = new Root(mockHttp);
afterEach(() => {
jest.clearAllMocks();
});
test('login calls get and post with expected content headers', async (done) => {
const headers = { 'Content-Type': 'application/x-www-form-urlencoded' };
await RootAPI.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 sends expected data', async (done) => {
await RootAPI.login('foo', 'bar');
await RootAPI.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('logout calls expected http method', async (done) => {
await RootAPI.logout();
expect(mockHttp.get).toHaveBeenCalledTimes(1);
done();
});
});

View File

@@ -0,0 +1,35 @@
import Teams from './Teams';
describe('TeamsAPI', () => {
const teamId = 1;
const roleId = 7;
const createPromise = () => Promise.resolve();
const mockHttp = ({ post: jest.fn(createPromise) });
const TeamsAPI = new Teams(mockHttp);
afterEach(() => {
jest.clearAllMocks();
});
test('associate role calls post with expected params', async (done) => {
await TeamsAPI.associateRole(teamId, roleId);
expect(mockHttp.post).toHaveBeenCalledTimes(1);
expect(mockHttp.post.mock.calls[0]).toContainEqual(`/api/v2/teams/${teamId}/roles/`, { id: roleId });
done();
});
test('read teams calls post with expected params', async (done) => {
await TeamsAPI.disassociateRole(teamId, roleId);
expect(mockHttp.post).toHaveBeenCalledTimes(1);
expect(mockHttp.post.mock.calls[0]).toContainEqual(`/api/v2/teams/${teamId}/roles/`, {
id: roleId,
disassociate: true
});
done();
});
});

View File

@@ -0,0 +1,35 @@
import Users from './Users';
describe('UsersAPI', () => {
const userId = 1;
const roleId = 7;
const createPromise = () => Promise.resolve();
const mockHttp = ({ post: jest.fn(createPromise) });
const UsersAPI = new Users(mockHttp);
afterEach(() => {
jest.clearAllMocks();
});
test('associate role calls post with expected params', async (done) => {
await UsersAPI.associateRole(userId, roleId);
expect(mockHttp.post).toHaveBeenCalledTimes(1);
expect(mockHttp.post.mock.calls[0]).toContainEqual(`/api/v2/users/${userId}/roles/`, { id: roleId });
done();
});
test('read users calls post with expected params', async (done) => {
await UsersAPI.disassociateRole(userId, roleId);
expect(mockHttp.post).toHaveBeenCalledTimes(1);
expect(mockHttp.post.mock.calls[0]).toContainEqual(`/api/v2/users/${userId}/roles/`, {
id: roleId,
disassociate: true
});
done();
});
});

View File

@@ -9,8 +9,8 @@ import {
TextListItem
} from '@patternfly/react-core';
import { BrandName } from '../variables';
import brandLogoImg from '../../images/brand-logo.svg';
import { BrandName } from '../../variables';
import brandLogoImg from '../../../images/brand-logo.svg';
class About extends React.Component {
static createSpeechBubble (version) {

View File

@@ -0,0 +1,26 @@
import React from 'react';
import { mountWithContexts } from '../../../testUtils/enzymeHelpers';
import About from './About';
describe('<About />', () => {
let aboutWrapper;
let closeButton;
const onClose = jest.fn();
test('initially renders without crashing', () => {
aboutWrapper = mountWithContexts(
<About isOpen onClose={onClose} />
);
expect(aboutWrapper.length).toBe(1);
aboutWrapper.unmount();
});
test('close button calls onClose handler', () => {
aboutWrapper = mountWithContexts(
<About isOpen onClose={onClose} />
);
closeButton = aboutWrapper.find('AboutModalBoxCloseButton Button');
closeButton.simulate('click');
expect(onClose).toBeCalled();
aboutWrapper.unmount();
});
});

View File

@@ -0,0 +1 @@
export { default } from './About';

View File

@@ -0,0 +1,224 @@
/* eslint-disable react/jsx-pascal-case */
import React from 'react';
import { shallow } from 'enzyme';
import { mountWithContexts } from '../../../testUtils/enzymeHelpers';
import AddResourceRole, { _AddResourceRole } from './AddResourceRole';
import { TeamsAPI, UsersAPI } from '../../api';
jest.mock('../../api');
describe('<_AddResourceRole />', () => {
UsersAPI.read.mockResolvedValue({
data: {
count: 2,
results: [
{ id: 1, username: 'foo' },
{ id: 2, username: 'bar' }
]
}
});
const roles = {
admin_role: {
description: 'Can manage all aspects of the organization',
id: 1,
name: 'Admin'
},
execute_role: {
description: 'May run any executable resources in the organization',
id: 2,
name: 'Execute'
}
};
test('initially renders without crashing', () => {
shallow(
<_AddResourceRole
onClose={() => {}}
onSave={() => {}}
roles={roles}
i18n={{ _: val => val.toString() }}
/>
);
});
test('handleRoleCheckboxClick properly updates state', () => {
const wrapper = shallow(
<_AddResourceRole
onClose={() => {}}
onSave={() => {}}
roles={roles}
i18n={{ _: val => val.toString() }}
/>
);
wrapper.setState({
selectedRoleRows: [
{
description: 'Can manage all aspects of the organization',
name: 'Admin',
id: 1
}
]
});
wrapper.instance().handleRoleCheckboxClick({
description: 'Can manage all aspects of the organization',
name: 'Admin',
id: 1
});
expect(wrapper.state('selectedRoleRows')).toEqual([]);
wrapper.instance().handleRoleCheckboxClick({
description: 'Can manage all aspects of the organization',
name: 'Admin',
id: 1
});
expect(wrapper.state('selectedRoleRows')).toEqual([{
description: 'Can manage all aspects of the organization',
name: 'Admin',
id: 1
}]);
});
test('handleResourceCheckboxClick properly updates state', () => {
const wrapper = shallow(
<_AddResourceRole
onClose={() => {}}
onSave={() => {}}
roles={roles}
i18n={{ _: val => val.toString() }}
/>
);
wrapper.setState({
selectedResourceRows: [
{
id: 1,
username: 'foobar'
}
]
});
wrapper.instance().handleResourceCheckboxClick({
id: 1,
username: 'foobar'
});
expect(wrapper.state('selectedResourceRows')).toEqual([]);
wrapper.instance().handleResourceCheckboxClick({
id: 1,
username: 'foobar'
});
expect(wrapper.state('selectedResourceRows')).toEqual([{
id: 1,
username: 'foobar'
}]);
});
test('clicking user/team cards updates state', () => {
const spy = jest.spyOn(_AddResourceRole.prototype, 'handleResourceSelect');
const wrapper = mountWithContexts(
<AddResourceRole
onClose={() => {}}
onSave={() => {}}
roles={roles}
/>, { context: { network: { handleHttpError: () => {} } } }
).find('AddResourceRole');
const selectableCardWrapper = wrapper.find('SelectableCard');
expect(selectableCardWrapper.length).toBe(2);
selectableCardWrapper.first().simulate('click');
expect(spy).toHaveBeenCalledWith('users');
expect(wrapper.state('selectedResource')).toBe('users');
selectableCardWrapper.at(1).simulate('click');
expect(spy).toHaveBeenCalledWith('teams');
expect(wrapper.state('selectedResource')).toBe('teams');
});
test('handleResourceSelect clears out selected lists and sets selectedResource', () => {
const wrapper = shallow(
<_AddResourceRole
onClose={() => {}}
onSave={() => {}}
roles={roles}
i18n={{ _: val => val.toString() }}
/>
);
wrapper.setState({
selectedResource: 'teams',
selectedResourceRows: [
{
id: 1,
username: 'foobar'
}
],
selectedRoleRows: [
{
description: 'Can manage all aspects of the organization',
id: 1,
name: 'Admin'
}
]
});
wrapper.instance().handleResourceSelect('users');
expect(wrapper.state()).toEqual({
selectedResource: 'users',
selectedResourceRows: [],
selectedRoleRows: [],
currentStepId: 1,
});
wrapper.instance().handleResourceSelect('teams');
expect(wrapper.state()).toEqual({
selectedResource: 'teams',
selectedResourceRows: [],
selectedRoleRows: [],
currentStepId: 1
});
});
test('handleWizardSave makes correct api calls, calls onSave when done', async () => {
const handleSave = jest.fn();
const wrapper = mountWithContexts(
<AddResourceRole
onClose={() => {}}
onSave={handleSave}
roles={roles}
/>, { context: { network: { handleHttpError: () => {} } } }
).find('AddResourceRole');
wrapper.setState({
selectedResource: 'users',
selectedResourceRows: [
{
id: 1,
username: 'foobar'
}
],
selectedRoleRows: [
{
description: 'Can manage all aspects of the organization',
id: 1,
name: 'Admin'
},
{
description: 'May run any executable resources in the organization',
id: 2,
name: 'Execute'
}
]
});
await wrapper.instance().handleWizardSave();
expect(UsersAPI.associateRole).toHaveBeenCalledTimes(2);
expect(handleSave).toHaveBeenCalled();
wrapper.setState({
selectedResource: 'teams',
selectedResourceRows: [
{
id: 1,
name: 'foobar'
}
],
selectedRoleRows: [
{
description: 'Can manage all aspects of the organization',
id: 1,
name: 'Admin'
},
{
description: 'May run any executable resources in the organization',
id: 2,
name: 'Execute'
}
]
});
await wrapper.instance().handleWizardSave();
expect(TeamsAPI.associateRole).toHaveBeenCalledTimes(2);
expect(handleSave).toHaveBeenCalled();
});
});

View File

@@ -0,0 +1,16 @@
import React from 'react';
import { shallow } from 'enzyme';
import CheckboxCard from './CheckboxCard';
describe('<CheckboxCard />', () => {
let wrapper;
test('initially renders without crashing', () => {
wrapper = shallow(
<CheckboxCard
name="Foobar"
itemId={5}
/>
);
expect(wrapper.length).toBe(1);
});
});

View File

@@ -5,7 +5,7 @@ import { withI18n } from '@lingui/react';
import { t } from '@lingui/macro';
import PaginatedDataList from '../PaginatedDataList';
import DataListToolbar from '../DataListToolbar';
import CheckboxListItem from '../ListItem';
import CheckboxListItem from '../CheckboxListItem';
import SelectedList from '../SelectedList';
import { getQSConfig, parseNamespacedQueryString } from '../../util/qs';

View File

@@ -0,0 +1,118 @@
import React from 'react';
import { createMemoryHistory } from 'history';
import { shallow } from 'enzyme';
import { mountWithContexts } from '../../../testUtils/enzymeHelpers';
import { sleep } from '../../../testUtils/testUtils';
import SelectResourceStep from './SelectResourceStep';
describe('<SelectResourceStep />', () => {
const columns = [
{ name: 'Username', key: 'username', isSortable: true }
];
afterEach(() => {
jest.restoreAllMocks();
});
test('initially renders without crashing', () => {
shallow(
<SelectResourceStep
columns={columns}
displayKey="username"
onRowClick={() => {}}
onSearch={() => {}}
sortedColumnKey="username"
/>
);
});
test('fetches resources on mount', async () => {
const handleSearch = jest.fn().mockResolvedValue({
data: {
count: 2,
results: [
{ id: 1, username: 'foo', url: 'item/1' },
{ id: 2, username: 'bar', url: 'item/2' }
]
}
});
mountWithContexts(
<SelectResourceStep
columns={columns}
displayKey="username"
onRowClick={() => {}}
onSearch={handleSearch}
sortedColumnKey="username"
/>
);
expect(handleSearch).toHaveBeenCalledWith({
order_by: 'username',
page: 1,
page_size: 5
});
});
test('readResourceList properly adds rows to state', async () => {
const selectedResourceRows = [
{ id: 1, username: 'foo', url: 'item/1' }
];
const handleSearch = jest.fn().mockResolvedValue({
data: {
count: 2,
results: [
{ id: 1, username: 'foo', url: 'item/1' },
{ id: 2, username: 'bar', url: 'item/2' }
]
}
});
const history = createMemoryHistory({
initialEntries: ['/organizations/1/access?resource.page=1&resource.order_by=-username'],
});
const wrapper = await mountWithContexts(
<SelectResourceStep
columns={columns}
displayKey="username"
onRowClick={() => {}}
onSearch={handleSearch}
selectedResourceRows={selectedResourceRows}
sortedColumnKey="username"
/>, { context: { router: { history, route: { location: history.location } } } }
).find('SelectResourceStep');
await wrapper.instance().readResourceList();
expect(handleSearch).toHaveBeenCalledWith({
order_by: '-username',
page: 1,
page_size: 5,
});
expect(wrapper.state('resources')).toEqual([
{ id: 1, username: 'foo', url: 'item/1' },
{ id: 2, username: 'bar', url: 'item/2' }
]);
});
test('clicking on row fires callback with correct params', async () => {
const handleRowClick = jest.fn();
const data = {
count: 2,
results: [
{ id: 1, username: 'foo', url: 'item/1' },
{ id: 2, username: 'bar', url: 'item/2' }
]
};
const wrapper = mountWithContexts(
<SelectResourceStep
columns={columns}
displayKey="username"
onRowClick={handleRowClick}
onSearch={() => ({ data })}
selectedResourceRows={[]}
sortedColumnKey="username"
/>
);
await sleep(0);
wrapper.update();
const checkboxListItemWrapper = wrapper.find('CheckboxListItem');
expect(checkboxListItemWrapper.length).toBe(2);
checkboxListItemWrapper.first().find('input[type="checkbox"]')
.simulate('change', { target: { checked: true } });
expect(handleRowClick).toHaveBeenCalledWith(data.results[0]);
});
});

View File

@@ -0,0 +1,64 @@
import React from 'react';
import { shallow } from 'enzyme';
import { mountWithContexts } from '../../../testUtils/enzymeHelpers';
import SelectRoleStep from './SelectRoleStep';
describe('<SelectRoleStep />', () => {
let wrapper;
const roles = {
project_admin_role: {
id: 1,
name: 'Project Admin',
description: 'Can manage all projects of the organization'
},
execute_role: {
id: 2,
name: 'Execute',
description: 'May run any executable resources in the organization'
}
};
const selectedRoles = [
{
id: 1,
name: 'Project Admin',
description: 'Can manage all projects of the organization'
}
];
const selectedResourceRows = [
{
id: 1,
name: 'foo'
}
];
test('initially renders without crashing', () => {
wrapper = shallow(
<SelectRoleStep
roles={roles}
selectedResourceRows={selectedResourceRows}
selectedRoleRows={selectedRoles}
/>
);
expect(wrapper.length).toBe(1);
wrapper.unmount();
});
test('clicking role fires onRolesClick callback', () => {
const onRolesClick = jest.fn();
wrapper = mountWithContexts(
<SelectRoleStep
onRolesClick={onRolesClick}
roles={roles}
selectedResourceRows={selectedResourceRows}
selectedRoleRows={selectedRoles}
/>
);
const CheckboxCards = wrapper.find('CheckboxCard');
expect(CheckboxCards.length).toBe(2);
CheckboxCards.first().prop('onSelect')();
expect(onRolesClick).toBeCalledWith({
id: 1,
name: 'Project Admin',
description: 'Can manage all projects of the organization'
});
wrapper.unmount();
});
});

View File

@@ -0,0 +1,27 @@
import React from 'react';
import { shallow } from 'enzyme';
import SelectableCard from './SelectableCard';
describe('<SelectableCard />', () => {
let wrapper;
const onClick = jest.fn();
test('initially renders without crashing when not selected', () => {
wrapper = shallow(
<SelectableCard
onClick={onClick}
/>
);
expect(wrapper.length).toBe(1);
wrapper.unmount();
});
test('initially renders without crashing when selected', () => {
wrapper = shallow(
<SelectableCard
isSelected
onClick={onClick}
/>
);
expect(wrapper.length).toBe(1);
wrapper.unmount();
});
});

View File

@@ -0,0 +1,5 @@
export { default as AddResourceRole } from './AddResourceRole';
export { default as CheckboxCard } from './CheckboxCard';
export { default as SelectableCard } from './SelectableCard';
export { default as SelectResourceStep } from './SelectResourceStep';
export { default as SelectRoleStep } from './SelectRoleStep';

View File

@@ -0,0 +1,13 @@
import React from 'react';
import { mount } from 'enzyme';
import AlertModal from './AlertModal';
describe('AlertModal', () => {
test('renders the expected content', () => {
const wrapper = mount(
<AlertModal title="Danger!" />
);
expect(wrapper).toHaveLength(1);
});
});

View File

@@ -0,0 +1 @@
export { default } from './AlertModal';

View File

@@ -0,0 +1,49 @@
import React from 'react';
import { mountWithContexts } from '../../../testUtils/enzymeHelpers';
import AnsibleSelect, { _AnsibleSelect } from './AnsibleSelect';
const label = 'test select';
const mockData = ['/venv/baz/', '/venv/ansible/'];
describe('<AnsibleSelect />', () => {
test('initially renders succesfully', async () => {
mountWithContexts(
<AnsibleSelect
value="foo"
name="bar"
onChange={() => { }}
label={label}
data={mockData}
/>
);
});
test('calls "onSelectChange" on dropdown select change', () => {
const spy = jest.spyOn(_AnsibleSelect.prototype, 'onSelectChange');
const wrapper = mountWithContexts(
<AnsibleSelect
value="foo"
name="bar"
onChange={() => { }}
label={label}
data={mockData}
/>
);
expect(spy).not.toHaveBeenCalled();
wrapper.find('select').simulate('change');
expect(spy).toHaveBeenCalled();
});
test('Returns correct select options if defaultSelected props is passed', () => {
const wrapper = mountWithContexts(
<AnsibleSelect
value="foo"
name="bar"
onChange={() => { }}
label={label}
data={mockData}
defaultSelected={mockData[1]}
/>
);
expect(wrapper.find('FormSelect')).toHaveLength(1);
});
});

View File

@@ -1,3 +1 @@
import AnsibleSelect from './AnsibleSelect';
export default AnsibleSelect;
export { default } from './AnsibleSelect';

View File

@@ -0,0 +1,13 @@
import React from 'react';
import { mount } from 'enzyme';
import Background from './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

@@ -0,0 +1 @@
export { default } from './Background';

View File

@@ -0,0 +1,24 @@
import React from 'react';
import { mountWithContexts } from '../../../testUtils/enzymeHelpers';
import BrandLogo from './BrandLogo';
let logoWrapper;
let brandLogoElem;
let svgElem;
const findChildren = () => {
brandLogoElem = logoWrapper.find('BrandLogo');
svgElem = logoWrapper.find('svg');
};
describe('<BrandLogo />', () => {
test('initially renders without crashing', () => {
logoWrapper = mountWithContexts(
<BrandLogo />
);
findChildren();
expect(logoWrapper.length).toBe(1);
expect(brandLogoElem.length).toBe(1);
expect(svgElem.length).toBe(1);
});
});

View File

@@ -1,3 +1 @@
import BrandLogo from './BrandLogo';
export default BrandLogo;
export { default } from './BrandLogo';

View File

@@ -0,0 +1,68 @@
import React from 'react';
import { mount } from 'enzyme';
import { MemoryRouter } from 'react-router-dom';
import Breadcrumbs from './Breadcrumbs';
describe('<Breadcrumb />', () => {
let breadcrumbWrapper;
let breadcrumb;
let breadcrumbItem;
let breadcrumbHeading;
const config = {
'/foo': 'Foo',
'/foo/1': 'One',
'/foo/1/bar': 'Bar',
'/foo/1/bar/fiz': 'Fiz'
};
const findChildren = () => {
breadcrumb = breadcrumbWrapper.find('Breadcrumb');
breadcrumbItem = breadcrumbWrapper.find('BreadcrumbItem');
breadcrumbHeading = breadcrumbWrapper.find('BreadcrumbHeading');
};
test('initially renders succesfully', () => {
breadcrumbWrapper = mount(
<MemoryRouter initialEntries={['/foo/1/bar']} initialIndex={0}>
<Breadcrumbs
breadcrumbConfig={config}
/>
</MemoryRouter>
);
findChildren();
expect(breadcrumb).toHaveLength(1);
expect(breadcrumbItem).toHaveLength(2);
expect(breadcrumbHeading).toHaveLength(1);
expect(breadcrumbItem.first().text()).toBe('Foo');
expect(breadcrumbItem.last().text()).toBe('One');
expect(breadcrumbHeading.text()).toBe('Bar');
breadcrumbWrapper.unmount();
});
test('renders breadcrumb items defined in breadcrumbConfig', () => {
const routes = [
['/fo', 0],
['/foo', 0],
['/foo/1', 1],
['/foo/baz', 1],
['/foo/1/bar', 2],
['/foo/1/bar/fiz', 3],
];
routes.forEach(([location, crumbLength]) => {
breadcrumbWrapper = mount(
<MemoryRouter initialEntries={[location]}>
<Breadcrumbs
breadcrumbConfig={config}
/>
</MemoryRouter>
);
expect(breadcrumbWrapper.find('BreadcrumbItem')).toHaveLength(crumbLength);
breadcrumbWrapper.unmount();
});
});
});

View File

@@ -0,0 +1 @@
export { default } from './Breadcrumbs';

View File

@@ -0,0 +1,23 @@
import React from 'react';
import { mountWithContexts } from '../../../testUtils/enzymeHelpers';
import CardCloseButton from './CardCloseButton';
describe('<CardCloseButton>', () => {
test('should render close button', () => {
const wrapper = mountWithContexts(<CardCloseButton />);
const button = wrapper.find('Button');
expect(button).toHaveLength(1);
expect(button.prop('variant')).toBe('plain');
expect(button.prop('aria-label')).toBe('Close');
expect(wrapper.find('Link')).toHaveLength(0);
});
test('should render close link when `linkTo` prop provided', () => {
const wrapper = mountWithContexts(<CardCloseButton linkTo="/foo" />);
expect(wrapper.find('Button')).toHaveLength(0);
const link = wrapper.find('Link');
expect(link).toHaveLength(1);
expect(link.prop('to')).toEqual('/foo');
expect(link.prop('aria-label')).toEqual('Close');
});
});

View File

@@ -0,0 +1 @@
export { default } from './CardCloseButton';

View File

@@ -0,0 +1,18 @@
import React from 'react';
import { mount } from 'enzyme';
import CheckboxListItem from './CheckboxListItem';
describe('CheckboxListItem', () => {
test('renders the expected content', () => {
const wrapper = mount(
<CheckboxListItem
itemId={1}
name="Buzz"
isSelected={false}
onSelect={() => {}}
/>
);
expect(wrapper).toHaveLength(1);
});
});

View File

@@ -0,0 +1 @@
export { default } from './CheckboxListItem';

View File

@@ -0,0 +1,11 @@
import React from 'react';
import { mount } from 'enzyme';
import Chip from './Chip';
describe('Chip', () => {
test('renders the expected content', () => {
const wrapper = mount(<Chip />);
expect(wrapper).toHaveLength(1);
});
});

View File

@@ -0,0 +1,69 @@
import React from 'react';
import { act } from 'react-dom/test-utils';
import { mountWithContexts } from '../../../testUtils/enzymeHelpers';
import { ChipGroup, Chip } from '.';
describe('<ChipGroup />', () => {
test('should render all chips', () => {
const wrapper = mountWithContexts(
<ChipGroup>
<Chip>One</Chip>
<Chip>Two</Chip>
<Chip>Three</Chip>
<Chip>Four</Chip>
<Chip>Five</Chip>
<Chip>Six</Chip>
</ChipGroup>
);
expect(wrapper.find(Chip)).toHaveLength(6);
expect(wrapper.find('li')).toHaveLength(6);
});
test('should render show more toggle', () => {
const wrapper = mountWithContexts(
<ChipGroup showOverflowAfter={5}>
<Chip>One</Chip>
<Chip>Two</Chip>
<Chip>Three</Chip>
<Chip>Four</Chip>
<Chip>Five</Chip>
<Chip>Six</Chip>
<Chip>Seven</Chip>
</ChipGroup>
);
expect(wrapper.find(Chip)).toHaveLength(6);
const toggle = wrapper.find(Chip).at(5);
expect(toggle.prop('isOverflowChip')).toBe(true);
expect(toggle.text()).toEqual('2 more');
});
test('should render show less toggle', () => {
const wrapper = mountWithContexts(
<ChipGroup showOverflowAfter={5}>
<Chip>One</Chip>
<Chip>Two</Chip>
<Chip>Three</Chip>
<Chip>Four</Chip>
<Chip>Five</Chip>
<Chip>Six</Chip>
<Chip>Seven</Chip>
</ChipGroup>
);
expect(wrapper.find(Chip)).toHaveLength(6);
const toggle = wrapper.find(Chip).at(5);
expect(toggle.prop('isOverflowChip')).toBe(true);
act(() => {
toggle.prop('onClick')();
});
wrapper.update();
expect(wrapper.find(Chip)).toHaveLength(8);
expect(wrapper.find(Chip).at(7).text()).toEqual('Show Less');
act(() => {
const toggle2 = wrapper.find(Chip).at(7);
expect(toggle2.prop('isOverflowChip')).toBe(true);
toggle2.prop('onClick')();
});
wrapper.update();
expect(wrapper.find(Chip)).toHaveLength(6);
});
});

View File

@@ -1,7 +1,7 @@
import React from 'react';
import { mount } from 'enzyme';
import { Formik } from 'formik';
import { sleep } from '../../../__tests__/testUtils';
import { sleep } from '../../../testUtils/testUtils';
import VariablesField from './VariablesField';
describe('VariablesField', () => {

View File

@@ -0,0 +1,11 @@
import React from 'react';
import { mountWithContexts } from '../../../testUtils/enzymeHelpers';
import ContentEmpty from './ContentEmpty';
describe('ContentEmpty', () => {
test('renders the expected content', () => {
const wrapper = mountWithContexts(<ContentEmpty />);
expect(wrapper).toHaveLength(1);
});
});

View File

@@ -0,0 +1 @@
export { default } from './ContentEmpty';

View File

@@ -0,0 +1,11 @@
import React from 'react';
import { mountWithContexts } from '../../../testUtils/enzymeHelpers';
import ContentError from './ContentError';
describe('ContentError', () => {
test('renders the expected content', () => {
const wrapper = mountWithContexts(<ContentError />);
expect(wrapper).toHaveLength(1);
});
});

View File

@@ -0,0 +1 @@
export { default } from './ContentError';

View File

@@ -0,0 +1,11 @@
import React from 'react';
import { mountWithContexts } from '../../../testUtils/enzymeHelpers';
import ContentLoading from './ContentLoading';
describe('ContentLoading', () => {
test('renders the expected content', () => {
const wrapper = mountWithContexts(<ContentLoading />);
expect(wrapper).toHaveLength(1);
});
});

View File

@@ -0,0 +1 @@
export { default } from './ContentLoading';

View File

@@ -0,0 +1,199 @@
import React from 'react';
import { mountWithContexts } from '../../../testUtils/enzymeHelpers';
import DataListToolbar from './DataListToolbar';
describe('<DataListToolbar />', () => {
let toolbar;
afterEach(() => {
if (toolbar) {
toolbar.unmount();
toolbar = null;
}
});
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"]';
const sort = 'button[aria-label="Sort"]';
const onSearch = jest.fn();
const onSort = jest.fn();
const onSelectAll = jest.fn();
toolbar = mountWithContexts(
<DataListToolbar
isAllSelected={false}
showExpandCollapse
sortedColumnKey="name"
sortOrder="ascending"
columns={columns}
onSearch={onSearch}
onSort={onSort}
onSelectAll={onSelectAll}
showSelectAll
/>
);
toolbar.find(sort).simulate('click');
toolbar.find(selectAll).simulate('change', { target: { checked: false } });
expect(onSelectAll).toHaveBeenCalledTimes(1);
expect(onSort).toHaveBeenCalledTimes(1);
expect(onSort).toBeCalledWith('name', 'descending');
expect(onSelectAll).toHaveBeenCalledTimes(1);
expect(onSelectAll.mock.calls[0][0]).toBe(false);
toolbar.find(searchTextInput).instance().value = 'test-321';
toolbar.find(searchTextInput).simulate('change');
toolbar.find(search).simulate('click');
expect(onSearch).toHaveBeenCalledTimes(1);
expect(onSearch).toBeCalledWith('test-321');
});
test('dropdown items sortable columns work', () => {
const sortDropdownToggleSelector = 'button[id="awx-sort"]';
const searchDropdownToggleSelector = 'button[id="awx-search"]';
const dropdownMenuItems = 'DropdownMenu > ul';
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 onSort = jest.fn();
toolbar = mountWithContexts(
<DataListToolbar
sortedColumnKey="foo"
sortOrder="ascending"
columns={multipleColumns}
onSort={onSort}
/>
);
const sortDropdownToggle = toolbar.find(sortDropdownToggleSelector);
expect(sortDropdownToggle.length).toBe(1);
sortDropdownToggle.simulate('click');
toolbar.update();
const sortDropdownItems = toolbar.find(dropdownMenuItems).children();
expect(sortDropdownItems.length).toBe(2);
const mockedSortEvent = { target: { innerText: 'Bar' } };
sortDropdownItems.at(0).simulate('click', mockedSortEvent);
toolbar = mountWithContexts(
<DataListToolbar
sortedColumnKey="foo"
sortOrder="descending"
columns={multipleColumns}
onSort={onSort}
/>
);
toolbar.update();
const sortDropdownToggleDescending = toolbar.find(sortDropdownToggleSelector);
expect(sortDropdownToggleDescending.length).toBe(1);
sortDropdownToggleDescending.simulate('click');
toolbar.update();
const sortDropdownItemsDescending = toolbar.find(dropdownMenuItems).children();
expect(sortDropdownItemsDescending.length).toBe(2);
sortDropdownToggleDescending.simulate('click'); // toggle close the sort dropdown
const mockedSortEventDescending = { target: { innerText: 'Bar' } };
sortDropdownItems.at(0).simulate('click', mockedSortEventDescending);
toolbar.update();
const searchDropdownToggle = toolbar.find(searchDropdownToggleSelector);
expect(searchDropdownToggle.length).toBe(1);
searchDropdownToggle.simulate('click');
toolbar.update();
const searchDropdownItems = toolbar.find(dropdownMenuItems).children();
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 }];
toolbar = mountWithContexts(
<DataListToolbar
sortedColumnKey="id"
sortOrder="descending"
columns={numericColumns}
/>
);
const downNumericIcon = toolbar.find(downNumericIconSelector);
expect(downNumericIcon.length).toBe(1);
toolbar = mountWithContexts(
<DataListToolbar
sortedColumnKey="id"
sortOrder="ascending"
columns={numericColumns}
/>
);
const upNumericIcon = toolbar.find(upNumericIconSelector);
expect(upNumericIcon.length).toBe(1);
toolbar = mountWithContexts(
<DataListToolbar
sortedColumnKey="name"
sortOrder="descending"
columns={alphaColumns}
/>
);
const downAlphaIcon = toolbar.find(downAlphaIconSelector);
expect(downAlphaIcon.length).toBe(1);
toolbar = mountWithContexts(
<DataListToolbar
sortedColumnKey="name"
sortOrder="ascending"
columns={alphaColumns}
/>
);
const upAlphaIcon = toolbar.find(upAlphaIconSelector);
expect(upAlphaIcon.length).toBe(1);
});
test('should render additionalControls', () => {
const columns = [{ name: 'Name', key: 'name', isSortable: true }];
const onSearch = jest.fn();
const onSort = jest.fn();
const onSelectAll = jest.fn();
toolbar = mountWithContexts(
<DataListToolbar
columns={columns}
onSearch={onSearch}
onSort={onSort}
onSelectAll={onSelectAll}
additionalControls={[<button key="1" id="test" type="button">click</button>]}
/>
);
const button = toolbar.find('#test');
expect(button).toHaveLength(1);
expect(button.text()).toEqual('click');
});
});

View File

@@ -1,3 +1 @@
import DataListToolbar from './DataListToolbar';
export default DataListToolbar;
export { default } from './DataListToolbar';

View File

@@ -0,0 +1,13 @@
import React from 'react';
import { mount } from 'enzyme';
import Detail from './Detail';
describe('Detail', () => {
test('renders the expected content', () => {
const wrapper = mount(
<Detail label="foo" />
);
expect(wrapper).toHaveLength(1);
});
});

View File

@@ -0,0 +1,11 @@
import React from 'react';
import { mount } from 'enzyme';
import DetailList from './DetailList';
describe('DetailList', () => {
test('renders the expected content', () => {
const wrapper = mount(<DetailList />);
expect(wrapper).toHaveLength(1);
});
});

View File

@@ -0,0 +1,20 @@
import React from 'react';
import { mountWithContexts } from '../../../testUtils/enzymeHelpers';
import ExpandCollapse from './ExpandCollapse';
describe('<ExpandCollapse />', () => {
const onCompact = jest.fn();
const onExpand = jest.fn();
const isCompact = false;
test('initially renders without crashing', () => {
const wrapper = mountWithContexts(
<ExpandCollapse
onCompact={onCompact}
onExpand={onExpand}
isCompact={isCompact}
/>
);
expect(wrapper.length).toBe(1);
wrapper.unmount();
});
});

View File

@@ -1,3 +1 @@
import ExpandCollapse from './ExpandCollapse';
export default ExpandCollapse;
export { default } from './ExpandCollapse';

View File

@@ -0,0 +1,16 @@
import React from 'react';
import { mountWithContexts } from '../../../testUtils/enzymeHelpers';
import FormActionGroup from './FormActionGroup';
describe('FormActionGroup', () => {
test('renders the expected content', () => {
const wrapper = mountWithContexts(
<FormActionGroup
onSubmit={() => {}}
onCancel={() => {}}
/>
);
expect(wrapper).toHaveLength(1);
});
});

View File

@@ -0,0 +1 @@
export { default } from './FormActionGroup';

View File

@@ -11,7 +11,7 @@ function FormField (props) {
name={name}
validate={validate}
render={({ field, form }) => {
const isValid = !form.touched[field.name] || !form.errors[field.name];
const isValid = form && (!form.touched[field.name] || !form.errors[field.name]);
return (
<FormGroup

View File

@@ -0,0 +1 @@
export { default } from './FormField';

View File

@@ -0,0 +1,11 @@
import React from 'react';
import { mount } from 'enzyme';
import FormRow from './FormRow';
describe('FormRow', () => {
test('renders the expected content', () => {
const wrapper = mount(<FormRow />);
expect(wrapper).toHaveLength(1);
});
});

View File

@@ -0,0 +1 @@
export { default } from './FormRow';

View File

@@ -1,3 +0,0 @@
import CheckboxListItem from './CheckboxListItem';
export default CheckboxListItem;

View File

@@ -13,7 +13,7 @@ import { t } from '@lingui/macro';
import PaginatedDataList from '../PaginatedDataList';
import DataListToolbar from '../DataListToolbar';
import CheckboxListItem from '../ListItem';
import CheckboxListItem from '../CheckboxListItem';
import SelectedList from '../SelectedList';
import { ChipGroup, Chip } from '../Chip';
import { getQSConfig, parseNamespacedQueryString } from '../../util/qs';

View File

@@ -0,0 +1,228 @@
/* eslint-disable react/jsx-pascal-case */
import React from 'react';
import { createMemoryHistory } from 'history';
import { mountWithContexts } from '../../../testUtils/enzymeHelpers';
import Lookup, { _Lookup } from './Lookup';
let mockData = [{ name: 'foo', id: 1, isChecked: false }];
const mockColumns = [
{ name: 'Name', key: 'name', isSortable: true }
];
describe('<Lookup />', () => {
test('initially renders succesfully', () => {
mountWithContexts(
<Lookup
lookupHeader="Foo Bar"
name="fooBar"
value={mockData}
onLookupSave={() => { }}
getItems={() => { }}
columns={mockColumns}
sortedColumnKey="name"
/>
);
});
test('API response is formatted properly', (done) => {
const wrapper = mountWithContexts(
<Lookup
lookupHeader="Foo Bar"
name="fooBar"
value={mockData}
onLookupSave={() => { }}
getItems={() => ({ data: { results: [{ name: 'test instance', id: 1 }] } })}
columns={mockColumns}
sortedColumnKey="name"
/>
).find('Lookup');
setImmediate(() => {
expect(wrapper.state().results).toEqual([{ id: 1, name: 'test instance' }]);
done();
});
});
test('Opens modal when search icon is clicked', () => {
const spy = jest.spyOn(_Lookup.prototype, 'handleModalToggle');
const mockSelected = [{ name: 'foo', id: 1 }];
const wrapper = mountWithContexts(
<Lookup
id="search"
lookupHeader="Foo Bar"
name="fooBar"
value={mockSelected}
onLookupSave={() => { }}
getItems={() => { }}
columns={mockColumns}
sortedColumnKey="name"
/>
).find('Lookup');
expect(spy).not.toHaveBeenCalled();
expect(wrapper.state('lookupSelectedItems')).toEqual(mockSelected);
const searchItem = wrapper.find('button[aria-label="Search"]');
searchItem.first().simulate('click');
expect(spy).toHaveBeenCalled();
expect(wrapper.state('lookupSelectedItems')).toEqual([{
id: 1,
name: 'foo'
}]);
expect(wrapper.state('isModalOpen')).toEqual(true);
});
test('calls "toggleSelected" when a user changes a checkbox', (done) => {
const spy = jest.spyOn(_Lookup.prototype, 'toggleSelected');
const mockSelected = [{ name: 'foo', id: 1 }];
const data = {
results: [
{ name: 'test instance', id: 1, url: '/foo' }
],
count: 1,
};
const wrapper = mountWithContexts(
<Lookup
id="search"
lookupHeader="Foo Bar"
name="fooBar"
value={mockSelected}
onLookupSave={() => { }}
getItems={() => ({ data })}
columns={mockColumns}
sortedColumnKey="name"
/>
);
setImmediate(() => {
const searchItem = wrapper.find('button[aria-label="Search"]');
searchItem.first().simulate('click');
wrapper.find('input[type="checkbox"]').simulate('change');
expect(spy).toHaveBeenCalled();
done();
});
});
test('calls "toggleSelected" when remove icon is clicked', () => {
const spy = jest.spyOn(_Lookup.prototype, 'toggleSelected');
mockData = [{ name: 'foo', id: 1 }, { name: 'bar', id: 2 }];
const data = {
results: [
{ name: 'test instance', id: 1, url: '/foo' }
],
count: 1,
};
const wrapper = mountWithContexts(
<Lookup
id="search"
lookupHeader="Foo Bar"
name="fooBar"
value={mockData}
onLookupSave={() => { }}
getItems={() => ({ data })}
columns={mockColumns}
sortedColumnKey="name"
/>
);
const removeIcon = wrapper.find('button[aria-label="close"]').first();
removeIcon.simulate('click');
expect(spy).toHaveBeenCalled();
});
test('renders chips from prop value', () => {
mockData = [{ name: 'foo', id: 0 }, { name: 'bar', id: 1 }];
const wrapper = mountWithContexts(
<Lookup
lookupHeader="Foo Bar"
onLookupSave={() => { }}
value={mockData}
selected={[]}
getItems={() => { }}
columns={mockColumns}
sortedColumnKey="name"
/>
).find('Lookup');
const chip = wrapper.find('.pf-c-chip');
expect(chip).toHaveLength(2);
});
test('toggleSelected successfully adds/removes row from lookupSelectedItems state', () => {
mockData = [];
const wrapper = mountWithContexts(
<Lookup
lookupHeader="Foo Bar"
onLookupSave={() => { }}
value={mockData}
getItems={() => { }}
columns={mockColumns}
sortedColumnKey="name"
/>
).find('Lookup');
wrapper.instance().toggleSelected({
id: 1,
name: 'foo'
});
expect(wrapper.state('lookupSelectedItems')).toEqual([{
id: 1,
name: 'foo'
}]);
wrapper.instance().toggleSelected({
id: 1,
name: 'foo'
});
expect(wrapper.state('lookupSelectedItems')).toEqual([]);
});
test('saveModal calls callback with selected items', () => {
mockData = [];
const onLookupSaveFn = jest.fn();
const wrapper = mountWithContexts(
<Lookup
lookupHeader="Foo Bar"
name="fooBar"
value={mockData}
onLookupSave={onLookupSaveFn}
getItems={() => { }}
sortedColumnKey="name"
/>
).find('Lookup');
wrapper.instance().toggleSelected({
id: 1,
name: 'foo'
});
expect(wrapper.state('lookupSelectedItems')).toEqual([{
id: 1,
name: 'foo'
}]);
wrapper.instance().saveModal();
expect(onLookupSaveFn).toHaveBeenCalledWith([{
id: 1,
name: 'foo'
}], 'fooBar');
});
test('should re-fetch data when URL params change', async () => {
const history = createMemoryHistory({
initialEntries: ['/organizations/add'],
});
const getItems = jest.fn();
const wrapper = mountWithContexts(
<_Lookup
lookupHeader="Foo Bar"
onLookupSave={() => { }}
value={mockData}
selected={[]}
columns={mockColumns}
sortedColumnKey="name"
getItems={getItems}
handleHttpError={() => {}}
location={{ history }}
i18n={{ _: val => val.toString() }}
/>
);
expect(getItems).toHaveBeenCalledTimes(1);
history.push('organizations/add?page=2');
wrapper.setProps({
location: { history },
});
wrapper.update();
expect(getItems).toHaveBeenCalledTimes(2);
});
});

View File

@@ -1,3 +1 @@
import Lookup from './Lookup';
export default Lookup;
export { default } from './Lookup';

View File

@@ -0,0 +1,63 @@
import React from 'react';
import { MemoryRouter } from 'react-router-dom';
import { mount } from 'enzyme';
import { Nav } from '@patternfly/react-core';
import NavExpandableGroup from './NavExpandableGroup';
describe('NavExpandableGroup', () => {
test('initialization and render', () => {
const component = mount(
<MemoryRouter initialEntries={['/foo']}>
<Nav aria-label="Test Navigation">
<NavExpandableGroup
groupId="test"
groupTitle="Test"
routes={[
{ path: '/foo', title: 'Foo' },
{ path: '/bar', title: 'Bar' },
{ path: '/fiz', title: 'Fiz' },
]}
/>
</Nav>
</MemoryRouter>
).find('NavExpandableGroup').instance();
expect(component.navItemPaths).toEqual(['/foo', '/bar', '/fiz']);
expect(component.isActiveGroup()).toEqual(true);
});
describe('isActivePath', () => {
const params = [
['/fo', '/foo', false],
['/foo', '/foo', true],
['/foo/1/bar/fiz', '/foo', true],
['/foo/1/bar/fiz', 'foo', false],
['/foo/1/bar/fiz', 'foo/', false],
['/foo/1/bar/fiz', '/bar', false],
['/foo/1/bar/fiz', '/fiz', false],
];
params.forEach(([location, path, expected]) => {
test(`when location is ${location}, isActivePath('${path}') returns ${expected} `, () => {
const component = mount(
<MemoryRouter initialEntries={[location]}>
<Nav aria-label="Test Navigation">
<NavExpandableGroup
groupId="test"
groupTitle="Test"
routes={[
{ path: '/foo', title: 'Foo' },
{ path: '/bar', title: 'Bar' },
{ path: '/fiz', title: 'Fiz' },
]}
/>
</Nav>
</MemoryRouter>
).find('NavExpandableGroup').instance();
expect(component.isActivePath(path)).toEqual(expected);
});
});
});
});

View File

@@ -0,0 +1 @@
export { default } from './NavExpandableGroup';

View File

@@ -0,0 +1,108 @@
import React from 'react';
import { mountWithContexts } from '../../../testUtils/enzymeHelpers';
import NotificationListItem from './NotificationListItem';
describe('<NotificationListItem canToggleNotifications />', () => {
let wrapper;
let toggleNotification;
beforeEach(() => {
toggleNotification = jest.fn();
});
afterEach(() => {
if (wrapper) {
wrapper.unmount();
wrapper = null;
}
jest.clearAllMocks();
});
test('initially renders succesfully', () => {
wrapper = mountWithContexts(
<NotificationListItem
notification={{
id: 9000,
name: 'Foo',
notification_type: 'slack',
}}
toggleNotification={toggleNotification}
detailUrl="/foo"
canToggleNotifications
/>
);
expect(wrapper.find('NotificationListItem')).toMatchSnapshot();
});
test('handles success click when toggle is on', () => {
wrapper = mountWithContexts(
<NotificationListItem
notification={{
id: 9000,
name: 'Foo',
notification_type: 'slack',
}}
successTurnedOn
toggleNotification={toggleNotification}
detailUrl="/foo"
canToggleNotifications
/>
);
wrapper.find('Switch').first().find('input').simulate('change');
expect(toggleNotification).toHaveBeenCalledWith(9000, true, 'success');
});
test('handles success click when toggle is off', () => {
wrapper = mountWithContexts(
<NotificationListItem
notification={{
id: 9000,
name: 'Foo',
notification_type: 'slack',
}}
successTurnedOn={false}
toggleNotification={toggleNotification}
detailUrl="/foo"
canToggleNotifications
/>
);
wrapper.find('Switch').first().find('input').simulate('change');
expect(toggleNotification).toHaveBeenCalledWith(9000, false, 'success');
});
test('handles error click when toggle is on', () => {
wrapper = mountWithContexts(
<NotificationListItem
notification={{
id: 9000,
name: 'Foo',
notification_type: 'slack',
}}
errorTurnedOn
toggleNotification={toggleNotification}
detailUrl="/foo"
canToggleNotifications
/>
);
wrapper.find('Switch').at(1).find('input').simulate('change');
expect(toggleNotification).toHaveBeenCalledWith(9000, true, 'error');
});
test('handles error click when toggle is off', () => {
wrapper = mountWithContexts(
<NotificationListItem
notification={{
id: 9000,
name: 'Foo',
notification_type: 'slack',
}}
errorTurnedOn={false}
toggleNotification={toggleNotification}
detailUrl="/foo"
canToggleNotifications
/>
);
wrapper.find('Switch').at(1).find('input').simulate('change');
expect(toggleNotification).toHaveBeenCalledWith(9000, false, 'error');
});
});

View File

@@ -0,0 +1,456 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<NotificationListItem canToggleNotifications /> initially renders succesfully 1`] = `
<NotificationListItem
canToggleNotifications={true}
detailUrl="/foo"
errorTurnedOn={false}
i18n={"/i18n/"}
notification={
Object {
"id": 9000,
"name": "Foo",
"notification_type": "slack",
}
}
successTurnedOn={false}
toggleNotification={[MockFunction]}
>
<DataListItem
aria-labelledby="items-list-item-9000"
className=""
isExpanded={false}
key="9000"
>
<li
aria-labelledby="items-list-item-9000"
className="pf-c-data-list__item"
>
<DataListItemRow
className=""
key=".0"
rowid="items-list-item-9000"
>
<div
className="pf-c-data-list__item-row"
>
<DataListItemCells
className=""
dataListCells={
Array [
<ForwardRef>
<ForwardRef
to={
Object {
"pathname": "/foo",
}
}
>
<b
id="items-list-item-9000"
>
Foo
</b>
</ForwardRef>
<ForwardRef
isRead={true}
>
slack
</ForwardRef>
</ForwardRef>,
<ForwardRef
righthalf="true"
>
<ForwardRef
aria-label="Toggle notification success"
id="notification-9000-success-toggle"
isChecked={false}
isDisabled={false}
label="Successful"
onChange={[Function]}
/>
<ForwardRef
aria-label="Toggle notification failure"
id="notification-9000-error-toggle"
isChecked={false}
isDisabled={false}
label="Failure"
onChange={[Function]}
/>
</ForwardRef>,
]
}
key=".0"
rowid="items-list-item-9000"
>
<div
className="pf-c-data-list__item-content"
>
<NotificationListItem__DataListCell
key="name"
>
<StyledComponent
forwardedComponent={
Object {
"$$typeof": Symbol(react.forward_ref),
"attrs": Array [],
"componentStyle": ComponentStyle {
"componentId": "NotificationListItem__DataListCell-j7c411-0",
"isStatic": false,
"lastClassName": "hoXOpW",
"rules": Array [
"display:flex;justify-content:",
[Function],
";padding-bottom:",
[Function],
";@media screen and (min-width:768px){justify-content:",
[Function],
";padding-bottom:0;}",
],
},
"displayName": "NotificationListItem__DataListCell",
"foldedComponentIds": Array [],
"render": [Function],
"styledComponentId": "NotificationListItem__DataListCell-j7c411-0",
"target": [Function],
"toString": [Function],
"warnTooManyClasses": [Function],
"withComponent": [Function],
}
}
forwardedRef={null}
>
<DataListCell
alignRight={false}
className="NotificationListItem__DataListCell-j7c411-0 kIdLtz"
isFilled={true}
isIcon={false}
width={1}
>
<div
className="pf-c-data-list__cell NotificationListItem__DataListCell-j7c411-0 kIdLtz"
>
<Styled(Link)
to={
Object {
"pathname": "/foo",
}
}
>
<StyledComponent
forwardedComponent={
Object {
"$$typeof": Symbol(react.forward_ref),
"attrs": Array [],
"componentStyle": ComponentStyle {
"componentId": "sc-bdVaJa",
"isStatic": true,
"lastClassName": "eBseNd",
"rules": Array [
"margin-right: 1.5em;",
],
},
"displayName": "Styled(Link)",
"foldedComponentIds": Array [],
"render": [Function],
"styledComponentId": "sc-bdVaJa",
"target": [Function],
"toString": [Function],
"warnTooManyClasses": [Function],
"withComponent": [Function],
}
}
forwardedRef={null}
to={
Object {
"pathname": "/foo",
}
}
>
<Link
className="sc-bdVaJa eBseNd"
replace={false}
to={
Object {
"pathname": "/foo",
}
}
>
<a
className="sc-bdVaJa eBseNd"
onClick={[Function]}
>
<b
id="items-list-item-9000"
>
Foo
</b>
</a>
</Link>
</StyledComponent>
</Styled(Link)>
<Styled(Badge)
isRead={true}
>
<StyledComponent
forwardedComponent={
Object {
"$$typeof": Symbol(react.forward_ref),
"attrs": Array [],
"componentStyle": ComponentStyle {
"componentId": "sc-bwzfXH",
"isStatic": true,
"lastClassName": "chTbOZ",
"rules": Array [
"text-transform: capitalize;",
],
},
"displayName": "Styled(Badge)",
"foldedComponentIds": Array [],
"render": [Function],
"styledComponentId": "sc-bwzfXH",
"target": [Function],
"toString": [Function],
"warnTooManyClasses": [Function],
"withComponent": [Function],
}
}
forwardedRef={null}
isRead={true}
>
<Badge
className="sc-bwzfXH chTbOZ"
isRead={true}
>
<span
className="pf-c-badge pf-m-read sc-bwzfXH chTbOZ"
>
slack
</span>
</Badge>
</StyledComponent>
</Styled(Badge)>
</div>
</DataListCell>
</StyledComponent>
</NotificationListItem__DataListCell>
<NotificationListItem__DataListCell
key="toggles"
righthalf="true"
>
<StyledComponent
forwardedComponent={
Object {
"$$typeof": Symbol(react.forward_ref),
"attrs": Array [],
"componentStyle": ComponentStyle {
"componentId": "NotificationListItem__DataListCell-j7c411-0",
"isStatic": false,
"lastClassName": "hoXOpW",
"rules": Array [
"display:flex;justify-content:",
[Function],
";padding-bottom:",
[Function],
";@media screen and (min-width:768px){justify-content:",
[Function],
";padding-bottom:0;}",
],
},
"displayName": "NotificationListItem__DataListCell",
"foldedComponentIds": Array [],
"render": [Function],
"styledComponentId": "NotificationListItem__DataListCell-j7c411-0",
"target": [Function],
"toString": [Function],
"warnTooManyClasses": [Function],
"withComponent": [Function],
}
}
forwardedRef={null}
righthalf="true"
>
<DataListCell
alignRight={false}
className="NotificationListItem__DataListCell-j7c411-0 hoXOpW"
isFilled={true}
isIcon={false}
righthalf="true"
width={1}
>
<div
className="pf-c-data-list__cell NotificationListItem__DataListCell-j7c411-0 hoXOpW"
righthalf="true"
>
<NotificationListItem__Switch
aria-label="Toggle notification success"
id="notification-9000-success-toggle"
isChecked={false}
isDisabled={false}
label="Successful"
onChange={[Function]}
>
<StyledComponent
aria-label="Toggle notification success"
forwardedComponent={
Object {
"$$typeof": Symbol(react.forward_ref),
"attrs": Array [],
"componentStyle": ComponentStyle {
"componentId": "NotificationListItem__Switch-j7c411-1",
"isStatic": true,
"lastClassName": "ceuHGn",
"rules": Array [
"display:flex;flex-wrap:no-wrap;",
],
},
"displayName": "NotificationListItem__Switch",
"foldedComponentIds": Array [],
"render": [Function],
"styledComponentId": "NotificationListItem__Switch-j7c411-1",
"target": [Function],
"toString": [Function],
"warnTooManyClasses": [Function],
"withComponent": [Function],
}
}
forwardedRef={null}
id="notification-9000-success-toggle"
isChecked={false}
isDisabled={false}
label="Successful"
onChange={[Function]}
>
<Switch
aria-label="Toggle notification success"
className="NotificationListItem__Switch-j7c411-1 ceuHGn"
id="notification-9000-success-toggle"
isChecked={false}
isDisabled={false}
label="Successful"
onChange={[Function]}
>
<label
className="pf-c-switch NotificationListItem__Switch-j7c411-1 ceuHGn"
htmlFor="notification-9000-success-toggle"
>
<input
aria-label="Toggle notification success"
checked={false}
className="pf-c-switch__input"
disabled={false}
id="notification-9000-success-toggle"
onChange={[Function]}
type="checkbox"
/>
<span
className="pf-c-switch__toggle"
/>
<span
aria-hidden="true"
className="pf-c-switch__label pf-m-on"
>
Successful
</span>
<span
aria-hidden="true"
className="pf-c-switch__label pf-m-off"
>
Successful
</span>
</label>
</Switch>
</StyledComponent>
</NotificationListItem__Switch>
<NotificationListItem__Switch
aria-label="Toggle notification failure"
id="notification-9000-error-toggle"
isChecked={false}
isDisabled={false}
label="Failure"
onChange={[Function]}
>
<StyledComponent
aria-label="Toggle notification failure"
forwardedComponent={
Object {
"$$typeof": Symbol(react.forward_ref),
"attrs": Array [],
"componentStyle": ComponentStyle {
"componentId": "NotificationListItem__Switch-j7c411-1",
"isStatic": true,
"lastClassName": "ceuHGn",
"rules": Array [
"display:flex;flex-wrap:no-wrap;",
],
},
"displayName": "NotificationListItem__Switch",
"foldedComponentIds": Array [],
"render": [Function],
"styledComponentId": "NotificationListItem__Switch-j7c411-1",
"target": [Function],
"toString": [Function],
"warnTooManyClasses": [Function],
"withComponent": [Function],
}
}
forwardedRef={null}
id="notification-9000-error-toggle"
isChecked={false}
isDisabled={false}
label="Failure"
onChange={[Function]}
>
<Switch
aria-label="Toggle notification failure"
className="NotificationListItem__Switch-j7c411-1 ceuHGn"
id="notification-9000-error-toggle"
isChecked={false}
isDisabled={false}
label="Failure"
onChange={[Function]}
>
<label
className="pf-c-switch NotificationListItem__Switch-j7c411-1 ceuHGn"
htmlFor="notification-9000-error-toggle"
>
<input
aria-label="Toggle notification failure"
checked={false}
className="pf-c-switch__input"
disabled={false}
id="notification-9000-error-toggle"
onChange={[Function]}
type="checkbox"
/>
<span
className="pf-c-switch__toggle"
/>
<span
aria-hidden="true"
className="pf-c-switch__label pf-m-on"
>
Failure
</span>
<span
aria-hidden="true"
className="pf-c-switch__label pf-m-off"
>
Failure
</span>
</label>
</Switch>
</StyledComponent>
</NotificationListItem__Switch>
</div>
</DataListCell>
</StyledComponent>
</NotificationListItem__DataListCell>
</div>
</DataListItemCells>
</div>
</DataListItemRow>
</li>
</DataListItem>
</NotificationListItem>
`;

View File

@@ -0,0 +1 @@
export { default } from './NotificationListItem';

View File

@@ -0,0 +1,46 @@
import React from 'react';
import { mountWithContexts } from '../../../testUtils/enzymeHelpers';
import PageHeaderToolbar from './PageHeaderToolbar';
describe('PageHeaderToolbar', () => {
const pageHelpDropdownSelector = 'Dropdown QuestionCircleIcon';
const pageUserDropdownSelector = 'Dropdown UserIcon';
const onAboutClick = jest.fn();
const onLogoutClick = jest.fn();
test('expected content is rendered on initialization', () => {
const wrapper = mountWithContexts(
<PageHeaderToolbar
onAboutClick={onAboutClick}
onLogoutClick={onLogoutClick}
/>
);
expect(wrapper.find(pageHelpDropdownSelector)).toHaveLength(1);
expect(wrapper.find(pageUserDropdownSelector)).toHaveLength(1);
});
test('dropdowns have expected items and callbacks', () => {
const wrapper = mountWithContexts(
<PageHeaderToolbar
onAboutClick={onAboutClick}
onLogoutClick={onLogoutClick}
/>
);
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

@@ -0,0 +1 @@
export { default } from './PageHeaderToolbar';

View File

@@ -0,0 +1,123 @@
import React from 'react';
import { createMemoryHistory } from 'history';
import { mountWithContexts } from '../../../testUtils/enzymeHelpers';
import { sleep } from '../../../testUtils/testUtils';
import PaginatedDataList from './PaginatedDataList';
const mockData = [
{ id: 1, name: 'one', url: '/org/team/1' },
{ id: 2, name: 'two', url: '/org/team/2' },
{ id: 3, name: 'three', url: '/org/team/3' },
{ id: 4, name: 'four', url: '/org/team/4' },
{ id: 5, name: 'five', url: '/org/team/5' },
];
const qsConfig = {
namespace: 'item',
defaultParams: { page: 1, page_size: 5 },
integerFields: [],
};
describe('<PaginatedDataList />', () => {
afterEach(() => {
jest.restoreAllMocks();
});
test('initially renders succesfully', () => {
mountWithContexts(
<PaginatedDataList
items={mockData}
itemCount={7}
queryParams={{
page: 1,
page_size: 5,
order_by: 'name',
}}
qsConfig={qsConfig}
/>
);
});
test('should navigate when DataListToolbar calls onSort prop', async () => {
const history = createMemoryHistory({
initialEntries: ['/organizations/1/teams'],
});
const wrapper = mountWithContexts(
<PaginatedDataList
items={mockData}
itemCount={7}
queryParams={{
page: 1,
page_size: 5,
order_by: 'name',
}}
qsConfig={qsConfig}
/>, { context: { router: { history } } }
);
const toolbar = wrapper.find('DataListToolbar');
expect(toolbar.prop('sortedColumnKey')).toEqual('name');
expect(toolbar.prop('sortOrder')).toEqual('ascending');
toolbar.prop('onSort')('name', 'descending');
expect(history.location.search).toEqual('?item.order_by=-name');
await sleep(0);
wrapper.update();
expect(toolbar.prop('sortedColumnKey')).toEqual('name');
// TODO: this assertion required updating queryParams prop. Consider
// fixing after #147 is done:
// expect(toolbar.prop('sortOrder')).toEqual('descending');
toolbar.prop('onSort')('name', 'ascending');
expect(history.location.search).toEqual('?item.order_by=name');
});
test('should navigate to page when Pagination calls onSetPage prop', () => {
const history = createMemoryHistory({
initialEntries: ['/organizations/1/teams'],
});
const wrapper = mountWithContexts(
<PaginatedDataList
items={mockData}
itemCount={7}
queryParams={{
page: 1,
page_size: 5,
order_by: 'name',
}}
qsConfig={qsConfig}
/>, { context: { router: { history } } }
);
const pagination = wrapper.find('Pagination');
pagination.prop('onSetPage')(null, 2);
expect(history.location.search).toEqual('?item.page=2');
wrapper.update();
pagination.prop('onSetPage')(null, 1);
expect(history.location.search).toEqual('?item.page=1');
});
test('should navigate to page when Pagination calls onPerPageSelect prop', () => {
const history = createMemoryHistory({
initialEntries: ['/organizations/1/teams'],
});
const wrapper = mountWithContexts(
<PaginatedDataList
items={mockData}
itemCount={7}
queryParams={{
page: 1,
page_size: 5,
order_by: 'name',
}}
qsConfig={qsConfig}
/>, { context: { router: { history } } }
);
const pagination = wrapper.find('Pagination');
pagination.prop('onPerPageSelect')(null, 5);
expect(history.location.search).toEqual('?item.page_size=5');
wrapper.update();
pagination.prop('onPerPageSelect')(null, 25);
expect(history.location.search).toEqual('?item.page_size=25');
});
});

View File

@@ -0,0 +1,25 @@
import React from 'react';
import { mountWithContexts } from '../../../testUtils/enzymeHelpers';
import ToolbarAddButton from './ToolbarAddButton';
describe('<ToolbarAddButton />', () => {
test('should render button', () => {
const onClick = jest.fn();
const wrapper = mountWithContexts(
<ToolbarAddButton onClick={onClick} />
);
const button = wrapper.find('button');
expect(button).toHaveLength(1);
button.simulate('click');
expect(onClick).toHaveBeenCalled();
});
test('should render link', () => {
const wrapper = mountWithContexts(
<ToolbarAddButton linkTo="/foo" />
);
const link = wrapper.find('Link');
expect(link).toHaveLength(1);
expect(link.prop('to')).toBe('/foo');
});
});

View File

@@ -0,0 +1,76 @@
import React from 'react';
import { mountWithContexts } from '../../../testUtils/enzymeHelpers';
import ToolbarDeleteButton from './ToolbarDeleteButton';
const itemA = {
id: 1,
name: 'Foo',
summary_fields: { user_capabilities: { delete: true } },
};
const itemB = {
id: 1,
name: 'Foo',
summary_fields: { user_capabilities: { delete: false } },
};
describe('<ToolbarDeleteButton />', () => {
test('should render button', () => {
const wrapper = mountWithContexts(
<ToolbarDeleteButton
onDelete={() => {}}
itemsToDelete={[]}
/>
);
expect(wrapper.find('button')).toHaveLength(1);
expect(wrapper.find('ToolbarDeleteButton')).toMatchSnapshot();
});
test('should open confirmation modal', () => {
const wrapper = mountWithContexts(
<ToolbarDeleteButton
onDelete={() => {}}
itemsToDelete={[itemA]}
/>
);
wrapper.find('button').simulate('click');
expect(wrapper.find('ToolbarDeleteButton').state('isModalOpen'))
.toBe(true);
wrapper.update();
expect(wrapper.find('Modal')).toHaveLength(1);
});
test('should invoke onDelete prop', () => {
const onDelete = jest.fn();
const wrapper = mountWithContexts(
<ToolbarDeleteButton
onDelete={onDelete}
itemsToDelete={[itemA]}
/>
);
wrapper.find('ToolbarDeleteButton').setState({ isModalOpen: true });
wrapper.find('button.pf-m-danger').simulate('click');
expect(onDelete).toHaveBeenCalled();
expect(wrapper.find('ToolbarDeleteButton').state('isModalOpen')).toBe(false);
});
test('should disable button when no delete permissions', () => {
const wrapper = mountWithContexts(
<ToolbarDeleteButton
onDelete={() => {}}
itemsToDelete={[itemB]}
/>
);
expect(wrapper.find('button[disabled]')).toHaveLength(1);
});
test('should render tooltip', () => {
const wrapper = mountWithContexts(
<ToolbarDeleteButton
onDelete={() => {}}
itemsToDelete={[itemA]}
/>
);
expect(wrapper.find('Tooltip')).toHaveLength(1);
expect(wrapper.find('Tooltip').prop('content')).toEqual('Delete');
});
});

View File

@@ -0,0 +1,202 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<ToolbarDeleteButton /> should render button 1`] = `
<ToolbarDeleteButton
i18n={"/i18n/"}
itemName="item"
itemsToDelete={Array []}
onDelete={[Function]}
>
<Tooltip
appendTo={[Function]}
className={null}
content="Select a row to delete"
enableFlip={true}
entryDelay={500}
exitDelay={500}
maxWidth="18.75rem"
position="top"
trigger="mouseenter focus"
zIndex={9999}
>
<Tippy
animateFill={false}
appendTo={[Function]}
content={
<div
className="pf-c-tooltip"
role="tooltip"
>
<TooltipArrow
className={null}
/>
<TooltipContent
className={null}
>
Select a row to delete
</TooltipContent>
</div>
}
delay={
Array [
500,
500,
]
}
distance={15}
flip={true}
lazy={true}
maxWidth="18.75rem"
onCreate={[Function]}
performance={true}
placement="top"
popperOptions={
Object {
"modifiers": Object {
"hide": Object {
"enabled": true,
},
"preventOverflow": Object {
"enabled": true,
},
},
}
}
theme="pf-tippy"
trigger="mouseenter focus"
zIndex={9999}
>
<div>
<ToolbarDeleteButton__DeleteButton
aria-label="Delete"
isDisabled={true}
onClick={[Function]}
variant="plain"
>
<StyledComponent
aria-label="Delete"
forwardedComponent={
Object {
"$$typeof": Symbol(react.forward_ref),
"attrs": Array [],
"componentStyle": ComponentStyle {
"componentId": "ToolbarDeleteButton__DeleteButton-sc-1e3r0eg-0",
"isStatic": true,
"lastClassName": "bQjfFG",
"rules": Array [
"padding:5px 8px;&:hover{background-color:#d9534f;color:white;}&[disabled]{color:var(--pf-c-button--m-plain--Color);pointer-events:initial;cursor:not-allowed;}",
],
},
"displayName": "ToolbarDeleteButton__DeleteButton",
"foldedComponentIds": Array [],
"render": [Function],
"styledComponentId": "ToolbarDeleteButton__DeleteButton-sc-1e3r0eg-0",
"target": [Function],
"toString": [Function],
"warnTooManyClasses": [Function],
"withComponent": [Function],
}
}
forwardedRef={null}
isDisabled={true}
onClick={[Function]}
variant="plain"
>
<Button
aria-label="Delete"
className="ToolbarDeleteButton__DeleteButton-sc-1e3r0eg-0 bQjfFG"
component="button"
isActive={false}
isBlock={false}
isDisabled={true}
isFocus={false}
isHover={false}
isInline={false}
onClick={[Function]}
type="button"
variant="plain"
>
<button
aria-disabled={null}
aria-label="Delete"
className="pf-c-button pf-m-plain pf-m-disabled ToolbarDeleteButton__DeleteButton-sc-1e3r0eg-0 bQjfFG"
disabled={true}
onClick={[Function]}
tabIndex={null}
type="button"
>
<TrashAltIcon
color="currentColor"
size="sm"
title={null}
>
<svg
aria-hidden={true}
aria-labelledby={null}
fill="currentColor"
height="1em"
role="img"
style={
Object {
"verticalAlign": "-0.125em",
}
}
viewBox="0 0 448 512"
width="1em"
>
<path
d="M32 464a48 48 0 0 0 48 48h288a48 48 0 0 0 48-48V128H32zm272-256a16 16 0 0 1 32 0v224a16 16 0 0 1-32 0zm-96 0a16 16 0 0 1 32 0v224a16 16 0 0 1-32 0zm-96 0a16 16 0 0 1 32 0v224a16 16 0 0 1-32 0zM432 32H312l-9.4-18.7A24 24 0 0 0 281.1 0H166.8a23.72 23.72 0 0 0-21.4 13.3L136 32H16A16 16 0 0 0 0 48v32a16 16 0 0 0 16 16h416a16 16 0 0 0 16-16V48a16 16 0 0 0-16-16z"
transform=""
/>
</svg>
</TrashAltIcon>
</button>
</Button>
</StyledComponent>
</ToolbarDeleteButton__DeleteButton>
</div>
<Portal
containerInfo={
<div>
<div
class="pf-c-tooltip"
role="tooltip"
>
<div
class="pf-c-tooltip__arrow"
/>
<div
class="pf-c-tooltip__content"
>
Select a row to delete
</div>
</div>
</div>
}
>
<div
className="pf-c-tooltip"
role="tooltip"
>
<TooltipArrow
className={null}
>
<div
className="pf-c-tooltip__arrow"
/>
</TooltipArrow>
<TooltipContent
className={null}
>
<div
className="pf-c-tooltip__content"
>
Select a row to delete
</div>
</TooltipContent>
</div>
</Portal>
</Tippy>
</Tooltip>
</ToolbarDeleteButton>
`;

View File

@@ -1,6 +1,4 @@
import PaginatedDataList from './PaginatedDataList';
export default PaginatedDataList;
export { default } from './PaginatedDataList';
export { default as PaginatedDataListItem } from './PaginatedDataListItem';
export { default as ToolbarDeleteButton } from './ToolbarDeleteButton';
export { default as ToolbarAddButton } from './ToolbarAddButton';

View File

@@ -0,0 +1,16 @@
import React from 'react';
import { mountWithContexts } from '../../../testUtils/enzymeHelpers';
import Pagination from './Pagination';
describe('Pagination', () => {
test('renders the expected content', () => {
const wrapper = mountWithContexts(
<Pagination
itemCount={0}
max={9000}
/>
);
expect(wrapper).toHaveLength(1);
});
});

View File

@@ -1,3 +1 @@
import Pagination from './Pagination';
export default Pagination;
export { default } from './Pagination';

View File

@@ -0,0 +1,64 @@
/* eslint-disable react/jsx-pascal-case */
import React from 'react';
import { mount, shallow } from 'enzyme';
import { Router } from 'react-router-dom';
import { createMemoryHistory } from 'history';
import { Tab } from '@patternfly/react-core';
import RoutedTabs, { _RoutedTabs } from './RoutedTabs';
let wrapper;
let history;
const tabs = [
{ name: 'Details', link: '/organizations/19/details', id: 1 },
{ name: 'Access', link: '/organizations/19/access', id: 2 },
{ name: 'Teams', link: '/organizations/19/teams', id: 3 },
{ name: 'Notification', link: '/organizations/19/notification', id: 4 }
];
describe('<RoutedTabs />', () => {
beforeEach(() => {
history = createMemoryHistory({
initialEntries: ['/organizations/19/teams'],
});
});
test('RoutedTabs renders successfully', () => {
wrapper = shallow(
<_RoutedTabs
tabsArray={tabs}
history={history}
/>
);
expect(wrapper.find(Tab)).toHaveLength(4);
});
test('Given a URL the correct tab is active', async () => {
wrapper = mount(
<Router history={history}>
<RoutedTabs
tabsArray={tabs}
/>
</Router>
);
expect(history.location.pathname).toEqual('/organizations/19/teams');
expect(wrapper.find('Tabs').prop('activeKey')).toBe(3);
});
test('should update history when new tab selected', async () => {
wrapper = mount(
<Router history={history}>
<RoutedTabs
tabsArray={tabs}
/>
</Router>
);
wrapper.find('Tabs').prop('onSelect')({}, 2);
wrapper.update();
expect(history.location.pathname).toEqual('/organizations/19/access');
expect(wrapper.find('Tabs').prop('activeKey')).toBe(2);
});
});

View File

@@ -0,0 +1 @@
export { default } from './RoutedTabs';

View File

@@ -0,0 +1,70 @@
import React from 'react';
import { mountWithContexts } from '../../../testUtils/enzymeHelpers';
import Search from './Search';
describe('<Search />', () => {
let search;
afterEach(() => {
if (search) {
search = null;
}
});
test('it triggers the expected callbacks', () => {
const columns = [{ name: 'Name', key: 'name', isSortable: true }];
const searchBtn = 'button[aria-label="Search"]';
const searchTextInput = 'input[aria-label="Search text input"]';
const onSearch = jest.fn();
search = mountWithContexts(
<Search
sortedColumnKey="name"
columns={columns}
onSearch={onSearch}
/>
);
search.find(searchTextInput).instance().value = 'test-321';
search.find(searchTextInput).simulate('change');
search.find(searchBtn).simulate('click');
expect(onSearch).toHaveBeenCalledTimes(1);
expect(onSearch).toBeCalledWith('test-321');
});
test('handleDropdownToggle properly updates state', async () => {
const columns = [{ name: 'Name', key: 'name', isSortable: true }];
const onSearch = jest.fn();
const wrapper = mountWithContexts(
<Search
sortedColumnKey="name"
columns={columns}
onSearch={onSearch}
/>
).find('Search');
expect(wrapper.state('isSearchDropdownOpen')).toEqual(false);
wrapper.instance().handleDropdownToggle(true);
expect(wrapper.state('isSearchDropdownOpen')).toEqual(true);
});
test('handleDropdownSelect properly updates state', async () => {
const columns = [
{ name: 'Name', key: 'name', isSortable: true },
{ name: 'Description', key: 'description', isSortable: true }
];
const onSearch = jest.fn();
const wrapper = mountWithContexts(
<Search
sortedColumnKey="name"
columns={columns}
onSearch={onSearch}
/>
).find('Search');
expect(wrapper.state('searchKey')).toEqual('name');
wrapper.instance().handleDropdownSelect({ target: { innerText: 'Description' } });
expect(wrapper.state('searchKey')).toEqual('description');
});
});

View File

@@ -1,3 +1 @@
import Search from './Search';
export default Search;
export { default } from './Search';

View File

@@ -0,0 +1,63 @@
import React from 'react';
import { mount } from 'enzyme';
import { ChipGroup } from '../Chip';
import SelectedList from './SelectedList';
describe('<SelectedList />', () => {
test('initially renders succesfully', () => {
const mockSelected = [
{
id: 1,
name: 'foo'
}, {
id: 2,
name: 'bar'
}
];
mount(
<SelectedList
label="Selectedeeee"
selected={mockSelected}
showOverflowAfter={5}
onRemove={() => {}}
/>
);
});
test('showOverflow should set showOverflow on ChipGroup', () => {
const wrapper = mount(
<SelectedList
label="Selected"
selected={[]}
showOverflowAfter={5}
onRemove={() => {}}
/>
);
const chipGroup = wrapper.find(ChipGroup);
expect(chipGroup).toHaveLength(1);
expect(chipGroup.prop('showOverflowAfter')).toEqual(5);
});
test('Clicking remove on chip calls onRemove callback prop with correct params', () => {
const onRemove = jest.fn();
const mockSelected = [
{
id: 1,
name: 'foo'
}
];
const wrapper = mount(
<SelectedList
label="Selected"
selected={mockSelected}
showOverflowAfter={3}
onRemove={onRemove}
/>
);
wrapper.find('.pf-c-chip button').first().simulate('click');
expect(onRemove).toBeCalledWith({
id: 1,
name: 'foo'
});
});
});

View File

@@ -1,3 +1 @@
import SelectedList from './SelectedList';
export default SelectedList;
export { default } from './SelectedList';

View File

@@ -0,0 +1,188 @@
import React from 'react';
import { mountWithContexts } from '../../../testUtils/enzymeHelpers';
import Sort from './Sort';
describe('<Sort />', () => {
let sort;
afterEach(() => {
if (sort) {
sort = null;
}
});
test('it triggers the expected callbacks', () => {
const columns = [{ name: 'Name', key: 'name', isSortable: true }];
const sortBtn = 'button[aria-label="Sort"]';
const onSort = jest.fn();
const wrapper = mountWithContexts(
<Sort
sortedColumnKey="name"
sortOrder="ascending"
columns={columns}
onSort={onSort}
/>
).find('Sort');
wrapper.find(sortBtn).simulate('click');
expect(onSort).toHaveBeenCalledTimes(1);
expect(onSort).toBeCalledWith('name', 'descending');
});
test('onSort properly passes back descending when ascending was passed as prop', () => {
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 onSort = jest.fn();
const wrapper = mountWithContexts(
<Sort
sortedColumnKey="foo"
sortOrder="ascending"
columns={multipleColumns}
onSort={onSort}
/>
).find('Sort');
const sortDropdownToggle = wrapper.find('Button');
expect(sortDropdownToggle.length).toBe(1);
sortDropdownToggle.simulate('click');
expect(onSort).toHaveBeenCalledWith('foo', 'descending');
});
test('onSort properly passes back ascending when descending was passed as prop', () => {
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 onSort = jest.fn();
const wrapper = mountWithContexts(
<Sort
sortedColumnKey="foo"
sortOrder="descending"
columns={multipleColumns}
onSort={onSort}
/>
).find('Sort');
const sortDropdownToggle = wrapper.find('Button');
expect(sortDropdownToggle.length).toBe(1);
sortDropdownToggle.simulate('click');
expect(onSort).toHaveBeenCalledWith('foo', 'ascending');
});
test('Changing dropdown correctly passes back new sort key', () => {
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 onSort = jest.fn();
const wrapper = mountWithContexts(
<Sort
sortedColumnKey="foo"
sortOrder="ascending"
columns={multipleColumns}
onSort={onSort}
/>
).find('Sort');
wrapper.instance().handleDropdownSelect({ target: { innerText: 'Bar' } });
expect(onSort).toBeCalledWith('bar', 'ascending');
});
test('Opening dropdown correctly updates state', () => {
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 onSort = jest.fn();
const wrapper = mountWithContexts(
<Sort
sortedColumnKey="foo"
sortOrder="ascending"
columns={multipleColumns}
onSort={onSort}
/>
).find('Sort');
expect(wrapper.state('isSortDropdownOpen')).toEqual(false);
wrapper.instance().handleDropdownToggle(true);
expect(wrapper.state('isSortDropdownOpen')).toEqual(true);
});
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 onSort = jest.fn();
sort = mountWithContexts(
<Sort
sortedColumnKey="id"
sortOrder="descending"
columns={numericColumns}
onSort={onSort}
/>
);
const downNumericIcon = sort.find(downNumericIconSelector);
expect(downNumericIcon.length).toBe(1);
sort = mountWithContexts(
<Sort
sortedColumnKey="id"
sortOrder="ascending"
columns={numericColumns}
onSort={onSort}
/>
);
const upNumericIcon = sort.find(upNumericIconSelector);
expect(upNumericIcon.length).toBe(1);
sort = mountWithContexts(
<Sort
sortedColumnKey="name"
sortOrder="descending"
columns={alphaColumns}
onSort={onSort}
/>
);
const downAlphaIcon = sort.find(downAlphaIconSelector);
expect(downAlphaIcon.length).toBe(1);
sort = mountWithContexts(
<Sort
sortedColumnKey="name"
sortOrder="ascending"
columns={alphaColumns}
onSort={onSort}
/>
);
const upAlphaIcon = sort.find(upAlphaIconSelector);
expect(upAlphaIcon.length).toBe(1);
});
});

View File

@@ -1,3 +1 @@
import Sort from './Sort';
export default Sort;
export { default } from './Sort';

View File

@@ -0,0 +1,11 @@
import React from 'react';
import { mount } from 'enzyme';
import VerticalSeparator from './VerticalSeparator';
describe('VerticalSeparator', () => {
test('renders the expected content', () => {
const wrapper = mount(<VerticalSeparator />);
expect(wrapper).toHaveLength(1);
});
});

View File

@@ -1,3 +1 @@
import VerticalSeparator from './VerticalSeparator';
export default VerticalSeparator;
export { default } from './VerticalSeparator';

View File

@@ -20,29 +20,29 @@ import App from './App';
import { BrandName } from './variables';
import { isAuthenticated } from './util/auth';
import Applications from './pages/Applications';
import Credentials from './pages/Credentials';
import CredentialTypes from './pages/CredentialTypes';
import Dashboard from './pages/Dashboard';
import InstanceGroups from './pages/InstanceGroups';
import Inventories from './pages/Inventories';
import InventoryScripts from './pages/InventoryScripts';
import Jobs from './pages/Jobs';
import Login from './pages/Login';
import ManagementJobs from './pages/ManagementJobs';
import NotificationTemplates from './pages/NotificationTemplates';
import Organizations from './pages/Organizations/Organizations';
import Portal from './pages/Portal';
import Projects from './pages/Projects';
import Schedules from './pages/Schedules';
import AuthSettings from './pages/AuthSettings';
import JobsSettings from './pages/JobsSettings';
import SystemSettings from './pages/SystemSettings';
import UISettings from './pages/UISettings';
import License from './pages/License';
import Teams from './pages/Teams';
import Templates from './pages/Templates/Templates';
import Users from './pages/Users';
import Applications from './screens/Application';
import Credentials from './screens/Credential';
import CredentialTypes from './screens/CredentialType';
import Dashboard from './screens/Dashboard';
import InstanceGroups from './screens/InstanceGroup';
import Inventories from './screens/Inventory';
import InventoryScripts from './screens/InventoryScript';
import { Jobs } from './screens/Job';
import Login from './screens/Login';
import ManagementJobs from './screens/ManagementJob';
import NotificationTemplates from './screens/NotificationTemplate';
import Organizations from './screens/Organization';
import Portal from './screens/Portal';
import Projects from './screens/Project';
import Schedules from './screens/Schedule';
import AuthSettings from './screens/AuthSetting';
import JobsSettings from './screens/JobsSetting';
import SystemSettings from './screens/SystemSetting';
import UISettings from './screens/UISetting';
import License from './screens/License';
import Teams from './screens/Team';
import Templates from './screens/Template';
import Users from './screens/User';
// eslint-disable-next-line import/prefer-default-export
export function main (render) {

17
src/index.test.jsx Normal file
View File

@@ -0,0 +1,17 @@
import React from 'react';
import { mount } from 'enzyme';
import { MemoryRouter } from 'react-router-dom';
import { main } from './index';
const render = template => mount(
<MemoryRouter>
{template}
</MemoryRouter>
);
describe('index.jsx', () => {
test('index.jsx loads without issue', () => {
const wrapper = main(render);
expect(wrapper.find('RootProvider')).toHaveLength(1);
});
});

View File

@@ -1,4 +0,0 @@
import JobDetail from './JobDetail';
export default JobDetail;

View File

@@ -1,4 +0,0 @@
import JobOutput from './JobOutput';
export default JobOutput;

View File

@@ -1,2 +0,0 @@
export { default as Job } from './Job';
export { default } from './Jobs';

View File

@@ -0,0 +1,29 @@
import React from 'react';
import { mountWithContexts } from '../../../testUtils/enzymeHelpers';
import Applications from './Applications';
describe('<Applications />', () => {
let pageWrapper;
let pageSections;
let title;
beforeEach(() => {
pageWrapper = mountWithContexts(<Applications />);
pageSections = pageWrapper.find('PageSection');
title = pageWrapper.find('Title');
});
afterEach(() => {
pageWrapper.unmount();
});
test('initially renders without crashing', () => {
expect(pageWrapper.length).toBe(1);
expect(pageSections.length).toBe(2);
expect(title.length).toBe(1);
expect(title.props().size).toBe('2xl');
pageSections.forEach(section => {
expect(section.props().variant).toBeDefined();
});
});
});

View File

@@ -0,0 +1 @@
export { default } from './Applications';

Some files were not shown because too many files have changed in this diff Show More