Skip to content

Commit

Permalink
Merge branch 'main' into next
Browse files Browse the repository at this point in the history
  • Loading branch information
SimenB committed Sep 29, 2021
2 parents 5c0504c + 84688e9 commit 0fd748f
Show file tree
Hide file tree
Showing 33 changed files with 2,245 additions and 800 deletions.
24 changes: 24 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,30 @@

* Drop support for Node 10 and 15

# [24.5.0](https://github.com/jest-community/eslint-plugin-jest/compare/v24.4.3...v24.5.0) (2021-09-29)


### Bug Fixes

* **no-deprecated-functions:** remove `process.cwd` from resolve paths ([#889](https://github.com/jest-community/eslint-plugin-jest/issues/889)) ([6940488](https://github.com/jest-community/eslint-plugin-jest/commit/6940488d7b5a47577e2823e6d4385b511c5becf4))
* **no-identical-title:** always consider `.each` titles unique ([#910](https://github.com/jest-community/eslint-plugin-jest/issues/910)) ([a41a40e](https://github.com/jest-community/eslint-plugin-jest/commit/a41a40eafaf1db444ba940cccd2014cb0dc41be9))


### Features

* create `prefer-expect-resolves` rule ([#822](https://github.com/jest-community/eslint-plugin-jest/issues/822)) ([2556020](https://github.com/jest-community/eslint-plugin-jest/commit/2556020a777f9daaf1d362a04e3f990415e82db8))
* create `prefer-to-be` rule ([#864](https://github.com/jest-community/eslint-plugin-jest/issues/864)) ([3a64aea](https://github.com/jest-community/eslint-plugin-jest/commit/3a64aea5bdc55465f1ef34f1426ae626d6c8a230))
* **require-top-level-describe:** support enforcing max num of describes ([#912](https://github.com/jest-community/eslint-plugin-jest/issues/912)) ([14a2d13](https://github.com/jest-community/eslint-plugin-jest/commit/14a2d1391c9f6f52509316542f45df35853c9b79))
* **valid-title:** allow custom matcher messages ([#913](https://github.com/jest-community/eslint-plugin-jest/issues/913)) ([ffc9392](https://github.com/jest-community/eslint-plugin-jest/commit/ffc93921348b0d4a394125f665d2bb09148ea37e))

## [24.4.3](https://github.com/jest-community/eslint-plugin-jest/compare/v24.4.2...v24.4.3) (2021-09-28)


### Bug Fixes

* **valid-expect-in-promise:** support `finally` ([#914](https://github.com/jest-community/eslint-plugin-jest/issues/914)) ([9c89855](https://github.com/jest-community/eslint-plugin-jest/commit/9c89855d23534272230afe6d9e665b8e11ef3075))
* **valid-expect-in-promise:** support additional test functions ([#915](https://github.com/jest-community/eslint-plugin-jest/issues/915)) ([4798005](https://github.com/jest-community/eslint-plugin-jest/commit/47980058d8d1ff86ee69a376c4edd182d462d594))

## [24.4.2](https://github.com/jest-community/eslint-plugin-jest/compare/v24.4.1...v24.4.2) (2021-09-17)


Expand Down
121 changes: 72 additions & 49 deletions README.md

Large diffs are not rendered by default.

9 changes: 4 additions & 5 deletions docs/rules/max-nested-describe.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,16 +116,15 @@ describe('foo', () => {
});
});

describe('foo2', function()) {
describe('bar2', function() {
it('should get something', function() {
describe('foo2', function () {
describe('bar2', function () {
it('should get something', function () {
expect(getSomething()).toBe('Something');
});

it('should get else', function() {
it('should get else', function () {
expect(getSomething()).toBe('Something');
});
});
});

```
60 changes: 57 additions & 3 deletions docs/rules/no-conditional-expect.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ assumed to be promises.

## Rule Details

Jest considered a test to have failed if it throws an error, rather than on if
any particular function is called, meaning conditional calls to `expect` could
result in tests silently being skipped.
Jest only considers a test to have failed if it throws an error, meaning if
calls to assertion functions like `expect` occur in conditional code such as a
`catch` statement, tests can end up passing but not actually test anything.

Additionally, conditionals tend to make tests more brittle and complex, as they
increase the amount of mental thinking needed to understand what is actually
Expand Down Expand Up @@ -79,3 +79,57 @@ it('throws an error', async () => {
await expect(foo).rejects.toThrow(Error);
});
```

### How to catch a thrown error for testing without violating this rule

A common situation that comes up with this rule is when wanting to test
properties on a thrown error, as Jest's `toThrow` matcher only checks the
`message` property.

Most people write something like this:

```typescript
describe('when the http request fails', () => {
it('includes the status code in the error', async () => {
try {
await makeRequest(url);
} catch (error) {
expect(error).toHaveProperty('statusCode', 404);
}
});
});
```

As stated above, the problem with this is that if `makeRequest()` doesn't throw
the test will still pass as if the `expect` had been called.

While you can use `expect.assertions` & `expect.hasAssertions` for these
situations, they only work with `expect`.

A better way to handle this situation is to introduce a wrapper to handle the
catching, and otherwise returns a specific "no error thrown" error if nothing is
thrown by the wrapped function:

```typescript
class NoErrorThrownError extends Error {}

const getError = async <TError>(call: () => unknown): Promise<TError> => {
try {
await call();

throw new NoErrorThrownError();
} catch (error: unknown) {
return error as TError;
}
};

describe('when the http request fails', () => {
it('includes the status code in the error', async () => {
const error = await getError(async () => makeRequest(url));

// check that the returned error wasn't that no error was thrown
expect(error).not.toBeInstanceOf(NoErrorThrownError);
expect(error).toHaveProperty('statusCode', 404);
});
});
```
5 changes: 5 additions & 0 deletions docs/rules/no-deprecated-functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ either been renamed for clarity, or replaced with more powerful APIs.
While typically these deprecated functions are kept in the codebase for a number
of majors, eventually they are removed completely.

This rule requires knowing which version of Jest you're using - see
[this section of the readme](../../README.md#jest-version-setting) for details
on how that is obtained automatically and how you can explicitly provide a
version if needed.

## Rule details

This rule warns about calls to deprecated functions, and provides details on
Expand Down
6 changes: 3 additions & 3 deletions docs/rules/no-done-callback.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
When calling asynchronous code in hooks and tests, `jest` needs to know when the
asynchronous work is complete to progress the current run.

Originally the most common pattern to archive this was to use callbacks:
Originally the most common pattern to achieve this was to use callbacks:

```js
test('the data is peanut butter', done => {
Expand All @@ -20,11 +20,11 @@ test('the data is peanut butter', done => {
});
```

This can be very error prone however, as it requires careful understanding of
This can be very error-prone however, as it requires careful understanding of
how assertions work in tests or otherwise tests won't behave as expected.

For example, if the `try/catch` was left out of the above code, the test would
timeout rather than fail. Even with the `try/catch`, forgetting to pass the
time out rather than fail. Even with the `try/catch`, forgetting to pass the
caught error to `done` will result in `jest` believing the test has passed.

A more straightforward way to handle asynchronous code is to use Promises:
Expand Down
6 changes: 3 additions & 3 deletions docs/rules/no-standalone-expect.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ trigger this rule.

This rule aims to eliminate `expect` statements that will not be executed. An
`expect` inside of a `describe` block but outside of a `test` or `it` block or
outside of a `describe` will not execute and therefore will trigger this rule.
It is viable, however, to have an `expect` in a helper function that is called
from within a `test` or `it` block so `expect` statements in a function will not
outside a `describe` will not execute and therefore will trigger this rule. It
is viable, however, to have an `expect` in a helper function that is called from
within a `test` or `it` block so `expect` statements in a function will not
trigger this rule.

Statements like `expect.hasAssertions()` will NOT trigger this rule since these
Expand Down
3 changes: 1 addition & 2 deletions docs/rules/no-test-return-statement.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@ If you are returning Promises then you should update the test to use

## Rule details

This rule triggers a warning if you use a return statement inside of a test
body.
This rule triggers a warning if you use a return statement inside a test body.

```js
/*eslint jest/no-test-return-statement: "error"*/
Expand Down
53 changes: 53 additions & 0 deletions docs/rules/prefer-expect-resolves.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Prefer `await expect(...).resolves` over `expect(await ...)` syntax (`prefer-expect-resolves`)

When working with promises, there are two primary ways you can test the resolved
value:

1. use the `resolve` modifier on `expect`
(`await expect(...).resolves.<matcher>` style)
2. `await` the promise and assert against its result
(`expect(await ...).<matcher>` style)

While the second style is arguably less dependent on `jest`, if the promise
rejects it will be treated as a general error, resulting in less predictable
behaviour and output from `jest`.

Additionally, favoring the first style ensures consistency with its `rejects`
counterpart, as there is no way of "awaiting" a rejection.

## Rule details

This rule triggers a warning if an `await` is done within an `expect`, and
recommends using `resolves` instead.

Examples of **incorrect** code for this rule

```js
it('passes', async () => {
expect(await someValue()).toBe(true);
});

it('is true', async () => {
const myPromise = Promise.resolve(true);

expect(await myPromise).toBe(true);
});
```

Examples of **correct** code for this rule

```js
it('passes', async () => {
await expect(someValue()).resolves.toBe(true);
});

it('is true', async () => {
const myPromise = Promise.resolve(true);

await expect(myPromise).resolves.toBe(true);
});

it('errors', async () => {
await expect(Promise.rejects('oh noes!')).rejects.toThrow('oh noes!');
});
```
53 changes: 53 additions & 0 deletions docs/rules/prefer-to-be.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Suggest using `toBe()` for primitive literals (`prefer-to-be`)

When asserting against primitive literals such as numbers and strings, the
equality matchers all operate the same, but read slightly differently in code.

This rule recommends using the `toBe` matcher in these situations, as it forms
the most grammatically natural sentence. For `null`, `undefined`, and `NaN` this
rule recommends using their specific `toBe` matchers, as they give better error
messages as well.

## Rule details

This rule triggers a warning if `toEqual()` or `toStrictEqual()` are used to
assert a primitive literal value such as numbers, strings, and booleans.

The following patterns are considered warnings:

```js
expect(value).not.toEqual(5);
expect(getMessage()).toStrictEqual('hello world');
expect(loadMessage()).resolves.toEqual('hello world');
```

The following pattern is not warning:

```js
expect(value).not.toBe(5);
expect(getMessage()).toBe('hello world');
expect(loadMessage()).resolves.toBe('hello world');
expect(didError).not.toBe(true);

expect(catchError()).toStrictEqual({ message: 'oh noes!' });
```

For `null`, `undefined`, and `NaN`, this rule triggers a warning if `toBe` is
used to assert against those literal values instead of their more specific
`toBe` counterparts:

```js
expect(value).not.toBe(undefined);
expect(getMessage()).toBe(null);
expect(countMessages()).resolves.not.toBe(NaN);
```

The following pattern is not warning:

```js
expect(value).toBeDefined();
expect(getMessage()).toBeNull();
expect(countMessages()).resolves.not.toBeNaN();

expect(catchError()).toStrictEqual({ message: undefined });
```
28 changes: 28 additions & 0 deletions docs/rules/require-top-level-describe.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,34 @@ describe('test suite', () => {
});
```

You can also enforce a limit on the number of describes allowed at the top-level
using the `maxNumberOfTopLevelDescribes` option:

```json
{
"jest/require-top-level-describe": [
"error",
{
"maxNumberOfTopLevelDescribes": 2
}
]
}
```

Examples of **incorrect** code with the above config:

```js
describe('test suite', () => {
it('test', () => {});
});

describe('test suite', () => {});

describe('test suite', () => {});
```

This option defaults to `Infinity`, allowing any number of top-level describes.

## When Not To Use It

Don't use this rule on non-jest test files.
32 changes: 30 additions & 2 deletions docs/rules/valid-title.md
Original file line number Diff line number Diff line change
Expand Up @@ -198,8 +198,9 @@ describe('the proper way to handle things', () => {});
Defaults: `{}`

Allows enforcing that titles must match or must not match a given Regular
Expression. An object can be provided to apply different Regular Expressions to
specific Jest test function groups (`describe`, `test`, and `it`).
Expression, with an optional message. An object can be provided to apply
different Regular Expressions (with optional messages) to specific Jest test
function groups (`describe`, `test`, and `it`).

Examples of **incorrect** code when using `mustMatch`:

Expand All @@ -226,3 +227,30 @@ describe('the tests that will be run', () => {});
test('that the stuff works', () => {});
xtest('that errors that thrown have messages', () => {});
```

Optionally you can provide a custom message to show for a particular matcher by
using a tuple at any level where you can provide a matcher:

```js
const prefixes = ['when', 'with', 'without', 'if', 'unless', 'for'];
const prefixesList = prefixes.join(' - \n');

module.exports = {
rules: {
'jest/valid-title': [
'error',
{
mustNotMatch: ['\\.$', 'Titles should not end with a full-stop'],
mustMatch: {
describe: [
new RegExp(`^(?:[A-Z]|\\b(${prefixes.join('|')})\\b`, 'u').source,
`Describe titles should either start with a capital letter or one of the following prefixes: ${prefixesList}`,
],
test: [/[^A-Z]/u.source],
it: /[^A-Z]/u.source,
},
},
],
},
};
```
2 changes: 2 additions & 0 deletions src/__tests__/__snapshots__/rules.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,11 @@ Object {
"jest/no-test-return-statement": "error",
"jest/prefer-called-with": "error",
"jest/prefer-expect-assertions": "error",
"jest/prefer-expect-resolves": "error",
"jest/prefer-hooks-on-top": "error",
"jest/prefer-spy-on": "error",
"jest/prefer-strict-equal": "error",
"jest/prefer-to-be": "error",
"jest/prefer-to-be-null": "error",
"jest/prefer-to-be-undefined": "error",
"jest/prefer-to-contain": "error",
Expand Down
2 changes: 1 addition & 1 deletion src/__tests__/rules.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { existsSync } from 'fs';
import { resolve } from 'path';
import plugin from '../';

const numberOfRules = 42;
const numberOfRules = 44;
const ruleNames = Object.keys(plugin.rules);
const deprecatedRules = Object.entries(plugin.rules)
.filter(([, rule]) => rule.meta.deprecated)
Expand Down
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ const importDefault = (moduleName: string) =>
interopRequireDefault(require(moduleName)).default;

const rulesDir = join(__dirname, 'rules');
const excludedFiles = ['__tests__', 'utils'];
const excludedFiles = ['__tests__', 'detectJestVersion', 'utils'];

const rules = readdirSync(rulesDir)
.map(rule => parse(rule).name)
Expand Down
Loading

0 comments on commit 0fd748f

Please sign in to comment.