import { describe, it, expect, vi, afterEach, beforeEach } from 'vitest' import { render, screen, waitFor, within } from '@testing-library/react' import userEvent from '@testing-library/user-event' import { act } from 'react' import type { ProxyHost } from '../../api/proxyHosts' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' // We'll use per-test module mocks via `vi.doMock` and dynamic imports to avoid // leaking mocks into other tests. Each test creates its own QueryClient. describe('ProxyHosts page - coverage targets (isolated)', () => { beforeEach(() => { vi.resetModules() vi.clearAllMocks() }) afterEach(() => { vi.useRealTimers() }) const renderPage = async () => { // Dynamic mocks const mockUpdateHost = vi.fn() vi.doMock('react-hot-toast', () => ({ toast: { success: vi.fn(), error: vi.fn(), loading: vi.fn(), dismiss: vi.fn() } })) vi.doMock('../../hooks/useProxyHosts', () => ({ useProxyHosts: vi.fn(() => ({ hosts: [ { uuid: 'host-1', name: 'StagingHost', domain_names: 'staging.example.com', forward_scheme: 'http', forward_host: '10.0.0.1', forward_port: 80, ssl_forced: true, websocket_support: true, certificate: undefined, enabled: true, created_at: '2025-01-01', updated_at: '2025-01-01', }, { uuid: 'host-2', name: 'CustomCertHost', domain_names: 'custom.example.com', forward_scheme: 'http', forward_host: '10.0.0.2', forward_port: 8080, ssl_forced: false, websocket_support: false, certificate: { provider: 'custom', name: 'ACME-CUSTOM' }, enabled: false, created_at: '2025-01-01', updated_at: '2025-01-01', } ], loading: false, isFetching: false, error: null, createHost: vi.fn(), updateHost: (uuid: string, data: Partial) => mockUpdateHost(uuid, data), deleteHost: vi.fn(), bulkUpdateACL: vi.fn(), isBulkUpdating: false, })) })) vi.doMock('../../hooks/useCertificates', () => ({ useCertificates: vi.fn(() => ({ certificates: [ { id: 1, name: 'StagingCert', domain: 'staging.example.com', status: 'untrusted', provider: 'letsencrypt-staging' } ], isLoading: false, error: null, })) })) vi.doMock('../../hooks/useAccessLists', () => ({ useAccessLists: vi.fn(() => ({ data: [] })) })) vi.doMock('../../api/settings', () => ({ getSettings: vi.fn(() => Promise.resolve({ 'ui.domain_link_behavior': 'new_window' })) })) // Import page after mocks are in place const { default: ProxyHosts } = await import('../ProxyHosts') const qc = new QueryClient({ defaultOptions: { queries: { retry: false } } }) const wrapper = (ui: React.ReactNode) => ( {ui} ) return { ProxyHosts, mockUpdateHost, wrapper } } it('renders SSL staging badge, websocket badge', async () => { const { ProxyHosts } = await renderPage() render( ) await waitFor(() => expect(screen.getByText('StagingHost')).toBeInTheDocument()) // Staging badge shows "Staging" text expect(screen.getByText('Staging')).toBeInTheDocument() // Websocket badge shows "WS" expect(screen.getByText('WS')).toBeInTheDocument() // Custom cert hosts don't show the cert name in the table - just check the host is shown expect(screen.getByText('CustomCertHost')).toBeInTheDocument() }) it('opens domain link in new window when linkBehavior is new_window', async () => { const { ProxyHosts } = await renderPage() const openSpy = vi.spyOn(window, 'open').mockImplementation(() => null) render( ) await waitFor(() => expect(screen.getByText('staging.example.com')).toBeInTheDocument()) const link = screen.getByText('staging.example.com').closest('a') as HTMLAnchorElement await act(async () => { await userEvent.click(link!) }) expect(openSpy).toHaveBeenCalled() openSpy.mockRestore() }) it('bulk apply merges host data and calls updateHost', async () => { const { ProxyHosts, mockUpdateHost } = await renderPage() render( ) await waitFor(() => expect(screen.getByText('StagingHost')).toBeInTheDocument()) // Select hosts by finding rows and clicking first checkbox (selection) const row1 = screen.getByText('StagingHost').closest('tr') as HTMLTableRowElement const row2 = screen.getByText('CustomCertHost').closest('tr') as HTMLTableRowElement await userEvent.click(within(row1).getAllByRole('checkbox')[0]) await userEvent.click(within(row2).getAllByRole('checkbox')[0]) const bulkBtn = screen.getByText('Bulk Apply') await userEvent.click(bulkBtn) // Find the modal dialog await waitFor(() => expect(screen.getByText('Bulk Apply Settings')).toBeInTheDocument()) // The bulk apply modal has checkboxes for each setting - find them by role const modalCheckboxes = screen.getAllByRole('checkbox').filter( cb => cb.closest('[role="dialog"]') !== null ) expect(modalCheckboxes.length).toBeGreaterThan(0) // Click the first setting checkbox to enable it await userEvent.click(modalCheckboxes[0]) const applyBtn = screen.getByRole('button', { name: /Apply/ }) await userEvent.click(applyBtn) await waitFor(() => { expect(mockUpdateHost).toHaveBeenCalled() }) const calls = vi.mocked(mockUpdateHost).mock.calls expect(calls.length).toBeGreaterThanOrEqual(1) const [calledUuid, calledData] = calls[0] expect(typeof calledUuid).toBe('string') expect(Object.prototype.hasOwnProperty.call(calledData, 'ssl_forced')).toBe(true) }) }) export {}