feat(v029): OnboardingWizard 3 옵션 + 설치 가이드 link
This commit is contained in:
42
src/renderer/inbox/components/OnboardingWizard.tsx
Normal file
42
src/renderer/inbox/components/OnboardingWizard.tsx
Normal file
@@ -0,0 +1,42 @@
|
||||
import React from 'react';
|
||||
import { inboxApi } from '../api.js';
|
||||
|
||||
/**
|
||||
* v0.2.9 Cut B Task 11 — 첫 launch onboarding 위저드.
|
||||
*
|
||||
* 3 옵션 (AI 사용 / 원문만 / 나중에) 중 하나를 선택. AI 옵션 (true/false) 은
|
||||
* setAiEnabled 로 settings 에 저장, 모든 옵션은 setOnboardingCompleted(true) 로
|
||||
* 두 번째 launch 부터 미노출. "나중에" 는 ai_enabled 기본값 (true) 유지 — 사용자
|
||||
* 가 SettingsPage 에서 추후 선택 가능.
|
||||
*/
|
||||
export function OnboardingWizard({ onClose }: { onClose: () => void }): React.ReactElement {
|
||||
async function choose(aiEnabled: boolean | null): Promise<void> {
|
||||
if (aiEnabled !== null) await inboxApi.setAiEnabled(aiEnabled);
|
||||
await inboxApi.setOnboardingCompleted(true);
|
||||
onClose();
|
||||
}
|
||||
|
||||
return (
|
||||
<div role="dialog" aria-label="시작 안내" style={{
|
||||
position: 'fixed', top: 0, left: 0, right: 0, bottom: 0,
|
||||
background: 'rgba(0,0,0,0.5)', display: 'flex', alignItems: 'center', justifyContent: 'center', zIndex: 1000
|
||||
}}>
|
||||
<div style={{ background: '#fff', padding: 24, borderRadius: 8, maxWidth: 520 }}>
|
||||
<h2 style={{ margin: '0 0 12px' }}>Inkling 사용 시작</h2>
|
||||
<p style={{ fontSize: 14, lineHeight: 1.6, marginBottom: 12 }}>
|
||||
Inkling 은 로컬 LLM (Ollama) 으로 메모를 자동 정리합니다.
|
||||
Ollama 가 설치되어 있고 한국어 지원 모델 (gemma3, gemma2 등) 이 pull 되어 있어야 최적의 경험이 가능합니다.
|
||||
</p>
|
||||
<p style={{ fontSize: 13, marginBottom: 16 }}>
|
||||
설치 가이드:
|
||||
<a href="https://ollama.com/download" target="_blank" rel="noopener noreferrer">ollama.com/download</a>
|
||||
</p>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
|
||||
<button onClick={() => choose(true)}>AI 자동 처리 사용 (Ollama 필요)</button>
|
||||
<button onClick={() => choose(false)}>원문만 저장 (AI 처리 안 함)</button>
|
||||
<button onClick={() => choose(null)} style={{ marginTop: 4 }}>나중에 설정</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
58
tests/unit/OnboardingWizard.test.tsx
Normal file
58
tests/unit/OnboardingWizard.test.tsx
Normal file
@@ -0,0 +1,58 @@
|
||||
// @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';
|
||||
|
||||
const { mockSetAi, mockSetCompleted } = vi.hoisted(() => ({
|
||||
mockSetAi: vi.fn(async () => ({ ok: true as const })),
|
||||
mockSetCompleted: vi.fn(async () => ({ ok: true as const }))
|
||||
}));
|
||||
vi.mock('../../src/renderer/inbox/api.js', () => ({
|
||||
inboxApi: { setAiEnabled: mockSetAi, setOnboardingCompleted: mockSetCompleted }
|
||||
}));
|
||||
|
||||
import { OnboardingWizard } from '../../src/renderer/inbox/components/OnboardingWizard';
|
||||
|
||||
describe('OnboardingWizard', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
cleanup();
|
||||
});
|
||||
|
||||
it('renders 3 buttons + 설치 가이드 link', () => {
|
||||
render(<OnboardingWizard onClose={vi.fn()} />);
|
||||
expect(screen.getByRole('button', { name: /AI 자동 처리 사용/ })).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: /원문만 저장/ })).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: /나중에 설정/ })).toBeInTheDocument();
|
||||
expect(screen.getByRole('link', { name: /ollama\.com/ })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('"AI 사용" → setAiEnabled(true) + setOnboardingCompleted(true) + onClose', async () => {
|
||||
const onClose = vi.fn();
|
||||
render(<OnboardingWizard onClose={onClose} />);
|
||||
fireEvent.click(screen.getByRole('button', { name: /AI 자동 처리 사용/ }));
|
||||
await waitFor(() => {
|
||||
expect(mockSetAi).toHaveBeenCalledWith(true);
|
||||
expect(mockSetCompleted).toHaveBeenCalledWith(true);
|
||||
expect(onClose).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it('"원문만" → setAiEnabled(false) + setOnboardingCompleted(true)', async () => {
|
||||
render(<OnboardingWizard onClose={vi.fn()} />);
|
||||
fireEvent.click(screen.getByRole('button', { name: /원문만 저장/ }));
|
||||
await waitFor(() => {
|
||||
expect(mockSetAi).toHaveBeenCalledWith(false);
|
||||
expect(mockSetCompleted).toHaveBeenCalledWith(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('"나중에" → setOnboardingCompleted(true) only (no setAiEnabled)', async () => {
|
||||
render(<OnboardingWizard onClose={vi.fn()} />);
|
||||
fireEvent.click(screen.getByRole('button', { name: /나중에 설정/ }));
|
||||
await waitFor(() => {
|
||||
expect(mockSetCompleted).toHaveBeenCalledWith(true);
|
||||
expect(mockSetAi).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user