From 048d1c56f386b56ab483a812c24aed8fefa9fe18 Mon Sep 17 00:00:00 2001 From: Simen Bekkhus Date: Fri, 11 Feb 2022 07:42:00 +0100 Subject: [PATCH 1/6] chore: re-order changelog --- CHANGELOG.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4366e726c58a..12885ce1ad1f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,26 +2,20 @@ ### Features -- `[expect]` [**BREAKING**] Migrate to ESM ([#12344](https://github.com/facebook/jest/pull/12344)) - `[jest-config]` [**BREAKING**] Stop shipping `jest-environment-jsdom` by default ([#12354](https://github.com/facebook/jest/pull/12354)) - `[jest-config]` [**BREAKING**] Stop shipping `jest-jasmine2` by default ([#12355](https://github.com/facebook/jest/pull/12355)) - `[jest-environment-jsdom]` [**BREAKING**] Upgrade jsdom to 19.0.0 ([#12290](https://github.com/facebook/jest/pull/12290)) - `[jest-environment-jsdom]` [**BREAKING**] Add default `browser` condition to `exportConditions` for `jsdom` environment ([#11924](https://github.com/facebook/jest/pull/11924)) -- `[jest-environment-jsdom]` [**BREAKING**] Migrate to ESM ([#12340](https://github.com/facebook/jest/pull/12340)) - `[jest-environment-node]` [**BREAKING**] Add default `node` and `node-addon` conditions to `exportConditions` for `node` environment ([#11924](https://github.com/facebook/jest/pull/11924)) -- `[jest-environment-node]` [**BREAKING**] Migrate to ESM ([#12340](https://github.com/facebook/jest/pull/12340)) - `[@jest/expect-utils]` New module exporting utils for `expect` ([#12323](https://github.com/facebook/jest/pull/12323)) -- `[jest-jasmine2, jest-runtime]` [**BREAKING**] Use `Symbol` to pass `jest.setTimeout` value instead of `jasmine` specific logic ([#12124](https://github.com/facebook/jest/pull/12124)) -- `[jest-jasmine2, jest-types]` [**BREAKING**] Move all `jasmine` specific types from `@jest/types` to its own package ([#12125](https://github.com/facebook/jest/pull/12125)) - `[jest-resolver]` [**BREAKING**] Add support for `package.json` `exports` ([11961](https://github.com/facebook/jest/pull/11961)) -- `[jest-snapshot]` [**BREAKING**] Migrate to ESM ([#12342](https://github.com/facebook/jest/pull/12342)) - `[jest-worker]` [**BREAKING**] Allow only absolute `workerPath` ([#12343](https://github.com/facebook/jest/pull/12343)) ### Fixes - `[expect]` Move typings of `.not`, `.rejects` and `.resolves` modifiers outside of `Matchers` interface ([#12346](https://github.com/facebook/jest/pull/12346)) - `[jest-environment-jsdom]` Make `jsdom` accessible to extending environments again ([#12232](https://github.com/facebook/jest/pull/12232)) -- `[jest-phabricator]` [**BREAKING**] Convert to ESM ([#12341](https://github.com/facebook/jest/pull/12341)) +- `[jest-jasmine2, jest-types]` [**BREAKING**] Move all `jasmine` specific types from `@jest/types` to its own package ([#12125](https://github.com/facebook/jest/pull/12125)) ### Chore & Maintenance @@ -29,12 +23,18 @@ - `[*]` [**BREAKING**] Drop support for `typescript@3.8`, minimum version is now `4.2` ([#11142](https://github.com/facebook/jest/pull/11142)) - `[*]` Bundle all `.d.ts` files into a single `index.d.ts` per module ([#12345](https://github.com/facebook/jest/pull/12345)) - `[expect]` [**BREAKING**] Remove support for importing `build/utils` ([#12323](https://github.com/facebook/jest/pull/12323)) +- `[expect]` [**BREAKING**] Migrate to ESM ([#12344](https://github.com/facebook/jest/pull/12344)) - `[jest-cli]` Update `yargs` to v17 ([#12357](https://github.com/facebook/jest/pull/12357)) - `[jest-config]` [**BREAKING**] Remove `getTestEnvironment` export ([#12353](https://github.com/facebook/jest/pull/12353)) - `[@jest/core]` Use `index.ts` instead of `jest.ts` as main export ([#12329](https://github.com/facebook/jest/pull/12329)) +- `[jest-environment-jsdom]` [**BREAKING**] Migrate to ESM ([#12340](https://github.com/facebook/jest/pull/12340)) +- `[jest-environment-node]` [**BREAKING**] Migrate to ESM ([#12340](https://github.com/facebook/jest/pull/12340)) - `[@jest/fake-timers]` Update `@sinonjs/fake_timers` to v9 ([#12357](https://github.com/facebook/jest/pull/12357)) +- `[jest-jasmine2, jest-runtime]` [**BREAKING**] Use `Symbol` to pass `jest.setTimeout` value instead of `jasmine` specific logic ([#12124](https://github.com/facebook/jest/pull/12124)) +- `[jest-phabricator]` [**BREAKING**] Migrate to ESM ([#12341](https://github.com/facebook/jest/pull/12341)) - `[jest-resolve]` [**BREAKING**] Make `requireResolveFunction` argument mandatory ([#12353](https://github.com/facebook/jest/pull/12353)) - `[jest-runner]` [**BREAKING**] Remove some type exports from `@jest/test-result` ([#12353](https://github.com/facebook/jest/pull/12353)) +- `[jest-snapshot]` [**BREAKING**] Migrate to ESM ([#12342](https://github.com/facebook/jest/pull/12342)) - `[jest-transform]` Update `write-file-atomic` to v4 ([#12357](https://github.com/facebook/jest/pull/12357)) - `[jest]` Use `index.ts` instead of `jest.ts` as main export ([#12329](https://github.com/facebook/jest/pull/12329)) From 8c5204545669e27140d867b79ee60faf60c7fc5c Mon Sep 17 00:00:00 2001 From: Simen Bekkhus Date: Fri, 11 Feb 2022 08:20:03 +0100 Subject: [PATCH 2/6] chore: fix failing test for node 17.5 --- e2e/__tests__/failures.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e2e/__tests__/failures.test.ts b/e2e/__tests__/failures.test.ts index 6dfb810be8d7..c6cbb8e0953e 100644 --- a/e2e/__tests__/failures.test.ts +++ b/e2e/__tests__/failures.test.ts @@ -94,7 +94,7 @@ test('errors after test has completed', () => { const {stderr} = runJest(dir, ['errorAfterTestComplete.test.js']); expect(stderr).toMatch( - /Error: Caught error after test environment was torn down/, + /Error(WithStack)?: Caught error after test environment was torn down/, ); expect(stderr).toMatch(/Failed: "fail async"/); }); From bc0a91afb87ec6bb401d5083b3e4c6004f8c7756 Mon Sep 17 00:00:00 2001 From: Simen Bekkhus Date: Fri, 11 Feb 2022 08:30:50 +0100 Subject: [PATCH 3/6] chore: update `write-file-atomic` again (#12368) --- packages/jest-transform/package.json | 4 ++-- yarn.lock | 33 ++++++++++------------------ 2 files changed, 14 insertions(+), 23 deletions(-) diff --git a/packages/jest-transform/package.json b/packages/jest-transform/package.json index 068d13140912..1159052c4cbe 100644 --- a/packages/jest-transform/package.json +++ b/packages/jest-transform/package.json @@ -31,7 +31,7 @@ "pirates": "^4.0.4", "slash": "^3.0.0", "source-map": "^0.6.1", - "write-file-atomic": "^4.0.0" + "write-file-atomic": "^4.0.1" }, "devDependencies": { "@jest/test-utils": "^28.0.0-alpha.0", @@ -40,7 +40,7 @@ "@types/fast-json-stable-stringify": "^2.0.0", "@types/graceful-fs": "^4.1.2", "@types/micromatch": "^4.0.1", - "@types/write-file-atomic": "^3.0.0", + "@types/write-file-atomic": "^4.0.0", "dedent": "^0.7.0" }, "engines": { diff --git a/yarn.lock b/yarn.lock index ea0db676e7c1..7b9ac8b8fe45 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2825,7 +2825,7 @@ __metadata: "@types/fast-json-stable-stringify": ^2.0.0 "@types/graceful-fs": ^4.1.2 "@types/micromatch": ^4.0.1 - "@types/write-file-atomic": ^3.0.0 + "@types/write-file-atomic": ^4.0.0 babel-plugin-istanbul: ^6.1.1 chalk: ^4.0.0 convert-source-map: ^1.4.0 @@ -2839,7 +2839,7 @@ __metadata: pirates: ^4.0.4 slash: ^3.0.0 source-map: ^0.6.1 - write-file-atomic: ^4.0.0 + write-file-atomic: ^4.0.1 languageName: unknown linkType: soft @@ -5394,12 +5394,12 @@ __metadata: languageName: node linkType: hard -"@types/write-file-atomic@npm:^3.0.0": - version: 3.0.3 - resolution: "@types/write-file-atomic@npm:3.0.3" +"@types/write-file-atomic@npm:^4.0.0": + version: 4.0.0 + resolution: "@types/write-file-atomic@npm:4.0.0" dependencies: "@types/node": "*" - checksum: 11dc5f02b219bf35eacf267f48478c086e4d0aa7851ba435d815e4c42472ff0b4e6fd640203bad052ce05e32137a649f9356a2636456c2ba887f96a841b0e20c + checksum: 8abd0f24442414cdd6eabe1a1c9c31f4a5d283150f33163b24d8c3bb4affb47de745a1aa0581237a2bc566e847793b4b5d626fada1aba4e2ec8d3c1c61e2c1cd languageName: node linkType: hard @@ -19687,7 +19687,7 @@ __metadata: languageName: node linkType: hard -"signal-exit@npm:^3.0.0, signal-exit@npm:^3.0.2, signal-exit@npm:^3.0.3": +"signal-exit@npm:^3.0.0, signal-exit@npm:^3.0.2, signal-exit@npm:^3.0.3, signal-exit@npm:^3.0.7": version: 3.0.7 resolution: "signal-exit@npm:3.0.7" checksum: a2f098f247adc367dffc27845853e9959b9e88b01cb301658cfe4194352d8d2bb32e18467c786a7fe15f1d44b233ea35633d076d5e737870b7139949d1ab6318 @@ -21219,13 +21219,6 @@ __metadata: languageName: node linkType: hard -"typedarray-to-buffer@npm:^4.0.0": - version: 4.0.0 - resolution: "typedarray-to-buffer@npm:4.0.0" - checksum: c1e4dc6597c98de417c3363da88263d92aefd23569a892b2d8a9b9385858b2c7323f6cae010ecb73fa63cae403d20763b8cad9a25a77f5597a9fb3da506ac7df - languageName: node - linkType: hard - "typedarray@npm:^0.0.6": version: 0.0.6 resolution: "typedarray@npm:0.0.6" @@ -22663,15 +22656,13 @@ __metadata: languageName: node linkType: hard -"write-file-atomic@npm:^4.0.0": - version: 4.0.0 - resolution: "write-file-atomic@npm:4.0.0" +"write-file-atomic@npm:^4.0.1": + version: 4.0.1 + resolution: "write-file-atomic@npm:4.0.1" dependencies: imurmurhash: ^0.1.4 - is-typedarray: ^1.0.0 - signal-exit: ^3.0.2 - typedarray-to-buffer: ^4.0.0 - checksum: c3f10342e5808f0c55f876fec5e5a1d1b341e4dd6a2ba83a44068b9ee2063453c12b5b5afab3d028437fea64a4c8f0cf527297429b9a4cb3545c9c026fdf6577 + signal-exit: ^3.0.7 + checksum: 8f780232533ca6223c63c9b9c01c4386ca8c625ebe5017a9ed17d037aec19462ae17109e0aa155bff5966ee4ae7a27b67a99f55caf3f32ffd84155e9da3929fc languageName: node linkType: hard From e3c84b54d6e87d46362ef2145676404b48d670a2 Mon Sep 17 00:00:00 2001 From: Nicolas Charpentier Date: Fri, 11 Feb 2022 02:31:28 -0500 Subject: [PATCH 4/6] chore: update React examples to match with the new React guidelines (#12217) --- CHANGELOG.md | 1 + docs/SnapshotTesting.md | 1 - docs/TutorialReact.md | 61 +++++++++++-------- examples/enzyme/.babelrc.js | 6 +- examples/enzyme/CheckboxWithLabel.js | 40 ++++++------ .../__tests__/CheckboxWithLabel-test.js | 1 - examples/react-testing-library/.babelrc.js | 6 +- .../CheckboxWithLabel.js | 8 +-- .../__tests__/CheckboxWithLabel-test.js | 1 - examples/react/.babelrc.js | 5 +- examples/react/CheckboxWithLabel.js | 13 ++-- .../react/__tests__/CheckboxWithLabel-test.js | 2 +- examples/snapshot/.babelrc.js | 6 +- examples/snapshot/Clock.js | 8 +-- examples/snapshot/Link.js | 8 +-- examples/snapshot/__tests__/clock.test.js | 1 - examples/snapshot/__tests__/link.test.js | 1 - examples/typescript/.babelrc.js | 2 +- examples/typescript/CheckboxWithLabel.tsx | 12 ++-- examples/typescript/tsconfig.json | 2 +- 20 files changed, 97 insertions(+), 88 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 12885ce1ad1f..7499ce185f5e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ - `[*]` [**BREAKING**] Drop support for Node v10 and v15 and target first LTS `16.13.0` ([#12220](https://github.com/facebook/jest/pull/12220)) - `[*]` [**BREAKING**] Drop support for `typescript@3.8`, minimum version is now `4.2` ([#11142](https://github.com/facebook/jest/pull/11142)) - `[*]` Bundle all `.d.ts` files into a single `index.d.ts` per module ([#12345](https://github.com/facebook/jest/pull/12345)) +- `[docs, examples]` Update React examples to match with the new React guidelines for code examples ([#12217](https://github.com/facebook/jest/pull/12217)) - `[expect]` [**BREAKING**] Remove support for importing `build/utils` ([#12323](https://github.com/facebook/jest/pull/12323)) - `[expect]` [**BREAKING**] Migrate to ESM ([#12344](https://github.com/facebook/jest/pull/12344)) - `[jest-cli]` Update `yargs` to v17 ([#12357](https://github.com/facebook/jest/pull/12357)) diff --git a/docs/SnapshotTesting.md b/docs/SnapshotTesting.md index 004ace393d13..882f451ef519 100644 --- a/docs/SnapshotTesting.md +++ b/docs/SnapshotTesting.md @@ -12,7 +12,6 @@ A typical snapshot test case renders a UI component, takes a snapshot, then comp A similar approach can be taken when it comes to testing your React components. Instead of rendering the graphical UI, which would require building the entire app, you can use a test renderer to quickly generate a serializable value for your React tree. Consider this [example test](https://github.com/facebook/jest/blob/main/examples/snapshot/__tests__/link.test.js) for a [Link component](https://github.com/facebook/jest/blob/main/examples/snapshot/Link.js): ```tsx -import React from 'react'; import renderer from 'react-test-renderer'; import Link from '../Link'; diff --git a/docs/TutorialReact.md b/docs/TutorialReact.md index 9b5ce3d25b88..da302694fa04 100644 --- a/docs/TutorialReact.md +++ b/docs/TutorialReact.md @@ -48,7 +48,10 @@ Your `package.json` should look something like this (where `` i ```js title="babel.config.js" module.exports = { - presets: ['@babel/preset-env', '@babel/preset-react'], + presets: [ + '@babel/preset-env', + ['@babel/preset-react', {runtime: 'automatic'}], + ], }; ``` @@ -59,14 +62,14 @@ module.exports = { Let's create a [snapshot test](SnapshotTesting.md) for a Link component that renders hyperlinks: ```tsx title="Link.js" -import React, {useState} from 'react'; +import {useState} from 'react'; const STATUS = { HOVERED: 'hovered', NORMAL: 'normal', }; -const Link = ({page, children}) => { +export default function Link({page, children}) { const [status, setStatus] = useState(STATUS.NORMAL); const onMouseEnter = () => { @@ -87,9 +90,7 @@ const Link = ({page, children}) => { {children} ); -}; - -export default Link; +} ``` > Note: Examples are using Function components, but Class components can be tested in the same way. See [React: Function and Class Components](https://reactjs.org/docs/components-and-props.html#function-and-class-components). **Reminders** that with Class components, we expect Jest to be used to test props and not methods directly. @@ -97,11 +98,10 @@ export default Link; Now let's use React's test renderer and Jest's snapshot feature to interact with the component and capture the rendered output and create a snapshot file: ```tsx title="Link.react.test.js" -import React from 'react'; -import renderer from 'react-test-renderer'; import Link from '../Link.react'; +import renderer from 'react-test-renderer'; -test('Link changes the class when hovered', () => { +it('changes the class when hovered', () => { const component = renderer.create( Facebook, ); @@ -109,13 +109,17 @@ test('Link changes the class when hovered', () => { expect(tree).toMatchSnapshot(); // manually trigger the callback - tree.props.onMouseEnter(); + renderer.act(() => { + tree.props.onMouseEnter(); + }); // re-rendering tree = component.toJSON(); expect(tree).toMatchSnapshot(); // manually trigger the callback - tree.props.onMouseLeave(); + renderer.act(() => { + tree.props.onMouseLeave(); + }); // re-rendering tree = component.toJSON(); expect(tree).toMatchSnapshot(); @@ -125,32 +129,35 @@ test('Link changes the class when hovered', () => { When you run `yarn test` or `jest`, this will produce an output file like this: ```javascript title="__tests__/__snapshots__/Link.react.test.js.snap" -exports[`Link changes the class when hovered 1`] = ` +exports[`changes the class when hovered 1`] = ` + onMouseLeave={[Function]} +> Facebook `; -exports[`Link changes the class when hovered 2`] = ` +exports[`changes the class when hovered 2`] = ` + onMouseLeave={[Function]} +> Facebook `; -exports[`Link changes the class when hovered 3`] = ` +exports[`changes the class when hovered 3`] = ` + onMouseLeave={[Function]} +> Facebook `; @@ -160,7 +167,7 @@ The next time you run the tests, the rendered output will be compared to the pre The code for this example is available at [examples/snapshot](https://github.com/facebook/jest/tree/main/examples/snapshot). -#### Snapshot Testing with Mocks, Enzyme and React 16 +#### Snapshot Testing with Mocks, Enzyme and React 16+ There's a caveat around snapshot testing when using Enzyme and React 16+. If you mock out a module using the following style: @@ -205,9 +212,9 @@ You have to run `yarn add --dev @testing-library/react` to use react-testing-lib Let's implement a checkbox which swaps between two labels: ```tsx title="CheckboxWithLabel.js" -import React, {useState} from 'react'; +import {useState} from 'react'; -const CheckboxWithLabel = ({labelOn, labelOff}) => { +export default function CheckboxWithLabel({labelOn, labelOff}) { const [isChecked, setIsChecked] = useState(false); const onChange = () => { @@ -220,13 +227,10 @@ const CheckboxWithLabel = ({labelOn, labelOff}) => { {isChecked ? labelOn : labelOff} ); -}; - -export default CheckboxWithLabel; +} ``` ```tsx title="__tests__/CheckboxWithLabel-test.js" -import React from 'react'; import {cleanup, fireEvent, render} from '@testing-library/react'; import CheckboxWithLabel from '../CheckboxWithLabel'; @@ -256,11 +260,14 @@ You have to run `yarn add --dev enzyme` to use Enzyme. If you are using a React Let's rewrite the test from above using Enzyme instead of react-testing-library. We use Enzyme's [shallow renderer](http://airbnb.io/enzyme/docs/api/shallow.html) in this example. ```tsx title="__tests__/CheckboxWithLabel-test.js" -import React from 'react'; -import {shallow} from 'enzyme'; +import Enzyme, {shallow} from 'enzyme'; +import Adapter from 'enzyme-adapter-react-16'; + +Enzyme.configure({adapter: new Adapter()}); + import CheckboxWithLabel from '../CheckboxWithLabel'; -test('CheckboxWithLabel changes the text after click', () => { +it('CheckboxWithLabel changes the text after click', () => { // Render a checkbox with label in the document const checkbox = shallow(); diff --git a/examples/enzyme/.babelrc.js b/examples/enzyme/.babelrc.js index 8310867ffaae..1de03c2d75ec 100644 --- a/examples/enzyme/.babelrc.js +++ b/examples/enzyme/.babelrc.js @@ -1,6 +1,8 @@ // Copyright (c) 2014-present, Facebook, Inc. All rights reserved. module.exports = { - presets: ['@babel/preset-env', '@babel/preset-react'], - plugins: ['@babel/plugin-proposal-class-properties'] + presets: [ + '@babel/preset-env', + ['@babel/preset-react', {runtime: 'automatic'}], + ], }; diff --git a/examples/enzyme/CheckboxWithLabel.js b/examples/enzyme/CheckboxWithLabel.js index 607f01c3c196..aaf3ccc6b9de 100644 --- a/examples/enzyme/CheckboxWithLabel.js +++ b/examples/enzyme/CheckboxWithLabel.js @@ -1,26 +1,28 @@ // Copyright 2004-present Facebook. All Rights Reserved. -import React from 'react'; +import {useState} from 'react'; -export default class CheckboxWithLabel extends React.Component { - state = { - isChecked: false, - }; +export default function CheckboxWithLabel({ + labelRef, + inputRef, + labelOn, + labelOff, +}) { + const [isChecked, setIsChecked] = useState(false); - onChange = () => { - this.setState({isChecked: !this.state.isChecked}); + const onChange = () => { + setIsChecked(!isChecked); }; - render() { - return ( - - ); - } + return ( + + ); } diff --git a/examples/enzyme/__tests__/CheckboxWithLabel-test.js b/examples/enzyme/__tests__/CheckboxWithLabel-test.js index be7aabef2437..068dfa172e76 100644 --- a/examples/enzyme/__tests__/CheckboxWithLabel-test.js +++ b/examples/enzyme/__tests__/CheckboxWithLabel-test.js @@ -1,6 +1,5 @@ // Copyright 2004-present Facebook. All Rights Reserved. -import React from 'react'; import Enzyme, {shallow} from 'enzyme'; import Adapter from 'enzyme-adapter-react-16'; diff --git a/examples/react-testing-library/.babelrc.js b/examples/react-testing-library/.babelrc.js index 1968dd119aca..1de03c2d75ec 100644 --- a/examples/react-testing-library/.babelrc.js +++ b/examples/react-testing-library/.babelrc.js @@ -1,6 +1,8 @@ // Copyright (c) 2014-present, Facebook, Inc. All rights reserved. module.exports = { - presets: ['@babel/preset-env', '@babel/preset-react'], - plugins: ['@babel/plugin-proposal-class-properties'], + presets: [ + '@babel/preset-env', + ['@babel/preset-react', {runtime: 'automatic'}], + ], }; diff --git a/examples/react-testing-library/CheckboxWithLabel.js b/examples/react-testing-library/CheckboxWithLabel.js index f38b715aba96..a8cf6a8cf190 100644 --- a/examples/react-testing-library/CheckboxWithLabel.js +++ b/examples/react-testing-library/CheckboxWithLabel.js @@ -1,8 +1,8 @@ // Copyright 2004-present Facebook. All Rights Reserved. -import React, {useState} from 'react'; +import {useState} from 'react'; -const CheckboxWithLabel = ({labelOn, labelOff}) => { +export default function CheckboxWithLabel({labelOn, labelOff}) { const [isChecked, setIsChecked] = useState(false); const onChange = () => { @@ -15,6 +15,4 @@ const CheckboxWithLabel = ({labelOn, labelOff}) => { {isChecked ? labelOn : labelOff} ); -}; - -export default CheckboxWithLabel; +} diff --git a/examples/react-testing-library/__tests__/CheckboxWithLabel-test.js b/examples/react-testing-library/__tests__/CheckboxWithLabel-test.js index cfc95680c90b..8d286b1ee361 100644 --- a/examples/react-testing-library/__tests__/CheckboxWithLabel-test.js +++ b/examples/react-testing-library/__tests__/CheckboxWithLabel-test.js @@ -1,6 +1,5 @@ // Copyright 2004-present Facebook. All Rights Reserved. -import React from 'react'; import {cleanup, fireEvent, render} from '@testing-library/react'; import CheckboxWithLabel from '../CheckboxWithLabel'; diff --git a/examples/react/.babelrc.js b/examples/react/.babelrc.js index 7256ac590a22..1de03c2d75ec 100644 --- a/examples/react/.babelrc.js +++ b/examples/react/.babelrc.js @@ -1,5 +1,8 @@ // Copyright (c) 2014-present, Facebook, Inc. All rights reserved. module.exports = { - presets: ['@babel/preset-env', '@babel/preset-react'], + presets: [ + '@babel/preset-env', + ['@babel/preset-react', {runtime: 'automatic'}], + ], }; diff --git a/examples/react/CheckboxWithLabel.js b/examples/react/CheckboxWithLabel.js index 88c0ee82f093..aaf3ccc6b9de 100644 --- a/examples/react/CheckboxWithLabel.js +++ b/examples/react/CheckboxWithLabel.js @@ -1,8 +1,13 @@ // Copyright 2004-present Facebook. All Rights Reserved. -import React, {useState} from 'react'; +import {useState} from 'react'; -const CheckboxWithLabel = ({labelRef, inputRef, labelOn, labelOff}) => { +export default function CheckboxWithLabel({ + labelRef, + inputRef, + labelOn, + labelOff, +}) { const [isChecked, setIsChecked] = useState(false); const onChange = () => { @@ -20,6 +25,4 @@ const CheckboxWithLabel = ({labelRef, inputRef, labelOn, labelOff}) => { {isChecked ? labelOn : labelOff} ); -}; - -export default CheckboxWithLabel; +} diff --git a/examples/react/__tests__/CheckboxWithLabel-test.js b/examples/react/__tests__/CheckboxWithLabel-test.js index b887304d42aa..05a89ab546b9 100644 --- a/examples/react/__tests__/CheckboxWithLabel-test.js +++ b/examples/react/__tests__/CheckboxWithLabel-test.js @@ -1,6 +1,6 @@ // Copyright 2004-present Facebook. All Rights Reserved. -import React, {createRef} from 'react'; +import {createRef} from 'react'; import * as TestUtils from 'react-dom/test-utils'; import CheckboxWithLabel from '../CheckboxWithLabel'; diff --git a/examples/snapshot/.babelrc.js b/examples/snapshot/.babelrc.js index 1968dd119aca..1de03c2d75ec 100644 --- a/examples/snapshot/.babelrc.js +++ b/examples/snapshot/.babelrc.js @@ -1,6 +1,8 @@ // Copyright (c) 2014-present, Facebook, Inc. All rights reserved. module.exports = { - presets: ['@babel/preset-env', '@babel/preset-react'], - plugins: ['@babel/plugin-proposal-class-properties'], + presets: [ + '@babel/preset-env', + ['@babel/preset-react', {runtime: 'automatic'}], + ], }; diff --git a/examples/snapshot/Clock.js b/examples/snapshot/Clock.js index 11a73525a4cb..cc74499b95b0 100644 --- a/examples/snapshot/Clock.js +++ b/examples/snapshot/Clock.js @@ -1,8 +1,8 @@ // Copyright 2004-present Facebook. All Rights Reserved. -import React, {useEffect, useState} from 'react'; +import {useEffect, useState} from 'react'; -const Clock = () => { +export default function Clock() { const [seconds, setSeconds] = useState(Date.now() / 1000); const tick = () => { @@ -16,6 +16,4 @@ const Clock = () => { }, []); return

{seconds} seconds have elapsed since the UNIX epoch.

; -}; - -export default Clock; +} diff --git a/examples/snapshot/Link.js b/examples/snapshot/Link.js index 398489600fba..06f6b19c3477 100644 --- a/examples/snapshot/Link.js +++ b/examples/snapshot/Link.js @@ -1,13 +1,13 @@ // Copyright 2004-present Facebook. All Rights Reserved. -import React, {useState} from 'react'; +import {useState} from 'react'; const STATUS = { HOVERED: 'hovered', NORMAL: 'normal', }; -const Link = ({page, children}) => { +export default function Link({page, children}) { const [status, setStatus] = useState(STATUS.NORMAL); const onMouseEnter = () => { @@ -28,6 +28,4 @@ const Link = ({page, children}) => { {children} ); -}; - -export default Link; +} diff --git a/examples/snapshot/__tests__/clock.test.js b/examples/snapshot/__tests__/clock.test.js index 59f91386f597..a4254cf284f7 100644 --- a/examples/snapshot/__tests__/clock.test.js +++ b/examples/snapshot/__tests__/clock.test.js @@ -2,7 +2,6 @@ 'use strict'; -import React from 'react'; import Clock from '../Clock'; import renderer from 'react-test-renderer'; diff --git a/examples/snapshot/__tests__/link.test.js b/examples/snapshot/__tests__/link.test.js index a86738d0b181..607c8a639526 100644 --- a/examples/snapshot/__tests__/link.test.js +++ b/examples/snapshot/__tests__/link.test.js @@ -2,7 +2,6 @@ 'use strict'; -import React from 'react'; import Link from '../Link'; import renderer from 'react-test-renderer'; diff --git a/examples/typescript/.babelrc.js b/examples/typescript/.babelrc.js index d3420d566912..6ab95c459d5c 100644 --- a/examples/typescript/.babelrc.js +++ b/examples/typescript/.babelrc.js @@ -4,6 +4,6 @@ module.exports = { presets: [ '@babel/preset-env', '@babel/preset-typescript', - '@babel/preset-react', + ['@babel/preset-react', {runtime: 'automatic'}], ], }; diff --git a/examples/typescript/CheckboxWithLabel.tsx b/examples/typescript/CheckboxWithLabel.tsx index f5ed06943f41..a2ca686bf948 100644 --- a/examples/typescript/CheckboxWithLabel.tsx +++ b/examples/typescript/CheckboxWithLabel.tsx @@ -1,6 +1,6 @@ // Copyright (c) 2014-present, Facebook, Inc. All rights reserved. -import * as React from 'react'; +import {useState} from 'react'; type CheckboxWithLabelProps = { labelRef: React.LegacyRef; @@ -9,13 +9,13 @@ type CheckboxWithLabelProps = { labelOn: string; }; -const CheckboxWithLabel = ({ +export default function CheckboxWithLabel({ labelRef, inputRef, labelOn, labelOff, -}: CheckboxWithLabelProps) => { - const [isChecked, setIsChecked] = React.useState(false); +}: CheckboxWithLabelProps) { + const [isChecked, setIsChecked] = useState(false); const onChange = () => { setIsChecked(!isChecked); @@ -32,6 +32,4 @@ const CheckboxWithLabel = ({ {isChecked ? labelOn : labelOff} ); -}; - -export default CheckboxWithLabel; +} diff --git a/examples/typescript/tsconfig.json b/examples/typescript/tsconfig.json index b1e34ecad232..2d2d665710bc 100644 --- a/examples/typescript/tsconfig.json +++ b/examples/typescript/tsconfig.json @@ -1,6 +1,6 @@ { "compilerOptions": { "module": "commonjs", - "jsx": "react" + "jsx": "react-jsx" } } From 60eb4160b5258eed5faf381e0c8ea6662076cc6c Mon Sep 17 00:00:00 2001 From: Ivan Rubinson Date: Fri, 11 Feb 2022 10:45:00 +0200 Subject: [PATCH 5/6] chore: add .gitattributes to force LF line ends (#12010) --- .gitattributes | 1 + 1 file changed, 1 insertion(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000000..176a458f94e0 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto From faef0b4b7082df574a0e4423b86d468847360f17 Mon Sep 17 00:00:00 2001 From: Tom Mrazauskas Date: Sat, 12 Feb 2022 08:44:04 +0200 Subject: [PATCH 6/6] fix(expect): expose `AsymmetricMatchers` and `RawMatcherFn` interfaces (#12363) --- CHANGELOG.md | 1 + .../expect-extend/__tests__/ranges.test.ts | 20 +++++ examples/expect-extend/package.json | 30 +++++++ examples/expect-extend/toBeWithinRange.ts | 43 ++++++++++ packages/expect/__typetests__/expect.test.ts | 75 +++++++++++++++-- packages/expect/src/index.ts | 10 ++- packages/expect/src/types.ts | 11 +-- packages/jest-jasmine2/src/jestExpect.ts | 4 +- packages/jest-jasmine2/src/types.ts | 21 +---- packages/jest-snapshot/src/index.ts | 84 +++++++++---------- packages/jest-snapshot/src/types.ts | 6 -- .../jest-types/__typetests__/expect.test.ts | 72 ++++++++++++---- tsconfig.json | 5 +- yarn.lock | 16 +++- 14 files changed, 293 insertions(+), 105 deletions(-) create mode 100644 examples/expect-extend/__tests__/ranges.test.ts create mode 100644 examples/expect-extend/package.json create mode 100644 examples/expect-extend/toBeWithinRange.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 7499ce185f5e..c74f57649f92 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ ### Fixes - `[expect]` Move typings of `.not`, `.rejects` and `.resolves` modifiers outside of `Matchers` interface ([#12346](https://github.com/facebook/jest/pull/12346)) +- `[expect]` Expose `AsymmetricMatchers` and `RawMatcherFn` interfaces ([#12363](https://github.com/facebook/jest/pull/12363)) - `[jest-environment-jsdom]` Make `jsdom` accessible to extending environments again ([#12232](https://github.com/facebook/jest/pull/12232)) - `[jest-jasmine2, jest-types]` [**BREAKING**] Move all `jasmine` specific types from `@jest/types` to its own package ([#12125](https://github.com/facebook/jest/pull/12125)) diff --git a/examples/expect-extend/__tests__/ranges.test.ts b/examples/expect-extend/__tests__/ranges.test.ts new file mode 100644 index 000000000000..77f80e8a5ef8 --- /dev/null +++ b/examples/expect-extend/__tests__/ranges.test.ts @@ -0,0 +1,20 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import {expect, test} from '@jest/globals'; +import '../toBeWithinRange'; + +test('is within range', () => expect(100).toBeWithinRange(90, 110)); + +test('is NOT within range', () => expect(101).not.toBeWithinRange(0, 100)); + +test('asymmetric ranges', () => { + expect({apples: 6, bananas: 3}).toEqual({ + apples: expect.toBeWithinRange(1, 10), + bananas: expect.not.toBeWithinRange(11, 20), + }); +}); diff --git a/examples/expect-extend/package.json b/examples/expect-extend/package.json new file mode 100644 index 000000000000..7ffa1f92b773 --- /dev/null +++ b/examples/expect-extend/package.json @@ -0,0 +1,30 @@ +{ + "private": true, + "version": "0.0.0", + "name": "example-expect-extend", + "devDependencies": { + "@babel/core": "*", + "@babel/preset-env": "*", + "@babel/preset-typescript": "*", + "@jest/globals": "workspace:*", + "babel-jest": "workspace:*", + "expect": "workspace:*", + "jest": "workspace:*" + }, + "scripts": { + "test": "jest" + }, + "babel": { + "presets": [ + [ + "@babel/preset-env", + { + "targets": { + "node": "current" + } + } + ], + "@babel/preset-typescript" + ] + } +} diff --git a/examples/expect-extend/toBeWithinRange.ts b/examples/expect-extend/toBeWithinRange.ts new file mode 100644 index 000000000000..b7d43cddec73 --- /dev/null +++ b/examples/expect-extend/toBeWithinRange.ts @@ -0,0 +1,43 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import {expect} from '@jest/globals'; +import type {RawMatcherFn} from 'expect'; + +const toBeWithinRange: RawMatcherFn = ( + actual: number, + floor: number, + ceiling: number, +) => { + const pass = actual >= floor && actual <= ceiling; + if (pass) { + return { + message: () => + `expected ${actual} not to be within range ${floor} - ${ceiling}`, + pass: true, + }; + } else { + return { + message: () => + `expected ${actual} to be within range ${floor} - ${ceiling}`, + pass: false, + }; + } +}; + +expect.extend({ + toBeWithinRange, +}); + +declare module 'expect' { + interface AsymmetricMatchers { + toBeWithinRange(a: number, b: number): void; + } + interface Matchers { + toBeWithinRange(a: number, b: number): R; + } +} diff --git a/packages/expect/__typetests__/expect.test.ts b/packages/expect/__typetests__/expect.test.ts index af5d2ad1fe21..acd40562f0a0 100644 --- a/packages/expect/__typetests__/expect.test.ts +++ b/packages/expect/__typetests__/expect.test.ts @@ -5,12 +5,77 @@ * LICENSE file in the root directory of this source tree. */ -import {expectError} from 'tsd-lite'; -import type * as expect from 'expect'; +import {expectError, expectType} from 'tsd-lite'; +import type {EqualsFunction, Tester} from '@jest/expect-utils'; +import {type Matchers, expect} from 'expect'; +import type * as jestMatcherUtils from 'jest-matcher-utils'; -type M = expect.Matchers; -type N = expect.Matchers; +type M = Matchers; +type N = Matchers; expectError(() => { - type E = expect.Matchers; + type E = Matchers; }); + +// extend + +type MatcherUtils = typeof jestMatcherUtils & { + iterableEquality: Tester; + subsetEquality: Tester; +}; + +expectType( + expect.extend({ + toBeWithinRange(actual: number, floor: number, ceiling: number) { + expectType(this.assertionCalls); + expectType(this.currentTestName); + expectType<(() => void) | undefined>(this.dontThrow); + expectType(this.error); + expectType(this.equals); + expectType(this.expand); + expectType(this.expectedAssertionsNumber); + expectType(this.expectedAssertionsNumberError); + expectType(this.isExpectingAssertions); + expectType(this.isExpectingAssertionsError); + expectType(this.isNot); + expectType(this.promise); + expectType>(this.suppressedErrors); + expectType(this.testPath); + expectType(this.utils); + + const pass = actual >= floor && actual <= ceiling; + if (pass) { + return { + message: () => + `expected ${actual} not to be within range ${floor} - ${ceiling}`, + pass: true, + }; + } else { + return { + message: () => + `expected ${actual} to be within range ${floor} - ${ceiling}`, + pass: false, + }; + } + }, + }), +); + +declare module 'expect' { + interface AsymmetricMatchers { + toBeWithinRange(floor: number, ceiling: number): void; + } + interface Matchers { + toBeWithinRange(floor: number, ceiling: number): void; + } +} + +expectType(expect(100).toBeWithinRange(90, 110)); +expectType(expect(101).not.toBeWithinRange(0, 100)); + +expectType( + expect({apples: 6, bananas: 3}).toEqual({ + apples: expect.toBeWithinRange(1, 10), + bananas: expect.not.toBeWithinRange(11, 20), + }), +); diff --git a/packages/expect/src/index.ts b/packages/expect/src/index.ts index 7edb89b4d54d..1be8f83c9c0c 100644 --- a/packages/expect/src/index.ts +++ b/packages/expect/src/index.ts @@ -49,7 +49,13 @@ import type { ThrowingMatcherFn, } from './types'; -export type {Expect, MatcherState, Matchers} from './types'; +export type { + AsymmetricMatchers, + Expect, + MatcherState, + Matchers, + RawMatcherFn, +} from './types'; export class JestAssertionError extends Error { matcherResult?: Omit & {message: string}; @@ -358,7 +364,7 @@ const makeThrowingMatcher = ( expect.extend = ( matchers: MatchersObject, -): void => setMatchers(matchers, false, expect); +) => setMatchers(matchers, false, expect); expect.anything = anything; expect.any = any; diff --git a/packages/expect/src/types.ts b/packages/expect/src/types.ts index d9b7f31307fd..9e037c42d9f0 100644 --- a/packages/expect/src/types.ts +++ b/packages/expect/src/types.ts @@ -13,7 +13,7 @@ import {INTERNAL_MATCHER_FLAG} from './jestMatchersObject'; export type SyncExpectationResult = { pass: boolean; - message: () => string; + message(): string; }; export type AsyncExpectationResult = Promise; @@ -21,7 +21,8 @@ export type AsyncExpectationResult = Promise; export type ExpectationResult = SyncExpectationResult | AsyncExpectationResult; export type RawMatcherFn = { - (this: T, received: any, expected: any, options?: any): ExpectationResult; + (this: T, actual: any, expected: any, options?: any): ExpectationResult; + /** @internal */ [INTERNAL_MATCHER_FLAG]?: boolean; }; @@ -31,7 +32,7 @@ export type PromiseMatcherFn = (actual: any) => Promise; export type MatcherState = { assertionCalls: number; currentTestName?: string; - dontThrow?: () => void; + dontThrow?(): void; error?: Error; equals: EqualsFunction; expand?: boolean; @@ -56,7 +57,7 @@ export interface AsymmetricMatcher { toAsymmetricMatcher?(): string; } export type MatchersObject = { - [id: string]: RawMatcherFn; + [name: string]: RawMatcherFn; }; export type ExpectedAssertionsErrors = Array<{ actual: string | number; @@ -73,7 +74,7 @@ export type Expect = { assertions(numberOfAssertions: number): void; // TODO: remove this `T extends` - should get from some interface merging extend(matchers: MatchersObject): void; - extractExpectedAssertionsErrors: () => ExpectedAssertionsErrors; + extractExpectedAssertionsErrors(): ExpectedAssertionsErrors; getState(): State; hasAssertions(): void; setState(state: Partial): void; diff --git a/packages/jest-jasmine2/src/jestExpect.ts b/packages/jest-jasmine2/src/jestExpect.ts index 6269cdfb40b0..836de548e832 100644 --- a/packages/jest-jasmine2/src/jestExpect.ts +++ b/packages/jest-jasmine2/src/jestExpect.ts @@ -7,7 +7,7 @@ /* eslint-disable local/prefer-spread-eventually */ -import {MatcherState, expect} from 'expect'; +import {type MatcherState, type RawMatcherFn, expect} from 'expect'; import { addSerializer, toMatchInlineSnapshot, @@ -15,7 +15,7 @@ import { toThrowErrorMatchingInlineSnapshot, toThrowErrorMatchingSnapshot, } from 'jest-snapshot'; -import type {JasmineMatchersObject, RawMatcherFn} from './types'; +import type {JasmineMatchersObject} from './types'; export default function jestExpect(config: {expand: boolean}): void { global.expect = expect; diff --git a/packages/jest-jasmine2/src/types.ts b/packages/jest-jasmine2/src/types.ts index 2d8282a4be6b..34ef38ebddec 100644 --- a/packages/jest-jasmine2/src/types.ts +++ b/packages/jest-jasmine2/src/types.ts @@ -7,7 +7,7 @@ import type {AssertionError} from 'assert'; import type {Config} from '@jest/types'; -import type {Expect} from 'expect'; +import type {Expect, RawMatcherFn} from 'expect'; import type CallTracker from './jasmine/CallTracker'; import type Env from './jasmine/Env'; import type JsApiReporter from './jasmine/JsApiReporter'; @@ -25,25 +25,6 @@ export interface AssertionErrorWithStack extends AssertionError { stack: string; } -// TODO Add expect types to @jest/types or leave it here -// Borrowed from "expect" -// -------START------- -export type SyncExpectationResult = { - pass: boolean; - message: () => string; -}; - -export type AsyncExpectationResult = Promise; - -export type ExpectationResult = SyncExpectationResult | AsyncExpectationResult; - -export type RawMatcherFn = ( - expected: unknown, - actual: unknown, - options?: unknown, -) => ExpectationResult; -// -------END------- - export type RunDetails = { totalSpecsDefined?: number; failedExpectations?: SuiteResult['failedExpectations']; diff --git a/packages/jest-snapshot/src/index.ts b/packages/jest-snapshot/src/index.ts index 322f4f549453..fd3b7f73fe2f 100644 --- a/packages/jest-snapshot/src/index.ts +++ b/packages/jest-snapshot/src/index.ts @@ -7,6 +7,7 @@ import * as fs from 'graceful-fs'; import type {Config} from '@jest/types'; +import type {RawMatcherFn} from 'expect'; import type {FS as HasteFS} from 'jest-haste-map'; import { BOLD_WEIGHT, @@ -30,7 +31,7 @@ import { printReceived, printSnapshotAndReceived, } from './printSnapshot'; -import type {Context, ExpectationResult, MatchSnapshotConfig} from './types'; +import type {Context, MatchSnapshotConfig} from './types'; import {deepMerge, escapeBacktickString, serialize} from './utils'; export {addSerializer, getSerializers} from './plugins'; @@ -155,12 +156,11 @@ export const cleanup = ( }; }; -export const toMatchSnapshot = function ( - this: Context, +export const toMatchSnapshot: RawMatcherFn = function ( received: unknown, propertiesOrHint?: object | Config.Path, hint?: Config.Path, -): ExpectationResult { +) { const matcherName = 'toMatchSnapshot'; let properties; @@ -214,12 +214,11 @@ export const toMatchSnapshot = function ( }); }; -export const toMatchInlineSnapshot = function ( - this: Context, +export const toMatchInlineSnapshot: RawMatcherFn = function ( received: unknown, propertiesOrSnapshot?: object | string, inlineSnapshot?: string, -): ExpectationResult { +) { const matcherName = 'toMatchInlineSnapshot'; let properties; @@ -408,12 +407,11 @@ const _toMatchSnapshot = (config: MatchSnapshotConfig) => { }; }; -export const toThrowErrorMatchingSnapshot = function ( - this: Context, +export const toThrowErrorMatchingSnapshot: RawMatcherFn = function ( received: unknown, hint: string | undefined, // because error TS1016 for hint?: string fromPromise: boolean, -): ExpectationResult { +) { const matcherName = 'toThrowErrorMatchingSnapshot'; // Future breaking change: Snapshot hint must be a string @@ -431,44 +429,40 @@ export const toThrowErrorMatchingSnapshot = function ( ); }; -export const toThrowErrorMatchingInlineSnapshot = function ( - this: Context, - received: unknown, - inlineSnapshot?: string, - fromPromise?: boolean, -): ExpectationResult { - const matcherName = 'toThrowErrorMatchingInlineSnapshot'; +export const toThrowErrorMatchingInlineSnapshot: RawMatcherFn = + function (received: unknown, inlineSnapshot?: string, fromPromise?: boolean) { + const matcherName = 'toThrowErrorMatchingInlineSnapshot'; - if (inlineSnapshot !== undefined && typeof inlineSnapshot !== 'string') { - const options: MatcherHintOptions = { - expectedColor: noColor, - isNot: this.isNot, - promise: this.promise, - }; + if (inlineSnapshot !== undefined && typeof inlineSnapshot !== 'string') { + const options: MatcherHintOptions = { + expectedColor: noColor, + isNot: this.isNot, + promise: this.promise, + }; - throw new Error( - matcherErrorMessage( - matcherHint(matcherName, undefined, SNAPSHOT_ARG, options), - 'Inline snapshot must be a string', - printWithType('Inline snapshot', inlineSnapshot, serialize), - ), - ); - } + throw new Error( + matcherErrorMessage( + matcherHint(matcherName, undefined, SNAPSHOT_ARG, options), + 'Inline snapshot must be a string', + printWithType('Inline snapshot', inlineSnapshot, serialize), + ), + ); + } - return _toThrowErrorMatchingSnapshot( - { - context: this, - inlineSnapshot: - inlineSnapshot !== undefined - ? stripAddedIndentation(inlineSnapshot) - : undefined, - isInline: true, - matcherName, - received, - }, - fromPromise, - ); -}; + return _toThrowErrorMatchingSnapshot( + { + context: this, + inlineSnapshot: + inlineSnapshot !== undefined + ? stripAddedIndentation(inlineSnapshot) + : undefined, + isInline: true, + matcherName, + received, + }, + fromPromise, + ); + }; const _toThrowErrorMatchingSnapshot = ( config: MatchSnapshotConfig, diff --git a/packages/jest-snapshot/src/types.ts b/packages/jest-snapshot/src/types.ts index 40ddd1cabfaa..dcc0c8e12de5 100644 --- a/packages/jest-snapshot/src/types.ts +++ b/packages/jest-snapshot/src/types.ts @@ -23,9 +23,3 @@ export type MatchSnapshotConfig = { }; export type SnapshotData = Record; - -// copied from `expect` - should be shared -export type ExpectationResult = { - pass: boolean; - message: () => string; -}; diff --git a/packages/jest-types/__typetests__/expect.test.ts b/packages/jest-types/__typetests__/expect.test.ts index 2c380a0c40c8..ece8c523f242 100644 --- a/packages/jest-types/__typetests__/expect.test.ts +++ b/packages/jest-types/__typetests__/expect.test.ts @@ -6,7 +6,9 @@ */ import {expectError, expectType} from 'tsd-lite'; +import type {EqualsFunction, Tester} from '@jest/expect-utils'; import {expect} from '@jest/globals'; +import type * as jestMatcherUtils from 'jest-matcher-utils'; // asymmetric matchers @@ -349,27 +351,63 @@ expectError(expect(jest.fn()).toThrowErrorMatchingInlineSnapshot(true)); // extend +type MatcherUtils = typeof jestMatcherUtils & { + iterableEquality: Tester; + subsetEquality: Tester; +}; + expectType( expect.extend({ - toBeDivisibleBy(actual: number, expected: number) { + toBeWithinRange(actual: number, floor: number, ceiling: number) { + expectType(this.assertionCalls); + expectType(this.currentTestName); + expectType<(() => void) | undefined>(this.dontThrow); + expectType(this.error); + expectType(this.equals); + expectType(this.expand); + expectType(this.expectedAssertionsNumber); + expectType(this.expectedAssertionsNumberError); + expectType(this.isExpectingAssertions); + expectType(this.isExpectingAssertionsError); expectType(this.isNot); - - const pass = actual % expected === 0; - const message = 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}; + expectType(this.promise); + expectType>(this.suppressedErrors); + expectType(this.testPath); + expectType(this.utils); + + const pass = actual >= floor && actual <= ceiling; + if (pass) { + return { + message: () => + `expected ${actual} not to be within range ${floor} - ${ceiling}`, + pass: true, + }; + } else { + return { + message: () => + `expected ${actual} to be within range ${floor} - ${ceiling}`, + pass: false, + }; + } }, }), ); -// TODO -// expect(4).toBeDivisibleBy(2); -// expect.toBeDivisibleBy(2); +declare module 'expect' { + interface AsymmetricMatchers { + toBeWithinRange(floor: number, ceiling: number): void; + } + interface Matchers { + toBeWithinRange(floor: number, ceiling: number): R; + } +} + +expectType(expect(100).toBeWithinRange(90, 110)); +expectType(expect(101).not.toBeWithinRange(0, 100)); + +expectType( + expect({apples: 6, bananas: 3}).toEqual({ + apples: expect.toBeWithinRange(1, 10), + bananas: expect.not.toBeWithinRange(11, 20), + }), +); diff --git a/tsconfig.json b/tsconfig.json index 90c8a266d483..cff2131d1fb2 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,11 +1,11 @@ { "extends": "@tsconfig/node12/tsconfig.json", "compilerOptions": { - "declaration": true, "composite": true, + "declaration": true, "emitDeclarationOnly": true, - "isolatedModules": true, "importsNotUsedAsValues": "error", + "stripInternal": true, "strict": true, @@ -19,6 +19,7 @@ "moduleResolution": "node", /* This needs to be false so our types are possible to consume without setting this */ "esModuleInterop": false, + "isolatedModules": true, "skipLibCheck": false, "resolveJsonModule": true } diff --git a/yarn.lock b/yarn.lock index 7b9ac8b8fe45..7ed274724ad5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9896,6 +9896,20 @@ __metadata: languageName: unknown linkType: soft +"example-expect-extend@workspace:examples/expect-extend": + version: 0.0.0-use.local + resolution: "example-expect-extend@workspace:examples/expect-extend" + dependencies: + "@babel/core": "*" + "@babel/preset-env": "*" + "@babel/preset-typescript": "*" + "@jest/globals": "workspace:*" + babel-jest: "workspace:*" + expect: "workspace:*" + jest: "workspace:*" + languageName: unknown + linkType: soft + "example-getting-started@workspace:examples/getting-started": version: 0.0.0-use.local resolution: "example-getting-started@workspace:examples/getting-started" @@ -10105,7 +10119,7 @@ __metadata: languageName: node linkType: hard -"expect@^28.0.0-alpha.0, expect@workspace:packages/expect": +"expect@^28.0.0-alpha.0, expect@workspace:*, expect@workspace:packages/expect": version: 0.0.0-use.local resolution: "expect@workspace:packages/expect" dependencies: