Skip to content

Commit 50a4cdb

Browse files
committed
add denouncer testing
1 parent deb51ba commit 50a4cdb

File tree

1 file changed

+95
-0
lines changed

1 file changed

+95
-0
lines changed
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
@testable import AsyncTimer
2+
import Foundation
3+
import Testing
4+
5+
@Suite struct AsyncDebouncerTests {
6+
// MARK: - Basic debounce (trailing)
7+
8+
@MainActor
9+
@Test func basic_debounce_trailing_executes_once() async throws {
10+
var count = 0
11+
let debouncer = AsyncDebouncer(debounceTime: .seconds(0.05))
12+
13+
await debouncer.call {
14+
@MainActor in count += 1
15+
}
16+
try await Task.sleep(nanoseconds: UInt64(0.01 * 1_000_000_000))
17+
await debouncer.call {
18+
@MainActor in count += 1
19+
}
20+
try await Task.sleep(nanoseconds: UInt64(0.01 * 1_000_000_000))
21+
await debouncer.call {
22+
@MainActor in count += 1
23+
}
24+
25+
// Wait slightly longer than debounce time; should only fire once
26+
try await Task.sleep(nanoseconds: UInt64(0.07 * 1_000_000_000))
27+
#expect(count == 1, "Debouncer should execute only once after the last call")
28+
}
29+
30+
// MARK: - Cancel
31+
32+
@MainActor
33+
@Test func cancel_prevents_execution() async throws {
34+
var count = 0
35+
let debouncer = AsyncDebouncer(debounceTime: .seconds(0.05))
36+
37+
await debouncer.call {
38+
@MainActor in count += 1
39+
}
40+
// Cancel before the debounce window elapses
41+
await debouncer.cancel()
42+
try await Task.sleep(nanoseconds: UInt64(0.06 * 1_000_000_000))
43+
#expect(count == 0, "Cancel should prevent the scheduled execution")
44+
}
45+
46+
// MARK: - Override debounce time per call
47+
48+
@MainActor
49+
@Test func override_debounce_time_per_call() async throws {
50+
var firedAt: TimeInterval = 0
51+
let start = Date().timeIntervalSince1970
52+
let debouncer = AsyncDebouncer(debounceTime: .seconds(0.2))
53+
54+
await debouncer.call(debounceTime: .seconds(0.05)) {
55+
@MainActor in firedAt = Date().timeIntervalSince1970 - start
56+
}
57+
58+
try await Task.sleep(nanoseconds: UInt64(0.07 * 1_000_000_000))
59+
#expect(firedAt > 0, "Handler should have fired")
60+
#expect(firedAt < 0.12, "Override should shorten debounce significantly")
61+
}
62+
63+
// MARK: - isWaiting state
64+
65+
@MainActor
66+
@Test func isWaiting_reflects_pending_state() async throws {
67+
let debouncer = AsyncDebouncer(debounceTime: .seconds(0.05))
68+
69+
await debouncer.call {}
70+
// Give the actor a tick to schedule the timer
71+
await Task.yield()
72+
73+
let waiting1 = await debouncer.isWaiting
74+
#expect(waiting1, "Debouncer should be waiting during the debounce window")
75+
76+
// After firing, it should no longer be waiting
77+
try await Task.sleep(nanoseconds: UInt64(0.07 * 1_000_000_000))
78+
let waiting2 = await debouncer.isWaiting
79+
#expect(!waiting2, "Debouncer should not be waiting after the handler fires")
80+
}
81+
82+
// MARK: - Zero interval executes immediately
83+
84+
@MainActor
85+
@Test func zero_interval_executes_immediately() async throws {
86+
var count = 0
87+
let debouncer = AsyncDebouncer(debounceTime: .seconds(0))
88+
89+
await debouncer.call { @MainActor in count += 1 }
90+
await debouncer.call { @MainActor in count += 1 }
91+
await debouncer.call { @MainActor in count += 1 }
92+
93+
#expect(count == 3, "Zero interval should bypass debouncing and execute immediately")
94+
}
95+
}

0 commit comments

Comments
 (0)