Files
makelore/tests/e2e/main-navigation.spec.ts
T

502 lines
30 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 { mkdir, writeFile } from 'node:fs/promises';
import { join } from 'node:path';
import { closeElectronApp, expect, getStableWindow, test } from './fixtures/electron';
import { billingSummary } from '../fixtures/permanent-points';
async function seedInactiveCodingProjects(resources: { rootDir: string; userDataDir: string }) {
const now = '2026-09-06T08:00:00.000Z';
const older = '2026-09-04T08:00:00.000Z';
const projects = [
{
id: 'project-moonlight',
name: '月光小游戏',
path: join(resources.rootDir, '月光小游戏'),
createdAt: older,
updatedAt: now,
lastOpenedAt: now,
projectId: '11111111-1111-4111-8111-111111111111',
},
{
id: 'project-storybook',
name: '故事绘本',
path: join(resources.rootDir, '故事绘本'),
createdAt: older,
updatedAt: older,
lastOpenedAt: older,
projectId: '22222222-2222-4222-8222-222222222222',
},
];
for (const project of projects) {
await mkdir(join(project.path, '.makelore'), { recursive: true });
await mkdir(join(project.path, 'knowledge'), { recursive: true });
await writeFile(join(project.path, '.makelore', 'project.json'), JSON.stringify({
schemaVersion: 2,
projectType: 'custom',
projectId: project.projectId,
initialized: true,
agents: [],
knowledgeDirectory: 'knowledge',
createdAt: project.createdAt,
updatedAt: project.updatedAt,
}));
}
await writeFile(join(resources.userDataDir, 'makelore-projects.json'), JSON.stringify({
projects: {
projects: Object.fromEntries(projects.map(({ projectId: _projectId, ...project }) => [project.id, project])),
activeProjectId: null,
},
}));
}
test.describe('Makelore module navigation without setup flow', () => {
test('keeps the home account wallet, profile, settings and logout available without creating a payment', async ({ launchElectronApp }, testInfo) => {
const app = await launchElectronApp({ skipSetup: true });
try {
const page = await getStableWindow(app);
await expect(page.getByTestId('ai-module-selection-page')).toBeVisible();
const applicationUrl = page.url();
await page.goto('about:blank');
await app.evaluate(({ ipcMain, BrowserWindow }, summary) => {
BrowserWindow.getAllWindows()[0].setSize(1440, 900);
const state = { loggedIn: true, requests: [] as { method: string; path: string }[] };
Object.assign(globalThis, { homeAccountFixture: state });
const result = (json: unknown) => ({ ok: true, data: { status: 200, ok: true, json } });
ipcMain.removeHandler('hostapi:fetch');
ipcMain.handle('hostapi:fetch', async (_event, request: { path?: string; method?: string }) => {
const path = request.path ?? '';
const method = (request.method ?? 'GET').toUpperCase();
state.requests.push({ method, path });
if (path === '/api/auth/session/sync') return result({ success: true, session: state.loggedIn ? { accessToken: 'home-account-fixture', tokenType: 'Bearer', expiresAt: Date.now() + 3600000, lastActiveAt: Date.now(), canRefresh: false } : null });
if (path === '/api/auth/me') return result({ success: true, user: state.loggedIn ? { username: 'child-wallet', userId: 'home-user', tenantId: null, deptId: null, authorities: [] } : null, moduleAccess: { programming: true, design: true, robot: true, cloud_agents: true } });
if (path === '/api/auth/logout' && method === 'POST') { state.loggedIn = false; return result({ success: true }); }
if (path === '/api/works/user/agent-profile' && method === 'GET') return result({ success: true, profile: { display_name: '小麦', age: null, gender: null, avatar_url: null, share_age_with_agents: false, share_gender_with_agents: false, analysis_enabled: true, completed: true, version: 1, updated_at: '2026-10-13T00:00:00Z' } });
if (path === '/api/works/billing/me' && method === 'GET') return result({ success: true, data: summary });
if (path.startsWith('/api/works/billing/recharge/orders?') && method === 'GET') return result({ success: true, data: { items: [] } });
if (path.startsWith('/api/works/billing/') || path === '/api/works/user/agent-profile') throw new Error('Unexpected home account write ' + method + ' ' + path);
return result({ success: true });
});
}, billingSummary());
// The setup-skipping fixture starts as a guest. Hydrate only this isolated
// Renderer with a fake persisted session before the auth store is created.
await page.addInitScript(() => {
const now = Date.now();
localStorage.setItem('niancode-auth', JSON.stringify({ version: 4, state: {
authBase: 'https://biz.nianxx.cn/auth/', clientId: 'app',
accessToken: 'home-account-fixture', tokenType: 'Bearer',
expiresAt: now + 3600000, lastActiveAt: now, canRefresh: false, legacyRefreshToken: null,
user: { username: 'child-wallet', userId: 'home-user', tenantId: null, deptId: null, authorities: [] },
moduleAccess: { programming: true, design: true, robot: true, cloud_agents: true },
} }));
// Setup-skipping mode also suppresses the home profile sync. Restore the
// isolated account's cached profile in the store's persisted shape.
localStorage.setItem('niancode-user-agent-profiles', JSON.stringify({ version: 1, state: {
profilesByUserId: { 'home-user': { displayName: '小麦', age: null, gender: 'undisclosed', avatarUrl: null,
shareAgeWithAgents: false, shareGenderWithAgents: false, analysisEnabled: true,
pendingSync: false, version: 1, updatedAt: '2026-10-13T00:00:00Z' } },
} }));
});
await page.goto(applicationUrl);
const trigger = page.getByTestId('home-account-menu-trigger');
const menu = page.getByTestId('home-account-menu');
await expect(trigger).toContainText('小麦');
await expect(page.locator('[data-testid^="ai-module-option-"]')).toHaveCount(3);
const footer = await trigger.evaluate(element => element.closest('footer')!.getBoundingClientRect().top);
const cards = await page.getByTestId('ai-module-options').boundingBox();
expect(footer).toBeGreaterThanOrEqual(cards!.y + cards!.height);
await trigger.click();
await expect(menu.getByTestId('sidebar-total-token-points')).toHaveText('75 点');
await expect(menu.getByText('永久有效 · 预占 5 点', { exact: true })).toBeVisible();
await expect(menu.getByRole('menuitem', { name: '词元点数与充值', exact: true })).toBeVisible();
await page.screenshot({ path: testInfo.outputPath('home-account-menu-desktop.png') });
await page.keyboard.press('Escape');
await expect(trigger).toHaveAttribute('aria-expanded', 'false');
await expect(trigger).toBeFocused();
await trigger.click();
await page.getByRole('heading', { name: '今天,想创作什么?', exact: true }).click();
await expect(trigger).toHaveAttribute('aria-expanded', 'false');
await trigger.click();
await menu.getByRole('menuitem', { name: '词元点数与充值', exact: true }).click();
const wallet = page.getByTestId('point-wallet-dialog');
await expect(wallet).toBeVisible();
await expect(wallet.getByTestId('point-recharge-recipient')).toHaveText('充值到:小麦(child-wallet)');
await expect(wallet.getByRole('button', { name: /500 点\s*¥10.00/ })).toBeEnabled();
await wallet.getByText('我的可用点数', { exact: true }).click();
await expect(trigger).toHaveAttribute('aria-expanded', 'false');
await expect(wallet).toBeVisible();
await wallet.getByRole('button', { name: '关闭词元点数', exact: true }).click();
await expect(wallet).toHaveCount(0);
await trigger.click();
await menu.getByRole('menuitem', { name: '个人资料', exact: true }).click();
const profile = page.getByRole('dialog', { name: '个人资料', exact: true });
await expect(profile.getByLabel(/名字/)).toHaveValue('小麦');
await profile.getByRole('button', { name: '关闭个人资料', exact: true }).click();
await trigger.click();
await menu.getByRole('menuitem', { name: '设置', exact: true }).click();
await expect(page).toHaveURL(/\/settings$/);
await page.evaluate(() => { window.location.hash = '#/module-select'; });
await expect(trigger).toContainText('小麦');
await app.evaluate(({ BrowserWindow }) => { const window = BrowserWindow.getAllWindows()[0]; window.setMinimumSize(360, 600); window.setSize(375, 800); });
await expect.poll(() => page.evaluate(() => innerWidth)).toBe(375);
await trigger.click();
await expect(menu.getByTestId('sidebar-total-token-points')).toHaveText('75 点');
await expect.poll(() => page.evaluate(() => document.documentElement.scrollWidth - innerWidth)).toBeLessThanOrEqual(1);
const menuBox = await menu.boundingBox();
expect(menuBox!.x).toBeGreaterThanOrEqual(0);
expect(menuBox!.x + menuBox!.width).toBeLessThanOrEqual(375);
await page.screenshot({ path: testInfo.outputPath('home-account-menu-375.png') });
await menu.getByRole('menuitem', { name: '退出登录', exact: true }).click();
await expect(page).toHaveURL(/\/login$/);
await expect(trigger).toHaveCount(0);
const requests = await app.evaluate(() => (globalThis as typeof globalThis & { homeAccountFixture: { requests: { method: string; path: string }[] } }).homeAccountFixture.requests);
const billingReads = requests.filter(request => request.path.startsWith('/api/works/billing/'));
expect(billingReads.length).toBeGreaterThan(0);
expect(billingReads.every(request => request.method === 'GET')).toBe(true);
expect(requests.filter(request => request.path === '/api/works/user/agent-profile').every(request => request.method === 'GET')).toBe(true);
expect(requests.filter(request => request.path === '/api/auth/logout' && request.method === 'POST')).toHaveLength(1);
} finally {
await app.evaluate(({ BrowserWindow }) => { for (const window of BrowserWindow.getAllWindows()) window.destroy(); }).catch(() => undefined);
await closeElectronApp(app);
}
});
test('opens remembered Code projects and keeps the module return entry in chat instead of project settings', async ({
electronTestResources,
launchElectronApp,
}) => {
await seedInactiveCodingProjects(electronTestResources);
const app = await launchElectronApp({ skipSetup: true });
try {
const page = await getStableWindow(app);
await expect(page.getByTestId('ai-module-selection-page')).toBeVisible();
await page.getByTestId('ai-module-option-programming').click();
await expect(page).toHaveURL(/\/chat$/);
await expect(page.getByRole('button', { name: '新增项目' })).toBeVisible();
await expect(page.getByText('已有项目')).toHaveCount(0);
await expect(page.getByText('选择一个项目继续')).toHaveCount(0);
const projectCards = page.getByTestId('coding-existing-projects');
await expect(projectCards).toBeVisible();
await expect(projectCards.getByRole('button')).toHaveCount(2);
await expect(projectCards.getByRole('button').nth(0)).toContainText('月光小游戏');
await expect(projectCards.getByRole('button').nth(1)).toContainText('故事绘本');
await projectCards.getByRole('button', { name: '进入项目 故事绘本' }).click();
await expect(page.getByTestId('coding-chat-panel')).toBeVisible();
await expect(page.getByTestId('coding-existing-projects')).toHaveCount(0);
await expect(page.getByTestId('sidebar-course-project-project-storybook')).toHaveAttribute('aria-current', 'page');
const sidebar = page.getByTestId('sidebar');
const moduleReturn = page.getByTestId('sidebar-module-switcher-trigger');
await expect(moduleReturn).toBeVisible();
await expect(moduleReturn).toHaveText('');
await expect(moduleReturn).toHaveAttribute('aria-label', '返回首页');
await expect(moduleReturn).toHaveAttribute('title', '返回首页');
await expect(page.getByTestId('sidebar-module-return-icon')).toBeVisible();
const brandBox = await sidebar.locator('.classroom-brand').boundingBox();
const returnBox = await moduleReturn.boundingBox();
const collapseBox = await page.getByRole('button', { name: '折叠侧栏' }).boundingBox();
const sidebarBox = await sidebar.boundingBox();
expect(brandBox).not.toBeNull();
expect(returnBox).not.toBeNull();
expect(collapseBox).not.toBeNull();
expect(returnBox!.y + returnBox!.height).toBeLessThanOrEqual(brandBox!.y);
const headerBox = await page.getByTestId('coding-conversation-header').boundingBox();
const tabsBox = await page.getByRole('tablist', { name: '当前工作' }).boundingBox();
expect(Math.abs(returnBox!.y + returnBox!.height / 2 - headerBox!.y - headerBox!.height / 2)).toBeLessThan(1);
expect(Math.abs(brandBox!.y + brandBox!.height / 2 - tabsBox!.y - tabsBox!.height / 2)).toBeLessThan(1);
expect(brandBox!.y - sidebarBox!.y).toBe(0);
expect(brandBox!.height).toBe(tabsBox!.height);
expect(returnBox!.width).toBe(32);
expect(Math.abs(returnBox!.y + returnBox!.height / 2 - collapseBox!.y - collapseBox!.height / 2)).toBeLessThan(1);
expect(returnBox!.x + returnBox!.width).toBeLessThanOrEqual(collapseBox!.x);
expect(sidebarBox!.x + sidebarBox!.width - collapseBox!.x - collapseBox!.width).toBeLessThanOrEqual(14);
await expect(page.getByTestId('titlebar-sidebar-toggle')).toHaveCount(1);
const community = sidebar.getByRole('button', { name: '作品广场', exact: true });
await expect(community).toHaveCount(1);
const communityBox = await community.boundingBox();
const createBox = await sidebar.getByTestId('sidebar-create-project').boundingBox();
const headingBox = await sidebar.locator('.classroom-projects-heading').boundingBox();
expect(communityBox!.y).toBeGreaterThanOrEqual(brandBox!.y + brandBox!.height);
expect(communityBox!.y - brandBox!.y - brandBox!.height).toBe(10);
expect(createBox!.y - communityBox!.y - communityBox!.height).toBe(10);
expect(createBox!.y - brandBox!.y - brandBox!.height).toBe(61);
expect(communityBox!.height).toBe(createBox!.height);
await expect(community).toHaveCSS('background-color', 'rgb(248, 250, 245)');
const buttonStyles = await page.evaluate(() => {
const styles = (selector: string) => {
const s = getComputedStyle(document.querySelector(selector)!);
return [s.borderRadius, s.borderWidth, s.borderColor, s.boxShadow, s.padding, s.fontSize, s.fontWeight];
};
return { community: styles('[data-testid=sidebar] .community-link'), create: styles('[data-testid=sidebar-create-project]') };
});
expect(buttonStyles.community).toEqual(buttonStyles.create);
expect(headingBox!.y).toBeGreaterThanOrEqual(createBox!.y + createBox!.height);
expect(headingBox!.y - createBox!.y - createBox!.height).toBeLessThanOrEqual(14);
await page.screenshot({ path: test.info().outputPath('chat-code-return-entry.png') });
// Exercise native state events without switching the test app into a macOS Space.
await app.evaluate(({ BrowserWindow }) => BrowserWindow.getAllWindows()[0].emit('enter-full-screen'));
await expect.poll(async () => (await moduleReturn.boundingBox())!.x).toBe(createBox!.x);
const fullscreenReturn = await moduleReturn.boundingBox();
expect(fullscreenReturn!.y).toBe(returnBox!.y);
expect(await page.getByRole('button', { name: '折叠侧栏' }).boundingBox()).toEqual(collapseBox);
expect(await sidebar.locator('.classroom-brand').boundingBox()).toEqual(brandBox);
await page.screenshot({ path: test.info().outputPath('chat-fullscreen-return-aligned.png') });
await app.evaluate(({ BrowserWindow }) => BrowserWindow.getAllWindows()[0].emit('leave-full-screen'));
await expect.poll(async () => (await moduleReturn.boundingBox())!.x).toBe(returnBox!.x);
const sidebarResizer = sidebar.getByRole('separator', { name: '调整左侧功能栏宽度' });
await sidebarResizer.press('Home');
await expect(sidebarResizer).toHaveAttribute('aria-valuenow', '128');
const narrowReturn = await moduleReturn.boundingBox();
const narrowToggle = await page.getByRole('button', { name: '折叠侧栏' }).boundingBox();
expect(narrowReturn!.x + narrowReturn!.width).toBeLessThanOrEqual(narrowToggle!.x);
await expect(moduleReturn).toBeVisible();
await expect(page.getByRole('button', { name: '折叠侧栏' })).toBeVisible();
const narrowHeader = await page.getByTestId('coding-conversation-header').boundingBox();
expect(narrowToggle!.x + narrowToggle!.width).toBeLessThanOrEqual(narrowHeader!.x);
expect(await moduleReturn.evaluate(el => {
const b = el.getBoundingClientRect();
return el.contains(document.elementFromPoint(b.x + b.width / 2, b.y + b.height / 2));
})).toBe(true);
await expect(community).toBeVisible();
expect(await community.evaluate(element => element.scrollWidth <= element.clientWidth)).toBe(true);
await page.screenshot({ path: test.info().outputPath('chat-narrow-navigation-row.png') });
await sidebarResizer.dblclick();
await expect(sidebarResizer).toHaveAttribute('aria-valuenow', '171');
await sidebar.getByRole('button', { name: '项目设置', exact: true }).click();
await expect(page).toHaveURL(/\/project-config$/);
await expect(page.getByTestId('project-configuration-page')).toBeVisible();
await expect(sidebar.getByTestId('sidebar-module-switcher')).toHaveCount(0);
const projectBack = page.getByTestId('project-configuration-back-button');
await expect(projectBack).toBeVisible();
await page.screenshot({ path: test.info().outputPath('project-config-without-module-return.png') });
await projectBack.click();
await expect(page).toHaveURL(/\/chat$/);
await expect(moduleReturn).toBeVisible();
await sidebar.getByRole('button', { name: '项目设置', exact: true }).click();
await page.getByTestId('resource-card-plugins').click();
await expect(page).toHaveURL(/\/project-config\/plugins\?/);
const plugins = page.getByTestId('project-plugins-sheet');
await expect(plugins).toBeVisible();
await expect(sidebar.getByTestId('sidebar-module-switcher')).toHaveCount(0);
await page.screenshot({ path: test.info().outputPath('project-config-plugins-without-module-return.png') });
await plugins.getByRole('button', { name: '关闭', exact: true }).click();
await expect(page).toHaveURL(/\/project-config$/);
await expect(projectBack).toBeVisible();
await sidebar.getByTestId('sidebar-course-project-project-storybook').click();
await expect(page).toHaveURL(/\/chat$/);
const sidebarToggle = page.getByTestId('titlebar-sidebar-toggle');
await sidebarToggle.click();
await page.getByTestId('coding-chat-panel').click({ position: { x: 500, y: 200 } });
await expect(sidebar).toHaveAttribute('aria-hidden', 'true');
const collapsedToggleBox = await sidebarToggle.boundingBox();
await expect(moduleReturn).toBeVisible();
await sidebarToggle.hover();
await expect(sidebar).toHaveAttribute('aria-hidden', 'false');
expect(await sidebarToggle.boundingBox()).toEqual(collapsedToggleBox);
await expect(moduleReturn).toBeVisible();
await page.screenshot({ path: test.info().outputPath('chat-collapsed-hover-code-return.png') });
await moduleReturn.click();
await expect(page).toHaveURL(/\/module-select$/);
await expect(page.getByTestId('ai-module-selection-page')).toBeVisible();
} finally {
await closeElectronApp(app);
}
});
test('aligns the desktop home title with the middle of three stacked horizontal module cards', async ({ launchElectronApp }) => {
const app = await launchElectronApp({ skipSetup: true });
try {
const page = await getStableWindow(app);
await expect(page.getByTestId('ai-module-selection-page')).toBeVisible();
const moduleCards = page.locator('[data-testid^="ai-module-option-"]');
await expect(moduleCards.locator('.module-option-card-title')).toHaveText([
'Agents 智能体', 'Code 编程', 'Canvas 设计',
]);
await expect(page.getByTestId('ai-module-option-robot')).toHaveCount(0);
await expect(moduleCards.first().locator('.module-option-card-description')).toHaveText('打造你想象中的AI助手');
await expect(moduleCards.locator('img')).toHaveCount(3);
await expect.poll(async () => await moduleCards.locator('img').evaluateAll((images) =>
images.every((image) => image.complete && image.naturalWidth > 0),
)).toBe(true);
const readCardMetrics = async () => await moduleCards.evaluateAll((elements) => elements.map((element) => {
const bounds = element.getBoundingClientRect();
const image = element.querySelector('.module-option-card-image')?.getBoundingClientRect();
const content = element.querySelector('.module-option-card-content')?.getBoundingClientRect();
return {
left: bounds.left,
top: bounds.top,
width: bounds.width,
height: bounds.height,
imageWidth: image?.width ?? 0,
imageHeight: image?.height ?? 0,
imageLeft: image?.left ?? 0,
contentLeft: content?.left ?? 0,
};
}));
await expect(moduleCards.first().locator('.module-option-card-arrow')).toHaveCount(0);
await expect(moduleCards.first().locator('.module-option-artwork')).toHaveCount(0);
await app.evaluate(({ BrowserWindow }) => {
BrowserWindow.getAllWindows()[0]?.setMinimumSize(360, 600);
});
for (const width of [1440, 1024, 768, 375]) {
await app.evaluate(({ BrowserWindow }, windowWidth) => BrowserWindow.getAllWindows()[0]?.setSize(windowWidth, windowWidth === 375 ? 800 : 900), width);
await expect.poll(() => page.evaluate(() => innerWidth)).toBe(width);
const cards = await readCardMetrics();
expect(cards).toHaveLength(3);
expect(Math.max(...cards.map(card => card.left)) - Math.min(...cards.map(card => card.left))).toBeLessThan(1);
expect(Math.max(...cards.map(card => card.width)) - Math.min(...cards.map(card => card.width))).toBeLessThan(1);
expect(Math.max(...cards.map(card => card.height)) - Math.min(...cards.map(card => card.height))).toBeLessThan(1);
const gaps = cards.slice(1).map((card, index) => card.top - cards[index]!.top - cards[index]!.height);
expect(Math.min(...gaps)).toBeGreaterThan(0);
expect(Math.abs(gaps[0]! - gaps[1]!)).toBeLessThan(1);
for (const card of cards) {
expect(card.left).toBeGreaterThanOrEqual(0);
expect(card.left + card.width).toBeLessThanOrEqual(width);
expect(card.imageWidth).toBeLessThan(card.width / 2);
expect(card.imageHeight).toBeGreaterThan(card.height * 0.75);
expect(card.contentLeft).toBeGreaterThanOrEqual(card.imageLeft + card.imageWidth - 1);
}
const heading = await page.getByRole('heading', { name: '今天,想创作什么?', exact: true }).boundingBox();
expect(heading).not.toBeNull();
if (width >= 1024) {
expect(heading!.x + heading!.width).toBeLessThanOrEqual(cards[0]!.left);
expect(Math.abs(heading!.y + heading!.height / 2 - cards[1]!.top - cards[1]!.height / 2)).toBeLessThan(1);
expect(await moduleCards.evaluateAll(elements => elements.every(element => {
const bounds = element.getBoundingClientRect();
return [[2, 2], [bounds.width - 2, bounds.height - 2], [bounds.width / 2, bounds.height / 2]].every(([x, y]) =>
element.contains(document.elementFromPoint(bounds.left + x!, bounds.top + y!)),
);
}))).toBe(true);
} else {
expect(heading!.y + heading!.height).toBeLessThanOrEqual(cards[0]!.top);
}
expect(await page.evaluate(() => document.documentElement.scrollWidth - innerWidth)).toBeLessThanOrEqual(1);
expect(await moduleCards.locator('.module-option-card-title, .module-option-card-description').evaluateAll(elements =>
elements.every(element => element.scrollWidth <= element.clientWidth),
)).toBe(true);
await page.screenshot({ path: test.info().outputPath(`module-entry-${width}.png`) });
}
await moduleCards.first().hover();
await moduleCards.first().focus();
await expect.poll(async () => await moduleCards.first().evaluate((element) => {
const surface = element.querySelector<HTMLElement>('.module-option-card-surface');
const image = element.querySelector<HTMLElement>('.module-option-card-image');
const imageMedia = element.querySelector<HTMLElement>('.module-option-card-image-media');
const content = element.querySelector<HTMLElement>('.module-option-card-content');
const title = element.querySelector<HTMLElement>('.module-option-card-title');
return Boolean(surface)
&& Boolean(image)
&& Boolean(imageMedia)
&& Boolean(content)
&& Boolean(title)
&& getComputedStyle(surface).transform === 'none'
&& getComputedStyle(image).transform === 'none'
&& getComputedStyle(imageMedia).transform === 'none'
&& getComputedStyle(content).transform === 'none'
&& getComputedStyle(title).transform === 'none';
})).toBe(true);
} finally {
await closeElectronApp(app);
}
});
test('omits Learning from the module chooser and redirects its retired route', async ({ launchElectronApp }) => {
const app = await launchElectronApp({ skipSetup: true });
try {
const page = await getStableWindow(app);
await expect(page.getByTestId('ai-module-selection-page')).toBeVisible();
await expect(page.locator('[data-testid^="ai-module-option-"]')).toHaveCount(3);
await expect(page.getByTestId('ai-module-option-learning')).toHaveCount(0);
await page.evaluate(() => {
window.location.hash = '#/learning';
});
await expect(page).toHaveURL(/\/module-select$/);
await expect(page.getByTestId('ai-module-selection-page')).toBeVisible();
} finally {
await closeElectronApp(app);
}
});
test('returns to the module chooser from the sidebar entry', async ({ launchElectronApp }) => {
const app = await launchElectronApp({ skipSetup: true });
try {
const page = await getStableWindow(app);
const minimumSize = await app.evaluate(({ BrowserWindow }) => BrowserWindow.getAllWindows()[0]?.getMinimumSize());
expect(minimumSize).toEqual([1024, 700]);
await expect(page.getByTestId('ai-module-selection-page')).toBeVisible();
await page.getByTestId('ai-module-option-programming').click();
await expect(page.getByTestId('main-layout')).toBeVisible();
await expect(page).toHaveURL(/\/chat$/);
await expect(page.getByText('请先新建项目')).toHaveCount(0);
await expect(page.getByRole('heading', { name: '你想让麦洛和你一起构建什么?' })).toBeVisible();
await expect(page.getByRole('img', { name: '麦洛 M 标识' })).toBeVisible();
await expect(page.getByRole('button', { name: '新增项目' })).toBeVisible();
await expect(page.getByTestId('coding-project-starter')).toHaveCount(0);
await expect(page.getByTestId('coding-existing-projects')).toHaveCount(0);
await expect(page.getByText('一念成光,万物可创。')).toHaveCount(0);
await expect(page.getByRole('img', { name: 'Makelore logo' })).toHaveCount(0);
await expect(page.getByTestId('sidebar').locator('.classroom-brand')).toBeVisible();
await expect(page.getByTestId('sidebar-module-switcher-trigger')).toHaveText('');
await expect(page.getByTestId('sidebar-module-return-icon')).toBeVisible();
await expect(page.getByTestId('sidebar-create-project')).toBeVisible();
await expect(page.getByTestId('sidebar-nav-publish')).toHaveCount(0);
await expect(page.getByTestId('sidebar-nav-project-config')).toHaveCount(0);
await expect(page.getByTestId('sidebar-nav-agent-chat')).toHaveCount(0);
await expect(page.getByRole('button', { name: '添加已有项目' })).toHaveCount(0);
await expect(page.getByText('成长成就在这里')).toHaveCount(0);
await expect(page.getByText('已创建 Phaser 2D 小游戏起步工程')).toHaveCount(0);
await expect(page.getByTestId('game-asset-browser')).toHaveCount(0);
await expect(page.getByTestId('game-asset-review-summary')).toHaveCount(0);
await page.getByTestId('coding-start-project-button').click();
const createProjectDialog = page.getByRole('dialog', { name: '新建项目' });
await expect(createProjectDialog).toBeVisible();
await createProjectDialog.getByRole('button', { name: '取消' }).first().click();
await expect(page.getByRole('dialog', { name: '新建项目' })).toHaveCount(0);
await expect(page.getByTestId('sidebar-module-switcher')).toBeVisible();
const moduleHomeTrigger = page.getByTestId('sidebar-module-switcher-trigger');
await expect(moduleHomeTrigger).toHaveAttribute('aria-label', '返回首页');
await expect(page.getByTestId('classroom-titlebar-navigation')).toBeVisible();
await expect(moduleHomeTrigger).toHaveCSS('width', '32px');
await expect(moduleHomeTrigger).toHaveCSS('height', '32px');
await expect(page.getByTestId('sidebar-module-switcher-menu')).toHaveCount(0);
await moduleHomeTrigger.click();
await expect(page).toHaveURL(/\/module-select$/);
await expect(page.getByTestId('ai-module-selection-page')).toBeVisible();
await expect(page.getByTestId('ai-module-option-robot')).toHaveCount(0);
await page.evaluate(() => { window.location.hash = '#/ai-hardware'; });
await expect(page).toHaveURL(/\/ai-hardware$/);
await expect(page.getByTestId('ai-hardware-page')).toBeVisible();
await expect(page.getByTestId('sidebar-robot-navigation')).toBeVisible();
await expect(page.getByTestId('sidebar-module-switcher-trigger')).toContainText('Robot 机器');
await page.getByTestId('sidebar-module-switcher-trigger').click();
await expect(page).toHaveURL(/\/module-select$/);
await expect(page.getByTestId('ai-module-selection-page')).toBeVisible();
} finally {
await closeElectronApp(app);
}
});
});