[Vis: Default editor] Unit test cases for controls (#41244) (#41564)

* Add unit tests

* Enable collecting test coverage

* Use brace expansion

* Fix types
This commit is contained in:
Daniil Suleiman 2019-07-19 14:15:39 +03:00 committed by GitHub
parent 51e3ad4eac
commit ca20f37aab
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 424 additions and 21 deletions

View file

@ -45,12 +45,10 @@ export default {
'packages/kbn-ui-framework/src/services/**/*.js',
'!packages/kbn-ui-framework/src/services/index.js',
'!packages/kbn-ui-framework/src/services/**/*/index.js',
'src/legacy/core_plugins/**/*.js',
'src/legacy/core_plugins/**/*.jsx',
'src/legacy/core_plugins/**/*.ts',
'src/legacy/core_plugins/**/*.tsx',
'!src/legacy/core_plugins/**/__test__/**/*',
'!src/legacy/core_plugins/**/__snapshots__/**/*',
'src/legacy/core_plugins/**/*.{js,jsx,ts,tsx}',
'!src/legacy/core_plugins/**/{__test__,__snapshots__}/**/*',
'src/legacy/ui/public/{agg_types,vis}/**/*.{ts,tsx}',
'!src/legacy/ui/public/{agg_types,vis}/**/*.d.ts',
],
moduleNameMapper: {
'^plugins/([^\/.]*)(.*)': '<rootDir>/src/legacy/core_plugins/$1/public$2',

View file

@ -21,7 +21,7 @@ import React from 'react';
const wrapWithInlineComp = Component => props => (
<div className={`visEditorAggParam--half visEditorAggParam--half-${props.aggParam.name}`}>
<Component {...props} wrappedWithInlineComp={true}/>
<Component {...props}/>
</div>);
export { wrapWithInlineComp };

View file

@ -0,0 +1,33 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`SizeParamEditor should init with the default set of props 1`] = `
<EuiFormRow
compressed={true}
describedByIds={Array []}
fullWidth={true}
hasEmptyLabelSpace={false}
isInvalid={false}
label={
<React.Fragment>
<FormattedMessage
defaultMessage="Size"
id="common.ui.aggTypes.sizeLabel"
values={Object {}}
/>
</React.Fragment>
}
labelType="label"
>
<EuiFieldNumber
compressed={false}
data-test-subj="sizeParamEditor"
fullWidth={true}
isInvalid={false}
isLoading={false}
min={1}
onBlur={[MockFunction]}
onChange={[Function]}
value=""
/>
</EuiFormRow>
`;

View file

@ -0,0 +1,57 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`TopAggregateParamEditor should init with the default set of props 1`] = `
<EuiFormRow
compressed={true}
describedByIds={Array []}
fullWidth={true}
hasEmptyLabelSpace={false}
isInvalid={false}
label={
<React.Fragment>
<FormattedMessage
defaultMessage="Aggregate with"
id="common.ui.aggTypes.aggregateWithLabel"
values={Object {}}
/>
<EuiIconTip
content="Choose a strategy for combining multiple hits or a multi-valued field into a single metric."
position="right"
type="questionInCircle"
/>
</React.Fragment>
}
labelType="label"
>
<EuiSelect
compressed={false}
disabled={false}
fullWidth={true}
hasNoInitialSelection={false}
isInvalid={false}
isLoading={false}
onBlur={[MockFunction]}
onChange={[Function]}
options={
Array [
Object {
"disabled": true,
"hidden": true,
"text": "",
"value": "EMPTY_VALUE",
},
Object {
"text": "Max",
"value": "max",
},
Object {
"text": "Min",
"value": "min",
},
]
}
value="EMPTY_VALUE"
/>
</EuiFormRow>
`;

View file

@ -38,7 +38,7 @@ function areBoundsValid({ min, max }: Bounds): boolean {
}
function ExtendedBoundsParamEditor({
value,
value = {} as Bounds,
setValue,
setValidity,
showValidation,

View file

@ -35,7 +35,7 @@ interface FilterValue {
id: string;
}
function FiltersParamEditor({ agg, value, setValue }: AggParamEditorProps<FilterValue[]>) {
function FiltersParamEditor({ agg, value = [], setValue }: AggParamEditorProps<FilterValue[]>) {
const [filters, setFilters] = useState(() =>
value.map(filter => ({ ...filter, id: generateId() }))
);

View file

@ -30,7 +30,6 @@ function OrderParamEditor({
setValue,
setValidity,
setTouched,
wrappedWithInlineComp,
}: AggParamEditorProps<OptionedValueProp> & OptionedParamEditorProps) {
const label = i18n.translate('common.ui.aggTypes.orderLabel', {
defaultMessage: 'Order',

View file

@ -27,7 +27,7 @@ import { NumberList } from '../number_list';
function PercentileRanksEditor({
agg,
showValidation,
value,
value = [],
setTouched,
setValidity,
setValue,

View file

@ -27,7 +27,7 @@ import { NumberList } from '../number_list';
function PercentilesEditor({
agg,
showValidation,
value,
value = [],
setTouched,
setValidity,
setValue,

View file

@ -28,7 +28,7 @@ import { isValidJson } from '../utils';
function RawJsonParamEditor({
agg,
showValidation,
value,
value = '',
setValidity,
setValue,
setTouched,
@ -68,7 +68,7 @@ function RawJsonParamEditor({
<EuiTextArea
id={`visEditorRawJson${agg.id}`}
isInvalid={showValidation ? !isValid : false}
value={value || ''}
value={value}
onChange={onChange}
rows={2}
fullWidth={true}

View file

@ -0,0 +1,94 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import React from 'react';
import { mountWithIntl, shallowWithIntl } from 'test_utils/enzyme_helpers';
import { EuiIconTip } from '@elastic/eui';
import { SizeParamEditor, SizeParamEditorProps } from './size';
import { aggParamCommonPropsMock } from './test_utils';
describe('SizeParamEditor', () => {
let defaultProps: SizeParamEditorProps;
beforeEach(() => {
defaultProps = {
...aggParamCommonPropsMock,
value: '',
setValue: jest.fn(),
setValidity: jest.fn(),
setTouched: jest.fn(),
};
});
it('should init with the default set of props', () => {
const comp = shallowWithIntl(<SizeParamEditor {...defaultProps} />);
expect(comp).toMatchSnapshot();
});
it('should render an iconTip in the label if it was passed', () => {
const iconTip = <EuiIconTip position="right" content={'test'} type="questionInCircle" />;
const comp = shallowWithIntl(<SizeParamEditor {...defaultProps} iconTip={iconTip} />);
expect(comp.props().label.props.children[1]).toEqual(iconTip);
});
it('should change its validity due to passed props', () => {
const comp = mountWithIntl(<SizeParamEditor {...defaultProps} />);
expect(defaultProps.setValidity).toHaveBeenCalledWith(false);
expect(comp.children().props()).toHaveProperty('isInvalid', false);
comp.setProps({ disabled: true, showValidation: true });
expect(defaultProps.setValidity).toHaveBeenCalledWith(true);
expect(comp.children().props()).toHaveProperty('isInvalid', false);
comp.setProps({ disabled: false, showValidation: true });
expect(defaultProps.setValidity).toHaveBeenCalledWith(false);
expect(comp.children().props()).toHaveProperty('isInvalid', true);
comp.setProps({ value: 2, showValidation: true });
expect(defaultProps.setValidity).toHaveBeenCalledWith(true);
expect(comp.children().props()).toHaveProperty('isInvalid', false);
expect(defaultProps.setValidity).toHaveBeenCalledTimes(4);
});
it('should set new parsed value', () => {
const comp = mountWithIntl(<SizeParamEditor {...defaultProps} />);
const input = comp.find('[type="number"]');
input.simulate('change', { target: { value: '3' } });
expect(defaultProps.setValue).toBeCalledWith(3);
input.simulate('change', { target: { value: '' } });
expect(defaultProps.setValue).toBeCalledWith('');
expect(defaultProps.setValue).toHaveBeenCalledTimes(2);
});
it('should call setTouched on blur', () => {
const comp = mountWithIntl(<SizeParamEditor {...defaultProps} />);
comp.find('[type="number"]').simulate('blur');
expect(defaultProps.setTouched).toHaveBeenCalledTimes(1);
});
});

View file

@ -23,7 +23,7 @@ import { AggParamEditorProps } from 'ui/vis/editors/default';
import { EuiFormRow, EuiFieldNumber } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
interface SizeParamEditorProps extends AggParamEditorProps<number | ''> {
export interface SizeParamEditorProps extends AggParamEditorProps<number | ''> {
iconTip?: React.ReactNode;
disabled?: boolean;
}
@ -36,7 +36,6 @@ function SizeParamEditor({
showValidation,
setValidity,
setTouched,
wrappedWithInlineComp,
}: SizeParamEditorProps) {
const label = (
<>

View file

@ -0,0 +1,33 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { AggConfig, VisState } from 'ui/vis';
import { EditorConfig } from 'ui/vis/editors/config/types';
import { SubAggParamsProp } from 'ui/vis/editors/default/components/default_editor_agg_params';
import { AggParam } from '..';
export const aggParamCommonPropsMock = {
agg: {} as AggConfig,
aggParam: {} as AggParam,
editorConfig: {} as EditorConfig,
metricAggs: [] as AggConfig[],
subAggParams: {} as SubAggParamsProp,
state: {} as VisState,
showValidation: false,
};

View file

@ -0,0 +1,189 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import React from 'react';
import { mountWithIntl, shallowWithIntl } from 'test_utils/enzyme_helpers';
import { AggConfig } from 'ui/vis';
import {
AggregateValueProp,
TopAggregateParamEditor,
TopAggregateParamEditorProps,
} from './top_aggregate';
import { aggParamCommonPropsMock } from './test_utils';
describe('TopAggregateParamEditor', () => {
let agg: AggConfig;
let aggParam: any;
let defaultProps: TopAggregateParamEditorProps;
let options: AggregateValueProp[];
beforeEach(() => {
options = [
{
text: 'Min',
isCompatible: jest.fn((aggr: AggConfig) => aggr.params.field.type === 'number'),
value: 'min',
},
{
text: 'Max',
isCompatible: jest.fn((aggr: AggConfig) => aggr.params.field.type === 'number'),
value: 'max',
},
{
text: 'Average',
isCompatible: jest.fn((aggr: AggConfig) => aggr.params.field.type === 'string'),
value: 'average',
},
];
Object.defineProperty(options, 'byValue', {
get: () =>
options.reduce((acc: { [key: string]: AggregateValueProp }, option: AggregateValueProp) => {
acc[option.value] = { ...option };
return acc;
}, {}),
});
aggParam = {
options,
};
agg = {
params: {
field: {
type: 'number',
},
},
getAggParams: jest.fn(() => [{ name: 'aggregate', options }]),
};
defaultProps = {
...aggParamCommonPropsMock,
agg,
aggParam,
setValue: jest.fn(),
setValidity: jest.fn(),
setTouched: jest.fn(),
};
});
it('should init with the default set of props', () => {
const comp = shallowWithIntl(<TopAggregateParamEditor {...defaultProps} />);
expect(comp).toMatchSnapshot();
});
it('should be disabled if a field type is set but there are no compatible options', () => {
options = [];
const comp = mountWithIntl(<TopAggregateParamEditor {...defaultProps} showValidation={true} />);
const select = comp.find('select');
expect(defaultProps.setValidity).toHaveBeenCalledWith(true);
expect(comp.children().props()).toHaveProperty('isInvalid', false);
expect(select.children()).toHaveLength(1);
expect(select.props()).toHaveProperty('disabled', true);
});
it('should change its validity due to passed props', () => {
const comp = mountWithIntl(
<TopAggregateParamEditor {...defaultProps} value={{ value: 'min' } as AggregateValueProp} />
);
expect(defaultProps.setValidity).toHaveBeenCalledWith(true);
comp.setProps({ showValidation: true, value: undefined });
expect(comp.children().props()).toHaveProperty('isInvalid', true);
expect(defaultProps.setValidity).toHaveBeenCalledWith(false);
comp.setProps({ value: { value: 'max' } });
const select = comp.find('select');
expect(comp.children().props()).toHaveProperty('isInvalid', false);
expect(defaultProps.setValidity).toHaveBeenCalledWith(true);
expect(defaultProps.setValidity).toHaveBeenCalledTimes(3);
expect(select.children()).toHaveLength(3);
expect(select.prop('value')).toEqual('max');
});
it('should call setValue on change', () => {
const comp = mountWithIntl(
<TopAggregateParamEditor {...defaultProps} value={{ value: 'min' } as AggregateValueProp} />
);
const select = comp.find('select');
expect(defaultProps.setValue).not.toHaveBeenCalled();
select.simulate('change', { target: { value: 'EMPTY_VALUE' } });
expect(defaultProps.setValue).toHaveBeenCalledWith();
select.simulate('change', { target: { value: 'max' } });
expect(defaultProps.setValue).toHaveBeenCalledWith(options[1]);
});
it('should reflect on fieldType changes', () => {
const comp = mountWithIntl(
<TopAggregateParamEditor {...defaultProps} value={{ value: 'min' } as AggregateValueProp} />
);
// should not be called on the first render
expect(defaultProps.setValue).not.toHaveBeenCalled();
agg = {
...agg,
params: {
field: {
type: 'string',
},
},
};
comp.setProps({ agg });
// should not reflect if field type was changed but options are still available
expect(defaultProps.setValue).not.toHaveBeenCalledWith();
options.shift();
agg = {
...agg,
params: {
field: {
type: 'date',
},
},
};
comp.setProps({ agg });
// should clear the value if the option is unavailable
expect(defaultProps.setValue).toHaveBeenCalledWith();
agg = {
...agg,
params: {
field: {
type: 'string',
},
},
};
comp.setProps({ agg, value: undefined });
// should set an option by default if it is only available
expect(defaultProps.setValue).toHaveBeenCalledWith(options[1]);
});
});

View file

@ -26,10 +26,13 @@ import { AggConfig } from 'ui/vis';
import { AggParam } from '../agg_param';
import { OptionedValueProp, OptionedParamEditorProps } from '../param_types/optioned';
interface AggregateValueProp extends OptionedValueProp {
export interface AggregateValueProp extends OptionedValueProp {
isCompatible(aggConfig: AggConfig): boolean;
}
export type TopAggregateParamEditorProps = AggParamEditorProps<AggregateValueProp> &
OptionedParamEditorProps<AggregateValueProp>;
function getCompatibleAggs(agg: AggConfig): AggregateValueProp[] {
const { options = [] } = agg.getAggParams().find(({ name }: AggParam) => name === 'aggregate');
return options.filter((option: AggregateValueProp) => option.isCompatible(agg));
@ -43,8 +46,7 @@ function TopAggregateParamEditor({
setValue,
setValidity,
setTouched,
wrappedWithInlineComp,
}: AggParamEditorProps<AggregateValueProp> & OptionedParamEditorProps<AggregateValueProp>) {
}: TopAggregateParamEditorProps) {
const isFirstRun = useRef(true);
const fieldType = agg.params.field && agg.params.field.type;
const emptyValue = { text: '', value: 'EMPTY_VALUE', disabled: true, hidden: true };

View file

@ -37,7 +37,7 @@ export interface AggParamCommonProps<T> {
indexedFields?: FieldParamType[];
showValidation: boolean;
state: VisState;
value: T;
value?: T;
metricAggs: AggConfig[];
subAggParams: SubAggParamsProp;
setValidity(isValid: boolean): void;
@ -45,6 +45,5 @@ export interface AggParamCommonProps<T> {
}
export interface AggParamEditorProps<T> extends AggParamCommonProps<T> {
wrappedWithInlineComp?: boolean;
setValue(value?: T): void;
}