合并远程主分支客户端收口
This commit is contained in:
@@ -91,6 +91,36 @@ async function installSlashCommandHost(
|
||||
title: 'Slash E2E',
|
||||
agent: 'game-development',
|
||||
};
|
||||
const agent = {
|
||||
id: 'game-development',
|
||||
avatarId: 'avatar-01',
|
||||
roleName: 'Game Development',
|
||||
name: 'Game Development',
|
||||
builtIn: false,
|
||||
enabled: true,
|
||||
model: 'niancode-user-models/qwen3.7-plus',
|
||||
skillIds: [],
|
||||
responsibility: {
|
||||
mission: 'Implement and verify project features.',
|
||||
owns: [],
|
||||
boundaries: [],
|
||||
collaborators: [],
|
||||
principles: [],
|
||||
},
|
||||
prompt: '',
|
||||
archivedAt: null,
|
||||
pinned: false,
|
||||
};
|
||||
const config = {
|
||||
schemaVersion: 1,
|
||||
projectType: 'custom',
|
||||
initialized: true,
|
||||
defaultModel: 'niancode-user-models/qwen3.7-plus',
|
||||
agents: [agent],
|
||||
knowledgeDirectory: 'knowledge',
|
||||
createdAt: '2026-07-18T00:00:00.000Z',
|
||||
updatedAt: '2026-07-18T00:00:00.000Z',
|
||||
};
|
||||
const status = {
|
||||
state: 'running',
|
||||
port: 4096,
|
||||
@@ -149,13 +179,30 @@ async function installSlashCommandHost(
|
||||
}
|
||||
if (path.startsWith('/api/opencode/projects/config?')) {
|
||||
return respond({
|
||||
status: 'missing',
|
||||
status: 'valid',
|
||||
config,
|
||||
knowledgeFiles: [],
|
||||
});
|
||||
}
|
||||
if (path.startsWith('/api/opencode/projects/template?')) {
|
||||
return respond({ status: 'missing' });
|
||||
}
|
||||
if (path.startsWith('/api/opencode/projects/conversations?')) {
|
||||
return respond({
|
||||
state: {
|
||||
schemaVersion: 1,
|
||||
sessions: [{
|
||||
sessionId: session.id,
|
||||
agentId: agent.id,
|
||||
archivedAt: null,
|
||||
unreadCount: 0,
|
||||
createdAt: '2026-07-18T00:00:00.000Z',
|
||||
updatedAt: '2026-07-18T00:00:00.000Z',
|
||||
}],
|
||||
updatedAt: '2026-07-18T00:00:00.000Z',
|
||||
},
|
||||
});
|
||||
}
|
||||
if (path === '/api/opencode/config-summary') {
|
||||
return respond({
|
||||
model: 'niancode-user-models/qwen3.7-plus',
|
||||
@@ -166,16 +213,30 @@ async function installSlashCommandHost(
|
||||
});
|
||||
}
|
||||
if (path === '/api/provider-accounts') {
|
||||
return respond([]);
|
||||
return respond([{
|
||||
id: 'niancode-user-models',
|
||||
vendorId: 'custom',
|
||||
label: 'Makelore Models',
|
||||
authMode: 'api_key',
|
||||
model: 'qwen3.7-plus',
|
||||
enabled: true,
|
||||
isDefault: true,
|
||||
createdAt: '2026-07-18T00:00:00.000Z',
|
||||
updatedAt: '2026-07-18T00:00:00.000Z',
|
||||
}]);
|
||||
}
|
||||
if (path === '/api/provider-accounts/key-info') {
|
||||
return respond([]);
|
||||
return respond([{
|
||||
accountId: 'niancode-user-models',
|
||||
hasKey: true,
|
||||
keyMasked: 'sk-***',
|
||||
}]);
|
||||
}
|
||||
if (path === '/api/provider-vendors') {
|
||||
return respond([]);
|
||||
}
|
||||
if (path === '/api/provider-accounts/default') {
|
||||
return respond({ accountId: null });
|
||||
return respond({ accountId: 'niancode-user-models' });
|
||||
}
|
||||
if (path === '/api/opencode/sessions') {
|
||||
return respond({ sessions: [session] });
|
||||
@@ -270,18 +331,25 @@ 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');
|
||||
await expect(composer).toBeVisible();
|
||||
|
||||
await composer.fill('/comp');
|
||||
await expect(page.getByRole('option', { name: /compact/ })).toBeVisible();
|
||||
await composer.press('Enter');
|
||||
await expect(composer).toHaveValue('/compact');
|
||||
await expect(
|
||||
page.getByText('再次按 Enter 执行'),
|
||||
).toBeVisible();
|
||||
expect(await readCapturedRequests(electronApp)).toEqual([]);
|
||||
expect((await readCapturedRequests(electronApp)).some((request) => (
|
||||
request.path.endsWith('/summarize')
|
||||
|| request.path.endsWith('/command')
|
||||
|| request.path.endsWith('/messages')
|
||||
))).toBe(false);
|
||||
|
||||
await composer.press('Enter');
|
||||
await expect(composer).toHaveValue('');
|
||||
@@ -310,16 +378,20 @@ test.describe('OpenCode slash commands', () => {
|
||||
);
|
||||
|
||||
await setSlashCommandFailure(electronApp, 'review failed');
|
||||
const commandCountBeforeFailure = (await readCapturedRequests(electronApp))
|
||||
.filter((request) => request.path.endsWith('/command')).length;
|
||||
await composer.fill('/Rev');
|
||||
await expect(page.getByRole('option', { name: /Review/i })).toBeVisible();
|
||||
await composer.fill('/Review staged changes ');
|
||||
await composer.press('Enter');
|
||||
await expect(page.getByText('review failed')).toBeVisible();
|
||||
await expect.poll(async () => (
|
||||
await readCapturedRequests(electronApp)
|
||||
).filter((request) => request.path.endsWith('/command')).length)
|
||||
.toBe(commandCountBeforeFailure + 1);
|
||||
await expect(composer).toHaveValue(
|
||||
'/Review staged changes ',
|
||||
);
|
||||
|
||||
await setSlashCommandFailure(electronApp, null);
|
||||
await composer.press('Enter');
|
||||
await expect(composer).toHaveValue('');
|
||||
const command = (await readCapturedRequests(electronApp))
|
||||
.findLast((request) => request.path.endsWith('/command'));
|
||||
expect(command?.body).toMatchObject({
|
||||
@@ -329,4 +401,94 @@ test.describe('OpenCode slash commands', () => {
|
||||
model: 'niancode-user-models/qwen3.7-plus',
|
||||
});
|
||||
});
|
||||
|
||||
test('keeps the project and conversation rails at the minimum width without covering the chat canvas', async ({
|
||||
electronApp,
|
||||
page,
|
||||
}) => {
|
||||
await completeSetup(page);
|
||||
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();
|
||||
|
||||
const createPartnerButton = page.getByRole('button', { name: '创建伙伴' });
|
||||
await expect(createPartnerButton).toHaveClass(/text-brand/);
|
||||
await expect(createPartnerButton).toHaveAttribute('aria-expanded', 'false');
|
||||
await createPartnerButton.click();
|
||||
const createPartnerDialog = page.getByRole('dialog', { name: '创建项目伙伴' });
|
||||
await expect(createPartnerDialog).toBeVisible();
|
||||
await expect(createPartnerDialog).toHaveCSS('z-index', '110');
|
||||
await createPartnerDialog.getByLabel(/伙伴名称/).fill('E2E层级检查');
|
||||
await page.getByRole('button', { name: '取消' }).click();
|
||||
await expect(createPartnerButton).toHaveClass(/text-brand/);
|
||||
|
||||
await expect(page.getByRole('heading', { name: '我的项目空间' })).toHaveCount(0);
|
||||
await page.getByTestId('agent-browser-panel-open').click();
|
||||
await expect(page.getByTestId('agent-browser-panel')).toBeVisible();
|
||||
const diagnostics = page.getByTestId('agent-browser-diagnostics');
|
||||
await expect(diagnostics).toHaveAttribute('data-state', 'closed');
|
||||
await page.getByRole('tab', { name: /Console/ }).click();
|
||||
await expect(diagnostics).toHaveAttribute('data-state', 'open');
|
||||
await expect(diagnostics).toHaveCSS('height', '260px');
|
||||
await page.getByRole('button', { name: '收起调试面板' }).click();
|
||||
await expect(diagnostics).toHaveAttribute('data-state', 'closed');
|
||||
|
||||
const rect = async (testId: string) => page.getByTestId(testId).evaluate((element) => {
|
||||
const box = element.getBoundingClientRect();
|
||||
return { top: box.top, left: box.left, right: box.right, width: box.width };
|
||||
});
|
||||
const [layout, project, conversations, conversationHeader, conversationContext, chat, browser] = await Promise.all([
|
||||
rect('opencode-chat-layout'),
|
||||
rect('sidebar'),
|
||||
rect('agent-conversation-sidebar'),
|
||||
rect('agent-conversation-sidebar-header'),
|
||||
rect('agent-conversation-dialog-context'),
|
||||
rect('opencode-chat-canvas'),
|
||||
rect('agent-browser-panel'),
|
||||
]);
|
||||
const isHeaderInsideConversationSidebar = async () => page.getByTestId('agent-conversation-sidebar').evaluate((sidebar) => sidebar.contains(document.querySelector('[data-testid="agent-conversation-sidebar-header"]')));
|
||||
|
||||
expect(project.width).toBeGreaterThanOrEqual(255);
|
||||
expect(project.width).toBeLessThan(257);
|
||||
expect(conversations.width).toBeGreaterThanOrEqual(255);
|
||||
expect(conversations.width).toBeLessThan(257);
|
||||
expect(Math.abs(conversations.top - project.top)).toBeLessThan(2);
|
||||
expect(Math.abs(conversationHeader.top)).toBeLessThan(2);
|
||||
expect(Math.abs(conversationHeader.left - conversations.left)).toBeLessThan(2);
|
||||
expect(Math.abs(conversationHeader.width - conversations.width)).toBeLessThan(2);
|
||||
expect(Math.abs(conversationContext.top)).toBeLessThan(2);
|
||||
expect(Math.abs(conversationContext.left - chat.left)).toBeLessThan(2);
|
||||
expect(await isHeaderInsideConversationSidebar()).toBe(false);
|
||||
expect(Math.abs(conversations.left - project.right)).toBeLessThan(2);
|
||||
expect(Math.abs(chat.left - conversations.right)).toBeLessThan(2);
|
||||
expect(Math.abs(browser.right - layout.right)).toBeLessThan(2);
|
||||
expect(browser.left).toBeGreaterThanOrEqual(chat.right - 1);
|
||||
|
||||
const readRailAlignment = async () => {
|
||||
const [collapsedProject, collapsedConversations] = await Promise.all([
|
||||
rect('sidebar'),
|
||||
rect('agent-conversation-sidebar'),
|
||||
]);
|
||||
return {
|
||||
projectWidth: collapsedProject.width,
|
||||
conversationLeft: collapsedConversations.left,
|
||||
projectRight: collapsedProject.right,
|
||||
alignmentDelta: Math.abs(collapsedConversations.left - collapsedProject.right),
|
||||
};
|
||||
};
|
||||
|
||||
await page.getByRole('button', { name: '折叠侧栏' }).click();
|
||||
await expect.poll(async () => (await readRailAlignment()).projectWidth).toBeLessThan(1);
|
||||
await expect.poll(async () => (await readRailAlignment()).conversationLeft).toBeLessThan(2);
|
||||
await expect.poll(isHeaderInsideConversationSidebar).toBe(false);
|
||||
|
||||
await page.getByRole('button', { name: '展开侧栏' }).click();
|
||||
await expect.poll(async () => (await readRailAlignment()).alignmentDelta).toBeLessThan(2);
|
||||
await expect.poll(async () => (await readRailAlignment()).projectWidth).toBeGreaterThan(255);
|
||||
await expect.poll(isHeaderInsideConversationSidebar).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user