diff --git a/packages/schematics/angular/refactor/jasmine-vitest/transformers/jasmine-matcher.ts b/packages/schematics/angular/refactor/jasmine-vitest/transformers/jasmine-matcher.ts index 1e0a87dc80f2..d2df0c78b57d 100644 --- a/packages/schematics/angular/refactor/jasmine-vitest/transformers/jasmine-matcher.ts +++ b/packages/schematics/angular/refactor/jasmine-vitest/transformers/jasmine-matcher.ts @@ -259,24 +259,51 @@ export function transformExpectAsync( node: ts.Node, { sourceFile, reporter }: RefactorContext, ): ts.Node { + if (!ts.isCallExpression(node) || !ts.isPropertyAccessExpression(node.expression)) { + return node; + } + + // When the chain continues past this call (e.g. the `withContext('msg')` link itself), + // the outermost matcher call handles the whole statement. if ( - !ts.isCallExpression(node) || - !ts.isPropertyAccessExpression(node.expression) || - !ts.isCallExpression(node.expression.expression) + node.parent && + ts.isPropertyAccessExpression(node.parent) && + node.parent.expression === node ) { return node; } const matcherCall = node; const matcherPae = node.expression; - const expectCall = node.expression.expression; + + // Walk down any modifier chain (`.not`, `.already`, `.withContext(...)`) to the root call. + // Modifiers have no direct vitest equivalent, so a modified matcher falls through to the + // TODO handling below. + const modifierNames: string[] = []; + let expectCall: ts.CallExpression | undefined; + let current: ts.Expression = matcherPae.expression; + while (!expectCall) { + if (ts.isCallExpression(current)) { + if (ts.isIdentifier(current.expression)) { + expectCall = current; + } else { + current = current.expression; + } + } else if (ts.isPropertyAccessExpression(current) && ts.isIdentifier(current.name)) { + modifierNames.unshift(current.name.text); + current = current.expression; + } else { + return node; + } + } if (!ts.isIdentifier(expectCall.expression) || expectCall.expression.text !== 'expectAsync') { return node; } const matcherName = ts.isIdentifier(matcherPae.name) ? matcherPae.name.text : undefined; - const mapping = matcherName ? ASYNC_MATCHER_CHANGES.get(matcherName) : undefined; + const mapping = + matcherName && modifierNames.length === 0 ? ASYNC_MATCHER_CHANGES.get(matcherName) : undefined; if (mapping) { reporter.reportTransformation( @@ -298,14 +325,14 @@ export function transformExpectAsync( } if (matcherName) { - if (matcherName === 'toBePending') { + if (matcherName === 'toBePending' && modifierNames.length === 0) { const category = 'toBePending'; reporter.recordTodo(category, sourceFile, node); addTodoComment(node, category); } else { const category = 'unsupported-expect-async-matcher'; reporter.recordTodo(category, sourceFile, node); - addTodoComment(node, category, { name: matcherName }); + addTodoComment(node, category, { name: [...modifierNames, matcherName].join('.') }); } } diff --git a/packages/schematics/angular/refactor/jasmine-vitest/transformers/jasmine-matcher_spec.ts b/packages/schematics/angular/refactor/jasmine-vitest/transformers/jasmine-matcher_spec.ts index 274202ed1689..f4e4da32e20d 100644 --- a/packages/schematics/angular/refactor/jasmine-vitest/transformers/jasmine-matcher_spec.ts +++ b/packages/schematics/angular/refactor/jasmine-vitest/transformers/jasmine-matcher_spec.ts @@ -84,6 +84,30 @@ describe('transformExpectAsync', () => { await expectAsync(myPromise).toBeSomethingElse(); `, }, + { + description: 'should add a TODO for a negated expectAsync matcher', + input: `await expectAsync(myPromise).not.toBeResolved();`, + expected: ` + // TODO: vitest-migration: Unsupported expectAsync matcher ".not.toBeResolved()" found. Please migrate this manually. + await expectAsync(myPromise).not.toBeResolved(); + `, + }, + { + description: 'should add a TODO for an expectAsync matcher modified with already', + input: `await expectAsync(myPromise).already.toBeResolved();`, + expected: ` + // TODO: vitest-migration: Unsupported expectAsync matcher ".already.toBeResolved()" found. Please migrate this manually. + await expectAsync(myPromise).already.toBeResolved(); + `, + }, + { + description: 'should add a TODO for an expectAsync matcher modified with withContext', + input: `await expectAsync(myPromise).withContext('msg').toBeResolved();`, + expected: ` + // TODO: vitest-migration: Unsupported expectAsync matcher ".withContext.toBeResolved()" found. Please migrate this manually. + await expectAsync(myPromise).withContext('msg').toBeResolved(); + `, + }, { description: 'should add a specific TODO for toBePending', input: `await expectAsync(myPromise).toBePending();`,