import { describe, expect, it } from 'vitest'; import { analyzeTeammateRuntimeCompatibility } from '@renderer/components/team/dialogs/teammateRuntimeCompatibility'; import type { TmuxStatus } from '@features/tmux-installer/contracts'; function buildTmuxStatus(ready: boolean): TmuxStatus { return { platform: 'win32', nativeSupported: false, checkedAt: '2026-04-25T00:00:00.000Z', host: { available: false, version: null, binaryPath: null, error: null, }, effective: { available: ready, location: ready ? 'wsl' : null, version: ready ? '3.4' : null, binaryPath: ready ? '/usr/bin/tmux' : null, runtimeReady: ready, detail: ready ? 'tmux is ready' : 'tmux is not available', }, error: null, autoInstall: { supported: false, strategy: 'manual', packageManagerLabel: null, requiresTerminalInput: false, requiresAdmin: false, requiresRestart: false, mayOpenExternalWindow: false, reasonIfUnsupported: null, manualHints: [], }, wsl: null, wslPreference: null, }; } describe('analyzeTeammateRuntimeCompatibility', () => { it('allows same-provider non-Codex teammates without tmux', () => { const result = analyzeTeammateRuntimeCompatibility({ leadProviderId: 'anthropic', members: [{ id: 'alice', name: 'alice', providerId: 'anthropic' }], tmuxStatus: buildTmuxStatus(false), tmuxStatusLoading: false, tmuxStatusError: null, }); expect(result.blocksSubmission).toBe(false); expect(result.visible).toBe(false); expect(result.memberWarningById).toEqual({}); }); it('allows mixed-provider teammates through native process transport when tmux is unavailable', () => { const result = analyzeTeammateRuntimeCompatibility({ leadProviderId: 'anthropic', members: [{ id: 'bob', name: 'bob', providerId: 'codex' }], tmuxStatus: buildTmuxStatus(false), tmuxStatusLoading: false, tmuxStatusError: null, }); expect(result.blocksSubmission).toBe(false); expect(result.visible).toBe(false); expect(result.memberWarningById).toEqual({}); }); it('allows OpenCode secondary-lane teammates without tmux under a non-OpenCode lead', () => { const result = analyzeTeammateRuntimeCompatibility({ leadProviderId: 'anthropic', members: [{ id: 'bob', name: 'bob', providerId: 'opencode' }], tmuxStatus: buildTmuxStatus(false), tmuxStatusLoading: false, tmuxStatusError: null, }); expect(result.blocksSubmission).toBe(false); expect(result.visible).toBe(false); expect(result.memberWarningById).toEqual({}); }); it('blocks OpenCode-led mixed teams independently of tmux readiness', () => { const result = analyzeTeammateRuntimeCompatibility({ leadProviderId: 'opencode', members: [{ id: 'bob', name: 'bob', providerId: 'anthropic' }], tmuxStatus: buildTmuxStatus(true), tmuxStatusLoading: false, tmuxStatusError: null, }); expect(result.blocksSubmission).toBe(true); expect(result.title).toBe('OpenCode cannot lead mixed-provider teams'); expect(result.message).toContain('mixed teams cannot use OpenCode as the lead'); expect(result.memberWarningById.bob).toContain('OpenCode cannot be the team lead'); }); it('allows same-provider Codex native teammates through native process transport when tmux is unavailable', () => { const result = analyzeTeammateRuntimeCompatibility({ leadProviderId: 'codex', leadProviderBackendId: 'codex-native', members: [{ id: 'jack', name: 'jack', providerId: 'codex' }], tmuxStatus: buildTmuxStatus(false), tmuxStatusLoading: false, tmuxStatusError: null, }); expect(result.blocksSubmission).toBe(false); expect(result.visible).toBe(false); expect(result.memberWarningById).toEqual({}); }); it('allows separate-process teammate requirements when tmux is ready', () => { const result = analyzeTeammateRuntimeCompatibility({ leadProviderId: 'anthropic', members: [{ id: 'bob', name: 'bob', providerId: 'codex' }], tmuxStatus: buildTmuxStatus(true), tmuxStatusLoading: false, tmuxStatusError: null, }); expect(result.blocksSubmission).toBe(false); expect(result.visible).toBe(false); }); it('ignores teammate runtime requirements for solo teams', () => { const result = analyzeTeammateRuntimeCompatibility({ leadProviderId: 'codex', leadProviderBackendId: 'codex-native', members: [{ id: 'jack', name: 'jack', providerId: 'codex' }], soloTeam: true, tmuxStatus: buildTmuxStatus(false), tmuxStatusLoading: false, tmuxStatusError: null, }); expect(result.blocksSubmission).toBe(false); expect(result.visible).toBe(false); }); it('blocks explicit tmux teammate mode when tmux is unavailable', () => { const result = analyzeTeammateRuntimeCompatibility({ leadProviderId: 'anthropic', members: [{ id: 'alice', name: 'alice', providerId: 'anthropic' }], extraCliArgs: '--teammate-mode tmux', tmuxStatus: buildTmuxStatus(false), tmuxStatusLoading: false, tmuxStatusError: null, }); expect(result.blocksSubmission).toBe(true); expect(result.details).toContain('Custom CLI args force --teammate-mode tmux.'); expect(result.message).toContain('native process transport'); }); it('blocks explicit in-process mode when a teammate requires a separate process', () => { const result = analyzeTeammateRuntimeCompatibility({ leadProviderId: 'anthropic', members: [{ id: 'bob', name: 'bob', providerId: 'codex' }], extraCliArgs: '--teammate-mode=in-process', tmuxStatus: buildTmuxStatus(true), tmuxStatusLoading: false, tmuxStatusError: null, }); expect(result.blocksSubmission).toBe(true); expect(result.title).toBe('This team cannot use in-process teammates'); expect(result.details).toContain('Custom CLI args force --teammate-mode in-process.'); expect(result.message).toContain('native process transport'); expect(result.memberWarningById.bob).toContain('requires a separate process'); }); });