From 68103850e3b6f7ca8a8a3e993d86af94b5c65443 Mon Sep 17 00:00:00 2001 From: Rui Figueira Date: Mon, 14 Oct 2024 22:25:33 +0100 Subject: [PATCH] chore(test): add optional generic parameters to matchers --- docs/src/api/class-genericassertions.md | 16 ++++ packages/playwright/types/test.d.ts | 84 ++++++++------------ tests/playwright-test/expect.spec.ts | 97 +++++++++--------------- utils/generate_types/overrides-test.d.ts | 18 ++--- 4 files changed, 89 insertions(+), 126 deletions(-) diff --git a/docs/src/api/class-genericassertions.md b/docs/src/api/class-genericassertions.md index 180a15dc4b..812fa29c73 100644 --- a/docs/src/api/class-genericassertions.md +++ b/docs/src/api/class-genericassertions.md @@ -30,6 +30,8 @@ expect(value).not.toBe(2); Compares value with [`param: expected`] by calling `Object.is`. This method compares objects by reference instead of their contents, similarly to the strict equality operator `===`. +Optionally, you can provide a type for the expected value via a generic. This is particularly useful for ensuring expected objects have the right structure. + **Usage** ```js @@ -144,6 +146,8 @@ The value to compare to. Ensures that value is an instance of a class. Uses `instanceof` operator. +Optionally, you can provide a type for the expected value via a generic. This is particularly useful for ensuring expected objects have the right structure. + **Usage** ```js @@ -280,6 +284,8 @@ Expected substring. Ensures that value is an `Array` or `Set` and contains an expected item. +Optionally, you can provide a type for the expected value via a generic. This is particularly useful for ensuring expected objects have the right structure. + **Usage** ```js @@ -305,6 +311,8 @@ For objects, this method recursively checks equality of all fields, rather than For primitive values, this method is equivalent to [`method: GenericAssertions.toContain#2`]. +Optionally, you can provide a type for the expected value via a generic. This is particularly useful for ensuring expected objects have the right structure. + **Usage** ```js @@ -334,6 +342,8 @@ For objects, this method recursively checks equality of all fields, rather than For primitive values, this method is equivalent to [`method: GenericAssertions.toBe`]. +Optionally, you can provide a type for the expected value via a generic. This is particularly useful for ensuring expected objects have the right structure. + **Usage** ```js @@ -404,6 +414,8 @@ Expected length. Ensures that property at provided `keyPath` exists on the object and optionally checks that property is equal to the [`param: expected`]. Equality is checked recursively, similarly to [`method: GenericAssertions.toEqual`]. +Optionally, you can provide a type for the expected value via a generic. This is particularly useful for ensuring expected objects have the right structure. + **Usage** ```js @@ -461,6 +473,8 @@ Compares contents of the value with contents of [`param: expected`], performing When comparing arrays, the number of items must match, and each item is checked recursively. +Optionally, you can provide a type for the expected value via a generic. This is particularly useful for ensuring expected objects have the right structure. + **Usage** ```js @@ -494,6 +508,8 @@ Differences from [`method: GenericAssertions.toEqual`]: * Array sparseness is checked. For example, `[, 1]` does not match `[undefined, 1]`. * Object types are checked to be equal. For example, a class instance with fields `a` and `b` will not equal a literal object with fields `a` and `b`. +Optionally, you can provide a type for the expected value via a generic. This is particularly useful for ensuring expected objects have the right structure. + **Usage** ```js diff --git a/packages/playwright/types/test.d.ts b/packages/playwright/types/test.d.ts index 3c7c8db0e4..2a694bebac 100644 --- a/packages/playwright/types/test.d.ts +++ b/packages/playwright/types/test.d.ts @@ -6088,6 +6088,9 @@ interface GenericAssertions { * calling `Object.is`. This method compares objects by reference instead of their contents, similarly to the strict * equality operator `===`. * + * Optionally, you can provide a type for the expected value via a generic. This is particularly useful for ensuring + * expected objects have the right structure. + * * **Usage** * * ```js @@ -6099,7 +6102,7 @@ interface GenericAssertions { * * @param expected Expected value. */ - toBe(expected: unknown): R; + toBe(expected: E): R; /** * Compares floating point numbers for approximate equality. Use this method instead of * [expect(value).toBe(expected)](https://playwright.dev/docs/api/class-genericassertions#generic-assertions-to-be) @@ -6170,6 +6173,9 @@ interface GenericAssertions { /** * Ensures that value is an instance of a class. Uses `instanceof` operator. * + * Optionally, you can provide a type for the expected value via a generic. This is particularly useful for ensuring + * expected objects have the right structure. + * * **Usage** * * ```js @@ -6181,7 +6187,7 @@ interface GenericAssertions { * * @param expected The class or constructor function. */ - toBeInstanceOf(expected: Function): R; + toBeInstanceOf(expected: E): R; /** * Ensures that `value < expected` for number or big integer values. * @@ -6274,6 +6280,9 @@ interface GenericAssertions { /** * Ensures that value is an `Array` or `Set` and contains an expected item. * + * Optionally, you can provide a type for the expected value via a generic. This is particularly useful for ensuring + * expected objects have the right structure. + * * **Usage** * * ```js @@ -6284,7 +6293,7 @@ interface GenericAssertions { * * @param expected Expected value in the collection. */ - toContain(expected: unknown): R; + toContain(expected: E): R; /** * Ensures that value is an `Array` or `Set` and contains an item equal to the expected. * @@ -6295,6 +6304,9 @@ interface GenericAssertions { * For primitive values, this method is equivalent to * [expect(value).toContain(expected)](https://playwright.dev/docs/api/class-genericassertions#generic-assertions-to-contain-2). * + * Optionally, you can provide a type for the expected value via a generic. This is particularly useful for ensuring + * expected objects have the right structure. + * * **Usage** * * ```js @@ -6309,7 +6321,7 @@ interface GenericAssertions { * * @param expected Expected value in the collection. */ - toContainEqual(expected: unknown): R; + toContainEqual(expected: E): R; /** * Compares contents of the value with contents of * [`expected`](https://playwright.dev/docs/api/class-genericassertions#generic-assertions-to-equal-option-expected), @@ -6322,6 +6334,9 @@ interface GenericAssertions { * For primitive values, this method is equivalent to * [expect(value).toBe(expected)](https://playwright.dev/docs/api/class-genericassertions#generic-assertions-to-be). * + * Optionally, you can provide a type for the expected value via a generic. This is particularly useful for ensuring + * expected objects have the right structure. + * * **Usage** * * ```js @@ -6370,7 +6385,7 @@ interface GenericAssertions { * * @param expected Expected value. */ - toEqual(expected: unknown): R; + toEqual(expected: E): R; /** * Ensures that value has a `.length` property equal to * [`expected`](https://playwright.dev/docs/api/class-genericassertions#generic-assertions-to-have-length-option-expected). @@ -6393,6 +6408,9 @@ interface GenericAssertions { * Equality is checked recursively, similarly to * [expect(value).toEqual(expected)](https://playwright.dev/docs/api/class-genericassertions#generic-assertions-to-equal). * + * Optionally, you can provide a type for the expected value via a generic. This is particularly useful for ensuring + * expected objects have the right structure. + * * **Usage** * * ```js @@ -6413,7 +6431,7 @@ interface GenericAssertions { * array items. * @param expected Optional expected value to compare the property to. */ - toHaveProperty(keyPath: string | Array, value?: unknown): R; + toHaveProperty(propertyPath: string | readonly any[], value?: E): R; /** * Ensures that string value matches a regular expression. * @@ -6436,31 +6454,8 @@ interface GenericAssertions { * * When comparing arrays, the number of items must match, and each item is checked recursively. * - * **Usage** - * - * ```js - * const value = { - * a: 1, - * b: 2, - * c: true, - * }; - * expect(value).toMatchObject({ a: 1, c: true }); - * expect(value).toMatchObject({ b: 2, c: true }); - * - * expect([{ a: 1, b: 2 }]).toMatchObject([{ a: 1 }]); - * ``` - * - * @param expected The expected object value to match against. - */ - toMatchObject>(expected: DeepPartial | Array>): R; - /** - * Compares contents of the value with contents of - * [`expected`](https://playwright.dev/docs/api/class-genericassertions#generic-assertions-to-match-object-option-expected), - * performing "deep equality" check. Allows extra properties to be present in the value, unlike - * [expect(value).toEqual(expected)](https://playwright.dev/docs/api/class-genericassertions#generic-assertions-to-equal), - * so you can check just a subset of object properties. - * - * When comparing arrays, the number of items must match, and each item is checked recursively. + * Optionally, you can provide a type for the expected value via a generic. This is particularly useful for ensuring + * expected objects have the right structure. * * **Usage** * @@ -6478,7 +6473,7 @@ interface GenericAssertions { * * @param expected The expected object value to match against. */ - toMatchObject(expected: Record | Array): R; + toMatchObject = Record | Array>(expected: E): R; /** * Compares contents of the value with contents of * [`expected`](https://playwright.dev/docs/api/class-genericassertions#generic-assertions-to-strict-equal-option-expected) @@ -6491,27 +6486,8 @@ interface GenericAssertions { * - Object types are checked to be equal. For example, a class instance with fields `a` and `b` will not equal a * literal object with fields `a` and `b`. * - * **Usage** - * - * ```js - * const value = { prop: 1 }; - * expect(value).toStrictEqual({ prop: 1 }); - * ``` - * - * @param expected Expected value. - */ - toStrictEqual(expected: K): R; - /** - * Compares contents of the value with contents of - * [`expected`](https://playwright.dev/docs/api/class-genericassertions#generic-assertions-to-strict-equal-option-expected) - * **and** their types. - * - * Differences from - * [expect(value).toEqual(expected)](https://playwright.dev/docs/api/class-genericassertions#generic-assertions-to-equal): - * - Keys with undefined properties are checked. For example, `{ a: undefined, b: 2 }` does not match `{ b: 2 }`. - * - Array sparseness is checked. For example, `[, 1]` does not match `[undefined, 1]`. - * - Object types are checked to be equal. For example, a class instance with fields `a` and `b` will not equal a - * literal object with fields `a` and `b`. + * Optionally, you can provide a type for the expected value via a generic. This is particularly useful for ensuring + * expected objects have the right structure. * * **Usage** * @@ -6522,7 +6498,7 @@ interface GenericAssertions { * * @param expected Expected value. */ - toStrictEqual(expected: unknown): R; + toStrictEqual(expected: E): R; /** * Calls the function and ensures it throws an error. * diff --git a/tests/playwright-test/expect.spec.ts b/tests/playwright-test/expect.spec.ts index 8db86f2041..81c3db0fe3 100644 --- a/tests/playwright-test/expect.spec.ts +++ b/tests/playwright-test/expect.spec.ts @@ -1140,76 +1140,49 @@ test('custom asymmetric matchers should work with expect.extend', async ({ runIn expect(result.output).not.toContain('should not run'); }); -test('should check types of overloaded toMatchObject with generic parameter', async ({ runTSC }) => { +test('should compile matchers with generic parameters', async ({ runTSC }) => { const result = await runTSC({ - 'playwright.config.ts': ` - import { defineConfig } from '@playwright/test'; - - export default defineConfig({ - }); - `, 'a.spec.ts': ` - import { test, expect } from '@playwright/test'; - test('my test', async () => { - const value = { a: 1, b: 'foo', c: { d: 2, e: 'bar' } }; + import { expect } from '@playwright/test'; - expect.soft(value).toMatchObject({}); - expect.soft(value).toMatchObject({ a: 1 }); - expect.soft(value).toMatchObject({ a: 1, c: { e: 'bar' } }); - expect.soft([value]).toMatchObject([{ a: 1 }]); - expect.soft([value]).toMatchObject([{ a: 1, c: { e: 'bar' } }]); + expect(42).toBe(42); + // @ts-expect-error + expect(42).toBe('forty-two'); - // @ts-expect-error - expect.soft(value).toMatchObject({ a: 'a' }); + class A {} + expect(new A()).toBeInstanceOf(A); + // @ts-expect-error + expect(new A()).toBeInstanceOf(A); - // @ts-expect-error - expect.soft(value).toMatchObject({ c: { e: 2 } }); - - // @ts-expect-error - expect.soft([value]).toMatchObject([{ a: 'a' }]); + expect([42]).toContain(42); + // @ts-expect-error + expect([42]).toContain('forty-two'); - // @ts-expect-error - expect.soft([value]).toMatchObject([{ c: { e: 2 } }]); - }); + expect(42).toContainEqual(42); + // @ts-expect-error + expect([42]).toContainEqual('forty-two'); + + expect(42).toEqual(42); + // @ts-expect-error + expect(42).toEqual('forty-two'); + + expect({ a: 42 }).toHaveProperty('a', 42); + // @ts-expect-error + expect({ a: 42 }).toHaveProperty('a', 'forty-two'); + + expect({ 'a': 42 }).toMatchObject<{ a: number }>({ 'a': 42 }); + // @ts-expect-error + expect({ 'a': 42 }).toMatchObject<{ a: number }>({ 'b': 42 }); + + expect([{ a: 42 }]).toMatchObject>([{ 'a': 42 }]); + // @ts-expect-error + expect([{ a: 42 }]).toMatchObject<{ a: number }>([{ 'a': 42 }]); + + expect(42).toStrictEqual(42); + // @ts-expect-error + expect(42).toStrictEqual('forty-two'); ` }); - expect(result.exitCode).toBe(0); -}); -test('should check types of overloaded toStrictEqual with generic parameter', async ({ runTSC }) => { - const result = await runTSC({ - 'playwright.config.ts': ` - import { defineConfig } from '@playwright/test'; - - export default defineConfig({ - }); - `, - 'a.spec.ts': ` - import { test, expect } from '@playwright/test'; - test('my test', async () => { - const value = { a: 1, b: 'foo', c: { d: 2, e: 'bar' } }; - - expect.soft(value).toStrictEqual({ a: 1, b: 'foo', c: { d: 2, e: 'bar' } }); - expect.soft([value]).toStrictEqual>([{ a: 1, b: 'foo', c: { d: 2, e: 'bar' } }]); - - expect.soft(new Date()).toStrictEqual(new Date()); - - // @ts-expect-error - expect.soft(value).toStrictEqual({ a: 1 }); - - // @ts-expect-error - expect.soft(value).toStrictEqual({ a: 'a' }); - - // @ts-expect-error - expect.soft(value).toStrictEqual({ c: { e: 2 } }); - - // @ts-expect-error - expect.soft([value]).toStrictEqual([{ a: 'a' }]); - - // @ts-expect-error - expect.soft([value]).toStrictEqual([{ c: { e: 2 } }]); - }); - ` - }); expect(result.exitCode).toBe(0); }); \ No newline at end of file diff --git a/utils/generate_types/overrides-test.d.ts b/utils/generate_types/overrides-test.d.ts index 6979c11cf9..2b6b322ace 100644 --- a/utils/generate_types/overrides-test.d.ts +++ b/utils/generate_types/overrides-test.d.ts @@ -273,13 +273,13 @@ interface AsymmetricMatchers { interface GenericAssertions { not: GenericAssertions; - toBe(expected: unknown): R; + toBe(expected: E): R; toBeCloseTo(expected: number, numDigits?: number): R; toBeDefined(): R; toBeFalsy(): R; toBeGreaterThan(expected: number | bigint): R; toBeGreaterThanOrEqual(expected: number | bigint): R; - toBeInstanceOf(expected: Function): R; + toBeInstanceOf(expected: E): R; toBeLessThan(expected: number | bigint): R; toBeLessThanOrEqual(expected: number | bigint): R; toBeNaN(): R; @@ -287,16 +287,14 @@ interface GenericAssertions { toBeTruthy(): R; toBeUndefined(): R; toContain(expected: string): R; - toContain(expected: unknown): R; - toContainEqual(expected: unknown): R; - toEqual(expected: unknown): R; + toContain(expected: E): R; + toContainEqual(expected: E): R; + toEqual(expected: E): R; toHaveLength(expected: number): R; - toHaveProperty(keyPath: string | Array, value?: unknown): R; + toHaveProperty(propertyPath: string | readonly any[], value?: E): R; toMatch(expected: RegExp | string): R; - toMatchObject>(expected: DeepPartial | Array>): R; - toMatchObject(expected: Record | Array): R; - toStrictEqual(expected: K): R; - toStrictEqual(expected: unknown): R; + toMatchObject = Record | Array>(expected: E): R; + toStrictEqual(expected: E): R; toThrow(error?: unknown): R; toThrowError(error?: unknown): R; }