Skip to content

Commit 70eb582

Browse files
committed
added unit tests to HistogramHelpers.ts and HistogramChart.ts
Signed-off-by: amanycodes <amanycodes@gmail.com>
1 parent b4526c0 commit 70eb582

File tree

2 files changed

+554
-0
lines changed

2 files changed

+554
-0
lines changed
Lines changed: 243 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
import React from 'react';
2+
import { mount, ReactWrapper } from 'enzyme';
3+
import HistogramChart from './HistogramChart';
4+
import { Histogram } from '../../types/types';
5+
6+
const mockFormat = jest.fn((value) => value.toString());
7+
const mockResolvedOptions = jest.fn().mockReturnValue({ locale: 'en-US', numberingSystem: 'latn', style: 'decimal' });
8+
const mockFormatToParts = jest.fn();
9+
const mockFormatRange = jest.fn();
10+
const mockFormatRangeToParts = jest.fn();
11+
12+
jest.spyOn(global.Intl, 'NumberFormat').mockImplementation(() => ({
13+
format: mockFormat,
14+
resolvedOptions: mockResolvedOptions,
15+
formatToParts: mockFormatToParts,
16+
formatRange: mockFormatRange,
17+
formatRangeToParts: mockFormatRangeToParts,
18+
}));
19+
20+
describe('HistogramChart', () => {
21+
let wrapper: ReactWrapper;
22+
23+
const histogramDataLinear: Histogram = {
24+
count: '30',
25+
sum: '350',
26+
buckets: [
27+
[1678886400, '0', '10', '5'],
28+
[1678886400, '10', '20', '15'],
29+
[1678886400, '20', '30', '10'],
30+
],
31+
};
32+
33+
const histogramDataExponential: Histogram = {
34+
count: '140',
35+
sum: '...',
36+
buckets: [
37+
[1678886400, '-100', '-10', '20'],
38+
[1678886400, '-10', '-1', '30'],
39+
[1678886400, '1', '10', '50'],
40+
[1678886400, '10', '100', '40'],
41+
],
42+
};
43+
44+
const histogramDataZeroCrossing: Histogram = {
45+
count: '30',
46+
sum: '...',
47+
buckets: [
48+
[1678886400, '-5', '-1', '10'],
49+
[1678886400, '-1', '1', '5'],
50+
[1678886400, '1', '5', '15'],
51+
],
52+
};
53+
54+
const histogramDataEmpty: Histogram = {
55+
count: '0',
56+
sum: '0',
57+
buckets: [],
58+
};
59+
60+
const histogramDataNull: Histogram = {
61+
count: '0',
62+
sum: '0',
63+
buckets: null as any,
64+
};
65+
66+
const defaultProps = {
67+
index: 0,
68+
scale: 'linear' as 'linear' | 'exponential',
69+
};
70+
71+
72+
beforeEach(() => {
73+
mockFormat.mockClear();
74+
mockResolvedOptions.mockClear();
75+
mockFormatToParts.mockClear();
76+
mockFormatRange.mockClear();
77+
mockFormatRangeToParts.mockClear();
78+
});
79+
80+
afterEach(() => {
81+
if (wrapper && wrapper.exists()) {
82+
wrapper.unmount();
83+
}
84+
});
85+
86+
it('renders without crashing', () => {
87+
wrapper = mount(<HistogramChart {...defaultProps} histogram={histogramDataLinear} scale="linear" />);
88+
expect(wrapper.find('.histogram-y-wrapper').exists()).toBe(true);
89+
expect(wrapper.find('.histogram-container').exists()).toBe(true);
90+
});
91+
92+
it('renders "No data" when buckets are empty', () => {
93+
wrapper = mount(<HistogramChart {...defaultProps} histogram={histogramDataEmpty} scale="linear" />);
94+
expect(wrapper.text()).toContain('No data');
95+
expect(wrapper.find('.histogram-container').exists()).toBe(false);
96+
});
97+
98+
it('renders "No data" when buckets are null', () => {
99+
wrapper = mount(<HistogramChart {...defaultProps} histogram={histogramDataNull} scale="linear" />);
100+
expect(wrapper.text()).toContain('No data');
101+
expect(wrapper.find('.histogram-container').exists()).toBe(false);
102+
});
103+
104+
describe('Linear Scale', () => {
105+
beforeEach(() => {
106+
wrapper = mount(<HistogramChart {...defaultProps} histogram={histogramDataLinear} scale="linear" />);
107+
});
108+
109+
it('renders the correct number of buckets', () => {
110+
expect(wrapper.find('.histogram-bucket')).toHaveLength(histogramDataLinear.buckets!.length);
111+
});
112+
113+
it('renders y-axis labels and grid lines', () => {
114+
expect(wrapper.find('.histogram-y-label')).toHaveLength(5);
115+
expect(wrapper.find('.histogram-y-grid')).toHaveLength(5);
116+
expect(wrapper.find('.histogram-y-tick')).toHaveLength(5);
117+
expect(wrapper.find('.histogram-y-label').at(0).text()).toBe('');
118+
expect(wrapper.find('.histogram-y-label').last().text()).toBe('0');
119+
});
120+
121+
it('renders x-axis labels and grid lines', () => {
122+
expect(wrapper.find('.histogram-x-label')).toHaveLength(1);
123+
expect(wrapper.find('.histogram-x-grid')).toHaveLength(6);
124+
expect(wrapper.find('.histogram-x-tick')).toHaveLength(3);
125+
expect(mockFormat).toHaveBeenCalledWith(0);
126+
expect(mockFormat).toHaveBeenCalledWith(30);
127+
expect(wrapper.find('.histogram-x-label').text()).toContain('0');
128+
expect(wrapper.find('.histogram-x-label').text()).toContain('30');
129+
});
130+
131+
it('calculates bucket styles correctly for linear scale', () => {
132+
const buckets = wrapper.find('.histogram-bucket-slot');
133+
const rangeMin = 0;
134+
const rangeMax = 30;
135+
const rangeWidth = rangeMax - rangeMin;
136+
const fdMax = 1.5;
137+
138+
const b1 = buckets.at(0);
139+
const expectedB1LeftNum = ((0 - rangeMin) / rangeWidth) * 100;
140+
const expectedB1WidthNum = ((10 - 0) / rangeWidth) * 100;
141+
const expectedB1HeightNum = (0.5 / fdMax) * 100;
142+
expect(parseFloat(b1.prop('style')?.left as string)).toBeCloseTo(expectedB1LeftNum, 1);
143+
expect(parseFloat(b1.prop('style')?.width as string)).toBeCloseTo(expectedB1WidthNum, 1);
144+
expect(parseFloat(b1.find('.histogram-bucket').prop('style')?.height as string)).toBeCloseTo(expectedB1HeightNum, 1);
145+
146+
const b2 = buckets.at(1);
147+
const expectedB2LeftNum = ((10 - rangeMin) / rangeWidth) * 100;
148+
const expectedB2WidthNum = ((20 - 10) / rangeWidth) * 100;
149+
const expectedB2HeightNum = (1.5 / fdMax) * 100;
150+
expect(parseFloat(b2.prop('style')?.left as string)).toBeCloseTo(expectedB2LeftNum, 1);
151+
expect(parseFloat(b2.prop('style')?.width as string)).toBeCloseTo(expectedB2WidthNum, 1);
152+
expect(parseFloat(b2.find('.histogram-bucket').prop('style')?.height as string)).toBeCloseTo(expectedB2HeightNum, 1);
153+
154+
const b3 = buckets.at(2);
155+
const expectedB3LeftNum = ((20 - rangeMin) / rangeWidth) * 100;
156+
const expectedB3WidthNum = ((30 - 20) / rangeWidth) * 100;
157+
const expectedB3HeightNum = (1.0 / fdMax) * 100;
158+
expect(parseFloat(b3.prop('style')?.left as string)).toBeCloseTo(expectedB3LeftNum, 1);
159+
expect(parseFloat(b3.prop('style')?.width as string)).toBeCloseTo(expectedB3WidthNum, 1);
160+
expect(parseFloat(b3.find('.histogram-bucket').prop('style')?.height as string)).toBeCloseTo(expectedB3HeightNum, 1);
161+
});
162+
});
163+
164+
describe('Exponential Scale', () => {
165+
beforeEach(() => {
166+
wrapper = mount(<HistogramChart {...defaultProps} index={1} histogram={histogramDataExponential} scale="exponential" />);
167+
});
168+
169+
it('renders the correct number of buckets', () => {
170+
expect(wrapper.find('.histogram-bucket')).toHaveLength(histogramDataExponential.buckets!.length);
171+
});
172+
173+
it('renders y-axis labels and grid lines with formatting', () => {
174+
expect(wrapper.find('.histogram-y-label')).toHaveLength(5);
175+
expect(wrapper.find('.histogram-y-grid')).toHaveLength(5);
176+
expect(wrapper.find('.histogram-y-tick')).toHaveLength(5);
177+
178+
const countMax = 50;
179+
expect(mockFormat).toHaveBeenCalledWith(countMax * 1);
180+
expect(mockFormat).toHaveBeenCalledWith(countMax * 0.75);
181+
expect(mockFormat).toHaveBeenCalledWith(countMax * 0.5);
182+
expect(mockFormat).toHaveBeenCalledWith(countMax * 0.25);
183+
184+
expect(wrapper.find('.histogram-y-label').at(0).text()).toBe('50');
185+
expect(wrapper.find('.histogram-y-label').at(1).text()).toBe('37.5');
186+
expect(wrapper.find('.histogram-y-label').last().text()).toBe('0');
187+
});
188+
189+
it('renders x-axis labels and grid lines with formatting', () => {
190+
expect(wrapper.find('.histogram-x-label')).toHaveLength(1);
191+
expect(wrapper.find('.histogram-x-grid')).toHaveLength(6);
192+
expect(wrapper.find('.histogram-x-tick')).toHaveLength(3);
193+
194+
expect(mockFormat).toHaveBeenCalledWith(-100);
195+
expect(mockFormat).toHaveBeenCalledWith(100);
196+
expect(wrapper.find('.histogram-x-label').text()).toContain('0');
197+
expect(wrapper.find('.histogram-x-label').text()).toContain('-100');
198+
expect(wrapper.find('.histogram-x-label').text()).toContain('100');
199+
});
200+
201+
it('calculates bucket styles correctly for exponential scale', () => {
202+
const buckets = wrapper.find('.histogram-bucket-slot');
203+
const countMax = 50;
204+
205+
const b1 = buckets.at(0);
206+
const b1Height = (20 / countMax) * 100;
207+
expect(b1.find('.histogram-bucket').prop('style')).toHaveProperty('height', `${b1Height}%`);
208+
expect(parseFloat(b1.prop('style')?.left as string)).toBeGreaterThanOrEqual(0);
209+
expect(parseFloat(b1.prop('style')?.width as string)).toBeGreaterThan(0);
210+
211+
const b2 = buckets.at(1);
212+
const b2Height = (30 / countMax) * 100;
213+
expect(b2.find('.histogram-bucket').prop('style')).toHaveProperty('height', `${b2Height}%`);
214+
expect(parseFloat(b2.prop('style')?.left as string)).toBeGreaterThan(0);
215+
expect(parseFloat(b2.prop('style')?.width as string)).toBeGreaterThan(0);
216+
217+
const b3 = buckets.at(2);
218+
const b3Height = (50 / countMax) * 100;
219+
expect(b3.find('.histogram-bucket').prop('style')).toHaveProperty('height', '100%');
220+
expect(parseFloat(b3.prop('style')?.left as string)).toBeGreaterThan(0);
221+
expect(parseFloat(b3.prop('style')?.width as string)).toBeGreaterThan(0);
222+
223+
const b4 = buckets.at(3);
224+
const b4Height = (40 / countMax) * 100;
225+
expect(b4.find('.histogram-bucket').prop('style')).toHaveProperty('height', `${b4Height}%`);
226+
expect(parseFloat(b4.prop('style')?.left as string)).toBeGreaterThan(0);
227+
expect(parseFloat(b4.prop('style')?.width as string)).toBeGreaterThan(0);
228+
expect(parseFloat(b4.prop('style')?.left as string) + parseFloat(b4.prop('style')?.width as string)).toBeLessThanOrEqual(100.01);
229+
});
230+
231+
it('handles zero-crossing bucket correctly in exponential scale', () => {
232+
wrapper = mount(<HistogramChart {...defaultProps} index={2} histogram={histogramDataZeroCrossing} scale="exponential" />);
233+
const buckets = wrapper.find('.histogram-bucket-slot');
234+
const countMax = 15;
235+
236+
const b2 = buckets.at(1);
237+
const b2Height = (5 / countMax) * 100;
238+
expect(b2.find('.histogram-bucket').prop('style')).toHaveProperty('height', expect.stringContaining(b2Height.toFixed(1)));
239+
expect(parseFloat(b2.prop('style')?.left as string)).toBeGreaterThanOrEqual(0);
240+
expect(parseFloat(b2.prop('style')?.width as string)).toBeGreaterThan(0);
241+
});
242+
});
243+
});

0 commit comments

Comments
 (0)