Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
206 changes: 206 additions & 0 deletions wework/src/components/layout/DesktopWorkbenchLayout.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { act, fireEvent, render, screen, waitFor, within } from '@testing-librar
import userEvent from '@testing-library/user-event'
import { beforeEach, describe, expect, test, vi } from 'vitest'
import { createDeviceApi } from '@/api/devices'
import { createProjectApi } from '@/api/projects'
import { createQuotaApi } from '@/api/quota'
import '@/i18n'
import { TITLEBAR_ACTIONS_PORTAL_ID } from '@/components/topnav/TitlebarActionsPortal'
Expand Down Expand Up @@ -45,13 +46,19 @@ vi.mock('@/api/devices', () => ({
createDeviceApi: vi.fn(),
}))

vi.mock('@/api/projects', () => ({
createProjectApi: vi.fn(),
}))

vi.mock('@/api/quota', () => ({
createQuotaApi: vi.fn(),
}))

const createDeviceApiMock = vi.mocked(createDeviceApi)
const createProjectApiMock = vi.mocked(createProjectApi)
const createQuotaApiMock = vi.mocked(createQuotaApi)
const fetchQuotaMock = vi.fn()
const startCodeServerSessionMock = vi.fn()

describe('DesktopWorkbenchLayout', () => {
function createDeferred<T>() {
Expand Down Expand Up @@ -139,6 +146,13 @@ describe('DesktopWorkbenchLayout', () => {
fetchQuota: fetchQuotaMock,
})
createDeviceApiMock.mockReturnValue(createMockDeviceApi() as never)
startCodeServerSessionMock.mockResolvedValue({
url: 'http://localhost/ide',
path: '/workspace/projects/github_wegent',
})
createProjectApiMock.mockReturnValue({
startCodeServerSession: startCodeServerSessionMock,
} as unknown as ReturnType<typeof createProjectApi>)
})

const baseProps = {
Expand Down Expand Up @@ -617,6 +631,198 @@ describe('DesktopWorkbenchLayout', () => {
)
})

test('opens project code-server from the Tauri titlebar', async () => {
Object.defineProperty(window, '__TAURI_INTERNALS__', {
configurable: true,
value: {},
})
const openSpy = vi.spyOn(window, 'open').mockImplementation(() => null)

render(
<DesktopWorkbenchLayout
{...baseProps}
state={{
...baseProps.state,
currentProject: {
id: 1,
name: 'github_wegent',
config: {
mode: 'workspace',
execution: {
targetType: 'local',
deviceId: '24a59054-4638-4744-983d-372706c30fcd',
},
},
tasks: [],
},
devices: [
{
id: 1,
device_id: '24a59054-4638-4744-983d-372706c30fcd',
name: 'cloud executor',
status: 'online',
is_default: false,
device_type: 'cloud',
bind_shell: 'claudecode',
executor_version: '1.8.5',
},
],
}}
/>
)

await userEvent.click(screen.getByTestId('open-code-server-titlebar-button'))

await waitFor(() => expect(startCodeServerSessionMock).toHaveBeenCalledWith(1))
expect(openSpy).toHaveBeenCalledWith('http://localhost/ide', '_blank', 'noopener')
expect(screen.getByTestId('titlebar-actions')).toContainElement(
screen.getByTestId('open-code-server-titlebar-button')
)
expect(screen.getByTestId('environment-info-button')).toHaveAttribute('title', '环境信息')
expect(screen.getByTestId('open-code-server-titlebar-button')).toHaveAttribute(
'title',
'打开项目 IDE'
)
expect(screen.getByTestId('toggle-bottom-workspace-panel-button')).toHaveAttribute(
'title',
'打开底部栏'
)
expect(screen.getByTestId('toggle-right-workspace-panel-button')).toHaveAttribute(
'title',
'打开右侧栏'
)
})

test('shows project code-server in the Tauri titlebar before devices hydrate', () => {
Object.defineProperty(window, '__TAURI_INTERNALS__', {
configurable: true,
value: {},
})

render(
<DesktopWorkbenchLayout
{...baseProps}
state={{
...baseProps.state,
currentProject: {
id: 1,
name: 'github_wegent',
config: {
mode: 'workspace',
execution: {
targetType: 'local',
deviceId: '24a59054-4638-4744-983d-372706c30fcd',
},
},
tasks: [],
},
devices: [],
}}
/>
)

expect(screen.getByTestId('titlebar-actions')).toContainElement(
screen.getByTestId('open-code-server-titlebar-button')
)
})

test('keeps project code-server disabled for local devices', async () => {
Object.defineProperty(window, '__TAURI_INTERNALS__', {
configurable: true,
value: {},
})

render(
<DesktopWorkbenchLayout
{...baseProps}
state={{
...baseProps.state,
currentProject: {
id: 1,
name: 'github_wegent',
config: {
mode: 'workspace',
execution: {
targetType: 'local',
deviceId: 'local-claude',
},
},
tasks: [],
},
devices: [
{
id: 1,
device_id: 'local-claude',
name: 'local claude',
status: 'online',
is_default: false,
device_type: 'local',
bind_shell: 'claudecode',
executor_version: '1.8.5',
},
],
}}
/>
)

const button = screen.getByTestId('open-code-server-titlebar-button')
expect(button).toBeDisabled()
expect(button).toHaveAttribute('title', '项目 IDE 仅支持云设备')

await userEvent.click(button)

expect(startCodeServerSessionMock).not.toHaveBeenCalled()
})

test('shows a dialog when project code-server fails to start', async () => {
Object.defineProperty(window, '__TAURI_INTERNALS__', {
configurable: true,
value: {},
})
startCodeServerSessionMock.mockRejectedValueOnce(
new Error('Local devices do not support terminal or code-server sessions')
)

render(
<DesktopWorkbenchLayout
{...baseProps}
state={{
...baseProps.state,
currentProject: {
id: 1,
name: 'github_wegent',
config: {
mode: 'workspace',
execution: {
targetType: 'local',
deviceId: '24a59054-4638-4744-983d-372706c30fcd',
},
},
tasks: [],
},
devices: [
{
id: 1,
device_id: '24a59054-4638-4744-983d-372706c30fcd',
name: 'cloud executor',
status: 'online',
is_default: false,
device_type: 'cloud',
bind_shell: 'claudecode',
executor_version: '1.8.5',
},
],
}}
/>
)

await userEvent.click(screen.getByTestId('open-code-server-titlebar-button'))

expect(await screen.findByTestId('code-server-error-dialog')).toHaveTextContent(
'Local devices do not support terminal or code-server sessions'
)
})

test('keeps workspace panel actions in the page topbar on web', () => {
render(<DesktopWorkbenchLayout {...baseProps} />)

Expand Down
2 changes: 2 additions & 0 deletions wework/src/components/layout/DesktopWorkbenchMain.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,8 @@ export function DesktopWorkbenchMain({
: t('workbench.empty_title', '我们该做什么?')
const workspacePanelActions = (
<WorkspacePanelActions
currentProject={currentProject}
devices={devices}
environmentInfo={environmentInfo}
onRefreshEnvironmentInfo={onRefreshEnvironmentInfo}
onCommitEnvironmentChanges={onCommitEnvironmentChanges}
Expand Down
37 changes: 13 additions & 24 deletions wework/src/components/layout/EnvironmentInfoPopover.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,4 @@
import {
CircleDot,
GitCommit,
GitPullRequest,
Info,
Laptop,
Settings,
} from 'lucide-react'
import { CircleDot, GitCommit, GitPullRequest, Info, Laptop, Settings } from 'lucide-react'
import { useEffect, useRef, useState, type FormEvent, type ReactNode } from 'react'
import { BranchSelector } from '@/components/common/BranchSelector'
import { useTranslation } from '@/hooks/useTranslation'
Expand Down Expand Up @@ -80,6 +73,7 @@ export function EnvironmentInfoPopover({
const shortDeviceId = formatDeviceId(info.deviceId)
const deviceTitle = info.deviceId ? `${executionLabel} · ${info.deviceId}` : executionLabel
const canShowBranchSelector = Boolean(info.branchName?.trim())
const environmentInfoLabel = t('workbench.environment_info')
function handleCreatePullRequest() {
if (!info.createPullRequestUrl) {
return
Expand Down Expand Up @@ -114,7 +108,11 @@ export function EnvironmentInfoPopover({
window.setTimeout(() => setCommitStatus('idle'), 1600)
} catch (error) {
setCommitStatus('idle')
setCommitError(error instanceof Error ? error.message : t('workbench.environment_commit_failed', '提交失败'))
setCommitError(
error instanceof Error
? error.message
: t('workbench.environment_commit_failed', '提交失败')
)
}
}

Expand Down Expand Up @@ -148,12 +146,10 @@ export function EnvironmentInfoPopover({
type="button"
data-testid="environment-info-button"
onClick={handleToggleOpen}
className={cn(
DESKTOP_TOP_BAR_BUTTON_CLASS,
open && 'bg-muted text-text-primary',
)}
className={cn(DESKTOP_TOP_BAR_BUTTON_CLASS, open && 'bg-muted text-text-primary')}
aria-expanded={open}
aria-label={t('workbench.environment_info', '环境信息')}
aria-label={environmentInfoLabel}
title={environmentInfoLabel}
>
<Info />
</button>
Expand Down Expand Up @@ -199,9 +195,7 @@ export function EnvironmentInfoPopover({
<span className="flex h-[18px] w-[18px] shrink-0 items-center justify-center text-text-secondary">
<Laptop className="h-[18px] w-[18px]" />
</span>
<span className="shrink-0">
{executionLabel}
</span>
<span className="shrink-0">{executionLabel}</span>
<span
data-testid="environment-device-id"
className="ml-auto min-w-0 flex-1 overflow-hidden text-ellipsis whitespace-nowrap text-right font-mono text-xs text-text-secondary"
Expand Down Expand Up @@ -248,10 +242,7 @@ export function EnvironmentInfoPopover({
)}
</button>
{commitFormOpen && (
<form
className="mb-2 ml-[30px] mt-1 space-y-2"
onSubmit={handleSubmitCommit}
>
<form className="mb-2 ml-[30px] mt-1 space-y-2" onSubmit={handleSubmitCommit}>
<input
data-testid="environment-commit-message-input"
value={commitMessage}
Expand Down Expand Up @@ -302,9 +293,7 @@ export function EnvironmentInfoPopover({
</div>

{info.error && (
<p className="mt-2 rounded-md bg-red-50 px-3 py-2 text-xs text-red-600">
{info.error}
</p>
<p className="mt-2 rounded-md bg-red-50 px-3 py-2 text-xs text-red-600">{info.error}</p>
)}
{commitError && (
<p className="mt-2 rounded-md bg-red-50 px-3 py-2 text-xs text-red-600">
Expand Down
Loading
Loading