Update to react-scripts 4

Co-authored-by: nixocio <nixocio@gmail.com>
This commit is contained in:
Jake McDermott
2021-03-22 09:14:39 -04:00
parent b862434bec
commit 562ba53833
195 changed files with 10926 additions and 8998 deletions

View File

@@ -392,7 +392,7 @@ clean-ui:
rm -rf $(UI_BUILD_FLAG_FILE) rm -rf $(UI_BUILD_FLAG_FILE)
awx/ui_next/node_modules: awx/ui_next/node_modules:
$(NPM_BIN) --prefix awx/ui_next --loglevel warn --ignore-scripts install $(NPM_BIN) --prefix awx/ui_next --loglevel warn install
$(UI_BUILD_FLAG_FILE): $(UI_BUILD_FLAG_FILE):
$(NPM_BIN) --prefix awx/ui_next --loglevel warn run compile-strings $(NPM_BIN) --prefix awx/ui_next --loglevel warn run compile-strings

File diff suppressed because it is too large Load Diff

View File

@@ -43,7 +43,7 @@
"enzyme": "^3.10.0", "enzyme": "^3.10.0",
"enzyme-adapter-react-16": "^1.14.0", "enzyme-adapter-react-16": "^1.14.0",
"enzyme-to-json": "^3.3.5", "enzyme-to-json": "^3.3.5",
"eslint": "^6.8.0", "eslint": "^7.11.0",
"eslint-config-airbnb": "^17.1.0", "eslint-config-airbnb": "^17.1.0",
"eslint-config-prettier": "^5.0.0", "eslint-config-prettier": "^5.0.0",
"eslint-import-resolver-webpack": "0.11.1", "eslint-import-resolver-webpack": "0.11.1",
@@ -56,7 +56,7 @@
"jest-websocket-mock": "^2.0.2", "jest-websocket-mock": "^2.0.2",
"mock-socket": "^9.0.3", "mock-socket": "^9.0.3",
"prettier": "^1.18.2", "prettier": "^1.18.2",
"react-scripts": "^3.4.4" "react-scripts": "^4.0.3"
}, },
"scripts": { "scripts": {
"prelint": "lingui compile", "prelint": "lingui compile",

View File

@@ -1,34 +1,37 @@
import Base from './Base'; import Base from './Base';
describe('Base', () => { describe('Base', () => {
const createPromise = () => Promise.resolve();
const mockBaseURL = '/api/v2/organizations/'; 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); let BaseAPI;
let mockHttp;
afterEach(() => { beforeEach(() => {
jest.clearAllMocks(); const createPromise = () => Promise.resolve();
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),
};
BaseAPI = new Base(mockHttp, mockBaseURL);
}); });
test('create calls http method with expected data', async done => { afterEach(() => {
jest.resetAllMocks();
});
test('create calls http method with expected data', async () => {
const data = { name: 'test ' }; const data = { name: 'test ' };
await BaseAPI.create(data); await BaseAPI.create(data);
expect(mockHttp.post).toHaveBeenCalledTimes(1); expect(mockHttp.post).toHaveBeenCalledTimes(1);
expect(mockHttp.post.mock.calls[0][1]).toEqual(data); expect(mockHttp.post.mock.calls[0][1]).toEqual(data);
done();
}); });
test('destroy calls http method with expected data', async done => { test('destroy calls http method with expected data', async () => {
const resourceId = 1; const resourceId = 1;
await BaseAPI.destroy(resourceId); await BaseAPI.destroy(resourceId);
@@ -36,11 +39,9 @@ describe('Base', () => {
expect(mockHttp.delete.mock.calls[0][0]).toEqual( expect(mockHttp.delete.mock.calls[0][0]).toEqual(
`${mockBaseURL}${resourceId}/` `${mockBaseURL}${resourceId}/`
); );
done();
}); });
test('read calls http method with expected data', async done => { test('read calls http method with expected data', async () => {
const testParams = { foo: 'bar' }; const testParams = { foo: 'bar' };
const testParamsDuplicates = { foo: ['bar', 'baz'] }; const testParamsDuplicates = { foo: ['bar', 'baz'] };
@@ -57,10 +58,9 @@ describe('Base', () => {
expect(mockHttp.get.mock.calls[2][1]).toEqual({ expect(mockHttp.get.mock.calls[2][1]).toEqual({
params: { foo: ['bar', 'baz'] }, params: { foo: ['bar', 'baz'] },
}); });
done();
}); });
test('readDetail calls http method with expected data', async done => { test('readDetail calls http method with expected data', async () => {
const resourceId = 1; const resourceId = 1;
await BaseAPI.readDetail(resourceId); await BaseAPI.readDetail(resourceId);
@@ -69,18 +69,16 @@ describe('Base', () => {
expect(mockHttp.get.mock.calls[0][0]).toEqual( expect(mockHttp.get.mock.calls[0][0]).toEqual(
`${mockBaseURL}${resourceId}/` `${mockBaseURL}${resourceId}/`
); );
done();
}); });
test('readOptions calls http method with expected data', async done => { test('readOptions calls http method with expected data', async () => {
await BaseAPI.readOptions(); await BaseAPI.readOptions();
expect(mockHttp.options).toHaveBeenCalledTimes(1); expect(mockHttp.options).toHaveBeenCalledTimes(1);
expect(mockHttp.options.mock.calls[0][0]).toEqual(`${mockBaseURL}`); expect(mockHttp.options.mock.calls[0][0]).toEqual(`${mockBaseURL}`);
done();
}); });
test('replace calls http method with expected data', async done => { test('replace calls http method with expected data', async () => {
const resourceId = 1; const resourceId = 1;
const data = { name: 'test ' }; const data = { name: 'test ' };
@@ -91,11 +89,9 @@ describe('Base', () => {
`${mockBaseURL}${resourceId}/` `${mockBaseURL}${resourceId}/`
); );
expect(mockHttp.put.mock.calls[0][1]).toEqual(data); expect(mockHttp.put.mock.calls[0][1]).toEqual(data);
done();
}); });
test('update calls http method with expected data', async done => { test('update calls http method with expected data', async () => {
const resourceId = 1; const resourceId = 1;
const data = { name: 'test ' }; const data = { name: 'test ' };
@@ -106,7 +102,5 @@ describe('Base', () => {
`${mockBaseURL}${resourceId}/` `${mockBaseURL}${resourceId}/`
); );
expect(mockHttp.patch.mock.calls[0][1]).toEqual(data); expect(mockHttp.patch.mock.calls[0][1]).toEqual(data);
done();
}); });
}); });

View File

@@ -3,16 +3,20 @@ import Organizations from './Organizations';
describe('OrganizationsAPI', () => { describe('OrganizationsAPI', () => {
const orgId = 1; const orgId = 1;
const createPromise = () => Promise.resolve(); let mockHttp;
const mockHttp = { get: jest.fn(createPromise) }; let OrganizationsAPI;
beforeEach(() => {
const createPromise = () => Promise.resolve();
mockHttp = { get: jest.fn(createPromise) };
const OrganizationsAPI = new Organizations(mockHttp); OrganizationsAPI = new Organizations(mockHttp);
afterEach(() => {
jest.clearAllMocks();
}); });
test('read access list calls get with expected params', async done => { afterEach(() => {
jest.resetAllMocks();
});
test('read access list calls get with expected params', async () => {
const testParams = { foo: 'bar' }; const testParams = { foo: 'bar' };
const testParamsDuplicates = { foo: ['bar', 'baz'] }; const testParamsDuplicates = { foo: ['bar', 'baz'] };
@@ -31,10 +35,9 @@ describe('OrganizationsAPI', () => {
expect(mockHttp.get.mock.calls[2][1]).toEqual({ expect(mockHttp.get.mock.calls[2][1]).toEqual({
params: { foo: ['bar', 'baz'] }, params: { foo: ['bar', 'baz'] },
}); });
done();
}); });
test('read teams calls get with expected params', async done => { test('read teams calls get with expected params', async () => {
const testParams = { foo: 'bar' }; const testParams = { foo: 'bar' };
const testParamsDuplicates = { foo: ['bar', 'baz'] }; const testParamsDuplicates = { foo: ['bar', 'baz'] };
@@ -53,7 +56,6 @@ describe('OrganizationsAPI', () => {
expect(mockHttp.get.mock.calls[2][1]).toEqual({ expect(mockHttp.get.mock.calls[2][1]).toEqual({
params: { foo: ['bar', 'baz'] }, params: { foo: ['bar', 'baz'] },
}); });
done();
}); });
}); });

View File

@@ -1,19 +1,23 @@
import Root from './Root'; import Root from './Root';
describe('RootAPI', () => { describe('RootAPI', () => {
const createPromise = () => Promise.resolve(); let mockHttp;
const mockHttp = { let RootAPI;
get: jest.fn(createPromise), beforeEach(() => {
post: jest.fn(createPromise), const createPromise = () => Promise.resolve();
}; mockHttp = {
get: jest.fn(createPromise),
post: jest.fn(createPromise),
};
const RootAPI = new Root(mockHttp); RootAPI = new Root(mockHttp);
});
afterEach(() => { afterEach(() => {
jest.clearAllMocks(); jest.clearAllMocks();
}); });
test('login calls get and post with expected content headers', async done => { test('login calls get and post with expected content headers', async () => {
const headers = { 'Content-Type': 'application/x-www-form-urlencoded' }; const headers = { 'Content-Type': 'application/x-www-form-urlencoded' };
await RootAPI.login('username', 'password'); await RootAPI.login('username', 'password');
@@ -23,11 +27,9 @@ describe('RootAPI', () => {
expect(mockHttp.post).toHaveBeenCalledTimes(1); expect(mockHttp.post).toHaveBeenCalledTimes(1);
expect(mockHttp.post.mock.calls[0]).toContainEqual({ headers }); expect(mockHttp.post.mock.calls[0]).toContainEqual({ headers });
done();
}); });
test('login sends expected data', async done => { test('login sends expected data', async () => {
await RootAPI.login('foo', 'bar'); await RootAPI.login('foo', 'bar');
await RootAPI.login('foo', 'bar', 'baz'); await RootAPI.login('foo', 'bar', 'baz');
@@ -38,15 +40,11 @@ describe('RootAPI', () => {
expect(mockHttp.post.mock.calls[1]).toContainEqual( expect(mockHttp.post.mock.calls[1]).toContainEqual(
'username=foo&password=bar&next=baz' 'username=foo&password=bar&next=baz'
); );
done();
}); });
test('logout calls expected http method', async done => { test('logout calls expected http method', async () => {
await RootAPI.logout(); await RootAPI.logout();
expect(mockHttp.get).toHaveBeenCalledTimes(1); expect(mockHttp.get).toHaveBeenCalledTimes(1);
done();
}); });
}); });

View File

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

View File

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

View File

@@ -32,11 +32,6 @@ const initialValues = {
describe('<AdHocDetailsStep />', () => { describe('<AdHocDetailsStep />', () => {
let wrapper; let wrapper;
afterEach(() => {
jest.clearAllMocks();
wrapper.unmount();
});
test('should mount properly', async () => { test('should mount properly', async () => {
await act(async () => { await act(async () => {
wrapper = mountWithContexts( wrapper = mountWithContexts(

View File

@@ -23,30 +23,6 @@ jest.mock('react-router-dom', () => ({
// this component write tests for it // this component write tests for it
describe('<_AddResourceRole />', () => { describe('<_AddResourceRole />', () => {
UsersAPI.read.mockResolvedValue({
data: {
count: 2,
results: [
{ id: 1, username: 'foo', url: '' },
{ id: 2, username: 'bar', url: '' },
],
},
});
UsersAPI.readOptions.mockResolvedValue({
data: { related: {}, actions: { GET: {} } },
});
TeamsAPI.read.mockResolvedValue({
data: {
count: 2,
results: [
{ id: 1, name: 'Team foo', url: '' },
{ id: 2, name: 'Team bar', url: '' },
],
},
});
TeamsAPI.readOptions.mockResolvedValue({
data: { related: {}, actions: { GET: {} } },
});
const roles = { const roles = {
admin_role: { admin_role: {
description: 'Can manage all aspects of the organization', description: 'Can manage all aspects of the organization',
@@ -59,6 +35,34 @@ describe('<_AddResourceRole />', () => {
name: 'Execute', name: 'Execute',
}, },
}; };
beforeEach(() => {
UsersAPI.read.mockResolvedValue({
data: {
count: 2,
results: [
{ id: 1, username: 'foo', url: '' },
{ id: 2, username: 'bar', url: '' },
],
},
});
UsersAPI.readOptions.mockResolvedValue({
data: { related: {}, actions: { GET: {} } },
});
TeamsAPI.read.mockResolvedValue({
data: {
count: 2,
results: [
{ id: 1, name: 'Team foo', url: '' },
{ id: 2, name: 'Team bar', url: '' },
],
},
});
TeamsAPI.readOptions.mockResolvedValue({
data: { related: {}, actions: { GET: {} } },
});
});
test('initially renders without crashing', () => { test('initially renders without crashing', () => {
shallow( shallow(
<_AddResourceRole <_AddResourceRole

View File

@@ -17,7 +17,7 @@ const mockData = [
describe('<AnsibleSelect />', () => { describe('<AnsibleSelect />', () => {
const onChange = jest.fn(); const onChange = jest.fn();
test('initially renders succesfully', async () => { test('initially renders successfully', async () => {
mountWithContexts( mountWithContexts(
<AnsibleSelect <AnsibleSelect
id="bar" id="bar"

View File

@@ -4,15 +4,6 @@ import WS from 'jest-websocket-mock';
import { mountWithContexts } from '../../../testUtils/enzymeHelpers'; import { mountWithContexts } from '../../../testUtils/enzymeHelpers';
import useWsPendingApprovalCount from './useWsPendingApprovalCount'; import useWsPendingApprovalCount from './useWsPendingApprovalCount';
/*
Jest mock timers dont play well with jest-websocket-mock,
so we'll stub out throttling to resolve immediately
*/
jest.mock('../../util/useThrottle', () => ({
__esModule: true,
default: jest.fn(val => val),
}));
function TestInner() { function TestInner() {
return <div />; return <div />;
} }
@@ -28,6 +19,14 @@ describe('useWsPendingApprovalCount hook', () => {
let debug; let debug;
let wrapper; let wrapper;
beforeEach(() => { beforeEach(() => {
/*
Jest mock timers dont play well with jest-websocket-mock,
so we'll stub out throttling to resolve immediately
*/
jest.mock('../../util/useThrottle', () => ({
__esModule: true,
default: jest.fn(val => val),
}));
debug = global.console.debug; // eslint-disable-line prefer-destructuring debug = global.console.debug; // eslint-disable-line prefer-destructuring
global.console.debug = () => {}; global.console.debug = () => {};
}); });

View File

@@ -12,20 +12,24 @@ jest.mock('../../api');
describe('<AssociateModal />', () => { describe('<AssociateModal />', () => {
let wrapper; let wrapper;
const onClose = jest.fn(); let onClose;
const onAssociate = jest.fn().mockResolvedValue(); let onAssociate;
const fetchRequest = jest.fn().mockReturnValue({ data: { ...mockHosts } }); let fetchRequest;
const optionsRequest = jest.fn().mockResolvedValue({ let optionsRequest;
data: {
actions: {
GET: {},
POST: {},
},
related_search_fields: [],
},
});
beforeEach(async () => { beforeEach(async () => {
onClose = jest.fn();
onAssociate = jest.fn().mockResolvedValue();
fetchRequest = jest.fn().mockReturnValue({ data: { ...mockHosts } });
optionsRequest = jest.fn().mockResolvedValue({
data: {
actions: {
GET: {},
POST: {},
},
related_search_fields: [],
},
});
await act(async () => { await act(async () => {
wrapper = mountWithContexts( wrapper = mountWithContexts(
<AssociateModal <AssociateModal
@@ -41,7 +45,6 @@ describe('<AssociateModal />', () => {
}); });
afterEach(() => { afterEach(() => {
wrapper.unmount();
jest.clearAllMocks(); jest.clearAllMocks();
}); });

View File

@@ -2,11 +2,15 @@ import React from 'react';
import { mountWithContexts } from '../../../testUtils/enzymeHelpers'; import { mountWithContexts } from '../../../testUtils/enzymeHelpers';
import ClipboardCopyButton from './ClipboardCopyButton'; import ClipboardCopyButton from './ClipboardCopyButton';
document.execCommand = jest.fn();
jest.useFakeTimers();
describe('ClipboardCopyButton', () => { describe('ClipboardCopyButton', () => {
beforeEach(() => {
document.execCommand = jest.fn();
});
afterEach(() => {
jest.clearAllMocks();
});
test('renders the expected content', () => { test('renders the expected content', () => {
const wrapper = mountWithContexts( const wrapper = mountWithContexts(
<ClipboardCopyButton <ClipboardCopyButton
@@ -20,7 +24,8 @@ describe('ClipboardCopyButton', () => {
); );
expect(wrapper).toHaveLength(1); expect(wrapper).toHaveLength(1);
}); });
test('clicking button calls execCommand to copy to clipboard', () => { test('clicking button calls execCommand to copy to clipboard', async () => {
const mockDelay = 1;
const wrapper = mountWithContexts( const wrapper = mountWithContexts(
<ClipboardCopyButton <ClipboardCopyButton
clickTip="foo" clickTip="foo"
@@ -29,13 +34,14 @@ describe('ClipboardCopyButton', () => {
copiedSuccessTip="qux" copiedSuccessTip="qux"
stringToCopy="foobar!" stringToCopy="foobar!"
isDisabled={false} isDisabled={false}
switchDelay={mockDelay}
/> />
).find('ClipboardCopyButton'); ).find('ClipboardCopyButton');
expect(wrapper.state('copied')).toBe(false); expect(wrapper.state('copied')).toBe(false);
wrapper.find('Button').simulate('click'); wrapper.find('Button').simulate('click');
expect(document.execCommand).toBeCalledWith('copy'); expect(document.execCommand).toBeCalledWith('copy');
expect(wrapper.state('copied')).toBe(true); expect(wrapper.state('copied')).toBe(true);
jest.runAllTimers(); await new Promise(resolve => setTimeout(resolve, mockDelay));
wrapper.update(); wrapper.update();
expect(wrapper.state('copied')).toBe(false); expect(wrapper.state('copied')).toBe(false);
}); });

View File

@@ -39,7 +39,6 @@ describe('<HostForm />', () => {
afterEach(() => { afterEach(() => {
jest.clearAllMocks(); jest.clearAllMocks();
wrapper.unmount();
}); });
test('changing inputs should update form values', async () => { test('changing inputs should update form values', async () => {

View File

@@ -116,20 +116,6 @@ const mockResults = [
}, },
]; ];
UnifiedJobsAPI.read.mockResolvedValue({
data: { count: 6, results: mockResults },
});
UnifiedJobsAPI.readOptions.mockResolvedValue({
data: {
actions: {
GET: {},
POST: {},
},
related_search_fields: [],
},
});
function waitForLoaded(wrapper) { function waitForLoaded(wrapper) {
return waitForElement( return waitForElement(
wrapper, wrapper,
@@ -142,7 +128,17 @@ describe('<JobList />', () => {
let debug; let debug;
beforeEach(() => { beforeEach(() => {
UnifiedJobsAPI.read.mockResolvedValue({ UnifiedJobsAPI.read.mockResolvedValue({
data: { count: 6, results: mockResults }, data: { count: 3, results: mockResults },
});
UnifiedJobsAPI.readOptions.mockResolvedValue({
data: {
actions: {
GET: {},
POST: {},
},
related_search_fields: [],
},
}); });
debug = global.console.debug; // eslint-disable-line prefer-destructuring debug = global.console.debug; // eslint-disable-line prefer-destructuring
global.console.debug = () => {}; global.console.debug = () => {};
@@ -150,9 +146,10 @@ describe('<JobList />', () => {
afterEach(() => { afterEach(() => {
global.console.debug = debug; global.console.debug = debug;
jest.clearAllMocks();
}); });
test('initially renders succesfully', async () => { test('initially renders successfully', async () => {
let wrapper; let wrapper;
await act(async () => { await act(async () => {
wrapper = mountWithContexts(<JobList />); wrapper = mountWithContexts(<JobList />);

View File

@@ -4,15 +4,6 @@ import WS from 'jest-websocket-mock';
import { mountWithContexts } from '../../../testUtils/enzymeHelpers'; import { mountWithContexts } from '../../../testUtils/enzymeHelpers';
import useWsJobs from './useWsJobs'; import useWsJobs from './useWsJobs';
/*
Jest mock timers dont play well with jest-websocket-mock,
so we'll stub out throttling to resolve immediately
*/
jest.mock('../../util/useThrottle', () => ({
__esModule: true,
default: jest.fn(val => val),
}));
function TestInner() { function TestInner() {
return <div />; return <div />;
} }
@@ -26,12 +17,21 @@ describe('useWsJobs hook', () => {
let debug; let debug;
let wrapper; let wrapper;
beforeEach(() => { beforeEach(() => {
/*
Jest mock timers dont play well with jest-websocket-mock,
so we'll stub out throttling to resolve immediately
*/
jest.mock('../../util/useThrottle', () => ({
__esModule: true,
default: jest.fn(val => val),
}));
debug = global.console.debug; // eslint-disable-line prefer-destructuring debug = global.console.debug; // eslint-disable-line prefer-destructuring
global.console.debug = () => {}; global.console.debug = () => {};
}); });
afterEach(() => { afterEach(() => {
global.console.debug = debug; global.console.debug = debug;
jest.clearAllMocks();
}); });
test('should return jobs list', () => { test('should return jobs list', () => {

View File

@@ -17,18 +17,6 @@ import {
jest.mock('../../api'); jest.mock('../../api');
describe('LaunchButton', () => { describe('LaunchButton', () => {
JobTemplatesAPI.readLaunch.mockResolvedValue({
data: {
can_start_without_user_input: true,
ask_inventory_on_launch: false,
ask_variables_on_launch: false,
ask_limit_on_launch: false,
ask_scm_branch_on_launch: false,
survey_enabled: false,
variables_needed_to_start: [],
},
});
const launchButton = ({ handleLaunch }) => ( const launchButton = ({ handleLaunch }) => (
<button type="submit" onClick={() => handleLaunch()} /> <button type="submit" onClick={() => handleLaunch()} />
); );
@@ -42,6 +30,20 @@ describe('LaunchButton', () => {
type: 'job_template', type: 'job_template',
}; };
beforeEach(() => {
JobTemplatesAPI.readLaunch.mockResolvedValue({
data: {
can_start_without_user_input: true,
ask_inventory_on_launch: false,
ask_variables_on_launch: false,
ask_limit_on_launch: false,
ask_scm_branch_on_launch: false,
survey_enabled: false,
variables_needed_to_start: [],
},
});
});
afterEach(() => jest.clearAllMocks()); afterEach(() => jest.clearAllMocks());
test('renders the expected content', () => { test('renders the expected content', () => {

View File

@@ -26,7 +26,6 @@ describe('CredentialLookup', () => {
afterEach(() => { afterEach(() => {
jest.clearAllMocks(); jest.clearAllMocks();
wrapper.unmount();
}); });
test('should render successfully', async () => { test('should render successfully', async () => {
@@ -89,27 +88,6 @@ describe('CredentialLookup', () => {
expect(_CredentialLookup.defaultProps.onBlur).not.toThrow(); expect(_CredentialLookup.defaultProps.onBlur).not.toThrow();
}); });
test('should auto-select credential when only one available and autoPopulate prop is true', async () => {
CredentialsAPI.read.mockReturnValue({
data: {
results: [{ id: 1 }],
count: 1,
},
});
const onChange = jest.fn();
await act(async () => {
wrapper = mountWithContexts(
<CredentialLookup
autoPopulate
credentialTypeId={1}
label="Foo"
onChange={onChange}
/>
);
});
expect(onChange).toHaveBeenCalledWith({ id: 1 });
});
test('should not auto-select credential when autoPopulate prop is false', async () => { test('should not auto-select credential when autoPopulate prop is false', async () => {
CredentialsAPI.read.mockReturnValue({ CredentialsAPI.read.mockReturnValue({
data: { data: {
@@ -151,3 +129,26 @@ describe('CredentialLookup', () => {
expect(onChange).not.toHaveBeenCalled(); expect(onChange).not.toHaveBeenCalled();
}); });
}); });
describe('CredentialLookup auto select', () => {
test('should auto-select credential when only one available and autoPopulate prop is true', async () => {
CredentialsAPI.read.mockResolvedValue({
data: {
results: [{ id: 1 }],
count: 1,
},
});
const onChange = jest.fn();
await act(async () => {
mountWithContexts(
<CredentialLookup
autoPopulate
credentialTypeId={1}
label="Foo"
onChange={onChange}
/>
);
});
expect(onChange).toHaveBeenCalledWith({ id: 1 });
});
});

View File

@@ -86,7 +86,7 @@ describe('<Lookup />', () => {
jest.restoreAllMocks(); jest.restoreAllMocks();
}); });
test('should render succesfully', async () => { test('should render successfully', async () => {
wrapper = await mountWrapper(); wrapper = await mountWrapper();
expect(wrapper.find('Lookup')).toHaveLength(1); expect(wrapper.find('Lookup')).toHaveLength(1);
}); });

View File

@@ -55,7 +55,12 @@ describe('<ProjectLookup />', () => {
test('project lookup should be enabled', async () => { test('project lookup should be enabled', async () => {
let wrapper; let wrapper;
ProjectsAPI.read.mockReturnValue({
data: {
results: [{ id: 1 }],
count: 1,
},
});
ProjectsAPI.readOptions.mockReturnValue({ ProjectsAPI.readOptions.mockReturnValue({
data: { data: {
actions: { actions: {

View File

@@ -31,17 +31,22 @@ function TagMultiSelect({ onChange, value }) {
)); ));
}; };
const onFilter = event => {
if (event) {
const str = event.target.value.toLowerCase();
const matches = options.filter(o => o.toLowerCase().includes(str));
return renderOptions(matches);
}
return null;
};
return ( return (
<Select <Select
variant={SelectVariant.typeaheadMulti} variant={SelectVariant.typeaheadMulti}
onToggle={toggleExpanded} onToggle={toggleExpanded}
onSelect={onSelect} onSelect={onSelect}
onClear={() => onChange('')} onClear={() => onChange('')}
onFilter={event => { onFilter={onFilter}
const str = event.target.value.toLowerCase();
const matches = options.filter(o => o.toLowerCase().includes(str));
return renderOptions(matches);
}}
isCreatable isCreatable
onCreateOption={name => { onCreateOption={name => {
name = name.trim(); name = name.trim();

View File

@@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import { act } from 'react-dom/test-utils'; import { act } from 'react-dom/test-utils';
import { mountWithContexts } from '../../../testUtils/enzymeHelpers'; import { mountWithContexts } from '../../../testUtils/enzymeHelpers';
import { NotificationTemplatesAPI } from '../../api'; import { NotificationTemplatesAPI, JobTemplatesAPI } from '../../api';
import NotificationList from './NotificationList'; import NotificationList from './NotificationList';
jest.mock('../../api'); jest.mock('../../api');
@@ -32,67 +32,51 @@ describe('<NotificationList />', () => {
], ],
}; };
const MockModel = jest.fn().mockImplementation(() => { beforeEach(async () => {
return { NotificationTemplatesAPI.readOptions.mockReturnValue({
readNotificationTemplatesSuccess: jest.fn(), data: {
readNotificationTemplatesError: jest.fn(), actions: {
readNotificationTemplatesStarted: jest.fn(), GET: {
associateNotificationTemplate: jest.fn(), notification_type: {
disassociateNotificationTemplate: jest.fn(), choices: [['email', 'Email']],
}; },
});
const MockModelAPI = new MockModel();
NotificationTemplatesAPI.readOptions.mockReturnValue({
data: {
actions: {
GET: {
notification_type: {
choices: [['email', 'Email']],
}, },
}, },
}, },
}, });
});
NotificationTemplatesAPI.read.mockReturnValue({ data }); NotificationTemplatesAPI.read.mockReturnValue({ data });
MockModelAPI.readNotificationTemplatesSuccess.mockReturnValue({ JobTemplatesAPI.readNotificationTemplatesSuccess.mockReturnValue({
data: { results: [{ id: 1 }] }, data: { results: [{ id: 1 }] },
}); });
MockModelAPI.readNotificationTemplatesError.mockReturnValue({ JobTemplatesAPI.readNotificationTemplatesError.mockReturnValue({
data: { results: [{ id: 2 }] }, data: { results: [{ id: 2 }] },
}); });
MockModelAPI.readNotificationTemplatesStarted.mockReturnValue({ JobTemplatesAPI.readNotificationTemplatesStarted.mockReturnValue({
data: { results: [{ id: 3 }] }, data: { results: [{ id: 3 }] },
}); });
beforeEach(async () => {
await act(async () => { await act(async () => {
wrapper = mountWithContexts( wrapper = mountWithContexts(
<NotificationList <NotificationList
id={1} id={1}
canToggleNotifications canToggleNotifications
apiModel={MockModelAPI} apiModel={JobTemplatesAPI}
/> />
); );
}); });
wrapper.update(); wrapper.update();
}); });
afterEach(() => {
wrapper.unmount();
});
test('should render list fetched of items', () => { test('should render list fetched of items', () => {
expect(NotificationTemplatesAPI.read).toHaveBeenCalled(); expect(NotificationTemplatesAPI.read).toHaveBeenCalled();
expect(NotificationTemplatesAPI.readOptions).toHaveBeenCalled(); expect(NotificationTemplatesAPI.readOptions).toHaveBeenCalled();
expect(MockModelAPI.readNotificationTemplatesSuccess).toHaveBeenCalled(); expect(JobTemplatesAPI.readNotificationTemplatesSuccess).toHaveBeenCalled();
expect(MockModelAPI.readNotificationTemplatesError).toHaveBeenCalled(); expect(JobTemplatesAPI.readNotificationTemplatesError).toHaveBeenCalled();
expect(MockModelAPI.readNotificationTemplatesStarted).toHaveBeenCalled(); expect(JobTemplatesAPI.readNotificationTemplatesStarted).toHaveBeenCalled();
expect(wrapper.find('NotificationListItem').length).toBe(3); expect(wrapper.find('NotificationListItem').length).toBe(3);
expect( expect(
wrapper.find('input#notification-1-success-toggle').props().checked wrapper.find('input#notification-1-success-toggle').props().checked
@@ -131,7 +115,7 @@ describe('<NotificationList />', () => {
wrapper.find('Switch#notification-2-success-toggle').prop('onChange')(); wrapper.find('Switch#notification-2-success-toggle').prop('onChange')();
}); });
wrapper.update(); wrapper.update();
expect(MockModelAPI.associateNotificationTemplate).toHaveBeenCalledWith( expect(JobTemplatesAPI.associateNotificationTemplate).toHaveBeenCalledWith(
1, 1,
2, 2,
'success' 'success'
@@ -149,7 +133,7 @@ describe('<NotificationList />', () => {
wrapper.find('Switch#notification-1-error-toggle').prop('onChange')(); wrapper.find('Switch#notification-1-error-toggle').prop('onChange')();
}); });
wrapper.update(); wrapper.update();
expect(MockModelAPI.associateNotificationTemplate).toHaveBeenCalledWith( expect(JobTemplatesAPI.associateNotificationTemplate).toHaveBeenCalledWith(
1, 1,
1, 1,
'error' 'error'
@@ -167,7 +151,7 @@ describe('<NotificationList />', () => {
wrapper.find('Switch#notification-1-started-toggle').prop('onChange')(); wrapper.find('Switch#notification-1-started-toggle').prop('onChange')();
}); });
wrapper.update(); wrapper.update();
expect(MockModelAPI.associateNotificationTemplate).toHaveBeenCalledWith( expect(JobTemplatesAPI.associateNotificationTemplate).toHaveBeenCalledWith(
1, 1,
1, 1,
'started' 'started'
@@ -185,11 +169,9 @@ describe('<NotificationList />', () => {
wrapper.find('Switch#notification-1-success-toggle').prop('onChange')(); wrapper.find('Switch#notification-1-success-toggle').prop('onChange')();
}); });
wrapper.update(); wrapper.update();
expect(MockModelAPI.disassociateNotificationTemplate).toHaveBeenCalledWith( expect(
1, JobTemplatesAPI.disassociateNotificationTemplate
1, ).toHaveBeenCalledWith(1, 1, 'success');
'success'
);
expect( expect(
wrapper.find('input#notification-1-success-toggle').props().checked wrapper.find('input#notification-1-success-toggle').props().checked
).toBe(false); ).toBe(false);
@@ -203,11 +185,9 @@ describe('<NotificationList />', () => {
wrapper.find('Switch#notification-2-error-toggle').prop('onChange')(); wrapper.find('Switch#notification-2-error-toggle').prop('onChange')();
}); });
wrapper.update(); wrapper.update();
expect(MockModelAPI.disassociateNotificationTemplate).toHaveBeenCalledWith( expect(
1, JobTemplatesAPI.disassociateNotificationTemplate
2, ).toHaveBeenCalledWith(1, 2, 'error');
'error'
);
expect( expect(
wrapper.find('input#notification-2-error-toggle').props().checked wrapper.find('input#notification-2-error-toggle').props().checked
).toBe(false); ).toBe(false);
@@ -221,18 +201,16 @@ describe('<NotificationList />', () => {
wrapper.find('Switch#notification-3-started-toggle').prop('onChange')(); wrapper.find('Switch#notification-3-started-toggle').prop('onChange')();
}); });
wrapper.update(); wrapper.update();
expect(MockModelAPI.disassociateNotificationTemplate).toHaveBeenCalledWith( expect(
1, JobTemplatesAPI.disassociateNotificationTemplate
3, ).toHaveBeenCalledWith(1, 3, 'started');
'started'
);
expect( expect(
wrapper.find('input#notification-3-started-toggle').props().checked wrapper.find('input#notification-3-started-toggle').props().checked
).toBe(false); ).toBe(false);
}); });
test('should throw toggle error', async () => { test('should throw toggle error', async () => {
MockModelAPI.associateNotificationTemplate.mockRejectedValue( JobTemplatesAPI.associateNotificationTemplate.mockRejectedValue(
new Error({ new Error({
response: { response: {
config: { config: {
@@ -248,7 +226,7 @@ describe('<NotificationList />', () => {
wrapper.find('Switch#notification-1-started-toggle').prop('onChange')(); wrapper.find('Switch#notification-1-started-toggle').prop('onChange')();
}); });
wrapper.update(); wrapper.update();
expect(MockModelAPI.associateNotificationTemplate).toHaveBeenCalledWith( expect(JobTemplatesAPI.associateNotificationTemplate).toHaveBeenCalledWith(
1, 1,
1, 1,
'started' 'started'

View File

@@ -28,7 +28,7 @@ describe('<NotificationListItem canToggleNotifications />', () => {
jest.clearAllMocks(); jest.clearAllMocks();
}); });
test('initially renders succesfully and displays correct label', () => { test('initially renders successfully and displays correct label', () => {
wrapper = mountWithContexts( wrapper = mountWithContexts(
<table> <table>
<tbody> <tbody>

View File

@@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<NotificationListItem canToggleNotifications /> initially renders succesfully and displays correct label 1`] = ` exports[`<NotificationListItem canToggleNotifications /> initially renders successfully and displays correct label 1`] = `
<NotificationListItem <NotificationListItem
approvalsTurnedOn={false} approvalsTurnedOn={false}
canToggleNotifications={true} canToggleNotifications={true}

View File

@@ -210,7 +210,9 @@ PaginatedDataList.defaultProps = {
toolbarSortColumns: [], toolbarSortColumns: [],
pluralizedItemName: 'Items', pluralizedItemName: 'Items',
showPageSizeOptions: true, showPageSizeOptions: true,
renderItem: item => <PaginatedDataListItem key={item.id} item={item} />, renderItem: ({ id, ...rest }) => (
<PaginatedDataListItem key={id} item={{ id, ...rest }} />
),
renderToolbar: props => <DataListToolbar {...props} />, renderToolbar: props => <DataListToolbar {...props} />,
onRowClick: () => null, onRowClick: () => null,
}; };

View File

@@ -2969,9 +2969,7 @@ message. For more information, refer to the",
} }
} }
trigger={ trigger={
<div <div>
aria-describedby="pf-tooltip-1"
>
<Button <Button
aria-label="Delete" aria-label="Delete"
isDisabled={true} isDisabled={true}
@@ -2988,9 +2986,7 @@ message. For more information, refer to the",
<FindRefWrapper <FindRefWrapper
onFoundRef={[Function]} onFoundRef={[Function]}
> >
<div <div>
aria-describedby="pf-tooltip-1"
>
<Button <Button
aria-label="Delete" aria-label="Delete"
isDisabled={true} isDisabled={true}

View File

@@ -53,10 +53,6 @@ describe('PromptDetail', () => {
); );
}); });
afterAll(() => {
wrapper.unmount();
});
test('should render successfully', () => { test('should render successfully', () => {
expect(wrapper.find('PromptDetail').length).toBe(1); expect(wrapper.find('PromptDetail').length).toBe(1);
}); });
@@ -111,10 +107,6 @@ describe('PromptDetail', () => {
wrapper = mountWithContexts(<PromptDetail resource={mockTemplate} />); wrapper = mountWithContexts(<PromptDetail resource={mockTemplate} />);
}); });
afterAll(() => {
wrapper.unmount();
});
test('should render basic detail values', () => { test('should render basic detail values', () => {
expect(wrapper.find(`Detail[label="Name"]`).length).toBe(1); expect(wrapper.find(`Detail[label="Name"]`).length).toBe(1);
expect(wrapper.find(`Detail[label="Description"]`).length).toBe(1); expect(wrapper.find(`Detail[label="Description"]`).length).toBe(1);
@@ -174,10 +166,6 @@ describe('PromptDetail', () => {
); );
}); });
afterAll(() => {
wrapper.unmount();
});
test('should render overridden details', () => { test('should render overridden details', () => {
function assertDetail(label, value) { function assertDetail(label, value) {
expect(wrapper.find(`Detail[label="${label}"] dt`).text()).toBe(label); expect(wrapper.find(`Detail[label="${label}"] dt`).text()).toBe(label);

View File

@@ -17,10 +17,6 @@ describe('PromptInventorySourceDetail', () => {
); );
}); });
afterAll(() => {
wrapper.unmount();
});
test('should render successfully', () => { test('should render successfully', () => {
expect(wrapper.find('PromptInventorySourceDetail')).toHaveLength(1); expect(wrapper.find('PromptInventorySourceDetail')).toHaveLength(1);
}); });

View File

@@ -30,10 +30,6 @@ describe('PromptJobTemplateDetail', () => {
wrapper = mountWithContexts(<PromptJobTemplateDetail resource={mockJT} />); wrapper = mountWithContexts(<PromptJobTemplateDetail resource={mockJT} />);
}); });
afterAll(() => {
wrapper.unmount();
});
test('should render successfully', () => { test('should render successfully', () => {
expect(wrapper.find('PromptJobTemplateDetail')).toHaveLength(1); expect(wrapper.find('PromptJobTemplateDetail')).toHaveLength(1);
}); });

View File

@@ -17,10 +17,6 @@ describe('PromptWFJobTemplateDetail', () => {
); );
}); });
afterAll(() => {
wrapper.unmount();
});
test('should render successfully', () => { test('should render successfully', () => {
expect(wrapper.find('PromptWFJobTemplateDetail')).toHaveLength(1); expect(wrapper.find('PromptWFJobTemplateDetail')).toHaveLength(1);
}); });

View File

@@ -92,6 +92,10 @@ describe('<ResourceAccessList />', () => {
], ],
}; };
const history = createMemoryHistory({
initialEntries: ['/organizations/1/access'],
});
beforeEach(async () => { beforeEach(async () => {
OrganizationsAPI.readAccessList.mockResolvedValue({ data }); OrganizationsAPI.readAccessList.mockResolvedValue({ data });
OrganizationsAPI.readAccessOptions.mockResolvedValue({ OrganizationsAPI.readAccessOptions.mockResolvedValue({
@@ -113,9 +117,7 @@ describe('<ResourceAccessList />', () => {
], ],
}, },
}); });
const history = createMemoryHistory({
initialEntries: ['/organizations/1/access'],
});
await act(async () => { await act(async () => {
wrapper = mountWithContexts( wrapper = mountWithContexts(
<ResourceAccessList <ResourceAccessList
@@ -125,6 +127,8 @@ describe('<ResourceAccessList />', () => {
{ context: { router: { history } } } { context: { router: { history } } }
); );
}); });
await waitForElement(wrapper, 'ContentLoading', el => el.length === 0);
wrapper.update(); wrapper.update();
}); });
@@ -133,18 +137,17 @@ describe('<ResourceAccessList />', () => {
jest.clearAllMocks(); jest.clearAllMocks();
}); });
test('initially renders succesfully', () => { test('initially renders successfully', () => {
expect(wrapper.find('PaginatedDataList')).toHaveLength(1); expect(wrapper.find('PaginatedDataList')).toHaveLength(1);
}); });
test('should fetch and display access records on mount', async done => { test('should fetch and display access records on mount', async () => {
await waitForElement(wrapper, 'ContentLoading', el => el.length === 0); await waitForElement(wrapper, 'ContentLoading', el => el.length === 0);
expect(OrganizationsAPI.readAccessList).toHaveBeenCalled(); expect(OrganizationsAPI.readAccessList).toHaveBeenCalled();
expect(wrapper.find('ResourceAccessListItem').length).toBe(2); expect(wrapper.find('ResourceAccessListItem').length).toBe(2);
done();
}); });
test('should open and close confirmation dialog when deleting role', async done => { test('should open and close confirmation dialog when deleting role', async () => {
await waitForElement(wrapper, 'ContentLoading', el => el.length === 0); await waitForElement(wrapper, 'ContentLoading', el => el.length === 0);
expect(wrapper.find('DeleteRoleConfirmationModal')).toHaveLength(0); expect(wrapper.find('DeleteRoleConfirmationModal')).toHaveLength(0);
const button = wrapper.find('Chip Button').at(0); const button = wrapper.find('Chip Button').at(0);
@@ -160,10 +163,9 @@ describe('<ResourceAccessList />', () => {
expect(wrapper.find('DeleteRoleConfirmationModal')).toHaveLength(0); expect(wrapper.find('DeleteRoleConfirmationModal')).toHaveLength(0);
expect(TeamsAPI.disassociateRole).not.toHaveBeenCalled(); expect(TeamsAPI.disassociateRole).not.toHaveBeenCalled();
expect(UsersAPI.disassociateRole).not.toHaveBeenCalled(); expect(UsersAPI.disassociateRole).not.toHaveBeenCalled();
done();
}); });
it('should delete user role', async done => { test('should delete user role', async () => {
await waitForElement(wrapper, 'ContentLoading', el => el.length === 0); await waitForElement(wrapper, 'ContentLoading', el => el.length === 0);
const button = wrapper.find('Chip Button').at(0); const button = wrapper.find('Chip Button').at(0);
await act(async () => { await act(async () => {
@@ -178,10 +180,9 @@ describe('<ResourceAccessList />', () => {
expect(TeamsAPI.disassociateRole).not.toHaveBeenCalled(); expect(TeamsAPI.disassociateRole).not.toHaveBeenCalled();
expect(UsersAPI.disassociateRole).toHaveBeenCalledWith(1, 1); expect(UsersAPI.disassociateRole).toHaveBeenCalledWith(1, 1);
expect(OrganizationsAPI.readAccessList).toHaveBeenCalledTimes(2); expect(OrganizationsAPI.readAccessList).toHaveBeenCalledTimes(2);
done();
}); });
it('should delete team role', async done => { test('should delete team role', async () => {
await waitForElement(wrapper, 'ContentLoading', el => el.length === 0); await waitForElement(wrapper, 'ContentLoading', el => el.length === 0);
const button = wrapper.find('Chip Button').at(1); const button = wrapper.find('Chip Button').at(1);
await act(async () => { await act(async () => {
@@ -196,8 +197,8 @@ describe('<ResourceAccessList />', () => {
expect(TeamsAPI.disassociateRole).toHaveBeenCalledWith(5, 3); expect(TeamsAPI.disassociateRole).toHaveBeenCalledWith(5, 3);
expect(UsersAPI.disassociateRole).not.toHaveBeenCalled(); expect(UsersAPI.disassociateRole).not.toHaveBeenCalled();
expect(OrganizationsAPI.readAccessList).toHaveBeenCalledTimes(2); expect(OrganizationsAPI.readAccessList).toHaveBeenCalledTimes(2);
done();
}); });
test('should call api to get org details', async () => { test('should call api to get org details', async () => {
await waitForElement(wrapper, 'ContentLoading', el => el.length === 0); await waitForElement(wrapper, 'ContentLoading', el => el.length === 0);

View File

@@ -29,7 +29,7 @@ const accessRecord = {
}; };
describe('<ResourceAccessListItem />', () => { describe('<ResourceAccessListItem />', () => {
test('initially renders succesfully', () => { test('initially renders successfully', () => {
const wrapper = mountWithContexts( const wrapper = mountWithContexts(
<ResourceAccessListItem <ResourceAccessListItem
accessRecord={accessRecord} accessRecord={accessRecord}

View File

@@ -5895,8 +5895,8 @@ message. For more information, refer to the",
className="" className=""
hasNoBodyWrapper={false} hasNoBodyWrapper={false}
header={ header={
<ForwardRef(AlertModal__Header)> <AlertModal__Header>
<ForwardRef(AlertModal___StyledExclamationCircleIcon) <AlertModal___StyledExclamationCircleIcon
size="lg" size="lg"
/> />
<Title <Title
@@ -5906,7 +5906,7 @@ message. For more information, refer to the",
> >
Remove Team Access Remove Team Access
</Title> </Title>
</ForwardRef(AlertModal__Header)> </AlertModal__Header>
} }
isOpen={true} isOpen={true}
onClose={[Function]} onClose={[Function]}
@@ -6057,8 +6057,8 @@ message. For more information, refer to the",
descriptorId="pf-modal-part-2" descriptorId="pf-modal-part-2"
hasNoBodyWrapper={false} hasNoBodyWrapper={false}
header={ header={
<ForwardRef(AlertModal__Header)> <AlertModal__Header>
<ForwardRef(AlertModal___StyledExclamationCircleIcon) <AlertModal___StyledExclamationCircleIcon
size="lg" size="lg"
/> />
<Title <Title
@@ -6068,7 +6068,7 @@ message. For more information, refer to the",
> >
Remove Team Access Remove Team Access
</Title> </Title>
</ForwardRef(AlertModal__Header)> </AlertModal__Header>
} }
isOpen={true} isOpen={true}
labelId="pf-modal-part-1" labelId="pf-modal-part-1"

View File

@@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<ResourceAccessListItem /> initially renders succesfully 1`] = ` exports[`<ResourceAccessListItem /> initially renders successfully 1`] = `
<ResourceAccessListItem <ResourceAccessListItem
accessRecord={ accessRecord={
Object { Object {
@@ -2954,12 +2954,12 @@ message. For more information, refer to the",
<ResourceAccessListItem__DataListItemCells <ResourceAccessListItem__DataListItemCells
dataListCells={ dataListCells={
Array [ Array [
<ForwardRef(Styled(PFDataListCell))> <DataListCell>
<TextContent> <TextContent>
<Text <Text
component="h6" component="h6"
> >
<ForwardRef(ResourceAccessListItem___StyledLink) <ResourceAccessListItem___StyledLink
to={ to={
Object { Object {
"pathname": "/users/2/details", "pathname": "/users/2/details",
@@ -2967,10 +2967,10 @@ message. For more information, refer to the",
} }
> >
jane jane
</ForwardRef(ResourceAccessListItem___StyledLink)> </ResourceAccessListItem___StyledLink>
</Text> </Text>
</TextContent> </TextContent>
<ForwardRef(Styled(DetailList)) <Styled(DetailList)
stacked={true} stacked={true}
> >
<Detail <Detail
@@ -2980,10 +2980,10 @@ message. For more information, refer to the",
label="Name" label="Name"
value="jane brown" value="jane brown"
/> />
</ForwardRef(Styled(DetailList))> </Styled(DetailList)>
</ForwardRef(Styled(PFDataListCell))>, </DataListCell>,
<ForwardRef(Styled(PFDataListCell))> <DataListCell>
<ForwardRef(Styled(DetailList)) <Styled(DetailList)
stacked={true} stacked={true}
> >
<Detail <Detail
@@ -3011,8 +3011,8 @@ message. For more information, refer to the",
</Unknown> </Unknown>
} }
/> />
</ForwardRef(Styled(DetailList))> </Styled(DetailList)>
</ForwardRef(Styled(PFDataListCell))>, </DataListCell>,
] ]
} }
key=".0" key=".0"
@@ -3021,12 +3021,12 @@ message. For more information, refer to the",
<StyledComponent <StyledComponent
dataListCells={ dataListCells={
Array [ Array [
<ForwardRef(Styled(PFDataListCell))> <DataListCell>
<TextContent> <TextContent>
<Text <Text
component="h6" component="h6"
> >
<ForwardRef(ResourceAccessListItem___StyledLink) <ResourceAccessListItem___StyledLink
to={ to={
Object { Object {
"pathname": "/users/2/details", "pathname": "/users/2/details",
@@ -3034,10 +3034,10 @@ message. For more information, refer to the",
} }
> >
jane jane
</ForwardRef(ResourceAccessListItem___StyledLink)> </ResourceAccessListItem___StyledLink>
</Text> </Text>
</TextContent> </TextContent>
<ForwardRef(Styled(DetailList)) <Styled(DetailList)
stacked={true} stacked={true}
> >
<Detail <Detail
@@ -3047,10 +3047,10 @@ message. For more information, refer to the",
label="Name" label="Name"
value="jane brown" value="jane brown"
/> />
</ForwardRef(Styled(DetailList))> </Styled(DetailList)>
</ForwardRef(Styled(PFDataListCell))>, </DataListCell>,
<ForwardRef(Styled(PFDataListCell))> <DataListCell>
<ForwardRef(Styled(DetailList)) <Styled(DetailList)
stacked={true} stacked={true}
> >
<Detail <Detail
@@ -3078,8 +3078,8 @@ message. For more information, refer to the",
</Unknown> </Unknown>
} }
/> />
</ForwardRef(Styled(DetailList))> </Styled(DetailList)>
</ForwardRef(Styled(PFDataListCell))>, </DataListCell>,
] ]
} }
forwardedComponent={ forwardedComponent={
@@ -3111,12 +3111,12 @@ message. For more information, refer to the",
className="ResourceAccessListItem__DataListItemCells-sc-658iqk-0 jCdAGK" className="ResourceAccessListItem__DataListItemCells-sc-658iqk-0 jCdAGK"
dataListCells={ dataListCells={
Array [ Array [
<ForwardRef(Styled(PFDataListCell))> <DataListCell>
<TextContent> <TextContent>
<Text <Text
component="h6" component="h6"
> >
<ForwardRef(ResourceAccessListItem___StyledLink) <ResourceAccessListItem___StyledLink
to={ to={
Object { Object {
"pathname": "/users/2/details", "pathname": "/users/2/details",
@@ -3124,10 +3124,10 @@ message. For more information, refer to the",
} }
> >
jane jane
</ForwardRef(ResourceAccessListItem___StyledLink)> </ResourceAccessListItem___StyledLink>
</Text> </Text>
</TextContent> </TextContent>
<ForwardRef(Styled(DetailList)) <Styled(DetailList)
stacked={true} stacked={true}
> >
<Detail <Detail
@@ -3137,10 +3137,10 @@ message. For more information, refer to the",
label="Name" label="Name"
value="jane brown" value="jane brown"
/> />
</ForwardRef(Styled(DetailList))> </Styled(DetailList)>
</ForwardRef(Styled(PFDataListCell))>, </DataListCell>,
<ForwardRef(Styled(PFDataListCell))> <DataListCell>
<ForwardRef(Styled(DetailList)) <Styled(DetailList)
stacked={true} stacked={true}
> >
<Detail <Detail
@@ -3168,8 +3168,8 @@ message. For more information, refer to the",
</Unknown> </Unknown>
} }
/> />
</ForwardRef(Styled(DetailList))> </Styled(DetailList)>
</ForwardRef(Styled(PFDataListCell))>, </DataListCell>,
] ]
} }
rowid="access-list-item" rowid="access-list-item"

View File

@@ -1,24 +1,11 @@
import React from 'react'; import React from 'react';
import { act } from 'react-dom/test-utils'; import { act } from 'react-dom/test-utils';
import { RRule } from 'rrule'; import { RRule } from 'rrule';
import { import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
mountWithContexts,
waitForElement,
} from '../../../../testUtils/enzymeHelpers';
import { SchedulesAPI, JobTemplatesAPI, InventoriesAPI } from '../../../api'; import { SchedulesAPI, JobTemplatesAPI, InventoriesAPI } from '../../../api';
import ScheduleAdd from './ScheduleAdd'; import ScheduleAdd from './ScheduleAdd';
jest.mock('../../../api/models/Schedules'); jest.mock('../../../api');
jest.mock('../../../api/models/JobTemplates');
jest.mock('../../../api/models/Inventories');
SchedulesAPI.readZoneInfo.mockResolvedValue({
data: [
{
name: 'America/New_York',
},
],
});
const launchConfig = { const launchConfig = {
can_start_without_user_input: false, can_start_without_user_input: false,
@@ -58,12 +45,18 @@ const launchConfig = {
}, },
}; };
JobTemplatesAPI.createSchedule.mockResolvedValue({ data: { id: 3 } });
let wrapper; let wrapper;
describe('<ScheduleAdd />', () => { describe('<ScheduleAdd />', () => {
beforeAll(async () => { beforeEach(async () => {
SchedulesAPI.readZoneInfo.mockResolvedValue({
data: [
{
name: 'America/New_York',
},
],
});
JobTemplatesAPI.createSchedule.mockResolvedValue({ data: { id: 3 } });
await act(async () => { await act(async () => {
wrapper = mountWithContexts( wrapper = mountWithContexts(
<ScheduleAdd <ScheduleAdd
@@ -78,10 +71,6 @@ describe('<ScheduleAdd />', () => {
/> />
); );
}); });
await waitForElement(wrapper, 'ContentLoading', el => el.length === 0);
});
afterEach(() => {
jest.clearAllMocks();
}); });
test('Successfully creates a schedule with repeat frequency: None (run once)', async () => { test('Successfully creates a schedule with repeat frequency: None (run once)', async () => {
await act(async () => { await act(async () => {

View File

@@ -9,9 +9,7 @@ import {
} from '../../../../testUtils/enzymeHelpers'; } from '../../../../testUtils/enzymeHelpers';
import ScheduleDetail from './ScheduleDetail'; import ScheduleDetail from './ScheduleDetail';
jest.mock('../../../api/models/JobTemplates'); jest.mock('../../../api');
jest.mock('../../../api/models/Schedules');
jest.mock('../../../api/models/WorkflowJobTemplates');
const allPrompts = { const allPrompts = {
data: { data: {
@@ -108,20 +106,21 @@ const scheduleWithPrompts = {
extra_data: { foo: 'fii' }, extra_data: { foo: 'fii' },
}; };
SchedulesAPI.createPreview.mockResolvedValue({
data: {
local: [],
utc: [],
},
});
describe('<ScheduleDetail />', () => { describe('<ScheduleDetail />', () => {
let wrapper; let wrapper;
const history = createMemoryHistory({ const history = createMemoryHistory({
initialEntries: ['/templates/job_template/1/schedules/1/details'], initialEntries: ['/templates/job_template/1/schedules/1/details'],
}); });
beforeEach(() => {
SchedulesAPI.createPreview.mockResolvedValue({
data: {
local: [],
utc: [],
},
});
});
afterEach(() => { afterEach(() => {
wrapper.unmount();
jest.clearAllMocks(); jest.clearAllMocks();
}); });

View File

@@ -13,66 +13,7 @@ import {
} from '../../../api'; } from '../../../api';
import ScheduleEdit from './ScheduleEdit'; import ScheduleEdit from './ScheduleEdit';
jest.mock('../../../api/models/Schedules'); jest.mock('../../../api');
jest.mock('../../../api/models/JobTemplates');
jest.mock('../../../api/models/Inventories');
jest.mock('../../../api/models/Credentials');
jest.mock('../../../api/models/CredentialTypes');
SchedulesAPI.readZoneInfo.mockResolvedValue({
data: [
{
name: 'America/New_York',
},
],
});
SchedulesAPI.readCredentials.mockResolvedValue({
data: {
results: [
{
name: 'schedule credential 1',
id: 1,
kind: 'vault',
credential_type: 3,
inputs: {},
},
{
name: 'schedule credential 2',
id: 2,
kind: 'aws',
credential_type: 4,
inputs: {},
},
],
count: 2,
},
});
CredentialTypesAPI.loadAllTypes.mockResolvedValue([
{ id: 1, name: 'ssh', kind: 'ssh' },
]);
CredentialsAPI.read.mockResolvedValue({
data: {
count: 3,
results: [
{ id: 1, name: 'Credential 1', kind: 'ssh', url: '', credential_type: 1 },
{ id: 2, name: 'Credential 2', kind: 'ssh', url: '', credential_type: 1 },
{ id: 3, name: 'Credential 3', kind: 'ssh', url: '', credential_type: 1 },
],
},
});
CredentialsAPI.readOptions.mockResolvedValue({
data: { related_search_fields: [], actions: { GET: { filterabled: true } } },
});
SchedulesAPI.update.mockResolvedValue({
data: {
id: 27,
},
});
let wrapper; let wrapper;
@@ -113,6 +54,81 @@ const mockSchedule = {
describe('<ScheduleEdit />', () => { describe('<ScheduleEdit />', () => {
beforeEach(async () => { beforeEach(async () => {
SchedulesAPI.readZoneInfo.mockResolvedValue({
data: [
{
name: 'America/New_York',
},
],
});
SchedulesAPI.readCredentials.mockResolvedValue({
data: {
results: [
{
name: 'schedule credential 1',
id: 1,
kind: 'vault',
credential_type: 3,
inputs: {},
},
{
name: 'schedule credential 2',
id: 2,
kind: 'aws',
credential_type: 4,
inputs: {},
},
],
count: 2,
},
});
CredentialTypesAPI.loadAllTypes.mockResolvedValue([
{ id: 1, name: 'ssh', kind: 'ssh' },
]);
CredentialsAPI.read.mockResolvedValue({
data: {
count: 3,
results: [
{
id: 1,
name: 'Credential 1',
kind: 'ssh',
url: '',
credential_type: 1,
},
{
id: 2,
name: 'Credential 2',
kind: 'ssh',
url: '',
credential_type: 1,
},
{
id: 3,
name: 'Credential 3',
kind: 'ssh',
url: '',
credential_type: 1,
},
],
},
});
CredentialsAPI.readOptions.mockResolvedValue({
data: {
related_search_fields: [],
actions: { GET: { filterabled: true } },
},
});
SchedulesAPI.update.mockResolvedValue({
data: {
id: 27,
},
});
await act(async () => { await act(async () => {
wrapper = mountWithContexts( wrapper = mountWithContexts(
<ScheduleEdit <ScheduleEdit
@@ -486,17 +502,13 @@ describe('<ScheduleEdit />', () => {
await act(async () => { await act(async () => {
wrapper wrapper
.find('input[aria-labelledby="check-action-item-3"]') .find('input[aria-labelledby="check-action-item-2"]')
.simulate('change', { .simulate('click');
target: {
checked: true,
},
});
}); });
wrapper.update(); wrapper.update();
expect( expect(
wrapper wrapper
.find('input[aria-labelledby="check-action-item-3"]') .find('input[aria-labelledby="check-action-item-2"]')
.prop('checked') .prop('checked')
).toBe(true); ).toBe(true);
await act(async () => await act(async () =>

View File

@@ -5,34 +5,39 @@ import { SchedulesAPI } from '../../../api';
import ScheduleList from './ScheduleList'; import ScheduleList from './ScheduleList';
import mockSchedules from '../data.schedules.json'; import mockSchedules from '../data.schedules.json';
jest.mock('../../../api/models/Schedules'); jest.mock('../../../api');
SchedulesAPI.destroy = jest.fn();
SchedulesAPI.update.mockResolvedValue({
data: mockSchedules.results[0],
});
SchedulesAPI.read.mockResolvedValue({ data: mockSchedules });
SchedulesAPI.readOptions.mockResolvedValue({
data: {
actions: {
GET: {},
POST: {},
},
},
});
const loadSchedules = params => SchedulesAPI.read(params);
const loadScheduleOptions = () => SchedulesAPI.readOptions();
describe('ScheduleList', () => { describe('ScheduleList', () => {
let wrapper; let wrapper;
let loadSchedules;
afterAll(() => { let loadScheduleOptions;
jest.clearAllMocks();
});
describe('read call successful', () => { describe('read call successful', () => {
beforeEach(async () => { beforeEach(async () => {
SchedulesAPI.destroy = jest.fn();
SchedulesAPI.update.mockResolvedValue({
data: mockSchedules.results[0],
});
SchedulesAPI.read.mockResolvedValue({ data: mockSchedules });
SchedulesAPI.readOptions.mockResolvedValue({
data: {
actions: {
GET: {},
POST: {},
},
},
});
loadSchedules = jest.fn();
loadSchedules.mockResolvedValue({ data: mockSchedules });
loadScheduleOptions = jest.fn();
loadScheduleOptions.mockResolvedValue({
data: {
actions: {
GET: {},
POST: {},
},
},
});
await act(async () => { await act(async () => {
wrapper = mountWithContexts( wrapper = mountWithContexts(
<ScheduleList <ScheduleList
@@ -52,7 +57,7 @@ describe('ScheduleList', () => {
}); });
test('should fetch schedules from api and render the list', () => { test('should fetch schedules from api and render the list', () => {
expect(SchedulesAPI.read).toHaveBeenCalled(); expect(loadSchedules).toHaveBeenCalled();
expect(wrapper.find('ScheduleListItem').length).toBe(5); expect(wrapper.find('ScheduleListItem').length).toBe(5);
}); });
@@ -193,6 +198,44 @@ describe('ScheduleList', () => {
}); });
describe('hidden add button', () => { describe('hidden add button', () => {
beforeEach(async () => {
SchedulesAPI.destroy = jest.fn();
SchedulesAPI.update.mockResolvedValue({
data: mockSchedules.results[0],
});
SchedulesAPI.read.mockResolvedValue({ data: mockSchedules });
SchedulesAPI.readOptions.mockResolvedValue({
data: {
actions: {
GET: {},
POST: {},
},
},
});
loadSchedules = jest.fn();
loadSchedules.mockResolvedValue({ data: mockSchedules });
loadScheduleOptions = jest.fn();
loadScheduleOptions.mockResolvedValue({
data: {
actions: {
GET: {},
POST: {},
},
},
});
await act(async () => {
wrapper = mountWithContexts(
<ScheduleList
loadSchedules={loadSchedules}
loadScheduleOptions={loadScheduleOptions}
resource={{ type: 'job_template', inventory: 1 }}
launchConfig={{ survey_enabled: false }}
surveyConfig={{}}
/>
);
});
wrapper.update();
});
test('should hide add button when flag is passed', async () => { test('should hide add button when flag is passed', async () => {
await act(async () => { await act(async () => {
wrapper = mountWithContexts( wrapper = mountWithContexts(
@@ -263,8 +306,47 @@ describe('ScheduleList', () => {
}); });
describe('read call unsuccessful', () => { describe('read call unsuccessful', () => {
beforeEach(async () => {
SchedulesAPI.destroy = jest.fn();
SchedulesAPI.update.mockResolvedValue({
data: mockSchedules.results[0],
});
SchedulesAPI.read.mockResolvedValue({ data: mockSchedules });
SchedulesAPI.readOptions.mockResolvedValue({
data: {
actions: {
GET: {},
POST: {},
},
},
});
loadSchedules = jest.fn();
loadSchedules.mockResolvedValue({ data: mockSchedules });
loadScheduleOptions = jest.fn();
loadScheduleOptions.mockResolvedValue({
data: {
actions: {
GET: {},
POST: {},
},
},
});
await act(async () => {
wrapper = mountWithContexts(
<ScheduleList
loadSchedules={loadSchedules}
loadScheduleOptions={loadScheduleOptions}
resource={{ type: 'job_template', inventory: 1 }}
launchConfig={{ survey_enabled: false }}
surveyConfig={{}}
/>
);
});
wrapper.update();
});
test('should show content error when read call unsuccessful', async () => { test('should show content error when read call unsuccessful', async () => {
SchedulesAPI.read.mockRejectedValue(new Error()); SchedulesAPI.read.mockRejectedValue(new Error());
loadSchedules.mockRejectedValue(new Error());
await act(async () => { await act(async () => {
wrapper = mountWithContexts( wrapper = mountWithContexts(
<ScheduleList <ScheduleList

View File

@@ -28,7 +28,7 @@ describe('<ScreenHeader />', () => {
breadcrumbHeading = breadcrumbWrapper.find('Title'); breadcrumbHeading = breadcrumbWrapper.find('Title');
}; };
test('initially renders succesfully', () => { test('initially renders successfully', () => {
breadcrumbWrapper = mountWithContexts( breadcrumbWrapper = mountWithContexts(
<MemoryRouter initialEntries={['/foo/1/bar']} initialIndex={0}> <MemoryRouter initialEntries={['/foo/1/bar']} initialIndex={0}>
<ScreenHeader streamType="all_activity" breadcrumbConfig={config} /> <ScreenHeader streamType="all_activity" breadcrumbConfig={config} />

View File

@@ -7,9 +7,7 @@ import {
} from '../../../testUtils/enzymeHelpers'; } from '../../../testUtils/enzymeHelpers';
import UserAndTeamAccessAdd from './UserAndTeamAccessAdd'; import UserAndTeamAccessAdd from './UserAndTeamAccessAdd';
jest.mock('../../api/models/Teams'); jest.mock('../../api');
jest.mock('../../api/models/Users');
jest.mock('../../api/models/JobTemplates');
describe('<UserAndTeamAccessAdd/>', () => { describe('<UserAndTeamAccessAdd/>', () => {
const resources = { const resources = {
@@ -66,8 +64,7 @@ describe('<UserAndTeamAccessAdd/>', () => {
await waitForElement(wrapper, 'Button[aria-label="Add"]'); await waitForElement(wrapper, 'Button[aria-label="Add"]');
}); });
afterEach(() => { afterEach(() => {
wrapper.unmount(); jest.resetAllMocks();
jest.clearAllMocks();
}); });
test('should mount properly', async () => { test('should mount properly', async () => {
expect(wrapper.find('Button[aria-label="Add"]').length).toBe(1); expect(wrapper.find('Button[aria-label="Add"]').length).toBe(1);

View File

@@ -4,14 +4,12 @@ import App from './App';
jest.mock('react-dom', () => ({ render: jest.fn() })); jest.mock('react-dom', () => ({ render: jest.fn() }));
const div = document.createElement('div');
div.setAttribute('id', 'app');
document.body.appendChild(div);
require('./index.jsx');
describe('index.jsx', () => { describe('index.jsx', () => {
it('renders ok', () => { it('renders ok', () => {
const div = document.createElement('div');
div.setAttribute('id', 'app');
document.body.appendChild(div);
require('./index.jsx'); // eslint-disable-line global-require
expect(ReactDOM.render).toHaveBeenCalledWith( expect(ReactDOM.render).toHaveBeenCalledWith(
<React.StrictMode> <React.StrictMode>
<App /> <App />

View File

@@ -3,7 +3,7 @@ import { mountWithContexts } from '../../../testUtils/enzymeHelpers';
import ActivityStreamDescription from './ActivityStreamDescription'; import ActivityStreamDescription from './ActivityStreamDescription';
describe('ActivityStreamDescription', () => { describe('ActivityStreamDescription', () => {
test('initially renders succesfully', () => { test('initially renders successfully', () => {
const description = mountWithContexts( const description = mountWithContexts(
<ActivityStreamDescription activity={{}} /> <ActivityStreamDescription activity={{}} />
); );

View File

@@ -7,7 +7,7 @@ import ActivityStreamDetailButton from './ActivityStreamDetailButton';
jest.mock('../../api/models/ActivityStream'); jest.mock('../../api/models/ActivityStream');
describe('<ActivityStreamDetailButton />', () => { describe('<ActivityStreamDetailButton />', () => {
test('initially renders succesfully', () => { test('initially renders successfully', () => {
mountWithContexts( mountWithContexts(
<ActivityStreamDetailButton <ActivityStreamDetailButton
streamItem={{ streamItem={{

View File

@@ -5,7 +5,7 @@ import ActivityStreamListItem from './ActivityStreamListItem';
jest.mock('../../api/models/ActivityStream'); jest.mock('../../api/models/ActivityStream');
describe('<ActivityStreamListItem />', () => { describe('<ActivityStreamListItem />', () => {
test('initially renders succesfully', () => { test('initially renders successfully', () => {
mountWithContexts( mountWithContexts(
<table> <table>
<tbody> <tbody>

View File

@@ -19,14 +19,16 @@ jest.mock('react-router-dom', () => ({
}), }),
})); }));
CredentialsAPI.readDetail.mockResolvedValueOnce({
data: mockCredential,
});
describe('<Credential />', () => { describe('<Credential />', () => {
let wrapper; let wrapper;
test('initially renders user-based credential succesfully', async () => { beforeEach(() => {
CredentialsAPI.readDetail.mockResolvedValueOnce({
data: mockCredential,
});
});
test('initially renders user-based credential successfully', async () => {
await act(async () => { await act(async () => {
wrapper = mountWithContexts(<Credential setBreadcrumb={() => {}} />); wrapper = mountWithContexts(<Credential setBreadcrumb={() => {}} />);
}); });
@@ -34,7 +36,7 @@ describe('<Credential />', () => {
await waitForElement(wrapper, '.pf-c-tabs__item', el => el.length === 3); await waitForElement(wrapper, '.pf-c-tabs__item', el => el.length === 3);
}); });
test('initially renders org-based credential succesfully', async () => { test('initially renders org-based credential successfully', async () => {
CredentialsAPI.readDetail.mockResolvedValueOnce({ CredentialsAPI.readDetail.mockResolvedValueOnce({
data: mockOrgCredential, data: mockOrgCredential,
}); });

View File

@@ -15,7 +15,7 @@ import CredentialAdd from './CredentialAdd';
jest.mock('../../../api'); jest.mock('../../../api');
CredentialTypesAPI.read.mockResolvedValue({ const mockCredentialResults = {
data: { data: {
results: [ results: [
{ {
@@ -100,9 +100,7 @@ CredentialTypesAPI.read.mockResolvedValue({
}, },
], ],
}, },
}); };
CredentialsAPI.create.mockResolvedValue({ data: { id: 13 } });
describe('<CredentialAdd />', () => { describe('<CredentialAdd />', () => {
let wrapper; let wrapper;
@@ -110,6 +108,8 @@ describe('<CredentialAdd />', () => {
describe('Initial GET request succeeds', () => { describe('Initial GET request succeeds', () => {
beforeEach(async () => { beforeEach(async () => {
CredentialsAPI.read.mockResolvedValue(mockCredentialResults);
CredentialsAPI.create.mockResolvedValue({ data: { id: 13 } });
history = createMemoryHistory({ initialEntries: ['/credentials'] }); history = createMemoryHistory({ initialEntries: ['/credentials'] });
await act(async () => { await act(async () => {
wrapper = mountWithContexts(<CredentialAdd />, { wrapper = mountWithContexts(<CredentialAdd />, {
@@ -118,10 +118,6 @@ describe('<CredentialAdd />', () => {
}); });
}); });
afterEach(() => {
wrapper.unmount();
});
test('handleSubmit should call the api and redirect to details page', async () => { test('handleSubmit should call the api and redirect to details page', async () => {
await waitForElement(wrapper, 'isLoading', el => el.length === 0); await waitForElement(wrapper, 'isLoading', el => el.length === 0);
await act(async () => { await act(async () => {

View File

@@ -33,16 +33,6 @@ const mockInputSource = {
}, },
}; };
CredentialTypesAPI.readDetail.mockResolvedValue({
data: mockCredentialType,
});
CredentialsAPI.readInputSources.mockResolvedValue({
data: {
results: [mockInputSource],
},
});
function expectDetailToMatch(wrapper, label, value) { function expectDetailToMatch(wrapper, label, value) {
const detail = wrapper.find(`Detail[label="${label}"]`); const detail = wrapper.find(`Detail[label="${label}"]`);
expect(detail).toHaveLength(1); expect(detail).toHaveLength(1);
@@ -53,6 +43,15 @@ describe('<CredentialDetail />', () => {
let wrapper; let wrapper;
beforeEach(async () => { beforeEach(async () => {
CredentialTypesAPI.readDetail.mockResolvedValue({
data: mockCredentialType,
});
CredentialsAPI.readInputSources.mockResolvedValue({
data: {
results: [mockInputSource],
},
});
await act(async () => { await act(async () => {
wrapper = mountWithContexts( wrapper = mountWithContexts(
<CredentialDetail credential={mockCredential} /> <CredentialDetail credential={mockCredential} />

View File

@@ -116,7 +116,7 @@ const mockCredential = {
kubernetes: false, kubernetes: false,
}; };
UsersAPI.readAdminOfOrganizations.mockResolvedValue({ const mockOrgAdmins = {
data: { data: {
count: 1, count: 1,
results: [ results: [
@@ -126,16 +126,16 @@ UsersAPI.readAdminOfOrganizations.mockResolvedValue({
}, },
], ],
}, },
}); };
OrganizationsAPI.read.mockResolvedValue({ const mockOrganizations = {
data: { data: {
results: [{ id: 1 }], results: [{ id: 1 }],
count: 1, count: 1,
}, },
}); };
CredentialTypesAPI.read.mockResolvedValue({ const mockCredentialResults = {
data: { data: {
results: [ results: [
{ {
@@ -272,10 +272,9 @@ CredentialTypesAPI.read.mockResolvedValue({
}, },
], ],
}, },
}); };
CredentialsAPI.update.mockResolvedValue({ data: { id: 3 } }); const mockInputSources = {
CredentialsAPI.readInputSources.mockResolvedValue({
data: { data: {
results: [ results: [
{ {
@@ -318,7 +317,7 @@ CredentialsAPI.readInputSources.mockResolvedValue({
}, },
], ],
}, },
}); };
describe('<CredentialEdit />', () => { describe('<CredentialEdit />', () => {
let wrapper; let wrapper;
@@ -326,6 +325,15 @@ describe('<CredentialEdit />', () => {
describe('Initial GET request succeeds', () => { describe('Initial GET request succeeds', () => {
beforeEach(async () => { beforeEach(async () => {
[
[UsersAPI.readAdminOfOrganizations, mockOrgAdmins],
[OrganizationsAPI.read, mockOrganizations],
[CredentialsAPI.read, mockCredentialResults],
[CredentialsAPI.update, { data: { id: 3 } }],
[CredentialsAPI.readInputSources, mockInputSources],
].forEach(([apiMethod, mockData]) => {
apiMethod.mockResolvedValue(mockData);
});
history = createMemoryHistory({ initialEntries: ['/credentials'] }); history = createMemoryHistory({ initialEntries: ['/credentials'] });
await act(async () => { await act(async () => {
wrapper = mountWithContexts( wrapper = mountWithContexts(

View File

@@ -14,7 +14,7 @@ describe('<Credentials />', () => {
wrapper.unmount(); wrapper.unmount();
}); });
test('initially renders succesfully', () => { test('initially renders successfully', () => {
wrapper = mountWithContexts(<Credentials />); wrapper = mountWithContexts(<Credentials />);
}); });

View File

@@ -1,4 +1,5 @@
import React from 'react'; import React from 'react';
import { act } from 'react-dom/test-utils';
import { Formik } from 'formik'; import { Formik } from 'formik';
import { mountWithContexts } from '../../../../../testUtils/enzymeHelpers'; import { mountWithContexts } from '../../../../../testUtils/enzymeHelpers';
import credentialTypes from '../data.credentialTypes.json'; import credentialTypes from '../data.credentialTypes.json';
@@ -67,7 +68,7 @@ describe('<CredentialField />', () => {
expect(wrapper.find('KeyIcon').length).toBe(1); expect(wrapper.find('KeyIcon').length).toBe(1);
expect(wrapper.find('PficonHistoryIcon').length).toBe(1); expect(wrapper.find('PficonHistoryIcon').length).toBe(1);
}); });
test('replace/revert button behaves as expected', () => { test('replace/revert button behaves as expected', async () => {
wrapper = mountWithContexts( wrapper = mountWithContexts(
<Formik <Formik
initialValues={{ initialValues={{
@@ -90,7 +91,10 @@ describe('<CredentialField />', () => {
.content .content
).toBe('Replace'); ).toBe('Replace');
expect(wrapper.find('TextInput').props().isDisabled).toBe(true); expect(wrapper.find('TextInput').props().isDisabled).toBe(true);
expect(wrapper.find('PficonHistoryIcon').simulate('click')); wrapper.find('PficonHistoryIcon').simulate('click');
await act(async () => {
wrapper.update();
});
expect( expect(
wrapper.find('Tooltip#credential-password-replace-tooltip').props() wrapper.find('Tooltip#credential-password-replace-tooltip').props()
.content .content
@@ -98,7 +102,10 @@ describe('<CredentialField />', () => {
expect(wrapper.find('TextInput').props().isDisabled).toBe(false); expect(wrapper.find('TextInput').props().isDisabled).toBe(false);
expect(wrapper.find('TextInput').props().value).toBe(''); expect(wrapper.find('TextInput').props().value).toBe('');
expect(wrapper.find('TextInput').props().placeholder).toBe(undefined); expect(wrapper.find('TextInput').props().placeholder).toBe(undefined);
expect(wrapper.find('PficonHistoryIcon').simulate('click')); wrapper.find('PficonHistoryIcon').simulate('click');
await act(async () => {
wrapper.update();
});
expect( expect(
wrapper.find('Tooltip#credential-password-replace-tooltip').props() wrapper.find('Tooltip#credential-password-replace-tooltip').props()
.content .content

View File

@@ -10,19 +10,16 @@ import azureVaultCredential from '../../data.azureVaultCredential.json';
import hashiCorpCredential from '../../data.hashiCorpCredential.json'; import hashiCorpCredential from '../../data.hashiCorpCredential.json';
import CredentialPluginPrompt from './CredentialPluginPrompt'; import CredentialPluginPrompt from './CredentialPluginPrompt';
jest.mock('../../../../../api/models/Credentials'); jest.mock('../../../../../api');
jest.mock('../../../../../api/models/CredentialTypes');
CredentialsAPI.test.mockResolvedValue({}); const mockCredentialResults = {
CredentialsAPI.read.mockResolvedValue({
data: { data: {
count: 3, count: 3,
results: [selectedCredential, azureVaultCredential, hashiCorpCredential], results: [selectedCredential, azureVaultCredential, hashiCorpCredential],
}, },
}); };
CredentialsAPI.readOptions.mockResolvedValue({ const mockCredentialOptions = {
data: { data: {
actions: { actions: {
GET: {}, GET: {},
@@ -30,9 +27,9 @@ CredentialsAPI.readOptions.mockResolvedValue({
}, },
related_search_fields: [], related_search_fields: [],
}, },
}); };
CredentialTypesAPI.readDetail.mockResolvedValue({ const mockCredentialTypeDetail = {
data: { data: {
id: 20, id: 20,
type: 'credential_type', type: 'credential_type',
@@ -83,7 +80,7 @@ CredentialTypesAPI.readDetail.mockResolvedValue({
}, },
injectors: {}, injectors: {},
}, },
}); };
describe('<CredentialPluginPrompt />', () => { describe('<CredentialPluginPrompt />', () => {
describe('Plugin not configured', () => { describe('Plugin not configured', () => {
@@ -91,15 +88,21 @@ describe('<CredentialPluginPrompt />', () => {
const onClose = jest.fn(); const onClose = jest.fn();
const onSubmit = jest.fn(); const onSubmit = jest.fn();
beforeAll(async () => { beforeAll(async () => {
CredentialsAPI.test.mockResolvedValue({});
CredentialsAPI.read.mockResolvedValue(mockCredentialResults);
CredentialsAPI.readOptions.mockResolvedValue(mockCredentialOptions);
CredentialTypesAPI.readDetail = async () => mockCredentialTypeDetail;
await act(async () => { await act(async () => {
wrapper = mountWithContexts( wrapper = mountWithContexts(
<CredentialPluginPrompt onClose={onClose} onSubmit={onSubmit} /> <CredentialPluginPrompt onClose={onClose} onSubmit={onSubmit} />
); );
}); });
}); });
afterAll(() => { afterAll(() => {
wrapper.unmount(); wrapper.unmount();
}); });
test('should render Wizard with all steps', async () => { test('should render Wizard with all steps', async () => {
const wizard = await waitForElement(wrapper, 'Wizard'); const wizard = await waitForElement(wrapper, 'Wizard');
const steps = wizard.prop('steps'); const steps = wizard.prop('steps');
@@ -108,6 +111,7 @@ describe('<CredentialPluginPrompt />', () => {
expect(steps[0].name).toEqual('Credential'); expect(steps[0].name).toEqual('Credential');
expect(steps[1].name).toEqual('Metadata'); expect(steps[1].name).toEqual('Metadata');
}); });
test('credentials step renders correctly', () => { test('credentials step renders correctly', () => {
expect(wrapper.find('CredentialsStep').length).toBe(1); expect(wrapper.find('CredentialsStep').length).toBe(1);
expect(wrapper.find('DataListItem').length).toBe(3); expect(wrapper.find('DataListItem').length).toBe(3);
@@ -182,6 +186,11 @@ describe('<CredentialPluginPrompt />', () => {
const onClose = jest.fn(); const onClose = jest.fn();
const onSubmit = jest.fn(); const onSubmit = jest.fn();
beforeAll(async () => { beforeAll(async () => {
jest.resetAllMocks();
CredentialsAPI.test.mockResolvedValue({});
CredentialsAPI.read.mockResolvedValue(mockCredentialResults);
CredentialsAPI.readOptions.mockResolvedValue(mockCredentialOptions);
CredentialTypesAPI.readDetail = async () => mockCredentialTypeDetail;
await act(async () => { await act(async () => {
wrapper = mountWithContexts( wrapper = mountWithContexts(
<CredentialPluginPrompt <CredentialPluginPrompt

View File

@@ -36,12 +36,6 @@ const credentialTypeData = {
}), }),
}; };
CredentialTypesAPI.create.mockResolvedValue({
data: {
id: 42,
},
});
describe('<CredentialTypeAdd/>', () => { describe('<CredentialTypeAdd/>', () => {
let wrapper; let wrapper;
let history; let history;
@@ -50,6 +44,11 @@ describe('<CredentialTypeAdd/>', () => {
history = createMemoryHistory({ history = createMemoryHistory({
initialEntries: ['/credential_types'], initialEntries: ['/credential_types'],
}); });
CredentialTypesAPI.create.mockResolvedValue({
data: {
id: 42,
},
});
await act(async () => { await act(async () => {
wrapper = mountWithContexts(<CredentialTypeAdd />, { wrapper = mountWithContexts(<CredentialTypeAdd />, {
context: { router: { history } }, context: { router: { history } },
@@ -59,7 +58,6 @@ describe('<CredentialTypeAdd/>', () => {
afterEach(() => { afterEach(() => {
jest.clearAllMocks(); jest.clearAllMocks();
wrapper.unmount();
}); });
test('handleSubmit should call the api and redirect to details page', async () => { test('handleSubmit should call the api and redirect to details page', async () => {

View File

@@ -67,7 +67,6 @@ function expectDetailToMatch(wrapper, label, value) {
describe('<CredentialTypeDetails/>', () => { describe('<CredentialTypeDetails/>', () => {
let wrapper; let wrapper;
afterEach(() => { afterEach(() => {
wrapper.unmount();
jest.clearAllMocks(); jest.clearAllMocks();
}); });
test('should render details properly', async () => { test('should render details properly', async () => {

View File

@@ -83,7 +83,6 @@ describe('<CredentialTypeEdit>', () => {
afterAll(() => { afterAll(() => {
jest.clearAllMocks(); jest.clearAllMocks();
wrapper.unmount();
}); });
test('handleSubmit should call the api and redirect to details page', async () => { test('handleSubmit should call the api and redirect to details page', async () => {

View File

@@ -43,6 +43,12 @@ const options = { data: { actions: { POST: true } } };
describe('<CredentialTypeList', () => { describe('<CredentialTypeList', () => {
let wrapper; let wrapper;
beforeEach(() => {
CredentialsAPI.read.mockResolvedValue({ data: { count: 0 } });
CredentialTypesAPI.read.mockResolvedValue(credentialTypes);
CredentialTypesAPI.readOptions.mockResolvedValue(options);
});
test('should mount successfully', async () => { test('should mount successfully', async () => {
await act(async () => { await act(async () => {
wrapper = mountWithContexts(<CredentialTypeList />); wrapper = mountWithContexts(<CredentialTypeList />);
@@ -57,9 +63,6 @@ describe('<CredentialTypeList', () => {
}); });
test('should have data fetched and render 2 rows', async () => { test('should have data fetched and render 2 rows', async () => {
CredentialTypesAPI.read.mockResolvedValue(credentialTypes);
CredentialTypesAPI.readOptions.mockResolvedValue(options);
await act(async () => { await act(async () => {
wrapper = mountWithContexts(<CredentialTypeList />); wrapper = mountWithContexts(<CredentialTypeList />);
}); });
@@ -71,9 +74,6 @@ describe('<CredentialTypeList', () => {
test('should delete item successfully', async () => { test('should delete item successfully', async () => {
CredentialTypesAPI.read.mockResolvedValue(credentialTypes); CredentialTypesAPI.read.mockResolvedValue(credentialTypes);
CredentialTypesAPI.readOptions.mockResolvedValue(options);
CredentialsAPI.read.mockResolvedValue({ data: { count: 0 } });
await act(async () => { await act(async () => {
wrapper = mountWithContexts(<CredentialTypeList />); wrapper = mountWithContexts(<CredentialTypeList />);
}); });
@@ -108,7 +108,19 @@ describe('<CredentialTypeList', () => {
); );
}); });
test('should not render add button', async () => {
CredentialTypesAPI.readOptions.mockResolvedValue({
data: { actions: { POST: false } },
});
await act(async () => {
wrapper = mountWithContexts(<CredentialTypeList />);
});
waitForElement(wrapper, 'CredentialTypeList', el => el.length > 0);
expect(wrapper.find('ToolbarAddButton').length).toBe(0);
});
test('should thrown content error', async () => { test('should thrown content error', async () => {
CredentialTypesAPI.destroy = jest.fn();
CredentialTypesAPI.read.mockRejectedValue( CredentialTypesAPI.read.mockRejectedValue(
new Error({ new Error({
response: { response: {
@@ -120,7 +132,6 @@ describe('<CredentialTypeList', () => {
}, },
}) })
); );
CredentialTypesAPI.readOptions.mockResolvedValue(options);
await act(async () => { await act(async () => {
wrapper = mountWithContexts(<CredentialTypeList />); wrapper = mountWithContexts(<CredentialTypeList />);
}); });
@@ -129,6 +140,7 @@ describe('<CredentialTypeList', () => {
}); });
test('should render deletion error modal', async () => { test('should render deletion error modal', async () => {
CredentialTypesAPI.destroy = jest.fn();
CredentialTypesAPI.destroy.mockRejectedValue( CredentialTypesAPI.destroy.mockRejectedValue(
new Error({ new Error({
response: { response: {
@@ -140,8 +152,6 @@ describe('<CredentialTypeList', () => {
}, },
}) })
); );
CredentialTypesAPI.read.mockResolvedValue(credentialTypes);
CredentialTypesAPI.readOptions.mockResolvedValue(options);
await act(async () => { await act(async () => {
wrapper = mountWithContexts(<CredentialTypeList />); wrapper = mountWithContexts(<CredentialTypeList />);
}); });
@@ -172,16 +182,4 @@ describe('<CredentialTypeList', () => {
wrapper.update(); wrapper.update();
expect(wrapper.find('ErrorDetail').length).toBe(1); expect(wrapper.find('ErrorDetail').length).toBe(1);
}); });
test('should not render add button', async () => {
CredentialTypesAPI.read.mockResolvedValue(credentialTypes);
CredentialTypesAPI.readOptions.mockResolvedValue({
data: { actions: { POST: false } },
});
await act(async () => {
wrapper = mountWithContexts(<CredentialTypeList />);
});
waitForElement(wrapper, 'CredentialTypeList', el => el.length > 0);
expect(wrapper.find('ToolbarAddButton').length).toBe(0);
});
}); });

View File

@@ -79,8 +79,7 @@ describe('<CredentialTypeForm/>', () => {
}); });
afterEach(() => { afterEach(() => {
jest.clearAllMocks(); jest.resetAllMocks();
wrapper.unmount();
}); });
test('Initially renders successfully', () => { test('Initially renders successfully', () => {
@@ -106,7 +105,7 @@ describe('<CredentialTypeForm/>', () => {
expect(onSubmit).toHaveBeenCalledTimes(1); expect(onSubmit).toHaveBeenCalledTimes(1);
}); });
test('should update form values', () => { test('should update form values', async () => {
act(() => { act(() => {
wrapper.find('input#credential-type-name').simulate('change', { wrapper.find('input#credential-type-name').simulate('change', {
target: { value: 'Foo', name: 'name' }, target: { value: 'Foo', name: 'name' },
@@ -115,7 +114,7 @@ describe('<CredentialTypeForm/>', () => {
target: { value: 'New description', name: 'description' }, target: { value: 'New description', name: 'description' },
}); });
}); });
wrapper.update(); await act(async () => wrapper.update());
expect(wrapper.find('input#credential-type-name').prop('value')).toEqual( expect(wrapper.find('input#credential-type-name').prop('value')).toEqual(
'Foo' 'Foo'
); );

View File

@@ -80,6 +80,12 @@ describe('<ExecutionEnvironmentAdd/>', () => {
history = createMemoryHistory({ history = createMemoryHistory({
initialEntries: ['/execution_environments'], initialEntries: ['/execution_environments'],
}); });
ExecutionEnvironmentsAPI.readOptions.mockResolvedValue(mockOptions);
ExecutionEnvironmentsAPI.create.mockResolvedValue({
data: {
id: 42,
},
});
await act(async () => { await act(async () => {
wrapper = mountWithContexts(<ExecutionEnvironmentAdd me={mockMe} />, { wrapper = mountWithContexts(<ExecutionEnvironmentAdd me={mockMe} />, {
context: { router: { history } }, context: { router: { history } },

View File

@@ -14,13 +14,6 @@ const hostData = {
variables: '---\nfoo: bar', variables: '---\nfoo: bar',
}; };
HostsAPI.create.mockResolvedValue({
data: {
...hostData,
id: 5,
},
});
describe('<HostAdd />', () => { describe('<HostAdd />', () => {
let wrapper; let wrapper;
let history; let history;
@@ -30,6 +23,12 @@ describe('<HostAdd />', () => {
initialEntries: ['/templates/job_templates/1/survey/edit/foo'], initialEntries: ['/templates/job_templates/1/survey/edit/foo'],
state: { some: 'state' }, state: { some: 'state' },
}); });
HostsAPI.create.mockResolvedValue({
data: {
...hostData,
id: 5,
},
});
await act(async () => { await act(async () => {
wrapper = mountWithContexts(<HostAdd />, { wrapper = mountWithContexts(<HostAdd />, {
context: { router: { history } }, context: { router: { history } },
@@ -39,7 +38,6 @@ describe('<HostAdd />', () => {
afterEach(() => { afterEach(() => {
jest.clearAllMocks(); jest.clearAllMocks();
wrapper.unmount();
}); });
test('handleSubmit should post to api', async () => { test('handleSubmit should post to api', async () => {

View File

@@ -19,10 +19,6 @@ describe('<HostDetail />', () => {
wrapper = mountWithContexts(<HostDetail host={mockHost} />); wrapper = mountWithContexts(<HostDetail host={mockHost} />);
}); });
afterAll(() => {
wrapper.unmount();
});
test('should render Details', async () => { test('should render Details', async () => {
function assertDetail(label, value) { function assertDetail(label, value) {
expect(wrapper.find(`Detail[label="${label}"] dt`).text()).toBe(label); expect(wrapper.find(`Detail[label="${label}"] dt`).text()).toBe(label);
@@ -81,10 +77,6 @@ describe('<HostDetail />', () => {
wrapper = mountWithContexts(<HostDetail host={mockHost} />); wrapper = mountWithContexts(<HostDetail host={mockHost} />);
}); });
afterAll(() => {
wrapper.unmount();
});
test('should hide activity stream when there are no recent jobs', async () => { test('should hide activity stream when there are no recent jobs', async () => {
expect(wrapper.find(`Detail[label="Activity"] Sparkline`)).toHaveLength( expect(wrapper.find(`Detail[label="Activity"] Sparkline`)).toHaveLength(
0 0

View File

@@ -29,7 +29,6 @@ describe('<HostEdit />', () => {
afterAll(() => { afterAll(() => {
jest.clearAllMocks(); jest.clearAllMocks();
wrapper.unmount();
}); });
test('handleSubmit should call api update', async () => { test('handleSubmit should call api update', async () => {

View File

@@ -31,7 +31,6 @@ describe('<HostFacts />', () => {
afterEach(() => { afterEach(() => {
jest.clearAllMocks(); jest.clearAllMocks();
wrapper.unmount();
}); });
test('initially renders successfully ', () => { test('initially renders successfully ', () => {

View File

@@ -10,7 +10,7 @@ jest.mock('react-router-dom', () => ({
})); }));
describe('<Hosts />', () => { describe('<Hosts />', () => {
test('initially renders succesfully', () => { test('initially renders successfully', () => {
mountWithContexts(<Hosts />); mountWithContexts(<Hosts />);
}); });

View File

@@ -37,18 +37,6 @@ const instanceGroupCreateData = {
'apiVersion: v1\nkind: Pod\nmetadata:\n namespace: default\nspec:\n containers:\n - image: ansible/ansible-runner\n tty: true\n stdin: true\n imagePullPolicy: Always\n args:\n - sleep\n - infinity\n - test', 'apiVersion: v1\nkind: Pod\nmetadata:\n namespace: default\nspec:\n containers:\n - image: ansible/ansible-runner\n tty: true\n stdin: true\n imagePullPolicy: Always\n args:\n - sleep\n - infinity\n - test',
}; };
InstanceGroupsAPI.create.mockResolvedValue({
data: {
id: 123,
},
});
InstanceGroupsAPI.readOptions.mockResolvedValue({
data: {
results: initialPodSpec,
},
});
describe('<ContainerGroupAdd/>', () => { describe('<ContainerGroupAdd/>', () => {
let wrapper; let wrapper;
let history; let history;
@@ -58,6 +46,18 @@ describe('<ContainerGroupAdd/>', () => {
initialEntries: ['/instance_groups'], initialEntries: ['/instance_groups'],
}); });
InstanceGroupsAPI.create.mockResolvedValue({
data: {
id: 123,
},
});
InstanceGroupsAPI.readOptions.mockResolvedValue({
data: {
results: initialPodSpec,
},
});
await act(async () => { await act(async () => {
wrapper = mountWithContexts(<ContainerGroupAdd />, { wrapper = mountWithContexts(<ContainerGroupAdd />, {
context: { router: { history } }, context: { router: { history } },

View File

@@ -41,12 +41,6 @@ const instanceGroupData = {
}, },
}; };
InstanceGroupsAPI.create.mockResolvedValue({
data: {
id: 42,
},
});
describe('<InstanceGroupAdd/>', () => { describe('<InstanceGroupAdd/>', () => {
let wrapper; let wrapper;
let history; let history;
@@ -55,6 +49,11 @@ describe('<InstanceGroupAdd/>', () => {
history = createMemoryHistory({ history = createMemoryHistory({
initialEntries: ['/instance_groups'], initialEntries: ['/instance_groups'],
}); });
InstanceGroupsAPI.create.mockResolvedValue({
data: {
id: 42,
},
});
await act(async () => { await act(async () => {
wrapper = mountWithContexts(<InstanceGroupAdd />, { wrapper = mountWithContexts(<InstanceGroupAdd />, {
context: { router: { history } }, context: { router: { history } },

View File

@@ -52,17 +52,19 @@ const instanceGroups = {
}; };
const options = { data: { actions: { POST: true } } }; const options = { data: { actions: { POST: true } } };
OrganizationsAPI.read.mockResolvedValue({ data: { count: 0 } });
InventoriesAPI.read.mockResolvedValue({ data: { count: 0 } });
UnifiedJobTemplatesAPI.read.mockResolvedValue({ data: { count: 0 } });
describe('<InstanceGroupList />', () => { describe('<InstanceGroupList />', () => {
let wrapper; let wrapper;
test('should have data fetched and render 3 rows', async () => { beforeEach(() => {
OrganizationsAPI.read.mockResolvedValue({ data: { count: 0 } });
InventoriesAPI.read.mockResolvedValue({ data: { count: 0 } });
UnifiedJobTemplatesAPI.read.mockResolvedValue({ data: { count: 0 } });
InstanceGroupsAPI.read.mockResolvedValue(instanceGroups); InstanceGroupsAPI.read.mockResolvedValue(instanceGroups);
InstanceGroupsAPI.readOptions.mockResolvedValue(options); InstanceGroupsAPI.readOptions.mockResolvedValue(options);
});
test('should have data fetched and render 3 rows', async () => {
await act(async () => { await act(async () => {
wrapper = mountWithContexts(<InstanceGroupList />); wrapper = mountWithContexts(<InstanceGroupList />);
}); });
@@ -73,9 +75,6 @@ describe('<InstanceGroupList />', () => {
}); });
test('should delete item successfully', async () => { test('should delete item successfully', async () => {
InstanceGroupsAPI.read.mockResolvedValue(instanceGroups);
InstanceGroupsAPI.readOptions.mockResolvedValue(options);
await act(async () => { await act(async () => {
wrapper = mountWithContexts(<InstanceGroupList />); wrapper = mountWithContexts(<InstanceGroupList />);
}); });
@@ -111,9 +110,6 @@ describe('<InstanceGroupList />', () => {
}); });
test('should not be able to delete tower instance group', async () => { test('should not be able to delete tower instance group', async () => {
InstanceGroupsAPI.read.mockResolvedValue(instanceGroups);
InstanceGroupsAPI.readOptions.mockResolvedValue(options);
await act(async () => { await act(async () => {
wrapper = mountWithContexts(<InstanceGroupList />); wrapper = mountWithContexts(<InstanceGroupList />);
}); });
@@ -144,6 +140,7 @@ describe('<InstanceGroupList />', () => {
}); });
test('should thrown content error', async () => { test('should thrown content error', async () => {
InstanceGroupsAPI.read = jest.fn();
InstanceGroupsAPI.read.mockRejectedValue( InstanceGroupsAPI.read.mockRejectedValue(
new Error({ new Error({
response: { response: {
@@ -155,7 +152,6 @@ describe('<InstanceGroupList />', () => {
}, },
}) })
); );
InstanceGroupsAPI.readOptions.mockResolvedValue(options);
await act(async () => { await act(async () => {
wrapper = mountWithContexts(<InstanceGroupList />); wrapper = mountWithContexts(<InstanceGroupList />);
}); });
@@ -165,6 +161,7 @@ describe('<InstanceGroupList />', () => {
test('should render deletion error modal', async () => { test('should render deletion error modal', async () => {
jest.setTimeout(5000 * 4); jest.setTimeout(5000 * 4);
InstanceGroupsAPI.destroy = jest.fn();
InstanceGroupsAPI.destroy.mockRejectedValue( InstanceGroupsAPI.destroy.mockRejectedValue(
new Error({ new Error({
response: { response: {
@@ -176,8 +173,6 @@ describe('<InstanceGroupList />', () => {
}, },
}) })
); );
InstanceGroupsAPI.read.mockResolvedValue(instanceGroups);
InstanceGroupsAPI.readOptions.mockResolvedValue(options);
await act(async () => { await act(async () => {
wrapper = mountWithContexts(<InstanceGroupList />); wrapper = mountWithContexts(<InstanceGroupList />);
}); });

View File

@@ -111,7 +111,7 @@ describe('<ContainerGroupForm/>', () => {
expect(wrapper.find('CredentialLookup').prop('value').name).toBe('test'); expect(wrapper.find('CredentialLookup').prop('value').name).toBe('test');
}); });
test('should update form values', () => { test('should update form values', async () => {
act(() => { act(() => {
wrapper.find('CredentialLookup').invoke('onBlur')(); wrapper.find('CredentialLookup').invoke('onBlur')();
wrapper.find('CredentialLookup').invoke('onChange')({ wrapper.find('CredentialLookup').invoke('onChange')({
@@ -122,7 +122,9 @@ describe('<ContainerGroupForm/>', () => {
target: { value: 'new Foo', name: 'name' }, target: { value: 'new Foo', name: 'name' },
}); });
}); });
wrapper.update(); await act(async () => {
wrapper.update();
});
expect(wrapper.find('CredentialLookup').prop('value')).toEqual({ expect(wrapper.find('CredentialLookup').prop('value')).toEqual({
id: 99, id: 99,
name: 'credential', name: 'credential',

View File

@@ -85,7 +85,7 @@ describe('<InstanceGroupForm/>', () => {
expect(onSubmit).toHaveBeenCalledTimes(1); expect(onSubmit).toHaveBeenCalledTimes(1);
}); });
test('should update form values', () => { test('should update form values', async () => {
act(() => { act(() => {
wrapper.find('input#instance-group-name').simulate('change', { wrapper.find('input#instance-group-name').simulate('change', {
target: { value: 'Foo', name: 'name' }, target: { value: 'Foo', name: 'name' },
@@ -96,7 +96,9 @@ describe('<InstanceGroupForm/>', () => {
target: { value: 10, name: 'policy_instance_minimum' }, target: { value: 10, name: 'policy_instance_minimum' },
}); });
}); });
wrapper.update(); await act(async () => {
wrapper.update();
});
expect(wrapper.find('input#instance-group-name').prop('value')).toEqual( expect(wrapper.find('input#instance-group-name').prop('value')).toEqual(
'Foo' 'Foo'
); );

View File

@@ -18,14 +18,16 @@ jest.mock('react-router-dom', () => ({
}), }),
})); }));
InventoriesAPI.readDetail.mockResolvedValue({
data: mockInventory,
});
describe('<Inventory />', () => { describe('<Inventory />', () => {
let wrapper; let wrapper;
test('initially renders succesfully', async () => { beforeEach(async () => {
InventoriesAPI.readDetail.mockResolvedValue({
data: mockInventory,
});
});
test('initially renders successfully', async () => {
await act(async () => { await act(async () => {
wrapper = mountWithContexts(<Inventory setBreadcrumb={() => {}} />); wrapper = mountWithContexts(<Inventory setBreadcrumb={() => {}} />);
}); });

View File

@@ -12,24 +12,23 @@ import InventoryAdd from './InventoryAdd';
jest.mock('../../../api'); jest.mock('../../../api');
CredentialTypesAPI.read.mockResolvedValue({
data: {
results: [
{
id: 14,
name: 'insights',
},
],
},
});
InventoriesAPI.create.mockResolvedValue({ data: { id: 13 } });
describe('<InventoryAdd />', () => { describe('<InventoryAdd />', () => {
let wrapper; let wrapper;
let history; let history;
beforeEach(async () => { beforeEach(async () => {
history = createMemoryHistory({ initialEntries: ['/inventories'] }); history = createMemoryHistory({ initialEntries: ['/inventories'] });
CredentialTypesAPI.read.mockResolvedValue({
data: {
results: [
{
id: 14,
name: 'insights',
},
],
},
});
InventoriesAPI.create.mockResolvedValue({ data: { id: 13 } });
await act(async () => { await act(async () => {
wrapper = mountWithContexts(<InventoryAdd />, { wrapper = mountWithContexts(<InventoryAdd />, {
context: { router: { history } }, context: { router: { history } },
@@ -37,10 +36,6 @@ describe('<InventoryAdd />', () => {
}); });
}); });
afterEach(() => {
wrapper.unmount();
});
test('Initially renders successfully', () => { test('Initially renders successfully', () => {
expect(wrapper.length).toBe(1); expect(wrapper.length).toBe(1);
}); });

View File

@@ -47,16 +47,6 @@ const mockInventory = {
pending_deletion: false, pending_deletion: false,
}; };
CredentialTypesAPI.read.mockResolvedValue({
data: {
results: [
{
id: 14,
name: 'insights',
},
],
},
});
const associatedInstanceGroups = [ const associatedInstanceGroups = [
{ {
id: 1, id: 1,
@@ -71,6 +61,18 @@ function expectDetailToMatch(wrapper, label, value) {
} }
describe('<InventoryDetail />', () => { describe('<InventoryDetail />', () => {
beforeEach(async () => {
CredentialTypesAPI.read.mockResolvedValue({
data: {
results: [
{
id: 14,
name: 'insights',
},
],
},
});
});
test('should render details', async () => { test('should render details', async () => {
InventoriesAPI.readInstanceGroups.mockResolvedValue({ InventoriesAPI.readInstanceGroups.mockResolvedValue({
data: { data: {
@@ -90,11 +92,11 @@ describe('<InventoryDetail />', () => {
expectDetailToMatch(wrapper, 'Type', 'Inventory'); expectDetailToMatch(wrapper, 'Type', 'Inventory');
const org = wrapper.find('Detail[label="Organization"]'); const org = wrapper.find('Detail[label="Organization"]');
expect(org.prop('value')).toMatchInlineSnapshot(` expect(org.prop('value')).toMatchInlineSnapshot(`
<ForwardRef <Link
to="/organizations/1/details" to="/organizations/1/details"
> >
The Organization The Organization
</ForwardRef> </Link>
`); `);
const vars = wrapper.find('VariablesDetail'); const vars = wrapper.find('VariablesDetail');
expect(vars).toHaveLength(1); expect(vars).toHaveLength(1);

View File

@@ -53,33 +53,33 @@ const mockInventory = {
pending_deletion: false, pending_deletion: false,
}; };
CredentialTypesAPI.read.mockResolvedValue({
data: {
results: [
{
id: 14,
name: 'insights',
},
],
},
});
const associatedInstanceGroups = [ const associatedInstanceGroups = [
{ {
id: 1, id: 1,
name: 'Foo', name: 'Foo',
}, },
]; ];
InventoriesAPI.readInstanceGroups.mockResolvedValue({
data: {
results: associatedInstanceGroups,
},
});
describe('<InventoryEdit />', () => { describe('<InventoryEdit />', () => {
let wrapper; let wrapper;
let history; let history;
beforeEach(async () => { beforeEach(async () => {
CredentialTypesAPI.read.mockResolvedValue({
data: {
results: [
{
id: 14,
name: 'insights',
},
],
},
});
InventoriesAPI.readInstanceGroups.mockResolvedValue({
data: {
results: associatedInstanceGroups,
},
});
history = createMemoryHistory({ initialEntries: ['/inventories'] }); history = createMemoryHistory({ initialEntries: ['/inventories'] });
await act(async () => { await act(async () => {
wrapper = mountWithContexts(<InventoryEdit inventory={mockInventory} />, { wrapper = mountWithContexts(<InventoryEdit inventory={mockInventory} />, {
@@ -88,10 +88,6 @@ describe('<InventoryEdit />', () => {
}); });
}); });
afterEach(() => {
wrapper.unmount();
});
test('initially renders successfully', async () => { test('initially renders successfully', async () => {
expect(wrapper.find('InventoryEdit').length).toBe(1); expect(wrapper.find('InventoryEdit').length).toBe(1);
}); });

View File

@@ -19,28 +19,27 @@ jest.mock('react-router-dom', () => ({
}), }),
})); }));
GroupsAPI.readDetail.mockResolvedValue({
data: {
id: 1,
name: 'Foo',
description: 'Bar',
variables: 'bizz: buzz',
summary_fields: {
inventory: { id: 1 },
created_by: { id: 1, username: 'Athena' },
modified_by: { id: 1, username: 'Apollo' },
},
created: '2020-04-25T01:23:45.678901Z',
modified: '2020-04-25T01:23:45.678901Z',
},
});
describe('<InventoryGroup />', () => { describe('<InventoryGroup />', () => {
let wrapper; let wrapper;
let history; let history;
const inventory = { id: 1, name: 'Foo' }; const inventory = { id: 1, name: 'Foo' };
beforeEach(async () => { beforeEach(async () => {
GroupsAPI.readDetail.mockResolvedValue({
data: {
id: 1,
name: 'Foo',
description: 'Bar',
variables: 'bizz: buzz',
summary_fields: {
inventory: { id: 1 },
created_by: { id: 1, username: 'Athena' },
modified_by: { id: 1, username: 'Apollo' },
},
created: '2020-04-25T01:23:45.678901Z',
modified: '2020-04-25T01:23:45.678901Z',
},
});
history = createMemoryHistory({ history = createMemoryHistory({
initialEntries: ['/inventories/inventory/1/groups/1/details'], initialEntries: ['/inventories/inventory/1/groups/1/details'],
}); });
@@ -55,10 +54,6 @@ describe('<InventoryGroup />', () => {
await waitForElement(wrapper, 'ContentLoading', el => el.length === 0); await waitForElement(wrapper, 'ContentLoading', el => el.length === 0);
}); });
afterEach(() => {
wrapper.unmount();
});
test('renders successfully', async () => { test('renders successfully', async () => {
expect(wrapper.length).toBe(1); expect(wrapper.length).toBe(1);
}); });

View File

@@ -30,9 +30,6 @@ describe('<InventoryGroupAdd />', () => {
); );
}); });
}); });
afterEach(() => {
wrapper.unmount();
});
test('InventoryGroupAdd renders successfully', () => { test('InventoryGroupAdd renders successfully', () => {
expect(wrapper.length).toBe(1); expect(wrapper.length).toBe(1);
}); });

View File

@@ -64,7 +64,6 @@ describe('<InventoryGroupDetail />', () => {
}); });
}); });
afterEach(() => { afterEach(() => {
wrapper.unmount();
jest.clearAllMocks(); jest.clearAllMocks();
}); });
test('InventoryGroupDetail renders successfully', () => { test('InventoryGroupDetail renders successfully', () => {
@@ -150,8 +149,6 @@ describe('<InventoryGroupDetail />', () => {
expect(wrapper.find('button[aria-label="Edit"]').length).toBe(0); expect(wrapper.find('button[aria-label="Edit"]').length).toBe(0);
expect(wrapper.find('button[aria-label="Delete"]').length).toBe(0); expect(wrapper.find('button[aria-label="Delete"]').length).toBe(0);
wrapper.unmount();
}); });
}); });
}); });

View File

@@ -8,17 +8,18 @@ import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
import InventoryGroupEdit from './InventoryGroupEdit'; import InventoryGroupEdit from './InventoryGroupEdit';
jest.mock('../../../api'); jest.mock('../../../api');
GroupsAPI.readDetail.mockResolvedValue({
data: {
name: 'Foo',
description: 'Bar',
variables: 'bizz: buzz',
},
});
describe('<InventoryGroupEdit />', () => { describe('<InventoryGroupEdit />', () => {
let wrapper; let wrapper;
let history; let history;
beforeEach(async () => { beforeEach(async () => {
GroupsAPI.readDetail.mockResolvedValue({
data: {
name: 'Foo',
description: 'Bar',
variables: 'bizz: buzz',
},
});
history = createMemoryHistory({ history = createMemoryHistory({
initialEntries: ['/inventories/inventory/1/groups/2/edit'], initialEntries: ['/inventories/inventory/1/groups/2/edit'],
}); });
@@ -37,9 +38,7 @@ describe('<InventoryGroupEdit />', () => {
); );
}); });
}); });
afterEach(() => {
wrapper.unmount();
});
test('InventoryGroupEdit renders successfully', () => { test('InventoryGroupEdit renders successfully', () => {
expect(wrapper.length).toBe(1); expect(wrapper.length).toBe(1);
}); });

View File

@@ -8,18 +8,19 @@ import { GroupsAPI } from '../../../api';
jest.mock('../../../api'); jest.mock('../../../api');
GroupsAPI.createHost.mockResolvedValue({
data: {
...mockHost,
},
});
describe('<InventoryGroupHostAdd />', () => { describe('<InventoryGroupHostAdd />', () => {
let wrapper; let wrapper;
let history; let history;
beforeAll(async () => { beforeEach(async () => {
history = createMemoryHistory(); history = createMemoryHistory();
GroupsAPI.createHost.mockResolvedValue({
data: {
...mockHost,
},
});
await act(async () => { await act(async () => {
wrapper = mountWithContexts( wrapper = mountWithContexts(
<InventoryGroupHostAdd inventoryGroup={{ id: 123, inventory: 3 }} />, <InventoryGroupHostAdd inventoryGroup={{ id: 123, inventory: 3 }} />,
@@ -32,7 +33,6 @@ describe('<InventoryGroupHostAdd />', () => {
afterAll(() => { afterAll(() => {
jest.clearAllMocks(); jest.clearAllMocks();
wrapper.unmount();
}); });
test('handleSubmit should post to api', async () => { test('handleSubmit should post to api', async () => {

View File

@@ -32,6 +32,10 @@ describe('<InventoryHost />', () => {
let history; let history;
beforeEach(async () => { beforeEach(async () => {
InventoriesAPI.readHostDetail.mockResolvedValue({
data: { ...mockHost },
});
await act(async () => { await act(async () => {
wrapper = mountWithContexts( wrapper = mountWithContexts(
<InventoryHost inventory={mockInventory} setBreadcrumb={() => {}} /> <InventoryHost inventory={mockInventory} setBreadcrumb={() => {}} />

View File

@@ -8,18 +8,17 @@ import { HostsAPI } from '../../../api';
jest.mock('../../../api'); jest.mock('../../../api');
HostsAPI.create.mockResolvedValue({
data: {
...mockHost,
},
});
describe('<InventoryHostAdd />', () => { describe('<InventoryHostAdd />', () => {
let wrapper; let wrapper;
let history; let history;
beforeAll(async () => { beforeEach(async () => {
history = createMemoryHistory(); history = createMemoryHistory();
HostsAPI.create.mockResolvedValue({
data: {
...mockHost,
},
});
await act(async () => { await act(async () => {
wrapper = mountWithContexts(<InventoryHostAdd inventory={{ id: 3 }} />, { wrapper = mountWithContexts(<InventoryHostAdd inventory={{ id: 3 }} />, {
context: { router: { history } }, context: { router: { history } },
@@ -29,7 +28,6 @@ describe('<InventoryHostAdd />', () => {
afterAll(() => { afterAll(() => {
jest.clearAllMocks(); jest.clearAllMocks();
wrapper.unmount();
}); });
test('handleSubmit should post to api', async () => { test('handleSubmit should post to api', async () => {

View File

@@ -18,10 +18,6 @@ describe('<InventoryHostDetail />', () => {
wrapper = mountWithContexts(<InventoryHostDetail host={mockHost} />); wrapper = mountWithContexts(<InventoryHostDetail host={mockHost} />);
}); });
afterAll(() => {
wrapper.unmount();
});
test('should render Details', async () => { test('should render Details', async () => {
function assertDetail(label, value) { function assertDetail(label, value) {
expect(wrapper.find(`Detail[label="${label}"] dt`).text()).toBe(label); expect(wrapper.find(`Detail[label="${label}"] dt`).text()).toBe(label);
@@ -91,10 +87,6 @@ describe('<InventoryHostDetail />', () => {
wrapper = mountWithContexts(<InventoryHostDetail host={readOnlyHost} />); wrapper = mountWithContexts(<InventoryHostDetail host={readOnlyHost} />);
}); });
afterAll(() => {
wrapper.unmount();
});
test('should hide activity stream when there are no recent jobs', async () => { test('should hide activity stream when there are no recent jobs', async () => {
expect(wrapper.find(`Detail[label="Activity"] Sparkline`)).toHaveLength( expect(wrapper.find(`Detail[label="Activity"] Sparkline`)).toHaveLength(
0 0

View File

@@ -32,7 +32,6 @@ describe('<InventoryHostEdit />', () => {
afterAll(() => { afterAll(() => {
jest.clearAllMocks(); jest.clearAllMocks();
wrapper.unmount();
}); });
test('handleSubmit should call api update', async () => { test('handleSubmit should call api update', async () => {

View File

@@ -9,7 +9,7 @@ import { HostsAPI } from '../../../api';
import mockHost from '../shared/data.host.json'; import mockHost from '../shared/data.host.json';
import mockHostFacts from '../shared/data.hostFacts.json'; import mockHostFacts from '../shared/data.hostFacts.json';
jest.mock('../../../api/models/Hosts'); jest.mock('../../../api');
jest.mock('react-router-dom', () => ({ jest.mock('react-router-dom', () => ({
...jest.requireActual('react-router-dom'), ...jest.requireActual('react-router-dom'),
useParams: () => ({ useParams: () => ({
@@ -31,7 +31,6 @@ describe('<InventoryHostFacts />', () => {
afterEach(() => { afterEach(() => {
jest.clearAllMocks(); jest.clearAllMocks();
wrapper.unmount();
}); });
test('initially renders successfully ', () => { test('initially renders successfully ', () => {

View File

@@ -100,7 +100,7 @@ describe('<InventoryHostList />', () => {
}); });
afterEach(() => { afterEach(() => {
jest.clearAllMocks(); jest.resetAllMocks();
}); });
test('initially renders successfully', () => { test('initially renders successfully', () => {

View File

@@ -4,15 +4,6 @@ import WS from 'jest-websocket-mock';
import { mountWithContexts } from '../../../../testUtils/enzymeHelpers'; import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
import useWsInventories from './useWsInventories'; import useWsInventories from './useWsInventories';
/*
Jest mock timers dont play well with jest-websocket-mock,
so we'll stub out throttling to resolve immediately
*/
jest.mock('../../../util/useThrottle', () => ({
__esModule: true,
default: jest.fn(val => val),
}));
function TestInner() { function TestInner() {
return <div />; return <div />;
} }
@@ -39,6 +30,14 @@ describe('useWsInventories hook', () => {
let debug; let debug;
let wrapper; let wrapper;
beforeEach(() => { beforeEach(() => {
/*
Jest mock timers dont play well with jest-websocket-mock,
so we'll stub out throttling to resolve immediately
*/
jest.mock('../../../util/useThrottle', () => ({
__esModule: true,
default: jest.fn(val => val),
}));
debug = global.console.debug; // eslint-disable-line prefer-destructuring debug = global.console.debug; // eslint-disable-line prefer-destructuring
global.console.debug = () => {}; global.console.debug = () => {};
}); });

View File

@@ -25,10 +25,6 @@ describe('<InventoryRelatedGroupAdd/>', () => {
wrapper = mountWithContexts(<InventoryRelatedGroupAdd />); wrapper = mountWithContexts(<InventoryRelatedGroupAdd />);
}); });
afterEach(() => {
wrapper.unmount();
});
test('should render properly', () => { test('should render properly', () => {
expect(wrapper.find('InventoryRelatedGroupAdd').length).toBe(1); expect(wrapper.find('InventoryRelatedGroupAdd').length).toBe(1);
}); });

View File

@@ -41,31 +41,33 @@ describe('<InventorySourceAdd />', () => {
organization: 2, organization: 2,
}; };
InventorySourcesAPI.readOptions.mockResolvedValue({ beforeEach(async () => {
data: { InventorySourcesAPI.readOptions.mockResolvedValue({
actions: { data: {
GET: { actions: {
source: { GET: {
choices: [ source: {
['file', 'File, Directory or Script'], choices: [
['scm', 'Sourced from a Project'], ['file', 'File, Directory or Script'],
['ec2', 'Amazon EC2'], ['scm', 'Sourced from a Project'],
['gce', 'Google Compute Engine'], ['ec2', 'Amazon EC2'],
['azure_rm', 'Microsoft Azure Resource Manager'], ['gce', 'Google Compute Engine'],
['vmware', 'VMware vCenter'], ['azure_rm', 'Microsoft Azure Resource Manager'],
['satellite6', 'Red Hat Satellite 6'], ['vmware', 'VMware vCenter'],
['openstack', 'OpenStack'], ['satellite6', 'Red Hat Satellite 6'],
['rhv', 'Red Hat Virtualization'], ['openstack', 'OpenStack'],
['tower', 'Ansible Tower'], ['rhv', 'Red Hat Virtualization'],
], ['tower', 'Ansible Tower'],
],
},
}, },
}, },
}, },
}, });
});
ProjectsAPI.readInventories.mockResolvedValue({ ProjectsAPI.readInventories.mockResolvedValue({
data: [], data: [],
});
}); });
afterEach(() => { afterEach(() => {

View File

@@ -13,36 +13,7 @@ import {
WorkflowJobTemplateNodesAPI, WorkflowJobTemplateNodesAPI,
} from '../../../api'; } from '../../../api';
jest.mock('../../../api/models/InventorySources'); jest.mock('../../../api');
jest.mock('../../../api/models/Inventories');
jest.mock('../../../api/models/WorkflowJobTemplateNodes');
InventoriesAPI.updateSources.mockResolvedValue({
data: [{ inventory_source: 1 }],
});
WorkflowJobTemplateNodesAPI.read.mockResolvedValue({ data: { count: 0 } });
InventorySourcesAPI.readOptions.mockResolvedValue({
data: {
actions: {
GET: {
source: {
choices: [
['file', 'File, Directory or Script'],
['scm', 'Sourced from a Project'],
['ec2', 'Amazon EC2'],
['gce', 'Google Compute Engine'],
['azure_rm', 'Microsoft Azure Resource Manager'],
['vmware', 'VMware vCenter'],
['satellite6', 'Red Hat Satellite 6'],
['openstack', 'OpenStack'],
['rhv', 'Red Hat Virtualization'],
['tower', 'Ansible Tower'],
],
},
},
},
},
});
function assertDetail(wrapper, label, value) { function assertDetail(wrapper, label, value) {
expect(wrapper.find(`Detail[label="${label}"] dt`).text()).toBe(label); expect(wrapper.find(`Detail[label="${label}"] dt`).text()).toBe(label);
@@ -52,8 +23,36 @@ function assertDetail(wrapper, label, value) {
describe('InventorySourceDetail', () => { describe('InventorySourceDetail', () => {
let wrapper; let wrapper;
beforeEach(async () => {
InventoriesAPI.updateSources.mockResolvedValue({
data: [{ inventory_source: 1 }],
});
WorkflowJobTemplateNodesAPI.read.mockResolvedValue({ data: { count: 0 } });
InventorySourcesAPI.readOptions.mockResolvedValue({
data: {
actions: {
GET: {
source: {
choices: [
['file', 'File, Directory or Script'],
['scm', 'Sourced from a Project'],
['ec2', 'Amazon EC2'],
['gce', 'Google Compute Engine'],
['azure_rm', 'Microsoft Azure Resource Manager'],
['vmware', 'VMware vCenter'],
['satellite6', 'Red Hat Satellite 6'],
['openstack', 'OpenStack'],
['rhv', 'Red Hat Virtualization'],
['tower', 'Ansible Tower'],
],
},
},
},
},
});
});
afterEach(() => { afterEach(() => {
wrapper.unmount();
jest.clearAllMocks(); jest.clearAllMocks();
}); });

View File

@@ -8,9 +8,7 @@ import {
import InventorySourceEdit from './InventorySourceEdit'; import InventorySourceEdit from './InventorySourceEdit';
import { CredentialsAPI, InventorySourcesAPI, ProjectsAPI } from '../../../api'; import { CredentialsAPI, InventorySourcesAPI, ProjectsAPI } from '../../../api';
jest.mock('../../../api/models/Projects'); jest.mock('../../../api');
jest.mock('../../../api/models/Credentials');
jest.mock('../../../api/models/InventorySources');
jest.mock('react-router-dom', () => ({ jest.mock('react-router-dom', () => ({
...jest.requireActual('react-router-dom'), ...jest.requireActual('react-router-dom'),
useParams: () => ({ useParams: () => ({
@@ -20,7 +18,6 @@ jest.mock('react-router-dom', () => ({
describe('<InventorySourceEdit />', () => { describe('<InventorySourceEdit />', () => {
let wrapper; let wrapper;
let history;
const mockInvSrc = { const mockInvSrc = {
id: 23, id: 23,
description: 'bar', description: 'bar',
@@ -42,57 +39,58 @@ describe('<InventorySourceEdit />', () => {
name: 'Foo', name: 'Foo',
organization: 1, organization: 1,
}; };
InventorySourcesAPI.readOptions.mockResolvedValue({ const history = createMemoryHistory();
data: {
actions: { beforeEach(async () => {
GET: { InventorySourcesAPI.readOptions.mockResolvedValue({
source: { data: {
choices: [ actions: {
['file', 'File, Directory or Script'], GET: {
['scm', 'Sourced from a Project'], source: {
['ec2', 'Amazon EC2'], choices: [
['gce', 'Google Compute Engine'], ['file', 'File, Directory or Script'],
['azure_rm', 'Microsoft Azure Resource Manager'], ['scm', 'Sourced from a Project'],
['vmware', 'VMware vCenter'], ['ec2', 'Amazon EC2'],
['satellite6', 'Red Hat Satellite 6'], ['gce', 'Google Compute Engine'],
['openstack', 'OpenStack'], ['azure_rm', 'Microsoft Azure Resource Manager'],
['rhv', 'Red Hat Virtualization'], ['vmware', 'VMware vCenter'],
['tower', 'Ansible Tower'], ['satellite6', 'Red Hat Satellite 6'],
], ['openstack', 'OpenStack'],
['rhv', 'Red Hat Virtualization'],
['tower', 'Ansible Tower'],
],
},
}, },
}, },
}, },
}, });
}); InventorySourcesAPI.replace.mockResolvedValue({
InventorySourcesAPI.replace.mockResolvedValue({ data: {
data: { ...mockInvSrc,
...mockInvSrc, },
}, });
}); ProjectsAPI.readInventories.mockResolvedValue({
ProjectsAPI.readInventories.mockResolvedValue({ data: [],
data: [], });
}); CredentialsAPI.read.mockResolvedValue({
CredentialsAPI.read.mockResolvedValue({ data: { count: 0, results: [] },
data: { count: 0, results: [] }, });
}); ProjectsAPI.read.mockResolvedValue({
ProjectsAPI.read.mockResolvedValue({ data: {
data: { count: 2,
count: 2, results: [
results: [ {
{ id: 1,
id: 1, name: 'mock proj one',
name: 'mock proj one', },
}, {
{ id: 2,
id: 2, name: 'mock proj two',
name: 'mock proj two', },
}, ],
], },
}, });
});
beforeAll(async () => {
history = createMemoryHistory();
await act(async () => { await act(async () => {
wrapper = mountWithContexts( wrapper = mountWithContexts(
<InventorySourceEdit inventory={mockInventory} source={mockInvSrc} />, <InventorySourceEdit inventory={mockInventory} source={mockInvSrc} />,
@@ -106,7 +104,6 @@ describe('<InventorySourceEdit />', () => {
afterAll(() => { afterAll(() => {
jest.clearAllMocks(); jest.clearAllMocks();
wrapper.unmount();
}); });
test('handleSubmit should call api update', async () => { test('handleSubmit should call api update', async () => {

View File

@@ -110,7 +110,6 @@ describe('<InventorySourceList />', () => {
}); });
}); });
afterEach(() => { afterEach(() => {
wrapper.unmount();
jest.clearAllMocks(); jest.clearAllMocks();
global.console.debug = debug; global.console.debug = debug;
}); });
@@ -366,7 +365,6 @@ describe('<InventorySourceList /> RBAC testing', () => {
el => el.length > 0 el => el.length > 0
); );
expect(newWrapper.find('ToolbarAddButton').length).toBe(0); expect(newWrapper.find('ToolbarAddButton').length).toBe(0);
newWrapper.unmount();
jest.clearAllMocks(); jest.clearAllMocks();
}); });
@@ -406,7 +404,6 @@ describe('<InventorySourceList /> RBAC testing', () => {
el => el.length > 0 el => el.length > 0
); );
expect(newWrapper.find('Button[aria-label="Sync All"]').length).toBe(0); expect(newWrapper.find('Button[aria-label="Sync All"]').length).toBe(0);
newWrapper.unmount();
jest.clearAllMocks(); jest.clearAllMocks();
}); });
}); });

View File

@@ -26,7 +26,7 @@ describe('<SmartInventory />', () => {
jest.clearAllMocks(); jest.clearAllMocks();
}); });
test('initially renders succesfully', async () => { test('initially renders successfully', async () => {
InventoriesAPI.readDetail.mockResolvedValue({ InventoriesAPI.readDetail.mockResolvedValue({
data: mockSmartInventory, data: mockSmartInventory,
}); });

View File

@@ -19,11 +19,7 @@ jest.mock('react-router-dom', () => ({
}), }),
})); }));
jest.mock('../../../api/models/Inventories'); jest.mock('../../../api');
jest.mock('../../../api/models/Organizations');
jest.mock('../../../api/models/InstanceGroups');
OrganizationsAPI.read.mockResolvedValue({ data: { results: [], count: 0 } });
InstanceGroupsAPI.read.mockResolvedValue({ data: { results: [], count: 0 } });
const formData = { const formData = {
name: 'Mock', name: 'Mock',
@@ -36,18 +32,34 @@ const formData = {
}; };
describe('<SmartInventoryAdd />', () => { describe('<SmartInventoryAdd />', () => {
describe('when initialized by users with POST capability', () => { const history = createMemoryHistory({
let history; initialEntries: [`/inventories/smart_inventory/add`],
let wrapper; });
beforeAll(async () => { describe('when initialized by users with POST capability', () => {
let wrapper;
let consoleError;
beforeAll(() => {
consoleError = global.console.error;
global.console.error = jest.fn();
});
beforeEach(async () => {
OrganizationsAPI.read.mockResolvedValue({
data: { results: [], count: 0 },
});
InstanceGroupsAPI.read.mockResolvedValue({
data: { results: [], count: 0 },
});
InventoriesAPI.create.mockResolvedValueOnce({ data: { id: 1 } }); InventoriesAPI.create.mockResolvedValueOnce({ data: { id: 1 } });
InventoriesAPI.readOptions.mockResolvedValue({ InventoriesAPI.readOptions.mockResolvedValue({
data: { actions: { POST: true } }, data: { actions: { POST: true } },
}); });
history = createMemoryHistory({ InstanceGroupsAPI.read.mockResolvedValue({
initialEntries: [`/inventories/smart_inventory/add`], data: { results: [], count: 0 },
}); });
await act(async () => { await act(async () => {
wrapper = mountWithContexts(<SmartInventoryAdd />, { wrapper = mountWithContexts(<SmartInventoryAdd />, {
context: { router: { history } }, context: { router: { history } },
@@ -57,8 +69,8 @@ describe('<SmartInventoryAdd />', () => {
}); });
afterAll(() => { afterAll(() => {
global.console.error = consoleError;
jest.clearAllMocks(); jest.clearAllMocks();
wrapper.unmount();
}); });
test('should enable save button', () => { test('should enable save button', () => {
@@ -116,18 +128,31 @@ describe('<SmartInventoryAdd />', () => {
describe('when initialized by users without POST capability', () => { describe('when initialized by users without POST capability', () => {
let wrapper; let wrapper;
beforeAll(async () => {
InventoriesAPI.readOptions.mockResolvedValueOnce({
data: { actions: { POST: false } },
});
await act(async () => {
wrapper = mountWithContexts(<SmartInventoryAdd />);
});
await waitForElement(wrapper, 'ContentLoading', el => el.length === 0);
});
afterAll(() => { afterAll(() => {
jest.clearAllMocks(); jest.clearAllMocks();
wrapper.unmount(); });
beforeEach(async () => {
OrganizationsAPI.read.mockResolvedValue({
data: { results: [], count: 0 },
});
InstanceGroupsAPI.read.mockResolvedValue({
data: { results: [], count: 0 },
});
InventoriesAPI.create.mockResolvedValueOnce({ data: { id: 1 } });
InventoriesAPI.readOptions.mockResolvedValue({
data: { actions: { POST: false } },
});
InstanceGroupsAPI.read.mockResolvedValue({
data: { results: [], count: 0 },
});
await act(async () => {
wrapper = mountWithContexts(<SmartInventoryAdd />, {
context: { router: { history } },
});
});
await waitForElement(wrapper, 'ContentLoading', el => el.length === 0);
}); });
test('should disable save button', () => { test('should disable save button', () => {

View File

@@ -9,32 +9,30 @@ import { InventoriesAPI, UnifiedJobsAPI } from '../../../api';
import mockSmartInventory from '../shared/data.smart_inventory.json'; import mockSmartInventory from '../shared/data.smart_inventory.json';
jest.mock('../../../api/models/UnifiedJobs'); jest.mock('../../../api');
jest.mock('../../../api/models/Inventories');
UnifiedJobsAPI.read.mockResolvedValue({
data: {
results: [
{
id: 1,
name: 'job 1',
type: 'job',
status: 'successful',
},
],
},
});
InventoriesAPI.readInstanceGroups.mockResolvedValue({
data: {
results: [{ id: 1, name: 'mock instance group' }],
},
});
describe('<SmartInventoryDetail />', () => { describe('<SmartInventoryDetail />', () => {
let wrapper; let wrapper;
describe('User has edit permissions', () => { describe('User has edit permissions', () => {
beforeAll(async () => { beforeEach(async () => {
UnifiedJobsAPI.read.mockResolvedValue({
data: {
results: [
{
id: 1,
name: 'job 1',
type: 'job',
status: 'successful',
},
],
},
});
InventoriesAPI.readInstanceGroups.mockResolvedValue({
data: {
results: [{ id: 1, name: 'mock instance group' }],
},
});
await act(async () => { await act(async () => {
wrapper = mountWithContexts( wrapper = mountWithContexts(
<SmartInventoryDetail inventory={mockSmartInventory} /> <SmartInventoryDetail inventory={mockSmartInventory} />
@@ -44,7 +42,6 @@ describe('<SmartInventoryDetail />', () => {
}); });
afterAll(() => { afterAll(() => {
wrapper.unmount();
jest.clearAllMocks(); jest.clearAllMocks();
}); });
@@ -118,7 +115,6 @@ describe('<SmartInventoryDetail />', () => {
describe('User has read-only permissions', () => { describe('User has read-only permissions', () => {
afterEach(() => { afterEach(() => {
wrapper.unmount();
jest.clearAllMocks(); jest.clearAllMocks();
}); });

View File

@@ -19,11 +19,7 @@ jest.mock('react-router-dom', () => ({
id: 2, id: 2,
}), }),
})); }));
jest.mock('../../../api/models/Inventories'); jest.mock('../../../api');
jest.mock('../../../api/models/Organizations');
jest.mock('../../../api/models/InstanceGroups');
OrganizationsAPI.read.mockResolvedValue({ data: { results: [], count: 0 } });
InstanceGroupsAPI.read.mockResolvedValue({ data: { results: [], count: 0 } });
const mockSmartInv = Object.assign( const mockSmartInv = Object.assign(
{}, {},
@@ -36,10 +32,19 @@ const mockSmartInv = Object.assign(
); );
describe('<SmartInventoryEdit />', () => { describe('<SmartInventoryEdit />', () => {
let history;
let wrapper; let wrapper;
beforeAll(async () => { const history = createMemoryHistory({
initialEntries: [`/inventories/smart_inventory/${mockSmartInv.id}/edit`],
});
beforeEach(async () => {
OrganizationsAPI.read.mockResolvedValue({
data: { results: [], count: 0 },
});
InstanceGroupsAPI.read.mockResolvedValue({
data: { results: [], count: 0 },
});
InventoriesAPI.associateInstanceGroup.mockResolvedValue(); InventoriesAPI.associateInstanceGroup.mockResolvedValue();
InventoriesAPI.disassociateInstanceGroup.mockResolvedValue(); InventoriesAPI.disassociateInstanceGroup.mockResolvedValue();
InventoriesAPI.update.mockResolvedValue({ data: mockSmartInv }); InventoriesAPI.update.mockResolvedValue({ data: mockSmartInv });
@@ -55,9 +60,7 @@ describe('<SmartInventoryEdit />', () => {
], ],
}, },
}); });
history = createMemoryHistory({
initialEntries: [`/inventories/smart_inventory/${mockSmartInv.id}/edit`],
});
await act(async () => { await act(async () => {
wrapper = mountWithContexts( wrapper = mountWithContexts(
<SmartInventoryEdit inventory={{ ...mockSmartInv }} />, <SmartInventoryEdit inventory={{ ...mockSmartInv }} />,
@@ -71,7 +74,6 @@ describe('<SmartInventoryEdit />', () => {
afterAll(() => { afterAll(() => {
jest.clearAllMocks(); jest.clearAllMocks();
wrapper.unmount();
}); });
test('should render CodeEditor field', () => { test('should render CodeEditor field', () => {

View File

@@ -73,6 +73,9 @@ describe('<SmartInventoryHost />', () => {
}); });
test('should show content error when user attempts to navigate to erroneous route', async () => { test('should show content error when user attempts to navigate to erroneous route', async () => {
InventoriesAPI.readHostDetail.mockResolvedValue({
data: { ...mockHost },
});
history = createMemoryHistory({ history = createMemoryHistory({
initialEntries: ['/inventories/smart_inventory/1/hosts/1/foobar'], initialEntries: ['/inventories/smart_inventory/1/hosts/1/foobar'],
}); });

View File

@@ -12,10 +12,6 @@ describe('<SmartInventoryHostDetail />', () => {
wrapper = mountWithContexts(<SmartInventoryHostDetail host={mockHost} />); wrapper = mountWithContexts(<SmartInventoryHostDetail host={mockHost} />);
}); });
afterAll(() => {
wrapper.unmount();
});
test('should render Details', () => { test('should render Details', () => {
function assertDetail(label, value) { function assertDetail(label, value) {
expect(wrapper.find(`Detail[label="${label}"] dt`).text()).toBe(label); expect(wrapper.find(`Detail[label="${label}"] dt`).text()).toBe(label);

View File

@@ -23,7 +23,7 @@ describe('<SmartInventoryHostList />', () => {
}, },
}; };
beforeAll(async () => { beforeEach(async () => {
InventoriesAPI.readHosts.mockResolvedValue({ InventoriesAPI.readHosts.mockResolvedValue({
data: mockHosts, data: mockHosts,
}); });

View File

@@ -77,7 +77,6 @@ describe('<InventoryForm />', () => {
}); });
afterAll(() => { afterAll(() => {
wrapper.unmount();
jest.clearAllMocks(); jest.clearAllMocks();
}); });

View File

@@ -19,9 +19,6 @@ describe('<InventoryGroupForm />', () => {
/> />
); );
}); });
afterEach(() => {
wrapper.unmount();
});
test('initially renders successfully', () => { test('initially renders successfully', () => {
expect(wrapper.length).toBe(1); expect(wrapper.length).toBe(1);
}); });

View File

@@ -29,7 +29,6 @@ describe('<InventoryGroupsDeleteModal />', () => {
}); });
}); });
afterEach(() => { afterEach(() => {
wrapper.unmount();
jest.clearAllMocks(); jest.clearAllMocks();
}); });
test('should mount properly', async () => { test('should mount properly', async () => {

View File

@@ -7,45 +7,43 @@ import {
import InventorySourceForm from './InventorySourceForm'; import InventorySourceForm from './InventorySourceForm';
import { InventorySourcesAPI, ProjectsAPI, CredentialsAPI } from '../../../api'; import { InventorySourcesAPI, ProjectsAPI, CredentialsAPI } from '../../../api';
jest.mock('../../../api/models/Credentials'); jest.mock('../../../api');
jest.mock('../../../api/models/InventorySources');
jest.mock('../../../api/models/Projects');
describe('<InventorySourceForm />', () => { describe('<InventorySourceForm />', () => {
let wrapper; let wrapper;
CredentialsAPI.read.mockResolvedValue({
data: { count: 0, results: [] },
});
ProjectsAPI.readInventories.mockResolvedValue({
data: ['foo', 'bar'],
});
InventorySourcesAPI.readOptions.mockResolvedValue({
data: {
actions: {
GET: {
source: {
choices: [
['file', 'File, Directory or Script'],
['scm', 'Sourced from a Project'],
['ec2', 'Amazon EC2'],
['gce', 'Google Compute Engine'],
['azure_rm', 'Microsoft Azure Resource Manager'],
['vmware', 'VMware vCenter'],
['satellite6', 'Red Hat Satellite 6'],
['openstack', 'OpenStack'],
['rhv', 'Red Hat Virtualization'],
['tower', 'Ansible Tower'],
],
},
},
},
},
});
describe('Successful form submission', () => { describe('Successful form submission', () => {
const onSubmit = jest.fn(); const onSubmit = jest.fn();
beforeAll(async () => { beforeAll(async () => {
CredentialsAPI.read.mockResolvedValue({
data: { count: 0, results: [] },
});
ProjectsAPI.readInventories.mockResolvedValue({
data: ['foo', 'bar'],
});
InventorySourcesAPI.readOptions = async () => ({
data: {
actions: {
GET: {
source: {
choices: [
['file', 'File, Directory or Script'],
['scm', 'Sourced from a Project'],
['ec2', 'Amazon EC2'],
['gce', 'Google Compute Engine'],
['azure_rm', 'Microsoft Azure Resource Manager'],
['vmware', 'VMware vCenter'],
['satellite6', 'Red Hat Satellite 6'],
['openstack', 'OpenStack'],
['rhv', 'Red Hat Virtualization'],
['tower', 'Ansible Tower'],
],
},
},
},
},
});
await act(async () => { await act(async () => {
wrapper = mountWithContexts( wrapper = mountWithContexts(
<InventorySourceForm onCancel={() => {}} onSubmit={onSubmit} /> <InventorySourceForm onCancel={() => {}} onSubmit={onSubmit} />
@@ -56,7 +54,6 @@ describe('<InventorySourceForm />', () => {
afterAll(() => { afterAll(() => {
jest.clearAllMocks(); jest.clearAllMocks();
wrapper.unmount();
}); });
test('should initially display primary form fields', () => { test('should initially display primary form fields', () => {
@@ -113,19 +110,6 @@ describe('<InventorySourceForm />', () => {
}); });
}); });
test('should display ContentError on throw', async () => {
InventorySourcesAPI.readOptions.mockImplementationOnce(() =>
Promise.reject(new Error())
);
await act(async () => {
wrapper = mountWithContexts(
<InventorySourceForm onCancel={() => {}} onSubmit={() => {}} />
);
});
await waitForElement(wrapper, 'ContentLoading', el => el.length === 0);
expect(wrapper.find('ContentError').length).toBe(1);
});
test('calls "onCancel" when Cancel button is clicked', async () => { test('calls "onCancel" when Cancel button is clicked', async () => {
const onCancel = jest.fn(); const onCancel = jest.fn();
await act(async () => { await act(async () => {
@@ -138,4 +122,16 @@ describe('<InventorySourceForm />', () => {
wrapper.find('button[aria-label="Cancel"]').prop('onClick')(); wrapper.find('button[aria-label="Cancel"]').prop('onClick')();
expect(onCancel).toBeCalled(); expect(onCancel).toBeCalled();
}); });
test('should display ContentError on throw', async () => {
InventorySourcesAPI.readOptions = jest.fn();
InventorySourcesAPI.readOptions.mockRejectedValueOnce(new Error());
await act(async () => {
wrapper = mountWithContexts(
<InventorySourceForm onCancel={() => {}} onSubmit={() => {}} />
);
});
await waitForElement(wrapper, 'ContentLoading', el => el.length === 0);
expect(wrapper.find('ContentError').length).toBe(1);
});
}); });

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