import { describe, it, expect, vi } from 'vitest' import { render, screen, fireEvent } from '@testing-library/react' import { StatusPill, StatusDropdown } from './StatusDropdown' describe('StatusPill', () => { it('renders with known status style', () => { render() const pill = screen.getByTitle('Active') expect(pill).toBeInTheDocument() expect(pill.textContent).toBe('Active') expect(pill.style.backgroundColor).toBe('var(--accent-green-light)') expect(pill.style.color).toBe('var(--accent-green)') }) it('renders with default style for unknown status', () => { render() const pill = screen.getByTitle('Custom Thing') expect(pill.style.backgroundColor).toBe('var(--accent-blue-light)') expect(pill.style.color).toBe('var(--muted-foreground)') }) it('applies truncate class for long names', () => { render() const pill = screen.getByTitle('Very Long Status Name That Should Truncate') expect(pill.className).toContain('truncate') }) }) describe('StatusDropdown', () => { const onSave = vi.fn() const onCancel = vi.fn() const defaultProps = { value: 'Active', vaultStatuses: ['Draft', 'Published'], onSave, onCancel, } beforeEach(() => { vi.clearAllMocks() }) it('renders dropdown with search input', () => { render() expect(screen.getByTestId('status-dropdown')).toBeInTheDocument() expect(screen.getByTestId('status-search-input')).toBeInTheDocument() }) it('shows suggested statuses', () => { render() expect(screen.getByTestId('status-option-Not started')).toBeInTheDocument() expect(screen.getByTestId('status-option-In progress')).toBeInTheDocument() expect(screen.getByTestId('status-option-Active')).toBeInTheDocument() expect(screen.getByTestId('status-option-Done')).toBeInTheDocument() expect(screen.getByTestId('status-option-Blocked')).toBeInTheDocument() }) it('shows vault statuses separately from suggested', () => { render() expect(screen.getByTestId('status-option-Draft')).toBeInTheDocument() expect(screen.getByTestId('status-option-Published')).toBeInTheDocument() expect(screen.getByText('From vault')).toBeInTheDocument() expect(screen.getByText('Suggested')).toBeInTheDocument() }) it('does not duplicate vault statuses in suggested list', () => { render() // Active should appear in vault section, not suggested const activeOptions = screen.getAllByTestId('status-option-Active') expect(activeOptions).toHaveLength(1) }) it('calls onSave when a status option is clicked', () => { render() fireEvent.click(screen.getByTestId('status-option-Done')) expect(onSave).toHaveBeenCalledWith('Done') }) it('calls onCancel when backdrop is clicked', () => { render() fireEvent.click(screen.getByTestId('status-dropdown-backdrop')) expect(onCancel).toHaveBeenCalled() }) it('calls onCancel when Escape is pressed', () => { render() fireEvent.keyDown(screen.getByTestId('status-search-input'), { key: 'Escape' }) expect(onCancel).toHaveBeenCalled() }) it('filters options when typing in search', () => { render() const input = screen.getByTestId('status-search-input') fireEvent.change(input, { target: { value: 'blo' } }) expect(screen.getByTestId('status-option-Blocked')).toBeInTheDocument() expect(screen.queryByTestId('status-option-Done')).not.toBeInTheDocument() }) it('shows create option when typing a new status name', () => { render() const input = screen.getByTestId('status-search-input') fireEvent.change(input, { target: { value: 'Needs Review' } }) expect(screen.getByTestId('status-create-option')).toBeInTheDocument() }) it('does not show create option when query matches existing status', () => { render() const input = screen.getByTestId('status-search-input') fireEvent.change(input, { target: { value: 'Active' } }) expect(screen.queryByTestId('status-create-option')).not.toBeInTheDocument() }) it('creates custom status on Enter when no match', () => { render() const input = screen.getByTestId('status-search-input') fireEvent.change(input, { target: { value: 'Needs Review' } }) fireEvent.keyDown(input, { key: 'Enter' }) expect(onSave).toHaveBeenCalledWith('Needs Review') }) it('navigates options with arrow keys', () => { render() const input = screen.getByTestId('status-search-input') // Arrow down to first option fireEvent.keyDown(input, { key: 'ArrowDown' }) // Arrow down again to second option fireEvent.keyDown(input, { key: 'ArrowDown' }) // Press Enter to select second option (In progress) fireEvent.keyDown(input, { key: 'Enter' }) expect(onSave).toHaveBeenCalledWith('In progress') }) it('shows no statuses message when filter yields no results and no create option', () => { render() const input = screen.getByTestId('status-search-input') // Type just a space (no trim match but not empty) fireEvent.change(input, { target: { value: ' ' } }) // With only spaces, query.trim() is empty so no create option // And no options match spaces // But query is not empty so the filter runs... let's use something truly no-match fireEvent.change(input, { target: { value: '' } }) // With empty query, all options are shown - this verifies the fallback expect(screen.getByTestId('status-option-Active')).toBeInTheDocument() }) it('shows only default suggestions when vault has no statuses', () => { render() expect(screen.queryByText('From vault')).not.toBeInTheDocument() expect(screen.getByText('Suggested')).toBeInTheDocument() expect(screen.getByTestId('status-option-Active')).toBeInTheDocument() }) it('preserves user input case — no title-casing', () => { render() const input = screen.getByTestId('status-search-input') fireEvent.change(input, { target: { value: 'wIP' } }) fireEvent.keyDown(input, { key: 'Enter' }) expect(onSave).toHaveBeenCalledWith('wIP') }) })