mirror of
https://github.com/apache/superset.git
synced 2026-05-12 03:15:55 +00:00
test: Adds tests to the TimeSeriesColumnControl component (#13712)
This commit is contained in:
committed by
GitHub
parent
7e394e556a
commit
61129f749f
@@ -0,0 +1,176 @@
|
||||
/**
|
||||
* 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 { render, screen } from 'spec/helpers/testing-library';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import TimeSeriesColumnControl from '.';
|
||||
|
||||
test('renders with default props', () => {
|
||||
render(<TimeSeriesColumnControl />);
|
||||
expect(screen.getByText('Time series columns')).toBeInTheDocument();
|
||||
expect(screen.getByRole('button')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('renders popover on edit', () => {
|
||||
render(<TimeSeriesColumnControl />);
|
||||
userEvent.click(screen.getByRole('button'));
|
||||
expect(screen.getByRole('tooltip')).toBeInTheDocument();
|
||||
expect(screen.getByText('Label')).toBeInTheDocument();
|
||||
expect(screen.getByText('Tooltip')).toBeInTheDocument();
|
||||
expect(screen.getByText('Type')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('triggers onChange when type changes', () => {
|
||||
const onChange = jest.fn();
|
||||
render(<TimeSeriesColumnControl onChange={onChange} />);
|
||||
userEvent.click(screen.getByRole('button'));
|
||||
userEvent.click(screen.getByText('Select...'));
|
||||
expect(onChange).not.toHaveBeenCalled();
|
||||
userEvent.click(screen.getByText('Time comparison'));
|
||||
expect(onChange).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('renders time comparison', () => {
|
||||
render(<TimeSeriesColumnControl colType="time" />);
|
||||
userEvent.click(screen.getByRole('button'));
|
||||
expect(screen.getByText('Time lag')).toBeInTheDocument();
|
||||
expect(screen.getAllByText('Type')[1]).toBeInTheDocument();
|
||||
expect(screen.getByText('Color bounds')).toBeInTheDocument();
|
||||
expect(screen.getByText('Number format')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('renders contribution', () => {
|
||||
render(<TimeSeriesColumnControl colType="contrib" />);
|
||||
userEvent.click(screen.getByRole('button'));
|
||||
expect(screen.getByText('Color bounds')).toBeInTheDocument();
|
||||
expect(screen.getByText('Number format')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('renders sparkline', () => {
|
||||
render(<TimeSeriesColumnControl colType="spark" />);
|
||||
userEvent.click(screen.getByRole('button'));
|
||||
expect(screen.getByText('Width')).toBeInTheDocument();
|
||||
expect(screen.getByText('Height')).toBeInTheDocument();
|
||||
expect(screen.getByText('Time ratio')).toBeInTheDocument();
|
||||
expect(screen.getByText('Show Y-axis')).toBeInTheDocument();
|
||||
expect(screen.getByText('Y-axis bounds')).toBeInTheDocument();
|
||||
expect(screen.getByText('Number format')).toBeInTheDocument();
|
||||
expect(screen.getByText('Date format')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('renders period average', () => {
|
||||
render(<TimeSeriesColumnControl colType="avg" />);
|
||||
userEvent.click(screen.getByRole('button'));
|
||||
expect(screen.getByText('Time lag')).toBeInTheDocument();
|
||||
expect(screen.getByText('Color bounds')).toBeInTheDocument();
|
||||
expect(screen.getByText('Number format')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('triggers onChange when time lag changes', () => {
|
||||
const onChange = jest.fn();
|
||||
render(<TimeSeriesColumnControl colType="time" onChange={onChange} />);
|
||||
userEvent.click(screen.getByRole('button'));
|
||||
expect(onChange).not.toHaveBeenCalled();
|
||||
userEvent.type(screen.getByPlaceholderText('Time Lag'), '1');
|
||||
expect(onChange).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('triggers onChange when color bounds changes', () => {
|
||||
const onChange = jest.fn();
|
||||
render(<TimeSeriesColumnControl colType="time" onChange={onChange} />);
|
||||
userEvent.click(screen.getByRole('button'));
|
||||
expect(onChange).not.toHaveBeenCalled();
|
||||
userEvent.type(screen.getByPlaceholderText('Min'), '1');
|
||||
userEvent.type(screen.getByPlaceholderText('Max'), '10');
|
||||
expect(onChange).toHaveBeenCalledTimes(3);
|
||||
});
|
||||
|
||||
test('triggers onChange when time type changes', () => {
|
||||
const onChange = jest.fn();
|
||||
render(<TimeSeriesColumnControl colType="time" onChange={onChange} />);
|
||||
userEvent.click(screen.getByRole('button'));
|
||||
userEvent.click(screen.getByText('Select...'));
|
||||
expect(onChange).not.toHaveBeenCalled();
|
||||
userEvent.click(screen.getByText('Difference'));
|
||||
expect(onChange).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('triggers onChange when number format changes', () => {
|
||||
const onChange = jest.fn();
|
||||
render(<TimeSeriesColumnControl colType="time" onChange={onChange} />);
|
||||
userEvent.click(screen.getByRole('button'));
|
||||
expect(onChange).not.toHaveBeenCalled();
|
||||
userEvent.type(screen.getByPlaceholderText('Number format string'), 'format');
|
||||
expect(onChange).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('triggers onChange when width changes', () => {
|
||||
const onChange = jest.fn();
|
||||
render(<TimeSeriesColumnControl colType="spark" onChange={onChange} />);
|
||||
userEvent.click(screen.getByRole('button'));
|
||||
expect(onChange).not.toHaveBeenCalled();
|
||||
userEvent.type(screen.getByPlaceholderText('Width'), '10');
|
||||
expect(onChange).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('triggers onChange when height changes', () => {
|
||||
const onChange = jest.fn();
|
||||
render(<TimeSeriesColumnControl colType="spark" onChange={onChange} />);
|
||||
userEvent.click(screen.getByRole('button'));
|
||||
expect(onChange).not.toHaveBeenCalled();
|
||||
userEvent.type(screen.getByPlaceholderText('Height'), '10');
|
||||
expect(onChange).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('triggers onChange when time ratio changes', () => {
|
||||
const onChange = jest.fn();
|
||||
render(<TimeSeriesColumnControl colType="spark" onChange={onChange} />);
|
||||
userEvent.click(screen.getByRole('button'));
|
||||
expect(onChange).not.toHaveBeenCalled();
|
||||
userEvent.type(screen.getByPlaceholderText('Time Ratio'), '10');
|
||||
expect(onChange).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('triggers onChange when show Y-axis changes', () => {
|
||||
const onChange = jest.fn();
|
||||
render(<TimeSeriesColumnControl colType="spark" onChange={onChange} />);
|
||||
userEvent.click(screen.getByRole('button'));
|
||||
expect(onChange).not.toHaveBeenCalled();
|
||||
userEvent.click(screen.getByRole('checkbox'));
|
||||
expect(onChange).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('triggers onChange when Y-axis bounds changes', () => {
|
||||
const onChange = jest.fn();
|
||||
render(<TimeSeriesColumnControl colType="spark" onChange={onChange} />);
|
||||
userEvent.click(screen.getByRole('button'));
|
||||
expect(onChange).not.toHaveBeenCalled();
|
||||
userEvent.type(screen.getByPlaceholderText('Min'), '1');
|
||||
userEvent.type(screen.getByPlaceholderText('Max'), '10');
|
||||
expect(onChange).toHaveBeenCalledTimes(3);
|
||||
});
|
||||
|
||||
test('triggers onChange when date format changes', () => {
|
||||
const onChange = jest.fn();
|
||||
render(<TimeSeriesColumnControl colType="spark" onChange={onChange} />);
|
||||
userEvent.click(screen.getByRole('button'));
|
||||
expect(onChange).not.toHaveBeenCalled();
|
||||
userEvent.type(screen.getByPlaceholderText('Date format string'), 'yy/MM/dd');
|
||||
expect(onChange).toHaveBeenCalled();
|
||||
});
|
||||
@@ -0,0 +1,322 @@
|
||||
/**
|
||||
* 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 PropTypes from 'prop-types';
|
||||
import { Row, Col, FormControl } from 'react-bootstrap';
|
||||
import Popover from 'src/components/Popover';
|
||||
import Select from 'src/components/Select';
|
||||
import { t } from '@superset-ui/core';
|
||||
import { InfoTooltipWithTrigger } from '@superset-ui/chart-controls';
|
||||
|
||||
import BoundsControl from '../BoundsControl';
|
||||
import CheckboxControl from '../CheckboxControl';
|
||||
|
||||
const propTypes = {
|
||||
label: PropTypes.string,
|
||||
tooltip: PropTypes.string,
|
||||
colType: PropTypes.string,
|
||||
width: PropTypes.string,
|
||||
height: PropTypes.string,
|
||||
timeLag: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
timeRatio: PropTypes.string,
|
||||
comparisonType: PropTypes.string,
|
||||
showYAxis: PropTypes.bool,
|
||||
yAxisBounds: PropTypes.array,
|
||||
bounds: PropTypes.array,
|
||||
d3format: PropTypes.string,
|
||||
dateFormat: PropTypes.string,
|
||||
onChange: PropTypes.func,
|
||||
};
|
||||
|
||||
const defaultProps = {
|
||||
label: t('Time series columns'),
|
||||
tooltip: '',
|
||||
colType: '',
|
||||
width: '',
|
||||
height: '',
|
||||
timeLag: '',
|
||||
timeRatio: '',
|
||||
comparisonType: '',
|
||||
showYAxis: false,
|
||||
yAxisBounds: [null, null],
|
||||
bounds: [null, null],
|
||||
d3format: '',
|
||||
dateFormat: '',
|
||||
};
|
||||
|
||||
const comparisonTypeOptions = [
|
||||
{ value: 'value', label: 'Actual value' },
|
||||
{ value: 'diff', label: 'Difference' },
|
||||
{ value: 'perc', label: 'Percentage' },
|
||||
{ value: 'perc_change', label: 'Percentage change' },
|
||||
];
|
||||
|
||||
const colTypeOptions = [
|
||||
{ value: 'time', label: 'Time comparison' },
|
||||
{ value: 'contrib', label: 'Contribution' },
|
||||
{ value: 'spark', label: 'Sparkline' },
|
||||
{ value: 'avg', label: 'Period average' },
|
||||
];
|
||||
|
||||
export default class TimeSeriesColumnControl extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
const state = {
|
||||
label: this.props.label,
|
||||
tooltip: this.props.tooltip,
|
||||
colType: this.props.colType,
|
||||
width: this.props.width,
|
||||
height: this.props.height,
|
||||
timeLag: this.props.timeLag || 0,
|
||||
timeRatio: this.props.timeRatio,
|
||||
comparisonType: this.props.comparisonType,
|
||||
showYAxis: this.props.showYAxis,
|
||||
yAxisBounds: this.props.yAxisBounds,
|
||||
bounds: this.props.bounds,
|
||||
d3format: this.props.d3format,
|
||||
dateFormat: this.props.dateFormat,
|
||||
};
|
||||
delete state.onChange;
|
||||
this.state = state;
|
||||
this.onChange = this.onChange.bind(this);
|
||||
}
|
||||
|
||||
onChange() {
|
||||
this.props.onChange(this.state);
|
||||
}
|
||||
|
||||
onSelectChange(attr, opt) {
|
||||
this.setState({ [attr]: opt.value }, this.onChange);
|
||||
}
|
||||
|
||||
onTextInputChange(attr, event) {
|
||||
this.setState({ [attr]: event.target.value }, this.onChange);
|
||||
}
|
||||
|
||||
onCheckboxChange(attr, value) {
|
||||
this.setState({ [attr]: value }, this.onChange);
|
||||
}
|
||||
|
||||
onBoundsChange(bounds) {
|
||||
this.setState({ bounds }, this.onChange);
|
||||
}
|
||||
|
||||
onYAxisBoundsChange(yAxisBounds) {
|
||||
this.setState({ yAxisBounds }, this.onChange);
|
||||
}
|
||||
|
||||
textSummary() {
|
||||
return `${this.state.label}`;
|
||||
}
|
||||
|
||||
formRow(label, tooltip, ttLabel, control) {
|
||||
return (
|
||||
<Row style={{ marginTop: '5px' }}>
|
||||
<Col md={5}>
|
||||
{`${label} `}
|
||||
<InfoTooltipWithTrigger
|
||||
placement="top"
|
||||
tooltip={tooltip}
|
||||
label={ttLabel}
|
||||
/>
|
||||
</Col>
|
||||
<Col md={7}>{control}</Col>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
|
||||
renderPopover() {
|
||||
return (
|
||||
<div id="ts-col-popo" style={{ width: 300 }}>
|
||||
{this.formRow(
|
||||
'Label',
|
||||
'The column header label',
|
||||
'time-lag',
|
||||
<FormControl
|
||||
value={this.state.label}
|
||||
onChange={this.onTextInputChange.bind(this, 'label')}
|
||||
bsSize="small"
|
||||
placeholder="Label"
|
||||
/>,
|
||||
)}
|
||||
{this.formRow(
|
||||
'Tooltip',
|
||||
'Column header tooltip',
|
||||
'col-tooltip',
|
||||
<FormControl
|
||||
value={this.state.tooltip}
|
||||
onChange={this.onTextInputChange.bind(this, 'tooltip')}
|
||||
bsSize="small"
|
||||
placeholder="Tooltip"
|
||||
/>,
|
||||
)}
|
||||
{this.formRow(
|
||||
'Type',
|
||||
'Type of comparison, value difference or percentage',
|
||||
'col-type',
|
||||
<Select
|
||||
value={this.state.colType}
|
||||
clearable={false}
|
||||
onChange={this.onSelectChange.bind(this, 'colType')}
|
||||
options={colTypeOptions}
|
||||
/>,
|
||||
)}
|
||||
<hr />
|
||||
{this.state.colType === 'spark' &&
|
||||
this.formRow(
|
||||
'Width',
|
||||
'Width of the sparkline',
|
||||
'spark-width',
|
||||
<FormControl
|
||||
value={this.state.width}
|
||||
onChange={this.onTextInputChange.bind(this, 'width')}
|
||||
bsSize="small"
|
||||
placeholder="Width"
|
||||
/>,
|
||||
)}
|
||||
{this.state.colType === 'spark' &&
|
||||
this.formRow(
|
||||
'Height',
|
||||
'Height of the sparkline',
|
||||
'spark-width',
|
||||
<FormControl
|
||||
value={this.state.height}
|
||||
onChange={this.onTextInputChange.bind(this, 'height')}
|
||||
bsSize="small"
|
||||
placeholder="Height"
|
||||
/>,
|
||||
)}
|
||||
{['time', 'avg'].indexOf(this.state.colType) >= 0 &&
|
||||
this.formRow(
|
||||
'Time lag',
|
||||
'Number of periods to compare against',
|
||||
'time-lag',
|
||||
<FormControl
|
||||
value={this.state.timeLag}
|
||||
onChange={this.onTextInputChange.bind(this, 'timeLag')}
|
||||
bsSize="small"
|
||||
placeholder="Time Lag"
|
||||
/>,
|
||||
)}
|
||||
{['spark'].indexOf(this.state.colType) >= 0 &&
|
||||
this.formRow(
|
||||
'Time ratio',
|
||||
'Number of periods to ratio against',
|
||||
'time-ratio',
|
||||
<FormControl
|
||||
value={this.state.timeRatio}
|
||||
onChange={this.onTextInputChange.bind(this, 'timeRatio')}
|
||||
bsSize="small"
|
||||
placeholder="Time Ratio"
|
||||
/>,
|
||||
)}
|
||||
{this.state.colType === 'time' &&
|
||||
this.formRow(
|
||||
'Type',
|
||||
'Type of comparison, value difference or percentage',
|
||||
'comp-type',
|
||||
<Select
|
||||
value={this.state.comparisonType}
|
||||
clearable={false}
|
||||
onChange={this.onSelectChange.bind(this, 'comparisonType')}
|
||||
options={comparisonTypeOptions}
|
||||
/>,
|
||||
)}
|
||||
{this.state.colType === 'spark' &&
|
||||
this.formRow(
|
||||
'Show Y-axis',
|
||||
'Show Y-axis on the sparkline. Will display the manually set min/max if set or min/max values in the data otherwise.',
|
||||
'show-y-axis-bounds',
|
||||
<CheckboxControl
|
||||
value={this.state.showYAxis}
|
||||
onChange={this.onCheckboxChange.bind(this, 'showYAxis')}
|
||||
/>,
|
||||
)}
|
||||
{this.state.colType === 'spark' &&
|
||||
this.formRow(
|
||||
'Y-axis bounds',
|
||||
'Manually set min/max values for the y-axis.',
|
||||
'y-axis-bounds',
|
||||
<BoundsControl
|
||||
value={this.state.yAxisBounds}
|
||||
onChange={this.onYAxisBoundsChange.bind(this)}
|
||||
/>,
|
||||
)}
|
||||
{this.state.colType !== 'spark' &&
|
||||
this.formRow(
|
||||
'Color bounds',
|
||||
`Number bounds used for color encoding from red to blue.
|
||||
Reverse the numbers for blue to red. To get pure red or blue,
|
||||
you can enter either only min or max.`,
|
||||
'bounds',
|
||||
<BoundsControl
|
||||
value={this.state.bounds}
|
||||
onChange={this.onBoundsChange.bind(this)}
|
||||
/>,
|
||||
)}
|
||||
{this.formRow(
|
||||
'Number format',
|
||||
'Optional d3 number format string',
|
||||
'd3-format',
|
||||
<FormControl
|
||||
value={this.state.d3format}
|
||||
onChange={this.onTextInputChange.bind(this, 'd3format')}
|
||||
bsSize="small"
|
||||
placeholder="Number format string"
|
||||
/>,
|
||||
)}
|
||||
{this.state.colType === 'spark' &&
|
||||
this.formRow(
|
||||
'Date format',
|
||||
'Optional d3 date format string',
|
||||
'date-format',
|
||||
<FormControl
|
||||
value={this.state.dateFormat}
|
||||
onChange={this.onTextInputChange.bind(this, 'dateFormat')}
|
||||
bsSize="small"
|
||||
placeholder="Date format string"
|
||||
/>,
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<span>
|
||||
{this.textSummary()}{' '}
|
||||
<Popover
|
||||
trigger="click"
|
||||
placement="right"
|
||||
content={this.renderPopover()}
|
||||
title="Column Configuration"
|
||||
>
|
||||
<InfoTooltipWithTrigger
|
||||
icon="edit"
|
||||
className="text-primary"
|
||||
label="edit-ts-column"
|
||||
/>
|
||||
</Popover>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
TimeSeriesColumnControl.propTypes = propTypes;
|
||||
TimeSeriesColumnControl.defaultProps = defaultProps;
|
||||
Reference in New Issue
Block a user