diff --git a/eslint.config.js b/eslint.config.js
index 285ca1abb30..2860db1a2c7 100644
--- a/eslint.config.js
+++ b/eslint.config.js
@@ -299,7 +299,7 @@ module.exports = [
},
},
{
- name: 'grafana/alerting-test-overrides',
+ name: 'grafana/tests',
plugins: {
'testing-library': testingLibraryPlugin,
'jest-dom': jestDomPlugin,
@@ -307,12 +307,25 @@ module.exports = [
files: [
'public/app/features/alerting/**/__tests__/**/*.[jt]s?(x)',
'public/app/features/alerting/**/?(*.)+(spec|test).[jt]s?(x)',
+ 'packages/grafana-ui/**/*.{spec,test}.{ts,tsx}',
],
rules: {
...testingLibraryPlugin.configs['flat/react'].rules,
...jestDomPlugin.configs['flat/recommended'].rules,
'testing-library/prefer-user-event': 'error',
- 'jest/expect-expect': ['error', { assertFunctionNames: ['expect*', 'reducerTester'] }],
+ 'jest/expect-expect': ['error', { assertFunctionNames: ['expect*', 'assert*', 'reducerTester'] }],
+ },
+ },
+ {
+ name: 'grafana/test-overrides-to-fix',
+ plugins: {
+ 'testing-library': testingLibraryPlugin,
+ },
+ files: ['packages/grafana-ui/**/*.{spec,test}.{ts,tsx}'],
+ rules: {
+ // grafana-ui has lots of violations of direct node access and container methods, so disabling for now
+ 'testing-library/no-node-access': 'off',
+ 'testing-library/no-container': 'off',
},
},
{
diff --git a/packages/grafana-ui/src/components/BigValue/BigValue.test.tsx b/packages/grafana-ui/src/components/BigValue/BigValue.test.tsx
index e7819cd110d..d69bc2d2635 100644
--- a/packages/grafana-ui/src/components/BigValue/BigValue.test.tsx
+++ b/packages/grafana-ui/src/components/BigValue/BigValue.test.tsx
@@ -46,7 +46,7 @@ describe('BigValue', () => {
it('should render without percent change', () => {
render();
- expect(screen.queryByText('%')).toBeNull();
+ expect(screen.queryByText('%')).not.toBeInTheDocument();
});
});
});
diff --git a/packages/grafana-ui/src/components/Card/Card.test.tsx b/packages/grafana-ui/src/components/Card/Card.test.tsx
index 86e81c784aa..a602ead887f 100644
--- a/packages/grafana-ui/src/components/Card/Card.test.tsx
+++ b/packages/grafana-ui/src/components/Card/Card.test.tsx
@@ -1,4 +1,5 @@
-import { render, screen, fireEvent } from '@testing-library/react';
+import { render, screen } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
import { Button } from '../Button';
import { IconButton } from '../IconButton/IconButton';
@@ -6,15 +7,16 @@ import { IconButton } from '../IconButton/IconButton';
import { Card } from './Card';
describe('Card', () => {
- it('should execute callback when clicked', () => {
+ it('should execute callback when clicked', async () => {
+ const user = userEvent.setup();
const callback = jest.fn();
render(
Test Heading
);
- fireEvent.click(screen.getByText('Test Heading'));
- expect(callback).toBeCalledTimes(1);
+ await user.click(screen.getByText('Test Heading'));
+ expect(callback).toHaveBeenCalledTimes(1);
});
describe('Card Actions', () => {
@@ -31,8 +33,8 @@ describe('Card', () => {
);
- expect(screen.getByRole('button', { name: 'Click Me' })).not.toBeDisabled();
- expect(screen.getByRole('button', { name: 'Delete' })).not.toBeDisabled();
+ expect(screen.getByRole('button', { name: 'Click Me' })).toBeEnabled();
+ expect(screen.getByRole('button', { name: 'Delete' })).toBeEnabled();
rerender(
@@ -78,8 +80,8 @@ describe('Card', () => {
);
- expect(screen.getByRole('button', { name: 'Click Me' })).not.toBeDisabled();
- expect(screen.getByRole('button', { name: 'Delete' })).not.toBeDisabled();
+ expect(screen.getByRole('button', { name: 'Click Me' })).toBeEnabled();
+ expect(screen.getByRole('button', { name: 'Delete' })).toBeEnabled();
});
it('Children should be conditional', () => {
@@ -97,7 +99,7 @@ describe('Card', () => {
);
- expect(screen.getByRole('button', { name: 'Click Me' })).not.toBeDisabled();
+ expect(screen.getByRole('button', { name: 'Click Me' })).toBeEnabled();
expect(screen.queryByRole('button', { name: 'Delete' })).not.toBeInTheDocument();
});
diff --git a/packages/grafana-ui/src/components/Carousel/Carousel.test.tsx b/packages/grafana-ui/src/components/Carousel/Carousel.test.tsx
index 0966fd029e9..1fba66ce5b3 100644
--- a/packages/grafana-ui/src/components/Carousel/Carousel.test.tsx
+++ b/packages/grafana-ui/src/components/Carousel/Carousel.test.tsx
@@ -111,13 +111,13 @@ describe('Carousel', () => {
await user.click(screen.getByText('Alert rule'));
- fireEvent.keyDown(screen.getByTestId('carousel-full-screen'), { key: 'ArrowRight' });
+ await user.keyboard('{ArrowRight}');
let previewImage = screen.getByTestId('carousel-full-image').querySelector('img');
expect(previewImage).toHaveAttribute('src', testImages[1].path);
- fireEvent.keyDown(screen.getByTestId('carousel-full-screen'), { key: 'ArrowLeft' });
- fireEvent.keyDown(screen.getByTestId('carousel-full-screen'), { key: 'ArrowLeft' });
+ await user.keyboard('{ArrowLeft}');
+ await user.keyboard('{ArrowLeft}');
previewImage = screen.getByTestId('carousel-full-image').querySelector('img');
expect(previewImage).toHaveAttribute('src', testImages[3].path);
@@ -129,7 +129,7 @@ describe('Carousel', () => {
await user.click(screen.getByText('Alert rule'));
expect(screen.getByTestId('carousel-full-screen')).toBeInTheDocument();
- fireEvent.keyDown(screen.getByTestId('carousel-full-screen'), { key: 'Escape' });
+ await user.keyboard('{Escape}');
expect(screen.queryByTestId('carousel-full-screen')).not.toBeInTheDocument();
});
diff --git a/packages/grafana-ui/src/components/ColorPicker/ColorPickerInput.test.tsx b/packages/grafana-ui/src/components/ColorPicker/ColorPickerInput.test.tsx
index 9f13e67918b..7a792920723 100644
--- a/packages/grafana-ui/src/components/ColorPicker/ColorPickerInput.test.tsx
+++ b/packages/grafana-ui/src/components/ColorPicker/ColorPickerInput.test.tsx
@@ -27,7 +27,7 @@ describe('ColorPickerInput', () => {
await userEvent.click(screen.getByRole('textbox'));
expect(screen.getByTestId('color-popover')).toBeInTheDocument();
await userEvent.click(screen.getAllByRole('slider')[0]);
- expect(screen.queryByTestId('color-popover')).toBeInTheDocument();
+ expect(screen.getByTestId('color-popover')).toBeInTheDocument();
});
it('should pass correct color to onChange callback', async () => {
diff --git a/packages/grafana-ui/src/components/Combobox/Combobox.test.tsx b/packages/grafana-ui/src/components/Combobox/Combobox.test.tsx
index 091e6fed227..537e26df1c9 100644
--- a/packages/grafana-ui/src/components/Combobox/Combobox.test.tsx
+++ b/packages/grafana-ui/src/components/Combobox/Combobox.test.tsx
@@ -30,6 +30,12 @@ const numericOptions: Array> = [
];
describe('Combobox', () => {
+ let user: ReturnType;
+
+ beforeEach(() => {
+ user = userEvent.setup({ applyAccept: false });
+ });
+
const onChangeHandler = jest.fn();
beforeAll(() => {
const mockGetBoundingClientRect = jest.fn(() => ({
@@ -116,13 +122,13 @@ describe('Combobox', () => {
await userEvent.keyboard('{ArrowDown}{ArrowDown}{Enter}'); // Focus is at index 0 to start with
expect(onChangeHandler).toHaveBeenCalledWith(options[2]);
- expect(screen.queryByDisplayValue('Option 3')).toBeInTheDocument();
+ expect(screen.getByDisplayValue('Option 3')).toBeInTheDocument();
});
it('clears selected value', async () => {
render();
- expect(screen.queryByDisplayValue('Option 2')).toBeInTheDocument();
+ expect(screen.getByDisplayValue('Option 2')).toBeInTheDocument();
const input = screen.getByRole('combobox');
await userEvent.click(input);
@@ -158,7 +164,7 @@ describe('Combobox', () => {
const input = screen.getByRole('combobox');
await userEvent.click(input);
await userEvent.click(screen.getByRole('option', { name: 'Default' }));
- expect(screen.queryByDisplayValue('Default')).toBeInTheDocument();
+ expect(screen.getByDisplayValue('Default')).toBeInTheDocument();
await userEvent.click(input);
@@ -252,6 +258,7 @@ describe('Combobox', () => {
});
describe('size support', () => {
+ // eslint-disable-next-line jest/expect-expect
it('should require minWidth to be set with auto width', () => {
// @ts-expect-error
render();
@@ -264,7 +271,7 @@ describe('Combobox', () => {
const inputWrapper = screen.getByTestId('input-wrapper');
const initialWidth = getComputedStyle(inputWrapper).width;
- fireEvent.change(input, { target: { value: 'very very long value' } });
+ await user.type(input, 'very very long value');
const newWidth = getComputedStyle(inputWrapper).width;
@@ -278,7 +285,7 @@ describe('Combobox', () => {
const inputWrapper = screen.getByTestId('input-wrapper');
const initialWidth = getComputedStyle(inputWrapper).width;
- fireEvent.change(input, { target: { value: 'very very long value' } });
+ await user.type(input, 'very very long value');
const newWidth = getComputedStyle(inputWrapper).width;
diff --git a/packages/grafana-ui/src/components/Combobox/MultiCombobox.test.tsx b/packages/grafana-ui/src/components/Combobox/MultiCombobox.test.tsx
index 5be7bc09098..f92371b6e3b 100644
--- a/packages/grafana-ui/src/components/Combobox/MultiCombobox.test.tsx
+++ b/packages/grafana-ui/src/components/Combobox/MultiCombobox.test.tsx
@@ -348,7 +348,7 @@ describe('MultiCombobox', () => {
jest.advanceTimersByTime(1500); // Resolve the first request, should be ignored
expect(screen.queryByRole('option', { name: 'first' })).not.toBeInTheDocument();
expect(screen.queryByRole('option', { name: 'second' })).not.toBeInTheDocument();
- expect(screen.queryByRole('option', { name: 'third' })).toBeInTheDocument();
+ expect(screen.getByRole('option', { name: 'third' })).toBeInTheDocument();
jest.clearAllTimers();
});
diff --git a/packages/grafana-ui/src/components/ConfirmButton/ConfirmButton.test.tsx b/packages/grafana-ui/src/components/ConfirmButton/ConfirmButton.test.tsx
index 98b0707a9c7..cd6f8596d0e 100644
--- a/packages/grafana-ui/src/components/ConfirmButton/ConfirmButton.test.tsx
+++ b/packages/grafana-ui/src/components/ConfirmButton/ConfirmButton.test.tsx
@@ -23,7 +23,7 @@ describe('ConfirmButton', () => {
expect(onConfirm).toHaveBeenCalled();
// Confirm button should be visible if closeOnConfirm is false
- expect(screen.queryByRole('button', { name: 'Confirm delete' })).toBeInTheDocument();
+ expect(screen.getByRole('button', { name: 'Confirm delete' })).toBeInTheDocument();
});
it('should hide confirm delete when closeOnConfirm is true', async () => {
diff --git a/packages/grafana-ui/src/components/ConfirmModal/ConfirmModal.test.tsx b/packages/grafana-ui/src/components/ConfirmModal/ConfirmModal.test.tsx
index a667bc2ee80..18fc8d5deb3 100644
--- a/packages/grafana-ui/src/components/ConfirmModal/ConfirmModal.test.tsx
+++ b/packages/grafana-ui/src/components/ConfirmModal/ConfirmModal.test.tsx
@@ -1,4 +1,4 @@
-import { act, fireEvent, render, screen, waitFor } from '@testing-library/react';
+import { act, render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { useState } from 'react';
@@ -207,7 +207,7 @@ describe('ConfirmModal', () => {
expect(confirmButton).toBeEnabled();
- fireEvent.click(confirmButton);
+ await user.click(confirmButton);
// Ensure React processes the state update and calls useEffect in ConfirmModal
await act(() => {
diff --git a/packages/grafana-ui/src/components/DataSourceSettings/AlertingSettings.test.tsx b/packages/grafana-ui/src/components/DataSourceSettings/AlertingSettings.test.tsx
index 4ceb92b0bbe..7dfa33b231c 100644
--- a/packages/grafana-ui/src/components/DataSourceSettings/AlertingSettings.test.tsx
+++ b/packages/grafana-ui/src/components/DataSourceSettings/AlertingSettings.test.tsx
@@ -38,7 +38,7 @@ describe('Alerting Settings', () => {
//see https://github.com/grafana/grafana/issues/51417
it('should not show the option to select alertmanager data sources', () => {
setup();
- expect(screen.queryByText('Alertmanager data source')).toBeNull();
+ expect(screen.queryByText('Alertmanager data source')).not.toBeInTheDocument();
});
it('should show the option to manager alerts', () => {
diff --git a/packages/grafana-ui/src/components/DataSourceSettings/CustomHeadersSettings.test.tsx b/packages/grafana-ui/src/components/DataSourceSettings/CustomHeadersSettings.test.tsx
index 007d74cbce6..828cf481ef7 100644
--- a/packages/grafana-ui/src/components/DataSourceSettings/CustomHeadersSettings.test.tsx
+++ b/packages/grafana-ui/src/components/DataSourceSettings/CustomHeadersSettings.test.tsx
@@ -66,7 +66,7 @@ describe('Render', () => {
setup();
const b = screen.getByRole('button', { name: 'Add header' });
expect(b).toBeInTheDocument();
- expect(b.getAttribute('type')).toBe('button');
+ expect(b).toHaveAttribute('type', 'button');
});
it('should remove a header', async () => {
diff --git a/packages/grafana-ui/src/components/DataSourceSettings/DataSourceHttpSettings.test.tsx b/packages/grafana-ui/src/components/DataSourceSettings/DataSourceHttpSettings.test.tsx
index bc73c393965..3b399650916 100644
--- a/packages/grafana-ui/src/components/DataSourceSettings/DataSourceHttpSettings.test.tsx
+++ b/packages/grafana-ui/src/components/DataSourceSettings/DataSourceHttpSettings.test.tsx
@@ -54,7 +54,7 @@ describe('DataSourceHttpSettings', () => {
it('should not render SIGV4 label if SIGV4 is not enabled', () => {
setup({ sigV4AuthToggleEnabled: false });
- expect(screen.queryByText('SigV4 auth')).toBeNull();
+ expect(screen.queryByText('SigV4 auth')).not.toBeInTheDocument();
});
it('should render SIGV4 editor if provided and SIGV4 is enabled', () => {
diff --git a/packages/grafana-ui/src/components/DateTimePickers/DatePicker/DatePicker.test.tsx b/packages/grafana-ui/src/components/DateTimePickers/DatePicker/DatePicker.test.tsx
index b0aeabb0e2e..7b46b665442 100644
--- a/packages/grafana-ui/src/components/DateTimePickers/DatePicker/DatePicker.test.tsx
+++ b/packages/grafana-ui/src/components/DateTimePickers/DatePicker/DatePicker.test.tsx
@@ -1,4 +1,5 @@
-import { fireEvent, render, screen } from '@testing-library/react';
+import { render, screen } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
import { DatePicker } from './DatePicker';
@@ -27,27 +28,29 @@ describe('DatePicker', () => {
expect(screen.getByText('December 2020')).toBeInTheDocument();
});
- it('calls onChange when date is selected', () => {
+ it('calls onChange when date is selected', async () => {
const onChange = jest.fn();
+ const user = userEvent.setup();
render();
expect(onChange).not.toHaveBeenCalled();
// clicking the date
- fireEvent.click(screen.getByText('14'));
+ await user.click(screen.getByText('14'));
expect(onChange).toHaveBeenCalledTimes(1);
});
- it('calls onClose when outside of wrapper is clicked', () => {
+ it('calls onClose when outside of wrapper is clicked', async () => {
const onClose = jest.fn();
+ const user = userEvent.setup();
render();
expect(onClose).not.toHaveBeenCalled();
- fireEvent.click(document);
+ await user.click(document.body);
expect(onClose).toHaveBeenCalledTimes(1);
});
diff --git a/packages/grafana-ui/src/components/DateTimePickers/DatePickerWithInput/DatePickerWithInput.test.tsx b/packages/grafana-ui/src/components/DateTimePickers/DatePickerWithInput/DatePickerWithInput.test.tsx
index 11c27cff02e..4dc244b9991 100644
--- a/packages/grafana-ui/src/components/DateTimePickers/DatePickerWithInput/DatePickerWithInput.test.tsx
+++ b/packages/grafana-ui/src/components/DateTimePickers/DatePickerWithInput/DatePickerWithInput.test.tsx
@@ -1,10 +1,17 @@
-import { fireEvent, render, screen } from '@testing-library/react';
+import { render, screen } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
import { dateTimeFormat } from '@grafana/data';
import { DatePickerWithInput } from './DatePickerWithInput';
describe('DatePickerWithInput', () => {
+ let user: ReturnType;
+
+ beforeEach(() => {
+ user = userEvent.setup({ applyAccept: false });
+ });
+
it('renders date input', () => {
render();
@@ -24,43 +31,42 @@ describe('DatePickerWithInput', () => {
});
describe('input is clicked', () => {
- it('renders input', () => {
+ it('renders input', async () => {
render();
-
- fireEvent.click(screen.getByPlaceholderText('Date'));
+ await user.click(screen.getByPlaceholderText('Date'));
expect(screen.getByPlaceholderText('Date')).toBeInTheDocument();
});
- it('renders calendar', () => {
+ it('renders calendar', async () => {
render();
- fireEvent.click(screen.getByPlaceholderText('Date'));
+ await user.click(screen.getByPlaceholderText('Date'));
- expect(screen.queryByTestId('date-picker')).toBeInTheDocument();
+ expect(screen.getByTestId('date-picker')).toBeInTheDocument();
});
});
- it('calls onChange after date is selected', () => {
+ it('calls onChange after date is selected', async () => {
const onChange = jest.fn();
render();
// open calendar and select a date
- fireEvent.click(screen.getByPlaceholderText('Date'));
- fireEvent.click(screen.getByText('14'));
+ await user.click(screen.getByPlaceholderText('Date'));
+ await user.click(screen.getByText('14'));
expect(onChange).toHaveBeenCalledTimes(1);
});
- it('closes calendar after outside wrapper is clicked', () => {
+ it('closes calendar after outside wrapper is clicked', async () => {
render();
// open calendar and click outside
- fireEvent.click(screen.getByPlaceholderText('Date'));
+ await user.click(screen.getByPlaceholderText('Date'));
expect(screen.getByTestId('date-picker')).toBeInTheDocument();
- fireEvent.click(document);
+ await user.click(document.body);
expect(screen.queryByTestId('date-picker')).not.toBeInTheDocument();
});
diff --git a/packages/grafana-ui/src/components/DateTimePickers/DateTimePicker/DateTimePicker.test.tsx b/packages/grafana-ui/src/components/DateTimePickers/DateTimePicker/DateTimePicker.test.tsx
index ca99959c7d0..ad5e60ce13a 100644
--- a/packages/grafana-ui/src/components/DateTimePickers/DateTimePicker/DateTimePicker.test.tsx
+++ b/packages/grafana-ui/src/components/DateTimePickers/DateTimePicker/DateTimePicker.test.tsx
@@ -30,7 +30,7 @@ describe('Date time picker', () => {
it('should render component', () => {
renderDatetimePicker();
- expect(screen.queryByTestId('date-time-picker')).toBeInTheDocument();
+ expect(screen.getByTestId('date-time-picker')).toBeInTheDocument();
});
it.each(TEST_TIMEZONES)('input should have a value (timezone: %s)', (timeZone) => {
diff --git a/packages/grafana-ui/src/components/DateTimePickers/RelativeTimeRangePicker/RelativeTimeRangePicker.test.tsx b/packages/grafana-ui/src/components/DateTimePickers/RelativeTimeRangePicker/RelativeTimeRangePicker.test.tsx
index 9d6a45c6f29..d88d4789ed4 100644
--- a/packages/grafana-ui/src/components/DateTimePickers/RelativeTimeRangePicker/RelativeTimeRangePicker.test.tsx
+++ b/packages/grafana-ui/src/components/DateTimePickers/RelativeTimeRangePicker/RelativeTimeRangePicker.test.tsx
@@ -1,4 +1,4 @@
-import { render, RenderResult } from '@testing-library/react';
+import { render, RenderResult, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { useState } from 'react';
@@ -17,34 +17,34 @@ function setup(initial: RelativeTimeRange = { from: 900, to: 0 }): RenderResult
describe('RelativeTimePicker', () => {
it('should render the picker button with an user friendly text', () => {
- const { getByText } = setup({ from: 900, to: 0 });
- expect(getByText('now-15m to now')).toBeInTheDocument();
+ setup({ from: 900, to: 0 });
+ expect(screen.getByText('now-15m to now')).toBeInTheDocument();
});
it('should open the picker when clicking the button', async () => {
- const { getByText } = setup({ from: 900, to: 0 });
+ setup({ from: 900, to: 0 });
- await userEvent.click(getByText('now-15m to now'));
+ await userEvent.click(screen.getByText('now-15m to now'));
- expect(getByText('Specify time range')).toBeInTheDocument();
- expect(getByText('Example time ranges')).toBeInTheDocument();
+ expect(screen.getByText('Specify time range')).toBeInTheDocument();
+ expect(screen.getByText('Example time ranges')).toBeInTheDocument();
});
it('should not have open picker without clicking the button', () => {
- const { queryByText } = setup({ from: 900, to: 0 });
- expect(queryByText('Specify time range')).toBeNull();
- expect(queryByText('Example time ranges')).toBeNull();
+ setup({ from: 900, to: 0 });
+ expect(screen.queryByText('Specify time range')).not.toBeInTheDocument();
+ expect(screen.queryByText('Example time ranges')).not.toBeInTheDocument();
});
it('should not be able to apply range via quick options', async () => {
- const { getByText, queryByText } = setup({ from: 900, to: 0 });
+ setup({ from: 900, to: 0 });
- await userEvent.click(getByText('now-15m to now')); // open the picker
- await userEvent.click(getByText('Last 30 minutes')); // select the quick range, should close picker.
+ await userEvent.click(screen.getByText('now-15m to now')); // open the picker
+ await userEvent.click(screen.getByText('Last 30 minutes')); // select the quick range, should close picker.
- expect(queryByText('Specify time range')).toBeNull();
- expect(queryByText('Example time ranges')).toBeNull();
+ expect(screen.queryByText('Specify time range')).not.toBeInTheDocument();
+ expect(screen.queryByText('Example time ranges')).not.toBeInTheDocument();
- expect(getByText('now-30m to now')).toBeInTheDocument(); // new text on picker button
+ expect(screen.getByText('now-30m to now')).toBeInTheDocument(); // new text on picker button
});
});
diff --git a/packages/grafana-ui/src/components/DateTimePickers/TimeRangeContext.test.tsx b/packages/grafana-ui/src/components/DateTimePickers/TimeRangeContext.test.tsx
index 4ef66005aa7..e10502e24c1 100644
--- a/packages/grafana-ui/src/components/DateTimePickers/TimeRangeContext.test.tsx
+++ b/packages/grafana-ui/src/components/DateTimePickers/TimeRangeContext.test.tsx
@@ -86,7 +86,7 @@ describe('TimeRangeProvider', () => {
context2 = val;
}
- const renderContext = render(
+ const { rerender } = render(
@@ -104,7 +104,7 @@ describe('TimeRangeProvider', () => {
syncedValue: timeRange,
});
- renderContext.rerender(
+ rerender(
diff --git a/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker.test.tsx b/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker.test.tsx
index 8fa3cb66231..0b2284e875f 100644
--- a/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker.test.tsx
+++ b/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker.test.tsx
@@ -20,7 +20,7 @@ const value: TimeRange = {
describe('TimePicker', () => {
it('renders buttons correctly', () => {
- const container = render(
+ render(
{}}
onChange={(value) => {}}
@@ -31,7 +31,7 @@ describe('TimePicker', () => {
/>
);
- expect(container.queryByLabelText(/Time range selected/i)).toBeInTheDocument();
+ expect(screen.getByLabelText(/Time range selected/i)).toBeInTheDocument();
});
it('switches overlay content visibility when toolbar button is clicked twice', async () => {
@@ -94,7 +94,7 @@ describe('TimePicker', () => {
it('does not submit wrapping forms', async () => {
const onSubmit = jest.fn();
- const container = render(
+ render(
);
- const clicks = container.getAllByRole('button').map((button) => userEvent.click(button));
+ const clicks = screen.getAllByRole('button').map((button) => userEvent.click(button));
await Promise.all(clicks);
diff --git a/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker/TimePickerContent.test.tsx b/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker/TimePickerContent.test.tsx
index 18fa0cf2f24..1ff6e8dee1f 100644
--- a/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker/TimePickerContent.test.tsx
+++ b/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker/TimePickerContent.test.tsx
@@ -15,16 +15,16 @@ describe('TimePickerContent', () => {
describe('Wide Screen', () => {
it('renders with history', () => {
renderComponent({ value: absoluteValue, history });
- expect(screen.queryByText(/recently used absolute ranges/i)).toBeInTheDocument();
- expect(screen.queryByText(/2019-12-17 07:48:27 to 2019-12-17 07:49:27/i)).toBeInTheDocument();
- expect(screen.queryByText(/2019-10-18 07:50:27 to 2019-10-18 07:51:27/i)).toBeInTheDocument();
+ expect(screen.getByText(/recently used absolute ranges/i)).toBeInTheDocument();
+ expect(screen.getByText(/2019-12-17 07:48:27 to 2019-12-17 07:49:27/i)).toBeInTheDocument();
+ expect(screen.getByText(/2019-10-18 07:50:27 to 2019-10-18 07:51:27/i)).toBeInTheDocument();
});
it('renders with empty history', () => {
renderComponent({ value: absoluteValue });
expect(screen.queryByText(/recently used absolute ranges/i)).not.toBeInTheDocument();
expect(
- screen.queryByText(
+ screen.getByText(
/it looks like you haven't used this time picker before\. as soon as you enter some time intervals, recently used intervals will appear here\./i
)
).toBeInTheDocument();
@@ -39,7 +39,7 @@ describe('TimePickerContent', () => {
it('renders with relative picker', () => {
renderComponent({ value: absoluteValue });
- expect(screen.queryByText(/Last 5 minutes/i)).toBeInTheDocument();
+ expect(screen.getByText(/Last 5 minutes/i)).toBeInTheDocument();
});
it('renders without relative picker', () => {
@@ -49,7 +49,7 @@ describe('TimePickerContent', () => {
it('renders with timezone picker', () => {
renderComponent({ value: absoluteValue, hideTimeZone: false });
- expect(screen.queryByText(/coordinated universal time/i)).toBeInTheDocument();
+ expect(screen.getByText(/coordinated universal time/i)).toBeInTheDocument();
});
it('renders without timezone picker', () => {
@@ -61,9 +61,9 @@ describe('TimePickerContent', () => {
describe('Narrow Screen', () => {
it('renders with history', () => {
renderComponent({ value: absoluteValue, history, isFullscreen: false });
- expect(screen.queryByText(/recently used absolute ranges/i)).toBeInTheDocument();
- expect(screen.queryByText(/2019-12-17 07:48:27 to 2019-12-17 07:49:27/i)).toBeInTheDocument();
- expect(screen.queryByText(/2019-10-18 07:50:27 to 2019-10-18 07:51:27/i)).toBeInTheDocument();
+ expect(screen.getByText(/recently used absolute ranges/i)).toBeInTheDocument();
+ expect(screen.getByText(/2019-12-17 07:48:27 to 2019-12-17 07:49:27/i)).toBeInTheDocument();
+ expect(screen.getByText(/2019-10-18 07:50:27 to 2019-10-18 07:51:27/i)).toBeInTheDocument();
});
it('renders with empty history', () => {
@@ -85,7 +85,7 @@ describe('TimePickerContent', () => {
it('renders with relative picker', () => {
renderComponent({ value: absoluteValue, isFullscreen: false });
- expect(screen.queryByText(/Last 5 minutes/i)).toBeInTheDocument();
+ expect(screen.getByText(/Last 5 minutes/i)).toBeInTheDocument();
});
it('renders without relative picker', () => {
@@ -95,12 +95,12 @@ describe('TimePickerContent', () => {
it('renders with absolute picker when absolute value and quick ranges are visible', () => {
renderComponent({ value: absoluteValue, isFullscreen: false });
- expect(screen.queryByLabelText('From')).toBeInTheDocument();
+ expect(screen.getByLabelText('From')).toBeInTheDocument();
});
it('renders with absolute picker when absolute value and quick ranges are hidden', () => {
renderComponent({ value: absoluteValue, isFullscreen: false, hideQuickRanges: true });
- expect(screen.queryByLabelText('From')).toBeInTheDocument();
+ expect(screen.getByLabelText('From')).toBeInTheDocument();
});
it('renders without absolute picker when narrow screen and quick ranges are visible', () => {
@@ -110,7 +110,7 @@ describe('TimePickerContent', () => {
it('renders with absolute picker when narrow screen and quick ranges are hidden', () => {
renderComponent({ value: relativeValue, isFullscreen: false, hideQuickRanges: true });
- expect(screen.queryByLabelText('From')).toBeInTheDocument();
+ expect(screen.getByLabelText('From')).toBeInTheDocument();
});
it('renders without timezone picker', () => {
diff --git a/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker/TimeRangeContent.test.tsx b/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker/TimeRangeContent.test.tsx
index 90777bfa3aa..7a130f08950 100644
--- a/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker/TimeRangeContent.test.tsx
+++ b/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker/TimeRangeContent.test.tsx
@@ -1,4 +1,4 @@
-import { fireEvent, render, RenderResult } from '@testing-library/react';
+import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { dateTimeParse, systemDateFormats, TimeRange } from '@grafana/data';
@@ -6,19 +6,11 @@ import { selectors } from '@grafana/e2e-selectors';
import { TimeRangeContent } from './TimeRangeContent';
-type TimeRangeFormRenderResult = RenderResult & {
- getCalendarDayByLabelText(label: string): HTMLButtonElement;
-};
-
const mockClipboard = {
writeText: jest.fn(),
readText: jest.fn(),
};
-Object.defineProperty(global.navigator, 'clipboard', {
- value: mockClipboard,
-});
-
const defaultTimeRange: TimeRange = {
from: dateTimeParse('2021-06-17 00:00:00', { timeZone: 'utc' }),
to: dateTimeParse('2021-06-19 23:59:00', { timeZone: 'utc' }),
@@ -33,21 +25,25 @@ const customRawTimeRange = {
to: '2023-06-19 23:59:00',
};
-function setup(initial: TimeRange = defaultTimeRange, timeZone = 'utc'): TimeRangeFormRenderResult {
- const result = render(
- {}} timeZone={timeZone} />
- );
-
+function setup(initial: TimeRange = defaultTimeRange, timeZone = 'utc') {
return {
- ...result,
+ ...render( {}} timeZone={timeZone} />),
getCalendarDayByLabelText: (label: string) => {
- const item = result.getByLabelText(label);
+ const item = screen.getByLabelText(label);
return item?.parentElement as HTMLButtonElement;
},
};
}
describe('TimeRangeForm', () => {
+ let user: ReturnType;
+ beforeEach(() => {
+ user = userEvent.setup();
+ Object.defineProperty(global.navigator, 'clipboard', {
+ value: mockClipboard,
+ });
+ });
+
it('should render form correctly', () => {
const { getByLabelText, getByText, getAllByRole } = setup();
@@ -57,13 +53,14 @@ describe('TimeRangeForm', () => {
expect(getByLabelText('To')).toBeInTheDocument();
});
- it('should display calendar when clicking the calendar icon', () => {
- const { getByLabelText, getAllByRole } = setup();
+ it('should display calendar when clicking the calendar icon', async () => {
+ const user = userEvent.setup();
+ setup();
const { TimePicker } = selectors.components;
- const openCalendarButton = getAllByRole('button', { name: 'Open calendar' });
+ const openCalendarButton = screen.getAllByRole('button', { name: 'Open calendar' });
- fireEvent.click(openCalendarButton[0]);
- expect(getByLabelText(TimePicker.calendar.label)).toBeInTheDocument();
+ await user.click(openCalendarButton[0]);
+ expect(screen.getByLabelText(TimePicker.calendar.label)).toBeInTheDocument();
});
it('should have passed time range entered in form', () => {
@@ -121,30 +118,30 @@ describe('TimeRangeForm', () => {
});
});
- it('should close calendar when clicking the close icon', () => {
+ it('should close calendar when clicking the close icon', async () => {
const { queryByLabelText, getAllByRole, getByRole } = setup();
const { TimePicker } = selectors.components;
const openCalendarButton = getAllByRole('button', { name: 'Open calendar' });
- fireEvent.click(openCalendarButton[0]);
+ await user.click(openCalendarButton[0]);
expect(getByRole('button', { name: 'Close calendar' })).toBeInTheDocument();
- fireEvent.click(getByRole('button', { name: 'Close calendar' }));
- expect(queryByLabelText(TimePicker.calendar.label)).toBeNull();
+ await user.click(getByRole('button', { name: 'Close calendar' }));
+ expect(queryByLabelText(TimePicker.calendar.label)).not.toBeInTheDocument();
});
it('should not display calendar without clicking the calendar icon', () => {
const { queryByLabelText } = setup();
const { TimePicker } = selectors.components;
- expect(queryByLabelText(TimePicker.calendar.label)).toBeNull();
+ expect(queryByLabelText(TimePicker.calendar.label)).not.toBeInTheDocument();
});
- it('should have passed time range selected in calendar', () => {
+ it('should have passed time range selected in calendar', async () => {
const { getAllByRole, getCalendarDayByLabelText } = setup();
const openCalendarButton = getAllByRole('button', { name: 'Open calendar' });
- fireEvent.click(openCalendarButton[0]);
+ await user.click(openCalendarButton[0]);
const from = getCalendarDayByLabelText('June 17, 2021');
const to = getCalendarDayByLabelText('June 19, 2021');
@@ -152,11 +149,11 @@ describe('TimeRangeForm', () => {
expect(to).toHaveClass('react-calendar__tile--rangeEnd');
});
- it('should select correct time range in calendar when having a custom time zone', () => {
+ it('should select correct time range in calendar when having a custom time zone', async () => {
const { getAllByRole, getCalendarDayByLabelText } = setup(defaultTimeRange, 'Asia/Tokyo');
const openCalendarButton = getAllByRole('button', { name: 'Open calendar' });
- fireEvent.click(openCalendarButton[1]);
+ await user.click(openCalendarButton[1]);
const from = getCalendarDayByLabelText('June 17, 2021');
const to = getCalendarDayByLabelText('June 19, 2021');
@@ -165,9 +162,9 @@ describe('TimeRangeForm', () => {
});
it('should copy time range to clipboard', async () => {
- const { getByTestId } = setup();
+ setup();
- await userEvent.click(getByTestId('data-testid TimePicker copy button'));
+ await user.click(screen.getByTestId('data-testid TimePicker copy button'));
expect(global.navigator.clipboard.writeText).toHaveBeenCalledWith(
JSON.stringify({ from: defaultTimeRange.raw.from, to: defaultTimeRange.raw.to })
);
diff --git a/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker/hooks.test.tsx b/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker/hooks.test.tsx
index 38fe7529625..98d9eb0588a 100644
--- a/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker/hooks.test.tsx
+++ b/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker/hooks.test.tsx
@@ -1,9 +1,16 @@
-import { act, fireEvent, render, renderHook, screen } from '@testing-library/react';
+import { render, renderHook, screen } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
import { createRef, KeyboardEvent, RefObject } from 'react';
import { useListFocus } from './hooks';
describe('useListFocus', () => {
+ let user: ReturnType;
+
+ beforeEach(() => {
+ user = userEvent.setup();
+ });
+
const testid = 'test';
const getListElement = (
ref: RefObject,
@@ -26,7 +33,7 @@ describe('useListFocus', () => {
{ from: 'now-7d', to: 'now', display: 'Last 7 days' },
];
- it('sets correct focused item on keydown', () => {
+ it('sets correct focused item on keydown', async () => {
const ref = createRef();
const { rerender } = render(getListElement(ref));
@@ -38,10 +45,7 @@ describe('useListFocus', () => {
expect(screen.getByText('Last 6 hours').tabIndex).toBe(-1);
expect(screen.getByText('Last 24 hours').tabIndex).toBe(-1);
expect(screen.getByText('Last 7 days').tabIndex).toBe(-1);
-
- act(() => {
- fireEvent.keyDown(screen.getByTestId(testid), { key: 'ArrowDown' });
- });
+ await user.type(screen.getByTestId(testid), '{ArrowDown}');
const [handleKeys2] = result.current;
rerender(getListElement(ref, handleKeys2));
@@ -51,9 +55,7 @@ describe('useListFocus', () => {
expect(screen.getByText('Last 24 hours').tabIndex).toBe(-1);
expect(screen.getByText('Last 7 days').tabIndex).toBe(-1);
- act(() => {
- fireEvent.keyDown(screen.getByTestId(testid), { key: 'ArrowDown' });
- });
+ await user.type(screen.getByTestId(testid), '{ArrowDown}');
const [handleKeys3] = result.current;
rerender(getListElement(ref, handleKeys3));
@@ -63,9 +65,7 @@ describe('useListFocus', () => {
expect(screen.getByText('Last 24 hours').tabIndex).toBe(0);
expect(screen.getByText('Last 7 days').tabIndex).toBe(-1);
- act(() => {
- fireEvent.keyDown(screen.getByTestId(testid), { key: 'ArrowUp' });
- });
+ await user.type(screen.getByTestId(testid), '{ArrowUp}');
const [handleKeys4] = result.current;
rerender(getListElement(ref, handleKeys4));
@@ -75,9 +75,7 @@ describe('useListFocus', () => {
expect(screen.getByText('Last 24 hours').tabIndex).toBe(-1);
expect(screen.getByText('Last 7 days').tabIndex).toBe(-1);
- act(() => {
- fireEvent.keyDown(screen.getByTestId(testid), { key: 'ArrowUp' });
- });
+ await user.type(screen.getByTestId(testid), '{ArrowUp}');
const [handleKeys5] = result.current;
rerender(getListElement(ref, handleKeys5));
@@ -87,9 +85,7 @@ describe('useListFocus', () => {
expect(screen.getByText('Last 24 hours').tabIndex).toBe(-1);
expect(screen.getByText('Last 7 days').tabIndex).toBe(-1);
- act(() => {
- fireEvent.keyDown(screen.getByTestId(testid), { key: 'ArrowUp' });
- });
+ await user.type(screen.getByTestId(testid), '{ArrowUp}');
const [handleKeys6] = result.current;
rerender(getListElement(ref, handleKeys6));
@@ -100,7 +96,7 @@ describe('useListFocus', () => {
expect(screen.getByText('Last 7 days').tabIndex).toBe(0);
});
- it('clicks focused item when Enter key is pressed', () => {
+ it('clicks focused item when Enter key is pressed', async () => {
const ref = createRef();
const onClick = jest.fn();
const { rerender } = render(getListElement(ref));
@@ -109,9 +105,7 @@ describe('useListFocus', () => {
const [handleKeys] = result.current;
rerender(getListElement(ref, handleKeys, onClick));
- act(() => {
- fireEvent.keyDown(screen.getByTestId(testid), { key: 'Enter' });
- });
+ await user.type(screen.getByTestId(testid), '{Enter}');
expect(onClick).toHaveBeenCalled();
});
diff --git a/packages/grafana-ui/src/components/ErrorBoundary/ErrorBoundary.test.tsx b/packages/grafana-ui/src/components/ErrorBoundary/ErrorBoundary.test.tsx
index d14867aef54..edcec107da7 100644
--- a/packages/grafana-ui/src/components/ErrorBoundary/ErrorBoundary.test.tsx
+++ b/packages/grafana-ui/src/components/ErrorBoundary/ErrorBoundary.test.tsx
@@ -67,6 +67,7 @@ describe('ErrorBoundary', () => {
await screen.findByText(problem.message);
expect(renderCount).toBeGreaterThan(0);
+ // eslint-disable-next-line testing-library/render-result-naming-convention
const oldRenderCount = renderCount;
rerender(
diff --git a/packages/grafana-ui/src/components/FileDropzone/FileDropzone.test.tsx b/packages/grafana-ui/src/components/FileDropzone/FileDropzone.test.tsx
index 23d965f6f10..450afdbe1bf 100644
--- a/packages/grafana-ui/src/components/FileDropzone/FileDropzone.test.tsx
+++ b/packages/grafana-ui/src/components/FileDropzone/FileDropzone.test.tsx
@@ -1,4 +1,5 @@
import { fireEvent, render, screen } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
import { FileDropzone } from './FileDropzone';
import { REMOVE_FILE } from './FileListItem';
@@ -53,13 +54,14 @@ describe('The FileDropzone component', () => {
});
it('should handle file removal from the list', async () => {
+ const user = userEvent.setup();
render();
dispatchEvt(screen.getByTestId('dropzone'), 'drop', mockData(files));
expect(await screen.findAllByLabelText(REMOVE_FILE)).toHaveLength(3);
- fireEvent.click(screen.getAllByLabelText(REMOVE_FILE)[0]);
+ await user.click(screen.getAllByLabelText(REMOVE_FILE)[0]);
expect(await screen.findAllByLabelText(REMOVE_FILE)).toHaveLength(2);
});
@@ -119,7 +121,7 @@ describe('The FileDropzone component', () => {
);
render(component);
- screen.getByText('Custom dropzone text');
+ expect(screen.getByText('Custom dropzone text')).toBeInTheDocument();
});
it('should handle file list overwrite when fileListRenderer is passed', async () => {
diff --git a/packages/grafana-ui/src/components/FileDropzone/FileListItem.test.tsx b/packages/grafana-ui/src/components/FileDropzone/FileListItem.test.tsx
index 587756b25b9..0de14953fea 100644
--- a/packages/grafana-ui/src/components/FileDropzone/FileListItem.test.tsx
+++ b/packages/grafana-ui/src/components/FileDropzone/FileListItem.test.tsx
@@ -1,4 +1,5 @@
-import { fireEvent, render, screen } from '@testing-library/react';
+import { render, screen } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
import { FileListItem, REMOVE_FILE } from './FileListItem';
@@ -9,6 +10,12 @@ const file = ({
}) => new File([fileBits], fileName, options);
describe('The FileListItem component', () => {
+ let user: ReturnType;
+
+ beforeEach(() => {
+ user = userEvent.setup({ applyAccept: false });
+ });
+
it('should show an error message when error prop is not null', () => {
render();
@@ -16,11 +23,11 @@ describe('The FileListItem component', () => {
expect(screen.queryByLabelText('Retry')).not.toBeInTheDocument();
});
- it('should show a retry icon when error is not null and retryUpload prop is passed', () => {
+ it('should show a retry icon when error is not null and retryUpload prop is passed', async () => {
const retryUpload = jest.fn();
render();
- fireEvent.click(screen.getByLabelText('Retry'));
+ await user.click(screen.getByLabelText('Retry'));
expect(screen.getByText('error')).toBeInTheDocument();
expect(screen.getByLabelText('Retry'));
@@ -41,21 +48,21 @@ describe('The FileListItem component', () => {
expect(screen.queryByRole('progressbar')).not.toBeInTheDocument();
});
- it('should show a Cancel button when abortUpload prop is passed', () => {
+ it('should show a Cancel button when abortUpload prop is passed', async () => {
const abortUpload = jest.fn();
render();
- fireEvent.click(screen.getByRole('button', { name: /cancel/i }));
+ await user.click(screen.getByRole('button', { name: /cancel/i }));
expect(abortUpload).toBeCalledTimes(1);
});
- it('should show a Remove icon when removeFile prop is passed', () => {
+ it('should show a Remove icon when removeFile prop is passed', async () => {
const removeFile = jest.fn();
const customFile = { file: file({}), id: '1', error: null };
render();
- fireEvent.click(screen.getByRole('button', { name: REMOVE_FILE }));
+ await user.click(screen.getByRole('button', { name: REMOVE_FILE }));
expect(removeFile).toHaveBeenCalledWith(customFile);
});
diff --git a/packages/grafana-ui/src/components/FileUpload/FileUpload.test.tsx b/packages/grafana-ui/src/components/FileUpload/FileUpload.test.tsx
index 8b76e4ab435..981f8e00bac 100644
--- a/packages/grafana-ui/src/components/FileUpload/FileUpload.test.tsx
+++ b/packages/grafana-ui/src/components/FileUpload/FileUpload.test.tsx
@@ -1,4 +1,4 @@
-import { render, waitFor, fireEvent, screen } from '@testing-library/react';
+import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { selectors } from '@grafana/e2e-selectors';
@@ -6,17 +6,23 @@ import { selectors } from '@grafana/e2e-selectors';
import { FileUpload } from './FileUpload';
describe('FileUpload', () => {
+ let user: ReturnType;
+
+ beforeEach(() => {
+ user = userEvent.setup({ applyAccept: false });
+ });
+
it('should render upload button with default text and no file name', () => {
render( {}} />);
expect(screen.getByText('Upload file')).toBeInTheDocument();
- expect(screen.queryByLabelText('File name')).toBeNull();
+ expect(screen.queryByLabelText('File name')).not.toBeInTheDocument();
});
it('clicking the button should trigger the input', async () => {
const mockInputOnClick = jest.fn();
- const { getByTestId } = render( {}} />);
+ render( {}} />);
const button = screen.getByText('Upload file');
- const input = getByTestId(selectors.components.FileUpload.inputField);
+ const input = screen.getByTestId(selectors.components.FileUpload.inputField);
// attach a click listener to the input
input.onclick = mockInputOnClick;
@@ -29,14 +35,10 @@ describe('FileUpload', () => {
const testFileName = 'grafana.png';
const file = new File(['(⌐□_□)'], testFileName, { type: 'image/png' });
const onFileUpload = jest.fn();
- const { getByTestId } = render();
- let uploader = getByTestId(selectors.components.FileUpload.inputField);
- await waitFor(() =>
- fireEvent.change(uploader, {
- target: { files: [file] },
- })
- );
- let uploaderLabel = getByTestId(selectors.components.FileUpload.fileNameSpan);
+ render();
+ const uploader = await screen.findByTestId(selectors.components.FileUpload.inputField);
+ await user.upload(uploader, file);
+ const uploaderLabel = await screen.findByTestId(selectors.components.FileUpload.fileNameSpan);
expect(uploaderLabel).toHaveTextContent(testFileName);
});
@@ -44,14 +46,10 @@ describe('FileUpload', () => {
const testFileName = 'longFileName.something.png';
const file = new File(['(⌐□_□)'], testFileName, { type: 'image/png' });
const onFileUpload = jest.fn();
- const { getByTestId } = render();
- let uploader = getByTestId(selectors.components.FileUpload.inputField);
- await waitFor(() =>
- fireEvent.change(uploader, {
- target: { files: [file] },
- })
- );
- let uploaderLabel = getByTestId(selectors.components.FileUpload.fileNameSpan);
+ render();
+ const uploader = screen.getByTestId(selectors.components.FileUpload.inputField);
+ await user.upload(uploader, file);
+ const uploaderLabel = screen.getByTestId(selectors.components.FileUpload.fileNameSpan);
expect(uploaderLabel).toHaveTextContent('longFileName.som....png');
});
});
diff --git a/packages/grafana-ui/src/components/Forms/Legacy/Input/Input.test.tsx b/packages/grafana-ui/src/components/Forms/Legacy/Input/Input.test.tsx
index dbd4ca81323..d1ba43f4a81 100644
--- a/packages/grafana-ui/src/components/Forms/Legacy/Input/Input.test.tsx
+++ b/packages/grafana-ui/src/components/Forms/Legacy/Input/Input.test.tsx
@@ -30,7 +30,7 @@ describe('Input', () => {
await userEvent.type(inputEl, 'abcde');
// blur the field
await userEvent.click(document.body);
- await screen.findByText(TEST_ERROR_MESSAGE);
+ expect(await screen.findByText(TEST_ERROR_MESSAGE)).toBeInTheDocument();
});
it('should validate without error onBlur', async () => {
diff --git a/packages/grafana-ui/src/components/InteractiveTable/InteractiveTable.test.tsx b/packages/grafana-ui/src/components/InteractiveTable/InteractiveTable.test.tsx
index 6526c22a0cd..651532409af 100644
--- a/packages/grafana-ui/src/components/InteractiveTable/InteractiveTable.test.tsx
+++ b/packages/grafana-ui/src/components/InteractiveTable/InteractiveTable.test.tsx
@@ -1,4 +1,4 @@
-import { getByRole, render, screen, cleanup } from '@testing-library/react';
+import { render, screen, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import * as React from 'react';
@@ -55,8 +55,8 @@ describe('InteractiveTable', () => {
const valueColumnHeader = screen.getByRole('columnheader', { name: 'Value' });
const countryColumnHeader = screen.getByRole('columnheader', { name: 'Country' });
- const valueColumnSortButton = getByRole(valueColumnHeader, 'button');
- const countryColumnSortButton = getByRole(countryColumnHeader, 'button');
+ const valueColumnSortButton = within(valueColumnHeader).getByRole('button');
+ const countryColumnSortButton = within(countryColumnHeader).getByRole('button');
expect(valueColumnHeader).not.toHaveAttribute('aria-sort');
expect(countryColumnHeader).not.toHaveAttribute('aria-sort');
@@ -95,8 +95,9 @@ describe('InteractiveTable', () => {
expect(screen.getByTestId('test-1')).toHaveTextContent('Sweden');
- expect(expanderButton.getAttribute('aria-controls')).toBe(
+ expect(expanderButton).toHaveAttribute(
// ancestor tr's id should match the expander button's aria-controls attribute
+ 'aria-controls',
screen.getByTestId('test-1').parentElement?.parentElement?.id
);
});
@@ -168,9 +169,9 @@ describe('InteractiveTable', () => {
const expandAllButton = screen.getByRole('button', { name: 'Expand all rows' });
await user.click(expandAllButton);
- expect(screen.queryByTestId('test-1')).toBeInTheDocument();
- expect(screen.queryByTestId('test-2')).toBeInTheDocument();
- expect(screen.queryByTestId('test-3')).toBeInTheDocument();
+ expect(screen.getByTestId('test-1')).toBeInTheDocument();
+ expect(screen.getByTestId('test-2')).toBeInTheDocument();
+ expect(screen.getByTestId('test-3')).toBeInTheDocument();
});
});
describe('pagination', () => {
@@ -182,8 +183,6 @@ describe('InteractiveTable', () => {
expect(screen.queryByRole('button', { name: /next/i })).not.toBeInTheDocument();
expect(screen.queryByRole('button', { name: /previous/i })).not.toBeInTheDocument();
- cleanup();
-
render();
expect(screen.queryByRole('button', { name: /next/i })).not.toBeInTheDocument();
diff --git a/packages/grafana-ui/src/components/MatchersUI/FieldsByFrameRefIdMatcher.test.tsx b/packages/grafana-ui/src/components/MatchersUI/FieldsByFrameRefIdMatcher.test.tsx
index 67abc701345..7646f67735c 100644
--- a/packages/grafana-ui/src/components/MatchersUI/FieldsByFrameRefIdMatcher.test.tsx
+++ b/packages/grafana-ui/src/components/MatchersUI/FieldsByFrameRefIdMatcher.test.tsx
@@ -1,4 +1,5 @@
import { fireEvent, render, screen } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
import { toDataFrame, FieldType } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
@@ -47,7 +48,10 @@ const multiProps: MultiProps = {
const setup = (testProps?: Partial) => {
const editorProps = { ...props, ...testProps };
- return render();
+ return {
+ ...render(),
+ user: userEvent.setup(),
+ };
};
const multiSetup = (testProps?: Partial) => {
@@ -57,10 +61,10 @@ const multiSetup = (testProps?: Partial) => {
describe('RefIDPicker', () => {
it('Should be able to select frame', async () => {
- setup();
+ const { user } = setup();
const select = await screen.findByRole('combobox');
- fireEvent.keyDown(select, { keyCode: 40 });
+ await user.type(select, '{ArrowDown}');
const selectOptions = screen.getAllByTestId(selectors.components.Select.option);
@@ -85,6 +89,8 @@ describe('RefIDMultiPicker', () => {
});
it('Should be able to select frame', async () => {
+ // TODO: Work out why this doesn't work correctly with userEvent
+ /* eslint-disable testing-library/prefer-user-event */
multiSetup();
const select = await screen.findByRole('combobox');
@@ -101,5 +107,6 @@ describe('RefIDMultiPicker', () => {
fireEvent.keyDown(select, { keyCode: 13 });
expect(mockOnChange).toHaveBeenLastCalledWith(['A', 'B']);
+ /* eslint-enable testing-library/prefer-user-event */
});
});
diff --git a/packages/grafana-ui/src/components/Menu/MenuItem.test.tsx b/packages/grafana-ui/src/components/Menu/MenuItem.test.tsx
index 1011634810e..569b4f1dfc9 100644
--- a/packages/grafana-ui/src/components/Menu/MenuItem.test.tsx
+++ b/packages/grafana-ui/src/components/Menu/MenuItem.test.tsx
@@ -1,10 +1,17 @@
-import { fireEvent, render, screen } from '@testing-library/react';
+import { render, screen } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
import { selectors } from '@grafana/e2e-selectors';
import { MenuItem, MenuItemProps } from './MenuItem';
describe('MenuItem', () => {
+ let user: ReturnType;
+
+ beforeEach(() => {
+ user = userEvent.setup();
+ });
+
const getMenuItem = (props?: Partial) => (
);
@@ -19,12 +26,12 @@ describe('MenuItem', () => {
expect(screen.getByLabelText(selectors.components.Menu.MenuItem('Test')).nodeName).toBe('A');
});
- it('calls onClick when item is clicked', () => {
+ it('calls onClick when item is clicked', async () => {
const onClick = jest.fn();
render(getMenuItem({ onClick }));
- fireEvent.click(screen.getByLabelText(selectors.components.Menu.MenuItem('Test')));
+ await user.click(screen.getByLabelText(selectors.components.Menu.MenuItem('Test')));
expect(onClick).toHaveBeenCalled();
});
@@ -41,7 +48,7 @@ describe('MenuItem', () => {
expect(screen.getByTestId(selectors.components.Menu.SubMenu.icon)).toBeInTheDocument();
expect(screen.queryByTestId(selectors.components.Menu.SubMenu.container)).not.toBeInTheDocument();
- fireEvent.mouseOver(screen.getByLabelText(selectors.components.Menu.MenuItem('Test')));
+ await user.hover(screen.getByLabelText(selectors.components.Menu.MenuItem('Test')));
const subMenuContainer = await screen.findByTestId(selectors.components.Menu.SubMenu.container);
@@ -57,10 +64,10 @@ describe('MenuItem', () => {
render(getMenuItem({ childItems, disabled: true }));
- fireEvent.mouseOver(screen.getByLabelText(selectors.components.Menu.MenuItem('Test')));
+ await user.hover(screen.getByLabelText(selectors.components.Menu.MenuItem('Test')));
const subMenuContainer = screen.queryByLabelText(selectors.components.Menu.SubMenu.container);
- expect(subMenuContainer).toBe(null);
+ expect(subMenuContainer).not.toBeInTheDocument();
});
it('opens subMenu on ArrowRight', async () => {
@@ -73,7 +80,7 @@ describe('MenuItem', () => {
expect(screen.queryByTestId(selectors.components.Menu.SubMenu.container)).not.toBeInTheDocument();
- fireEvent.keyDown(screen.getByLabelText(selectors.components.Menu.MenuItem('Test')), { key: 'ArrowRight' });
+ await user.type(screen.getByLabelText(selectors.components.Menu.MenuItem('Test')), '{ArrowRight}');
expect(await screen.findByTestId(selectors.components.Menu.SubMenu.container)).toBeInTheDocument();
});
diff --git a/packages/grafana-ui/src/components/Menu/hooks.test.tsx b/packages/grafana-ui/src/components/Menu/hooks.test.tsx
index 9ad532b14ca..7b7e161194f 100644
--- a/packages/grafana-ui/src/components/Menu/hooks.test.tsx
+++ b/packages/grafana-ui/src/components/Menu/hooks.test.tsx
@@ -1,9 +1,16 @@
-import { act, fireEvent, render, renderHook, screen } from '@testing-library/react';
+import { render, renderHook, screen } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
import { createRef, KeyboardEvent, RefObject } from 'react';
import { useMenuFocus } from './hooks';
describe('useMenuFocus', () => {
+ let user: ReturnType;
+
+ beforeEach(() => {
+ user = userEvent.setup();
+ });
+
const testid = 'test';
const getMenuElement = (
ref: RefObject,
@@ -23,7 +30,7 @@ describe('useMenuFocus', () => {
);
- it('sets correct focused item on keydown', () => {
+ it('sets correct focused item on keydown', async () => {
const ref = createRef();
const { result } = renderHook(() => useMenuFocus({ localRef: ref }));
const [handleKeys] = result.current;
@@ -34,9 +41,7 @@ describe('useMenuFocus', () => {
expect(screen.getByText('Item 3').tabIndex).toBe(-1);
expect(screen.getByText('Item 4').tabIndex).toBe(-1);
- act(() => {
- fireEvent.keyDown(screen.getByTestId(testid), { key: 'ArrowDown' });
- });
+ await user.type(screen.getByTestId(testid), '{ArrowDown}');
const [handleKeys2] = result.current;
rerender(getMenuElement(ref, handleKeys2));
@@ -46,9 +51,7 @@ describe('useMenuFocus', () => {
expect(screen.getByText('Item 3').tabIndex).toBe(-1);
expect(screen.getByText('Item 4').tabIndex).toBe(-1);
- act(() => {
- fireEvent.keyDown(screen.getByTestId(testid), { key: 'ArrowDown' });
- });
+ await user.type(screen.getByTestId(testid), '{ArrowDown}');
const [handleKeys3] = result.current;
rerender(getMenuElement(ref, handleKeys3));
@@ -58,9 +61,7 @@ describe('useMenuFocus', () => {
expect(screen.getByText('Item 3').tabIndex).toBe(-1);
expect(screen.getByText('Item 4').tabIndex).toBe(-1);
- act(() => {
- fireEvent.keyDown(screen.getByTestId(testid), { key: 'ArrowUp' });
- });
+ await user.type(screen.getByTestId(testid), '{ArrowUp}');
const [handleKeys4] = result.current;
rerender(getMenuElement(ref, handleKeys4));
@@ -70,9 +71,7 @@ describe('useMenuFocus', () => {
expect(screen.getByText('Item 3').tabIndex).toBe(-1);
expect(screen.getByText('Item 4').tabIndex).toBe(-1);
- act(() => {
- fireEvent.keyDown(screen.getByTestId(testid), { key: 'ArrowUp' });
- });
+ await user.type(screen.getByTestId(testid), '{ArrowUp}');
const [handleKeys5] = result.current;
rerender(getMenuElement(ref, handleKeys5));
@@ -82,9 +81,7 @@ describe('useMenuFocus', () => {
expect(screen.getByText('Item 3').tabIndex).toBe(-1);
expect(screen.getByText('Item 4').tabIndex).toBe(0);
- act(() => {
- fireEvent.keyDown(screen.getByTestId(testid), { key: 'ArrowUp' });
- });
+ await user.type(screen.getByTestId(testid), '{ArrowUp}');
const [handleKeys6] = result.current;
rerender(getMenuElement(ref, handleKeys6));
@@ -95,16 +92,14 @@ describe('useMenuFocus', () => {
expect(screen.getByText('Item 4').tabIndex).toBe(-1);
});
- it('calls close on ArrowLeft and unfocuses all items', () => {
+ it('calls close on ArrowLeft and unfocuses all items', async () => {
const ref = createRef();
const close = jest.fn();
const { result } = renderHook(() => useMenuFocus({ localRef: ref, close }));
const [handleKeys] = result.current;
const { rerender } = render(getMenuElement(ref, handleKeys));
- act(() => {
- fireEvent.keyDown(screen.getByTestId(testid), { key: 'ArrowDown' });
- });
+ await user.type(screen.getByTestId(testid), '{ArrowDown}');
const [handleKeys2] = result.current;
rerender(getMenuElement(ref, handleKeys2));
@@ -113,9 +108,7 @@ describe('useMenuFocus', () => {
expect(screen.getByText('Item 2').tabIndex).toBe(-1);
expect(screen.getByText('Item 3').tabIndex).toBe(-1);
- act(() => {
- fireEvent.keyDown(screen.getByTestId(testid), { key: 'ArrowLeft' });
- });
+ await user.type(screen.getByTestId(testid), '{ArrowLeft}');
expect(close).toHaveBeenCalled();
expect(screen.getByText('Item 1').tabIndex).toBe(-1);
@@ -123,7 +116,7 @@ describe('useMenuFocus', () => {
expect(screen.getByText('Item 3').tabIndex).toBe(-1);
});
- it('forwards keydown and open events', () => {
+ it('forwards keydown and open events', async () => {
const ref = createRef();
const onOpen = jest.fn();
const onKeyDown = jest.fn();
@@ -132,10 +125,7 @@ describe('useMenuFocus', () => {
render(getMenuElement(ref, handleKeys));
- act(() => {
- fireEvent.keyDown(screen.getByTestId(testid), { key: 'ArrowDown' });
- fireEvent.keyDown(screen.getByTestId(testid), { key: 'Home' });
- });
+ await user.type(screen.getByTestId(testid), '{ArrowDown}{Home}');
expect(onOpen).toHaveBeenCalled();
expect(onKeyDown).toHaveBeenCalledTimes(2);
@@ -152,28 +142,24 @@ describe('useMenuFocus', () => {
expect(screen.getByText('Item 1').tabIndex).toBe(0);
});
- it('clicks focused item when Enter key is pressed', () => {
+ it('clicks focused item when Enter key is pressed', async () => {
const ref = createRef();
const onClick = jest.fn();
const { result } = renderHook(() => useMenuFocus({ localRef: ref }));
const [handleKeys] = result.current;
const { rerender } = render(getMenuElement(ref, handleKeys, undefined, onClick));
- act(() => {
- fireEvent.keyDown(screen.getByTestId(testid), { key: 'ArrowDown' });
- });
+ await user.type(screen.getByTestId(testid), '{ArrowDown}');
const [handleKeys2] = result.current;
rerender(getMenuElement(ref, handleKeys2, undefined, onClick));
- act(() => {
- fireEvent.keyDown(screen.getByTestId(testid), { key: 'Enter' });
- });
+ await user.type(screen.getByTestId(testid), '{Enter}');
expect(onClick).toHaveBeenCalled();
});
- it('calls onClose on Tab or Escape', () => {
+ it('calls onClose on Tab or Escape', async () => {
const ref = createRef();
const onClose = jest.fn();
const { result } = renderHook(() => useMenuFocus({ localRef: ref, onClose }));
@@ -181,10 +167,7 @@ describe('useMenuFocus', () => {
render(getMenuElement(ref, handleKeys));
- act(() => {
- fireEvent.keyDown(screen.getByTestId(testid), { key: 'Tab' });
- fireEvent.keyDown(screen.getByTestId(testid), { key: 'Escape' });
- });
+ await user.type(screen.getByTestId(testid), '{Tab}{Escape}');
expect(onClose).toHaveBeenCalledTimes(2);
});
diff --git a/packages/grafana-ui/src/components/PanelChrome/PanelChrome.test.tsx b/packages/grafana-ui/src/components/PanelChrome/PanelChrome.test.tsx
index a13c38567b0..3032099a069 100644
--- a/packages/grafana-ui/src/components/PanelChrome/PanelChrome.test.tsx
+++ b/packages/grafana-ui/src/components/PanelChrome/PanelChrome.test.tsx
@@ -1,4 +1,5 @@
-import { screen, render, fireEvent } from '@testing-library/react';
+import { screen, render } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
import { useToggle } from 'react-use';
import { LoadingState } from '@grafana/data';
@@ -16,7 +17,10 @@ const setup = (propOverrides?: Partial) => {
};
Object.assign(props, propOverrides);
- return render();
+ return {
+ ...render(),
+ user: userEvent.setup(),
+ };
};
const setupWithToggleCollapsed = (propOverrides?: Partial) => {
@@ -37,7 +41,10 @@ const setupWithToggleCollapsed = (propOverrides?: Partial) =>
return ;
};
- return render();
+ return {
+ ...render(),
+ user: userEvent.setup(),
+ };
};
it('renders an empty panel with required props only', () => {
@@ -154,17 +161,17 @@ it('renders streaming indicator in the panel header if loadingState is streaming
expect(screen.getByTestId('panel-streaming')).toBeInTheDocument();
});
-it('collapses the controlled panel when user clicks on the chevron or the title', () => {
- setupWithToggleCollapsed({ title: 'Default title' });
+it('collapses the controlled panel when user clicks on the chevron or the title', async () => {
+ const { user } = setupWithToggleCollapsed({ title: 'Default title' });
expect(screen.getByText("Panel's Content")).toBeInTheDocument();
const button = screen.getByRole('button', { name: 'Default title' });
const content = screen.getByTestId(selectors.components.Panels.Panel.content);
// collapse button should have same aria-controls as the panel's content
- expect(button.getAttribute('aria-controls')).toBe(content.id);
+ expect(button).toHaveAttribute('aria-controls', content.id);
- fireEvent.click(button);
+ await user.click(button);
expect(screen.queryByText("Panel's Content")).not.toBeInTheDocument();
// aria-controls should be removed when panel is collapsed
@@ -172,8 +179,8 @@ it('collapses the controlled panel when user clicks on the chevron or the title'
expect(screen.queryByTestId(selectors.components.Panels.Panel.content)?.id).toBe(undefined);
});
-it('collapses the uncontrolled panel when user clicks on the chevron or the title', () => {
- setup({ title: 'Default title', collapsible: true });
+it('collapses the uncontrolled panel when user clicks on the chevron or the title', async () => {
+ const { user } = setup({ title: 'Default title', collapsible: true });
expect(screen.getByText("Panel's Content")).toBeInTheDocument();
@@ -181,9 +188,9 @@ it('collapses the uncontrolled panel when user clicks on the chevron or the titl
const content = screen.getByTestId(selectors.components.Panels.Panel.content);
// collapse button should have same aria-controls as the panel's content
- expect(button.getAttribute('aria-controls')).toBe(content.id);
+ expect(button).toHaveAttribute('aria-controls', content.id);
- fireEvent.click(button);
+ await user.click(button);
expect(screen.queryByText("Panel's Content")).not.toBeInTheDocument();
// aria-controls should be removed when panel is collapsed
expect(button).not.toHaveAttribute('aria-controlls');
diff --git a/packages/grafana-ui/src/components/SecretInput/SecretInput.test.tsx b/packages/grafana-ui/src/components/SecretInput/SecretInput.test.tsx
index aeb9adf4cef..1d59862fd1d 100644
--- a/packages/grafana-ui/src/components/SecretInput/SecretInput.test.tsx
+++ b/packages/grafana-ui/src/components/SecretInput/SecretInput.test.tsx
@@ -13,7 +13,7 @@ describe('', () => {
// Should show an enabled input
expect(input).toBeInTheDocument();
- expect(input).not.toBeDisabled();
+ expect(input).toBeEnabled();
// Should not show a "Reset" button
expect(screen.queryByRole('button', { name: RESET_BUTTON_TEXT })).not.toBeInTheDocument();
@@ -30,7 +30,7 @@ describe('', () => {
expect(input).toHaveValue(CONFIGURED_TEXT);
// Should show a reset button
- expect(screen.queryByRole('button', { name: RESET_BUTTON_TEXT })).toBeInTheDocument();
+ expect(screen.getByRole('button', { name: RESET_BUTTON_TEXT })).toBeInTheDocument();
});
it('should be possible to reset a configured secret', async () => {
@@ -40,7 +40,7 @@ describe('', () => {
// Should show a reset button and a disabled input
expect(screen.queryByPlaceholderText(PLACEHOLDER_TEXT)).toBeDisabled();
- expect(screen.queryByRole('button', { name: RESET_BUTTON_TEXT })).toBeInTheDocument();
+ expect(screen.getByRole('button', { name: RESET_BUTTON_TEXT })).toBeInTheDocument();
// Click on "Reset"
await userEvent.click(screen.getByRole('button', { name: RESET_BUTTON_TEXT }));
diff --git a/packages/grafana-ui/src/components/SecretTextArea/SecretTextArea.test.tsx b/packages/grafana-ui/src/components/SecretTextArea/SecretTextArea.test.tsx
index f2543a5002c..ac2902c6d85 100644
--- a/packages/grafana-ui/src/components/SecretTextArea/SecretTextArea.test.tsx
+++ b/packages/grafana-ui/src/components/SecretTextArea/SecretTextArea.test.tsx
@@ -15,7 +15,7 @@ describe('', () => {
// Should show an enabled input
expect(input).toBeInTheDocument();
- expect(input).not.toBeDisabled();
+ expect(input).toBeEnabled();
// Should not show a "Reset" button
expect(screen.queryByRole('button', { name: RESET_BUTTON_TEXT })).not.toBeInTheDocument();
@@ -34,7 +34,7 @@ describe('', () => {
expect(textArea).toHaveValue(CONFIGURED_TEXT);
// Should show a reset button
- expect(screen.queryByRole('button', { name: RESET_BUTTON_TEXT })).toBeInTheDocument();
+ expect(screen.getByRole('button', { name: RESET_BUTTON_TEXT })).toBeInTheDocument();
});
it('should be possible to reset a configured secret', async () => {
@@ -44,7 +44,7 @@ describe('', () => {
// Should show a reset button and a disabled input
expect(screen.queryByPlaceholderText(PLACEHOLDER_TEXT)).toBeDisabled();
- expect(screen.queryByRole('button', { name: RESET_BUTTON_TEXT })).toBeInTheDocument();
+ expect(screen.getByRole('button', { name: RESET_BUTTON_TEXT })).toBeInTheDocument();
// Click on "Reset"
await userEvent.click(screen.getByRole('button', { name: RESET_BUTTON_TEXT }));
diff --git a/packages/grafana-ui/src/components/Select/SelectBase.test.tsx b/packages/grafana-ui/src/components/Select/SelectBase.test.tsx
index 3c0dc9446a2..1279d2fd896 100644
--- a/packages/grafana-ui/src/components/Select/SelectBase.test.tsx
+++ b/packages/grafana-ui/src/components/Select/SelectBase.test.tsx
@@ -26,7 +26,7 @@ describe('SelectBase', () => {
];
it('renders without error', () => {
- render();
+ expect(() => render()).not.toThrow();
});
it('renders empty options information', async () => {
@@ -60,7 +60,7 @@ describe('SelectBase', () => {
};
render();
- expect(screen.queryByText('Test label')).toBeInTheDocument();
+ expect(screen.getByText('Test label')).toBeInTheDocument();
await userEvent.click(screen.getByText('clear value'));
expect(screen.queryByText('Test label')).not.toBeInTheDocument();
});
@@ -76,13 +76,15 @@ describe('SelectBase', () => {
describe('is not provided', () => {
it.each`
key
- ${'ArrowDown'}
- ${'ArrowUp'}
+ ${'{ArrowDown}'}
+ ${'{ArrowUp}'}
${' '}
- `('opens on arrow down/up or space', ({ key }) => {
+ `('opens on arrow down/up or space', async ({ key }) => {
+ const user = userEvent.setup();
+
render();
- fireEvent.focus(screen.getByRole('combobox'));
- fireEvent.keyDown(screen.getByRole('combobox'), { key });
+
+ await user.type(screen.getByRole('combobox'), key);
expect(screen.queryByText(/no options found/i)).toBeVisible();
});
});
@@ -293,7 +295,7 @@ describe('SelectBase', () => {
);
await userEvent.click(screen.getByText(/Option 1/i));
const toggleAllOptions = screen.getByTestId(selectors.components.Select.toggleAllOptions);
- expect(toggleAllOptions.textContent).toBe('Selected (1)');
+ expect(toggleAllOptions).toHaveTextContent('Selected (1)');
});
it('correctly removes all selected options when in indeterminate state', async () => {
@@ -308,7 +310,7 @@ describe('SelectBase', () => {
);
await userEvent.click(screen.getByText(/Option 1/i));
let toggleAllOptions = screen.getByTestId(selectors.components.Select.toggleAllOptions);
- expect(toggleAllOptions.textContent).toBe('Selected (1)');
+ expect(toggleAllOptions).toHaveTextContent('Selected (1)');
// Toggle all unselected when in indeterminate state
await userEvent.click(toggleAllOptions);
@@ -327,7 +329,7 @@ describe('SelectBase', () => {
);
await userEvent.click(screen.getByText(/Option 1/i));
let toggleAllOptions = screen.getByTestId(selectors.components.Select.toggleAllOptions);
- expect(toggleAllOptions.textContent).toBe('Selected (2)');
+ expect(toggleAllOptions).toHaveTextContent('Selected (2)');
// Toggle all unselected when in indeterminate state
await userEvent.click(toggleAllOptions);
@@ -346,7 +348,7 @@ describe('SelectBase', () => {
);
await userEvent.click(screen.getByText(/Choose/i));
let toggleAllOptions = screen.getByTestId(selectors.components.Select.toggleAllOptions);
- expect(toggleAllOptions.textContent).toBe('Selected (0)');
+ expect(toggleAllOptions).toHaveTextContent('Selected (0)');
// Toggle all unselected when in indeterminate state
await userEvent.click(toggleAllOptions);
diff --git a/packages/grafana-ui/src/components/Table/Table.test.tsx b/packages/grafana-ui/src/components/Table/Table.test.tsx
index f8078e9b27a..8983273534c 100644
--- a/packages/grafana-ui/src/components/Table/Table.test.tsx
+++ b/packages/grafana-ui/src/components/Table/Table.test.tsx
@@ -308,13 +308,13 @@ describe('Table', () => {
}),
});
- expect(within(getFooter()).getByRole('columnheader').getElementsByTagName('span')[0].textContent).toEqual('7');
+ expect(within(getFooter()).getByRole('columnheader').getElementsByTagName('span')[0]).toHaveTextContent('7');
await userEvent.click(within(getColumnHeader(/number/)).getByRole('button', { name: '' }));
await userEvent.click(screen.getByLabelText('1'));
await userEvent.click(screen.getByText('Ok'));
- expect(within(getFooter()).getByRole('columnheader').getElementsByTagName('span')[0].textContent).toEqual('3');
+ expect(within(getFooter()).getByRole('columnheader').getElementsByTagName('span')[0]).toHaveTextContent('3');
});
it('should filter rows and recalculate footer values when multiple filter values are selected', async () => {
@@ -338,7 +338,7 @@ describe('Table', () => {
});
expect(within(getTable()).getAllByRole('row')).toHaveLength(8);
- expect(within(getFooter()).getByRole('columnheader').getElementsByTagName('span')[0].textContent).toEqual('13');
+ expect(within(getFooter()).getByRole('columnheader').getElementsByTagName('span')[0]).toHaveTextContent('13');
await userEvent.click(within(getColumnHeader(/number/)).getByRole('button', { name: '' }));
await userEvent.click(screen.getByLabelText('2'));
@@ -347,7 +347,7 @@ describe('Table', () => {
//4 + header row
expect(within(getTable()).getAllByRole('row')).toHaveLength(5);
- expect(within(getFooter()).getByRole('columnheader').getElementsByTagName('span')[0].textContent).toEqual('10');
+ expect(within(getFooter()).getByRole('columnheader').getElementsByTagName('span')[0]).toHaveTextContent('10');
});
it('should reset when clear filters button is pressed', async () => {
@@ -376,14 +376,14 @@ describe('Table', () => {
//3 + header row
expect(within(getTable()).getAllByRole('row')).toHaveLength(4);
- expect(within(getFooter()).getByRole('columnheader').getElementsByTagName('span')[0].textContent).toEqual('3');
+ expect(within(getFooter()).getByRole('columnheader').getElementsByTagName('span')[0]).toHaveTextContent('3');
await userEvent.click(within(getColumnHeader(/number/)).getByRole('button', { name: '' }));
await userEvent.click(screen.getByText('Clear filter'));
//5 + header row
expect(within(getTable()).getAllByRole('row')).toHaveLength(6);
- expect(within(getFooter()).getByRole('columnheader').getElementsByTagName('span')[0].textContent).toEqual('7');
+ expect(within(getFooter()).getByRole('columnheader').getElementsByTagName('span')[0]).toHaveTextContent('7');
});
});
@@ -410,7 +410,7 @@ describe('Table', () => {
//5 + header row
expect(within(getTable()).getAllByRole('row')).toHaveLength(6);
- expect(within(getFooter()).getByRole('columnheader').getElementsByTagName('span')[0].textContent).toEqual('7');
+ expect(within(getFooter()).getByRole('columnheader').getElementsByTagName('span')[0]).toHaveTextContent('7');
const onSortByChange = jest.fn();
const onCellFilterAdded = jest.fn();
@@ -450,7 +450,7 @@ describe('Table', () => {
//4 + header row
expect(within(getTable()).getAllByRole('row')).toHaveLength(5);
- expect(within(getFooter()).getByRole('columnheader').getElementsByTagName('span')[0].textContent).toEqual('5');
+ expect(within(getFooter()).getByRole('columnheader').getElementsByTagName('span')[0]).toHaveTextContent('5');
});
});
@@ -500,7 +500,7 @@ describe('Table', () => {
}),
});
- expect(within(getFooter()).getByRole('columnheader').getElementsByTagName('span')[0].textContent).toEqual('4');
+ expect(within(getFooter()).getByRole('columnheader').getElementsByTagName('span')[0]).toHaveTextContent('4');
});
it('should show count of rows when `count rows` is selected', async () => {
@@ -523,10 +523,8 @@ describe('Table', () => {
}),
});
- expect(within(getFooter()).getByRole('columnheader').getElementsByTagName('span')[0].textContent).toEqual(
- 'Count'
- );
- expect(within(getFooter()).getByRole('columnheader').getElementsByTagName('span')[1].textContent).toEqual('5');
+ expect(within(getFooter()).getByRole('columnheader').getElementsByTagName('span')[0]).toHaveTextContent('Count');
+ expect(within(getFooter()).getByRole('columnheader').getElementsByTagName('span')[1]).toHaveTextContent('5');
});
it('should show correct counts when turning `count rows` on and off', async () => {
@@ -549,10 +547,8 @@ describe('Table', () => {
}),
});
- expect(within(getFooter()).getByRole('columnheader').getElementsByTagName('span')[0].textContent).toEqual(
- 'Count'
- );
- expect(within(getFooter()).getByRole('columnheader').getElementsByTagName('span')[1].textContent).toEqual('5');
+ expect(within(getFooter()).getByRole('columnheader').getElementsByTagName('span')[0]).toHaveTextContent('Count');
+ expect(within(getFooter()).getByRole('columnheader').getElementsByTagName('span')[1]).toHaveTextContent('5');
const onSortByChange = jest.fn();
const onCellFilterAdded = jest.fn();
@@ -590,7 +586,7 @@ describe('Table', () => {
rerender();
- expect(within(getFooter()).getByRole('columnheader').getElementsByTagName('span')[0].textContent).toEqual('4');
+ expect(within(getFooter()).getByRole('columnheader').getElementsByTagName('span')[0]).toHaveTextContent('4');
});
});
@@ -714,7 +710,7 @@ describe('Table', () => {
tables = screen.getAllByRole('table');
expect(tables).toHaveLength(3);
let subTable = screen.getAllByRole('table')[2];
- expect(subTable.style.height).toBe('108px');
+ expect(subTable).toHaveStyle({ height: '108px' });
// Sort again rows
tables = screen.getAllByRole('table');
@@ -732,7 +728,7 @@ describe('Table', () => {
rows = within(getTable()).getAllByRole('row');
await userEvent.click(within(rows[1]).getByLabelText('Expand row'));
subTable = screen.getAllByRole('table')[2];
- expect(subTable.style.height).toBe('108px');
+ expect(subTable).toHaveStyle({ height: '108px' });
});
});
diff --git a/packages/grafana-ui/src/components/Table/TableNG/TableNG.test.tsx b/packages/grafana-ui/src/components/Table/TableNG/TableNG.test.tsx
index 7cebf48d1ad..5108f104c71 100644
--- a/packages/grafana-ui/src/components/Table/TableNG/TableNG.test.tsx
+++ b/packages/grafana-ui/src/components/Table/TableNG/TableNG.test.tsx
@@ -1,4 +1,5 @@
-import { render, screen, fireEvent, act } from '@testing-library/react';
+import { render, screen, fireEvent } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
import { applyFieldOverrides, createTheme, DataFrame, FieldType, toDataFrame, EventBus } from '@grafana/data';
import { TableCellDisplayMode } from '@grafana/schema';
@@ -288,6 +289,40 @@ const createTimeDataFrame = (): DataFrame => {
};
describe('TableNG', () => {
+ let user: ReturnType;
+ let origResizeObserver = global.ResizeObserver;
+ let origScrollIntoView = window.HTMLElement.prototype.scrollIntoView;
+
+ beforeEach(() => {
+ user = userEvent.setup();
+ origResizeObserver = global.ResizeObserver;
+ origScrollIntoView = window.HTMLElement.prototype.scrollIntoView;
+ // Mock ResizeObserver
+ global.ResizeObserver = class ResizeObserver {
+ constructor(callback: any) {
+ // Store the callback
+ this.callback = callback;
+ }
+ callback: any;
+ observe() {
+ // Do nothing
+ }
+ unobserve() {
+ // Do nothing
+ }
+ disconnect() {
+ // Do nothing
+ }
+ };
+
+ window.HTMLElement.prototype.scrollIntoView = jest.fn();
+ });
+
+ afterEach(() => {
+ global.ResizeObserver = origResizeObserver;
+ window.HTMLElement.prototype.scrollIntoView = origScrollIntoView;
+ });
+
describe('Basic TableNG rendering', () => {
it('renders a simple table with columns and rows', () => {
const { container } = render(
@@ -332,7 +367,7 @@ describe('TableNG', () => {
expect(expandIcons.length).toBeGreaterThan(0);
});
- it('expands nested data when clicking expand button', () => {
+ it('expands nested data when clicking expand button', async () => {
// Mock scrollIntoView
window.HTMLElement.prototype.scrollIntoView = jest.fn();
@@ -356,7 +391,7 @@ describe('TableNG', () => {
// Click the expand button
if (expandButton) {
- fireEvent.click(expandButton);
+ await user.click(expandButton);
// After expansion, we should have more rows
const expandedRows = container.querySelectorAll('[role="row"]');
@@ -581,7 +616,7 @@ describe('TableNG', () => {
if (nextButton) {
// Click to go to the next page
- fireEvent.click(nextButton);
+ await user.click(nextButton);
// Get all cell content on the second page
const newCells = container.querySelectorAll('[role="gridcell"]');
@@ -597,7 +632,7 @@ describe('TableNG', () => {
// Check that the pagination summary shows we're on a different page
// The format appears to be "X - Y of Z rows" where X and Y are the row range
- expect(container.textContent).toMatch(/\d+ - \d+ of 100 rows/);
+ expect(container).toHaveTextContent(/\d+ - \d+ of 100 rows/);
// Verify that the pagination summary has changed
const paginationSummary = container.querySelector('.paginationSummary, [class*="paginationSummary"]');
@@ -606,7 +641,7 @@ describe('TableNG', () => {
expect(summaryText).toContain('of 100 rows');
} else {
// If we can't find the pagination summary by class, just check the container text
- expect(container.textContent).toContain('of 100 rows');
+ expect(container).toHaveTextContent(/of 100 rows/);
}
}
});
@@ -634,7 +669,7 @@ describe('TableNG', () => {
const sortButton = columnHeader.querySelector('button') || columnHeader;
// Click the sort button
- fireEvent.click(sortButton);
+ await user.click(sortButton);
// After clicking, the header should have an aria-sort attribute
const newSortAttribute = columnHeader.getAttribute('aria-sort');
@@ -666,7 +701,7 @@ describe('TableNG', () => {
}
});
- it('cycles through ascending, descending, and no sort states', () => {
+ it('cycles through ascending, descending, and no sort states', async () => {
// Mock scrollIntoView
window.HTMLElement.prototype.scrollIntoView = jest.fn();
@@ -682,23 +717,23 @@ describe('TableNG', () => {
const sortButton = columnHeader.querySelector('button') || columnHeader;
// Initial state - no sort
- expect(columnHeader.getAttribute('aria-sort')).toBeNull();
+ expect(columnHeader).not.toHaveAttribute('aria-sort');
// First click - should sort ascending
- fireEvent.click(sortButton);
- expect(columnHeader.getAttribute('aria-sort')).toBe('ascending');
+ await user.click(sortButton);
+ expect(columnHeader).toHaveAttribute('aria-sort', 'ascending');
// Second click - should sort descending
- fireEvent.click(sortButton);
- expect(columnHeader.getAttribute('aria-sort')).toBe('descending');
+ await user.click(sortButton);
+ expect(columnHeader).toHaveAttribute('aria-sort', 'descending');
// Third click - should remove sort
- fireEvent.click(sortButton);
- expect(columnHeader.getAttribute('aria-sort')).toBeNull();
+ await user.click(sortButton);
+ expect(columnHeader).not.toHaveAttribute('aria-sort');
}
});
- it('supports multi-column sorting with shift key', () => {
+ it('supports multi-column sorting with shift key', async () => {
// Mock scrollIntoView
window.HTMLElement.prototype.scrollIntoView = jest.fn();
@@ -761,7 +796,7 @@ describe('TableNG', () => {
const valueColumnButton = columnHeaders[1].querySelector('button') || columnHeaders[1];
// 1. First sort by Category (ascending)
- fireEvent.click(categoryColumnButton);
+ await user.click(categoryColumnButton);
// Check data is sorted by Category
const categoryOnlySortedRows = getCellTextContent();
@@ -796,7 +831,8 @@ describe('TableNG', () => {
expect(categoryBValues).toContain('4');
// 2. Now add second sort column (Value) with shift key
- fireEvent.click(valueColumnButton, { shiftKey: true });
+ await user.keyboard('{Shift>}');
+ await user.click(valueColumnButton);
// Check data is sorted by Category and then by Value
const multiSortedRows = getCellTextContent();
@@ -830,7 +866,8 @@ describe('TableNG', () => {
expect(multiSortedRows[4][2]).toBe('Alice');
// 3. Change Value sort direction to descending
- fireEvent.click(valueColumnButton, { shiftKey: true });
+ await user.keyboard('{Shift>}');
+ await user.click(valueColumnButton);
// Check data is sorted by Category (asc) and then by Value (desc)
const multiSortedRowsDesc = getCellTextContent();
@@ -864,7 +901,8 @@ describe('TableNG', () => {
expect(multiSortedRowsDesc[4][2]).toBe('Jane');
// 4. Test removing the secondary sort by clicking a third time
- fireEvent.click(valueColumnButton, { shiftKey: true });
+ await user.keyboard('{Shift>}');
+ await user.click(valueColumnButton);
// The data should still be sorted by Category only
const singleSortRows = getCellTextContent();
@@ -879,7 +917,7 @@ describe('TableNG', () => {
expect(singleSortRows[4][0]).toBe('B');
});
- it('correctly sorts different data types', () => {
+ it('correctly sorts different data types', async () => {
// Create a data frame with different data types
const mixedDataFrame = toDataFrame({
name: 'MixedData',
@@ -918,7 +956,7 @@ describe('TableNG', () => {
// Test string column sorting
const stringColumnButton = columnHeaders[0].querySelector('button') || columnHeaders[0];
- fireEvent.click(stringColumnButton);
+ await user.click(stringColumnButton);
// Get cell values after sorting
let cells = container.querySelectorAll('[role="gridcell"]');
@@ -930,7 +968,7 @@ describe('TableNG', () => {
// Test number column sorting
const numberColumnButton = columnHeaders[1].querySelector('button') || columnHeaders[1];
- fireEvent.click(numberColumnButton);
+ await user.click(numberColumnButton);
// Get cell values after sorting
cells = container.querySelectorAll('[role="gridcell"]');
@@ -1138,11 +1176,16 @@ describe('TableNG', () => {
// Find resize handle
const resizeHandles = container.querySelectorAll('.rdg-header-row > [role="columnheader"] .rdg-resizer');
+ // TODO: This `if` doesn't even trigger - the test is evergreen.
+ // We should work out a reliable way to actually find and trigger the resize methods
+ // The querySelector doesn't return anything!
if (resizeHandles.length > 0) {
// Simulate resize by triggering mousedown, mousemove, mouseup
+ /* eslint-disable testing-library/prefer-user-event */
fireEvent.mouseDown(resizeHandles[0]);
fireEvent.mouseMove(resizeHandles[0], { clientX: 250 });
fireEvent.mouseUp(resizeHandles[0]);
+ /* eslint-enable testing-library/prefer-user-event */
// Check that onColumnResize was called
expect(onColumnResize).toHaveBeenCalled();
@@ -1202,28 +1245,6 @@ describe('TableNG', () => {
});
describe('Context menu', () => {
- beforeEach(() => {
- // Mock ResizeObserver
- global.ResizeObserver = class ResizeObserver {
- constructor(callback: any) {
- // Store the callback
- this.callback = callback;
- }
- callback: any;
- observe() {
- // Do nothing
- }
- unobserve() {
- // Do nothing
- }
- disconnect() {
- // Do nothing
- }
- };
-
- window.HTMLElement.prototype.scrollIntoView = jest.fn();
- });
-
it('should show context menu on right-click', async () => {
const { container } = render(
@@ -1248,7 +1269,7 @@ describe('TableNG', () => {
});
describe('Cell inspection', () => {
- it('shows inspect icon when hovering over a cell with inspection enabled', () => {
+ it('shows inspect icon when hovering over a cell with inspection enabled', async () => {
const inspectDataFrame = {
...createBasicDataFrame(),
fields: createBasicDataFrame().fields.map((field) => ({
@@ -1285,7 +1306,7 @@ describe('TableNG', () => {
if (cellContent) {
// Trigger mouse enter on the cell content
- fireEvent.mouseEnter(cellContent);
+ await user.hover(cellContent);
// Look for the inspect icon
const inspectIcon = container.querySelector('[aria-label="Inspect value"]');
@@ -1491,13 +1512,12 @@ describe('TableNG', () => {
const dataGrid = screen.getByRole('grid');
// Simulate scrolling
- act(() => {
- fireEvent.scroll(dataGrid, {
- target: {
- scrollLeft: 100,
- scrollTop: 50,
- },
- });
+
+ fireEvent.scroll(dataGrid, {
+ target: {
+ scrollLeft: 100,
+ scrollTop: 50,
+ },
});
// Rerender with the same data but different fieldConfig to trigger revId change
diff --git a/packages/grafana-ui/src/components/Table/TableNG/utils.test.ts b/packages/grafana-ui/src/components/Table/TableNG/utils.test.ts
index 46b47a27929..8e6d662f19f 100644
--- a/packages/grafana-ui/src/components/Table/TableNG/utils.test.ts
+++ b/packages/grafana-ui/src/components/Table/TableNG/utils.test.ts
@@ -1781,18 +1781,18 @@ describe('TableNG utils', () => {
const props = createMockProps(1, false, 0);
const expandedRows: number[] = []; // No expanded rows
- const result = myRowRenderer('key-0', props, expandedRows, mockPanelContext, mockData, false);
+ const view = myRowRenderer('key-0', props, expandedRows, mockPanelContext, mockData, false);
- expect(result).toBeNull();
+ expect(view).toBeNull();
});
it('renders child rows when parent is expanded', () => {
const props = createMockProps(1, false, 0);
const expandedRows: number[] = [0]; // Row 0 is expanded
- const result = myRowRenderer('key-0', props, expandedRows, mockPanelContext, mockData, false);
+ const view = myRowRenderer('key-0', props, expandedRows, mockPanelContext, mockData, false);
- expect(result).not.toBeNull();
+ expect(view).not.toBeNull();
});
it('adds aria-expanded attribute to parent rows with nested data', () => {
diff --git a/packages/grafana-ui/src/components/TagsInput/TagsInput.test.tsx b/packages/grafana-ui/src/components/TagsInput/TagsInput.test.tsx
index 2cd8a0f7b3a..7721897de31 100644
--- a/packages/grafana-ui/src/components/TagsInput/TagsInput.test.tsx
+++ b/packages/grafana-ui/src/components/TagsInput/TagsInput.test.tsx
@@ -1,22 +1,25 @@
-import { render, fireEvent, screen } from '@testing-library/react';
+import { render, screen } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
import { TagsInput } from './TagsInput';
describe('TagsInput', () => {
it('removes tag when clicking on remove button', async () => {
+ const user = userEvent.setup();
const onChange = jest.fn();
render();
- fireEvent.click(await screen.findByRole('button', { name: /Remove tag: One/i }));
+ await user.click(await screen.findByRole('button', { name: /Remove tag: One/i }));
expect(onChange).toHaveBeenCalledWith(['Two']);
});
it('does NOT remove tag when clicking on remove button when disabled', async () => {
+ const user = userEvent.setup();
const onChange = jest.fn();
render();
- fireEvent.click(await screen.findByRole('button', { name: /Remove tag: One/i }));
+ await user.click(await screen.findByRole('button', { name: /Remove tag: One/i }));
expect(onChange).not.toHaveBeenCalled();
});
diff --git a/packages/grafana-ui/src/components/Toggletip/Toggletip.test.tsx b/packages/grafana-ui/src/components/Toggletip/Toggletip.test.tsx
index 347f2273624..5d503426735 100644
--- a/packages/grafana-ui/src/components/Toggletip/Toggletip.test.tsx
+++ b/packages/grafana-ui/src/components/Toggletip/Toggletip.test.tsx
@@ -75,7 +75,7 @@ describe('Toggletip', () => {
const button = screen.getByTestId('myButton');
await userEvent.click(button);
- expect(await screen.queryByTestId('toggletip-content')).not.toBeInTheDocument();
+ expect(screen.queryByTestId('toggletip-content')).not.toBeInTheDocument();
expect(onOpen).toHaveBeenCalledTimes(1);
});
diff --git a/packages/grafana-ui/src/components/uPlot/Plot.test.tsx b/packages/grafana-ui/src/components/uPlot/Plot.test.tsx
index 045b27a576d..8669061afaf 100644
--- a/packages/grafana-ui/src/components/uPlot/Plot.test.tsx
+++ b/packages/grafana-ui/src/components/uPlot/Plot.test.tsx
@@ -1,4 +1,4 @@
-import { render } from '@testing-library/react';
+import { render, screen } from '@testing-library/react';
import createMockRaf from 'mock-raf';
import uPlot from 'uplot';
@@ -115,11 +115,11 @@ describe('UPlotChart', () => {
describe('config update', () => {
it('skips uPlot intialization for width and height equal 0', async () => {
const { data, config } = mockData();
- const { queryAllByTestId } = render(
+ render(
);
- expect(queryAllByTestId('uplot-main-div')).toHaveLength(1);
+ expect(screen.queryAllByTestId('uplot-main-div')).toHaveLength(1);
expect(uPlot).not.toBeCalled();
});