Skip to content
Merged
8 changes: 6 additions & 2 deletions docs/api/expect.md
Original file line number Diff line number Diff line change
Expand Up @@ -379,9 +379,13 @@ test('getApplesCount has some unusual side effects...', () => {

## toBeOneOf

- **Type:** `(sample: Array<any>) => any`
- **Type:** `(sample: Array<any> | Set<any>) => any`

`toBeOneOf` asserts if a value matches any of the values in the provided array.
`toBeOneOf` asserts if a value matches any of the values in the provided array or set.

::: warning EXPERIMENTAL
Providing a `Set` is an experimental feature and may change in a future release.
:::

```ts
import { expect, test } from 'vitest'
Expand Down
22 changes: 14 additions & 8 deletions packages/expect/src/custom-matchers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,21 +27,27 @@ ${printReceived(actual)}`,
}
},

toBeOneOf(actual: unknown, expected: Array<unknown>) {
toBeOneOf(actual: unknown, expected: Array<unknown> | Set<unknown>) {
const { equals, customTesters } = this
const { printReceived, printExpected, matcherHint } = this.utils

if (!Array.isArray(expected)) {
let pass: boolean

if (Array.isArray(expected)) {
pass = expected.length === 0
|| expected.some(item =>
equals(item, actual, customTesters),
)
}
else if (expected instanceof Set) {
pass = expected.size === 0 || expected.has(actual) || [...expected].some(item => equals(item, actual, customTesters))
}
else {
throw new TypeError(
`You must provide an array to ${matcherHint('.toBeOneOf')}, not '${typeof expected}'.`,
`You must provide an array or set to ${matcherHint('.toBeOneOf')}, not '${typeof expected}'.`,
)
}

const pass = expected.length === 0
|| expected.some(item =>
equals(item, actual, customTesters),
)

return {
pass,
message: () =>
Expand Down
4 changes: 2 additions & 2 deletions packages/expect/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,14 +129,14 @@ interface CustomMatcher {
toSatisfy: (matcher: (value: any) => boolean, message?: string) => any

/**
* Matches if the received value is one of the values in the expected array.
* Matches if the received value is one of the values in the expected array or set.
*
* @example
* expect(1).toBeOneOf([1, 2, 3])
* expect('foo').toBeOneOf([expect.any(String)])
* expect({ a: 1 }).toEqual({ a: expect.toBeOneOf(['1', '2', '3']) })
*/
toBeOneOf: <T>(sample: Array<T>) => any
toBeOneOf: <T>(sample: Array<T> | Set<T>) => any
}

export interface AsymmetricMatchersContaining extends CustomMatcher {
Expand Down
6 changes: 6 additions & 0 deletions test/core/test/jest-expect.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -636,7 +636,9 @@ describe('toBeOneOf()', () => {
expect(0).toBeOneOf([0, 1, 2])
expect(0).toBeOneOf([expect.any(Number)])
expect('apple').toBeOneOf(['apple', 'banana', 'orange'])
expect('apple').toBeOneOf(new Set(['apple', 'banana', 'orange']))
expect('apple').toBeOneOf([expect.any(String)])
expect('apple').toBeOneOf(new Set([expect.any(String)]))
expect(true).toBeOneOf([true, false])
expect(true).toBeOneOf([expect.any(Boolean)])
expect(null).toBeOneOf([expect.any(Object)])
Expand All @@ -647,15 +649,19 @@ describe('toBeOneOf()', () => {
expect(3).not.toBeOneOf([0, 1, 2])
expect(3).not.toBeOneOf([expect.any(String)])
expect('mango').not.toBeOneOf(['apple', 'banana', 'orange'])
expect('mango').not.toBeOneOf(new Set(['apple', 'banana', 'orange']))
expect('mango').not.toBeOneOf([expect.any(Number)])
expect('mango').not.toBeOneOf(new Set([expect.any(Number)]))
expect(null).not.toBeOneOf([undefined])
})

it.fails('fail with missing negotiation', () => {
expect(3).toBeOneOf([0, 1, 2])
expect(3).toBeOneOf([expect.any(String)])
expect('mango').toBeOneOf(['apple', 'banana', 'orange'])
expect('mango').toBeOneOf(new Set(['apple', 'banana', 'orange']))
expect('mango').toBeOneOf([expect.any(Number)])
expect('mango').toBeOneOf(new Set([expect.any(Number)]))
expect(null).toBeOneOf([undefined])
})

Expand Down
Loading