feat: add NotificationWizard + ProviderWizard tests, fix lint, update docs
Agent-Logs-Url: https://github.com/christianlouis/InboxConverge/sessions/6b527e29-8e26-4f86-88e2-847687f759ad Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
This commit is contained in:
@@ -12,9 +12,11 @@ jest.mock('next/navigation', () => ({
|
||||
|
||||
// Mock next/link to render a simple anchor
|
||||
jest.mock('next/link', () => {
|
||||
return ({ children, href, ...props }: { children: React.ReactNode; href: string; [key: string]: unknown }) => (
|
||||
const MockLink = ({ children, href, ...props }: { children: React.ReactNode; href: string; [key: string]: unknown }) => (
|
||||
<a href={href} {...props}>{children}</a>
|
||||
);
|
||||
MockLink.displayName = 'MockLink';
|
||||
return MockLink;
|
||||
});
|
||||
|
||||
// Mock @tanstack/react-query
|
||||
|
||||
@@ -0,0 +1,346 @@
|
||||
import React from 'react';
|
||||
import { render, screen, fireEvent } from '@testing-library/react';
|
||||
import { NotificationWizard } from './NotificationWizard';
|
||||
|
||||
// Mock lucide-react icons as simple spans
|
||||
jest.mock('lucide-react', () => ({
|
||||
ArrowLeft: ({ className }: { className?: string }) => <span data-testid="icon-ArrowLeft" className={className} />,
|
||||
Bell: ({ className }: { className?: string }) => <span data-testid="icon-Bell" className={className} />,
|
||||
Check: ({ className }: { className?: string }) => <span data-testid="icon-Check" className={className} />,
|
||||
Eye: ({ className }: { className?: string }) => <span data-testid="icon-Eye" className={className} />,
|
||||
EyeOff: ({ className }: { className?: string }) => <span data-testid="icon-EyeOff" className={className} />,
|
||||
Send: ({ className }: { className?: string }) => <span data-testid="icon-Send" className={className} />,
|
||||
}));
|
||||
|
||||
const mockOnComplete = jest.fn();
|
||||
const mockOnCancel = jest.fn();
|
||||
|
||||
describe('NotificationWizard', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
// ── Step 1: Channel Selection ──────────────────────────────────────────
|
||||
|
||||
describe('Step 1 - Channel Selection', () => {
|
||||
it('should render all channel options', () => {
|
||||
render(<NotificationWizard onComplete={mockOnComplete} onCancel={mockOnCancel} />);
|
||||
expect(screen.getByText('Telegram')).toBeInTheDocument();
|
||||
expect(screen.getByText('Discord')).toBeInTheDocument();
|
||||
expect(screen.getByText('Slack')).toBeInTheDocument();
|
||||
expect(screen.getByText('Email')).toBeInTheDocument();
|
||||
expect(screen.getByText('Webhook')).toBeInTheDocument();
|
||||
expect(screen.getByText('Custom Apprise URL')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should show the channel selection heading', () => {
|
||||
render(<NotificationWizard onComplete={mockOnComplete} onCancel={mockOnCancel} />);
|
||||
expect(screen.getByText('Choose Notification Channel')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should call onCancel when Cancel button is clicked', () => {
|
||||
render(<NotificationWizard onComplete={mockOnComplete} onCancel={mockOnCancel} />);
|
||||
fireEvent.click(screen.getByText('Cancel'));
|
||||
expect(mockOnCancel).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should navigate to step 2 when a channel is selected', () => {
|
||||
render(<NotificationWizard onComplete={mockOnComplete} onCancel={mockOnCancel} />);
|
||||
fireEvent.click(screen.getByText('Telegram'));
|
||||
// Step 2 shows field labels
|
||||
expect(screen.getByText('Bot Token')).toBeInTheDocument();
|
||||
expect(screen.getByText('Chat ID')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
// ── Step 2: Field Entry ────────────────────────────────────────────────
|
||||
|
||||
describe('Step 2 - Field Entry', () => {
|
||||
function goToStep2(channel: string) {
|
||||
render(<NotificationWizard onComplete={mockOnComplete} onCancel={mockOnCancel} />);
|
||||
fireEvent.click(screen.getByText(channel));
|
||||
}
|
||||
|
||||
it('should show Telegram fields', () => {
|
||||
goToStep2('Telegram');
|
||||
expect(screen.getByText('Bot Token')).toBeInTheDocument();
|
||||
expect(screen.getByText('Chat ID')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should show Discord fields', () => {
|
||||
goToStep2('Discord');
|
||||
expect(screen.getByText('Discord Webhook URL')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should show Slack fields', () => {
|
||||
goToStep2('Slack');
|
||||
expect(screen.getByText('Slack Webhook URL')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should show Email fields', () => {
|
||||
goToStep2('Email');
|
||||
expect(screen.getByText('Username / Email')).toBeInTheDocument();
|
||||
expect(screen.getByText('SMTP Password')).toBeInTheDocument();
|
||||
expect(screen.getByText('SMTP Host')).toBeInTheDocument();
|
||||
expect(screen.getByText('SMTP Port')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should show Webhook fields', () => {
|
||||
goToStep2('Webhook');
|
||||
expect(screen.getByText('Webhook URL')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should show Custom Apprise URL fields', () => {
|
||||
goToStep2('Custom Apprise URL');
|
||||
expect(screen.getByText('Apprise URL')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should go back to step 1 when Back button is clicked', () => {
|
||||
goToStep2('Telegram');
|
||||
fireEvent.click(screen.getByText('Back to channel selection'));
|
||||
expect(screen.getByText('Choose Notification Channel')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should disable Next button when required fields are empty', () => {
|
||||
goToStep2('Telegram');
|
||||
const nextButton = screen.getByText('Next').closest('button');
|
||||
expect(nextButton).toBeDisabled();
|
||||
});
|
||||
|
||||
it('should enable Next button when required fields are filled', () => {
|
||||
goToStep2('Telegram');
|
||||
const inputs = screen.getAllByRole('textbox');
|
||||
fireEvent.change(inputs[0], { target: { value: '110201543:AAHdqTcvCH1vGWJxfSeofSAs0K5PALDsaw' } });
|
||||
fireEvent.change(inputs[1], { target: { value: '12345678' } });
|
||||
const nextButton = screen.getByText('Next').closest('button');
|
||||
expect(nextButton).not.toBeDisabled();
|
||||
});
|
||||
|
||||
it('should call onCancel when Cancel is clicked on step 2', () => {
|
||||
goToStep2('Telegram');
|
||||
fireEvent.click(screen.getByText('Cancel'));
|
||||
expect(mockOnCancel).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
// ── Step 3: Preview + Preferences ──────────────────────────────────────
|
||||
|
||||
describe('Step 3 - Preview and Preferences', () => {
|
||||
function goToStep3WithTelegram() {
|
||||
render(<NotificationWizard onComplete={mockOnComplete} onCancel={mockOnCancel} />);
|
||||
// Step 1: select Telegram
|
||||
fireEvent.click(screen.getByText('Telegram'));
|
||||
// Step 2: fill fields
|
||||
const inputs = screen.getAllByRole('textbox');
|
||||
fireEvent.change(inputs[0], { target: { value: 'mybot:AAHdqTcvCH1vGWJx' } });
|
||||
fireEvent.change(inputs[1], { target: { value: '12345678' } });
|
||||
// Go to step 3
|
||||
fireEvent.click(screen.getByText('Next').closest('button')!);
|
||||
}
|
||||
|
||||
it('should show Final Setup heading', () => {
|
||||
goToStep3WithTelegram();
|
||||
expect(screen.getByText('Final Setup')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should show Channel Name input', () => {
|
||||
goToStep3WithTelegram();
|
||||
expect(screen.getByText('Channel Name')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should show notification preference checkboxes', () => {
|
||||
goToStep3WithTelegram();
|
||||
expect(screen.getByText('Email processing errors occur')).toBeInTheDocument();
|
||||
expect(screen.getByText('Emails are successfully forwarded')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should have errors checkbox checked by default', () => {
|
||||
goToStep3WithTelegram();
|
||||
const checkboxes = screen.getAllByRole('checkbox');
|
||||
// First checkbox is "notify on errors" (default: true)
|
||||
expect(checkboxes[0]).toBeChecked();
|
||||
// Second is "notify on success" (default: false)
|
||||
expect(checkboxes[1]).not.toBeChecked();
|
||||
});
|
||||
|
||||
it('should disable Save button when name is empty', () => {
|
||||
goToStep3WithTelegram();
|
||||
const saveButton = screen.getByText('Save Channel').closest('button');
|
||||
expect(saveButton).toBeDisabled();
|
||||
});
|
||||
|
||||
it('should enable Save button when name is filled', () => {
|
||||
goToStep3WithTelegram();
|
||||
const nameInput = screen.getByPlaceholderText('e.g. My Telegram Alert');
|
||||
fireEvent.change(nameInput, { target: { value: 'My Alert' } });
|
||||
const saveButton = screen.getByText('Save Channel').closest('button');
|
||||
expect(saveButton).not.toBeDisabled();
|
||||
});
|
||||
|
||||
it('should call onComplete with correct data on Save', () => {
|
||||
goToStep3WithTelegram();
|
||||
const nameInput = screen.getByPlaceholderText('e.g. My Telegram Alert');
|
||||
fireEvent.change(nameInput, { target: { value: 'My Telegram Alert' } });
|
||||
fireEvent.click(screen.getByText('Save Channel').closest('button')!);
|
||||
|
||||
expect(mockOnComplete).toHaveBeenCalledWith({
|
||||
name: 'My Telegram Alert',
|
||||
channel: 'telegram',
|
||||
apprise_url: 'tgram://mybot:AAHdqTcvCH1vGWJx/12345678/',
|
||||
notify_on_errors: true,
|
||||
notify_on_success: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('should toggle notification preferences', () => {
|
||||
goToStep3WithTelegram();
|
||||
const checkboxes = screen.getAllByRole('checkbox');
|
||||
|
||||
// Uncheck errors
|
||||
fireEvent.click(checkboxes[0]);
|
||||
// Check success
|
||||
fireEvent.click(checkboxes[1]);
|
||||
|
||||
const nameInput = screen.getByPlaceholderText('e.g. My Telegram Alert');
|
||||
fireEvent.change(nameInput, { target: { value: 'Test' } });
|
||||
fireEvent.click(screen.getByText('Save Channel').closest('button')!);
|
||||
|
||||
expect(mockOnComplete).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
notify_on_errors: false,
|
||||
notify_on_success: true,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should toggle URL visibility', () => {
|
||||
goToStep3WithTelegram();
|
||||
// URL should be hidden by default (masked with dots)
|
||||
const urlText = screen.getByText(/^•+$/);
|
||||
expect(urlText).toBeInTheDocument();
|
||||
|
||||
// Click the show/hide button
|
||||
const toggleButton = screen.getByLabelText('Show URL');
|
||||
fireEvent.click(toggleButton);
|
||||
|
||||
// Now the URL should be visible
|
||||
expect(screen.getByText(/^tgram:\/\//)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should go back to step 2 when Back is clicked', () => {
|
||||
goToStep3WithTelegram();
|
||||
fireEvent.click(screen.getByText('Back to configuration'));
|
||||
// Should be back on step 2 with Telegram fields
|
||||
expect(screen.getByText('Bot Token')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
// ── buildAppriseUrl via component behavior ─────────────────────────────
|
||||
|
||||
describe('Apprise URL generation', () => {
|
||||
function fillAndSubmit(channel: string, fields: Record<string, string>) {
|
||||
render(<NotificationWizard onComplete={mockOnComplete} onCancel={mockOnCancel} />);
|
||||
fireEvent.click(screen.getByText(channel));
|
||||
|
||||
const inputs = screen.getAllByRole('textbox');
|
||||
const passwordInputs = document.querySelectorAll('input[type="password"]');
|
||||
const allInputs = [...Array.from(inputs), ...Array.from(passwordInputs)];
|
||||
|
||||
Object.values(fields).forEach((value, i) => {
|
||||
fireEvent.change(allInputs[i], { target: { value } });
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByText('Next').closest('button')!);
|
||||
|
||||
const nameInput = screen.getByPlaceholderText('e.g. My Telegram Alert');
|
||||
fireEvent.change(nameInput, { target: { value: 'Test' } });
|
||||
fireEvent.click(screen.getByText('Save Channel').closest('button')!);
|
||||
|
||||
return mockOnComplete.mock.calls[0][0].apprise_url;
|
||||
}
|
||||
|
||||
it('should build correct Telegram URL', () => {
|
||||
const url = fillAndSubmit('Telegram', {
|
||||
bot_token: '110201543:AAHdqTcvCH1vGWJxfSeofSAs0K5PALDsaw',
|
||||
chat_id: '12345678',
|
||||
});
|
||||
expect(url).toBe('tgram://110201543:AAHdqTcvCH1vGWJxfSeofSAs0K5PALDsaw/12345678/');
|
||||
});
|
||||
|
||||
it('should build correct Discord URL from webhook', () => {
|
||||
const url = fillAndSubmit('Discord', {
|
||||
webhook_url: 'https://discord.com/api/webhooks/123456789/abcdefghij',
|
||||
});
|
||||
expect(url).toBe('discord://123456789/abcdefghij/');
|
||||
});
|
||||
|
||||
it('should build correct Slack URL from webhook', () => {
|
||||
const url = fillAndSubmit('Slack', {
|
||||
webhook_url: 'https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXX',
|
||||
});
|
||||
expect(url).toBe('slack://T00000000/B00000000/XXXXXXXXXXXXXXXX/');
|
||||
});
|
||||
|
||||
it('should build correct Webhook URL', () => {
|
||||
const url = fillAndSubmit('Webhook', {
|
||||
url: 'https://hooks.example.com/notify',
|
||||
});
|
||||
expect(url).toBe('https://hooks.example.com/notify');
|
||||
});
|
||||
|
||||
it('should pass through Custom Apprise URL', () => {
|
||||
const url = fillAndSubmit('Custom Apprise URL', {
|
||||
apprise_url: 'tgram://mybot/mychat/',
|
||||
});
|
||||
expect(url).toBe('tgram://mybot/mychat/');
|
||||
});
|
||||
});
|
||||
|
||||
// ── Edit mode (initialData) ────────────────────────────────────────────
|
||||
|
||||
describe('Edit mode with initialData', () => {
|
||||
const initialData = {
|
||||
name: 'My Existing Alert',
|
||||
channel: 'telegram',
|
||||
apprise_url: 'tgram://existingbot/existingchat/',
|
||||
notify_on_errors: true,
|
||||
notify_on_success: true,
|
||||
};
|
||||
|
||||
it('should start on step 3 when initialData is provided', () => {
|
||||
render(
|
||||
<NotificationWizard
|
||||
onComplete={mockOnComplete}
|
||||
onCancel={mockOnCancel}
|
||||
initialData={initialData}
|
||||
/>
|
||||
);
|
||||
expect(screen.getByText('Final Setup')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should pre-fill the name from initialData', () => {
|
||||
render(
|
||||
<NotificationWizard
|
||||
onComplete={mockOnComplete}
|
||||
onCancel={mockOnCancel}
|
||||
initialData={initialData}
|
||||
/>
|
||||
);
|
||||
const nameInput = screen.getByPlaceholderText('e.g. My Telegram Alert') as HTMLInputElement;
|
||||
expect(nameInput.value).toBe('My Existing Alert');
|
||||
});
|
||||
|
||||
it('should pre-set notification preferences from initialData', () => {
|
||||
render(
|
||||
<NotificationWizard
|
||||
onComplete={mockOnComplete}
|
||||
onCancel={mockOnCancel}
|
||||
initialData={initialData}
|
||||
/>
|
||||
);
|
||||
const checkboxes = screen.getAllByRole('checkbox');
|
||||
expect(checkboxes[0]).toBeChecked(); // notify_on_errors
|
||||
expect(checkboxes[1]).toBeChecked(); // notify_on_success
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,231 @@
|
||||
import React from 'react';
|
||||
import { render, screen, fireEvent } from '@testing-library/react';
|
||||
import { ProviderWizard } from './ProviderWizard';
|
||||
|
||||
// Mock next/image
|
||||
jest.mock('next/image', () => {
|
||||
return function MockImage({ alt, ...props }: { alt: string; [key: string]: unknown }) {
|
||||
// eslint-disable-next-line @next/next/no-img-element
|
||||
return <img alt={alt} {...props} />;
|
||||
};
|
||||
});
|
||||
|
||||
// Mock lucide-react icons
|
||||
jest.mock('lucide-react', () => ({
|
||||
Mail: ({ className }: { className?: string }) => <span data-testid="icon-Mail" className={className} />,
|
||||
ArrowLeft: ({ className }: { className?: string }) => <span data-testid="icon-ArrowLeft" className={className} />,
|
||||
}));
|
||||
|
||||
const mockOnSelect = jest.fn();
|
||||
const mockOnManual = jest.fn();
|
||||
|
||||
describe('ProviderWizard', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
// ── Provider List ──────────────────────────────────────────────────────
|
||||
|
||||
describe('Provider List', () => {
|
||||
it('should render all email providers', () => {
|
||||
render(<ProviderWizard onSelect={mockOnSelect} onManual={mockOnManual} />);
|
||||
expect(screen.getByText('Gmail')).toBeInTheDocument();
|
||||
expect(screen.getByText('GMX')).toBeInTheDocument();
|
||||
expect(screen.getByText('WEB.DE')).toBeInTheDocument();
|
||||
expect(screen.getByText('Outlook / Hotmail')).toBeInTheDocument();
|
||||
expect(screen.getByText('Yahoo Mail')).toBeInTheDocument();
|
||||
expect(screen.getByText('AOL Mail')).toBeInTheDocument();
|
||||
expect(screen.getByText('T-Online')).toBeInTheDocument();
|
||||
expect(screen.getByText('1&1 / IONOS')).toBeInTheDocument();
|
||||
expect(screen.getByText('Freenet')).toBeInTheDocument();
|
||||
expect(screen.getByText('iCloud Mail')).toBeInTheDocument();
|
||||
expect(screen.getByText('Posteo')).toBeInTheDocument();
|
||||
expect(screen.getByText('Proton Mail')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should show the quick setup heading', () => {
|
||||
render(<ProviderWizard onSelect={mockOnSelect} onManual={mockOnManual} />);
|
||||
expect(screen.getByText('Quick Setup — Select Your Email Provider')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should show Configure Manually button', () => {
|
||||
render(<ProviderWizard onSelect={mockOnSelect} onManual={mockOnManual} />);
|
||||
expect(screen.getByText('Configure Manually')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should call onManual when Configure Manually is clicked', () => {
|
||||
render(<ProviderWizard onSelect={mockOnSelect} onManual={mockOnManual} />);
|
||||
fireEvent.click(screen.getByText('Configure Manually'));
|
||||
expect(mockOnManual).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
// ── Provider Detail ────────────────────────────────────────────────────
|
||||
|
||||
describe('Provider Detail (after selecting a provider)', () => {
|
||||
it('should show provider details when Gmail is selected', () => {
|
||||
render(<ProviderWizard onSelect={mockOnSelect} onManual={mockOnManual} />);
|
||||
fireEvent.click(screen.getByText('Gmail'));
|
||||
expect(screen.getByText(/gmail\.com, googlemail\.com/)).toBeInTheDocument();
|
||||
expect(screen.getByText(/Enable IMAP\/POP3 in Gmail settings/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should show protocol selection buttons', () => {
|
||||
render(<ProviderWizard onSelect={mockOnSelect} onManual={mockOnManual} />);
|
||||
fireEvent.click(screen.getByText('Gmail'));
|
||||
expect(screen.getByText('IMAP (Recommended)')).toBeInTheDocument();
|
||||
expect(screen.getByText('POP3')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should show IMAP server details for Gmail', () => {
|
||||
render(<ProviderWizard onSelect={mockOnSelect} onManual={mockOnManual} />);
|
||||
fireEvent.click(screen.getByText('Gmail'));
|
||||
expect(screen.getByText('imap.gmail.com:993')).toBeInTheDocument();
|
||||
expect(screen.getByText('pop.gmail.com:995')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should show Back to providers button', () => {
|
||||
render(<ProviderWizard onSelect={mockOnSelect} onManual={mockOnManual} />);
|
||||
fireEvent.click(screen.getByText('Gmail'));
|
||||
expect(screen.getByText('Back to providers')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should go back to provider list when Back is clicked', () => {
|
||||
render(<ProviderWizard onSelect={mockOnSelect} onManual={mockOnManual} />);
|
||||
fireEvent.click(screen.getByText('Gmail'));
|
||||
fireEvent.click(screen.getByText('Back to providers'));
|
||||
expect(screen.getByText('Quick Setup — Select Your Email Provider')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should show Use button with provider name', () => {
|
||||
render(<ProviderWizard onSelect={mockOnSelect} onManual={mockOnManual} />);
|
||||
fireEvent.click(screen.getByText('Gmail'));
|
||||
expect(screen.getByText('Use Gmail Settings')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
// ── Provider Selection Callback ────────────────────────────────────────
|
||||
|
||||
describe('onSelect callback', () => {
|
||||
it('should call onSelect with IMAP config when IMAP is chosen for Gmail', () => {
|
||||
render(<ProviderWizard onSelect={mockOnSelect} onManual={mockOnManual} />);
|
||||
fireEvent.click(screen.getByText('Gmail'));
|
||||
// IMAP is selected by default
|
||||
fireEvent.click(screen.getByText('Use Gmail Settings'));
|
||||
expect(mockOnSelect).toHaveBeenCalledWith({
|
||||
name: 'Gmail',
|
||||
provider_name: 'Gmail',
|
||||
protocol: 'imap_ssl',
|
||||
host: 'imap.gmail.com',
|
||||
port: 993,
|
||||
use_ssl: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('should call onSelect with POP3 config when POP3 is chosen for Gmail', () => {
|
||||
render(<ProviderWizard onSelect={mockOnSelect} onManual={mockOnManual} />);
|
||||
fireEvent.click(screen.getByText('Gmail'));
|
||||
fireEvent.click(screen.getByText('POP3'));
|
||||
fireEvent.click(screen.getByText('Use Gmail Settings'));
|
||||
expect(mockOnSelect).toHaveBeenCalledWith({
|
||||
name: 'Gmail',
|
||||
provider_name: 'Gmail',
|
||||
protocol: 'pop3_ssl',
|
||||
host: 'pop.gmail.com',
|
||||
port: 995,
|
||||
use_ssl: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('should call onSelect with correct config for Outlook', () => {
|
||||
render(<ProviderWizard onSelect={mockOnSelect} onManual={mockOnManual} />);
|
||||
fireEvent.click(screen.getByText('Outlook / Hotmail'));
|
||||
fireEvent.click(screen.getByText('Use Outlook / Hotmail Settings'));
|
||||
expect(mockOnSelect).toHaveBeenCalledWith({
|
||||
name: 'Outlook / Hotmail',
|
||||
provider_name: 'Outlook / Hotmail',
|
||||
protocol: 'imap_ssl',
|
||||
host: 'outlook.office365.com',
|
||||
port: 993,
|
||||
use_ssl: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('should call onSelect with correct config for GMX', () => {
|
||||
render(<ProviderWizard onSelect={mockOnSelect} onManual={mockOnManual} />);
|
||||
fireEvent.click(screen.getByText('GMX'));
|
||||
fireEvent.click(screen.getByText('Use GMX Settings'));
|
||||
expect(mockOnSelect).toHaveBeenCalledWith({
|
||||
name: 'GMX',
|
||||
provider_name: 'GMX',
|
||||
protocol: 'imap_ssl',
|
||||
host: 'imap.gmx.net',
|
||||
port: 993,
|
||||
use_ssl: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// ── IMAP-only providers ────────────────────────────────────────────────
|
||||
|
||||
describe('IMAP-only providers', () => {
|
||||
it('should only show IMAP option for iCloud', () => {
|
||||
render(<ProviderWizard onSelect={mockOnSelect} onManual={mockOnManual} />);
|
||||
fireEvent.click(screen.getByText('iCloud Mail'));
|
||||
expect(screen.getByText('IMAP (Recommended)')).toBeInTheDocument();
|
||||
expect(screen.queryByText('POP3')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should only show IMAP option for Posteo', () => {
|
||||
render(<ProviderWizard onSelect={mockOnSelect} onManual={mockOnManual} />);
|
||||
fireEvent.click(screen.getByText('Posteo'));
|
||||
expect(screen.getByText('IMAP (Recommended)')).toBeInTheDocument();
|
||||
expect(screen.queryByText('POP3')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should auto-select IMAP for iCloud and call onSelect correctly', () => {
|
||||
render(<ProviderWizard onSelect={mockOnSelect} onManual={mockOnManual} />);
|
||||
fireEvent.click(screen.getByText('iCloud Mail'));
|
||||
fireEvent.click(screen.getByText('Use iCloud Mail Settings'));
|
||||
expect(mockOnSelect).toHaveBeenCalledWith({
|
||||
name: 'iCloud Mail',
|
||||
provider_name: 'iCloud Mail',
|
||||
protocol: 'imap_ssl',
|
||||
host: 'imap.mail.me.com',
|
||||
port: 993,
|
||||
use_ssl: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// ── Proton Mail (Bridge) ───────────────────────────────────────────────
|
||||
|
||||
describe('Proton Mail (Bridge)', () => {
|
||||
it('should show Proton Mail Bridge notes', () => {
|
||||
render(<ProviderWizard onSelect={mockOnSelect} onManual={mockOnManual} />);
|
||||
fireEvent.click(screen.getByText('Proton Mail'));
|
||||
expect(screen.getByText(/Requires Proton Mail Bridge/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should show localhost connection details', () => {
|
||||
render(<ProviderWizard onSelect={mockOnSelect} onManual={mockOnManual} />);
|
||||
fireEvent.click(screen.getByText('Proton Mail'));
|
||||
expect(screen.getByText('127.0.0.1:1143')).toBeInTheDocument();
|
||||
expect(screen.getByText('127.0.0.1:1144')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should call onSelect with localhost config', () => {
|
||||
render(<ProviderWizard onSelect={mockOnSelect} onManual={mockOnManual} />);
|
||||
fireEvent.click(screen.getByText('Proton Mail'));
|
||||
fireEvent.click(screen.getByText('Use Proton Mail Settings'));
|
||||
expect(mockOnSelect).toHaveBeenCalledWith({
|
||||
name: 'Proton Mail',
|
||||
provider_name: 'Proton Mail',
|
||||
protocol: 'imap_ssl',
|
||||
host: '127.0.0.1',
|
||||
port: 1143,
|
||||
use_ssl: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -45,6 +45,7 @@ jest.mock('axios', () => ({
|
||||
}));
|
||||
|
||||
// Force module initialization to capture interceptors
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||
require('./api');
|
||||
|
||||
describe('API module setup', () => {
|
||||
|
||||
Reference in New Issue
Block a user