Skip to content

Commit fe6dcba

Browse files
committed
feat(@schematics/angular): stabilize refactor-jasmine-vitest schematic
Stabilize `refactor-jasmine-vitest` schematic by covering the known remaining test patterns and cases.
1 parent 7fbc715 commit fe6dcba

File tree

11 files changed

+605
-130
lines changed

11 files changed

+605
-130
lines changed

packages/schematics/angular/refactor/jasmine-vitest/test-file-transformer.integration_spec.ts

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,7 @@ describe('Jasmine to Vitest Transformer - Integration Tests', () => {
243243
244244
it('should handle spy call order', () => {
245245
const spyA = jasmine.createSpy('spyA');
246-
const spyB = jasmine.createSpy('spyB');
246+
const spyB = jasmine.createSpy();
247247
spyA();
248248
spyB();
249249
expect(spyA).toHaveBeenCalledBefore(spyB);
@@ -288,7 +288,7 @@ describe('Jasmine to Vitest Transformer - Integration Tests', () => {
288288
});
289289
290290
it('should handle spy call order', () => {
291-
const spyA = vi.fn();
291+
const spyA = vi.fn().mockName('spyA');
292292
const spyB = vi.fn();
293293
spyA();
294294
spyB();
@@ -340,12 +340,28 @@ describe('Jasmine to Vitest Transformer - Integration Tests', () => {
340340
}
341341
});
342342
343+
it('should handle fail() with a specific error', () => {
344+
try {
345+
expect(1).toBe(2);
346+
fail(new Error('Expected test to fail'));
347+
} catch (err) {
348+
expect(err.message).toBe('1 !== 2');
349+
}
350+
});
351+
343352
it('should handle spyOnProperty', () => {
344353
const obj = { get myProp() { return 'original'; } };
345354
spyOnProperty(obj, 'myProp', 'get').and.returnValue('mocked');
346355
expect(obj.myProp).toBe('mocked');
347356
});
348357
358+
it('should handle async spies', async () => {
359+
const ctx = { myMethod: () => Promise.resolve('real') };
360+
spyOn(ctx, 'myMethod').and.returnValue(Promise.resolve('mocked'));
361+
362+
expect(await ctx.myMethod()).toBe('mocked');
363+
});
364+
349365
it('should handle spies throwing errors', () => {
350366
const spy = jasmine.createSpy('mySpy').and.throwError('Test Error');
351367
expect(() => spy()).toThrowError('Test Error');
@@ -380,14 +396,30 @@ describe('Jasmine to Vitest Transformer - Integration Tests', () => {
380396
}
381397
});
382398
399+
it('should handle fail() with a specific error', () => {
400+
try {
401+
expect(1).toBe(2);
402+
throw new Error('Expected test to fail');
403+
} catch (err) {
404+
expect(err.message).toBe('1 !== 2');
405+
}
406+
});
407+
383408
it('should handle spyOnProperty', () => {
384409
const obj = { get myProp() { return 'original'; } };
385410
vi.spyOn(obj, 'myProp', 'get').mockReturnValue('mocked');
386411
expect(obj.myProp).toBe('mocked');
387412
});
388413
414+
it('should handle async spies', async () => {
415+
const ctx = { myMethod: () => Promise.resolve('real') };
416+
vi.spyOn(ctx, 'myMethod').mockResolvedValue('mocked');
417+
418+
expect(await ctx.myMethod()).toBe('mocked');
419+
});
420+
389421
it('should handle spies throwing errors', () => {
390-
const spy = vi.fn().mockImplementation(() => { throw new Error('Test Error') });
422+
const spy = vi.fn().mockName('mySpy').mockImplementation(() => { throw new Error('Test Error') });
391423
expect(() => spy()).toThrowError('Test Error');
392424
});
393425
});
@@ -438,6 +470,8 @@ describe('Jasmine to Vitest Transformer - Integration Tests', () => {
438470
const jasmineCode = `
439471
describe('Unsupported Features', () => {
440472
beforeAll(() => {
473+
jasmine.MAX_PRETTY_PRINT_CHARS = 100;
474+
441475
jasmine.addMatchers({
442476
toBeAwesome: () => ({
443477
compare: (actual) => ({ pass: actual === 'awesome' })
@@ -467,6 +501,8 @@ describe('Jasmine to Vitest Transformer - Integration Tests', () => {
467501
const vitestCode = `
468502
describe('Unsupported Features', () => {
469503
beforeAll(() => {
504+
// TODO: vitest-migration: jasmine.MAX_PRETTY_PRINT_CHARS is not supported.
505+
// jasmine.MAX_PRETTY_PRINT_CHARS = 100;
470506
// TODO: vitest-migration: jasmine.addMatchers is not supported. Please manually migrate to expect.extend(). See: https://vitest.dev/api/expect.html#expect-extend
471507
jasmine.addMatchers({
472508
toBeAwesome: () => ({

packages/schematics/angular/refactor/jasmine-vitest/test-file-transformer.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,19 +27,21 @@ import {
2727
transformExpectAsync,
2828
transformExpectNothing,
2929
transformSyntacticSugarMatchers,
30+
transformToBeNullish,
31+
transformToHaveBeenCalledBefore,
3032
transformToHaveClass,
3133
transformWithContext,
32-
transformtoHaveBeenCalledBefore,
3334
} from './transformers/jasmine-matcher';
3435
import {
35-
transformDefaultTimeoutInterval,
3636
transformFail,
37-
transformGlobalFunctions,
37+
transformJasmineMembers,
3838
transformTimerMocks,
3939
transformUnknownJasmineProperties,
40+
transformUnsupportedGlobalFunctions,
4041
transformUnsupportedJasmineCalls,
4142
} from './transformers/jasmine-misc';
4243
import {
44+
transformCreateSpy,
4345
transformCreateSpyObj,
4446
transformSpies,
4547
transformSpyCallInspection,
@@ -116,16 +118,18 @@ const callExpressionTransformers = [
116118
transformSyntacticSugarMatchers,
117119
transformComplexMatchers,
118120
transformSpies,
121+
transformCreateSpy,
119122
transformCreateSpyObj,
120123
transformSpyReset,
121124
transformSpyCallInspection,
122-
transformtoHaveBeenCalledBefore,
125+
transformToHaveBeenCalledBefore,
123126
transformToHaveClass,
127+
transformToBeNullish,
124128

125129
// **Stage 3: Global Functions & Cleanup**
126130
// These handle global Jasmine functions and catch-alls for unsupported APIs.
127131
transformTimerMocks,
128-
transformGlobalFunctions,
132+
transformUnsupportedGlobalFunctions,
129133
transformUnsupportedJasmineCalls,
130134
];
131135

@@ -149,7 +153,7 @@ const expressionStatementTransformers = [
149153
transformArrayWithExactContents,
150154
transformExpectNothing,
151155
transformFail,
152-
transformDefaultTimeoutInterval,
156+
transformJasmineMembers,
153157
];
154158

155159
/**

packages/schematics/angular/refactor/jasmine-vitest/test-file-transformer_spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ describe('Jasmine to Vitest Transformer - Nested Transformations', () => {
2828
await expectAsync(service.myProp).toBeResolvedTo(42);
2929
`,
3030
expected: `
31-
vi.spyOn(service, 'myProp', 'get').mockReturnValue(Promise.resolve(42));
31+
vi.spyOn(service, 'myProp', 'get').mockResolvedValue(42);
3232
await expect(service.myProp).resolves.toEqual(42);
3333
`,
3434
},

packages/schematics/angular/refactor/jasmine-vitest/transformers/jasmine-matcher.ts

Lines changed: 72 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ export function transformAsymmetricMatchers(
121121
return node;
122122
}
123123

124-
export function transformtoHaveBeenCalledBefore(
124+
export function transformToHaveBeenCalledBefore(
125125
node: ts.Node,
126126
{ sourceFile, reporter }: RefactorContext,
127127
): ts.Node {
@@ -205,7 +205,7 @@ export function transformToHaveClass(
205205
expectExpression = expectExpression.expression;
206206
}
207207

208-
if (matcherName !== 'toHaveClass') {
208+
if (matcherName !== 'toHaveClass' || !ts.isCallExpression(expectExpression)) {
209209
return node;
210210
}
211211

@@ -218,21 +218,17 @@ export function transformToHaveClass(
218218
const [className] = node.arguments;
219219
const newExpectArgs: ts.Expression[] = [];
220220

221-
if (ts.isCallExpression(expectExpression)) {
222-
const [element] = expectExpression.arguments;
223-
const classListContains = ts.factory.createCallExpression(
224-
createPropertyAccess(createPropertyAccess(element, 'classList'), 'contains'),
225-
undefined,
226-
[className],
227-
);
228-
newExpectArgs.push(classListContains);
221+
const [element] = expectExpression.arguments;
222+
const classListContains = ts.factory.createCallExpression(
223+
createPropertyAccess(createPropertyAccess(element, 'classList'), 'contains'),
224+
undefined,
225+
[className],
226+
);
227+
newExpectArgs.push(classListContains);
229228

230-
// Pass the context message from withContext to the new expect call
231-
if (expectExpression.arguments.length > 1) {
232-
newExpectArgs.push(expectExpression.arguments[1]);
233-
}
234-
} else {
235-
return node;
229+
// Pass the context message from withContext to the new expect call
230+
if (expectExpression.arguments.length > 1) {
231+
newExpectArgs.push(expectExpression.arguments[1]);
236232
}
237233

238234
const newExpect = createExpectCallExpression(newExpectArgs);
@@ -626,3 +622,63 @@ export function transformExpectNothing(
626622

627623
return replacement;
628624
}
625+
626+
export function transformToBeNullish(
627+
node: ts.Node,
628+
{ sourceFile, reporter }: RefactorContext,
629+
): ts.Node {
630+
if (
631+
!ts.isCallExpression(node) ||
632+
!ts.isPropertyAccessExpression(node.expression) ||
633+
node.arguments.length !== 0
634+
) {
635+
return node;
636+
}
637+
638+
const pae = node.expression;
639+
const matcherName = pae.name.text;
640+
let isNegated = false;
641+
642+
let expectExpression = pae.expression;
643+
if (ts.isPropertyAccessExpression(expectExpression) && expectExpression.name.text === 'not') {
644+
isNegated = true;
645+
expectExpression = expectExpression.expression;
646+
}
647+
648+
if (matcherName !== 'toBeNullish' || !ts.isCallExpression(expectExpression)) {
649+
return node;
650+
}
651+
652+
reporter.reportTransformation(
653+
sourceFile,
654+
node,
655+
'Transformed `.toBeNullish()` to a `element === null || element === undefined` check.',
656+
);
657+
658+
const element = expectExpression.arguments[0];
659+
660+
const nullCheck = ts.factory.createBinaryExpression(
661+
element,
662+
ts.SyntaxKind.EqualsEqualsEqualsToken,
663+
ts.factory.createNull(),
664+
);
665+
666+
const undefinedCheck = ts.factory.createBinaryExpression(
667+
element,
668+
ts.SyntaxKind.EqualsEqualsEqualsToken,
669+
ts.factory.createIdentifier('undefined'),
670+
);
671+
672+
const fullExpression = ts.factory.createBinaryExpression(
673+
nullCheck,
674+
ts.SyntaxKind.BarBarToken,
675+
undefinedCheck,
676+
);
677+
678+
const newExpect = createExpectCallExpression([fullExpression]);
679+
const newMatcher = isNegated ? ts.factory.createFalse() : ts.factory.createTrue();
680+
681+
return ts.factory.createCallExpression(createPropertyAccess(newExpect, 'toBe'), undefined, [
682+
newMatcher,
683+
]);
684+
}

packages/schematics/angular/refactor/jasmine-vitest/transformers/jasmine-matcher_spec.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -356,3 +356,24 @@ describe('transformToHaveClass', () => {
356356
});
357357
});
358358
});
359+
360+
describe('transformToBeNullish', () => {
361+
const testCases = [
362+
{
363+
description: 'should transform toBeNullish',
364+
input: `expect(element).toBeNullish();`,
365+
expected: `expect(element === null || element === undefined).toBe(true);`,
366+
},
367+
{
368+
description: 'should transform not.toBeNullish',
369+
input: `expect(element).not.toBeNullish();`,
370+
expected: `expect(element === null || element === undefined).toBe(false);`,
371+
},
372+
];
373+
374+
testCases.forEach(({ description, input, expected }) => {
375+
it(description, async () => {
376+
await expectTransformation(input, expected);
377+
});
378+
});
379+
});

0 commit comments

Comments
 (0)