kibana/x-pack/plugins/lens/public/xy_visualization/expression.test.tsx
Brandon Kobel 4584a8b570
Elastic License 2.0 (#90099)
* Updating everything except the license headers themselves

* Applying ESLint rules

* Manually replacing the stragglers
2021-02-03 18:12:39 -08:00

1912 lines
57 KiB
TypeScript

/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import {
AreaSeries,
Axis,
BarSeries,
Position,
LineSeries,
Settings,
ScaleType,
GeometryValue,
XYChartSeriesIdentifier,
SeriesNameFn,
Fit,
} from '@elastic/charts';
import { PaletteOutput } from 'src/plugins/charts/public';
import {
calculateMinInterval,
xyChart,
XYChart,
XYChartProps,
XYChartRenderProps,
} from './expression';
import { LensMultiTable } from '../types';
import { Datatable, DatatableRow } from '../../../../../src/plugins/expressions/public';
import React from 'react';
import { shallow } from 'enzyme';
import {
XYArgs,
LegendConfig,
legendConfig,
layerConfig,
LayerArgs,
AxesSettingsConfig,
tickLabelsConfig,
gridlinesConfig,
} from './types';
import { createMockExecutionContext } from '../../../../../src/plugins/expressions/common/mocks';
import { mountWithIntl } from '@kbn/test/jest';
import { chartPluginMock } from '../../../../../src/plugins/charts/public/mocks';
import { EmptyPlaceholder } from '../shared_components/empty_placeholder';
const onClickValue = jest.fn();
const onSelectRange = jest.fn();
const chartsThemeService = chartPluginMock.createSetupContract().theme;
const paletteService = chartPluginMock.createPaletteRegistry();
const mockPaletteOutput: PaletteOutput = {
type: 'palette',
name: 'mock',
params: {},
};
const dateHistogramData: LensMultiTable = {
type: 'lens_multitable',
tables: {
timeLayer: {
type: 'datatable',
rows: [
{
xAccessorId: 1585758120000,
splitAccessorId: "Men's Clothing",
yAccessorId: 1,
},
{
xAccessorId: 1585758360000,
splitAccessorId: "Women's Accessories",
yAccessorId: 1,
},
{
xAccessorId: 1585758360000,
splitAccessorId: "Women's Clothing",
yAccessorId: 1,
},
{
xAccessorId: 1585759380000,
splitAccessorId: "Men's Clothing",
yAccessorId: 1,
},
{
xAccessorId: 1585759380000,
splitAccessorId: "Men's Shoes",
yAccessorId: 1,
},
{
xAccessorId: 1585759380000,
splitAccessorId: "Women's Clothing",
yAccessorId: 1,
},
{
xAccessorId: 1585760700000,
splitAccessorId: "Men's Clothing",
yAccessorId: 1,
},
{
xAccessorId: 1585760760000,
splitAccessorId: "Men's Clothing",
yAccessorId: 1,
},
{
xAccessorId: 1585760760000,
splitAccessorId: "Men's Shoes",
yAccessorId: 1,
},
{
xAccessorId: 1585761120000,
splitAccessorId: "Men's Shoes",
yAccessorId: 1,
},
],
columns: [
{
id: 'xAccessorId',
name: 'order_date per minute',
meta: {
type: 'date',
field: 'order_date',
source: 'esaggs',
index: 'indexPatternId',
sourceParams: {
indexPatternId: 'indexPatternId',
type: 'date_histogram',
params: {
field: 'order_date',
timeRange: { from: '2020-04-01T16:14:16.246Z', to: '2020-04-01T17:15:41.263Z' },
useNormalizedEsInterval: true,
scaleMetricValues: false,
interval: '1m',
drop_partials: false,
min_doc_count: 0,
extended_bounds: {},
},
},
params: { id: 'date', params: { pattern: 'HH:mm' } },
},
},
{
id: 'splitAccessorId',
name: 'Top values of category.keyword',
meta: {
type: 'string',
field: 'category.keyword',
source: 'esaggs',
index: 'indexPatternId',
sourceParams: {
indexPatternId: 'indexPatternId',
type: 'terms',
params: {
field: 'category.keyword',
orderBy: 'yAccessorId',
order: 'desc',
size: 3,
otherBucket: false,
otherBucketLabel: 'Other',
missingBucket: false,
missingBucketLabel: 'Missing',
},
},
params: {
id: 'terms',
params: {
id: 'string',
otherBucketLabel: 'Other',
missingBucketLabel: 'Missing',
parsedUrl: {
origin: 'http://localhost:5601',
pathname: '/jiy/app/kibana',
basePath: '/jiy',
},
},
},
},
},
{
id: 'yAccessorId',
name: 'Count of records',
meta: {
type: 'number',
source: 'esaggs',
index: 'indexPatternId',
sourceParams: {
indexPatternId: 'indexPatternId',
params: {},
},
params: { id: 'number' },
},
},
],
},
},
dateRange: {
fromDate: new Date('2020-04-01T16:14:16.246Z'),
toDate: new Date('2020-04-01T17:15:41.263Z'),
},
};
const dateHistogramLayer: LayerArgs = {
layerId: 'timeLayer',
hide: false,
xAccessor: 'xAccessorId',
yScaleType: 'linear',
xScaleType: 'time',
isHistogram: true,
splitAccessor: 'splitAccessorId',
seriesType: 'bar_stacked',
accessors: ['yAccessorId'],
palette: mockPaletteOutput,
};
const createSampleDatatableWithRows = (rows: DatatableRow[]): Datatable => ({
type: 'datatable',
columns: [
{
id: 'a',
name: 'a',
meta: { type: 'number', params: { id: 'number', params: { pattern: '0,0.000' } } },
},
{
id: 'b',
name: 'b',
meta: { type: 'number', params: { id: 'number', params: { pattern: '000,0' } } },
},
{
id: 'c',
name: 'c',
meta: {
type: 'date',
field: 'order_date',
sourceParams: { type: 'date-histogram', params: { interval: 'auto' } },
params: { id: 'string' },
},
},
{ id: 'd', name: 'ColD', meta: { type: 'string' } },
],
rows,
});
const sampleLayer: LayerArgs = {
layerId: 'first',
seriesType: 'line',
xAccessor: 'c',
accessors: ['a', 'b'],
splitAccessor: 'd',
columnToLabel: '{"a": "Label A", "b": "Label B", "d": "Label D"}',
xScaleType: 'ordinal',
yScaleType: 'linear',
isHistogram: false,
palette: mockPaletteOutput,
};
const createArgsWithLayers = (layers: LayerArgs[] = [sampleLayer]): XYArgs => ({
xTitle: '',
yTitle: '',
yRightTitle: '',
legend: {
type: 'lens_xy_legendConfig',
isVisible: false,
position: Position.Top,
},
valueLabels: 'hide',
axisTitlesVisibilitySettings: {
type: 'lens_xy_axisTitlesVisibilityConfig',
x: true,
yLeft: true,
yRight: true,
},
tickLabelsVisibilitySettings: {
type: 'lens_xy_tickLabelsConfig',
x: true,
yLeft: false,
yRight: false,
},
gridlinesVisibilitySettings: {
type: 'lens_xy_gridlinesConfig',
x: true,
yLeft: false,
yRight: false,
},
layers,
});
function sampleArgs() {
const data: LensMultiTable = {
type: 'lens_multitable',
tables: {
first: createSampleDatatableWithRows([
{ a: 1, b: 2, c: 'I', d: 'Foo' },
{ a: 1, b: 5, c: 'J', d: 'Bar' },
]),
},
dateRange: {
fromDate: new Date('2019-01-02T05:00:00.000Z'),
toDate: new Date('2019-01-03T05:00:00.000Z'),
},
};
const args: XYArgs = createArgsWithLayers();
return { data, args };
}
describe('xy_expression', () => {
describe('configs', () => {
test('legendConfig produces the correct arguments', () => {
const args: LegendConfig = {
isVisible: true,
position: Position.Left,
};
const result = legendConfig.fn(null, args, createMockExecutionContext());
expect(result).toEqual({
type: 'lens_xy_legendConfig',
...args,
});
});
test('layerConfig produces the correct arguments', () => {
const args: LayerArgs = {
layerId: 'first',
seriesType: 'line',
xAccessor: 'c',
accessors: ['a', 'b'],
splitAccessor: 'd',
xScaleType: 'linear',
yScaleType: 'linear',
isHistogram: false,
palette: mockPaletteOutput,
};
const result = layerConfig.fn(null, args, createMockExecutionContext());
expect(result).toEqual({
type: 'lens_xy_layer',
...args,
});
});
});
test('tickLabelsConfig produces the correct arguments', () => {
const args: AxesSettingsConfig = {
x: true,
yLeft: false,
yRight: false,
};
const result = tickLabelsConfig.fn(null, args, createMockExecutionContext());
expect(result).toEqual({
type: 'lens_xy_tickLabelsConfig',
...args,
});
});
test('gridlinesConfig produces the correct arguments', () => {
const args: AxesSettingsConfig = {
x: true,
yLeft: false,
yRight: false,
};
const result = gridlinesConfig.fn(null, args, createMockExecutionContext());
expect(result).toEqual({
type: 'lens_xy_gridlinesConfig',
...args,
});
});
describe('xyChart', () => {
test('it renders with the specified data and args', () => {
const { data, args } = sampleArgs();
const result = xyChart.fn(data, args, createMockExecutionContext());
expect(result).toEqual({
type: 'render',
as: 'lens_xy_chart_renderer',
value: { data, args },
});
});
});
describe('XYChart component', () => {
let getFormatSpy: jest.Mock;
let convertSpy: jest.Mock;
let defaultProps: Omit<XYChartRenderProps, 'data' | 'args'>;
const dataWithoutFormats: LensMultiTable = {
type: 'lens_multitable',
tables: {
first: {
type: 'datatable',
columns: [
{ id: 'a', name: 'a', meta: { type: 'number' } },
{ id: 'b', name: 'b', meta: { type: 'number' } },
{ id: 'c', name: 'c', meta: { type: 'string' } },
{ id: 'd', name: 'd', meta: { type: 'string' } },
],
rows: [
{ a: 1, b: 2, c: 'I', d: 'Row 1' },
{ a: 1, b: 5, c: 'J', d: 'Row 2' },
],
},
},
};
const dataWithFormats: LensMultiTable = {
type: 'lens_multitable',
tables: {
first: {
type: 'datatable',
columns: [
{ id: 'a', name: 'a', meta: { type: 'number' } },
{ id: 'b', name: 'b', meta: { type: 'number' } },
{ id: 'c', name: 'c', meta: { type: 'string' } },
{ id: 'd', name: 'd', meta: { type: 'string', params: { id: 'custom' } } },
],
rows: [
{ a: 1, b: 2, c: 'I', d: 'Row 1' },
{ a: 1, b: 5, c: 'J', d: 'Row 2' },
],
},
},
};
const getRenderedComponent = (data: LensMultiTable, args: XYArgs) => {
return shallow(<XYChart {...defaultProps} data={data} args={args} />);
};
beforeEach(() => {
convertSpy = jest.fn((x) => x);
getFormatSpy = jest.fn();
getFormatSpy.mockReturnValue({ convert: convertSpy });
defaultProps = {
formatFactory: getFormatSpy,
timeZone: 'UTC',
renderMode: 'display',
chartsThemeService,
paletteService,
minInterval: 50,
onClickValue,
onSelectRange,
syncColors: false,
};
});
test('it renders line', () => {
const { data, args } = sampleArgs();
const component = shallow(
<XYChart
{...defaultProps}
data={data}
args={{ ...args, layers: [{ ...args.layers[0], seriesType: 'line' }] }}
/>
);
expect(component).toMatchSnapshot();
expect(component.find(LineSeries)).toHaveLength(2);
expect(component.find(LineSeries).at(0).prop('yAccessors')).toEqual(['a']);
expect(component.find(LineSeries).at(1).prop('yAccessors')).toEqual(['b']);
});
describe('date range', () => {
const timeSampleLayer: LayerArgs = {
layerId: 'first',
seriesType: 'line',
xAccessor: 'c',
accessors: ['a', 'b'],
splitAccessor: 'd',
columnToLabel: '{"a": "Label A", "b": "Label B", "d": "Label D"}',
xScaleType: 'time',
yScaleType: 'linear',
isHistogram: false,
palette: mockPaletteOutput,
};
const multiLayerArgs = createArgsWithLayers([
timeSampleLayer,
{
...timeSampleLayer,
layerId: 'second',
seriesType: 'bar',
xScaleType: 'time',
},
]);
test('it uses the full date range', () => {
const { data, args } = sampleArgs();
const component = shallow(
<XYChart
{...defaultProps}
data={{
...data,
dateRange: {
fromDate: new Date('2019-01-02T05:00:00.000Z'),
toDate: new Date('2019-01-03T05:00:00.000Z'),
},
}}
args={{
...args,
layers: [{ ...args.layers[0], seriesType: 'line', xScaleType: 'time' }],
}}
minInterval={undefined}
/>
);
expect(component.find(Settings).prop('xDomain')).toMatchInlineSnapshot(`
Object {
"max": 1546491600000,
"min": 1546405200000,
"minInterval": undefined,
}
`);
});
test('it uses passed in minInterval', () => {
const data: LensMultiTable = {
type: 'lens_multitable',
tables: {
first: createSampleDatatableWithRows([{ a: 1, b: 2, c: 'I', d: 'Foo' }]),
second: createSampleDatatableWithRows([]),
},
};
const component = shallow(
<XYChart
{...defaultProps}
data={{
...data,
dateRange: {
fromDate: new Date('2019-01-02T05:00:00.000Z'),
toDate: new Date('2019-01-03T05:00:00.000Z'),
},
}}
args={multiLayerArgs}
/>
);
// real auto interval is 30mins = 1800000
expect(component.find(Settings).prop('xDomain')).toMatchInlineSnapshot(`
Object {
"max": 1546491600000,
"min": 1546405200000,
"minInterval": 50,
}
`);
});
});
test('it does not use date range if the x is not a time scale', () => {
const { data, args } = sampleArgs();
const component = shallow(
<XYChart
{...defaultProps}
data={{
...data,
dateRange: {
fromDate: new Date('2019-01-02T05:00:00.000Z'),
toDate: new Date('2019-01-03T05:00:00.000Z'),
},
}}
args={{
...args,
layers: [{ ...args.layers[0], seriesType: 'line', xScaleType: 'linear' }],
}}
/>
);
expect(component.find(Settings).prop('xDomain')).toBeUndefined();
});
test('it renders bar', () => {
const { data, args } = sampleArgs();
const component = shallow(
<XYChart
{...defaultProps}
data={data}
args={{ ...args, layers: [{ ...args.layers[0], seriesType: 'bar' }] }}
/>
);
expect(component).toMatchSnapshot();
expect(component.find(BarSeries)).toHaveLength(2);
expect(component.find(BarSeries).at(0).prop('yAccessors')).toEqual(['a']);
expect(component.find(BarSeries).at(1).prop('yAccessors')).toEqual(['b']);
});
test('it renders area', () => {
const { data, args } = sampleArgs();
const component = shallow(
<XYChart
{...defaultProps}
data={data}
args={{ ...args, layers: [{ ...args.layers[0], seriesType: 'area' }] }}
/>
);
expect(component).toMatchSnapshot();
expect(component.find(AreaSeries)).toHaveLength(2);
expect(component.find(AreaSeries).at(0).prop('yAccessors')).toEqual(['a']);
expect(component.find(AreaSeries).at(1).prop('yAccessors')).toEqual(['b']);
});
test('it renders horizontal bar', () => {
const { data, args } = sampleArgs();
const component = shallow(
<XYChart
{...defaultProps}
data={data}
args={{ ...args, layers: [{ ...args.layers[0], seriesType: 'bar_horizontal' }] }}
/>
);
expect(component).toMatchSnapshot();
expect(component.find(BarSeries)).toHaveLength(2);
expect(component.find(BarSeries).at(0).prop('yAccessors')).toEqual(['a']);
expect(component.find(BarSeries).at(1).prop('yAccessors')).toEqual(['b']);
expect(component.find(Settings).prop('rotation')).toEqual(90);
});
test('it renders regular bar empty placeholder for no results', () => {
const { data, args } = sampleArgs();
// send empty data to the chart
data.tables.first.rows = [];
const component = shallow(<XYChart {...defaultProps} data={data} args={args} />);
expect(component.find(BarSeries)).toHaveLength(0);
expect(component.find(EmptyPlaceholder).prop('icon')).toBeDefined();
});
test('onBrushEnd returns correct context data for date histogram data', () => {
const { args } = sampleArgs();
const wrapper = mountWithIntl(
<XYChart
{...defaultProps}
data={dateHistogramData}
args={{
...args,
layers: [dateHistogramLayer],
}}
/>
);
wrapper.find(Settings).first().prop('onBrushEnd')!({ x: [1585757732783, 1585758880838] });
expect(onSelectRange).toHaveBeenCalledWith({
column: 0,
table: dateHistogramData.tables.timeLayer,
range: [1585757732783, 1585758880838],
timeFieldName: 'order_date',
});
});
test('onBrushEnd returns correct context data for number histogram data', () => {
const { args } = sampleArgs();
const numberLayer: LayerArgs = {
layerId: 'numberLayer',
hide: false,
xAccessor: 'xAccessorId',
yScaleType: 'linear',
xScaleType: 'linear',
isHistogram: true,
seriesType: 'bar_stacked',
accessors: ['yAccessorId'],
palette: mockPaletteOutput,
};
const numberHistogramData: LensMultiTable = {
type: 'lens_multitable',
tables: {
numberLayer: {
type: 'datatable',
rows: [
{
xAccessorId: 5,
yAccessorId: 1,
},
{
xAccessorId: 7,
yAccessorId: 1,
},
{
xAccessorId: 8,
yAccessorId: 1,
},
{
xAccessorId: 10,
yAccessorId: 1,
},
],
columns: [
{
id: 'xAccessorId',
name: 'bytes',
meta: { type: 'number' },
},
{
id: 'yAccessorId',
name: 'Count of records',
meta: { type: 'number' },
},
],
},
},
dateRange: {
fromDate: new Date('2020-04-01T16:14:16.246Z'),
toDate: new Date('2020-04-01T17:15:41.263Z'),
},
};
const wrapper = mountWithIntl(
<XYChart
{...defaultProps}
data={numberHistogramData}
args={{
...args,
layers: [numberLayer],
}}
/>
);
wrapper.find(Settings).first().prop('onBrushEnd')!({ x: [5, 8] });
expect(onSelectRange).toHaveBeenCalledWith({
column: 0,
table: numberHistogramData.tables.numberLayer,
range: [5, 8],
timeFieldName: undefined,
});
});
test('onBrushEnd is not set on noInteractivity mode', () => {
const { args, data } = sampleArgs();
const wrapper = mountWithIntl(
<XYChart {...defaultProps} data={data} args={args} renderMode="noInteractivity" />
);
expect(wrapper.find(Settings).first().prop('onBrushEnd')).toBeUndefined();
});
test('onElementClick returns correct context data', () => {
const geometry: GeometryValue = { x: 5, y: 1, accessor: 'y1', mark: null, datum: {} };
const series = {
key: 'spec{d}yAccessor{d}splitAccessors{b-2}',
specId: 'd',
yAccessor: 'd',
splitAccessors: {},
seriesKeys: [2, 'd'],
};
const { args, data } = sampleArgs();
const wrapper = mountWithIntl(
<XYChart
{...defaultProps}
data={data}
args={{
...args,
layers: [
{
layerId: 'first',
isHistogram: true,
seriesType: 'bar_stacked',
xAccessor: 'b',
yScaleType: 'linear',
xScaleType: 'time',
splitAccessor: 'b',
accessors: ['d'],
columnToLabel: '{"a": "Label A", "b": "Label B", "d": "Label D"}',
palette: mockPaletteOutput,
},
],
}}
/>
);
wrapper.find(Settings).first().prop('onElementClick')!([
[geometry, series as XYChartSeriesIdentifier],
]);
expect(onClickValue).toHaveBeenCalledWith({
data: [
{
column: 1,
row: 1,
table: data.tables.first,
value: 5,
},
{
column: 1,
row: 0,
table: data.tables.first,
value: 2,
},
],
});
});
test('onElementClick is not triggering event on noInteractivity mode', () => {
const { args, data } = sampleArgs();
const wrapper = mountWithIntl(
<XYChart {...defaultProps} data={data} args={args} renderMode="noInteractivity" />
);
expect(wrapper.find(Settings).first().prop('onElementClick')).toBeUndefined();
});
test('it renders stacked bar', () => {
const { data, args } = sampleArgs();
const component = shallow(
<XYChart
{...defaultProps}
data={data}
args={{ ...args, layers: [{ ...args.layers[0], seriesType: 'bar_stacked' }] }}
/>
);
expect(component).toMatchSnapshot();
expect(component.find(BarSeries)).toHaveLength(2);
expect(component.find(BarSeries).at(0).prop('stackAccessors')).toHaveLength(1);
expect(component.find(BarSeries).at(1).prop('stackAccessors')).toHaveLength(1);
});
test('it renders stacked area', () => {
const { data, args } = sampleArgs();
const component = shallow(
<XYChart
{...defaultProps}
data={data}
args={{ ...args, layers: [{ ...args.layers[0], seriesType: 'area_stacked' }] }}
/>
);
expect(component).toMatchSnapshot();
expect(component.find(AreaSeries)).toHaveLength(2);
expect(component.find(AreaSeries).at(0).prop('stackAccessors')).toHaveLength(1);
expect(component.find(AreaSeries).at(1).prop('stackAccessors')).toHaveLength(1);
});
test('it renders stacked horizontal bar', () => {
const { data, args } = sampleArgs();
const component = shallow(
<XYChart
{...defaultProps}
data={data}
args={{
...args,
layers: [{ ...args.layers[0], seriesType: 'bar_horizontal_stacked' }],
}}
/>
);
expect(component).toMatchSnapshot();
expect(component.find(BarSeries)).toHaveLength(2);
expect(component.find(BarSeries).at(0).prop('stackAccessors')).toHaveLength(1);
expect(component.find(BarSeries).at(1).prop('stackAccessors')).toHaveLength(1);
expect(component.find(Settings).prop('rotation')).toEqual(90);
});
test('it renders stacked bar empty placeholder for no results', () => {
const { data, args } = sampleArgs();
const component = shallow(
<XYChart
{...defaultProps}
data={data}
args={{
...args,
layers: [
{
...args.layers[0],
xAccessor: undefined,
splitAccessor: 'e',
seriesType: 'bar_stacked',
},
],
}}
/>
);
expect(component.find(BarSeries)).toHaveLength(0);
expect(component.find(EmptyPlaceholder).prop('icon')).toBeDefined();
});
test('it passes time zone to the series', () => {
const { data, args } = sampleArgs();
const component = shallow(
<XYChart {...defaultProps} data={data} args={args} timeZone="CEST" />
);
expect(component.find(LineSeries).at(0).prop('timeZone')).toEqual('CEST');
expect(component.find(LineSeries).at(1).prop('timeZone')).toEqual('CEST');
});
test('it applies histogram mode to the series for single series', () => {
const { data, args } = sampleArgs();
const firstLayer: LayerArgs = {
...args.layers[0],
accessors: ['b'],
seriesType: 'bar',
isHistogram: true,
};
delete firstLayer.splitAccessor;
const component = shallow(
<XYChart {...defaultProps} data={data} args={{ ...args, layers: [firstLayer] }} />
);
expect(component.find(BarSeries).at(0).prop('enableHistogramMode')).toEqual(true);
});
test('it does not apply histogram mode to more than one bar series for unstacked bar chart', () => {
const { data, args } = sampleArgs();
const firstLayer: LayerArgs = { ...args.layers[0], seriesType: 'bar', isHistogram: true };
delete firstLayer.splitAccessor;
const component = shallow(
<XYChart {...defaultProps} data={data} args={{ ...args, layers: [firstLayer] }} />
);
expect(component.find(BarSeries).at(0).prop('enableHistogramMode')).toEqual(false);
expect(component.find(BarSeries).at(1).prop('enableHistogramMode')).toEqual(false);
});
test('it applies histogram mode to more than one the series for unstacked line/area chart', () => {
const { data, args } = sampleArgs();
const firstLayer: LayerArgs = { ...args.layers[0], seriesType: 'line', isHistogram: true };
delete firstLayer.splitAccessor;
const secondLayer: LayerArgs = { ...args.layers[0], seriesType: 'line', isHistogram: true };
delete secondLayer.splitAccessor;
const component = shallow(
<XYChart
{...defaultProps}
data={data}
args={{ ...args, layers: [firstLayer, secondLayer] }}
/>
);
expect(component.find(LineSeries).at(0).prop('enableHistogramMode')).toEqual(true);
expect(component.find(LineSeries).at(1).prop('enableHistogramMode')).toEqual(true);
});
test('it applies histogram mode to the series for stacked series', () => {
const { data, args } = sampleArgs();
const component = shallow(
<XYChart
{...defaultProps}
data={data}
args={{
...args,
layers: [
{
...args.layers[0],
seriesType: 'bar_stacked',
isHistogram: true,
},
],
}}
/>
);
expect(component.find(BarSeries).at(0).prop('enableHistogramMode')).toEqual(true);
expect(component.find(BarSeries).at(1).prop('enableHistogramMode')).toEqual(true);
});
test('it does not apply histogram mode for splitted series', () => {
const { data, args } = sampleArgs();
const component = shallow(
<XYChart
{...defaultProps}
data={data}
args={{
...args,
layers: [{ ...args.layers[0], seriesType: 'bar', isHistogram: true }],
}}
/>
);
expect(component.find(BarSeries).at(0).prop('enableHistogramMode')).toEqual(false);
expect(component.find(BarSeries).at(1).prop('enableHistogramMode')).toEqual(false);
});
describe('y axes', () => {
test('single axis if possible', () => {
const args = createArgsWithLayers();
const component = getRenderedComponent(dataWithoutFormats, args);
const axes = component.find(Axis);
expect(axes).toHaveLength(2);
});
test('multiple axes because of config', () => {
const args = createArgsWithLayers();
const newArgs = {
...args,
layers: [
{
...args.layers[0],
accessors: ['a', 'b'],
yConfig: [
{
forAccessor: 'a',
axisMode: 'left',
},
{
forAccessor: 'b',
axisMode: 'right',
},
],
},
],
} as XYArgs;
const component = getRenderedComponent(dataWithoutFormats, newArgs);
const axes = component.find(Axis);
expect(axes).toHaveLength(3);
expect(component.find(LineSeries).at(0).prop('groupId')).toEqual(
axes.at(1).prop('groupId')
);
expect(component.find(LineSeries).at(1).prop('groupId')).toEqual(
axes.at(2).prop('groupId')
);
});
test('multiple axes because of incompatible formatters', () => {
const args = createArgsWithLayers();
const newArgs = {
...args,
layers: [
{
...args.layers[0],
accessors: ['c', 'd'],
},
],
} as XYArgs;
const component = getRenderedComponent(dataWithFormats, newArgs);
const axes = component.find(Axis);
expect(axes).toHaveLength(3);
expect(component.find(LineSeries).at(0).prop('groupId')).toEqual(
axes.at(1).prop('groupId')
);
expect(component.find(LineSeries).at(1).prop('groupId')).toEqual(
axes.at(2).prop('groupId')
);
});
test('single axis despite different formatters if enforced', () => {
const args = createArgsWithLayers();
const newArgs = {
...args,
layers: [
{
...args.layers[0],
accessors: ['c', 'd'],
yConfig: [
{
forAccessor: 'c',
axisMode: 'left',
},
{
forAccessor: 'd',
axisMode: 'left',
},
],
},
],
} as XYArgs;
const component = getRenderedComponent(dataWithoutFormats, newArgs);
const axes = component.find(Axis);
expect(axes).toHaveLength(2);
});
});
describe('y series coloring', () => {
test('color is applied to chart for multiple series', () => {
const args = createArgsWithLayers();
const newArgs = {
...args,
layers: [
{
...args.layers[0],
splitAccessor: undefined,
accessors: ['a', 'b'],
yConfig: [
{
forAccessor: 'a',
color: '#550000',
},
{
forAccessor: 'b',
color: '#FFFF00',
},
],
},
{
...args.layers[0],
splitAccessor: undefined,
accessors: ['c'],
yConfig: [
{
forAccessor: 'c',
color: '#FEECDF',
},
],
},
],
} as XYArgs;
const component = getRenderedComponent(dataWithoutFormats, newArgs);
expect(
(component.find(LineSeries).at(0).prop('color') as Function)!({
yAccessor: 'a',
seriesKeys: ['a'],
})
).toEqual('#550000');
expect(
(component.find(LineSeries).at(1).prop('color') as Function)!({
yAccessor: 'b',
seriesKeys: ['b'],
})
).toEqual('#FFFF00');
expect(
(component.find(LineSeries).at(2).prop('color') as Function)!({
yAccessor: 'c',
seriesKeys: ['c'],
})
).toEqual('#FEECDF');
});
test('color is not applied to chart when splitAccessor is defined or when yConfig is not configured', () => {
const args = createArgsWithLayers();
const newArgs = {
...args,
layers: [
{
...args.layers[0],
accessors: ['a'],
yConfig: [
{
forAccessor: 'a',
color: '#550000',
},
],
},
{
...args.layers[0],
splitAccessor: undefined,
accessors: ['c'],
},
],
} as XYArgs;
const component = getRenderedComponent(dataWithoutFormats, newArgs);
expect(
(component.find(LineSeries).at(0).prop('color') as Function)!({
yAccessor: 'a',
seriesKeys: ['a'],
})
).toEqual('blue');
expect(
(component.find(LineSeries).at(1).prop('color') as Function)!({
yAccessor: 'c',
seriesKeys: ['c'],
})
).toEqual('blue');
});
});
describe('provides correct series naming', () => {
const nameFnArgs = {
seriesKeys: [],
key: '',
specId: 'a',
yAccessor: '',
splitAccessors: new Map(),
};
test('simplest xy chart without human-readable name', () => {
const args = createArgsWithLayers();
const newArgs = {
...args,
layers: [
{
...args.layers[0],
accessors: ['a'],
splitAccessor: undefined,
columnToLabel: '',
},
],
};
const component = getRenderedComponent(dataWithoutFormats, newArgs);
const nameFn = component.find(LineSeries).prop('name') as SeriesNameFn;
// In this case, the ID is used as the name. This shouldn't happen in practice
expect(nameFn({ ...nameFnArgs, seriesKeys: ['a'] }, false)).toEqual('');
expect(nameFn({ ...nameFnArgs, seriesKeys: ['nonsense'] }, false)).toEqual('');
});
test('simplest xy chart with empty name', () => {
const args = createArgsWithLayers();
const newArgs = {
...args,
layers: [
{
...args.layers[0],
accessors: ['a'],
splitAccessor: undefined,
columnToLabel: '{"a":""}',
},
],
};
const component = getRenderedComponent(dataWithoutFormats, newArgs);
const nameFn = component.find(LineSeries).prop('name') as SeriesNameFn;
// In this case, the ID is used as the name. This shouldn't happen in practice
expect(nameFn({ ...nameFnArgs, seriesKeys: ['a'] }, false)).toEqual('');
expect(nameFn({ ...nameFnArgs, seriesKeys: ['nonsense'] }, false)).toEqual('');
});
test('simplest xy chart with human-readable name', () => {
const args = createArgsWithLayers();
const newArgs = {
...args,
layers: [
{
...args.layers[0],
accessors: ['a'],
splitAccessor: undefined,
columnToLabel: '{"a":"Column A"}',
},
],
};
const component = getRenderedComponent(dataWithoutFormats, newArgs);
const nameFn = component.find(LineSeries).prop('name') as SeriesNameFn;
expect(nameFn({ ...nameFnArgs, seriesKeys: ['a'] }, false)).toEqual('Column A');
});
test('multiple y accessors', () => {
const args = createArgsWithLayers();
const newArgs = {
...args,
layers: [
{
...args.layers[0],
accessors: ['a', 'b'],
splitAccessor: undefined,
columnToLabel: '{"a": "Label A"}',
},
],
};
const component = getRenderedComponent(dataWithoutFormats, newArgs);
const nameFn1 = component.find(LineSeries).at(0).prop('name') as SeriesNameFn;
const nameFn2 = component.find(LineSeries).at(1).prop('name') as SeriesNameFn;
// This accessor has a human-readable name
expect(nameFn1({ ...nameFnArgs, seriesKeys: ['a'] }, false)).toEqual('Label A');
// This accessor does not
expect(nameFn2({ ...nameFnArgs, seriesKeys: ['b'] }, false)).toEqual('');
expect(nameFn1({ ...nameFnArgs, seriesKeys: ['nonsense'] }, false)).toEqual('');
});
test('split series without formatting and single y accessor', () => {
const args = createArgsWithLayers();
const newArgs = {
...args,
layers: [
{
...args.layers[0],
accessors: ['a'],
splitAccessor: 'd',
columnToLabel: '{"a": "Label A"}',
},
],
};
const component = getRenderedComponent(dataWithoutFormats, newArgs);
const nameFn = component.find(LineSeries).prop('name') as SeriesNameFn;
expect(nameFn({ ...nameFnArgs, seriesKeys: ['split1', 'a'] }, false)).toEqual('split1');
});
test('split series with formatting and single y accessor', () => {
const args = createArgsWithLayers();
const newArgs = {
...args,
layers: [
{
...args.layers[0],
accessors: ['a'],
splitAccessor: 'd',
columnToLabel: '{"a": "Label A"}',
},
],
};
const component = getRenderedComponent(dataWithFormats, newArgs);
const nameFn = component.find(LineSeries).prop('name') as SeriesNameFn;
convertSpy.mockReturnValueOnce('formatted');
expect(nameFn({ ...nameFnArgs, seriesKeys: ['split1', 'a'] }, false)).toEqual('formatted');
expect(getFormatSpy).toHaveBeenCalledWith({ id: 'custom' });
});
test('split series without formatting with multiple y accessors', () => {
const args = createArgsWithLayers();
const newArgs = {
...args,
layers: [
{
...args.layers[0],
accessors: ['a', 'b'],
splitAccessor: 'd',
columnToLabel: '{"a": "Label A","b": "Label B"}',
},
],
};
const component = getRenderedComponent(dataWithoutFormats, newArgs);
const nameFn1 = component.find(LineSeries).at(0).prop('name') as SeriesNameFn;
const nameFn2 = component.find(LineSeries).at(0).prop('name') as SeriesNameFn;
expect(nameFn1({ ...nameFnArgs, seriesKeys: ['split1', 'a'] }, false)).toEqual(
'split1 - Label A'
);
expect(nameFn2({ ...nameFnArgs, seriesKeys: ['split1', 'b'] }, false)).toEqual(
'split1 - Label B'
);
});
test('split series with formatting with multiple y accessors', () => {
const args = createArgsWithLayers();
const newArgs = {
...args,
layers: [
{
...args.layers[0],
accessors: ['a', 'b'],
splitAccessor: 'd',
columnToLabel: '{"a": "Label A","b": "Label B"}',
},
],
};
const component = getRenderedComponent(dataWithFormats, newArgs);
const nameFn1 = component.find(LineSeries).at(0).prop('name') as SeriesNameFn;
const nameFn2 = component.find(LineSeries).at(1).prop('name') as SeriesNameFn;
convertSpy.mockReturnValueOnce('formatted1').mockReturnValueOnce('formatted2');
expect(nameFn1({ ...nameFnArgs, seriesKeys: ['split1', 'a'] }, false)).toEqual(
'formatted1 - Label A'
);
expect(nameFn2({ ...nameFnArgs, seriesKeys: ['split1', 'b'] }, false)).toEqual(
'formatted2 - Label B'
);
});
});
test('it set the scale of the x axis according to the args prop', () => {
const { data, args } = sampleArgs();
const component = shallow(
<XYChart
{...defaultProps}
data={data}
args={{ ...args, layers: [{ ...args.layers[0], xScaleType: 'ordinal' }] }}
/>
);
expect(component.find(LineSeries).at(0).prop('xScaleType')).toEqual(ScaleType.Ordinal);
expect(component.find(LineSeries).at(1).prop('xScaleType')).toEqual(ScaleType.Ordinal);
});
test('it set the scale of the y axis according to the args prop', () => {
const { data, args } = sampleArgs();
const component = shallow(
<XYChart
{...defaultProps}
data={data}
args={{ ...args, layers: [{ ...args.layers[0], yScaleType: 'sqrt' }] }}
/>
);
expect(component.find(LineSeries).at(0).prop('yScaleType')).toEqual(ScaleType.Sqrt);
expect(component.find(LineSeries).at(1).prop('yScaleType')).toEqual(ScaleType.Sqrt);
});
test('it gets the formatter for the x axis', () => {
const { data, args } = sampleArgs();
shallow(<XYChart {...defaultProps} data={{ ...data }} args={{ ...args }} />);
expect(getFormatSpy).toHaveBeenCalledWith({ id: 'string' });
});
test('it gets the formatter for the y axis if there is only one accessor', () => {
const { data, args } = sampleArgs();
shallow(
<XYChart
{...defaultProps}
data={{ ...data }}
args={{ ...args, layers: [{ ...args.layers[0], accessors: ['a'] }] }}
/>
);
expect(getFormatSpy).toHaveBeenCalledWith({
id: 'number',
params: { pattern: '0,0.000' },
});
});
test('it should pass the formatter function to the axis', () => {
const { data, args } = sampleArgs();
const instance = shallow(<XYChart {...defaultProps} data={{ ...data }} args={{ ...args }} />);
const tickFormatter = instance.find(Axis).first().prop('tickFormat');
if (!tickFormatter) {
throw new Error('tickFormatter prop not found');
}
tickFormatter('I');
expect(convertSpy).toHaveBeenCalledWith('I');
});
test('it should set the tickLabel visibility on the x axis if the tick labels is hidden', () => {
const { data, args } = sampleArgs();
args.tickLabelsVisibilitySettings = {
x: false,
yLeft: true,
yRight: true,
type: 'lens_xy_tickLabelsConfig',
};
const instance = shallow(<XYChart {...defaultProps} data={{ ...data }} args={{ ...args }} />);
const axisStyle = instance.find(Axis).first().prop('style');
expect(axisStyle).toMatchObject({
tickLabel: {
visible: false,
},
});
});
test('it should set the tickLabel visibility on the y axis if the tick labels is hidden', () => {
const { data, args } = sampleArgs();
args.tickLabelsVisibilitySettings = {
x: true,
yLeft: false,
yRight: false,
type: 'lens_xy_tickLabelsConfig',
};
const instance = shallow(<XYChart {...defaultProps} data={{ ...data }} args={{ ...args }} />);
const axisStyle = instance.find(Axis).at(1).prop('style');
expect(axisStyle).toMatchObject({
tickLabel: {
visible: false,
},
});
});
test('it should set the tickLabel visibility on the x axis if the tick labels is shown', () => {
const { data, args } = sampleArgs();
args.tickLabelsVisibilitySettings = {
x: true,
yLeft: true,
yRight: true,
type: 'lens_xy_tickLabelsConfig',
};
const instance = shallow(<XYChart {...defaultProps} data={{ ...data }} args={{ ...args }} />);
const axisStyle = instance.find(Axis).first().prop('style');
expect(axisStyle).toMatchObject({
tickLabel: {
visible: true,
},
});
});
test('it should set the tickLabel visibility on the y axis if the tick labels is shown', () => {
const { data, args } = sampleArgs();
args.tickLabelsVisibilitySettings = {
x: false,
yLeft: true,
yRight: true,
type: 'lens_xy_tickLabelsConfig',
};
const instance = shallow(<XYChart {...defaultProps} data={{ ...data }} args={{ ...args }} />);
const axisStyle = instance.find(Axis).at(1).prop('style');
expect(axisStyle).toMatchObject({
tickLabel: {
visible: true,
},
});
});
test('it should remove invalid rows', () => {
const data: LensMultiTable = {
type: 'lens_multitable',
tables: {
first: {
type: 'datatable',
columns: [
{ id: 'a', name: 'a', meta: { type: 'number' } },
{ id: 'b', name: 'b', meta: { type: 'number' } },
{ id: 'c', name: 'c', meta: { type: 'string' } },
],
rows: [
{ a: undefined, b: 2, c: 'I', d: 'Row 1' },
{ a: 1, b: 5, c: 'J', d: 'Row 2' },
],
},
second: {
type: 'datatable',
columns: [
{ id: 'a', name: 'a', meta: { type: 'number' } },
{ id: 'b', name: 'b', meta: { type: 'number' } },
{ id: 'c', name: 'c', meta: { type: 'string' } },
],
rows: [
{ a: undefined, b: undefined, c: undefined },
{ a: undefined, b: undefined, c: undefined },
],
},
},
};
const args: XYArgs = {
xTitle: '',
yTitle: '',
yRightTitle: '',
legend: { type: 'lens_xy_legendConfig', isVisible: false, position: Position.Top },
valueLabels: 'hide',
tickLabelsVisibilitySettings: {
type: 'lens_xy_tickLabelsConfig',
x: true,
yLeft: true,
yRight: true,
},
gridlinesVisibilitySettings: {
type: 'lens_xy_gridlinesConfig',
x: true,
yLeft: false,
yRight: false,
},
layers: [
{
layerId: 'first',
seriesType: 'line',
xAccessor: 'a',
accessors: ['c'],
splitAccessor: 'b',
columnToLabel: '',
xScaleType: 'ordinal',
yScaleType: 'linear',
isHistogram: false,
palette: mockPaletteOutput,
},
{
layerId: 'second',
seriesType: 'line',
xAccessor: 'a',
accessors: ['c'],
splitAccessor: 'b',
columnToLabel: '',
xScaleType: 'ordinal',
yScaleType: 'linear',
isHistogram: false,
palette: mockPaletteOutput,
},
],
};
const component = shallow(<XYChart {...defaultProps} data={data} args={args} />);
const series = component.find(LineSeries);
// Only one series should be rendered, even though 2 are configured
// This one series should only have one row, even though 2 are sent
expect(series.prop('data')).toEqual([{ a: 1, b: 5, c: 'J', d: 'Row 2' }]);
});
test('it should not remove rows with falsy but non-undefined values', () => {
const data: LensMultiTable = {
type: 'lens_multitable',
tables: {
first: {
type: 'datatable',
columns: [
{ id: 'a', name: 'a', meta: { type: 'number' } },
{ id: 'b', name: 'b', meta: { type: 'number' } },
{ id: 'c', name: 'c', meta: { type: 'number' } },
],
rows: [
{ a: 0, b: 2, c: 5 },
{ a: 1, b: 0, c: 7 },
],
},
},
};
const args: XYArgs = {
xTitle: '',
yTitle: '',
yRightTitle: '',
legend: { type: 'lens_xy_legendConfig', isVisible: false, position: Position.Top },
valueLabels: 'hide',
tickLabelsVisibilitySettings: {
type: 'lens_xy_tickLabelsConfig',
x: true,
yLeft: false,
yRight: false,
},
gridlinesVisibilitySettings: {
type: 'lens_xy_gridlinesConfig',
x: true,
yLeft: false,
yRight: false,
},
layers: [
{
layerId: 'first',
seriesType: 'line',
xAccessor: 'a',
accessors: ['c'],
splitAccessor: 'b',
columnToLabel: '',
xScaleType: 'ordinal',
yScaleType: 'linear',
isHistogram: false,
palette: mockPaletteOutput,
},
],
};
const component = shallow(<XYChart {...defaultProps} data={data} args={args} />);
const series = component.find(LineSeries);
expect(series.prop('data')).toEqual([
{ a: 0, b: 2, c: 5 },
{ a: 1, b: 0, c: 7 },
]);
});
test('it should show legend for split series, even with one row', () => {
const data: LensMultiTable = {
type: 'lens_multitable',
tables: {
first: {
type: 'datatable',
columns: [
{ id: 'a', name: 'a', meta: { type: 'number' } },
{ id: 'b', name: 'b', meta: { type: 'number' } },
{ id: 'c', name: 'c', meta: { type: 'string' } },
],
rows: [{ a: 1, b: 5, c: 'J' }],
},
},
};
const args: XYArgs = {
xTitle: '',
yTitle: '',
yRightTitle: '',
legend: { type: 'lens_xy_legendConfig', isVisible: true, position: Position.Top },
valueLabels: 'hide',
tickLabelsVisibilitySettings: {
type: 'lens_xy_tickLabelsConfig',
x: true,
yLeft: false,
yRight: false,
},
gridlinesVisibilitySettings: {
type: 'lens_xy_gridlinesConfig',
x: true,
yLeft: false,
yRight: false,
},
layers: [
{
layerId: 'first',
seriesType: 'line',
xAccessor: 'a',
accessors: ['c'],
splitAccessor: 'b',
columnToLabel: '',
xScaleType: 'ordinal',
yScaleType: 'linear',
isHistogram: false,
palette: mockPaletteOutput,
},
],
};
const component = shallow(<XYChart {...defaultProps} data={data} args={args} />);
expect(component.find(Settings).prop('showLegend')).toEqual(true);
});
test('it should always show legend if showSingleSeries is set', () => {
const { data, args } = sampleArgs();
const component = shallow(
<XYChart
{...defaultProps}
data={{ ...data }}
args={{
...args,
layers: [{ ...args.layers[0], accessors: ['a'], splitAccessor: undefined }],
legend: { ...args.legend, isVisible: true, showSingleSeries: true },
}}
/>
);
expect(component.find(Settings).prop('showLegend')).toEqual(true);
});
test('it not show legend if isVisible is set to false', () => {
const { data, args } = sampleArgs();
const component = shallow(
<XYChart
{...defaultProps}
data={{ ...data }}
args={{
...args,
legend: { ...args.legend, isVisible: false },
}}
/>
);
expect(component.find(Settings).prop('showLegend')).toEqual(false);
});
test('it should show legend on right side', () => {
const { data, args } = sampleArgs();
const component = shallow(
<XYChart
{...defaultProps}
data={{ ...data }}
args={{
...args,
legend: { ...args.legend, position: 'top' },
}}
/>
);
expect(component.find(Settings).prop('legendPosition')).toEqual('top');
});
test('it should apply the fitting function to all non-bar series', () => {
const data: LensMultiTable = {
type: 'lens_multitable',
tables: {
first: createSampleDatatableWithRows([
{ a: 1, b: 2, c: 'I', d: 'Foo' },
{ a: 1, b: 5, c: 'J', d: 'Bar' },
]),
},
};
const args: XYArgs = createArgsWithLayers([
{ ...sampleLayer, accessors: ['a'] },
{ ...sampleLayer, seriesType: 'bar', accessors: ['a'] },
{ ...sampleLayer, seriesType: 'area', accessors: ['a'] },
{ ...sampleLayer, seriesType: 'area_stacked', accessors: ['a'] },
]);
const component = shallow(
<XYChart
{...defaultProps}
data={{ ...data }}
args={{ ...args, fittingFunction: 'Carry' }}
/>
);
expect(component.find(LineSeries).prop('fit')).toEqual({ type: Fit.Carry });
expect(component.find(BarSeries).prop('fit')).toEqual(undefined);
expect(component.find(AreaSeries).at(0).prop('fit')).toEqual({ type: Fit.Carry });
expect(component.find(AreaSeries).at(0).prop('stackAccessors')).toEqual([]);
expect(component.find(AreaSeries).at(1).prop('fit')).toEqual({ type: Fit.Carry });
expect(component.find(AreaSeries).at(1).prop('stackAccessors')).toEqual(['c']);
});
test('it should apply None fitting function if not specified', () => {
const { data, args } = sampleArgs();
args.layers[0].accessors = ['a'];
const component = shallow(
<XYChart {...defaultProps} data={{ ...data }} args={{ ...args }} />
);
expect(component.find(LineSeries).prop('fit')).toEqual({ type: Fit.None });
});
test('it should apply the xTitle if is specified', () => {
const { data, args } = sampleArgs();
args.xTitle = 'My custom x-axis title';
const component = shallow(
<XYChart {...defaultProps} data={{ ...data }} args={{ ...args }} />
);
expect(component.find(Axis).at(0).prop('title')).toEqual('My custom x-axis title');
});
test('it should hide the X axis title if the corresponding switch is off', () => {
const { data, args } = sampleArgs();
args.axisTitlesVisibilitySettings = {
x: false,
yLeft: true,
yRight: true,
type: 'lens_xy_axisTitlesVisibilityConfig',
};
const component = shallow(
<XYChart {...defaultProps} data={{ ...data }} args={{ ...args }} />
);
const axisStyle = component.find(Axis).first().prop('style');
expect(axisStyle).toMatchObject({
axisTitle: {
visible: false,
},
});
});
test('it should show the X axis gridlines if the setting is on', () => {
const { data, args } = sampleArgs();
args.gridlinesVisibilitySettings = {
x: true,
yLeft: false,
yRight: false,
type: 'lens_xy_gridlinesConfig',
};
const component = shallow(
<XYChart {...defaultProps} data={{ ...data }} args={{ ...args }} />
);
expect(component.find(Axis).at(0).prop('gridLine')).toMatchObject({
visible: true,
});
});
});
describe('calculateMinInterval', () => {
let xyProps: XYChartProps;
beforeEach(() => {
xyProps = sampleArgs();
xyProps.args.layers[0].xScaleType = 'time';
});
it('should use first valid layer and determine interval', async () => {
const result = await calculateMinInterval(
xyProps,
jest.fn().mockResolvedValue({ interval: '5m' })
);
expect(result).toEqual(5 * 60 * 1000);
});
it('should return undefined if data table is empty', async () => {
xyProps.data.tables.first.rows = [];
const result = await calculateMinInterval(
xyProps,
jest.fn().mockResolvedValue({ interval: '5m' })
);
expect(result).toEqual(undefined);
});
it('should return undefined if interval can not be checked', async () => {
const result = await calculateMinInterval(xyProps, jest.fn().mockResolvedValue(undefined));
expect(result).toEqual(undefined);
});
it('should return undefined if date column is not found', async () => {
xyProps.data.tables.first.columns.splice(2, 1);
const result = await calculateMinInterval(xyProps, jest.fn().mockResolvedValue(undefined));
expect(result).toEqual(undefined);
});
it('should return undefined if x axis is not a date', async () => {
xyProps.args.layers[0].xScaleType = 'ordinal';
xyProps.data.tables.first.columns.splice(2, 1);
const result = await calculateMinInterval(xyProps, jest.fn().mockResolvedValue(undefined));
expect(result).toEqual(undefined);
});
});
});