Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Semi WIP] jasmine-globals: support arbitrary jasmineSpy.and.* usage #160

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
88 changes: 88 additions & 0 deletions src/transformers/jasmine-globals.js
Original file line number Diff line number Diff line change
Expand Up @@ -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, {
Expand Down Expand Up @@ -72,6 +96,8 @@ export default function jasmineGlobals(fileInfo, api, options) {
break;
}
}

setCheckForChainIfNeeded(path);
});

root
Expand All @@ -96,6 +122,8 @@ export default function jasmineGlobals(fileInfo, api, options) {
j.identifier('fn')
);
path.node.arguments = [];

setCheckForChainIfNeeded(path);
});

root
Expand Down Expand Up @@ -172,6 +200,8 @@ export default function jasmineGlobals(fileInfo, api, options) {
break;
}
}

setCheckForChainIfNeeded(path);
});

root
Expand All @@ -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, {
Expand Down
196 changes: 174 additions & 22 deletions src/transformers/jasmine-globals.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',
`
Expand All @@ -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();
Expand All @@ -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()',
`
Expand Down