import { fireEvent, render, screen } from '@testing-library/react'; import { beforeEach, describe, expect, it } from 'vitest'; import { MemoryRouter, Route, Routes, useLocation } from 'react-router-dom'; import { ModuleSwitcher } from '@/components/layout/ModuleSwitcher'; import { ModuleSelection } from '@/pages/ModuleSelection'; import { useAuthStore } from '@/stores/auth'; import { useOpencodeStore } from '@/stores/opencode'; function LocationProbe() { const location = useLocation(); return {location.pathname}; } describe('AI module navigation', () => { beforeEach(() => { useAuthStore.setState({ initialized: true, loading: false, accessToken: 'access-token', tokenType: 'Bearer', expiresAt: Date.now() + 60_000, lastActiveAt: Date.now(), canRefresh: true, legacyRefreshToken: null, user: { username: 'zhangsan@example.com', userId: 'user-1', tenantId: null, deptId: null, authorities: [] }, }); useOpencodeStore.setState({ activeProject: null }); }); it('shows the four module choices and routes Canvas from the chooser', async () => { render( } /> } /> , ); expect(screen.getByTestId('ai-module-selection-page')).toBeInTheDocument(); expect(screen.getByRole('heading', { name: '今天,想创作什么?' })).toBeInTheDocument(); expect(screen.getByRole('heading', { name: '今天,想创作什么?' })).toHaveClass('whitespace-nowrap'); expect(screen.getByTestId('ai-module-options')).toHaveClass('module-selection-options'); expect(screen.getByText('zhangsan,欢迎回来!')).toBeInTheDocument(); expect(screen.getByTestId('ai-module-option-programming')).toHaveClass('module-option-card', 'module-option-card-horizontal'); for (const moduleId of ['programming', 'painting', 'learning', 'robot'] as const) { expect(screen.getByTestId(`ai-module-option-${moduleId}`).querySelector('.module-option-artwork')).toBeNull(); } expect(screen.queryByText('先选一个入口。进入后,你还可以从左下角随时切换到其他模块。')).not.toBeInTheDocument(); expect(screen.queryByText('选择创作空间。你可以随时从侧边栏切换。')).not.toBeInTheDocument(); expect(screen.getByAltText('Makelore')).toHaveClass('h-8', 'w-40', 'object-cover'); expect(screen.getByAltText('Makelore').closest('header')).toHaveClass('absolute', 'inset-x-0', 'top-0', 'h-16'); expect(screen.getByTestId('module-selection-drag-region')).toHaveClass('drag-region'); expect(screen.getByText('Code 编程')).toBeInTheDocument(); expect(screen.getByAltText('游戏创作工作台')).toBeInTheDocument(); expect(screen.getByTestId('ai-module-option-programming')).toHaveTextContent('把创意编程变成可运行的产品、游戏或工具'); expect(screen.getByText('Canvas 设计')).toBeInTheDocument(); expect(screen.getByAltText('数位板设计创作')).toBeInTheDocument(); expect(screen.getByTestId('ai-module-option-painting')).toHaveTextContent('用灵感描绘色彩缤纷的世界'); const learningOption = screen.getByTestId('ai-module-option-learning'); expect(learningOption).toBeEnabled(); expect(learningOption).not.toHaveClass('module-option-card-disabled'); expect(learningOption).toHaveTextContent('Learning 学习'); expect(learningOption).toHaveTextContent('用顶尖的方法解锁万物规律'); expect(screen.getByAltText('数学科技宇宙')).toBeInTheDocument(); const robotOption = screen.getByTestId('ai-module-option-robot'); expect(robotOption).toBeEnabled(); expect(robotOption).not.toHaveClass('module-option-card-disabled'); expect(robotOption).toHaveTextContent('Robot 机器'); expect(robotOption).toHaveTextContent('制作你的第一个机器人小伙伴'); expect(screen.getByAltText('青少年管理机器人硬件与设备绑定')).toBeInTheDocument(); fireEvent.click(learningOption); expect(screen.getByTestId('ai-module-selection-page')).toBeInTheDocument(); fireEvent.click(screen.getByTestId('ai-module-option-painting')); expect(await screen.findByTestId('location-path')).toHaveTextContent('/image-canvas'); }); it('routes Robot from the chooser to the AI hardware workspace', async () => { render( } /> } /> , ); fireEvent.click(screen.getByTestId('ai-module-option-robot')); expect(await screen.findByTestId('location-path')).toHaveTextContent('/ai-hardware'); }); it('routes AI programming from the chooser to the project conversation entry', async () => { render( } /> } /> , ); fireEvent.click(screen.getByTestId('ai-module-option-programming')); expect(await screen.findByTestId('location-path')).toHaveTextContent('/opencode-chat'); }); it('keeps the chooser public and sends signed-out users to login when they choose a module', async () => { useAuthStore.setState({ accessToken: null, tokenType: null, expiresAt: null, lastActiveAt: null, canRefresh: false, legacyRefreshToken: null, user: null, }); render( } /> } /> , ); expect(screen.getByTestId('ai-module-selection-page')).toBeInTheDocument(); fireEvent.click(screen.getByTestId('ai-module-option-programming')); expect(await screen.findByTestId('location-path')).toHaveTextContent('/login'); }); it('returns to the module chooser from the Sidebar entry', async () => { render( } /> , ); expect(screen.getByTestId('sidebar-module-switcher-trigger')).toHaveTextContent('绘画 Canvas'); expect(screen.getByTestId('sidebar-module-switcher-trigger')).toHaveAttribute('aria-label', '返回首页'); expect(screen.getByTestId('sidebar-module-switcher-trigger')).toHaveClass('p-3'); expect(screen.queryByTestId('sidebar-module-switcher-menu')).not.toBeInTheDocument(); fireEvent.click(screen.getByTestId('sidebar-module-switcher-trigger')); expect(await screen.findByTestId('location-path')).toHaveTextContent('/module-select'); }); it('shows Robot as the active module and returns to the chooser from the AI hardware route', async () => { render( } /> , ); expect(screen.getByTestId('sidebar-module-switcher-trigger')).toHaveTextContent('Robot 机器'); fireEvent.click(screen.getByTestId('sidebar-module-switcher-trigger')); expect(await screen.findByTestId('location-path')).toHaveTextContent('/module-select'); }); it('keeps breathing room around the compact module switcher trigger', () => { render( , ); const trigger = screen.getByTestId('sidebar-module-switcher-trigger'); expect(trigger).toHaveClass('inline-flex', 'w-fit', 'p-2'); }); });