import { describe, it, expect, vi, beforeEach } from 'vitest'; import { render, screen, fireEvent, waitFor } from '@testing-library/svelte'; import HandleInput from './HandleInput.svelte'; // Mock fetch global.fetch = vi.fn(); describe('HandleInput', () => { beforeEach(() => { vi.clearAllMocks(); }); it('resolves the first suggestion on Enter when no item is active', async () => { const fetchMock = vi.fn().mockResolvedValue({ ok: true, json: () => Promise.resolve({ actors: [{ handle: 'test.bsky.social', did: 'did:test' }] }) }); global.fetch = fetchMock; const form = document.createElement('form'); const requestSubmitSpy = vi.spyOn(form, 'requestSubmit').mockImplementation(() => {}); document.body.appendChild(form); render(HandleInput, { props: { value: '' }, target: form }); const input = screen.getByRole('combobox'); // Trigger input to load suggestions await fireEvent.input(input, { target: { value: 'test' } }); // Wait for debounce (300ms) and fetch await waitFor(() => expect(fetchMock).toHaveBeenCalled(), { timeout: 2000 }); // Wait for dropdown to appear await waitFor(() => expect(screen.queryByRole('listbox')).toBeTruthy(), { timeout: 2000 }); // Press Enter await fireEvent.keyDown(input, { key: 'Enter' }); // Should resolve to the first suggestion await waitFor(() => { const hiddenInput = form.querySelector('input[type="hidden"]'); expect(hiddenInput).toBeTruthy(); expect((hiddenInput as HTMLInputElement).value).toBe('test.bsky.social'); }, { timeout: 2000 }); // Should have requested submit await waitFor(() => expect(requestSubmitSpy).toHaveBeenCalled(), { timeout: 2000 }); document.body.removeChild(form); }); it('resolves the active suggestion on Enter', async () => { const fetchMock = vi.fn().mockResolvedValue({ ok: true, json: () => Promise.resolve({ actors: [ { handle: 'one.bsky.social', did: 'did:1' }, { handle: 'two.bsky.social', did: 'did:2' } ] }) }); global.fetch = fetchMock; const form = document.createElement('form'); const requestSubmitSpy = vi.spyOn(form, 'requestSubmit').mockImplementation(() => {}); document.body.appendChild(form); render(HandleInput, { props: { value: '' }, target: form }); const input = screen.getByRole('combobox'); await fireEvent.input(input, { target: { value: 'test' } }); await waitFor(() => expect(fetchMock).toHaveBeenCalled(), { timeout: 2000 }); await waitFor(() => expect(screen.queryByRole('listbox')).toBeTruthy(), { timeout: 2000 }); // Navigate to the second item (activeIndex starts at -1) await fireEvent.keyDown(input, { key: 'ArrowDown' }); // index 0 await fireEvent.keyDown(input, { key: 'ArrowDown' }); // index 1 // Press Enter await fireEvent.keyDown(input, { key: 'Enter' }); await waitFor(() => { const hiddenInput = form.querySelector('input[type="hidden"]'); expect(hiddenInput).toBeTruthy(); expect((hiddenInput as HTMLInputElement).value).toBe('two.bsky.social'); }, { timeout: 2000 }); await waitFor(() => expect(requestSubmitSpy).toHaveBeenCalled(), { timeout: 2000 }); document.body.removeChild(form); }); it('calls onselect and does NOT show a chip when onselect is provided', async () => { const fetchMock = vi.fn().mockResolvedValue({ ok: true, json: () => Promise.resolve({ actors: [{ handle: 'test.bsky.social', did: 'did:test' }] }) }); global.fetch = fetchMock; const onselect = vi.fn(); render(HandleInput, { props: { value: '', onselect } }); const input = screen.getByRole('combobox'); await fireEvent.input(input, { target: { value: 'test' } }); await waitFor(() => expect(fetchMock).toHaveBeenCalled(), { timeout: 2000 }); await waitFor(() => expect(screen.queryByRole('listbox')).toBeTruthy(), { timeout: 2000 }); // Press Enter await fireEvent.keyDown(input, { key: 'Enter' }); // Should call onselect await waitFor(() => expect(onselect).toHaveBeenCalledWith(expect.objectContaining({ handle: 'test.bsky.social' })), { timeout: 2000 }); // Should NOT show chip (hidden input should NOT exist) expect(document.querySelector('input[type="hidden"]')).toBeNull(); // Text input should STILL exist expect(screen.queryByRole('combobox')).toBeTruthy(); }); });