@@ -24,6 +24,37 @@ import { addTodoComment } from '../utils/comment-helpers';
2424import { RefactorContext } from '../utils/refactor-context' ;
2525import { createViCallExpression } from '../utils/refactor-helpers' ;
2626
27+ function isChainedWithAnd ( node : ts . Node ) : boolean {
28+ let parent = node . parent ;
29+ while ( parent ) {
30+ if ( ts . isPropertyAccessExpression ( parent ) ) {
31+ if ( ts . isIdentifier ( parent . name ) && parent . name . text === 'and' ) {
32+ return true ;
33+ }
34+ } else if ( ts . isElementAccessExpression ( parent ) ) {
35+ if (
36+ ts . isStringLiteralLike ( parent . argumentExpression ) &&
37+ parent . argumentExpression . text === 'and'
38+ ) {
39+ return true ;
40+ }
41+ } else if (
42+ ts . isParenthesizedExpression ( parent ) ||
43+ ts . isAsExpression ( parent ) ||
44+ ts . isNonNullExpression ( parent ) ||
45+ ts . isTypeAssertionExpression ( parent ) ||
46+ ts . isSatisfiesExpression ( parent )
47+ ) {
48+ parent = parent . parent ;
49+ continue ;
50+ }
51+ break ;
52+ }
53+
54+ return false ;
55+ }
56+
57+ /* eslint-disable max-lines-per-function */
2758export function transformSpies ( node : ts . Node , refactorCtx : RefactorContext ) : ts . Node {
2859 const { sourceFile, reporter, pendingVitestValueImports } = refactorCtx ;
2960 if ( ! ts . isCallExpression ( node ) ) {
@@ -35,29 +66,58 @@ export function transformSpies(node: ts.Node, refactorCtx: RefactorContext): ts.
3566 ( node . expression . text === 'spyOn' || node . expression . text === 'spyOnProperty' )
3667 ) {
3768 addVitestValueImport ( pendingVitestValueImports , 'vi' ) ;
38- reporter . reportTransformation (
39- sourceFile ,
40- node ,
41- `Transformed \`${ node . expression . text } \` to \`vi.spyOn\`.` ,
42- ) ;
4369
44- return ts . factory . updateCallExpression (
70+ const viSpyOnCall = ts . factory . updateCallExpression (
4571 node ,
4672 createPropertyAccess ( 'vi' , 'spyOn' ) ,
4773 node . typeArguments ,
4874 node . arguments ,
4975 ) ;
76+
77+ if ( isChainedWithAnd ( node ) ) {
78+ reporter . reportTransformation (
79+ sourceFile ,
80+ node ,
81+ `Transformed \`${ node . expression . text } \` to \`vi.spyOn\`.` ,
82+ ) ;
83+
84+ return viSpyOnCall ;
85+ }
86+
87+ reporter . reportTransformation (
88+ sourceFile ,
89+ node ,
90+ `Transformed \`${ node . expression . text } \` to \`vi.spyOn\`, ` +
91+ `appending \`.mockReturnValue(undefined)\` to preserve stub-by-default semantics.` ,
92+ ) ;
93+
94+ return ts . factory . createCallExpression (
95+ createPropertyAccess ( viSpyOnCall , 'mockReturnValue' ) ,
96+ undefined ,
97+ [ ts . factory . createIdentifier ( 'undefined' ) ] ,
98+ ) ;
5099 }
51100
52101 if ( ts . isPropertyAccessExpression ( node . expression ) ) {
53102 const pae = node . expression ;
54103
104+ let spyCall : ts . Expression | undefined ;
105+
55106 if (
56107 ts . isPropertyAccessExpression ( pae . expression ) &&
57108 ts . isIdentifier ( pae . expression . name ) &&
58109 pae . expression . name . text === 'and'
59110 ) {
60- const spyCall = pae . expression . expression ;
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+ }
119+
120+ if ( spyCall ) {
61121 let newMethodName : string | undefined ;
62122 let args = node . arguments ;
63123
@@ -66,7 +126,8 @@ export function transformSpies(node: ts.Node, refactorCtx: RefactorContext): ts.
66126 switch ( strategyName ) {
67127 case 'returnValue' :
68128 {
69- const result = getPromiseResolveRejectMethod ( args [ 0 ] ) ;
129+ const firstArg = args [ 0 ] ;
130+ const result = firstArg ? getPromiseResolveRejectMethod ( firstArg ) : null ;
70131 if ( result ) {
71132 const methodMapping = {
72133 'resolve' : 'mockResolvedValue' ,
@@ -146,12 +207,12 @@ export function transformSpies(node: ts.Node, refactorCtx: RefactorContext): ts.
146207 ) ;
147208 const errorArg = node . arguments [ 0 ] ;
148209 const throwStatement = ts . factory . createThrowStatement (
149- ts . isNewExpression ( errorArg )
210+ errorArg && ts . isNewExpression ( errorArg )
150211 ? errorArg
151212 : ts . factory . createNewExpression (
152213 ts . factory . createIdentifier ( 'Error' ) ,
153214 undefined ,
154- node . arguments ,
215+ errorArg ? [ errorArg ] : [ ] ,
155216 ) ,
156217 ) ;
157218 const arrowFunction = ts . factory . createArrowFunction (
0 commit comments