Chore: Apply testing-library rules to grafana-ui package tests (#105140)
* Apply testing-library rules to grafana-ui package tests * Apply auto fixes * Fix eslint test issues in grafana-ui package * Fix prettier issues * Address review feedback
This commit is contained in:
+15
-2
@@ -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',
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -46,7 +46,7 @@ describe('BigValue', () => {
|
||||
|
||||
it('should render without percent change', () => {
|
||||
render(<BigValue {...getProps()} />);
|
||||
expect(screen.queryByText('%')).toBeNull();
|
||||
expect(screen.queryByText('%')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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(
|
||||
<Card onClick={callback}>
|
||||
<Card.Heading>Test Heading</Card.Heading>
|
||||
</Card>
|
||||
);
|
||||
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', () => {
|
||||
</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(
|
||||
<Card disabled>
|
||||
@@ -78,8 +80,8 @@ describe('Card', () => {
|
||||
</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', () => {
|
||||
</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();
|
||||
});
|
||||
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -30,6 +30,12 @@ const numericOptions: Array<ComboboxOption<number>> = [
|
||||
];
|
||||
|
||||
describe('Combobox', () => {
|
||||
let user: ReturnType<typeof userEvent.setup>;
|
||||
|
||||
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(<Combobox options={options} value={options[1].value} onChange={onChangeHandler} isClearable />);
|
||||
|
||||
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(<Combobox options={options} value={null} onChange={onChangeHandler} width="auto" />);
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
+1
-1
@@ -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 () => {
|
||||
|
||||
+1
-1
@@ -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', () => {
|
||||
|
||||
@@ -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(<DatePicker isOpen={true} onChange={onChange} onClose={jest.fn()} />);
|
||||
|
||||
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(<DatePicker isOpen={true} onChange={jest.fn()} onClose={onClose} />);
|
||||
|
||||
expect(onClose).not.toHaveBeenCalled();
|
||||
|
||||
fireEvent.click(document);
|
||||
await user.click(document.body);
|
||||
|
||||
expect(onClose).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
+19
-13
@@ -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<typeof userEvent.setup>;
|
||||
|
||||
beforeEach(() => {
|
||||
user = userEvent.setup({ applyAccept: false });
|
||||
});
|
||||
|
||||
it('renders date input', () => {
|
||||
render(<DatePickerWithInput onChange={jest.fn()} value={new Date(1400000000000)} />);
|
||||
|
||||
@@ -24,43 +31,42 @@ describe('DatePickerWithInput', () => {
|
||||
});
|
||||
|
||||
describe('input is clicked', () => {
|
||||
it('renders input', () => {
|
||||
it('renders input', async () => {
|
||||
render(<DatePickerWithInput onChange={jest.fn()} />);
|
||||
|
||||
fireEvent.click(screen.getByPlaceholderText('Date'));
|
||||
await user.click(screen.getByPlaceholderText('Date'));
|
||||
|
||||
expect(screen.getByPlaceholderText('Date')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders calendar', () => {
|
||||
it('renders calendar', async () => {
|
||||
render(<DatePickerWithInput onChange={jest.fn()} />);
|
||||
|
||||
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(<DatePickerWithInput onChange={onChange} />);
|
||||
|
||||
// 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(<DatePickerWithInput onChange={jest.fn()} />);
|
||||
|
||||
// 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();
|
||||
});
|
||||
|
||||
+1
-1
@@ -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) => {
|
||||
|
||||
+16
-16
@@ -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
|
||||
});
|
||||
});
|
||||
|
||||
@@ -86,7 +86,7 @@ describe('TimeRangeProvider', () => {
|
||||
context2 = val;
|
||||
}
|
||||
|
||||
const renderContext = render(
|
||||
const { rerender } = render(
|
||||
<TimeRangeProvider>
|
||||
<TestComponent onContextChange={onContextChange} />
|
||||
<TestComponent onContextChange={onContextChange2} />
|
||||
@@ -104,7 +104,7 @@ describe('TimeRangeProvider', () => {
|
||||
syncedValue: timeRange,
|
||||
});
|
||||
|
||||
renderContext.rerender(
|
||||
rerender(
|
||||
<TimeRangeProvider>
|
||||
<TestComponent onContextChange={onContextChange2} />
|
||||
</TimeRangeProvider>
|
||||
|
||||
@@ -20,7 +20,7 @@ const value: TimeRange = {
|
||||
|
||||
describe('TimePicker', () => {
|
||||
it('renders buttons correctly', () => {
|
||||
const container = render(
|
||||
render(
|
||||
<TimeRangePicker
|
||||
onChangeTimeZone={() => {}}
|
||||
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(
|
||||
<form onSubmit={onSubmit}>
|
||||
<TimeRangePicker
|
||||
onChangeTimeZone={() => {}}
|
||||
@@ -107,7 +107,7 @@ it('does not submit wrapping forms', async () => {
|
||||
</form>
|
||||
);
|
||||
|
||||
const clicks = container.getAllByRole('button').map((button) => userEvent.click(button));
|
||||
const clicks = screen.getAllByRole('button').map((button) => userEvent.click(button));
|
||||
|
||||
await Promise.all(clicks);
|
||||
|
||||
|
||||
+13
-13
@@ -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', () => {
|
||||
|
||||
+29
-32
@@ -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(
|
||||
<TimeRangeContent isFullscreen={true} value={initial} onApply={() => {}} timeZone={timeZone} />
|
||||
);
|
||||
|
||||
function setup(initial: TimeRange = defaultTimeRange, timeZone = 'utc') {
|
||||
return {
|
||||
...result,
|
||||
...render(<TimeRangeContent isFullscreen={true} value={initial} onApply={() => {}} 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<typeof userEvent.setup>;
|
||||
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 })
|
||||
);
|
||||
|
||||
@@ -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<typeof userEvent.setup>;
|
||||
|
||||
beforeEach(() => {
|
||||
user = userEvent.setup();
|
||||
});
|
||||
|
||||
const testid = 'test';
|
||||
const getListElement = (
|
||||
ref: RefObject<HTMLUListElement>,
|
||||
@@ -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<HTMLUListElement>();
|
||||
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<HTMLUListElement>();
|
||||
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();
|
||||
});
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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(<FileDropzone />);
|
||||
|
||||
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 () => {
|
||||
|
||||
@@ -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<typeof userEvent.setup>;
|
||||
|
||||
beforeEach(() => {
|
||||
user = userEvent.setup({ applyAccept: false });
|
||||
});
|
||||
|
||||
it('should show an error message when error prop is not null', () => {
|
||||
render(<FileListItem file={{ file: file({}), id: '1', error: new DOMException('error') }} />);
|
||||
|
||||
@@ -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(<FileListItem file={{ file: file({}), id: '1', error: new DOMException('error'), retryUpload }} />);
|
||||
|
||||
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(<FileListItem file={{ file: file({}), id: '1', error: null, progress: 6, abortUpload }} />);
|
||||
|
||||
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(<FileListItem file={customFile} removeFile={removeFile} />);
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: REMOVE_FILE }));
|
||||
await user.click(screen.getByRole('button', { name: REMOVE_FILE }));
|
||||
|
||||
expect(removeFile).toHaveBeenCalledWith(customFile);
|
||||
});
|
||||
|
||||
@@ -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<typeof userEvent.setup>;
|
||||
|
||||
beforeEach(() => {
|
||||
user = userEvent.setup({ applyAccept: false });
|
||||
});
|
||||
|
||||
it('should render upload button with default text and no file name', () => {
|
||||
render(<FileUpload onFileUpload={() => {}} />);
|
||||
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(<FileUpload onFileUpload={() => {}} />);
|
||||
render(<FileUpload onFileUpload={() => {}} />);
|
||||
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(<FileUpload onFileUpload={onFileUpload} showFileName={true} />);
|
||||
let uploader = getByTestId(selectors.components.FileUpload.inputField);
|
||||
await waitFor(() =>
|
||||
fireEvent.change(uploader, {
|
||||
target: { files: [file] },
|
||||
})
|
||||
);
|
||||
let uploaderLabel = getByTestId(selectors.components.FileUpload.fileNameSpan);
|
||||
render(<FileUpload onFileUpload={onFileUpload} showFileName={true} />);
|
||||
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(<FileUpload onFileUpload={onFileUpload} showFileName={true} />);
|
||||
let uploader = getByTestId(selectors.components.FileUpload.inputField);
|
||||
await waitFor(() =>
|
||||
fireEvent.change(uploader, {
|
||||
target: { files: [file] },
|
||||
})
|
||||
);
|
||||
let uploaderLabel = getByTestId(selectors.components.FileUpload.fileNameSpan);
|
||||
render(<FileUpload onFileUpload={onFileUpload} showFileName={true} />);
|
||||
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');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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(<InteractiveTable columns={columns} data={data} getRowId={getRowId} pageSize={0} />);
|
||||
|
||||
expect(screen.queryByRole('button', { name: /next/i })).not.toBeInTheDocument();
|
||||
|
||||
@@ -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<Props>) => {
|
||||
const editorProps = { ...props, ...testProps };
|
||||
return render(<RefIDPicker {...editorProps} />);
|
||||
return {
|
||||
...render(<RefIDPicker {...editorProps} />),
|
||||
user: userEvent.setup(),
|
||||
};
|
||||
};
|
||||
|
||||
const multiSetup = (testProps?: Partial<MultiProps>) => {
|
||||
@@ -57,10 +61,10 @@ const multiSetup = (testProps?: Partial<MultiProps>) => {
|
||||
|
||||
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 */
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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<typeof userEvent.setup>;
|
||||
|
||||
beforeEach(() => {
|
||||
user = userEvent.setup();
|
||||
});
|
||||
|
||||
const getMenuItem = (props?: Partial<MenuItemProps>) => (
|
||||
<MenuItem ariaLabel={selectors.components.Menu.MenuItem('Test')} label="item1" icon="history" {...props} />
|
||||
);
|
||||
@@ -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();
|
||||
});
|
||||
|
||||
@@ -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<typeof userEvent.setup>;
|
||||
|
||||
beforeEach(() => {
|
||||
user = userEvent.setup();
|
||||
});
|
||||
|
||||
const testid = 'test';
|
||||
const getMenuElement = (
|
||||
ref: RefObject<HTMLDivElement>,
|
||||
@@ -23,7 +30,7 @@ describe('useMenuFocus', () => {
|
||||
</div>
|
||||
);
|
||||
|
||||
it('sets correct focused item on keydown', () => {
|
||||
it('sets correct focused item on keydown', async () => {
|
||||
const ref = createRef<HTMLDivElement>();
|
||||
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<HTMLDivElement>();
|
||||
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<HTMLDivElement>();
|
||||
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<HTMLDivElement>();
|
||||
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<HTMLDivElement>();
|
||||
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);
|
||||
});
|
||||
|
||||
@@ -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<PanelChromeProps>) => {
|
||||
};
|
||||
|
||||
Object.assign(props, propOverrides);
|
||||
return render(<PanelChrome {...props} />);
|
||||
return {
|
||||
...render(<PanelChrome {...props} />),
|
||||
user: userEvent.setup(),
|
||||
};
|
||||
};
|
||||
|
||||
const setupWithToggleCollapsed = (propOverrides?: Partial<PanelChromeProps>) => {
|
||||
@@ -37,7 +41,10 @@ const setupWithToggleCollapsed = (propOverrides?: Partial<PanelChromeProps>) =>
|
||||
return <PanelChrome {...props} collapsed={collapsed} onToggleCollapse={toggleCollapsed} />;
|
||||
};
|
||||
|
||||
return render(<ControlledCollapseComponent />);
|
||||
return {
|
||||
...render(<ControlledCollapseComponent />),
|
||||
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');
|
||||
|
||||
@@ -13,7 +13,7 @@ describe('<SecretInput />', () => {
|
||||
|
||||
// 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('<SecretInput />', () => {
|
||||
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('<SecretInput />', () => {
|
||||
|
||||
// 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 }));
|
||||
|
||||
@@ -15,7 +15,7 @@ describe('<SecretTextArea />', () => {
|
||||
|
||||
// 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('<SecretTextArea />', () => {
|
||||
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('<SecretTextArea />', () => {
|
||||
|
||||
// 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 }));
|
||||
|
||||
@@ -26,7 +26,7 @@ describe('SelectBase', () => {
|
||||
];
|
||||
|
||||
it('renders without error', () => {
|
||||
render(<SelectBase onChange={onChangeHandler} />);
|
||||
expect(() => render(<SelectBase onChange={onChangeHandler} />)).not.toThrow();
|
||||
});
|
||||
|
||||
it('renders empty options information', async () => {
|
||||
@@ -60,7 +60,7 @@ describe('SelectBase', () => {
|
||||
};
|
||||
|
||||
render(<Test />);
|
||||
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(<SelectBase onChange={onChangeHandler} />);
|
||||
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);
|
||||
|
||||
@@ -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(<Table {...props} />);
|
||||
|
||||
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' });
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -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<typeof userEvent.setup>;
|
||||
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(
|
||||
<TableNG enableVirtualization={false} data={createBasicDataFrame()} width={400} height={400} />
|
||||
@@ -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
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
@@ -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(<TagsInput onChange={onChange} tags={['One', 'Two']} />);
|
||||
|
||||
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(<TagsInput onChange={onChange} tags={['One', 'Two']} disabled />);
|
||||
|
||||
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();
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
|
||||
@@ -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(
|
||||
<UPlotChart data={preparePlotData2(data, getStackingGroups(data))} config={config} width={0} height={0} />
|
||||
);
|
||||
|
||||
expect(queryAllByTestId('uplot-main-div')).toHaveLength(1);
|
||||
expect(screen.queryAllByTestId('uplot-main-div')).toHaveLength(1);
|
||||
expect(uPlot).not.toBeCalled();
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user