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-09-07 20:28:05 +08:00
71 changed files with 1478 additions and 2170 deletions

View File

@@ -321,6 +321,10 @@ test.describe('AI Design V2 workspace', () => {
await page.addInitScript(() => {
const now = Date.now();
localStorage.setItem('niancode-settings', JSON.stringify({
state: { sidebarCollapsed: true },
version: 0,
}));
localStorage.setItem('niancode-auth', JSON.stringify({
state: {
authBase: 'https://biz.nianxx.cn/auth/',
@@ -352,9 +356,19 @@ test.describe('AI Design V2 workspace', () => {
await page.getByTestId('ai-module-option-painting').click();
await expect(page.getByTestId('image-workspace')).toBeVisible();
await expect(page.getByTestId('image-workspace-conversation')).toBeVisible();
await expect(page.getByTestId('image-workspace-works-rail')).toBeVisible();
await expect(page.getByRole('heading', { name: '和 AI 一起完善创作' })).toBeVisible();
await expect(page.getByTestId('image-workspace-conversation')).toHaveClass(/bg-background/);
await expect(page.getByTestId('design-message-composer')).toHaveClass(/max-w-\[50rem\]/);
await expect(page.getByTestId('design-message-composer-surface')).toHaveClass(/chat-composer-surface/);
await expect(page.getByTestId('sidebar-image-workspace')).toBeVisible();
await expect(page.getByTestId('image-workspace-history-rail')).toBeVisible();
await expect(page.getByTestId('image-workspace-works-rail')).toHaveCount(0);
await expect(page.getByTestId('titlebar-sidebar-toggle')).toHaveCount(0);
await expect(page.getByRole('heading', { name: '和 AI 一起完善创作' })).toHaveCount(0);
await expect(page.getByText('当前设计项目的制作记录')).toHaveCount(0);
await expect(page.getByText('实时同步')).toHaveCount(0);
await expect(page.getByRole('button', { name: '刷新作品' })).toHaveCount(0);
await expect(page.getByRole('heading', { name: '制作方案' })).toBeVisible();
await expect(page.getByTestId('youth-creation-card')).not.toHaveClass(/border|shadow-sm/);
await expect(page.getByRole('textbox', { name: '创作提示词' })).toHaveValue(
'把便携咖啡机呈现为城市通勤中的精密工具',
);
@@ -365,38 +379,69 @@ test.describe('AI Design V2 workspace', () => {
const layout = await page.evaluate(() => {
const conversation = document.querySelector('[data-testid="image-workspace-conversation"]')
?.getBoundingClientRect();
const worksRail = document.querySelector('[data-testid="image-workspace-works-rail"]')
const projectSidebar = document.querySelector('[data-testid="sidebar"]')
?.getBoundingClientRect();
return conversation && worksRail
const workspace = document.querySelector('[data-testid="image-workspace"]')
?.getBoundingClientRect();
const historyRail = document.querySelector('[data-testid="image-workspace-history-rail"]')
?.getBoundingClientRect();
const workspaceHeader = document.querySelector('[data-testid="image-workspace-header"]')
?.getBoundingClientRect();
const historyHeader = document.querySelector('[data-testid="image-workspace-history-header"]')
?.getBoundingClientRect();
return conversation && projectSidebar && workspace && historyRail && workspaceHeader && historyHeader
? {
conversationHeight: conversation.height,
railHeight: worksRail.height,
topGap: Math.abs(conversation.top - worksRail.top),
conversationWidth: conversation.width,
railWidth: worksRail.width,
sidebarWidth: projectSidebar.width,
horizontalGap: Math.abs(projectSidebar.right - conversation.left),
historyRailWidth: historyRail.width,
historyRailHeight: historyRail.height,
historyTopGap: Math.abs(workspace.top - historyRail.top),
historyBottomGap: Math.abs(workspace.bottom - historyRail.bottom),
historyHorizontalGap: Math.abs(conversation.right - historyRail.left),
workspaceHeaderHeight: workspaceHeader.height,
historyHeaderHeight: historyHeader.height,
headerTopGap: Math.abs(workspaceHeader.top - historyHeader.top),
headerBottomGap: Math.abs(workspaceHeader.bottom - historyHeader.bottom),
}
: null;
});
expect(layout).not.toBeNull();
expect(layout!.conversationHeight).toBeGreaterThan(300);
expect(Math.abs(layout!.conversationHeight - layout!.railHeight)).toBeLessThanOrEqual(1);
expect(layout!.topGap).toBeLessThanOrEqual(1);
expect(layout!.conversationWidth).toBeGreaterThan(layout!.railWidth);
expect(layout!.sidebarWidth).toBe(256);
expect(layout!.horizontalGap).toBeLessThanOrEqual(1);
expect(layout!.historyRailWidth).toBe(256);
expect(layout!.historyRailWidth).toBe(layout!.sidebarWidth);
expect(layout!.historyRailHeight).toBeGreaterThan(500);
expect(layout!.historyTopGap).toBeLessThanOrEqual(1);
expect(layout!.historyBottomGap).toBeLessThanOrEqual(1);
expect(layout!.historyHorizontalGap).toBeLessThanOrEqual(1);
expect(layout!.workspaceHeaderHeight).toBe(40);
expect(layout!.historyHeaderHeight).toBe(40);
expect(layout!.headerTopGap).toBeLessThanOrEqual(1);
expect(layout!.headerBottomGap).toBeLessThanOrEqual(1);
expect(layout!.conversationWidth).toBeGreaterThan(600);
const desktopViewport = page.viewportSize();
await page.setViewportSize({ width: 375, height: 800 });
await expect(page.getByTestId('sidebar')).toBeHidden();
await expect(page.getByTestId('image-workspace-history-rail')).toHaveCount(0);
await expect(page.getByTestId('image-workspace-conversation')).toBeVisible();
await expect(page.getByTestId('youth-creation-card')).toBeVisible();
await page.getByRole('button', { name: '作品', exact: true }).click();
await page.getByRole('button', { name: '项目', exact: true }).click();
await expect(page.getByTestId('image-workspace-works-rail')).toBeVisible();
await expect(page.getByRole('dialog')).toHaveClass(/left-0/);
await page.keyboard.press('Escape');
await page.setViewportSize({ width: 768, height: 800 });
await expect(page.getByTestId('image-workspace-conversation')).toBeVisible();
await expect(page.getByRole('button', { name: '作品', exact: true })).toBeVisible();
await expect(page.getByRole('button', { name: '项目', exact: true })).toBeVisible();
await page.setViewportSize(desktopViewport ?? { width: 1280, height: 800 });
await expect(page.getByTestId('image-workspace-works-rail')).toBeVisible();
await expect(page.getByTestId('sidebar-image-workspace')).toBeVisible();
await expect(page.getByTestId('image-workspace-history-rail')).toBeVisible();
await expect(page.getByTestId('image-workspace-works-rail')).toHaveCount(0);
await page.getByRole('combobox', { name: '画幅' }).selectOption('16:9');
await expect(page.getByRole('combobox', { name: '画幅' })).toHaveValue('16:9');
@@ -415,7 +460,8 @@ test.describe('AI Design V2 workspace', () => {
await expect(page.getByText(/规格版本|编译器|生成方式|不可变/)).toHaveCount(0);
await page.getByRole('button', { name: '确认并开始制作' }).click();
await expect(page.getByTestId('design-plan-history-task-1')).toBeVisible();
await expect(page.getByTestId('image-workspace-history-rail').getByTestId('design-plan-history-task-1')).toBeVisible();
await expect(page.getByTestId('image-workspace-conversation').getByTestId('design-plan-history-task-1')).toHaveCount(0);
await expect(page.getByText('等待制作')).toBeVisible();
await expect(page.getByTestId('youth-creation-card')).toHaveCount(0);
await expect(page.getByText('1200', { exact: true })).toHaveCount(0);

View File

@@ -93,17 +93,20 @@ test.describe('Unified plugin workspace', () => {
await expect(page.getByTestId('project-plugins-sheet')).toBeVisible();
await expect(page.getByTestId('plugins-page')).toBeVisible();
await expect(page).toHaveURL(/\/project-config\/plugins\?scope=all/);
await expect(page.getByTestId('plugins-page').getByText('尚未获取')).toBeVisible();
await expect(page.getByText('为你的智能体添加插件,拓展更多能力。')).toBeVisible();
await expect(page.getByRole('button', { name: /刷新/ })).toHaveCount(0);
await expect(page.getByRole('searchbox')).toHaveCount(0);
await expect(page.getByRole('combobox')).toHaveCount(0);
await expect(page.getByRole('button', { name: '添加灵感笔记' })).toBeVisible();
await page.getByRole('button', { name: '查看灵感笔记详情' }).click();
await expect(page.getByRole('dialog', { name: '灵感笔记' })).toContainText('详细介绍');
await page.getByRole('button', { name: '免费获取灵感笔记' }).click();
await expect(page.getByRole('button', { name: '从账号移除灵感笔记' })).toBeVisible();
await expect(page.getByRole('dialog', { name: '灵感笔记' })).not.toContainText('读取项目名称');
await page.getByRole('button', { name: '添加灵感笔记' }).click();
await expect(page.getByRole('button', { name: '从插件库移除灵感笔记' })).toBeVisible();
await page.getByRole('button', { name: '关闭插件详情' }).click();
await page.getByRole('combobox', { name: '状态', exact: true }).selectOption('mine');
await expect(page).toHaveURL(/state=mine/);
await expect(page.getByRole('heading', { name: '灵感笔记' })).toBeVisible();
await expect(page.getByText('不适用项目启用')).toBeVisible();
await expect(page.getByRole('button', { name: '安装灵感笔记' })).toBeVisible();
const requests = await app.evaluate(() => (
(globalThis as typeof globalThis & { __pluginsE2E?: { requests: string[] } }).__pluginsE2E?.requests ?? []
));
@@ -157,17 +160,21 @@ test.describe('Unified plugin workspace', () => {
await expect(page.getByTestId('project-configuration-empty-state')).toBeVisible();
await expect(page.getByTestId('project-plugins-sheet')).toBeVisible();
await expect(page.getByTestId('plugins-page')).toBeVisible();
await expect(page.getByRole('alert')).toContainText('官方目录刷新失败');
await expect(page.getByRole('alert')).toContainText('官方目录加载失败');
await expect(page.getByRole('heading', { name: 'Local Search' })).toBeVisible();
await expect(page.getByText('本机全局已启用')).toBeVisible();
await expect(page.getByText(/仅通过对话安装/)).toBeVisible();
await expect(page.getByRole('button', { name: 'Local Search:已安装' })).toBeDisabled();
await expect(page.getByRole('button', { name: /刷新/ })).toHaveCount(0);
await expect(page.getByRole('searchbox')).toHaveCount(0);
await expect(page.getByRole('combobox')).toHaveCount(0);
await expect(page.getByRole('textbox', { name: /包|路径|source/i })).toHaveCount(0);
await page.getByRole('button', { name: '查看Local Search详情' }).click();
await expect(page.getByText(/完整桌面权限/)).toBeVisible();
await page.getByRole('button', { name: '本机全局停用Local Search' }).click();
await expect(page.getByRole('button', { name: '本机全局启用Local Search' })).toBeVisible();
await page.getByRole('button', { name: '本机全局启用Local Search' }).click();
await expect(page.getByRole('button', { name: '本机全局停用Local Search' })).toBeVisible();
await expect(page.getByText('为智能体提供可在本机使用的扩展能力。')).toBeVisible();
await expect(page.getByText(/完整桌面权限/)).toHaveCount(0);
await expect(page.getByText(/Pi extension|Skill 脚本/)).toHaveCount(0);
await page.getByRole('button', { name: '停用Local Search' }).click();
await expect(page.getByRole('button', { name: '启用Local Search' })).toBeVisible();
await page.getByRole('button', { name: '启用Local Search' }).click();
await expect(page.getByRole('button', { name: '停用Local Search' })).toBeVisible();
const requests = await app.evaluate(() => (
(globalThis as typeof globalThis & { __pluginsE2E?: { requests: string[] } }).__pluginsE2E?.requests ?? []
@@ -291,24 +298,23 @@ test.describe('Unified plugin workspace', () => {
await expect.poll(async () => app.evaluate(() => (
(globalThis as typeof globalThis & { __pluginsE2E?: { requests: string[] } }).__pluginsE2E?.requests ?? []
))).toContain('GET /api/coding/plugin-marketplace/library');
const bundledCard = page.getByRole('heading', { name: '游戏资源生成' }).locator('..');
await expect(bundledCard).toContainText('账号已获取');
await expect(page.getByRole('button', { name: '游戏资源生成:随应用提供' })).toBeDisabled();
await page.getByRole('button', { name: '查看开发数据服务详情' }).click();
await expect(page.getByText('当前状态没有可执行操作。')).toBeVisible();
await expect(page.getByText('当前无需进行其他操作。')).toBeVisible();
await page.getByRole('button', { name: '关闭插件详情' }).click();
await page.getByRole('button', { name: '查看游戏资源生成详情' }).click();
await expect(page.getByRole('button', { name: '从账号移除游戏资源生成' })).toBeVisible();
await expect(page.getByRole('button', { name: /下载.*游戏资源生成|删除.*游戏资源生成.*官方设备包/ })).toHaveCount(0);
await page.getByRole('button', { name: '从账号移除游戏资源生成' }).click();
await page.getByRole('button', { name: '确认从账号移除' }).click();
await expect(page.getByRole('button', { name: '从插件库移除游戏资源生成' })).toBeVisible();
await expect(page.getByRole('button', { name: /安装.*游戏资源生成|从本机卸载.*游戏资源生成/ })).toHaveCount(0);
await page.getByRole('button', { name: '从插件库移除游戏资源生成' }).click();
await page.getByRole('button', { name: '确认移除' }).click();
await page.getByRole('button', { name: '关闭插件详情' }).click();
await page.getByRole('button', { name: '查看Local Tool详情' }).click();
await expect(page.getByRole('button', { name: '本机全局停用Local Tool' })).toBeVisible();
await expect(page.getByRole('button', { name: /账号.*Local Tool|下载.*Local Tool/ })).toHaveCount(0);
await page.getByRole('button', { name: '本机全局停用Local Tool' }).click();
await expect(page.getByRole('button', { name: '停用Local Tool' })).toBeVisible();
await expect(page.getByRole('button', { name: /插件库.*Local Tool|安装.*Local Tool/ })).toHaveCount(0);
await page.getByRole('button', { name: '停用Local Tool' }).click();
const requests = await app.evaluate(() => (
(globalThis as typeof globalThis & { __pluginsE2E?: { requests: string[] } }).__pluginsE2E?.requests ?? []

View File

@@ -4,8 +4,8 @@ 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 }) => {
test.describe('Project configuration plugin capabilities', () => {
test('merges Skill management into the plugin tabs and removes standalone local Skills', async ({ launchElectronApp }) => {
const parentPath = await mkdtemp(path.join(tmpdir(), 'makelore-project-configuration-e2e-'));
const app = await launchElectronApp({ skipSetup: true });
@@ -48,18 +48,41 @@ test.describe('Project configuration skills', () => {
expect(actionBarBottomGap).toBeLessThan(40);
await expect(page.getByRole('button', { name: '创建智能体' })).toBeVisible();
const resourceCardGeometry = await page.getByTestId('project-resource-row').locator(':scope > button').evaluateAll((cards) => cards.map((card) => {
const rect = card.getBoundingClientRect();
return { left: rect.left, top: rect.top, width: rect.width };
}));
expect(resourceCardGeometry).toHaveLength(3);
expect(Math.max(...resourceCardGeometry.map(({ top }) => top)) - Math.min(...resourceCardGeometry.map(({ top }) => top))).toBeLessThanOrEqual(1);
expect(resourceCardGeometry.every(({ width }) => width > 0)).toBe(true);
expect(resourceCardGeometry.map(({ left }) => left)).toEqual([...resourceCardGeometry.map(({ left }) => left)].sort((a, b) => a - b));
const drawerWidths: number[] = [];
for (const [resourceId, title] of [
['resource-card-models', '可用模型'],
['resource-card-knowledge', '知识文件'],
['resource-card-skills', '可用技能'],
['resource-card-plugins', '插件'],
] as const) {
await page.getByTestId(resourceId).click();
const drawer = page.getByRole('dialog', { name: title });
await expect(drawer).toBeVisible();
const drawerBox = await drawer.boundingBox();
if (!drawerBox) throw new Error(`未能读取 ${title} 抽屉的布局区域`);
drawerWidths.push(drawerBox.width);
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);
}
if (resourceId === 'resource-card-plugins') {
await expect(page.getByTestId('project-plugins-sheet')).toBeVisible();
await expect(drawer.getByTestId('plugins-page')).toBeVisible();
await expect(drawer.getByText('统一管理云端下发的插件与 Skill,以及本机安装的能力。')).toBeVisible();
await expect(drawer.getByRole('tab', { name: /可选/ })).toBeVisible();
await expect(drawer.getByRole('tab', { name: /已安装/ })).toBeVisible();
await expect(drawer.getByRole('button', { name: /刷新/ })).toHaveCount(0);
await expect(drawer.getByRole('searchbox')).toHaveCount(0);
await expect(drawer.getByRole('combobox')).toHaveCount(0);
}
const closeButton = drawer.getByRole('button', { name: '关闭' });
const closeGeometry = await closeButton.evaluate((button) => {
const icon = button.querySelector('svg');
@@ -81,6 +104,8 @@ test.describe('Project configuration skills', () => {
await closeButton.locator('svg').click();
await expect(page.getByRole('dialog', { name: title })).toHaveCount(0);
}
expect(Math.max(...drawerWidths) - Math.min(...drawerWidths)).toBeLessThanOrEqual(1);
expect(Math.max(...drawerWidths)).toBeLessThanOrEqual(430);
const now = new Date().toISOString();
await mkdir(path.join(parentPath, '.makelore'), { recursive: true });
@@ -96,7 +121,7 @@ test.describe('Project configuration skills', () => {
enabled: true,
model: { accountId: 'moonshot', modelId: 'kimi-k2.6', thinkingLevel: 'off' },
modelResolution: 'resolved',
skillIds: [],
skillIds: ['agent-browser', 'grilling'],
responsibility: { mission: '验证智能体编辑弹窗。', owns: [], boundaries: [], collaborators: [], principles: [] },
prompt: '',
archivedAt: null,
@@ -111,18 +136,12 @@ test.describe('Project configuration skills', () => {
await expect(page.getByTestId('legacy-project-identity-card')).toHaveCount(0);
await expect(page.getByText(/项目 ID|项目身份|独立副本/u)).toHaveCount(0);
await page.getByTestId('resource-card-skills').click();
await expect(page.getByTestId('resource-card-skills')).toHaveCount(0);
await expect(page.getByTestId('project-agent-e2e-agent')).toContainText('0 项插件能力');
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-agent-browser')).toHaveCount(0);
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();

View File

@@ -103,10 +103,11 @@ test.describe('Unified project plugin workspace', () => {
includeHidden: true,
})).toBeAttached();
await page.getByRole('button', { name: '查看开发数据服务详情' }).click();
await expect(page.getByText('按平台包含,不按单次插件调用扣点。')).toBeVisible();
await expect(page.getByRole('heading', { name: '生效范围' })).toBeVisible();
await expect(page.getByText(/启用当前项目后.*无需单独分配/)).toBeVisible();
await expect(page.getByRole('button', { name: '分配开发数据服务给伙伴' })).toHaveCount(0);
const pluginDialog = page.getByRole('dialog', { name: '开发数据服务' });
await expect(pluginDialog).toContainText('为当前项目提供数据。');
await expect(pluginDialog).not.toContainText('Token Point');
await expect(pluginDialog).not.toContainText('data-service.control');
await expect(page.getByRole('button', { name: '将开发数据服务分配给智能体' })).toHaveCount(0);
const localProjectId = await app.evaluate(() => (
(globalThis as typeof globalThis & { __projectPluginE2E?: { localProjectId: string } }).__projectPluginE2E?.localProjectId
));
@@ -114,15 +115,15 @@ test.describe('Unified project plugin workspace', () => {
await expect(page.getByText(localProjectId as string)).toHaveCount(0);
await expect(page.getByText('11111111-1111-4111-8111-111111111111')).toHaveCount(0);
await page.getByRole('button', { name: '启用开发数据服务到当前项目' }).click();
await expect(page.getByRole('button', { name: '从当前项目禁用开发数据服务' })).toBeVisible();
await expect(page.getByText('插件专属设置')).toBeVisible();
await page.getByRole('button', { name: '将开发数据服务添加到项目' }).click();
await expect(page.getByRole('button', { name: '从项目移除开发数据服务' })).toBeVisible();
await expect(page.getByText('使用设置')).toBeVisible();
await expect(page.getByRole('button', { name: '创建开发数据空间' })).toBeDisabled();
await page.getByRole('button', { name: '从当前项目禁用开发数据服务' }).click();
await expect(page.getByRole('alertdialog')).toContainText('从当前项目禁用开发数据服务');
await page.getByRole('button', { name: '确认从当前项目禁用' }).click();
await expect(page.getByRole('button', { name: '启用开发数据服务到当前项目' })).toBeVisible();
await page.getByRole('button', { name: '从项目移除开发数据服务' }).click();
await expect(page.getByRole('alertdialog')).toContainText('从当前项目移除开发数据服务');
await page.getByRole('button', { name: '确认从项目移除' }).click();
await expect(page.getByRole('button', { name: '将开发数据服务添加到项目' })).toBeVisible();
const state = await app.evaluate(() => (
(globalThis as typeof globalThis & {