Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -392,14 +392,14 @@ describe('Jasmine to Vitest Transformer - Integration Tests', () => {

it('should handle fail()', () => {
if (true) {
throw new Error('This should not have happened');
expect.fail('This should not have happened');
}
});

it('should handle fail() with a specific error', () => {
try {
expect(1).toBe(2);
throw new Error('Expected test to fail');
expect.fail('Expected test to fail');
} catch (err) {
expect(err.message).toBe('1 !== 2');
}
Comment thread
clydin marked this conversation as resolved.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ const callExpressionTransformers = [

// **Stage 3: Global Functions & Cleanup**
// These handle global Jasmine functions and catch-alls for unsupported APIs.
transformFail,
transformTimerMocks,
transformUnsupportedGlobalFunctions,
transformUnsupportedJasmineCalls,
Expand All @@ -168,7 +169,6 @@ const expressionStatementTransformers = [
transformCalledOnceWith,
transformArrayWithExactContents,
transformExpectNothing,
transformFail,
transformJasmineMembers,
];

Expand Down Expand Up @@ -227,18 +227,16 @@ export function transformJasmineToVitest(
}

for (const transformer of callExpressionTransformers) {
if (
!(
(options.browserMode && transformer === transformToHaveClass) ||
(options.fakeAsync === false &&
[
transformFakeAsyncFlush,
transformFakeAsyncFlushMicrotasks,
transformFakeAsyncTick,
transformFakeAsyncTest,
].includes(transformer))
)
) {
if (!(
(options.browserMode && transformer === transformToHaveClass) ||
(options.fakeAsync === false &&
[
transformFakeAsyncFlush,
transformFakeAsyncFlushMicrotasks,
transformFakeAsyncTick,
transformFakeAsyncTest,
].includes(transformer))
)) {
transformedNode = transformer(transformedNode, refactorCtx);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -178,4 +178,24 @@ describe('Jasmine to Vitest Transformer - addImports option', () => {
`;
await expectTransformation(input, expected, true);
});

it('should add import for `expect` when `fail()` is used and addImports is true', async () => {
const input = `
describe('My Suite', () => {
it('fails', () => {
fail('Something went wrong');
});
});
`;
const expected = `
import { describe, expect, it } from 'vitest';

describe('My Suite', () => {
it('fails', () => {
expect.fail('Something went wrong');
});
});
`;
await expectTransformation(input, expected, true);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -90,29 +90,53 @@ export function transformTimerMocks(node: ts.Node, ctx: RefactorContext): ts.Nod
return node;
}

export function transformFail(node: ts.Node, { sourceFile, reporter }: RefactorContext): ts.Node {
export function transformFail(
node: ts.Node,
{ sourceFile, reporter, pendingVitestValueImports }: RefactorContext,
): ts.Node {
if (
ts.isExpressionStatement(node) &&
ts.isCallExpression(node.expression) &&
ts.isIdentifier(node.expression.expression) &&
node.expression.expression.text === 'fail'
ts.isCallExpression(node) &&
ts.isIdentifier(node.expression) &&
node.expression.text === 'fail'
) {
reporter.reportTransformation(sourceFile, node, 'Transformed `fail()` to `throw new Error()`.');

const arg = node.expression.arguments[0];
let throwExpression: ts.Expression;

if (arg && ts.isNewExpression(arg)) {
throwExpression = arg;
} else {
throwExpression = ts.factory.createNewExpression(
ts.factory.createIdentifier('Error'),
undefined,
arg ? [arg] : [],
);
addVitestValueImport(pendingVitestValueImports, 'expect');
reporter.reportTransformation(sourceFile, node, 'Transformed `fail()` to `expect.fail()`.');

const arg = node.arguments[0];
let replacementArg: ts.Expression | undefined = arg;
let hasNonStringArg = false;

if (arg) {
if (ts.isNewExpression(arg)) {
replacementArg = arg.arguments && arg.arguments.length > 0 ? arg.arguments[0] : undefined;
} else if (
!ts.isStringLiteral(arg) &&
!ts.isNoSubstitutionTemplateLiteral(arg) &&
!ts.isTemplateExpression(arg)
) {
replacementArg = ts.factory.createCallExpression(
ts.factory.createIdentifier('String'),
undefined,
[arg],
);
hasNonStringArg = true;
}
}

const replacement = ts.factory.createThrowStatement(throwExpression);
const replacement = ts.factory.createCallExpression(
ts.factory.createPropertyAccessExpression(
ts.factory.createIdentifier('expect'),
ts.factory.createIdentifier('fail'),
),
undefined,
replacementArg ? [replacementArg] : [],
);
Comment thread
clydin marked this conversation as resolved.

if (hasNonStringArg) {
const category = 'fail-non-string-argument';
reporter.recordTodo(category, sourceFile, node);
addTodoComment(replacement, category);
}

return ts.setOriginalNode(ts.setTextRange(replacement, node), node);
}
Expand Down Expand Up @@ -197,11 +221,7 @@ const UNSUPPORTED_GLOBAL_FUNCTION_CATEGORIES = new Set<TodoCategory>([
function isUnsupportedGlobalFunction(
methodName: string,
): methodName is
| 'setSpecProperty'
| 'setSuiteProperty'
| 'throwUnless'
| 'throwUnlessAsync'
| 'getSpecProperty' {
'setSpecProperty' | 'setSuiteProperty' | 'throwUnless' | 'throwUnlessAsync' | 'getSpecProperty' {
return UNSUPPORTED_GLOBAL_FUNCTION_CATEGORIES.has(methodName as TodoCategory);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,19 +59,31 @@ jasmine.clock().withMock(noop);`,
describe('transformFail', () => {
const testCases = [
{
description: 'should transform fail() to throw new Error()',
description: 'should transform fail() to expect.fail()',
input: `fail('This should not happen');`,
expected: `throw new Error('This should not happen');`,
expected: `expect.fail('This should not happen');`,
},
{
description: 'should transform fail() without a message to throw new Error()',
description: 'should transform fail() without a message to expect.fail()',
input: `fail();`,
expected: `throw new Error();`,
expected: `expect.fail();`,
},
{
description: 'should transform fail() with an Error object',
input: `fail(new TypeError('Invalid input'));`,
expected: `throw new TypeError('Invalid input');`,
expected: `expect.fail('Invalid input');`,
},
{
description: 'should transform fail() with an empty Error object',
input: `fail(new Error());`,
expected: `expect.fail();`,
},
{
description: 'should transform fail() with a non-string argument and add a TODO note',
input: `fail(err);`,
// eslint-disable-next-line max-len
expected: `// TODO: vitest-migration: expect.fail() only accepts a string message. Verify that converting this argument with String() produces the expected failure output. See: https://vitest.dev/api/expect.html#expect-fail
expect.fail(String(err));`,
},
Comment thread
clydin marked this conversation as resolved.
];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,12 @@ export const TODO_NOTES = {
message:
'expect().nothing() has been removed because it is redundant in Vitest. Tests without assertions pass by default.',
},
'fail-non-string-argument': {
message:
'expect.fail() only accepts a string message. ' +
'Verify that converting this argument with String() produces the expected failure output.',
url: 'https://vitest.dev/api/expect.html#expect-fail',
},
'unsupported-jasmine-member': {
message: (context: { name: string }): string => `jasmine.${context.name} is not supported.`,
},
Expand Down