mirror of
https://github.com/apache/superset.git
synced 2026-04-18 23:55:00 +00:00
SIP-32: Moving frontend code to the base of the repo (#9098)
* move assets out, get webpack dev working * update docs to reference superset-frontend * draw the rest of the owl * fix docs * fix webpack script * rats * correct docs * fix tox dox
This commit is contained in:
committed by
GitHub
parent
0cf354cc88
commit
2913063924
@@ -0,0 +1,164 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF 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.
|
||||
*/
|
||||
/* eslint-disable no-unused-expressions */
|
||||
import React from 'react';
|
||||
import sinon from 'sinon';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import AdhocFilter, {
|
||||
EXPRESSION_TYPES,
|
||||
CLAUSES,
|
||||
} from '../../../../src/explore/AdhocFilter';
|
||||
import AdhocFilterControl from '../../../../src/explore/components/controls/AdhocFilterControl';
|
||||
import AdhocMetric from '../../../../src/explore/AdhocMetric';
|
||||
import { AGGREGATES, OPERATORS } from '../../../../src/explore/constants';
|
||||
import OnPasteSelect from '../../../../src/components/OnPasteSelect';
|
||||
|
||||
const simpleAdhocFilter = new AdhocFilter({
|
||||
expressionType: EXPRESSION_TYPES.SIMPLE,
|
||||
subject: 'value',
|
||||
operator: '>',
|
||||
comparator: '10',
|
||||
clause: CLAUSES.WHERE,
|
||||
});
|
||||
|
||||
const sumValueAdhocMetric = new AdhocMetric({
|
||||
expressionType: EXPRESSION_TYPES.SIMPLE,
|
||||
column: { type: 'VARCHAR(255)', column_name: 'source' },
|
||||
aggregate: AGGREGATES.SUM,
|
||||
});
|
||||
|
||||
const savedMetric = { metric_name: 'sum__value', expression: 'SUM(value)' };
|
||||
|
||||
const columns = [
|
||||
{ type: 'VARCHAR(255)', column_name: 'source' },
|
||||
{ type: 'VARCHAR(255)', column_name: 'target' },
|
||||
{ type: 'DOUBLE', column_name: 'value' },
|
||||
];
|
||||
|
||||
const formData = {
|
||||
metric: undefined,
|
||||
metrics: [sumValueAdhocMetric, savedMetric.saved_metric_name],
|
||||
};
|
||||
|
||||
function setup(overrides) {
|
||||
const onChange = sinon.spy();
|
||||
const props = {
|
||||
onChange,
|
||||
value: [simpleAdhocFilter],
|
||||
datasource: { type: 'table' },
|
||||
columns,
|
||||
savedMetrics: [savedMetric],
|
||||
formData,
|
||||
...overrides,
|
||||
};
|
||||
const wrapper = shallow(<AdhocFilterControl {...props} />);
|
||||
return { wrapper, onChange };
|
||||
}
|
||||
|
||||
describe('AdhocFilterControl', () => {
|
||||
it('renders an onPasteSelect', () => {
|
||||
const { wrapper } = setup();
|
||||
expect(wrapper.find(OnPasteSelect)).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('handles saved metrics being selected to filter on', () => {
|
||||
const { wrapper, onChange } = setup({ value: [] });
|
||||
const select = wrapper.find(OnPasteSelect);
|
||||
select.simulate('change', [{ saved_metric_name: 'sum__value' }]);
|
||||
|
||||
const adhocFilter = onChange.lastCall.args[0][0];
|
||||
expect(adhocFilter instanceof AdhocFilter).toBe(true);
|
||||
expect(
|
||||
adhocFilter.equals(
|
||||
new AdhocFilter({
|
||||
expressionType: EXPRESSION_TYPES.SQL,
|
||||
subject: savedMetric.expression,
|
||||
operator: OPERATORS['>'],
|
||||
comparator: 0,
|
||||
clause: CLAUSES.HAVING,
|
||||
}),
|
||||
),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('handles adhoc metrics being selected to filter on', () => {
|
||||
const { wrapper, onChange } = setup({ value: [] });
|
||||
const select = wrapper.find(OnPasteSelect);
|
||||
select.simulate('change', [sumValueAdhocMetric]);
|
||||
|
||||
const adhocFilter = onChange.lastCall.args[0][0];
|
||||
expect(adhocFilter instanceof AdhocFilter).toBe(true);
|
||||
expect(
|
||||
adhocFilter.equals(
|
||||
new AdhocFilter({
|
||||
expressionType: EXPRESSION_TYPES.SQL,
|
||||
subject: sumValueAdhocMetric.label,
|
||||
operator: OPERATORS['>'],
|
||||
comparator: 0,
|
||||
clause: CLAUSES.HAVING,
|
||||
}),
|
||||
),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('handles columns being selected to filter on', () => {
|
||||
const { wrapper, onChange } = setup({ value: [] });
|
||||
const select = wrapper.find(OnPasteSelect);
|
||||
select.simulate('change', [columns[0]]);
|
||||
|
||||
const adhocFilter = onChange.lastCall.args[0][0];
|
||||
expect(adhocFilter instanceof AdhocFilter).toBe(true);
|
||||
expect(
|
||||
adhocFilter.equals(
|
||||
new AdhocFilter({
|
||||
expressionType: EXPRESSION_TYPES.SIMPLE,
|
||||
subject: columns[0].column_name,
|
||||
operator: OPERATORS['=='],
|
||||
comparator: '',
|
||||
clause: CLAUSES.WHERE,
|
||||
}),
|
||||
),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('persists existing filters even when new filters are added', () => {
|
||||
const { wrapper, onChange } = setup();
|
||||
const select = wrapper.find(OnPasteSelect);
|
||||
select.simulate('change', [simpleAdhocFilter, columns[0]]);
|
||||
|
||||
const existingAdhocFilter = onChange.lastCall.args[0][0];
|
||||
expect(existingAdhocFilter instanceof AdhocFilter).toBe(true);
|
||||
expect(existingAdhocFilter.equals(simpleAdhocFilter)).toBe(true);
|
||||
|
||||
const newAdhocFilter = onChange.lastCall.args[0][1];
|
||||
expect(newAdhocFilter instanceof AdhocFilter).toBe(true);
|
||||
expect(
|
||||
newAdhocFilter.equals(
|
||||
new AdhocFilter({
|
||||
expressionType: EXPRESSION_TYPES.SIMPLE,
|
||||
subject: columns[0].column_name,
|
||||
operator: OPERATORS['=='],
|
||||
comparator: '',
|
||||
clause: CLAUSES.WHERE,
|
||||
}),
|
||||
),
|
||||
).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,169 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF 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.
|
||||
*/
|
||||
/* eslint-disable no-unused-expressions */
|
||||
import React from 'react';
|
||||
import sinon from 'sinon';
|
||||
import { shallow } from 'enzyme';
|
||||
import { FormGroup } from 'react-bootstrap';
|
||||
|
||||
import AdhocFilter, {
|
||||
EXPRESSION_TYPES,
|
||||
CLAUSES,
|
||||
} from '../../../../src/explore/AdhocFilter';
|
||||
import AdhocMetric from '../../../../src/explore/AdhocMetric';
|
||||
import AdhocFilterEditPopoverSimpleTabContent from '../../../../src/explore/components/AdhocFilterEditPopoverSimpleTabContent';
|
||||
import { AGGREGATES } from '../../../../src/explore/constants';
|
||||
|
||||
const simpleAdhocFilter = new AdhocFilter({
|
||||
expressionType: EXPRESSION_TYPES.SIMPLE,
|
||||
subject: 'value',
|
||||
operator: '>',
|
||||
comparator: '10',
|
||||
clause: CLAUSES.WHERE,
|
||||
});
|
||||
|
||||
const simpleMultiAdhocFilter = new AdhocFilter({
|
||||
expressionType: EXPRESSION_TYPES.SIMPLE,
|
||||
subject: 'value',
|
||||
operator: 'in',
|
||||
comparator: ['10'],
|
||||
clause: CLAUSES.WHERE,
|
||||
});
|
||||
|
||||
const sumValueAdhocMetric = new AdhocMetric({
|
||||
expressionType: EXPRESSION_TYPES.SIMPLE,
|
||||
column: { type: 'VARCHAR(255)', column_name: 'source' },
|
||||
aggregate: AGGREGATES.SUM,
|
||||
});
|
||||
|
||||
const options = [
|
||||
{ type: 'VARCHAR(255)', column_name: 'source' },
|
||||
{ type: 'VARCHAR(255)', column_name: 'target' },
|
||||
{ type: 'DOUBLE', column_name: 'value' },
|
||||
{ saved_metric_name: 'my_custom_metric' },
|
||||
sumValueAdhocMetric,
|
||||
];
|
||||
|
||||
function setup(overrides) {
|
||||
const onChange = sinon.spy();
|
||||
const onHeightChange = sinon.spy();
|
||||
const props = {
|
||||
adhocFilter: simpleAdhocFilter,
|
||||
onChange,
|
||||
onHeightChange,
|
||||
options,
|
||||
datasource: {},
|
||||
...overrides,
|
||||
};
|
||||
const wrapper = shallow(
|
||||
<AdhocFilterEditPopoverSimpleTabContent {...props} />,
|
||||
);
|
||||
return { wrapper, onChange, onHeightChange };
|
||||
}
|
||||
|
||||
describe('AdhocFilterEditPopoverSimpleTabContent', () => {
|
||||
it('renders the simple tab form', () => {
|
||||
const { wrapper } = setup();
|
||||
expect(wrapper.find(FormGroup)).toHaveLength(3);
|
||||
});
|
||||
|
||||
it('passes the new adhocFilter to onChange after onSubjectChange', () => {
|
||||
const { wrapper, onChange } = setup();
|
||||
wrapper
|
||||
.instance()
|
||||
.onSubjectChange({ type: 'VARCHAR(255)', column_name: 'source' });
|
||||
expect(onChange.calledOnce).toBe(true);
|
||||
expect(
|
||||
onChange.lastCall.args[0].equals(
|
||||
simpleAdhocFilter.duplicateWith({ subject: 'source' }),
|
||||
),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('may alter the clause in onSubjectChange if the old clause is not appropriate', () => {
|
||||
const { wrapper, onChange } = setup();
|
||||
wrapper.instance().onSubjectChange(sumValueAdhocMetric);
|
||||
expect(onChange.calledOnce).toBe(true);
|
||||
expect(
|
||||
onChange.lastCall.args[0].equals(
|
||||
simpleAdhocFilter.duplicateWith({
|
||||
subject: sumValueAdhocMetric.label,
|
||||
clause: CLAUSES.HAVING,
|
||||
}),
|
||||
),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('will convert from individual comparator to array if the operator changes to multi', () => {
|
||||
const { wrapper, onChange } = setup();
|
||||
wrapper.instance().onOperatorChange({ operator: 'in' });
|
||||
expect(onChange.calledOnce).toBe(true);
|
||||
expect(onChange.lastCall.args[0].comparator).toHaveLength(1);
|
||||
expect(onChange.lastCall.args[0].comparator[0]).toBe('10');
|
||||
expect(onChange.lastCall.args[0].operator).toBe('in');
|
||||
});
|
||||
|
||||
it('will convert from array to individual comparators if the operator changes from multi', () => {
|
||||
const { wrapper, onChange } = setup({
|
||||
adhocFilter: simpleMultiAdhocFilter,
|
||||
});
|
||||
wrapper.instance().onOperatorChange({ operator: '<' });
|
||||
expect(onChange.calledOnce).toBe(true);
|
||||
expect(
|
||||
onChange.lastCall.args[0].equals(
|
||||
simpleAdhocFilter.duplicateWith({ operator: '<', comparator: '10' }),
|
||||
),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('passes the new adhocFilter to onChange after onComparatorChange', () => {
|
||||
const { wrapper, onChange } = setup();
|
||||
wrapper.instance().onComparatorChange('20');
|
||||
expect(onChange.calledOnce).toBe(true);
|
||||
expect(
|
||||
onChange.lastCall.args[0].equals(
|
||||
simpleAdhocFilter.duplicateWith({ comparator: '20' }),
|
||||
),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('will filter operators for table datasources', () => {
|
||||
const { wrapper } = setup({ datasource: { type: 'table' } });
|
||||
expect(wrapper.instance().isOperatorRelevant('regex')).toBe(false);
|
||||
expect(wrapper.instance().isOperatorRelevant('LIKE')).toBe(true);
|
||||
});
|
||||
|
||||
it('will filter operators for druid datasources', () => {
|
||||
const { wrapper } = setup({ datasource: { type: 'druid' } });
|
||||
expect(wrapper.instance().isOperatorRelevant('regex')).toBe(true);
|
||||
expect(wrapper.instance().isOperatorRelevant('LIKE')).toBe(false);
|
||||
});
|
||||
|
||||
it('expands when its multi comparator input field expands', () => {
|
||||
const { wrapper, onHeightChange } = setup();
|
||||
|
||||
wrapper.instance().multiComparatorComponent = {
|
||||
_selectRef: { select: { control: { clientHeight: 57 } } },
|
||||
};
|
||||
wrapper.instance().handleMultiComparatorInputHeightChange();
|
||||
|
||||
expect(onHeightChange.calledOnce).toBe(true);
|
||||
expect(onHeightChange.lastCall.args[0]).toBe(27);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,77 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF 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.
|
||||
*/
|
||||
/* eslint-disable no-unused-expressions */
|
||||
import React from 'react';
|
||||
import sinon from 'sinon';
|
||||
import { shallow } from 'enzyme';
|
||||
import { FormGroup } from 'react-bootstrap';
|
||||
|
||||
import AdhocFilter, {
|
||||
EXPRESSION_TYPES,
|
||||
CLAUSES,
|
||||
} from '../../../../src/explore/AdhocFilter';
|
||||
import AdhocFilterEditPopoverSqlTabContent from '../../../../src/explore/components/AdhocFilterEditPopoverSqlTabContent';
|
||||
|
||||
const sqlAdhocFilter = new AdhocFilter({
|
||||
expressionType: EXPRESSION_TYPES.SQL,
|
||||
sqlExpression: 'value > 10',
|
||||
clause: CLAUSES.WHERE,
|
||||
});
|
||||
|
||||
function setup(overrides) {
|
||||
const onChange = sinon.spy();
|
||||
const props = {
|
||||
adhocFilter: sqlAdhocFilter,
|
||||
onChange,
|
||||
options: [],
|
||||
height: 100,
|
||||
...overrides,
|
||||
};
|
||||
const wrapper = shallow(<AdhocFilterEditPopoverSqlTabContent {...props} />);
|
||||
return { wrapper, onChange };
|
||||
}
|
||||
|
||||
describe('AdhocFilterEditPopoverSqlTabContent', () => {
|
||||
it('renders the sql tab form', () => {
|
||||
const { wrapper } = setup();
|
||||
expect(wrapper.find(FormGroup)).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('passes the new clause to onChange after onSqlExpressionClauseChange', () => {
|
||||
const { wrapper, onChange } = setup();
|
||||
wrapper.instance().onSqlExpressionClauseChange(CLAUSES.HAVING);
|
||||
expect(onChange.calledOnce).toBe(true);
|
||||
expect(
|
||||
onChange.lastCall.args[0].equals(
|
||||
sqlAdhocFilter.duplicateWith({ clause: CLAUSES.HAVING }),
|
||||
),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('passes the new query to onChange after onSqlExpressionChange', () => {
|
||||
const { wrapper, onChange } = setup();
|
||||
wrapper.instance().onSqlExpressionChange('value < 5');
|
||||
expect(onChange.calledOnce).toBe(true);
|
||||
expect(
|
||||
onChange.lastCall.args[0].equals(
|
||||
sqlAdhocFilter.duplicateWith({ sqlExpression: 'value < 5' }),
|
||||
),
|
||||
).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,135 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF 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.
|
||||
*/
|
||||
/* eslint-disable no-unused-expressions */
|
||||
import React from 'react';
|
||||
import sinon from 'sinon';
|
||||
import { shallow } from 'enzyme';
|
||||
import { Button, Popover, Tab, Tabs } from 'react-bootstrap';
|
||||
|
||||
import AdhocFilter, {
|
||||
EXPRESSION_TYPES,
|
||||
CLAUSES,
|
||||
} from '../../../../src/explore/AdhocFilter';
|
||||
import AdhocMetric from '../../../../src/explore/AdhocMetric';
|
||||
import AdhocFilterEditPopover from '../../../../src/explore/components/AdhocFilterEditPopover';
|
||||
import AdhocFilterEditPopoverSimpleTabContent from '../../../../src/explore/components/AdhocFilterEditPopoverSimpleTabContent';
|
||||
import AdhocFilterEditPopoverSqlTabContent from '../../../../src/explore/components/AdhocFilterEditPopoverSqlTabContent';
|
||||
import { AGGREGATES } from '../../../../src/explore/constants';
|
||||
|
||||
const simpleAdhocFilter = new AdhocFilter({
|
||||
expressionType: EXPRESSION_TYPES.SIMPLE,
|
||||
subject: 'value',
|
||||
operator: '>',
|
||||
comparator: '10',
|
||||
clause: CLAUSES.WHERE,
|
||||
});
|
||||
|
||||
const sqlAdhocFilter = new AdhocFilter({
|
||||
expressionType: EXPRESSION_TYPES.SQL,
|
||||
sqlExpression: 'value > 10',
|
||||
clause: CLAUSES.WHERE,
|
||||
});
|
||||
|
||||
const sumValueAdhocMetric = new AdhocMetric({
|
||||
expressionType: EXPRESSION_TYPES.SIMPLE,
|
||||
column: { type: 'VARCHAR(255)', column_name: 'source' },
|
||||
aggregate: AGGREGATES.SUM,
|
||||
});
|
||||
|
||||
const options = [
|
||||
{ type: 'VARCHAR(255)', column_name: 'source' },
|
||||
{ type: 'VARCHAR(255)', column_name: 'target' },
|
||||
{ type: 'DOUBLE', column_name: 'value' },
|
||||
{ saved_metric_name: 'my_custom_metric' },
|
||||
sumValueAdhocMetric,
|
||||
];
|
||||
|
||||
function setup(overrides) {
|
||||
const onChange = sinon.spy();
|
||||
const onClose = sinon.spy();
|
||||
const onResize = sinon.spy();
|
||||
const props = {
|
||||
adhocFilter: simpleAdhocFilter,
|
||||
onChange,
|
||||
onClose,
|
||||
onResize,
|
||||
options,
|
||||
datasource: {},
|
||||
...overrides,
|
||||
};
|
||||
const wrapper = shallow(<AdhocFilterEditPopover {...props} />);
|
||||
return { wrapper, onChange, onClose, onResize };
|
||||
}
|
||||
|
||||
describe('AdhocFilterEditPopover', () => {
|
||||
it('renders simple tab content by default', () => {
|
||||
const { wrapper } = setup();
|
||||
expect(wrapper.find(Popover)).toHaveLength(1);
|
||||
expect(wrapper.find(Tabs)).toHaveLength(1);
|
||||
expect(wrapper.find(Tab)).toHaveLength(2);
|
||||
expect(wrapper.find(Button)).toHaveLength(2);
|
||||
expect(wrapper.find(AdhocFilterEditPopoverSimpleTabContent)).toHaveLength(
|
||||
1,
|
||||
);
|
||||
});
|
||||
|
||||
it('renders sql tab content when the adhoc filter expressionType is sql', () => {
|
||||
const { wrapper } = setup({ adhocFilter: sqlAdhocFilter });
|
||||
expect(wrapper.find(Popover)).toHaveLength(1);
|
||||
expect(wrapper.find(Tabs)).toHaveLength(1);
|
||||
expect(wrapper.find(Tab)).toHaveLength(2);
|
||||
expect(wrapper.find(Button)).toHaveLength(2);
|
||||
expect(wrapper.find(AdhocFilterEditPopoverSqlTabContent)).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('overwrites the adhocFilter in state with onAdhocFilterChange', () => {
|
||||
const { wrapper } = setup();
|
||||
wrapper.instance().onAdhocFilterChange(sqlAdhocFilter);
|
||||
expect(wrapper.state('adhocFilter')).toEqual(sqlAdhocFilter);
|
||||
});
|
||||
|
||||
it('prevents saving if the filter is invalid', () => {
|
||||
const { wrapper } = setup();
|
||||
expect(wrapper.find(Button).find({ disabled: true })).toHaveLength(0);
|
||||
wrapper
|
||||
.instance()
|
||||
.onAdhocFilterChange(simpleAdhocFilter.duplicateWith({ operator: null }));
|
||||
expect(wrapper.find(Button).find({ disabled: true })).toHaveLength(1);
|
||||
wrapper.instance().onAdhocFilterChange(sqlAdhocFilter);
|
||||
expect(wrapper.find(Button).find({ disabled: true })).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('highlights save if changes are present', () => {
|
||||
const { wrapper } = setup();
|
||||
expect(wrapper.find(Button).find({ bsStyle: 'primary' })).toHaveLength(0);
|
||||
wrapper.instance().onAdhocFilterChange(sqlAdhocFilter);
|
||||
expect(wrapper.find(Button).find({ bsStyle: 'primary' })).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('will initiate a drag when clicked', () => {
|
||||
const { wrapper } = setup();
|
||||
wrapper.instance().onDragDown = sinon.spy();
|
||||
wrapper.instance().forceUpdate();
|
||||
|
||||
expect(wrapper.find('i.glyphicon-resize-full')).toHaveLength(1);
|
||||
expect(wrapper.instance().onDragDown.calledOnce).toBe(false);
|
||||
wrapper.find('i.glyphicon-resize-full').simulate('mouseDown', {});
|
||||
expect(wrapper.instance().onDragDown.calledOnce).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF 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.
|
||||
*/
|
||||
/* eslint-disable no-unused-expressions */
|
||||
import React from 'react';
|
||||
import sinon from 'sinon';
|
||||
import { shallow } from 'enzyme';
|
||||
import { Label, OverlayTrigger } from 'react-bootstrap';
|
||||
|
||||
import AdhocFilter, {
|
||||
EXPRESSION_TYPES,
|
||||
CLAUSES,
|
||||
} from '../../../../src/explore/AdhocFilter';
|
||||
import AdhocFilterOption from '../../../../src/explore/components/AdhocFilterOption';
|
||||
|
||||
const simpleAdhocFilter = new AdhocFilter({
|
||||
expressionType: EXPRESSION_TYPES.SIMPLE,
|
||||
subject: 'value',
|
||||
operator: '>',
|
||||
comparator: '10',
|
||||
clause: CLAUSES.WHERE,
|
||||
});
|
||||
|
||||
function setup(overrides) {
|
||||
const onFilterEdit = sinon.spy();
|
||||
const props = {
|
||||
adhocFilter: simpleAdhocFilter,
|
||||
onFilterEdit,
|
||||
options: [],
|
||||
datasource: {},
|
||||
...overrides,
|
||||
};
|
||||
const wrapper = shallow(<AdhocFilterOption {...props} />);
|
||||
return { wrapper };
|
||||
}
|
||||
|
||||
describe('AdhocFilterOption', () => {
|
||||
it('renders an overlay trigger wrapper for the label', () => {
|
||||
const { wrapper } = setup();
|
||||
expect(wrapper.find(OverlayTrigger)).toHaveLength(1);
|
||||
expect(wrapper.find(Label)).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,69 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF 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.
|
||||
*/
|
||||
/* eslint-disable no-unused-expressions */
|
||||
import React from 'react';
|
||||
import sinon from 'sinon';
|
||||
import { shallow } from 'enzyme';
|
||||
import { OverlayTrigger } from 'react-bootstrap';
|
||||
|
||||
import AdhocMetric from '../../../../src/explore/AdhocMetric';
|
||||
import AdhocMetricEditPopoverTitle from '../../../../src/explore/components/AdhocMetricEditPopoverTitle';
|
||||
import { AGGREGATES } from '../../../../src/explore/constants';
|
||||
|
||||
const columns = [
|
||||
{ type: 'VARCHAR(255)', column_name: 'source' },
|
||||
{ type: 'VARCHAR(255)', column_name: 'target' },
|
||||
{ type: 'DOUBLE', column_name: 'value' },
|
||||
];
|
||||
|
||||
const sumValueAdhocMetric = new AdhocMetric({
|
||||
column: columns[2],
|
||||
aggregate: AGGREGATES.SUM,
|
||||
});
|
||||
|
||||
function setup(overrides) {
|
||||
const onChange = sinon.spy();
|
||||
const props = {
|
||||
adhocMetric: sumValueAdhocMetric,
|
||||
onChange,
|
||||
...overrides,
|
||||
};
|
||||
const wrapper = shallow(<AdhocMetricEditPopoverTitle {...props} />);
|
||||
return { wrapper, onChange };
|
||||
}
|
||||
|
||||
describe('AdhocMetricEditPopoverTitle', () => {
|
||||
it('renders an OverlayTrigger wrapper with the title', () => {
|
||||
const { wrapper } = setup();
|
||||
expect(wrapper.find(OverlayTrigger)).toHaveLength(1);
|
||||
expect(
|
||||
wrapper
|
||||
.find(OverlayTrigger)
|
||||
.find('span')
|
||||
.text(),
|
||||
).toBe('My Metric\xa0');
|
||||
});
|
||||
|
||||
it('transfers to edit mode when clicked', () => {
|
||||
const { wrapper } = setup();
|
||||
expect(wrapper.state('isEditable')).toBe(false);
|
||||
wrapper.simulate('click');
|
||||
expect(wrapper.state('isEditable')).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,137 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF 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.
|
||||
*/
|
||||
/* eslint-disable no-unused-expressions */
|
||||
import React from 'react';
|
||||
import sinon from 'sinon';
|
||||
import { shallow } from 'enzyme';
|
||||
import { Button, FormGroup, Popover } from 'react-bootstrap';
|
||||
|
||||
import AdhocMetric, {
|
||||
EXPRESSION_TYPES,
|
||||
} from '../../../../src/explore/AdhocMetric';
|
||||
import AdhocMetricEditPopover from '../../../../src/explore/components/AdhocMetricEditPopover';
|
||||
import { AGGREGATES } from '../../../../src/explore/constants';
|
||||
|
||||
const columns = [
|
||||
{ type: 'VARCHAR(255)', column_name: 'source' },
|
||||
{ type: 'VARCHAR(255)', column_name: 'target' },
|
||||
{ type: 'DOUBLE', column_name: 'value' },
|
||||
];
|
||||
|
||||
const sumValueAdhocMetric = new AdhocMetric({
|
||||
expressionType: EXPRESSION_TYPES.SIMPLE,
|
||||
column: columns[2],
|
||||
aggregate: AGGREGATES.SUM,
|
||||
});
|
||||
|
||||
const sqlExpressionAdhocMetric = new AdhocMetric({
|
||||
expressionType: EXPRESSION_TYPES.SQL,
|
||||
sqlExpression: 'COUNT(*)',
|
||||
});
|
||||
|
||||
function setup(overrides) {
|
||||
const onChange = sinon.spy();
|
||||
const onClose = sinon.spy();
|
||||
const props = {
|
||||
adhocMetric: sumValueAdhocMetric,
|
||||
onChange,
|
||||
onClose,
|
||||
columns,
|
||||
...overrides,
|
||||
};
|
||||
const wrapper = shallow(<AdhocMetricEditPopover {...props} />);
|
||||
return { wrapper, onChange, onClose };
|
||||
}
|
||||
|
||||
describe('AdhocMetricEditPopover', () => {
|
||||
it('renders a popover with edit metric form contents', () => {
|
||||
const { wrapper } = setup();
|
||||
expect(wrapper.find(Popover)).toHaveLength(1);
|
||||
expect(wrapper.find(FormGroup)).toHaveLength(3);
|
||||
expect(wrapper.find(Button)).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('overwrites the adhocMetric in state with onColumnChange', () => {
|
||||
const { wrapper } = setup();
|
||||
wrapper.instance().onColumnChange(columns[0]);
|
||||
expect(wrapper.state('adhocMetric')).toEqual(
|
||||
sumValueAdhocMetric.duplicateWith({ column: columns[0] }),
|
||||
);
|
||||
});
|
||||
|
||||
it('overwrites the adhocMetric in state with onAggregateChange', () => {
|
||||
const { wrapper } = setup();
|
||||
wrapper.instance().onAggregateChange({ aggregate: AGGREGATES.AVG });
|
||||
expect(wrapper.state('adhocMetric')).toEqual(
|
||||
sumValueAdhocMetric.duplicateWith({ aggregate: AGGREGATES.AVG }),
|
||||
);
|
||||
});
|
||||
|
||||
it('overwrites the adhocMetric in state with onSqlExpressionChange', () => {
|
||||
const { wrapper } = setup({ adhocMetric: sqlExpressionAdhocMetric });
|
||||
wrapper.instance().onSqlExpressionChange('COUNT(1)');
|
||||
expect(wrapper.state('adhocMetric')).toEqual(
|
||||
sqlExpressionAdhocMetric.duplicateWith({ sqlExpression: 'COUNT(1)' }),
|
||||
);
|
||||
});
|
||||
|
||||
it('overwrites the adhocMetric in state with onLabelChange', () => {
|
||||
const { wrapper } = setup();
|
||||
wrapper.instance().onLabelChange({ target: { value: 'new label' } });
|
||||
expect(wrapper.state('adhocMetric').label).toBe('new label');
|
||||
expect(wrapper.state('adhocMetric').hasCustomLabel).toBe(true);
|
||||
});
|
||||
|
||||
it('returns to default labels when the custom label is cleared', () => {
|
||||
const { wrapper } = setup();
|
||||
wrapper.instance().onLabelChange({ target: { value: 'new label' } });
|
||||
wrapper.instance().onLabelChange({ target: { value: '' } });
|
||||
expect(wrapper.state('adhocMetric').label).toBe('SUM(value)');
|
||||
expect(wrapper.state('adhocMetric').hasCustomLabel).toBe(false);
|
||||
});
|
||||
|
||||
it('prevents saving if no column or aggregate is chosen', () => {
|
||||
const { wrapper } = setup();
|
||||
expect(wrapper.find(Button).find({ disabled: true })).toHaveLength(0);
|
||||
wrapper.instance().onColumnChange(null);
|
||||
expect(wrapper.find(Button).find({ disabled: true })).toHaveLength(1);
|
||||
wrapper.instance().onColumnChange({ column: columns[0] });
|
||||
expect(wrapper.find(Button).find({ disabled: true })).toHaveLength(0);
|
||||
wrapper.instance().onAggregateChange(null);
|
||||
expect(wrapper.find(Button).find({ disabled: true })).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('highlights save if changes are present', () => {
|
||||
const { wrapper } = setup();
|
||||
expect(wrapper.find(Button).find({ bsStyle: 'primary' })).toHaveLength(0);
|
||||
wrapper.instance().onColumnChange({ column: columns[1] });
|
||||
expect(wrapper.find(Button).find({ bsStyle: 'primary' })).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('will initiate a drag when clicked', () => {
|
||||
const { wrapper } = setup();
|
||||
wrapper.instance().onDragDown = sinon.spy();
|
||||
wrapper.instance().forceUpdate();
|
||||
|
||||
expect(wrapper.find('i.glyphicon-resize-full')).toHaveLength(1);
|
||||
expect(wrapper.instance().onDragDown.calledOnce).toBe(false);
|
||||
wrapper.find('i.glyphicon-resize-full').simulate('mouseDown');
|
||||
expect(wrapper.instance().onDragDown.calledOnce).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF 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.
|
||||
*/
|
||||
/* eslint-disable no-unused-expressions */
|
||||
import React from 'react';
|
||||
import sinon from 'sinon';
|
||||
import { shallow } from 'enzyme';
|
||||
import { Label, OverlayTrigger } from 'react-bootstrap';
|
||||
|
||||
import AdhocMetric from '../../../../src/explore/AdhocMetric';
|
||||
import AdhocMetricOption from '../../../../src/explore/components/AdhocMetricOption';
|
||||
import { AGGREGATES } from '../../../../src/explore/constants';
|
||||
|
||||
const columns = [
|
||||
{ type: 'VARCHAR(255)', column_name: 'source' },
|
||||
{ type: 'VARCHAR(255)', column_name: 'target' },
|
||||
{ type: 'DOUBLE', column_name: 'value' },
|
||||
];
|
||||
|
||||
const sumValueAdhocMetric = new AdhocMetric({
|
||||
column: columns[2],
|
||||
aggregate: AGGREGATES.SUM,
|
||||
});
|
||||
|
||||
function setup(overrides) {
|
||||
const onMetricEdit = sinon.spy();
|
||||
const props = {
|
||||
adhocMetric: sumValueAdhocMetric,
|
||||
onMetricEdit,
|
||||
columns,
|
||||
...overrides,
|
||||
};
|
||||
const wrapper = shallow(<AdhocMetricOption {...props} />);
|
||||
return { wrapper, onMetricEdit };
|
||||
}
|
||||
|
||||
describe('AdhocMetricOption', () => {
|
||||
it('renders an overlay trigger wrapper for the label', () => {
|
||||
const { wrapper } = setup();
|
||||
expect(wrapper.find(OverlayTrigger)).toHaveLength(1);
|
||||
expect(wrapper.find(Label)).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF 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.
|
||||
*/
|
||||
/* eslint-disable no-unused-expressions */
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import AdhocMetricStaticOption from '../../../../src/explore/components/AdhocMetricStaticOption';
|
||||
import AdhocMetric, {
|
||||
EXPRESSION_TYPES,
|
||||
} from '../../../../src/explore/AdhocMetric';
|
||||
import { AGGREGATES } from '../../../../src/explore/constants';
|
||||
|
||||
const sumValueAdhocMetric = new AdhocMetric({
|
||||
expressionType: EXPRESSION_TYPES.SIMPLE,
|
||||
column: { type: 'VARCHAR(255)', column_name: 'source' },
|
||||
aggregate: AGGREGATES.SUM,
|
||||
});
|
||||
|
||||
describe('AdhocMetricStaticOption', () => {
|
||||
it('renders the adhoc metrics label', () => {
|
||||
const wrapper = shallow(
|
||||
<AdhocMetricStaticOption adhocMetric={sumValueAdhocMetric} />,
|
||||
);
|
||||
expect(wrapper.text()).toBe('SUM(source)');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF 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.
|
||||
*/
|
||||
/* eslint-disable no-unused-expressions */
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import AggregateOption from '../../../../src/explore/components/AggregateOption';
|
||||
|
||||
describe('AggregateOption', () => {
|
||||
it('renders the aggregate', () => {
|
||||
const wrapper = shallow(
|
||||
<AggregateOption aggregate={{ aggregate_name: 'SUM' }} />,
|
||||
);
|
||||
expect(wrapper.text()).toBe('SUM');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF 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.
|
||||
*/
|
||||
/* eslint-disable no-unused-expressions */
|
||||
import React from 'react';
|
||||
import { FormControl } from 'react-bootstrap';
|
||||
import sinon from 'sinon';
|
||||
import { mount } from 'enzyme';
|
||||
|
||||
import BoundsControl from '../../../../src/explore/components/controls/BoundsControl';
|
||||
|
||||
const defaultProps = {
|
||||
name: 'y_axis_bounds',
|
||||
label: 'Bounds of the y axis',
|
||||
onChange: sinon.spy(),
|
||||
};
|
||||
|
||||
describe('BoundsControl', () => {
|
||||
let wrapper;
|
||||
|
||||
beforeEach(() => {
|
||||
wrapper = mount(<BoundsControl {...defaultProps} />);
|
||||
});
|
||||
|
||||
it('renders two FormControls', () => {
|
||||
expect(wrapper.find(FormControl)).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('errors on non-numeric', () => {
|
||||
wrapper
|
||||
.find(FormControl)
|
||||
.first()
|
||||
.simulate('change', { target: { value: 's' } });
|
||||
expect(defaultProps.onChange.calledWith([null, null])).toBe(true);
|
||||
expect(defaultProps.onChange.getCall(0).args[1][0]).toContain(
|
||||
'value should be numeric',
|
||||
);
|
||||
});
|
||||
it('casts to numeric', () => {
|
||||
wrapper
|
||||
.find(FormControl)
|
||||
.first()
|
||||
.simulate('change', { target: { value: '1' } });
|
||||
wrapper
|
||||
.find(FormControl)
|
||||
.last()
|
||||
.simulate('change', { target: { value: '5' } });
|
||||
expect(defaultProps.onChange.calledWith([1, 5])).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF 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.
|
||||
*/
|
||||
/* eslint-disable no-unused-expressions */
|
||||
import React from 'react';
|
||||
import sinon from 'sinon';
|
||||
import { shallow, mount } from 'enzyme';
|
||||
|
||||
import CheckboxControl from '../../../../src/explore/components/controls/CheckboxControl';
|
||||
import ControlHeader from '../../../../src/explore/components/ControlHeader';
|
||||
import Checkbox from '../../../../src/components/Checkbox';
|
||||
|
||||
const defaultProps = {
|
||||
name: 'show_legend',
|
||||
onChange: sinon.spy(),
|
||||
value: false,
|
||||
label: 'checkbox label',
|
||||
};
|
||||
|
||||
describe('CheckboxControl', () => {
|
||||
let wrapper;
|
||||
|
||||
beforeEach(() => {
|
||||
wrapper = shallow(<CheckboxControl {...defaultProps} />);
|
||||
});
|
||||
|
||||
it('renders a Checkbox', () => {
|
||||
const controlHeader = wrapper.find(ControlHeader);
|
||||
expect(controlHeader).toHaveLength(1);
|
||||
|
||||
const headerWrapper = controlHeader.shallow();
|
||||
expect(headerWrapper.find(Checkbox)).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('Checks the box when the label is clicked', () => {
|
||||
const fullComponent = mount(<CheckboxControl {...defaultProps} />);
|
||||
|
||||
const spy = sinon.spy(fullComponent.instance(), 'onChange');
|
||||
|
||||
fullComponent.instance().forceUpdate();
|
||||
|
||||
fullComponent
|
||||
.find('label span')
|
||||
.last()
|
||||
.simulate('click');
|
||||
|
||||
expect(spy.calledOnce).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF 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.
|
||||
*/
|
||||
/* eslint-disable no-unused-expressions */
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
import { OverlayTrigger } from 'react-bootstrap';
|
||||
import { SketchPicker } from 'react-color';
|
||||
import {
|
||||
CategoricalScheme,
|
||||
getCategoricalSchemeRegistry,
|
||||
} from '@superset-ui/color';
|
||||
|
||||
import ColorPickerControl from '../../../../src/explore/components/controls/ColorPickerControl';
|
||||
import ControlHeader from '../../../../src/explore/components/ControlHeader';
|
||||
|
||||
const defaultProps = {
|
||||
value: {},
|
||||
};
|
||||
|
||||
describe('ColorPickerControl', () => {
|
||||
let wrapper;
|
||||
let inst;
|
||||
beforeEach(() => {
|
||||
getCategoricalSchemeRegistry()
|
||||
.registerValue(
|
||||
'test',
|
||||
new CategoricalScheme({
|
||||
id: 'test',
|
||||
colors: ['red', 'green', 'blue'],
|
||||
}),
|
||||
)
|
||||
.setDefaultKey('test');
|
||||
wrapper = shallow(<ColorPickerControl {...defaultProps} />);
|
||||
inst = wrapper.instance();
|
||||
});
|
||||
|
||||
it('renders a OverlayTrigger', () => {
|
||||
const controlHeader = wrapper.find(ControlHeader);
|
||||
expect(controlHeader).toHaveLength(1);
|
||||
expect(wrapper.find(OverlayTrigger)).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('renders a Popover with a SketchPicker', () => {
|
||||
const popOver = shallow(inst.renderPopover());
|
||||
expect(popOver.find(SketchPicker)).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF 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.
|
||||
*/
|
||||
/* eslint-disable no-unused-expressions */
|
||||
import React from 'react';
|
||||
import { mount } from 'enzyme';
|
||||
import { Creatable } from 'react-select';
|
||||
import { getCategoricalSchemeRegistry } from '@superset-ui/color';
|
||||
|
||||
import ColorSchemeControl from '../../../../src/explore/components/controls/ColorSchemeControl';
|
||||
|
||||
const defaultProps = {
|
||||
options: getCategoricalSchemeRegistry()
|
||||
.keys()
|
||||
.map(s => [s, s]),
|
||||
};
|
||||
|
||||
describe('ColorSchemeControl', () => {
|
||||
let wrapper;
|
||||
beforeEach(() => {
|
||||
wrapper = mount(<ColorSchemeControl {...defaultProps} />);
|
||||
});
|
||||
|
||||
it('renders a Creatable', () => {
|
||||
expect(wrapper.find(Creatable)).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF 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 { shallow } from 'enzyme';
|
||||
import { Panel } from 'react-bootstrap';
|
||||
|
||||
import InfoTooltipWithTrigger from '../../../../src/components/InfoTooltipWithTrigger';
|
||||
|
||||
import ControlPanelSection from '../../../../src/explore/components/ControlPanelSection';
|
||||
|
||||
const defaultProps = {
|
||||
children: <div>a child element</div>,
|
||||
};
|
||||
|
||||
const optionalProps = {
|
||||
label: 'my label',
|
||||
description: 'my description',
|
||||
tooltip: 'my tooltip',
|
||||
};
|
||||
|
||||
describe('ControlPanelSection', () => {
|
||||
let wrapper;
|
||||
let props;
|
||||
it('is a valid element', () => {
|
||||
expect(
|
||||
React.isValidElement(<ControlPanelSection {...defaultProps} />),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('renders a Panel component', () => {
|
||||
wrapper = shallow(<ControlPanelSection {...defaultProps} />);
|
||||
expect(wrapper.find(Panel)).toHaveLength(1);
|
||||
});
|
||||
|
||||
describe('with optional props', () => {
|
||||
beforeEach(() => {
|
||||
props = Object.assign(defaultProps, optionalProps);
|
||||
wrapper = shallow(<ControlPanelSection {...props} />);
|
||||
});
|
||||
|
||||
it('renders a label if present', () => {
|
||||
expect(
|
||||
wrapper
|
||||
.find(Panel)
|
||||
.dive()
|
||||
.text(),
|
||||
).toContain('my label');
|
||||
});
|
||||
|
||||
it('renders a InfoTooltipWithTrigger if label and tooltip is present', () => {
|
||||
expect(
|
||||
wrapper
|
||||
.find(Panel)
|
||||
.dive()
|
||||
.find(InfoTooltipWithTrigger),
|
||||
).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,97 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF 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 { shallow } from 'enzyme';
|
||||
import { getChartControlPanelRegistry } from '@superset-ui/chart';
|
||||
import { t } from '@superset-ui/translation';
|
||||
import { defaultControls } from 'src/explore/store';
|
||||
import { getFormDataFromControls } from 'src/explore/controlUtils';
|
||||
import { ControlPanelsContainer } from 'src/explore/components/ControlPanelsContainer';
|
||||
import ControlPanelSection from 'src/explore/components/ControlPanelSection';
|
||||
|
||||
describe('ControlPanelsContainer', () => {
|
||||
let wrapper;
|
||||
|
||||
beforeAll(() => {
|
||||
getChartControlPanelRegistry().registerValue('table', {
|
||||
controlPanelSections: [
|
||||
{
|
||||
label: t('GROUP BY'),
|
||||
description: t(
|
||||
'Use this section if you want a query that aggregates',
|
||||
),
|
||||
expanded: true,
|
||||
controlSetRows: [
|
||||
['groupby'],
|
||||
['metrics'],
|
||||
['percent_metrics'],
|
||||
['timeseries_limit_metric', 'row_limit'],
|
||||
['include_time', 'order_desc'],
|
||||
],
|
||||
},
|
||||
{
|
||||
label: t('NOT GROUPED BY'),
|
||||
description: t('Use this section if you want to query atomic rows'),
|
||||
expanded: true,
|
||||
controlSetRows: [
|
||||
['all_columns'],
|
||||
['order_by_cols'],
|
||||
['row_limit', null],
|
||||
],
|
||||
},
|
||||
{
|
||||
label: t('Query'),
|
||||
expanded: true,
|
||||
controlSetRows: [['adhoc_filters']],
|
||||
},
|
||||
{
|
||||
label: t('Options'),
|
||||
expanded: true,
|
||||
controlSetRows: [
|
||||
['table_timestamp_format'],
|
||||
['page_length', null],
|
||||
['include_search', 'table_filter'],
|
||||
['align_pn', 'color_pn'],
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
getChartControlPanelRegistry().remove('table');
|
||||
});
|
||||
|
||||
function getDefaultProps() {
|
||||
return {
|
||||
datasource_type: 'table',
|
||||
actions: {},
|
||||
controls: defaultControls,
|
||||
// Note: default viz_type is table
|
||||
form_data: getFormDataFromControls(defaultControls),
|
||||
isDatasourceMetaLoading: false,
|
||||
exploreState: {},
|
||||
};
|
||||
}
|
||||
|
||||
it('renders ControlPanelSections', () => {
|
||||
wrapper = shallow(<ControlPanelsContainer {...getDefaultProps()} />);
|
||||
expect(wrapper.find(ControlPanelSection)).toHaveLength(6);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF 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 { shallow } from 'enzyme';
|
||||
import ControlSetRow from '../../../../src/explore/components/ControlRow';
|
||||
|
||||
describe('ControlSetRow', () => {
|
||||
it('renders a single row with one element', () => {
|
||||
const wrapper = shallow(<ControlSetRow controls={[<a />]} />);
|
||||
expect(wrapper.find('.row')).toHaveLength(1);
|
||||
expect(wrapper.find('.row').find('a')).toHaveLength(1);
|
||||
});
|
||||
it('renders a single row with two elements', () => {
|
||||
const wrapper = shallow(<ControlSetRow controls={[<a />, <a />]} />);
|
||||
expect(wrapper.find('.row')).toHaveLength(1);
|
||||
expect(wrapper.find('.row').find('a')).toHaveLength(2);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF 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 sinon from 'sinon';
|
||||
import configureStore from 'redux-mock-store';
|
||||
import { shallow } from 'enzyme';
|
||||
import DatasourceModal from '../../../../src/datasource/DatasourceModal';
|
||||
import ChangeDatasourceModal from '../../../../src/datasource/ChangeDatasourceModal';
|
||||
import DatasourceControl from '../../../../src/explore/components/controls/DatasourceControl';
|
||||
|
||||
const defaultProps = {
|
||||
name: 'datasource',
|
||||
label: 'Datasource',
|
||||
value: '1__table',
|
||||
datasource: {
|
||||
name: 'birth_names',
|
||||
type: 'table',
|
||||
uid: '1__table',
|
||||
id: 1,
|
||||
columns: [],
|
||||
metrics: [],
|
||||
database: {
|
||||
backend: 'mysql',
|
||||
name: 'main',
|
||||
},
|
||||
},
|
||||
onChange: sinon.spy(),
|
||||
};
|
||||
|
||||
describe('DatasourceControl', () => {
|
||||
function setup() {
|
||||
const mockStore = configureStore([]);
|
||||
const store = mockStore({});
|
||||
return shallow(<DatasourceControl {...defaultProps} />, {
|
||||
context: { store },
|
||||
});
|
||||
}
|
||||
|
||||
it('renders a Modal', () => {
|
||||
const wrapper = setup();
|
||||
expect(wrapper.find(DatasourceModal)).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('renders a ChangeDatasourceModal', () => {
|
||||
const wrapper = setup();
|
||||
expect(wrapper.find(ChangeDatasourceModal)).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,81 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF 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.
|
||||
*/
|
||||
/* eslint-disable no-unused-expressions */
|
||||
import React from 'react';
|
||||
import sinon from 'sinon';
|
||||
import { shallow } from 'enzyme';
|
||||
import { Button, Label } from 'react-bootstrap';
|
||||
|
||||
import DateFilterControl from '../../../../src/explore/components/controls/DateFilterControl';
|
||||
import ControlHeader from '../../../../src/explore/components/ControlHeader';
|
||||
|
||||
const defaultProps = {
|
||||
animation: false,
|
||||
name: 'date',
|
||||
onChange: sinon.spy(),
|
||||
value: '90 days ago',
|
||||
label: 'date',
|
||||
};
|
||||
|
||||
describe('DateFilterControl', () => {
|
||||
let wrapper;
|
||||
|
||||
beforeEach(() => {
|
||||
wrapper = shallow(<DateFilterControl {...defaultProps} />);
|
||||
});
|
||||
|
||||
it('renders a ControlHeader', () => {
|
||||
const controlHeader = wrapper.find(ControlHeader);
|
||||
expect(controlHeader).toHaveLength(1);
|
||||
});
|
||||
it('renders 3 Buttons', () => {
|
||||
const label = wrapper.find(Label).first();
|
||||
label.simulate('click');
|
||||
setTimeout(() => {
|
||||
expect(wrapper.find(Button)).toHaveLength(3);
|
||||
}, 10);
|
||||
});
|
||||
it('loads the right state', () => {
|
||||
const label = wrapper.find(Label).first();
|
||||
label.simulate('click');
|
||||
setTimeout(() => {
|
||||
expect(wrapper.state().num).toBe('90');
|
||||
}, 10);
|
||||
});
|
||||
it('renders 2 dimmed sections', () => {
|
||||
const label = wrapper.find(Label).first();
|
||||
label.simulate('click');
|
||||
setTimeout(() => {
|
||||
expect(wrapper.find(Button)).toHaveLength(3);
|
||||
}, 10);
|
||||
});
|
||||
it('opens and closes', () => {
|
||||
const label = wrapper.find(Label).first();
|
||||
label.simulate('click');
|
||||
setTimeout(() => {
|
||||
expect(wrapper.find('.popover')).toHaveLength(1);
|
||||
expect(wrapper.find('.ok'))
|
||||
.first()
|
||||
.simulate('click');
|
||||
setTimeout(() => {
|
||||
expect(wrapper.find('.popover')).toHaveLength(0);
|
||||
}, 10);
|
||||
}, 10);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF 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 { mount } from 'enzyme';
|
||||
import ModalTrigger from './../../../../src/components/ModalTrigger';
|
||||
|
||||
import DisplayQueryButton from '../../../../src/explore/components/DisplayQueryButton';
|
||||
|
||||
describe('DisplayQueryButton', () => {
|
||||
const defaultProps = {
|
||||
animation: false,
|
||||
queryResponse: {
|
||||
query: 'SELECT * FROM foo',
|
||||
language: 'sql',
|
||||
},
|
||||
chartStatus: 'success',
|
||||
queryEndpoint: 'localhost',
|
||||
latestQueryFormData: {
|
||||
datasource: '1__table',
|
||||
},
|
||||
};
|
||||
|
||||
it('is valid', () => {
|
||||
expect(React.isValidElement(<DisplayQueryButton {...defaultProps} />)).toBe(
|
||||
true,
|
||||
);
|
||||
});
|
||||
it('renders a dropdown', () => {
|
||||
const wrapper = mount(<DisplayQueryButton {...defaultProps} />);
|
||||
expect(wrapper.find(ModalTrigger)).toHaveLength(3);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF 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 { shallow, mount } from 'enzyme';
|
||||
import { OverlayTrigger } from 'react-bootstrap';
|
||||
import sinon from 'sinon';
|
||||
|
||||
import EmbedCodeButton from '../../../../src/explore/components/EmbedCodeButton';
|
||||
import * as exploreUtils from '../../../../src/explore/exploreUtils';
|
||||
|
||||
describe('EmbedCodeButton', () => {
|
||||
const defaultProps = {
|
||||
latestQueryFormData: { datasource: '107__table' },
|
||||
};
|
||||
|
||||
it('renders', () => {
|
||||
expect(React.isValidElement(<EmbedCodeButton {...defaultProps} />)).toBe(
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
it('renders overlay trigger', () => {
|
||||
const wrapper = shallow(<EmbedCodeButton {...defaultProps} />);
|
||||
expect(wrapper.find(OverlayTrigger)).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('returns correct embed code', () => {
|
||||
const stub = sinon
|
||||
.stub(exploreUtils, 'getExploreLongUrl')
|
||||
.callsFake(() => 'endpoint_url');
|
||||
const wrapper = mount(<EmbedCodeButton {...defaultProps} />);
|
||||
wrapper.setState({
|
||||
height: '1000',
|
||||
width: '2000',
|
||||
});
|
||||
const embedHTML =
|
||||
'<iframe\n' +
|
||||
' width="2000"\n' +
|
||||
' height="1000"\n' +
|
||||
' seamless\n' +
|
||||
' frameBorder="0"\n' +
|
||||
' scrolling="no"\n' +
|
||||
' src="http://localhostendpoint_url&height=1000"\n' +
|
||||
'>\n' +
|
||||
'</iframe>';
|
||||
expect(wrapper.instance().generateEmbedHTML()).toBe(embedHTML);
|
||||
stub.restore();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF 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 { shallow } from 'enzyme';
|
||||
import ExploreActionButtons from '../../../../src/explore/components/ExploreActionButtons';
|
||||
|
||||
describe('ExploreActionButtons', () => {
|
||||
const defaultProps = {
|
||||
actions: {},
|
||||
canDownload: 'True',
|
||||
latestQueryFormData: {},
|
||||
queryEndpoint: 'localhost',
|
||||
};
|
||||
|
||||
it('renders', () => {
|
||||
expect(
|
||||
React.isValidElement(<ExploreActionButtons {...defaultProps} />),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('should render 5 children/buttons', () => {
|
||||
const wrapper = shallow(<ExploreActionButtons {...defaultProps} />);
|
||||
expect(wrapper.children()).toHaveLength(5);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,88 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF 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 { shallow } from 'enzyme';
|
||||
|
||||
import ExploreChartHeader from '../../../../src/explore/components/ExploreChartHeader';
|
||||
import ExploreActionButtons from '../../../../src/explore/components/ExploreActionButtons';
|
||||
import EditableTitle from '../../../../src/components/EditableTitle';
|
||||
|
||||
const stub = jest.fn(() => ({
|
||||
then: () => {},
|
||||
}));
|
||||
const mockProps = {
|
||||
actions: {
|
||||
saveSlice: stub,
|
||||
},
|
||||
can_overwrite: true,
|
||||
can_download: true,
|
||||
isStarred: true,
|
||||
slice: {
|
||||
form_data: {
|
||||
viz_type: 'line',
|
||||
},
|
||||
},
|
||||
table_name: 'foo',
|
||||
form_data: {
|
||||
viz_type: 'table',
|
||||
},
|
||||
timeout: 1000,
|
||||
chart: {
|
||||
queryResponse: {},
|
||||
},
|
||||
};
|
||||
|
||||
describe('ExploreChartHeader', () => {
|
||||
let wrapper;
|
||||
beforeEach(() => {
|
||||
wrapper = shallow(<ExploreChartHeader {...mockProps} />);
|
||||
});
|
||||
|
||||
it('is valid', () => {
|
||||
expect(React.isValidElement(<ExploreChartHeader {...mockProps} />)).toBe(
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
it('renders', () => {
|
||||
expect(wrapper.find(EditableTitle)).toHaveLength(1);
|
||||
expect(wrapper.find(ExploreActionButtons)).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('should updateChartTitleOrSaveSlice for existed slice', () => {
|
||||
const newTitle = 'New Chart Title';
|
||||
wrapper.instance().updateChartTitleOrSaveSlice(newTitle);
|
||||
expect(stub.call).toHaveLength(1);
|
||||
expect(stub).toHaveBeenCalledWith(mockProps.slice.form_data, {
|
||||
action: 'overwrite',
|
||||
slice_name: newTitle,
|
||||
});
|
||||
});
|
||||
|
||||
it('should updateChartTitleOrSaveSlice for new slice', () => {
|
||||
const newTitle = 'New Chart Title';
|
||||
wrapper.setProps({ slice: undefined });
|
||||
wrapper.instance().updateChartTitleOrSaveSlice(newTitle);
|
||||
expect(stub.call).toHaveLength(1);
|
||||
expect(stub).toHaveBeenCalledWith(mockProps.form_data, {
|
||||
action: 'saveas',
|
||||
slice_name: newTitle,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF 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 ChartContainer from '../../../../src/explore/components/ExploreChartPanel';
|
||||
|
||||
describe('ChartContainer', () => {
|
||||
const mockProps = {
|
||||
sliceName: 'Trend Line',
|
||||
vizType: 'line',
|
||||
height: '500px',
|
||||
};
|
||||
|
||||
it('renders when vizType is line', () => {
|
||||
expect(React.isValidElement(<ChartContainer {...mockProps} />)).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,110 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF 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 configureStore from 'redux-mock-store';
|
||||
import thunk from 'redux-thunk';
|
||||
import sinon from 'sinon';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import getInitialState from 'src/explore/reducers/getInitialState';
|
||||
import ExploreViewContainer from 'src/explore/components/ExploreViewContainer';
|
||||
import QueryAndSaveBtns from 'src/explore/components/QueryAndSaveBtns';
|
||||
import ControlPanelsContainer from 'src/explore/components/ControlPanelsContainer';
|
||||
import ChartContainer from 'src/explore/components/ExploreChartPanel';
|
||||
import * as featureFlags from 'src/featureFlags';
|
||||
|
||||
describe('ExploreViewContainer', () => {
|
||||
const middlewares = [thunk];
|
||||
const mockStore = configureStore(middlewares);
|
||||
let store;
|
||||
let wrapper;
|
||||
let isFeatureEnabledMock;
|
||||
|
||||
beforeAll(() => {
|
||||
isFeatureEnabledMock = jest
|
||||
.spyOn(featureFlags, 'isFeatureEnabled')
|
||||
.mockReturnValue(false);
|
||||
|
||||
const bootstrapData = {
|
||||
common: {
|
||||
conf: {},
|
||||
},
|
||||
datasource: {
|
||||
columns: [],
|
||||
},
|
||||
};
|
||||
store = mockStore(getInitialState(bootstrapData), {});
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
isFeatureEnabledMock.mockRestore();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
wrapper = shallow(<ExploreViewContainer />, {
|
||||
context: { store },
|
||||
disableLifecycleMethods: true,
|
||||
}).dive();
|
||||
});
|
||||
|
||||
it('renders', () => {
|
||||
expect(React.isValidElement(<ExploreViewContainer />)).toBe(true);
|
||||
});
|
||||
|
||||
it('renders QueryAndSaveButtons', () => {
|
||||
expect(wrapper.find(QueryAndSaveBtns)).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('renders ControlPanelsContainer', () => {
|
||||
expect(wrapper.find(ControlPanelsContainer)).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('renders ChartContainer', () => {
|
||||
expect(wrapper.find(ChartContainer)).toHaveLength(1);
|
||||
});
|
||||
|
||||
describe('componentWillReceiveProps()', () => {
|
||||
it('when controls change, should call resetControls', () => {
|
||||
expect(wrapper.instance().props.controls.viz_type.value).toBe('table');
|
||||
const resetControls = sinon.stub(
|
||||
wrapper.instance().props.actions,
|
||||
'resetControls',
|
||||
);
|
||||
const triggerQuery = sinon.stub(
|
||||
wrapper.instance().props.actions,
|
||||
'triggerQuery',
|
||||
);
|
||||
|
||||
// triggers componentWillReceiveProps
|
||||
wrapper.setProps({
|
||||
controls: {
|
||||
viz_type: {
|
||||
value: 'bar',
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(resetControls.callCount).toBe(1);
|
||||
// exploreview container should not force chart run query
|
||||
// it should be controlled by redux state.
|
||||
expect(triggerQuery.callCount).toBe(0);
|
||||
resetControls.reset();
|
||||
triggerQuery.reset();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF 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.
|
||||
*/
|
||||
/* eslint-disable no-unused-expressions */
|
||||
import React from 'react';
|
||||
import sinon from 'sinon';
|
||||
import { shallow } from 'enzyme';
|
||||
import { OverlayTrigger } from 'react-bootstrap';
|
||||
|
||||
import FilterBoxItemControl from '../../../../src/explore/components/controls/FilterBoxItemControl';
|
||||
import FormRow from '../../../../src/components/FormRow';
|
||||
import datasources from '../../../fixtures/mockDatasource';
|
||||
|
||||
const defaultProps = {
|
||||
datasource: datasources['7__table'],
|
||||
onChange: sinon.spy(),
|
||||
};
|
||||
|
||||
describe('FilterBoxItemControl', () => {
|
||||
let wrapper;
|
||||
let inst;
|
||||
|
||||
const getWrapper = propOverrides => {
|
||||
const props = { ...defaultProps, ...propOverrides };
|
||||
return shallow(<FilterBoxItemControl {...props} />);
|
||||
};
|
||||
beforeEach(() => {
|
||||
wrapper = getWrapper();
|
||||
inst = wrapper.instance();
|
||||
});
|
||||
|
||||
it('renders an OverlayTrigger', () => {
|
||||
expect(wrapper.find(OverlayTrigger)).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('renderForms does the job', () => {
|
||||
const popover = shallow(inst.renderForm());
|
||||
expect(popover.find(FormRow)).toHaveLength(7);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF 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.
|
||||
*/
|
||||
/* eslint-disable no-unused-expressions */
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import FilterDefinitionOption from '../../../../src/explore/components/FilterDefinitionOption';
|
||||
import ColumnOption from '../../../../src/components/ColumnOption';
|
||||
import AdhocMetricStaticOption from '../../../../src/explore/components/AdhocMetricStaticOption';
|
||||
import AdhocMetric, {
|
||||
EXPRESSION_TYPES,
|
||||
} from '../../../../src/explore/AdhocMetric';
|
||||
import { AGGREGATES } from '../../../../src/explore/constants';
|
||||
|
||||
const sumValueAdhocMetric = new AdhocMetric({
|
||||
expressionType: EXPRESSION_TYPES.SIMPLE,
|
||||
column: { type: 'VARCHAR(255)', column_name: 'source' },
|
||||
aggregate: AGGREGATES.SUM,
|
||||
});
|
||||
|
||||
describe('FilterDefinitionOption', () => {
|
||||
it('renders a ColumnOption given a column', () => {
|
||||
const wrapper = shallow(
|
||||
<FilterDefinitionOption option={{ column_name: 'a_column' }} />,
|
||||
);
|
||||
expect(wrapper.find(ColumnOption)).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('renders a AdhocMetricStaticOption given an adhoc metric', () => {
|
||||
const wrapper = shallow(
|
||||
<FilterDefinitionOption option={sumValueAdhocMetric} />,
|
||||
);
|
||||
expect(wrapper.find(AdhocMetricStaticOption)).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('renders the metric name given a saved metric', () => {
|
||||
const wrapper = shallow(
|
||||
<FilterDefinitionOption
|
||||
option={{ saved_metric_name: 'my_custom_metric' }}
|
||||
/>,
|
||||
);
|
||||
expect(wrapper.text()).toBe('<ColumnTypeLabel />my_custom_metric');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF 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.
|
||||
*/
|
||||
/* eslint-disable no-unused-expressions */
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import FixedOrMetricControl from '../../../../src/explore/components/controls/FixedOrMetricControl';
|
||||
import TextControl from '../../../../src/explore/components/controls/TextControl';
|
||||
import MetricsControl from '../../../../src/explore/components/controls/MetricsControl';
|
||||
|
||||
const defaultProps = {
|
||||
value: {},
|
||||
datasource: {},
|
||||
};
|
||||
|
||||
describe('FixedOrMetricControl', () => {
|
||||
let wrapper;
|
||||
|
||||
beforeEach(() => {
|
||||
wrapper = shallow(<FixedOrMetricControl {...defaultProps} />);
|
||||
});
|
||||
|
||||
it('renders a TextControl and a SelectControl', () => {
|
||||
expect(wrapper.find(TextControl)).toHaveLength(1);
|
||||
expect(wrapper.find(MetricsControl)).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF 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 configureStore from 'redux-mock-store';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import MetricDefinitionOption from '../../../../src/explore/components/MetricDefinitionOption';
|
||||
import MetricOption from '../../../../src/components/MetricOption';
|
||||
import ColumnOption from '../../../../src/components/ColumnOption';
|
||||
import AggregateOption from '../../../../src/explore/components/AggregateOption';
|
||||
|
||||
describe('MetricDefinitionOption', () => {
|
||||
const mockStore = configureStore([]);
|
||||
const store = mockStore({});
|
||||
|
||||
function setup(props) {
|
||||
return shallow(<MetricDefinitionOption {...props} />, {
|
||||
context: { store },
|
||||
}).dive();
|
||||
}
|
||||
|
||||
it('renders a MetricOption given a saved metric', () => {
|
||||
const wrapper = setup({ option: { metric_name: 'a_saved_metric' } });
|
||||
expect(wrapper.find(MetricOption)).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('renders a ColumnOption given a column', () => {
|
||||
const wrapper = setup({ option: { column_name: 'a_column' } });
|
||||
expect(wrapper.find(ColumnOption)).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('renders an AggregateOption given an aggregate metric', () => {
|
||||
const wrapper = setup({ option: { aggregate_name: 'an_aggregate' } });
|
||||
expect(wrapper.find(AggregateOption)).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF 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.
|
||||
*/
|
||||
/* eslint-disable no-unused-expressions */
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import MetricDefinitionValue from '../../../../src/explore/components/MetricDefinitionValue';
|
||||
import MetricOption from '../../../../src/components/MetricOption';
|
||||
import AdhocMetricOption from '../../../../src/explore/components/AdhocMetricOption';
|
||||
import AdhocMetric from '../../../../src/explore/AdhocMetric';
|
||||
import { AGGREGATES } from '../../../../src/explore/constants';
|
||||
|
||||
const sumValueAdhocMetric = new AdhocMetric({
|
||||
column: { type: 'DOUBLE', column_name: 'value' },
|
||||
aggregate: AGGREGATES.SUM,
|
||||
});
|
||||
|
||||
describe('MetricDefinitionValue', () => {
|
||||
it('renders a MetricOption given a saved metric', () => {
|
||||
const wrapper = shallow(
|
||||
<MetricDefinitionValue option={{ metric_name: 'a_saved_metric' }} />,
|
||||
);
|
||||
expect(wrapper.find(MetricOption)).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('renders an AdhocMetricOption given an adhoc metric', () => {
|
||||
const wrapper = shallow(
|
||||
<MetricDefinitionValue
|
||||
onMetricEdit={() => {}}
|
||||
option={sumValueAdhocMetric}
|
||||
/>,
|
||||
);
|
||||
expect(wrapper.find(AdhocMetricOption)).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,446 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF 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.
|
||||
*/
|
||||
/* eslint-disable no-unused-expressions */
|
||||
import React from 'react';
|
||||
import sinon from 'sinon';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import MetricsControl from '../../../../src/explore/components/controls/MetricsControl';
|
||||
import { AGGREGATES } from '../../../../src/explore/constants';
|
||||
import OnPasteSelect from '../../../../src/components/OnPasteSelect';
|
||||
import AdhocMetric, {
|
||||
EXPRESSION_TYPES,
|
||||
} from '../../../../src/explore/AdhocMetric';
|
||||
|
||||
const defaultProps = {
|
||||
name: 'metrics',
|
||||
label: 'Metrics',
|
||||
value: undefined,
|
||||
multi: true,
|
||||
columns: [
|
||||
{ type: 'VARCHAR(255)', column_name: 'source' },
|
||||
{ type: 'VARCHAR(255)', column_name: 'target' },
|
||||
{ type: 'DOUBLE', column_name: 'value' },
|
||||
],
|
||||
savedMetrics: [
|
||||
{ metric_name: 'sum__value', expression: 'SUM(energy_usage.value)' },
|
||||
{ metric_name: 'avg__value', expression: 'AVG(energy_usage.value)' },
|
||||
],
|
||||
datasourceType: 'sqla',
|
||||
};
|
||||
|
||||
function setup(overrides) {
|
||||
const onChange = sinon.spy();
|
||||
const props = {
|
||||
onChange,
|
||||
...defaultProps,
|
||||
...overrides,
|
||||
};
|
||||
const wrapper = shallow(<MetricsControl {...props} />);
|
||||
return { wrapper, onChange };
|
||||
}
|
||||
|
||||
const valueColumn = { type: 'DOUBLE', column_name: 'value' };
|
||||
|
||||
const sumValueAdhocMetric = new AdhocMetric({
|
||||
column: valueColumn,
|
||||
aggregate: AGGREGATES.SUM,
|
||||
label: 'SUM(value)',
|
||||
});
|
||||
|
||||
describe('MetricsControl', () => {
|
||||
it('renders an OnPasteSelect', () => {
|
||||
const { wrapper } = setup();
|
||||
expect(wrapper.find(OnPasteSelect)).toHaveLength(1);
|
||||
});
|
||||
|
||||
describe('constructor', () => {
|
||||
it('unifies options for the dropdown select with aggregates', () => {
|
||||
const { wrapper } = setup();
|
||||
expect(wrapper.state('options')).toEqual([
|
||||
{
|
||||
optionName: '_col_source',
|
||||
type: 'VARCHAR(255)',
|
||||
column_name: 'source',
|
||||
},
|
||||
{
|
||||
optionName: '_col_target',
|
||||
type: 'VARCHAR(255)',
|
||||
column_name: 'target',
|
||||
},
|
||||
{ optionName: '_col_value', type: 'DOUBLE', column_name: 'value' },
|
||||
...Object.keys(AGGREGATES).map(aggregate => ({
|
||||
aggregate_name: aggregate,
|
||||
optionName: '_aggregate_' + aggregate,
|
||||
})),
|
||||
{
|
||||
optionName: 'sum__value',
|
||||
metric_name: 'sum__value',
|
||||
expression: 'SUM(energy_usage.value)',
|
||||
},
|
||||
{
|
||||
optionName: 'avg__value',
|
||||
metric_name: 'avg__value',
|
||||
expression: 'AVG(energy_usage.value)',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('does not show aggregates in options if no columns', () => {
|
||||
const { wrapper } = setup({ columns: [] });
|
||||
expect(wrapper.state('options')).toEqual([
|
||||
{
|
||||
optionName: 'sum__value',
|
||||
metric_name: 'sum__value',
|
||||
expression: 'SUM(energy_usage.value)',
|
||||
},
|
||||
{
|
||||
optionName: 'avg__value',
|
||||
metric_name: 'avg__value',
|
||||
expression: 'AVG(energy_usage.value)',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('coerces Adhoc Metrics from form data into instances of the AdhocMetric class and leaves saved metrics', () => {
|
||||
const { wrapper } = setup({
|
||||
value: [
|
||||
{
|
||||
expressionType: EXPRESSION_TYPES.SIMPLE,
|
||||
column: { type: 'double', column_name: 'value' },
|
||||
aggregate: AGGREGATES.SUM,
|
||||
label: 'SUM(value)',
|
||||
optionName: 'blahblahblah',
|
||||
},
|
||||
'avg__value',
|
||||
],
|
||||
});
|
||||
|
||||
const adhocMetric = wrapper.state('value')[0];
|
||||
expect(adhocMetric instanceof AdhocMetric).toBe(true);
|
||||
expect(adhocMetric.optionName.length).toBeGreaterThan(10);
|
||||
expect(wrapper.state('value')).toEqual([
|
||||
{
|
||||
expressionType: EXPRESSION_TYPES.SIMPLE,
|
||||
column: { type: 'double', column_name: 'value' },
|
||||
aggregate: AGGREGATES.SUM,
|
||||
fromFormData: true,
|
||||
label: 'SUM(value)',
|
||||
hasCustomLabel: false,
|
||||
optionName: 'blahblahblah',
|
||||
sqlExpression: null,
|
||||
},
|
||||
'avg__value',
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('onChange', () => {
|
||||
it('handles saved metrics being selected', () => {
|
||||
const { wrapper, onChange } = setup();
|
||||
const select = wrapper.find(OnPasteSelect);
|
||||
select.simulate('change', [{ metric_name: 'sum__value' }]);
|
||||
expect(onChange.lastCall.args).toEqual([['sum__value']]);
|
||||
});
|
||||
|
||||
it('handles columns being selected', () => {
|
||||
const { wrapper, onChange } = setup();
|
||||
const select = wrapper.find(OnPasteSelect);
|
||||
select.simulate('change', [valueColumn]);
|
||||
|
||||
const adhocMetric = onChange.lastCall.args[0][0];
|
||||
expect(adhocMetric instanceof AdhocMetric).toBe(true);
|
||||
expect(onChange.lastCall.args).toEqual([
|
||||
[
|
||||
{
|
||||
expressionType: EXPRESSION_TYPES.SIMPLE,
|
||||
column: valueColumn,
|
||||
aggregate: AGGREGATES.SUM,
|
||||
label: 'SUM(value)',
|
||||
fromFormData: false,
|
||||
hasCustomLabel: false,
|
||||
optionName: adhocMetric.optionName,
|
||||
sqlExpression: null,
|
||||
},
|
||||
],
|
||||
]);
|
||||
});
|
||||
|
||||
it('handles aggregates being selected', () => {
|
||||
const { wrapper, onChange } = setup();
|
||||
const select = wrapper.find(OnPasteSelect);
|
||||
|
||||
// mock out the Select ref
|
||||
const setInputSpy = sinon.spy();
|
||||
const handleInputSpy = sinon.spy();
|
||||
wrapper.instance().select = {
|
||||
setInputValue: setInputSpy,
|
||||
handleInputChange: handleInputSpy,
|
||||
input: { input: {} },
|
||||
};
|
||||
|
||||
select.simulate('change', [{ aggregate_name: 'SUM', optionName: 'SUM' }]);
|
||||
|
||||
expect(setInputSpy.calledWith('SUM()')).toBe(true);
|
||||
expect(handleInputSpy.calledWith({ target: { value: 'SUM()' } })).toBe(
|
||||
true,
|
||||
);
|
||||
expect(onChange.lastCall.args).toEqual([[]]);
|
||||
});
|
||||
|
||||
it('preserves existing selected AdhocMetrics', () => {
|
||||
const { wrapper, onChange } = setup();
|
||||
const select = wrapper.find(OnPasteSelect);
|
||||
select.simulate('change', [
|
||||
{ metric_name: 'sum__value' },
|
||||
sumValueAdhocMetric,
|
||||
]);
|
||||
expect(onChange.lastCall.args).toEqual([
|
||||
['sum__value', sumValueAdhocMetric],
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('onMetricEdit', () => {
|
||||
it('accepts an edited metric from an AdhocMetricEditPopover', () => {
|
||||
const { wrapper, onChange } = setup({
|
||||
value: [sumValueAdhocMetric],
|
||||
});
|
||||
|
||||
const editedMetric = sumValueAdhocMetric.duplicateWith({
|
||||
aggregate: AGGREGATES.AVG,
|
||||
});
|
||||
wrapper.instance().onMetricEdit(editedMetric);
|
||||
|
||||
expect(onChange.lastCall.args).toEqual([[editedMetric]]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('checkIfAggregateInInput', () => {
|
||||
it('handles an aggregate in the input', () => {
|
||||
const { wrapper } = setup();
|
||||
|
||||
expect(wrapper.state('aggregateInInput')).toBeNull();
|
||||
wrapper.instance().checkIfAggregateInInput('AVG(');
|
||||
expect(wrapper.state('aggregateInInput')).toBe(AGGREGATES.AVG);
|
||||
});
|
||||
|
||||
it('handles no aggregate in the input', () => {
|
||||
const { wrapper } = setup();
|
||||
|
||||
expect(wrapper.state('aggregateInInput')).toBeNull();
|
||||
wrapper.instance().checkIfAggregateInInput('colu');
|
||||
expect(wrapper.state('aggregateInInput')).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('option filter', () => {
|
||||
it('includes user defined metrics', () => {
|
||||
const { wrapper } = setup({ datasourceType: 'druid' });
|
||||
|
||||
expect(
|
||||
!!wrapper.instance().selectFilterOption(
|
||||
{
|
||||
metric_name: 'a_metric',
|
||||
optionName: 'a_metric',
|
||||
expression: 'SUM(FANCY(metric))',
|
||||
},
|
||||
'a',
|
||||
),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('includes auto generated avg metrics for druid', () => {
|
||||
const { wrapper } = setup({ datasourceType: 'druid' });
|
||||
|
||||
expect(
|
||||
!!wrapper.instance().selectFilterOption(
|
||||
{
|
||||
metric_name: 'avg__metric',
|
||||
optionName: 'avg__metric',
|
||||
expression: 'AVG(metric)',
|
||||
},
|
||||
'a',
|
||||
),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('includes columns and aggregates', () => {
|
||||
const { wrapper } = setup();
|
||||
|
||||
expect(
|
||||
!!wrapper.instance().selectFilterOption(
|
||||
{
|
||||
type: 'VARCHAR(255)',
|
||||
column_name: 'source',
|
||||
optionName: '_col_source',
|
||||
},
|
||||
'sou',
|
||||
),
|
||||
).toBe(true);
|
||||
|
||||
expect(
|
||||
!!wrapper
|
||||
.instance()
|
||||
.selectFilterOption(
|
||||
{ aggregate_name: 'AVG', optionName: '_aggregate_AVG' },
|
||||
'av',
|
||||
),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('includes columns based on verbose_name', () => {
|
||||
const { wrapper } = setup();
|
||||
|
||||
expect(
|
||||
!!wrapper.instance().selectFilterOption(
|
||||
{
|
||||
metric_name: 'sum__num',
|
||||
verbose_name: 'babies',
|
||||
optionName: '_col_sum_num',
|
||||
},
|
||||
'bab',
|
||||
),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('excludes auto generated avg metrics for sqla', () => {
|
||||
const { wrapper } = setup();
|
||||
|
||||
expect(
|
||||
!!wrapper.instance().selectFilterOption(
|
||||
{
|
||||
metric_name: 'avg__metric',
|
||||
optionName: 'avg__metric',
|
||||
expression: 'AVG(metric)',
|
||||
},
|
||||
'a',
|
||||
),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it('includes custom made simple saved metrics', () => {
|
||||
const { wrapper } = setup();
|
||||
|
||||
expect(
|
||||
!!wrapper.instance().selectFilterOption(
|
||||
{
|
||||
metric_name: 'my_fancy_sum_metric',
|
||||
optionName: 'my_fancy_sum_metric',
|
||||
expression: 'SUM(value)',
|
||||
},
|
||||
'sum',
|
||||
),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('excludes auto generated metrics', () => {
|
||||
const { wrapper } = setup();
|
||||
|
||||
expect(
|
||||
!!wrapper.instance().selectFilterOption(
|
||||
{
|
||||
metric_name: 'sum__value',
|
||||
optionName: 'sum__value',
|
||||
expression: 'SUM(value)',
|
||||
},
|
||||
'sum',
|
||||
),
|
||||
).toBe(false);
|
||||
|
||||
expect(
|
||||
!!wrapper.instance().selectFilterOption(
|
||||
{
|
||||
metric_name: 'sum__value',
|
||||
optionName: 'sum__value',
|
||||
expression: 'SUM("table"."value")',
|
||||
},
|
||||
'sum',
|
||||
),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it('filters out metrics if the input begins with an aggregate', () => {
|
||||
const { wrapper } = setup();
|
||||
wrapper.setState({ aggregateInInput: true });
|
||||
|
||||
expect(
|
||||
!!wrapper
|
||||
.instance()
|
||||
.selectFilterOption(
|
||||
{ metric_name: 'metric', expression: 'SUM(FANCY(metric))' },
|
||||
'SUM(',
|
||||
),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it('includes columns if the input begins with an aggregate', () => {
|
||||
const { wrapper } = setup();
|
||||
wrapper.setState({ aggregateInInput: true });
|
||||
|
||||
expect(
|
||||
!!wrapper
|
||||
.instance()
|
||||
.selectFilterOption({ type: 'DOUBLE', column_name: 'value' }, 'SUM('),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('Removes metrics if savedMetrics changes', () => {
|
||||
const { props, wrapper, onChange } = setup({
|
||||
value: [
|
||||
{
|
||||
expressionType: EXPRESSION_TYPES.SIMPLE,
|
||||
column: { type: 'double', column_name: 'value' },
|
||||
aggregate: AGGREGATES.SUM,
|
||||
label: 'SUM(value)',
|
||||
optionName: 'blahblahblah',
|
||||
},
|
||||
],
|
||||
});
|
||||
expect(wrapper.state('value')).toHaveLength(1);
|
||||
|
||||
wrapper.setProps({ ...props, columns: [] });
|
||||
expect(onChange.lastCall.args).toEqual([[]]);
|
||||
});
|
||||
|
||||
it('Does not remove custom sql metric if savedMetrics changes', () => {
|
||||
const { props, wrapper, onChange } = setup({
|
||||
value: [
|
||||
{
|
||||
expressionType: EXPRESSION_TYPES.SQL,
|
||||
sqlExpression: 'COUNT(*)',
|
||||
label: 'old label',
|
||||
hasCustomLabel: true,
|
||||
},
|
||||
],
|
||||
});
|
||||
expect(wrapper.state('value')).toHaveLength(1);
|
||||
|
||||
wrapper.setProps({ ...props, columns: [] });
|
||||
expect(onChange.calledOnce).toEqual(false);
|
||||
});
|
||||
it('Does not fail if no columns or savedMetrics are passed', () => {
|
||||
const { wrapper } = setup({
|
||||
savedMetrics: null,
|
||||
columns: null,
|
||||
});
|
||||
expect(wrapper.exists('.metrics-select')).toEqual(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,62 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF 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 { shallow } from 'enzyme';
|
||||
import sinon from 'sinon';
|
||||
|
||||
import QueryAndSaveButtons from '../../../../src/explore/components/QueryAndSaveBtns';
|
||||
import Button from '../../../../src/components/Button';
|
||||
|
||||
describe('QueryAndSaveButtons', () => {
|
||||
const defaultProps = {
|
||||
canAdd: 'True',
|
||||
onQuery: sinon.spy(),
|
||||
};
|
||||
|
||||
// It must render
|
||||
it('renders', () => {
|
||||
expect(
|
||||
React.isValidElement(<QueryAndSaveButtons {...defaultProps} />),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
// Test the output
|
||||
describe('output', () => {
|
||||
let wrapper;
|
||||
|
||||
beforeEach(() => {
|
||||
wrapper = shallow(<QueryAndSaveButtons {...defaultProps} />);
|
||||
});
|
||||
|
||||
it('renders 2 buttons', () => {
|
||||
expect(wrapper.find(Button)).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('renders buttons with correct text', () => {
|
||||
expect(wrapper.find(Button).contains(' Run Query')).toBe(true);
|
||||
expect(wrapper.find(Button).contains(' Save')).toBe(true);
|
||||
});
|
||||
|
||||
it('calls onQuery when query button is clicked', () => {
|
||||
const queryButton = wrapper.find('.query');
|
||||
queryButton.simulate('click');
|
||||
expect(defaultProps.onQuery.called).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF 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 { shallow } from 'enzyme';
|
||||
import { Label } from 'react-bootstrap';
|
||||
|
||||
import TooltipWrapper from './../../../../src/components/TooltipWrapper';
|
||||
|
||||
import RowCountLabel from '../../../../src/explore/components/RowCountLabel';
|
||||
|
||||
describe('RowCountLabel', () => {
|
||||
const defaultProps = {
|
||||
rowcount: 51,
|
||||
limit: 100,
|
||||
};
|
||||
|
||||
it('is valid', () => {
|
||||
expect(React.isValidElement(<RowCountLabel {...defaultProps} />)).toBe(
|
||||
true,
|
||||
);
|
||||
});
|
||||
it('renders a Label and a TooltipWrapper', () => {
|
||||
const wrapper = shallow(<RowCountLabel {...defaultProps} />);
|
||||
expect(wrapper.find(Label)).toHaveLength(1);
|
||||
expect(wrapper.find(TooltipWrapper)).toHaveLength(1);
|
||||
});
|
||||
it('renders a danger when limit is reached', () => {
|
||||
const props = {
|
||||
rowcount: 100,
|
||||
limit: 100,
|
||||
};
|
||||
const wrapper = shallow(<RowCountLabel {...props} />);
|
||||
expect(
|
||||
wrapper
|
||||
.find(Label)
|
||||
.first()
|
||||
.props().bsStyle,
|
||||
).toBe('danger');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF 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 { shallow } from 'enzyme';
|
||||
|
||||
import RunQueryActionButton from '../../../../src/SqlLab/components/RunQueryActionButton';
|
||||
import Button from '../../../../src/components/Button';
|
||||
|
||||
describe('RunQueryActionButton', () => {
|
||||
let wrapper;
|
||||
const defaultProps = {
|
||||
allowAsync: false,
|
||||
dbId: 1,
|
||||
queryState: 'pending',
|
||||
runQuery: () => {}, // eslint-disable-line
|
||||
selectedText: null,
|
||||
stopQuery: () => {}, // eslint-disable-line
|
||||
sql: '',
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
wrapper = shallow(<RunQueryActionButton {...defaultProps} />);
|
||||
});
|
||||
|
||||
it('is a valid react element', () => {
|
||||
expect(
|
||||
React.isValidElement(<RunQueryActionButton {...defaultProps} />),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('renders a single Button', () => {
|
||||
expect(wrapper.find(Button)).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,324 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF 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 configureStore from 'redux-mock-store';
|
||||
import thunk from 'redux-thunk';
|
||||
import { bindActionCreators } from 'redux';
|
||||
|
||||
import { shallow, mount } from 'enzyme';
|
||||
import { Modal, Button, Radio } from 'react-bootstrap';
|
||||
import sinon from 'sinon';
|
||||
import fetchMock from 'fetch-mock';
|
||||
|
||||
import * as exploreUtils from '../../../../src/explore/exploreUtils';
|
||||
import * as saveModalActions from '../../../../src/explore/actions/saveModalActions';
|
||||
import SaveModal from '../../../../src/explore/components/SaveModal';
|
||||
|
||||
describe('SaveModal', () => {
|
||||
const middlewares = [thunk];
|
||||
const mockStore = configureStore(middlewares);
|
||||
const initialState = {
|
||||
chart: {},
|
||||
saveModal: {
|
||||
dashboards: [],
|
||||
},
|
||||
explore: {
|
||||
can_overwrite: true,
|
||||
user_id: '1',
|
||||
datasource: {},
|
||||
slice: {
|
||||
slice_id: 1,
|
||||
slice_name: 'title',
|
||||
},
|
||||
alert: null,
|
||||
},
|
||||
};
|
||||
const store = mockStore(initialState);
|
||||
|
||||
const defaultProps = {
|
||||
onHide: () => ({}),
|
||||
actions: bindActionCreators(saveModalActions, arg => {
|
||||
if (typeof arg === 'function') {
|
||||
return arg(jest.fn);
|
||||
}
|
||||
return arg;
|
||||
}),
|
||||
form_data: { datasource: '107__table' },
|
||||
};
|
||||
const mockEvent = {
|
||||
target: {
|
||||
value: 'mock event target',
|
||||
},
|
||||
value: 'mock value',
|
||||
};
|
||||
|
||||
const getWrapper = () =>
|
||||
shallow(<SaveModal {...defaultProps} />, {
|
||||
context: { store },
|
||||
}).dive();
|
||||
|
||||
it('renders a Modal with 7 inputs and 2 buttons', () => {
|
||||
const wrapper = getWrapper();
|
||||
expect(wrapper.find(Modal)).toHaveLength(1);
|
||||
expect(wrapper.find('input')).toHaveLength(2);
|
||||
expect(wrapper.find(Button)).toHaveLength(2);
|
||||
expect(wrapper.find(Radio)).toHaveLength(5);
|
||||
});
|
||||
|
||||
it('does not show overwrite option for new slice', () => {
|
||||
const wrapperNewSlice = getWrapper();
|
||||
wrapperNewSlice.setProps({ slice: null });
|
||||
expect(wrapperNewSlice.find('#overwrite-radio')).toHaveLength(0);
|
||||
expect(wrapperNewSlice.find('#saveas-radio')).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('disable overwrite option for non-owner', () => {
|
||||
const wrapperForNonOwner = getWrapper();
|
||||
wrapperForNonOwner.setProps({ can_overwrite: false });
|
||||
const overwriteRadio = wrapperForNonOwner.find('#overwrite-radio');
|
||||
expect(overwriteRadio).toHaveLength(1);
|
||||
expect(overwriteRadio.prop('disabled')).toBe(true);
|
||||
});
|
||||
|
||||
it('saves a new slice', () => {
|
||||
const wrapperForNewSlice = getWrapper();
|
||||
wrapperForNewSlice.setProps({ can_overwrite: false });
|
||||
wrapperForNewSlice.instance().changeAction('saveas');
|
||||
const saveasRadio = wrapperForNewSlice.find('#saveas-radio');
|
||||
saveasRadio.simulate('click');
|
||||
expect(wrapperForNewSlice.state().action).toBe('saveas');
|
||||
});
|
||||
|
||||
it('overwrite a slice', () => {
|
||||
const wrapperForOverwrite = getWrapper();
|
||||
const overwriteRadio = wrapperForOverwrite.find('#overwrite-radio');
|
||||
overwriteRadio.simulate('click');
|
||||
expect(wrapperForOverwrite.state().action).toBe('overwrite');
|
||||
});
|
||||
|
||||
it('componentDidMount', () => {
|
||||
sinon.spy(SaveModal.prototype, 'componentDidMount');
|
||||
sinon.spy(defaultProps.actions, 'fetchDashboards');
|
||||
mount(<SaveModal {...defaultProps} />, {
|
||||
context: { store },
|
||||
});
|
||||
expect(SaveModal.prototype.componentDidMount.calledOnce).toBe(true);
|
||||
expect(defaultProps.actions.fetchDashboards.calledOnce).toBe(true);
|
||||
|
||||
SaveModal.prototype.componentDidMount.restore();
|
||||
defaultProps.actions.fetchDashboards.restore();
|
||||
});
|
||||
|
||||
it('onChange', () => {
|
||||
const wrapper = getWrapper();
|
||||
|
||||
wrapper.instance().onChange('newSliceName', mockEvent);
|
||||
expect(wrapper.state().newSliceName).toBe(mockEvent.target.value);
|
||||
|
||||
wrapper.instance().onChange('saveToDashboardId', mockEvent);
|
||||
expect(wrapper.state().saveToDashboardId).toBe(mockEvent.value);
|
||||
|
||||
wrapper.instance().onChange('newDashboardName', mockEvent);
|
||||
expect(wrapper.state().newDashboardName).toBe(mockEvent.target.value);
|
||||
});
|
||||
|
||||
describe('saveOrOverwrite', () => {
|
||||
beforeEach(() => {
|
||||
sinon
|
||||
.stub(exploreUtils, 'getExploreUrlAndPayload')
|
||||
.callsFake(() => ({ url: 'mockURL', payload: defaultProps.form_data }));
|
||||
|
||||
sinon.stub(defaultProps.actions, 'saveSlice').callsFake(() =>
|
||||
Promise.resolve({
|
||||
data: {
|
||||
dashboard: '/mock_dashboard/',
|
||||
slice: { slice_url: '/mock_slice/' },
|
||||
},
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
exploreUtils.getExploreUrlAndPayload.restore();
|
||||
defaultProps.actions.saveSlice.restore();
|
||||
});
|
||||
|
||||
it('should save slice', () => {
|
||||
const wrapper = getWrapper();
|
||||
wrapper.instance().saveOrOverwrite(true);
|
||||
const args = defaultProps.actions.saveSlice.getCall(0).args;
|
||||
expect(args[0]).toEqual(defaultProps.form_data);
|
||||
});
|
||||
|
||||
it('existing dashboard', () => {
|
||||
const wrapper = getWrapper();
|
||||
const saveToDashboardId = 100;
|
||||
|
||||
wrapper.setState({ addToDash: 'existing' });
|
||||
wrapper.instance().saveOrOverwrite(true);
|
||||
expect(wrapper.state().alert).toBe('Please select a dashboard');
|
||||
|
||||
wrapper.setState({ saveToDashboardId });
|
||||
wrapper.instance().saveOrOverwrite(true);
|
||||
const args = defaultProps.actions.saveSlice.getCall(0).args;
|
||||
expect(args[1].save_to_dashboard_id).toBe(saveToDashboardId);
|
||||
});
|
||||
|
||||
it('new dashboard', () => {
|
||||
const wrapper = getWrapper();
|
||||
const newDashboardName = 'new dashboard name';
|
||||
|
||||
wrapper.setState({ addToDash: 'new' });
|
||||
wrapper.instance().saveOrOverwrite(true);
|
||||
expect(wrapper.state().alert).toBe('Please enter a dashboard name');
|
||||
|
||||
wrapper.setState({ newDashboardName });
|
||||
wrapper.instance().saveOrOverwrite(true);
|
||||
const args = defaultProps.actions.saveSlice.getCall(0).args;
|
||||
expect(args[1].new_dashboard_name).toBe(newDashboardName);
|
||||
});
|
||||
|
||||
describe('should always reload or redirect', () => {
|
||||
let wrapper;
|
||||
beforeEach(() => {
|
||||
wrapper = getWrapper();
|
||||
sinon.stub(window.location, 'assign');
|
||||
});
|
||||
afterEach(() => {
|
||||
window.location.assign.restore();
|
||||
});
|
||||
|
||||
it('Save & go to dashboard', done => {
|
||||
wrapper.instance().saveOrOverwrite(true);
|
||||
defaultProps.actions.saveSlice().then(() => {
|
||||
expect(window.location.assign.callCount).toEqual(1);
|
||||
expect(window.location.assign.getCall(0).args[0]).toEqual(
|
||||
'http://localhost/mock_dashboard/',
|
||||
);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('saveas new slice', done => {
|
||||
wrapper.setState({ action: 'saveas', newSliceName: 'new slice name' });
|
||||
wrapper.instance().saveOrOverwrite(false);
|
||||
defaultProps.actions.saveSlice().then(() => {
|
||||
expect(window.location.assign.callCount).toEqual(1);
|
||||
expect(window.location.assign.getCall(0).args[0]).toEqual(
|
||||
'/mock_slice/',
|
||||
);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('overwrite original slice', done => {
|
||||
wrapper.setState({ action: 'overwrite' });
|
||||
wrapper.instance().saveOrOverwrite(false);
|
||||
defaultProps.actions.saveSlice().then(() => {
|
||||
expect(window.location.assign.callCount).toEqual(1);
|
||||
expect(window.location.assign.getCall(0).args[0]).toEqual(
|
||||
'/mock_slice/',
|
||||
);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('fetchDashboards', () => {
|
||||
let dispatch;
|
||||
let actionThunk;
|
||||
const userID = 1;
|
||||
|
||||
const mockDashboardData = {
|
||||
pks: ['id'],
|
||||
result: [{ id: 'id', dashboard_title: 'dashboard title' }],
|
||||
};
|
||||
|
||||
const saveEndpoint = `glob:*/dashboardasync/api/read?_flt_0_owners=${1}`;
|
||||
|
||||
beforeAll(() => {
|
||||
fetchMock.get(saveEndpoint, mockDashboardData);
|
||||
});
|
||||
|
||||
afterAll(fetchMock.restore);
|
||||
|
||||
beforeEach(() => {
|
||||
dispatch = sinon.spy();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
fetchMock.resetHistory();
|
||||
});
|
||||
|
||||
const makeRequest = () => {
|
||||
actionThunk = saveModalActions.fetchDashboards(userID);
|
||||
return actionThunk(dispatch);
|
||||
};
|
||||
|
||||
it('makes the fetch request', () =>
|
||||
makeRequest().then(() => {
|
||||
expect(fetchMock.calls(saveEndpoint)).toHaveLength(1);
|
||||
|
||||
return Promise.resolve();
|
||||
}));
|
||||
|
||||
it('calls correct actions on success', () =>
|
||||
makeRequest().then(() => {
|
||||
expect(dispatch.callCount).toBe(1);
|
||||
expect(dispatch.getCall(0).args[0].type).toBe(
|
||||
saveModalActions.FETCH_DASHBOARDS_SUCCEEDED,
|
||||
);
|
||||
|
||||
return Promise.resolve();
|
||||
}));
|
||||
|
||||
it('calls correct actions on error', () => {
|
||||
fetchMock.get(
|
||||
saveEndpoint,
|
||||
{ throws: 'error' },
|
||||
{ overwriteRoutes: true },
|
||||
);
|
||||
|
||||
return makeRequest().then(() => {
|
||||
expect(dispatch.callCount).toBe(1);
|
||||
expect(dispatch.getCall(0).args[0].type).toBe(
|
||||
saveModalActions.FETCH_DASHBOARDS_FAILED,
|
||||
);
|
||||
|
||||
fetchMock.get(saveEndpoint, mockDashboardData, {
|
||||
overwriteRoutes: true,
|
||||
});
|
||||
|
||||
return Promise.resolve();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('removeAlert', () => {
|
||||
sinon.spy(defaultProps.actions, 'removeSaveModalAlert');
|
||||
const wrapper = getWrapper();
|
||||
wrapper.setProps({ alert: 'old alert' });
|
||||
|
||||
wrapper.instance().removeAlert();
|
||||
expect(defaultProps.actions.removeSaveModalAlert.callCount).toBe(1);
|
||||
expect(wrapper.state().alert).toBeNull();
|
||||
defaultProps.actions.removeSaveModalAlert.restore();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,166 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF 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.
|
||||
*/
|
||||
/* eslint-disable no-unused-expressions */
|
||||
import React from 'react';
|
||||
import Select, { Creatable } from 'react-select';
|
||||
import VirtualizedSelect from 'react-virtualized-select';
|
||||
import sinon from 'sinon';
|
||||
import { shallow } from 'enzyme';
|
||||
import OnPasteSelect from '../../../../src/components/OnPasteSelect';
|
||||
import VirtualizedRendererWrap from '../../../../src/components/VirtualizedRendererWrap';
|
||||
import SelectControl from '../../../../src/explore/components/controls/SelectControl';
|
||||
|
||||
const defaultProps = {
|
||||
choices: [
|
||||
['1 year ago', '1 year ago'],
|
||||
['today', 'today'],
|
||||
],
|
||||
name: 'row_limit',
|
||||
label: 'Row Limit',
|
||||
valueKey: 'value', // shallow isn't passing SelectControl.defaultProps.valueKey through
|
||||
onChange: sinon.spy(),
|
||||
};
|
||||
|
||||
const options = [
|
||||
{ value: '1 year ago', label: '1 year ago' },
|
||||
{ value: 'today', label: 'today' },
|
||||
];
|
||||
|
||||
describe('SelectControl', () => {
|
||||
let wrapper;
|
||||
|
||||
beforeEach(() => {
|
||||
wrapper = shallow(<SelectControl {...defaultProps} />);
|
||||
wrapper.setProps(defaultProps);
|
||||
});
|
||||
|
||||
it('renders an OnPasteSelect', () => {
|
||||
expect(wrapper.find(OnPasteSelect)).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('calls onChange when toggled', () => {
|
||||
const select = wrapper.find(OnPasteSelect);
|
||||
select.simulate('change', { value: 50 });
|
||||
expect(defaultProps.onChange.calledWith(50)).toBe(true);
|
||||
});
|
||||
|
||||
it('returns all options on select all', () => {
|
||||
const expectedValues = ['one', 'two'];
|
||||
const selectAllProps = {
|
||||
multi: true,
|
||||
allowAll: true,
|
||||
choices: expectedValues,
|
||||
name: 'row_limit',
|
||||
label: 'Row Limit',
|
||||
valueKey: 'value',
|
||||
onChange: sinon.spy(),
|
||||
};
|
||||
wrapper.setProps(selectAllProps);
|
||||
const select = wrapper.find(OnPasteSelect);
|
||||
select.simulate('change', [{ meta: true, value: 'Select All' }]);
|
||||
expect(selectAllProps.onChange.calledWith(expectedValues)).toBe(true);
|
||||
});
|
||||
|
||||
it('passes VirtualizedSelect as selectWrap', () => {
|
||||
const select = wrapper.find(OnPasteSelect);
|
||||
expect(select.props().selectWrap).toBe(VirtualizedSelect);
|
||||
});
|
||||
|
||||
it('passes Creatable as selectComponent when freeForm=true', () => {
|
||||
wrapper = shallow(<SelectControl {...defaultProps} freeForm />);
|
||||
const select = wrapper.find(OnPasteSelect);
|
||||
expect(select.props().selectComponent).toBe(Creatable);
|
||||
});
|
||||
|
||||
it('passes Select as selectComponent when freeForm=false', () => {
|
||||
const select = wrapper.find(OnPasteSelect);
|
||||
expect(select.props().selectComponent).toBe(Select);
|
||||
});
|
||||
|
||||
it('wraps optionRenderer in a VirtualizedRendererWrap', () => {
|
||||
const select = wrapper.find(OnPasteSelect);
|
||||
const defaultOptionRenderer = SelectControl.defaultProps.optionRenderer;
|
||||
const wrappedRenderer = VirtualizedRendererWrap(defaultOptionRenderer);
|
||||
expect(typeof select.props().optionRenderer).toBe('function');
|
||||
// different instances of wrapper with same inner renderer are unequal
|
||||
expect(select.props().optionRenderer.name).toBe(wrappedRenderer.name);
|
||||
});
|
||||
|
||||
describe('getOptions', () => {
|
||||
it('returns the correct options', () => {
|
||||
wrapper.setProps(defaultProps);
|
||||
expect(wrapper.instance().getOptions(defaultProps)).toEqual(options);
|
||||
});
|
||||
|
||||
it('shows Select-All when enabled', () => {
|
||||
const selectAllProps = {
|
||||
choices: ['one', 'two'],
|
||||
name: 'name',
|
||||
freeForm: true,
|
||||
allowAll: true,
|
||||
multi: true,
|
||||
valueKey: 'value',
|
||||
};
|
||||
wrapper.setProps(selectAllProps);
|
||||
expect(wrapper.instance().getOptions(selectAllProps)).toContainEqual({
|
||||
label: 'Select All',
|
||||
meta: true,
|
||||
value: 'Select All',
|
||||
});
|
||||
});
|
||||
|
||||
it('returns the correct options when freeform is set to true', () => {
|
||||
const freeFormProps = {
|
||||
choices: [],
|
||||
freeForm: true,
|
||||
value: ['one', 'two'],
|
||||
name: 'row_limit',
|
||||
label: 'Row Limit',
|
||||
valueKey: 'value',
|
||||
onChange: sinon.spy(),
|
||||
};
|
||||
const newOptions = [
|
||||
{ value: 'one', label: 'one' },
|
||||
{ value: 'two', label: 'two' },
|
||||
];
|
||||
wrapper.setProps(freeFormProps);
|
||||
expect(wrapper.instance().getOptions(freeFormProps)).toEqual(newOptions);
|
||||
});
|
||||
});
|
||||
|
||||
describe('componentWillReceiveProps', () => {
|
||||
it('sets state.options if props.choices has changed', () => {
|
||||
const updatedOptions = [
|
||||
{ value: 'three', label: 'three' },
|
||||
{ value: 'four', label: 'four' },
|
||||
];
|
||||
const newProps = {
|
||||
choices: [
|
||||
['three', 'three'],
|
||||
['four', 'four'],
|
||||
],
|
||||
name: 'name',
|
||||
freeForm: false,
|
||||
value: null,
|
||||
};
|
||||
wrapper.setProps(newProps);
|
||||
expect(wrapper.state().options).toEqual(updatedOptions);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF 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.
|
||||
*/
|
||||
/* eslint-disable no-unused-expressions */
|
||||
import React from 'react';
|
||||
import { FormControl } from 'react-bootstrap';
|
||||
import sinon from 'sinon';
|
||||
import { shallow } from 'enzyme';
|
||||
import AceEditor from 'react-ace';
|
||||
|
||||
import TextAreaControl from '../../../../src/explore/components/controls/TextAreaControl';
|
||||
|
||||
const defaultProps = {
|
||||
name: 'x_axis_label',
|
||||
label: 'X Axis Label',
|
||||
onChange: sinon.spy(),
|
||||
};
|
||||
|
||||
describe('SelectControl', () => {
|
||||
let wrapper;
|
||||
beforeEach(() => {
|
||||
wrapper = shallow(<TextAreaControl {...defaultProps} />);
|
||||
});
|
||||
|
||||
it('renders a FormControl', () => {
|
||||
expect(wrapper.find(FormControl)).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('calls onChange when toggled', () => {
|
||||
const select = wrapper.find(FormControl);
|
||||
select.simulate('change', { target: { value: 'x' } });
|
||||
expect(defaultProps.onChange.calledWith('x')).toBe(true);
|
||||
});
|
||||
|
||||
it('renders a AceEditor when language is specified', () => {
|
||||
const props = Object.assign({}, defaultProps);
|
||||
props.language = 'markdown';
|
||||
wrapper = shallow(<TextAreaControl {...props} />);
|
||||
expect(wrapper.find(FormControl)).toHaveLength(0);
|
||||
expect(wrapper.find(AceEditor)).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF 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.
|
||||
*/
|
||||
/* eslint-disable no-unused-expressions */
|
||||
import React from 'react';
|
||||
import { FormControl, OverlayTrigger } from 'react-bootstrap';
|
||||
import sinon from 'sinon';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import TimeSeriesColumnControl from '../../../../src/explore/components/controls/TimeSeriesColumnControl';
|
||||
|
||||
const defaultProps = {
|
||||
name: 'x_axis_label',
|
||||
label: 'X Axis Label',
|
||||
onChange: sinon.spy(),
|
||||
};
|
||||
|
||||
describe('SelectControl', () => {
|
||||
let wrapper;
|
||||
let inst;
|
||||
beforeEach(() => {
|
||||
wrapper = shallow(<TimeSeriesColumnControl {...defaultProps} />);
|
||||
inst = wrapper.instance();
|
||||
});
|
||||
|
||||
it('renders an OverlayTrigger', () => {
|
||||
expect(wrapper.find(OverlayTrigger)).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('renders an Popover', () => {
|
||||
const popOver = shallow(inst.renderPopover());
|
||||
expect(popOver.find(FormControl)).toHaveLength(3);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF 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.
|
||||
*/
|
||||
/* eslint-disable no-unused-expressions */
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
import { OverlayTrigger, Label } from 'react-bootstrap';
|
||||
|
||||
import ViewportControl from '../../../../src/explore/components/controls/ViewportControl';
|
||||
import TextControl from '../../../../src/explore/components/controls/TextControl';
|
||||
import ControlHeader from '../../../../src/explore/components/ControlHeader';
|
||||
|
||||
const defaultProps = {
|
||||
value: {
|
||||
longitude: 6.85236157047845,
|
||||
latitude: 31.222656842808707,
|
||||
zoom: 1,
|
||||
bearing: 0,
|
||||
pitch: 0,
|
||||
},
|
||||
};
|
||||
|
||||
describe('ViewportControl', () => {
|
||||
let wrapper;
|
||||
let inst;
|
||||
beforeEach(() => {
|
||||
wrapper = shallow(<ViewportControl {...defaultProps} />);
|
||||
inst = wrapper.instance();
|
||||
});
|
||||
|
||||
it('renders a OverlayTrigger', () => {
|
||||
const controlHeader = wrapper.find(ControlHeader);
|
||||
expect(controlHeader).toHaveLength(1);
|
||||
expect(wrapper.find(OverlayTrigger)).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('renders a Popover with 5 TextControl', () => {
|
||||
const popOver = shallow(inst.renderPopover());
|
||||
expect(popOver.find(TextControl)).toHaveLength(5);
|
||||
});
|
||||
|
||||
it('renders a summary in the label', () => {
|
||||
expect(
|
||||
wrapper
|
||||
.find(Label)
|
||||
.first()
|
||||
.render()
|
||||
.text(),
|
||||
).toBe('6° 51\' 8.50" | 31° 13\' 21.56"');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF 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 sinon from 'sinon';
|
||||
import { shallow } from 'enzyme';
|
||||
import { Modal } from 'react-bootstrap';
|
||||
import { getChartMetadataRegistry, ChartMetadata } from '@superset-ui/chart';
|
||||
import VizTypeControl from '../../../../src/explore/components/controls/VizTypeControl';
|
||||
|
||||
const defaultProps = {
|
||||
name: 'viz_type',
|
||||
label: 'Visualization Type',
|
||||
value: 'vis1',
|
||||
onChange: sinon.spy(),
|
||||
};
|
||||
|
||||
describe('VizTypeControl', () => {
|
||||
let wrapper;
|
||||
|
||||
const registry = getChartMetadataRegistry();
|
||||
registry
|
||||
.registerValue(
|
||||
'vis1',
|
||||
new ChartMetadata({
|
||||
name: 'vis1',
|
||||
thumbnail: '',
|
||||
}),
|
||||
)
|
||||
.registerValue(
|
||||
'vis2',
|
||||
new ChartMetadata({
|
||||
name: 'vis2',
|
||||
thumbnail: '',
|
||||
}),
|
||||
);
|
||||
|
||||
beforeEach(() => {
|
||||
wrapper = shallow(<VizTypeControl {...defaultProps} />);
|
||||
});
|
||||
|
||||
it('renders a Modal', () => {
|
||||
expect(wrapper.find(Modal)).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('calls onChange when toggled', () => {
|
||||
const select = wrapper.find('.viztype-selector-container').first();
|
||||
select.simulate('click');
|
||||
expect(defaultProps.onChange.called).toBe(true);
|
||||
});
|
||||
it('filters images based on text input', () => {
|
||||
expect(wrapper.find('img')).toHaveLength(2);
|
||||
wrapper.setState({ filter: 'vis2' });
|
||||
expect(wrapper.find('img')).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,127 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF 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 sinon from 'sinon';
|
||||
import { shallow } from 'enzyme';
|
||||
import fetchMock from 'fetch-mock';
|
||||
|
||||
import MetricsControl from '../../../../src/explore/components/controls/MetricsControl';
|
||||
import withVerification from '../../../../src/explore/components/controls/withVerification';
|
||||
|
||||
const defaultProps = {
|
||||
name: 'metrics',
|
||||
label: 'Metrics',
|
||||
value: undefined,
|
||||
multi: true,
|
||||
columns: [
|
||||
{ type: 'VARCHAR(255)', column_name: 'source' },
|
||||
{ type: 'VARCHAR(255)', column_name: 'target' },
|
||||
{ type: 'DOUBLE', column_name: 'value' },
|
||||
],
|
||||
savedMetrics: [
|
||||
{ metric_name: 'sum__value', expression: 'SUM(energy_usage.value)' },
|
||||
{ metric_name: 'avg__value', expression: 'AVG(energy_usage.value)' },
|
||||
],
|
||||
datasourceType: 'sqla',
|
||||
getEndpoint: controlValues => `valid_metrics?data=${controlValues}`,
|
||||
};
|
||||
|
||||
const VALID_METRIC = {
|
||||
metric_name: 'sum__value',
|
||||
expression: 'SUM(energy_usage.value)',
|
||||
};
|
||||
|
||||
function setup(overrides, validMetric) {
|
||||
const onChange = sinon.spy();
|
||||
const props = {
|
||||
onChange,
|
||||
...defaultProps,
|
||||
...overrides,
|
||||
};
|
||||
const VerifiedControl = withVerification(
|
||||
MetricsControl,
|
||||
'metric_name',
|
||||
'savedMetrics',
|
||||
);
|
||||
const wrapper = shallow(<VerifiedControl {...props} />);
|
||||
fetchMock.mock(
|
||||
'glob:*/valid_metrics*',
|
||||
validMetric || `["${VALID_METRIC.metric_name}"]`,
|
||||
);
|
||||
return { props, wrapper, onChange };
|
||||
}
|
||||
|
||||
afterEach(fetchMock.restore);
|
||||
|
||||
describe('VerifiedMetricsControl', () => {
|
||||
it('Gets valid options', () => {
|
||||
const { wrapper } = setup();
|
||||
setTimeout(() => {
|
||||
expect(fetchMock.calls(defaultProps.getEndpoint())).toHaveLength(1);
|
||||
expect(wrapper.state('validOptions')).toEqual([VALID_METRIC]);
|
||||
fetchMock.reset();
|
||||
}, 0);
|
||||
});
|
||||
|
||||
it('Returns verified options', () => {
|
||||
const { wrapper } = setup();
|
||||
setTimeout(() => {
|
||||
expect(fetchMock.calls(defaultProps.getEndpoint())).toHaveLength(1);
|
||||
const child = wrapper.find(MetricsControl);
|
||||
expect(child.props().savedMetrics).toEqual([VALID_METRIC]);
|
||||
fetchMock.reset();
|
||||
}, 0);
|
||||
});
|
||||
|
||||
it('Makes no calls if endpoint is not set', () => {
|
||||
const { wrapper } = setup({
|
||||
getEndpoint: () => null,
|
||||
});
|
||||
setTimeout(() => {
|
||||
expect(fetchMock.calls(defaultProps.getEndpoint())).toHaveLength(0);
|
||||
expect(wrapper.state('validOptions')).toEqual(new Set());
|
||||
fetchMock.reset();
|
||||
}, 0);
|
||||
});
|
||||
|
||||
it('Calls endpoint if control values change', () => {
|
||||
const { props, wrapper } = setup({
|
||||
controlValues: { metrics: 'sum__value' },
|
||||
});
|
||||
setTimeout(() => {
|
||||
expect(fetchMock.calls(defaultProps.getEndpoint())).toHaveLength(1);
|
||||
fetchMock.reset();
|
||||
}, 0);
|
||||
wrapper.setProps({ ...props, controlValues: { metrics: 'avg__value' } });
|
||||
setTimeout(() => {
|
||||
expect(fetchMock.calls(defaultProps.getEndpoint())).toHaveLength(1);
|
||||
fetchMock.reset();
|
||||
}, 0);
|
||||
});
|
||||
|
||||
it('Returns no verified options if none are valid', () => {
|
||||
const { wrapper } = setup({}, []);
|
||||
setTimeout(() => {
|
||||
expect(fetchMock.calls(defaultProps.getEndpoint())).toHaveLength(1);
|
||||
const child = wrapper.find(MetricsControl);
|
||||
expect(child.props().savedMetrics).toEqual([]);
|
||||
fetchMock.reset();
|
||||
}, 0);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user