diff --git a/lib/ruby_lsp/ruby_lsp_rspec/code_lens.rb b/lib/ruby_lsp/ruby_lsp_rspec/code_lens.rb index a5c999d..abd1399 100644 --- a/lib/ruby_lsp/ruby_lsp_rspec/code_lens.rb +++ b/lib/ruby_lsp/ruby_lsp_rspec/code_lens.rb @@ -55,9 +55,12 @@ def log_message(message) puts "[#{self.class}]: #{message}" end + # A node is valid if it has a block and the receiver is RSpec (or nil) #: (Prism::CallNode) -> bool def valid_group?(node) - !(node.block.nil? || (node.receiver && node.receiver&.slice != "RSpec")) + return false if node.block.nil? + + node.receiver.nil? || node.receiver&.slice == "RSpec" end #: (Prism::CallNode) -> String diff --git a/lib/ruby_lsp/ruby_lsp_rspec/test_discovery.rb b/lib/ruby_lsp/ruby_lsp_rspec/test_discovery.rb index e349ab6..41ff7ca 100644 --- a/lib/ruby_lsp/ruby_lsp_rspec/test_discovery.rb +++ b/lib/ruby_lsp/ruby_lsp_rspec/test_discovery.rb @@ -30,6 +30,8 @@ def on_call_node_enter(node) case node.message when "describe", "context" + return unless valid_group?(node) + handle_describe(node) when "it", "specify", "example" handle_example(node) @@ -116,9 +118,12 @@ def find_parent_test_group @group_stack.last end + # A node is valid if it has a block and the receiver is RSpec (or nil) #: (Prism::CallNode) -> bool def valid_group?(node) - !(node.block.nil? || (node.receiver && node.receiver&.slice != "RSpec")) + return false if node.block.nil? + + node.receiver.nil? || node.receiver&.slice == "RSpec" end #: (Prism::CallNode) -> String diff --git a/spec/code_lens_spec.rb b/spec/code_lens_spec.rb index e31f156..b86b6a4 100644 --- a/spec/code_lens_spec.rb +++ b/spec/code_lens_spec.rb @@ -229,6 +229,122 @@ def dummy end end + it "ignores describe and context calls without blocks" do + source = <<~RUBY + RSpec.describe "Valid group with block" do + it "test in valid group" do + end + end + + # These should be ignored because they don't have blocks + RSpec.describe "Invalid group without block" + RSpec.context "Another invalid group" + + # This should also work with non-RSpec receivers + describe "Valid group without RSpec prefix" do + it "test in valid group" do + end + end + + # Invalid without block, even without RSpec prefix + describe "Invalid without block" + context "Invalid context without block" + RUBY + + with_server(source, uri) do |server, uri| + server.process_message( + { + id: 1, + method: "textDocument/codeLens", + params: { + textDocument: { uri: uri }, + position: { line: 0, character: 0 }, + }, + }, + ) + + response = pop_result(server).response + + # Should only generate code lens for the 2 valid groups (with blocks) and their children + # Each group gets 3 code lenses (run, run in terminal, debug) + # Each example gets 3 code lenses + # So: 2 groups * 3 + 2 examples * 3 = 12 total + expect(response.count).to eq(12) + + # Verify the valid groups are present + group_commands = response.select { |r| r.data[:kind] == :group } + expect(group_commands.count).to eq(6) # 2 groups * 3 commands each + + # Check that the correct groups are present + group_names = group_commands.map { |cmd| cmd.command.arguments[1] }.uniq + expect(group_names).to contain_exactly("Valid group with block", "Valid group without RSpec prefix") + + # Verify examples are present + example_commands = response.select { |r| r.data[:kind] == :example } + expect(example_commands.count).to eq(6) # 2 examples * 3 commands each + end + end + + it "handles nested groups where some lack blocks" do + source = <<~RUBY + RSpec.describe "Outer group with block" do + # Valid nested group + describe "Valid nested group" do + it "nested test" do + end + end + + # Invalid nested group (no block) - should be ignored + describe "Invalid nested group" + + # Another valid nested group + context "Valid context" do + it "context test" do + end + end + + # Invalid context (no block) - should be ignored + context "Invalid context" + end + RUBY + + with_server(source, uri) do |server, uri| + server.process_message( + { + id: 1, + method: "textDocument/codeLens", + params: { + textDocument: { uri: uri }, + position: { line: 0, character: 0 }, + }, + }, + ) + + response = pop_result(server).response + + # Should generate code lens for: + # - 1 outer group (3 commands) + # - 2 valid nested groups (2 * 3 = 6 commands) + # - 2 examples (2 * 3 = 6 commands) + # Total: 15 commands + expect(response.count).to eq(15) + + # Check that only the valid groups are present + group_commands = response.select { |r| r.data[:kind] == :group } + expect(group_commands.count).to eq(9) # 3 groups * 3 commands each + + group_names = group_commands.map { |cmd| cmd.command.arguments[1] }.uniq + expect(group_names).to contain_exactly("Outer group with block", "Valid nested group", "Valid context") + + # Verify examples are present + example_commands = response.select { |r| r.data[:kind] == :example } + expect(example_commands.count).to eq(6) # 2 examples * 3 commands each + + example_names = example_commands.map { |cmd| cmd.command.arguments[1] }.uniq + expect(example_names).to contain_exactly("nested test", "context test") + end + end + context "with a custom rspec command configured" do let(:configuration) do { diff --git a/spec/test_discovery_spec.rb b/spec/test_discovery_spec.rb index 5b59f13..bbe01e4 100644 --- a/spec/test_discovery_spec.rb +++ b/spec/test_discovery_spec.rb @@ -178,5 +178,113 @@ expect(third_example[:label]).to eq("example at ./spec/fake_spec.rb:10") end end + + it "ignores describe and context calls without blocks" do + source = <<~RUBY + RSpec.describe "Valid group with block" do + it "test in valid group" do + expect(true).to be(true) + end + end + + # These should be ignored because they don't have blocks + RSpec.describe "Invalid group without block" + RSpec.context "Another invalid group" + + # This should also work with non-RSpec receivers + describe "Valid group without RSpec prefix" do + it "test in valid group" do + expect(true).to be(true) + end + end + + # Invalid without block, even without RSpec prefix + describe "Invalid without block" + context "Invalid context without block" + 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 + + # Should only find 2 valid groups (the ones with blocks) + expect(items.length).to eq(2) + + first_group = items.first + expect(first_group[:label]).to eq("Valid group with block") + expect(first_group[:children].length).to eq(1) + expect(first_group[:children][0][:label]).to eq("test in valid group") + + second_group = items[1] + expect(second_group[:label]).to eq("Valid group without RSpec prefix") + expect(second_group[:children].length).to eq(1) + expect(second_group[:children][0][:label]).to eq("test in valid group") + end + end + + it "handles nested groups where some lack blocks" do + source = <<~RUBY + RSpec.describe "Outer group with block" do + # Valid nested group + describe "Valid nested group" do + it "nested test" do + expect(true).to be(true) + end + end + + # Invalid nested group (no block) - should be ignored + describe "Invalid nested group" + + # Another valid nested group + context "Valid context" do + it "context test" do + expect(true).to be(true) + end + end + + # Invalid context (no block) - should be ignored + context "Invalid context" + 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(1) + + outer_group = items.first + expect(outer_group[:label]).to eq("Outer group with block") + # Should only have 2 children (the valid nested groups) + expect(outer_group[:children].length).to eq(2) + + nested_groups = outer_group[:children] + expect(nested_groups[0][:label]).to eq("Valid nested group") + expect(nested_groups[0][:children].length).to eq(1) + expect(nested_groups[0][:children][0][:label]).to eq("nested test") + + expect(nested_groups[1][:label]).to eq("Valid context") + expect(nested_groups[1][:children].length).to eq(1) + expect(nested_groups[1][:children][0][:label]).to eq("context test") + end + end end end