GrafanaUI: Added Unit Tests to Components (#105302)

* GrafanaUI: Added Unit Tests to Components

* Updates per feedback / lint fix
This commit is contained in:
Collin Fingar
2025-05-13 11:11:29 -04:00
committed by GitHub
parent 9fe523b9e6
commit 56d67f9ffc
4 changed files with 260 additions and 0 deletions
@@ -0,0 +1,101 @@
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { IconName } from '@grafana/data';
import { TabbedContainer } from './TabbedContainer';
const icon: IconName = 'info-circle';
const icon2: IconName = 'user';
const icon3: IconName = 'cog';
const mockTabs = [
{
label: 'Tab 1',
value: 'tab1',
content: <div data-testid="tab1-content">Tab 1 Content</div>,
icon: icon,
},
{
label: 'Tab 2',
value: 'tab2',
content: <div data-testid="tab2-content">Tab 2 Content</div>,
icon: icon2,
},
{
label: 'Tab 3',
value: 'tab3',
content: <div data-testid="tab3-content">Tab 3 Content</div>,
icon: icon3,
},
];
const mockOnClose = jest.fn();
const defaultProps = {
tabs: mockTabs,
onClose: mockOnClose,
};
const setup = (jsx: JSX.Element) => {
return {
user: userEvent.setup(),
...render(jsx),
};
};
describe('TabbedContainer', () => {
beforeEach(() => {
jest.clearAllMocks();
});
it('should render all tabs with their labels and icons', () => {
render(<TabbedContainer {...defaultProps} />);
mockTabs.forEach((tab) => {
expect(screen.getByText(tab.label)).toBeInTheDocument();
expect(screen.getByText(tab.label).closest('button')?.querySelector('svg')).toBeInTheDocument();
});
});
it('should render the first tab content by default', () => {
render(<TabbedContainer {...defaultProps} />);
expect(screen.getByTestId('tab1-content')).toBeInTheDocument();
expect(screen.queryByTestId('tab2-content')).not.toBeInTheDocument();
expect(screen.queryByTestId('tab3-content')).not.toBeInTheDocument();
});
it('should render the default tab content when specified', () => {
render(<TabbedContainer {...defaultProps} defaultTab="tab2" />);
expect(screen.queryByTestId('tab1-content')).not.toBeInTheDocument();
expect(screen.getByTestId('tab2-content')).toBeInTheDocument();
expect(screen.queryByTestId('tab3-content')).not.toBeInTheDocument();
});
it('should switch content when clicking on different tabs', async () => {
const { user } = setup(<TabbedContainer {...defaultProps} />);
expect(screen.getByTestId('tab1-content')).toBeInTheDocument();
await user.click(screen.getByText('Tab 2'));
expect(screen.queryByTestId('tab1-content')).not.toBeInTheDocument();
expect(screen.getByTestId('tab2-content')).toBeInTheDocument();
await user.click(screen.getByText('Tab 3'));
expect(screen.queryByTestId('tab1-content')).not.toBeInTheDocument();
expect(screen.queryByTestId('tab2-content')).not.toBeInTheDocument();
expect(screen.getByTestId('tab3-content')).toBeInTheDocument();
});
it('should call onClose when close button is clicked', async () => {
const { user } = setup(<TabbedContainer {...defaultProps} />);
const closeButton = screen.getByRole('button', { name: /close/i });
await user.click(closeButton);
expect(mockOnClose).toHaveBeenCalledTimes(1);
});
});
@@ -0,0 +1,50 @@
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { Tag } from './Tag';
const setup = (jsx: JSX.Element) => {
return {
user: userEvent.setup(),
...render(jsx),
};
};
const mockOnClick = jest.fn();
describe('Tag', () => {
beforeEach(() => {
jest.clearAllMocks();
});
it('should render with icon when provided', () => {
render(<Tag name="test-tag" icon="info-circle" />);
const tag = screen.getByText('test-tag');
expect(tag).toBeInTheDocument();
expect(tag.parentElement?.querySelector('svg')).toBeInTheDocument();
});
it('should handle click events', async () => {
const { user } = setup(<Tag name="test-tag" onClick={mockOnClick} />);
const tag = screen.getByText('test-tag');
await user.click(tag);
expect(mockOnClick).toHaveBeenCalledWith('test-tag', expect.any(Object));
});
it('should render as button when onClick is provided', () => {
render(<Tag name="test-tag" onClick={mockOnClick} />);
const tag = screen.getByRole('button', { name: 'test-tag' });
expect(tag).toBeInTheDocument();
});
it('should not render as button when onClick is not provided', () => {
render(<Tag name="test-tag" />);
const tag = screen.queryByRole('button', { name: 'test-tag' });
expect(tag).not.toBeInTheDocument();
});
});
@@ -0,0 +1,55 @@
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { TagList } from './TagList';
const setup = (jsx: JSX.Element) => {
return {
user: userEvent.setup(),
...render(jsx),
};
};
const mockTags = ['tag1', 'tag2', 'tag3', 'tag4', 'tag5'];
const mockOnClick = jest.fn();
describe('TagList', () => {
beforeEach(() => {
jest.clearAllMocks();
});
it('should render all tags when displayMax is not provided', () => {
render(<TagList tags={mockTags} />);
mockTags.forEach((tag) => {
expect(screen.getByText(tag)).toBeInTheDocument();
});
});
it('should limit displayed tags when displayMax is provided', () => {
const displayMax = 3;
render(<TagList tags={mockTags} displayMax={displayMax} />);
for (let i = 0; i < displayMax; i++) {
expect(screen.getByText(mockTags[i])).toBeInTheDocument();
}
expect(screen.getByText(`+ ${mockTags.length - displayMax}`)).toBeInTheDocument();
});
it('should handle tag clicks correctly', async () => {
const { user } = setup(<TagList tags={mockTags} onClick={mockOnClick} />);
const firstTag = screen.getByText(mockTags[0]);
await user.click(firstTag);
expect(mockOnClick).toHaveBeenCalledWith(mockTags[0], expect.any(Object));
});
it('should apply custom aria labels when provided', () => {
const getAriaLabel = (name: string, index: number) => `Custom label for ${name} at ${index}`;
render(<TagList tags={mockTags} getAriaLabel={getAriaLabel} />);
const firstTag = screen.getByLabelText('Custom label for tag1 at 0');
expect(firstTag).toBeInTheDocument();
});
});
@@ -0,0 +1,54 @@
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { ValuePicker } from './ValuePicker';
const mockOptions = [
{ label: 'Option 1', value: 'opt1' },
{ label: 'Option 2', value: 'opt2' },
{ label: 'Option 3', value: 'opt3' },
];
const mockOnChange = jest.fn();
const defaultProps = {
label: 'Add Option',
options: mockOptions,
onChange: mockOnChange,
};
const setup = (jsx: JSX.Element) => {
return {
user: userEvent.setup(),
...render(jsx),
};
};
describe('ValuePicker', () => {
beforeEach(() => {
jest.clearAllMocks();
});
it('should switch to select view when button is clicked', async () => {
const { user } = setup(<ValuePicker {...defaultProps} />);
expect(screen.getByRole('button')).toBeInTheDocument();
expect(screen.queryByRole('combobox')).not.toBeInTheDocument();
await user.click(screen.getByRole('button'));
expect(screen.queryByRole('button')).not.toBeInTheDocument();
expect(screen.getByRole('combobox')).toBeInTheDocument();
});
it('should call onChange and switch back to button view when option is selected', async () => {
const { user } = setup(<ValuePicker {...defaultProps} />);
await user.click(screen.getByRole('button'));
await user.keyboard('{ArrowDown}{Enter}');
expect(mockOnChange).toHaveBeenCalledWith(mockOptions[0]);
expect(screen.getByRole('button')).toBeInTheDocument();
expect(screen.queryByRole('combobox')).not.toBeInTheDocument();
});
});