Skip to content

Commit

Permalink
chore(website): Add unit tests for autocomplete field (#2083)
Browse files Browse the repository at this point in the history
  • Loading branch information
theosanderson committed Jun 18, 2024
1 parent 1d4de86 commit 461f3e3
Show file tree
Hide file tree
Showing 2 changed files with 209 additions and 0 deletions.
208 changes: 208 additions & 0 deletions website/src/components/SearchPage/fields/AutoCompleteField.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
import { render, screen, fireEvent } from '@testing-library/react';
import { describe, it, expect, beforeEach, vi } from 'vitest';

import { AutoCompleteField } from './AutoCompleteField';
import { lapisClientHooks } from '../../../services/serviceHooks.ts';
import { type MetadataFilter } from '../../../types/config.ts';

vi.mock('../../../services/serviceHooks.ts');
vi.mock('../../../clientLogger.ts', () => ({
getClientLogger: () => ({
error: vi.fn(),
}),
}));

const mockUseAggregated = vi.fn();
// @ts-expect-error because mockReturnValue is not defined in the type definition
lapisClientHooks.mockReturnValue({
zodiosHooks: {
useAggregated: mockUseAggregated,
},
});

describe('AutoCompleteField', () => {
const field: MetadataFilter = {
name: 'testField',
label: 'Test Field',
type: 'string',
autocomplete: true,
};
const setAFieldValue = vi.fn();
const lapisUrl = 'https://example.com/api';
const lapisSearchParameters = { param1: 'value1' };

beforeEach(() => {
setAFieldValue.mockClear();
});

it('renders input and shows all all options on empty input', async () => {
mockUseAggregated.mockReturnValue({
data: {
data: [
{ testField: 'Option 1', count: 10 },
{ testField: 'Option 2', count: 20 },
],
},
isLoading: false,
error: null,
mutate: vi.fn(),
});

render(
<AutoCompleteField
field={field}
setAFieldValue={setAFieldValue}
lapisUrl={lapisUrl}
lapisSearchParameters={lapisSearchParameters}
/>,
);

const input = screen.getByLabelText('Test Field');
expect(input).toBeInTheDocument();

fireEvent.focus(input);

const options = await screen.findAllByRole('option');
expect(options).toHaveLength(2);
expect(options[0]).toHaveTextContent('Option 1(10)');
expect(options[1]).toHaveTextContent('Option 2(20)');
});

it('filters options based on query', async () => {
mockUseAggregated.mockReturnValue({
data: {
data: [
{ testField: 'Option 1', count: 10 },
{ testField: 'Option 2', count: 20 },
],
},
isLoading: false,
error: null,
mutate: vi.fn(),
});
render(
<AutoCompleteField
field={field}
setAFieldValue={setAFieldValue}
lapisUrl={lapisUrl}
lapisSearchParameters={lapisSearchParameters}
/>,
);

const input = screen.getByLabelText('Test Field');
fireEvent.focus(input);

fireEvent.change(input, { target: { value: 'Option 2' } });

const options = await screen.findAllByRole('option');
expect(options).toHaveLength(1);
expect(options[0]).toHaveTextContent('Option 2(20)');
});

it('displays loading state when aggregated endpoint is in isLoading state', () => {
mockUseAggregated.mockReturnValueOnce({
data: null,
isLoading: true,
error: null,
mutate: vi.fn(),
});

render(
<AutoCompleteField
field={field}
setAFieldValue={setAFieldValue}
lapisUrl={lapisUrl}
lapisSearchParameters={lapisSearchParameters}
/>,
);

const input = screen.getByLabelText('Test Field');
fireEvent.focus(input);

expect(screen.getByText('Loading...')).toBeInTheDocument();
});

it('displays error message when aggregated returns an error', () => {
mockUseAggregated.mockReturnValueOnce({
data: null,
isLoading: false,
error: { message: 'Error message', stack: 'Error stack' },
mutate: vi.fn(),
});

render(
<AutoCompleteField
field={field}
setAFieldValue={setAFieldValue}
lapisUrl={lapisUrl}
lapisSearchParameters={lapisSearchParameters}
/>,
);

const input = screen.getByLabelText('Test Field');
fireEvent.focus(input);

expect(screen.getByText('No options available')).toBeInTheDocument();
});

it('calls setAFieldValue, when an option is selected', async () => {
mockUseAggregated.mockReturnValue({
data: {
data: [
{ testField: 'Option 1', count: 10 },
{ testField: 'Option 2', count: 20 },
],
},
isLoading: false,
error: null,
mutate: vi.fn(),
});
render(
<AutoCompleteField
field={field}
setAFieldValue={setAFieldValue}
lapisUrl={lapisUrl}
lapisSearchParameters={lapisSearchParameters}
/>,
);

const input = screen.getByLabelText('Test Field');
fireEvent.focus(input);

const options = await screen.findAllByRole('option');
fireEvent.click(options[0]);

expect(setAFieldValue).toHaveBeenCalledWith('testField', 'Option 1');
});

it('clears input value on clear button click', async () => {
mockUseAggregated.mockReturnValue({
data: {
data: [
{ testField: 'Option 1', count: 10 },
{ testField: 'Option 2', count: 20 },
],
},
isLoading: false,
error: null,
mutate: vi.fn(),
});
render(
<AutoCompleteField
field={field}
setAFieldValue={setAFieldValue}
lapisUrl={lapisUrl}
fieldValue='Option 1'
lapisSearchParameters={lapisSearchParameters}
/>,
);

const input = screen.getByLabelText('Test Field');
fireEvent.focus(input);

const clearButton = screen.getByLabelText('Clear');
fireEvent.click(clearButton);

expect(setAFieldValue).toHaveBeenCalledWith('testField', '');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ export const AutoCompleteField = ({
setQuery('');
setAFieldValue(field.name, '');
}}
aria-label='Clear'
>
<svg className='w-5 h-5 text-gray-400' fill='currentColor' viewBox='0 0 20 20'>
<path
Expand Down

0 comments on commit 461f3e3

Please sign in to comment.