Skip to content

Commit d69361a

Browse files
kris70lesgokasya
andauthored
Add comprehensive tests for LineChart component (#2068)
* Add comprehensive tests for LineChart component * Fix make check issues and tests --------- Co-authored-by: Kate Golovanova <[email protected]>
1 parent c08611c commit d69361a

File tree

1 file changed

+343
-0
lines changed

1 file changed

+343
-0
lines changed
Lines changed: 343 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,343 @@
1+
import { render, screen } from '@testing-library/react'
2+
import { useTheme } from 'next-themes'
3+
import type { ApexLineChartSeries } from 'types/healthMetrics'
4+
import LineChart from 'components/LineChart'
5+
6+
// Mock dependencies
7+
jest.mock('next-themes', () => ({
8+
useTheme: jest.fn(),
9+
}))
10+
11+
// Store the formatter function globally so tests can access it
12+
let mockFormatter: ((value: number) => string) | null = null
13+
14+
jest.mock('next/dynamic', () => {
15+
return jest.fn().mockImplementation(() => {
16+
const MockChart = ({ options, series, height }) => {
17+
// Store the formatter function globally for tests to access
18+
if (options.yaxis?.labels?.formatter) {
19+
mockFormatter = options.yaxis.labels.formatter
20+
}
21+
22+
return (
23+
<div
24+
data-testid="mock-chart"
25+
data-options={JSON.stringify({
26+
...options,
27+
yaxis: {
28+
...options.yaxis,
29+
labels: {
30+
...options.yaxis?.labels,
31+
// Don't serialize the function, just indicate it exists
32+
formatter: 'function',
33+
},
34+
},
35+
})}
36+
data-series={JSON.stringify(series)}
37+
data-height={height}
38+
/>
39+
)
40+
}
41+
return MockChart
42+
})
43+
})
44+
45+
jest.mock('components/AnchorTitle', () => {
46+
return jest.fn().mockImplementation(({ title }) => <div data-testid="anchor-title">{title}</div>)
47+
})
48+
49+
jest.mock('components/SecondaryCard', () => {
50+
return jest.fn().mockImplementation(({ title, children }) => (
51+
<div data-testid="secondary-card">
52+
<div data-testid="card-title">{title}</div>
53+
<div data-testid="card-content">{children}</div>
54+
</div>
55+
))
56+
})
57+
58+
describe('LineChart', () => {
59+
const mockUseTheme = useTheme as jest.MockedFunction<typeof useTheme>
60+
61+
const defaultProps = {
62+
title: 'Test Chart',
63+
series: [
64+
{
65+
name: 'Test Series',
66+
data: [10, 20, 30, 40, 50],
67+
},
68+
] as ApexLineChartSeries[],
69+
}
70+
71+
beforeEach(() => {
72+
mockUseTheme.mockReturnValue({
73+
theme: 'light',
74+
setTheme: jest.fn(),
75+
resolvedTheme: 'light',
76+
themes: ['light', 'dark'],
77+
systemTheme: 'light',
78+
})
79+
// Reset the mock formatter
80+
mockFormatter = null
81+
})
82+
83+
afterEach(() => {
84+
jest.clearAllMocks()
85+
})
86+
87+
describe('Renders successfully with minimal required props', () => {
88+
it('renders with title and series only', () => {
89+
render(<LineChart {...defaultProps} />)
90+
91+
expect(screen.getByTestId('secondary-card')).toBeInTheDocument()
92+
expect(screen.getByTestId('anchor-title')).toBeInTheDocument()
93+
expect(screen.getByTestId('mock-chart')).toBeInTheDocument()
94+
expect(screen.getByText('Test Chart')).toBeInTheDocument()
95+
})
96+
})
97+
98+
describe('Prop-based behavior', () => {
99+
it('passes title to AnchorTitle component', () => {
100+
render(<LineChart {...defaultProps} title="Custom Title" />)
101+
102+
expect(screen.getByText('Custom Title')).toBeInTheDocument()
103+
})
104+
105+
it('passes icon to SecondaryCard when provided', () => {
106+
const iconProp = 'chart-line'
107+
render(<LineChart {...defaultProps} icon={iconProp} />)
108+
109+
const secondaryCard = screen.getByTestId('secondary-card')
110+
expect(secondaryCard).toBeInTheDocument()
111+
})
112+
113+
it('renders without icon when not provided', () => {
114+
render(<LineChart {...defaultProps} />)
115+
116+
const secondaryCard = screen.getByTestId('secondary-card')
117+
expect(secondaryCard).toBeInTheDocument()
118+
})
119+
120+
it('passes series data to Chart component', () => {
121+
const testSeries = [
122+
{ name: 'Series 1', data: [1, 2, 3] },
123+
{ name: 'Series 2', data: [4, 5, 6] },
124+
] as ApexLineChartSeries[]
125+
126+
render(<LineChart {...defaultProps} series={testSeries} />)
127+
128+
const chart = screen.getByTestId('mock-chart')
129+
expect(chart).toHaveAttribute('data-series', JSON.stringify(testSeries))
130+
})
131+
132+
it('passes labels to chart options when provided', () => {
133+
const labels = ['Jan', 'Feb', 'Mar']
134+
render(<LineChart {...defaultProps} labels={labels} />)
135+
136+
const chart = screen.getByTestId('mock-chart')
137+
const options = JSON.parse(chart.getAttribute('data-options') || '{}')
138+
expect(options.xaxis.categories).toEqual(labels)
139+
})
140+
141+
it('does not set categories when labels not provided', () => {
142+
render(<LineChart {...defaultProps} />)
143+
144+
const chart = screen.getByTestId('mock-chart')
145+
const options = JSON.parse(chart.getAttribute('data-options') || '{}')
146+
expect(options.xaxis.categories).toBeUndefined()
147+
})
148+
})
149+
150+
describe('Theme-based behavior', () => {
151+
it('uses light theme colors when theme is light', () => {
152+
mockUseTheme.mockReturnValue({
153+
theme: 'light',
154+
setTheme: jest.fn(),
155+
resolvedTheme: 'light',
156+
themes: ['light', 'dark'],
157+
systemTheme: 'light',
158+
})
159+
160+
render(<LineChart {...defaultProps} />)
161+
162+
const chart = screen.getByTestId('mock-chart')
163+
const options = JSON.parse(chart.getAttribute('data-options') || '{}')
164+
expect(options.chart.foreColor).toBe('#1E1E2C')
165+
expect(options.tooltip.theme).toBe('light')
166+
})
167+
168+
it('uses dark theme colors when theme is dark', () => {
169+
mockUseTheme.mockReturnValue({
170+
theme: 'dark',
171+
setTheme: jest.fn(),
172+
resolvedTheme: 'dark',
173+
themes: ['light', 'dark'],
174+
systemTheme: 'dark',
175+
})
176+
177+
render(<LineChart {...defaultProps} />)
178+
179+
const chart = screen.getByTestId('mock-chart')
180+
const options = JSON.parse(chart.getAttribute('data-options') || '{}')
181+
expect(options.chart.foreColor).toBe('#ECECEC')
182+
expect(options.tooltip.theme).toBe('dark')
183+
})
184+
})
185+
186+
describe('Chart configuration', () => {
187+
it('sets fixed height to 200', () => {
188+
render(<LineChart {...defaultProps} />)
189+
190+
const chart = screen.getByTestId('mock-chart')
191+
expect(chart).toHaveAttribute('data-height', '200')
192+
})
193+
194+
it('configures chart options correctly', () => {
195+
render(<LineChart {...defaultProps} />)
196+
197+
const chart = screen.getByTestId('mock-chart')
198+
const options = JSON.parse(chart.getAttribute('data-options') || '{}')
199+
200+
expect(options.chart.toolbar.show).toBe(false)
201+
expect(options.xaxis.tickAmount).toBe(10)
202+
expect(options.stroke.curve).toBe('smooth')
203+
})
204+
205+
it('includes yaxis formatter function', () => {
206+
render(<LineChart {...defaultProps} />)
207+
208+
const chart = screen.getByTestId('mock-chart')
209+
const options = JSON.parse(chart.getAttribute('data-options') || '{}')
210+
211+
expect(options.yaxis.labels.formatter).toBe('function')
212+
expect(mockFormatter).toBeDefined()
213+
expect(typeof mockFormatter).toBe('function')
214+
})
215+
})
216+
217+
describe('Y-axis formatter logic', () => {
218+
it('formats values >= 1000 with K suffix', () => {
219+
render(<LineChart {...defaultProps} />)
220+
221+
expect(mockFormatter).toBeDefined()
222+
expect(mockFormatter(1000)).toBe('1.0K')
223+
expect(mockFormatter(1500)).toBe('1.5K')
224+
expect(mockFormatter(10000)).toBe('10.0K')
225+
})
226+
227+
it('formats values < 1000 with 2 decimal places by default', () => {
228+
render(<LineChart {...defaultProps} />)
229+
230+
expect(mockFormatter).toBeDefined()
231+
expect(mockFormatter(999)).toBe('999.00')
232+
expect(mockFormatter(50.5)).toBe('50.50')
233+
expect(mockFormatter(0)).toBe('0.00')
234+
})
235+
236+
it('formats values < 1000 with 0 decimal places when round is true', () => {
237+
render(<LineChart {...defaultProps} round={true} />)
238+
239+
expect(mockFormatter).toBeDefined()
240+
expect(mockFormatter(999)).toBe('999')
241+
expect(mockFormatter(50.5)).toBe('51')
242+
expect(mockFormatter(0.7)).toBe('1')
243+
})
244+
})
245+
246+
describe('Default values and fallbacks', () => {
247+
it('works without optional labels prop', () => {
248+
render(<LineChart title="Test" series={defaultProps.series} />)
249+
250+
expect(screen.getByTestId('mock-chart')).toBeInTheDocument()
251+
})
252+
253+
it('works without optional icon prop', () => {
254+
render(<LineChart title="Test" series={defaultProps.series} />)
255+
256+
expect(screen.getByTestId('secondary-card')).toBeInTheDocument()
257+
})
258+
259+
it('works without optional round prop (defaults to false)', () => {
260+
render(<LineChart title="Test" series={defaultProps.series} />)
261+
262+
expect(mockFormatter).toBeDefined()
263+
expect(mockFormatter(50.5)).toBe('50.50') // 2 decimal places when round is falsy
264+
})
265+
})
266+
267+
describe('Edge cases and invalid inputs', () => {
268+
it('handles empty series array', () => {
269+
render(<LineChart title="Test" series={[]} />)
270+
271+
const chart = screen.getByTestId('mock-chart')
272+
expect(chart).toHaveAttribute('data-series', '[]')
273+
})
274+
275+
it('handles empty labels array', () => {
276+
render(<LineChart {...defaultProps} labels={[]} />)
277+
278+
const chart = screen.getByTestId('mock-chart')
279+
const options = JSON.parse(chart.getAttribute('data-options') || '{}')
280+
expect(options.xaxis.categories).toEqual([])
281+
})
282+
283+
it('handles undefined theme gracefully', () => {
284+
mockUseTheme.mockReturnValue({
285+
theme: undefined,
286+
setTheme: jest.fn(),
287+
resolvedTheme: undefined,
288+
themes: ['light', 'dark'],
289+
systemTheme: 'light',
290+
})
291+
292+
render(<LineChart {...defaultProps} />)
293+
294+
const chart = screen.getByTestId('mock-chart')
295+
const options = JSON.parse(chart.getAttribute('data-options') || '{}')
296+
// Should default to light theme color when theme is undefined
297+
expect(options.chart.foreColor).toBe('#1E1E2C')
298+
})
299+
})
300+
301+
describe('DOM structure and accessibility', () => {
302+
it('renders SecondaryCard as container', () => {
303+
render(<LineChart {...defaultProps} />)
304+
305+
expect(screen.getByTestId('secondary-card')).toBeInTheDocument()
306+
})
307+
308+
it('renders AnchorTitle with proper title', () => {
309+
render(<LineChart {...defaultProps} />)
310+
311+
expect(screen.getByTestId('anchor-title')).toBeInTheDocument()
312+
expect(screen.getByText('Test Chart')).toBeInTheDocument()
313+
})
314+
315+
it('renders chart within card content', () => {
316+
render(<LineChart {...defaultProps} />)
317+
318+
const cardContent = screen.getByTestId('card-content')
319+
const chart = screen.getByTestId('mock-chart')
320+
321+
expect(cardContent).toContainElement(chart)
322+
})
323+
})
324+
325+
describe('Component integration', () => {
326+
it('uses theme key for Chart component to handle theme changes', () => {
327+
const { rerender } = render(<LineChart {...defaultProps} />)
328+
329+
mockUseTheme.mockReturnValue({
330+
theme: 'dark',
331+
setTheme: jest.fn(),
332+
resolvedTheme: 'dark',
333+
themes: ['light', 'dark'],
334+
systemTheme: 'dark',
335+
})
336+
337+
rerender(<LineChart {...defaultProps} />)
338+
339+
// Chart should re-render with new theme-based key
340+
expect(screen.getByTestId('mock-chart')).toBeInTheDocument()
341+
})
342+
})
343+
})

0 commit comments

Comments
 (0)