-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathaudio-capture.svelte.spec.ts
More file actions
126 lines (99 loc) · 3.26 KB
/
audio-capture.svelte.spec.ts
File metadata and controls
126 lines (99 loc) · 3.26 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
import { flushSync } from 'svelte'
import { describe, expect, it, vi } from 'vitest'
import { createAudioCapturer } from '../create-audio-capturer.svelte.ts'
type MockAudioInClient = {
callOptions: Record<string, unknown>
getAudio: ReturnType<typeof vi.fn>
}
const makeClient = (mockGetAudio: ReturnType<typeof vi.fn>): { current: MockAudioInClient } => ({
current: {
callOptions: {},
getAudio: mockGetAudio,
},
})
async function* makeChunkStream(chunks: Uint8Array[]) {
for (const chunk of chunks) {
yield { audioData: chunk }
}
}
async function* makeErrorStream(message: string) {
yield { audioData: new Uint8Array([1]) }
throw new Error(message)
}
async function* makeAbortableStream(signal: AbortSignal) {
yield { audioData: new Uint8Array([1, 2, 3]) }
await new Promise<never>((_, reject) => {
signal.addEventListener('abort', () => reject(signal.reason))
})
}
describe('createAudioCapture', () => {
it('successful capture sets status to done and provides downloadUrl with correct totalBytes', async () => {
const chunk1 = new Uint8Array([1, 2, 3])
const chunk2 = new Uint8Array([4, 5])
const mockGetAudio = vi.fn().mockReturnValue(makeChunkStream([chunk1, chunk2]))
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const client = makeClient(mockGetAudio) as any
let capture: ReturnType<typeof createAudioCapturer> | undefined
const cleanup = $effect.root(() => {
capture = createAudioCapturer(client)
})
try {
await capture!.start('wav', 3)
flushSync()
expect(capture!.status).toBe('done')
expect(capture!.downloadUrl).toBeDefined()
expect(capture!.totalBytes).toBe(chunk1.byteLength + chunk2.byteLength)
expect(capture!.error).toBeNull()
} finally {
cleanup()
}
})
it('error in stream sets status to error with the error message', async () => {
const mockGetAudio = vi.fn().mockReturnValue(makeErrorStream('stream failed'))
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const client = makeClient(mockGetAudio) as any
let capture: ReturnType<typeof createAudioCapturer> | undefined
const cleanup = $effect.root(() => {
capture = createAudioCapturer(client)
})
try {
await capture!.start('wav', 3)
flushSync()
expect(capture!.status).toBe('error')
expect(capture!.error?.message).toBe('stream failed')
} finally {
cleanup()
}
})
it('stop aborts the stream and sets status to done without an error', async () => {
const mockGetAudio = vi
.fn()
.mockImplementation(
(
_codec: string,
_duration: number,
_offset: bigint,
_extra: unknown,
callOptions: { signal: AbortSignal }
) => makeAbortableStream(callOptions.signal)
)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const client = makeClient(mockGetAudio) as any
let capture: ReturnType<typeof createAudioCapturer> | undefined
const cleanup = $effect.root(() => {
capture = createAudioCapturer(client)
})
try {
const startPromise = capture!.start('wav', 0)
// Let the stream start and yield the first chunk
await new Promise((resolve) => setTimeout(resolve, 0))
capture!.stop()
await startPromise
flushSync()
expect(capture!.status).toBe('done')
expect(capture!.error).toBeNull()
} finally {
cleanup()
}
})
})