diff --git a/packages/schematics/angular/BUILD.bazel b/packages/schematics/angular/BUILD.bazel index d101299a5ab3..b3fa1e537720 100644 --- a/packages/schematics/angular/BUILD.bazel +++ b/packages/schematics/angular/BUILD.bazel @@ -72,6 +72,7 @@ ts_project( include = ["**/*.ts"], exclude = [ "**/*_spec.ts", + "refactor/jasmine-vitest/test-helpers.ts", # Also exclude templated files. "*/files/**/*.ts", "*/other-files/**/*.ts", @@ -114,6 +115,7 @@ ts_project( include = [ "**/*_spec.ts", "utility/test/**/*.ts", + "refactor/jasmine-vitest/test-helpers.ts", ], exclude = [ # NB: we need to exclude the nested node_modules that is laid out by yarn workspaces diff --git a/packages/schematics/angular/refactor/jasmine-vitest/test-file-transformer.ts b/packages/schematics/angular/refactor/jasmine-vitest/test-file-transformer.ts index c159f9bc3de2..2ba86669e687 100644 --- a/packages/schematics/angular/refactor/jasmine-vitest/test-file-transformer.ts +++ b/packages/schematics/angular/refactor/jasmine-vitest/test-file-transformer.ts @@ -74,19 +74,28 @@ export function transformJasmineToVitest( // Transform the node itself based on its type if (ts.isCallExpression(transformedNode)) { const transformations = [ + // **Stage 1: High-Level & Context-Sensitive Transformations** + // These transformers often wrap or fundamentally change the nature of the call, + // so they need to run before more specific matchers. transformWithContext, transformExpectAsync, - transformSyntacticSugarMatchers, transformFocusedAndSkippedTests, + transformPending, + transformDoneCallback, + + // **Stage 2: Core Matcher & Spy Transformations** + // This is the bulk of the `expect(...)` and `spyOn(...)` conversions. + transformSyntacticSugarMatchers, transformComplexMatchers, transformSpies, transformCreateSpyObj, transformSpyReset, transformSpyCallInspection, - transformPending, - transformDoneCallback, transformtoHaveBeenCalledBefore, transformToHaveClass, + + // **Stage 3: Global Functions & Cleanup** + // These handle global Jasmine functions and catch-alls for unsupported APIs. transformTimerMocks, transformGlobalFunctions, transformUnsupportedJasmineCalls, @@ -97,6 +106,7 @@ export function transformJasmineToVitest( } } else if (ts.isPropertyAccessExpression(transformedNode)) { const transformations = [ + // These transformers handle `jasmine.any()` and other `jasmine.*` properties. transformAsymmetricMatchers, transformSpyCallInspection, transformUnknownJasmineProperties, @@ -105,6 +115,8 @@ export function transformJasmineToVitest( transformedNode = transformer(transformedNode, refactorCtx); } } else if (ts.isExpressionStatement(transformedNode)) { + // Statement-level transformers are mutually exclusive. The first one that + // matches will be applied, and then the visitor will stop for this node. const statementTransformers = [ transformCalledOnceWith, transformArrayWithExactContents, @@ -130,7 +142,7 @@ export function transformJasmineToVitest( } }; - return (node) => ts.visitNode(node, visitor) as ts.SourceFile; + return (node) => ts.visitEachChild(node, visitor, context); }; const result = ts.transform(sourceFile, [transformer]); diff --git a/packages/schematics/angular/refactor/jasmine-vitest/test-file-transformer_spec.ts b/packages/schematics/angular/refactor/jasmine-vitest/test-file-transformer_spec.ts index ddb6c903d496..bc55811306b3 100644 --- a/packages/schematics/angular/refactor/jasmine-vitest/test-file-transformer_spec.ts +++ b/packages/schematics/angular/refactor/jasmine-vitest/test-file-transformer_spec.ts @@ -6,20 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import { logging } from '@angular-devkit/core'; -import { format } from 'prettier'; -import { transformJasmineToVitest } from './test-file-transformer'; -import { RefactorReporter } from './utils/refactor-reporter'; - -async function expectTransformation(input: string, expected: string): Promise { - const logger = new logging.NullLogger(); - const reporter = new RefactorReporter(logger); - const transformed = transformJasmineToVitest('spec.ts', input, reporter); - const formattedTransformed = await format(transformed, { parser: 'typescript' }); - const formattedExpected = await format(expected, { parser: 'typescript' }); - - expect(formattedTransformed).toBe(formattedExpected); -} +import { expectTransformation } from './test-helpers'; describe('Jasmine to Vitest Transformer', () => { describe('Nested Transformations', () => { @@ -164,7 +151,6 @@ describe('Jasmine to Vitest Transformer', () => { */ .mockReturnValue(true); `, - skipped: true, }, { description: 'should preserve a trailing comment on a matcher line', diff --git a/packages/schematics/angular/refactor/jasmine-vitest/test-helpers.ts b/packages/schematics/angular/refactor/jasmine-vitest/test-helpers.ts new file mode 100644 index 000000000000..bf162192ab2a --- /dev/null +++ b/packages/schematics/angular/refactor/jasmine-vitest/test-helpers.ts @@ -0,0 +1,34 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import { logging } from '@angular-devkit/core'; +import { format } from 'prettier'; +import { transformJasmineToVitest } from './test-file-transformer'; +import { RefactorReporter } from './utils/refactor-reporter'; + +/** + * A test helper to run the Jasmine to Vitest transformer on a given code + * snippet and compare it to an expected output. + * + * This function automatically handles the setup of a `RefactorReporter` and + * formats both the transformed and expected code using Prettier. This ensures + * that test comparisons are consistent and not affected by minor formatting + * differences. + * + * @param input The Jasmine code snippet to be transformed. + * @param expected The expected Vitest code snippet after transformation. + */ +export async function expectTransformation(input: string, expected: string): Promise { + const logger = new logging.NullLogger(); + const reporter = new RefactorReporter(logger); + const transformed = transformJasmineToVitest('spec.ts', input, reporter); + const formattedTransformed = await format(transformed, { parser: 'typescript' }); + const formattedExpected = await format(expected, { parser: 'typescript' }); + + expect(formattedTransformed).toBe(formattedExpected); +} diff --git a/packages/schematics/angular/refactor/jasmine-vitest/transformers/jasmine-lifecycle_spec.ts b/packages/schematics/angular/refactor/jasmine-vitest/transformers/jasmine-lifecycle_spec.ts index 3e91e7b74d30..d5f9e3231180 100644 --- a/packages/schematics/angular/refactor/jasmine-vitest/transformers/jasmine-lifecycle_spec.ts +++ b/packages/schematics/angular/refactor/jasmine-vitest/transformers/jasmine-lifecycle_spec.ts @@ -6,20 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import { logging } from '@angular-devkit/core'; -import { format } from 'prettier'; -import { transformJasmineToVitest } from '../test-file-transformer'; -import { RefactorReporter } from '../utils/refactor-reporter'; - -async function expectTransformation(input: string, expected: string): Promise { - const logger = new logging.NullLogger(); - const reporter = new RefactorReporter(logger); - const transformed = transformJasmineToVitest('spec.ts', input, reporter); - const formattedTransformed = await format(transformed, { parser: 'typescript' }); - const formattedExpected = await format(expected, { parser: 'typescript' }); - - expect(formattedTransformed).toBe(formattedExpected); -} +import { expectTransformation } from '../test-helpers'; describe('Jasmine to Vitest Transformer', () => { describe('transformDoneCallback', () => { 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 6bb4befbaced..69b0637254aa 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 @@ -6,20 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import { logging } from '@angular-devkit/core'; -import { format } from 'prettier'; -import { transformJasmineToVitest } from '../test-file-transformer'; -import { RefactorReporter } from '../utils/refactor-reporter'; - -async function expectTransformation(input: string, expected: string): Promise { - const logger = new logging.NullLogger(); - const reporter = new RefactorReporter(logger); - const transformed = transformJasmineToVitest('spec.ts', input, reporter); - const formattedTransformed = await format(transformed, { parser: 'typescript' }); - const formattedExpected = await format(expected, { parser: 'typescript' }); - - expect(formattedTransformed).toBe(formattedExpected); -} +import { expectTransformation } from '../test-helpers'; describe('Jasmine to Vitest Transformer', () => { describe('transformAsymmetricMatchers', () => { diff --git a/packages/schematics/angular/refactor/jasmine-vitest/transformers/jasmine-misc.ts b/packages/schematics/angular/refactor/jasmine-vitest/transformers/jasmine-misc.ts index dd6e27c63943..1a2c15b2c539 100644 --- a/packages/schematics/angular/refactor/jasmine-vitest/transformers/jasmine-misc.ts +++ b/packages/schematics/angular/refactor/jasmine-vitest/transformers/jasmine-misc.ts @@ -78,13 +78,15 @@ export function transformFail(node: ts.Node, { sourceFile, reporter }: RefactorC reporter.reportTransformation(sourceFile, node, 'Transformed `fail()` to `throw new Error()`.'); const reason = node.expression.arguments[0]; - return ts.factory.createThrowStatement( + const replacement = ts.factory.createThrowStatement( ts.factory.createNewExpression( ts.factory.createIdentifier('Error'), undefined, reason ? [reason] : [], ), ); + + return ts.setOriginalNode(ts.setTextRange(replacement, node), node); } return node; @@ -119,7 +121,7 @@ export function transformDefaultTimeoutInterval( ), ]); - return ts.factory.createExpressionStatement(setConfigCall); + return ts.factory.updateExpressionStatement(node, setConfigCall); } } diff --git a/packages/schematics/angular/refactor/jasmine-vitest/transformers/jasmine-misc_spec.ts b/packages/schematics/angular/refactor/jasmine-vitest/transformers/jasmine-misc_spec.ts index e23f140de382..5095a4448db7 100644 --- a/packages/schematics/angular/refactor/jasmine-vitest/transformers/jasmine-misc_spec.ts +++ b/packages/schematics/angular/refactor/jasmine-vitest/transformers/jasmine-misc_spec.ts @@ -6,20 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import { logging } from '@angular-devkit/core'; -import { format } from 'prettier'; -import { transformJasmineToVitest } from '../test-file-transformer'; -import { RefactorReporter } from '../utils/refactor-reporter'; - -async function expectTransformation(input: string, expected: string): Promise { - const logger = new logging.NullLogger(); - const reporter = new RefactorReporter(logger); - const transformed = transformJasmineToVitest('spec.ts', input, reporter); - const formattedTransformed = await format(transformed, { parser: 'typescript' }); - const formattedExpected = await format(expected, { parser: 'typescript' }); - - expect(formattedTransformed).toBe(formattedExpected); -} +import { expectTransformation } from '../test-helpers'; describe('Jasmine to Vitest Transformer', () => { describe('transformTimerMocks', () => { 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 a3f95e7786d1..759a93301438 100644 --- a/packages/schematics/angular/refactor/jasmine-vitest/transformers/jasmine-spy.ts +++ b/packages/schematics/angular/refactor/jasmine-vitest/transformers/jasmine-spy.ts @@ -160,7 +160,12 @@ export function transformSpies(node: ts.Node, refactorCtx: RefactorContext): ts. node, `Transformed spy strategy \`.and.${strategyName}()\` to \`.${newMethodName}()\`.`, ); - const newExpression = createPropertyAccess(spyCall, newMethodName); + + const newExpression = ts.factory.updatePropertyAccessExpression( + pae, + spyCall, + ts.factory.createIdentifier(newMethodName), + ); return ts.factory.updateCallExpression( 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 7833f6310496..c84b35c422ac 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 @@ -6,20 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import { logging } from '@angular-devkit/core'; -import { format } from 'prettier'; -import { transformJasmineToVitest } from '../test-file-transformer'; -import { RefactorReporter } from '../utils/refactor-reporter'; - -async function expectTransformation(input: string, expected: string): Promise { - const logger = new logging.NullLogger(); - const reporter = new RefactorReporter(logger); - const transformed = transformJasmineToVitest('spec.ts', input, reporter); - const formattedTransformed = await format(transformed, { parser: 'typescript' }); - const formattedExpected = await format(expected, { parser: 'typescript' }); - - expect(formattedTransformed).toBe(formattedExpected); -} +import { expectTransformation } from '../test-helpers'; describe('Jasmine to Vitest Transformer', () => { describe('transformSpies', () => {