Repository: skovhus/jest-codemods
Pull request #357 (2022-08-19 19:28:57)

[sinon] implement callsArg*
Reviewers (in addition to @skovhus): @catc @lencioni @jayrobin

codecov[bot]: # [Codecov](https://codecov.io/gh/skovhus/jest-codemods/pull/357?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Kenneth+Skovhus) Report
> Merging [#357](https://codecov.io/gh/skovhus/jest-codemods/pull/357?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Kenneth+Skovhus) (3c7ae36) into [main](https://codecov.io/gh/skovhus/jest-codemods/commit/1a26e298022e7956bc5954cc75816cd6ca58260a?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Kenneth+Skovhus) (1a26e29) will **decrease** coverage by `0.05%`.
> The diff coverage is `87.50%`.

```diff
@@            Coverage Diff             @@
##             main     #357      +/-   ##
==========================================
- Coverage   92.09%   92.03%   -0.06%     
==========================================
  Files          26       26              
  Lines        1872     1896      +24     
  Branches      393      398       +5     
==========================================
+ Hits         1724     1745      +21     
- Misses        102      103       +1     
- Partials       46       48       +2     
```


| [Impacted Files](https://codecov.io/gh/skovhus/jest-codemods/pull/357?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Kenneth+Skovhus) | Coverage Δ | |
|---|---|---|
| [src/transformers/sinon.ts](https://codecov.io/gh/skovhus/jest-codemods/pull/357/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Kenneth+Skovhus#diff-c3JjL3RyYW5zZm9ybWVycy9zaW5vbi50cw==) | `87.50% <87.50%> (ø)` | |

Help us with your feedback. Take ten seconds to tell us [how you rate us](https://about.codecov.io/nps?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Kenneth+Skovhus). Have a feature suggestion? [Share it here.](https://app.codecov.io/gh/feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Kenneth+Skovhus)


Patch:
diff --git a/src/transformers/sinon.ts b/src/transformers/sinon.ts
index 54d341a..78182ae 100644
--- a/src/transformers/sinon.ts
+++ b/src/transformers/sinon.ts
@@ -55,6 +55,86 @@ const SINON_NTH_CALLS = new Set(['firstCall', 'secondCall', 'thirdCall', 'lastCa
 const EXPECT_PREFIXES = new Set(['to'])
 const isPrefix = (name) => EXPECT_PREFIXES.has(name)
 
+const SINON_CALLS_ARG = new Set([
+  'callsArg',
+  'callsArgOn',
+  'callsArgWith',
+  'callsArgOnWith',
+])
+
+/* 
+  stub.callsArg(0) -> stub.mockImplementation((...args: any[]) => args[0]())
+  stub.callsArgOn(1, thisArg) -> stub.mockImplementation((...args: any[]) => args[1].call(thisArg))
+  stub.callsArgWith(2, arg1, arg2) -> stub.mockImplementation((...args: any[]) => args[2](arg1, arg2))
+  stub.callsArgOnWith(3, thisArg, arg1, arg2) -> stub.mockImplementation((...args: any[]) => args[3].call(thisArg, arg1, arg2))
+*/
+function transformCallsArg(j, ast, parser) {
+  ast
+    .find(j.CallExpression, {
+      callee: {
+        type: j.MemberExpression.name,
+        property: {
+          name: (name) => SINON_CALLS_ARG.has(name),
+        },
+      },
+    })
+    .replaceWith((np) => {
+      const { node } = np
+
+      if (node.arguments.length < 1) return node
+
+      const argName = j.memberExpression(j.identifier('args'), node.arguments[0], true)
+
+      const isTypescript = parser === 'tsx' || parser === 'ts'
+      const mockImplementationArg = j.spreadPropertyPattern(
+        j.identifier.from({
+          name: 'args',
+          typeAnnotation: isTypescript
+            ? j.typeAnnotation(j.arrayTypeAnnotation(j.anyTypeAnnotation()))
+            : null,
+        })
+      )
+
+      let mockImplementationInvocation
+
+      switch (node.callee.property.name) {
+        case 'callsArg':
+          mockImplementationInvocation = j.callExpression(argName, [])
+          break
+        case 'callsArgOn':
+          mockImplementationInvocation = j.callExpression(
+            j.memberExpression(argName, j.identifier('call')),
+            [node.arguments[1]]
+          )
+          break
+        case 'callsArgWith':
+          mockImplementationInvocation = j.callExpression(
+            argName,
+            node.arguments.slice(1)
+          )
+          break
+        case 'callsArgOnWith':
+          mockImplementationInvocation = j.callExpression(
+            j.memberExpression(argName, j.identifier('call')),
+            node.arguments.slice(1)
+          )
+          break
+      }
+
+      const mockImplementationFn = j.arrowFunctionExpression(
+        [mockImplementationArg],
+        mockImplementationInvocation
+      )
+
+      const mockFn = node.callee.object
+
+      return j.callExpression(
+        j.memberExpression(mockFn, j.identifier('mockImplementation')),
+        [mockImplementationFn]
+      )
+    })
+}
+
 /* 
   expect(spy.called).to.be(true) -> expect(spy).toHaveBeenCalled()
   expect(spy.callCount).to.equal(2) -> expect(spy).toHaveBeenCalledTimes(2)
@@ -719,6 +799,7 @@ export default function transformer(fileInfo: FileInfo, api: API, options) {
   transformMockTimers(j, ast)
   transformMock(j, ast, options.parser)
   transformMockResets(j, ast)
+  transformCallsArg(j, ast, options.parser)
   transformCallCountAssertions(j, ast)
   transformCalledWithAssertions(j, ast)
   transformMatch(j, ast)


Test patch:
diff --git a/src/transformers/sinon.test.ts b/src/transformers/sinon.test.ts
index 9faed7a..fa03982 100644
--- a/src/transformers/sinon.test.ts
+++ b/src/transformers/sinon.test.ts
@@ -293,6 +293,56 @@ describe('spies and stubs', () => {
 `
     )
   })
+
+  it('handles .callsArg*', () => {
+    expectTransformation(
+      `
+        import sinon from 'sinon'
+
+        apiStub.callsArg(0)
+        apiStub.callsArgOn(1, thisArg)
+        apiStub.callsArgWith(2, 'a', 'b')
+        apiStub.callsArgOnWith(3, thisArg, 'c', 'd')
+        apiStub.callsArg(idx)
+
+        apiStub.callsArg() // Ignored
+`,
+      `
+        apiStub.mockImplementation((...args) => args[0]())
+        apiStub.mockImplementation((...args) => args[1].call(thisArg))
+        apiStub.mockImplementation((...args) => args[2]('a', 'b'))
+        apiStub.mockImplementation((...args) => args[3].call(thisArg, 'c', 'd'))
+        apiStub.mockImplementation((...args) => args[idx]())
+
+        apiStub.callsArg() // Ignored
+`
+    )
+  })
+  it('handles .callsArg* (parser: ts)', () => {
+    expectTransformation(
+      `
+        import sinon from 'sinon'
+
+        apiStub.callsArg(0)
+        apiStub.callsArgOn(1, thisArg)
+        apiStub.callsArgWith(2, 'a', 'b')
+        apiStub.callsArgOnWith(3, thisArg, 'c', 'd')
+        apiStub.callsArg(idx)
+
+        apiStub.callsArg() // Ignored
+`,
+      `
+        apiStub.mockImplementation((...args: any[]) => args[0]())
+        apiStub.mockImplementation((...args: any[]) => args[1].call(thisArg))
+        apiStub.mockImplementation((...args: any[]) => args[2]('a', 'b'))
+        apiStub.mockImplementation((...args: any[]) => args[3].call(thisArg, 'c', 'd'))
+        apiStub.mockImplementation((...args: any[]) => args[idx]())
+
+        apiStub.callsArg() // Ignored
+`,
+      { parser: 'ts' }
+    )
+  })
 })
 
 describe('mocks', () => {