Created
July 15, 2026 21:53
-
-
Save kettanaito/24540d794c11057619ebe3da21826854 to your computer and use it in GitHub Desktop.
expect.optional
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { isDeepStrictEqual } from 'node:util' | |
| import { expect } from 'vitest' | |
| class OptionalMatcher { | |
| readonly $$typeof = Symbol.for('jest.asymmetricMatcher') | |
| constructor(private readonly expected: unknown) {} | |
| asymmetricMatch(received: unknown): boolean { | |
| if (received === undefined) { | |
| return true | |
| } | |
| return isDeepStrictEqual(received, this.expected) | |
| } | |
| toString(): string { | |
| return 'Optional' | |
| } | |
| getExpectedType(): string { | |
| return 'any' | |
| } | |
| toAsymmetricMatcher(): string { | |
| return `Optional<${JSON.stringify(this.expected)}>` | |
| } | |
| } | |
| function isPlainObject(value: unknown): value is Record<string, unknown> { | |
| if (value === null || typeof value !== 'object') { | |
| return false | |
| } | |
| if (Array.isArray(value)) { | |
| return false | |
| } | |
| if ( | |
| '$$typeof' in value && | |
| value.$$typeof === Symbol.for('jest.asymmetricMatcher') | |
| ) { | |
| return false | |
| } | |
| const prototype = Object.getPrototypeOf(value) | |
| return prototype === null || prototype === Object.prototype | |
| } | |
| /** | |
| * Custom equality testers: | |
| * | |
| * 1. Trailing-optional arrays — when one array is longer than the other and | |
| * every "extra" slot in the longer one is an `OptionalMatcher`, treat | |
| * those slots as absent and compare only the shared prefix. | |
| * | |
| * 2. Missing-optional object keys — when a key is present on one object but | |
| * absent on the other AND its value on the present side is an | |
| * `OptionalMatcher`, consider the key absent on both sides. | |
| * | |
| * Both are needed because vitest's default deep-equality rejects on size | |
| * mismatch (array length, key count) BEFORE consulting asymmetric matchers, | |
| * so the matcher's "received === undefined ⇒ true" branch never gets a | |
| * chance to run otherwise. | |
| */ | |
| expect.addEqualityTesters([ | |
| function trailingOptional(a, b, customTesters) { | |
| if (!Array.isArray(a) || !Array.isArray(b)) { | |
| return undefined | |
| } | |
| if (a.length === b.length) { | |
| return undefined | |
| } | |
| const [shorter, longer] = a.length < b.length ? [a, b] : [b, a] | |
| for (let index = shorter.length; index < longer.length; index++) { | |
| if (!(longer[index] instanceof OptionalMatcher)) { | |
| return undefined | |
| } | |
| } | |
| for (let index = 0; index < shorter.length; index++) { | |
| if (!this.equals(a[index], b[index], customTesters)) { | |
| return false | |
| } | |
| } | |
| return true | |
| }, | |
| function missingOptionalKey(a, b, customTesters) { | |
| if (!isPlainObject(a) || !isPlainObject(b)) { | |
| return undefined | |
| } | |
| let hasMissingOptional = false | |
| for (const key of Object.keys(a)) { | |
| if (!(key in b) && a[key] instanceof OptionalMatcher) { | |
| hasMissingOptional = true | |
| break | |
| } | |
| } | |
| if (!hasMissingOptional) { | |
| for (const key of Object.keys(b)) { | |
| if (!(key in a) && b[key] instanceof OptionalMatcher) { | |
| hasMissingOptional = true | |
| break | |
| } | |
| } | |
| } | |
| if (!hasMissingOptional) { | |
| return undefined | |
| } | |
| const allKeys = new Set<string>([...Object.keys(a), ...Object.keys(b)]) | |
| for (const key of allKeys) { | |
| if (!this.equals(a[key], b[key], customTesters)) { | |
| return false | |
| } | |
| } | |
| return true | |
| }, | |
| ]) | |
| Object.defineProperty(expect, 'optional', { | |
| value: <T>(expected: T): OptionalMatcher => new OptionalMatcher(expected), | |
| }) | |
| declare module 'vitest' { | |
| interface AsymmetricMatchersContaining { | |
| /** | |
| * Always accepts a value. | |
| * Passes if no value is present at its place. | |
| * If a value is present, matches it for equality and passes only if equal. | |
| */ | |
| optional<T>(expected: T): unknown | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment