feat(v029): OnboardingWizard 3 옵션 + 설치 가이드 link

This commit is contained in:
altair823
2026-05-09 16:18:19 +09:00
parent d3150976d4
commit d2c7bf1b39
2 changed files with 100 additions and 0 deletions

View 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 }}>
:&nbsp;
<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>
);
}

View 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();
});
});
});