From a13ef03f8275ae4f0dcc9cf43fbea7ab8e755d12 Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Tue, 28 Apr 2026 10:46:33 +0900 Subject: [PATCH] feat(team-mode): add close-team-member-pane command with tests --- .../close-team-member-pane.test.ts | 62 +++++++++++++++++++ .../close-team-member-pane.ts | 31 ++++++++++ 2 files changed, 93 insertions(+) create mode 100644 src/features/team-mode/team-layout-tmux/close-team-member-pane.test.ts create mode 100644 src/features/team-mode/team-layout-tmux/close-team-member-pane.ts diff --git a/src/features/team-mode/team-layout-tmux/close-team-member-pane.test.ts b/src/features/team-mode/team-layout-tmux/close-team-member-pane.test.ts new file mode 100644 index 000000000..7a13f960f --- /dev/null +++ b/src/features/team-mode/team-layout-tmux/close-team-member-pane.test.ts @@ -0,0 +1,62 @@ +/// + +import { afterEach, beforeEach, describe, expect, test, mock, spyOn } from "bun:test" + +import * as sharedModule from "../../../shared" +import * as sharedTmuxModule from "../../../shared/tmux" +import { closeTeamMemberPane } from "./close-team-member-pane" + +const closeTmuxPaneMock = mock(async (): Promise => true) +const logMock = mock(() => undefined) + +describe("closeTeamMemberPane", () => { + afterEach(() => { + mock.restore() + }) + + beforeEach(() => { + closeTmuxPaneMock.mockClear() + logMock.mockClear() + + closeTmuxPaneMock.mockResolvedValue(true) + spyOn(sharedModule, "log").mockImplementation(logMock) + spyOn(sharedTmuxModule, "closeTmuxPane").mockImplementation(closeTmuxPaneMock) + }) + + test("#given member has both tmuxPaneId and tmuxGridPaneId #when closeTeamMemberPane runs #then close is invoked for both ids (2 calls) and returns true when either succeeds", async () => { + // given + closeTmuxPaneMock.mockResolvedValueOnce(false) + closeTmuxPaneMock.mockResolvedValueOnce(true) + + // when + const result = await closeTeamMemberPane({ tmuxPaneId: "%42", tmuxGridPaneId: "%84" }) + + // then + expect(result).toBe(true) + expect(closeTmuxPaneMock).toHaveBeenCalledTimes(2) + expect(closeTmuxPaneMock).toHaveBeenCalledWith("%42") + expect(closeTmuxPaneMock).toHaveBeenCalledWith("%84") + }) + + test("#given member has only tmuxPaneId #when closeTeamMemberPane runs #then close is invoked once and returns true when it succeeds", async () => { + // when + const result = await closeTeamMemberPane({ tmuxPaneId: "%42" }) + + // then + expect(result).toBe(true) + expect(closeTmuxPaneMock).toHaveBeenCalledTimes(1) + expect(closeTmuxPaneMock).toHaveBeenCalledWith("%42") + }) + + test("#given both closes fail #when closeTeamMemberPane runs #then returns false", async () => { + // given + closeTmuxPaneMock.mockResolvedValue(false) + + // when + const result = await closeTeamMemberPane({ tmuxPaneId: "%42", tmuxGridPaneId: "%84" }) + + // then + expect(result).toBe(false) + expect(closeTmuxPaneMock).toHaveBeenCalledTimes(2) + }) +}) diff --git a/src/features/team-mode/team-layout-tmux/close-team-member-pane.ts b/src/features/team-mode/team-layout-tmux/close-team-member-pane.ts new file mode 100644 index 000000000..83a3b8cb6 --- /dev/null +++ b/src/features/team-mode/team-layout-tmux/close-team-member-pane.ts @@ -0,0 +1,31 @@ +/// + +import type { RuntimeStateMember } from "../types" + +type TeamMemberPaneIds = Pick + +export async function closeTeamMemberPane(member: TeamMemberPaneIds): Promise { + const paneIds = [member.tmuxPaneId, member.tmuxGridPaneId].filter((paneId): paneId is string => paneId !== undefined && paneId.length > 0) + if (paneIds.length === 0) { + return false + } + + const [{ log }, { closeTmuxPane }] = await Promise.all([ + import("../../../shared"), + import("../../../shared/tmux"), + ]) + + const results = await Promise.all(paneIds.map(async (paneId) => { + try { + return await closeTmuxPane(paneId) + } catch (error) { + log("[closeTeamMemberPane] FAILED", { + paneId, + error: error instanceof Error ? error.message : String(error), + }) + return false + } + })) + + return results.some(Boolean) +}