diff --git a/crates/oxc_linter/src/rules/jest/prefer_to_have_been_called.rs b/crates/oxc_linter/src/rules/jest/prefer_to_have_been_called.rs index c358a86966949..dc537074a87fc 100644 --- a/crates/oxc_linter/src/rules/jest/prefer_to_have_been_called.rs +++ b/crates/oxc_linter/src/rules/jest/prefer_to_have_been_called.rs @@ -169,6 +169,13 @@ fn test() { "expect(method).not.toHaveBeenCalled();", None, ), + ( + "expect(method).toHaveBeenCalledTimes( + 0, + );", + "expect(method).not.toHaveBeenCalled();", + None, + ), ( "expect(method).not.toHaveBeenCalledTimes(0);", "expect(method).toHaveBeenCalled();", @@ -189,6 +196,26 @@ fn test() { "expect(method).rejects.toHaveBeenCalled();", None, ), + ( + "expect(method).rejects.not.toHaveBeenCalledTimes(0,);", + "expect(method).rejects.toHaveBeenCalled();", + None, + ), + ( + "expect(method).rejects.not.toHaveBeenCalledTimes( + 0, + );", + "expect(method).rejects.toHaveBeenCalled();", + None, + ), + ( + "expect(method).rejects.not.toHaveBeenCalledTimes( + /* call this zero times (because I said so) */ + 0, + );", + "expect(method).rejects.toHaveBeenCalled();", + None, + ), ( "expect(method).toBeCalledTimes(0 as number);", "expect(method).not.toHaveBeenCalled();", diff --git a/crates/oxc_linter/src/rules/jest/prefer_to_have_been_called_times.rs b/crates/oxc_linter/src/rules/jest/prefer_to_have_been_called_times.rs index 5d8ff4b486fa1..15953b7ed6319 100644 --- a/crates/oxc_linter/src/rules/jest/prefer_to_have_been_called_times.rs +++ b/crates/oxc_linter/src/rules/jest/prefer_to_have_been_called_times.rs @@ -214,6 +214,21 @@ fn test() { "expect(method).toHaveBeenCalledTimes(1);", None, ), + ( + "expect(method.mock.calls).toHaveLength( + 1, + );", + "expect(method).toHaveBeenCalledTimes(1);", + None, + ), + ( + "expect(method.mock.calls).toHaveLength( + /* number of calls (one) */ + 1, + );", + "expect(method).toHaveBeenCalledTimes(1);", + None, + ), ( "expect(method.mock.calls).resolves.toHaveLength(x);", "expect(method).resolves.toHaveBeenCalledTimes(x);", diff --git a/crates/oxc_linter/src/rules/vitest/prefer_called_once.rs b/crates/oxc_linter/src/rules/vitest/prefer_called_once.rs index e5d0d4aff013c..a37901af8ddc5 100644 --- a/crates/oxc_linter/src/rules/vitest/prefer_called_once.rs +++ b/crates/oxc_linter/src/rules/vitest/prefer_called_once.rs @@ -112,13 +112,28 @@ impl PreferCalledOnce { ctx.diagnostic_with_fix( prefer_called_once_diagnostic(matcher_and_args_span, new_matcher_name.as_ref()), |fixer| { + let argument_without_parenthesis_span = ctx + .find_next_token_within( + called_times_value.span.end, + call_expr.span.end, + ",", + ) + .map(|i| Span::sized(called_times_value.span.end + i, 1)); + + let number_of_fixes = + if argument_without_parenthesis_span.is_some() { 3 } else { 2 }; + let multi_fix = fixer.for_multifix(); - let mut fixes = multi_fix.new_fix_with_capacity(2); + let mut fixes = multi_fix.new_fix_with_capacity(number_of_fixes); fixes.push(fixer.replace(matcher_to_be_fixed.span, new_matcher_name)); fixes.push(fixer.delete(&called_times_value.span)); - fixes.with_message("Replace API with preferOnce instead of Times") + if let Some(comma_span) = argument_without_parenthesis_span { + fixes.push(fixer.delete(&comma_span)); + } + + fixes.with_message("Replace API with prefer Once instead of Times") }, ); } @@ -153,6 +168,9 @@ fn test() { "expect(fn).resolves.toBeCalledTimes(1);", "expect(fn).resolves.toHaveBeenCalledTimes(1);", "expect(fn).resolves.toHaveBeenCalledTimes(/*comment*/1);", + "expect(window.HTMLElement.prototype.scrollIntoView).toHaveBeenCalledTimes( + 1, + );", ]; let fix = vec![ @@ -165,6 +183,44 @@ fn test() { "expect(fn).resolves.toHaveBeenCalledTimes(1);", "expect(fn).resolves.toHaveBeenCalledOnce();", ), + ( + "expect(window.HTMLElement.prototype.scrollIntoView).toHaveBeenCalledTimes( +1, + );", + "expect(window.HTMLElement.prototype.scrollIntoView).toHaveBeenCalledOnce( + + );", + ), + ( + "expect(window.HTMLElement.prototype.scrollIntoView).toHaveBeenCalledTimes(1,);", + "expect(window.HTMLElement.prototype.scrollIntoView).toHaveBeenCalledOnce();", + ), + ( + "expect(window.HTMLElement.prototype.scrollIntoView).toHaveBeenCalledTimes(/* comment (because why not) */1,);", + "expect(window.HTMLElement.prototype.scrollIntoView).toHaveBeenCalledOnce(/* comment (because why not) */);", + ), + ( + "expect(window.HTMLElement.prototype.scrollIntoView).toHaveBeenCalledTimes(1/* comment (because why not) */,);", + "expect(window.HTMLElement.prototype.scrollIntoView).toHaveBeenCalledOnce(/* comment (because why not) */);", + ), + ( + "expect(window.HTMLElement.prototype.scrollIntoView).toHaveBeenCalledTimes( + /* I only want to call this function 1 (ONE) time, please. */ +1, + );", + "expect(window.HTMLElement.prototype.scrollIntoView).toHaveBeenCalledOnce( + /* I only want to call this function 1 (ONE) time, please. */ + + );", + ), + ( + "expect(fn).resolves.toHaveBeenCalledTimes(/*comment,*/1,);", + "expect(fn).resolves.toHaveBeenCalledOnce(/*comment,*/);", + ), + ( + "expect(fn).resolves.toHaveBeenCalledTimes(/*comment,*/1/*comment,*/,);", + "expect(fn).resolves.toHaveBeenCalledOnce(/*comment,*//*comment,*/);", + ), ]; Tester::new(PreferCalledOnce::NAME, PreferCalledOnce::PLUGIN, pass, fail) .expect_fix(fix) diff --git a/crates/oxc_linter/src/snapshots/vitest_prefer_called_once.snap b/crates/oxc_linter/src/snapshots/vitest_prefer_called_once.snap index fb6015d5bc45d..78a9680d61509 100644 --- a/crates/oxc_linter/src/snapshots/vitest_prefer_called_once.snap +++ b/crates/oxc_linter/src/snapshots/vitest_prefer_called_once.snap @@ -49,3 +49,11 @@ source: crates/oxc_linter/src/tester.rs · ─────────────────────────────────── ╰──── help: Prefer `toHaveBeenCalledOnce()`. + + ⚠ eslint-plugin-vitest(prefer-called-once): The use of `toBeCalledTimes(1)` and `toHaveBeenCalledTimes(1)` is discouraged. + ╭─[prefer_called_once.tsx:1:53] + 1 │ ╭─▶ expect(window.HTMLElement.prototype.scrollIntoView).toHaveBeenCalledTimes( + 2 │ │ 1, + 3 │ ╰─▶ ); + ╰──── + help: Prefer `toHaveBeenCalledOnce()`.