Skip to content

Commit bd8eb88

Browse files
committed
Fix P0 docs and expand core behavior tests
1 parent b059e59 commit bd8eb88

2 files changed

Lines changed: 117 additions & 1 deletion

File tree

README.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ Privacy-friendly by design: no system notifications, no message content, no exte
1111
```json
1212
{
1313
"$schema": "https://opencode.ai/config.json",
14-
"plugin": ["opencode-bell@0.1.0"]
14+
"plugin": ["opencode-bell@0.1.1"]
1515
}
1616
```
1717

@@ -32,6 +32,12 @@ The plugin triggers a terminal bell for these events:
3232
- `session.idle`
3333
- `session.error`
3434

35+
Behavior details:
36+
37+
- Rings only when `process.stdout.isTTY` is true.
38+
- Debounces repeated rings for the same key within 1200ms.
39+
- Uses per-session keys when `event.properties.sessionID` is present.
40+
3541
## Verify
3642

3743
- Trigger a permission request (e.g., any tool that requires approval).

test/plugin.test.js

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,47 @@ import assert from "node:assert/strict"
33

44
import { OpencodeBellPlugin } from "../index.js"
55

6+
const withStdoutStub = async ({ isTTY = true }, run) => {
7+
const originalWrite = process.stdout.write
8+
const originalDescriptor = Object.getOwnPropertyDescriptor(process.stdout, "isTTY")
9+
const writes = []
10+
11+
process.stdout.write = (chunk) => {
12+
writes.push(chunk)
13+
return true
14+
}
15+
Object.defineProperty(process.stdout, "isTTY", {
16+
configurable: true,
17+
value: isTTY
18+
})
19+
20+
try {
21+
await run(writes)
22+
} finally {
23+
process.stdout.write = originalWrite
24+
if (originalDescriptor) {
25+
Object.defineProperty(process.stdout, "isTTY", originalDescriptor)
26+
}
27+
}
28+
}
29+
30+
const withMockedNow = async (times, run) => {
31+
const originalNow = Date.now
32+
let index = 0
33+
34+
Date.now = () => {
35+
const value = times[index]
36+
index += 1
37+
return value
38+
}
39+
40+
try {
41+
await run()
42+
} finally {
43+
Date.now = originalNow
44+
}
45+
}
46+
647
test("does not throw when event.properties is missing", async () => {
748
const plugin = await OpencodeBellPlugin()
849
await assert.doesNotReject(
@@ -11,3 +52,72 @@ test("does not throw when event.properties is missing", async () => {
1152
})
1253
)
1354
})
55+
56+
test("rings once for each supported event type", async () => {
57+
const plugin = await OpencodeBellPlugin()
58+
59+
await withStdoutStub({ isTTY: true }, async (writes) => {
60+
await plugin.event({ event: { type: "permission.asked" } })
61+
await plugin.event({ event: { type: "question.asked" } })
62+
await plugin.event({ event: { type: "session.idle" } })
63+
await plugin.event({ event: { type: "session.error" } })
64+
65+
assert.equal(writes.length, 4)
66+
assert.deepEqual(writes, ["\x07", "\x07", "\x07", "\x07"])
67+
})
68+
})
69+
70+
test("does not ring for unsupported event types", async () => {
71+
const plugin = await OpencodeBellPlugin()
72+
73+
await withStdoutStub({ isTTY: true }, async (writes) => {
74+
await plugin.event({ event: { type: "session.started" } })
75+
assert.equal(writes.length, 0)
76+
})
77+
})
78+
79+
test("does not ring when stdout is not a TTY", async () => {
80+
const plugin = await OpencodeBellPlugin()
81+
82+
await withStdoutStub({ isTTY: false }, async (writes) => {
83+
await plugin.event({ event: { type: "session.idle" } })
84+
assert.equal(writes.length, 0)
85+
})
86+
})
87+
88+
test("debounces repeated events within 1200ms for the same key", async () => {
89+
const plugin = await OpencodeBellPlugin()
90+
91+
await withStdoutStub({ isTTY: true }, async (writes) => {
92+
await withMockedNow([2000, 2500, 3301], async () => {
93+
await plugin.event({ event: { type: "permission.asked" } })
94+
await plugin.event({ event: { type: "permission.asked" } })
95+
await plugin.event({ event: { type: "permission.asked" } })
96+
})
97+
98+
assert.equal(writes.length, 2)
99+
})
100+
})
101+
102+
test("uses session-specific debounce keys", async () => {
103+
const plugin = await OpencodeBellPlugin()
104+
105+
await withStdoutStub({ isTTY: true }, async (writes) => {
106+
await withMockedNow([2000, 2100], async () => {
107+
await plugin.event({
108+
event: {
109+
type: "permission.asked",
110+
properties: { sessionID: "A" }
111+
}
112+
})
113+
await plugin.event({
114+
event: {
115+
type: "permission.asked",
116+
properties: { sessionID: "B" }
117+
}
118+
})
119+
})
120+
121+
assert.equal(writes.length, 2)
122+
})
123+
})

0 commit comments

Comments
 (0)