Files
makelore/tests/e2e/coding-project-identity.spec.ts

118 lines
6.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
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';
async function readProjectConfig(projectPath: string): Promise<Record<string, unknown>> {
return JSON.parse(await readFile(path.join(projectPath, '.niancode', 'project.json'), 'utf8')) as Record<string, unknown>;
}
async function selectProgrammingProjectFolder(
app: Parameters<typeof closeElectronApp>[0],
projectPath: string,
) {
await app.evaluate(({ ipcMain }, selectedPath) => {
ipcMain.removeHandler('dialog:open');
ipcMain.handle('dialog:open', async () => ({ canceled: false, filePaths: [selectedPath] }));
}, projectPath);
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();
return page;
}
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-'));
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 createDialog.getByRole('button', { name: '选择路径' }).click();
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();
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();
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');
const legacyConfig = await readProjectConfig(projectPath);
delete legacyConfig.projectId;
await writeFile(path.join(projectPath, '.niancode', '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);
} finally {
await closeElectronApp(app);
await rm(projectPath, { recursive: true, force: true });
}
});
});