Skip to content

Commit c135ab5

Browse files
committed
test(sanity): add toMatchEmissions matcher
1 parent 9ffb28c commit c135ab5

File tree

4 files changed

+271
-74
lines changed

4 files changed

+271
-74
lines changed

packages/sanity/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,7 @@
305305
"@types/semver": "^6.2.3",
306306
"@types/tar-fs": "^2.0.1",
307307
"@vitejs/plugin-react": "^4.3.4",
308+
"@vitest/expect": "^3.0.5",
308309
"@vvo/tzdb": "6.137.0",
309310
"babel-plugin-react-compiler": "19.0.0-beta-714736e-20250131",
310311
"blob-polyfill": "^9.0.20240710",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import {type AsyncExpectationResult, type MatcherState} from '@vitest/expect'
2+
import {firstValueFrom, type OperatorFunction, Subject, toArray} from 'rxjs'
3+
4+
export const NO_EMISSION = Symbol('NO_EMISSION')
5+
6+
type Snapshot<A, B> = [A, B | typeof NO_EMISSION]
7+
8+
interface OperatorFunctionMatchers<Type = unknown> {
9+
/**
10+
* Ensure each entry in the provided array results in the expected value being emitted when piped
11+
* to the observable.
12+
*/
13+
toMatchEmissions: Type extends () => OperatorFunction<infer A, infer B>
14+
? (snapshots: Snapshot<A, B>[]) => Promise<Type>
15+
: never
16+
}
17+
18+
declare module 'vitest' {
19+
interface Assertion<T = any> extends OperatorFunctionMatchers<T> {}
20+
interface AsymmetricMatchersContaining extends OperatorFunctionMatchers {}
21+
}
22+
23+
export async function toMatchEmissions(
24+
this: MatcherState,
25+
createOperator: () => OperatorFunction<unknown, unknown>,
26+
snapshots: [A: unknown, B: unknown][],
27+
): AsyncExpectationResult {
28+
const {equals} = this
29+
const input$ = new Subject()
30+
31+
const expectedEmissions = snapshots
32+
.filter(([, expectedEmission]) => expectedEmission !== NO_EMISSION)
33+
.map(([, expectedEmission]) => expectedEmission)
34+
35+
const emissions$ = input$.pipe(createOperator(), toArray())
36+
const emissions = firstValueFrom(emissions$)
37+
38+
snapshots.forEach(([value]) => input$.next(value))
39+
input$.complete()
40+
41+
const actualEmissions = await emissions
42+
43+
return {
44+
pass: equals(actualEmissions, expectedEmissions),
45+
message: () => 'Observable emissions did not match',
46+
actual: actualEmissions,
47+
expected: expectedEmissions,
48+
}
49+
}

packages/sanity/test/setup/environment.ts

+7-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,13 @@ import './clipboardItemPolyfill'
66
import '@testing-library/jest-dom/vitest'
77

88
import {cleanup} from '@testing-library/react'
9-
import {afterEach, beforeEach, vi} from 'vitest'
9+
import {afterEach, beforeEach, expect, vi} from 'vitest'
10+
11+
import {toMatchEmissions} from '../matchers/toMatchEmissions'
12+
13+
expect.extend({
14+
toMatchEmissions,
15+
})
1016

1117
afterEach(() => cleanup())
1218

0 commit comments

Comments
 (0)