123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256 |
- import {render, screen, userEvent} from 'sentry-test/reactTestingLibrary';
- import type {Column} from 'sentry/utils/discover/fields';
- import {generateFieldAsString} from 'sentry/utils/discover/fields';
- import ArithmeticInput from 'sentry/views/discover/table/arithmeticInput';
- describe('ArithmeticInput', function () {
- const operators = ['+', '-', '*', '/', '(', ')'];
- const numericColumns: Column[] = [
- {kind: 'field', field: 'transaction.duration'},
- {kind: 'field', field: 'measurements.lcp'},
- {kind: 'field', field: 'spans.http'},
- {kind: 'function', function: ['p50', '', undefined, undefined]},
- {
- kind: 'function',
- function: ['percentile', 'transaction.duration', '0.25', undefined],
- },
- {kind: 'function', function: ['count', '', undefined, undefined]},
- ];
- const columns: Column[] = [
- ...numericColumns,
- // these columns will not be rendered in the dropdown
- {kind: 'function', function: ['any', 'transaction.duration', undefined, undefined]},
- {kind: 'field', field: 'transaction'},
- {kind: 'function', function: ['failure_rate', '', undefined, undefined]},
- {kind: 'equation', field: 'transaction.duration+measurements.lcp'},
- ];
- it('can toggle autocomplete dropdown on focus and blur', async function () {
- render(
- <ArithmeticInput
- name="refinement"
- key="parameter:text"
- type="text"
- required
- value=""
- onUpdate={jest.fn()}
- options={columns}
- />
- );
- expect(screen.queryByText('Fields')).not.toBeInTheDocument();
- // focus the input
- await userEvent.click(screen.getByRole('textbox'));
- expect(screen.getByText('Fields')).toBeInTheDocument();
- // moves focus away from the input
- await userEvent.tab();
- expect(screen.queryByText('Fields')).not.toBeInTheDocument();
- });
- it('renders only numeric options in autocomplete', async function () {
- render(
- <ArithmeticInput
- name="refinement"
- key="parameter:text"
- type="text"
- required
- value=""
- onUpdate={jest.fn()}
- options={columns}
- />
- );
- // focus the input
- await userEvent.click(screen.getByRole('textbox'));
- const listItems = screen.getAllByRole('listitem');
- // options + headers that are inside listitem
- expect(listItems).toHaveLength(numericColumns.length + operators.length + 2);
- const options = listItems.filter(
- item => item.textContent !== 'Fields' && item.textContent !== 'Operators'
- );
- options.forEach((option, i) => {
- if (i < numericColumns.length) {
- expect(option).toHaveTextContent(generateFieldAsString(numericColumns[i]));
- return;
- }
- expect(option).toHaveTextContent(operators[i - numericColumns.length]);
- });
- });
- it('can use keyboard to select an option', async function () {
- render(
- <ArithmeticInput
- name="refinement"
- key="parameter:text"
- type="text"
- required
- value=""
- onUpdate={jest.fn()}
- options={columns}
- />
- );
- // focus the input
- await userEvent.click(screen.getByRole('textbox'));
- for (const column of numericColumns) {
- await userEvent.keyboard('{ArrowDown}');
- expect(
- screen.getByRole('listitem', {name: generateFieldAsString(column)})
- ).toHaveClass('active', {exact: false});
- }
- for (const operator of operators) {
- await userEvent.keyboard('{ArrowDown}');
- expect(screen.getByRole('listitem', {name: operator})).toHaveClass('active', {
- exact: false,
- });
- }
- // wrap around to the first option again
- await userEvent.keyboard('{ArrowDown}');
- for (const operator of [...operators].reverse()) {
- await userEvent.keyboard('{ArrowUp}');
- expect(screen.getByRole('listitem', {name: operator})).toHaveClass('active', {
- exact: false,
- });
- }
- for (const column of [...numericColumns].reverse()) {
- await userEvent.keyboard('{ArrowUp}');
- expect(
- screen.getByRole('listitem', {name: generateFieldAsString(column)})
- ).toHaveClass('active', {
- exact: false,
- });
- }
- // the update is buffered until blur happens
- await userEvent.keyboard('{Enter}');
- await userEvent.keyboard('{Escape}');
- expect(screen.getByRole('textbox')).toHaveValue(
- `${generateFieldAsString(numericColumns[0])} `
- );
- });
- it('can use mouse to select an option', async function () {
- render(
- <ArithmeticInput
- name="refinement"
- key="parameter:text"
- type="text"
- required
- value=""
- onUpdate={jest.fn()}
- options={columns}
- />
- );
- await userEvent.click(screen.getByRole('textbox'));
- await userEvent.click(screen.getByText(generateFieldAsString(numericColumns[2])));
- expect(screen.getByRole('textbox')).toHaveValue(
- `${generateFieldAsString(numericColumns[2])} `
- );
- });
- it('autocompletes the current term when it is in the front', async function () {
- render(
- <ArithmeticInput
- name="refinement"
- key="parameter:text"
- type="text"
- required
- value=""
- onUpdate={jest.fn()}
- options={columns}
- />
- );
- const element = screen.getByRole('textbox') as HTMLInputElement;
- await userEvent.type(element, 'lcp + transaction.duration');
- await userEvent.type(element, '{ArrowLeft>24}');
- await userEvent.click(screen.getByText('measurements.lcp'));
- expect(screen.getByRole('textbox')).toHaveValue(
- 'measurements.lcp + transaction.duration'
- );
- });
- it('autocompletes the current term when it is in the end', async function () {
- render(
- <ArithmeticInput
- name="refinement"
- key="parameter:text"
- type="text"
- required
- value=""
- onUpdate={jest.fn()}
- options={columns}
- />
- );
- await userEvent.type(screen.getByRole('textbox'), 'transaction.duration + lcp');
- await userEvent.click(screen.getByText('measurements.lcp'));
- expect(screen.getByRole('textbox')).toHaveValue(
- 'transaction.duration + measurements.lcp '
- );
- });
- it('handles autocomplete on invalid term', async function () {
- render(
- <ArithmeticInput
- name="refinement"
- key="parameter:text"
- type="text"
- required
- value=""
- onUpdate={jest.fn()}
- options={columns}
- />
- );
- // focus the input
- await userEvent.type(screen.getByRole('textbox'), 'foo + bar');
- await userEvent.keyboard('{keydown}');
- expect(screen.getAllByText('No items found')).toHaveLength(2);
- });
- it('can hide Fields options', async function () {
- render(
- <ArithmeticInput
- name="refinement"
- type="text"
- required
- value=""
- onUpdate={() => {}}
- options={[]}
- hideFieldOptions
- />
- );
- // focus the input
- await userEvent.click(screen.getByRole('textbox'));
- expect(screen.getByText('Operators')).toBeInTheDocument();
- expect(screen.queryByText('Fields')).not.toBeInTheDocument();
- });
- });
|