From d0802abdecadabc5c3248ebf75a466831f6dfbe4 Mon Sep 17 00:00:00 2001 From: "( Nechiforel David-Samuel ) NsdHSO" Date: Wed, 1 Jul 2026 20:36:57 +0300 Subject: [PATCH] grammars: Fix runnable gutter detection for `describe.skipIf` / `test.skipIf` in JS/TS/TSX (#60153) I came across issue #60149 and wanted to help. The runnable gutter (play button) was not showing for Vitest's conditional test wrappers like it.skipIf(condition) and it.runIf(condition). image image image Closes #60149 Release Notes: - Fixed runnable gutter detection for Vitest conditional test wrappers (skipIf/runIf) in JS/TS/TSX files --------- Co-authored-by: Kirill Bulatov --- crates/grammars/src/javascript/outline.scm | 14 +- crates/grammars/src/javascript/runnables.scm | 14 +- crates/grammars/src/tsx/outline.scm | 14 +- crates/grammars/src/tsx/runnables.scm | 14 +- crates/grammars/src/typescript/outline.scm | 14 +- crates/grammars/src/typescript/runnables.scm | 14 +- crates/languages/src/typescript.rs | 178 +++++++++++++++++++ 7 files changed, 250 insertions(+), 12 deletions(-) diff --git a/crates/grammars/src/javascript/outline.scm b/crates/grammars/src/javascript/outline.scm index ce6d9e6bd9..c87c53cbc2 100644 --- a/crates/grammars/src/javascript/outline.scm +++ b/crates/grammars/src/javascript/outline.scm @@ -169,6 +169,7 @@ name: (_) @name) @item ; Add support for (node:test, bun:test and Jest) runnable +; Also matches direct modifiers: .skip, .todo, .only, .failing (Jest, Bun, Vitest) ((call_expression function: [ (identifier) @_name @@ -188,7 +189,10 @@ (identifier) @name ]))) @item -; Add support for parameterized tests +; Parameterized and conditional tests. Docs per runner: +; Jest: https://jestjs.io/docs/api#testeachtablename-fn-timeout +; Vitest: https://vitest.dev/api/ +; Bun: https://bun.sh/docs/test/writing-tests#test-modifiers ((call_expression function: (call_expression function: (member_expression @@ -199,7 +203,13 @@ ] property: (property_identifier) @_property) (#any-of? @_name "it" "test" "describe" "context" "suite") - (#eq? @_property "each")) + (#any-of? @_property + ; Jest, Bun, Vitest + "each" + ; Vitest + "skipIf" "runIf" + ; Bun + "if" "todoIf")) arguments: (arguments . [ diff --git a/crates/grammars/src/javascript/runnables.scm b/crates/grammars/src/javascript/runnables.scm index b410fb4d8c..8b0b160c86 100644 --- a/crates/grammars/src/javascript/runnables.scm +++ b/crates/grammars/src/javascript/runnables.scm @@ -1,5 +1,6 @@ ; Add support for (node:test, bun:test and Jest) runnable ; Function expression that has `it`, `test` or `describe` as the function name +; Also matches direct modifiers: .skip, .todo, .only, .failing (Jest, Bun, Vitest) ((call_expression function: [ (identifier) @_name @@ -20,7 +21,10 @@ ])) @_js-test (#set! tag js-test)) -; Add support for parameterized tests +; Parameterized and conditional tests. Docs per runner: +; Jest: https://jestjs.io/docs/api#testeachtablename-fn-timeout +; Vitest: https://vitest.dev/api/ +; Bun: https://bun.sh/docs/test/writing-tests#test-modifiers ((call_expression function: (call_expression function: (member_expression @@ -31,7 +35,13 @@ ] property: (property_identifier) @_property) (#any-of? @_name "it" "test" "describe" "context" "suite") - (#eq? @_property "each")) + (#any-of? @_property + ; Jest, Bun, Vitest + "each" + ; Vitest + "skipIf" "runIf" + ; Bun + "if" "todoIf")) arguments: (arguments . [ diff --git a/crates/grammars/src/tsx/outline.scm b/crates/grammars/src/tsx/outline.scm index 2c08c30ad5..19099b0076 100644 --- a/crates/grammars/src/tsx/outline.scm +++ b/crates/grammars/src/tsx/outline.scm @@ -175,6 +175,7 @@ name: (_) @name) @item ; Add support for (node:test, bun:test and Jest) runnable +; Also matches direct modifiers: .skip, .todo, .only, .failing (Jest, Bun, Vitest) ((call_expression function: [ (identifier) @_name @@ -194,7 +195,10 @@ (identifier) @name ]))) @item -; Add support for parameterized tests +; Parameterized and conditional tests. Docs per runner: +; Jest: https://jestjs.io/docs/api#testeachtablename-fn-timeout +; Vitest: https://vitest.dev/api/ +; Bun: https://bun.sh/docs/test/writing-tests#test-modifiers ((call_expression function: (call_expression function: (member_expression @@ -205,7 +209,13 @@ ] property: (property_identifier) @_property) (#any-of? @_name "it" "test" "describe" "context" "suite") - (#any-of? @_property "each")) + (#any-of? @_property + ; Jest, Bun, Vitest + "each" + ; Vitest + "skipIf" "runIf" + ; Bun + "if" "todoIf")) arguments: (arguments . [ diff --git a/crates/grammars/src/tsx/runnables.scm b/crates/grammars/src/tsx/runnables.scm index db1f69a2c2..8b0b160c86 100644 --- a/crates/grammars/src/tsx/runnables.scm +++ b/crates/grammars/src/tsx/runnables.scm @@ -1,5 +1,6 @@ ; Add support for (node:test, bun:test and Jest) runnable ; Function expression that has `it`, `test` or `describe` as the function name +; Also matches direct modifiers: .skip, .todo, .only, .failing (Jest, Bun, Vitest) ((call_expression function: [ (identifier) @_name @@ -20,7 +21,10 @@ ])) @_js-test (#set! tag js-test)) -; Add support for parameterized tests +; Parameterized and conditional tests. Docs per runner: +; Jest: https://jestjs.io/docs/api#testeachtablename-fn-timeout +; Vitest: https://vitest.dev/api/ +; Bun: https://bun.sh/docs/test/writing-tests#test-modifiers ((call_expression function: (call_expression function: (member_expression @@ -31,7 +35,13 @@ ] property: (property_identifier) @_property) (#any-of? @_name "it" "test" "describe" "context" "suite") - (#any-of? @_property "each")) + (#any-of? @_property + ; Jest, Bun, Vitest + "each" + ; Vitest + "skipIf" "runIf" + ; Bun + "if" "todoIf")) arguments: (arguments . [ diff --git a/crates/grammars/src/typescript/outline.scm b/crates/grammars/src/typescript/outline.scm index 2c08c30ad5..19099b0076 100644 --- a/crates/grammars/src/typescript/outline.scm +++ b/crates/grammars/src/typescript/outline.scm @@ -175,6 +175,7 @@ name: (_) @name) @item ; Add support for (node:test, bun:test and Jest) runnable +; Also matches direct modifiers: .skip, .todo, .only, .failing (Jest, Bun, Vitest) ((call_expression function: [ (identifier) @_name @@ -194,7 +195,10 @@ (identifier) @name ]))) @item -; Add support for parameterized tests +; Parameterized and conditional tests. Docs per runner: +; Jest: https://jestjs.io/docs/api#testeachtablename-fn-timeout +; Vitest: https://vitest.dev/api/ +; Bun: https://bun.sh/docs/test/writing-tests#test-modifiers ((call_expression function: (call_expression function: (member_expression @@ -205,7 +209,13 @@ ] property: (property_identifier) @_property) (#any-of? @_name "it" "test" "describe" "context" "suite") - (#any-of? @_property "each")) + (#any-of? @_property + ; Jest, Bun, Vitest + "each" + ; Vitest + "skipIf" "runIf" + ; Bun + "if" "todoIf")) arguments: (arguments . [ diff --git a/crates/grammars/src/typescript/runnables.scm b/crates/grammars/src/typescript/runnables.scm index 38fee610e8..b2321b816c 100644 --- a/crates/grammars/src/typescript/runnables.scm +++ b/crates/grammars/src/typescript/runnables.scm @@ -1,5 +1,6 @@ ; Add support for (node:test, bun:test, Jest and Deno.test) runnable ; Function expression that has `it`, `test` or `describe` as the function name +; Also matches direct modifiers: .skip, .todo, .only, .failing (Jest, Bun, Vitest) ((call_expression function: [ (identifier) @_name @@ -20,7 +21,10 @@ ])) @_js-test (#set! tag js-test)) -; Add support for parameterized tests +; Parameterized and conditional tests. Docs per runner: +; Jest: https://jestjs.io/docs/api#testeachtablename-fn-timeout +; Vitest: https://vitest.dev/api/ +; Bun: https://bun.sh/docs/test/writing-tests#test-modifiers ((call_expression function: (call_expression function: (member_expression @@ -31,7 +35,13 @@ ] property: (property_identifier) @_property) (#any-of? @_name "it" "test" "describe" "context" "suite") - (#any-of? @_property "each")) + (#any-of? @_property + ; Jest, Bun, Vitest + "each" + ; Vitest + "skipIf" "runIf" + ; Bun + "if" "todoIf")) arguments: (arguments . [ diff --git a/crates/languages/src/typescript.rs b/crates/languages/src/typescript.rs index 5572e43f04..e8d18840e0 100644 --- a/crates/languages/src/typescript.rs +++ b/crates/languages/src/typescript.rs @@ -1433,6 +1433,184 @@ mod tests { ); } + #[gpui::test] + async fn test_conditional_test_wrappers(cx: &mut TestAppContext) { + for language in [ + crate::language( + "typescript", + tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into(), + ), + crate::language("tsx", tree_sitter_typescript::LANGUAGE_TSX.into()), + crate::language("javascript", tree_sitter_typescript::LANGUAGE_TSX.into()), + ] { + let text = r#" + it.runIf(true)("runIf test", () => { + true; + }); + + it.skipIf(false)("skipIf test", () => { + true; + }); + + test.runIf(true)("runIf test 2", () => { + true; + }); + + test.skipIf(false)("skipIf test 2", () => { + true; + }); + + describe.runIf(true)("runIf describe", () => { + it("inner test", () => { + true; + }); + }); + + describe.skipIf(false)("skipIf describe", () => { + it("inner test 2", () => { + true; + }); + }); + + it.todoIf(false)("todoIf test", () => { + true; + }); + + it.if(true)("if test", () => { + true; + }); + + test.todoIf(false)("todoIf test 2", () => { + true; + }); + + test.if(true)("if test 2", () => { + true; + }); + + describe.todoIf(false)("todoIf describe", () => { + it("inner todoIf", () => { + true; + }); + }); + + describe.if(true)("if describe", () => { + it("inner if", () => { + true; + }); + }); + + test.failing("failing test", () => { + true; + }); + + it.failing("failing it", () => { + true; + }); + + it.each([1, 2, 3])("each test", () => { + true; + }); + + describe.each([1, 2])("each describe", () => { + it("inner each", () => { + true; + }); + }); + + it.skip("skip test", () => { + true; + }); + + it.only("only test", () => { + true; + }); + + it.todo("todo test"); + "# + .unindent(); + + let text_len = text.len(); + let buffer = cx.new(|cx| language::Buffer::local(text, cx).with_language(language, cx)); + cx.executor().run_until_parked(); + + let outline = buffer.update(cx, |buffer, _cx| buffer.snapshot().outline(None)); + let outline_names = outline + .items + .iter() + .map(|item| item.text.as_str()) + .collect::>(); + assert_eq!( + outline_names, + [ + "runIf test", + "skipIf test", + "runIf test 2", + "skipIf test 2", + "runIf describe", + "it inner test", + "skipIf describe", + "it inner test 2", + "todoIf test", + "if test", + "todoIf test 2", + "if test 2", + "todoIf describe", + "it inner todoIf", + "if describe", + "it inner if", + "test.failing failing test", + "it.failing failing it", + "each test", + "each describe", + "it inner each", + "it.skip skip test", + "it.only only test", + "it.todo todo test", + ] + ); + + let snapshot = buffer.update(cx, |buffer, _| buffer.snapshot()); + let runnable_names = snapshot + .runnable_ranges(0..text_len) + .map(|runnable| { + snapshot + .text_for_range(runnable.run_range) + .collect::() + }) + .collect::>(); + assert_eq!( + runnable_names, + [ + "runIf test", + "skipIf test", + "runIf test 2", + "skipIf test 2", + "runIf describe", + "inner test", + "skipIf describe", + "inner test 2", + "todoIf test", + "if test", + "todoIf test 2", + "if test 2", + "todoIf describe", + "inner todoIf", + "if describe", + "inner if", + "failing test", + "failing it", + "each test", + "each describe", + "inner each", + "skip test", + "only test", + "todo test", + ] + ); + } + } + #[gpui::test] async fn test_package_json_discovery(executor: BackgroundExecutor, cx: &mut TestAppContext) { cx.update(|cx| {