+ 이미지 분석 모델 (선택사항)
+
+
+
+
+ {at !== null && (
+
+ 마지막 감지: {new Date(at).toLocaleString('ko-KR')}
+
+ )}
+ {feedback !== null && (
+ {feedback}
+ )}
+ {models.length === 0 && (
+
+ 감지된 모델 없음. Ollama 에 vision 모델을 설치하고 "다시 감지" 클릭.
+
+ )}
+
+ );
+}
diff --git a/tests/unit/AiProviderSection.test.tsx b/tests/unit/AiProviderSection.test.tsx
index 4a9bea7..13d6618 100644
--- a/tests/unit/AiProviderSection.test.tsx
+++ b/tests/unit/AiProviderSection.test.tsx
@@ -11,7 +11,11 @@ vi.mock('../../src/renderer/inbox/api.js', () => ({
getSettings: vi.fn(async () => ({ ai_enabled: true })),
setAiEnabled: vi.fn(async () => ({ ok: true })),
getDisabledCount: vi.fn(async () => 0),
- enqueueDisabled: vi.fn(async () => ({ count: 0 }))
+ enqueueDisabled: vi.fn(async () => ({ count: 0 })),
+ // 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: [] }))
}
}));
diff --git a/tests/unit/App.test.tsx b/tests/unit/App.test.tsx
index f49a065..0c269f7 100644
--- a/tests/unit/App.test.tsx
+++ b/tests/unit/App.test.tsx
@@ -62,7 +62,11 @@ vi.mock('../../src/renderer/inbox/api.js', () => ({
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 }))
+ 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: [] }))
}
}));
diff --git a/tests/unit/SettingsPage.test.tsx b/tests/unit/SettingsPage.test.tsx
index 7405c46..ea8b313 100644
--- a/tests/unit/SettingsPage.test.tsx
+++ b/tests/unit/SettingsPage.test.tsx
@@ -52,7 +52,11 @@ vi.mock('../../src/renderer/inbox/api.js', () => ({
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 }))
+ 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: [] }))
}
}));
diff --git a/tests/unit/VisionSection.test.tsx b/tests/unit/VisionSection.test.tsx
new file mode 100644
index 0000000..f610bca
--- /dev/null
+++ b/tests/unit/VisionSection.test.tsx
@@ -0,0 +1,75 @@
+// @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 { mockGet, mockSet, mockRefresh } = vi.hoisted(() => ({
+ mockGet: vi.fn(),
+ mockSet: vi.fn(),
+ mockRefresh: vi.fn()
+}));
+
+vi.mock('../../src/renderer/inbox/api.js', () => ({
+ inboxApi: {
+ getVisionModels: mockGet,
+ setVisionModel: mockSet,
+ refreshVisionCache: mockRefresh
+ }
+}));
+
+import { VisionSection } from '../../src/renderer/inbox/components/settings/VisionSection';
+
+describe('VisionSection', () => {
+ beforeEach(() => {
+ vi.clearAllMocks();
+ cleanup();
+ mockGet.mockResolvedValue({
+ models: ['gemma3:12b-vision', 'llava:13b'],
+ at: '2026-05-10T05:00:00Z',
+ selected: 'gemma3:12b-vision'
+ });
+ mockSet.mockResolvedValue({ ok: true });
+ mockRefresh.mockResolvedValue({ ok: true, models: ['gemma3:12b-vision', 'llava:13b'] });
+ });
+
+ it('open 시 cache 로드 + dropdown 옵션 표시 + 선택된 모델 default', async () => {
+ render(