feat(team-mode): add rebalance-team-window command with tests

This commit is contained in:
YeonGyu-Kim
2026-04-28 10:46:34 +09:00
parent a13ef03f82
commit eb61091f6d
2 changed files with 154 additions and 0 deletions
@@ -0,0 +1,84 @@
/// <reference types="bun-types" />
import { beforeEach, describe, expect, it, mock } from "bun:test"
import {
rebalanceTeamWindowWith,
type RebalanceTeamWindowDeps,
} from "./rebalance-team-window"
describe("rebalanceTeamWindowWith", () => {
let runTmux: RebalanceTeamWindowDeps["runTmux"]
let log: RebalanceTeamWindowDeps["log"]
let calls: Array<Array<string>>
beforeEach(() => {
calls = []
runTmux = mock(async (args: string[]): Promise<{ success: boolean }> => {
calls.push(args)
return { success: true }
})
log = mock((): void => undefined)
})
it("#given main-vertical #when rebalance #then select-layout, set main-pane-width 60%, re-select-layout", async () => {
// given
const deps: RebalanceTeamWindowDeps = { runTmux, log }
// when
const result = await rebalanceTeamWindowWith("@1", "main-vertical", deps)
// then
expect(result).toBe(true)
expect(calls).toEqual([
["select-layout", "-t", "@1", "main-vertical"],
["set-window-option", "-t", "@1", "main-pane-width", "60%"],
["select-layout", "-t", "@1", "main-vertical"],
])
})
it("#given focus windowId and pane-list shrunk from 3 to 2 #when rebalanceTeamWindow runs #then select-layout is invoked with main-vertical", async () => {
// given
const deps: RebalanceTeamWindowDeps = { runTmux, log }
// when
const result = await rebalanceTeamWindowWith("@focus", "main-vertical", deps)
// then
expect(result).toBe(true)
expect(calls).toEqual([
["select-layout", "-t", "@focus", "main-vertical"],
["set-window-option", "-t", "@focus", "main-pane-width", "60%"],
["select-layout", "-t", "@focus", "main-vertical"],
])
})
it("#given tiled #when rebalance #then only select-layout called", async () => {
// given
const deps: RebalanceTeamWindowDeps = { runTmux, log }
// when
const result = await rebalanceTeamWindowWith("@1", "tiled", deps)
// then
expect(result).toBe(true)
expect(calls).toEqual([["select-layout", "-t", "@1", "tiled"]])
})
it("#given select-layout fails #when rebalance #then returns false, log once", async () => {
// given
runTmux = mock(async (args: string[]): Promise<{ success: boolean }> => {
calls.push(args)
return { success: false }
})
const deps: RebalanceTeamWindowDeps = { runTmux, log }
// when
const result = await rebalanceTeamWindowWith("@1", "main-vertical", deps)
// then
expect(result).toBe(false)
expect(log).toHaveBeenCalledTimes(1)
})
})
@@ -0,0 +1,70 @@
export type RebalanceLayout = "main-vertical" | "tiled"
export type RebalanceTeamWindowDeps = {
runTmux: (args: string[]) => Promise<{ success: boolean }>
log: (message: string, meta?: Record<string, unknown>) => void
}
export async function rebalanceTeamWindowWith(
windowId: string,
layout: RebalanceLayout,
deps: RebalanceTeamWindowDeps,
): Promise<boolean> {
if (windowId.length === 0) {
return false
}
const selectLayoutArgs = ["select-layout", "-t", windowId, layout]
const initialLayout = await deps.runTmux(selectLayoutArgs)
if (!initialLayout.success) {
deps.log("[rebalanceTeamWindow] FAILED", { windowId, layout, step: "select-layout" })
return false
}
if (layout === "tiled") {
return true
}
const setMainPaneWidth = await deps.runTmux([
"set-window-option",
"-t",
windowId,
"main-pane-width",
"60%",
])
if (!setMainPaneWidth.success) {
deps.log("[rebalanceTeamWindow] FAILED", { windowId, layout, step: "set-window-option" })
return false
}
// tmux applies main-pane-width against the active layout, so select-layout again after resizing.
const finalLayout = await deps.runTmux(selectLayoutArgs)
if (!finalLayout.success) {
deps.log("[rebalanceTeamWindow] FAILED", { windowId, layout, step: "select-layout" })
return false
}
return true
}
export async function rebalanceTeamWindow(
windowId: string,
layout: RebalanceLayout,
): Promise<boolean> {
const [{ log }, { getTmuxPath }, { runTmuxCommand }] = await Promise.all([
import("../../../shared"),
import("../../../tools/interactive-bash/tmux-path-resolver"),
import("../../../shared/tmux"),
])
const tmuxPath = await getTmuxPath()
if (!tmuxPath) {
log("[rebalanceTeamWindow] SKIP: tmux not found", { windowId, layout })
return false
}
return rebalanceTeamWindowWith(windowId, layout, {
runTmux: (args) => runTmuxCommand(tmuxPath, args),
log,
})
}