feat: update Makelore modules and conversations
This commit is contained in:
@@ -99,34 +99,117 @@ describe('Sidebar project initialization flow', () => {
|
||||
it('shows create project first and removes redundant project navigation actions', async () => {
|
||||
hostApiFetchMock.mockResolvedValue({ projects: [], activeProject: null });
|
||||
render(<MemoryRouter><Sidebar /></MemoryRouter>);
|
||||
expect(screen.getByRole('img', { name: 'Makelore logo' })).toBeInTheDocument();
|
||||
expect(screen.getByText('Makelore 工作台')).toBeInTheDocument();
|
||||
expect(screen.queryByText(/NITU/)).not.toBeInTheDocument();
|
||||
expect(screen.getByTestId('sidebar-create-project')).toHaveTextContent('新建项目');
|
||||
const switcher = screen.getByTestId('sidebar-module-switcher');
|
||||
expect(switcher).toHaveTextContent('编程 Code');
|
||||
expect(screen.queryByRole('img', { name: 'Makelore logo' })).not.toBeInTheDocument();
|
||||
expect(screen.queryByText('一念成光,万物可创。')).not.toBeInTheDocument();
|
||||
const createProject = screen.getByTestId('sidebar-create-project');
|
||||
expect(createProject).toHaveTextContent('新建项目');
|
||||
expect(createProject).toHaveClass('bg-transparent', 'border-0', 'shadow-none');
|
||||
expect(screen.getByTestId('sidebar')).toHaveClass('border-r', 'bg-surface-sidebar');
|
||||
expect(screen.queryByTestId('sidebar-nav-asset-square')).not.toBeInTheDocument();
|
||||
expect(screen.queryByTestId('sidebar-nav-project-config')).not.toBeInTheDocument();
|
||||
expect(screen.queryByTestId('sidebar-nav-agent-chat')).not.toBeInTheDocument();
|
||||
expect(screen.queryByRole('button', { name: '添加已有项目' })).not.toBeInTheDocument();
|
||||
expect(screen.queryByText('课程路径')).not.toBeInTheDocument();
|
||||
expect(screen.queryByText('伙伴广场')).not.toBeInTheDocument();
|
||||
expect(screen.getByTestId('sidebar-module-switcher')).toHaveTextContent('切换模块');
|
||||
expect(screen.getByTestId('sidebar-module-switcher-trigger')).toHaveAttribute('aria-expanded', 'false');
|
||||
const moduleTrigger = screen.getByTestId('sidebar-module-switcher-trigger');
|
||||
expect(moduleTrigger).toHaveAttribute('aria-expanded', 'false');
|
||||
expect(moduleTrigger).toHaveClass('bg-transparent');
|
||||
expect(moduleTrigger).toHaveClass('w-fit', 'justify-start', 'gap-1');
|
||||
expect(moduleTrigger).not.toHaveClass('bg-brand-soft');
|
||||
expect(moduleTrigger).toHaveTextContent('编程 Code');
|
||||
expect(screen.queryByTestId('sidebar-module-programming')).not.toBeInTheDocument();
|
||||
fireEvent.click(screen.getByTestId('sidebar-module-switcher-trigger'));
|
||||
expect(screen.getByTestId('sidebar-module-programming')).toHaveTextContent('Makelore Code');
|
||||
expect(screen.getByTestId('sidebar-module-painting')).toHaveTextContent('Makelore Canvas');
|
||||
const programmingModule = screen.getByTestId('sidebar-module-programming');
|
||||
expect(programmingModule).toHaveTextContent('编程 Code');
|
||||
expect(programmingModule).toHaveClass('bg-brand-soft');
|
||||
expect(programmingModule.querySelector('svg')).not.toBeInTheDocument();
|
||||
expect(screen.getByTestId('sidebar-module-painting')).toHaveTextContent('绘画 Canvas');
|
||||
expect(screen.getByTestId('sidebar-module-painting')).not.toHaveClass('bg-brand-soft');
|
||||
expect(screen.getByTestId('sidebar-module-programming')).not.toHaveTextContent('AI');
|
||||
expect(screen.getByTestId('sidebar-module-painting')).not.toHaveTextContent('AI');
|
||||
expect(screen.getByTestId('sidebar-module-programming')).not.toHaveTextContent('Makelore');
|
||||
expect(screen.getByTestId('sidebar-module-painting')).not.toHaveTextContent('Makelore');
|
||||
expect(screen.queryByTestId('sidebar-nav-image-canvas')).not.toBeInTheDocument();
|
||||
expect(await screen.findByText(/暂无真实项目/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('places the module switcher above personal profile in the lower-left area', async () => {
|
||||
it('keeps new project and personal profile as dismissible modal dialogs', async () => {
|
||||
hostApiFetchMock.mockResolvedValue({ projects: [], activeProject: null });
|
||||
render(<MemoryRouter><Sidebar /></MemoryRouter>);
|
||||
|
||||
fireEvent.click(screen.getByTestId('sidebar-create-project'));
|
||||
const createDialog = screen.getByRole('dialog', { name: '新建项目' });
|
||||
expect(createDialog).toHaveAttribute('aria-modal', 'true');
|
||||
act(() => fireEvent.keyDown(document, { key: 'Escape' }));
|
||||
await waitFor(() => expect(screen.queryByRole('dialog', { name: '新建项目' })).not.toBeInTheDocument());
|
||||
|
||||
fireEvent.click(screen.getByTestId('sidebar-member-menu-trigger'));
|
||||
fireEvent.click(screen.getByRole('menuitem', { name: '个人资料' }));
|
||||
const profileDialog = screen.getByRole('dialog', { name: '个人资料' });
|
||||
expect(profileDialog).toHaveAttribute('aria-modal', 'true');
|
||||
act(() => fireEvent.keyDown(document, { key: 'Escape' }));
|
||||
await waitFor(() => expect(screen.queryByRole('dialog', { name: '个人资料' })).not.toBeInTheDocument());
|
||||
});
|
||||
|
||||
it('keeps the expanded account menu aligned and scrollable within the sidebar', async () => {
|
||||
hostApiFetchMock.mockResolvedValue({ projects: [], activeProject: null });
|
||||
render(<MemoryRouter><Sidebar /></MemoryRouter>);
|
||||
|
||||
const account = screen.getByTestId('sidebar-member-menu-trigger');
|
||||
expect(account).toHaveClass('min-h-9', 'px-2', 'py-1');
|
||||
fireEvent.click(account);
|
||||
|
||||
const menu = screen.getByTestId('sidebar-account-menu');
|
||||
expect(menu).toHaveClass('absolute', 'left-0', 'w-full', 'overflow-y-auto');
|
||||
expect(menu).toHaveClass('max-h-[calc(100vh-7rem)]');
|
||||
expect(menu).not.toHaveClass('fixed');
|
||||
|
||||
const usageItem = screen.getByTestId('sidebar-account-usage-menuitem');
|
||||
expect(usageItem).toHaveClass('min-h-8');
|
||||
expect(screen.queryByTestId('sidebar-account-usage-drawer')).not.toBeInTheDocument();
|
||||
|
||||
fireEvent.click(usageItem);
|
||||
const usageDrawer = screen.getByTestId('sidebar-account-usage-drawer');
|
||||
expect(usageDrawer).toHaveTextContent('订阅等级');
|
||||
expect(usageDrawer).toHaveTextContent('5 小时');
|
||||
expect(usageDrawer).toHaveTextContent('1 周');
|
||||
});
|
||||
|
||||
it('places the module switcher in the sidebar header above project actions', async () => {
|
||||
hostApiFetchMock.mockResolvedValue({ projects: [], activeProject: null });
|
||||
render(<MemoryRouter><Sidebar /></MemoryRouter>);
|
||||
|
||||
const switcher = screen.getByTestId('sidebar-module-switcher');
|
||||
const profileMenuItem = screen.getByRole('menuitem', { name: '个人资料' });
|
||||
expect(Boolean(switcher.compareDocumentPosition(profileMenuItem) & Node.DOCUMENT_POSITION_FOLLOWING)).toBe(true);
|
||||
const createProject = screen.getByTestId('sidebar-create-project');
|
||||
expect(Boolean(switcher.compareDocumentPosition(createProject) & Node.DOCUMENT_POSITION_FOLLOWING)).toBe(true);
|
||||
});
|
||||
|
||||
it('fully hides the collapsed sidebar and exposes a full hover preview', () => {
|
||||
useSettingsStore.setState({ sidebarCollapsed: true });
|
||||
const onSidebarPeekChange = vi.fn();
|
||||
const { rerender } = render(
|
||||
<MemoryRouter>
|
||||
<Sidebar sidebarPeekOpen={false} onSidebarPeekChange={onSidebarPeekChange} />
|
||||
</MemoryRouter>,
|
||||
);
|
||||
|
||||
const sidebar = screen.getByTestId('sidebar');
|
||||
expect(sidebar).toHaveClass('w-0', 'pointer-events-none', 'opacity-0');
|
||||
|
||||
rerender(
|
||||
<MemoryRouter>
|
||||
<Sidebar sidebarPeekOpen onSidebarPeekChange={onSidebarPeekChange} />
|
||||
</MemoryRouter>,
|
||||
);
|
||||
|
||||
expect(sidebar).toHaveClass('w-64', 'absolute', 'shadow-float');
|
||||
expect(screen.getByTestId('sidebar-create-project')).toHaveTextContent('新建项目');
|
||||
fireEvent.pointerEnter(sidebar);
|
||||
expect(onSidebarPeekChange).toHaveBeenCalledWith(true);
|
||||
fireEvent.pointerLeave(sidebar);
|
||||
expect(onSidebarPeekChange).toHaveBeenCalledWith(false);
|
||||
});
|
||||
|
||||
it('shows an available update beside the lower-left account control', async () => {
|
||||
@@ -149,9 +232,9 @@ describe('Sidebar project initialization flow', () => {
|
||||
expect(update).toHaveAccessibleName('下载新版本 0.9.2');
|
||||
});
|
||||
|
||||
it('creates a path-backed game project with the selected replacement template and opens configuration', async () => {
|
||||
it('creates a path-backed project without selecting a template and opens configuration', async () => {
|
||||
const project = { id: 'prj_game', path: 'D:/repo/星星收集器', name: '星星收集器', createdAt: '2026-07-11T00:00:00.000Z', updatedAt: '2026-07-11T00:00:00.000Z', lastOpenedAt: '2026-07-11T00:00:00.000Z' };
|
||||
const config = createProjectConfig('game-development');
|
||||
const config = createProjectConfig();
|
||||
invokeIpcMock.mockResolvedValue({ canceled: false, filePaths: ['D:/repo/星星收集器'] });
|
||||
hostApiFetchMock.mockImplementation(async (path: string, init?: RequestInit) => {
|
||||
if (path === '/api/opencode/projects') return { projects: [], activeProject: null };
|
||||
@@ -167,17 +250,14 @@ describe('Sidebar project initialization flow', () => {
|
||||
fireEvent.click(screen.getByRole('button', { name: '选择路径' }));
|
||||
await waitFor(() => expect(screen.getByLabelText('项目路径')).toHaveValue('D:/repo/星星收集器'));
|
||||
expect(screen.getByText('将直接接入此文件夹,项目名称使用“星星收集器”,不会重命名目录。')).toBeInTheDocument();
|
||||
expect(screen.queryByTestId('project-template-option-youth-ai-course')).not.toBeInTheDocument();
|
||||
expect(screen.getByTestId('project-template-option-standard-development')).toBeInTheDocument();
|
||||
fireEvent.click(screen.getByTestId('project-template-option-game-development'));
|
||||
fireEvent.click(screen.getByRole('button', { name: '确认创建' }));
|
||||
await waitFor(() => expect(hostApiFetchMock).toHaveBeenCalledWith('/api/opencode/projects/create', { method: 'POST', body: JSON.stringify({ projectPath: 'D:/repo/星星收集器', templateId: 'game-development' }) }));
|
||||
await waitFor(() => expect(hostApiFetchMock).toHaveBeenCalledWith('/api/opencode/projects/create', { method: 'POST', body: JSON.stringify({ projectPath: 'D:/repo/星星收集器' }) }));
|
||||
await waitFor(() => expect(navigateMock).toHaveBeenCalledWith('/project-config'));
|
||||
});
|
||||
|
||||
it('keeps creating a named child folder when that directory mode is selected', async () => {
|
||||
const project = { id: 'prj_child', path: 'D:/repo/星星收集器', name: '星星收集器', createdAt: '', updatedAt: '', lastOpenedAt: '' };
|
||||
const config = createProjectConfig('standard-development');
|
||||
const config = createProjectConfig();
|
||||
invokeIpcMock.mockResolvedValue({ canceled: false, filePaths: ['D:/repo'] });
|
||||
hostApiFetchMock.mockImplementation(async (path: string, init?: RequestInit) => {
|
||||
if (path === '/api/opencode/projects') return { projects: [], activeProject: null };
|
||||
@@ -193,7 +273,6 @@ describe('Sidebar project initialization flow', () => {
|
||||
fireEvent.change(screen.getByLabelText('项目名称'), { target: { value: '星星收集器' } });
|
||||
fireEvent.click(screen.getByRole('button', { name: '选择路径' }));
|
||||
await waitFor(() => expect(screen.getByLabelText('项目路径')).toHaveValue('D:/repo'));
|
||||
fireEvent.click(screen.getByTestId('project-template-option-standard-development'));
|
||||
fireEvent.click(screen.getByRole('button', { name: '确认创建' }));
|
||||
|
||||
await waitFor(() => expect(hostApiFetchMock).toHaveBeenCalledWith('/api/opencode/projects/create', {
|
||||
@@ -201,14 +280,13 @@ describe('Sidebar project initialization flow', () => {
|
||||
body: JSON.stringify({
|
||||
parentPath: 'D:/repo',
|
||||
projectName: '星星收集器',
|
||||
templateId: 'standard-development',
|
||||
}),
|
||||
}));
|
||||
});
|
||||
|
||||
it('opens initialized projects directly in Agent collaboration', async () => {
|
||||
const project = { id: 'prj_ready', path: 'D:/repo/ready', name: 'ready', createdAt: '', updatedAt: '', lastOpenedAt: '' };
|
||||
const config = { ...createProjectConfig('standard-development'), initialized: true };
|
||||
const config = { ...createProjectConfig(), initialized: true };
|
||||
hostApiFetchMock.mockImplementation(async (path: string) => {
|
||||
if (path === '/api/opencode/projects') return { projects: [project], activeProject: null };
|
||||
if (path === `/api/opencode/projects/config?projectId=${project.id}`) return { status: 'valid', config, knowledgeFiles: [] };
|
||||
@@ -222,7 +300,7 @@ describe('Sidebar project initialization flow', () => {
|
||||
|
||||
it('removes a project card when its local project configuration is missing', async () => {
|
||||
const project = { id: 'prj_missing', path: 'D:/repo/missing', name: 'missing', createdAt: '', updatedAt: '', lastOpenedAt: '' };
|
||||
const staleConfig = createProjectConfig('standard-development');
|
||||
const staleConfig = createProjectConfig();
|
||||
useProjectConfigStore.setState({ configsByProjectId: { [project.id]: staleConfig }, knowledgeByProjectId: {}, errorsByProjectId: {}, loadingProjectId: null });
|
||||
hostApiFetchMock.mockImplementation(async (path: string, init?: RequestInit) => {
|
||||
if (path === '/api/opencode/projects') return { projects: [project], activeProject: null };
|
||||
@@ -232,10 +310,13 @@ describe('Sidebar project initialization flow', () => {
|
||||
});
|
||||
|
||||
render(<MemoryRouter><Sidebar /></MemoryRouter>);
|
||||
fireEvent.click(await screen.findByRole('button', { name: `配置项目 ${project.name}` }));
|
||||
expect(await screen.findByTestId('project-template-error')).toBeInTheDocument();
|
||||
fireEvent.click(await screen.findByRole('button', { name: `进入项目 ${project.name}` }));
|
||||
expect(await screen.findByTestId('project-config-error')).toBeInTheDocument();
|
||||
fireEvent.click(screen.getByRole('button', { name: '移除项目卡片' }));
|
||||
expect(screen.getByRole('dialog', { name: `移除项目“${project.name}”?` })).toBeInTheDocument();
|
||||
const removeDialog = screen.getByRole('dialog', { name: `移除项目“${project.name}”?` });
|
||||
expect(removeDialog).toBeInTheDocument();
|
||||
expect(removeDialog).toHaveAttribute('aria-modal', 'true');
|
||||
expect(removeDialog.closest('[data-testid="sidebar"]')).toBeNull();
|
||||
fireEvent.click(screen.getByRole('button', { name: '确认移除' }));
|
||||
|
||||
await waitFor(() => expect(hostApiFetchMock).toHaveBeenCalledWith('/api/opencode/projects/remove', {
|
||||
@@ -246,15 +327,19 @@ describe('Sidebar project initialization flow', () => {
|
||||
expect(useProjectConfigStore.getState().configsByProjectId[project.id]).toBeUndefined();
|
||||
});
|
||||
|
||||
it('shows project identity with explicit enter and configuration actions', async () => {
|
||||
it('shows project identity with an explicit enter action', async () => {
|
||||
const project = { id: 'prj_config', path: 'D:/repo/config', name: 'config', createdAt: '', updatedAt: '', lastOpenedAt: '' };
|
||||
const config = {
|
||||
...createProjectConfig('standard-development'),
|
||||
...createProjectConfig(),
|
||||
initialized: true,
|
||||
agents: createProjectConfig('standard-development').agents.map((agent, index) => ({
|
||||
...agent,
|
||||
name: ['小策', '小设', '小码', '小发'][index],
|
||||
avatarId: ['avatar-16', 'avatar-15', 'avatar-14', 'avatar-13'][index],
|
||||
agents: ['小策', '小设', '小码', '小发'].map((name, index) => ({
|
||||
id: `manual-agent-${index}`,
|
||||
name,
|
||||
builtIn: false,
|
||||
model: null,
|
||||
skills: [],
|
||||
responsibility: '',
|
||||
avatarId: `avatar-${16 - index}`,
|
||||
})),
|
||||
};
|
||||
hostApiFetchMock.mockImplementation(async (path: string) => {
|
||||
@@ -265,8 +350,8 @@ describe('Sidebar project initialization flow', () => {
|
||||
});
|
||||
render(<MemoryRouter><Sidebar /></MemoryRouter>);
|
||||
const card = await screen.findByTestId(`sidebar-course-project-${project.id}`);
|
||||
await waitFor(() => expect(card).toHaveTextContent('普通开发项目'));
|
||||
expect(card).toHaveTextContent(project.name);
|
||||
expect(card).not.toHaveTextContent('普通开发项目');
|
||||
expect(card).not.toHaveTextContent(project.path);
|
||||
expect(card).not.toHaveTextContent('已初始化');
|
||||
expect(card).not.toHaveTextContent('最后活跃');
|
||||
@@ -275,8 +360,7 @@ describe('Sidebar project initialization flow', () => {
|
||||
expect(screen.getByTitle('小策').querySelector('img')).toHaveAttribute('src', getAgentAvatarSrc('avatar-16'));
|
||||
expect(screen.getByTitle('小设').querySelector('img')).toHaveAttribute('src', getAgentAvatarSrc('avatar-15'));
|
||||
expect(screen.getByRole('button', { name: `进入项目 ${project.name}` })).toHaveTextContent('进入项目');
|
||||
fireEvent.click(screen.getByRole('button', { name: `配置项目 ${project.name}` }));
|
||||
await waitFor(() => expect(navigateMock).toHaveBeenCalledWith('/project-config'));
|
||||
expect(screen.queryByRole('button', { name: `配置项目 ${project.name}` })).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('switches the sidebar to cloud projects and Agents inside the AI painting module', async () => {
|
||||
@@ -330,7 +414,9 @@ describe('Sidebar project initialization flow', () => {
|
||||
|
||||
expect(await screen.findByTestId('sidebar-image-project-cloud-project')).toHaveTextContent('云端概念设计');
|
||||
expect(screen.getByText('概念设计 Agent')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('sidebar-create-image-project')).toBeEnabled();
|
||||
const createImageProject = screen.getByTestId('sidebar-create-image-project');
|
||||
expect(createImageProject).toBeEnabled();
|
||||
expect(createImageProject).toHaveClass('bg-transparent', 'border-0', 'shadow-none');
|
||||
expect(screen.queryByTestId('sidebar-create-project')).not.toBeInTheDocument();
|
||||
expect(screen.queryByText('项目路径')).not.toBeInTheDocument();
|
||||
expect(screen.queryByText('项目模板')).not.toBeInTheDocument();
|
||||
|
||||
Reference in New Issue
Block a user