Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion lib/ruby_lsp/ruby_lsp_rspec/code_lens.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 6 additions & 1 deletion lib/ruby_lsp/ruby_lsp_rspec/test_discovery.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down
116 changes: 116 additions & 0 deletions spec/code_lens_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down
108 changes: 108 additions & 0 deletions spec/test_discovery_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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