Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,7 @@ Manually fixable by
| [valid-describe-callback](docs/rules/valid-describe-callback.md) | Enforce valid `describe()` callback | ✅ | | | |
| [valid-expect](docs/rules/valid-expect.md) | Enforce valid `expect()` usage | ✅ | | 🔧 | |
| [valid-expect-in-promise](docs/rules/valid-expect-in-promise.md) | Require promises that have expectations in their chain to be valid | ✅ | | | |
| [valid-mock-module-path](docs/rules/valid-mock-module-path.md) | Disallow mocking of non-existing module paths | | | | |
| [valid-title](docs/rules/valid-title.md) | Enforce valid titles | ✅ | | 🔧 | |

### Requires Type Checking
Expand Down
70 changes: 70 additions & 0 deletions docs/rules/valid-mock-module-path.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# Disallow mocking of non-existing module paths (`valid-mock-module-path`)

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

This rule raises an error when using `jest.mock` and `jest.doMock` and the first
argument for mocked object (module/local file) do not exist.

## Rule details

This rule checks existence of the supplied path for `jest.mock` or `jest.doMock`
in the first argument.

The following patterns are considered errors:

```js
// Module(s) that cannot be found
jest.mock('@org/some-module-not-in-package-json');
jest.mock('some-module-not-in-package-json');

// Local module (directory) that cannot be found
jest.mock('../../this/module/does/not/exist');

// Local file that cannot be found
jest.mock('../../this/path/does/not/exist.js');
```

The following patterns are **not** considered errors:

```js
// Module(s) that can be found
jest.mock('@org/some-module-in-package-json');
jest.mock('some-module-in-package-json');

// Local module that cannot be found
jest.mock('../../this/module/really/does/exist');

// Local file that cannot be found
jest.mock('../../this/path/really/does/exist.js');
```

## Options

```json
{
"jest/valid-mock-module-path": [
"error",
{
"moduleFileExtensions": [".js", ".ts", ".jsx", ".tsx", ".json"]
}
]
}
```

### `moduleFileExtensions`

This array option controls which file extensions the plugin checks for
existence. The default extensions are:

- `".js"`
- `".ts"`
- `".jsx"`
- `".tsx"`
- `".json"`

For any custom extension, a preceding dot **must** be present before the file
extension for desired effect.

## When Not To Use It

Don't use this rule on non-jest test files.
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 @@ -72,6 +72,7 @@ exports[`rules should export configs that refer to actual rules 1`] = `
"jest/valid-describe-callback": "error",
"jest/valid-expect": "error",
"jest/valid-expect-in-promise": "error",
"jest/valid-mock-module-path": "error",
"jest/valid-title": "error",
},
},
Expand Down Expand Up @@ -164,6 +165,7 @@ exports[`rules should export configs that refer to actual rules 1`] = `
"jest/valid-describe-callback": "error",
"jest/valid-expect": "error",
"jest/valid-expect-in-promise": "error",
"jest/valid-mock-module-path": "error",
"jest/valid-title": "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 = 63;
const numberOfRules = 64;
const ruleNames = Object.keys(plugin.rules);
const deprecatedRules = Object.entries(plugin.rules)
.filter(([, rule]) => rule.meta.deprecated)
Expand Down
6 changes: 6 additions & 0 deletions src/rules/__tests__/fixtures/module/bar.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.bar-container {
background-color: #f5f5f5;
padding: 16px;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
1 change: 1 addition & 0 deletions src/rules/__tests__/fixtures/module/bar.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
1 change: 1 addition & 0 deletions src/rules/__tests__/fixtures/module/foo.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const foo = 'foo_js';
1 change: 1 addition & 0 deletions src/rules/__tests__/fixtures/module/foo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const foo = 'foo_ts';
1 change: 1 addition & 0 deletions src/rules/__tests__/fixtures/module/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './foo';
Empty file.
Empty file.
125 changes: 125 additions & 0 deletions src/rules/__tests__/valid-mock-module-path.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import dedent from 'dedent';
import rule from '../valid-mock-module-path';
import { FlatCompatRuleTester as RuleTester, espreeParser } from './test-utils';

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

ruleTester.run('valid-mock-module-path', rule, {
valid: [
{ filename: __filename, code: 'jest.mock("./fixtures/module")' },
{ filename: __filename, code: 'jest.mock("./fixtures/module", () => {})' },
{ filename: __filename, code: 'jest.mock()' },
{
filename: __filename,
code: 'jest.doMock("./fixtures/module", () => {})',
},
{
filename: __filename,
code: dedent`
describe("foo", () => {});
`,
},
{ filename: __filename, code: 'jest.doMock("./fixtures/module")' },
{ filename: __filename, code: 'jest.mock("./fixtures/module/foo.ts")' },
{ filename: __filename, code: 'jest.doMock("./fixtures/module/foo.ts")' },
{ filename: __filename, code: 'jest.mock("./fixtures/module/foo.js")' },
{ filename: __filename, code: 'jest.doMock("./fixtures/module/foo.js")' },
'jest.mock("eslint")',
'jest.doMock("eslint")',
'jest.mock("child_process")',
'jest.mock(() => {})',
{
filename: __filename,
code: dedent`
const a = "../module/does/not/exist";
jest.mock(a);
`,
},
{ filename: __filename, code: 'jest.mock("./fixtures/module/jsx/foo")' },
{ filename: __filename, code: 'jest.mock("./fixtures/module/tsx/foo")' },
{
filename: __filename,
code: 'jest.mock("./fixtures/module/tsx/foo")',
options: [{ moduleFileExtensions: ['.jsx'] }],
},
{
filename: __filename,
code: 'jest.mock("./fixtures/module/bar")',
options: [{ moduleFileExtensions: ['.json'] }],
},
{
filename: __filename,
code: 'jest.mock("./fixtures/module/bar")',
options: [{ moduleFileExtensions: ['.css'] }],
},
],
invalid: [
{
filename: __filename,
code: "jest.mock('../module/does/not/exist')",
errors: [
{
messageId: 'invalidMockModulePath',
data: { moduleName: "'../module/does/not/exist'" },
column: 1,
line: 1,
},
],
},
{
filename: __filename,
code: 'jest.mock("../file/does/not/exist.ts")',
errors: [
{
messageId: 'invalidMockModulePath',
data: { moduleName: '"../file/does/not/exist.ts"' },
column: 1,
line: 1,
},
],
},
{
filename: __filename,
code: 'jest.mock("./fixtures/module/foo.jsx")',
options: [{ moduleFileExtensions: ['.tsx'] }],
errors: [
{
messageId: 'invalidMockModulePath',
data: { moduleName: '"./fixtures/module/foo.jsx"' },
column: 1,
line: 1,
},
],
},
{
filename: __filename,
code: 'jest.mock("./fixtures/module/foo.jsx")',
options: [{ moduleFileExtensions: undefined }],
errors: [
{
messageId: 'invalidMockModulePath',
data: { moduleName: '"./fixtures/module/foo.jsx"' },
column: 1,
line: 1,
},
],
},
{
filename: __filename,
code: 'jest.mock("@doesnotexist/module")',
errors: [
{
messageId: 'invalidMockModulePath',
data: { moduleName: '"@doesnotexist/module"' },
column: 1,
line: 1,
},
],
},
],
});
11 changes: 1 addition & 10 deletions src/rules/no-untyped-mock-factory.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,13 @@
import { AST_NODE_TYPES, type TSESTree } from '@typescript-eslint/utils';
import {
createRule,
findModuleName,
getAccessorValue,
isFunction,
isSupportedAccessor,
isTypeOfJestFnCall,
} from './utils';

const findModuleName = (
node: TSESTree.Literal | TSESTree.Node,
): TSESTree.StringLiteral | null => {
if (node.type === AST_NODE_TYPES.Literal && typeof node.value === 'string') {
return node;
}

return null;
};

export default createRule({
name: __filename,
meta: {
Expand Down
10 changes: 10 additions & 0 deletions src/rules/utils/misc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -225,3 +225,13 @@ export const getFirstMatcherArg = (

return followTypeAssertionChain(firstArg);
};

export const findModuleName = (
node: TSESTree.Literal | TSESTree.Node,
): TSESTree.StringLiteral | null => {
if (node.type === AST_NODE_TYPES.Literal && typeof node.value === 'string') {
return node;
}

return null;
};
Loading
Loading