cc-haha/desktop/src/stores/openTargetStore.test.ts
程序员阿江(Relakkes) db7432a39a Add desktop project open targets
Desktop sessions need a fast local escape hatch that opens the same materialized cwd the agent is editing, without showing unavailable IDE choices or persisting detection state. This adds a local open-targets API with silent in-memory detection for common IDEs and platform file managers, then wires a compact Codex-style toolbar menu into the desktop TabBar for active session workdirs.

Constraint: The first version is local IDE/editor and Finder/Explorer/file-manager only, no terminal targets or IDE plugin integration.

Constraint: The opened path must come from the active session workDir so isolated worktrees open the actual agent editing surface.

Rejected: Persisting detected applications | detection is cheap and temporary state avoids stale app inventory.

Rejected: Rendering unavailable IDEs as disabled menu rows | the user asked to show only detected targets and fall back to Finder/Explorer when no IDE is available.

Confidence: high

Scope-risk: moderate

Directive: Keep this path session-workdir based; do not switch it to repository root without proving isolated worktree behavior.

Tested: bun test src/server/__tests__/open-target-service.test.ts src/server/__tests__/open-target-api.test.ts

Tested: cd desktop && bun run test -- src/stores/openTargetStore.test.ts src/components/layout/OpenProjectMenu.test.tsx src/components/layout/TabBar.test.tsx

Tested: cd desktop && bun run lint

Tested: bun run check:desktop

Tested: bun test src/server/__tests__/h5-access-auth.test.ts -t 'allows local desktop H5 access settings under explicit server auth with a valid bearer'

Not-tested: bun run check:server full suite had one unrelated H5 auth integration timeout in the full concurrent run; the timed-out test passed when rerun alone.
2026-05-13 14:56:19 +08:00

53 lines
1.6 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from 'vitest'
const apiMocks = vi.hoisted(() => ({
list: vi.fn(),
open: vi.fn(),
}))
vi.mock('../api/openTargets', () => ({
openTargetsApi: apiMocks,
}))
describe('openTargetStore', () => {
beforeEach(async () => {
vi.resetModules()
apiMocks.list.mockReset()
apiMocks.open.mockReset()
})
it('caches detected targets inside the TTL', async () => {
const { useOpenTargetStore } = await import('./openTargetStore')
apiMocks.list.mockResolvedValue({
platform: 'darwin',
targets: [{ id: 'finder', kind: 'file_manager', label: 'Finder', icon: 'finder', platform: 'darwin' }],
primaryTargetId: 'finder',
cachedAt: 1,
ttlMs: 60_000,
})
await useOpenTargetStore.getState().refreshTargets()
await useOpenTargetStore.getState().ensureTargets()
expect(apiMocks.list).toHaveBeenCalledTimes(1)
expect(useOpenTargetStore.getState().primaryTargetId).toBe('finder')
})
it('remembers the last successful target for this runtime', async () => {
const { useOpenTargetStore } = await import('./openTargetStore')
apiMocks.list.mockResolvedValue({
platform: 'darwin',
targets: [{ id: 'vscode', kind: 'ide', label: 'VS Code', icon: 'vscode', platform: 'darwin' }],
primaryTargetId: 'vscode',
cachedAt: 1,
ttlMs: 60_000,
})
apiMocks.open.mockResolvedValue({ ok: true, targetId: 'vscode', path: '/repo' })
await useOpenTargetStore.getState().refreshTargets()
await useOpenTargetStore.getState().openTarget('vscode', '/repo')
expect(useOpenTargetStore.getState().lastSuccessfulTargetId).toBe('vscode')
})
})