Skip to content

Commit a1916d1

Browse files
XunnamiusG-Rath
andauthored
fix(valid-mock-module-path): don't report virtual mocks (#1946)
* fix(src): do not consider virtual mocks as invalid * test(src): add tests * docs(rules): update documentation * test(src): match existing test exhaustiveness (missing object spread test) * refactor(src): remove istanbul ignore comments and use util function * chore(src): remove unnecessarily duplicated test * test(valid-mock-module-path): add cases for spread * test(valid-mock-module-path): add a bunch of mostly-silly cases --------- Co-authored-by: Xunnamius (Romulus) <Xunnamius@users.noreply.github.com> Co-authored-by: Gareth Jones <3151613+G-Rath@users.noreply.github.com>
1 parent 4a52787 commit a1916d1

3 files changed

Lines changed: 200 additions & 1 deletion

File tree

docs/rules/valid-mock-module-path.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ jest.mock('../../this/module/does/not/exist');
2424

2525
// Local file that cannot be found
2626
jest.mock('../../this/path/does/not/exist.js');
27+
28+
// Local file that cannot be found and is NOT virtual
29+
jest.mock('../../this/path/does/not/exist.js', undefined, { virtual: false });
2730
```
2831

2932
The following patterns are **not** considered errors:
@@ -38,6 +41,13 @@ jest.mock('../../this/module/really/does/exist');
3841

3942
// Local file that cannot be found
4043
jest.mock('../../this/path/really/does/exist.js');
44+
45+
// Module(s) that cannot be found but are configured as virtual
46+
jest.mock('@org/some-module-not-in-package-json', undefined, { virtual: true });
47+
jest.mock('some-module-not-in-package-json', undefined, { virtual: true });
48+
49+
// Local file that cannot be found but is configured as virtual
50+
jest.mock('../../this/path/does/not/exist.js', undefined, { virtual: true });
4151
```
4252

4353
## Options

src/rules/__tests__/valid-mock-module-path.test.ts

Lines changed: 168 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {
1010
const ruleTester = new RuleTester({
1111
parser: espreeParser,
1212
parserOptions: {
13-
ecmaVersion: 2015,
13+
ecmaVersion: 2018,
1414
},
1515
});
1616

@@ -62,6 +62,52 @@ ruleTester.run('valid-mock-module-path', rule, {
6262
code: 'jest.mock("./fixtures/module/bar")',
6363
options: [{ moduleFileExtensions: ['.css'] }],
6464
},
65+
{
66+
filename: __filename,
67+
code: 'jest.mock("./fixtures/module/tsx/foo", undefined, { virtual: false })',
68+
},
69+
{
70+
filename: __filename,
71+
code: 'jest.doMock("./fixtures/module/tsx/foo", undefined, { virtual: false })',
72+
},
73+
{
74+
filename: __filename,
75+
code: 'jest.doMock("./fixtures/module/tsx/foo", undefined, { ...{} })',
76+
},
77+
{
78+
filename: __filename,
79+
code: 'jest.doMock("./fixtures/module/tsx/foo", undefined, { virtual: [] })',
80+
},
81+
{
82+
filename: __filename,
83+
code: 'jest.mock("../module/does/not/exist", undefined, { virtual: true })',
84+
},
85+
{
86+
filename: __filename,
87+
code: 'jest.doMock("../module/does/not/exist", undefined, { virtual: true })',
88+
},
89+
{
90+
filename: __filename,
91+
code: 'jest.doMock("../module/does/not/exist", undefined, { "virtual": true })',
92+
},
93+
{
94+
filename: __filename,
95+
code: 'jest.doMock("../module/does/not/exist", undefined, { ["virtual"]: true })',
96+
},
97+
{
98+
filename: __filename,
99+
code: 'jest.doMock("../module/does/not/exist", undefined, { [`virtual`]: true })',
100+
},
101+
{
102+
// jest only cares if the value is truthy...
103+
filename: __filename,
104+
code: 'jest.doMock("../module/does/not/exist", undefined, { virtual: 1 })',
105+
},
106+
{
107+
// jest only cares if the value is truthy...
108+
filename: __filename,
109+
code: 'jest.doMock("../module/does/not/exist", undefined, { virtual: "yes" })',
110+
},
65111
],
66112
invalid: [
67113
{
@@ -154,6 +200,127 @@ ruleTester.run('valid-mock-module-path', rule, {
154200
},
155201
],
156202
},
203+
{
204+
filename: __filename,
205+
code: "jest.mock('../module/does/not/exist', undefined, {})",
206+
errors: [
207+
{
208+
messageId: 'invalidMockModulePath',
209+
data: { moduleName: "'../module/does/not/exist'" },
210+
column: 1,
211+
line: 1,
212+
},
213+
],
214+
},
215+
{
216+
filename: __filename,
217+
code: "jest.mock('../module/does/not/exist', undefined, { assumeExists: true })",
218+
errors: [
219+
{
220+
messageId: 'invalidMockModulePath',
221+
data: { moduleName: "'../module/does/not/exist'" },
222+
column: 1,
223+
line: 1,
224+
},
225+
],
226+
},
227+
{
228+
filename: __filename,
229+
code: "jest.mock('../module/does/not/exist', undefined, { virtual: false })",
230+
errors: [
231+
{
232+
messageId: 'invalidMockModulePath',
233+
data: { moduleName: "'../module/does/not/exist'" },
234+
column: 1,
235+
line: 1,
236+
},
237+
],
238+
},
239+
{
240+
filename: __filename,
241+
code: "jest.doMock('../module/does/not/exist', undefined, { virtual: false })",
242+
errors: [
243+
{
244+
messageId: 'invalidMockModulePath',
245+
data: { moduleName: "'../module/does/not/exist'" },
246+
column: 1,
247+
line: 1,
248+
},
249+
],
250+
},
251+
{
252+
filename: __filename,
253+
code: "jest.doMock('../module/does/not/exist', undefined, { virtual: 0 })",
254+
errors: [
255+
{
256+
messageId: 'invalidMockModulePath',
257+
data: { moduleName: "'../module/does/not/exist'" },
258+
column: 1,
259+
line: 1,
260+
},
261+
],
262+
},
263+
{
264+
filename: __filename,
265+
code: dedent`
266+
const virtual = false;
267+
268+
jest.doMock('../module/does/not/exist', undefined, { virtual })
269+
`,
270+
errors: [
271+
{
272+
messageId: 'invalidMockModulePath',
273+
data: { moduleName: "'../module/does/not/exist'" },
274+
column: 1,
275+
line: 3,
276+
},
277+
],
278+
},
279+
{
280+
filename: __filename,
281+
code: dedent`
282+
const virtual = true;
283+
284+
jest.doMock('../module/does/not/exist', undefined, { virtual })
285+
`,
286+
errors: [
287+
{
288+
messageId: 'invalidMockModulePath',
289+
data: { moduleName: "'../module/does/not/exist'" },
290+
column: 1,
291+
line: 3,
292+
},
293+
],
294+
},
295+
{
296+
filename: __filename,
297+
code: dedent`
298+
const prop = 'virtual';
299+
300+
jest.doMock('../module/does/not/exist', undefined, { [prop]: true })
301+
`,
302+
errors: [
303+
{
304+
messageId: 'invalidMockModulePath',
305+
data: { moduleName: "'../module/does/not/exist'" },
306+
column: 1,
307+
line: 3,
308+
},
309+
],
310+
},
311+
{
312+
// we don't attempt to resolve the result of object spreads
313+
filename: __filename,
314+
code: "jest.doMock('../module/does/not/exist', undefined, { ...{ virtual: true } })",
315+
errors: [
316+
{
317+
messageId: 'invalidMockModulePath',
318+
data: { moduleName: "'../module/does/not/exist'" },
319+
column: 1,
320+
line: 1,
321+
},
322+
],
323+
},
157324
],
158325
});
159326

src/rules/valid-mock-module-path.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,28 @@ export default createRule<
7373
return;
7474
}
7575

76+
if (node.arguments[2]?.type === AST_NODE_TYPES.ObjectExpression) {
77+
const hasTrueVirtualProperty = node.arguments[2].properties.some(
78+
expression => {
79+
if (expression.type === AST_NODE_TYPES.Property) {
80+
const { key, value } = expression;
81+
82+
return (
83+
isSupportedAccessor(key, 'virtual') &&
84+
value.type === AST_NODE_TYPES.Literal &&
85+
value.value
86+
);
87+
}
88+
89+
return false;
90+
},
91+
);
92+
93+
if (hasTrueVirtualProperty) {
94+
return;
95+
}
96+
}
97+
7698
try {
7799
if (!moduleName.value.startsWith('.')) {
78100
require.resolve(moduleName.value);

0 commit comments

Comments
 (0)