Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions docs/api/expect.md
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,42 @@ test('getApplesCount has some unusual side effects...', () => {
})
```

## toBeOneOf

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

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

```ts
import { expect, test } from 'vitest'

test('fruit is one of the allowed values', () => {
expect(fruit).toBeOneOf(['apple', 'banana', 'orange'])
})
```

The asymmetric matcher is particularly useful when testing optional properties that could be either `null` or `undefined`:

```ts
test('optional properties can be null or undefined', () => {
const user = {
firstName: 'John',
middleName: undefined,
lastName: 'Doe'
}

expect(user).toEqual({
firstName: expect.any(String),
middleName: expect.toBeOneOf([expect.any(String), undefined]),
lastName: expect.any(String),
})
})
```

:::tip
You can use `expect.not` with this matcher to ensure a value does NOT match any of the provided options.
:::

## toBeTypeOf

- **Type:** `(c: 'bigint' | 'boolean' | 'function' | 'number' | 'object' | 'string' | 'symbol' | 'undefined') => Awaitable<void>`
Expand Down
37 changes: 37 additions & 0 deletions packages/expect/src/custom-matchers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,43 @@ ${matcherHint('.toSatisfy', 'received', '')}
Expected value to satisfy:
${message || printExpected(expected)}

Received:
${printReceived(actual)}`,
}
},

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

if (!Array.isArray(expected)) {
throw new TypeError(
`You must provide an array to ${matcherHint('.toBeOneOf')}, not '${typeof expected}'.`,
)
}

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

return {
pass,
message: () =>
pass
? `\
${matcherHint('.not.toBeOneOf', 'received', '')}

Expected value to not be one of:
${printExpected(expected)}
Received:
${printReceived(actual)}`
: `\
${matcherHint('.toBeOneOf', 'received', '')}

Expected value to be one of:
${printExpected(expected)}

Received:
${printReceived(actual)}`,
}
Expand Down
2 changes: 1 addition & 1 deletion packages/expect/src/jest-extend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ function JestExtendPlugin(
}

toAsymmetricMatcher() {
return `${this.toString()}<${this.sample.map(String).join(', ')}>`
return `${this.toString()}<${this.sample.map(item => stringify(item)).join(', ')}>`
}
}

Expand Down
10 changes: 10 additions & 0 deletions packages/expect/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,16 @@ interface CustomMatcher {
* expect(age).toEqual(expect.toSatisfy(val => val >= 18, 'Age must be at least 18'));
*/
toSatisfy: (matcher: (value: any) => boolean, message?: string) => any

/**
* Matches if the received value is one of the values in the expected array.
*
* @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
}

export interface AsymmetricMatchersContaining extends CustomMatcher {
Expand Down
Loading
Loading