From 5996e9db3bd0f688b2834a62916f2ca1e94fb54d Mon Sep 17 00:00:00 2001 From: Jacob Hilker Date: Thu, 4 Apr 2019 15:16:38 -0400 Subject: [PATCH] jasmine-globals: support arbitrary jasmineSpy.and.* usage --- src/transformers/jasmine-globals.js | 88 ++++++++++ src/transformers/jasmine-globals.test.js | 196 ++++++++++++++++++++--- 2 files changed, 262 insertions(+), 22 deletions(-) diff --git a/src/transformers/jasmine-globals.js b/src/transformers/jasmine-globals.js index 3ad0c4c4..62037aac 100644 --- a/src/transformers/jasmine-globals.js +++ b/src/transformers/jasmine-globals.js @@ -13,6 +13,30 @@ export default function jasmineGlobals(fileInfo, api, options) { const logWarning = (msg, path) => logger(fileInfo, msg, path); + const spiesToCheckForChain = []; + + const setCheckForChainIfNeeded = path => { + if (path.parentPath) { + const parentType = path.parentPath.node.type; + + let varName; + if (parentType === 'VariableDeclarator') { + varName = path.parentPath.node.id.name; + } else if (parentType === 'AssignmentExpression') { + varName = path.parentPath.node.left.name; + } else { + return; + } + + const varScope = path.parentPath.scope.lookup(varName); + + spiesToCheckForChain.push({ + varName, + varScope, + }); + } + }; + root // find `jasmine.createSpy(*).and.*()` expressions .find(j.CallExpression, { @@ -72,6 +96,8 @@ export default function jasmineGlobals(fileInfo, api, options) { break; } } + + setCheckForChainIfNeeded(path); }); root @@ -96,6 +122,8 @@ export default function jasmineGlobals(fileInfo, api, options) { j.identifier('fn') ); path.node.arguments = []; + + setCheckForChainIfNeeded(path); }); root @@ -172,6 +200,8 @@ export default function jasmineGlobals(fileInfo, api, options) { break; } } + + setCheckForChainIfNeeded(path); }); root @@ -185,8 +215,66 @@ export default function jasmineGlobals(fileInfo, api, options) { j.identifier('jest'), j.identifier('spyOn') ); + + setCheckForChainIfNeeded(path); }); + spiesToCheckForChain.forEach(({ varName, varScope }) => { + // find `jasmineSpy.and.*` within the scope of where the variable was defined + j(varScope.path) + .find(j.CallExpression, { + callee: { + type: 'MemberExpression', + object: { + type: 'MemberExpression', + object: { + type: 'Identifier', + name: varName, + }, + property: { + type: 'Identifier', + name: 'and', + }, + }, + }, + }) + .forEach(path => { + const spyType = path.node.callee.property.name; + switch (spyType) { + // `jasmineSpy.and.callFake()` is equivalent of + // `jestSpy.mockImplementation(); + case 'callFake': { + path.node.callee.object = path.node.callee.object.object; + path.node.callee.property.name = 'mockImplementation'; + break; + } + // `jasmineSpy.and.returnValue()` is equivalent of + // `jestSpy.mockReturnValue()` + case 'returnValue': { + path.node.callee.object = path.node.callee.object.object; + path.node.callee.property.name = 'mockReturnValue'; + break; + } + case 'callThrough': { + // This is similar to `jestSpy.mockRestore()`, but we don't + // want to reset the calls or run it on spies created without `spyOn` + logWarning( + 'Unsupported Jasmine functionality "jasmineSpy.and.callThrough()' + ); + break; + } + default: { + logWarning( + `Unsupported Jasmine functionality "jasmineSpy.and.${ + spyType + }".`, + path + ); + break; + } + } + }); + }); root // find all `*.calls.count()` .find(j.CallExpression, { diff --git a/src/transformers/jasmine-globals.test.js b/src/transformers/jasmine-globals.test.js index c8c377d8..67b11dff 100644 --- a/src/transformers/jasmine-globals.test.js +++ b/src/transformers/jasmine-globals.test.js @@ -19,28 +19,6 @@ beforeEach(() => { console.warn = v => consoleWarnings.push(v); }); -testChanged( - 'spyOn', - ` - jest.spyOn().mockReturnValue(); - spyOn(stuff).and.callThrough(); - spyOn(stuff).and.callFake(() => 'lol'); - spyOn(stuff).and.returnValue('lmao'); - jest.spyOn(); - spyOn(stuff); - jest.spyOn().mockImplementation(); - `, - ` - jest.spyOn().mockReturnValue(); - jest.spyOn(stuff); - jest.spyOn(stuff).mockImplementation(() => 'lol'); - jest.spyOn(stuff).mockReturnValue('lmao'); - jest.spyOn(); - jest.spyOn(stuff).mockImplementation(() => {}); - jest.spyOn().mockImplementation(); - ` -); - testChanged( 'jasmine.createSpy', ` @@ -61,6 +39,82 @@ testChanged( ` ); +testChanged( + 'jasmine.createSpy with later .and usage', + ` + let bar; + + it('does a thing', () => { + const foo = jasmine.createSpy(); + bar = jasmine.createSpy(); + const baz = jasmine.createSpy().and.returnValue('baz'); + + foo.and.returnValue('lmao'); + foo.and.callFake(arg => arg); + + bar.and.returnValue('lmao'); + bar.and.callFake(arg => arg); + + baz.and.returnValue('lmao'); + baz.and.callFake(arg => arg); + }); + + bar.and.returnValue('lmao'); + bar.and.callFake(arg => arg); + + it('does a different thing', () => { + const foo = 'some value'; + bar = 'another value'; + + // unchanged + foo.and.returnValue('lmao'); + foo.and.callFake(arg => arg); + baz.and.returnValue('lmao'); + baz.and.callFake(arg => arg); + + // changed within scope of being a spy + bar.and.returnValue('lmao'); + bar.and.callFake(arg => arg); + }); + `, + ` + let bar; + + it('does a thing', () => { + const foo = jest.fn(); + bar = jest.fn(); + const baz = jest.fn(() => 'baz'); + + foo.mockReturnValue('lmao'); + foo.mockImplementation(arg => arg); + + bar.mockReturnValue('lmao'); + bar.mockImplementation(arg => arg); + + baz.mockReturnValue('lmao'); + baz.mockImplementation(arg => arg); + }); + + bar.mockReturnValue('lmao'); + bar.mockImplementation(arg => arg); + + it('does a different thing', () => { + const foo = 'some value'; + bar = 'another value'; + + // unchanged + foo.and.returnValue('lmao'); + foo.and.callFake(arg => arg); + baz.and.returnValue('lmao'); + baz.and.callFake(arg => arg); + + // changed within scope of being a spy + bar.mockReturnValue('lmao'); + bar.mockImplementation(arg => arg); + }); + ` +); + test('not supported jasmine.createSpy().and.*', () => { wrappedPlugin(` jasmine.createSpy().and.unknownUtil(); @@ -71,6 +125,104 @@ test('not supported jasmine.createSpy().and.*', () => { ]); }); +testChanged( + 'spyOn', + ` + jest.spyOn().mockReturnValue(); + spyOn(stuff).and.callThrough(); + spyOn(stuff).and.callFake(() => 'lol'); + spyOn(stuff).and.returnValue('lmao'); + jest.spyOn(); + spyOn(stuff); + jest.spyOn().mockImplementation(); + `, + ` + jest.spyOn().mockReturnValue(); + jest.spyOn(stuff); + jest.spyOn(stuff).mockImplementation(() => 'lol'); + jest.spyOn(stuff).mockReturnValue('lmao'); + jest.spyOn(); + jest.spyOn(stuff).mockImplementation(() => {}); + jest.spyOn().mockImplementation(); + ` +); + +testChanged( + 'spyOn() with later .and usage', + ` + let bar; + + it('does a thing', () => { + const foo = spyOn(stuff); + bar = spyOn(stuff); + const baz = spyOn(stuff).and.returnValue('baz'); + + foo.and.returnValue('lmao'); + foo.and.callFake(arg => arg); + + bar.and.returnValue('lmao'); + bar.and.callFake(arg => arg); + + baz.and.returnValue('lmao'); + baz.and.callFake(arg => arg); + }); + + bar.and.returnValue('lmao'); + bar.and.callFake(arg => arg); + + it('does a different thing', () => { + const foo = 'some value'; + bar = 'another value'; + + // unchanged + foo.and.returnValue('lmao'); + foo.and.callFake(arg => arg); + baz.and.returnValue('lmao'); + baz.and.callFake(arg => arg); + + // changed within scope of being a spy + bar.and.returnValue('lmao'); + bar.and.callFake(arg => arg); + }); + `, + ` + let bar; + + it('does a thing', () => { + const foo = jest.spyOn(stuff); + bar = jest.spyOn(stuff); + const baz = jest.spyOn(stuff).mockReturnValue('baz'); + + foo.mockReturnValue('lmao'); + foo.mockImplementation(arg => arg); + + bar.mockReturnValue('lmao'); + bar.mockImplementation(arg => arg); + + baz.mockReturnValue('lmao'); + baz.mockImplementation(arg => arg); + }); + + bar.mockReturnValue('lmao'); + bar.mockImplementation(arg => arg); + + it('does a different thing', () => { + const foo = 'some value'; + bar = 'another value'; + + // unchanged + foo.and.returnValue('lmao'); + foo.and.callFake(arg => arg); + baz.and.returnValue('lmao'); + baz.and.callFake(arg => arg); + + // changed within scope of being a spy + bar.mockReturnValue('lmao'); + bar.mockImplementation(arg => arg); + }); + ` +); + testChanged( '*.calls.count()', `