From cc33e6c7a8f065e9de444294d33fcd538ff6b59e Mon Sep 17 00:00:00 2001 From: Amit Haridas Date: Mon, 8 Jun 2026 06:34:40 +0530 Subject: [PATCH] feat(renderer): add FirstRunWizard with theme/channel/template steps --- src/renderer/components/FirstRunWizard.tsx | 81 ++++++++++++++++++++++ tests/component/FirstRunWizard.test.tsx | 44 ++++++++++++ 2 files changed, 125 insertions(+) create mode 100644 src/renderer/components/FirstRunWizard.tsx create mode 100644 tests/component/FirstRunWizard.test.tsx diff --git a/src/renderer/components/FirstRunWizard.tsx b/src/renderer/components/FirstRunWizard.tsx new file mode 100644 index 0000000..6d17b6e --- /dev/null +++ b/src/renderer/components/FirstRunWizard.tsx @@ -0,0 +1,81 @@ +import { useState } from 'react'; +import { useAppStore } from '@/stores/app-store'; +import { useSettingsStore } from '@/stores/settings-store'; + +const TEMPLATES = { + blank: '', + readme: '# Project\n\nDescription.\n\n## Usage\n\n```\nnpm install\n```\n', + meeting: '# Meeting Notes — YYYY-MM-DD\n\n## Attendees\n\n- \n## Agenda\n\n1. \n\n## Action items\n\n- [ ] \n', + blog: '# Title\n\n*Subtitle*\n\nLorem ipsum.\n\n---\n\n## Section 1\n', +}; + +export function FirstRunWizard() { + const firstRun = useAppStore((s) => s.firstRun); + const setFirstRun = useAppStore((s) => s.setFirstRun); + const theme = useSettingsStore((s) => s.theme); + const setSetting = useSettingsStore((s) => s.setSetting); + const updateChannel = useSettingsStore((s) => s.updateChannel); + const [step, setStep] = useState(0); + const [template, setTemplate] = useState('blank'); + + if (!firstRun) return null; + + const close = () => setFirstRun(false); + + return ( +
+
+ {step === 0 && ( +
+

Pick a theme

+
+ {(['light', 'dark', 'system'] as const).map((t) => ( + + ))} +
+
+ )} + {step === 1 && ( +
+

Update channel

+
+ + +
+
+ )} + {step === 2 && ( +
+

Starter template

+ +
+ )} +
+ +
+ {step > 0 && } + {step < 2 ? ( + + ) : ( + + )} +
+
+
+
+ ); +} diff --git a/tests/component/FirstRunWizard.test.tsx b/tests/component/FirstRunWizard.test.tsx new file mode 100644 index 0000000..31aae12 --- /dev/null +++ b/tests/component/FirstRunWizard.test.tsx @@ -0,0 +1,44 @@ +import { describe, expect, test, beforeEach, vi } from 'vitest'; +import { render, screen, fireEvent } from '@testing-library/react'; +import { useAppStore } from '@/stores/app-store'; +import { useSettingsStore } from '@/stores/settings-store'; +import { FirstRunWizard } from '@/components/FirstRunWizard'; + +beforeEach(() => { + useAppStore.setState({ firstRun: true, modal: { kind: null } } as any); + useSettingsStore.setState({ theme: 'system', updateChannel: 'github' } as any); +}); + +describe('FirstRunWizard', () => { + test('renders nothing when firstRun is false', () => { + useAppStore.setState({ firstRun: false } as any); + const { container } = render(); + expect(container.firstChild).toBeNull(); + }); + + test('renders step 1 by default', () => { + render(); + expect(screen.getByText(/theme/i)).toBeInTheDocument(); + }); + + test('skip link closes the wizard and sets firstRun false', () => { + render(); + fireEvent.click(screen.getByText(/skip/i)); + expect(useAppStore.getState().firstRun).toBe(false); + }); + + test('selecting a theme and clicking Next moves to step 2', () => { + render(); + fireEvent.click(screen.getByLabelText(/dark/i)); + fireEvent.click(screen.getByText(/next/i)); + expect(useSettingsStore.getState().theme).toBe('dark'); + }); + + test('selecting concreteinfo channel persists it', () => { + render(); + fireEvent.click(screen.getByText(/next/i)); + fireEvent.click(screen.getByLabelText(/concreteinfo/i)); + fireEvent.click(screen.getByText(/next/i)); + expect(useSettingsStore.getState().updateChannel).toBe('concreteinfo'); + }); +});