175 lines
6.1 KiB
TypeScript
175 lines
6.1 KiB
TypeScript
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');
|
|
});
|
|
});
|