Governed substrate for autonomous agents: scoped identity (passports), audited actions, MCP workspace. Infra IPs and secrets redacted for public release.
189 lines
6.2 KiB
TypeScript
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();
|
|
});
|
|
});
|
|
});
|