// useTerminalContext : le terminal focalisé impose le worktree actif. C'était le chaînon manquant // (openTerminal / focusTerminal ne posaient pas `activeContext`), et c'est ce qui fait suivre la vue // Changements, la barre de statut et les abonnements FS. import { describe, it, expect, beforeEach } from 'vitest'; import { createPinia, setActivePinia } from 'pinia'; import { effectScope, nextTick } from 'vue'; import type { GroupSummary, SessionSummary, WorktreeSummary } from '@arboretum/shared'; import { useTerminalContext } from '../src/composables/useTerminalContext'; import { useIdeStore } from '../src/stores/ide'; import { useWorktreesStore } from '../src/stores/worktrees'; import { useSessionsStore } from '../src/stores/sessions'; import { useGroupsStore } from '../src/stores/groups'; function fakeStorage() { const m = new Map(); return { getItem: (k: string) => (m.has(k) ? (m.get(k) as string) : null), setItem: (k: string, v: string) => void m.set(k, v), removeItem: (k: string) => void m.delete(k), clear: () => m.clear(), }; } function wt(repoId: string, path: string): WorktreeSummary { return { repoId, path, branch: 'main', head: 'abc1234', detached: false, locked: false, prunable: false, isMain: true, git: { ahead: 0, behind: 0, dirtyCount: 0, upstream: null }, sessions: [], }; } function session(id: string, cwd: string, extra: Partial = {}): SessionSummary { return { id, cwd, command: 'claude', title: null, status: 'running', live: true, createdAt: '2026-01-01T00:00:00.000Z', endedAt: null, exitCode: null, clients: 0, source: 'managed', claudeSessionId: null, pid: 1234, resumable: false, attachable: true, registryStatus: null, ...extra, }; } const group = (id: string, repoIds: string[]): GroupSummary => ({ id, label: id, description: null, color: null, repoIds, createdAt: '2026-01-01T00:00:00.000Z', }); describe('useTerminalContext', () => { beforeEach(() => { (globalThis as unknown as { localStorage: unknown }).localStorage = fakeStorage(); setActivePinia(createPinia()); }); it('focaliser un terminal pose le worktree actif', async () => { const ide = useIdeStore(); const worktrees = useWorktreesStore(); const sessions = useSessionsStore(); worktrees.worktrees = [wt('r1', '/wt/a'), wt('r2', '/wt/b')]; sessions.sessions = [session('s1', '/wt/b')]; const scope = effectScope(); scope.run(() => useTerminalContext()); ide.openTerminal('s1'); await nextTick(); expect(ide.activeContext).toEqual({ repoId: 'r2', wtPath: '/wt/b' }); scope.stop(); }); it('rattache par contenance un terminal lancé dans un sous-répertoire', async () => { const ide = useIdeStore(); const worktrees = useWorktreesStore(); const sessions = useSessionsStore(); worktrees.worktrees = [wt('r1', '/wt/a')]; sessions.sessions = [session('s1', '/wt/a/packages/api')]; const scope = effectScope(); scope.run(() => useTerminalContext()); ide.openTerminal('s1'); await nextTick(); expect(ide.activeContext).toEqual({ repoId: 'r1', wtPath: '/wt/a' }); scope.stop(); }); it('session de GROUPE : le contexte n’est pas écrasé (un groupe n’a pas de worktree unique)', async () => { const ide = useIdeStore(); const worktrees = useWorktreesStore(); const sessions = useSessionsStore(); const groups = useGroupsStore(); worktrees.worktrees = [wt('r1', '/wt/a'), wt('r2', '/wt/b')]; groups.groups = [group('g1', ['r1', 'r2'])]; sessions.sessions = [session('sg', '/wt', { addedDirs: ['/wt/a', '/wt/b'], groupId: 'g1' })]; ide.setActiveWorktree('r1', '/wt/a'); const scope = effectScope(); scope.run(() => useTerminalContext()); ide.openTerminal('sg'); await nextTick(); expect(ide.activeContext).toEqual({ repoId: 'r1', wtPath: '/wt/a' }); scope.stop(); }); it('cwd hors de tout worktree connu : contexte inchangé', async () => { const ide = useIdeStore(); const worktrees = useWorktreesStore(); const sessions = useSessionsStore(); worktrees.worktrees = [wt('r1', '/wt/a')]; sessions.sessions = [session('s1', '/tmp/scratch')]; ide.setActiveWorktree('r1', '/wt/a'); const scope = effectScope(); scope.run(() => useTerminalContext()); ide.openTerminal('s1'); await nextTick(); expect(ide.activeContext).toEqual({ repoId: 'r1', wtPath: '/wt/a' }); scope.stop(); }); it('la session peut arriver APRÈS le terminal (dock persisté, liste encore vide)', async () => { const ide = useIdeStore(); const worktrees = useWorktreesStore(); const sessions = useSessionsStore(); worktrees.worktrees = [wt('r1', '/wt/a')]; ide.openTerminal('s1'); // session pas encore connue const scope = effectScope(); scope.run(() => useTerminalContext()); await nextTick(); expect(ide.activeContext).toBeNull(); sessions.sessions = [session('s1', '/wt/a')]; await nextTick(); expect(ide.activeContext).toEqual({ repoId: 'r1', wtPath: '/wt/a' }); scope.stop(); }); });