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(
);
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(
);
// 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(
);
// 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(
);
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(
);
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(
);
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(
);
// 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(
{}}
options={[]}
hideFieldOptions
/>
);
// focus the input
await userEvent.click(screen.getByRole('textbox'));
expect(screen.getByText('Operators')).toBeInTheDocument();
expect(screen.queryByText('Fields')).not.toBeInTheDocument();
});
});