From 8a6088dcec277981d9de5be0febf5f5154e66b36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Makuch?= Date: Sat, 26 Aug 2023 11:54:58 +0200 Subject: [PATCH] Supporting tables that have both horizontal and vertical headers --- lib/capybara/node/matchers.rb | 8 ++--- lib/capybara/selector/definition/table.rb | 27 ++++++++++++--- lib/capybara/spec/session/has_table_spec.rb | 37 +++++++++++++++++++++ lib/capybara/spec/views/tables.erb | 23 +++++++++++++ 4 files changed, 86 insertions(+), 9 deletions(-) diff --git a/lib/capybara/node/matchers.rb b/lib/capybara/node/matchers.rb index 8eeeb19e30..aefcb62da4 100644 --- a/lib/capybara/node/matchers.rb +++ b/lib/capybara/node/matchers.rb @@ -517,13 +517,13 @@ def has_no_select?(locator = nil, **options, &optional_filter_block) # # @param [String] locator The id or caption of a table # @option options [Array>] :rows - # Text which should be contained in the tables `` elements organized by row (`` visibility is not considered) + # Text which should be contained in the tables `` and `` elements organized by row (`` and `` visibility is not considered) # @option options [Array>, Array>] :with_rows - # Partial set of text which should be contained in the tables `` elements organized by row (`` visibility is not considered) + # Partial set of text which should be contained in the tables `` and `` elements organized by row (`` and `` visibility is not considered) # @option options [Array>] :cols - # Text which should be contained in the tables `` elements organized by column (`` visibility is not considered) + # Text which should be contained in the tables `` and `` elements organized by row (`` and `` visibility is not considered) # @option options [Array>, Array>] :with_cols - # Partial set of text which should be contained in the tables `` elements organized by column (`` visibility is not considered) + # Partial set of which should be contained in the tables `` and `` elements organized by row (`` and `` visibility is not considered) # @return [Boolean] Whether it exists # def has_table?(locator = nil, **options, &optional_filter_block) diff --git a/lib/capybara/selector/definition/table.rb b/lib/capybara/selector/definition/table.rb index b93f4812fe..3d9180088c 100644 --- a/lib/capybara/selector/definition/table.rb +++ b/lib/capybara/selector/definition/table.rb @@ -52,6 +52,7 @@ expression_filter(:with_rows, valid_values: [Array]) do |xpath, rows| rows_conditions = rows.map { |row| match_row(row) }.reduce(:&) + xpath[rows_conditions] end @@ -77,10 +78,25 @@ def match_row(row, match_size: false) if row.is_a? Hash row_match_cells_to_headers(row) else - XPath.descendant(:td)[row_match_ordered_cells(row)] + XPath.descendant(:td, :th)[row_match_ordered_cells(row)] end ] - xp = xp[XPath.descendant(:td).count.equals(row.size)] if match_size + + xp = xp[ + # there's no THEAD for this TR + XPath.ancestor(:table)[XPath.child(:thead)].not + # expect as many TDs (and only TDs) as provided by the user + .and(XPath.descendant(:td).count.equals(row.size)) + # bacause nodes other than TDs act as headers + .or( + # there's a THEAD for this TR + XPath.ancestor(:table)[XPath.child(:thead)] + # expect as many TDs or THs as provided by the user + .and(XPath.descendant(:td, :th).count.equals(row.size)) + # because they are all considered row cells in that case + ) + ] if match_size + xp end @@ -92,16 +108,17 @@ def match_row_count(size) def row_match_cells_to_headers(row) row.map do |header, cell| header_xp = XPath.ancestor(:table)[1].descendant(:tr)[1].descendant(:th)[XPath.string.n.is(header)] - XPath.descendant(:td)[ + XPath.descendant(:td, :th)[ XPath.string.n.is(cell) & header_xp.boolean & XPath.position.equals(header_xp.preceding_sibling.count.plus(1)) ] end.reduce(:&) end def row_match_ordered_cells(row) - row_conditions = row.map do |cell| - XPath.self(:td)[XPath.string.n.is(cell)] + row_conditions = row.map do |cell| + XPath.self(:td, :th)[XPath.string.n.is(cell)] end + row_conditions.reverse.reduce do |cond, cell| cell[XPath.following_sibling[cond]] end diff --git a/lib/capybara/spec/session/has_table_spec.rb b/lib/capybara/spec/session/has_table_spec.rb index 42ac1775e7..517c0ef699 100644 --- a/lib/capybara/spec/session/has_table_spec.rb +++ b/lib/capybara/spec/session/has_table_spec.rb @@ -59,6 +59,34 @@ ]) end + context 'with a table that has both horizontal and vertical headers' do + it 'should accept arrays representing rows' do + expect(@session).to have_table('Horizontal and Vertical Headers', rows: + [ + %w[Tim London 555-1234], + %w[Joe Berlin 555-5678], + ]) + end + + it 'should key rows by vertical headers' do + expect(@session).to have_table('Horizontal and Vertical Headers', with_rows: + [ + { 'Name' => 'Tim', 'City' => 'London', 'Phone' => '555-1234' }, + { 'Name' => 'Joe', 'City' => 'Berlin', 'Phone' => '555-5678' }, + ]) + end + + it 'should match all cols with array of cell values' do + expect(@session).to have_table('Horizontal and Vertical Headers', cols: + [ + %w[Tim Joe], + %w[London Berlin], + %w[555-1234 555-5678], + ] + ) + end + end + it 'should match with vertical headers' do expect(@session).to have_table('Vertical Headers', with_cols: [ @@ -115,6 +143,15 @@ ]) end + it "should accept arrays representing TDs of rows with THs" do + expect(@session).to have_table('Vertical Headers', with_rows: + [ + ["Thomas", "Danilo", "Vern", "Ratke", "Palmer"], + ["Walpole", "Wilkinson", "Konopelski", "Lawrence", "Sawayn"], + ["Oceanside", "Johnsonville", "Everette", "East Sorayashire", "West Trinidad"] + ]) + end + it 'should be false if the table is not on the page' do expect(@session).not_to have_table('Monkey') end diff --git a/lib/capybara/spec/views/tables.erb b/lib/capybara/spec/views/tables.erb index e26e20be0e..e8b069cbb7 100644 --- a/lib/capybara/spec/views/tables.erb +++ b/lib/capybara/spec/views/tables.erb @@ -62,6 +62,29 @@ + + + + + + + + + + + + + + + + + + + + + +
Horizontal and Vertical Headers
NameCityPhone
TimLondon555-1234
JoeBerlin555-5678
+
Horizontal Headers