Skip to content

Commit

Permalink
fix: revert types resolution from @jest/globals (#604)
Browse files Browse the repository at this point in the history
  • Loading branch information
gregberge authored Oct 26, 2024
1 parent 130c640 commit 4bc420b
Show file tree
Hide file tree
Showing 14 changed files with 402 additions and 288 deletions.
524 changes: 333 additions & 191 deletions README.md

Large diffs are not rendered by default.

37 changes: 18 additions & 19 deletions packages/expect-puppeteer/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ Modify your Jest configuration:

Writing integration test is very hard, especially when you are testing a Single Page Applications. Data are loaded asynchronously and it is difficult to know exactly when an element will be displayed in the page.

[Puppeteer API](https://pptr.dev/api) is great, but it is low level and not designed for integration testing.
[Puppeteer API](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md) is great, but it is low level and not designed for integration testing.

This API is designed for integration testing:

Expand Down Expand Up @@ -81,11 +81,11 @@ await expect(page).toMatchElement("div.inner", { text: "some text" });

Expect an element to be in the page or element, then click on it.

- `instance` <[Page]|[Frame]|[ElementHandle]> Context
- `instance` <[Page]|[ElementHandle]> Context
- `selector` <[string]|[MatchSelector](#MatchSelector)> A [selector] or a [MatchSelector](#MatchSelector) to click on.
- `options` <[Object]> Optional parameters
- `button` <"left"|"right"|"middle"> Defaults to `left`.
- `count` <[number]> defaults to 1. See [UIEvent.detail].
- `clickCount` <[number]> defaults to 1. See [UIEvent.detail].
- `delay` <[number]> Time to wait between `mousedown` and `mouseup` in milliseconds. Defaults to 0.
- `text` <[string]|[RegExp]> A text or a RegExp to match in element `textContent`.

Expand All @@ -111,8 +111,8 @@ const dialog = await expect(page).toDisplayDialog(async () => {

Expect a control to be in the page or element, then fill it with text.

- `instance` <[Page]|[Frame]|[ElementHandle]> Context
- `selector` <[string]|[MatchSelector](#MatchSelector)> A [selector] or a [MatchSelector](#MatchSelector) to match field
- `instance` <[Page]|[ElementHandle]> Context
- `selector` <[string]> A [selector] to match field
- `value` <[string]> Value to fill
- `options` <[Object]> Optional parameters
- `delay` <[number]> delay to pass to [the puppeteer `element.type` API](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#elementhandletypetext-options)
Expand All @@ -125,8 +125,8 @@ await expect(page).toFill('input[name="firstName"]', "James");

Expect a form to be in the page or element, then fill its controls.

- `instance` <[Page]|[Frame]|[ElementHandle]> Context
- `selector` <[string]|[MatchSelector](#MatchSelector)> A [selector] or a [MatchSelector](#MatchSelector) to match form
- `instance` <[Page]|[ElementHandle]> Context
- `selector` <[string]> A [selector] to match form
- `values` <[Object]> Values to fill
- `options` <[Object]> Optional parameters
- `delay` <[number]> delay to pass to [the puppeteer `element.type` API](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#elementhandletypetext-options)
Expand All @@ -142,7 +142,7 @@ await expect(page).toFillForm('form[name="myForm"]', {

Expect a text or a string RegExp to be present in the page or element.

- `instance` <[Page]|[Frame]|[ElementHandle]> Context
- `instance` <[Page]|[ElementHandle]> Context
- `matcher` <[string]|[RegExp]> A text or a RegExp to match in page
- `options` <[Object]> Optional parameters
- `polling` <[string]|[number]> An interval at which the `pageFunction` is executed, defaults to `raf`. If `polling` is a number, then it is treated as an interval in milliseconds at which the function would be executed. If `polling` is a string, then it can be one of the following values:
Expand All @@ -162,8 +162,8 @@ await expect(page).toMatchTextContent(/lo.*/);

Expect an element be present in the page or element.

- `instance` <[Page]|[Frame]|[ElementHandle]> Context
- `selector` <[string]|[MatchSelector](#MatchSelector)> A [selector] or a [MatchSelector](#MatchSelector) to match element
- `instance` <[Page]|[ElementHandle]> Context
- `selector` <[string]> A [selector] to match element
- `options` <[Object]> Optional parameters
- `polling` <[string]|[number]> An interval at which the `pageFunction` is executed, defaults to `raf`. If `polling` is a number, then it is treated as an interval in milliseconds at which the function would be executed. If `polling` is a string, then it can be one of the following values:
- `raf` - to constantly execute `pageFunction` in `requestAnimationFrame` callback. This is the tightest polling mode which is suitable to observe styling changes.
Expand All @@ -183,8 +183,8 @@ await expect(row).toClick("td:nth-child(3) a");

Expect a select control to be present in the page or element, then select the specified option.

- `instance` <[Page]|[Frame]|[ElementHandle]> Context
- `selector` <[string]|[MatchSelector](#MatchSelector)> A [selector] or a [MatchSelector](#MatchSelector) to match select [element]
- `instance` <[Page]|[ElementHandle]> Context
- `selector` <[string]> A [selector] to match select [element]
- `valueOrText` <[string]> Value or text matching option

```js
Expand All @@ -195,9 +195,9 @@ await expect(page).toSelect('select[name="choices"]', "Choice 1");

Expect a input file control to be present in the page or element, then fill it with a local file.

- `instance` <[Page]|[Frame]|[ElementHandle]> Context
- `selector` <[string]|[MatchSelector](#MatchSelector)> A [selector] or a [MatchSelector](#MatchSelector) to match input [element]
- `filePath` <[string]|[Array]<[string]>> A file path or array of file paths
- `instance` <[Page]|[ElementHandle]> Context
- `selector` <[string]> A [selector] to match input [element]
- `filePath` <[string]> A file path

```js
import { join } from "node:path";
Expand All @@ -208,7 +208,7 @@ await expect(page).toUploadFile(
);
```

### <a name="MatchSelector"></a>Match Selector
### <a name="MatchSelector"></a>{type: [string], value: [string]}

An object used as parameter in order to select an element.

Expand Down Expand Up @@ -242,7 +242,6 @@ setDefaultOptions({ timeout: 1000 });
[element]: https://developer.mozilla.org/en-US/docs/Web/API/element "Element"
[map]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map "Map"
[selector]: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors "selector"
[page]: https://pptr.dev/api/puppeteer.page "Page"
[frame]: https://pptr.dev/api/puppeteer.frame "Frame"
[elementhandle]: https://pptr.dev/api/puppeteer.elementhandle/ "ElementHandle"
[page]: https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#class-page "Page"
[elementhandle]: https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#class-elementhandle "ElementHandle"
[uievent.detail]: https://developer.mozilla.org/en-US/docs/Web/API/UIEvent/detail
1 change: 1 addition & 0 deletions packages/expect-puppeteer/src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { getDefaultOptions, setDefaultOptions } from "expect-puppeteer";

// import globals
import "jest-puppeteer";
import "expect-puppeteer";

expect.addSnapshotSerializer({
print: () => "hello",
Expand Down
91 changes: 41 additions & 50 deletions packages/expect-puppeteer/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@ type Wrapper<T> = T extends (
? (...args: A) => R
: never;

// declare common matchers list
type InstanceMatchers<T> = T extends PuppeteerInstance
// declare matchers list
type PuppeteerMatchers<T> = T extends PuppeteerInstance
? {
// common
toClick: Wrapper<typeof toClick>;
Expand All @@ -64,24 +64,24 @@ type InstanceMatchers<T> = T extends PuppeteerInstance
: never;

// declare page matchers list
interface PageMatchers extends InstanceMatchers<Page> {
interface PageMatchers extends PuppeteerMatchers<Page> {
// instance specific
toDisplayDialog: Wrapper<typeof toDisplayDialog>;
// inverse matchers
not: InstanceMatchers<Page>[`not`] & {};
not: PuppeteerMatchers<Page>[`not`] & {};
}

// declare frame matchers list
interface FrameMatchers extends InstanceMatchers<Frame> {
interface FrameMatchers extends PuppeteerMatchers<Frame> {
// inverse matchers
not: InstanceMatchers<Frame>[`not`] & {};
not: PuppeteerMatchers<Frame>[`not`] & {};
}

// declare element matchers list
interface ElementHandleMatchers
extends InstanceMatchers<ElementHandle<Element>> {
extends PuppeteerMatchers<ElementHandle<Element>> {
// inverse matchers
not: InstanceMatchers<ElementHandle<Element>>[`not`] & {};
not: PuppeteerMatchers<ElementHandle<Element>>[`not`] & {};
}

// declare matchers per instance type
Expand All @@ -103,50 +103,43 @@ type GlobalWithExpect = typeof globalThis & { expect: PuppeteerExpect };

// ---------------------------

// not possible to use PMatchersPerType directly ...
interface PuppeteerMatchers<T> {
// common
toClick: T extends PuppeteerInstance ? Wrapper<typeof toClick> : never;
toFill: T extends PuppeteerInstance ? Wrapper<typeof toFill> : never;
toFillForm: T extends PuppeteerInstance ? Wrapper<typeof toFillForm> : never;
toMatchTextContent: T extends PuppeteerInstance
? Wrapper<typeof toMatchTextContent>
: never;
toMatchElement: T extends PuppeteerInstance
? Wrapper<typeof toMatchElement>
: never;
toSelect: T extends PuppeteerInstance ? Wrapper<typeof toSelect> : never;
toUploadFile: T extends PuppeteerInstance
? Wrapper<typeof toUploadFile>
: never;
// page
toDisplayDialog: T extends Page ? Wrapper<typeof toDisplayDialog> : never;
// inverse matchers
not: {
toMatchTextContent: T extends PuppeteerInstance
? Wrapper<typeof notToMatchTextContent>
: never;
toMatchElement: T extends PuppeteerInstance
? Wrapper<typeof notToMatchElement>
: never;
};
}

// support for @types/jest
// extend global jest object
declare global {
// eslint-disable-next-line @typescript-eslint/no-namespace
namespace jest {
// eslint-disable-next-line @typescript-eslint/no-empty-object-type, @typescript-eslint/no-unused-vars
interface Matchers<R, T> extends PuppeteerMatchers<T> {}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
interface Matchers<R, T> {
// common
toClick: T extends PuppeteerInstance ? Wrapper<typeof toClick> : never;
toFill: T extends PuppeteerInstance ? Wrapper<typeof toFill> : never;
toFillForm: T extends PuppeteerInstance
? Wrapper<typeof toFillForm>
: never;
toMatchTextContent: T extends PuppeteerInstance
? Wrapper<typeof toMatchTextContent>
: never;
toMatchElement: T extends PuppeteerInstance
? Wrapper<typeof toMatchElement>
: never;
toSelect: T extends PuppeteerInstance ? Wrapper<typeof toSelect> : never;
toUploadFile: T extends PuppeteerInstance
? Wrapper<typeof toUploadFile>
: never;
// page
toDisplayDialog: T extends Page ? Wrapper<typeof toDisplayDialog> : never;
// inverse matchers
not: {
toMatchTextContent: T extends PuppeteerInstance
? Wrapper<typeof notToMatchTextContent>
: never;
toMatchElement: T extends PuppeteerInstance
? Wrapper<typeof notToMatchElement>
: never;
};
}
}
}

// support for @jest/types
declare module "@jest/expect" {
// eslint-disable-next-line @typescript-eslint/no-empty-object-type, @typescript-eslint/no-unused-vars
interface Matchers<R, T> extends PuppeteerMatchers<T> {}
}

// ---------------------------
// @ts-expect-error global node object w/ initial jest expect prop attached
const jestExpect = global.expect as JestExpect;
Expand All @@ -158,7 +151,7 @@ const wrapMatcher = <T extends PuppeteerInstance>(
instance: T,
) =>
async function throwingMatcher(...args: unknown[]): Promise<unknown> {
// update the assertions counter
// ???
jestExpect.getState().assertionCalls += 1;
try {
// run async matcher
Expand All @@ -183,9 +176,7 @@ const puppeteerExpect = <T extends PuppeteerInstance>(instance: T) => {
];

if (!isPage && !isFrame && !isHandle)
throw new Error(
`${String(instance?.constructor?.name ?? `current instance`)} is not supported`,
);
throw new Error(`${instance} is not supported`);

// retrieve matchers
const expectation = {
Expand Down
4 changes: 2 additions & 2 deletions packages/expect-puppeteer/src/matchers/toClick.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export async function toClick(
selector: Selector | string,
options: ToClickOptions = {},
) {
const { delay, button, count, offset, ...otherOptions } = options;
const { delay, button, clickCount, offset, ...otherOptions } = options;
const element = await toMatchElement(instance, selector, otherOptions);
await element.click({ delay, button, count, offset });
await element.click({ delay, button, clickCount, offset });
}
11 changes: 0 additions & 11 deletions packages/jest-environment-puppeteer/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,6 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.

## [10.1.3](https://github.com/argos-ci/jest-puppeteer/compare/v10.1.2...v10.1.3) (2024-10-22)


### Bug Fixes

* fix types resolution when importing jest types from @jest/globals ([#602](https://github.com/argos-ci/jest-puppeteer/issues/602)) ([e5b2e1a](https://github.com/argos-ci/jest-puppeteer/commit/e5b2e1a7c0282aba496ffe2806201778b84a96fc))





## [10.1.2](https://github.com/argos-ci/jest-puppeteer/compare/v10.1.1...v10.1.2) (2024-10-10)


Expand Down
15 changes: 0 additions & 15 deletions packages/jest-environment-puppeteer/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,21 +37,6 @@ describe("Google", () => {
});
```

## Use with TypeScript

_Note : If you have upgraded to version v10.1.2 or above, we strongly recommend that you uninstall the community provided types :_

```bash
npm uninstall --save-dev @types/jest-environment-puppeteer @types/expect-puppeteer
```

If using TypeScript, jest-puppeteer has to be explicitly imported in order to expose the global API :

```ts
// import jest-puppeteer globals
import "jest-puppeteer";
```

## API

### `global.browser`
Expand Down
1 change: 1 addition & 0 deletions packages/jest-environment-puppeteer/tests/basic.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// import globals
import "jest-puppeteer";
import "expect-puppeteer";

describe("Basic", () => {
beforeAll(async () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// import globals
import "jest-puppeteer";
import "expect-puppeteer";

describe("browserContext", () => {
const test = process.env.INCOGNITO ? it : it.skip;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// import globals
import "jest-puppeteer";
import "expect-puppeteer";

describe("browserContext", () => {
const test = process.env.INCOGNITO ? it : it.skip;
Expand Down
1 change: 1 addition & 0 deletions packages/jest-environment-puppeteer/tests/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { readConfig } from "../src/config";

// import globals
import "jest-puppeteer";
import "expect-puppeteer";

// This test does not run on Node.js < v20 (segfault)
xdescribe("readConfig", () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// import globals
import "jest-puppeteer";
import "expect-puppeteer";

describe("resetBrowser", () => {
test("should reset browser", async () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// import globals
import "jest-puppeteer";
import "expect-puppeteer";

describe("resetPage", () => {
test("should reset page", async () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// import globals
import "jest-puppeteer";
import "expect-puppeteer";

describe("runBeforeUnloadOnClose", () => {
it("shouldn’t call page.close with runBeforeUnload by default", async () => {
Expand Down

0 comments on commit 4bc420b

Please sign in to comment.