Files
makelore/tests/unit/module-navigation.test.tsx
inman c809002180
Some checks failed
Electron E2E / Electron E2E (macos-latest) (push) Has been cancelled
Electron E2E / Electron E2E (ubuntu-latest) (push) Has been cancelled
Electron E2E / Electron E2E (windows-latest) (push) Has been cancelled
feat: add bundled OpenCode skills and refine module flow
2026-08-14 18:15:55 +08:00

177 lines
8.1 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 Canvas 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');
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(
<MemoryRouter initialEntries={['/module-select']}>
<Routes>
<Route path="/module-select" element={<ModuleSelection />} />
<Route path="*" element={<LocationProbe />} />
</Routes>
</MemoryRouter>,
);
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(
<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('returns to the module chooser 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')).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(
<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(await screen.findByTestId('location-path')).toHaveTextContent('/module-select');
});
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', 'p-2');
});
});