Skip to content

Commit

Permalink
feat(linter): eslint-plugin-vitest/expect-expect (#4299)
Browse files Browse the repository at this point in the history
support
[eslint-plugin-vitest/expect-expect](https://github.com/veritem/eslint-plugin-vitest/blob/main/src/rules/expect-expect.ts)

---------

Co-authored-by: wenzhe <mysteryven@gmail.com>
  • Loading branch information
eryue0220 and mysteryven authored Jul 18, 2024
1 parent ed49e16 commit acc5729
Show file tree
Hide file tree
Showing 3 changed files with 371 additions and 11 deletions.
285 changes: 275 additions & 10 deletions crates/oxc_linter/src/rules/jest/expect_expect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@ use crate::{
context::LintContext,
rule::Rule,
utils::{
collect_possible_jest_call_node, get_node_name, is_type_of_jest_fn_call, JestFnKind,
JestGeneralFnKind, PossibleJestNode,
collect_possible_jest_call_node, get_node_name, get_test_plugin_name,
is_type_of_jest_fn_call, JestFnKind, JestGeneralFnKind, PossibleJestNode, TestPluginName,
},
};

fn expect_expect_diagnostic(span0: Span) -> OxcDiagnostic {
OxcDiagnostic::warn("eslint-plugin-jest(expect-expect): Test has no assertions")
fn expect_expect_diagnostic(x0: TestPluginName, span0: Span) -> OxcDiagnostic {
OxcDiagnostic::warn(format!("{x0}(expect-expect): Test has no assertions"))
.with_help("Add assertion(s) in this Test")
.with_label(span0)
}
Expand Down Expand Up @@ -67,6 +67,17 @@ declare_oxc_lint!(
/// });
/// test('should assert something', () => {});
/// ```
///
/// This rule is compatible with [eslint-plugin-vitest](https://github.com/veritem/eslint-plugin-vitest/blob/main/docs/rules/expect-expect.md),
/// to use it, add the following configuration to your `.eslintrc.json`:
///
/// ```json
/// {
/// "rules": {
/// "vitest/expect-expect": "error"
/// }
/// }
/// ```
ExpectExpect,
correctness
);
Expand Down Expand Up @@ -111,6 +122,7 @@ fn run<'a>(
) {
let node = possible_jest_node.node;
if let AstKind::CallExpression(call_expr) = node.kind() {
let plugin_name = get_test_plugin_name(ctx);
let name = get_node_name(&call_expr.callee);
if is_type_of_jest_fn_call(
call_expr,
Expand All @@ -126,6 +138,9 @@ fn run<'a>(
if property_name == "todo" {
return;
}
if property_name == "skip" && plugin_name.eq(&TestPluginName::Vitest) {
return;
}
}

// Record visited nodes to avoid infinite loop.
Expand All @@ -135,7 +150,7 @@ fn run<'a>(
check_arguments(call_expr, &rule.assert_function_names, &mut visited, ctx);

if !has_assert_function {
ctx.diagnostic(expect_expect_diagnostic(call_expr.callee.span()));
ctx.diagnostic(expect_expect_diagnostic(plugin_name, call_expr.callee.span()));
}
}
}
Expand Down Expand Up @@ -271,7 +286,7 @@ fn convert_pattern(pattern: &str) -> String {
fn test() {
use crate::tester::Tester;

let pass = vec![
let mut pass = vec![
("it.todo('will test something eventually')", None),
("test.todo('will test something eventually')", None),
("['x']();", None),
Expand Down Expand Up @@ -330,8 +345,8 @@ fn test() {
(
"
theoretically('the number {input} is correctly translated to string', theories, theory => {
const output = NumberToLongString(theory.input);
expect(output).toBe(theory.expected);
const output = NumberToLongString(theory.input);
expect(output).toBe(theory.expected);
})
",
Some(serde_json::json!([{ "additionalTestBlockFunctions": ["theoretically"] }])),
Expand Down Expand Up @@ -394,7 +409,7 @@ fn test() {
),
];

let fail = vec![
let mut fail = vec![
("it(\"should fail\", () => {});", None),
("it(\"should fail\", myTest); function myTest() {}", None),
("test(\"should fail\", () => {});", None),
Expand Down Expand Up @@ -486,5 +501,255 @@ fn test() {
),
];

Tester::new(ExpectExpect::NAME, pass, fail).with_jest_plugin(true).test_and_snapshot();
let pass_vitest = vec![
(
"
import { test } from 'vitest';
test.skip(\"skipped test\", () => {})
",
None,
),
("it.todo(\"will test something eventually\")", None),
("test.todo(\"will test something eventually\")", None),
("['x']();", None),
("it(\"should pass\", () => expect(true).toBeDefined())", None),
("test(\"should pass\", () => expect(true).toBeDefined())", None),
("it(\"should pass\", () => somePromise().then(() => expect(true).toBeDefined()))", None),
("it(\"should pass\", myTest); function myTest() { expect(true).toBeDefined() }", None),
(
"
test('should pass', () => {
expect(true).toBeDefined();
foo(true).toBe(true);
});
",
Some(serde_json::json!([{ "assertFunctionNames": ["expect", "foo"] }]))
),
(
"
import { bench } from 'vitest'
bench('normal sorting', () => {
const x = [1, 5, 4, 2, 3]
x.sort((a, b) => {
return a - b
})
}, { time: 1000 })
",
None,
),
(
"it(\"should return undefined\", () => expectSaga(mySaga).returns());",
Some(serde_json::json!([{ "assertFunctionNames": ["expectSaga"] }])),
),
(
"test('verifies expect method call', () => expect$(123));",
Some(serde_json::json!([{ "assertFunctionNames": ["expect\\$"] }])),
),
(
"test('verifies expect method call', () => new Foo().expect(123));",
Some(serde_json::json!([{ "assertFunctionNames": ["Foo.expect"] }])),
),
(
"
test('verifies deep expect method call', () => {
tester.foo().expect(123);
});
",
Some(serde_json::json!([{ "assertFunctionNames": ["tester.foo.expect"] }])),
),
(
"
test('verifies chained expect method call', () => {
tester
.foo()
.bar()
.expect(456);
});
",
Some(serde_json::json!([{ "assertFunctionNames": ["tester.foo.bar.expect"] }])),
),
(
"
test(\"verifies the function call\", () => {
td.verify(someFunctionCall())
})
",
Some(serde_json::json!([{ "assertFunctionNames": ["td.verify"] }])),
),
(
"it(\"should pass\", () => expect(true).toBeDefined())",
Some(serde_json::json!([{
"assertFunctionNames": "undefined",
"additionalTestBlockFunctions": "undefined",
}])),
),
(
"
theoretically('the number {input} is correctly translated to string', theories, theory => {
const output = NumberToLongString(theory.input);
expect(output).toBe(theory.expected);
})
",
Some(serde_json::json!([{ "additionalTestBlockFunctions": ["theoretically"] }])),
),
(
"test('should pass *', () => expect404ToBeLoaded());",
Some(serde_json::json!([{ "assertFunctionNames": ["expect*"] }])),
),
(
"test('should pass *', () => expect.toHaveStatus404());",
Some(serde_json::json!([{ "assertFunctionNames": ["expect.**"] }])),
),
(
"test('should pass', () => tester.foo().expect(123));",
Some(serde_json::json!([{ "assertFunctionNames": ["tester.*.expect"] }])),
),
(
"test('should pass **', () => tester.foo().expect(123));",
Some(serde_json::json!([{ "assertFunctionNames": ["**"] }])),
),
(
"test('should pass *', () => tester.foo().expect(123));",
Some(serde_json::json!([{ "assertFunctionNames": ["*"] }])),
),
(
"test('should pass', () => tester.foo().expect(123));",
Some(serde_json::json!([{ "assertFunctionNames": ["tester.**"] }])),
),
(
"test('should pass', () => tester.foo().expect(123));",
Some(serde_json::json!([{ "assertFunctionNames": ["tester.*"] }])),
),
(
"test('should pass', () => tester.foo().bar().expectIt(456));",
Some(serde_json::json!([{ "assertFunctionNames": ["tester.**.expect*"] }])),
),
(
"test('should pass', () => request.get().foo().expect(456));",
Some(serde_json::json!([{ "assertFunctionNames": ["request.**.expect"] }])),
),
(
"test('should pass', () => request.get().foo().expect(456));",
Some(serde_json::json!([{ "assertFunctionNames": ["request.**.e*e*t"] }])),
),
(
"
import { test } from 'vitest';
test('should pass', () => {
expect(true).toBeDefined();
foo(true).toBe(true);
});
",
Some(serde_json::json!([{ "assertFunctionNames": ["expect", "foo"] }])),
),
(
"
import { test as checkThat } from 'vitest';
checkThat('this passes', () => {
expect(true).toBeDefined();
foo(true).toBe(true);
});
",
Some(serde_json::json!([{ "assertFunctionNames": ["expect", "foo"] }])),
),
(
"
const { test } = require('vitest');
test('verifies chained expect method call', () => {
tester
.foo()
.bar()
.expect(456);
});
",
Some(serde_json::json!([{ "assertFunctionNames": ["tester.foo.bar.expect"] }])),
),
(
"
it(\"should pass with 'typecheck' enabled\", () => {
expectTypeOf({ a: 1 }).toEqualTypeOf<{ a: number }>()
});
",
None
),
];

let fail_vitest = vec![
("it(\"should fail\", () => {});", None),
("it(\"should fail\", myTest); function myTest() {}", None),
("test(\"should fail\", () => {});", None),
(
"afterEach(() => {});",
Some(serde_json::json!([{ "additionalTestBlockFunctions": ["afterEach"] }])),
),
// Todo: currently it's not support
// (
// "
// theoretically('the number {input} is correctly translated to string', theories, theory => {
// const output = NumberToLongString(theory.input);
// })
// ",
// Some(serde_json::json!([{ "additionalTestBlockFunctions": ["theoretically"] }])),
// ),
("it(\"should fail\", () => { somePromise.then(() => {}); });", None),
(
"test(\"should fail\", () => { foo(true).toBe(true); })",
Some(serde_json::json!([{ "assertFunctionNames": ["expect"] }])),
),
(
"it(\"should also fail\",() => expectSaga(mySaga).returns());",
Some(serde_json::json!([{ "assertFunctionNames": ["expect"] }])),
),
(
"test('should fail', () => request.get().foo().expect(456));",
Some(serde_json::json!([{ "assertFunctionNames": ["request.*.expect"] }])),
),
(
"test('should fail', () => request.get().foo().bar().expect(456));",
Some(serde_json::json!([{ "assertFunctionNames": ["request.foo**.expect"] }])),
),
(
"test('should fail', () => tester.request(123));",
Some(serde_json::json!([{ "assertFunctionNames": ["request.*"] }])),
),
(
"test('should fail', () => request(123));",
Some(serde_json::json!([{ "assertFunctionNames": ["request.*"] }])),
),
(
"test('should fail', () => request(123));",
Some(serde_json::json!([{ "assertFunctionNames": ["request.**"] }])),
),
(
"
import { test as checkThat } from 'vitest';
checkThat('this passes', () => {
// ...
});
",
Some(serde_json::json!([{ "assertFunctionNames": ["expect", "foo"] }])),
),
// Todo: currently we couldn't support ignore the typecheck option.
// (
// "
// it(\"should fail without 'typecheck' enabled\", () => {
// expectTypeOf({ a: 1 }).toEqualTypeOf<{ a: number }>()
// });
// ",
// None,
// ),
];

pass.extend(pass_vitest);
fail.extend(fail_vitest);

Tester::new(ExpectExpect::NAME, pass, fail)
.with_jest_plugin(true)
.with_vitest_plugin(true)
.test_and_snapshot();
}
Loading

0 comments on commit acc5729

Please sign in to comment.