fix(knowledge): align cards and remove extra guidance
This commit is contained in:
1 parent
c8ce94b778
commit
2899009215
27 files changed
+516
-100
No files matched your search
@@ -56,24 +56,45 @@ test.describe('Makelore module navigation without setup flow', () => {
|
||||
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 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: state.loggedIn ? { accessToken: 'home-account-fixture', tokenType: 'Bearer', expiresAt: Date.now() + 3600000, lastActiveAt: Date.now(), canRefresh: false } : null });
|
||||
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 });
|
||||
});
|
||||
@@ -82,6 +103,7 @@ test.describe('Makelore module navigation without setup flow', () => {
|
||||
// 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',
|
||||
@@ -97,7 +119,7 @@ test.describe('Makelore module navigation without setup flow', () => {
|
||||
pendingSync: false, version: 1, updatedAt: '2026-10-13T00:00:00Z' } },
|
||||
} }));
|
||||
});
|
||||
await page.goto(applicationUrl);
|
||||
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');
|
||||
@@ -106,22 +128,45 @@ test.describe('Makelore module navigation without setup flow', () => {
|
||||
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(128, 1);
|
||||
expect(visible.height).toBeCloseTo(25, 1);
|
||||
expect(avatar.width).toBeCloseTo(16, 1);
|
||||
expect(avatar.height).toBeCloseTo(16, 1);
|
||||
expect(target.width).toBeCloseTo(128, 1);
|
||||
expect(target.height).toBeCloseTo(50, 1);
|
||||
expect(visible.x).toBeGreaterThanOrEqual(16);
|
||||
expect(visible.x).toBeCloseTo(width === 1440 ? 80 - 1.5 * 96 / 2.54 : 16, 1);
|
||||
expect(await page.evaluate(() => innerHeight) - visible.y - visible.height).toBeCloseTo(16, 0);
|
||||
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();
|
||||
@@ -159,9 +204,35 @@ test.describe('Makelore module navigation without setup flow', () => {
|
||||
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 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();
|
||||
@@ -169,6 +240,29 @@ test.describe('Makelore module navigation without setup flow', () => {
|
||||
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);
|
||||
@@ -178,6 +272,14 @@ test.describe('Makelore module navigation without setup flow', () => {
|
||||
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);
|
||||
|
||||
Reference in new issue
Block a user