| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573 |
- /**
- * 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(<VirtualPrinterSettings />);
- // Should show loading spinner
- expect(document.querySelector('.animate-spin')).toBeInTheDocument();
- });
- it('renders component title', async () => {
- render(<VirtualPrinterSettings />);
- await waitFor(() => {
- expect(screen.getByText('Virtual Printer')).toBeInTheDocument();
- });
- });
- it('renders enable toggle', async () => {
- render(<VirtualPrinterSettings />);
- await waitFor(() => {
- expect(screen.getByText('Enable Virtual Printer')).toBeInTheDocument();
- });
- });
- it('renders access code section', async () => {
- render(<VirtualPrinterSettings />);
- await waitFor(() => {
- expect(screen.getByText('Access Code')).toBeInTheDocument();
- });
- });
- it('renders mode section', async () => {
- render(<VirtualPrinterSettings />);
- await waitFor(() => {
- expect(screen.getByText('Mode')).toBeInTheDocument();
- });
- });
- it('renders how it works info', async () => {
- render(<VirtualPrinterSettings />);
- 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(<VirtualPrinterSettings />);
- 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(<VirtualPrinterSettings />);
- 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(<VirtualPrinterSettings />);
- 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(<VirtualPrinterSettings />);
- 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(<VirtualPrinterSettings />);
- await waitFor(() => {
- expect(screen.getByText('Access code is set')).toBeInTheDocument();
- });
- });
- it('shows character count while typing', async () => {
- const user = userEvent.setup();
- render(<VirtualPrinterSettings />);
- 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(<VirtualPrinterSettings />);
- 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(<VirtualPrinterSettings />);
- 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(<VirtualPrinterSettings />);
- await waitFor(() => {
- expect(screen.getByText('Archive')).toBeInTheDocument();
- expect(screen.getByText('Archive files immediately')).toBeInTheDocument();
- });
- });
- it('renders Review mode option', async () => {
- render(<VirtualPrinterSettings />);
- await waitFor(() => {
- expect(screen.getByText('Review')).toBeInTheDocument();
- expect(screen.getByText('Review before archiving')).toBeInTheDocument();
- });
- });
- it('renders Queue mode option', async () => {
- render(<VirtualPrinterSettings />);
- 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(<VirtualPrinterSettings />);
- 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(<VirtualPrinterSettings />);
- 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(<VirtualPrinterSettings />);
- 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(<VirtualPrinterSettings />);
- 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(<VirtualPrinterSettings />);
- 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(<VirtualPrinterSettings />);
- 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(<VirtualPrinterSettings />);
- 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(<VirtualPrinterSettings />);
- await waitFor(() => {
- expect(screen.getByText('Setup Required')).toBeInTheDocument();
- });
- });
- it('shows link to setup guide', async () => {
- render(<VirtualPrinterSettings />);
- await waitFor(() => {
- expect(screen.getByText('Read the setup guide before enabling')).toBeInTheDocument();
- });
- });
- it('shows how it works section', async () => {
- render(<VirtualPrinterSettings />);
- 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(<VirtualPrinterSettings />);
- 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(<VirtualPrinterSettings />);
- 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(<VirtualPrinterSettings />);
- 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(<VirtualPrinterSettings />);
- 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(<VirtualPrinterSettings />);
- 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' });
- });
- });
- });
- });
|