From 1f69a6b3ddf5e4aee25fa043e76ef74ff29755e8 Mon Sep 17 00:00:00 2001 From: Dan Beam <251287+danbeam@users.noreply.github.com> Date: Mon, 22 Aug 2022 16:07:40 -0700 Subject: [PATCH 1/3] [sinon] implement on{,First,Second,Third}Call([...]) --- src/transformers/sinon.test.ts | 72 ++++++++++++++++++++++++++ src/transformers/sinon.ts | 92 ++++++++++++++++++++++++++++++++++ 2 files changed, 164 insertions(+) diff --git a/src/transformers/sinon.test.ts b/src/transformers/sinon.test.ts index 5fddb526..717ddd9e 100644 --- a/src/transformers/sinon.test.ts +++ b/src/transformers/sinon.test.ts @@ -356,6 +356,78 @@ describe('spies and stubs', () => { { parser: 'ts' } ) }) + + it('handles on*Call', () => { + expectTransformation( + ` + import sinon from 'sinon-sandbox' + + stub.onFirstCall().returns(5) + stub.onSecondCall().returns(6, 7) + + stub.onFirstCall().returnsArg(0) + stub.onSecondCall().returnsArg(1) + + // invalid onCall() params + stub.onCall().returns('invalid') +`, + ` + stub.mockImplementation(() => { + if (stub.mock.calls.length === 0) + return 5; + }) + stub.mockImplementation(() => { + if (stub.mock.calls.length === 1) + return 6; + }) + + stub.mockImplementation((...args) => { + if (stub.mock.calls.length === 0) + return args[0]; + }) + stub.mockImplementation((...args) => { + if (stub.mock.calls.length === 1) + return args[1]; + }) + + // invalid onCall() params + stub.onCall().mockReturnValue('invalid') +` + ) + }) + it('handles on*Call (parser: ts)', () => { + expectTransformation( + ` + import sinon from 'sinon-sandbox' + + stub.onThirdCall().returns([8, 9, 10]) + stub.onCall(3).returns(biscuits) + + stub.onThirdCall().returnsArg(2) + stub.onCall(3).returnsArg(biscuits) +`, + ` + stub.mockImplementation(() => { + if (stub.mock.calls.length === 2) + return [8, 9, 10]; + }) + stub.mockImplementation(() => { + if (stub.mock.calls.length === 2) + return biscuits; + }) + + stub.mockImplementation((...args: any[]) => { + if (stub.mock.calls.length === 2) + return args[2]; + }) + stub.mockImplementation((...args: any[]) => { + if (stub.mock.calls.length === 2) + return args[biscuits]; + }) +`, + { parser: 'tsx' } + ) + }) }) describe('mocks', () => { diff --git a/src/transformers/sinon.ts b/src/transformers/sinon.ts index fccb71e6..b1fd2905 100644 --- a/src/transformers/sinon.ts +++ b/src/transformers/sinon.ts @@ -54,6 +54,7 @@ const SINON_MATCHERS_WITH_ARGS = { string: 'string', } const SINON_NTH_CALLS = new Set(['firstCall', 'secondCall', 'thirdCall', 'lastCall']) +const SINON_ON_NTH_CALLS = new Set(['onFirstCall', 'onSecondCall', 'onThirdCall']) const EXPECT_PREFIXES = new Set(['to']) const isPrefix = (name) => EXPECT_PREFIXES.has(name) @@ -370,6 +371,96 @@ function transformStub(j, ast, sinonExpression, logWarning) { }) } +/* + transform .onCall(0), .on{First,Second,Third}Call() + + stub.onCall(4).return(biscuits) -> stub.mockImplementation(() => { if (stub.mock.calls.length === 3) return biscuits; }) + stub.onFirstCall().returnArg(2) -> stub.mockImplementation((...args: any[]) => { if (stub.mock.calls.length === 0) return args[2]; }) +*/ +function transformStubOnCalls(j, ast, parser) { + ast + .find(j.CallExpression, { + callee: { + object: { + callee: { + property: { + name: (n) => n === 'onCall' || SINON_ON_NTH_CALLS.has(n), + }, + }, + }, + property: { + name: (n) => ['returns', 'returnsArg'].includes(n), + }, + }, + }) + .replaceWith(({ node }) => { + let index + switch (node.callee.object.callee.property.name) { + case 'onCall': { + const onCallArgs = node.callee.object.arguments + if (onCallArgs.length > 0) { + index = isFinite(onCallArgs[0].value) + ? j.numericLiteral(onCallArgs[0].value - 1) + : onCallArgs[0] + } + break + } + case 'onFirstCall': + index = j.numericLiteral(0) + break + case 'onSecondCall': + index = j.numericLiteral(1) + break + case 'onThirdCall': + index = j.numericLiteral(2) + break + } + if (!index) return node + + // `jest.spyOn` or `jest.fn` + const mockFn = node.callee.object.callee.object + const callLengthConditionalExpression = j.binaryExpression( + '===', + j.memberExpression(mockFn, j.identifier('mock.calls.length')), + index + ) + + const isReturns = node.callee.property.name === 'returns' + const isTypescript = parser === 'ts' || parser === 'tsx' + + const mockImplementationArgs = isReturns + ? [] + : [ + j.spreadPropertyPattern( + j.identifier.from({ + name: 'args', + typeAnnotation: isTypescript + ? j.typeAnnotation(j.arrayTypeAnnotation(j.anyTypeAnnotation())) + : null, + }) + ), + ] + const mockImplementationReturn = isReturns + ? node.arguments[0] + : j.memberExpression(j.identifier('args'), node.arguments[0], true) + + const mockImplementationFn = j.arrowFunctionExpression( + mockImplementationArgs, + j.blockStatement([ + j.ifStatement( + callLengthConditionalExpression, + j.returnStatement(mockImplementationReturn) + ), + ]) + ) + + return j.callExpression( + j.memberExpression(mockFn, j.identifier('mockImplementation')), + [mockImplementationFn] + ) + }) +} + /* stub.getCall(0) -> stub.mock.calls[0] stub.getCall(0).args[1] -> stub.mock.calls[0][1] @@ -804,6 +895,7 @@ export default function transformer(fileInfo: FileInfo, api: API, options) { const logWarning = (msg, node) => logger(fileInfo, msg, node) transformStub(j, ast, sinonExpression, logWarning) + transformStubOnCalls(j, ast, options.parser) transformMockTimers(j, ast) transformMock(j, ast, options.parser) transformMockResets(j, ast) From 7d2e990406031e008cd663fb005275b3f6fef094 Mon Sep 17 00:00:00 2001 From: Dan Beam <251287+danbeam@users.noreply.github.com> Date: Fri, 19 Aug 2022 18:38:21 -0700 Subject: [PATCH 2/3] whoops, trying too hard --- src/transformers/sinon.test.ts | 4 ++-- src/transformers/sinon.ts | 10 ++-------- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/src/transformers/sinon.test.ts b/src/transformers/sinon.test.ts index 717ddd9e..33586aa6 100644 --- a/src/transformers/sinon.test.ts +++ b/src/transformers/sinon.test.ts @@ -412,7 +412,7 @@ describe('spies and stubs', () => { return [8, 9, 10]; }) stub.mockImplementation(() => { - if (stub.mock.calls.length === 2) + if (stub.mock.calls.length === 3) return biscuits; }) @@ -421,7 +421,7 @@ describe('spies and stubs', () => { return args[2]; }) stub.mockImplementation((...args: any[]) => { - if (stub.mock.calls.length === 2) + if (stub.mock.calls.length === 3) return args[biscuits]; }) `, diff --git a/src/transformers/sinon.ts b/src/transformers/sinon.ts index b1fd2905..5e2a881b 100644 --- a/src/transformers/sinon.ts +++ b/src/transformers/sinon.ts @@ -396,15 +396,9 @@ function transformStubOnCalls(j, ast, parser) { .replaceWith(({ node }) => { let index switch (node.callee.object.callee.property.name) { - case 'onCall': { - const onCallArgs = node.callee.object.arguments - if (onCallArgs.length > 0) { - index = isFinite(onCallArgs[0].value) - ? j.numericLiteral(onCallArgs[0].value - 1) - : onCallArgs[0] - } + case 'onCall': + index = node.callee.object.arguments[0] break - } case 'onFirstCall': index = j.numericLiteral(0) break From e1f57d6f8d5a0b4056e0194f4c3433a60fb9b5a8 Mon Sep 17 00:00:00 2001 From: Dan Beam <251287+danbeam@users.noreply.github.com> Date: Mon, 22 Aug 2022 17:24:42 -0700 Subject: [PATCH 3/3] add block statement --- src/transformers/sinon.test.ts | 48 ++++++++++++++++++++++------------ src/transformers/sinon.ts | 4 +-- 2 files changed, 34 insertions(+), 18 deletions(-) diff --git a/src/transformers/sinon.test.ts b/src/transformers/sinon.test.ts index 33586aa6..297e73ca 100644 --- a/src/transformers/sinon.test.ts +++ b/src/transformers/sinon.test.ts @@ -180,32 +180,39 @@ describe('spies and stubs', () => { `, ` jest.fn().mockImplementation((...args) => { - if (args[0] === 'foo') + if (args[0] === 'foo') { return 'something'; + } }) jest.fn().mockImplementation((...args) => { - if (args[0] === 'foo' && args[1] === 'bar') + if (args[0] === 'foo' && args[1] === 'bar') { return 'something'; + } }) jest.fn().mockImplementation((...args) => { - if (args[0] === 'foo' && args[1] === 'bar' && args[2] === 1) + if (args[0] === 'foo' && args[1] === 'bar' && args[2] === 1) { return 'something'; + } }) jest.spyOn(Api, 'get').mockClear().mockImplementation((...args) => { - if (args[0] === 'foo' && args[1] === 'bar' && args[2] === 1) + if (args[0] === 'foo' && args[1] === 'bar' && args[2] === 1) { return 'something'; + } }) const stub = jest.spyOn(foo, 'bar').mockClear().mockImplementation((...args) => { - if (args[0] === 'foo' && args[1] === 1) + if (args[0] === 'foo' && args[1] === 1) { return 'something'; + } }) jest.spyOn(foo, 'bar').mockClear().mockImplementation((...args) => { - if (args[0] === 'foo' && typeof args[1] === 'object') + if (args[0] === 'foo' && typeof args[1] === 'object') { return 'something'; + } }) jest.fn().mockImplementation((...args) => { - if (args[0] === 'foo' && args.length >= 2) + if (args[0] === 'foo' && args.length >= 2) { return 'something'; + } }) ` ) @@ -220,8 +227,9 @@ describe('spies and stubs', () => { `, ` jest.fn().mockImplementation((...args: any[]) => { - if (args[0] === 'foo') + if (args[0] === 'foo') { return 'something'; + } }) `, { parser: 'tsx' } @@ -373,21 +381,25 @@ describe('spies and stubs', () => { `, ` stub.mockImplementation(() => { - if (stub.mock.calls.length === 0) + if (stub.mock.calls.length === 0) { return 5; + } }) stub.mockImplementation(() => { - if (stub.mock.calls.length === 1) + if (stub.mock.calls.length === 1) { return 6; + } }) stub.mockImplementation((...args) => { - if (stub.mock.calls.length === 0) + if (stub.mock.calls.length === 0) { return args[0]; + } }) stub.mockImplementation((...args) => { - if (stub.mock.calls.length === 1) + if (stub.mock.calls.length === 1) { return args[1]; + } }) // invalid onCall() params @@ -408,21 +420,25 @@ describe('spies and stubs', () => { `, ` stub.mockImplementation(() => { - if (stub.mock.calls.length === 2) + if (stub.mock.calls.length === 2) { return [8, 9, 10]; + } }) stub.mockImplementation(() => { - if (stub.mock.calls.length === 3) + if (stub.mock.calls.length === 3) { return biscuits; + } }) stub.mockImplementation((...args: any[]) => { - if (stub.mock.calls.length === 2) + if (stub.mock.calls.length === 2) { return args[2]; + } }) stub.mockImplementation((...args: any[]) => { - if (stub.mock.calls.length === 3) + if (stub.mock.calls.length === 3) { return args[biscuits]; + } }) `, { parser: 'tsx' } diff --git a/src/transformers/sinon.ts b/src/transformers/sinon.ts index 5e2a881b..370f1ba2 100644 --- a/src/transformers/sinon.ts +++ b/src/transformers/sinon.ts @@ -443,7 +443,7 @@ function transformStubOnCalls(j, ast, parser) { j.blockStatement([ j.ifStatement( callLengthConditionalExpression, - j.returnStatement(mockImplementationReturn) + j.blockStatement([j.returnStatement(mockImplementationReturn)]) ), ]) ) @@ -632,7 +632,7 @@ function transformMock(j: core.JSCodeshift, ast, parser: string) { j.blockStatement([ j.ifStatement( mockImplementationConditionalExpression, - j.returnStatement(mockImplementationReturn[0]) + j.blockStatement([j.returnStatement(mockImplementationReturn[0])]) ), ]) )