// @vitest-environment jsdom import { describe, it, expect, beforeEach, vi } from 'vitest'; import '@testing-library/jest-dom/vitest'; import { render, screen, fireEvent, cleanup } from '@testing-library/react'; // inboxApi 는 window.inkling.inbox 를 참조하므로 jsdom 환경에서 import 자체가 throw. // SettingsPage 가 마운트하는 AiProviderSection 의 useEffect 가 loadOllamaSettings 를 호출하므로 // 빈 객체 대신 필요한 메서드를 stub 한다. vi.mock('../../src/renderer/inbox/api.js', () => ({ inboxApi: { // setShowSettings(false) → setView('inbox') → loadByView('inbox') 가 listByStatus 호출. listByStatus: vi.fn(async () => []), loadOllamaSettings: vi.fn(async () => null), saveOllamaSettings: vi.fn(async () => ({ ok: true })), ollamaRecheck: vi.fn(async () => ({ ok: true })), getAutostart: vi.fn(async () => ({ openAtLogin: false, diagnostic: { withArgs: { openAtLogin: false, executableWillLaunchAtLogin: false }, noArgs: { openAtLogin: false, executableWillLaunchAtLogin: false }, execPath: '/p' } })), setAutostart: vi.fn(async (open: boolean) => ({ openAtLogin: open, diagnostic: { withArgs: { openAtLogin: open, executableWillLaunchAtLogin: open }, noArgs: { openAtLogin: open, executableWillLaunchAtLogin: open }, execPath: '/p' } })), runBackup: vi.fn(async () => ({ ok: true })), runExport: vi.fn(async () => ({ ok: true })), runImport: vi.fn(async () => ({ ok: true })), runSync: vi.fn(async () => ({ ok: true })), runExportTelemetry: vi.fn(async () => ({ ok: true })), getAppInfo: vi.fn(async () => ({ version: '0.2.7', electron: '41.3.0', node: '22.x', os: 'darwin 23.6.0', profileDir: '/tmp/Inkling' })), openProfileDir: vi.fn(async () => undefined), copyAppInfo: vi.fn(async () => undefined), // v0.2.9 Cut B Task 15-16 — AiProviderSection 의 토글 + disabled 메모 prompt. getSettings: vi.fn(async () => ({ ai_enabled: true, onboarding_completed: true })), setAiEnabled: vi.fn(async () => ({ ok: true as const })), setOnboardingCompleted: vi.fn(async () => ({ ok: true as const })), getDisabledCount: vi.fn(async () => 0), enqueueDisabled: vi.fn(async () => ({ count: 0 })), // v0.3.0 Cut E — SyncSection 이 SettingsPage 에 마운트되어 호출. getSyncStatus: vi.fn(async () => ({ lastAt: null, lastResult: null, nextAt: null })), setSyncAutoEnabled: vi.fn(async () => ({ ok: true as const })), setSyncIntervalMin: vi.fn(async () => ({ ok: true as const })), configureSync: vi.fn(async () => ({ ok: true as const })), testSyncConnection: vi.fn(async () => ({ ok: true as const })), // v0.3.1 Cut F — VisionSection 이 AiProviderSection 에 마운트되어 호출. getVisionModels: vi.fn(async () => ({ models: [], at: null, selected: null })), setVisionModel: vi.fn(async () => ({ ok: true as const })), refreshVisionCache: vi.fn(async () => ({ ok: true as const, models: [] })) } })); import { SettingsPage } from '../../src/renderer/inbox/components/SettingsPage'; import { useInbox } from '../../src/renderer/inbox/store'; describe('SettingsPage', () => { beforeEach(() => { cleanup(); useInbox.setState({ showSettings: true }); }); it('renders header with "← 돌아가기" button', () => { render(); expect(screen.getByRole('button', { name: /돌아가기/ })).toBeInTheDocument(); }); it('renders 5 section headings', () => { render(); expect(screen.getByText('AI 제공자')).toBeInTheDocument(); expect(screen.getByText('자동 실행')).toBeInTheDocument(); expect(screen.getByText('백업 / 복원')).toBeInTheDocument(); expect(screen.getByText('정보')).toBeInTheDocument(); expect(screen.getByText('동기화')).toBeInTheDocument(); }); it('clicking "← 돌아가기" sets showSettings to false', () => { render(); fireEvent.click(screen.getByRole('button', { name: /돌아가기/ })); expect(useInbox.getState().showSettings).toBe(false); }); });