/* @vitest-environment jsdom */ import { cleanup, fireEvent, render, screen, within, } from '@testing-library/react'; import { expect, test, vi } from 'vitest'; import { PuzzleOnboardingLoginOverlay, type PuzzleOnboardingPhase, PuzzleOnboardingView, } from './PuzzleOnboardingView'; function renderOnboarding({ prompt = '月亮糖果工厂', phase = 'input', error = null, onPromptChange = vi.fn(), onSubmit = vi.fn(), onSkip = vi.fn(), }: { prompt?: string; phase?: PuzzleOnboardingPhase; error?: string | null; onPromptChange?: (value: string) => void; onSubmit?: () => void; onSkip?: () => void; } = {}) { render( , ); return { onPromptChange, onSubmit, onSkip }; } test('PuzzleOnboardingView uses shared dark textarea and error status chrome', () => { const { onPromptChange } = renderOnboarding({ error: '拼图生成失败', }); const textarea = screen.getByPlaceholderText('把你的梦讲给我听吧'); fireEvent.change(textarea, { target: { value: '一座会唱歌的城堡' } }); expect(textarea.tagName).toBe('TEXTAREA'); expect(textarea.className).toContain('platform-text-field--editor-dark'); expect(textarea.className).toContain('min-h-32'); expect(onPromptChange).toHaveBeenCalledWith('一座会唱歌的城堡'); expect(screen.getByText('拼图生成失败').className).toContain( 'platform-status-message', ); expect(screen.getByText('拼图生成失败').className).toContain( 'border-rose-300/15', ); expect(screen.queryByRole('dialog')).toBeNull(); }); test('PuzzleOnboardingView preserves submit, skip, and disabled phase behavior', () => { const { onSubmit, onSkip } = renderOnboarding(); const submitButton = screen.getByRole('button', { name: '生成' }); const skipButton = screen.getByRole('button', { name: '跳过' }); expect(submitButton.className).toContain('platform-action-button--accent'); expect(submitButton.className).toContain('bg-amber-200'); expect(submitButton.className).toContain('w-full'); expect(skipButton.className).toContain( 'platform-action-button--editor-dark', ); expect(skipButton.className).toContain('rounded-full'); expect(skipButton.className).toContain('absolute'); fireEvent.click(submitButton); fireEvent.click(skipButton); expect(onSubmit).toHaveBeenCalledTimes(1); expect(onSkip).toHaveBeenCalledTimes(1); cleanup(); renderOnboarding({ prompt: '', phase: 'input' }); expect(screen.getByRole('button', { name: '生成' })).toHaveProperty( 'disabled', true, ); cleanup(); renderOnboarding({ phase: 'generating' }); expect(screen.getByPlaceholderText('把你的梦讲给我听吧')).toHaveProperty( 'disabled', true, ); expect(screen.getByRole('button', { name: '跳过' })).toHaveProperty( 'disabled', true, ); cleanup(); renderOnboarding({ phase: 'generated' }); expect(screen.getByPlaceholderText('把你的梦讲给我听吧')).toHaveProperty( 'disabled', true, ); expect(screen.getByRole('button', { name: '生成' })).toHaveProperty( 'disabled', true, ); }); test('PuzzleOnboardingLoginOverlay uses shared error status and keeps login action', () => { const onLogin = vi.fn(); const { rerender } = render( , ); const dialog = screen.getByRole('dialog', { name: '登录后保存你的拼图' }); expect(dialog.className).toContain('platform-modal-shell'); expect(dialog.className).toContain('!max-w-[24rem]'); expect(dialog.parentElement?.className).toContain('z-[110]'); expect( within(dialog).queryByRole('button', { name: '关闭' }), ).toBeNull(); fireEvent.click(screen.getByRole('button', { name: '注册账号 / 登录' })); expect(onLogin).toHaveBeenCalledTimes(1); expect(screen.getByRole('button', { name: '注册账号 / 登录' }).className).toContain( 'platform-action-button--accent', ); expect(screen.getByText('保存首访拼图失败').className).toContain( 'platform-status-message', ); rerender( , ); expect(screen.getByRole('button', { name: '注册账号 / 登录' })).toHaveProperty( 'disabled', true, ); });