Select targets tests (#1307)

* Adds tests for the TargetDetails component

* Adds tests for Select Targets Menu

* Rename target_stub to target_mock

* Adds tests for the SelectTargetsDropdown & minor refactor
This commit is contained in:
Mike Stone 2017-03-03 10:05:03 -05:00 committed by GitHub
parent 71a384f34a
commit 7a7fb9eac9
9 changed files with 431 additions and 50 deletions

View File

@ -29,20 +29,19 @@ class SelectTargetsDropdown extends Component {
constructor (props) {
super(props);
this.mounted = true;
this.state = {
isEmpty: false,
isLoadingTargets: false,
moreInfoTarget: null,
query: '',
targets: [],
wrapperHeight: 0,
};
}
componentDidMount () {
componentWillMount () {
this.fetchTargets();
this.mounted = true;
this.wrapperHeight = 0;
return false;
}
@ -106,19 +105,6 @@ class SelectTargetsDropdown extends Component {
return false;
}
const { target_type: targetType } = moreInfoTarget;
if (targetType.toLowerCase() === 'labels') {
return Kolide.getLabelHosts(moreInfoTarget.id)
.then((hosts) => {
this.setState({
moreInfoTarget: { ...moreInfoTarget, hosts },
});
return false;
});
}
this.setState({ moreInfoTarget });
return false;
@ -126,12 +112,10 @@ class SelectTargetsDropdown extends Component {
}
onBackToResults = () => {
this.setState({
moreInfoTarget: null,
});
this.setState({ moreInfoTarget: null });
}
fetchTargets = (query, selectedTargets = this.props.selectedTargets) => {
fetchTargets = (query = '', selectedTargets = this.props.selectedTargets) => {
const { onFetchTargets } = this.props;
if (!this.mounted) {
@ -142,26 +126,25 @@ class SelectTargetsDropdown extends Component {
return Kolide.targets.loadAll(query, formatSelectedTargetsForApi(selectedTargets))
.then((response) => {
const {
targets,
} = response;
const { targets } = response;
const isEmpty = targets.length === 0;
if (!this.mounted) {
return false;
}
if (targets.length === 0) {
if (isEmpty) {
// We don't want the lib's default "No Results" so we fake it
targets.push({});
this.setState({ isEmpty: true });
} else {
this.setState({ isEmpty: false });
}
onFetchTargets(query, response);
this.setState({ isLoadingTargets: false, targets });
this.setState({
isEmpty,
isLoadingTargets: false,
targets,
});
return query;
})

View File

@ -0,0 +1,169 @@
import React from 'react';
import expect, { createSpy, restoreSpies } from 'expect';
import { mount } from 'enzyme';
import nock from 'nock';
import { noop } from 'lodash';
import SelectTargetsDropdown from 'components/forms/fields/SelectTargetsDropdown';
import Test from 'test';
describe('SelectTargetsDropdown - component', () => {
const defaultProps = {
disabled: false,
label: 'Select Targets',
onFetchTargets: noop,
onSelect: noop,
selectedTargets: [],
targetsCount: 0,
};
const DefaultComponent = mount(<SelectTargetsDropdown {...defaultProps} />);
afterEach(() => nock.cleanAll());
it('sets default state', () => {
Test.Mocks.targetMock();
expect(DefaultComponent.state()).toEqual({
isEmpty: false,
isLoadingTargets: false,
moreInfoTarget: null,
query: '',
targets: [],
});
});
describe('rendering', () => {
beforeEach(() => Test.Mocks.targetMock());
it('renders', () => {
expect(DefaultComponent.length).toEqual(1, 'Expected component to render');
});
it('renders the SelectTargetsInput', () => {
const SelectTargetsInput = DefaultComponent.find('SelectTargetsInput');
expect(SelectTargetsInput.length).toEqual(1, 'Expected SelectTargetsInput to render');
});
it('renders a label when passed as a prop', () => {
const noLabelProps = { ...defaultProps, label: undefined };
const ComponentWithoutLabel = mount(<SelectTargetsDropdown {...noLabelProps} />);
const Label = DefaultComponent.find('.target-select__label');
const NoLabel = ComponentWithoutLabel.find('.target-select__label');
expect(Label.length).toEqual(1, 'Expected label to render');
expect(NoLabel.length).toEqual(0, 'Expected label to not render');
});
it('renders the error when passed as a prop', () => {
const errorProps = { ...defaultProps, error: "You can't do this!" };
const ErrorComponent = mount(<SelectTargetsDropdown {...errorProps} />);
const Error = ErrorComponent.find('.target-select__label--error');
const NoError = DefaultComponent.find('.target-select__label--error');
expect(Error.length).toEqual(1, 'Expected error to render');
expect(NoError.length).toEqual(0, 'Expected error to not render');
});
it('renders the target count', () => {
const targetCountProps = { ...defaultProps, targetsCount: 10 };
const TargetCountComponent = mount(<SelectTargetsDropdown {...targetCountProps} />);
expect(DefaultComponent.text()).toInclude('0 unique hosts');
expect(TargetCountComponent.text()).toInclude('10 unique hosts');
});
});
describe('#fetchTargets', () => {
const apiResponseWithTargets = {
targets: {
hosts: [],
labels: [Test.Stubs.labelStub],
},
};
const apiResponseWithoutTargets = {
targets: {
hosts: [],
labels: [],
},
};
const defaultSelectedTargets = { hosts: [], labels: [] };
const defaultParams = {
query: '',
selected: defaultSelectedTargets,
};
const expectedApiClientResponseWithTargets = {
targets: [{ ...Test.Stubs.labelStub, target_type: 'labels' }],
};
afterEach(() => restoreSpies());
it('calls the api', () => {
nock.cleanAll();
const request = Test.Mocks.targetMock(defaultParams, apiResponseWithTargets);
const Component = mount(<SelectTargetsDropdown {...defaultProps} />);
const node = Component.node;
return node.fetchTargets()
.then(() => {
expect(request.isDone()).toEqual(true);
});
});
it('calls the onFetchTargets prop', () => {
nock.cleanAll();
const onFetchTargets = createSpy();
const props = { ...defaultProps, onFetchTargets };
const Component = mount(<SelectTargetsDropdown {...props} />);
const node = Component.node;
Test.Mocks.targetMock(defaultParams, apiResponseWithTargets);
return node.fetchTargets()
.then(() => {
expect(onFetchTargets).toHaveBeenCalledWith('', expectedApiClientResponseWithTargets);
});
});
it('does not call the onFetchTargets prop when the component is not mounted', () => {
const onFetchTargets = createSpy();
const props = { ...defaultProps, onFetchTargets };
const Component = mount(<SelectTargetsDropdown {...props} />);
const node = Component.node;
node.mounted = false;
expect(node.fetchTargets()).toEqual(false);
expect(onFetchTargets).toNotHaveBeenCalled();
});
it('sets state correctly when no targets are returned', () => {
const Component = mount(<SelectTargetsDropdown {...defaultProps} />);
const node = Component.node;
Test.Mocks.targetMock(defaultParams, apiResponseWithoutTargets);
return node.fetchTargets()
.then(() => {
expect(Component.state('isEmpty')).toEqual(true);
expect(Component.state('targets')).toEqual([{}]);
expect(Component.state('isLoadingTargets')).toEqual(false);
});
});
it('returns the query', () => {
const query = 'select * from users';
const Component = mount(<SelectTargetsDropdown {...defaultProps} />);
const node = Component.node;
Test.Mocks.targetMock(defaultParams);
return node.fetchTargets(query)
.then((q) => {
expect(q).toEqual(query);
});
});
});
});

View File

@ -0,0 +1,93 @@
import React, { PropTypes } from 'react';
import expect, { createSpy, restoreSpies } from 'expect';
import { mount } from 'enzyme';
import { noop } from 'lodash';
import SelectTargetsMenuWrapper from 'components/forms/fields/SelectTargetsDropdown/SelectTargetsMenu';
import Test from 'test';
const DummyOption = (props) => {
return (
<div>{props.children}</div>
);
};
DummyOption.propTypes = { children: PropTypes.node };
describe('SelectTargetsMenu - component', () => {
afterEach(() => restoreSpies());
let onMoreInfoClick = noop;
const moreInfoTarget = undefined;
const handleBackToResults = noop;
const defaultProps = {
focusedOption: undefined,
instancePrefix: '',
onFocus: noop,
onOptionRef: noop,
onSelect: noop,
optionComponent: DummyOption,
};
describe('rendering', () => {
it('renders', () => {
const SelectTargetsMenu = SelectTargetsMenuWrapper(onMoreInfoClick, moreInfoTarget, handleBackToResults);
const Component = mount(<SelectTargetsMenu {...defaultProps} />);
expect(Component.length).toEqual(1);
});
it('renders no target text', () => {
const SelectTargetsMenu = SelectTargetsMenuWrapper(onMoreInfoClick, moreInfoTarget, handleBackToResults);
const Component = mount(<SelectTargetsMenu {...defaultProps} />);
const componentText = Component.text();
expect(componentText).toInclude('Unable to find any matching labels.');
expect(componentText).toInclude('Unable to find any matching hosts.');
});
it('renders a target option component for each target', () => {
const SelectTargetsMenu = SelectTargetsMenuWrapper(onMoreInfoClick, moreInfoTarget, handleBackToResults);
const options = [Test.Stubs.labelStub, Test.Stubs.hostStub];
const props = { ...defaultProps, options };
const Component = mount(<SelectTargetsMenu {...props} />);
const TargetOption = Component.find('TargetOption');
expect(TargetOption.length).toEqual(options.length);
expect(options).toInclude(TargetOption.first().prop('target'));
expect(options).toInclude(TargetOption.last().prop('target'));
});
});
describe('clicking a target', () => {
it('calls the onMoreInfoClick function', () => {
const spy = createSpy();
onMoreInfoClick = (t) => {
return () => spy(t);
};
const SelectTargetsMenu = SelectTargetsMenuWrapper(onMoreInfoClick, moreInfoTarget, handleBackToResults);
const options = [Test.Stubs.labelStub];
const props = { ...defaultProps, options };
const Component = mount(<SelectTargetsMenu {...props} />);
const TargetOption = Component.find('TargetOption');
TargetOption.find('.target-option__target-content').simulate('click');
expect(spy).toHaveBeenCalledWith(Test.Stubs.labelStub);
});
it('calls the onSelect prop when the add button is clicked', () => {
const spy = createSpy();
const SelectTargetsMenu = SelectTargetsMenuWrapper(onMoreInfoClick, moreInfoTarget, handleBackToResults);
const options = [Test.Stubs.labelStub];
const props = { ...defaultProps, onSelect: spy, options };
const Component = mount(<SelectTargetsMenu {...props} />);
const TargetOption = Component.find('TargetOption');
TargetOption.find('.target-option__add-btn').simulate('click');
expect(spy).toHaveBeenCalled();
});
});
});

View File

@ -0,0 +1,121 @@
import React from 'react';
import expect, { createSpy, restoreSpies } from 'expect';
import { mount } from 'enzyme';
import TargetDetails from 'components/forms/fields/SelectTargetsDropdown/TargetDetails';
import Test from 'test';
describe('TargetDetails - component', () => {
afterEach(() => restoreSpies());
const defaultProps = { target: Test.Stubs.labelStub };
describe('rendering', () => {
it('does not render without a target', () => {
const Component = mount(<TargetDetails />);
expect(Component.html()).toNotExist();
});
it('renders when there is a target', () => {
const Component = mount(<TargetDetails {...defaultProps} />);
expect(Component.length).toEqual(1);
});
describe('when the target is a host', () => {
it('renders target information', () => {
const target = {
display_text: 'display_text',
host_mac: 'host_mac',
host_ip_address: 'host_ip_address',
memory: 1074000000, // 1 GB memory
osquery_version: 'osquery_version',
os_version: 'os_version',
platform: 'platform',
status: 'status',
};
const Component = mount(<TargetDetails target={target} />);
const componentText = Component.text();
expect(componentText).toInclude(target.display_text);
expect(componentText).toInclude(target.host_mac);
expect(componentText).toInclude(target.host_ip_address);
expect(componentText).toInclude('1.00 GB');
expect(componentText).toInclude(target.osquery_version);
expect(componentText).toInclude(target.os_version);
expect(componentText).toInclude(target.platform);
expect(componentText).toInclude(target.status);
});
it('renders a success check icon when the target is online', () => {
const target = { ...Test.Stubs.hostStub, status: 'online' };
const Component = mount(<TargetDetails target={target} />);
const Icon = Component.find('Icon');
const onlineIcon = Icon.find('.host-target__icon--online');
const offlineIcon = Icon.find('.host-target__icon--offline');
expect(onlineIcon.length).toEqual(1, 'Expected the online icon to render');
expect(offlineIcon.length).toEqual(0, 'Expected the offline icon to not render');
});
it('renders a offline icon when the target is offline', () => {
const target = { ...Test.Stubs.hostStub, status: 'offline' };
const Component = mount(<TargetDetails target={target} />);
const Icon = Component.find('Icon');
const onlineIcon = Icon.find('.host-target__icon--online');
const offlineIcon = Icon.find('.host-target__icon--offline');
expect(onlineIcon.length).toEqual(0, 'Expected the online icon to not render');
expect(offlineIcon.length).toEqual(1, 'Expected the offline icon to render');
});
});
describe('when the target is a label', () => {
const target = {
...Test.Stubs.labelStub,
count: 10,
description: 'target description',
display_text: 'display_text',
label_type: 0,
online: 10,
query: 'query',
};
const Component = mount(<TargetDetails target={target} />);
it('renders the label data', () => {
const componentText = Component.text();
expect(componentText).toInclude('100% ONLINE');
expect(componentText).toInclude(target.display_text);
expect(componentText).toInclude(target.description);
});
it('renders a read-only AceEditor', () => {
const AceEditor = Component.find('ReactAce');
expect(AceEditor.prop('readOnly')).toEqual(true);
});
});
});
it('calls the handleBackToResults prop when the back button is clicked', () => {
const labelSpy = createSpy();
const labelProps = { ...defaultProps, handleBackToResults: labelSpy };
const LabelComponent = mount(<TargetDetails {...labelProps} />);
const LabelBackButton = LabelComponent.find('.label-target__back');
const hostSpy = createSpy();
const hostProps = { target: Test.Stubs.hostStub, handleBackToResults: hostSpy };
const HostComponent = mount(<TargetDetails {...hostProps} />);
const HostBackButton = HostComponent.find('.host-target__back');
LabelBackButton.simulate('click');
expect(labelSpy).toHaveBeenCalled();
HostBackButton.simulate('click');
expect(hostSpy).toHaveBeenCalled();
});
});

View File

@ -4,11 +4,11 @@ import { mount } from 'enzyme';
import { noop } from 'lodash';
import { fillInFormInput } from 'test/helpers';
import targetStub from 'test/target_stub';
import targetMock from 'test/target_mock';
import PackForm from './index';
describe('PackForm - component', () => {
beforeEach(targetStub);
beforeEach(targetMock);
afterEach(restoreSpies);
it('renders the base error', () => {

View File

@ -4,7 +4,7 @@ import { mount } from 'enzyme';
import { noop } from 'lodash';
import { fillInFormInput } from 'test/helpers';
import targetStub from 'test/target_stub';
import targetMock from 'test/target_mock';
import QueryForm from './index';
const query = {
@ -16,7 +16,7 @@ const query = {
const queryText = 'SELECT * FROM users';
describe('QueryForm - component', () => {
beforeEach(targetStub);
beforeEach(targetMock);
afterEach(restoreSpies);
it('renders the base error', () => {

10
frontend/test/index.js Normal file
View File

@ -0,0 +1,10 @@
import helpers from 'test/helpers';
import stubs from 'test/stubs';
import mocks from 'test/mocks';
import targetMock from 'test/target_mock';
export default {
Helpers: helpers,
Mocks: { ...mocks, targetMock },
Stubs: stubs,
};

View File

@ -0,0 +1,21 @@
import nock from 'nock';
const defaultParams = {
selected: {
hosts: [],
labels: [],
},
};
const defaultResponse = {
targets: {
hosts: [],
labels: [],
},
};
export default (params = defaultParams, response = defaultResponse, responseStatus = 200) => {
return nock('http://localhost:8080')
.post('/api/v1/kolide/targets', JSON.stringify(params))
.reply(responseStatus, response);
};

View File

@ -1,16 +0,0 @@
import nock from 'nock';
const defaultParams = {
selected: {
hosts: [],
labels: [],
},
};
export default (params = defaultParams) => {
nock('http://localhost:8080')
.post('/api/v1/kolide/targets', JSON.stringify(params))
.reply(200, {
targets: [],
});
};