@@ -3,6 +3,47 @@ import assert from "node:assert/strict"
33
44import { 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+
647test ( "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