godcrm/components/__tests__/MentionInput.test.tsx
GOD CRM Release f89e074dd1
Some checks failed
CI / Lint / Typecheck / Test / Build (push) Has been cancelled
CI / PostgreSQL Integration Tests (push) Has been cancelled
GOD CRM — public scrubbed snapshot
Governed substrate for autonomous agents: scoped identity (passports),
audited actions, MCP workspace. Infra IPs and secrets redacted for public release.
2026-08-10 04:01:45 +03:00

189 lines
6.2 KiB
TypeScript

/**
* Tests for MentionInput Component
* ADR-023: Agent-as-User & Infinite Chat Architecture
*/
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { MentionInput, MentionUser } from '../MentionInput';
describe('MentionInput', () => {
const mockUsers: MentionUser[] = [
{ id: 1, name: 'General Assistant', type: 'agent', icon: '🤖' },
{ id: 2, name: 'Data Analyst', type: 'agent', icon: '📊' },
{ id: 3, name: 'John Doe', type: 'human', email: 'john@example.com' }
];
const defaultProps = {
value: '',
onChange: vi.fn(),
onMention: vi.fn(),
onSubmit: vi.fn(),
availableUsers: mockUsers
};
beforeEach(() => {
vi.clearAllMocks();
});
it('should render textarea with placeholder', () => {
render(<MentionInput {...defaultProps} />);
expect(screen.getByPlaceholderText(/Type @ to mention/i)).toBeInTheDocument();
});
it('should call onChange when typing', async () => {
const user = userEvent.setup();
render(<MentionInput {...defaultProps} />);
const input = screen.getByRole('textbox');
await user.type(input, 'Hello');
expect(defaultProps.onChange).toHaveBeenCalled();
});
// Note: dropdown is rendered via portal and depends on cursor position
// which is difficult to simulate in jsdom. Tested manually via e2e.
it.skip('should show dropdown when typing @', async () => {
const user = userEvent.setup();
const { rerender } = render(<MentionInput {...defaultProps} />);
const input = screen.getByRole('textbox');
await user.type(input, '@');
// Update value prop to simulate controlled input
rerender(<MentionInput {...defaultProps} value="@" />);
// Dropdown should appear with available users
await waitFor(() => {
expect(screen.getByText('General Assistant')).toBeInTheDocument();
});
});
// Note: filtering requires dropdown to be visible
it.skip('should filter users based on mention query', async () => {
const user = userEvent.setup();
const { rerender } = render(<MentionInput {...defaultProps} />);
const input = screen.getByRole('textbox');
await user.type(input, '@data');
rerender(<MentionInput {...defaultProps} value="@data" />);
await waitFor(() => {
expect(screen.getByText('Data Analyst')).toBeInTheDocument();
expect(screen.queryByText('General Assistant')).not.toBeInTheDocument();
});
});
// Note: selecting user requires dropdown to be visible
it.skip('should call onMention when selecting a user', async () => {
const user = userEvent.setup();
const { rerender } = render(<MentionInput {...defaultProps} />);
const input = screen.getByRole('textbox');
await user.type(input, '@');
rerender(<MentionInput {...defaultProps} value="@" />);
await waitFor(() => {
expect(screen.getByText('General Assistant')).toBeInTheDocument();
});
await user.click(screen.getByText('General Assistant'));
expect(defaultProps.onMention).toHaveBeenCalledWith(
expect.objectContaining({
id: 1,
name: 'General Assistant',
type: 'agent'
})
);
});
it('should call onSubmit when pressing Enter', async () => {
const user = userEvent.setup();
render(<MentionInput {...defaultProps} value="Hello world" />);
const input = screen.getByRole('textbox');
await user.type(input, '{enter}');
expect(defaultProps.onSubmit).toHaveBeenCalled();
});
it('should not call onSubmit when pressing Shift+Enter', async () => {
const user = userEvent.setup();
render(<MentionInput {...defaultProps} value="Hello world" />);
const input = screen.getByRole('textbox');
await user.type(input, '{shift>}{enter}{/shift}');
expect(defaultProps.onSubmit).not.toHaveBeenCalled();
});
it('should be disabled when disabled prop is true', () => {
render(<MentionInput {...defaultProps} disabled />);
const input = screen.getByRole('textbox');
expect(input).toBeDisabled();
});
// Note: "No results" message is rendered via portal and requires cursor position
// which is difficult to test in jsdom. This is tested manually in e2e tests.
it.skip('should show no results message when no matches', async () => {
const user = userEvent.setup();
const { rerender } = render(<MentionInput {...defaultProps} />);
const input = screen.getByRole('textbox');
await user.type(input, '@xyz123');
rerender(<MentionInput {...defaultProps} value="@xyz123" />);
await waitFor(() => {
expect(screen.getByText(/No users matching/)).toBeInTheDocument();
});
});
// Note: Arrow key navigation requires complex state management
// that depends on dropdown being visible, which requires cursor position
it.skip('should navigate dropdown with arrow keys', async () => {
const user = userEvent.setup();
const { rerender } = render(<MentionInput {...defaultProps} />);
const input = screen.getByRole('textbox');
await user.type(input, '@');
rerender(<MentionInput {...defaultProps} value="@" />);
await waitFor(() => {
expect(screen.getByText('General Assistant')).toBeInTheDocument();
});
// Arrow down should select next item
await user.keyboard('{arrowdown}');
// The second item (Data Analyst) should now be highlighted
// This is verified by checking if the element has the selected class
});
it('should close dropdown when pressing Escape', async () => {
const user = userEvent.setup();
const { rerender } = render(<MentionInput {...defaultProps} />);
const input = screen.getByRole('textbox');
await user.type(input, '@');
rerender(<MentionInput {...defaultProps} value="@" />);
await waitFor(() => {
expect(screen.getByText('General Assistant')).toBeInTheDocument();
});
await user.keyboard('{escape}');
await waitFor(() => {
expect(screen.queryByText('General Assistant')).not.toBeInTheDocument();
});
});
});