mirror of https://github.com/buster-so/buster.git
tests for some common helpers
This commit is contained in:
parent
6741e46e94
commit
a810e6261a
|
@ -0,0 +1,498 @@
|
|||
import { formatXAxisLabel, formatYAxisLabel, yAxisSimilar } from './axisHelper';
|
||||
import { formatLabel } from '@/lib/columnFormatter';
|
||||
import { formatChartLabelDelimiter } from './labelHelpers';
|
||||
import { ChartType } from '@/api/asset_interfaces/metric/charts';
|
||||
import type { SimplifiedColumnType, ColumnMetaData } from '@/api/asset_interfaces/metric';
|
||||
|
||||
// Mock dependencies
|
||||
jest.mock('@/lib/columnFormatter', () => ({
|
||||
formatLabel: jest.fn()
|
||||
}));
|
||||
|
||||
jest.mock('./labelHelpers', () => ({
|
||||
formatChartLabelDelimiter: jest.fn()
|
||||
}));
|
||||
|
||||
describe('formatXAxisLabel', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should format a number value using formatLabel', () => {
|
||||
// Setup
|
||||
const value = 1000;
|
||||
const selectedAxis = { x: ['revenue'], y: ['growth'] };
|
||||
const columnLabelFormats = {
|
||||
revenue: {
|
||||
columnType: 'number' as SimplifiedColumnType,
|
||||
style: 'currency' as const,
|
||||
currency: 'USD'
|
||||
}
|
||||
};
|
||||
const xAxisColumnMetadata: ColumnMetaData = {
|
||||
name: 'revenue',
|
||||
min_value: 0,
|
||||
max_value: 5000,
|
||||
unique_values: 100,
|
||||
simple_type: 'number',
|
||||
type: 'float'
|
||||
};
|
||||
const selectedChartType = ChartType.Bar;
|
||||
|
||||
// Execute
|
||||
const result = formatXAxisLabel(
|
||||
value,
|
||||
selectedAxis,
|
||||
columnLabelFormats,
|
||||
xAxisColumnMetadata,
|
||||
selectedChartType
|
||||
);
|
||||
|
||||
// Verify
|
||||
expect(formatLabel).toHaveBeenCalledWith(value, {
|
||||
columnType: 'number',
|
||||
style: 'currency',
|
||||
currency: 'USD',
|
||||
compactNumbers: true
|
||||
});
|
||||
});
|
||||
|
||||
it('should use formatLabel for Scatter charts even with string values', () => {
|
||||
// Setup
|
||||
const value = 'some-string';
|
||||
const selectedAxis = { x: ['category'], y: ['count'] };
|
||||
const columnLabelFormats = {
|
||||
category: {
|
||||
columnType: 'text' as SimplifiedColumnType,
|
||||
style: 'string' as const
|
||||
}
|
||||
};
|
||||
const xAxisColumnMetadata = undefined;
|
||||
const selectedChartType = ChartType.Scatter;
|
||||
|
||||
// Execute
|
||||
formatXAxisLabel(
|
||||
value,
|
||||
selectedAxis,
|
||||
columnLabelFormats,
|
||||
xAxisColumnMetadata,
|
||||
selectedChartType
|
||||
);
|
||||
|
||||
// Verify
|
||||
expect(formatLabel).toHaveBeenCalledWith(value, {
|
||||
columnType: 'text',
|
||||
style: 'string',
|
||||
compactNumbers: false
|
||||
});
|
||||
});
|
||||
|
||||
it('should not use compact numbers when range is small', () => {
|
||||
// Setup
|
||||
const value = 100;
|
||||
const selectedAxis = { x: ['smallValue'], y: ['count'] };
|
||||
const columnLabelFormats = {
|
||||
smallValue: {
|
||||
columnType: 'number' as SimplifiedColumnType,
|
||||
style: 'number' as const
|
||||
}
|
||||
};
|
||||
const xAxisColumnMetadata: ColumnMetaData = {
|
||||
name: 'smallValue',
|
||||
min_value: 0,
|
||||
max_value: 500,
|
||||
unique_values: 50,
|
||||
simple_type: 'number',
|
||||
type: 'float'
|
||||
};
|
||||
const selectedChartType = ChartType.Line;
|
||||
|
||||
// Execute
|
||||
formatXAxisLabel(
|
||||
value,
|
||||
selectedAxis,
|
||||
columnLabelFormats,
|
||||
xAxisColumnMetadata,
|
||||
selectedChartType
|
||||
);
|
||||
|
||||
// Verify
|
||||
expect(formatLabel).toHaveBeenCalledWith(value, {
|
||||
columnType: 'number',
|
||||
style: 'number',
|
||||
compactNumbers: false
|
||||
});
|
||||
});
|
||||
|
||||
it('should use compact numbers when range is large', () => {
|
||||
// Setup
|
||||
const value = 5000;
|
||||
const selectedAxis = { x: ['largeValue'], y: ['count'] };
|
||||
const columnLabelFormats = {
|
||||
largeValue: {
|
||||
columnType: 'number' as SimplifiedColumnType,
|
||||
style: 'number' as const
|
||||
}
|
||||
};
|
||||
const xAxisColumnMetadata: ColumnMetaData = {
|
||||
name: 'largeValue',
|
||||
min_value: 0,
|
||||
max_value: 10000,
|
||||
unique_values: 200,
|
||||
simple_type: 'number',
|
||||
type: 'float'
|
||||
};
|
||||
const selectedChartType = ChartType.Bar;
|
||||
|
||||
// Execute
|
||||
formatXAxisLabel(
|
||||
value,
|
||||
selectedAxis,
|
||||
columnLabelFormats,
|
||||
xAxisColumnMetadata,
|
||||
selectedChartType
|
||||
);
|
||||
|
||||
// Verify
|
||||
expect(formatLabel).toHaveBeenCalledWith(value, {
|
||||
columnType: 'number',
|
||||
style: 'number',
|
||||
compactNumbers: true
|
||||
});
|
||||
});
|
||||
|
||||
it('should use formatChartLabelDelimiter for string values in non-scatter charts', () => {
|
||||
// Setup
|
||||
const value = 'category-name';
|
||||
const selectedAxis = { x: ['category'], y: ['count'] };
|
||||
const columnLabelFormats = {
|
||||
category: {
|
||||
columnType: 'text' as SimplifiedColumnType,
|
||||
style: 'string' as const
|
||||
}
|
||||
};
|
||||
const xAxisColumnMetadata = undefined;
|
||||
const selectedChartType = ChartType.Bar;
|
||||
|
||||
// Execute
|
||||
formatXAxisLabel(
|
||||
value,
|
||||
selectedAxis,
|
||||
columnLabelFormats,
|
||||
xAxisColumnMetadata,
|
||||
selectedChartType
|
||||
);
|
||||
|
||||
// Verify
|
||||
expect(formatChartLabelDelimiter).toHaveBeenCalledWith(value, columnLabelFormats);
|
||||
});
|
||||
});
|
||||
|
||||
describe('formatYAxisLabel', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should use percentage formatting when usePercentageModeAxis is true', () => {
|
||||
// Setup
|
||||
const value = 0.75;
|
||||
const axisColumnNames = ['growth', 'revenue'];
|
||||
const canUseSameFormatter = true;
|
||||
const columnLabelFormats = {
|
||||
growth: {
|
||||
columnType: 'number' as SimplifiedColumnType,
|
||||
style: 'number' as const
|
||||
},
|
||||
revenue: {
|
||||
columnType: 'number' as SimplifiedColumnType,
|
||||
style: 'currency' as const,
|
||||
currency: 'USD'
|
||||
}
|
||||
};
|
||||
const usePercentageModeAxis = true;
|
||||
|
||||
// Execute
|
||||
const result = formatYAxisLabel(
|
||||
value,
|
||||
axisColumnNames,
|
||||
canUseSameFormatter,
|
||||
columnLabelFormats,
|
||||
usePercentageModeAxis
|
||||
);
|
||||
|
||||
// Verify
|
||||
expect(formatLabel).toHaveBeenCalledWith(
|
||||
value,
|
||||
{ columnType: 'number', style: 'percent' },
|
||||
false
|
||||
);
|
||||
});
|
||||
|
||||
it('should use the first column format when canUseSameFormatter is true', () => {
|
||||
// Setup
|
||||
const value = 1000;
|
||||
const axisColumnNames = ['revenue', 'cost'];
|
||||
const canUseSameFormatter = true;
|
||||
const columnLabelFormats = {
|
||||
revenue: {
|
||||
columnType: 'number' as SimplifiedColumnType,
|
||||
style: 'currency' as const,
|
||||
currency: 'USD'
|
||||
},
|
||||
cost: {
|
||||
columnType: 'number' as SimplifiedColumnType,
|
||||
style: 'currency' as const,
|
||||
currency: 'EUR'
|
||||
}
|
||||
};
|
||||
const usePercentageModeAxis = false;
|
||||
const compactNumbers = true;
|
||||
|
||||
// Execute
|
||||
formatYAxisLabel(
|
||||
value,
|
||||
axisColumnNames,
|
||||
canUseSameFormatter,
|
||||
columnLabelFormats,
|
||||
usePercentageModeAxis,
|
||||
compactNumbers
|
||||
);
|
||||
|
||||
// Verify
|
||||
expect(formatLabel).toHaveBeenCalledWith(
|
||||
value,
|
||||
{
|
||||
columnType: 'number',
|
||||
style: 'currency',
|
||||
currency: 'USD',
|
||||
compactNumbers: true
|
||||
},
|
||||
false
|
||||
);
|
||||
});
|
||||
|
||||
it('should use generic number format when canUseSameFormatter is false', () => {
|
||||
// Setup
|
||||
const value = 1000;
|
||||
const axisColumnNames = ['revenue', 'growth'];
|
||||
const canUseSameFormatter = false;
|
||||
const columnLabelFormats = {
|
||||
revenue: {
|
||||
columnType: 'number' as SimplifiedColumnType,
|
||||
style: 'currency' as const,
|
||||
currency: 'USD'
|
||||
},
|
||||
growth: {
|
||||
columnType: 'number' as SimplifiedColumnType,
|
||||
style: 'percent' as const
|
||||
}
|
||||
};
|
||||
const usePercentageModeAxis = false;
|
||||
const compactNumbers = true;
|
||||
|
||||
// Execute
|
||||
formatYAxisLabel(
|
||||
value,
|
||||
axisColumnNames,
|
||||
canUseSameFormatter,
|
||||
columnLabelFormats,
|
||||
usePercentageModeAxis,
|
||||
compactNumbers
|
||||
);
|
||||
|
||||
// Verify
|
||||
expect(formatLabel).toHaveBeenCalledWith(
|
||||
value,
|
||||
{
|
||||
columnType: 'number',
|
||||
style: 'number',
|
||||
compactNumbers: true
|
||||
},
|
||||
false
|
||||
);
|
||||
});
|
||||
|
||||
it('should respect the compactNumbers parameter when false', () => {
|
||||
// Setup
|
||||
const value = 10000;
|
||||
const axisColumnNames = ['count'];
|
||||
const canUseSameFormatter = true;
|
||||
const columnLabelFormats = {
|
||||
count: {
|
||||
columnType: 'number' as SimplifiedColumnType,
|
||||
style: 'number' as const
|
||||
}
|
||||
};
|
||||
const usePercentageModeAxis = false;
|
||||
const compactNumbers = false;
|
||||
|
||||
// Execute
|
||||
formatYAxisLabel(
|
||||
value,
|
||||
axisColumnNames,
|
||||
canUseSameFormatter,
|
||||
columnLabelFormats,
|
||||
usePercentageModeAxis,
|
||||
compactNumbers
|
||||
);
|
||||
|
||||
// Verify
|
||||
expect(formatLabel).toHaveBeenCalledWith(
|
||||
value,
|
||||
{
|
||||
columnType: 'number',
|
||||
style: 'number',
|
||||
compactNumbers: false
|
||||
},
|
||||
false
|
||||
);
|
||||
});
|
||||
|
||||
it('should default compactNumbers to true if not provided', () => {
|
||||
// Setup
|
||||
const value = 10000;
|
||||
const axisColumnNames = ['count'];
|
||||
const canUseSameFormatter = true;
|
||||
const columnLabelFormats = {
|
||||
count: {
|
||||
columnType: 'number' as SimplifiedColumnType,
|
||||
style: 'number' as const
|
||||
}
|
||||
};
|
||||
const usePercentageModeAxis = false;
|
||||
// No compactNumbers parameter provided
|
||||
|
||||
// Execute
|
||||
formatYAxisLabel(
|
||||
value,
|
||||
axisColumnNames,
|
||||
canUseSameFormatter,
|
||||
columnLabelFormats,
|
||||
usePercentageModeAxis
|
||||
);
|
||||
|
||||
// Verify
|
||||
expect(formatLabel).toHaveBeenCalledWith(
|
||||
value,
|
||||
{
|
||||
columnType: 'number',
|
||||
style: 'number',
|
||||
compactNumbers: true
|
||||
},
|
||||
false
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('yAxisSimilar', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should return true when all y-axis variables have the same style and currency', () => {
|
||||
// Setup
|
||||
const yAxis = ['revenue', 'sales'];
|
||||
const columnLabelFormats = {
|
||||
revenue: {
|
||||
columnType: 'number' as SimplifiedColumnType,
|
||||
style: 'currency' as const,
|
||||
currency: 'USD'
|
||||
},
|
||||
sales: {
|
||||
columnType: 'number' as SimplifiedColumnType,
|
||||
style: 'currency' as const,
|
||||
currency: 'USD'
|
||||
}
|
||||
};
|
||||
|
||||
// Execute
|
||||
const result = yAxisSimilar(yAxis, columnLabelFormats);
|
||||
|
||||
// Verify
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false when y-axis variables have different styles', () => {
|
||||
// Setup
|
||||
const yAxis = ['revenue', 'percentage'];
|
||||
const columnLabelFormats = {
|
||||
revenue: {
|
||||
columnType: 'number' as SimplifiedColumnType,
|
||||
style: 'currency' as const,
|
||||
currency: 'USD'
|
||||
},
|
||||
percentage: {
|
||||
columnType: 'number' as SimplifiedColumnType,
|
||||
style: 'percent' as const
|
||||
}
|
||||
};
|
||||
|
||||
// Execute
|
||||
const result = yAxisSimilar(yAxis, columnLabelFormats);
|
||||
|
||||
// Verify
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false when y-axis variables have different currencies', () => {
|
||||
// Setup
|
||||
const yAxis = ['usd_revenue', 'eur_revenue'];
|
||||
const columnLabelFormats = {
|
||||
usd_revenue: {
|
||||
columnType: 'number' as SimplifiedColumnType,
|
||||
style: 'currency' as const,
|
||||
currency: 'USD'
|
||||
},
|
||||
eur_revenue: {
|
||||
columnType: 'number' as SimplifiedColumnType,
|
||||
style: 'currency' as const,
|
||||
currency: 'EUR'
|
||||
}
|
||||
};
|
||||
|
||||
// Execute
|
||||
const result = yAxisSimilar(yAxis, columnLabelFormats);
|
||||
|
||||
// Verify
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('should return true for a single y-axis variable', () => {
|
||||
// Setup
|
||||
const yAxis = ['revenue'];
|
||||
const columnLabelFormats = {
|
||||
revenue: {
|
||||
columnType: 'number' as SimplifiedColumnType,
|
||||
style: 'currency' as const,
|
||||
currency: 'USD'
|
||||
}
|
||||
};
|
||||
|
||||
// Execute
|
||||
const result = yAxisSimilar(yAxis, columnLabelFormats);
|
||||
|
||||
// Verify
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should handle y-axis variables with missing format properties', () => {
|
||||
// Setup
|
||||
const yAxis = ['value1', 'value2'];
|
||||
const columnLabelFormats = {
|
||||
value1: {
|
||||
columnType: 'number' as SimplifiedColumnType,
|
||||
style: 'number' as const
|
||||
},
|
||||
value2: {
|
||||
columnType: 'number' as SimplifiedColumnType,
|
||||
style: 'number' as const
|
||||
}
|
||||
};
|
||||
|
||||
// Execute
|
||||
const result = yAxisSimilar(yAxis, columnLabelFormats);
|
||||
|
||||
// Verify
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,69 @@
|
|||
import { InnerLabelTitleRecord, getPieInnerLabelTitle } from './pieLabelHelpers';
|
||||
import type { BusterChartConfigProps } from '@/api/asset_interfaces/metric/charts';
|
||||
|
||||
describe('pieLabelHelpers', () => {
|
||||
describe('InnerLabelTitleRecord', () => {
|
||||
it('should have the correct titles for each aggregate type', () => {
|
||||
expect(InnerLabelTitleRecord.sum).toBe('Total');
|
||||
expect(InnerLabelTitleRecord.average).toBe('Average');
|
||||
expect(InnerLabelTitleRecord.median).toBe('Median');
|
||||
expect(InnerLabelTitleRecord.max).toBe('Max');
|
||||
expect(InnerLabelTitleRecord.min).toBe('Min');
|
||||
expect(InnerLabelTitleRecord.count).toBe('Count');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getPieInnerLabelTitle', () => {
|
||||
it('should return the provided title when pieInnerLabelTitle is provided', () => {
|
||||
const pieInnerLabelTitle = 'Custom Title';
|
||||
const pieInnerLabelAggregate: BusterChartConfigProps['pieInnerLabelAggregate'] = 'sum';
|
||||
|
||||
const result = getPieInnerLabelTitle(pieInnerLabelTitle, pieInnerLabelAggregate);
|
||||
|
||||
expect(result).toBe('Custom Title');
|
||||
});
|
||||
|
||||
it('should return the aggregate title when pieInnerLabelTitle is not provided', () => {
|
||||
const pieInnerLabelTitle = undefined;
|
||||
const pieInnerLabelAggregate: BusterChartConfigProps['pieInnerLabelAggregate'] = 'average';
|
||||
|
||||
const result = getPieInnerLabelTitle(pieInnerLabelTitle, pieInnerLabelAggregate);
|
||||
|
||||
expect(result).toBe('Average');
|
||||
});
|
||||
|
||||
it('should default to "sum" aggregate when pieInnerLabelAggregate is not provided', () => {
|
||||
const pieInnerLabelTitle = undefined;
|
||||
const pieInnerLabelAggregate = undefined;
|
||||
|
||||
const result = getPieInnerLabelTitle(pieInnerLabelTitle, pieInnerLabelAggregate);
|
||||
|
||||
expect(result).toBe('Total');
|
||||
});
|
||||
|
||||
it('should fall back to aggregate title when pieInnerLabelTitle is null', () => {
|
||||
const pieInnerLabelTitle = null as unknown as BusterChartConfigProps['pieInnerLabelTitle'];
|
||||
const pieInnerLabelAggregate: BusterChartConfigProps['pieInnerLabelAggregate'] = 'median';
|
||||
|
||||
const result = getPieInnerLabelTitle(pieInnerLabelTitle, pieInnerLabelAggregate);
|
||||
|
||||
expect(result).toBe('Median');
|
||||
});
|
||||
|
||||
it('should work with each type of aggregate', () => {
|
||||
const testCases: Array<NonNullable<BusterChartConfigProps['pieInnerLabelAggregate']>> = [
|
||||
'sum',
|
||||
'average',
|
||||
'median',
|
||||
'max',
|
||||
'min',
|
||||
'count'
|
||||
];
|
||||
|
||||
testCases.forEach((aggregate) => {
|
||||
const result = getPieInnerLabelTitle(undefined, aggregate);
|
||||
expect(result).toBe(InnerLabelTitleRecord[aggregate]);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,58 @@
|
|||
import { truncateWithEllipsis } from './titleHelpers';
|
||||
|
||||
describe('truncateWithEllipsis', () => {
|
||||
it('should return the original text when shorter than maxLength', () => {
|
||||
const shortText = 'Short text';
|
||||
expect(truncateWithEllipsis(shortText)).toBe(shortText);
|
||||
});
|
||||
|
||||
it('should truncate text with ellipsis when longer than default maxLength', () => {
|
||||
const longText =
|
||||
'This is a very long text that should be truncated because it exceeds the default max length of 52 characters';
|
||||
const result = truncateWithEllipsis(longText);
|
||||
|
||||
// The result should be shorter than the original
|
||||
expect(result.length).toBeLessThan(longText.length);
|
||||
|
||||
// Should match the default max length (including the ellipsis)
|
||||
expect(result.length).toBeLessThanOrEqual(52);
|
||||
|
||||
// Should end with ellipsis
|
||||
expect(result.endsWith('...')).toBe(true);
|
||||
});
|
||||
|
||||
it('should truncate to the specified maxLength', () => {
|
||||
const longText = 'This text should be truncated to 20 characters';
|
||||
const maxLength = 20;
|
||||
const result = truncateWithEllipsis(longText, maxLength);
|
||||
|
||||
// Should match the specified max length (including the ellipsis)
|
||||
expect(result.length).toBeLessThanOrEqual(maxLength);
|
||||
|
||||
// Should end with ellipsis
|
||||
expect(result.endsWith('...')).toBe(true);
|
||||
});
|
||||
|
||||
it('should handle edge case with maxLength less than 4', () => {
|
||||
// Testing with very small maxLength value
|
||||
// lodash/truncate handles the ellipsis calculations internally
|
||||
const text = 'Some text';
|
||||
const maxLength = 3;
|
||||
const result = truncateWithEllipsis(text, maxLength);
|
||||
|
||||
expect(result.length).toBeLessThanOrEqual(maxLength);
|
||||
});
|
||||
|
||||
it('should handle empty string', () => {
|
||||
const emptyText = '';
|
||||
expect(truncateWithEllipsis(emptyText)).toBe(emptyText);
|
||||
});
|
||||
|
||||
it('should handle strings exactly at the maxLength boundary', () => {
|
||||
const text = 'a'.repeat(52); // Exactly the default maxLength
|
||||
expect(truncateWithEllipsis(text)).toBe(text);
|
||||
|
||||
const text2 = 'a'.repeat(53); // One over the default maxLength
|
||||
expect(truncateWithEllipsis(text2).length).toBeLessThan(text2.length);
|
||||
});
|
||||
});
|
|
@ -1,2 +1,4 @@
|
|||
import truncate from 'lodash/truncate';
|
||||
|
||||
export const truncateWithEllipsis = (text: string, maxLength: number = 52) =>
|
||||
text.length > maxLength ? `${text.slice(0, maxLength - 3)}...` : text;
|
||||
text.length > maxLength ? truncate(text, { length: maxLength }) : text;
|
||||
|
|
Loading…
Reference in New Issue