Files
makelore/tests/e2e/project-configuration-skills.spec.ts
brother7 5a275b93a7 feat: remove legacy OpenCode runtime
Cut product flows over to Coding/Pi and retain only the migration-owned v1 boundary. Promote supported native optional packages because electron-builder omitted pnpm transitive optional closure from the packaged ASAR.
2026-08-24 12:17:43 +08:00

174 lines
9.6 KiB
TypeScript

import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import path from 'node:path';
import { closeElectronApp, expect, getStableWindow, test } from './fixtures/electron';
import { createCodingProjectConfigV2 } from '../../electron/coding-projects/project-config';
test.describe('Project configuration skills', () => {
test('does not expose the removed Superpowers feature and supports Skill details', async ({ launchElectronApp }) => {
const parentPath = await mkdtemp(path.join(tmpdir(), 'niancode-project-configuration-e2e-'));
const app = await launchElectronApp({ skipSetup: true });
try {
await app.evaluate(({ ipcMain }, selectedPath) => {
ipcMain.removeHandler('dialog:open');
ipcMain.handle('dialog:open', async () => ({ canceled: false, filePaths: [selectedPath] }));
}, parentPath);
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.getByTestId('main-layout')).toBeVisible();
await page.getByTestId('sidebar-create-project').click();
await expect(page.getByRole('radio', { name: '小游戏' })).toBeChecked();
await expect(page.getByRole('radio', { name: '小程序' })).not.toBeChecked();
await expect(page.getByRole('radio', { name: '自定义项目' })).not.toBeChecked();
await page.getByRole('radio', { name: '小程序' }).click();
await expect(page.getByRole('radio', { name: '小程序' })).toBeChecked();
await page.getByRole('radio', { name: '小游戏' }).click();
await expect(page.getByRole('radio', { name: '直接使用所选文件夹(默认)' })).toBeChecked();
await page.getByRole('button', { name: '选择路径' }).click();
await expect(page.getByLabel('项目路径')).toHaveValue(parentPath);
await page.getByRole('button', { name: '确认创建' }).click();
await expect(page.getByTestId('project-configuration-page')).toBeVisible();
await expect(page.getByTestId('sidebar-nav-publish')).toHaveCount(0);
await expect(page.getByText(/新项目还没有伙伴/)).toHaveCount(0);
await expect(page.getByTestId('project-configuration-actions')).toBeVisible();
const actionBarBottomGap = await page.getByTestId('project-configuration-actions').evaluate((element) => window.innerHeight - element.getBoundingClientRect().bottom);
expect(actionBarBottomGap).toBeLessThan(40);
await expect(page.getByRole('button', { name: '创建伙伴' })).toBeVisible();
for (const [resourceId, title] of [
['resource-card-models', '大脑(大语言模型)'],
['resource-card-knowledge', '笔记本(知识库)'],
['resource-card-skills', '工具箱(技能)'],
] as const) {
await page.getByTestId(resourceId).click();
const drawer = page.getByRole('dialog', { name: title });
await expect(drawer).toBeVisible();
if (resourceId === 'resource-card-models') {
await expect(drawer.getByRole('combobox')).toHaveCount(0);
await expect(drawer.locator('[data-testid="project-model-list"], [data-testid="project-model-empty-state"]')).toHaveCount(1);
}
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);
}
const now = new Date().toISOString();
await mkdir(path.join(parentPath, '.niancode'), { recursive: true });
await writeFile(path.join(parentPath, '.niancode', 'project.json'), JSON.stringify({
...createCodingProjectConfigV2(now, 'mini_game'),
initialized: true,
agents: [{
id: 'e2e-partner',
avatarId: 'avatar-01',
roleName: '项目伙伴',
name: 'E2E伙伴',
builtIn: false,
enabled: true,
model: { accountId: 'moonshot', modelId: 'kimi-k2.6', thinkingLevel: 'off' },
modelResolution: 'resolved',
skillIds: [],
responsibility: { mission: '验证伙伴维护弹窗。', owns: [], boundaries: [], collaborators: [], principles: [] },
prompt: '',
archivedAt: null,
pinned: false,
createdAt: now,
updatedAt: now,
}],
}, null, 2));
await page.reload();
await expect(page.getByTestId('project-configuration-page')).toBeVisible();
await page.getByTestId('resource-card-skills').click();
await expect(page.getByTestId('superpowers-card')).toHaveCount(0);
await expect(page.getByRole('switch', { name: '启用 Superpowers' })).toHaveCount(0);
await expect(page.getByTestId('skill-card-agent-browser')).toBeVisible();
await page.getByTestId('skill-card-agent-browser').click();
await expect(page.getByTestId('skill-detail-view')).toBeVisible();
await expect(page.getByTestId('skill-structure')).toContainText('SKILL.md');
await expect(page.getByTestId('skill-main-file')).toContainText('# Makelore 开发浏览器');
await page.getByRole('button', { name: '返回技能列表' }).locator('svg').click();
await expect(page.getByTestId('skill-card-agent-browser')).toBeVisible();
await expect(page.getByTestId('skill-card-game-engine')).toHaveCount(0);
await page.getByRole('button', { name: '关闭' }).click();
await expect(page.getByTestId('resource-card-publish')).toHaveCount(0);
await expect(page.getByRole('button', { name: '一键提交审核' })).toBeVisible();
await app.evaluate(({ ipcMain }) => {
ipcMain.removeHandler('hostapi:fetch');
ipcMain.handle('hostapi:fetch', async (_event, request: { path?: string }) => {
if (/^\/api\/works\/projects\/mine\/[^/]+\/status$/.test(request.path ?? '')) {
return {
ok: true,
data: {
status: 200,
ok: true,
json: {
success: false,
status: 404,
code: 'PROJECT_NOT_FOUND',
error: 'Project not found',
},
},
};
}
return { ok: false, error: { message: `Unexpected E2E Host API request: ${request.path ?? ''}` } };
});
});
await page.getByRole('button', { name: '一键提交审核' }).click();
const publishDialog = page.getByRole('dialog', { name: '完善作品信息后提交' });
await expect(publishDialog).toBeVisible();
await expect(publishDialog.getByLabel(/项目封面/)).toBeVisible();
await publishDialog.getByLabel(/发布者姓名/).fill('E2E 作者');
await publishDialog.getByLabel(/发布者年龄/).fill('12');
await publishDialog.getByLabel(/项目简介/).fill('验证首次提审必须选择项目封面。');
await publishDialog.getByRole('button', { name: '提交审核' }).click();
await expect(publishDialog.getByRole('alert')).toContainText('首次提交必须上传');
const coverPath = path.join(process.cwd(), 'src/assets/agent-avatars/avatar-01.png');
await publishDialog.getByLabel(/项目封面/).setInputFiles(coverPath);
await expect(publishDialog.getByAltText('项目封面预览')).toBeVisible();
await expect(publishDialog.getByText('avatar-01.png')).toBeVisible();
await expect(publishDialog.getByText('重新选择')).toBeVisible();
await publishDialog.getByRole('button', { name: '取消' }).click();
const agentCard = page.getByTestId('project-agent-e2e-partner');
await expect(agentCard).toContainText('E2E伙伴');
await agentCard.click();
const maintenanceDialog = page.getByRole('dialog', { name: 'E2E伙伴 · 伙伴维护' });
await expect(maintenanceDialog).toBeVisible();
await expect(maintenanceDialog.getByLabel(/伙伴名称/)).toHaveValue('E2E伙伴');
await maintenanceDialog.getByRole('button', { name: '更换头像' }).click();
const avatarDialog = page.getByRole('dialog', { name: '选择头像' });
await expect(avatarDialog).toBeVisible();
await avatarDialog.getByLabel('上传伙伴头像').setInputFiles(path.join(process.cwd(), 'src/assets/agent-avatars/avatar-01.png'));
await expect(avatarDialog).toHaveCount(0);
await expect(maintenanceDialog.getByAltText('当前上传头像')).toBeVisible();
await expect(page.getByText('完成配置')).toHaveCount(0);
} finally {
await closeElectronApp(app);
await rm(parentPath, { recursive: true, force: true });
}
});
});