tests for some common helpers

This commit is contained in:
Nate Kelley 2025-04-04 10:07:16 -06:00
parent 6741e46e94
commit a810e6261a
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
4 changed files with 628 additions and 1 deletions

View File

@ -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);
});
});

View File

@ -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]);
});
});
});
});

View File

@ -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);
});
});

View File

@ -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;