test(claude-code-hooks): batch 30 (3 files)
This commit is contained in:
@@ -0,0 +1,19 @@
|
|||||||
|
export function normalizeHookText(value: string | undefined): string | undefined {
|
||||||
|
if (value === undefined) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalized = value.replace(/\r\n/g, "\n").replace(/\r/g, "\n").trim()
|
||||||
|
return normalized.length > 0 ? normalized : undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
export function normalizeHookTextList(values: readonly (string | undefined)[]): string[] {
|
||||||
|
const normalizedValues: string[] = []
|
||||||
|
for (const value of values) {
|
||||||
|
const normalized = normalizeHookText(value)
|
||||||
|
if (normalized !== undefined) {
|
||||||
|
normalizedValues.push(normalized)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return normalizedValues
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
import { afterEach, describe, expect, it, mock, spyOn } from "bun:test"
|
||||||
|
import * as dispatchHookModule from "./dispatch-hook"
|
||||||
|
import { executePostToolUseHooks, type PostToolUseContext } from "./post-tool-use"
|
||||||
|
import type { ClaudeHooksConfig } from "./types"
|
||||||
|
|
||||||
|
function createContext(overrides?: Partial<PostToolUseContext>): PostToolUseContext {
|
||||||
|
return {
|
||||||
|
sessionId: "test-session",
|
||||||
|
toolName: "write",
|
||||||
|
toolInput: { file_path: "/tmp/test.md", content: "hello" },
|
||||||
|
toolOutput: { output: "wrote file" },
|
||||||
|
cwd: "/tmp",
|
||||||
|
...overrides,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createConfig(matchers: ClaudeHooksConfig["PostToolUse"]): ClaudeHooksConfig {
|
||||||
|
return { PostToolUse: matchers }
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("executePostToolUseHooks", () => {
|
||||||
|
afterEach(() => {
|
||||||
|
mock.restore()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("#given hook JSON with multiline additional context #when PostToolUse runs #then it exposes readable context without raw JSON", async () => {
|
||||||
|
// given
|
||||||
|
spyOn(dispatchHookModule, "dispatchHook").mockResolvedValue({
|
||||||
|
exitCode: 0,
|
||||||
|
stdout: JSON.stringify({
|
||||||
|
hookSpecificOutput: {
|
||||||
|
hookEventName: "PostToolUse",
|
||||||
|
additionalContext: "\r\nFirst line\r\n indented second line\rThird line\r\n",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
stderr: "",
|
||||||
|
})
|
||||||
|
const config = createConfig([
|
||||||
|
{ matcher: "Write", hooks: [{ type: "command", command: "hook-context" }] },
|
||||||
|
])
|
||||||
|
|
||||||
|
// when
|
||||||
|
const result = await executePostToolUseHooks(createContext(), config)
|
||||||
|
|
||||||
|
// then
|
||||||
|
expect(result.additionalContext).toBe("First line\n indented second line\nThird line")
|
||||||
|
expect(result.message).toBeUndefined()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("#given non-json hook stdout with CRLF #when PostToolUse runs #then it normalizes the user-visible message", async () => {
|
||||||
|
// given
|
||||||
|
spyOn(dispatchHookModule, "dispatchHook").mockResolvedValue({
|
||||||
|
exitCode: 0,
|
||||||
|
stdout: "\r\nFirst line\r\nSecond line\rThird line\r\n",
|
||||||
|
stderr: "",
|
||||||
|
})
|
||||||
|
const config = createConfig([
|
||||||
|
{ matcher: "Write", hooks: [{ type: "command", command: "hook-text" }] },
|
||||||
|
])
|
||||||
|
|
||||||
|
// when
|
||||||
|
const result = await executePostToolUseHooks(createContext(), config)
|
||||||
|
|
||||||
|
// then
|
||||||
|
expect(result.message).toBe("First line\nSecond line\nThird line")
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
import { afterEach, describe, expect, it, mock, spyOn } from "bun:test"
|
||||||
|
import * as dispatchHookModule from "./dispatch-hook"
|
||||||
|
import { executePreCompactHooks, type PreCompactContext } from "./pre-compact"
|
||||||
|
import type { ClaudeHooksConfig } from "./types"
|
||||||
|
|
||||||
|
function createContext(overrides?: Partial<PreCompactContext>): PreCompactContext {
|
||||||
|
return {
|
||||||
|
sessionId: "test-session",
|
||||||
|
cwd: "/tmp",
|
||||||
|
...overrides,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createConfig(matchers: ClaudeHooksConfig["PreCompact"]): ClaudeHooksConfig {
|
||||||
|
return { PreCompact: matchers }
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("executePreCompactHooks", () => {
|
||||||
|
afterEach(() => {
|
||||||
|
mock.restore()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("#given hook context with CRLF and bare CR #when PreCompact runs #then it returns normalized context entries", async () => {
|
||||||
|
// given
|
||||||
|
spyOn(dispatchHookModule, "dispatchHook").mockResolvedValue({
|
||||||
|
exitCode: 0,
|
||||||
|
stdout: JSON.stringify({
|
||||||
|
hookSpecificOutput: {
|
||||||
|
hookEventName: "PreCompact",
|
||||||
|
additionalContext: ["\r\nfirst context\r\n detail\rsecond context\r\n"],
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
stderr: "",
|
||||||
|
})
|
||||||
|
const config = createConfig([
|
||||||
|
{ matcher: "*", hooks: [{ type: "command", command: "pre-compact-hook" }] },
|
||||||
|
])
|
||||||
|
|
||||||
|
// when
|
||||||
|
const result = await executePreCompactHooks(createContext(), config)
|
||||||
|
|
||||||
|
// then
|
||||||
|
expect(result.context).toEqual(["first context\n detail\nsecond context"])
|
||||||
|
})
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user