use std::{borrow::Cow, str::FromStr}; use oxc_ast::{ast::Expression, AstKind}; use oxc_diagnostics::OxcDiagnostic; use oxc_macros::declare_oxc_lint; use oxc_semantic::ScopeId; use oxc_span::{GetSpan, Span}; use rustc_hash::FxHashMap; use crate::{ context::LintContext, rule::Rule, utils::{ collect_possible_jest_call_node, parse_jest_fn_call, JestFnKind, JestGeneralFnKind, ParsedJestFnCallNew, PossibleJestNode, }, }; fn consistent_method(x1: &str, x2: &str, span0: Span) -> OxcDiagnostic { OxcDiagnostic::warn("Enforce `test` and `it` usage conventions") .with_help(format!("Prefer using {x1:?} instead of {x2:?}")) .with_label(span0) } fn consistent_method_within_describe(x1: &str, x2: &str, span0: Span) -> OxcDiagnostic { OxcDiagnostic::warn("Enforce `test` and `it` usage conventions") .with_help(format!("Prefer using {x1:?} instead of {x2:?} within describe")) .with_label(span0) } #[derive(Debug, Clone, Copy, Eq, PartialEq)] enum TestCaseName { Fit, IT, Test, Xit, Xtest, } impl TestCaseName { pub fn as_str(self) -> &'static str { match self { Self::Fit => "fit", Self::IT => "it", Self::Test => "test", Self::Xit => "xit", Self::Xtest => "xtest", } } } impl std::fmt::Display for TestCaseName { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.as_str().fmt(f) } } impl std::str::FromStr for TestCaseName { type Err = &'static str; fn from_str(s: &str) -> Result { match s { "fit" => Ok(TestCaseName::Fit), "it" => Ok(TestCaseName::IT), "test" => Ok(TestCaseName::Test), "xit" => Ok(TestCaseName::Xit), "xtest" => Ok(TestCaseName::Xtest), _ => Err("Unknown Test case name"), } } } #[derive(Debug, Default, Clone)] pub struct ConsistentTestIt(Box); #[derive(Debug, Clone)] pub struct ConsistentTestItConfig { within_describe: TestCaseName, within_fn: TestCaseName, } impl std::ops::Deref for ConsistentTestIt { type Target = ConsistentTestItConfig; fn deref(&self) -> &Self::Target { &self.0 } } impl Default for ConsistentTestItConfig { fn default() -> Self { Self { within_describe: TestCaseName::IT, within_fn: TestCaseName::Test } } } declare_oxc_lint!( /// ### What it does /// /// Jest allows you to choose how you want to define your tests, using the `it` or /// the `test` keywords, with multiple permutations for each: /// /// - **it:** `it`, `xit`, `fit`, `it.only`, `it.skip`. /// - **test:** `test`, `xtest`, `test.only`, `test.skip`. /// /// ### Example /// /// ```javascript /// /*eslint jest/consistent-test-it: ["error", {"fn": "test"}]*/ /// test('foo'); // valid /// test.only('foo'); // valid /// /// it('foo'); // invalid /// it.only('foo'); // invalid /// ``` /// /// ```javascript /// /*eslint jest/consistent-test-it: ["error", {"fn": "it"}]*/ /// it('foo'); // valid /// it.only('foo'); // valid /// test('foo'); // invalid /// test.only('foo'); // invalid /// ``` /// /// ```javascript /// /*eslint jest/consistent-test-it: ["error", {"fn": "it", "withinDescribe": "test"}]*/ /// it('foo'); // valid /// describe('foo', function () { /// test('bar'); // valid /// }); /// /// test('foo'); // invalid /// describe('foo', function () { /// it('bar'); // invalid /// }); /// ``` /// /// #### Options /// /// This rule can be configured as follows /// ```json5 /// { /// type: 'object', /// properties: { /// fn: { /// enum: ['it', 'test'], /// }, /// withinDescribe: { /// enum: ['it', 'test'], /// }, /// }, /// additionalProperties: false, /// } /// ``` /// /// ##### fn /// Decides whether to use `test` or `it`. /// /// ##### withinDescribe /// Decides whether to use `test` or `it` within a `describe` scope. /// /// /// This rule is compatible with [eslint-plugin-vitest](https://github.com/veritem/eslint-plugin-vitest/blob/main/docs/rules/consistent-test-it.md), /// to use it, add the following configuration to your `.eslintrc.json`: /// /// ```json /// { /// "rules": { /// "vitest/consistent-test-it": "error" /// } /// } ConsistentTestIt, style, fix ); impl Rule for ConsistentTestIt { fn from_configuration(value: serde_json::Value) -> Self { let config = value.get(0); let within_fn = config .and_then(|config| config.get("fn")) .and_then(serde_json::Value::as_str) .and_then(|x| TestCaseName::from_str(x).ok()) .unwrap_or(TestCaseName::Test); let within_describe = config .and_then(|config| config.get("withinDescribe")) .and_then(serde_json::Value::as_str) .and_then(|x| TestCaseName::from_str(x).ok()) .unwrap_or( config .and_then(|config| config.get("fn")) .and_then(serde_json::Value::as_str) .and_then(|x| TestCaseName::from_str(x).ok()) .unwrap_or(TestCaseName::IT), ); Self(Box::new(ConsistentTestItConfig { within_describe, within_fn })) } fn run_once(&self, ctx: &LintContext) { let mut describe_nesting_hash: FxHashMap = FxHashMap::default(); let mut possible_jest_nodes = collect_possible_jest_call_node(ctx); possible_jest_nodes.sort_by_key(|n| n.node.id()); for possible_jest_node in &possible_jest_nodes { self.run(&mut describe_nesting_hash, possible_jest_node, ctx); } } } impl ConsistentTestIt { fn run<'a>( &self, describe_nesting_hash: &mut FxHashMap, possible_jest_node: &PossibleJestNode<'a, '_>, ctx: &LintContext<'a>, ) { let node = possible_jest_node.node; let AstKind::CallExpression(call_expr) = node.kind() else { return; }; let Some(ParsedJestFnCallNew::GeneralJest(jest_fn_call)) = parse_jest_fn_call(call_expr, possible_jest_node, ctx) else { return; }; if matches!(jest_fn_call.kind, JestFnKind::General(JestGeneralFnKind::Describe)) { let scope_id = node.scope_id(); let current_count = describe_nesting_hash.get(&scope_id).unwrap_or(&0); describe_nesting_hash.insert(scope_id, *current_count + 1); return; } let is_test = matches!(jest_fn_call.kind, JestFnKind::General(JestGeneralFnKind::Test)); let fn_to_str = self.within_fn.as_str(); if is_test && describe_nesting_hash.is_empty() && !jest_fn_call.name.ends_with(fn_to_str) { let opposite_test_keyword = Self::get_opposite_test_case(self.within_fn); if let Some((span, prefer_test_name)) = Self::get_prefer_test_name_and_span( call_expr.callee.get_inner_expression(), &jest_fn_call.name, fn_to_str, ) { ctx.diagnostic_with_fix( consistent_method(fn_to_str, opposite_test_keyword, span), |fixer| fixer.replace(span, prefer_test_name), ); } } let describe_to_str = self.within_describe.as_str(); if is_test && !describe_nesting_hash.is_empty() && !jest_fn_call.name.ends_with(describe_to_str) { let opposite_test_keyword = Self::get_opposite_test_case(self.within_describe); if let Some((span, prefer_test_name)) = Self::get_prefer_test_name_and_span( call_expr.callee.get_inner_expression(), &jest_fn_call.name, describe_to_str, ) { ctx.diagnostic_with_fix( consistent_method_within_describe(describe_to_str, opposite_test_keyword, span), |fixer| fixer.replace(span, prefer_test_name), ); } } } fn get_opposite_test_case(test_case_name: TestCaseName) -> &'static str { if matches!(test_case_name, TestCaseName::Test) { TestCaseName::IT.as_str() } else { TestCaseName::Test.as_str() } } fn get_prefer_test_name_and_span<'s>( expr: &Expression, test_name: &str, fix_jest_name: &'s str, ) -> Option<(Span, Cow<'s, str>)> { match expr { Expression::Identifier(ident) => { if ident.name.eq("fit") { return Some((ident.span, Cow::Borrowed("test.only"))); } let prefer_test_name = match test_name.chars().next() { Some('x') => Cow::Owned(format!("x{fix_jest_name}")), Some('f') => Cow::Owned(format!("f{fix_jest_name}")), _ => Cow::Borrowed(fix_jest_name), }; Some((ident.span(), prefer_test_name)) } Expression::StaticMemberExpression(expr) => { Self::get_prefer_test_name_and_span(&expr.object, test_name, fix_jest_name) } Expression::CallExpression(call_expr) => Self::get_prefer_test_name_and_span( call_expr.callee.get_inner_expression(), test_name, fix_jest_name, ), Expression::TaggedTemplateExpression(expr) => Self::get_prefer_test_name_and_span( expr.tag.get_inner_expression(), test_name, fix_jest_name, ), _ => None, } } } #[test] fn test() { use crate::tester::Tester; let mut pass = vec![ // consistent-test-it with fn=test ("test(\"foo\")", Some(serde_json::json!([{ "fn": "test" }]))), ("test.only(\"foo\")", Some(serde_json::json!([{ "fn": "test" }]))), ("test.skip(\"foo\")", Some(serde_json::json!([{ "fn": "test" }]))), ("test.concurrent(\"foo\")", Some(serde_json::json!([{ "fn": "test" }]))), ("xtest(\"foo\")", Some(serde_json::json!([{ "fn": "test" }]))), ("test.each([])(\"foo\")", Some(serde_json::json!([{ "fn": "test" }]))), ("test.each``(\"foo\")", Some(serde_json::json!([{ "fn": "test" }]))), ( "describe(\"suite\", () => { test(\"foo\") })", Some(serde_json::json!([{ "fn": "test" }])), ), // consistent-test-it with fn=it ("it(\"foo\")", Some(serde_json::json!([{ "fn": "it" }]))), ("fit(\"foo\")", Some(serde_json::json!([{ "fn": "it" }]))), ("xit(\"foo\")", Some(serde_json::json!([{ "fn": "it" }]))), ("it.only(\"foo\")", Some(serde_json::json!([{ "fn": "it" }]))), ("it.skip(\"foo\")", Some(serde_json::json!([{ "fn": "it" }]))), ("it.concurrent(\"foo\")", Some(serde_json::json!([{ "fn": "it" }]))), ("it.each([])(\"foo\")", Some(serde_json::json!([{ "fn": "it" }]))), ("it.each``(\"foo\")", Some(serde_json::json!([{ "fn": "it" }]))), ("describe(\"suite\", () => { it(\"foo\") })", Some(serde_json::json!([{ "fn": "it" }]))), // consistent-test-it with fn=test and withinDescribe=it ("test(\"foo\")", Some(serde_json::json!([{ "fn": "test", "withinDescribe": "it" }]))), ("test.only(\"foo\")", Some(serde_json::json!([{ "fn": "test", "withinDescribe": "it" }]))), ("test.skip(\"foo\")", Some(serde_json::json!([{ "fn": "test", "withinDescribe": "it" }]))), ( "test.concurrent(\"foo\")", Some(serde_json::json!([{ "fn": "test", "withinDescribe": "it" }])), ), ("xtest(\"foo\")", Some(serde_json::json!([{ "fn": "test", "withinDescribe": "it" }]))), ( "[1,2,3].forEach(() => { test(\"foo\") })", Some(serde_json::json!([{ "fn": "test", "withinDescribe": "it" }])), ), // consistent-test-it with fn=it and withinDescribe=test ("it(\"foo\")", Some(serde_json::json!([{ "fn": "it", "withinDescribe": "test" }]))), ("it.only(\"foo\")", Some(serde_json::json!([{ "fn": "it", "withinDescribe": "test" }]))), ("it.skip(\"foo\")", Some(serde_json::json!([{ "fn": "it", "withinDescribe": "test" }]))), ( "it.concurrent(\"foo\")", Some(serde_json::json!([{ "fn": "it", "withinDescribe": "test" }])), ), ("xit(\"foo\")", Some(serde_json::json!([{ "fn": "it", "withinDescribe": "test" }]))), ( "[1,2,3].forEach(() => { it(\"foo\") })", Some(serde_json::json!([{ "fn": "it", "withinDescribe": "test" }])), ), // consistent-test-it with fn=test and withinDescribe=test ( "describe(\"suite\", () => { test(\"foo\") })", Some(serde_json::json!([{ "fn": "test", "withinDescribe": "test" }])), ), ("test(\"foo\");", Some(serde_json::json!([{ "fn": "test", "withinDescribe": "test" }]))), // consistent-test-it with fn=it and withinDescribe=it ( "describe(\"suite\", () => { it(\"foo\") })", Some(serde_json::json!([{ "fn": "it", "withinDescribe": "it" }])), ), ("it(\"foo\")", Some(serde_json::json!([{ "fn": "it", "withinDescribe": "it" }]))), // consistent-test-it defaults without config object ("test(\"foo\")", None), // consistent-test-it with withinDescribe=it ("test(\"foo\")", Some(serde_json::json!([{ "withinDescribe": "it" }]))), ( "describe(\"suite\", () => { it(\"foo\") })", Some(serde_json::json!([{ "withinDescribe": "it" }])), ), // consistent-test-it with withinDescribe=test ("test(\"foo\")", Some(serde_json::json!([{ "withinDescribe": "test" }]))), ( "describe(\"suite\", () => { test(\"foo\") })", Some(serde_json::json!([{ "withinDescribe": "test" }])), ), ]; let mut fail = vec![ // consistent-test-it with fn=test ("it(\"foo\")", Some(serde_json::json!([{ "fn": "test" }]))), ( " import { it } from '@jest/globals'; it(\"foo\") ", Some(serde_json::json!([{ "fn": "test" }])), ), ( " import { it as testThisThing } from '@jest/globals'; testThisThing(\"foo\") ", Some(serde_json::json!([{ "fn": "test" }])), ), ("xit(\"foo\")", Some(serde_json::json!([{ "fn": "test" }]))), ("fit(\"foo\")", Some(serde_json::json!([{ "fn": "test" }]))), ("it.skip(\"foo\")", Some(serde_json::json!([{ "fn": "test" }]))), ("it.concurrent(\"foo\")", Some(serde_json::json!([{ "fn": "test" }]))), ("it.only(\"foo\")", Some(serde_json::json!([{ "fn": "test" }]))), ("it.each([])(\"foo\")", Some(serde_json::json!([{ "fn": "test" }]))), ("it.each``(\"foo\")", Some(serde_json::json!([{ "fn": "test" }]))), ( "describe.each``(\"foo\", () => { it.each``(\"bar\") })", Some(serde_json::json!([{ "fn": "test" }])), ), ( "describe.each``(\"foo\", () => { test.each``(\"bar\") })", Some(serde_json::json!([{ "fn": "it" }])), ), ( " describe.each()(\"%s\", () => { test(\"is valid, but should not be\", () => {}); it(\"is not valid, but should be\", () => {}); }); ", Some(serde_json::json!([{ "fn": "test", "withinDescribe": "it" }])), ), ( " describe.only.each()(\"%s\", () => { test(\"is valid, but should not be\", () => {}); it(\"is not valid, but should be\", () => {}); }); ", Some(serde_json::json!([{ "fn": "test", "withinDescribe": "it" }])), ), ("describe(\"suite\", () => { it(\"foo\") })", Some(serde_json::json!([{ "fn": "test" }]))), // consistent-test-it with fn=it ("test(\"foo\")", Some(serde_json::json!([{ "fn": "it" }]))), ("xtest(\"foo\")", Some(serde_json::json!([{ "fn": "it" }]))), ("test.skip(\"foo\")", Some(serde_json::json!([{ "fn": "it" }]))), ("test.concurrent(\"foo\")", Some(serde_json::json!([{ "fn": "it" }]))), ("test.only(\"foo\")", Some(serde_json::json!([{ "fn": "it" }]))), ("test.each([])(\"foo\")", Some(serde_json::json!([{ "fn": "it" }]))), ( "describe.each``(\"foo\", () => { test.each``(\"bar\") })", Some(serde_json::json!([{ "fn": "it" }])), ), ("test.each``(\"foo\")", Some(serde_json::json!([{ "fn": "it" }]))), ("describe(\"suite\", () => { test(\"foo\") })", Some(serde_json::json!([{ "fn": "it" }]))), // consistent-test-it with fn=test and withinDescribe=it ( "describe(\"suite\", () => { test(\"foo\") })", Some(serde_json::json!([{ "fn": "test", "withinDescribe": "it" }])), ), ( "describe(\"suite\", () => { test.only(\"foo\") })", Some(serde_json::json!([{ "fn": "test", "withinDescribe": "it" }])), ), ( "describe(\"suite\", () => { xtest(\"foo\") })", Some(serde_json::json!([{ "fn": "test", "withinDescribe": "it" }])), ), ( " import { xtest as dontTestThis } from '@jest/globals'; describe(\"suite\", () => { dontTestThis(\"foo\") }); ", Some(serde_json::json!([{ "fn": "test", "withinDescribe": "it" }])), ), ( " import { describe as context, xtest as dontTestThis } from '@jest/globals'; context(\"suite\", () => { dontTestThis(\"foo\") }); ", Some(serde_json::json!([{ "fn": "test", "withinDescribe": "it" }])), ), ( "describe(\"suite\", () => { test.skip(\"foo\") })", Some(serde_json::json!([{ "fn": "test", "withinDescribe": "it" }])), ), ( "describe(\"suite\", () => { test.concurrent(\"foo\") })", Some(serde_json::json!([{ "fn": "test", "withinDescribe": "it" }])), ), // consistent-test-it with fn=it and withinDescribe=test ( "describe(\"suite\", () => { test(\"foo\") })", Some(serde_json::json!([{ "fn": "test", "withinDescribe": "it" }])), ), ( "describe(\"suite\", () => { test.only(\"foo\") })", Some(serde_json::json!([{ "fn": "test", "withinDescribe": "it" }])), ), ( "describe(\"suite\", () => { xtest(\"foo\") })", Some(serde_json::json!([{ "fn": "test", "withinDescribe": "it" }])), ), ( " import { xtest as dontTestThis } from '@jest/globals'; describe(\"suite\", () => { dontTestThis(\"foo\") }); ", Some(serde_json::json!([{ "fn": "test", "withinDescribe": "it" }])), ), ( " import { describe as context, xtest as dontTestThis } from '@jest/globals'; context(\"suite\", () => { dontTestThis(\"foo\") }); ", Some(serde_json::json!([{ "fn": "test", "withinDescribe": "it" }])), ), ( "describe(\"suite\", () => { test.skip(\"foo\") })", Some(serde_json::json!([{ "fn": "test", "withinDescribe": "it" }])), ), ( "describe(\"suite\", () => { test.concurrent(\"foo\") })", Some(serde_json::json!([{ "fn": "test", "withinDescribe": "it" }])), ), // consistent-test-it with fn=test and withinDescribe=test ( "describe(\"suite\", () => { it(\"foo\") })", Some(serde_json::json!([{ "fn": "test", "withinDescribe": "test" }])), ), ("it(\"foo\")", Some(serde_json::json!([{ "fn": "test", "withinDescribe": "test" }]))), // consistent-test-it with fn=it and withinDescribe=it ( "describe(\"suite\", () => { test(\"foo\") })", Some(serde_json::json!([{ "fn": "it", "withinDescribe": "it" }])), ), ("test(\"foo\")", Some(serde_json::json!([{ "fn": "it", "withinDescribe": "it" }]))), // consistent-test-it defaults without config object ("describe(\"suite\", () => { test(\"foo\") })", None), // consistent-test-it with withinDescribe=it ("it(\"foo\")", Some(serde_json::json!([{ "withinDescribe": "it" }]))), ( "describe(\"suite\", () => { test(\"foo\") })", Some(serde_json::json!([{ "withinDescribe": "it" }])), ), // consistent-test-it with withinDescribe=test ("it(\"foo\")", Some(serde_json::json!([{ "withinDescribe": "test" }]))), ( "describe(\"suite\", () => { it(\"foo\") })", Some(serde_json::json!([{ "withinDescribe": "test" }])), ), ]; let mut fix = vec![ // consistent-test-it with fn=test ("it(\"foo\")", "test(\"foo\")"), ( " import { it } from '@jest/globals'; it(\"foo\") ", " import { it } from '@jest/globals'; test(\"foo\") ", ), ( " import { it as testThisThing } from '@jest/globals'; testThisThing(\"foo\") ", " import { it as testThisThing } from '@jest/globals'; test(\"foo\") ", ), ("xit(\"foo\")", "xtest(\"foo\")"), ("fit(\"foo\")", "test.only(\"foo\")"), ("it.skip(\"foo\")", "test.skip(\"foo\")"), ("it.concurrent(\"foo\")", "test.concurrent(\"foo\")"), ("it.only(\"foo\")", "test.only(\"foo\")"), ("it.each([])(\"foo\")", "test.each([])(\"foo\")"), ("it.each``(\"foo\")", "test.each``(\"foo\")"), // Note: couldn't fix // Todo: this need to fixer support option configuration. // ( // "describe.each``(\"foo\", () => { it.each``(\"bar\") })", // "describe.each``(\"foo\", () => { test.each``(\"bar\") })", // ), ( "describe.each``(\"foo\", () => { test.each``(\"bar\") })", "describe.each``(\"foo\", () => { it.each``(\"bar\") })", ), ( " describe.each()(\"%s\", () => { test(\"is valid, but should not be\", () => {}); it(\"is not valid, but should be\", () => {}); }); ", " describe.each()(\"%s\", () => { it(\"is valid, but should not be\", () => {}); it(\"is not valid, but should be\", () => {}); }); ", ), ( " describe.only.each()(\"%s\", () => { test(\"is valid, but should not be\", () => {}); it(\"is not valid, but should be\", () => {}); }); ", " describe.only.each()(\"%s\", () => { it(\"is valid, but should not be\", () => {}); it(\"is not valid, but should be\", () => {}); }); ", ), // Note: couldn't fix, because the fixer couldn't be set option `fn=it` // ( // "describe(\"suite\", () => { it(\"foo\") })", // "describe(\"suite\", () => { test(\"foo\") })", // ), // consistent-test-it with fn=it // ("test(\"foo\")", "it(\"foo\")"), // ("xtest(\"foo\")", "xit(\"foo\")"), // ("test.skip(\"foo\")", "it.skip(\"foo\")"), // ("test.concurrent(\"foo\")", "it.concurrent(\"foo\")"), // ("test.only(\"foo\")", "it.only(\"foo\")"), // ("test.each([])(\"foo\")", "it.each([])(\"foo\")"), // ("test.each``(\"foo\")", "it.each``(\"foo\")"), ( "describe.each``(\"foo\", () => { test.each``(\"bar\") })", "describe.each``(\"foo\", () => { it.each``(\"bar\") })", ), ( "describe(\"suite\", () => { test(\"foo\") })", "describe(\"suite\", () => { it(\"foo\") })", ), // // consistent-test-it with fn=test and withinDescribe=it ( "describe(\"suite\", () => { test(\"foo\") })", "describe(\"suite\", () => { it(\"foo\") })", ), ( "describe(\"suite\", () => { test.only(\"foo\") })", "describe(\"suite\", () => { it.only(\"foo\") })", ), ( "describe(\"suite\", () => { xtest(\"foo\") })", "describe(\"suite\", () => { xit(\"foo\") })", ), ( " import { xtest as dontTestThis } from '@jest/globals'; describe(\"suite\", () => { dontTestThis(\"foo\") }); ", " import { xtest as dontTestThis } from '@jest/globals'; describe(\"suite\", () => { xit(\"foo\") }); ", ), ( " import { describe as context, xtest as dontTestThis } from '@jest/globals'; context(\"suite\", () => { dontTestThis(\"foo\") }); ", " import { describe as context, xtest as dontTestThis } from '@jest/globals'; context(\"suite\", () => { xit(\"foo\") }); ", ), ( "describe(\"suite\", () => { test.skip(\"foo\") })", "describe(\"suite\", () => { it.skip(\"foo\") })", ), ( "describe(\"suite\", () => { test.concurrent(\"foo\") })", "describe(\"suite\", () => { it.concurrent(\"foo\") })", ), // consistent-test-it with fn=it and withinDescribe=test ( "describe(\"suite\", () => { test(\"foo\") })", "describe(\"suite\", () => { it(\"foo\") })", ), ( "describe(\"suite\", () => { test.only(\"foo\") })", "describe(\"suite\", () => { it.only(\"foo\") })", ), ( "describe(\"suite\", () => { xtest(\"foo\") })", "describe(\"suite\", () => { xit(\"foo\") })", ), ( " import { xtest as dontTestThis } from '@jest/globals'; describe(\"suite\", () => { dontTestThis(\"foo\") }); ", " import { xtest as dontTestThis } from '@jest/globals'; describe(\"suite\", () => { xit(\"foo\") }); ", ), ( " import { describe as context, xtest as dontTestThis } from '@jest/globals'; context(\"suite\", () => { dontTestThis(\"foo\") }); ", " import { describe as context, xtest as dontTestThis } from '@jest/globals'; context(\"suite\", () => { xit(\"foo\") }); ", ), ( "describe(\"suite\", () => { test.skip(\"foo\") })", "describe(\"suite\", () => { it.skip(\"foo\") })", ), ( "describe(\"suite\", () => { test.concurrent(\"foo\") })", "describe(\"suite\", () => { it.concurrent(\"foo\") })", ), // Note: couldn't fix // Todo: this need to fixer support option configuration. // consistent-test-it with fn=test and withinDescribe=test // ( // "describe(\"suite\", () => { it(\"foo\") })", // "describe(\"suite\", () => { test(\"foo\") })", // ), // ("it(\"foo\")", "test(\"foo\")"), // // consistent-test-it with fn=it and withinDescribe=it ( "describe(\"suite\", () => { test(\"foo\") })", "describe(\"suite\", () => { it(\"foo\") })", ), // ("test(\"foo\")", "it(\"foo\")"), // consistent-test-it defaults without config object ( "describe(\"suite\", () => { test(\"foo\") })", "describe(\"suite\", () => { it(\"foo\") })", ), // consistent-test-it with withinDescribe=it ("it(\"foo\")", "test(\"foo\")"), ( "describe(\"suite\", () => { test(\"foo\") })", "describe(\"suite\", () => { it(\"foo\") })", ), // consistent-test-it with withinDescribe=test ("it(\"foo\")", "test(\"foo\")"), // Note: couldn't fixed // Todo: this need to fixer support option configuration. // ( // "describe(\"suite\", () => { it(\"foo\") })", // "describe(\"suite\", () => { test(\"foo\") })", // ), ]; let pass_vitest = vec![ ( " it(\"shows error\", () => { expect(true).toBe(false); }); ", Some(serde_json::json!([{ "fn": "it" }])), ), ( " it(\"foo\", function () { expect(true).toBe(false); }) ", Some(serde_json::json!([{ "fn": "it" }])), ), ( " it('foo', () => { expect(true).toBe(false); }); function myTest() { if ('bar') {} } ", Some(serde_json::json!([{ "fn": "it" }])), ), ( " test(\"shows error\", () => { expect(true).toBe(false); }); ", Some(serde_json::json!([{ "fn": "test" }])), ), ("test.skip(\"foo\")", Some(serde_json::json!([{ "fn": "test" }]))), ("test.concurrent(\"foo\")", Some(serde_json::json!([{ "fn": "test" }]))), ("xtest(\"foo\")", Some(serde_json::json!([{ "fn": "test" }]))), ("test.each([])(\"foo\")", Some(serde_json::json!([{ "fn": "test" }]))), ("test.each``(\"foo\")", Some(serde_json::json!([{ "fn": "test" }]))), ( "describe(\"suite\", () => { test(\"foo\") })", Some(serde_json::json!([{ "fn": "test" }])), ), ( "describe(\"suite\", () => { it(\"foo\") })", Some(serde_json::json!([{ "fn": "it", "withinDescribe": "it" }])), ), ("it(\"foo\")", Some(serde_json::json!([{ "fn": "it", "withinDescribe": "it" }]))), ("test(\"shows error\", () => {});", None), ("test(\"foo\")", Some(serde_json::json!([{ "withinDescribe": "it" }]))), ( "describe(\"suite\", () => { it(\"foo\") })", Some(serde_json::json!([{ "withinDescribe": "it" }])), ), ("test(\"foo\")", Some(serde_json::json!([{ "withinDescribe": "test" }]))), ( "describe(\"suite\", () => { test(\"foo\") })", Some(serde_json::json!([{ "withinDescribe": "test" }])), ), ]; let fail_vitest = vec![ ("test(\"shows error\", () => {});", Some(serde_json::json!([{ "fn": "it" }]))), ("test.skip(\"shows error\");", Some(serde_json::json!([{ "fn": "it" }]))), ("test.only('shows error');", Some(serde_json::json!([{ "fn": "it" }]))), ( "describe('foo', () => { it('bar', () => {}); });", Some(serde_json::json!([{ "fn": "it", "withinDescribe": "test" }])), ), ( "import { test } from \"vitest\"\ntest(\"shows error\", () => {});", Some(serde_json::json!([{ "fn": "it" }])), ), ("it(\"shows error\", () => {});", Some(serde_json::json!([{ "fn": "test" }]))), ("describe(\"suite\", () => { it(\"foo\") })", Some(serde_json::json!([{ "fn": "test" }]))), ( "describe(\"suite\", () => { test(\"foo\") })", Some(serde_json::json!([{ "fn": "it", "withinDescribe": "it" }])), ), ("test(\"foo\")", Some(serde_json::json!([{ "fn": "it", "withinDescribe": "it" }]))), ("describe(\"suite\", () => { test(\"foo\") })", None), ("it(\"foo\")", Some(serde_json::json!([{ "withinDescribe": "it" }]))), ( "describe(\"suite\", () => { test(\"foo\") })", Some(serde_json::json!([{ "withinDescribe": "it" }])), ), ("it(\"foo\")", Some(serde_json::json!([{ "withinDescribe": "test" }]))), ( "import { it } from \"vitest\"\nit(\"foo\")", Some(serde_json::json!([{ "withinDescribe": "test" }])), ), ( "describe(\"suite\", () => { it(\"foo\") })", Some(serde_json::json!([{ "withinDescribe": "test" }])), ), ]; let fix_vitest = vec![ // Note: couldn't fixed, because the fixer doesn't support to set the options for the fix cases. // Todo: this need to fixer support option configuration. // ("test(\"shows error\", () => {});", "it(\"shows error\", () => {});"), // ("test.skip(\"shows error\");", "it.skip(\"shows error\");"), // ("test.only('shows error');", "it.only('shows error');"), // ( // "describe('foo', () => { it('bar', () => {}); });", // "describe('foo', () => { test('bar', () => {}); });" // ), // ( // "import { test } from \"vitest\"\ntest(\"shows error\", () => {});", // "import { it } from \"vitest\"\nit(\"shows error\", () => {});", // ), // ("describe(\"suite\", () => { it(\"foo\") })", "describe(\"suite\", () => { test(\"foo\") })"), // ("test(\"foo\")", "it(\"foo\")"), // ("describe(\"suite\", () => { it(\"foo\") })", "describe(\"suite\", () => { test(\"foo\") })"), // ("it(\"shows error\", () => {});", "test(\"shows error\", () => {});"), ( "describe(\"suite\", () => { test(\"foo\") })", "describe(\"suite\", () => { it(\"foo\") })", ), ( "describe(\"suite\", () => { test(\"foo\") })", "describe(\"suite\", () => { it(\"foo\") })", ), ("it(\"foo\")", "test(\"foo\")"), ( "describe(\"suite\", () => { test(\"foo\") })", "describe(\"suite\", () => { it(\"foo\") })", ), ("it(\"foo\")", "test(\"foo\")"), // Todo: need to be fixed // ( // "import { it } from \"vitest\"\nit(\"foo\")", // "import { test } from \"vitest\"\ntest(\"foo\")" // ), ]; pass.extend(pass_vitest); fail.extend(fail_vitest); fix.extend(fix_vitest); Tester::new(ConsistentTestIt::NAME, pass, fail) .with_jest_plugin(true) .with_vitest_plugin(true) .expect_fix(fix) .test_and_snapshot(); }