diff --git a/website/src/components/SearchPage/fields/AutoCompleteField.spec.tsx b/website/src/components/SearchPage/fields/AutoCompleteField.spec.tsx
new file mode 100644
index 000000000..1510ea6e6
--- /dev/null
+++ b/website/src/components/SearchPage/fields/AutoCompleteField.spec.tsx
@@ -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(
+ ,
+ );
+
+ 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(
+ ,
+ );
+
+ 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(
+ ,
+ );
+
+ 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(
+ ,
+ );
+
+ 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(
+ ,
+ );
+
+ 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(
+ ,
+ );
+
+ const input = screen.getByLabelText('Test Field');
+ fireEvent.focus(input);
+
+ const clearButton = screen.getByLabelText('Clear');
+ fireEvent.click(clearButton);
+
+ expect(setAFieldValue).toHaveBeenCalledWith('testField', '');
+ });
+});
diff --git a/website/src/components/SearchPage/fields/AutoCompleteField.tsx b/website/src/components/SearchPage/fields/AutoCompleteField.tsx
index 9d71fd81b..25b7a1299 100644
--- a/website/src/components/SearchPage/fields/AutoCompleteField.tsx
+++ b/website/src/components/SearchPage/fields/AutoCompleteField.tsx
@@ -105,6 +105,7 @@ export const AutoCompleteField = ({
setQuery('');
setAFieldValue(field.name, '');
}}
+ aria-label='Clear'
>