From 5edde30420819c276e0809963ac255ca0c5a7086 Mon Sep 17 00:00:00 2001 From: rk Date: Sat, 24 Oct 2026 02:30:29 +0500 Subject: [PATCH 1/2] fix(@schematics/angular): do not drop callThrough() on existing spies in refactor-jasmine-vitest bare `spyOn` is migrated as stub now, so `spy.and.callThrough()` on spy created earlier is what turns real method back on, yet it was still dropped as redundant and left just `spy;` with no TODO vitest has no exact equivalent (`mockReset()` brings original implemntation back but also clears recorded calls), so call is now kept with TODO, chained `spyOn(...).and.callThrough()` is still removed like before --- .../jasmine-vitest/transformers/jasmine-spy.ts | 10 ++++++++++ .../jasmine-vitest/transformers/jasmine-spy_spec.ts | 11 +++++++++++ .../refactor/jasmine-vitest/utils/todo-notes.ts | 7 +++++++ 3 files changed, 28 insertions(+) diff --git a/packages/schematics/angular/refactor/jasmine-vitest/transformers/jasmine-spy.ts b/packages/schematics/angular/refactor/jasmine-vitest/transformers/jasmine-spy.ts index 6ac2a12c41f0..fb9989214885 100644 --- a/packages/schematics/angular/refactor/jasmine-vitest/transformers/jasmine-spy.ts +++ b/packages/schematics/angular/refactor/jasmine-vitest/transformers/jasmine-spy.ts @@ -224,6 +224,16 @@ function transformSpyStrategy(node: ts.CallExpression, refactorCtx: RefactorCont newMethodName = 'mockImplementation'; break; case 'callThrough': + if (!ts.isCallExpression(unwrapExpression(spyCall))) { + // On an existing spy, `.and.callThrough()` restores the original implementation, + // so it is not redundant and cannot be dropped. + const category = 'callThrough-on-existing-spy'; + reporter.recordTodo(category, sourceFile, node); + addTodoComment(node, category); + + return node; + } + reporter.reportTransformation( sourceFile, node, diff --git a/packages/schematics/angular/refactor/jasmine-vitest/transformers/jasmine-spy_spec.ts b/packages/schematics/angular/refactor/jasmine-vitest/transformers/jasmine-spy_spec.ts index 27b4655b5c4a..69d84d71198d 100644 --- a/packages/schematics/angular/refactor/jasmine-vitest/transformers/jasmine-spy_spec.ts +++ b/packages/schematics/angular/refactor/jasmine-vitest/transformers/jasmine-spy_spec.ts @@ -48,6 +48,17 @@ describe('Jasmine to Vitest Transformer - transformSpies', () => { input: `spyOn(service, 'myMethod').and.callThrough();`, expected: `vi.spyOn(service, 'myMethod');`, }, + { + description: 'should remove .and.callThrough() on a wrapped spyOn call', + input: `(spyOn(service, 'myMethod')!).and.callThrough();`, + expected: `(vi.spyOn(service, 'myMethod')!);`, + }, + { + description: 'should add a TODO for .and.callThrough() on an existing spy', + input: `mySpy.and.callThrough();`, + expected: `// TODO: vitest-migration: Vitest does not have a direct equivalent for .and.callThrough() on an existing spy. Please migrate this manually, for example with spy.mockReset(), which restores the original implementation but also clears the recorded calls. See: https://vitest.dev/api/mock.html#mockreset +mySpy.and.callThrough();`, + }, { description: 'should transform jasmine.createSpy("name") to vi.fn().mockName("name")', input: `const mySpy = jasmine.createSpy('mySpy');`, diff --git a/packages/schematics/angular/refactor/jasmine-vitest/utils/todo-notes.ts b/packages/schematics/angular/refactor/jasmine-vitest/utils/todo-notes.ts index c22e9affedc7..0aa2a820698d 100644 --- a/packages/schematics/angular/refactor/jasmine-vitest/utils/todo-notes.ts +++ b/packages/schematics/angular/refactor/jasmine-vitest/utils/todo-notes.ts @@ -158,6 +158,13 @@ export const TODO_NOTES = { `Unsupported spy strategy ".and.${context.name}()" found. Please migrate this manually.`, url: 'https://vitest.dev/api/mocked.html#mock', }, + 'callThrough-on-existing-spy': { + message: + 'Vitest does not have a direct equivalent for .and.callThrough() on an existing spy.' + + ' Please migrate this manually, for example with spy.mockReset(), which restores' + + ' the original implementation but also clears the recorded calls.', + url: 'https://vitest.dev/api/mock.html#mockreset', + }, 'mostRecent-without-args': { message: 'Direct usage of mostRecent() is not supported.' + From 95a4c02f3629716e62bf6f04b96cb3c4737ce530 Mon Sep 17 00:00:00 2001 From: rk Date: Wed, 28 Oct 2026 14:26:50 +0500 Subject: [PATCH 2/2] fixup! fix(@schematics/angular): do not drop callThrough() on existing spies in refactor-jasmine-vitest --- .../jasmine-vitest/transformers/jasmine-spy.ts | 15 ++++++++++++++- .../transformers/jasmine-spy_spec.ts | 6 ++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/packages/schematics/angular/refactor/jasmine-vitest/transformers/jasmine-spy.ts b/packages/schematics/angular/refactor/jasmine-vitest/transformers/jasmine-spy.ts index fb9989214885..68a76b378194 100644 --- a/packages/schematics/angular/refactor/jasmine-vitest/transformers/jasmine-spy.ts +++ b/packages/schematics/angular/refactor/jasmine-vitest/transformers/jasmine-spy.ts @@ -95,6 +95,19 @@ function isChainedWithAnd(node: ts.Node): boolean { return false; } +function isSpyCreation(node: ts.Expression): boolean { + const expression = unwrapExpression(node); + if (!ts.isCallExpression(expression)) { + return false; + } + + if (ts.isIdentifier(expression.expression)) { + return expression.expression.text === 'spyOn' || expression.expression.text === 'spyOnProperty'; + } + + return isJasmineCallExpression(expression, 'createSpy'); +} + function transformPrimarySpy(node: ts.CallExpression, refactorCtx: RefactorContext): ts.Node { const { sourceFile, reporter, pendingVitestValueImports } = refactorCtx; if ( @@ -224,7 +237,7 @@ function transformSpyStrategy(node: ts.CallExpression, refactorCtx: RefactorCont newMethodName = 'mockImplementation'; break; case 'callThrough': - if (!ts.isCallExpression(unwrapExpression(spyCall))) { + if (!isSpyCreation(spyCall)) { // On an existing spy, `.and.callThrough()` restores the original implementation, // so it is not redundant and cannot be dropped. const category = 'callThrough-on-existing-spy'; diff --git a/packages/schematics/angular/refactor/jasmine-vitest/transformers/jasmine-spy_spec.ts b/packages/schematics/angular/refactor/jasmine-vitest/transformers/jasmine-spy_spec.ts index 69d84d71198d..e14de03c3c17 100644 --- a/packages/schematics/angular/refactor/jasmine-vitest/transformers/jasmine-spy_spec.ts +++ b/packages/schematics/angular/refactor/jasmine-vitest/transformers/jasmine-spy_spec.ts @@ -58,6 +58,12 @@ describe('Jasmine to Vitest Transformer - transformSpies', () => { input: `mySpy.and.callThrough();`, expected: `// TODO: vitest-migration: Vitest does not have a direct equivalent for .and.callThrough() on an existing spy. Please migrate this manually, for example with spy.mockReset(), which restores the original implementation but also clears the recorded calls. See: https://vitest.dev/api/mock.html#mockreset mySpy.and.callThrough();`, + }, + { + description: 'should add a TODO for .and.callThrough() on a spy returned by a call', + input: `getSpy().and.callThrough();`, + expected: `// TODO: vitest-migration: Vitest does not have a direct equivalent for .and.callThrough() on an existing spy. Please migrate this manually, for example with spy.mockReset(), which restores the original implementation but also clears the recorded calls. See: https://vitest.dev/api/mock.html#mockreset +getSpy().and.callThrough();`, }, { description: 'should transform jasmine.createSpy("name") to vi.fn().mockName("name")',