// @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'; import React from 'react'; const { mockGetSettings, mockConfigureSync, mockTestSyncConnection, mockGetSyncStatus, mockSetAuto, mockSetInterval } = vi.hoisted(() => ({ mockGetSettings: vi.fn(async () => ({ sync_repo_url: '', sync_auto_enabled: true, sync_interval_min: 30 })), mockConfigureSync: vi.fn(async () => ({ ok: true as const })), mockTestSyncConnection: vi.fn(async () => ({ ok: true as const })), mockGetSyncStatus: vi.fn(async () => ({ lastAt: null, lastResult: null, nextAt: null })), mockSetAuto: vi.fn(async () => ({ ok: true as const })), mockSetInterval: vi.fn(async () => ({ ok: true as const })) })); vi.mock('../../src/renderer/inbox/api.js', () => ({ inboxApi: { getSettings: mockGetSettings, configureSync: mockConfigureSync, testSyncConnection: mockTestSyncConnection, getSyncStatus: mockGetSyncStatus, setSyncAutoEnabled: mockSetAuto, setSyncIntervalMin: mockSetInterval } })); // ConflictModal is imported by SyncSection — mock it to avoid needing listConflicts vi.mock('../../src/renderer/inbox/components/ConflictModal.js', () => ({ ConflictModal: () => null })); import { SyncSection } from '../../src/renderer/inbox/components/settings/SyncSection'; describe('SyncSection', () => { beforeEach(() => { vi.clearAllMocks(); cleanup(); mockGetSettings.mockResolvedValue({ sync_repo_url: '', sync_auto_enabled: true, sync_interval_min: 30 }); mockGetSyncStatus.mockResolvedValue({ lastAt: null, lastResult: null, nextAt: null }); }); it('빈 URL — 저장/연결 테스트 버튼 + 자동 sync 옵션 hide', async () => { render(); await waitFor(() => screen.getByRole('button', { name: /저장/ })); expect(screen.queryByText(/자동 sync/)).not.toBeInTheDocument(); }); it('URL 입력 + 저장 → configureSync 호출 + 자동 sync 옵션 표시', async () => { mockGetSettings.mockResolvedValueOnce({ sync_repo_url: 'git@host:u/r.git', sync_auto_enabled: true, sync_interval_min: 30 }); render(); await waitFor(() => screen.getByText(/자동 sync/)); expect(screen.getByText(/자동 sync/)).toBeInTheDocument(); }); it('연결 테스트 클릭 → testSyncConnection 호출 + 결과 표시', async () => { mockGetSettings.mockResolvedValueOnce({ sync_repo_url: 'git@host:u/r.git', sync_auto_enabled: true, sync_interval_min: 30 }); render(); await waitFor(() => screen.getByRole('button', { name: /연결 테스트/ })); fireEvent.click(screen.getByRole('button', { name: /연결 테스트/ })); await waitFor(() => { expect(mockTestSyncConnection).toHaveBeenCalled(); expect(screen.getByText(/연결 성공/)).toBeInTheDocument(); }); }); it('자동 sync 토글 → setSyncAutoEnabled 호출', async () => { mockGetSettings.mockResolvedValueOnce({ sync_repo_url: 'git@host:u/r.git', sync_auto_enabled: true, sync_interval_min: 30 }); render(); await waitFor(() => screen.getByLabelText(/자동 sync/)); fireEvent.click(screen.getByLabelText(/자동 sync/)); await waitFor(() => { expect(mockSetAuto).toHaveBeenCalledWith(false); }); }); it('도움말 버튼 클릭 → SyncHelpModal open', async () => { render(); await waitFor(() => screen.getByRole('button', { name: /저장/ })); fireEvent.click(screen.getByRole('button', { name: /^도움말$/ })); await waitFor(() => screen.getByRole('heading', { name: /동기화 도움말/ })); expect(screen.getByRole('heading', { name: /동기화 도움말/ })).toBeInTheDocument(); }); });