// @vitest-environment jsdom import { describe, it, expect, vi, beforeEach } from 'vitest'; import '@testing-library/jest-dom/vitest'; import { render, screen, fireEvent, cleanup, waitFor } from '@testing-library/react'; vi.mock('../../src/renderer/inbox/api.js', () => ({ inboxApi: { loadOllamaSettings: vi.fn(async () => ({ endpoint: 'http://localhost:11434', model: 'gemma2:2b' })), saveOllamaSettings: vi.fn(async () => ({ ok: true })), ollamaRecheck: vi.fn(async () => ({ ok: true })), getSettings: vi.fn(async () => ({ ai_enabled: true })), setAiEnabled: vi.fn(async () => ({ ok: true })), getDisabledCount: vi.fn(async () => 0), enqueueDisabled: vi.fn(async () => ({ count: 0 })) } })); import { AiProviderSection } from '../../src/renderer/inbox/components/settings/AiProviderSection'; describe('AiProviderSection', () => { beforeEach(() => { vi.clearAllMocks(); cleanup(); }); it('loads current settings on mount', async () => { render(); expect(await screen.findByDisplayValue('http://localhost:11434')).toBeInTheDocument(); expect(screen.getByDisplayValue('gemma2:2b')).toBeInTheDocument(); }); it('rejects invalid endpoint URL', async () => { render(); await screen.findByDisplayValue('http://localhost:11434'); const input = screen.getByLabelText(/Endpoint/); fireEvent.change(input, { target: { value: 'not-a-url' } }); fireEvent.click(screen.getByRole('button', { name: /저장/ })); expect(await screen.findByText(/올바른 URL/)).toBeInTheDocument(); }); it('"지금 재확인" calls ollamaRecheck and shows result', async () => { const { inboxApi } = await import('../../src/renderer/inbox/api.js'); render(); await screen.findByDisplayValue('http://localhost:11434'); fireEvent.click(screen.getByRole('button', { name: /지금 재확인/ })); expect(inboxApi.ollamaRecheck).toHaveBeenCalled(); }); // v0.2.9 Cut B Task 15 — AI 자동 처리 토글 + OFF 안내문. it('renders AI 자동 처리 toggle (default true)', async () => { const { inboxApi } = await import('../../src/renderer/inbox/api.js'); vi.mocked(inboxApi.getSettings).mockResolvedValue({ ai_enabled: true } as never); render(); const toggle = await screen.findByLabelText(/AI 자동 처리 사용/); expect((toggle as HTMLInputElement).checked).toBe(true); }); it('toggling calls setAiEnabled', async () => { const { inboxApi } = await import('../../src/renderer/inbox/api.js'); vi.mocked(inboxApi.getSettings).mockResolvedValue({ ai_enabled: true } as never); vi.mocked(inboxApi.setAiEnabled).mockResolvedValue({ ok: true } as never); render(); const toggle = await screen.findByLabelText(/AI 자동 처리 사용/); fireEvent.click(toggle); await waitFor(() => expect(inboxApi.setAiEnabled).toHaveBeenCalledWith(false)); }); it('shows OFF state explanation when ai_enabled=false', async () => { const { inboxApi } = await import('../../src/renderer/inbox/api.js'); vi.mocked(inboxApi.getSettings).mockResolvedValue({ ai_enabled: false } as never); render(); await screen.findByLabelText(/AI 자동 처리 사용/); expect(screen.getByText(/원문만 저장 모드/)).toBeInTheDocument(); expect(screen.getByRole('link', { name: /ollama\.com|설치/ })).toBeInTheDocument(); }); // v0.2.9 Cut B Task 16 — ON 전환 후 disabled 메모 처리 prompt + 버튼. it('shows disabled count + 처리 버튼 when ai_enabled=true and disabledCount > 0', async () => { const { inboxApi } = await import('../../src/renderer/inbox/api.js'); vi.mocked(inboxApi.getSettings).mockResolvedValue({ ai_enabled: true } as never); vi.mocked(inboxApi.getDisabledCount).mockResolvedValue(5); render(); await screen.findByText(/5건/); expect(screen.getByRole('button', { name: /지금 모두 처리/ })).toBeInTheDocument(); }); it('clicking 처리 버튼 calls enqueueDisabled', async () => { const { inboxApi } = await import('../../src/renderer/inbox/api.js'); vi.mocked(inboxApi.getSettings).mockResolvedValue({ ai_enabled: true } as never); vi.mocked(inboxApi.getDisabledCount).mockResolvedValue(3); vi.mocked(inboxApi.enqueueDisabled).mockResolvedValue({ count: 3 } as never); render(); await screen.findByText(/3건/); fireEvent.click(screen.getByRole('button', { name: /지금 모두 처리/ })); await waitFor(() => expect(inboxApi.enqueueDisabled).toHaveBeenCalled()); }); });