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