/** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * */ import { test, expect } from './fixtures'; import { expect as expectUnderTest } from '../../packages/playwright/bundles/expect/src/expectBundleImpl'; expectUnderTest.extend({ toBeDivisibleBy(actual: number, expected: number) { const pass = actual % expected === 0; const message: () => string = pass ? () => `expected ${this.utils.printReceived( actual, )} not to be divisible by ${expected}` : () => `expected ${this.utils.printReceived( actual, )} to be divisible by ${expected}`; return { message, pass }; }, toBeSymbol(actual: symbol, expected: symbol) { const pass = actual === expected; const message = () => `expected ${actual.toString()} to be Symbol ${expected.toString()}`; return { message, pass }; }, toBeWithinRange(actual: number, floor: number, ceiling: number) { const pass = actual >= floor && actual <= ceiling; const message = pass ? () => `expected ${this.utils.printReceived( actual, )} not to be within range ${floor} - ${ceiling}` : () => `expected ${this.utils.printReceived( actual, )} to be within range ${floor} - ${ceiling}`; return { message, pass }; }, }); const expectUnderTestAsAny = expectUnderTest as any; test('is available globally when matcher is unary', () => { expectUnderTestAsAny(15).toBeDivisibleBy(5); expectUnderTestAsAny(15).toBeDivisibleBy(3); expectUnderTestAsAny(15).not.toBeDivisibleBy(6); expect(() => expectUnderTestAsAny(15).toBeDivisibleBy(2), ).toThrowErrorMatchingSnapshot(`expected 15 to be divisible by 2`); }); test('is available globally when matcher is variadic', () => { expectUnderTestAsAny(15).toBeWithinRange(10, 20); expectUnderTestAsAny(15).not.toBeWithinRange(6, 10); expect(() => expectUnderTestAsAny(15).toBeWithinRange(1, 3), ).toThrowErrorMatchingSnapshot(`expected 15 to be within range 1 - 3`); }); test.skip('exposes matcherUtils in context', () => { // expectUnderTest.extend({ // shouldNotError(_actual: unknown) { // const pass: boolean = this.equals( // this.utils, // Object.assign(matcherUtils, { // iterableEquality, // subsetEquality, // }), // ); // const message = pass // ? () => 'expected this.utils to be defined in an extend call' // : () => 'expected this.utils not to be defined in an extend call'; // return { message, pass }; // }, // }); // expectUnderTestAsAny('test').shouldNotError(); }); test('is ok if there is no message specified', () => { expectUnderTest.extend({ toFailWithoutMessage(_expected: unknown) { return { message: () => '', pass: false }; }, }); expect(() => expectUnderTestAsAny(true).toFailWithoutMessage(), ).toThrowErrorMatchingSnapshot(`No message was specified for this matcher.`); }); test('exposes an equality function to custom matchers', () => { expectUnderTest.extend({ toBeOne(_expected: unknown) { return { message: () => '', pass: !!this.equals(1, 1) }; }, }); expect(() => expectUnderTestAsAny('test').toBeOne()).not.toThrow(); }); test('defines asymmetric unary matchers', () => { expect(() => expectUnderTest({ value: 2 }).toEqual({ value: expectUnderTestAsAny.toBeDivisibleBy(2) }), ).not.toThrow(); expect(() => expectUnderTest({ value: 3 }).toEqual({ value: expectUnderTestAsAny.toBeDivisibleBy(2) }), ).toThrowErrorMatchingSnapshot(`expect(received).toEqual(expected) // deep equality - Expected - 1 + Received + 1 Object { - "value": toBeDivisibleBy<2>, + "value": 3, }`); }); test('defines asymmetric unary matchers that can be prefixed by not', () => { expect(() => expectUnderTest({ value: 2 }).toEqual({ value: expectUnderTestAsAny.not.toBeDivisibleBy(2) }), ).toThrowErrorMatchingSnapshot(`expect(received).toEqual(expected) // deep equality - Expected - 1 + Received + 1 Object { - "value": not.toBeDivisibleBy<2>, + "value": 2, }`); expect(() => expectUnderTest({ value: 3 }).toEqual({ value: expectUnderTestAsAny.not.toBeDivisibleBy(2) }), ).not.toThrow(); }); test('defines asymmetric variadic matchers', () => { expect(() => expectUnderTest({ value: 2 }).toEqual({ value: expectUnderTestAsAny.toBeWithinRange(1, 3) }), ).not.toThrow(); expect(() => expectUnderTest({ value: 3 }).toEqual({ value: expectUnderTestAsAny.toBeWithinRange(4, 11) }), ).toThrowErrorMatchingSnapshot(`expect(received).toEqual(expected) // deep equality - Expected - 1 + Received + 1 Object { - "value": toBeWithinRange<4, 11>, + "value": 3, }`); }); test('defines asymmetric variadic matchers that can be prefixed by not', () => { expect(() => expectUnderTest({ value: 2 }).toEqual({ value: expectUnderTestAsAny.not.toBeWithinRange(1, 3), }), ).toThrowErrorMatchingSnapshot(`expect(received).toEqual(expected) // deep equality - Expected - 1 + Received + 1 Object { - "value": not.toBeWithinRange<1, 3>, + "value": 2, }`); expect(() => expectUnderTest({ value: 3 }).toEqual({ value: expectUnderTestAsAny.not.toBeWithinRange(5, 7), }), ).not.toThrow(); }); test('prints the Symbol into the error message', () => { const foo = Symbol('foo'); const bar = Symbol('bar'); expect(() => expectUnderTest({ a: foo }).toEqual({ a: expectUnderTestAsAny.toBeSymbol(bar), }), ).toThrowErrorMatchingSnapshot(`expect(received).toEqual(expected) // deep equality - Expected - 1 + Received + 1 Object { - "a": toBeSymbol, + "a": Symbol(foo), }`); }); test('allows overriding existing extension', () => { expectUnderTest.extend({ toAllowOverridingExistingMatcher(_expected: unknown) { return { message: () => '', pass: _expected === 'bar' }; }, }); expectUnderTestAsAny('foo').not.toAllowOverridingExistingMatcher(); expectUnderTest.extend({ toAllowOverridingExistingMatcher(_expected: unknown) { return { message: () => '', pass: _expected === 'foo' }; }, }); expectUnderTestAsAny('foo').toAllowOverridingExistingMatcher(); }); test('throws descriptive errors for invalid matchers', () => { expect(() => expectUnderTest.extend({ default: undefined, }), ).toThrow( 'expect.extend: `default` is not a valid matcher. Must be a function, is "undefined"', ); expect(() => expectUnderTest.extend({ // @ts-expect-error: Testing runtime error default: 42, }), ).toThrow( 'expect.extend: `default` is not a valid matcher. Must be a function, is "number"', ); expect(() => expectUnderTest.extend({ // @ts-expect-error: Testing runtime error default: 'foobar', }), ).toThrow( 'expect.extend: `default` is not a valid matcher. Must be a function, is "string"', ); });