mirror of
https://github.com/ansible/awx.git
synced 2026-02-03 02:28:12 -03:30
update content loading and error handling
unwind error handling use auth cookie as source of truth, fetch config only when authenticated
This commit is contained in:
@@ -1,16 +1,33 @@
|
||||
import React from 'react';
|
||||
|
||||
import { mountWithContexts, waitForElement } from './enzymeHelpers';
|
||||
|
||||
import { asyncFlush } from '../jest.setup';
|
||||
|
||||
import App from '../src/App';
|
||||
|
||||
import { RootAPI } from '../src/api';
|
||||
import { ConfigAPI, MeAPI, RootAPI } from '../src/api';
|
||||
|
||||
jest.mock('../src/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
|
||||
@@ -34,7 +51,7 @@ describe('<App />', () => {
|
||||
render={({ routeGroups }) => (
|
||||
routeGroups.map(({ groupId }) => (<div key={groupId} id={groupId} />))
|
||||
)}
|
||||
/>
|
||||
/>,
|
||||
);
|
||||
|
||||
// page components
|
||||
@@ -54,12 +71,8 @@ describe('<App />', () => {
|
||||
});
|
||||
|
||||
test('opening the about modal renders prefetched config data', async (done) => {
|
||||
const ansible_version = '111';
|
||||
const version = '222';
|
||||
|
||||
const config = { ansible_version, version };
|
||||
|
||||
const wrapper = mountWithContexts(<App />, { context: { config } });
|
||||
const wrapper = mountWithContexts(<App />);
|
||||
wrapper.update();
|
||||
|
||||
// open about modal
|
||||
const aboutDropdown = 'Dropdown QuestionCircleIcon';
|
||||
@@ -67,9 +80,11 @@ describe('<App />', () => {
|
||||
const aboutModalContent = 'AboutModalBoxContent';
|
||||
const aboutModalClose = 'button[aria-label="Close Dialog"]';
|
||||
|
||||
expect(wrapper.find(aboutModalContent)).toHaveLength(0);
|
||||
await waitForElement(wrapper, aboutDropdown);
|
||||
wrapper.find(aboutDropdown).simulate('click');
|
||||
wrapper.find(aboutButton).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);
|
||||
@@ -83,24 +98,21 @@ describe('<App />', () => {
|
||||
done();
|
||||
});
|
||||
|
||||
test('onNavToggle sets state.isNavOpen to opposite', () => {
|
||||
test('handleNavToggle sets state.isNavOpen to opposite', () => {
|
||||
const appWrapper = mountWithContexts(<App />).find('App');
|
||||
const { onNavToggle } = appWrapper.instance();
|
||||
|
||||
const { handleNavToggle } = appWrapper.instance();
|
||||
[true, false, true, false, true].forEach(expected => {
|
||||
expect(appWrapper.state().isNavOpen).toBe(expected);
|
||||
onNavToggle();
|
||||
handleNavToggle();
|
||||
});
|
||||
});
|
||||
|
||||
test('onLogout makes expected call to api client', async (done) => {
|
||||
const appWrapper = mountWithContexts(<App />, {
|
||||
context: { network: { handleHttpError: () => {} } }
|
||||
}).find('App');
|
||||
|
||||
appWrapper.instance().onLogout();
|
||||
const appWrapper = mountWithContexts(<App />).find('App');
|
||||
appWrapper.instance().handleLogout();
|
||||
await asyncFlush();
|
||||
expect(RootAPI.logout).toHaveBeenCalledTimes(1);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,20 +2,16 @@
|
||||
|
||||
exports[`mountWithContexts injected ConfigProvider should mount and render with custom Config value 1`] = `
|
||||
<Foo>
|
||||
<Config>
|
||||
<div>
|
||||
Fizz
|
||||
1.1
|
||||
</div>
|
||||
</Config>
|
||||
<div>
|
||||
Fizz
|
||||
1.1
|
||||
</div>
|
||||
</Foo>
|
||||
`;
|
||||
|
||||
exports[`mountWithContexts injected ConfigProvider should mount and render with default values 1`] = `
|
||||
<Foo>
|
||||
<Config>
|
||||
<div />
|
||||
</Config>
|
||||
<div />
|
||||
</Foo>
|
||||
`;
|
||||
|
||||
|
||||
@@ -19,7 +19,6 @@ describe('<Lookup />', () => {
|
||||
getItems={() => { }}
|
||||
columns={mockColumns}
|
||||
sortedColumnKey="name"
|
||||
handleHttpError={() => {}}
|
||||
/>
|
||||
);
|
||||
});
|
||||
@@ -34,7 +33,6 @@ describe('<Lookup />', () => {
|
||||
getItems={() => ({ data: { results: [{ name: 'test instance', id: 1 }] } })}
|
||||
columns={mockColumns}
|
||||
sortedColumnKey="name"
|
||||
handleHttpError={() => {}}
|
||||
/>
|
||||
).find('Lookup');
|
||||
|
||||
@@ -57,7 +55,6 @@ describe('<Lookup />', () => {
|
||||
getItems={() => { }}
|
||||
columns={mockColumns}
|
||||
sortedColumnKey="name"
|
||||
handleHttpError={() => {}}
|
||||
/>
|
||||
).find('Lookup');
|
||||
expect(spy).not.toHaveBeenCalled();
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
import React from 'react';
|
||||
import { mountWithContexts } from '../enzymeHelpers';
|
||||
|
||||
import { _NotifyAndRedirect } from '../../src/components/NotifyAndRedirect';
|
||||
|
||||
describe('<NotifyAndRedirect />', () => {
|
||||
test('initially renders succesfully and calls setRootDialogMessage', () => {
|
||||
const setRootDialogMessage = jest.fn();
|
||||
mountWithContexts(
|
||||
<_NotifyAndRedirect
|
||||
to="foo"
|
||||
setRootDialogMessage={setRootDialogMessage}
|
||||
location={{ pathname: 'foo' }}
|
||||
i18n={{ _: val => val.toString() }}
|
||||
/>
|
||||
);
|
||||
expect(setRootDialogMessage).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -3,12 +3,10 @@
|
||||
* derived from https://lingui.js.org/guides/testing.html
|
||||
*/
|
||||
import React from 'react';
|
||||
import { shape, object, string, arrayOf, func } from 'prop-types';
|
||||
import { shape, object, string, arrayOf } from 'prop-types';
|
||||
import { mount, shallow } from 'enzyme';
|
||||
import { I18nProvider } from '@lingui/react';
|
||||
import { ConfigProvider } from '../src/contexts/Config';
|
||||
import { _NetworkProvider } from '../src/contexts/Network';
|
||||
import { RootDialogProvider } from '../src/contexts/RootDialog';
|
||||
|
||||
const language = 'en-US';
|
||||
const intlProvider = new I18nProvider(
|
||||
@@ -36,8 +34,6 @@ const defaultContexts = {
|
||||
ansible_version: null,
|
||||
custom_virtualenvs: [],
|
||||
version: null,
|
||||
custom_logo: null,
|
||||
custom_login_info: null,
|
||||
toJSON: () => '/config/'
|
||||
},
|
||||
router: {
|
||||
@@ -69,30 +65,19 @@ const defaultContexts = {
|
||||
},
|
||||
toJSON: () => '/router/',
|
||||
},
|
||||
network: {
|
||||
handleHttpError: () => {},
|
||||
},
|
||||
dialog: {}
|
||||
};
|
||||
|
||||
function wrapContexts (node, context) {
|
||||
const { config, network, dialog } = context;
|
||||
const { config } = context;
|
||||
class Wrap extends React.Component {
|
||||
render () {
|
||||
// eslint-disable-next-line react/no-this-in-sfc
|
||||
const { children, ...props } = this.props;
|
||||
const component = React.cloneElement(children, props);
|
||||
return (
|
||||
<RootDialogProvider value={dialog}>
|
||||
<_NetworkProvider value={network}>
|
||||
<ConfigProvider
|
||||
value={config}
|
||||
i18n={defaultContexts.linguiPublisher.i18n}
|
||||
>
|
||||
{component}
|
||||
</ConfigProvider>
|
||||
</_NetworkProvider>
|
||||
</RootDialogProvider>
|
||||
<ConfigProvider value={config}>
|
||||
{component}
|
||||
</ConfigProvider>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -131,8 +116,6 @@ export function mountWithContexts (node, options = {}) {
|
||||
ansible_version: string,
|
||||
custom_virtualenvs: arrayOf(string),
|
||||
version: string,
|
||||
custom_logo: string,
|
||||
custom_login_info: string,
|
||||
}),
|
||||
router: shape({
|
||||
route: shape({
|
||||
@@ -141,36 +124,31 @@ export function mountWithContexts (node, options = {}) {
|
||||
}).isRequired,
|
||||
history: shape({}).isRequired,
|
||||
}),
|
||||
network: shape({
|
||||
handleHttpError: func.isRequired,
|
||||
}),
|
||||
dialog: shape({
|
||||
title: string,
|
||||
setRootDialogMessage: func,
|
||||
clearRootDialogMessage: func,
|
||||
}),
|
||||
...options.childContextTypes
|
||||
};
|
||||
return mount(wrapContexts(node, context), { context, childContextTypes });
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for element to exist.
|
||||
* Wait for element(s) to achieve a desired state.
|
||||
*
|
||||
* @param[wrapper] - A ReactWrapper instance
|
||||
* @param[selector] - The selector of the element to wait for.
|
||||
* @param[selector] - The selector of the element(s) to wait for.
|
||||
* @param[callback] - Callback to poll - by default this checks for a node count of 1.
|
||||
*/
|
||||
export function waitForElement (wrapper, selector) {
|
||||
export function waitForElement (wrapper, selector, callback = el => el.length === 1) {
|
||||
const interval = 100;
|
||||
return new Promise((resolve, reject) => {
|
||||
let attempts = 30;
|
||||
(function pollElement () {
|
||||
wrapper.update();
|
||||
if (wrapper.exists(selector)) {
|
||||
return resolve(wrapper.find(selector));
|
||||
const el = wrapper.find(selector);
|
||||
if (callback(el)) {
|
||||
return resolve(el);
|
||||
}
|
||||
if (--attempts <= 0) {
|
||||
return reject(new Error(`Element not found using ${selector}`));
|
||||
const message = `Expected condition for <${selector}> not met: ${callback.toString()}`;
|
||||
return reject(new Error(message));
|
||||
}
|
||||
return setTimeout(pollElement, interval);
|
||||
}());
|
||||
|
||||
@@ -4,7 +4,6 @@ import { withI18n } from '@lingui/react';
|
||||
import { t } from '@lingui/macro';
|
||||
import { mountWithContexts, waitForElement } from './enzymeHelpers';
|
||||
import { Config } from '../src/contexts/Config';
|
||||
import { withRootDialog } from '../src/contexts/RootDialog';
|
||||
|
||||
describe('mountWithContexts', () => {
|
||||
describe('injected I18nProvider', () => {
|
||||
@@ -109,68 +108,6 @@ describe('mountWithContexts', () => {
|
||||
expect(wrapper.find('Foo')).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe('injected root dialog', () => {
|
||||
it('should mount and render', () => {
|
||||
const Foo = ({ title, setRootDialogMessage }) => (
|
||||
<div>
|
||||
<span>{title}</span>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setRootDialogMessage({ title: 'error' })}
|
||||
>
|
||||
click
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
const Bar = withRootDialog(Foo);
|
||||
const wrapper = mountWithContexts(<Bar />);
|
||||
|
||||
expect(wrapper.find('span').text()).toEqual('');
|
||||
wrapper.find('button').simulate('click');
|
||||
wrapper.update();
|
||||
expect(wrapper.find('span').text()).toEqual('error');
|
||||
});
|
||||
|
||||
it('should mount and render with stubbed value', () => {
|
||||
const dialog = {
|
||||
title: 'this be the title',
|
||||
setRootDialogMessage: jest.fn(),
|
||||
};
|
||||
const Foo = ({ title, setRootDialogMessage }) => (
|
||||
<div>
|
||||
<span>{title}</span>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setRootDialogMessage('error')}
|
||||
>
|
||||
click
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
const Bar = withRootDialog(Foo);
|
||||
const wrapper = mountWithContexts(<Bar />, { context: { dialog } });
|
||||
|
||||
expect(wrapper.find('span').text()).toEqual('this be the title');
|
||||
wrapper.find('button').simulate('click');
|
||||
expect(dialog.setRootDialogMessage).toHaveBeenCalledWith('error');
|
||||
});
|
||||
});
|
||||
|
||||
it('should set props on wrapped component', () => {
|
||||
function TestComponent ({ text }) {
|
||||
return (<div>{text}</div>);
|
||||
}
|
||||
|
||||
const wrapper = mountWithContexts(
|
||||
<TestComponent text="foo" />
|
||||
);
|
||||
expect(wrapper.find('div').text()).toEqual('foo');
|
||||
wrapper.setProps({
|
||||
text: 'bar'
|
||||
});
|
||||
expect(wrapper.find('div').text()).toEqual('bar');
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -184,9 +121,7 @@ class TestAsyncComponent extends Component {
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
setTimeout(() => {
|
||||
this.setState({ displayElement: true });
|
||||
}, 1000);
|
||||
setTimeout(() => this.setState({ displayElement: true }), 500);
|
||||
}
|
||||
|
||||
render () {
|
||||
@@ -211,16 +146,15 @@ describe('waitForElement', () => {
|
||||
});
|
||||
|
||||
it('eventually throws an error for elements that don\'t exist', async (done) => {
|
||||
const selector = '#does-not-exist';
|
||||
const wrapper = mountWithContexts(<div />);
|
||||
|
||||
let error;
|
||||
try {
|
||||
await waitForElement(wrapper, selector);
|
||||
await waitForElement(wrapper, '#does-not-exist');
|
||||
} catch (err) {
|
||||
error = err;
|
||||
} finally {
|
||||
expect(error).toEqual(new Error(`Element not found using ${selector}`));
|
||||
expect(error).toEqual(new Error('Expected condition for <#does-not-exist> not met: el => el.length === 1'));
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,169 +1,215 @@
|
||||
import React from 'react';
|
||||
import { mountWithContexts } from '../enzymeHelpers';
|
||||
import { asyncFlush } from '../../jest.setup';
|
||||
import { mountWithContexts, waitForElement } from '../enzymeHelpers';
|
||||
import AWXLogin from '../../src/pages/Login';
|
||||
import { RootAPI } from '../../src/api';
|
||||
|
||||
jest.mock('../../src/api');
|
||||
|
||||
describe('<Login />', () => {
|
||||
let loginWrapper;
|
||||
let awxLogin;
|
||||
let loginPage;
|
||||
let loginForm;
|
||||
let usernameInput;
|
||||
let passwordInput;
|
||||
let submitButton;
|
||||
let loginHeaderLogo;
|
||||
async function findChildren (wrapper) {
|
||||
const [
|
||||
awxLogin,
|
||||
loginPage,
|
||||
loginForm,
|
||||
usernameInput,
|
||||
passwordInput,
|
||||
submitButton,
|
||||
loginHeaderLogo,
|
||||
] = await Promise.all([
|
||||
waitForElement(wrapper, 'AWXLogin', (el) => el.length === 1),
|
||||
waitForElement(wrapper, 'LoginPage', (el) => el.length === 1),
|
||||
waitForElement(wrapper, 'LoginForm', (el) => el.length === 1),
|
||||
waitForElement(wrapper, 'input#pf-login-username-id', (el) => el.length === 1),
|
||||
waitForElement(wrapper, 'input#pf-login-password-id', (el) => el.length === 1),
|
||||
waitForElement(wrapper, 'Button[type="submit"]', (el) => el.length === 1),
|
||||
waitForElement(wrapper, 'img', (el) => el.length === 1),
|
||||
]);
|
||||
return {
|
||||
awxLogin,
|
||||
loginPage,
|
||||
loginForm,
|
||||
usernameInput,
|
||||
passwordInput,
|
||||
submitButton,
|
||||
loginHeaderLogo,
|
||||
};
|
||||
}
|
||||
|
||||
const mountLogin = () => {
|
||||
loginWrapper = mountWithContexts(<AWXLogin />, { context: { network: {} } });
|
||||
};
|
||||
|
||||
const findChildren = () => {
|
||||
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 = loginPage.find('img');
|
||||
};
|
||||
beforeEach(() => {
|
||||
RootAPI.read.mockResolvedValue({
|
||||
data: {
|
||||
custom_login_info: '',
|
||||
custom_logo: 'images/foo.jpg'
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
loginWrapper.unmount();
|
||||
});
|
||||
|
||||
test('initially renders without crashing', () => {
|
||||
mountLogin();
|
||||
findChildren();
|
||||
expect(loginWrapper.length).toBe(1);
|
||||
expect(loginPage.length).toBe(1);
|
||||
expect(loginForm.length).toBe(1);
|
||||
expect(usernameInput.length).toBe(1);
|
||||
test('initially renders without crashing', async (done) => {
|
||||
const loginWrapper = mountWithContexts(
|
||||
<AWXLogin isAuthenticated={() => false} />
|
||||
);
|
||||
const {
|
||||
awxLogin,
|
||||
usernameInput,
|
||||
passwordInput,
|
||||
submitButton,
|
||||
} = await findChildren(loginWrapper);
|
||||
expect(usernameInput.props().value).toBe('');
|
||||
expect(passwordInput.length).toBe(1);
|
||||
expect(passwordInput.props().value).toBe('');
|
||||
expect(awxLogin.state().isInputValid).toBe(true);
|
||||
expect(submitButton.length).toBe(1);
|
||||
expect(awxLogin.state('validationError')).toBe(false);
|
||||
expect(submitButton.props().isDisabled).toBe(false);
|
||||
expect(loginHeaderLogo.length).toBe(1);
|
||||
done();
|
||||
});
|
||||
|
||||
test('custom logo renders Brand component with correct src and alt', () => {
|
||||
loginWrapper = mountWithContexts(<AWXLogin logo="images/foo.jpg" alt="Foo Application" />);
|
||||
findChildren();
|
||||
expect(loginHeaderLogo.length).toBe(1);
|
||||
expect(loginHeaderLogo.props().src).toBe('data:image/jpeg;images/foo.jpg');
|
||||
expect(loginHeaderLogo.props().alt).toBe('Foo Application');
|
||||
test('custom logo renders Brand component with correct src and alt', async (done) => {
|
||||
const loginWrapper = mountWithContexts(
|
||||
<AWXLogin alt="Foo Application" isAuthenticated={() => false} />
|
||||
);
|
||||
const { loginHeaderLogo } = await findChildren(loginWrapper);
|
||||
const { alt, src } = loginHeaderLogo.props();
|
||||
expect([alt, src]).toEqual(['Foo Application', 'data:image/jpeg;images/foo.jpg']);
|
||||
done();
|
||||
});
|
||||
|
||||
test('default logo renders Brand component with correct src and alt', () => {
|
||||
mountLogin();
|
||||
findChildren();
|
||||
expect(loginHeaderLogo.length).toBe(1);
|
||||
expect(loginHeaderLogo.props().src).toBe('brand-logo.svg');
|
||||
expect(loginHeaderLogo.props().alt).toBe('AWX');
|
||||
test('default logo renders Brand component with correct src and alt', async (done) => {
|
||||
RootAPI.read.mockResolvedValue({ data: {} });
|
||||
const loginWrapper = mountWithContexts(
|
||||
<AWXLogin isAuthenticated={() => false} />
|
||||
);
|
||||
const { loginHeaderLogo } = await findChildren(loginWrapper);
|
||||
const { alt, src } = loginHeaderLogo.props();
|
||||
expect([alt, src]).toEqual(['AWX', 'brand-logo.svg']);
|
||||
done();
|
||||
});
|
||||
|
||||
test('state maps to un/pw input value props', () => {
|
||||
mountLogin();
|
||||
findChildren();
|
||||
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('default logo renders on data initialization error', async (done) => {
|
||||
RootAPI.read.mockRejectedValueOnce({ response: { status: 500 } });
|
||||
const loginWrapper = mountWithContexts(
|
||||
<AWXLogin isAuthenticated={() => false} />
|
||||
);
|
||||
const { loginHeaderLogo } = await findChildren(loginWrapper);
|
||||
const { alt, src } = loginHeaderLogo.props();
|
||||
expect([alt, src]).toEqual(['AWX', 'brand-logo.svg']);
|
||||
done();
|
||||
});
|
||||
|
||||
test('updating un/pw clears out error', () => {
|
||||
mountLogin();
|
||||
findChildren();
|
||||
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(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);
|
||||
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(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('state maps to un/pw input value props', async (done) => {
|
||||
const loginWrapper = mountWithContexts(
|
||||
<AWXLogin isAuthenticated={() => false} />
|
||||
);
|
||||
const { usernameInput, passwordInput } = await findChildren(loginWrapper);
|
||||
usernameInput.props().onChange({ currentTarget: { value: 'un' } });
|
||||
passwordInput.props().onChange({ currentTarget: { value: 'pw' } });
|
||||
await waitForElement(loginWrapper, 'AWXLogin', (el) => el.state('username') === 'un');
|
||||
await waitForElement(loginWrapper, 'AWXLogin', (el) => el.state('password') === 'pw');
|
||||
done();
|
||||
});
|
||||
|
||||
test('login API not called when loading', () => {
|
||||
mountLogin();
|
||||
findChildren();
|
||||
expect(awxLogin.state().isLoading).toBe(false);
|
||||
awxLogin.setState({ isLoading: true });
|
||||
test('handles input validation errors and clears on input value change', async (done) => {
|
||||
const formError = '.pf-c-form__helper-text.pf-m-error';
|
||||
const loginWrapper = mountWithContexts(
|
||||
<AWXLogin isAuthenticated={() => false} />
|
||||
);
|
||||
const {
|
||||
usernameInput,
|
||||
passwordInput,
|
||||
submitButton
|
||||
} = await findChildren(loginWrapper);
|
||||
|
||||
RootAPI.login.mockRejectedValueOnce({ response: { status: 401 } });
|
||||
usernameInput.props().onChange({ currentTarget: { value: 'invalid' } });
|
||||
passwordInput.props().onChange({ currentTarget: { value: 'invalid' } });
|
||||
submitButton.simulate('click');
|
||||
await waitForElement(loginWrapper, 'AWXLogin', (el) => el.state('username') === 'invalid');
|
||||
await waitForElement(loginWrapper, 'AWXLogin', (el) => el.state('password') === 'invalid');
|
||||
await waitForElement(loginWrapper, 'AWXLogin', (el) => el.state('validationError') === true);
|
||||
await waitForElement(loginWrapper, formError, (el) => el.length === 1);
|
||||
|
||||
usernameInput.props().onChange({ currentTarget: { value: 'dsarif' } });
|
||||
passwordInput.props().onChange({ currentTarget: { value: 'freneticpny' } });
|
||||
await waitForElement(loginWrapper, 'AWXLogin', (el) => el.state('username') === 'dsarif');
|
||||
await waitForElement(loginWrapper, 'AWXLogin', (el) => el.state('password') === 'freneticpny');
|
||||
await waitForElement(loginWrapper, 'AWXLogin', (el) => el.state('validationError') === false);
|
||||
await waitForElement(loginWrapper, formError, (el) => el.length === 0);
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
test('handles other errors and clears on resubmit', async (done) => {
|
||||
const loginWrapper = mountWithContexts(
|
||||
<AWXLogin isAuthenticated={() => false} />
|
||||
);
|
||||
const {
|
||||
usernameInput,
|
||||
passwordInput,
|
||||
submitButton
|
||||
} = await findChildren(loginWrapper);
|
||||
|
||||
RootAPI.login.mockRejectedValueOnce({ response: { status: 500 } });
|
||||
submitButton.simulate('click');
|
||||
await waitForElement(loginWrapper, 'AWXLogin', (el) => el.state('authenticationError') === true);
|
||||
|
||||
usernameInput.props().onChange({ currentTarget: { value: 'sgrimes' } });
|
||||
passwordInput.props().onChange({ currentTarget: { value: 'ovid' } });
|
||||
await waitForElement(loginWrapper, 'AWXLogin', (el) => el.state('username') === 'sgrimes');
|
||||
await waitForElement(loginWrapper, 'AWXLogin', (el) => el.state('password') === 'ovid');
|
||||
await waitForElement(loginWrapper, 'AWXLogin', (el) => el.state('authenticationError') === true);
|
||||
|
||||
submitButton.simulate('click');
|
||||
await waitForElement(loginWrapper, 'AWXLogin', (el) => el.state('authenticationError') === false);
|
||||
done();
|
||||
});
|
||||
|
||||
test('no login requests are made when already authenticating', async (done) => {
|
||||
const loginWrapper = mountWithContexts(
|
||||
<AWXLogin isAuthenticated={() => false} />
|
||||
);
|
||||
const { awxLogin, submitButton } = await findChildren(loginWrapper);
|
||||
|
||||
awxLogin.setState({ isAuthenticating: true });
|
||||
submitButton.simulate('click');
|
||||
submitButton.simulate('click');
|
||||
expect(RootAPI.login).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
test('submit calls login API successfully', async () => {
|
||||
RootAPI.login = jest.fn().mockImplementation(() => Promise.resolve({}));
|
||||
mountLogin();
|
||||
findChildren();
|
||||
expect(awxLogin.state().isLoading).toBe(false);
|
||||
awxLogin.setState({ username: 'unamee', password: 'pwordd' });
|
||||
awxLogin.setState({ isAuthenticating: false });
|
||||
submitButton.simulate('click');
|
||||
submitButton.simulate('click');
|
||||
expect(RootAPI.login).toHaveBeenCalledTimes(1);
|
||||
expect(RootAPI.login).toHaveBeenCalledWith('unamee', 'pwordd');
|
||||
expect(awxLogin.state().isLoading).toBe(true);
|
||||
await asyncFlush();
|
||||
expect(awxLogin.state().isLoading).toBe(false);
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
test('submit calls login API and handles 401 error', async () => {
|
||||
RootAPI.login = jest.fn().mockImplementation(() => {
|
||||
const err = new Error('401 error');
|
||||
err.response = { status: 401, message: 'problem' };
|
||||
return Promise.reject(err);
|
||||
});
|
||||
mountLogin();
|
||||
findChildren();
|
||||
expect(awxLogin.state().isLoading).toBe(false);
|
||||
expect(awxLogin.state().isInputValid).toBe(true);
|
||||
awxLogin.setState({ username: 'unamee', password: 'pwordd' });
|
||||
test('submit calls api.login successfully', async (done) => {
|
||||
const loginWrapper = mountWithContexts(
|
||||
<AWXLogin isAuthenticated={() => false} />
|
||||
);
|
||||
const {
|
||||
usernameInput,
|
||||
passwordInput,
|
||||
submitButton,
|
||||
} = await findChildren(loginWrapper);
|
||||
|
||||
usernameInput.props().onChange({ currentTarget: { value: 'gthorpe' } });
|
||||
passwordInput.props().onChange({ currentTarget: { value: 'hydro' } });
|
||||
submitButton.simulate('click');
|
||||
await waitForElement(loginWrapper, 'AWXLogin', (el) => el.state('isAuthenticating') === true);
|
||||
await waitForElement(loginWrapper, 'AWXLogin', (el) => el.state('isAuthenticating') === false);
|
||||
expect(RootAPI.login).toHaveBeenCalledTimes(1);
|
||||
expect(RootAPI.login).toHaveBeenCalledWith('unamee', 'pwordd');
|
||||
expect(awxLogin.state().isLoading).toBe(true);
|
||||
await asyncFlush();
|
||||
expect(awxLogin.state().isInputValid).toBe(false);
|
||||
expect(awxLogin.state().isLoading).toBe(false);
|
||||
expect(RootAPI.login).toHaveBeenCalledWith('gthorpe', 'hydro');
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
test('submit calls login API and handles non-401 error', async () => {
|
||||
RootAPI.login = jest.fn().mockImplementation(() => {
|
||||
const err = new Error('500 error');
|
||||
err.response = { status: 500, message: 'problem' };
|
||||
return Promise.reject(err);
|
||||
});
|
||||
mountLogin();
|
||||
findChildren();
|
||||
expect(awxLogin.state().isLoading).toBe(false);
|
||||
awxLogin.setState({ username: 'unamee', password: 'pwordd' });
|
||||
submitButton.simulate('click');
|
||||
expect(RootAPI.login).toHaveBeenCalledTimes(1);
|
||||
expect(RootAPI.login).toHaveBeenCalledWith('unamee', 'pwordd');
|
||||
expect(awxLogin.state().isLoading).toBe(true);
|
||||
await asyncFlush();
|
||||
expect(awxLogin.state().isLoading).toBe(false);
|
||||
});
|
||||
|
||||
test('render Redirect to / when already authenticated', () => {
|
||||
mountLogin();
|
||||
findChildren();
|
||||
awxLogin.setState({ isAuthenticated: true });
|
||||
const redirectElem = loginWrapper.find('Redirect');
|
||||
expect(redirectElem.length).toBe(1);
|
||||
expect(redirectElem.props().to).toBe('/');
|
||||
test('render Redirect to / when already authenticated', async (done) => {
|
||||
const loginWrapper = mountWithContexts(
|
||||
<AWXLogin isAuthenticated={() => true} />
|
||||
);
|
||||
await waitForElement(loginWrapper, 'Redirect', (el) => el.length === 1);
|
||||
await waitForElement(loginWrapper, 'Redirect', (el) => el.props().to === '/');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,6 +2,8 @@ import React from 'react';
|
||||
import { mountWithContexts } from '../../enzymeHelpers';
|
||||
import Organizations from '../../../src/pages/Organizations/Organizations';
|
||||
|
||||
jest.mock('../../../src/api');
|
||||
|
||||
describe('<Organizations />', () => {
|
||||
test('initially renders succesfully', () => {
|
||||
mountWithContexts(
|
||||
|
||||
@@ -1,63 +1,232 @@
|
||||
import React from 'react';
|
||||
import { createMemoryHistory } from 'history';
|
||||
import { mountWithContexts } from '../../../../enzymeHelpers';
|
||||
import { sleep } from '../../../../testUtils';
|
||||
import { mountWithContexts, waitForElement } from '../../../../enzymeHelpers';
|
||||
import Organization from '../../../../../src/pages/Organizations/screens/Organization/Organization';
|
||||
import { OrganizationsAPI } from '../../../../../src/api';
|
||||
|
||||
jest.mock('../../../../../src/api');
|
||||
|
||||
describe.only('<Organization />', () => {
|
||||
const me = {
|
||||
is_super_user: true,
|
||||
is_system_auditor: false
|
||||
};
|
||||
const mockMe = {
|
||||
is_super_user: true,
|
||||
is_system_auditor: false
|
||||
};
|
||||
|
||||
const mockNoResults = {
|
||||
count: 0,
|
||||
next: null,
|
||||
previous: null,
|
||||
data: { results: [] }
|
||||
};
|
||||
|
||||
const mockDetails = {
|
||||
data: {
|
||||
id: 1,
|
||||
type: 'organization',
|
||||
url: '/api/v2/organizations/1/',
|
||||
related: {
|
||||
notification_templates: '/api/v2/organizations/1/notification_templates/',
|
||||
notification_templates_any: '/api/v2/organizations/1/notification_templates_any/',
|
||||
notification_templates_success: '/api/v2/organizations/1/notification_templates_success/',
|
||||
notification_templates_error: '/api/v2/organizations/1/notification_templates_error/',
|
||||
object_roles: '/api/v2/organizations/1/object_roles/',
|
||||
access_list: '/api/v2/organizations/1/access_list/',
|
||||
instance_groups: '/api/v2/organizations/1/instance_groups/'
|
||||
},
|
||||
summary_fields: {
|
||||
created_by: {
|
||||
id: 1,
|
||||
username: 'admin',
|
||||
first_name: 'Super',
|
||||
last_name: 'User'
|
||||
},
|
||||
modified_by: {
|
||||
id: 1,
|
||||
username: 'admin',
|
||||
first_name: 'Super',
|
||||
last_name: 'User'
|
||||
},
|
||||
object_roles: {
|
||||
admin_role: {
|
||||
description: 'Can manage all aspects of the organization',
|
||||
name: 'Admin',
|
||||
id: 42
|
||||
},
|
||||
notification_admin_role: {
|
||||
description: 'Can manage all notifications of the organization',
|
||||
name: 'Notification Admin',
|
||||
id: 1683
|
||||
},
|
||||
auditor_role: {
|
||||
description: 'Can view all aspects of the organization',
|
||||
name: 'Auditor',
|
||||
id: 41
|
||||
},
|
||||
},
|
||||
user_capabilities: {
|
||||
edit: true,
|
||||
delete: true
|
||||
},
|
||||
related_field_counts: {
|
||||
users: 51,
|
||||
admins: 19,
|
||||
inventories: 23,
|
||||
teams: 12,
|
||||
projects: 33,
|
||||
job_templates: 30
|
||||
}
|
||||
},
|
||||
created: '2015-07-07T17:21:26.429745Z',
|
||||
modified: '2017-09-05T19:23:15.418808Z',
|
||||
name: 'Sarif Industries',
|
||||
description: '',
|
||||
max_hosts: 0,
|
||||
custom_virtualenv: null
|
||||
}
|
||||
};
|
||||
|
||||
const adminOrganization = {
|
||||
id: 1,
|
||||
type: 'organization',
|
||||
url: '/api/v2/organizations/1/',
|
||||
related: {
|
||||
instance_groups: '/api/v2/organizations/1/instance_groups/',
|
||||
object_roles: '/api/v2/organizations/1/object_roles/',
|
||||
access_list: '/api/v2/organizations/1/access_list/',
|
||||
},
|
||||
summary_fields: {
|
||||
created_by: {
|
||||
id: 1,
|
||||
username: 'admin',
|
||||
first_name: 'Super',
|
||||
last_name: 'User'
|
||||
},
|
||||
modified_by: {
|
||||
id: 1,
|
||||
username: 'admin',
|
||||
first_name: 'Super',
|
||||
last_name: 'User'
|
||||
},
|
||||
},
|
||||
created: '2015-07-07T17:21:26.429745Z',
|
||||
modified: '2017-09-05T19:23:15.418808Z',
|
||||
name: 'Sarif Industries',
|
||||
description: '',
|
||||
max_hosts: 0,
|
||||
custom_virtualenv: null
|
||||
};
|
||||
|
||||
const auditorOrganization = {
|
||||
id: 2,
|
||||
type: 'organization',
|
||||
url: '/api/v2/organizations/2/',
|
||||
related: {
|
||||
instance_groups: '/api/v2/organizations/2/instance_groups/',
|
||||
object_roles: '/api/v2/organizations/2/object_roles/',
|
||||
access_list: '/api/v2/organizations/2/access_list/',
|
||||
},
|
||||
summary_fields: {
|
||||
created_by: {
|
||||
id: 2,
|
||||
username: 'admin',
|
||||
first_name: 'Super',
|
||||
last_name: 'User'
|
||||
},
|
||||
modified_by: {
|
||||
id: 2,
|
||||
username: 'admin',
|
||||
first_name: 'Super',
|
||||
last_name: 'User'
|
||||
},
|
||||
},
|
||||
created: '2015-07-07T17:21:26.429745Z',
|
||||
modified: '2017-09-05T19:23:15.418808Z',
|
||||
name: 'Autobots',
|
||||
description: '',
|
||||
max_hosts: 0,
|
||||
custom_virtualenv: null
|
||||
};
|
||||
|
||||
const notificationAdminOrganization = {
|
||||
id: 3,
|
||||
type: 'organization',
|
||||
url: '/api/v2/organizations/3/',
|
||||
related: {
|
||||
instance_groups: '/api/v2/organizations/3/instance_groups/',
|
||||
object_roles: '/api/v2/organizations/3/object_roles/',
|
||||
access_list: '/api/v2/organizations/3/access_list/',
|
||||
},
|
||||
summary_fields: {
|
||||
created_by: {
|
||||
id: 1,
|
||||
username: 'admin',
|
||||
first_name: 'Super',
|
||||
last_name: 'User'
|
||||
},
|
||||
modified_by: {
|
||||
id: 1,
|
||||
username: 'admin',
|
||||
first_name: 'Super',
|
||||
last_name: 'User'
|
||||
},
|
||||
},
|
||||
created: '2015-07-07T17:21:26.429745Z',
|
||||
modified: '2017-09-05T19:23:15.418808Z',
|
||||
name: 'Decepticons',
|
||||
description: '',
|
||||
max_hosts: 0,
|
||||
custom_virtualenv: null
|
||||
};
|
||||
|
||||
const allOrganizations = [
|
||||
adminOrganization,
|
||||
auditorOrganization,
|
||||
notificationAdminOrganization
|
||||
];
|
||||
|
||||
async function getOrganizations (params) {
|
||||
let results = allOrganizations;
|
||||
if (params && params.role_level) {
|
||||
if (params.role_level === 'admin_role') {
|
||||
results = [adminOrganization];
|
||||
}
|
||||
if (params.role_level === 'auditor_role') {
|
||||
results = [auditorOrganization];
|
||||
}
|
||||
if (params.role_level === 'notification_admin_role') {
|
||||
results = [notificationAdminOrganization];
|
||||
}
|
||||
}
|
||||
return {
|
||||
count: results.length,
|
||||
next: null,
|
||||
previous: null,
|
||||
data: { results }
|
||||
};
|
||||
}
|
||||
|
||||
describe.only('<Organization />', () => {
|
||||
test('initially renders succesfully', () => {
|
||||
mountWithContexts(<Organization me={me} />);
|
||||
OrganizationsAPI.readDetail.mockResolvedValue(mockDetails);
|
||||
OrganizationsAPI.read.mockImplementation(getOrganizations);
|
||||
mountWithContexts(<Organization setBreadcrumb={() => {}} me={mockMe} />);
|
||||
});
|
||||
|
||||
test('notifications tab shown/hidden based on permissions', async () => {
|
||||
OrganizationsAPI.readDetail.mockResolvedValue({
|
||||
data: {
|
||||
id: 1,
|
||||
name: 'foo'
|
||||
}
|
||||
});
|
||||
OrganizationsAPI.read.mockResolvedValue({
|
||||
data: {
|
||||
results: []
|
||||
}
|
||||
});
|
||||
const history = createMemoryHistory({
|
||||
initialEntries: ['/organizations/1/details'],
|
||||
});
|
||||
const match = { path: '/organizations/:id', url: '/organizations/1' };
|
||||
const wrapper = mountWithContexts(
|
||||
<Organization
|
||||
me={me}
|
||||
setBreadcrumb={() => {}}
|
||||
/>,
|
||||
{
|
||||
context: {
|
||||
router: {
|
||||
history,
|
||||
route: {
|
||||
location: history.location,
|
||||
match
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
await sleep(0);
|
||||
wrapper.update();
|
||||
expect(wrapper.find('.pf-c-tabs__item').length).toBe(3);
|
||||
expect(wrapper.find('.pf-c-tabs__button[children="Notifications"]').length).toBe(0);
|
||||
wrapper.find('Organization').setState({
|
||||
isNotifAdmin: true
|
||||
});
|
||||
expect(wrapper.find('.pf-c-tabs__item').length).toBe(4);
|
||||
expect(wrapper.find('button.pf-c-tabs__button[children="Notifications"]').length).toBe(1);
|
||||
test('notifications tab shown for admins', async (done) => {
|
||||
OrganizationsAPI.readDetail.mockResolvedValue(mockDetails);
|
||||
OrganizationsAPI.read.mockImplementation(getOrganizations);
|
||||
|
||||
const wrapper = mountWithContexts(<Organization setBreadcrumb={() => {}} me={mockMe} />);
|
||||
const tabs = await waitForElement(wrapper, '.pf-c-tabs__item', el => el.length === 4);
|
||||
expect(tabs.last().text()).toEqual('Notifications');
|
||||
done();
|
||||
});
|
||||
|
||||
test('notifications tab hidden with reduced permissions', async (done) => {
|
||||
OrganizationsAPI.readDetail.mockResolvedValue(mockDetails);
|
||||
OrganizationsAPI.read.mockResolvedValue(mockNoResults);
|
||||
|
||||
const wrapper = mountWithContexts(<Organization setBreadcrumb={() => {}} me={mockMe} />);
|
||||
const tabs = await waitForElement(wrapper, '.pf-c-tabs__item', el => el.length === 3);
|
||||
tabs.forEach(tab => expect(tab.text()).not.toEqual('Notifications'));
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
import React from 'react';
|
||||
import { mountWithContexts } from '../../../../enzymeHelpers';
|
||||
import { mountWithContexts, waitForElement } from '../../../../enzymeHelpers';
|
||||
import OrganizationAccess from '../../../../../src/pages/Organizations/screens/Organization/OrganizationAccess';
|
||||
import { sleep } from '../../../../testUtils';
|
||||
|
||||
import { OrganizationsAPI, TeamsAPI, UsersAPI } from '../../../../../src/api';
|
||||
|
||||
jest.mock('../../../../../src/api');
|
||||
|
||||
describe('<OrganizationAccess />', () => {
|
||||
const network = {};
|
||||
const organization = {
|
||||
id: 1,
|
||||
name: 'Default',
|
||||
@@ -64,7 +62,9 @@ describe('<OrganizationAccess />', () => {
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
OrganizationsAPI.readAccessList.mockReturnValue({ data });
|
||||
OrganizationsAPI.readAccessList.mockResolvedValue({ data });
|
||||
TeamsAPI.disassociateRole.mockResolvedValue({});
|
||||
UsersAPI.disassociateRole.mockResolvedValue({});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -72,31 +72,21 @@ describe('<OrganizationAccess />', () => {
|
||||
});
|
||||
|
||||
test('initially renders succesfully', () => {
|
||||
const wrapper = mountWithContexts(
|
||||
<OrganizationAccess organization={organization} />,
|
||||
{ context: { network } }
|
||||
);
|
||||
const wrapper = mountWithContexts(<OrganizationAccess organization={organization} />);
|
||||
expect(wrapper.find('OrganizationAccess')).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should fetch and display access records on mount', async () => {
|
||||
const wrapper = mountWithContexts(
|
||||
<OrganizationAccess organization={organization} />,
|
||||
{ context: { network } }
|
||||
);
|
||||
await sleep(0);
|
||||
wrapper.update();
|
||||
expect(OrganizationsAPI.readAccessList).toHaveBeenCalled();
|
||||
expect(wrapper.find('OrganizationAccess').state('isInitialized')).toBe(true);
|
||||
test('should fetch and display access records on mount', async (done) => {
|
||||
const wrapper = mountWithContexts(<OrganizationAccess organization={organization} />);
|
||||
await waitForElement(wrapper, 'OrganizationAccessItem', el => el.length === 2);
|
||||
expect(wrapper.find('PaginatedDataList').prop('items')).toEqual(data.results);
|
||||
expect(wrapper.find('OrganizationAccessItem')).toHaveLength(2);
|
||||
expect(wrapper.find('OrganizationAccess').state('contentLoading')).toBe(false);
|
||||
expect(wrapper.find('OrganizationAccess').state('contentError')).toBe(false);
|
||||
done();
|
||||
});
|
||||
|
||||
test('should open confirmation dialog when deleting role', async () => {
|
||||
const wrapper = mountWithContexts(
|
||||
<OrganizationAccess organization={organization} />,
|
||||
{ context: { network } }
|
||||
);
|
||||
test('should open confirmation dialog when deleting role', async (done) => {
|
||||
const wrapper = mountWithContexts(<OrganizationAccess organization={organization} />);
|
||||
await sleep(0);
|
||||
wrapper.update();
|
||||
|
||||
@@ -105,18 +95,16 @@ describe('<OrganizationAccess />', () => {
|
||||
wrapper.update();
|
||||
|
||||
const component = wrapper.find('OrganizationAccess');
|
||||
expect(component.state('roleToDelete'))
|
||||
expect(component.state('deletionRole'))
|
||||
.toEqual(data.results[0].summary_fields.direct_access[0].role);
|
||||
expect(component.state('roleToDeleteAccessRecord'))
|
||||
expect(component.state('deletionRecord'))
|
||||
.toEqual(data.results[0]);
|
||||
expect(component.find('DeleteRoleConfirmationModal')).toHaveLength(1);
|
||||
done();
|
||||
});
|
||||
|
||||
it('should close dialog when cancel button clicked', async () => {
|
||||
const wrapper = mountWithContexts(
|
||||
<OrganizationAccess organization={organization} />,
|
||||
{ context: { network } }
|
||||
);
|
||||
it('should close dialog when cancel button clicked', async (done) => {
|
||||
const wrapper = mountWithContexts(<OrganizationAccess organization={organization} />);
|
||||
await sleep(0);
|
||||
wrapper.update();
|
||||
const button = wrapper.find('ChipButton').at(0);
|
||||
@@ -125,55 +113,50 @@ describe('<OrganizationAccess />', () => {
|
||||
|
||||
wrapper.find('DeleteRoleConfirmationModal').prop('onCancel')();
|
||||
const component = wrapper.find('OrganizationAccess');
|
||||
expect(component.state('roleToDelete')).toBeNull();
|
||||
expect(component.state('roleToDeleteAccessRecord')).toBeNull();
|
||||
expect(component.state('deletionRole')).toBeNull();
|
||||
expect(component.state('deletionRecord')).toBeNull();
|
||||
expect(TeamsAPI.disassociateRole).not.toHaveBeenCalled();
|
||||
expect(UsersAPI.disassociateRole).not.toHaveBeenCalled();
|
||||
done();
|
||||
});
|
||||
|
||||
it('should delete user role', async () => {
|
||||
const wrapper = mountWithContexts(
|
||||
<OrganizationAccess organization={organization} />,
|
||||
{ context: { network } }
|
||||
);
|
||||
it('should delete user role', async (done) => {
|
||||
const wrapper = mountWithContexts(<OrganizationAccess organization={organization} />);
|
||||
const button = await waitForElement(wrapper, 'ChipButton', el => el.length === 2);
|
||||
button.at(0).prop('onClick')();
|
||||
|
||||
const confirmation = await waitForElement(wrapper, 'DeleteRoleConfirmationModal');
|
||||
confirmation.prop('onConfirm')();
|
||||
await waitForElement(wrapper, 'DeleteRoleConfirmationModal', el => el.length === 0);
|
||||
|
||||
await sleep(0);
|
||||
wrapper.update();
|
||||
const button = wrapper.find('ChipButton').at(0);
|
||||
button.prop('onClick')();
|
||||
wrapper.update();
|
||||
|
||||
wrapper.find('DeleteRoleConfirmationModal').prop('onConfirm')();
|
||||
await sleep(0);
|
||||
wrapper.update();
|
||||
|
||||
const component = wrapper.find('OrganizationAccess');
|
||||
expect(component.state('roleToDelete')).toBeNull();
|
||||
expect(component.state('roleToDeleteAccessRecord')).toBeNull();
|
||||
expect(component.state('deletionRole')).toBeNull();
|
||||
expect(component.state('deletionRecord')).toBeNull();
|
||||
expect(TeamsAPI.disassociateRole).not.toHaveBeenCalled();
|
||||
expect(UsersAPI.disassociateRole).toHaveBeenCalledWith(1, 1);
|
||||
expect(OrganizationsAPI.readAccessList).toHaveBeenCalledTimes(2);
|
||||
done();
|
||||
});
|
||||
|
||||
it('should delete team role', async () => {
|
||||
const wrapper = mountWithContexts(
|
||||
<OrganizationAccess organization={organization} />,
|
||||
{ context: { network } }
|
||||
);
|
||||
it('should delete team role', async (done) => {
|
||||
const wrapper = mountWithContexts(<OrganizationAccess organization={organization} />);
|
||||
const button = await waitForElement(wrapper, 'ChipButton', el => el.length === 2);
|
||||
button.at(1).prop('onClick')();
|
||||
|
||||
const confirmation = await waitForElement(wrapper, 'DeleteRoleConfirmationModal');
|
||||
confirmation.prop('onConfirm')();
|
||||
await waitForElement(wrapper, 'DeleteRoleConfirmationModal', el => el.length === 0);
|
||||
|
||||
await sleep(0);
|
||||
wrapper.update();
|
||||
const button = wrapper.find('ChipButton').at(1);
|
||||
button.prop('onClick')();
|
||||
wrapper.update();
|
||||
|
||||
wrapper.find('DeleteRoleConfirmationModal').prop('onConfirm')();
|
||||
await sleep(0);
|
||||
wrapper.update();
|
||||
|
||||
const component = wrapper.find('OrganizationAccess');
|
||||
expect(component.state('roleToDelete')).toBeNull();
|
||||
expect(component.state('roleToDeleteAccessRecord')).toBeNull();
|
||||
expect(component.state('deletionRole')).toBeNull();
|
||||
expect(component.state('deletionRecord')).toBeNull();
|
||||
expect(TeamsAPI.disassociateRole).toHaveBeenCalledWith(5, 3);
|
||||
expect(UsersAPI.disassociateRole).not.toHaveBeenCalled();
|
||||
expect(OrganizationsAPI.readAccessList).toHaveBeenCalledTimes(2);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import React from 'react';
|
||||
import { mountWithContexts } from '../../../../enzymeHelpers';
|
||||
import { mountWithContexts, waitForElement } from '../../../../enzymeHelpers';
|
||||
import OrganizationDetail from '../../../../../src/pages/Organizations/screens/Organization/OrganizationDetail';
|
||||
import { OrganizationsAPI } from '../../../../../src/api';
|
||||
|
||||
jest.mock('../../../../../src/api');
|
||||
|
||||
describe('<OrganizationDetail />', () => {
|
||||
const mockDetails = {
|
||||
const mockOrganization = {
|
||||
name: 'Foo',
|
||||
description: 'Bar',
|
||||
custom_virtualenv: 'Fizz',
|
||||
@@ -19,107 +19,75 @@ describe('<OrganizationDetail />', () => {
|
||||
}
|
||||
}
|
||||
};
|
||||
const mockInstanceGroups = {
|
||||
data: {
|
||||
results: [
|
||||
{ name: 'One', id: 1 },
|
||||
{ name: 'Two', id: 2 }
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
OrganizationsAPI.readInstanceGroups.mockResolvedValue(mockInstanceGroups);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
test('initially renders succesfully', () => {
|
||||
mountWithContexts(
|
||||
<OrganizationDetail
|
||||
organization={mockDetails}
|
||||
/>
|
||||
);
|
||||
mountWithContexts(<OrganizationDetail organization={mockOrganization} />);
|
||||
});
|
||||
|
||||
test('should request instance groups from api', () => {
|
||||
mountWithContexts(
|
||||
<OrganizationDetail
|
||||
organization={mockDetails}
|
||||
/>, { context: {
|
||||
network: { handleHttpError: () => {} }
|
||||
} }
|
||||
).find('OrganizationDetail');
|
||||
|
||||
mountWithContexts(<OrganizationDetail organization={mockOrganization} />);
|
||||
expect(OrganizationsAPI.readInstanceGroups).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('should handle setting instance groups to state', async () => {
|
||||
const mockInstanceGroups = [
|
||||
{ name: 'One', id: 1 },
|
||||
{ name: 'Two', id: 2 }
|
||||
];
|
||||
OrganizationsAPI.readInstanceGroups.mockResolvedValue({
|
||||
data: { results: mockInstanceGroups }
|
||||
});
|
||||
test('should handle setting instance groups to state', async (done) => {
|
||||
const wrapper = mountWithContexts(
|
||||
<OrganizationDetail
|
||||
organization={mockDetails}
|
||||
/>, { context: {
|
||||
network: { handleHttpError: () => {} }
|
||||
} }
|
||||
).find('OrganizationDetail');
|
||||
|
||||
await OrganizationsAPI.readInstanceGroups();
|
||||
expect(wrapper.state().instanceGroups).toEqual(mockInstanceGroups);
|
||||
});
|
||||
|
||||
test('should render Details', async () => {
|
||||
const wrapper = mountWithContexts(
|
||||
<OrganizationDetail
|
||||
organization={mockDetails}
|
||||
/>
|
||||
<OrganizationDetail organization={mockOrganization} />
|
||||
);
|
||||
|
||||
const detailWrapper = wrapper.find('Detail');
|
||||
expect(detailWrapper.length).toBe(6);
|
||||
|
||||
const nameDetail = detailWrapper.findWhere(node => node.props().label === 'Name');
|
||||
const descriptionDetail = detailWrapper.findWhere(node => node.props().label === 'Description');
|
||||
const custom_virtualenvDetail = detailWrapper.findWhere(node => node.props().label === 'Ansible Environment');
|
||||
const max_hostsDetail = detailWrapper.findWhere(node => node.props().label === 'Max Hosts');
|
||||
const createdDetail = detailWrapper.findWhere(node => node.props().label === 'Created');
|
||||
const modifiedDetail = detailWrapper.findWhere(node => node.props().label === 'Last Modified');
|
||||
expect(nameDetail.find('dt').text()).toBe('Name');
|
||||
expect(nameDetail.find('dd').text()).toBe('Foo');
|
||||
|
||||
expect(descriptionDetail.find('dt').text()).toBe('Description');
|
||||
expect(descriptionDetail.find('dd').text()).toBe('Bar');
|
||||
|
||||
expect(custom_virtualenvDetail.find('dt').text()).toBe('Ansible Environment');
|
||||
expect(custom_virtualenvDetail.find('dd').text()).toBe('Fizz');
|
||||
|
||||
expect(createdDetail.find('dt').text()).toBe('Created');
|
||||
expect(createdDetail.find('dd').text()).toBe('Bat');
|
||||
|
||||
expect(modifiedDetail.find('dt').text()).toBe('Last Modified');
|
||||
expect(modifiedDetail.find('dd').text()).toBe('Boo');
|
||||
|
||||
expect(max_hostsDetail.find('dt').text()).toBe('Max Hosts');
|
||||
expect(max_hostsDetail.find('dd').text()).toBe('0');
|
||||
const component = await waitForElement(wrapper, 'OrganizationDetail');
|
||||
expect(component.state().instanceGroups).toEqual(mockInstanceGroups.data.results);
|
||||
done();
|
||||
});
|
||||
|
||||
test('should show edit button for users with edit permission', () => {
|
||||
const wrapper = mountWithContexts(
|
||||
<OrganizationDetail
|
||||
organization={mockDetails}
|
||||
/>
|
||||
).find('OrganizationDetail');
|
||||
const editButton = wrapper.find('Button');
|
||||
expect((editButton).prop('to')).toBe('/organizations/undefined/edit');
|
||||
test('should render Details', async (done) => {
|
||||
const wrapper = mountWithContexts(<OrganizationDetail organization={mockOrganization} />);
|
||||
const testParams = [
|
||||
{ label: 'Name', value: 'Foo' },
|
||||
{ label: 'Description', value: 'Bar' },
|
||||
{ label: 'Ansible Environment', value: 'Fizz' },
|
||||
{ label: 'Created', value: 'Bat' },
|
||||
{ label: 'Last Modified', value: 'Boo' },
|
||||
{ label: 'Max Hosts', value: '0' },
|
||||
];
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
for (const { label, value } of testParams) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
const detail = await waitForElement(wrapper, `Detail[label="${label}"]`);
|
||||
expect(detail.find('dt').text()).toBe(label);
|
||||
expect(detail.find('dd').text()).toBe(value);
|
||||
}
|
||||
done();
|
||||
});
|
||||
|
||||
test('should hide edit button for users without edit permission', () => {
|
||||
const readOnlyOrg = { ...mockDetails };
|
||||
test('should show edit button for users with edit permission', async (done) => {
|
||||
const wrapper = mountWithContexts(<OrganizationDetail organization={mockOrganization} />);
|
||||
const editButton = await waitForElement(wrapper, 'OrganizationDetail Button');
|
||||
expect(editButton.text()).toEqual('Edit');
|
||||
expect(editButton.prop('to')).toBe('/organizations/undefined/edit');
|
||||
done();
|
||||
});
|
||||
|
||||
test('should hide edit button for users without edit permission', async (done) => {
|
||||
const readOnlyOrg = { ...mockOrganization };
|
||||
readOnlyOrg.summary_fields.user_capabilities.edit = false;
|
||||
const wrapper = mountWithContexts(
|
||||
<OrganizationDetail
|
||||
organization={readOnlyOrg}
|
||||
/>
|
||||
).find('OrganizationDetail');
|
||||
|
||||
const editLink = wrapper
|
||||
.findWhere(node => node.props().to === '/organizations/undefined/edit');
|
||||
expect(editLink.length).toBe(0);
|
||||
const wrapper = mountWithContexts(<OrganizationDetail organization={readOnlyOrg} />);
|
||||
await waitForElement(wrapper, 'OrganizationDetail');
|
||||
expect(wrapper.find('OrganizationDetail Button').length).toBe(0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -37,7 +37,6 @@ describe('<OrganizationEdit />', () => {
|
||||
organization={mockData}
|
||||
/>, { context: { network: {
|
||||
api,
|
||||
handleHttpError: () => {}
|
||||
} } }
|
||||
);
|
||||
|
||||
@@ -57,7 +56,6 @@ describe('<OrganizationEdit />', () => {
|
||||
organization={mockData}
|
||||
/>, { context: { network: {
|
||||
api,
|
||||
handleHttpError: () => {}
|
||||
} } }
|
||||
);
|
||||
|
||||
@@ -84,7 +82,6 @@ describe('<OrganizationEdit />', () => {
|
||||
/>, { context: {
|
||||
network: {
|
||||
api: { api },
|
||||
handleHttpError: () => {}
|
||||
},
|
||||
router: { history }
|
||||
} }
|
||||
|
||||
@@ -8,7 +8,6 @@ jest.mock('../../../../../src/api');
|
||||
|
||||
describe('<OrganizationNotifications />', () => {
|
||||
let data;
|
||||
const network = {};
|
||||
|
||||
beforeEach(() => {
|
||||
data = {
|
||||
@@ -40,8 +39,7 @@ describe('<OrganizationNotifications />', () => {
|
||||
|
||||
test('initially renders succesfully', async () => {
|
||||
const wrapper = mountWithContexts(
|
||||
<OrganizationNotifications id={1} canToggleNotifications />,
|
||||
{ context: { network } }
|
||||
<OrganizationNotifications id={1} canToggleNotifications />
|
||||
);
|
||||
await sleep(0);
|
||||
wrapper.update();
|
||||
@@ -50,10 +48,7 @@ describe('<OrganizationNotifications />', () => {
|
||||
|
||||
test('should render list fetched of items', async () => {
|
||||
const wrapper = mountWithContexts(
|
||||
<OrganizationNotifications id={1} canToggleNotifications />,
|
||||
{
|
||||
context: { network }
|
||||
}
|
||||
<OrganizationNotifications id={1} canToggleNotifications />
|
||||
);
|
||||
await sleep(0);
|
||||
wrapper.update();
|
||||
@@ -71,10 +66,7 @@ describe('<OrganizationNotifications />', () => {
|
||||
|
||||
test('should enable success notification', async () => {
|
||||
const wrapper = mountWithContexts(
|
||||
<OrganizationNotifications id={1} canToggleNotifications />,
|
||||
{
|
||||
context: { network }
|
||||
}
|
||||
<OrganizationNotifications id={1} canToggleNotifications />
|
||||
);
|
||||
await sleep(0);
|
||||
wrapper.update();
|
||||
@@ -84,7 +76,7 @@ describe('<OrganizationNotifications />', () => {
|
||||
).toEqual([1]);
|
||||
const items = wrapper.find('NotificationListItem');
|
||||
items.at(1).find('Switch').at(0).prop('onChange')();
|
||||
expect(OrganizationsAPI.associateNotificationTemplatesSuccess).toHaveBeenCalled();
|
||||
expect(OrganizationsAPI.updateNotificationTemplateAssociation).toHaveBeenCalledWith(1, 2, 'success', true);
|
||||
await sleep(0);
|
||||
wrapper.update();
|
||||
expect(
|
||||
@@ -94,10 +86,7 @@ describe('<OrganizationNotifications />', () => {
|
||||
|
||||
test('should enable error notification', async () => {
|
||||
const wrapper = mountWithContexts(
|
||||
<OrganizationNotifications id={1} canToggleNotifications />,
|
||||
{
|
||||
context: { network }
|
||||
}
|
||||
<OrganizationNotifications id={1} canToggleNotifications />
|
||||
);
|
||||
await sleep(0);
|
||||
wrapper.update();
|
||||
@@ -107,7 +96,7 @@ describe('<OrganizationNotifications />', () => {
|
||||
).toEqual([2]);
|
||||
const items = wrapper.find('NotificationListItem');
|
||||
items.at(0).find('Switch').at(1).prop('onChange')();
|
||||
expect(OrganizationsAPI.associateNotificationTemplatesError).toHaveBeenCalled();
|
||||
expect(OrganizationsAPI.updateNotificationTemplateAssociation).toHaveBeenCalledWith(1, 1, 'error', true);
|
||||
await sleep(0);
|
||||
wrapper.update();
|
||||
expect(
|
||||
@@ -117,10 +106,7 @@ describe('<OrganizationNotifications />', () => {
|
||||
|
||||
test('should disable success notification', async () => {
|
||||
const wrapper = mountWithContexts(
|
||||
<OrganizationNotifications id={1} canToggleNotifications />,
|
||||
{
|
||||
context: { network }
|
||||
}
|
||||
<OrganizationNotifications id={1} canToggleNotifications />
|
||||
);
|
||||
await sleep(0);
|
||||
wrapper.update();
|
||||
@@ -130,7 +116,7 @@ describe('<OrganizationNotifications />', () => {
|
||||
).toEqual([1]);
|
||||
const items = wrapper.find('NotificationListItem');
|
||||
items.at(0).find('Switch').at(0).prop('onChange')();
|
||||
expect(OrganizationsAPI.disassociateNotificationTemplatesSuccess).toHaveBeenCalled();
|
||||
expect(OrganizationsAPI.updateNotificationTemplateAssociation).toHaveBeenCalledWith(1, 1, 'success', false);
|
||||
await sleep(0);
|
||||
wrapper.update();
|
||||
expect(
|
||||
@@ -140,10 +126,7 @@ describe('<OrganizationNotifications />', () => {
|
||||
|
||||
test('should disable error notification', async () => {
|
||||
const wrapper = mountWithContexts(
|
||||
<OrganizationNotifications id={1} canToggleNotifications />,
|
||||
{
|
||||
context: { network }
|
||||
}
|
||||
<OrganizationNotifications id={1} canToggleNotifications />
|
||||
);
|
||||
await sleep(0);
|
||||
wrapper.update();
|
||||
@@ -153,7 +136,7 @@ describe('<OrganizationNotifications />', () => {
|
||||
).toEqual([2]);
|
||||
const items = wrapper.find('NotificationListItem');
|
||||
items.at(1).find('Switch').at(1).prop('onChange')();
|
||||
expect(OrganizationsAPI.disassociateNotificationTemplatesError).toHaveBeenCalled();
|
||||
expect(OrganizationsAPI.updateNotificationTemplateAssociation).toHaveBeenCalledWith(1, 2, 'error', false);
|
||||
await sleep(0);
|
||||
wrapper.update();
|
||||
expect(
|
||||
|
||||
@@ -20,22 +20,21 @@ const listData = {
|
||||
}
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
OrganizationsAPI.readTeams.mockResolvedValue(listData);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('<OrganizationTeams />', () => {
|
||||
beforeEach(() => {
|
||||
OrganizationsAPI.readTeams.mockResolvedValue(listData);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
test('renders succesfully', () => {
|
||||
shallow(
|
||||
<_OrganizationTeams
|
||||
id={1}
|
||||
searchString=""
|
||||
location={{ search: '', pathname: '/organizations/1/teams' }}
|
||||
handleHttpError={() => {}}
|
||||
/>
|
||||
);
|
||||
});
|
||||
@@ -45,9 +44,7 @@ describe('<OrganizationTeams />', () => {
|
||||
<OrganizationTeams
|
||||
id={1}
|
||||
searchString=""
|
||||
/>, { context: {
|
||||
network: {} }
|
||||
}
|
||||
/>
|
||||
).find('OrganizationTeams');
|
||||
expect(OrganizationsAPI.readTeams).toHaveBeenCalledWith(1, {
|
||||
page: 1,
|
||||
@@ -61,9 +58,7 @@ describe('<OrganizationTeams />', () => {
|
||||
<OrganizationTeams
|
||||
id={1}
|
||||
searchString=""
|
||||
/>, { context: {
|
||||
network: { handleHttpError: () => {} } }
|
||||
}
|
||||
/>
|
||||
);
|
||||
|
||||
await sleep(0);
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
exports[`<OrganizationAccess /> initially renders succesfully 1`] = `
|
||||
<OrganizationAccess
|
||||
handleHttpError={[Function]}
|
||||
history={"/history/"}
|
||||
i18n={"/i18n/"}
|
||||
location={
|
||||
@@ -34,8 +33,228 @@ exports[`<OrganizationAccess /> initially renders succesfully 1`] = `
|
||||
}
|
||||
}
|
||||
>
|
||||
<div>
|
||||
Loading...
|
||||
</div>
|
||||
<WithI18n
|
||||
contentError={false}
|
||||
contentLoading={true}
|
||||
itemCount={0}
|
||||
itemName="role"
|
||||
items={Array []}
|
||||
qsConfig={
|
||||
Object {
|
||||
"defaultParams": Object {
|
||||
"order_by": "first_name",
|
||||
"page": 1,
|
||||
"page_size": 5,
|
||||
},
|
||||
"integerFields": Array [
|
||||
"page",
|
||||
"page_size",
|
||||
],
|
||||
"namespace": "access",
|
||||
}
|
||||
}
|
||||
renderItem={[Function]}
|
||||
renderToolbar={[Function]}
|
||||
toolbarColumns={
|
||||
Array [
|
||||
Object {
|
||||
"isSortable": true,
|
||||
"key": "first_name",
|
||||
"name": "Name",
|
||||
},
|
||||
Object {
|
||||
"isSortable": true,
|
||||
"key": "username",
|
||||
"name": "Username",
|
||||
},
|
||||
Object {
|
||||
"isSortable": true,
|
||||
"key": "last_name",
|
||||
"name": "Last Name",
|
||||
},
|
||||
]
|
||||
}
|
||||
>
|
||||
<I18n
|
||||
update={true}
|
||||
withHash={true}
|
||||
>
|
||||
<withRouter(PaginatedDataList)
|
||||
contentError={false}
|
||||
contentLoading={true}
|
||||
i18n={"/i18n/"}
|
||||
itemCount={0}
|
||||
itemName="role"
|
||||
items={Array []}
|
||||
qsConfig={
|
||||
Object {
|
||||
"defaultParams": Object {
|
||||
"order_by": "first_name",
|
||||
"page": 1,
|
||||
"page_size": 5,
|
||||
},
|
||||
"integerFields": Array [
|
||||
"page",
|
||||
"page_size",
|
||||
],
|
||||
"namespace": "access",
|
||||
}
|
||||
}
|
||||
renderItem={[Function]}
|
||||
renderToolbar={[Function]}
|
||||
toolbarColumns={
|
||||
Array [
|
||||
Object {
|
||||
"isSortable": true,
|
||||
"key": "first_name",
|
||||
"name": "Name",
|
||||
},
|
||||
Object {
|
||||
"isSortable": true,
|
||||
"key": "username",
|
||||
"name": "Username",
|
||||
},
|
||||
Object {
|
||||
"isSortable": true,
|
||||
"key": "last_name",
|
||||
"name": "Last Name",
|
||||
},
|
||||
]
|
||||
}
|
||||
>
|
||||
<Route>
|
||||
<PaginatedDataList
|
||||
contentError={false}
|
||||
contentLoading={true}
|
||||
history={"/history/"}
|
||||
i18n={"/i18n/"}
|
||||
itemCount={0}
|
||||
itemName="role"
|
||||
itemNamePlural=""
|
||||
items={Array []}
|
||||
location={
|
||||
Object {
|
||||
"hash": "",
|
||||
"pathname": "",
|
||||
"search": "",
|
||||
"state": "",
|
||||
}
|
||||
}
|
||||
match={
|
||||
Object {
|
||||
"isExact": false,
|
||||
"params": Object {},
|
||||
"path": "",
|
||||
"url": "",
|
||||
}
|
||||
}
|
||||
qsConfig={
|
||||
Object {
|
||||
"defaultParams": Object {
|
||||
"order_by": "first_name",
|
||||
"page": 1,
|
||||
"page_size": 5,
|
||||
},
|
||||
"integerFields": Array [
|
||||
"page",
|
||||
"page_size",
|
||||
],
|
||||
"namespace": "access",
|
||||
}
|
||||
}
|
||||
renderItem={[Function]}
|
||||
renderToolbar={[Function]}
|
||||
showPageSizeOptions={true}
|
||||
toolbarColumns={
|
||||
Array [
|
||||
Object {
|
||||
"isSortable": true,
|
||||
"key": "first_name",
|
||||
"name": "Name",
|
||||
},
|
||||
Object {
|
||||
"isSortable": true,
|
||||
"key": "username",
|
||||
"name": "Username",
|
||||
},
|
||||
Object {
|
||||
"isSortable": true,
|
||||
"key": "last_name",
|
||||
"name": "Last Name",
|
||||
},
|
||||
]
|
||||
}
|
||||
>
|
||||
<WithI18n>
|
||||
<I18n
|
||||
update={true}
|
||||
withHash={true}
|
||||
>
|
||||
<ContentLoading
|
||||
i18n={"/i18n/"}
|
||||
>
|
||||
<EmptyState
|
||||
className=""
|
||||
variant="large"
|
||||
>
|
||||
<div
|
||||
className="pf-c-empty-state pf-m-lg"
|
||||
>
|
||||
<EmptyStateBody
|
||||
className=""
|
||||
>
|
||||
<p
|
||||
className="pf-c-empty-state__body"
|
||||
>
|
||||
Loading...
|
||||
</p>
|
||||
</EmptyStateBody>
|
||||
</div>
|
||||
</EmptyState>
|
||||
</ContentLoading>
|
||||
</I18n>
|
||||
</WithI18n>
|
||||
</PaginatedDataList>
|
||||
</Route>
|
||||
</withRouter(PaginatedDataList)>
|
||||
</I18n>
|
||||
</WithI18n>
|
||||
<_default
|
||||
isOpen={false}
|
||||
onClose={[Function]}
|
||||
title="Error!"
|
||||
variant="danger"
|
||||
>
|
||||
<Modal
|
||||
actions={Array []}
|
||||
ariaDescribedById=""
|
||||
className="awx-c-modal at-c-alertModal at-c-alertModal--danger"
|
||||
hideTitle={false}
|
||||
isLarge={false}
|
||||
isOpen={false}
|
||||
isSmall={false}
|
||||
onClose={[Function]}
|
||||
title="Error!"
|
||||
width={null}
|
||||
>
|
||||
<Portal
|
||||
containerInfo={<div />}
|
||||
>
|
||||
<ModalContent
|
||||
actions={Array []}
|
||||
ariaDescribedById=""
|
||||
className="awx-c-modal at-c-alertModal at-c-alertModal--danger"
|
||||
hideTitle={false}
|
||||
id="pf-modal-0"
|
||||
isLarge={false}
|
||||
isOpen={false}
|
||||
isSmall={false}
|
||||
onClose={[Function]}
|
||||
title="Error!"
|
||||
width={null}
|
||||
/>
|
||||
</Portal>
|
||||
</Modal>
|
||||
</_default>
|
||||
</OrganizationAccess>
|
||||
`;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,27 +1,15 @@
|
||||
import React from 'react';
|
||||
|
||||
import { mountWithContexts } from '../../../enzymeHelpers';
|
||||
import { mountWithContexts, waitForElement } from '../../../enzymeHelpers';
|
||||
|
||||
import OrganizationAdd from '../../../../src/pages/Organizations/screens/OrganizationAdd';
|
||||
import { OrganizationsAPI } from '../../../../src/api';
|
||||
|
||||
jest.mock('../../../../src/api');
|
||||
|
||||
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
|
||||
|
||||
describe('<OrganizationAdd />', () => {
|
||||
let networkProviderValue;
|
||||
|
||||
beforeEach(() => {
|
||||
networkProviderValue = {
|
||||
handleHttpError: () => {}
|
||||
};
|
||||
});
|
||||
|
||||
test('handleSubmit should post to api', () => {
|
||||
const wrapper = mountWithContexts(<OrganizationAdd />, {
|
||||
context: { network: networkProviderValue }
|
||||
});
|
||||
const wrapper = mountWithContexts(<OrganizationAdd />);
|
||||
const updatedOrgData = {
|
||||
name: 'new name',
|
||||
description: 'new description',
|
||||
@@ -35,9 +23,10 @@ describe('<OrganizationAdd />', () => {
|
||||
const history = {
|
||||
push: jest.fn(),
|
||||
};
|
||||
const wrapper = mountWithContexts(<OrganizationAdd />, {
|
||||
context: { router: { history } }
|
||||
});
|
||||
const wrapper = mountWithContexts(
|
||||
<OrganizationAdd />,
|
||||
{ context: { router: { history } } }
|
||||
);
|
||||
expect(history.push).not.toHaveBeenCalled();
|
||||
wrapper.find('button[aria-label="Cancel"]').prop('onClick')();
|
||||
expect(history.push).toHaveBeenCalledWith('/organizations');
|
||||
@@ -47,15 +36,16 @@ describe('<OrganizationAdd />', () => {
|
||||
const history = {
|
||||
push: jest.fn(),
|
||||
};
|
||||
const wrapper = mountWithContexts(<OrganizationAdd />, {
|
||||
context: { router: { history } }
|
||||
});
|
||||
const wrapper = mountWithContexts(
|
||||
<OrganizationAdd />,
|
||||
{ context: { router: { history } } }
|
||||
);
|
||||
expect(history.push).not.toHaveBeenCalled();
|
||||
wrapper.find('button[aria-label="Close"]').prop('onClick')();
|
||||
expect(history.push).toHaveBeenCalledWith('/organizations');
|
||||
});
|
||||
|
||||
test('successful form submission should trigger redirect', async () => {
|
||||
test('successful form submission should trigger redirect', async (done) => {
|
||||
const history = {
|
||||
push: jest.fn(),
|
||||
};
|
||||
@@ -64,7 +54,7 @@ describe('<OrganizationAdd />', () => {
|
||||
description: 'new description',
|
||||
custom_virtualenv: 'Buzz',
|
||||
};
|
||||
OrganizationsAPI.create.mockReturnValueOnce({
|
||||
OrganizationsAPI.create.mockResolvedValueOnce({
|
||||
data: {
|
||||
id: 5,
|
||||
related: {
|
||||
@@ -73,24 +63,23 @@ describe('<OrganizationAdd />', () => {
|
||||
...orgData,
|
||||
}
|
||||
});
|
||||
const wrapper = mountWithContexts(<OrganizationAdd />, {
|
||||
context: { router: { history }, network: networkProviderValue }
|
||||
});
|
||||
wrapper.find('OrganizationForm').prop('handleSubmit')(orgData, [], []);
|
||||
await sleep(0);
|
||||
const wrapper = mountWithContexts(
|
||||
<OrganizationAdd />,
|
||||
{ context: { router: { history } } }
|
||||
);
|
||||
await waitForElement(wrapper, 'button[aria-label="Save"]');
|
||||
await wrapper.find('OrganizationForm').prop('handleSubmit')(orgData, [3], []);
|
||||
expect(history.push).toHaveBeenCalledWith('/organizations/5');
|
||||
done();
|
||||
});
|
||||
|
||||
test('handleSubmit should post instance groups', async () => {
|
||||
const wrapper = mountWithContexts(<OrganizationAdd />, {
|
||||
context: { network: networkProviderValue }
|
||||
});
|
||||
test('handleSubmit should post instance groups', async (done) => {
|
||||
const orgData = {
|
||||
name: 'new name',
|
||||
description: 'new description',
|
||||
custom_virtualenv: 'Buzz',
|
||||
};
|
||||
OrganizationsAPI.create.mockReturnValueOnce({
|
||||
OrganizationsAPI.create.mockResolvedValueOnce({
|
||||
data: {
|
||||
id: 5,
|
||||
related: {
|
||||
@@ -99,19 +88,22 @@ describe('<OrganizationAdd />', () => {
|
||||
...orgData,
|
||||
}
|
||||
});
|
||||
wrapper.find('OrganizationForm').prop('handleSubmit')(orgData, [3], []);
|
||||
await sleep(0);
|
||||
const wrapper = mountWithContexts(<OrganizationAdd />);
|
||||
await waitForElement(wrapper, 'button[aria-label="Save"]');
|
||||
await wrapper.find('OrganizationForm').prop('handleSubmit')(orgData, [3], []);
|
||||
expect(OrganizationsAPI.associateInstanceGroup)
|
||||
.toHaveBeenCalledWith(5, 3);
|
||||
done();
|
||||
});
|
||||
|
||||
test('AnsibleSelect component renders if there are virtual environments', () => {
|
||||
const config = {
|
||||
custom_virtualenvs: ['foo', 'bar'],
|
||||
};
|
||||
const wrapper = mountWithContexts(<OrganizationAdd />, {
|
||||
context: { network: networkProviderValue, config }
|
||||
}).find('AnsibleSelect');
|
||||
const wrapper = mountWithContexts(
|
||||
<OrganizationAdd />,
|
||||
{ context: { config } }
|
||||
).find('AnsibleSelect');
|
||||
expect(wrapper.find('FormSelect')).toHaveLength(1);
|
||||
expect(wrapper.find('FormSelectOption')).toHaveLength(2);
|
||||
});
|
||||
@@ -120,9 +112,10 @@ describe('<OrganizationAdd />', () => {
|
||||
const config = {
|
||||
custom_virtualenvs: [],
|
||||
};
|
||||
const wrapper = mountWithContexts(<OrganizationAdd />, {
|
||||
context: { network: networkProviderValue, config }
|
||||
}).find('AnsibleSelect');
|
||||
const wrapper = mountWithContexts(
|
||||
<OrganizationAdd />,
|
||||
{ context: { config } }
|
||||
).find('AnsibleSelect');
|
||||
expect(wrapper.find('FormSelect')).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -59,25 +59,13 @@ const mockAPIOrgsList = {
|
||||
|
||||
describe('<OrganizationsList />', () => {
|
||||
let wrapper;
|
||||
let api;
|
||||
|
||||
beforeEach(() => {
|
||||
api = {
|
||||
getOrganizations: () => {},
|
||||
destroyOrganization: jest.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
test('initially renders succesfully', () => {
|
||||
mountWithContexts(
|
||||
<OrganizationsList />
|
||||
);
|
||||
mountWithContexts(<OrganizationsList />);
|
||||
});
|
||||
|
||||
test('Puts 1 selected Org in state when handleSelect is called.', () => {
|
||||
wrapper = mountWithContexts(
|
||||
<OrganizationsList />
|
||||
).find('OrganizationsList');
|
||||
wrapper = mountWithContexts(<OrganizationsList />).find('OrganizationsList');
|
||||
|
||||
wrapper.setState({
|
||||
organizations: mockAPIOrgsList.data.results,
|
||||
@@ -91,9 +79,7 @@ describe('<OrganizationsList />', () => {
|
||||
});
|
||||
|
||||
test('Puts all Orgs in state when handleSelectAll is called.', () => {
|
||||
wrapper = mountWithContexts(
|
||||
<OrganizationsList />
|
||||
);
|
||||
wrapper = mountWithContexts(<OrganizationsList />);
|
||||
const list = wrapper.find('OrganizationsList');
|
||||
list.setState({
|
||||
organizations: mockAPIOrgsList.data.results,
|
||||
@@ -108,16 +94,7 @@ describe('<OrganizationsList />', () => {
|
||||
});
|
||||
|
||||
test('api is called to delete Orgs for each org in selected.', () => {
|
||||
const fetchOrganizations = jest.fn(() => wrapper.find('OrganizationsList').setState({
|
||||
organizations: []
|
||||
}));
|
||||
wrapper = mountWithContexts(
|
||||
<OrganizationsList
|
||||
fetchOrganizations={fetchOrganizations}
|
||||
/>, {
|
||||
context: { network: { api } }
|
||||
}
|
||||
);
|
||||
wrapper = mountWithContexts(<OrganizationsList />);
|
||||
const component = wrapper.find('OrganizationsList');
|
||||
wrapper.find('OrganizationsList').setState({
|
||||
organizations: mockAPIOrgsList.data.results,
|
||||
@@ -130,14 +107,10 @@ describe('<OrganizationsList />', () => {
|
||||
expect(OrganizationsAPI.destroy).toHaveBeenCalledTimes(component.state('selected').length);
|
||||
});
|
||||
|
||||
test('call fetchOrganizations after org(s) have been deleted', () => {
|
||||
const fetchOrgs = jest.spyOn(_OrganizationsList.prototype, 'fetchOrganizations');
|
||||
test('call loadOrganizations after org(s) have been deleted', () => {
|
||||
const fetchOrgs = jest.spyOn(_OrganizationsList.prototype, 'loadOrganizations');
|
||||
const event = { preventDefault: () => { } };
|
||||
wrapper = mountWithContexts(
|
||||
<OrganizationsList />, {
|
||||
context: { network: { api } }
|
||||
}
|
||||
);
|
||||
wrapper = mountWithContexts(<OrganizationsList />);
|
||||
wrapper.find('OrganizationsList').setState({
|
||||
organizations: mockAPIOrgsList.data.results,
|
||||
itemCount: 3,
|
||||
@@ -153,13 +126,9 @@ describe('<OrganizationsList />', () => {
|
||||
const history = createMemoryHistory({
|
||||
initialEntries: ['organizations?order_by=name&page=1&page_size=5'],
|
||||
});
|
||||
const handleError = jest.fn();
|
||||
wrapper = mountWithContexts(
|
||||
<OrganizationsList />, {
|
||||
context: {
|
||||
router: { history }, network: { api, handleHttpError: handleError }
|
||||
}
|
||||
}
|
||||
<OrganizationsList />,
|
||||
{ context: { router: { history } } }
|
||||
);
|
||||
await wrapper.setState({
|
||||
organizations: mockAPIOrgsList.data.results,
|
||||
@@ -173,6 +142,5 @@ describe('<OrganizationsList />', () => {
|
||||
wrapper.update();
|
||||
const component = wrapper.find('OrganizationsList');
|
||||
component.instance().handleOrgDelete();
|
||||
expect(handleError).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,21 +1,11 @@
|
||||
import React from 'react';
|
||||
import { mountWithContexts } from '../../enzymeHelpers';
|
||||
import { mountWithContexts, waitForElement } from '../../enzymeHelpers';
|
||||
import TemplatesList, { _TemplatesList } from '../../../src/pages/Templates/TemplatesList';
|
||||
import { UnifiedJobTemplatesAPI } from '../../../src/api';
|
||||
|
||||
jest.mock('../../../src/api');
|
||||
|
||||
const setDefaultState = (templatesList) => {
|
||||
templatesList.setState({
|
||||
itemCount: mockUnifiedJobTemplatesFromAPI.length,
|
||||
isLoading: false,
|
||||
isInitialized: true,
|
||||
selected: [],
|
||||
templates: mockUnifiedJobTemplatesFromAPI,
|
||||
});
|
||||
templatesList.update();
|
||||
};
|
||||
|
||||
const mockUnifiedJobTemplatesFromAPI = [{
|
||||
const mockTemplates = [{
|
||||
id: 1,
|
||||
name: 'Template 1',
|
||||
url: '/templates/job_template/1',
|
||||
@@ -47,6 +37,19 @@ const mockUnifiedJobTemplatesFromAPI = [{
|
||||
}];
|
||||
|
||||
describe('<TemplatesList />', () => {
|
||||
beforeEach(() => {
|
||||
UnifiedJobTemplatesAPI.read.mockResolvedValue({
|
||||
data: {
|
||||
count: mockTemplates.length,
|
||||
results: mockTemplates
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
test('initially renders succesfully', () => {
|
||||
mountWithContexts(
|
||||
<TemplatesList
|
||||
@@ -55,46 +58,33 @@ describe('<TemplatesList />', () => {
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
test('Templates are retrieved from the api and the components finishes loading', async (done) => {
|
||||
const readTemplates = jest.spyOn(_TemplatesList.prototype, 'readUnifiedJobTemplates');
|
||||
|
||||
const wrapper = mountWithContexts(<TemplatesList />).find('TemplatesList');
|
||||
|
||||
expect(wrapper.state('isLoading')).toBe(true);
|
||||
await expect(readTemplates).toHaveBeenCalled();
|
||||
wrapper.update();
|
||||
expect(wrapper.state('isLoading')).toBe(false);
|
||||
const loadUnifiedJobTemplates = jest.spyOn(_TemplatesList.prototype, 'loadUnifiedJobTemplates');
|
||||
const wrapper = mountWithContexts(<TemplatesList />);
|
||||
await waitForElement(wrapper, 'TemplatesList', (el) => el.state('contentLoading') === true);
|
||||
expect(loadUnifiedJobTemplates).toHaveBeenCalled();
|
||||
await waitForElement(wrapper, 'TemplatesList', (el) => el.state('contentLoading') === false);
|
||||
done();
|
||||
});
|
||||
|
||||
test('handleSelect is called when a template list item is selected', async () => {
|
||||
test('handleSelect is called when a template list item is selected', async (done) => {
|
||||
const handleSelect = jest.spyOn(_TemplatesList.prototype, 'handleSelect');
|
||||
|
||||
const wrapper = mountWithContexts(<TemplatesList />);
|
||||
|
||||
const templatesList = wrapper.find('TemplatesList');
|
||||
setDefaultState(templatesList);
|
||||
|
||||
expect(templatesList.state('isLoading')).toBe(false);
|
||||
await waitForElement(wrapper, 'TemplatesList', (el) => el.state('contentLoading') === false);
|
||||
wrapper.find('DataListCheck#select-jobTemplate-1').props().onChange();
|
||||
expect(handleSelect).toBeCalled();
|
||||
templatesList.update();
|
||||
expect(templatesList.state('selected').length).toBe(1);
|
||||
await waitForElement(wrapper, 'TemplatesList', (el) => el.state('selected').length === 1);
|
||||
done();
|
||||
});
|
||||
|
||||
test('handleSelectAll is called when a template list item is selected', async () => {
|
||||
test('handleSelectAll is called when a template list item is selected', async (done) => {
|
||||
const handleSelectAll = jest.spyOn(_TemplatesList.prototype, 'handleSelectAll');
|
||||
|
||||
const wrapper = mountWithContexts(<TemplatesList />);
|
||||
|
||||
const templatesList = wrapper.find('TemplatesList');
|
||||
setDefaultState(templatesList);
|
||||
|
||||
expect(templatesList.state('isLoading')).toBe(false);
|
||||
await waitForElement(wrapper, 'TemplatesList', (el) => el.state('contentLoading') === false);
|
||||
wrapper.find('Checkbox#select-all').props().onChange(true);
|
||||
expect(handleSelectAll).toBeCalled();
|
||||
wrapper.update();
|
||||
expect(templatesList.state('selected').length).toEqual(templatesList.state('templates')
|
||||
.length);
|
||||
await waitForElement(wrapper, 'TemplatesList', (el) => el.state('selected').length === 3);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user