import { expect } from '@jest/globals'; import type { MatcherFunction } from 'expect'; import { $, _, Lazy } from '../src/core'; class Deferrable { constructor( public readonly when: 'always' | 'deferred' | 'never', public readonly what: Lazy) { } } const toEvaluateTo: MatcherFunction<[expected: unknown]> = function (actual, expected) { if (!(actual instanceof Lazy)) { throw new TypeError('Actual must be of type Lazy!'); } if (this.equals($(actual), expected)) { return { message: () => `expected ${this.utils.printReceived($(actual))} not to evaluate to ${this.utils.printExpected(expected)}`, pass: true, }; } else { return { message: () => `expected ${this.utils.printReceived($(actual))} to evaluate to ${this.utils.printExpected(expected)}`, pass: false, }; } }; const toDeferEvaluationOf: MatcherFunction<(unknown | Deferrable)[]> = function (actual, ...args) { if (typeof actual !== 'function') { throw new TypeError('Actual must be of type function!'); } if (this.isNot) { throw new TypeError('`toDeferEvaluationOf` can not be `not`ted, use `always`/`deferred`/`never` on arguments!'); } const largs: Deferrable[] = args.map(v => v instanceof Deferrable ? v : v instanceof Lazy ? new Deferrable('deferred', _(v.value)) : new Deferrable('deferred', _(v))); const cargs: Deferrable[] = []; let result = actual; for (const la of largs) { result = result(la.what); cargs.push(la); const mismatchingArg: Deferrable | undefined = cargs.find(v => v.when === 'always' ? !v.what.evaluated : v.what.evaluated); if (mismatchingArg) { return { message: () => mismatchingArg.when === 'always' ? `expected ${this.utils.printExpected(actual.name)} to evaluate ${this.utils.printReceived(`${largs.indexOf(mismatchingArg) + 1}th argument`)} immediately` : `expected ${this.utils.printExpected(actual.name)} not to evaluate ${this.utils.printReceived(`${largs.indexOf(mismatchingArg) + 1}th argument`)} immediately`, pass: false, }; } } if (!(result instanceof Lazy)) { throw new TypeError('Actual did not resolve to value, more arguments expected.'); } result.value; const mismatchingArg: Deferrable | undefined = cargs.find(v => v.when === 'never' && v.what.evaluated); if (mismatchingArg) { return { message: () => `expected ${this.utils.printExpected(actual.name)} not to evaluate ${this.utils.printReceived(`${largs.indexOf(mismatchingArg) + 1}th argument`)}`, pass: false, }; } return { message: () => `expected ${this.utils.printExpected('toDeferEvaluationOf')} to ${this.utils.printReceived('fail')}`, pass: true, }; }; expect.extend({ toEvaluateTo, toDeferEvaluationOf, }); expect.evaluated = (v) => new Deferrable('always', _(v)); expect.deferred = (v) => new Deferrable('deferred', _(v)); expect.skipped = (v) => new Deferrable('never', _(v)); declare module 'expect' { interface AsymmetricMatchers { evaluated(value: T): Deferrable; deferred(value: T): Deferrable; skipped(value: T): Deferrable; toEvaluateTo(value: any): void; toDeferEvaluationOf(...args: any[]): void; } interface Matchers { toEvaluateTo(value: any): R; toDeferEvaluationOf(...args: any[]): R; } }