From ae4a2bc2b2218dcad798457c2f9d196b1272c62b Mon Sep 17 00:00:00 2001 From: Micah Geisel Date: Wed, 18 Jan 2023 09:48:22 -0600 Subject: [PATCH] ruby: introduce Gherkin::Query#parent_locations. Cucumber::Core::Compiler will use this to tell Cucumber::Core::Test::Case the locations of its parent lines, such as `Feature:`, `Background:`, and `Rule:`, so that it will correctly match them during location filtering. --- CHANGELOG.md | 1 + ruby/lib/gherkin/query.rb | 32 +++++++++++++++------ ruby/spec/gherkin/query_spec.rb | 51 +++++++++++++++++++++++++++++++++ 3 files changed, 75 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 59cba7c44..c0a008ae2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ This document is formatted according to the principles of [Keep A CHANGELOG](htt - (i18n) Added Danish translation of "Rule" - (i18n) Added Dutch translation of "Rule" - (i18n) Added Esperanto translation of "Rule" +- [Ruby] Added `Gherkin::Query#parent_locations` for determining a scenario's parents' line numbers ([#89](https://github.com/cucumber/gherkin/pull/89)) ## [26.2.0] - 2023-04-07 ### Changed diff --git a/ruby/lib/gherkin/query.rb b/ruby/lib/gherkin/query.rb index b129e0e9a..12fa8226f 100644 --- a/ruby/lib/gherkin/query.rb +++ b/ruby/lib/gherkin/query.rb @@ -2,12 +2,19 @@ module Gherkin class Query def initialize @ast_node_locations = {} + @scenario_parent_locations = {} + @background_locations = {} end def update(message) update_feature(message.gherkin_document.feature) if message.gherkin_document end + def scenario_parent_locations(scenario_node_id) + return @scenario_parent_locations[scenario_node_id] if @scenario_parent_locations.has_key?(scenario_node_id) + raise AstNodeNotLocatedException, "No scenario parent locations found for #{scenario_node_id} }. Known: #{@scenario_parent_locations.keys}" + end + def location(ast_node_id) return @ast_node_locations[ast_node_id] if @ast_node_locations.has_key?(ast_node_id) raise AstNodeNotLocatedException, "No location found for #{ast_node_id} }. Known: #{@ast_node_locations.keys}" @@ -20,27 +27,27 @@ def update_feature(feature) store_nodes_location(feature.tags) feature.children.each do |child| - update_rule(child.rule) if child.rule - update_background(child.background) if child.background - update_scenario(child.scenario) if child.scenario + update_rule(feature, child.rule) if child.rule + update_background(feature, child.background) if child.background + update_scenario(feature, child.rule, child.scenario) if child.scenario end end - def update_rule(rule) + def update_rule(feature, rule) return if rule.nil? store_nodes_location(rule.tags) - rule.children.each do |child| - update_background(child.background) if child.background - update_scenario(child.scenario) if child.scenario + update_background(rule, child.background) if child.background + update_scenario(feature, rule, child.scenario) if child.scenario end end - def update_background(background) + def update_background(parent, background) update_steps(background.steps) + @background_locations[parent] = background.location end - def update_scenario(scenario) + def update_scenario(feature, rule, scenario) store_node_location(scenario) store_nodes_location(scenario.tags) update_steps(scenario.steps) @@ -48,6 +55,13 @@ def update_scenario(scenario) store_nodes_location(examples.tags || []) store_nodes_location(examples.table_body || []) end + + @scenario_parent_locations[scenario.id] = [ + feature.location, + @background_locations[feature], + rule&.location, + @background_locations[rule], + ].compact end def update_steps(steps) diff --git a/ruby/spec/gherkin/query_spec.rb b/ruby/spec/gherkin/query_spec.rb index f62b28c45..75b85b497 100644 --- a/ruby/spec/gherkin/query_spec.rb +++ b/ruby/spec/gherkin/query_spec.rb @@ -47,6 +47,7 @@ def find_message_by_attribute(messages, attribute) Examples: | Status | | passed | + | failed | @rule-tag Rule: this is a rule @@ -71,6 +72,56 @@ def find_message_by_attribute(messages, attribute) end end + describe '#scenario_parent_locations' do + before do + messages.each { |message| subject.update(message) } + end + + let(:background) { find_message_by_attribute(gherkin_document.feature.children, :background) } + let(:scenarios) { filter_messages_by_attribute(gherkin_document.feature.children, :scenario) } + + context 'without rule' do + let(:scenario) { scenarios.first } + + it 'provides the feature and background locations of a given scenario node id' do + expect(subject.scenario_parent_locations(scenario.id)).to eq([ + gherkin_document.feature.location, + background.location, + ]) + end + end + + context 'with rule' do + let(:rule) { find_message_by_attribute(gherkin_document.feature.children, :rule) } + let(:rule_background) { find_message_by_attribute(rule.children, :background) } + let(:scenario) { find_message_by_attribute(rule.children, :scenario) } + + it 'provides the feature, background, rule, and rule background locations of a given scenario node id' do + expect(subject.scenario_parent_locations(scenario.id)).to eq([ + gherkin_document.feature.location, + background.location, + rule.location, + rule_background.location, + ]) + end + end + + context 'in a scenario outline' do + let(:scenario) { scenarios.last } + + it 'provides the feature and background locations of a given scenario outline node id' do + expect(subject.scenario_parent_locations(scenario.id)).to eq([ + gherkin_document.feature.location, + background.location, + ]) + end + end + + it 'raises an exception if called with an invalid id' do + expect { subject.scenario_parent_locations("BAD") }.to raise_error(Gherkin::AstNodeNotLocatedException) + end + end + describe '#location' do before do messages.each { |message| subject.update(message) }