Skip to content

Commit 0fdd724

Browse files
committed
refactor(@schematics/angular): decompose transformSpies into modular helper functions
1 parent 673dbaf commit 0fdd724

1 file changed

Lines changed: 175 additions & 148 deletions

File tree

  • packages/schematics/angular/refactor/jasmine-vitest/transformers

packages/schematics/angular/refactor/jasmine-vitest/transformers/jasmine-spy.ts

Lines changed: 175 additions & 148 deletions
Original file line numberDiff line numberDiff line change
@@ -54,13 +54,8 @@ function isChainedWithAnd(node: ts.Node): boolean {
5454
return false;
5555
}
5656

57-
/* eslint-disable max-lines-per-function */
58-
export function transformSpies(node: ts.Node, refactorCtx: RefactorContext): ts.Node {
57+
function transformPrimarySpy(node: ts.CallExpression, refactorCtx: RefactorContext): ts.Node {
5958
const { sourceFile, reporter, pendingVitestValueImports } = refactorCtx;
60-
if (!ts.isCallExpression(node)) {
61-
return node;
62-
}
63-
6459
if (
6560
ts.isIdentifier(node.expression) &&
6661
(node.expression.text === 'spyOn' || node.expression.text === 'spyOnProperty')
@@ -98,172 +93,186 @@ export function transformSpies(node: ts.Node, refactorCtx: RefactorContext): ts.
9893
);
9994
}
10095

101-
if (ts.isPropertyAccessExpression(node.expression)) {
102-
const pae = node.expression;
96+
return node;
97+
}
10398

104-
let spyCall: ts.Expression | undefined;
99+
function transformSpyStrategy(node: ts.CallExpression, refactorCtx: RefactorContext): ts.Node {
100+
const { sourceFile, reporter } = refactorCtx;
101+
if (!ts.isPropertyAccessExpression(node.expression)) {
102+
return node;
103+
}
105104

106-
if (
107-
ts.isPropertyAccessExpression(pae.expression) &&
108-
ts.isIdentifier(pae.expression.name) &&
109-
pae.expression.name.text === 'and'
110-
) {
111-
spyCall = pae.expression.expression;
112-
} else if (
113-
ts.isElementAccessExpression(pae.expression) &&
114-
ts.isStringLiteralLike(pae.expression.argumentExpression) &&
115-
pae.expression.argumentExpression.text === 'and'
116-
) {
117-
spyCall = pae.expression.expression;
118-
}
105+
const pae = node.expression;
106+
let spyCall: ts.Expression | undefined;
119107

120-
if (spyCall) {
121-
let newMethodName: string | undefined;
122-
let args = node.arguments;
123-
124-
if (ts.isIdentifier(pae.name)) {
125-
const strategyName = pae.name.text;
126-
switch (strategyName) {
127-
case 'returnValue':
128-
{
129-
const firstArg = args[0];
130-
const result = firstArg ? getPromiseResolveRejectMethod(firstArg) : null;
131-
if (result) {
132-
const methodMapping = {
133-
'resolve': 'mockResolvedValue',
134-
'reject': 'mockRejectedValue',
135-
};
136-
newMethodName = methodMapping[result.methodName];
137-
args = result.arguments;
138-
} else {
139-
newMethodName = 'mockReturnValue';
140-
}
141-
}
142-
break;
143-
case 'resolveTo':
144-
newMethodName = 'mockResolvedValue';
145-
break;
146-
case 'rejectWith':
147-
newMethodName = 'mockRejectedValue';
148-
break;
149-
case 'returnValues': {
150-
reporter.reportTransformation(
151-
sourceFile,
152-
node,
153-
'Transformed `.and.returnValues()` to chained `.mockReturnValueOnce()` calls.',
154-
);
155-
const returnValues = node.arguments;
156-
if (returnValues.length === 0) {
157-
// No values, so it's a no-op. Just transform the spyOn call.
158-
return transformSpies(spyCall, refactorCtx);
159-
}
160-
// spy.and.returnValues(a, b) -> spy.mockReturnValueOnce(a).mockReturnValueOnce(b)
161-
let chainedCall: ts.Expression = spyCall;
162-
for (const value of returnValues) {
163-
const mockCall = ts.factory.createCallExpression(
164-
createPropertyAccess(chainedCall, 'mockReturnValueOnce'),
165-
undefined,
166-
[value],
167-
);
168-
chainedCall = mockCall;
169-
}
108+
if (
109+
ts.isPropertyAccessExpression(pae.expression) &&
110+
ts.isIdentifier(pae.expression.name) &&
111+
pae.expression.name.text === 'and'
112+
) {
113+
spyCall = pae.expression.expression;
114+
} else if (
115+
ts.isElementAccessExpression(pae.expression) &&
116+
ts.isStringLiteralLike(pae.expression.argumentExpression) &&
117+
pae.expression.argumentExpression.text === 'and'
118+
) {
119+
spyCall = pae.expression.expression;
120+
}
170121

171-
return chainedCall;
122+
if (spyCall) {
123+
let newMethodName: string | undefined;
124+
let args = node.arguments;
125+
126+
if (ts.isIdentifier(pae.name)) {
127+
const strategyName = pae.name.text;
128+
switch (strategyName) {
129+
case 'returnValue':
130+
{
131+
const firstArg = args[0];
132+
const result = firstArg ? getPromiseResolveRejectMethod(firstArg) : null;
133+
if (result) {
134+
const methodMapping = {
135+
'resolve': 'mockResolvedValue',
136+
'reject': 'mockRejectedValue',
137+
};
138+
newMethodName = methodMapping[result.methodName];
139+
args = result.arguments;
140+
} else {
141+
newMethodName = 'mockReturnValue';
142+
}
172143
}
173-
case 'callFake':
174-
newMethodName = 'mockImplementation';
175-
break;
176-
case 'callThrough':
177-
reporter.reportTransformation(
178-
sourceFile,
179-
node,
180-
'Removed redundant `.and.callThrough()` call.',
181-
);
182-
183-
return transformSpies(spyCall, refactorCtx); // .and.callThrough() is redundant, just transform spyOn.
184-
case 'stub': {
185-
reporter.reportTransformation(
186-
sourceFile,
187-
node,
188-
'Transformed `.and.stub()` to `.mockImplementation()`.',
189-
);
190-
const newExpression = createPropertyAccess(spyCall, 'mockImplementation');
191-
const arrowFn = ts.factory.createArrowFunction(
192-
undefined,
193-
undefined,
194-
[],
195-
undefined,
196-
ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken),
197-
ts.factory.createBlock([], /* multiline */ true),
198-
);
199-
200-
return ts.factory.createCallExpression(newExpression, undefined, [arrowFn]);
144+
break;
145+
case 'resolveTo':
146+
newMethodName = 'mockResolvedValue';
147+
break;
148+
case 'rejectWith':
149+
newMethodName = 'mockRejectedValue';
150+
break;
151+
case 'returnValues': {
152+
reporter.reportTransformation(
153+
sourceFile,
154+
node,
155+
'Transformed `.and.returnValues()` to chained `.mockReturnValueOnce()` calls.',
156+
);
157+
const returnValues = node.arguments;
158+
if (returnValues.length === 0) {
159+
// No values, so it's a no-op. Just transform the spyOn call.
160+
return transformSpies(spyCall, refactorCtx);
201161
}
202-
case 'throwError': {
203-
reporter.reportTransformation(
204-
sourceFile,
205-
node,
206-
'Transformed `.and.throwError()` to `.mockImplementation()`.',
207-
);
208-
const errorArg = node.arguments[0];
209-
const throwStatement = ts.factory.createThrowStatement(
210-
errorArg && ts.isNewExpression(errorArg)
211-
? errorArg
212-
: ts.factory.createNewExpression(
213-
ts.factory.createIdentifier('Error'),
214-
undefined,
215-
errorArg ? [errorArg] : [],
216-
),
217-
);
218-
const arrowFunction = ts.factory.createArrowFunction(
162+
// spy.and.returnValues(a, b) -> spy.mockReturnValueOnce(a).mockReturnValueOnce(b)
163+
let chainedCall: ts.Expression = spyCall;
164+
for (const value of returnValues) {
165+
const mockCall = ts.factory.createCallExpression(
166+
createPropertyAccess(chainedCall, 'mockReturnValueOnce'),
219167
undefined,
220-
undefined,
221-
[],
222-
undefined,
223-
ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken),
224-
ts.factory.createBlock([throwStatement], true),
168+
[value],
225169
);
226-
const newExpression = createPropertyAccess(spyCall, 'mockImplementation');
227-
228-
return ts.factory.createCallExpression(newExpression, undefined, [arrowFunction]);
170+
chainedCall = mockCall;
229171
}
230-
case 'identity': {
231-
reporter.reportTransformation(
232-
sourceFile,
233-
node,
234-
'Transformed `.and.identity()` to `.getMockName()`.',
235-
);
236-
const newExpression = createPropertyAccess(spyCall, 'getMockName');
237172

238-
return ts.factory.createCallExpression(newExpression, undefined, undefined);
239-
}
240-
default: {
241-
const category = 'unsupported-spy-strategy';
242-
reporter.recordTodo(category, sourceFile, node);
243-
addTodoComment(node, category, { name: strategyName });
244-
break;
245-
}
173+
return chainedCall;
246174
}
175+
case 'callFake':
176+
newMethodName = 'mockImplementation';
177+
break;
178+
case 'callThrough':
179+
reporter.reportTransformation(
180+
sourceFile,
181+
node,
182+
'Removed redundant `.and.callThrough()` call.',
183+
);
184+
185+
return transformSpies(spyCall, refactorCtx); // .and.callThrough() is redundant, just transform spyOn.
186+
case 'stub': {
187+
reporter.reportTransformation(
188+
sourceFile,
189+
node,
190+
'Transformed `.and.stub()` to `.mockImplementation()`.',
191+
);
192+
const newExpression = createPropertyAccess(spyCall, 'mockImplementation');
193+
const arrowFn = ts.factory.createArrowFunction(
194+
undefined,
195+
undefined,
196+
[],
197+
undefined,
198+
ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken),
199+
ts.factory.createBlock([], /* multiline */ true),
200+
);
247201

248-
if (newMethodName) {
202+
return ts.factory.createCallExpression(newExpression, undefined, [arrowFn]);
203+
}
204+
case 'throwError': {
249205
reporter.reportTransformation(
250206
sourceFile,
251207
node,
252-
`Transformed spy strategy \`.and.${strategyName}()\` to \`.${newMethodName}()\`.`,
208+
'Transformed `.and.throwError()` to `.mockImplementation()`.',
209+
);
210+
const errorArg = node.arguments[0];
211+
const throwStatement = ts.factory.createThrowStatement(
212+
errorArg && ts.isNewExpression(errorArg)
213+
? errorArg
214+
: ts.factory.createNewExpression(
215+
ts.factory.createIdentifier('Error'),
216+
undefined,
217+
errorArg ? [errorArg] : [],
218+
),
219+
);
220+
const arrowFunction = ts.factory.createArrowFunction(
221+
undefined,
222+
undefined,
223+
[],
224+
undefined,
225+
ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken),
226+
ts.factory.createBlock([throwStatement], true),
253227
);
228+
const newExpression = createPropertyAccess(spyCall, 'mockImplementation');
254229

255-
const newExpression = ts.factory.updatePropertyAccessExpression(
256-
pae,
257-
spyCall,
258-
ts.factory.createIdentifier(newMethodName),
230+
return ts.factory.createCallExpression(newExpression, undefined, [arrowFunction]);
231+
}
232+
case 'identity': {
233+
reporter.reportTransformation(
234+
sourceFile,
235+
node,
236+
'Transformed `.and.identity()` to `.getMockName()`.',
259237
);
238+
const newExpression = createPropertyAccess(spyCall, 'getMockName');
260239

261-
return ts.factory.updateCallExpression(node, newExpression, node.typeArguments, args);
240+
return ts.factory.createCallExpression(newExpression, undefined, undefined);
241+
}
242+
default: {
243+
const category = 'unsupported-spy-strategy';
244+
reporter.recordTodo(category, sourceFile, node);
245+
addTodoComment(node, category, { name: strategyName });
246+
break;
262247
}
263248
}
249+
250+
if (newMethodName) {
251+
reporter.reportTransformation(
252+
sourceFile,
253+
node,
254+
`Transformed spy strategy \`.and.${strategyName}()\` to \`.${newMethodName}()\`.`,
255+
);
256+
257+
const newExpression = ts.factory.updatePropertyAccessExpression(
258+
pae,
259+
spyCall,
260+
ts.factory.createIdentifier(newMethodName),
261+
);
262+
263+
return ts.factory.updateCallExpression(node, newExpression, node.typeArguments, args);
264+
}
264265
}
265266
}
266267

268+
return node;
269+
}
270+
271+
function transformSpyOnAllFunctions(
272+
node: ts.CallExpression,
273+
refactorCtx: RefactorContext,
274+
): ts.Node {
275+
const { sourceFile, reporter } = refactorCtx;
267276
if (getJasmineMethodName(node) === 'spyOnAllFunctions') {
268277
reporter.reportTransformation(
269278
sourceFile,
@@ -280,6 +289,24 @@ export function transformSpies(node: ts.Node, refactorCtx: RefactorContext): ts.
280289
return node;
281290
}
282291

292+
export function transformSpies(node: ts.Node, refactorCtx: RefactorContext): ts.Node {
293+
if (!ts.isCallExpression(node)) {
294+
return node;
295+
}
296+
297+
const primaryResult = transformPrimarySpy(node, refactorCtx);
298+
if (primaryResult !== node) {
299+
return primaryResult;
300+
}
301+
302+
const strategyResult = transformSpyStrategy(node, refactorCtx);
303+
if (strategyResult !== node) {
304+
return strategyResult;
305+
}
306+
307+
return transformSpyOnAllFunctions(node, refactorCtx);
308+
}
309+
283310
export function transformCreateSpy(node: ts.Node, ctx: RefactorContext): ts.Node {
284311
const { reporter, sourceFile, pendingVitestValueImports } = ctx;
285312
if (!isJasmineCallExpression(node, 'createSpy')) {

0 commit comments

Comments
 (0)