/**
* Tests for the VirtualPrinterSettings component.
*
* Tests the virtual printer configuration UI including:
* - Enable/disable toggle
* - Access code management
* - Archive mode selection
* - Status display
*/
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { render } from '../utils';
import { VirtualPrinterSettings } from '../../components/VirtualPrinterSettings';
// Mock the API client
vi.mock('../../api/client', () => ({
api: {
getSettings: vi.fn().mockResolvedValue({}),
updateSettings: vi.fn().mockResolvedValue({}),
},
virtualPrinterApi: {
getSettings: vi.fn(),
updateSettings: vi.fn(),
getModels: vi.fn().mockResolvedValue({
models: {
'3DPrinter-X1-Carbon': 'X1C',
'C12': 'P1S',
'N7': 'P2S',
},
}),
},
}));
// Import mocked module
import { virtualPrinterApi } from '../../api/client';
// Mock data factory
const createMockSettings = (overrides = {}) => ({
enabled: false,
access_code_set: false,
mode: 'immediate' as const,
model: '3DPrinter-X1-Carbon',
target_printer_id: null as number | null,
remote_interface_ip: null as string | null,
status: {
enabled: false,
running: false,
mode: 'immediate',
name: 'Bambuddy',
serial: '00M00A391800001',
model: '3DPrinter-X1-Carbon',
model_name: 'X1C',
pending_files: 0,
},
...overrides,
});
describe('VirtualPrinterSettings', () => {
beforeEach(() => {
vi.clearAllMocks();
// Default mock implementation
vi.mocked(virtualPrinterApi.getSettings).mockResolvedValue(createMockSettings());
vi.mocked(virtualPrinterApi.updateSettings).mockResolvedValue(createMockSettings());
});
describe('rendering', () => {
it('renders loading state initially', () => {
// Delay the API response to catch loading state
vi.mocked(virtualPrinterApi.getSettings).mockImplementation(
() => new Promise(() => {}) // Never resolves
);
render();
// Should show loading spinner
expect(document.querySelector('.animate-spin')).toBeInTheDocument();
});
it('renders component title', async () => {
render();
await waitFor(() => {
expect(screen.getByText('Virtual Printer')).toBeInTheDocument();
});
});
it('renders enable toggle', async () => {
render();
await waitFor(() => {
expect(screen.getByText('Enable Virtual Printer')).toBeInTheDocument();
});
});
it('renders access code section', async () => {
render();
await waitFor(() => {
expect(screen.getByText('Access Code')).toBeInTheDocument();
});
});
it('renders mode section', async () => {
render();
await waitFor(() => {
expect(screen.getByText('Mode')).toBeInTheDocument();
});
});
it('renders how it works info', async () => {
render();
await waitFor(() => {
expect(screen.getByText('How it works:')).toBeInTheDocument();
});
});
});
describe('status indicator', () => {
it('shows Stopped when not running', async () => {
vi.mocked(virtualPrinterApi.getSettings).mockResolvedValue(
createMockSettings({ status: { ...createMockSettings().status, running: false } })
);
render();
await waitFor(() => {
expect(screen.getByText('Stopped')).toBeInTheDocument();
});
});
it('shows Running when active', async () => {
vi.mocked(virtualPrinterApi.getSettings).mockResolvedValue(
createMockSettings({
enabled: true,
status: { ...createMockSettings().status, enabled: true, running: true },
})
);
render();
await waitFor(() => {
expect(screen.getByText('Running')).toBeInTheDocument();
});
});
it('shows status details when running', async () => {
vi.mocked(virtualPrinterApi.getSettings).mockResolvedValue(
createMockSettings({
enabled: true,
status: {
enabled: true,
running: true,
mode: 'immediate',
name: 'Bambuddy',
serial: '00M00A391800001',
model: '3DPrinter-X1-Carbon',
model_name: 'X1C',
pending_files: 0,
},
})
);
render();
await waitFor(() => {
expect(screen.getByText('Status Details')).toBeInTheDocument();
expect(screen.getByText('Bambuddy')).toBeInTheDocument();
expect(screen.getByText('00M00A391800001')).toBeInTheDocument();
});
});
});
describe('access code', () => {
it('shows warning when access code not set', async () => {
vi.mocked(virtualPrinterApi.getSettings).mockResolvedValue(
createMockSettings({ access_code_set: false })
);
render();
await waitFor(() => {
expect(screen.getByText('No access code set - required to enable')).toBeInTheDocument();
});
});
it('shows success when access code is set', async () => {
vi.mocked(virtualPrinterApi.getSettings).mockResolvedValue(
createMockSettings({ access_code_set: true })
);
render();
await waitFor(() => {
expect(screen.getByText('Access code is set')).toBeInTheDocument();
});
});
it('shows character count while typing', async () => {
const user = userEvent.setup();
render();
await waitFor(() => {
expect(screen.getByText('Access Code')).toBeInTheDocument();
});
const input = screen.getByPlaceholderText('Enter 8-char code');
await user.type(input, '1234');
expect(screen.getByText('(4/8)')).toBeInTheDocument();
});
it('saves access code on button click', async () => {
const user = userEvent.setup();
vi.mocked(virtualPrinterApi.updateSettings).mockResolvedValue(
createMockSettings({ access_code_set: true })
);
render();
await waitFor(() => {
expect(screen.getByText('Access Code')).toBeInTheDocument();
});
const input = screen.getByPlaceholderText('Enter 8-char code');
await user.type(input, '12345678');
const saveButton = screen.getByRole('button', { name: 'Save' });
await user.click(saveButton);
await waitFor(() => {
expect(virtualPrinterApi.updateSettings).toHaveBeenCalledWith({
access_code: '12345678',
});
});
});
it('toggles password visibility', async () => {
const user = userEvent.setup();
render();
await waitFor(() => {
expect(screen.getByText('Access Code')).toBeInTheDocument();
});
const input = screen.getByPlaceholderText('Enter 8-char code');
expect(input).toHaveAttribute('type', 'password');
// Find and click the visibility toggle (eye icon button)
const toggleButtons = screen.getAllByRole('button');
const visibilityToggle = toggleButtons.find(
(btn) => btn.querySelector('svg') && btn.className.includes('absolute')
);
if (visibilityToggle) {
await user.click(visibilityToggle);
expect(input).toHaveAttribute('type', 'text');
}
});
});
describe('mode selection', () => {
it('renders Archive mode option', async () => {
render();
await waitFor(() => {
expect(screen.getByText('Archive')).toBeInTheDocument();
expect(screen.getByText('Archive files immediately')).toBeInTheDocument();
});
});
it('renders Review mode option', async () => {
render();
await waitFor(() => {
expect(screen.getByText('Review')).toBeInTheDocument();
expect(screen.getByText('Review before archiving')).toBeInTheDocument();
});
});
it('renders Queue mode option', async () => {
render();
await waitFor(() => {
expect(screen.getByText('Queue')).toBeInTheDocument();
expect(screen.getByText('Archive and add to queue')).toBeInTheDocument();
});
});
it('highlights current mode (review)', async () => {
vi.mocked(virtualPrinterApi.getSettings).mockResolvedValue(
createMockSettings({ mode: 'review' })
);
render();
await waitFor(() => {
const reviewButton = screen.getByText('Review').closest('button');
expect(reviewButton?.className).toContain('border-bambu-green');
});
});
it('highlights current mode (legacy queue maps to review)', async () => {
vi.mocked(virtualPrinterApi.getSettings).mockResolvedValue(
createMockSettings({ mode: 'queue' })
);
render();
await waitFor(() => {
const reviewButton = screen.getByText('Review').closest('button');
expect(reviewButton?.className).toContain('border-bambu-green');
});
});
it('changes mode to review on click', async () => {
const user = userEvent.setup();
vi.mocked(virtualPrinterApi.updateSettings).mockResolvedValue(
createMockSettings({ mode: 'review' })
);
render();
await waitFor(() => {
expect(screen.getByText('Review')).toBeInTheDocument();
});
const reviewButton = screen.getByText('Review').closest('button');
if (reviewButton) {
await user.click(reviewButton);
}
await waitFor(() => {
expect(virtualPrinterApi.updateSettings).toHaveBeenCalledWith({ mode: 'review' });
});
});
it('changes mode to print_queue on click', async () => {
const user = userEvent.setup();
vi.mocked(virtualPrinterApi.updateSettings).mockResolvedValue(
createMockSettings({ mode: 'print_queue' })
);
render();
await waitFor(() => {
expect(screen.getByText('Queue')).toBeInTheDocument();
});
const queueButton = screen.getByText('Queue').closest('button');
if (queueButton) {
await user.click(queueButton);
}
await waitFor(() => {
expect(virtualPrinterApi.updateSettings).toHaveBeenCalledWith({ mode: 'print_queue' });
});
});
});
describe('enable/disable toggle', () => {
it('cannot enable without access code', async () => {
const user = userEvent.setup();
vi.mocked(virtualPrinterApi.getSettings).mockResolvedValue(
createMockSettings({ enabled: false, access_code_set: false })
);
render();
await waitFor(() => {
expect(screen.getByText('Enable Virtual Printer')).toBeInTheDocument();
});
// Find the toggle switch (it's a button with relative class containing the slider)
const allButtons = screen.getAllByRole('button');
const toggle = allButtons.find((btn) => btn.className.includes('rounded-full') && btn.className.includes('w-12'));
if (toggle) {
await user.click(toggle);
}
// Should not call update API (no access code set)
expect(virtualPrinterApi.updateSettings).not.toHaveBeenCalled();
});
it('can enable when access code is set', async () => {
const user = userEvent.setup();
vi.mocked(virtualPrinterApi.getSettings).mockResolvedValue(
createMockSettings({ enabled: false, access_code_set: true })
);
vi.mocked(virtualPrinterApi.updateSettings).mockResolvedValue(
createMockSettings({ enabled: true, access_code_set: true })
);
render();
await waitFor(() => {
expect(screen.getByText('Enable Virtual Printer')).toBeInTheDocument();
});
// Find the toggle switch (it's a button with rounded-full and w-12 classes)
const allButtons = screen.getAllByRole('button');
const toggle = allButtons.find((btn) => btn.className.includes('rounded-full') && btn.className.includes('w-12'));
expect(toggle).toBeDefined();
if (toggle) {
await user.click(toggle);
}
await waitFor(() => {
expect(virtualPrinterApi.updateSettings).toHaveBeenCalledWith(
expect.objectContaining({ enabled: true })
);
});
});
it('can disable when enabled', async () => {
const user = userEvent.setup();
vi.mocked(virtualPrinterApi.getSettings).mockResolvedValue(
createMockSettings({ enabled: true, access_code_set: true })
);
vi.mocked(virtualPrinterApi.updateSettings).mockResolvedValue(
createMockSettings({ enabled: false, access_code_set: true })
);
render();
await waitFor(() => {
expect(screen.getByText('Enable Virtual Printer')).toBeInTheDocument();
});
// Find the toggle switch
const allButtons = screen.getAllByRole('button');
const toggle = allButtons.find((btn) => btn.className.includes('rounded-full') && btn.className.includes('w-12'));
expect(toggle).toBeDefined();
if (toggle) {
await user.click(toggle);
}
await waitFor(() => {
expect(virtualPrinterApi.updateSettings).toHaveBeenCalledWith(
expect.objectContaining({ enabled: false })
);
});
});
});
describe('info section', () => {
it('shows setup required warning', async () => {
render();
await waitFor(() => {
expect(screen.getByText('Setup Required')).toBeInTheDocument();
});
});
it('shows link to setup guide', async () => {
render();
await waitFor(() => {
expect(screen.getByText('Read the setup guide before enabling')).toBeInTheDocument();
});
});
it('shows how it works section', async () => {
render();
await waitFor(() => {
expect(screen.getByText('How it works:')).toBeInTheDocument();
expect(screen.getByText(/Complete the setup guide for your platform/)).toBeInTheDocument();
});
});
});
describe('proxy mode', () => {
it('renders Proxy mode option', async () => {
render();
await waitFor(() => {
expect(screen.getByText('Proxy')).toBeInTheDocument();
expect(screen.getByText('Relay to real printer')).toBeInTheDocument();
});
});
it('highlights proxy mode when selected', async () => {
vi.mocked(virtualPrinterApi.getSettings).mockResolvedValue(
createMockSettings({ mode: 'proxy', target_printer_id: 1 })
);
render();
await waitFor(() => {
const proxyButton = screen.getByText('Proxy').closest('button');
expect(proxyButton?.className).toContain('border-blue-500');
});
});
it('shows proxy status details when running in proxy mode', async () => {
vi.mocked(virtualPrinterApi.getSettings).mockResolvedValue(
createMockSettings({
enabled: true,
mode: 'proxy',
target_printer_id: 1,
status: {
enabled: true,
running: true,
mode: 'proxy',
name: 'Bambuddy (Proxy)',
serial: '00M00A391800001',
model: '3DPrinter-X1-Carbon',
model_name: 'X1C',
pending_files: 0,
proxy: {
running: true,
target_host: '192.168.1.100',
ftp_port: 990, // Privileged port for Bambu Studio compatibility
mqtt_port: 8883,
ftp_connections: 1,
mqtt_connections: 2,
},
},
})
);
render();
await waitFor(() => {
expect(screen.getByText('Running')).toBeInTheDocument();
expect(screen.getByText('Status Details')).toBeInTheDocument();
// IP address appears in multiple places (target printer and status details)
expect(screen.getAllByText('192.168.1.100').length).toBeGreaterThan(0);
});
});
it('shows target printer dropdown in proxy mode', async () => {
vi.mocked(virtualPrinterApi.getSettings).mockResolvedValue(
createMockSettings({ mode: 'proxy' })
);
render();
await waitFor(() => {
expect(screen.getByText('Target Printer')).toBeInTheDocument();
expect(screen.getByText('Select a printer...')).toBeInTheDocument();
});
});
it('changes mode to proxy on click', async () => {
const user = userEvent.setup();
vi.mocked(virtualPrinterApi.updateSettings).mockResolvedValue(
createMockSettings({ mode: 'proxy' })
);
render();
await waitFor(() => {
expect(screen.getByText('Proxy')).toBeInTheDocument();
});
const proxyButton = screen.getByText('Proxy').closest('button');
if (proxyButton) {
await user.click(proxyButton);
}
await waitFor(() => {
expect(virtualPrinterApi.updateSettings).toHaveBeenCalledWith({ mode: 'proxy' });
});
});
});
});