From 9ba555ba7af8e49d0eb8cf78f7f50b2fcbfd9ce9 Mon Sep 17 00:00:00 2001 From: Tom Mrazauskas Date: Mon, 3 Oct 2022 09:36:48 +0300 Subject: [PATCH] chore: fix types of few `expect` tests (#13361) --- .../src/__tests__/asymmetricMatchers.test.ts | 76 +++++++++++++------ packages/expect/src/__tests__/extend.test.ts | 47 ++++++++---- .../expect/src/__tests__/stacktrace.test.ts | 28 ++++--- 3 files changed, 104 insertions(+), 47 deletions(-) diff --git a/packages/expect/src/__tests__/asymmetricMatchers.test.ts b/packages/expect/src/__tests__/asymmetricMatchers.test.ts index c1db50a353da..0718a8e61672 100644 --- a/packages/expect/src/__tests__/asymmetricMatchers.test.ts +++ b/packages/expect/src/__tests__/asymmetricMatchers.test.ts @@ -104,7 +104,10 @@ test('Any.toAsymmetricMatcher() with function name', () => { }); test('Any throws when called with empty constructor', () => { - jestExpect(() => any()).toThrow(); + // @ts-expect-error: Testing runtime error + jestExpect(() => any()).toThrow( + 'any() expects to be passed a constructor function. Please pass one or use anything() to match any object.', + ); }); test('Anything matches any type', () => { @@ -150,8 +153,9 @@ test('ArrayContaining does not match', () => { test('ArrayContaining throws for non-arrays', () => { jestExpect(() => { + // @ts-expect-error: Testing runtime error arrayContaining('foo').asymmetricMatch([]); - }).toThrow(); + }).toThrow("You must provide an array to ArrayContaining, not 'string'."); }); test('ArrayNotContaining matches', () => { @@ -171,8 +175,9 @@ test('ArrayNotContaining does not match', () => { test('ArrayNotContaining throws for non-arrays', () => { jestExpect(() => { + // @ts-expect-error: Testing runtime error arrayNotContaining('foo').asymmetricMatch([]); - }).toThrow(); + }).toThrow("You must provide an array to ArrayNotContaining, not 'string'."); }); test('ObjectContaining matches', () => { @@ -224,13 +229,16 @@ test('ObjectContaining matches prototype properties', () => { function Foo() {} Foo.prototype = prototypeObject; Foo.prototype.constructor = Foo; - obj = new Foo(); + obj = new (Foo as any)(); } jestExpect(objectContaining({foo: 'bar'}).asymmetricMatch(obj)).toBe(true); }); test('ObjectContaining throws for non-objects', () => { - jestExpect(() => objectContaining(1337).asymmetricMatch()).toThrow(); + // @ts-expect-error: Testing runtime error + jestExpect(() => objectContaining(1337).asymmetricMatch()).toThrow( + "You must provide an object to ObjectContaining, not 'number'.", + ); }); test('ObjectContaining does not mutate the sample', () => { @@ -243,6 +251,8 @@ test('ObjectContaining does not mutate the sample', () => { test('ObjectNotContaining matches', () => { [ + objectContaining({}).asymmetricMatch(null), + objectContaining({}).asymmetricMatch(undefined), objectNotContaining({foo: 'foo'}).asymmetricMatch({bar: 'bar'}), objectNotContaining({foo: 'foo'}).asymmetricMatch({foo: 'foox'}), objectNotContaining({foo: undefined}).asymmetricMatch({}), @@ -278,6 +288,7 @@ test('ObjectNotContaining does not match', () => { first: objectContaining({second: {}}), }).asymmetricMatch({first: {second: {}}}), objectNotContaining({}).asymmetricMatch(null), + objectNotContaining({}).asymmetricMatch(undefined), objectNotContaining({}).asymmetricMatch({}), ].forEach(test => { jestExpect(test).toEqual(false); @@ -285,17 +296,19 @@ test('ObjectNotContaining does not match', () => { }); test('ObjectNotContaining inverts ObjectContaining', () => { - [ - [{}, null], - [{foo: 'foo'}, {foo: 'foo', jest: 'jest'}], - [{foo: 'foo', jest: 'jest'}, {foo: 'foo'}], - [{foo: undefined}, {foo: undefined}], - [{foo: undefined}, {}], - [{first: {second: {}}}, {first: {second: {}}}], - [{first: objectContaining({second: {}})}, {first: {second: {}}}], - [{first: objectNotContaining({second: {}})}, {first: {second: {}}}], - [{}, {foo: undefined}], - ].forEach(([sample, received]) => { + ( + [ + [{}, null], + [{foo: 'foo'}, {foo: 'foo', jest: 'jest'}], + [{foo: 'foo', jest: 'jest'}, {foo: 'foo'}], + [{foo: undefined}, {foo: undefined}], + [{foo: undefined}, {}], + [{first: {second: {}}}, {first: {second: {}}}], + [{first: objectContaining({second: {}})}, {first: {second: {}}}], + [{first: objectNotContaining({second: {}})}, {first: {second: {}}}], + [{}, {foo: undefined}], + ] as const + ).forEach(([sample, received]) => { jestExpect(objectNotContaining(sample).asymmetricMatch(received)).toEqual( !objectContaining(sample).asymmetricMatch(received), ); @@ -303,7 +316,12 @@ test('ObjectNotContaining inverts ObjectContaining', () => { }); test('ObjectNotContaining throws for non-objects', () => { - jestExpect(() => objectNotContaining(1337).asymmetricMatch()).toThrow(); + jestExpect(() => { + // @ts-expect-error: Testing runtime error + objectNotContaining(1337).asymmetricMatch(); + }).toThrow( + "You must provide an object to ObjectNotContaining, not 'number'.", + ); }); test('StringContaining matches string against string', () => { @@ -313,8 +331,9 @@ test('StringContaining matches string against string', () => { test('StringContaining throws if expected value is not string', () => { jestExpect(() => { + // @ts-expect-error: Testing runtime error stringContaining([1]).asymmetricMatch('queen'); - }).toThrow(); + }).toThrow('Expected is not a string'); }); test('StringContaining returns false if received value is not string', () => { @@ -328,8 +347,9 @@ test('StringNotContaining matches string against string', () => { test('StringNotContaining throws if expected value is not string', () => { jestExpect(() => { + // @ts-expect-error: Testing runtime error stringNotContaining([1]).asymmetricMatch('queen'); - }).toThrow(); + }).toThrow('Expected is not a string'); }); test('StringNotContaining returns true if received value is not string', () => { @@ -348,8 +368,9 @@ test('StringMatching matches string against string', () => { test('StringMatching throws if expected value is neither string nor regexp', () => { jestExpect(() => { + // @ts-expect-error: Testing runtime error stringMatching([1]).asymmetricMatch('queen'); - }).toThrow(); + }).toThrow('Expected is not a String or a RegExp'); }); test('StringMatching returns false if received value is not string', () => { @@ -372,8 +393,9 @@ test('StringNotMatching matches string against string', () => { test('StringNotMatching throws if expected value is neither string nor regexp', () => { jestExpect(() => { + // @ts-expect-error: Testing runtime error stringNotMatching([1]).asymmetricMatch('queen'); - }).toThrow(); + }).toThrow('Expected is not a String or a RegExp'); }); test('StringNotMatching returns true if received value is not string', () => { @@ -451,26 +473,30 @@ describe('closeTo', () => { test('closeTo throw if expected is not number', () => { jestExpect(() => { + // @ts-expect-error: Testing runtime error closeTo('a'); - }).toThrow(); + }).toThrow('Expected is not a Number'); }); test('notCloseTo throw if expected is not number', () => { jestExpect(() => { + // @ts-expect-error: Testing runtime error notCloseTo('a'); - }).toThrow(); + }).toThrow('Expected is not a Number'); }); test('closeTo throw if precision is not number', () => { jestExpect(() => { + // @ts-expect-error: Testing runtime error closeTo(1, 'a'); - }).toThrow(); + }).toThrow('Precision is not a Number'); }); test('notCloseTo throw if precision is not number', () => { jestExpect(() => { + // @ts-expect-error: Testing runtime error notCloseTo(1, 'a'); - }).toThrow(); + }).toThrow('Precision is not a Number'); }); test('closeTo return false if received is not number', () => { diff --git a/packages/expect/src/__tests__/extend.test.ts b/packages/expect/src/__tests__/extend.test.ts index 9ad98e356f9c..91ddb9e22907 100644 --- a/packages/expect/src/__tests__/extend.test.ts +++ b/packages/expect/src/__tests__/extend.test.ts @@ -16,7 +16,7 @@ expect.addSnapshotSerializer(alignedAnsiStyleSerializer); jestExpect.extend({ toBeDivisibleBy(actual: number, expected: number) { const pass = actual % expected === 0; - const message = pass + const message: () => string = pass ? () => `expected ${this.utils.printReceived( actual, @@ -51,29 +51,47 @@ jestExpect.extend({ }, }); +declare module '../types' { + interface AsymmetricMatchers { + toBeDivisibleBy(expected: number): void; + toBeSymbol(expected: symbol): void; + toBeWithinRange(floor: number, ceiling: number): void; + } + interface Matchers { + toBeDivisibleBy(expected: number): R; + toBeSymbol(expected: symbol): R; + toBeWithinRange(floor: number, ceiling: number): R; + + shouldNotError(): R; + toFailWithoutMessage(): R; + toBeOne(): R; + toAllowOverridingExistingMatcher(): R; + } +} + it('is available globally when matcher is unary', () => { jestExpect(15).toBeDivisibleBy(5); jestExpect(15).toBeDivisibleBy(3); jestExpect(15).not.toBeDivisibleBy(6); - jestExpect(() => + expect(() => jestExpect(15).toBeDivisibleBy(2), ).toThrowErrorMatchingSnapshot(); }); it('is available globally when matcher is variadic', () => { jestExpect(15).toBeWithinRange(10, 20); - jestExpect(15).not.toBeWithinRange(6); + jestExpect(15).not.toBeWithinRange(6, 10); - jestExpect(() => + expect(() => jestExpect(15).toBeWithinRange(1, 3), ).toThrowErrorMatchingSnapshot(); }); it('exposes matcherUtils in context', () => { jestExpect.extend({ - _shouldNotError(_actual: unknown, _expected: unknown) { - const pass = this.equals( + shouldNotError(_actual: unknown) { + const pass: boolean = this.equals( this.utils, Object.assign(matcherUtils, { iterableEquality, @@ -88,13 +106,13 @@ it('exposes matcherUtils in context', () => { }, }); - jestExpect()._shouldNotError(); + jestExpect('test').shouldNotError(); }); it('is ok if there is no message specified', () => { jestExpect.extend({ toFailWithoutMessage(_expected: unknown) { - return {pass: false}; + return {message: () => '', pass: false}; }, }); @@ -107,13 +125,13 @@ it('exposes an equality function to custom matchers', () => { // jestExpect and expect share the same global state expect.assertions(3); jestExpect.extend({ - toBeOne() { + toBeOne(_expected: unknown) { expect(this.equals).toBe(equals); - return {pass: !!this.equals(1, 1)}; + return {message: () => '', pass: !!this.equals(1, 1)}; }, }); - expect(() => jestExpect().toBeOne()).not.toThrow(); + expect(() => jestExpect('test').toBeOne()).not.toThrow(); }); it('defines asymmetric unary matchers', () => { @@ -170,7 +188,7 @@ it('prints the Symbol into the error message', () => { it('allows overriding existing extension', () => { jestExpect.extend({ toAllowOverridingExistingMatcher(_expected: unknown) { - return {pass: _expected === 'bar'}; + return {message: () => '', pass: _expected === 'bar'}; }, }); @@ -178,7 +196,7 @@ it('allows overriding existing extension', () => { jestExpect.extend({ toAllowOverridingExistingMatcher(_expected: unknown) { - return {pass: _expected === 'foo'}; + return {message: () => '', pass: _expected === 'foo'}; }, }); @@ -188,6 +206,7 @@ it('allows overriding existing extension', () => { it('throws descriptive errors for invalid matchers', () => { expect(() => jestExpect.extend({ + // @ts-expect-error: Testing runtime error default: undefined, }), ).toThrow( @@ -195,6 +214,7 @@ it('throws descriptive errors for invalid matchers', () => { ); expect(() => jestExpect.extend({ + // @ts-expect-error: Testing runtime error default: 42, }), ).toThrow( @@ -202,6 +222,7 @@ it('throws descriptive errors for invalid matchers', () => { ); expect(() => jestExpect.extend({ + // @ts-expect-error: Testing runtime error default: 'foobar', }), ).toThrow( diff --git a/packages/expect/src/__tests__/stacktrace.test.ts b/packages/expect/src/__tests__/stacktrace.test.ts index ca2235b9d29c..40c3e148e027 100644 --- a/packages/expect/src/__tests__/stacktrace.test.ts +++ b/packages/expect/src/__tests__/stacktrace.test.ts @@ -9,20 +9,23 @@ import jestExpect from '../'; jestExpect.extend({ - toCustomMatch(callback: () => unknown, expectation: unknown) { + toCustomMatch(callback: () => unknown, expected: unknown) { const actual = callback(); - if (actual !== expectation) { + if (actual !== expected) { return { - message: () => `Expected "${expectation}" but got "${actual}"`, + message: () => `Expected "${expected}" but got "${actual}"`, pass: false, }; } - return {pass: true}; + return { + message: () => '', + pass: true, + }; }, - toMatchPredicate(received: unknown, argument: (a: unknown) => void) { - argument(received); + toMatchPredicate(received: unknown, expected: (a: unknown) => void) { + expected(received); return { message: () => '', pass: true, @@ -30,11 +33,18 @@ jestExpect.extend({ }, }); +declare module '../types' { + interface Matchers { + toCustomMatch(expected: unknown): R; + toMatchPredicate(expected: (a: unknown) => void): R; + } +} + it('stack trace points to correct location when using matchers', () => { try { jestExpect(true).toBe(false); } catch (error: any) { - expect(error.stack).toContain('stacktrace.test.ts:35'); + expect(error.stack).toContain('stacktrace.test.ts:45:22'); } }); @@ -44,7 +54,7 @@ it('stack trace points to correct location when using nested matchers', () => { jestExpect(value).toBe(false); }); } catch (error: any) { - expect(error.stack).toContain('stacktrace.test.ts:44'); + expect(error.stack).toContain('stacktrace.test.ts:54:25'); } }); @@ -60,6 +70,6 @@ it('stack trace points to correct location when throwing from a custom matcher', foo(); }).toCustomMatch('bar'); } catch (error: any) { - expect(error.stack).toContain('stacktrace.test.ts:57'); + expect(error.stack).toContain('stacktrace.test.ts:67:15'); } });