Skip to content

Commit

Permalink
feat: add no-confusing-set-time rule (#1425)
Browse files Browse the repository at this point in the history
* feat: add no-confusing-set-time rule

* fix: cr problems

* fix: ci

* feat: reactor rule

* chore: fix lint

* fix: ci problems

* fix: cr problems

* fix: ci

* docs: regenerate

---------

Co-authored-by: Simen Bekkhus <sbekkhus91@gmail.com>
Co-authored-by: Gareth Jones <jones258@gmail.com>
  • Loading branch information
3 people committed Sep 15, 2023
1 parent 1b96756 commit ff8e482
Show file tree
Hide file tree
Showing 6 changed files with 393 additions and 1 deletion.
1 change: 1 addition & 0 deletions README.md
Expand Up @@ -220,6 +220,7 @@ set to warn in.\
| [no-commented-out-tests](docs/rules/no-commented-out-tests.md) | Disallow commented out tests | || | | |
| [no-conditional-expect](docs/rules/no-conditional-expect.md) | Disallow calling `expect` conditionally || | | | |
| [no-conditional-in-test](docs/rules/no-conditional-in-test.md) | Disallow conditional logic in tests | | | | | |
| [no-confusing-set-timeout](docs/rules/no-confusing-set-timeout.md) | Disallow confusing usages of jest.setTimeout | | | | | |
| [no-deprecated-functions](docs/rules/no-deprecated-functions.md) | Disallow use of deprecated functions || | 🔧 | | |
| [no-disabled-tests](docs/rules/no-disabled-tests.md) | Disallow disabled tests | || | | |
| [no-done-callback](docs/rules/no-done-callback.md) | Disallow using a callback in asynchronous tests and hooks || | | 💡 | |
Expand Down
62 changes: 62 additions & 0 deletions docs/rules/no-confusing-set-timeout.md
@@ -0,0 +1,62 @@
# Disallow confusing usages of jest.setTimeout (`no-confusing-set-timeout`)

<!-- end auto-generated rule header -->

While `jest.setTimeout` can be called multiple times anywhere within a single
test file only the last call before any test functions run will have an effect.

## Rule details

this rule checks for several confusing usages of `jest.setTimeout` that looks
like it applies to specific tests within the same file, such as:

- being called anywhere other than in global scope
- being called multiple times
- being called after other Jest functions like hooks, `describe`, `test`, or
`it`

Examples of **incorrect** code for this rule:

```js
describe('test foo', () => {
jest.setTimeout(1000);
it('test-description', () => {
// test logic;
});
});

describe('test bar', () => {
it('test-description', () => {
jest.setTimeout(1000);
// test logic;
});
});

test('foo-bar', () => {
jest.setTimeout(1000);
});

describe('unit test', () => {
beforeEach(() => {
jest.setTimeout(1000);
});
});
```

Examples of **correct** code for this rule:

```js
jest.setTimeout(500);
test('test test', () => {
// do some stuff
});
```

```js
jest.setTimeout(1000);
describe('test bar bar', () => {
it('test-description', () => {
// test logic;
});
});
```
1 change: 1 addition & 0 deletions src/__tests__/__snapshots__/rules.test.ts.snap
Expand Up @@ -18,6 +18,7 @@ exports[`rules should export configs that refer to actual rules 1`] = `
"jest/no-commented-out-tests": "error",
"jest/no-conditional-expect": "error",
"jest/no-conditional-in-test": "error",
"jest/no-confusing-set-timeout": "error",
"jest/no-deprecated-functions": "error",
"jest/no-disabled-tests": "error",
"jest/no-done-callback": "error",
Expand Down
2 changes: 1 addition & 1 deletion src/__tests__/rules.test.ts
Expand Up @@ -2,7 +2,7 @@ import { existsSync } from 'fs';
import { resolve } from 'path';
import plugin from '../';

const numberOfRules = 52;
const numberOfRules = 53;
const ruleNames = Object.keys(plugin.rules);
const deprecatedRules = Object.entries(plugin.rules)
.filter(([, rule]) => rule.meta.deprecated)
Expand Down
258 changes: 258 additions & 0 deletions src/rules/__tests__/no-confusing-set-timeout.test.ts
@@ -0,0 +1,258 @@
import { TSESLint } from '@typescript-eslint/utils';
import dedent from 'dedent';
import rule from '../no-confusing-set-timeout';
import { espreeParser } from './test-utils';

const ruleTester = new TSESLint.RuleTester({
parser: espreeParser,
parserOptions: {
ecmaVersion: 2020,
},
});

ruleTester.run('no-confusing-set-timeout', rule, {
valid: [
dedent`
jest.setTimeout(1000);
describe('A', () => {
beforeEach(async () => { await new Promise(resolve => { setTimeout(resolve, 10000).unref(); });});
it('A.1', async () => { await new Promise(resolve => { setTimeout(resolve, 10000).unref(); });});
it('A.2', async () => { await new Promise(resolve => { setTimeout(resolve, 10000).unref(); });});
});
`,
dedent`
jest.setTimeout(1000);
window.setTimeout(6000)
describe('A', () => {
beforeEach(async () => { await new Promise(resolve => { setTimeout(resolve, 10000).unref(); });});
it('test foo', async () => { await new Promise(resolve => { setTimeout(resolve, 10000).unref(); });});
});
`,
{
code: dedent`
import { handler } from 'dep/mod';
jest.setTimeout(800);
describe('A', () => {
beforeEach(async () => { await new Promise(resolve => { setTimeout(resolve, 10000).unref(); });});
it('A.1', async () => { await new Promise(resolve => { setTimeout(resolve, 10000).unref(); });});
it('A.2', async () => { await new Promise(resolve => { setTimeout(resolve, 10000).unref(); });});
});
`,
parserOptions: { sourceType: 'module' },
},
dedent`
function handler() {}
jest.setTimeout(800);
describe('A', () => {
beforeEach(async () => { await new Promise(resolve => { setTimeout(resolve, 10000).unref(); });});
it('A.1', async () => { await new Promise(resolve => { setTimeout(resolve, 10000).unref(); });});
it('A.2', async () => { await new Promise(resolve => { setTimeout(resolve, 10000).unref(); });});
});
`,
dedent`
const { handler } = require('dep/mod');
jest.setTimeout(800);
describe('A', () => {
beforeEach(async () => { await new Promise(resolve => { setTimeout(resolve, 10000).unref(); });});
it('A.1', async () => { await new Promise(resolve => { setTimeout(resolve, 10000).unref(); });});
it('A.2', async () => { await new Promise(resolve => { setTimeout(resolve, 10000).unref(); });});
});
`,
dedent`
jest.setTimeout(1000);
window.setTimeout(60000);
`,
'window.setTimeout(60000);',
'setTimeout(1000);',
dedent`
jest.setTimeout(1000);
test('test case', () => {
setTimeout(() => {
Promise.resolv();
}, 5000);
});
`,
dedent`
test('test case', () => {
setTimeout(() => {
Promise.resolv();
}, 5000);
});
`,
],
invalid: [
{
code: dedent`
jest.setTimeout(1000);
setTimeout(1000);
window.setTimeout(1000);
describe('A', () => {
beforeEach(async () => { await new Promise(resolve => { setTimeout(resolve, 10000).unref(); });});
it('A.1', async () => { await new Promise(resolve => { setTimeout(resolve, 10000).unref(); });});
it('A.2', async () => { await new Promise(resolve => { setTimeout(resolve, 10000).unref(); });});
});
jest.setTimeout(800);
`,
errors: [
{
messageId: 'orderSetTimeout',
line: 9,
column: 1,
},
{
messageId: 'multipleSetTimeouts',
line: 9,
column: 1,
},
],
},
{
code: dedent`
describe('A', () => {
jest.setTimeout(800);
beforeEach(async () => { await new Promise(resolve => { setTimeout(resolve, 10000).unref(); });});
it('A.1', async () => { await new Promise(resolve => { setTimeout(resolve, 10000).unref(); });});
it('A.2', async () => { await new Promise(resolve => { setTimeout(resolve, 10000).unref(); });});
});
`,
errors: [
{
messageId: 'globalSetTimeout',
line: 2,
column: 3,
},
{
messageId: 'orderSetTimeout',
line: 2,
column: 3,
},
],
},
{
code: dedent`
describe('B', () => {
it('B.1', async () => {
await new Promise((resolve) => {
jest.setTimeout(1000);
setTimeout(resolve, 10000).unref();
});
});
it('B.2', async () => {
await new Promise((resolve) => { setTimeout(resolve, 10000).unref(); });
});
});
`,
errors: [
{
messageId: 'globalSetTimeout',
line: 4,
column: 7,
},
{
messageId: 'orderSetTimeout',
line: 4,
column: 7,
},
],
},
{
code: dedent`
test('test-suite', () => {
jest.setTimeout(1000);
});
`,
errors: [
{
messageId: 'globalSetTimeout',
line: 2,
column: 3,
},
{
messageId: 'orderSetTimeout',
line: 2,
column: 3,
},
],
},
{
code: dedent`
describe('A', () => {
beforeEach(async () => { await new Promise(resolve => { setTimeout(resolve, 10000).unref(); });});
it('A.1', async () => { await new Promise(resolve => { setTimeout(resolve, 10000).unref(); });});
it('A.2', async () => { await new Promise(resolve => { setTimeout(resolve, 10000).unref(); });});
});
jest.setTimeout(1000);
`,
errors: [
{
messageId: 'orderSetTimeout',
line: 6,
column: 1,
},
],
},
{
code: dedent`
import { jest } from '@jest/globals';
{
jest.setTimeout(800);
}
describe('A', () => {
beforeEach(async () => { await new Promise(resolve => { setTimeout(resolve, 10000).unref(); });});
it('A.1', async () => { await new Promise(resolve => { setTimeout(resolve, 10000).unref(); });});
it('A.2', async () => { await new Promise(resolve => { setTimeout(resolve, 10000).unref(); });});
});
`,
parserOptions: { sourceType: 'module' },
errors: [
{
messageId: 'globalSetTimeout',
line: 3,
column: 3,
},
],
},
{
code: dedent`
jest.setTimeout(800);
jest.setTimeout(900);
`,
errors: [
{
messageId: 'multipleSetTimeouts',
line: 2,
column: 1,
},
],
},
{
code: dedent`
expect(1 + 2).toEqual(3);
jest.setTimeout(800);
`,
errors: [
{
messageId: 'orderSetTimeout',
line: 2,
column: 1,
},
],
},
{
code: dedent`
import { jest as Jest } from '@jest/globals';
{
Jest.setTimeout(800);
}
`,
parserOptions: { sourceType: 'module' },
errors: [
{
messageId: 'globalSetTimeout',
line: 3,
column: 3,
},
],
},
],
});

0 comments on commit ff8e482

Please sign in to comment.