Merge branch 'main' of https://git.nianxx.cn/wangxuming/makelore
Some checks failed
Electron E2E / Electron E2E (macos-latest) (push) Has been cancelled
Electron E2E / Electron E2E (ubuntu-latest) (push) Has been cancelled
Electron E2E / Electron E2E (windows-latest) (push) Has been cancelled

This commit is contained in:
2026-08-15 10:40:33 +08:00
31 changed files with 1153 additions and 241 deletions

View File

@@ -24,14 +24,49 @@ test.describe('Makelore module navigation without setup flow', () => {
const initialMetrics = await readCardMetrics();
expect(initialMetrics).toHaveLength(4);
await expect(moduleCards.first().locator('.module-option-card-arrow')).toHaveCount(0);
await expect(moduleCards.first().locator('.module-option-artwork')).toHaveCount(0);
await expect.poll(async () => await moduleCards.first().evaluate((element) => {
const surface = element.querySelector<HTMLElement>('.module-option-card-surface');
const frame = element.querySelector<HTMLElement>('.module-option-card-frame');
if (!surface || !frame) return false;
const surfaceStyle = getComputedStyle(surface);
const frameStyle = getComputedStyle(frame);
const surfaceInsets = [surfaceStyle.top, surfaceStyle.right, surfaceStyle.bottom, surfaceStyle.left]
.map((value) => Number.parseFloat(value));
return frameStyle.display === 'none'
&& surfaceInsets.every((value) => value === 0)
&& surfaceStyle.transform === 'none';
})).toBe(true);
const initialImageStyles = await moduleCards.first().evaluate((element) => {
const image = element.querySelector<HTMLElement>('.module-option-card-image');
const cardHeight = element.getBoundingClientRect().height;
if (!image) return null;
const style = getComputedStyle(image);
return {
height: Number.parseFloat(style.height),
cardHeight,
marginTop: Number.parseFloat(style.marginTop),
marginBottom: Number.parseFloat(style.marginBottom),
marginLeft: Number.parseFloat(style.marginLeft),
};
});
expect(initialImageStyles).not.toBeNull();
expect(initialImageStyles!.height).toBeLessThan(initialImageStyles!.cardHeight);
expect(initialImageStyles!.marginTop).toBe(1);
expect(initialImageStyles!.marginBottom).toBe(1);
expect(initialImageStyles!.marginTop).toBe(initialImageStyles!.marginBottom);
expect(initialImageStyles!.marginLeft).toBeGreaterThan(0);
expect(initialImageStyles!.marginLeft).toBe(initialImageStyles!.marginTop);
expect(Math.max(...initialMetrics.map((card) => card.left)) - Math.min(...initialMetrics.map((card) => card.left))).toBeLessThan(1);
expect(initialMetrics.map((card) => card.top)).toEqual(
[...initialMetrics].sort((top, bottom) => top.top - bottom.top).map((card) => card.top),
);
const initialWidth = initialMetrics[0]?.width ?? 0;
expect(initialMetrics[0]?.imageWidth ?? 0).toBeGreaterThan(initialWidth * 0.24);
expect(initialMetrics[0]?.imageWidth ?? 0).toBeLessThan(initialWidth / 2);
expect(initialMetrics[0]?.imageHeight ?? 0).toBeGreaterThan((initialMetrics[0]?.height ?? 0) * 0.9);
expect(initialMetrics[0]?.imageHeight ?? 0).toBeGreaterThan((initialMetrics[0]?.height ?? 0) * 0.75);
await app.evaluate(({ BrowserWindow }) => {
BrowserWindow.getAllWindows()[0]?.setSize(1100, 700);
@@ -45,14 +80,76 @@ test.describe('Makelore module navigation without setup flow', () => {
expect(resizedMetrics.map((card) => card.top)).toEqual(
[...resizedMetrics].sort((top, bottom) => top.top - bottom.top).map((card) => card.top),
);
expect(resizedMetrics[0]!.imageWidth).toBeGreaterThan(resizedMetrics[0]!.width * 0.24);
expect(resizedMetrics[0]!.imageWidth).toBeLessThan(resizedMetrics[0]!.width / 2);
expect(resizedMetrics[0]!.imageHeight).toBeGreaterThan(resizedMetrics[0]!.height * 0.9);
expect(resizedMetrics[0]!.imageHeight).toBeGreaterThan(resizedMetrics[0]!.height * 0.75);
await page.mouse.move(20, 20);
const restingTransforms = 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 {
surfaceShadow: surface ? getComputedStyle(surface).boxShadow : 'none',
image: image ? getComputedStyle(image).transform : 'none',
imageMedia: imageMedia ? getComputedStyle(imageMedia).transform : 'none',
content: content ? getComputedStyle(content).transform : 'none',
title: title ? getComputedStyle(title).transform : 'none',
};
});
await moduleCards.first().hover();
await moduleCards.first().focus();
await expect.poll(async () => await moduleCards.first().evaluate((element, rest) => {
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');
const description = element.querySelector<HTMLElement>('.module-option-card-description');
const getTranslateZ = (transform: string) => {
if (!transform.startsWith('matrix3d(')) return 0;
const values = transform.slice(9, -1).split(',').map(Number);
return values[14] ?? 0;
};
const titleTransform = title ? getComputedStyle(title).transform : 'none';
const descriptionTransform = description ? getComputedStyle(description).transform : 'none';
return Boolean(surface)
&& Boolean(image)
&& Boolean(imageMedia)
&& Boolean(content)
&& Boolean(title)
&& Boolean(description)
&& getComputedStyle(surface).transform !== 'none'
&& getComputedStyle(surface).boxShadow !== rest.surfaceShadow
&& getComputedStyle(surface).borderTopColor === 'rgb(255, 255, 255)'
&& getComputedStyle(image).transform !== 'none'
&& getComputedStyle(imageMedia).transform !== 'none'
&& getComputedStyle(content).transform !== 'none'
&& titleTransform !== 'none'
&& descriptionTransform !== 'none'
&& getTranslateZ(titleTransform) > getTranslateZ(descriptionTransform) + 20;
}, restingTransforms)).toBe(true);
await expect.poll(async () => await moduleCards.first().evaluate((element, rest) => {
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(image && imageMedia && content && title)
&& getComputedStyle(image!).transform !== rest.image
&& getComputedStyle(imageMedia!).transform !== rest.imageMedia
&& getComputedStyle(content!).transform !== rest.content
&& getComputedStyle(title!).transform !== rest.title;
}, restingTransforms)).toBe(true);
} finally {
await closeElectronApp(app);
}
});
test('opens the module chooser and switches between enabled modules', async ({ launchElectronApp }) => {
test('returns to the module chooser from the sidebar entry', async ({ launchElectronApp }) => {
const app = await launchElectronApp({ skipSetup: true });
try {
@@ -79,33 +176,24 @@ test.describe('Makelore module navigation without setup flow', () => {
await expect(page.getByTestId('game-asset-review-summary')).toHaveCount(0);
await expect(page.getByTestId('sidebar-module-switcher')).toBeVisible();
await expect(page.getByTestId('sidebar-module-switcher-trigger')).toHaveAttribute('aria-expanded', 'false');
await page.getByTestId('sidebar-module-switcher-trigger').click();
await expect(page.getByTestId('sidebar-module-programming')).toHaveAttribute('aria-current', 'page');
await expect(page.getByTestId('sidebar-module-learning')).toBeDisabled();
await expect(page.getByTestId('sidebar-module-robot')).toContainText('Robot 机器');
await page.getByTestId('sidebar-module-painting').click();
await expect(page.getByTestId('image-canvas-page')).toBeVisible();
await expect(page.getByTestId('image-workspace-unavailable')).toBeVisible();
await expect(page.getByText('AI 设计暂不可用').first()).toBeVisible();
await expect(page.getByTestId('sidebar-image-workspace')).toBeVisible();
await expect(page.getByTestId('sidebar-create-image-project')).toBeVisible();
await expect(page.getByTestId('sidebar-image-projects')).toBeVisible();
await expect(page.getByText('本地开发')).toHaveCount(0);
await expect(page.getByTestId('sidebar-reset-local-image-workspace')).toHaveCount(0);
await expect(page.getByTestId('sidebar-create-project')).toHaveCount(0);
await expect(page.getByTestId('sidebar-module-painting')).toHaveAttribute('aria-current', 'page');
await page.getByTestId('sidebar-module-switcher-trigger').click();
await page.getByTestId('sidebar-module-robot').click();
const moduleHomeTrigger = page.getByTestId('sidebar-module-switcher-trigger');
await expect(moduleHomeTrigger).toHaveAttribute('aria-label', '返回首页');
await expect(moduleHomeTrigger).toHaveCSS('padding-top', '8px');
await expect(moduleHomeTrigger).toHaveCSS('padding-right', '8px');
await expect(moduleHomeTrigger).toHaveCSS('padding-bottom', '8px');
await expect(moduleHomeTrigger).toHaveCSS('padding-left', '8px');
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 page.getByTestId('ai-module-option-robot').click();
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-create-project')).toHaveCount(0);
await expect(page.getByTestId('sidebar-module-robot')).toHaveAttribute('aria-current', 'page');
await expect(page.getByTestId('sidebar-module-switcher-trigger')).toContainText('Robot 机器');
await page.getByTestId('sidebar-module-switcher-trigger').click();
await page.getByTestId('sidebar-module-programming').click();
await expect(page.getByText('请先新建项目')).toHaveCount(0);
await expect(page.getByText('一念成光,万物可创。')).toBeVisible();
await expect(page.getByTestId('chat-operation-page')).toHaveCount(0);
await expect(page).toHaveURL(/\/module-select$/);
await expect(page.getByTestId('ai-module-selection-page')).toBeVisible();
} finally {
await closeElectronApp(app);
}

View File

@@ -398,6 +398,15 @@ async function installSlashCommandHost(
shareEnabled: true,
});
}
if (path === '/api/opencode/skills') {
return respond({
skills: [
{ name: 'frontend-slides' },
{ name: 'grilling' },
{ name: 'planning-with-files' },
],
});
}
if (
path
=== '/api/opencode/sessions/ses_slash_e2e/summarize'
@@ -571,8 +580,6 @@ test.describe('OpenCode slash commands', () => {
await completeSetup(page);
await installSlashCommandHost(electronApp);
await page.reload();
await page.getByTestId('sidebar-module-switcher-trigger').click();
await page.getByTestId('sidebar-module-programming').click();
await expect(page).toHaveURL(/\/opencode-chat$/);
await page.getByTestId('project-agent-chat-game-development').click();
const composer = page.getByRole('textbox');
@@ -759,8 +766,6 @@ test.describe('OpenCode slash commands', () => {
await installSlashCommandHost(electronApp);
await page.reload();
await page.setViewportSize({ width: 1280, height: 800 });
await page.getByTestId('sidebar-module-switcher-trigger').click();
await page.getByTestId('sidebar-module-programming').click();
await expect(page).toHaveURL(/\/opencode-chat$/);
await expect(page.getByTestId('opencode-message-composer')).toBeVisible();
@@ -771,6 +776,11 @@ test.describe('OpenCode slash commands', () => {
const createPartnerDialog = page.getByRole('dialog', { name: '创建项目伙伴' });
await expect(createPartnerDialog).toBeVisible();
await expect(createPartnerDialog).toHaveCSS('z-index', '110');
await expect(createPartnerDialog.getByLabel('绑定技能:项目演示')).not.toBeChecked();
await expect(createPartnerDialog.getByLabel('绑定技能:方案质询')).toBeChecked();
await expect(createPartnerDialog.getByLabel('绑定技能:项目规划')).toBeChecked();
await createPartnerDialog.getByLabel('绑定技能:方案质询').uncheck();
await expect(createPartnerDialog.getByLabel('绑定技能:方案质询')).not.toBeChecked();
await createPartnerDialog.getByLabel(/伙伴名称/).fill('E2E层级检查');
await page.getByRole('button', { name: '取消' }).click();
await expect(createPartnerButton).toHaveClass(/text-brand/);

View File

@@ -57,7 +57,25 @@ test.describe('Project configuration skills', () => {
await expect(drawer.getByRole('combobox')).toHaveCount(0);
await expect(drawer.locator('[data-testid="project-model-list"], [data-testid="project-model-empty-state"]')).toHaveCount(1);
}
await page.getByRole('button', { name: '关闭' }).click();
const closeButton = drawer.getByRole('button', { name: '关闭' });
const closeGeometry = await closeButton.evaluate((button) => {
const icon = button.querySelector('svg');
if (!icon) return null;
const buttonRect = button.getBoundingClientRect();
const iconRect = icon.getBoundingClientRect();
return {
button: { x: buttonRect.x, y: buttonRect.y, width: buttonRect.width, height: buttonRect.height },
icon: { x: iconRect.x, y: iconRect.y, width: iconRect.width, height: iconRect.height },
};
});
if (!closeGeometry) {
throw new Error(`未能读取 ${title} 抽屉关闭按钮的布局区域`);
}
expect(closeGeometry.button.width).toBe(40);
expect(closeGeometry.button.height).toBe(40);
expect(Math.abs((closeGeometry.icon.x + closeGeometry.icon.width / 2) - (closeGeometry.button.x + closeGeometry.button.width / 2))).toBeLessThanOrEqual(0.5);
expect(Math.abs((closeGeometry.icon.y + closeGeometry.icon.height / 2) - (closeGeometry.button.y + closeGeometry.button.height / 2))).toBeLessThanOrEqual(0.5);
await closeButton.locator('svg').click();
await expect(page.getByRole('dialog', { name: title })).toHaveCount(0);
}
@@ -94,7 +112,7 @@ test.describe('Project configuration skills', () => {
await expect(page.getByTestId('skill-detail-view')).toBeVisible();
await expect(page.getByTestId('skill-structure')).toContainText('references/guide.md');
await expect(page.getByTestId('skill-main-file')).toContainText('# Browser debugging');
await page.getByRole('button', { name: '返回技能列表' }).click();
await page.getByRole('button', { name: '返回技能列表' }).locator('svg').click();
await expect(page.getByTestId('skill-card-agent-browser')).toBeVisible();
await page.getByRole('button', { name: '关闭' }).click();
await expect(page.getByTestId('resource-card-publish')).toHaveCount(0);