// @vitest-environment happy-dom
import { beforeEach, afterEach, describe, expect, it, vi } from 'vitest';
import { Autocomplete, AutocompleteData } from './autocomplete';
// Mock dependencies if required by your setup
vi.mock('../dropdown/dropdown', () => ({
Dropdown: {
init: vi.fn(() => ({
open: vi.fn(),
close: vi.fn(),
recalculateDimensions: vi.fn(),
isOpen: false
}))
}
}));
vi.mock('../../src/utils', () => ({
Utils: {
guid: () => '12345',
keys: {
ENTER: ['Enter'],
ARROW_UP: ['ArrowUp'],
ARROW_DOWN: ['ArrowDown']
},
tabPressed: false
}
}));
describe('Autocomplete Component', () => {
let inputEl: HTMLInputElement;
let sampleData: AutocompleteData[];
beforeEach(() => {
document.body.innerHTML = `
`;
inputEl = document.querySelector('#autocomplete-input') as HTMLInputElement;
sampleData = [
{ id: 1, text: 'Apple', description: 'Fruit' },
{ id: 2, text: 'Banana', description: 'Fruit' },
{ id: 3, text: 'Cherry', description: 'Fruit' }
];
});
afterEach(() => {
const instance = Autocomplete.getInstance(inputEl);
if (instance) {
instance.destroy();
}
document.body.innerHTML = '';
vi.clearAllMocks();
});
it('should initialize correctly and attach instance to element', () => {
const instance = new Autocomplete(inputEl, { data: sampleData });
expect(instance).toBeInstanceOf(Autocomplete);
expect(Autocomplete.getInstance(inputEl)).toBe(instance);
expect(inputEl.getAttribute('data-target')).toContain('autocomplete-options-');
});
it('should render menu items when opened', () => {
const instance = new Autocomplete(inputEl, { data: sampleData, minLength: 1 });
inputEl.value = 'App';
instance.open();
const listItems = instance.container.querySelectorAll('li');
expect(listItems.length).toBe(3);
expect(listItems[0].getAttribute('data-id')).toBe('1');
expect(listItems[0].textContent).toContain('Apple');
});
it('should filter items via default onSearch handler', () => {
const instance = new Autocomplete(inputEl, { data: sampleData });
inputEl.value = 'Ban';
// Simulate input change detection triggered by keyup/focus
instance._inputChangeDetection('ban');
expect(instance.menuItems.length).toBe(1);
expect(instance.menuItems[0].text).toBe('Banana');
});
it('should trigger onAutocomplete callback when an option is selected', () => {
const onAutocompleteMock = vi.fn();
const instance = new Autocomplete(inputEl, {
data: sampleData,
onAutocomplete: onAutocompleteMock
});
instance.selectOption(2);
expect(onAutocompleteMock).toHaveBeenCalledWith([
{ id: 2, text: 'Banana', description: 'Fruit' }
]);
expect(inputEl.value).toBe('Banana');
});
it('should handle multi-selection correctly', () => {
const onAutocompleteMock = vi.fn();
const instance = new Autocomplete(inputEl, {
data: sampleData,
isMultiSelect: true,
onAutocomplete: onAutocompleteMock
});
instance.selectOption(1);
instance.selectOption(3);
expect(instance.selectedValues.length).toBe(2);
expect(instance.selectedValues).toEqual([sampleData[0], sampleData[2]]);
expect(onAutocompleteMock).toHaveBeenLastCalledWith([sampleData[0], sampleData[2]]);
});
it('should highlight partial matching text correctly', () => {
const instance = new Autocomplete(inputEl, { data: sampleData });
inputEl.value = 'App';
const item = instance._createDropdownItem(sampleData[0]);
const highlightSpan = item.querySelector('.highlight');
expect(highlightSpan).not.toBeNull();
expect(highlightSpan?.textContent).toBe('App');
});
it('should clean up DOM and event listeners on destroy', () => {
const instance = new Autocomplete(inputEl, { data: sampleData });
const containerId = instance.container.id;
instance.destroy();
expect(Autocomplete.getInstance(inputEl)).toBeUndefined();
expect(document.getElementById(containerId)).toBeNull();
});
});