From ee688692ff2a1ce8ca7cfdd638c6e0ba3e7eca23 Mon Sep 17 00:00:00 2001 From: Nathan Daniels Date: Mon, 28 Jul 2025 21:42:01 -0400 Subject: [PATCH 1/4] feat: add capybara keyword support --- lib/ruby_lsp/ruby_lsp_rspec/code_lens.rb | 6 +++--- lib/ruby_lsp/ruby_lsp_rspec/document_symbol.rb | 6 +++--- lib/ruby_lsp/ruby_lsp_rspec/test_discovery.rb | 8 ++++---- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/lib/ruby_lsp/ruby_lsp_rspec/code_lens.rb b/lib/ruby_lsp/ruby_lsp_rspec/code_lens.rb index a5c999d..836caad 100644 --- a/lib/ruby_lsp/ruby_lsp_rspec/code_lens.rb +++ b/lib/ruby_lsp/ruby_lsp_rspec/code_lens.rb @@ -24,10 +24,10 @@ def initialize(response_builder, uri, dispatcher, rspec_command, debug: false) #: (Prism::CallNode) -> void def on_call_node_enter(node) case node.message - when "example", "it", "specify" + when "example", "it", "specify", "scenario" name = generate_name(node) add_test_code_lens(node, name: name, kind: :example) - when "context", "describe" + when "context", "describe", "feature" return unless valid_group?(node) name = generate_name(node) @@ -41,7 +41,7 @@ def on_call_node_enter(node) #: (Prism::CallNode) -> void def on_call_node_leave(node) case node.message - when "context", "describe" + when "context", "describe", "feature" return unless valid_group?(node) @group_id_stack.pop diff --git a/lib/ruby_lsp/ruby_lsp_rspec/document_symbol.rb b/lib/ruby_lsp/ruby_lsp_rspec/document_symbol.rb index 83b9ceb..61f803d 100644 --- a/lib/ruby_lsp/ruby_lsp_rspec/document_symbol.rb +++ b/lib/ruby_lsp/ruby_lsp_rspec/document_symbol.rb @@ -16,7 +16,7 @@ def initialize(response_builder, dispatcher) #: (Prism::CallNode) -> void def on_call_node_enter(node) case node.message - when "example", "it", "specify" + when "example", "it", "specify", "scenario" name = generate_name(node) return unless name @@ -27,7 +27,7 @@ def on_call_node_enter(node) selection_range: range_from_node(node), range: range_from_node(node), ) - when "context", "describe", "shared_examples", "shared_context", "shared_examples_for" + when "context", "describe", "shared_examples", "shared_context", "shared_examples_for", "feature", "scenario" return if node.receiver && node.receiver&.slice != "RSpec" name = generate_name(node) @@ -50,7 +50,7 @@ def on_call_node_enter(node) #: (Prism::CallNode) -> void def on_call_node_leave(node) case node.message - when "context", "describe", "shared_examples", "shared_context", "shared_examples_for" + when "context", "describe", "shared_examples", "shared_context", "shared_examples_for", "feature", "scenario" return if node.receiver && node.receiver&.slice != "RSpec" @response_builder.pop diff --git a/lib/ruby_lsp/ruby_lsp_rspec/test_discovery.rb b/lib/ruby_lsp/ruby_lsp_rspec/test_discovery.rb index e349ab6..3906b93 100644 --- a/lib/ruby_lsp/ruby_lsp_rspec/test_discovery.rb +++ b/lib/ruby_lsp/ruby_lsp_rspec/test_discovery.rb @@ -26,12 +26,12 @@ def initialize(response_builder, dispatcher, uri, workspace_path) #: (Prism::CallNode) -> void def on_call_node_enter(node) - return unless ["describe", "context", "it", "specify", "example"].include?(node.message) + return unless ["describe", "context", "it", "specify", "example", "feature", "scenario"].include?(node.message) case node.message - when "describe", "context" + when "describe", "context", "feature" handle_describe(node) - when "it", "specify", "example" + when "it", "specify", "example", "scenario" handle_example(node) end end @@ -39,7 +39,7 @@ def on_call_node_enter(node) #: (Prism::CallNode) -> void def on_call_node_leave(node) case node.message - when "context", "describe" + when "context", "describe", "feature" return unless valid_group?(node) @group_stack.pop From 609478c6b51814aab9a3762f9925c6e66257b1f4 Mon Sep 17 00:00:00 2001 From: Nathan Daniels Date: Tue, 4 Nov 2025 11:08:54 -0500 Subject: [PATCH 2/4] Add tests --- spec/test_discovery_spec.rb | 61 +++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/spec/test_discovery_spec.rb b/spec/test_discovery_spec.rb index bbe01e4..09fc12d 100644 --- a/spec/test_discovery_spec.rb +++ b/spec/test_discovery_spec.rb @@ -81,6 +81,67 @@ end end + # Tests capybara feature/scenario syntax + # see https://github.com/teamcapybara/capybara + it "discovers Capybara examples" do + source = <<~RUBY + feature "Sample test" do + scenario "first test" do + expect(true).to be(true) + end + + # Test a mixed syntax + it "second test" do + expect(true).to be(true) + end + end + + RSpec.describe Foo do + it "third test" do + expect(true).to be(true) + end + end + RUBY + + with_server(source, uri) do |server, uri| + server.process_message( + { + id: 1, + method: "rubyLsp/discoverTests", + params: { + textDocument: { uri: uri }, + }, + }, + ) + + items = pop_result(server).response + + expect(items.length).to eq(2) + + first_group = items.first + expect(first_group[:id]).to eq("./spec/fake_spec.rb:1") + expect(first_group[:label]).to eq("Sample test") + expect(first_group[:children].length).to eq(2) + + test_ids = first_group[:children].map { |i| i[:id] } + expect(test_ids).to include("./spec/fake_spec.rb:1::./spec/fake_spec.rb:2") + expect(test_ids).to include("./spec/fake_spec.rb:1::./spec/fake_spec.rb:6") + + test_labels = first_group[:children].map { |i| i[:label] } + expect(test_labels).to include("first test") + + expect(test_labels).to include("second test") + + second_group = items[1] + expect(second_group[:id]).to eq("./spec/fake_spec.rb:11") + expect(second_group[:label]).to eq("Foo") + expect(second_group[:children].length).to eq(1) + + test_ids = second_group[:children].map { |i| i[:id] } + expect(test_ids).to include("./spec/fake_spec.rb:11::./spec/fake_spec.rb:12") + end + end + it "discovers nested example groups" do source = <<~RUBY RSpec.describe "Outer group" do From bc55429a054c2ad24eb5c2d8ab4ad907a6382e6c Mon Sep 17 00:00:00 2001 From: Nathan Daniels Date: Tue, 4 Nov 2025 11:13:07 -0500 Subject: [PATCH 3/4] fix spec --- spec/test_discovery_spec.rb | 5 ----- 1 file changed, 5 deletions(-) diff --git a/spec/test_discovery_spec.rb b/spec/test_discovery_spec.rb index 09fc12d..c80b4ec 100644 --- a/spec/test_discovery_spec.rb +++ b/spec/test_discovery_spec.rb @@ -125,7 +125,6 @@ test_ids = first_group[:children].map { |i| i[:id] } expect(test_ids).to include("./spec/fake_spec.rb:1::./spec/fake_spec.rb:2") - expect(test_ids).to include("./spec/fake_spec.rb:1::./spec/fake_spec.rb:6") test_labels = first_group[:children].map { |i| i[:label] } expect(test_labels).to include("first test") @@ -133,12 +132,8 @@ expect(test_labels).to include("second test") second_group = items[1] - expect(second_group[:id]).to eq("./spec/fake_spec.rb:11") expect(second_group[:label]).to eq("Foo") expect(second_group[:children].length).to eq(1) - - test_ids = second_group[:children].map { |i| i[:id] } - expect(test_ids).to include("./spec/fake_spec.rb:11::./spec/fake_spec.rb:12") end end From 6fef815b2c5b4c8781319e1d9b0ebdd19bb55afa Mon Sep 17 00:00:00 2001 From: Ryan Mohrman Date: Wed, 19 Nov 2025 20:36:11 -0800 Subject: [PATCH 4/4] chore: Remove unecessary scenario keyword from test discovery --- lib/ruby_lsp/ruby_lsp_rspec/document_symbol.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/ruby_lsp/ruby_lsp_rspec/document_symbol.rb b/lib/ruby_lsp/ruby_lsp_rspec/document_symbol.rb index 61f803d..36fab7b 100644 --- a/lib/ruby_lsp/ruby_lsp_rspec/document_symbol.rb +++ b/lib/ruby_lsp/ruby_lsp_rspec/document_symbol.rb @@ -27,7 +27,7 @@ def on_call_node_enter(node) selection_range: range_from_node(node), range: range_from_node(node), ) - when "context", "describe", "shared_examples", "shared_context", "shared_examples_for", "feature", "scenario" + when "context", "describe", "shared_examples", "shared_context", "shared_examples_for", "feature" return if node.receiver && node.receiver&.slice != "RSpec" name = generate_name(node) @@ -50,7 +50,7 @@ def on_call_node_enter(node) #: (Prism::CallNode) -> void def on_call_node_leave(node) case node.message - when "context", "describe", "shared_examples", "shared_context", "shared_examples_for", "feature", "scenario" + when "context", "describe", "shared_examples", "shared_context", "shared_examples_for", "feature" return if node.receiver && node.receiver&.slice != "RSpec" @response_builder.pop