796 lines
51 KiB
TypeScript
796 lines
51 KiB
TypeScript
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';
|
||
import { createCodingProjectConfigV2 } from '../../electron/coding-projects/project-config';
|
||
|
||
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({
|
||
...createCodingProjectConfigV2(project.createdAt, 'custom', project.projectId),
|
||
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('opens each home module with the sidebar expanded after a saved collapse', async ({ launchElectronApp }) => {
|
||
const app = await launchElectronApp({ skipSetup: true });
|
||
try {
|
||
const page = await getStableWindow(app);
|
||
await expect(page.getByTestId('ai-module-selection-page')).toBeVisible();
|
||
await app.evaluate(({ ipcMain, BrowserWindow }, summary) => {
|
||
const window = BrowserWindow.getAllWindows()[0];
|
||
window.setSize(1440, 900);
|
||
const session = () => ({ accessToken: 'home-sidebar-fixture', tokenType: 'Bearer', expiresAt: Date.now() + 3600000, lastActiveAt: Date.now(), canRefresh: false });
|
||
const send = window.webContents.send.bind(window.webContents);
|
||
window.webContents.send = (channel: string, ...args: unknown[]) => send(channel, ...(channel === 'auth:session-changed' ? [session()] : args));
|
||
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 }) => {
|
||
const path = request.path ?? '';
|
||
if (path === '/api/auth/session/sync') return result({ success: true, session: session() });
|
||
if (path === '/api/auth/me') return result({ success: true, user: { username: 'sidebar-child', userId: 'sidebar-user', tenantId: null, deptId: null, authorities: [] }, moduleAccess: { programming: true, design: true, robot: true, cloud_agents: true } });
|
||
if (['/api/provider-accounts', '/api/provider-accounts/key-info', '/api/provider-vendors', '/api/providers'].includes(path)) return result([]);
|
||
if (path === '/api/provider-accounts/default') return result({ accountId: null });
|
||
if (path === '/api/coding/projects') return result({ projects: [], activeProjectId: null });
|
||
if (path === '/api/settings') return result({ classroomSidebarWidth: 171, sidebarCollapsed: true, setupComplete: true });
|
||
if (path === '/api/user-sync/bootstrap') return result({ success: true, bootstrap: { preferences: {}, providerAccounts: [], partners: [], usageAggregates: [] } });
|
||
if (path === '/api/works/user/agent-profile') 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-14T00:00:00Z' } });
|
||
if (path === '/api/works/billing/me') return result({ success: true, data: summary });
|
||
if (path === '/api/works/image-workspace') return result({ success: true, data: {
|
||
capabilities: { conversation: true, generation: false, image: false, video: false, generationOptions: {
|
||
image: { available: false, supportedAspectRatios: [], supportedDurationSeconds: [], maxOutputCount: 1, requiresFirstFrame: false, supportedReferenceRoles: [] },
|
||
video: { available: false, supportedAspectRatios: [], supportedDurationSeconds: [], maxOutputCount: 1, requiresFirstFrame: true, supportedReferenceRoles: [] },
|
||
} }, workspaces: [],
|
||
} });
|
||
if (path.startsWith('/api/cloud-agents/')) {
|
||
if (path.endsWith('/recovery')) return result({ recent: null, pending: [] });
|
||
if (path.endsWith('/agents') || path.endsWith('/bootstrap')) return result({ agents: [], next_cursor: null });
|
||
}
|
||
return result({ success: true });
|
||
});
|
||
}, billingSummary());
|
||
await page.addInitScript(() => {
|
||
const now = Date.now();
|
||
localStorage.setItem('niancode-settings', JSON.stringify({ version: 0, state: { classroomSidebarWidth: 171, sidebarCollapsed: true } }));
|
||
localStorage.setItem('niancode-auth', JSON.stringify({ version: 4, state: {
|
||
authBase: 'https://biz.nianxx.cn/auth/', clientId: 'app', accessToken: 'home-sidebar-fixture', tokenType: 'Bearer',
|
||
expiresAt: now + 3600000, lastActiveAt: now, canRefresh: false, legacyRefreshToken: null,
|
||
user: { username: 'sidebar-child', userId: 'sidebar-user', tenantId: null, deptId: null, authorities: [] },
|
||
moduleAccess: { programming: true, design: true, robot: true, cloud_agents: true },
|
||
} }));
|
||
localStorage.setItem('niancode-user-agent-profiles', JSON.stringify({ version: 1, state: {
|
||
profilesByUserId: { 'sidebar-user': { displayName: '小麦', age: null, gender: 'undisclosed', avatarUrl: null,
|
||
shareAgeWithAgents: false, shareGenderWithAgents: false, analysisEnabled: true,
|
||
pendingSync: false, version: 1, updatedAt: '2026-10-14T00:00:00Z' } },
|
||
} }));
|
||
});
|
||
await page.reload({ waitUntil: 'domcontentloaded' });
|
||
const savedCollapsed = () => page.evaluate(() => JSON.parse(localStorage.getItem('niancode-settings')!).state.sidebarCollapsed as boolean);
|
||
const checkExpanded = async () => {
|
||
await expect(page.getByTestId('sidebar')).toBeVisible();
|
||
await expect(page.getByTestId('sidebar')).toHaveAttribute('aria-hidden', 'false');
|
||
await expect.poll(async () => (await page.getByTestId('sidebar').boundingBox())?.width ?? 0).toBeGreaterThan(150);
|
||
await expect.poll(savedCollapsed).toBe(false);
|
||
};
|
||
const collapseAndReturnHome = async () => {
|
||
await page.getByTestId('titlebar-sidebar-toggle').click();
|
||
await page.mouse.click(1000, 500);
|
||
await expect.poll(savedCollapsed).toBe(true);
|
||
await expect(page.getByTestId('sidebar')).toHaveAttribute('aria-hidden', 'true');
|
||
await page.getByTestId('sidebar-module-switcher-trigger').click();
|
||
await expect(page.getByTestId('ai-module-selection-page')).toBeVisible();
|
||
await expect.poll(savedCollapsed).toBe(true);
|
||
};
|
||
for (const [moduleId, route] of [['programming', /\/chat$/], ['cloud_agents', /\/cloud-agents\?view=mine$/]] as const) {
|
||
await expect.poll(savedCollapsed).toBe(true);
|
||
await page.getByTestId(`ai-module-option-${moduleId}`).click();
|
||
await expect(page).toHaveURL(route);
|
||
await checkExpanded();
|
||
await collapseAndReturnHome();
|
||
await page.getByTestId(`ai-module-option-${moduleId}`).click();
|
||
await expect(page).toHaveURL(route);
|
||
await checkExpanded();
|
||
await collapseAndReturnHome();
|
||
}
|
||
await page.getByTestId('ai-module-option-painting').click();
|
||
await expect(page).toHaveURL(/\/image-canvas$/);
|
||
await checkExpanded();
|
||
await expect(page.getByTestId('titlebar-sidebar-toggle')).toHaveCount(0);
|
||
} finally {
|
||
await app.evaluate(({ BrowserWindow }) => { for (const window of BrowserWindow.getAllWindows()) window.destroy(); }).catch(() => undefined);
|
||
await closeElectronApp(app);
|
||
}
|
||
});
|
||
|
||
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();
|
||
await app.evaluate(({ ipcMain, BrowserWindow }, summary) => {
|
||
const window = BrowserWindow.getAllWindows()[0];
|
||
window.setSize(1440, 900);
|
||
const state = { loggedIn: true, requests: [] as { method: string; path: string }[] };
|
||
Object.assign(globalThis, { homeAccountFixture: state });
|
||
const session = () => state.loggedIn ? { accessToken: 'home-account-fixture', tokenType: 'Bearer', expiresAt: Date.now() + 3600000, lastActiveAt: Date.now(), canRefresh: false } : null;
|
||
// Main pushes its session after each Renderer load. Own that event boundary
|
||
// together with sync/me so the isolated fixture cannot receive a guest push.
|
||
const send = window.webContents.send.bind(window.webContents);
|
||
window.webContents.send = (channel: string, ...args: unknown[]) => send(channel, ...(channel === 'auth:session-changed' ? [session()] : args));
|
||
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: session() });
|
||
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 (method === 'GET') {
|
||
if (['/api/provider-accounts', '/api/provider-accounts/key-info', '/api/provider-vendors', '/api/providers'].includes(path)) return result([]);
|
||
if (path === '/api/provider-accounts/default') return result({ accountId: null });
|
||
if (path === '/api/coding/projects') return result({ projects: [], activeProjectId: null });
|
||
if (path === '/api/settings') return result({ classroomSidebarWidth: 171, sidebarCollapsed: false, setupComplete: true });
|
||
if (path === '/api/user-sync/bootstrap') return result({ success: true, bootstrap: { preferences: {}, providerAccounts: [], partners: [], usageAggregates: [] } });
|
||
}
|
||
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 === '/api/works/image-workspace' && method === 'GET') return result({ success: true, data: {
|
||
capabilities: { conversation: true, generation: false, image: false, video: false, generationOptions: {
|
||
image: { available: false, supportedAspectRatios: [], supportedDurationSeconds: [], maxOutputCount: 1, requiresFirstFrame: false, supportedReferenceRoles: [] },
|
||
video: { available: false, supportedAspectRatios: [], supportedDurationSeconds: [], maxOutputCount: 1, requiresFirstFrame: true, supportedReferenceRoles: [] },
|
||
} }, workspaces: [],
|
||
} });
|
||
if (path.startsWith('/api/cloud-agents/') && method === 'GET') {
|
||
if (path.endsWith('/recovery')) return result({ recent: null, pending: [] });
|
||
if (path.endsWith('/agents') || path.endsWith('/bootstrap')) return result({ agents: [], next_cursor: null });
|
||
}
|
||
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-settings', JSON.stringify({ version: 0, state: { classroomSidebarWidth: 171, sidebarCollapsed: false } }));
|
||
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.reload({ waitUntil: 'domcontentloaded' });
|
||
const trigger = page.getByTestId('home-account-menu-trigger');
|
||
const menu = page.getByTestId('home-account-menu');
|
||
const surface = page.getByTestId('home-account-trigger-surface');
|
||
const checkAccountGeometry = async (width: number) => {
|
||
await expect.poll(async () => (await menu.boundingBox())!.width).toBeCloseTo(288, 1);
|
||
const visible = (await surface.boundingBox())!;
|
||
const target = (await trigger.boundingBox())!;
|
||
const avatar = (await surface.locator('span').first().boundingBox())!;
|
||
await expect.poll(async () => (await surface.locator('svg').boundingBox())!.width).toBeCloseTo(14, 1);
|
||
const chevron = (await surface.locator('svg').boundingBox())!;
|
||
const panel = (await menu.boundingBox())!;
|
||
expect(visible.width).toBeCloseTo(150, 1);
|
||
expect(visible.height).toBeCloseTo(42, 1);
|
||
expect(avatar.width).toBeCloseTo(28, 1);
|
||
expect(avatar.height).toBeCloseTo(28, 1);
|
||
await expect(surface).toHaveCSS('font-size', '12px');
|
||
expect(chevron.width).toBeCloseTo(14, 1);
|
||
expect(chevron.height).toBeCloseTo(14, 1);
|
||
expect(target.width).toBeCloseTo(150, 1);
|
||
expect(target.height).toBeCloseTo(42, 1);
|
||
expect(visible.x).toBeCloseTo(10, 1);
|
||
expect(await page.evaluate(() => innerHeight) - visible.y - visible.height).toBeCloseTo(12, 0);
|
||
expect(panel.x).toBeCloseTo(visible.x, 1);
|
||
expect(visible.y - panel.y - panel.height).toBeCloseTo(8, 1);
|
||
expect(panel.y).toBeGreaterThanOrEqual(0);
|
||
expect(panel.x + panel.width).toBeLessThanOrEqual(width);
|
||
};
|
||
const readTriggerAppearance = (accountTrigger: typeof trigger) => accountTrigger.evaluate(element => {
|
||
const face = element.firstElementChild!;
|
||
const bounds = element.getBoundingClientRect();
|
||
const avatar = face.firstElementChild!.getBoundingClientRect();
|
||
const arrow = face.querySelector('svg')!.getBoundingClientRect();
|
||
const style = getComputedStyle(face);
|
||
return { width: bounds.width, height: bounds.height, left: bounds.left, bottom: innerHeight - bounds.bottom,
|
||
avatarWidth: avatar.width, avatarHeight: avatar.height, arrowWidth: arrow.width, arrowHeight: arrow.height,
|
||
fontSize: style.fontSize, color: style.color, background: style.backgroundColor,
|
||
border: style.borderTopColor, radius: style.borderRadius, padding: style.paddingLeft, gap: style.gap };
|
||
});
|
||
const checkSettingsShell = async () => {
|
||
await expect(page.getByTestId('settings-page')).toBeVisible();
|
||
await expect(page.getByTestId('sidebar')).toHaveCount(0);
|
||
await expect(page.getByTestId('titlebar-sidebar-toggle')).toHaveCount(0);
|
||
await expect(page.getByTestId('coding-conversation-header')).toHaveCount(0);
|
||
await expect(page.getByTestId('settings-back-button')).toBeVisible();
|
||
};
|
||
await expect(trigger).toContainText('小麦');
|
||
const homeAppearance = await readTriggerAppearance(trigger);
|
||
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 checkAccountGeometry(1440);
|
||
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 checkSettingsShell();
|
||
expect(await readTriggerAppearance(trigger)).toEqual(homeAppearance);
|
||
await page.getByTestId('settings-back-button').click();
|
||
await expect(page).toHaveURL(/\/module-select$/);
|
||
await expect(trigger).toContainText('小麦');
|
||
|
||
let codeAppearance: typeof homeAppearance | undefined;
|
||
for (const [label, origin] of [['code', '/chat'], ['agents', '/cloud-agents?view=mine'], ['canvas', '/image-canvas']]) {
|
||
await page.evaluate(path => { window.location.hash = '#' + path; }, origin);
|
||
const sidebarTrigger = page.getByTestId('sidebar-member-menu-trigger');
|
||
await expect(sidebarTrigger).toBeVisible();
|
||
// Route changes may replace the visible sidebar during lazy workspace mounting.
|
||
await expect.poll(() => readTriggerAppearance(sidebarTrigger)).toEqual(homeAppearance);
|
||
const appearance = await readTriggerAppearance(sidebarTrigger);
|
||
if (label === 'code') { codeAppearance = appearance; expect(homeAppearance).toEqual(codeAppearance); }
|
||
else expect(appearance).toEqual(codeAppearance);
|
||
await sidebarTrigger.click();
|
||
await page.screenshot({ path: testInfo.outputPath(`account-menu-${label}-desktop.png`) });
|
||
await page.getByTestId('sidebar-account-menu').getByRole('menuitem', { name: '设置', exact: true }).click();
|
||
await expect(page).toHaveURL(/\/settings$/);
|
||
await checkSettingsShell();
|
||
expect(await readTriggerAppearance(trigger)).toEqual(codeAppearance);
|
||
await page.getByTestId('settings-back-button').click();
|
||
await expect.poll(() => page.evaluate(() => window.location.hash)).toBe('#' + origin);
|
||
await expect(sidebarTrigger).toBeVisible();
|
||
}
|
||
await page.getByTestId('sidebar-module-switcher-trigger').click();
|
||
await expect(page).toHaveURL(/\/module-select$/);
|
||
|
||
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);
|
||
await checkAccountGeometry(375);
|
||
await page.screenshot({ path: testInfo.outputPath('home-account-menu-375.png') });
|
||
await menu.getByRole('menuitem', { name: '设置', exact: true }).click();
|
||
await expect(page).toHaveURL(/\/settings$/);
|
||
await checkSettingsShell();
|
||
await trigger.click();
|
||
await expect(menu.getByTestId('sidebar-total-token-points')).toHaveText('75 点');
|
||
await checkAccountGeometry(375);
|
||
await page.screenshot({ path: testInfo.outputPath('settings-account-menu-375.png') });
|
||
await page.keyboard.press('Escape');
|
||
await page.getByTestId('settings-back-button').click();
|
||
await expect(page).toHaveURL(/\/module-select$/);
|
||
await page.evaluate(() => { window.location.hash = '#/image-canvas'; });
|
||
await expect(page.getByTestId('standalone-account-dock')).toBeVisible();
|
||
await expect.poll(() => readTriggerAppearance(trigger)).toEqual(homeAppearance);
|
||
await trigger.click();
|
||
await checkAccountGeometry(375);
|
||
await page.screenshot({ path: testInfo.outputPath('canvas-account-menu-375.png') });
|
||
await menu.getByRole('menuitem', { name: '设置', exact: true }).click();
|
||
await checkSettingsShell();
|
||
await page.getByTestId('settings-back-button').click();
|
||
await expect(page).toHaveURL(/\/image-canvas$/);
|
||
await page.evaluate(() => { window.location.hash = '#/module-select'; });
|
||
await expect(trigger).toBeVisible();
|
||
await trigger.click();
|
||
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);
|
||
} catch (error) {
|
||
const page = app.windows().at(-1);
|
||
if (page && !page.isClosed()) await testInfo.attach('account-failure-dom', { body: await page.locator('body').innerText(), contentType: 'text/plain' });
|
||
await testInfo.attach('account-fixture-requests', {
|
||
body: JSON.stringify(await app.evaluate(() => (globalThis as typeof globalThis & { homeAccountFixture?: unknown }).homeAccountFixture)),
|
||
contentType: 'application/json',
|
||
});
|
||
throw error;
|
||
} 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 shared Code sidebar chrome in 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();
|
||
await expect(sidebar.locator('.classroom-brand')).toHaveCount(0);
|
||
await expect(sidebar).not.toContainText('把脑洞变成作品');
|
||
const returnBox = await moduleReturn.boundingBox();
|
||
const collapseBox = await page.getByRole('button', { name: '折叠侧栏' }).boundingBox();
|
||
const sidebarBox = await sidebar.boundingBox();
|
||
expect(returnBox).not.toBeNull();
|
||
expect(collapseBox).not.toBeNull();
|
||
expect(returnBox!.y + returnBox!.height).toBeLessThanOrEqual(sidebarBox!.y);
|
||
const headerBox = await page.getByTestId('coding-conversation-header').boundingBox();
|
||
expect(Math.abs(returnBox!.y + returnBox!.height / 2 - headerBox!.y - headerBox!.height / 2)).toBeLessThan(1);
|
||
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();
|
||
const accountBox = await sidebar.getByTestId('sidebar-member-menu-trigger').boundingBox();
|
||
expect(createBox!.y).toBe(sidebarBox!.y);
|
||
expect(headingBox!.y - sidebarBox!.y).toBe(56);
|
||
expect(communityBox!.x).toBe(createBox!.x);
|
||
expect(communityBox!.y).toBeGreaterThan(headingBox!.y + headingBox!.height);
|
||
expect(accountBox!.y - communityBox!.y - communityBox!.height).toBeCloseTo(0, 0);
|
||
expect(communityBox!.height).toBe(createBox!.height);
|
||
await expect(community).toHaveCSS('background-color', 'rgba(0, 0, 0, 0)');
|
||
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.getByTestId('sidebar-create-project').boundingBox()).toEqual(createBox);
|
||
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');
|
||
|
||
const readCodeChrome = () => page.evaluate(() => {
|
||
if (document.querySelector('[data-testid="sidebar"] .classroom-brand')) throw new Error('Removed Code brand reappeared');
|
||
const selectors = {
|
||
sidebar: '[data-testid="sidebar"]',
|
||
titlebar: '[data-testid="titlebar-sidebar-surface"]',
|
||
navigation: '[data-testid="classroom-titlebar-navigation"]',
|
||
home: '[data-testid="sidebar-module-switcher-trigger"]',
|
||
toggle: '[data-testid="titlebar-sidebar-toggle"]',
|
||
create: '[data-testid="sidebar-create-project"]',
|
||
heading: '[data-testid="sidebar"] .classroom-projects-heading',
|
||
community: '[data-testid="sidebar"] .community-link[data-variant="sidebar"]',
|
||
communityIcon: '[data-testid="sidebar"] .community-link-icon',
|
||
account: '[data-testid="sidebar-member-menu-trigger"]',
|
||
};
|
||
return Object.fromEntries(Object.entries(selectors).map(([name, selector]) => {
|
||
const element = document.querySelector(selector);
|
||
if (!element) throw new Error('Code chrome element missing: ' + name);
|
||
const bounds = element.getBoundingClientRect();
|
||
const style = getComputedStyle(element);
|
||
const svg = element.querySelector('svg');
|
||
return [name, {
|
||
x: Number(bounds.x.toFixed(1)), y: Number(bounds.y.toFixed(1)),
|
||
width: Number(bounds.width.toFixed(1)), height: Number(bounds.height.toFixed(1)),
|
||
// The titlebar surface is empty chrome; its inherited text color
|
||
// belongs to the route's content theme and paints no text or icons.
|
||
color: name === 'titlebar' ? null : style.color,
|
||
background: style.backgroundColor, border: style.borderTopColor,
|
||
radius: style.borderRadius, padding: style.padding,
|
||
iconWidth: svg ? Number(svg.getBoundingClientRect().width.toFixed(1)) : null,
|
||
iconStroke: svg?.getAttribute('stroke-width') ?? null,
|
||
}];
|
||
}));
|
||
});
|
||
// Create local metadata only; no prompt is sent and no model runs.
|
||
await sidebar.getByRole('button', { name: '新建对话', exact: true }).click();
|
||
const conversationActions = sidebar.getByRole('button', { name: '对话操作:新对话', exact: true }).first();
|
||
await expect(conversationActions).toBeAttached();
|
||
await page.mouse.move(1000, 500);
|
||
const chatChrome = await readCodeChrome();
|
||
await conversationActions.hover();
|
||
await conversationActions.click();
|
||
await expect(page.getByRole('menuitem', { name: '会话编程设置', exact: true })).toHaveCount(0);
|
||
await expect(page.getByRole('menuitem', { name: '删除对话', exact: true })).toBeVisible();
|
||
await page.keyboard.press('Escape');
|
||
await sidebar.getByRole('button', { name: '项目操作:故事绘本', exact: true }).click();
|
||
await page.getByRole('menuitem', { name: '项目设置', exact: true }).click();
|
||
await expect(page).toHaveURL(/\/project-config$/);
|
||
await expect(page.getByTestId('project-configuration-page')).toBeVisible();
|
||
await expect(moduleReturn).toBeVisible();
|
||
const projectBack = page.getByTestId('project-configuration-back-button');
|
||
await expect(projectBack).toBeVisible();
|
||
await page.mouse.move(1000, 500);
|
||
await expect.poll(readCodeChrome).toEqual(chatChrome);
|
||
await expect(sidebar).toHaveCSS('background-color', 'rgb(243, 247, 251)');
|
||
const defaultModel = page.getByRole('combobox', { name: '默认模型', exact: true });
|
||
await expect(defaultModel).toBeVisible();
|
||
const originalModel = await defaultModel.inputValue();
|
||
await expect(page.getByLabel('项目提示词', { exact: true })).toHaveCount(0);
|
||
await expect(page.getByRole('heading', { name: '项目 Skills', exact: true })).toHaveCount(0);
|
||
await page.screenshot({ path: test.info().outputPath('project-config-shared-code-sidebar.png') });
|
||
|
||
// Reload directly on the settings URL, without mounting /chat first.
|
||
await page.reload({ waitUntil: 'domcontentloaded' });
|
||
await expect(page).toHaveURL(/\/project-config$/);
|
||
await expect(page.getByTestId('project-configuration-page')).toBeVisible();
|
||
await expect.poll(readCodeChrome).toEqual(chatChrome);
|
||
const sidebarToggle = page.getByTestId('titlebar-sidebar-toggle');
|
||
await sidebarToggle.click();
|
||
await page.getByTestId('project-configuration-page').click({ position: { x: 500, y: 20 } });
|
||
await expect(sidebar).toHaveAttribute('aria-hidden', 'true');
|
||
await expect(moduleReturn).toBeVisible();
|
||
await sidebarToggle.click();
|
||
await page.mouse.move(1000, 500);
|
||
await expect(sidebar).toHaveAttribute('aria-hidden', 'false');
|
||
await expect.poll(readCodeChrome).toEqual(chatChrome);
|
||
await expect(defaultModel).toHaveValue(originalModel);
|
||
await projectBack.click();
|
||
await expect(page).toHaveURL(/\/chat$/);
|
||
await expect(moduleReturn).toBeVisible();
|
||
|
||
await sidebar.getByRole('button', { name: '项目操作:故事绘本', exact: true }).click();
|
||
await page.getByRole('menuitem', { 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(moduleReturn).toBeVisible();
|
||
await expect.poll(readCodeChrome).toEqual(chatChrome);
|
||
await page.screenshot({ path: test.info().outputPath('project-config-plugins-shared-code-sidebar.png') });
|
||
await plugins.getByRole('button', { name: '关闭', exact: true }).click();
|
||
await expect(page).toHaveURL(/\/project-config(?:\?|$)/);
|
||
await expect(plugins).toHaveCount(0);
|
||
await expect(page.getByTestId('project-configuration-page')).toBeVisible();
|
||
await expect(projectBack).toBeVisible();
|
||
await sidebar.getByTestId('sidebar-course-project-project-storybook').click();
|
||
await expect(page).toHaveURL(/\/chat$/);
|
||
|
||
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 title = page.getByRole('heading', { name: '今天,想创作什么?', exact: true });
|
||
const heading = await title.boundingBox();
|
||
expect(heading).not.toBeNull();
|
||
const textWidth = await title.evaluate(element => {
|
||
const range = document.createRange(); range.selectNodeContents(element);
|
||
return range.getBoundingClientRect().width;
|
||
});
|
||
expect(Math.abs(heading!.width - textWidth)).toBeLessThan(1);
|
||
if (width >= 1024) {
|
||
const shift = await title.evaluate(element => {
|
||
const intro = element.parentElement!;
|
||
const original = intro.style.transform;
|
||
const shiftedLeft = element.getBoundingClientRect().left;
|
||
try {
|
||
intro.style.transform = 'none';
|
||
return shiftedLeft - element.getBoundingClientRect().left;
|
||
} finally { intro.style.transform = original; }
|
||
});
|
||
expect(shift).toBeCloseTo(1.5 * 96 / 2.54, 1);
|
||
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')).toHaveCount(0);
|
||
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);
|
||
}
|
||
});
|
||
});
|