test(tmux): isolate pane close logic tests
This commit is contained in:
@@ -1,104 +1,114 @@
|
||||
import { beforeEach, describe, expect, it, mock } from "bun:test"
|
||||
import { describe, expect, it } from "bun:test"
|
||||
|
||||
import type { TmuxCommandResult } from "../runner"
|
||||
import { closeTmuxPaneWithDependencies } from "./pane-close"
|
||||
|
||||
const paneCloseSpecifier = import.meta.resolve("./pane-close")
|
||||
const environmentSpecifier = import.meta.resolve("./environment")
|
||||
const loggerSpecifier = import.meta.resolve("../../logger")
|
||||
const runnerSpecifier = import.meta.resolve("../runner")
|
||||
const tmuxPathResolverSpecifier = import.meta.resolve("../../../tools/interactive-bash/tmux-path-resolver")
|
||||
type CloseTmuxPaneDependencies = Parameters<typeof closeTmuxPaneWithDependencies>[1]
|
||||
|
||||
const runTmuxCommandMock = mock(async (): Promise<TmuxCommandResult> => ({
|
||||
success: true,
|
||||
output: "",
|
||||
stdout: "",
|
||||
stderr: "",
|
||||
exitCode: 0,
|
||||
}))
|
||||
const isInsideTmuxMock = mock((): boolean => true)
|
||||
const getTmuxPathMock = mock(async (): Promise<string | undefined> => "tmux")
|
||||
const logMock = mock(() => undefined)
|
||||
|
||||
async function loadCloseTmuxPane(): Promise<typeof import("./pane-close").closeTmuxPane> {
|
||||
const module = await import(`${paneCloseSpecifier}?test=${crypto.randomUUID()}`)
|
||||
return module.closeTmuxPane
|
||||
type TmuxCommandCall = {
|
||||
readonly tmux: string
|
||||
readonly args: string[]
|
||||
}
|
||||
|
||||
function registerModuleMocks(): void {
|
||||
mock.module(environmentSpecifier, () => ({ isInsideTmux: isInsideTmuxMock }))
|
||||
mock.module(loggerSpecifier, () => ({ log: logMock }))
|
||||
mock.module(runnerSpecifier, () => ({ runTmuxCommand: runTmuxCommandMock }))
|
||||
mock.module(tmuxPathResolverSpecifier, () => ({ getTmuxPath: getTmuxPathMock }))
|
||||
type ClosePaneFixture = {
|
||||
readonly calls: TmuxCommandCall[]
|
||||
readonly delayCalls: number[]
|
||||
readonly dependencies: CloseTmuxPaneDependencies
|
||||
}
|
||||
|
||||
type FixtureOptions = {
|
||||
readonly insideTmux?: boolean
|
||||
readonly tmuxPath?: string | undefined
|
||||
readonly results?: TmuxCommandResult[]
|
||||
}
|
||||
|
||||
function tmuxResult(overrides: Partial<TmuxCommandResult> = {}): TmuxCommandResult {
|
||||
return {
|
||||
success: true,
|
||||
output: "",
|
||||
stdout: "",
|
||||
stderr: "",
|
||||
exitCode: 0,
|
||||
...overrides,
|
||||
}
|
||||
}
|
||||
|
||||
function createFixture(options: FixtureOptions = {}): ClosePaneFixture {
|
||||
const calls: TmuxCommandCall[] = []
|
||||
const delayCalls: number[] = []
|
||||
const results = [...(options.results ?? [tmuxResult()])]
|
||||
const tmuxPath = "tmuxPath" in options ? options.tmuxPath : "tmux"
|
||||
|
||||
return {
|
||||
calls,
|
||||
delayCalls,
|
||||
dependencies: {
|
||||
isInsideTmux: () => options.insideTmux ?? true,
|
||||
getTmuxPath: async () => tmuxPath,
|
||||
runTmuxCommand: async (tmux, args) => {
|
||||
calls.push({ tmux, args: [...args] })
|
||||
return results.shift() ?? tmuxResult()
|
||||
},
|
||||
log: () => undefined,
|
||||
delay: async (milliseconds) => {
|
||||
delayCalls.push(milliseconds)
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
describe("closeTmuxPane", () => {
|
||||
beforeEach(() => {
|
||||
registerModuleMocks()
|
||||
runTmuxCommandMock.mockClear()
|
||||
isInsideTmuxMock.mockClear()
|
||||
getTmuxPathMock.mockClear()
|
||||
logMock.mockClear()
|
||||
|
||||
runTmuxCommandMock.mockResolvedValue({
|
||||
success: true,
|
||||
output: "",
|
||||
stdout: "",
|
||||
stderr: "",
|
||||
exitCode: 0,
|
||||
})
|
||||
isInsideTmuxMock.mockReturnValue(true)
|
||||
getTmuxPathMock.mockResolvedValue("tmux")
|
||||
})
|
||||
|
||||
it("#given pane exists #when closeTmuxPane called #then returns true and invokes send-keys + kill-pane in order", async () => {
|
||||
// given
|
||||
const closeTmuxPane = await loadCloseTmuxPane()
|
||||
const fixture = createFixture()
|
||||
|
||||
// when
|
||||
const result = await closeTmuxPane("%42")
|
||||
const result = await closeTmuxPaneWithDependencies("%42", fixture.dependencies)
|
||||
|
||||
// then
|
||||
expect(result).toBe(true)
|
||||
expect(runTmuxCommandMock).toHaveBeenCalledTimes(2)
|
||||
expect(runTmuxCommandMock).toHaveBeenNthCalledWith(1, "tmux", ["send-keys", "-t", "%42", "C-c"])
|
||||
expect(runTmuxCommandMock).toHaveBeenNthCalledWith(2, "tmux", ["kill-pane", "-t", "%42"])
|
||||
expect(fixture.calls).toEqual([
|
||||
{ tmux: "tmux", args: ["send-keys", "-t", "%42", "C-c"] },
|
||||
{ tmux: "tmux", args: ["kill-pane", "-t", "%42"] },
|
||||
])
|
||||
expect(fixture.delayCalls).toEqual([250])
|
||||
})
|
||||
|
||||
it("#given not inside tmux #when closeTmuxPane called #then returns false without runner calls", async () => {
|
||||
// given
|
||||
const closeTmuxPane = await loadCloseTmuxPane()
|
||||
isInsideTmuxMock.mockReturnValue(false)
|
||||
const fixture = createFixture({ insideTmux: false })
|
||||
|
||||
// when
|
||||
const result = await closeTmuxPane("%42")
|
||||
const result = await closeTmuxPaneWithDependencies("%42", fixture.dependencies)
|
||||
|
||||
// then
|
||||
expect(result).toBe(false)
|
||||
expect(runTmuxCommandMock).not.toHaveBeenCalled()
|
||||
expect(fixture.calls).toEqual([])
|
||||
})
|
||||
|
||||
it("#given tmux not found #when closeTmuxPane called #then returns false without runner calls", async () => {
|
||||
// given
|
||||
const closeTmuxPane = await loadCloseTmuxPane()
|
||||
getTmuxPathMock.mockResolvedValue(undefined)
|
||||
const fixture = createFixture({ tmuxPath: undefined })
|
||||
|
||||
// when
|
||||
const result = await closeTmuxPane("%42")
|
||||
const result = await closeTmuxPaneWithDependencies("%42", fixture.dependencies)
|
||||
|
||||
// then
|
||||
expect(result).toBe(false)
|
||||
expect(runTmuxCommandMock).not.toHaveBeenCalled()
|
||||
expect(fixture.calls).toEqual([])
|
||||
})
|
||||
|
||||
it("#given kill-pane fails with unknown error #when closeTmuxPane called #then returns false", async () => {
|
||||
// given
|
||||
const closeTmuxPane = await loadCloseTmuxPane()
|
||||
runTmuxCommandMock
|
||||
.mockResolvedValueOnce({ success: true, output: "", stdout: "", stderr: "", exitCode: 0 })
|
||||
.mockResolvedValueOnce({ success: false, output: "", stdout: "", stderr: "permission denied", exitCode: 1 })
|
||||
const fixture = createFixture({
|
||||
results: [
|
||||
tmuxResult(),
|
||||
tmuxResult({ success: false, stderr: "permission denied", exitCode: 1 }),
|
||||
],
|
||||
})
|
||||
|
||||
// when
|
||||
const result = await closeTmuxPane("%42")
|
||||
const result = await closeTmuxPaneWithDependencies("%42", fixture.dependencies)
|
||||
|
||||
// then
|
||||
expect(result).toBe(false)
|
||||
@@ -106,13 +116,15 @@ describe("closeTmuxPane", () => {
|
||||
|
||||
it("#given pane already closed by Ctrl+C #when kill-pane reports can't find pane #then returns true", async () => {
|
||||
// given
|
||||
const closeTmuxPane = await loadCloseTmuxPane()
|
||||
runTmuxCommandMock
|
||||
.mockResolvedValueOnce({ success: true, output: "", stdout: "", stderr: "", exitCode: 0 })
|
||||
.mockResolvedValueOnce({ success: false, output: "", stdout: "", stderr: "can't find pane: %42", exitCode: 1 })
|
||||
const fixture = createFixture({
|
||||
results: [
|
||||
tmuxResult(),
|
||||
tmuxResult({ success: false, stderr: "can't find pane: %42", exitCode: 1 }),
|
||||
],
|
||||
})
|
||||
|
||||
// when
|
||||
const result = await closeTmuxPane("%42")
|
||||
const result = await closeTmuxPaneWithDependencies("%42", fixture.dependencies)
|
||||
|
||||
// then
|
||||
expect(result).toBe(true)
|
||||
|
||||
Reference in New Issue
Block a user