diff --git a/docs/rules/valid-mock-module-path.md b/docs/rules/valid-mock-module-path.md index 9f5f8bdad..a3220dc8c 100644 --- a/docs/rules/valid-mock-module-path.md +++ b/docs/rules/valid-mock-module-path.md @@ -24,6 +24,9 @@ jest.mock('../../this/module/does/not/exist'); // Local file that cannot be found jest.mock('../../this/path/does/not/exist.js'); + +// Local file that cannot be found and is NOT virtual +jest.mock('../../this/path/does/not/exist.js', undefined, { virtual: false }); ``` The following patterns are **not** considered errors: @@ -38,6 +41,13 @@ jest.mock('../../this/module/really/does/exist'); // Local file that cannot be found jest.mock('../../this/path/really/does/exist.js'); + +// Module(s) that cannot be found but are configured as virtual +jest.mock('@org/some-module-not-in-package-json', undefined, { virtual: true }); +jest.mock('some-module-not-in-package-json', undefined, { virtual: true }); + +// Local file that cannot be found but is configured as virtual +jest.mock('../../this/path/does/not/exist.js', undefined, { virtual: true }); ``` ## Options diff --git a/src/rules/__tests__/valid-mock-module-path.test.ts b/src/rules/__tests__/valid-mock-module-path.test.ts index 8151a9961..48e82d876 100644 --- a/src/rules/__tests__/valid-mock-module-path.test.ts +++ b/src/rules/__tests__/valid-mock-module-path.test.ts @@ -10,7 +10,7 @@ import { const ruleTester = new RuleTester({ parser: espreeParser, parserOptions: { - ecmaVersion: 2015, + ecmaVersion: 2018, }, }); @@ -62,6 +62,52 @@ ruleTester.run('valid-mock-module-path', rule, { code: 'jest.mock("./fixtures/module/bar")', options: [{ moduleFileExtensions: ['.css'] }], }, + { + filename: __filename, + code: 'jest.mock("./fixtures/module/tsx/foo", undefined, { virtual: false })', + }, + { + filename: __filename, + code: 'jest.doMock("./fixtures/module/tsx/foo", undefined, { virtual: false })', + }, + { + filename: __filename, + code: 'jest.doMock("./fixtures/module/tsx/foo", undefined, { ...{} })', + }, + { + filename: __filename, + code: 'jest.doMock("./fixtures/module/tsx/foo", undefined, { virtual: [] })', + }, + { + filename: __filename, + code: 'jest.mock("../module/does/not/exist", undefined, { virtual: true })', + }, + { + filename: __filename, + code: 'jest.doMock("../module/does/not/exist", undefined, { virtual: true })', + }, + { + filename: __filename, + code: 'jest.doMock("../module/does/not/exist", undefined, { "virtual": true })', + }, + { + filename: __filename, + code: 'jest.doMock("../module/does/not/exist", undefined, { ["virtual"]: true })', + }, + { + filename: __filename, + code: 'jest.doMock("../module/does/not/exist", undefined, { [`virtual`]: true })', + }, + { + // jest only cares if the value is truthy... + filename: __filename, + code: 'jest.doMock("../module/does/not/exist", undefined, { virtual: 1 })', + }, + { + // jest only cares if the value is truthy... + filename: __filename, + code: 'jest.doMock("../module/does/not/exist", undefined, { virtual: "yes" })', + }, ], invalid: [ { @@ -154,6 +200,127 @@ ruleTester.run('valid-mock-module-path', rule, { }, ], }, + { + filename: __filename, + code: "jest.mock('../module/does/not/exist', undefined, {})", + errors: [ + { + messageId: 'invalidMockModulePath', + data: { moduleName: "'../module/does/not/exist'" }, + column: 1, + line: 1, + }, + ], + }, + { + filename: __filename, + code: "jest.mock('../module/does/not/exist', undefined, { assumeExists: true })", + errors: [ + { + messageId: 'invalidMockModulePath', + data: { moduleName: "'../module/does/not/exist'" }, + column: 1, + line: 1, + }, + ], + }, + { + filename: __filename, + code: "jest.mock('../module/does/not/exist', undefined, { virtual: false })", + errors: [ + { + messageId: 'invalidMockModulePath', + data: { moduleName: "'../module/does/not/exist'" }, + column: 1, + line: 1, + }, + ], + }, + { + filename: __filename, + code: "jest.doMock('../module/does/not/exist', undefined, { virtual: false })", + errors: [ + { + messageId: 'invalidMockModulePath', + data: { moduleName: "'../module/does/not/exist'" }, + column: 1, + line: 1, + }, + ], + }, + { + filename: __filename, + code: "jest.doMock('../module/does/not/exist', undefined, { virtual: 0 })", + errors: [ + { + messageId: 'invalidMockModulePath', + data: { moduleName: "'../module/does/not/exist'" }, + column: 1, + line: 1, + }, + ], + }, + { + filename: __filename, + code: dedent` + const virtual = false; + + jest.doMock('../module/does/not/exist', undefined, { virtual }) + `, + errors: [ + { + messageId: 'invalidMockModulePath', + data: { moduleName: "'../module/does/not/exist'" }, + column: 1, + line: 3, + }, + ], + }, + { + filename: __filename, + code: dedent` + const virtual = true; + + jest.doMock('../module/does/not/exist', undefined, { virtual }) + `, + errors: [ + { + messageId: 'invalidMockModulePath', + data: { moduleName: "'../module/does/not/exist'" }, + column: 1, + line: 3, + }, + ], + }, + { + filename: __filename, + code: dedent` + const prop = 'virtual'; + + jest.doMock('../module/does/not/exist', undefined, { [prop]: true }) + `, + errors: [ + { + messageId: 'invalidMockModulePath', + data: { moduleName: "'../module/does/not/exist'" }, + column: 1, + line: 3, + }, + ], + }, + { + // we don't attempt to resolve the result of object spreads + filename: __filename, + code: "jest.doMock('../module/does/not/exist', undefined, { ...{ virtual: true } })", + errors: [ + { + messageId: 'invalidMockModulePath', + data: { moduleName: "'../module/does/not/exist'" }, + column: 1, + line: 1, + }, + ], + }, ], }); diff --git a/src/rules/valid-mock-module-path.ts b/src/rules/valid-mock-module-path.ts index 3954f4db2..0a59d9ab6 100644 --- a/src/rules/valid-mock-module-path.ts +++ b/src/rules/valid-mock-module-path.ts @@ -73,6 +73,28 @@ export default createRule< return; } + if (node.arguments[2]?.type === AST_NODE_TYPES.ObjectExpression) { + const hasTrueVirtualProperty = node.arguments[2].properties.some( + expression => { + if (expression.type === AST_NODE_TYPES.Property) { + const { key, value } = expression; + + return ( + isSupportedAccessor(key, 'virtual') && + value.type === AST_NODE_TYPES.Literal && + value.value + ); + } + + return false; + }, + ); + + if (hasTrueVirtualProperty) { + return; + } + } + try { if (!moduleName.value.startsWith('.')) { require.resolve(moduleName.value);