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).
<img width="905" height="395" alt="image"
src="https://github.com/user-attachments/assets/7386ae4c-f2e6-4fe8-9e33-674d6821d3fa"
/>
<img width="972" height="787" alt="image"
src="https://github.com/user-attachments/assets/c7c55bb2-ed50-4ce0-ae31-af341ab9eb3a"
/>
<img width="167" height="488" alt="image"
src="https://github.com/user-attachments/assets/75e07913-390e-4674-a5fb-7113ffd1fa85"
/>

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 <kirill@zed.dev>
This commit is contained in:
( Nechiforel David-Samuel ) NsdHSOandKirill Bulatov authored and GitHub committed 2026-07-01 17:36:57 +00:00
1 parent b206841b4b
commit d0802abdec
7 files changed
+250 -12

No files matched your search

+12 -2
View File
@@ -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
.
[
+12 -2
View File
@@ -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
.
[
+12 -2
View File
@@ -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
.
[
+12 -2
View File
@@ -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
.
[
+12 -2
View File
@@ -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
.
[
+12 -2
View File
@@ -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
.
[
+178
View File
@@ -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::<Vec<_>>();
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::<String>()
})
.collect::<Vec<_>>();
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| {