Skip to content

Commit

Permalink
[sinon][ts] convert sinon.SinonStub -> jest.Mock, sinon.SinonSpy -> j…
Browse files Browse the repository at this point in the history
…est.SpyInstance (#359)

* [sinon][ts] convert sinon.SinonStub -> jest.Mock, sinon.SinonSpy -> jest.SpyInstance

* move withParser() to test setup code

Co-authored-by: Kenneth Skovhus <skovhus@users.noreply.github.com>
  • Loading branch information
danbeam and skovhus authored Aug 23, 2022
1 parent b4ca35b commit c9c541c
Show file tree
Hide file tree
Showing 3 changed files with 90 additions and 11 deletions.
36 changes: 36 additions & 0 deletions src/transformers/sinon.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -690,3 +690,39 @@ describe('mock timers', () => {
)
})
})

describe('updates types', () => {
it('rewrites types for mocks & stubs', () => {
expectTransformation(
`
import sinon from 'sinon-sandbox'
let stub1: sinon.SinonStub
let stub2: sinon.SinonStub = getStub()
let spy1: sinon.SinonSpy
let spy2: sinon.SinonSpy = getSpy()
let doStubThing = (stub: sinon.SinonStub) => stub
let doSpyThing = (spy: sinon.SinonSpy) => spy
// These should be ignored
let num: number = 5
let str: string = 'asdf'
let keys = (obj: object) => Object.keys(obj)
`,
`
let stub1: jest.Mock
let stub2: jest.Mock = getStub()
let spy1: jest.SpyInstance
let spy2: jest.SpyInstance = getSpy()
let doStubThing = (stub: jest.Mock) => stub
let doSpyThing = (spy: jest.SpyInstance) => spy
// These should be ignored
let num: number = 5
let str: string = 'asdf'
let keys = (obj: object) => Object.keys(obj)
`,
{ parser: 'ts' }
)
})
})
51 changes: 44 additions & 7 deletions src/transformers/sinon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ const SINON_ON_NTH_CALLS = new Set(['onFirstCall', 'onSecondCall', 'onThirdCall'
const EXPECT_PREFIXES = new Set(['to'])
const isPrefix = (name) => EXPECT_PREFIXES.has(name)

const isTypescript = (parser: string) => parser === 'tsx' || parser === 'ts'

const SINON_CALLS_ARG = new Set([
'callsArg',
'callsArgOn',
Expand Down Expand Up @@ -88,11 +90,10 @@ function transformCallsArg(j, ast, parser) {

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
typeAnnotation: isTypescript(parser)
? j.typeAnnotation(j.arrayTypeAnnotation(j.anyTypeAnnotation()))
: null,
})
Expand Down Expand Up @@ -552,8 +553,6 @@ function transformStubGetCalls(j: core.JSCodeshift, ast) {
.returnsArg
*/
function transformMock(j: core.JSCodeshift, ast, parser: string) {
const isTypescript = parser === 'tsx' || parser === 'ts'

// stub.withArgs(111).returns('foo') => stub.mockImplementation((...args) => { if (args[0] === '111') return 'foo' })
ast
.find(j.CallExpression, {
Expand Down Expand Up @@ -621,7 +620,7 @@ function transformMock(j: core.JSCodeshift, ast, parser: string) {
const mockImplementationArg = j.spreadPropertyPattern(
j.identifier.from({
name: 'args',
typeAnnotation: isTypescript
typeAnnotation: isTypescript(parser)
? j.typeAnnotation(j.arrayTypeAnnotation(j.anyTypeAnnotation()))
: null,
})
Expand Down Expand Up @@ -671,7 +670,7 @@ function transformMock(j: core.JSCodeshift, ast, parser: string) {

const argsVar = j.identifier.from({
name: 'args',
typeAnnotation: isTypescript
typeAnnotation: isTypescript(parser)
? j.typeAnnotation(j.arrayTypeAnnotation(j.anyTypeAnnotation()))
: null,
})
Expand Down Expand Up @@ -871,6 +870,44 @@ function transformMockTimers(j, ast) {
})
}

// let stub: sinon.SinonStub -> let stub: jest.Mock
// let spy: sinon.SinonSpy -> let spy: jest.SpyInstance
function transformTypes(j, ast, parser) {
if (!isTypescript(parser)) return

ast
.find(j.TSTypeReference, {
typeName: {
left: {
name: 'sinon',
},
right: {
name: 'SinonStub',
},
},
})
.forEach((np) => {
np.node.typeName.left.name = 'jest'
np.node.typeName.right.name = 'Mock'
})

ast
.find(j.TSTypeReference, {
typeName: {
left: {
name: 'sinon',
},
right: {
name: 'SinonSpy',
},
},
})
.forEach((np) => {
np.node.typeName.left.name = 'jest'
np.node.typeName.right.name = 'SpyInstance'
})
}

export default function transformer(fileInfo: FileInfo, api: API, options) {
const j = api.jscodeshift
const ast = j(fileInfo.source)
Expand All @@ -879,7 +916,6 @@ export default function transformer(fileInfo: FileInfo, api: API, options) {
removeDefaultImport(j, ast, 'sinon-sandbox') || removeDefaultImport(j, ast, 'sinon')

if (!sinonExpression) {
console.warn(`no sinon for "${fileInfo.path}"`)
if (!options.skipImportDetection) {
return fileInfo.source
}
Expand All @@ -898,6 +934,7 @@ export default function transformer(fileInfo: FileInfo, api: API, options) {
transformCalledWithAssertions(j, ast)
transformMatch(j, ast)
transformStubGetCalls(j, ast)
transformTypes(j, ast, options.parser)

return finale(fileInfo, j, ast, options)
}
14 changes: 10 additions & 4 deletions src/utils/test-helpers.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,23 @@
import jscodeshift from 'jscodeshift'

// simulate the jscodeshift api
export function api(): jscodeshift.API {
export function api(options): jscodeshift.API {
let j = jscodeshift

if (options.parser) {
j = j.withParser(options.parser)
}

return {
jscodeshift,
j: jscodeshift,
jscodeshift: j,
j,
stats: () => {},
report: () => {},
}
}

export function runPlugin(plugin: jscodeshift.Transform, source: string, options = {}) {
return plugin({ source, path: 'test.js' }, api(), options)
return plugin({ source, path: 'test.js' }, api(options), options)
}

export function wrapPlugin(plugin: jscodeshift.Transform) {
Expand Down

0 comments on commit c9c541c

Please sign in to comment.