diff --git a/packages/schematics/angular/refactor/jasmine-vitest/index.ts b/packages/schematics/angular/refactor/jasmine-vitest/index.ts index a7e34ad26c02..2498855c4607 100644 --- a/packages/schematics/angular/refactor/jasmine-vitest/index.ts +++ b/packages/schematics/angular/refactor/jasmine-vitest/index.ts @@ -121,6 +121,7 @@ export default function (options: Schema): Rule { const content = tree.readText(file); const newContent = transformJasmineToVitest(file, content, reporter, { addImports: !!options.addImports, + browserMode: !!options.browerMode, }); if (content !== newContent) { diff --git a/packages/schematics/angular/refactor/jasmine-vitest/schema.json b/packages/schematics/angular/refactor/jasmine-vitest/schema.json index 99f34057ffb5..cb361280e473 100644 --- a/packages/schematics/angular/refactor/jasmine-vitest/schema.json +++ b/packages/schematics/angular/refactor/jasmine-vitest/schema.json @@ -30,6 +30,11 @@ "type": "boolean", "description": "Whether to add imports for the Vitest API. The Angular `unit-test` system automatically uses the Vitest globals option, which means explicit imports for global APIs like `describe`, `it`, `expect`, and `vi` are often not strictly necessary unless Vitest has been configured not to use globals.", "default": false + }, + "browserMode": { + "type": "boolean", + "description": "Whether the tests are intended to run in browser mode. If true, the `toHaveClass` assertions are left as is because Vitest browser mode has such an assertion. Otherwise they're migrated to an equivalent assertion.", + "default": false } } } diff --git a/packages/schematics/angular/refactor/jasmine-vitest/test-file-transformer.integration_spec.ts b/packages/schematics/angular/refactor/jasmine-vitest/test-file-transformer.integration_spec.ts index 8944e34dfa14..079718212d84 100644 --- a/packages/schematics/angular/refactor/jasmine-vitest/test-file-transformer.integration_spec.ts +++ b/packages/schematics/angular/refactor/jasmine-vitest/test-file-transformer.integration_spec.ts @@ -11,10 +11,17 @@ import { format } from 'prettier'; import { transformJasmineToVitest } from './test-file-transformer'; import { RefactorReporter } from './utils/refactor-reporter'; -async function expectTransformation(input: string, expected: string): Promise { +async function expectTransformation( + input: string, + expected: string, + options: { addImports: boolean; browserMode: boolean } = { + addImports: false, + browserMode: false, + }, +): Promise { const logger = new logging.NullLogger(); const reporter = new RefactorReporter(logger); - const transformed = transformJasmineToVitest('spec.ts', input, reporter, { addImports: false }); + const transformed = transformJasmineToVitest('spec.ts', input, reporter, options); const formattedTransformed = await format(transformed, { parser: 'typescript' }); const formattedExpected = await format(expected, { parser: 'typescript' }); @@ -389,6 +396,44 @@ describe('Jasmine to Vitest Transformer - Integration Tests', () => { await expectTransformation(jasmineCode, vitestCode); }); + it('should not transform toHaveClass in browser mode', async () => { + const jasmineCode = ` + describe('toHaveClass in browser mode', () => { + let el: HTMLElement; + + beforeEach(() => { + el = document.createElement('div'); + }); + + it('should handle DOM matchers like toHaveClass', () => { + el.classList.add('my-class'); + expect(el).withContext('element should have my-class').toHaveClass('my-class'); + el.classList.remove('my-class'); + expect(el).not.toHaveClass('my-class'); + }); + }); + `; + + const vitestCode = ` + describe('toHaveClass in browser mode', () => { + let el: HTMLElement; + + beforeEach(() => { + el = document.createElement('div'); + }); + + it('should handle DOM matchers like toHaveClass', () => { + el.classList.add('my-class'); + expect(el, 'element should have my-class').toHaveClass('my-class'); + el.classList.remove('my-class'); + expect(el).not.toHaveClass('my-class'); + }); + }); + `; + + await expectTransformation(jasmineCode, vitestCode, { addImports: false, browserMode: true }); + }); + it('should add TODOs for unsupported Jasmine features', async () => { const jasmineCode = ` describe('Unsupported Features', () => { 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 eb8afdfe0449..cc9563bd3e15 100644 --- a/packages/schematics/angular/refactor/jasmine-vitest/test-file-transformer.ts +++ b/packages/schematics/angular/refactor/jasmine-vitest/test-file-transformer.ts @@ -152,7 +152,7 @@ export function transformJasmineToVitest( filePath: string, content: string, reporter: RefactorReporter, - options: { addImports: boolean }, + options: { addImports: boolean; browserMode: boolean }, ): string { const contentWithPlaceholders = preserveBlankLines(content); @@ -189,7 +189,9 @@ export function transformJasmineToVitest( } for (const transformer of callExpressionTransformers) { - transformedNode = transformer(transformedNode, refactorCtx); + if (!(options.browserMode && transformer === transformToHaveClass)) { + transformedNode = transformer(transformedNode, refactorCtx); + } } } else if (ts.isPropertyAccessExpression(transformedNode)) { for (const transformer of propertyAccessExpressionTransformers) { diff --git a/packages/schematics/angular/refactor/jasmine-vitest/test-helpers.ts b/packages/schematics/angular/refactor/jasmine-vitest/test-helpers.ts index a93875b912e9..9aa6532206da 100644 --- a/packages/schematics/angular/refactor/jasmine-vitest/test-helpers.ts +++ b/packages/schematics/angular/refactor/jasmine-vitest/test-helpers.ts @@ -30,7 +30,10 @@ export async function expectTransformation( ): Promise { const logger = new logging.NullLogger(); const reporter = new RefactorReporter(logger); - const transformed = transformJasmineToVitest('spec.ts', input, reporter, { addImports }); + const transformed = transformJasmineToVitest('spec.ts', input, reporter, { + addImports, + browserMode: false, + }); const formattedTransformed = await format(transformed, { parser: 'typescript' }); const formattedExpected = await format(expected, { parser: 'typescript' });