-
-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathevents_buffer.ts
More file actions
243 lines (220 loc) · 6.39 KB
/
events_buffer.ts
File metadata and controls
243 lines (220 loc) · 6.39 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
/*
* @adonisjs/events
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import is from '@sindresorhus/is'
import string from '@poppinss/utils/string'
import { AssertionError } from 'node:assert'
import { type Constructor } from '@poppinss/utils/types'
import type { AllowedEventTypes, BufferedEvent, BufferedEventsList } from './types.ts'
/**
* Callback function to narrow down an event from
* the events buffer list
*/
type EventFinderCallback<
EventsList extends Record<string | symbol | number, any>,
Event extends keyof EventsList | Constructor<any>,
> = (
event: Event extends keyof EventsList
? BufferedEvent<Event, EventsList[Event]>
: Event extends Constructor<infer A>
? BufferedEvent<Event, A>
: never
) => boolean
/**
* Exposes API to filter, find events from the events buffer.
*/
export class EventsBuffer<EventsList extends Record<string | symbol | number, any>> {
/**
* Buffered events
*/
#events: BufferedEventsList<EventsList>[] = []
/**
* Function to call when disposing the buffer to restore
* the emitter to its original state
*/
#restoreFn: () => void
constructor(restoreFn: () => void) {
this.#restoreFn = restoreFn
}
/**
* Track emitted event
*
* @param event - The event that was emitted
* @param data - The data passed with the event
*/
add<Name extends AllowedEventTypes>(event: Name, data: any): void {
this.#events.push({ event: event as any, data })
}
/**
* Get all the emitted events
*
* @returns Array of all buffered events
*/
all(): BufferedEventsList<EventsList>[] {
return this.#events
}
/**
* Returns the size of captured events
*
* @returns The number of captured events
*/
size(): number {
return this.#events.length
}
/**
* Find if an event was emitted
*
* @param event - The event to check for
* @param finder - Optional callback to filter specific event instances
* @returns True if the event was emitted
*/
exists<Event extends keyof EventsList | Constructor<any>>(
event: Event,
finder?: EventFinderCallback<EventsList, Event>
): boolean {
return !!this.find(event, finder)
}
/**
* Find a specific event
*
* @param event - The event to find
* @param finder - Optional callback to filter specific event instances
* @returns The found event or null
*/
find<Event extends keyof EventsList | Constructor<any>>(
event: Event,
finder?: EventFinderCallback<EventsList, Event>
):
| (Event extends keyof EventsList
? BufferedEvent<Event, EventsList[Event]>
: Event extends Constructor<infer A>
? BufferedEvent<Event, A>
: never)
| null {
return (this.#events.find((bufferedEvent) => {
if (!finder) {
return bufferedEvent.event === event
}
return (
bufferedEvent.event === event &&
finder(bufferedEvent as Parameters<EventFinderCallback<EventsList, Event>>[0])
)
}) || null) as any
}
/**
* Assert a given event has been emitted
*
* @param event - The event to assert was emitted
* @param finder - Optional callback to filter specific event instances
* @throws AssertionError if the event was not emitted
*/
assertEmitted<Event extends keyof EventsList | Constructor<any>>(
event: Event,
finder?: EventFinderCallback<EventsList, Event>
): void {
const hasEvent = this.exists(event, finder)
if (!hasEvent) {
const message = is.class(event)
? `Expected "[class ${event.name}]" event to be emitted`
: `Expected "${String(event)}" event to be emitted`
throw new AssertionError({
message: message,
expected: true,
actual: false,
operator: 'strictEqual',
stackStartFn: this.assertEmitted,
})
}
}
/**
* Assert number of times an event has been emitted
*
* @param event - The event to check emission count for
* @param count - The expected number of emissions
* @throws AssertionError if the count doesn't match
*/
assertEmittedCount<Event extends keyof EventsList | Constructor<any>>(
event: Event,
count: number
): void {
const actual = this.all().filter((bufferedEvent) => bufferedEvent.event === event).length
if (actual !== count) {
const eventName = is.class(event) ? `[class ${event.name}]` : String(event)
throw new AssertionError({
message: `Expected "${eventName}" event to be emitted "${count}" ${string.pluralize(
'time',
count
)}, instead it was emitted "${actual}" ${string.pluralize('time', actual)}`,
actual,
expected: count,
})
}
}
/**
* Assert a given event has been not been emitted
*
* @param event - The event to assert was not emitted
* @param finder - Optional callback to filter specific event instances
* @throws AssertionError if the event was emitted
*/
assertNotEmitted<Event extends keyof EventsList | Constructor<any>>(
event: Event,
finder?: EventFinderCallback<EventsList, Event>
): void {
const hasEvent = this.exists(event, finder)
if (hasEvent) {
const isClass = is.class(event)
const message = isClass
? `Unexpected "[class ${event.name}]" event was emitted`
: `Unexpected "${String(event)}" event was emitted`
throw new AssertionError({
message: message,
expected: false,
actual: true,
operator: 'strictEqual',
stackStartFn: this.assertNotEmitted,
})
}
}
/**
* Assert no events have been emitted
*
* @throws AssertionError if any events were emitted
*/
assertNoneEmitted(): void {
const eventsSize = this.size()
if (eventsSize > 0) {
throw new AssertionError(
Object.assign(
{
message: `Expected zero events to be emitted. Instead received "${eventsSize}" ${string.pluralize(
'event',
eventsSize
)}`,
expected: 0,
actual: eventsSize,
operator: 'strictEqual',
stackStartFn: this.assertNoneEmitted,
},
{
showDiff: true,
}
)
)
}
}
/**
* Flush events collected within memory
*/
flush(): void {
this.#events = []
}
[Symbol.dispose](): void {
this.#restoreFn()
}
}