Skip to content

Commit 3d742b2

Browse files
zirkelchi-ogawa
andauthored
feat(expect): add toBeOneOf matcher (#6974)
Co-authored-by: Hiroshi Ogawa <hi.ogawa.zz@gmail.com>
1 parent 78b62ff commit 3d742b2

File tree

6 files changed

+340
-35
lines changed

6 files changed

+340
-35
lines changed

docs/api/expect.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,42 @@ test('getApplesCount has some unusual side effects...', () => {
309309
})
310310
```
311311

312+
## toBeOneOf
313+
314+
- **Type:** `(sample: Array<any>) => any`
315+
316+
`toBeOneOf` asserts if a value matches any of the values in the provided array.
317+
318+
```ts
319+
import { expect, test } from 'vitest'
320+
321+
test('fruit is one of the allowed values', () => {
322+
expect(fruit).toBeOneOf(['apple', 'banana', 'orange'])
323+
})
324+
```
325+
326+
The asymmetric matcher is particularly useful when testing optional properties that could be either `null` or `undefined`:
327+
328+
```ts
329+
test('optional properties can be null or undefined', () => {
330+
const user = {
331+
firstName: 'John',
332+
middleName: undefined,
333+
lastName: 'Doe'
334+
}
335+
336+
expect(user).toEqual({
337+
firstName: expect.any(String),
338+
middleName: expect.toBeOneOf([expect.any(String), undefined]),
339+
lastName: expect.any(String),
340+
})
341+
})
342+
```
343+
344+
:::tip
345+
You can use `expect.not` with this matcher to ensure a value does NOT match any of the provided options.
346+
:::
347+
312348
## toBeTypeOf
313349

314350
- **Type:** `(c: 'bigint' | 'boolean' | 'function' | 'number' | 'object' | 'string' | 'symbol' | 'undefined') => Awaitable<void>`

packages/expect/src/custom-matchers.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,43 @@ ${matcherHint('.toSatisfy', 'received', '')}
2222
Expected value to satisfy:
2323
${message || printExpected(expected)}
2424
25+
Received:
26+
${printReceived(actual)}`,
27+
}
28+
},
29+
30+
toBeOneOf(actual: unknown, expected: Array<unknown>) {
31+
const { equals, customTesters } = this
32+
const { printReceived, printExpected, matcherHint } = this.utils
33+
34+
if (!Array.isArray(expected)) {
35+
throw new TypeError(
36+
`You must provide an array to ${matcherHint('.toBeOneOf')}, not '${typeof expected}'.`,
37+
)
38+
}
39+
40+
const pass = expected.length === 0
41+
|| expected.some(item =>
42+
equals(item, actual, customTesters),
43+
)
44+
45+
return {
46+
pass,
47+
message: () =>
48+
pass
49+
? `\
50+
${matcherHint('.not.toBeOneOf', 'received', '')}
51+
52+
Expected value to not be one of:
53+
${printExpected(expected)}
54+
Received:
55+
${printReceived(actual)}`
56+
: `\
57+
${matcherHint('.toBeOneOf', 'received', '')}
58+
59+
Expected value to be one of:
60+
${printExpected(expected)}
61+
2562
Received:
2663
${printReceived(actual)}`,
2764
}

packages/expect/src/jest-extend.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ function JestExtendPlugin(
132132
}
133133

134134
toAsymmetricMatcher() {
135-
return `${this.toString()}<${this.sample.map(String).join(', ')}>`
135+
return `${this.toString()}<${this.sample.map(item => stringify(item)).join(', ')}>`
136136
}
137137
}
138138

packages/expect/src/types.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,16 @@ interface CustomMatcher {
118118
* expect(age).toEqual(expect.toSatisfy(val => val >= 18, 'Age must be at least 18'));
119119
*/
120120
toSatisfy: (matcher: (value: any) => boolean, message?: string) => any
121+
122+
/**
123+
* Matches if the received value is one of the values in the expected array.
124+
*
125+
* @example
126+
* expect(1).toBeOneOf([1, 2, 3])
127+
* expect('foo').toBeOneOf([expect.any(String)])
128+
* expect({ a: 1 }).toEqual({ a: expect.toBeOneOf(['1', '2', '3']) })
129+
*/
130+
toBeOneOf: <T>(sample: Array<T>) => any
121131
}
122132

123133
export interface AsymmetricMatchersContaining extends CustomMatcher {

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy