import React from 'react';
import { render, screen, fireEvent, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event'; // For more realistic interactions with Select
import EditSlackConnectorConfigForm from './EditSlackConnectorConfigForm';
import { SearchSourceConnector } from '@/hooks/useSearchSourceConnectors'; // Adjust path as needed
// Mock UI components that are not part of the core logic being tested, if necessary
// For ShadCN components, often direct interaction is fine, but sometimes they need setup.
// For this test, we'll assume direct interaction works.
// jest.mock("@/components/ui/input", () => (props: any) => );
// jest.mock("@/components/ui/checkbox", () => (props: any) => );
// jest.mock("@/components/ui/select", () => ({
// Select: (props: any) =>
{props.children}
,
// SelectTrigger: (props: any) => {props.children} ,
// SelectValue: (props: any) => {props.placeholder}
,
// SelectContent: (props: any) => {props.children}
,
// SelectItem: (props: any) => {props.children} ,
// }));
const initialMockConfig = {
SLACK_BOT_TOKEN: 'xoxb-initial-token',
slack_membership_filter_type: 'all_member_channels',
slack_selected_channel_ids: ['C123', 'C456'],
slack_initial_indexing_days: 30,
slack_initial_max_messages_per_channel: 1000,
slack_periodic_indexing_enabled: false,
slack_periodic_indexing_frequency: 'daily',
slack_max_messages_per_channel_periodic: 100,
};
const mockConnector: SearchSourceConnector = {
id: 1,
name: 'Slack Test Connector',
connector_type: 'SLACK_CONNECTOR',
is_indexable: true,
last_indexed_at: null,
config: { ...initialMockConfig },
user_id: 'user1',
created_at: new Date().toISOString(),
};
describe('EditSlackConnectorConfigForm', () => {
let mockOnConfigChange: jest.Mock;
beforeEach(() => {
mockOnConfigChange = jest.fn();
});
test('renders all form fields correctly with initial values', () => {
render(
);
// Authentication
expect(screen.getByLabelText(/Slack Bot Token/i)).toHaveValue(initialMockConfig.SLACK_BOT_TOKEN);
// Initial Indexing Settings
expect(screen.getByText('Index All Channels Where Bot is Member')).toBeInTheDocument(); // For Select value display
expect(screen.getByText(/Channel selection is managed in the 'Channels' tab/i)).toBeInTheDocument();
expect(screen.getByLabelText(/Initial Indexing Period \(days\)/i)).toHaveValue(initialMockConfig.slack_initial_indexing_days);
expect(screen.getByLabelText(/Max Messages Per Channel \(Initial Sync\)/i)).toHaveValue(initialMockConfig.slack_initial_max_messages_per_channel);
// Periodic Indexing Settings
const periodicCheckbox = screen.getByLabelText(/Enable Periodic Indexing/i) as HTMLInputElement;
expect(periodicCheckbox.checked).toBe(initialMockConfig.slack_periodic_indexing_enabled);
// Periodic fields should initially be hidden if checkbox is false
if (!initialMockConfig.slack_periodic_indexing_enabled) {
expect(screen.queryByLabelText(/Periodic Indexing Frequency/i)).not.toBeInTheDocument();
expect(screen.queryByLabelText(/Max Messages Per Channel \(Periodic Sync\)/i)).not.toBeInTheDocument();
}
});
test('calls onConfigChange with updated config for SLACK_BOT_TOKEN', () => {
render(
);
const tokenInput = screen.getByLabelText(/Slack Bot Token/i);
fireEvent.change(tokenInput, { target: { value: 'new-token' } });
expect(mockOnConfigChange).toHaveBeenCalledWith(
expect.objectContaining({ SLACK_BOT_TOKEN: 'new-token' })
);
});
test('calls onConfigChange for slack_initial_indexing_days', () => {
render( );
const input = screen.getByLabelText(/Initial Indexing Period \(days\)/i);
fireEvent.change(input, { target: { value: '10' } });
expect(mockOnConfigChange).toHaveBeenCalledWith(expect.objectContaining({ slack_initial_indexing_days: 10 }));
fireEvent.change(input, { target: { value: '-1' } });
expect(mockOnConfigChange).toHaveBeenCalledWith(expect.objectContaining({ slack_initial_indexing_days: -1 }));
fireEvent.change(input, { target: { value: '' } }); // Empty value
expect(mockOnConfigChange).toHaveBeenCalledWith(expect.objectContaining({ slack_initial_indexing_days: null }));
});
test('calls onConfigChange for slack_initial_max_messages_per_channel', () => {
render( );
const input = screen.getByLabelText(/Max Messages Per Channel \(Initial Sync\)/i);
fireEvent.change(input, { target: { value: '500' } });
expect(mockOnConfigChange).toHaveBeenCalledWith(expect.objectContaining({ slack_initial_max_messages_per_channel: 500 }));
});
test('calls onConfigChange for slack_membership_filter_type', async () => {
render( );
// For ShadCN Select, we need to click the trigger, then the item.
// `userEvent` is better for this, but `fireEvent` can also work.
const selectTrigger = screen.getByRole('combobox', { name: /Channel Indexing Behavior/i });
await userEvent.click(selectTrigger);
// Assuming SelectItem roles are 'option' or similar, and they are now in the document
const option = await screen.findByText('Index Only Selected Channels'); // Wait for option to appear
await userEvent.click(option);
expect(mockOnConfigChange).toHaveBeenCalledWith(
expect.objectContaining({ slack_membership_filter_type: 'selected_member_channels' })
);
});
test('shows/hides periodic indexing fields and calls onConfigChange', async () => {
render( );
const periodicCheckbox = screen.getByLabelText(/Enable Periodic Indexing/i);
// Initially periodic fields are not visible (as per initialMockConfig)
expect(screen.queryByLabelText(/Periodic Indexing Frequency/i)).not.toBeInTheDocument();
expect(screen.queryByLabelText(/Max Messages Per Channel \(Periodic Sync\)/i)).not.toBeInTheDocument();
// Check the checkbox
fireEvent.click(periodicCheckbox);
expect(mockOnConfigChange).toHaveBeenCalledWith(
expect.objectContaining({ slack_periodic_indexing_enabled: true })
);
// Now the fields should be visible
const frequencySelect = await screen.findByLabelText(/Periodic Indexing Frequency/i); // Wait for it
const maxMessagesInput = screen.getByLabelText(/Max Messages Per Channel \(Periodic Sync\)/i);
expect(frequencySelect).toBeInTheDocument();
expect(maxMessagesInput).toBeInTheDocument();
// Change frequency
const freqSelectTrigger = screen.getByRole('combobox', { name: /Periodic Indexing Frequency/i });
await userEvent.click(freqSelectTrigger);
const weeklyOption = await screen.findByText('Weekly');
await userEvent.click(weeklyOption);
expect(mockOnConfigChange).toHaveBeenCalledWith(
expect.objectContaining({ slack_periodic_indexing_frequency: 'weekly', slack_periodic_indexing_enabled: true })
);
// Change max messages periodic
fireEvent.change(maxMessagesInput, { target: { value: '75' } });
expect(mockOnConfigChange).toHaveBeenCalledWith(
expect.objectContaining({ slack_max_messages_per_channel_periodic: 75, slack_periodic_indexing_enabled: true })
);
// Uncheck the checkbox
fireEvent.click(periodicCheckbox);
expect(mockOnConfigChange).toHaveBeenCalledWith(
expect.objectContaining({ slack_periodic_indexing_enabled: false })
);
// Fields should be hidden again
expect(screen.queryByLabelText(/Periodic Indexing Frequency/i)).not.toBeInTheDocument();
expect(screen.queryByLabelText(/Max Messages Per Channel \(Periodic Sync\)/i)).not.toBeInTheDocument();
});
test('preserves other config values when one changes', () => {
render( );
const tokenInput = screen.getByLabelText(/Slack Bot Token/i);
fireEvent.change(tokenInput, { target: { value: 'new-xoxb-token' } });
expect(mockOnConfigChange).toHaveBeenCalledWith({
...initialMockConfig, // All original values
SLACK_BOT_TOKEN: 'new-xoxb-token', // Only this one changed
});
});
test('disables all form fields when disabled prop is true', () => {
render( );
expect(screen.getByLabelText(/Slack Bot Token/i)).toBeDisabled();
expect(screen.getByRole('combobox', { name: /Channel Indexing Behavior/i })).toBeDisabled();
expect(screen.getByLabelText(/Initial Indexing Period \(days\)/i)).toBeDisabled();
expect(screen.getByLabelText(/Max Messages Per Channel \(Initial Sync\)/i)).toBeDisabled();
expect(screen.getByLabelText(/Enable Periodic Indexing/i)).toBeDisabled();
// If periodic indexing was enabled by default in mock, check those too
const connectorWithPeriodicEnabled = {
...mockConnector,
config: { ...mockConnector.config, slack_periodic_indexing_enabled: true }
};
render( );
expect(screen.getByRole('combobox', { name: /Periodic Indexing Frequency/i })).toBeDisabled();
expect(screen.getByLabelText(/Max Messages Per Channel \(Periodic Sync\)/i)).toBeDisabled();
});
test('handles empty string for number inputs correctly by converting to null', () => {
render( );
const initialDaysInput = screen.getByLabelText(/Initial Indexing Period \(days\)/i);
fireEvent.change(initialDaysInput, { target: { value: '' } });
expect(mockOnConfigChange).toHaveBeenCalledWith(expect.objectContaining({ slack_initial_indexing_days: null }));
const initialMaxMessagesInput = screen.getByLabelText(/Max Messages Per Channel \(Initial Sync\)/i);
fireEvent.change(initialMaxMessagesInput, { target: { value: '' } });
expect(mockOnConfigChange).toHaveBeenCalledWith(expect.objectContaining({ slack_initial_max_messages_per_channel: null }));
// Enable periodic to test that field
const periodicCheckbox = screen.getByLabelText(/Enable Periodic Indexing/i);
fireEvent.click(periodicCheckbox); // Enable
const periodicMaxMessagesInput = screen.getByLabelText(/Max Messages Per Channel \(Periodic Sync\)/i);
fireEvent.change(periodicMaxMessagesInput, { target: { value: '' } });
expect(mockOnConfigChange).toHaveBeenCalledWith(expect.objectContaining({ slack_max_messages_per_channel_periodic: null }));
});
});