From cda398260eedbb3d3b3c199e3dceb442222ecf48 Mon Sep 17 00:00:00 2001 From: coyoteecd <47973420+coyoteecd@users.noreply.github.com> Date: Mon, 23 Mar 2020 17:31:47 +0200 Subject: [PATCH] Allow other expected values to be nested in jasmine.objectContaining for TypeScript 3.1+ (#43204) * [jasmine] Fix unit tests for jasmine.objectContaining to use $ExpectError instead of commments * [jasmine] Fix type definitions for jasmine.objectContaining when using TypeScript 3.1+ --- types/jasmine/jasmine-tests.ts | 52 +++++++++++++++++++++++----- types/jasmine/ts3.1/index.d.ts | 4 +-- types/jasmine/ts3.1/jasmine-tests.ts | 52 +++++++++++++++++++++++----- types/jasmine/v2/jasmine-tests.ts | 24 +++++++++---- 4 files changed, 107 insertions(+), 25 deletions(-) diff --git a/types/jasmine/jasmine-tests.ts b/types/jasmine/jasmine-tests.ts index 3617a8f463..100bed9fee 100644 --- a/types/jasmine/jasmine-tests.ts +++ b/types/jasmine/jasmine-tests.ts @@ -1042,6 +1042,9 @@ describe("jasmine.objectContaining", () => { a: number; b: number; bar: string; + nested: { + child: string; + }; } var foo: fooType; @@ -1049,24 +1052,57 @@ describe("jasmine.objectContaining", () => { foo = { a: 1, b: 2, - bar: "baz" + bar: "baz", + nested: { + child: 'child-baz' + } }; }); - it("matches objects with the expect key/value pairs", () => { - // not explictly providing the type on objectContaining only guards against - // missmatching types on know properties + it("matches objects with the correct type for known properties", () => { + // not explicitly providing the type on objectContaining only guards against + // mismatching types on known properties + + // this does not cause an error as the compiler cannot infer the type completely expect(foo).not.toEqual(jasmine.objectContaining({ a: 37, - foo: 2, // <-- this does not cause an error as the compiler cannot infer the type completely - // b: '123', <-- this would cause an error as `b` defined as number in fooType + foo: 2, })); - // explictly providing the type on objectContaining makes the guard more precise + // Contrary to the test in v2/jasmine-tests.ts, this does not cause an error + // even though `b` is defined as number in fooType. + // + // This is because the type definition of jasmine.Expected matches the return type of jasmine.objectContaining(), + // which is jasmine.ObjectContaining<{ a: number; b: string; }> + // + // Not sure how to fix this without breaking backwards compatibility in type definitions, so I'll let it be for the moment + expect(foo).not.toEqual(jasmine.objectContaining({ + a: 37, + b: '123', + })); + }); + + it("matches objects with the exact property names when providing a generic type", () => { + // explicitly providing the type on objectContaining makes the guard more precise // as misspelled properties are detected as well expect(foo).not.toEqual(jasmine.objectContaining({ bar: '', - // foo: 1, <-- this would cause an error as `foo` is not defined in fooType + foo: 1, // $ExpectError + })); + }); + + it("matches objects with jasmine matchers as property values when providing a generic type", () => { + expect(foo).not.toEqual(jasmine.objectContaining({ + b: jasmine.any(Number), + bar: jasmine.stringMatching('ba'), + })); + }); + + it("matches objects with jasmine matchers as nested property values when providing a generic type", () => { + expect(foo).not.toEqual(jasmine.objectContaining({ + nested: jasmine.objectContaining({ + child: jasmine.stringMatching('child') + }) })); }); diff --git a/types/jasmine/ts3.1/index.d.ts b/types/jasmine/ts3.1/index.d.ts index 01f0ef7e42..d27f386d04 100644 --- a/types/jasmine/ts3.1/index.d.ts +++ b/types/jasmine/ts3.1/index.d.ts @@ -266,7 +266,7 @@ declare namespace jasmine { function arrayContaining(sample: ArrayLike): ArrayContaining; function arrayWithExactContents(sample: ArrayLike): ArrayContaining; - function objectContaining(sample: Partial): ObjectContaining; + function objectContaining(sample: {[K in keyof T]?: ExpectedRecursive}): ObjectContaining; function setDefaultSpyStrategy(and: SpyAnd): void; function createSpy(name?: string, originalFn?: Fn): Spy; @@ -311,7 +311,7 @@ declare namespace jasmine { new?(sample: ArrayLike): ArrayLike; } - interface ObjectContaining extends AsymmetricMatcher { + interface ObjectContaining extends AsymmetricMatcher { new?(sample: {[K in keyof T]?: any}): {[K in keyof T]?: any}; jasmineMatches(other: any, mismatchKeys: any[], mismatchValues: any[]): boolean; diff --git a/types/jasmine/ts3.1/jasmine-tests.ts b/types/jasmine/ts3.1/jasmine-tests.ts index 14e9a0277a..e7671f239f 100644 --- a/types/jasmine/ts3.1/jasmine-tests.ts +++ b/types/jasmine/ts3.1/jasmine-tests.ts @@ -1042,6 +1042,9 @@ describe("jasmine.objectContaining", () => { a: number; b: number; bar: string; + nested: { + child: string; + }; } var foo: fooType; @@ -1049,24 +1052,57 @@ describe("jasmine.objectContaining", () => { foo = { a: 1, b: 2, - bar: "baz" + bar: "baz", + nested: { + child: 'child-baz' + }, }; }); - it("matches objects with the expect key/value pairs", () => { - // not explictly providing the type on objectContaining only guards against - // missmatching types on know properties + it("matches objects with the correct type for known properties", () => { + // not explicitly providing the type on objectContaining only guards against + // mismatching types on known properties + + // this does not cause an error as the compiler cannot infer the type completely expect(foo).not.toEqual(jasmine.objectContaining({ a: 37, - foo: 2, // <-- this does not cause an error as the compiler cannot infer the type completely - // b: '123', <-- this would cause an error as `b` defined as number in fooType + foo: 2, })); - // explictly providing the type on objectContaining makes the guard more precise + // Contrary to the test in ../v2/jasmine-tests.ts, this does not cause an error + // even though `b` is defined as number in fooType. + // + // This is because the type definition of jasmine.Expected matches the return type of jasmine.objectContaining(), + // which is jasmine.ObjectContaining<{ a: number; b: string; }> + // + // Not sure how to fix this without breaking backwards compatibility in type definitions, so I'll let it be for the moment + expect(foo).not.toEqual(jasmine.objectContaining({ + a: 37, + b: '123', + })); + }); + + it("matches objects with the exact property names when providing a generic type", () => { + // explicitly providing the type on objectContaining makes the guard more precise // as misspelled properties are detected as well expect(foo).not.toEqual(jasmine.objectContaining({ bar: '', - // foo: 1, <-- this would cause an error as `foo` is not defined in fooType + foo: 1, // $ExpectError + })); + }); + + it("matches objects with jasmine matchers as property values when providing a generic type", () => { + expect(foo).not.toEqual(jasmine.objectContaining({ + b: jasmine.any(Number), + bar: jasmine.stringMatching('ba'), + })); + }); + + it("matches objects with jasmine matchers as nested property values when providing a generic type", () => { + expect(foo).not.toEqual(jasmine.objectContaining({ + nested: jasmine.objectContaining({ + child: jasmine.stringMatching('child') + }) })); }); diff --git a/types/jasmine/v2/jasmine-tests.ts b/types/jasmine/v2/jasmine-tests.ts index 167aca1981..9dc67c8e8d 100644 --- a/types/jasmine/v2/jasmine-tests.ts +++ b/types/jasmine/v2/jasmine-tests.ts @@ -758,20 +758,30 @@ describe("jasmine.objectContaining", () => { }; }); - it("matches objects with the expect key/value pairs", () => { - // not explictly providing the type on objectContaining only guards against - // missmatching types on know properties + it("matches objects with the correct type for known properties", () => { + // not explicitly providing the type on objectContaining only guards against + // mismatching types on known properties + + // this does not cause an error as the compiler cannot infer the type completely expect(foo).not.toEqual(jasmine.objectContaining({ a: 37, - foo: 2, // <-- this does not cause an error as the compiler cannot infer the type completely - // b: '123', <-- this would cause an error as `b` defined as number in fooType + foo: 2, })); - // explictly providing the type on objectContaining makes the guard more precise + // this causes an error as `b` defined as number in fooType + // $ExpectError + expect(foo).not.toEqual(jasmine.objectContaining({ + a: 37, + b: '123', + })); + }); + + it("matches objects with the exact property names when providing a generic type", () => { + // explicitly providing the type on objectContaining makes the guard more precise // as misspelled properties are detected as well expect(foo).not.toEqual(jasmine.objectContaining({ bar: '', - // foo: 1, <-- this would cause an error as `foo` is not defined in fooType + foo: 1, // $ExpectError })); });