Files
superset2/superset-frontend/plugins/plugin-chart-echarts/test/utils/series.test.ts
Maxime Beauchemin bce6ca1ae0 refactor: eliminate all static theme dependencies and enable true dynamic theming
This comprehensive architectural transformation removes all static theme imports
(supersetTheme, themeObject) across the entire codebase, replacing them with
proper dynamic theme access patterns that support real-time theme switching.

## What Changed

**Static Exports Eliminated:**
- Removed `supersetTheme` and `themeObject` exports from core theme module
- Eliminated static theme dependencies across 47 files
- Updated ESLint rules to reflect removed exports

**Dynamic Theme Architecture:**
- Functional components: Use `useTheme()` hook for reactive theme access
- Class components: Use `withTheme()` HOC for theme injection
- Transform functions: Access `theme` from chartProps parameter
- Test infrastructure: Use `Theme.fromConfig()` for isolated testing
- Singleton pattern: `DEFAULT_THEME` for efficient fallbacks

**Test Architecture Cleanup:**
- Removed unnecessary theme setup from 30+ test files
- Eliminated legacy `dynamicTheme` cruft from logic tests
- Simplified theme assertions to focus on behavior vs implementation details
- Maintained theme testing only where legitimately needed

**Core Infrastructure:**
- ThemeController uses dynamic theme creation instead of static imports
- ChartProps uses singleton DEFAULT_THEME for efficient fallbacks
- Theme providers only at app root and isolated contexts (tests, storybook)

## Why This Was Needed

The previous architecture had static theme imports that:
- Always returned light theme values regardless of current theme mode
- Broke dark mode compatibility in visualizations (fixed in previous commit)
- Created performance overhead with redundant theme instance creation
- Prevented real-time theme switching across components
- Led to inconsistent theme access patterns

## Benefits

-  Perfect dark mode support - no static dependencies to break theming
-  True dynamic theming - all components react to theme changes
-  Clean architecture - minimal providers, consistent patterns
-  Better performance - singleton pattern eliminates waste
-  Future-proof - ready for theme customization and user preferences
-  Developer experience - clear patterns for every context

This transformation enables the next generation of Superset theming with
complete dynamic theme support and perfect dark mode compatibility.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-16 10:57:36 -07:00

1254 lines
29 KiB
TypeScript

/**
* 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 { SortSeriesType } from '@superset-ui/chart-controls';
import {
AxisType,
DataRecord,
GenericDataType,
getNumberFormatter,
getTimeFormatter,
DEFAULT_THEME,
} from '@superset-ui/core';
import {
calculateLowerLogTick,
dedupSeries,
extractGroupbyLabel,
extractSeries,
extractShowValueIndexes,
extractTooltipKeys,
formatSeriesName,
getAxisType,
getChartPadding,
getLegendProps,
getOverMaxHiddenFormatter,
getMinAndMaxFromBounds,
sanitizeHtml,
sortAndFilterSeries,
sortRows,
getTimeCompareStackId,
} from '../../src/utils/series';
import {
EchartsTimeseriesSeriesType,
LegendOrientation,
LegendType,
} from '../../src/types';
import { defaultLegendPadding } from '../../src/defaults';
import { NULL_STRING } from '../../src/constants';
const theme = DEFAULT_THEME;
const expectedThemeProps = {
selector: ['all', 'inverse'],
selected: undefined,
selectorLabel: {
fontFamily: theme.fontFamily,
fontSize: theme.fontSizeSM,
color: theme.colorText,
borderColor: theme.colorBorder,
},
};
const sortData: DataRecord[] = [
{ my_x_axis: 'abc', x: 1, y: 0, z: 2 },
{ my_x_axis: 'foo', x: null, y: 10, z: 5 },
{ my_x_axis: null, x: 4, y: 3, z: 7 },
];
const sortDataWithNumbers: DataRecord[] = [
{
my_x_axis: 'my_axis',
'9. September': 6,
6: 1,
'11. November': 8,
8: 2,
'10. October': 1,
10: 4,
'3. March': 2,
'8. August': 6,
2: 1,
12: 3,
9: 1,
'1. January': 1,
'4. April': 12,
'2. February': 9,
5: 4,
3: 1,
11: 2,
'12. December': 4,
1: 7,
'6. June': 1,
4: 5,
7: 2,
c: 0,
'7. July': 2,
d: 0,
'5. May': 4,
a: 1,
},
];
const totalStackedValues = [3, 15, 14];
test('sortRows by name ascending', () => {
expect(
sortRows(
sortData,
totalStackedValues,
'my_x_axis',
SortSeriesType.Name,
true,
),
).toEqual([
{ row: { my_x_axis: 'abc', x: 1, y: 0, z: 2 }, totalStackedValue: 3 },
{ row: { my_x_axis: 'foo', x: null, y: 10, z: 5 }, totalStackedValue: 15 },
{ row: { my_x_axis: null, x: 4, y: 3, z: 7 }, totalStackedValue: 14 },
]);
});
test('sortRows by name descending', () => {
expect(
sortRows(
sortData,
totalStackedValues,
'my_x_axis',
SortSeriesType.Name,
false,
),
).toEqual([
{ row: { my_x_axis: null, x: 4, y: 3, z: 7 }, totalStackedValue: 14 },
{ row: { my_x_axis: 'foo', x: null, y: 10, z: 5 }, totalStackedValue: 15 },
{ row: { my_x_axis: 'abc', x: 1, y: 0, z: 2 }, totalStackedValue: 3 },
]);
});
test('sortRows by sum ascending', () => {
expect(
sortRows(
sortData,
totalStackedValues,
'my_x_axis',
SortSeriesType.Sum,
true,
),
).toEqual([
{ row: { my_x_axis: 'abc', x: 1, y: 0, z: 2 }, totalStackedValue: 3 },
{ row: { my_x_axis: null, x: 4, y: 3, z: 7 }, totalStackedValue: 14 },
{ row: { my_x_axis: 'foo', x: null, y: 10, z: 5 }, totalStackedValue: 15 },
]);
});
test('sortRows by sum descending', () => {
expect(
sortRows(
sortData,
totalStackedValues,
'my_x_axis',
SortSeriesType.Sum,
false,
),
).toEqual([
{ row: { my_x_axis: 'foo', x: null, y: 10, z: 5 }, totalStackedValue: 15 },
{ row: { my_x_axis: null, x: 4, y: 3, z: 7 }, totalStackedValue: 14 },
{ row: { my_x_axis: 'abc', x: 1, y: 0, z: 2 }, totalStackedValue: 3 },
]);
});
test('sortRows by avg ascending', () => {
expect(
sortRows(
sortData,
totalStackedValues,
'my_x_axis',
SortSeriesType.Avg,
true,
),
).toEqual([
{ row: { my_x_axis: 'abc', x: 1, y: 0, z: 2 }, totalStackedValue: 3 },
{ row: { my_x_axis: null, x: 4, y: 3, z: 7 }, totalStackedValue: 14 },
{ row: { my_x_axis: 'foo', x: null, y: 10, z: 5 }, totalStackedValue: 15 },
]);
});
test('sortRows by avg descending', () => {
expect(
sortRows(
sortData,
totalStackedValues,
'my_x_axis',
SortSeriesType.Avg,
false,
),
).toEqual([
{ row: { my_x_axis: 'foo', x: null, y: 10, z: 5 }, totalStackedValue: 15 },
{ row: { my_x_axis: null, x: 4, y: 3, z: 7 }, totalStackedValue: 14 },
{ row: { my_x_axis: 'abc', x: 1, y: 0, z: 2 }, totalStackedValue: 3 },
]);
});
test('sortRows by min ascending', () => {
expect(
sortRows(
sortData,
totalStackedValues,
'my_x_axis',
SortSeriesType.Min,
true,
),
).toEqual([
{ row: { my_x_axis: 'abc', x: 1, y: 0, z: 2 }, totalStackedValue: 3 },
{ row: { my_x_axis: null, x: 4, y: 3, z: 7 }, totalStackedValue: 14 },
{ row: { my_x_axis: 'foo', x: null, y: 10, z: 5 }, totalStackedValue: 15 },
]);
});
test('sortRows by min descending', () => {
expect(
sortRows(
sortData,
totalStackedValues,
'my_x_axis',
SortSeriesType.Min,
false,
),
).toEqual([
{ row: { my_x_axis: 'foo', x: null, y: 10, z: 5 }, totalStackedValue: 15 },
{ row: { my_x_axis: null, x: 4, y: 3, z: 7 }, totalStackedValue: 14 },
{ row: { my_x_axis: 'abc', x: 1, y: 0, z: 2 }, totalStackedValue: 3 },
]);
});
test('sortRows by max ascending', () => {
expect(
sortRows(
sortData,
totalStackedValues,
'my_x_axis',
SortSeriesType.Min,
true,
),
).toEqual([
{ row: { my_x_axis: 'abc', x: 1, y: 0, z: 2 }, totalStackedValue: 3 },
{ row: { my_x_axis: null, x: 4, y: 3, z: 7 }, totalStackedValue: 14 },
{ row: { my_x_axis: 'foo', x: null, y: 10, z: 5 }, totalStackedValue: 15 },
]);
});
test('sortRows by max descending', () => {
expect(
sortRows(
sortData,
totalStackedValues,
'my_x_axis',
SortSeriesType.Min,
false,
),
).toEqual([
{ row: { my_x_axis: 'foo', x: null, y: 10, z: 5 }, totalStackedValue: 15 },
{ row: { my_x_axis: null, x: 4, y: 3, z: 7 }, totalStackedValue: 14 },
{ row: { my_x_axis: 'abc', x: 1, y: 0, z: 2 }, totalStackedValue: 3 },
]);
});
test('sortAndFilterSeries by min ascending', () => {
expect(
sortAndFilterSeries(sortData, 'my_x_axis', [], SortSeriesType.Min, true),
).toEqual(['y', 'x', 'z']);
});
test('sortAndFilterSeries by min descending', () => {
expect(
sortAndFilterSeries(sortData, 'my_x_axis', [], SortSeriesType.Min, false),
).toEqual(['z', 'x', 'y']);
});
test('sortAndFilterSeries by max ascending', () => {
expect(
sortAndFilterSeries(sortData, 'my_x_axis', [], SortSeriesType.Max, true),
).toEqual(['x', 'z', 'y']);
});
test('sortAndFilterSeries by max descending', () => {
expect(
sortAndFilterSeries(sortData, 'my_x_axis', [], SortSeriesType.Max, false),
).toEqual(['y', 'z', 'x']);
});
test('sortAndFilterSeries by avg ascending', () => {
expect(
sortAndFilterSeries(sortData, 'my_x_axis', [], SortSeriesType.Avg, true),
).toEqual(['x', 'y', 'z']);
});
test('sortAndFilterSeries by avg descending', () => {
expect(
sortAndFilterSeries(sortData, 'my_x_axis', [], SortSeriesType.Avg, false),
).toEqual(['z', 'y', 'x']);
});
test('sortAndFilterSeries by sum ascending', () => {
expect(
sortAndFilterSeries(sortData, 'my_x_axis', [], SortSeriesType.Sum, true),
).toEqual(['x', 'y', 'z']);
});
test('sortAndFilterSeries by sum descending', () => {
expect(
sortAndFilterSeries(sortData, 'my_x_axis', [], SortSeriesType.Sum, false),
).toEqual(['z', 'y', 'x']);
});
test('sortAndFilterSeries by name ascending', () => {
expect(
sortAndFilterSeries(sortData, 'my_x_axis', [], SortSeriesType.Name, true),
).toEqual(['x', 'y', 'z']);
});
test('sortAndFilterSeries by name descending', () => {
expect(
sortAndFilterSeries(sortData, 'my_x_axis', [], SortSeriesType.Name, false),
).toEqual(['z', 'y', 'x']);
});
test('sortAndFilterSeries by name with numbers asc', () => {
expect(
sortAndFilterSeries(
sortDataWithNumbers,
'my_x_axis',
[],
SortSeriesType.Name,
true,
),
).toEqual([
'1',
'1. January',
'2',
'2. February',
'3',
'3. March',
'4',
'4. April',
'5',
'5. May',
'6',
'6. June',
'7',
'7. July',
'8',
'8. August',
'9',
'9. September',
'10',
'10. October',
'11',
'11. November',
'12',
'12. December',
'a',
'c',
'd',
]);
});
test('sortAndFilterSeries by name with numbers desc', () => {
expect(
sortAndFilterSeries(
sortDataWithNumbers,
'my_x_axis',
[],
SortSeriesType.Name,
false,
),
).toEqual([
'd',
'c',
'a',
'12. December',
'12',
'11. November',
'11',
'10. October',
'10',
'9. September',
'9',
'8. August',
'8',
'7. July',
'7',
'6. June',
'6',
'5. May',
'5',
'4. April',
'4',
'3. March',
'3',
'2. February',
'2',
'1. January',
'1',
]);
});
describe('extractSeries', () => {
it('should generate a valid ECharts timeseries series object', () => {
const data = [
{
__timestamp: '2000-01-01',
Hulk: null,
abc: 2,
},
{
__timestamp: '2000-02-01',
Hulk: 2,
abc: 10,
},
{
__timestamp: '2000-03-01',
Hulk: 1,
abc: 5,
},
];
const totalStackedValues = [2, 12, 6];
expect(extractSeries(data, { totalStackedValues })).toEqual([
[
{
id: 'Hulk',
name: 'Hulk',
data: [
['2000-01-01', null],
['2000-02-01', 2],
['2000-03-01', 1],
],
},
{
id: 'abc',
name: 'abc',
data: [
['2000-01-01', 2],
['2000-02-01', 10],
['2000-03-01', 5],
],
},
],
totalStackedValues,
1,
]);
});
it('should remove rows that have a null x-value', () => {
const data = [
{
x: 1,
Hulk: null,
abc: 2,
},
{
x: null,
Hulk: 2,
abc: 10,
},
{
x: 2,
Hulk: 1,
abc: 5,
},
];
const totalStackedValues = [3, 12, 8];
expect(
extractSeries(data, {
totalStackedValues,
xAxis: 'x',
removeNulls: true,
}),
).toEqual([
[
{
id: 'Hulk',
name: 'Hulk',
data: [[2, 1]],
},
{
id: 'abc',
name: 'abc',
data: [
[1, 2],
[2, 5],
],
},
],
totalStackedValues,
1,
]);
});
it('should convert NULL x-values to NULL_STRING for categorical axis', () => {
const data = [
{
browser: 'Firefox',
count: 5,
},
{
browser: null,
count: 10,
},
{
browser: 'Chrome',
count: 8,
},
];
expect(
extractSeries(data, {
xAxis: 'browser',
xAxisType: AxisType.Category,
}),
).toEqual([
[
{
id: 'count',
name: 'count',
data: [
['Firefox', 5],
[NULL_STRING, 10],
['Chrome', 8],
],
},
],
[],
5,
]);
});
it('should do missing value imputation', () => {
const data = [
{
__timestamp: '2000-01-01',
abc: null,
},
{
__timestamp: '2000-02-01',
abc: null,
},
{
__timestamp: '2000-03-01',
abc: 1,
},
{
__timestamp: '2000-04-01',
abc: null,
},
{
__timestamp: '2000-05-01',
abc: null,
},
{
__timestamp: '2000-06-01',
abc: null,
},
{
__timestamp: '2000-07-01',
abc: 2,
},
{
__timestamp: '2000-08-01',
abc: 3,
},
{
__timestamp: '2000-09-01',
abc: null,
},
{
__timestamp: '2000-10-01',
abc: null,
},
];
const totalStackedValues = [0, 0, 1, 0, 0, 0, 2, 3, 0, 0];
expect(
extractSeries(data, { totalStackedValues, fillNeighborValue: 0 }),
).toEqual([
[
{
id: 'abc',
name: 'abc',
data: [
['2000-01-01', null],
['2000-02-01', 0],
['2000-03-01', 1],
['2000-04-01', 0],
['2000-05-01', null],
['2000-06-01', 0],
['2000-07-01', 2],
['2000-08-01', 3],
['2000-09-01', 0],
['2000-10-01', null],
],
},
],
totalStackedValues,
1,
]);
});
});
describe('extractGroupbyLabel', () => {
it('should join together multiple groupby labels', () => {
expect(
extractGroupbyLabel({
datum: { a: 'abc', b: 'qwerty' },
groupby: ['a', 'b'],
}),
).toEqual('abc, qwerty');
});
it('should handle a single groupby', () => {
expect(
extractGroupbyLabel({ datum: { xyz: 'qqq' }, groupby: ['xyz'] }),
).toEqual('qqq');
});
it('should handle mixed types', () => {
expect(
extractGroupbyLabel({
datum: { strcol: 'abc', intcol: 123, floatcol: 0.123, boolcol: true },
groupby: ['strcol', 'intcol', 'floatcol', 'boolcol'],
}),
).toEqual('abc, 123, 0.123, true');
});
it('should handle null and undefined groupby', () => {
expect(
extractGroupbyLabel({
datum: { strcol: 'abc', intcol: 123, floatcol: 0.123, boolcol: true },
groupby: null,
}),
).toEqual('');
expect(extractGroupbyLabel({})).toEqual('');
});
});
describe('extractShowValueIndexes', () => {
it('should return the latest index for stack', () => {
expect(
extractShowValueIndexes(
[
{
id: 'abc',
name: 'abc',
data: [
['2000-01-01', null],
['2000-02-01', 0],
['2000-03-01', 1],
['2000-04-01', 0],
['2000-05-01', null],
['2000-06-01', 0],
['2000-07-01', 2],
['2000-08-01', 3],
['2000-09-01', null],
['2000-10-01', null],
],
},
{
id: 'def',
name: 'def',
data: [
['2000-01-01', null],
['2000-02-01', 0],
['2000-03-01', null],
['2000-04-01', 0],
['2000-05-01', null],
['2000-06-01', 0],
['2000-07-01', 2],
['2000-08-01', 3],
['2000-09-01', null],
['2000-10-01', 0],
],
},
{
id: 'def',
name: 'def',
data: [
['2000-01-01', null],
['2000-02-01', null],
['2000-03-01', null],
['2000-04-01', null],
['2000-05-01', null],
['2000-06-01', 3],
['2000-07-01', null],
['2000-08-01', null],
['2000-09-01', null],
['2000-10-01', null],
],
},
],
{ stack: true, onlyTotal: false, isHorizontal: false },
),
).toEqual([undefined, 1, 0, 1, undefined, 2, 1, 1, undefined, 1]);
});
it('should handle the negative numbers for total only', () => {
expect(
extractShowValueIndexes(
[
{
id: 'abc',
name: 'abc',
data: [
['2000-01-01', null],
['2000-02-01', 0],
['2000-03-01', -1],
['2000-04-01', 0],
['2000-05-01', null],
['2000-06-01', 0],
['2000-07-01', -2],
['2000-08-01', -3],
['2000-09-01', null],
['2000-10-01', null],
],
},
{
id: 'def',
name: 'def',
data: [
['2000-01-01', null],
['2000-02-01', 0],
['2000-03-01', null],
['2000-04-01', 0],
['2000-05-01', null],
['2000-06-01', 0],
['2000-07-01', 2],
['2000-08-01', -3],
['2000-09-01', null],
['2000-10-01', 0],
],
},
{
id: 'def',
name: 'def',
data: [
['2000-01-01', null],
['2000-02-01', 0],
['2000-03-01', null],
['2000-04-01', 1],
['2000-05-01', null],
['2000-06-01', 0],
['2000-07-01', -2],
['2000-08-01', 3],
['2000-09-01', null],
['2000-10-01', 0],
],
},
],
{ stack: true, onlyTotal: true, isHorizontal: false },
),
).toEqual([undefined, 1, 0, 2, undefined, 1, 1, 2, undefined, 1]);
});
});
describe('formatSeriesName', () => {
const numberFormatter = getNumberFormatter();
const timeFormatter = getTimeFormatter();
it('should handle missing values properly', () => {
expect(formatSeriesName(undefined)).toEqual('<NULL>');
expect(formatSeriesName(null)).toEqual('<NULL>');
});
it('should handle string values properly', () => {
expect(formatSeriesName('abc XYZ!')).toEqual('abc XYZ!');
});
it('should handle boolean values properly', () => {
expect(formatSeriesName(true)).toEqual('true');
});
it('should use default formatting for numeric values without formatter', () => {
expect(formatSeriesName(12345678.9)).toEqual('12345678.9');
});
it('should use numberFormatter for numeric values when formatter is provided', () => {
expect(formatSeriesName(12345678.9, { numberFormatter })).toEqual('12.3M');
});
it('should use default formatting for date values without formatter', () => {
expect(formatSeriesName(new Date('2020-09-11'))).toEqual(
'2020-09-11T00:00:00.000Z',
);
});
it('should use timeFormatter for date values when formatter is provided', () => {
expect(formatSeriesName(new Date('2020-09-11'), { timeFormatter })).toEqual(
'2020-09-11 00:00:00',
);
});
it('should normalize non-UTC string based timestamp', () => {
const annualTimeFormatter = getTimeFormatter('%Y');
expect(
formatSeriesName('1995-01-01 00:00:00.000000', {
timeFormatter: annualTimeFormatter,
coltype: GenericDataType.Temporal,
}),
).toEqual('1995');
});
});
describe('getLegendProps', () => {
it('should return the correct props for scroll type with top orientation without zoom', () => {
expect(
getLegendProps(
LegendType.Scroll,
LegendOrientation.Top,
true,
theme,
false,
),
).toEqual({
show: true,
top: 0,
right: 0,
orient: 'horizontal',
type: 'scroll',
...expectedThemeProps,
});
});
it('should return the correct props for scroll type with top orientation with zoom', () => {
expect(
getLegendProps(
LegendType.Scroll,
LegendOrientation.Top,
true,
theme,
true,
),
).toEqual({
show: true,
top: 0,
right: 55,
orient: 'horizontal',
type: 'scroll',
...expectedThemeProps,
});
});
it('should return the correct props for plain type with left orientation', () => {
expect(
getLegendProps(LegendType.Plain, LegendOrientation.Left, true, theme),
).toEqual({
show: true,
left: 0,
orient: 'vertical',
type: 'plain',
...expectedThemeProps,
});
});
it('should return the correct props for plain type with right orientation without zoom', () => {
expect(
getLegendProps(
LegendType.Plain,
LegendOrientation.Right,
false,
theme,
false,
),
).toEqual({
show: false,
right: 0,
top: 0,
orient: 'vertical',
type: 'plain',
...expectedThemeProps,
});
});
it('should return the correct props for plain type with right orientation with zoom', () => {
expect(
getLegendProps(
LegendType.Plain,
LegendOrientation.Right,
false,
theme,
true,
),
).toEqual({
show: false,
right: 0,
top: 30,
orient: 'vertical',
type: 'plain',
...expectedThemeProps,
});
});
it('should return the correct props for plain type with bottom orientation', () => {
expect(
getLegendProps(LegendType.Plain, LegendOrientation.Bottom, false, theme),
).toEqual({
show: false,
bottom: 0,
orient: 'horizontal',
type: 'plain',
...expectedThemeProps,
});
});
});
describe('getChartPadding', () => {
it('should handle top default', () => {
expect(getChartPadding(true, LegendOrientation.Top)).toEqual({
bottom: 0,
left: 0,
right: 0,
top: defaultLegendPadding[LegendOrientation.Top],
});
});
it('should handle left default', () => {
expect(getChartPadding(true, LegendOrientation.Left)).toEqual({
bottom: 0,
left: defaultLegendPadding[LegendOrientation.Left],
right: 0,
top: 0,
});
});
it('should return the default padding when show is false', () => {
expect(
getChartPadding(false, LegendOrientation.Left, 100, {
top: 10,
bottom: 20,
left: 30,
right: 40,
}),
).toEqual({
bottom: 20,
left: 30,
right: 40,
top: 10,
});
});
it('should return the correct padding for left orientation', () => {
expect(getChartPadding(true, LegendOrientation.Left, 100)).toEqual({
bottom: 0,
left: 100,
right: 0,
top: 0,
});
expect(
getChartPadding(true, LegendOrientation.Left, 100, undefined, true),
).toEqual({
bottom: 100,
left: 0,
right: 0,
top: 0,
});
});
it('should return the correct padding for right orientation', () => {
expect(getChartPadding(true, LegendOrientation.Right, 50)).toEqual({
bottom: 0,
left: 0,
right: 50,
top: 0,
});
expect(
getChartPadding(true, LegendOrientation.Right, 50, undefined, true),
).toEqual({
bottom: 0,
left: 0,
right: 50,
top: 0,
});
});
it('should return the correct padding for top orientation', () => {
expect(getChartPadding(true, LegendOrientation.Top, 20)).toEqual({
bottom: 0,
left: 0,
right: 0,
top: 20,
});
expect(
getChartPadding(true, LegendOrientation.Top, 20, undefined, true),
).toEqual({
bottom: 0,
left: 0,
right: 0,
top: 20,
});
});
it('should return the correct padding for bottom orientation', () => {
expect(getChartPadding(true, LegendOrientation.Bottom, 10)).toEqual({
bottom: 10,
left: 0,
right: 0,
top: 0,
});
expect(
getChartPadding(true, LegendOrientation.Bottom, 10, undefined, true),
).toEqual({
bottom: 0,
left: 10,
right: 0,
top: 0,
});
});
});
describe('dedupSeries', () => {
it('should deduplicate ids in series', () => {
expect(
dedupSeries([
{
id: 'foo',
},
{
id: 'bar',
},
{
id: 'foo',
},
{
id: 'foo',
},
]),
).toEqual([
{ id: 'foo' },
{ id: 'bar' },
{ id: 'foo (1)' },
{ id: 'foo (2)' },
]);
});
});
describe('sanitizeHtml', () => {
it('should remove html tags from series name', () => {
expect(sanitizeHtml(NULL_STRING)).toEqual('&lt;NULL&gt;');
});
});
describe('getOverMaxHiddenFormatter', () => {
it('should hide value if greater than max', () => {
const formatter = getOverMaxHiddenFormatter({ max: 81000 });
expect(formatter.format(84500)).toEqual('');
});
it('should show value if less or equal than max', () => {
const formatter = getOverMaxHiddenFormatter({ max: 81000 });
expect(formatter.format(81000)).toEqual('81000');
expect(formatter.format(50000)).toEqual('50000');
});
});
test('calculateLowerLogTick', () => {
expect(calculateLowerLogTick(1000000)).toEqual(1000000);
expect(calculateLowerLogTick(456)).toEqual(100);
expect(calculateLowerLogTick(100)).toEqual(100);
expect(calculateLowerLogTick(99)).toEqual(10);
expect(calculateLowerLogTick(2)).toEqual(1);
expect(calculateLowerLogTick(0.005)).toEqual(0.001);
});
test('getAxisType without forced categorical', () => {
expect(getAxisType(false, false, GenericDataType.Temporal)).toEqual(
AxisType.Time,
);
expect(getAxisType(false, false, GenericDataType.Numeric)).toEqual(
AxisType.Value,
);
expect(getAxisType(true, false, GenericDataType.Numeric)).toEqual(
AxisType.Category,
);
expect(getAxisType(false, false, GenericDataType.Boolean)).toEqual(
AxisType.Category,
);
expect(getAxisType(false, false, GenericDataType.String)).toEqual(
AxisType.Category,
);
});
test('getAxisType with forced categorical', () => {
expect(getAxisType(false, true, GenericDataType.Numeric)).toEqual(
AxisType.Category,
);
});
test('getMinAndMaxFromBounds returns empty object when not truncating', () => {
expect(
getMinAndMaxFromBounds(
AxisType.Value,
false,
10,
100,
EchartsTimeseriesSeriesType.Bar,
),
).toEqual({});
});
test('getMinAndMaxFromBounds returns empty object for categorical axis', () => {
expect(
getMinAndMaxFromBounds(
AxisType.Category,
false,
10,
100,
EchartsTimeseriesSeriesType.Bar,
),
).toEqual({});
});
test('getMinAndMaxFromBounds returns empty object for time axis', () => {
expect(
getMinAndMaxFromBounds(
AxisType.Time,
false,
10,
100,
EchartsTimeseriesSeriesType.Bar,
),
).toEqual({});
});
test('getMinAndMaxFromBounds returns dataMin/dataMax for non-bar charts', () => {
expect(
getMinAndMaxFromBounds(
AxisType.Value,
true,
undefined,
undefined,
EchartsTimeseriesSeriesType.Line,
),
).toEqual({
min: 'dataMin',
max: 'dataMax',
});
});
test('getMinAndMaxFromBounds returns bound without scale for non-bar charts', () => {
expect(
getMinAndMaxFromBounds(
AxisType.Value,
true,
10,
undefined,
EchartsTimeseriesSeriesType.Line,
),
).toEqual({
min: 10,
max: 'dataMax',
});
});
test('getMinAndMaxFromBounds returns scale when truncating without bounds', () => {
expect(
getMinAndMaxFromBounds(
AxisType.Value,
true,
undefined,
undefined,
EchartsTimeseriesSeriesType.Bar,
),
).toEqual({ scale: true });
});
test('getMinAndMaxFromBounds returns automatic upper bound when truncating', () => {
expect(
getMinAndMaxFromBounds(
AxisType.Value,
true,
10,
undefined,
EchartsTimeseriesSeriesType.Bar,
),
).toEqual({
min: 10,
scale: true,
});
});
test('getMinAndMaxFromBounds returns automatic lower bound when truncating', () => {
expect(
getMinAndMaxFromBounds(
AxisType.Value,
true,
undefined,
100,
EchartsTimeseriesSeriesType.Bar,
),
).toEqual({
max: 100,
scale: true,
});
});
describe('getTimeCompareStackId', () => {
it('returns the defaultId when timeCompare is empty', () => {
const result = getTimeCompareStackId('default', []);
expect(result).toEqual('default');
});
it('returns the defaultId when no value in timeCompare is included in name', () => {
const result = getTimeCompareStackId(
'default',
['compare1', 'compare2'],
'test__name',
);
expect(result).toEqual('default');
});
it('returns the first value in timeCompare that is included in name', () => {
const result = getTimeCompareStackId(
'default',
['compare1', 'compare2'],
'test__compare1',
);
expect(result).toEqual('compare1');
});
it('handles name being a number', () => {
const result = getTimeCompareStackId('default', ['123', '456'], 123);
expect(result).toEqual('123');
});
});
const forecastValue = [
{
data: [0, 1],
seriesId: 'foo',
},
{
data: [0, 2],
seriesId: 'bar',
},
];
test('extractTooltipKeys with rich tooltip', () => {
const result = extractTooltipKeys(forecastValue, 1, true, false);
expect(result).toEqual(['foo', 'bar']);
});
test('extractTooltipKeys with rich tooltip and sorting by metrics', () => {
const result = extractTooltipKeys(forecastValue, 1, true, true);
expect(result).toEqual(['bar', 'foo']);
});
test('extractTooltipKeys with non-rich tooltip', () => {
const result = extractTooltipKeys(forecastValue, 1, false, false);
expect(result).toEqual(['foo']);
});