mirror of
https://github.com/prometheus/prometheus.git
synced 2025-07-03 19:13:23 +00:00
Merge pull request #16417 from amanycodes/histogram-helper-test
Some checks are pending
buf.build / lint and publish (push) Waiting to run
CI / Go tests (push) Waiting to run
CI / More Go tests (push) Waiting to run
CI / Go tests with previous Go version (push) Waiting to run
CI / UI tests (push) Waiting to run
CI / Go tests on Windows (push) Waiting to run
CI / Mixins tests (push) Waiting to run
CI / Build Prometheus for common architectures (push) Waiting to run
CI / Build Prometheus for all architectures (push) Waiting to run
CI / Report status of build Prometheus for all architectures (push) Blocked by required conditions
CI / Check generated parser (push) Waiting to run
CI / golangci-lint (push) Waiting to run
CI / fuzzing (push) Waiting to run
CI / codeql (push) Waiting to run
CI / Publish main branch artifacts (push) Blocked by required conditions
CI / Publish release artefacts (push) Blocked by required conditions
CI / Publish UI on npm Registry (push) Blocked by required conditions
Scorecards supply-chain security / Scorecards analysis (push) Waiting to run
Some checks are pending
buf.build / lint and publish (push) Waiting to run
CI / Go tests (push) Waiting to run
CI / More Go tests (push) Waiting to run
CI / Go tests with previous Go version (push) Waiting to run
CI / UI tests (push) Waiting to run
CI / Go tests on Windows (push) Waiting to run
CI / Mixins tests (push) Waiting to run
CI / Build Prometheus for common architectures (push) Waiting to run
CI / Build Prometheus for all architectures (push) Waiting to run
CI / Report status of build Prometheus for all architectures (push) Blocked by required conditions
CI / Check generated parser (push) Waiting to run
CI / golangci-lint (push) Waiting to run
CI / fuzzing (push) Waiting to run
CI / codeql (push) Waiting to run
CI / Publish main branch artifacts (push) Blocked by required conditions
CI / Publish release artefacts (push) Blocked by required conditions
CI / Publish UI on npm Registry (push) Blocked by required conditions
Scorecards supply-chain security / Scorecards analysis (push) Waiting to run
ui-tests: Add Unit tests to Native histogram and its helpers.
This commit is contained in:
commit
32b471ed47
2 changed files with 554 additions and 0 deletions
243
web/ui/react-app/src/pages/graph/HistogramChart.test.tsx
Normal file
243
web/ui/react-app/src/pages/graph/HistogramChart.test.tsx
Normal file
|
@ -0,0 +1,243 @@
|
|||
import React from 'react';
|
||||
import { mount, ReactWrapper } from 'enzyme';
|
||||
import HistogramChart from './HistogramChart';
|
||||
import { Histogram } from '../../types/types';
|
||||
|
||||
const mockFormat = jest.fn((value) => value.toString());
|
||||
const mockResolvedOptions = jest.fn().mockReturnValue({ locale: 'en-US', numberingSystem: 'latn', style: 'decimal' });
|
||||
const mockFormatToParts = jest.fn();
|
||||
const mockFormatRange = jest.fn();
|
||||
const mockFormatRangeToParts = jest.fn();
|
||||
|
||||
jest.spyOn(global.Intl, 'NumberFormat').mockImplementation(() => ({
|
||||
format: mockFormat,
|
||||
resolvedOptions: mockResolvedOptions,
|
||||
formatToParts: mockFormatToParts,
|
||||
formatRange: mockFormatRange,
|
||||
formatRangeToParts: mockFormatRangeToParts,
|
||||
}));
|
||||
|
||||
describe('HistogramChart', () => {
|
||||
let wrapper: ReactWrapper;
|
||||
|
||||
const histogramDataLinear: Histogram = {
|
||||
count: '30',
|
||||
sum: '350',
|
||||
buckets: [
|
||||
[1678886400, '0', '10', '5'],
|
||||
[1678886400, '10', '20', '15'],
|
||||
[1678886400, '20', '30', '10'],
|
||||
],
|
||||
};
|
||||
|
||||
const histogramDataExponential: Histogram = {
|
||||
count: '140',
|
||||
sum: '...',
|
||||
buckets: [
|
||||
[1678886400, '-100', '-10', '20'],
|
||||
[1678886400, '-10', '-1', '30'],
|
||||
[1678886400, '1', '10', '50'],
|
||||
[1678886400, '10', '100', '40'],
|
||||
],
|
||||
};
|
||||
|
||||
const histogramDataZeroCrossing: Histogram = {
|
||||
count: '30',
|
||||
sum: '...',
|
||||
buckets: [
|
||||
[1678886400, '-5', '-1', '10'],
|
||||
[1678886400, '-1', '1', '5'],
|
||||
[1678886400, '1', '5', '15'],
|
||||
],
|
||||
};
|
||||
|
||||
const histogramDataEmpty: Histogram = {
|
||||
count: '0',
|
||||
sum: '0',
|
||||
buckets: [],
|
||||
};
|
||||
|
||||
const histogramDataNull: Histogram = {
|
||||
count: '0',
|
||||
sum: '0',
|
||||
buckets: null as any,
|
||||
};
|
||||
|
||||
const defaultProps = {
|
||||
index: 0,
|
||||
scale: 'linear' as 'linear' | 'exponential',
|
||||
};
|
||||
|
||||
|
||||
beforeEach(() => {
|
||||
mockFormat.mockClear();
|
||||
mockResolvedOptions.mockClear();
|
||||
mockFormatToParts.mockClear();
|
||||
mockFormatRange.mockClear();
|
||||
mockFormatRangeToParts.mockClear();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
if (wrapper && wrapper.exists()) {
|
||||
wrapper.unmount();
|
||||
}
|
||||
});
|
||||
|
||||
it('renders without crashing', () => {
|
||||
wrapper = mount(<HistogramChart {...defaultProps} histogram={histogramDataLinear} scale="linear" />);
|
||||
expect(wrapper.find('.histogram-y-wrapper').exists()).toBe(true);
|
||||
expect(wrapper.find('.histogram-container').exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('renders "No data" when buckets are empty', () => {
|
||||
wrapper = mount(<HistogramChart {...defaultProps} histogram={histogramDataEmpty} scale="linear" />);
|
||||
expect(wrapper.text()).toContain('No data');
|
||||
expect(wrapper.find('.histogram-container').exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('renders "No data" when buckets are null', () => {
|
||||
wrapper = mount(<HistogramChart {...defaultProps} histogram={histogramDataNull} scale="linear" />);
|
||||
expect(wrapper.text()).toContain('No data');
|
||||
expect(wrapper.find('.histogram-container').exists()).toBe(false);
|
||||
});
|
||||
|
||||
describe('Linear Scale', () => {
|
||||
beforeEach(() => {
|
||||
wrapper = mount(<HistogramChart {...defaultProps} histogram={histogramDataLinear} scale="linear" />);
|
||||
});
|
||||
|
||||
it('renders the correct number of buckets', () => {
|
||||
expect(wrapper.find('.histogram-bucket')).toHaveLength(histogramDataLinear.buckets!.length);
|
||||
});
|
||||
|
||||
it('renders y-axis labels and grid lines', () => {
|
||||
expect(wrapper.find('.histogram-y-label')).toHaveLength(5);
|
||||
expect(wrapper.find('.histogram-y-grid')).toHaveLength(5);
|
||||
expect(wrapper.find('.histogram-y-tick')).toHaveLength(5);
|
||||
expect(wrapper.find('.histogram-y-label').at(0).text()).toBe('');
|
||||
expect(wrapper.find('.histogram-y-label').last().text()).toBe('0');
|
||||
});
|
||||
|
||||
it('renders x-axis labels and grid lines', () => {
|
||||
expect(wrapper.find('.histogram-x-label')).toHaveLength(1);
|
||||
expect(wrapper.find('.histogram-x-grid')).toHaveLength(6);
|
||||
expect(wrapper.find('.histogram-x-tick')).toHaveLength(3);
|
||||
expect(mockFormat).toHaveBeenCalledWith(0);
|
||||
expect(mockFormat).toHaveBeenCalledWith(30);
|
||||
expect(wrapper.find('.histogram-x-label').text()).toContain('0');
|
||||
expect(wrapper.find('.histogram-x-label').text()).toContain('30');
|
||||
});
|
||||
|
||||
it('calculates bucket styles correctly for linear scale', () => {
|
||||
const buckets = wrapper.find('.histogram-bucket-slot');
|
||||
const rangeMin = 0;
|
||||
const rangeMax = 30;
|
||||
const rangeWidth = rangeMax - rangeMin;
|
||||
const fdMax = 1.5;
|
||||
|
||||
const b1 = buckets.at(0);
|
||||
const expectedB1LeftNum = ((0 - rangeMin) / rangeWidth) * 100;
|
||||
const expectedB1WidthNum = ((10 - 0) / rangeWidth) * 100;
|
||||
const expectedB1HeightNum = (0.5 / fdMax) * 100;
|
||||
expect(parseFloat(b1.prop('style')?.left as string)).toBeCloseTo(expectedB1LeftNum, 1);
|
||||
expect(parseFloat(b1.prop('style')?.width as string)).toBeCloseTo(expectedB1WidthNum, 1);
|
||||
expect(parseFloat(b1.find('.histogram-bucket').prop('style')?.height as string)).toBeCloseTo(expectedB1HeightNum, 1);
|
||||
|
||||
const b2 = buckets.at(1);
|
||||
const expectedB2LeftNum = ((10 - rangeMin) / rangeWidth) * 100;
|
||||
const expectedB2WidthNum = ((20 - 10) / rangeWidth) * 100;
|
||||
const expectedB2HeightNum = (1.5 / fdMax) * 100;
|
||||
expect(parseFloat(b2.prop('style')?.left as string)).toBeCloseTo(expectedB2LeftNum, 1);
|
||||
expect(parseFloat(b2.prop('style')?.width as string)).toBeCloseTo(expectedB2WidthNum, 1);
|
||||
expect(parseFloat(b2.find('.histogram-bucket').prop('style')?.height as string)).toBeCloseTo(expectedB2HeightNum, 1);
|
||||
|
||||
const b3 = buckets.at(2);
|
||||
const expectedB3LeftNum = ((20 - rangeMin) / rangeWidth) * 100;
|
||||
const expectedB3WidthNum = ((30 - 20) / rangeWidth) * 100;
|
||||
const expectedB3HeightNum = (1.0 / fdMax) * 100;
|
||||
expect(parseFloat(b3.prop('style')?.left as string)).toBeCloseTo(expectedB3LeftNum, 1);
|
||||
expect(parseFloat(b3.prop('style')?.width as string)).toBeCloseTo(expectedB3WidthNum, 1);
|
||||
expect(parseFloat(b3.find('.histogram-bucket').prop('style')?.height as string)).toBeCloseTo(expectedB3HeightNum, 1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Exponential Scale', () => {
|
||||
beforeEach(() => {
|
||||
wrapper = mount(<HistogramChart {...defaultProps} index={1} histogram={histogramDataExponential} scale="exponential" />);
|
||||
});
|
||||
|
||||
it('renders the correct number of buckets', () => {
|
||||
expect(wrapper.find('.histogram-bucket')).toHaveLength(histogramDataExponential.buckets!.length);
|
||||
});
|
||||
|
||||
it('renders y-axis labels and grid lines with formatting', () => {
|
||||
expect(wrapper.find('.histogram-y-label')).toHaveLength(5);
|
||||
expect(wrapper.find('.histogram-y-grid')).toHaveLength(5);
|
||||
expect(wrapper.find('.histogram-y-tick')).toHaveLength(5);
|
||||
|
||||
const countMax = 50;
|
||||
expect(mockFormat).toHaveBeenCalledWith(countMax * 1);
|
||||
expect(mockFormat).toHaveBeenCalledWith(countMax * 0.75);
|
||||
expect(mockFormat).toHaveBeenCalledWith(countMax * 0.5);
|
||||
expect(mockFormat).toHaveBeenCalledWith(countMax * 0.25);
|
||||
|
||||
expect(wrapper.find('.histogram-y-label').at(0).text()).toBe('50');
|
||||
expect(wrapper.find('.histogram-y-label').at(1).text()).toBe('37.5');
|
||||
expect(wrapper.find('.histogram-y-label').last().text()).toBe('0');
|
||||
});
|
||||
|
||||
it('renders x-axis labels and grid lines with formatting', () => {
|
||||
expect(wrapper.find('.histogram-x-label')).toHaveLength(1);
|
||||
expect(wrapper.find('.histogram-x-grid')).toHaveLength(6);
|
||||
expect(wrapper.find('.histogram-x-tick')).toHaveLength(3);
|
||||
|
||||
expect(mockFormat).toHaveBeenCalledWith(-100);
|
||||
expect(mockFormat).toHaveBeenCalledWith(100);
|
||||
expect(wrapper.find('.histogram-x-label').text()).toContain('0');
|
||||
expect(wrapper.find('.histogram-x-label').text()).toContain('-100');
|
||||
expect(wrapper.find('.histogram-x-label').text()).toContain('100');
|
||||
});
|
||||
|
||||
it('calculates bucket styles correctly for exponential scale', () => {
|
||||
const buckets = wrapper.find('.histogram-bucket-slot');
|
||||
const countMax = 50;
|
||||
|
||||
const b1 = buckets.at(0);
|
||||
const b1Height = (20 / countMax) * 100;
|
||||
expect(b1.find('.histogram-bucket').prop('style')).toHaveProperty('height', `${b1Height}%`);
|
||||
expect(parseFloat(b1.prop('style')?.left as string)).toBeGreaterThanOrEqual(0);
|
||||
expect(parseFloat(b1.prop('style')?.width as string)).toBeGreaterThan(0);
|
||||
|
||||
const b2 = buckets.at(1);
|
||||
const b2Height = (30 / countMax) * 100;
|
||||
expect(b2.find('.histogram-bucket').prop('style')).toHaveProperty('height', `${b2Height}%`);
|
||||
expect(parseFloat(b2.prop('style')?.left as string)).toBeGreaterThan(0);
|
||||
expect(parseFloat(b2.prop('style')?.width as string)).toBeGreaterThan(0);
|
||||
|
||||
const b3 = buckets.at(2);
|
||||
const b3Height = (50 / countMax) * 100;
|
||||
expect(b3.find('.histogram-bucket').prop('style')).toHaveProperty('height', '100%');
|
||||
expect(parseFloat(b3.prop('style')?.left as string)).toBeGreaterThan(0);
|
||||
expect(parseFloat(b3.prop('style')?.width as string)).toBeGreaterThan(0);
|
||||
|
||||
const b4 = buckets.at(3);
|
||||
const b4Height = (40 / countMax) * 100;
|
||||
expect(b4.find('.histogram-bucket').prop('style')).toHaveProperty('height', `${b4Height}%`);
|
||||
expect(parseFloat(b4.prop('style')?.left as string)).toBeGreaterThan(0);
|
||||
expect(parseFloat(b4.prop('style')?.width as string)).toBeGreaterThan(0);
|
||||
expect(parseFloat(b4.prop('style')?.left as string) + parseFloat(b4.prop('style')?.width as string)).toBeLessThanOrEqual(100.01);
|
||||
});
|
||||
|
||||
it('handles zero-crossing bucket correctly in exponential scale', () => {
|
||||
wrapper = mount(<HistogramChart {...defaultProps} index={2} histogram={histogramDataZeroCrossing} scale="exponential" />);
|
||||
const buckets = wrapper.find('.histogram-bucket-slot');
|
||||
const countMax = 15;
|
||||
|
||||
const b2 = buckets.at(1);
|
||||
const b2Height = (5 / countMax) * 100;
|
||||
expect(b2.find('.histogram-bucket').prop('style')).toHaveProperty('height', expect.stringContaining(b2Height.toFixed(1)));
|
||||
expect(parseFloat(b2.prop('style')?.left as string)).toBeGreaterThanOrEqual(0);
|
||||
expect(parseFloat(b2.prop('style')?.width as string)).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
});
|
311
web/ui/react-app/src/pages/graph/HistorgramHelpers.test.tsx
Normal file
311
web/ui/react-app/src/pages/graph/HistorgramHelpers.test.tsx
Normal file
|
@ -0,0 +1,311 @@
|
|||
import {
|
||||
calculateDefaultExpBucketWidth,
|
||||
findMinPositive,
|
||||
findMaxNegative,
|
||||
findZeroBucket,
|
||||
findZeroAxisLeft,
|
||||
showZeroAxis,
|
||||
} from './HistogramHelpers';
|
||||
|
||||
type Bucket = [number, string, string, string];
|
||||
|
||||
describe('HistogramHelpers', () => {
|
||||
const bucketsAllPositive: Bucket[] = [
|
||||
[0, '1', '10', '5'],
|
||||
[0, '10', '100', '15'],
|
||||
[0, '100', '1000', '10'],
|
||||
];
|
||||
|
||||
const bucketsAllNegative: Bucket[] = [
|
||||
[0, '-1000', '-100', '10'],
|
||||
[0, '-100', '-10', '15'],
|
||||
[0, '-10', '-1', '5'],
|
||||
];
|
||||
|
||||
const bucketsCrossingZeroMid: Bucket[] = [
|
||||
[0, '-100', '-10', '10'],
|
||||
[0, '-10', '-1', '15'],
|
||||
[0, '-1', '1', '5'],
|
||||
[0, '1', '10', '20'],
|
||||
[0, '10', '100', '8'],
|
||||
];
|
||||
|
||||
const bucketsWithExactZeroBucket: Bucket[] = [
|
||||
[0, '-10', '-1', '15'],
|
||||
[0, '0', '0', '5'],
|
||||
[0, '1', '10', '20'],
|
||||
];
|
||||
|
||||
const bucketsStartingWithZeroCross: Bucket[] = [
|
||||
[0, '-1', '1', '5'],
|
||||
[0, '1', '10', '20'],
|
||||
[0, '10', '100', '8'],
|
||||
];
|
||||
|
||||
const bucketsEndingWithZeroCross: Bucket[] = [
|
||||
[0, '-100', '-10', '10'],
|
||||
[0, '-10', '-1', '15'],
|
||||
[0, '-1', '1', '5'],
|
||||
];
|
||||
|
||||
const singleZeroBucket: Bucket[] = [
|
||||
[0, '0', '0', '10'],
|
||||
];
|
||||
|
||||
const emptyBuckets: Bucket[] = [];
|
||||
|
||||
const bucketsWithZeroFallback: Bucket[] = [
|
||||
[0, '1', '10', '5'],
|
||||
[0, '10', '100', '15'],
|
||||
[0, '0', '0', '2']
|
||||
];
|
||||
|
||||
const bucketsNegThenPosNoCross: Bucket[] = [
|
||||
[0, '-10', '-1', '15'],
|
||||
[0, '5', '10', '20'],
|
||||
];
|
||||
|
||||
|
||||
describe('calculateDefaultExpBucketWidth', () => {
|
||||
it('calculates width for a standard positive bucket', () => {
|
||||
const lastBucket = bucketsAllPositive[bucketsAllPositive.length - 1];
|
||||
const expected = Math.log(1000) - Math.log(100);
|
||||
expect(calculateDefaultExpBucketWidth(lastBucket, bucketsAllPositive)).toBeCloseTo(expected);
|
||||
});
|
||||
|
||||
it('calculates width for a standard negative bucket', () => {
|
||||
const lastBucket = bucketsAllNegative[bucketsAllNegative.length - 1];
|
||||
const expectedAbs = Math.abs(Math.log(Math.abs(parseFloat(lastBucket[2]))) - Math.log(Math.abs(parseFloat(lastBucket[1]))));
|
||||
expect(calculateDefaultExpBucketWidth(lastBucket, bucketsAllNegative)).toBeCloseTo(expectedAbs);
|
||||
});
|
||||
|
||||
it('uses the previous bucket if the last bucket is [0, 0]', () => {
|
||||
const lastBucket = bucketsWithZeroFallback[bucketsWithZeroFallback.length - 1];
|
||||
const expected = Math.log(100) - Math.log(10);
|
||||
expect(calculateDefaultExpBucketWidth(lastBucket, bucketsWithZeroFallback)).toBeCloseTo(expected);
|
||||
});
|
||||
|
||||
it('throws an error if only a single [0, 0] bucket exists', () => {
|
||||
const lastBucket = singleZeroBucket[0];
|
||||
expect(() => calculateDefaultExpBucketWidth(lastBucket, singleZeroBucket)).toThrow(
|
||||
'Only one bucket in histogram ([-0, 0]). Cannot calculate defaultExpBucketWidth.'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('findMinPositive', () => {
|
||||
it('returns the first positive left bound when all are positive', () => {
|
||||
expect(findMinPositive(bucketsAllPositive)).toEqual(1);
|
||||
});
|
||||
|
||||
it('returns the left bound when it is the first positive value', () => {
|
||||
expect(findMinPositive(bucketsNegThenPosNoCross)).toBe(5);
|
||||
});
|
||||
|
||||
it('returns the right bound when left is negative and right is positive (middle cross)', () => {
|
||||
expect(findMinPositive(bucketsCrossingZeroMid)).toBe(1);
|
||||
});
|
||||
|
||||
it('returns the right bound when the first bucket crosses zero', () => {
|
||||
expect(findMinPositive(bucketsStartingWithZeroCross)).toBe(1);
|
||||
});
|
||||
|
||||
it('returns the right bound when the last bucket crosses zero', () => {
|
||||
expect(findMinPositive(bucketsEndingWithZeroCross)).toBe(1);
|
||||
});
|
||||
|
||||
it('returns 0 when all buckets are negative', () => {
|
||||
expect(findMinPositive(bucketsAllNegative)).toBe(0);
|
||||
});
|
||||
|
||||
it('returns 0 for empty buckets', () => {
|
||||
expect(findMinPositive(emptyBuckets)).toBe(0);
|
||||
});
|
||||
|
||||
it('returns 0 for only zero bucket', () => {
|
||||
expect(findMinPositive(singleZeroBucket)).toBe(0);
|
||||
});
|
||||
|
||||
it('returns 0 when buckets is undefined', () => {
|
||||
expect(findMinPositive(undefined as any)).toBe(0);
|
||||
});
|
||||
|
||||
it('returns the correct positive bound with exact zero bucket present', () => {
|
||||
expect(findMinPositive(bucketsWithExactZeroBucket)).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('findMaxNegative', () => {
|
||||
it('returns 0 when all buckets are positive', () => {
|
||||
expect(findMaxNegative(bucketsAllPositive)).toBe(0);
|
||||
});
|
||||
|
||||
it('returns the right bound of the last negative bucket when all are negative', () => {
|
||||
expect(findMaxNegative(bucketsAllNegative)).toEqual(-1);
|
||||
});
|
||||
|
||||
it('returns the right bound of the bucket before the middle zero-crossing bucket', () => {
|
||||
expect(findMaxNegative(bucketsCrossingZeroMid)).toEqual(-1);
|
||||
});
|
||||
|
||||
it('returns the left bound when the first bucket crosses zero', () => {
|
||||
expect(findMaxNegative(bucketsStartingWithZeroCross)).toBe(-1);
|
||||
});
|
||||
|
||||
it('returns the right bound of the bucket before the last zero-crossing bucket', () => {
|
||||
expect(findMaxNegative(bucketsEndingWithZeroCross)).toEqual(-1);
|
||||
});
|
||||
|
||||
it('returns 0 for empty buckets', () => {
|
||||
expect(findMaxNegative(emptyBuckets)).toBe(0);
|
||||
});
|
||||
|
||||
it('returns 0 for only zero bucket', () => {
|
||||
expect(findMaxNegative(singleZeroBucket)).toBe(0);
|
||||
});
|
||||
|
||||
it('returns 0 when buckets is undefined', () => {
|
||||
expect(findMaxNegative(undefined as any)).toBe(0);
|
||||
});
|
||||
|
||||
it('returns the right bound of the bucket before an exact zero bucket', () => {
|
||||
expect(findMaxNegative(bucketsWithExactZeroBucket)).toEqual(-1);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('findZeroBucket', () => {
|
||||
it('returns the index of bucket strictly containing zero', () => {
|
||||
expect(findZeroBucket(bucketsCrossingZeroMid)).toBe(2);
|
||||
});
|
||||
|
||||
it('returns the index of bucket with zero as left boundary', () => {
|
||||
const buckets: Bucket[] = [[0, '-5','-1', '10'], [0, '0', '5', '15']];
|
||||
expect(findZeroBucket(buckets)).toBe(1);
|
||||
});
|
||||
|
||||
it('returns the index of bucket with zero as right boundary', () => {
|
||||
const buckets: Bucket[] = [[0, '-5', '0', '10'], [0, '1', '5', '15']];
|
||||
expect(findZeroBucket(buckets)).toBe(0);
|
||||
});
|
||||
|
||||
it('returns the index of an exact [0, 0] bucket', () => {
|
||||
expect(findZeroBucket(bucketsWithExactZeroBucket)).toBe(1);
|
||||
});
|
||||
|
||||
it('returns -1 when there is a gap around zero', () => {
|
||||
expect(findZeroBucket(bucketsNegThenPosNoCross)).toBe(-1);
|
||||
});
|
||||
|
||||
it('returns -1 when all buckets are positive', () => {
|
||||
expect(findZeroBucket(bucketsAllPositive)).toBe(-1);
|
||||
});
|
||||
|
||||
it('returns -1 when all buckets are negative', () => {
|
||||
expect(findZeroBucket(bucketsAllNegative)).toBe(-1);
|
||||
});
|
||||
|
||||
it('returns 0 if the first bucket crosses zero', () => {
|
||||
expect(findZeroBucket(bucketsStartingWithZeroCross)).toBe(0);
|
||||
});
|
||||
|
||||
it('returns the last index if the last bucket crosses zero', () => {
|
||||
expect(findZeroBucket(bucketsEndingWithZeroCross)).toBe(2);
|
||||
});
|
||||
|
||||
it('returns -1 when buckets array is empty', () => {
|
||||
expect(findZeroBucket(emptyBuckets)).toBe(-1);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('findZeroAxisLeft', () => {
|
||||
it('calculates correctly for linear scale crossing zero', () => {
|
||||
const rangeMin = -100; const rangeMax = 100;
|
||||
const expected = '50%';
|
||||
const result = findZeroAxisLeft('linear', rangeMin, rangeMax, 1, -1, 2, 0, 0, 0);
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
|
||||
it('calculates correctly for asymmetric linear scale crossing zero', () => {
|
||||
const rangeMin = -10; const rangeMax = 90;
|
||||
const expectedNumber = ((0 - rangeMin) / (rangeMax - rangeMin)) * 100;
|
||||
const resultString = findZeroAxisLeft('linear', rangeMin, rangeMax, 1, -1, 0, 0, 0, 0);
|
||||
expect(parseFloat(resultString)).toBeCloseTo(expectedNumber, 1);
|
||||
});
|
||||
|
||||
it('calculates correctly for linear scale all positive (off-scale left)', () => {
|
||||
const rangeMin = 10; const rangeMax = 100;
|
||||
const expectedNumber = ((0 - rangeMin) / (rangeMax - rangeMin)) * 100;
|
||||
const resultString = findZeroAxisLeft('linear', rangeMin, rangeMax, 10, 0, -1, 0, 0, 0);
|
||||
expect(parseFloat(resultString)).toBeCloseTo(expectedNumber, 1);
|
||||
});
|
||||
|
||||
it('calculates correctly for linear scale all negative (off-scale right)', () => {
|
||||
const rangeMin = -100; const rangeMax = -10;
|
||||
const expectedNumber = ((0 - rangeMin) / (rangeMax - rangeMin)) * 100;
|
||||
const resultString = findZeroAxisLeft('linear', rangeMin, rangeMax, 0, -10, -1, 0, 0, 0);
|
||||
expect(parseFloat(resultString)).toBeCloseTo(expectedNumber, 1);
|
||||
});
|
||||
|
||||
|
||||
const expMinPos = 1;
|
||||
const expMaxNeg = -1;
|
||||
const expZeroIdx = 2;
|
||||
const defaultExpBW = Math.log(10);
|
||||
const expNegWidth = Math.abs(Math.log(Math.abs(-1)) - Math.log(Math.abs(-100)));
|
||||
const expPosWidth = Math.log(100) - Math.log(1);
|
||||
const expTotalWidth = expNegWidth + expPosWidth + defaultExpBW;
|
||||
|
||||
it('returns 0% for exponential scale when maxNegative is 0', () => {
|
||||
expect(findZeroAxisLeft('exponential', 1, 100, 1, 0, -1, 0, expPosWidth + defaultExpBW, defaultExpBW)).toEqual('0%');
|
||||
});
|
||||
|
||||
it('returns 100% for exponential scale when minPositive is 0', () => {
|
||||
expect(findZeroAxisLeft('exponential', -100, -1, 0, -1, -1, expNegWidth, expNegWidth + defaultExpBW, defaultExpBW)).toEqual('100%');
|
||||
});
|
||||
|
||||
it('calculates position between buckets when zeroBucketIdx is -1 (exponential)', () => {
|
||||
const minPos = 5; const maxNeg = -1; const zeroIdx = -1;
|
||||
const negW = Math.log(Math.abs(-1)) - Math.log(Math.abs(-10));
|
||||
const posW = Math.log(10) - Math.log(5);
|
||||
const totalW = Math.abs(negW) + posW + defaultExpBW;
|
||||
const expectedNumber = (Math.abs(negW) / totalW) * 100;
|
||||
const resultString = findZeroAxisLeft('exponential', -10, 10, minPos, maxNeg, zeroIdx, Math.abs(negW), totalW, defaultExpBW);
|
||||
expect(parseFloat(resultString)).toBeCloseTo(expectedNumber, 1);
|
||||
});
|
||||
|
||||
it('calculates position using bucket width when zeroBucketIdx exists (exponential)', () => {
|
||||
const expectedNumber = ((expNegWidth + 0.5 * defaultExpBW) / expTotalWidth) * 100;
|
||||
const resultString = findZeroAxisLeft('exponential', -100, 100, expMinPos, expMaxNeg, expZeroIdx, expNegWidth, expTotalWidth, defaultExpBW);
|
||||
expect(parseFloat(resultString)).toBeCloseTo(expectedNumber, 1);
|
||||
});
|
||||
|
||||
it('returns 0% for exponential when calculation is negative (edge case)', () => {
|
||||
expect(findZeroAxisLeft('exponential', -10, 20, 1, -5, 1, -5, 15, 2)).toBe('0%');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('showZeroAxis', () => {
|
||||
it('returns true when axis is between 5% and 95%', () => {
|
||||
expect(showZeroAxis('5.01%')).toBe(true);
|
||||
expect(showZeroAxis('50%')).toBe(true);
|
||||
expect(showZeroAxis('94.99%')).toBe(true);
|
||||
});
|
||||
|
||||
it('returns false when axis is less than or equal to 5%', () => {
|
||||
expect(showZeroAxis('5%')).toBe(false);
|
||||
expect(showZeroAxis('0%')).toBe(false);
|
||||
expect(showZeroAxis('-10%')).toBe(false);
|
||||
});
|
||||
|
||||
it('returns false when axis is greater than or equal to 95%', () => {
|
||||
expect(showZeroAxis('95%')).toBe(false);
|
||||
expect(showZeroAxis('100%')).toBe(false);
|
||||
expect(showZeroAxis('120%')).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue