diff --git a/docs/rules/no-jasmine-globals.md b/docs/rules/no-jasmine-globals.md new file mode 100644 index 000000000..c8057f6a1 --- /dev/null +++ b/docs/rules/no-jasmine-globals.md @@ -0,0 +1,59 @@ +# Disallow Jasmine globals + +`jest` uses `jasmine` as a test runner. A side effect of this is that both a +`jasmine` object, and some jasmine-specific globals, are exposed to the test +environment. Most functionality offered by Jasmine has been ported to Jest, and +the Jasmine globals will stop working in the future. Developers should therefor +migrate to Jest's documented API instead of relying on the undocumented Jasmine +API. + +### Rule details + +This rule reports on any usage of Jasmine globals which is not ported to Jest, +and suggests alternative from Jest's own API. + +### Default configuration + +The following patterns are considered warnings: + +```js +jasmine.DEFAULT_TIMEOUT_INTERVAL = 5000; + +test('my test', () => { + pending(); +}); + +test('my test', () => { + fail(); +}); + +test('my test', () => { + spyOn(some, 'object'); +}); + +test('my test', () => { + jasmine.createSpy(); +}); + +test('my test', () => { + expect('foo').toEqual(jasmine.anything()); +}); +``` + +The following patterns would not be considered warnings: + +```js +jest.setTimeout(5000); + +test('my test', () => { + jest.spyOn(some, 'object'); +}); + +test('my test', () => { + jest.fn(); +}); + +test('my test', () => { + expect('foo').toEqual(expect.anything()); +}); +``` diff --git a/index.js b/index.js index 8dc0aaf23..3e7da34d3 100644 --- a/index.js +++ b/index.js @@ -6,6 +6,7 @@ const noDisabledTests = require('./rules/no-disabled-tests'); const noFocusedTests = require('./rules/no-focused-tests'); const noHooks = require('./rules/no-hooks'); const noIdenticalTitle = require('./rules/no-identical-title'); +const noJasmineGlobals = require('./rules/no-jasmine-globals'); const noJestImport = require('./rules/no-jest-import'); const noLargeSnapshots = require('./rules/no-large-snapshots'); const noTestPrefixes = require('./rules/no-test-prefixes'); @@ -69,6 +70,7 @@ module.exports = { 'no-focused-tests': noFocusedTests, 'no-hooks': noHooks, 'no-identical-title': noIdenticalTitle, + 'no-jasmine-globals': noJasmineGlobals, 'no-jest-import': noJestImport, 'no-large-snapshots': noLargeSnapshots, 'no-test-prefixes': noTestPrefixes, diff --git a/rules/__tests__/no-jasmine-globals.test.js b/rules/__tests__/no-jasmine-globals.test.js new file mode 100644 index 000000000..6dd66c9a4 --- /dev/null +++ b/rules/__tests__/no-jasmine-globals.test.js @@ -0,0 +1,224 @@ +'use strict'; + +const RuleTester = require('eslint').RuleTester; +const rule = require('../no-jasmine-globals'); + +const ruleTester = new RuleTester(); + +ruleTester.run('no-jasmine-globals', rule, { + valid: [ + 'jest.spyOn()', + 'jest.fn()', + 'expect.extend()', + 'expect.any()', + 'it("foo", function () {})', + 'test("foo", function () {})', + 'foo()', + ], + invalid: [ + { + code: 'spyOn(some, "object")', + errors: [ + { + message: 'Illegal usage of global `spyOn`, prefer `jest.spyOn`', + column: 1, + line: 1, + }, + ], + }, + { + code: 'spyOnProperty(some, "object")', + errors: [ + { + message: + 'Illegal usage of global `spyOnProperty`, prefer `jest.spyOn`', + column: 1, + line: 1, + }, + ], + }, + { + code: 'fail()', + errors: [ + { + message: + 'Illegal usage of `fail`, prefer throwing an error, or the `done.fail` callback', + column: 1, + line: 1, + }, + ], + }, + { + code: 'pending()', + errors: [ + { + message: + 'Illegal usage of `pending`, prefer explicitly skipping a test using `test.skip`', + column: 1, + line: 1, + }, + ], + }, + { + code: 'jasmine.DEFAULT_TIMEOUT_INTERVAL = 5000;', + errors: [ + { + message: 'Illegal usage of jasmine global', + column: 1, + line: 1, + }, + ], + output: 'jest.setTimeout(5000);', + }, + { + code: 'jasmine.addMatchers(matchers)', + errors: [ + { + message: + 'Illegal usage of `jasmine.addMatchers`, prefer `expect.extend`', + column: 1, + line: 1, + }, + ], + }, + { + code: 'jasmine.createSpy()', + errors: [ + { + message: 'Illegal usage of `jasmine.createSpy`, prefer `jest.fn`', + column: 1, + line: 1, + }, + ], + }, + { + code: 'jasmine.any()', + errors: [ + { + message: 'Illegal usage of `jasmine.any`, prefer `expect.any`', + column: 1, + line: 1, + }, + ], + output: 'expect.any()', + }, + { + code: 'jasmine.anything()', + errors: [ + { + message: + 'Illegal usage of `jasmine.anything`, prefer `expect.anything`', + column: 1, + line: 1, + }, + ], + output: 'expect.anything()', + }, + { + code: 'jasmine.arrayContaining()', + errors: [ + { + message: + 'Illegal usage of `jasmine.arrayContaining`, prefer `expect.arrayContaining`', + column: 1, + line: 1, + }, + ], + output: 'expect.arrayContaining()', + }, + { + code: 'jasmine.objectContaining()', + errors: [ + { + message: + 'Illegal usage of `jasmine.objectContaining`, prefer `expect.objectContaining`', + column: 1, + line: 1, + }, + ], + output: 'expect.objectContaining()', + }, + { + code: 'jasmine.stringMatching()', + errors: [ + { + message: + 'Illegal usage of `jasmine.stringMatching`, prefer `expect.stringMatching`', + column: 1, + line: 1, + }, + ], + output: 'expect.stringMatching()', + }, + { + code: 'jasmine.getEnv()', + errors: [ + { + message: 'Illegal usage of jasmine global', + column: 1, + line: 1, + }, + ], + }, + { + code: 'jasmine.empty()', + errors: [ + { + message: 'Illegal usage of jasmine global', + column: 1, + line: 1, + }, + ], + }, + { + code: 'jasmine.falsy()', + errors: [ + { + message: 'Illegal usage of jasmine global', + column: 1, + line: 1, + }, + ], + }, + { + code: 'jasmine.truthy()', + errors: [ + { + message: 'Illegal usage of jasmine global', + column: 1, + line: 1, + }, + ], + }, + { + code: 'jasmine.arrayWithExactContents()', + errors: [ + { + message: 'Illegal usage of jasmine global', + column: 1, + line: 1, + }, + ], + }, + { + code: 'jasmine.clock()', + errors: [ + { + message: 'Illegal usage of jasmine global', + column: 1, + line: 1, + }, + ], + }, + { + code: 'jasmine.MAX_PRETTY_PRINT_ARRAY_LENGTH = 42', + errors: [ + { + message: 'Illegal usage of jasmine global', + column: 1, + line: 1, + }, + ], + }, + ], +}); diff --git a/rules/no-jasmine-globals.js b/rules/no-jasmine-globals.js new file mode 100644 index 000000000..769a84775 --- /dev/null +++ b/rules/no-jasmine-globals.js @@ -0,0 +1,114 @@ +'use strict'; + +const getDocsUrl = require('./util').getDocsUrl; +const getNodeName = require('./util').getNodeName; + +module.exports = { + meta: { + docs: { + url: getDocsUrl(__filename), + }, + fixable: 'code', + }, + create(context) { + return { + CallExpression(node) { + const calleeName = getNodeName(node.callee); + + if (calleeName === 'spyOn' || calleeName === 'spyOnProperty') { + context.report({ + node, + message: `Illegal usage of global \`${calleeName}\`, prefer \`jest.spyOn\``, + }); + return; + } + + if (calleeName === 'fail') { + context.report({ + node, + message: + 'Illegal usage of `fail`, prefer throwing an error, or the `done.fail` callback', + }); + return; + } + + if (calleeName === 'pending') { + context.report({ + node, + message: + 'Illegal usage of `pending`, prefer explicitly skipping a test using `test.skip`', + }); + return; + } + + if (calleeName.startsWith('jasmine.')) { + const functionName = calleeName.replace('jasmine.', ''); + + if ( + functionName === 'any' || + functionName === 'anything' || + functionName === 'arrayContaining' || + functionName === 'objectContaining' || + functionName === 'stringMatching' + ) { + context.report({ + fix(fixer) { + return [fixer.replaceText(node.callee.object, 'expect')]; + }, + node, + message: `Illegal usage of \`${calleeName}\`, prefer \`expect.${functionName}\``, + }); + return; + } + + if (functionName === 'addMatchers') { + context.report({ + node, + message: `Illegal usage of \`${calleeName}\`, prefer \`expect.extend\``, + }); + return; + } + + if (functionName === 'createSpy') { + context.report({ + node, + message: `Illegal usage of \`${calleeName}\`, prefer \`jest.fn\``, + }); + return; + } + + context.report({ + node, + message: 'Illegal usage of jasmine global', + }); + } + }, + MemberExpression(node) { + if (node.object.name === 'jasmine') { + if (node.parent.type === 'AssignmentExpression') { + if (node.property.name === 'DEFAULT_TIMEOUT_INTERVAL') { + context.report({ + fix(fixer) { + return [ + fixer.replaceText( + node.parent, + `jest.setTimeout(${node.parent.right.value})` + ), + ]; + }, + node, + message: 'Illegal usage of jasmine global', + }); + return; + } + + context.report({ + node, + message: 'Illegal usage of jasmine global', + }); + } + } + }, + }; + }, +};