mirror of
https://github.com/stablyai/orca.git
synced 2026-09-22 08:02:28 +00:00
refactor(workspaces): remove replaced host combobox (#13507)
This commit is contained in:
@@ -1502,12 +1502,6 @@
|
||||
"auto.components.new.workspace.ProjectCombobox.search": {
|
||||
"ko": "프로젝트 검색..."
|
||||
},
|
||||
"auto.components.new.workspace.ProjectHostSetupCombobox.empty": {
|
||||
"ko": "이 프로젝트에 준비된 호스트가 없습니다."
|
||||
},
|
||||
"auto.components.new.workspace.ProjectHostSetupCombobox.placeholder": {
|
||||
"ko": "호스트 선택"
|
||||
},
|
||||
"auto.components.notification.sound.options.0acd3d384e": {
|
||||
"ko": "클랙"
|
||||
},
|
||||
|
||||
@@ -1,157 +0,0 @@
|
||||
// @vitest-environment happy-dom
|
||||
|
||||
import React, { act } from 'react'
|
||||
import { createRoot, type Root } from 'react-dom/client'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import type {
|
||||
NeedsSetupProjectHostOption,
|
||||
ProjectHostSetupOption
|
||||
} from '@/lib/project-host-setup-options'
|
||||
import ProjectHostSetupCombobox from './ProjectHostSetupCombobox'
|
||||
|
||||
vi.mock('@/components/ui/popover', () => ({
|
||||
Popover: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||||
PopoverContent: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||||
PopoverTrigger: ({ children }: { children: React.ReactNode }) => <>{children}</>
|
||||
}))
|
||||
|
||||
vi.mock('@/components/ui/command', () => ({
|
||||
Command: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||||
CommandEmpty: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||||
CommandList: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||||
CommandItem: ({
|
||||
children,
|
||||
disabled,
|
||||
onSelect,
|
||||
value
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
disabled?: boolean
|
||||
onSelect?: (value: string) => void
|
||||
value: string
|
||||
}) => (
|
||||
<button
|
||||
type="button"
|
||||
data-command-value={value}
|
||||
disabled={disabled}
|
||||
onClick={() => onSelect?.(value)}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
)
|
||||
}))
|
||||
|
||||
let container: HTMLDivElement
|
||||
let root: Root
|
||||
|
||||
const readyOption: ProjectHostSetupOption = {
|
||||
id: 'local-setup',
|
||||
kind: 'ready',
|
||||
projectId: 'project-1',
|
||||
hostId: 'local',
|
||||
repoId: 'local-repo',
|
||||
label: 'Local Mac',
|
||||
detail: 'Orca',
|
||||
path: '/Users/alice/orca'
|
||||
}
|
||||
|
||||
const needsSetupOption: NeedsSetupProjectHostOption = {
|
||||
id: 'needs-setup:ssh:builder',
|
||||
kind: 'needs-setup',
|
||||
projectId: 'project-1',
|
||||
hostId: 'ssh:builder',
|
||||
label: 'Builder',
|
||||
detail: 'Project location not set',
|
||||
isAvailable: true,
|
||||
attention: false
|
||||
}
|
||||
|
||||
const unavailableOption: NeedsSetupProjectHostOption = {
|
||||
id: 'needs-setup:runtime:old',
|
||||
kind: 'needs-setup',
|
||||
projectId: 'project-1',
|
||||
hostId: 'runtime:old',
|
||||
label: 'Old server',
|
||||
detail: 'Update Orca on this host to set up projects',
|
||||
isAvailable: false,
|
||||
attention: false
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
container = document.createElement('div')
|
||||
document.body.appendChild(container)
|
||||
root = createRoot(container)
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
act(() => {
|
||||
root.unmount()
|
||||
})
|
||||
container.remove()
|
||||
})
|
||||
|
||||
function renderCombobox({
|
||||
onValueChange = vi.fn()
|
||||
}: {
|
||||
onValueChange?: (setupId: string) => void
|
||||
} = {}): void {
|
||||
act(() => {
|
||||
root.render(
|
||||
<ProjectHostSetupCombobox
|
||||
options={[readyOption, needsSetupOption]}
|
||||
value={readyOption.id}
|
||||
onValueChange={onValueChange}
|
||||
/>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
describe('ProjectHostSetupCombobox', () => {
|
||||
it('routes ready setup rows through onValueChange', () => {
|
||||
const onValueChange = vi.fn()
|
||||
|
||||
renderCombobox({ onValueChange })
|
||||
|
||||
act(() => {
|
||||
container
|
||||
.querySelector<HTMLButtonElement>('[data-command-value="local-setup"]')
|
||||
?.dispatchEvent(new MouseEvent('click', { bubbles: true }))
|
||||
})
|
||||
|
||||
expect(onValueChange).toHaveBeenCalledWith('local-setup')
|
||||
})
|
||||
|
||||
it('hides hosts that need setup from the run target list', () => {
|
||||
const onValueChange = vi.fn()
|
||||
|
||||
renderCombobox({ onValueChange })
|
||||
|
||||
expect(
|
||||
container.querySelector<HTMLButtonElement>('[data-command-value="needs-setup:ssh:builder"]')
|
||||
).toBeNull()
|
||||
expect(container.textContent).not.toContain('Project location not set')
|
||||
expect(onValueChange).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('hides unavailable setup rows from the run target list', () => {
|
||||
const onValueChange = vi.fn()
|
||||
|
||||
act(() => {
|
||||
root.render(
|
||||
<ProjectHostSetupCombobox
|
||||
options={[readyOption, unavailableOption]}
|
||||
value={readyOption.id}
|
||||
onValueChange={onValueChange}
|
||||
/>
|
||||
)
|
||||
})
|
||||
|
||||
const unavailableButton = container.querySelector<HTMLButtonElement>(
|
||||
'[data-command-value="needs-setup:runtime:old"]'
|
||||
)
|
||||
expect(unavailableButton).toBeNull()
|
||||
expect(container.textContent).not.toContain('Update Orca on this host')
|
||||
|
||||
expect(onValueChange).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
@@ -1,105 +0,0 @@
|
||||
import React from 'react'
|
||||
import { Check, ChevronsUpDown, Server } from 'lucide-react'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Command, CommandEmpty, CommandItem, CommandList } from '@/components/ui/command'
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
|
||||
import { cn } from '@/lib/utils'
|
||||
import type { ProjectHostSetupOption } from '@/lib/project-host-setup-options'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
|
||||
type ProjectHostSetupComboboxProps = {
|
||||
options: readonly ProjectHostSetupOption[]
|
||||
value: string | null
|
||||
onValueChange: (setupId: string) => void
|
||||
}
|
||||
|
||||
export default function ProjectHostSetupCombobox({
|
||||
options,
|
||||
value,
|
||||
onValueChange
|
||||
}: ProjectHostSetupComboboxProps): React.JSX.Element {
|
||||
const [open, setOpen] = React.useState(false)
|
||||
const readyOptions = options.filter((option) => option.kind === 'ready')
|
||||
const selected = readyOptions.find((option) => option.id === value) ?? readyOptions[0] ?? null
|
||||
|
||||
const handleSelect = React.useCallback(
|
||||
(setupId: string): void => {
|
||||
const option = options.find((candidate) => candidate.id === setupId)
|
||||
if (!option) {
|
||||
return
|
||||
}
|
||||
if (!readyOptions.some((candidate) => candidate.id === setupId)) {
|
||||
return
|
||||
}
|
||||
onValueChange(setupId)
|
||||
setOpen(false)
|
||||
},
|
||||
[onValueChange, options, readyOptions]
|
||||
)
|
||||
|
||||
return (
|
||||
<Popover open={open} onOpenChange={setOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
role="combobox"
|
||||
aria-expanded={open}
|
||||
className="h-9 w-full justify-between border-input px-3 text-sm font-normal focus:border-ring focus:ring-[3px] focus:ring-ring/50"
|
||||
>
|
||||
{selected ? (
|
||||
<span className="inline-flex min-w-0 items-center gap-1.5">
|
||||
<Server className="size-3.5 shrink-0 text-muted-foreground" />
|
||||
<span className="truncate">{selected.label}</span>
|
||||
</span>
|
||||
) : (
|
||||
<span className="text-muted-foreground">
|
||||
{translate(
|
||||
'auto.components.new.workspace.ProjectHostSetupCombobox.placeholder',
|
||||
'Choose host'
|
||||
)}
|
||||
</span>
|
||||
)}
|
||||
<ChevronsUpDown className="size-3.5 shrink-0 opacity-50" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
align="start"
|
||||
className="w-[var(--radix-popover-trigger-width)] min-w-[18rem] p-0"
|
||||
>
|
||||
<Command value={selected?.id ?? ''}>
|
||||
<CommandList>
|
||||
<CommandEmpty>
|
||||
{translate(
|
||||
'auto.components.new.workspace.ProjectHostSetupCombobox.empty',
|
||||
'No hosts are ready for this project.'
|
||||
)}
|
||||
</CommandEmpty>
|
||||
{readyOptions.map((option) => (
|
||||
<CommandItem
|
||||
key={option.id}
|
||||
value={option.id}
|
||||
onSelect={() => handleSelect(option.id)}
|
||||
className="items-center gap-2 px-3 py-2"
|
||||
>
|
||||
<Check
|
||||
className={cn(
|
||||
'size-4 text-foreground',
|
||||
option.id === selected?.id ? 'opacity-100' : 'opacity-0'
|
||||
)}
|
||||
/>
|
||||
<Server className="size-3.5 shrink-0 text-muted-foreground" />
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="truncate text-sm">{option.label}</div>
|
||||
<div className="mt-0.5 truncate text-[11px] text-muted-foreground">
|
||||
{option.path}
|
||||
</div>
|
||||
</div>
|
||||
</CommandItem>
|
||||
))}
|
||||
</CommandList>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
)
|
||||
}
|
||||
@@ -12254,10 +12254,6 @@
|
||||
"jiraSelectBindFailed": "Couldn’t link this Jira issue. Pick the matching site or reconnect Jira, then try again.",
|
||||
"emoji": "Emoji"
|
||||
},
|
||||
"ProjectHostSetupCombobox": {
|
||||
"empty": "No hosts are ready for this project.",
|
||||
"placeholder": "Choose host"
|
||||
},
|
||||
"ProjectCombobox": {
|
||||
"empty": "No projects match your search.",
|
||||
"addProject": "Add a new project",
|
||||
|
||||
@@ -11742,10 +11742,6 @@
|
||||
"jiraSelectBindFailed": "Couldn’t link this Jira issue. Pick the matching site or reconnect Jira, then try again.",
|
||||
"emoji": "Emoji"
|
||||
},
|
||||
"ProjectHostSetupCombobox": {
|
||||
"empty": "No hay hosts listos para este proyecto.",
|
||||
"placeholder": "Elegir host"
|
||||
},
|
||||
"ProjectCombobox": {
|
||||
"empty": "Ningún proyecto coincide con tu búsqueda.",
|
||||
"addProject": "Add a new project",
|
||||
|
||||
@@ -11742,10 +11742,6 @@
|
||||
"jiraSelectBindFailed": "この Jira Issue をリンクできませんでした。該当するサイトを選択するか Jira に再接続してから、もう一度お試しください。",
|
||||
"emoji": "絵文字"
|
||||
},
|
||||
"ProjectHostSetupCombobox": {
|
||||
"empty": "このプロジェクトで準備が整ったホストはありません。",
|
||||
"placeholder": "ホストを選択"
|
||||
},
|
||||
"ProjectCombobox": {
|
||||
"empty": "検索条件に一致するプロジェクトはありません。",
|
||||
"addProject": "新規プロジェクトを追加",
|
||||
|
||||
@@ -11742,10 +11742,6 @@
|
||||
"jiraSelectBindFailed": "Couldn’t link this Jira issue. Pick the matching site or reconnect Jira, then try again.",
|
||||
"emoji": "Emoji"
|
||||
},
|
||||
"ProjectHostSetupCombobox": {
|
||||
"empty": "이 프로젝트에 준비된 호스트가 없습니다.",
|
||||
"placeholder": "호스트 선택"
|
||||
},
|
||||
"ProjectCombobox": {
|
||||
"empty": "검색과 일치하는 프로젝트가 없습니다.",
|
||||
"addProject": "새 프로젝트 추가",
|
||||
|
||||
@@ -11762,10 +11762,6 @@
|
||||
"jiraSelectBindFailed": "无法关联此 Jira 议题。请选择匹配的站点或重新连接 Jira,然后重试。",
|
||||
"emoji": "Emoji"
|
||||
},
|
||||
"ProjectHostSetupCombobox": {
|
||||
"empty": "此项目没有已就绪的主机。",
|
||||
"placeholder": "选择主机"
|
||||
},
|
||||
"ProjectCombobox": {
|
||||
"empty": "没有匹配搜索的项目。",
|
||||
"addProject": "添加新项目",
|
||||
|
||||
Reference in New Issue
Block a user