feat: simplify code project creation
This commit is contained in:
@@ -3,7 +3,7 @@ import { tmpdir } from 'node:os';
|
||||
import path from 'node:path';
|
||||
import { closeElectronApp, expect, getStableWindow, test } from './fixtures/electron';
|
||||
|
||||
const BOUND_PROJECT_ID = 'aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa';
|
||||
const CANONICAL_PROJECT_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/u;
|
||||
|
||||
async function readProjectConfig(projectPath: string): Promise<Record<string, unknown>> {
|
||||
return JSON.parse(await readFile(path.join(projectPath, '.makelore', 'project.json'), 'utf8')) as Record<string, unknown>;
|
||||
@@ -25,90 +25,50 @@ async function selectProgrammingProjectFolder(
|
||||
}
|
||||
|
||||
test.describe('Coding project identity UX', () => {
|
||||
test('supports default create, bind validation, cancellation, legacy resolution, and independent copies', async ({ launchElectronApp }) => {
|
||||
const projectPath = await mkdtemp(path.join(tmpdir(), 'niancode-identity-e2e-'));
|
||||
test('keeps the service identity automatic and invisible to young users', async ({ launchElectronApp }) => {
|
||||
const projectPath = await mkdtemp(path.join(tmpdir(), 'makelore-identity-e2e-'));
|
||||
const app = await launchElectronApp({ skipSetup: true });
|
||||
|
||||
try {
|
||||
const page = await selectProgrammingProjectFolder(app, projectPath);
|
||||
await page.getByTestId('sidebar-create-project').click();
|
||||
const createDialog = page.getByRole('dialog', { name: '新建项目' });
|
||||
await expect(createDialog.getByRole('radio', { name: '创建新的项目 ID' })).toBeChecked();
|
||||
|
||||
await createDialog.getByRole('radio', { name: '绑定已有项目 ID' }).click();
|
||||
await createDialog.getByRole('textbox', { name: '已有项目 ID' }).fill('not-a-project-id');
|
||||
await expect(createDialog.getByText(/项目 ID|项目身份|独立副本/u)).toHaveCount(0);
|
||||
await expect(createDialog.getByRole('radio', { name: '创建新的项目 ID' })).toHaveCount(0);
|
||||
await expect(createDialog.getByRole('radio', { name: '绑定已有项目 ID' })).toHaveCount(0);
|
||||
await createDialog.getByRole('button', { name: '选择路径' }).click();
|
||||
await expect(createDialog.getByLabel('项目路径')).toHaveValue(projectPath);
|
||||
await createDialog.getByRole('button', { name: '确认创建' }).click();
|
||||
await expect(createDialog).toContainText('请输入有效的项目 ID(小写 UUID)。');
|
||||
await createDialog.getByRole('button', { name: '取消' }).first().click();
|
||||
await expect(page.getByRole('dialog', { name: '新建项目' })).toHaveCount(0);
|
||||
|
||||
await page.getByTestId('sidebar-create-project').click();
|
||||
const defaultCreateDialog = page.getByRole('dialog', { name: '新建项目' });
|
||||
await expect(defaultCreateDialog.getByRole('radio', { name: '创建新的项目 ID' })).toBeChecked();
|
||||
await defaultCreateDialog.getByRole('button', { name: '选择路径' }).click();
|
||||
await expect(defaultCreateDialog.getByLabel('项目路径')).toHaveValue(projectPath);
|
||||
await defaultCreateDialog.getByRole('button', { name: '确认创建' }).click();
|
||||
|
||||
await expect(page.getByTestId('project-configuration-page')).toBeVisible();
|
||||
await expect(page.getByTestId('coding-chat-empty-agent')).toBeVisible();
|
||||
const createdConfig = await readProjectConfig(projectPath);
|
||||
const generatedProjectId = createdConfig.projectId;
|
||||
expect(generatedProjectId).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/u);
|
||||
await expect(page.getByTestId('project-identity-value')).toHaveText(String(generatedProjectId));
|
||||
await expect(page.getByText('同一账号绑定相同项目 ID 会共享开发数据;项目 ID 不是凭据。')).toBeVisible();
|
||||
const generatedProjectId = String(createdConfig.projectId);
|
||||
expect(generatedProjectId).toMatch(CANONICAL_PROJECT_ID);
|
||||
|
||||
await writeFile(path.join(projectPath, 'identity-sentinel.txt'), 'keep this file in place');
|
||||
const independentButton = page.getByTestId('independent-copy-button');
|
||||
await independentButton.click();
|
||||
const independentDialog = page.getByRole('dialog', { name: '设为独立副本?' });
|
||||
await expect(independentDialog).toContainText('不会移动或复制文件');
|
||||
await expect(independentDialog).toContainText('不会复制云端开发数据');
|
||||
await independentDialog.getByRole('button', { name: '取消' }).click();
|
||||
await expect(independentDialog).toHaveCount(0);
|
||||
expect((await readProjectConfig(projectPath)).projectId).toBe(generatedProjectId);
|
||||
|
||||
await independentButton.click();
|
||||
await page.getByRole('dialog', { name: '设为独立副本?' }).getByRole('button', { name: '确认设为独立副本' }).click();
|
||||
await expect(page.getByTestId('project-identity-value')).not.toHaveText(String(generatedProjectId));
|
||||
const independentConfig = await readProjectConfig(projectPath);
|
||||
expect(independentConfig.projectId).not.toBe(generatedProjectId);
|
||||
expect(await readFile(path.join(projectPath, 'identity-sentinel.txt'), 'utf8')).toBe('keep this file in place');
|
||||
await page.getByRole('button', { name: '创建项目智能体' }).click();
|
||||
await expect(page.getByTestId('project-configuration-page')).toBeVisible();
|
||||
await expect(page.getByTestId('project-identity-card')).toHaveCount(0);
|
||||
await expect(page.getByTestId('legacy-project-identity-card')).toHaveCount(0);
|
||||
await expect(page.getByTestId('independent-copy-button')).toHaveCount(0);
|
||||
await expect(page.getByText(/项目 ID|项目身份|独立副本/u)).toHaveCount(0);
|
||||
|
||||
const legacyConfig = await readProjectConfig(projectPath);
|
||||
delete legacyConfig.projectId;
|
||||
await writeFile(path.join(projectPath, '.makelore', 'project.json'), JSON.stringify(legacyConfig, null, 2));
|
||||
await writeFile(
|
||||
path.join(projectPath, '.makelore', 'project.json'),
|
||||
JSON.stringify(legacyConfig, null, 2),
|
||||
);
|
||||
await page.reload();
|
||||
await expect(page.getByTestId('legacy-project-identity-card')).toBeVisible();
|
||||
|
||||
const legacyCard = page.getByTestId('legacy-project-identity-card');
|
||||
await legacyCard.getByRole('radio', { name: '绑定已有项目 ID' }).click();
|
||||
await legacyCard.getByRole('textbox', { name: '已有项目 ID' }).fill(BOUND_PROJECT_ID);
|
||||
await legacyCard.getByTestId('resolve-project-identity-button').click();
|
||||
await expect(page.getByTestId('project-identity-value')).toHaveText(BOUND_PROJECT_ID);
|
||||
await expect(page.getByTestId('legacy-project-identity-card')).toHaveCount(0);
|
||||
} finally {
|
||||
await closeElectronApp(app);
|
||||
await rm(projectPath, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test('creates a project bound to an explicit canonical project ID', async ({ launchElectronApp }) => {
|
||||
const projectPath = await mkdtemp(path.join(tmpdir(), 'niancode-bound-identity-e2e-'));
|
||||
const app = await launchElectronApp({ skipSetup: true });
|
||||
|
||||
try {
|
||||
const page = await selectProgrammingProjectFolder(app, projectPath);
|
||||
await page.getByTestId('sidebar-create-project').click();
|
||||
const createDialog = page.getByRole('dialog', { name: '新建项目' });
|
||||
await createDialog.getByRole('radio', { name: '绑定已有项目 ID' }).click();
|
||||
await expect(createDialog.getByText('同一账号绑定相同项目 ID 会共享开发数据;项目 ID 不是凭据。')).toBeVisible();
|
||||
await createDialog.getByRole('textbox', { name: '已有项目 ID' }).fill(BOUND_PROJECT_ID);
|
||||
await createDialog.getByRole('button', { name: '选择路径' }).click();
|
||||
await createDialog.getByRole('button', { name: '确认创建' }).click();
|
||||
|
||||
await expect(page.getByTestId('project-configuration-page')).toBeVisible();
|
||||
await expect(page.getByTestId('project-identity-value')).toHaveText(BOUND_PROJECT_ID);
|
||||
expect((await readProjectConfig(projectPath)).projectId).toBe(BOUND_PROJECT_ID);
|
||||
const repairedConfig = await readProjectConfig(projectPath);
|
||||
const repairedProjectId = String(repairedConfig.projectId);
|
||||
expect(repairedProjectId).toMatch(CANONICAL_PROJECT_ID);
|
||||
expect(repairedProjectId).not.toBe(generatedProjectId);
|
||||
await expect(page.getByTestId('project-identity-card')).toHaveCount(0);
|
||||
await expect(page.getByTestId('legacy-project-identity-card')).toHaveCount(0);
|
||||
await expect(page.getByText(/项目 ID|项目身份|独立副本/u)).toHaveCount(0);
|
||||
} finally {
|
||||
await closeElectronApp(app);
|
||||
await rm(projectPath, { recursive: true, force: true });
|
||||
|
||||
@@ -20,8 +20,10 @@ test.describe('Project configuration skills', () => {
|
||||
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: '交互式 AI 应用' })).toBeChecked();
|
||||
await expect(page.getByRole('radio', { name: '自定义项目' })).not.toBeChecked();
|
||||
await expect(page.getByRole('radio', { name: '创建新的项目 ID' })).toHaveCount(0);
|
||||
await expect(page.getByRole('radio', { name: '绑定已有项目 ID' })).toHaveCount(0);
|
||||
await expect(page.getByRole('radio', { name: '交互式 AI 应用' })).toHaveCount(0);
|
||||
await expect(page.getByRole('radio', { name: '自定义项目' })).toHaveCount(0);
|
||||
await expect(page.getByRole('radio', { name: '小游戏' })).toHaveCount(0);
|
||||
await expect(page.getByRole('radio', { name: '小程序' })).toHaveCount(0);
|
||||
await expect(page.getByRole('radio', { name: '直接使用所选文件夹(默认)' })).toBeChecked();
|
||||
@@ -29,7 +31,16 @@ test.describe('Project configuration skills', () => {
|
||||
await expect(page.getByLabel('项目路径')).toHaveValue(parentPath);
|
||||
await page.getByRole('button', { name: '确认创建' }).click();
|
||||
|
||||
await expect(page.getByTestId('coding-chat-empty-agent')).toBeVisible();
|
||||
await expect(page.getByTestId('project-initialization-gate')).toHaveCount(0);
|
||||
await page.getByRole('button', { name: '创建项目智能体' }).click();
|
||||
await expect(page.getByTestId('project-configuration-page')).toBeVisible();
|
||||
await expect(page.getByTestId('project-identity-card')).toHaveCount(0);
|
||||
await expect(page.getByTestId('legacy-project-identity-card')).toHaveCount(0);
|
||||
await expect(page.getByTestId('independent-copy-button')).toHaveCount(0);
|
||||
await expect(page.getByText(/项目 ID|项目身份|独立副本/u)).toHaveCount(0);
|
||||
await expect(page.getByRole('button', { name: '保存项目配置' })).toBeVisible();
|
||||
await expect(page.getByRole('button', { name: '确认并完成初始化' })).toHaveCount(0);
|
||||
await expect(page.getByTestId('sidebar-nav-publish')).toHaveCount(0);
|
||||
await expect(page.getByText(/新项目还没有智能体/)).toHaveCount(0);
|
||||
await expect(page.getByTestId('project-configuration-actions')).toBeVisible();
|
||||
@@ -96,6 +107,9 @@ test.describe('Project configuration skills', () => {
|
||||
}, null, 2));
|
||||
await page.reload();
|
||||
await expect(page.getByTestId('project-configuration-page')).toBeVisible();
|
||||
await expect(page.getByTestId('project-identity-card')).toHaveCount(0);
|
||||
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('superpowers-card')).toHaveCount(0);
|
||||
|
||||
Reference in New Issue
Block a user