Files
makelore/tests/unit/module-navigation.test.tsx

176 lines
8.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 <output data-testid="location-path">{location.pathname}</output>;
}
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 enabled modules from the chooser', async () => {
render(
<MemoryRouter initialEntries={['/module-select']}>
<Routes>
<Route path="/module-select" element={<ModuleSelection />} />
<Route path="*" element={<LocationProbe />} />
</Routes>
</MemoryRouter>,
);
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');
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).toBeDisabled();
expect(learningOption).toHaveClass('module-option-card-disabled');
expect(learningOption).toHaveTextContent('Learning 学习');
expect(learningOption).toHaveTextContent('用顶尖的方法解锁万物规律');
expect(learningOption).toHaveTextContent('暂未开放');
expect(screen.getByAltText('数学科技宇宙')).toBeInTheDocument();
const robotOption = screen.getByTestId('ai-module-option-robot');
expect(robotOption).toBeEnabled();
expect(robotOption).toHaveTextContent('Robot 机器');
expect(robotOption).toHaveTextContent('制作你的第一个机器人小伙伴');
expect(robotOption).not.toHaveTextContent('暂未开放');
expect(screen.getByAltText('青少年管理机器人硬件与设备绑定')).toBeInTheDocument();
fireEvent.click(robotOption);
expect(await screen.findByTestId('location-path')).toHaveTextContent('/ai-hardware');
});
it('routes AI programming from the chooser to the project conversation entry', async () => {
render(
<MemoryRouter initialEntries={['/module-select']}>
<Routes>
<Route path="/module-select" element={<ModuleSelection />} />
<Route path="*" element={<LocationProbe />} />
</Routes>
</MemoryRouter>,
);
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(
<MemoryRouter initialEntries={['/module-select']}>
<Routes>
<Route path="/module-select" element={<ModuleSelection />} />
<Route path="*" element={<LocationProbe />} />
</Routes>
</MemoryRouter>,
);
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('keeps the current module active and switches from the Sidebar entry', async () => {
render(
<MemoryRouter initialEntries={['/image-canvas']}>
<Routes>
<Route path="*" element={<><ModuleSwitcher sidebarCollapsed={false} /><LocationProbe /></>} />
</Routes>
</MemoryRouter>,
);
expect(screen.getByTestId('sidebar-module-switcher-trigger')).toHaveAttribute('aria-expanded', 'false');
expect(screen.getByTestId('sidebar-module-switcher-trigger')).toHaveTextContent('绘画 Canvas');
expect(screen.getByTestId('sidebar-module-switcher-menu')).toHaveAttribute('data-state', 'closed');
expect(screen.getByTestId('sidebar-module-painting')).toBeInTheDocument();
fireEvent.click(screen.getByTestId('sidebar-module-switcher-trigger'));
expect(screen.getByTestId('sidebar-module-switcher-menu')).toBeInTheDocument();
expect(screen.getByTestId('sidebar-module-painting')).toHaveAttribute('aria-current', 'page');
expect(screen.getByTestId('sidebar-module-programming')).not.toHaveAttribute('aria-current');
expect(screen.getByTestId('sidebar-module-robot')).not.toHaveAttribute('aria-current');
const learningModule = screen.getByTestId('sidebar-module-learning');
expect(learningModule).toBeDisabled();
expect(learningModule).toHaveTextContent('学习 Learning');
expect(learningModule).toHaveTextContent('AI 学习');
expect(learningModule).not.toHaveAttribute('aria-current');
fireEvent.click(learningModule);
expect(screen.getByTestId('location-path')).toHaveTextContent('/image-canvas');
fireEvent.click(screen.getByTestId('sidebar-module-programming'));
expect(await screen.findByTestId('location-path')).toHaveTextContent('/opencode-chat');
});
it('marks Robot active on the AI hardware route and switches back to Canvas', async () => {
render(
<MemoryRouter initialEntries={['/ai-hardware']}>
<Routes>
<Route path="*" element={<><ModuleSwitcher sidebarCollapsed={false} /><LocationProbe /></>} />
</Routes>
</MemoryRouter>,
);
expect(screen.getByTestId('sidebar-module-switcher-trigger')).toHaveTextContent('Robot 机器');
fireEvent.click(screen.getByTestId('sidebar-module-switcher-trigger'));
expect(screen.getByTestId('sidebar-module-robot')).toHaveAttribute('aria-current', 'page');
expect(screen.getByTestId('sidebar-module-programming')).not.toHaveAttribute('aria-current');
fireEvent.click(screen.getByTestId('sidebar-module-painting'));
expect(await screen.findByTestId('location-path')).toHaveTextContent('/image-canvas');
});
it('keeps breathing room around the compact module switcher trigger', () => {
render(
<MemoryRouter initialEntries={['/opencode-chat']}>
<ModuleSwitcher sidebarCollapsed={false} compact />
</MemoryRouter>,
);
const trigger = screen.getByTestId('sidebar-module-switcher-trigger');
expect(trigger).toHaveClass('inline-flex', 'w-fit', 'px-2');
});
});