From 5146c4aa1f7bd95a0520582aa34ecf55243ea313 Mon Sep 17 00:00:00 2001 From: Thomas Walpole Date: Mon, 18 Mar 2019 11:02:27 -0700 Subject: [PATCH] Implement :cols option for :table selector and add documentation --- History.md | 2 +- lib/capybara/node/matchers.rb | 12 +++++++++-- lib/capybara/selector.rb | 22 ++++++++++++++++++++- lib/capybara/selector/selector.rb | 7 +++++++ lib/capybara/spec/session/has_table_spec.rb | 13 +++++++++++- 5 files changed, 51 insertions(+), 5 deletions(-) diff --git a/History.md b/History.md index a24bed83..8c255a2c 100644 --- a/History.md +++ b/History.md @@ -4,7 +4,7 @@ Release date: unreleased ### Added * `attach_file` now supports a block mode on JS capable drivers to more accurately test user behavior when file inputs are hidden (beta) -* :table selector now supports `with_rows` and `with_cols` filters +* :table selector now supports `with_rows`, 'rows', `with_cols`, and 'cols' filters ### Fixed diff --git a/lib/capybara/node/matchers.rb b/lib/capybara/node/matchers.rb index 4309622f..e33a09f8 100644 --- a/lib/capybara/node/matchers.rb +++ b/lib/capybara/node/matchers.rb @@ -514,8 +514,16 @@ module Capybara # # page.has_table?('People') # - # @param [String] locator The id or caption of a table - # @return [Boolean] Whether it exist + # @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) + # @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) + # @option options [Array>] :cols + # Text which should be contained in the tables `` elements organized by column (`` 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) + # @return [Boolean] Whether it exists # def has_table?(locator = nil, **options, &optional_filter_block) has_selector?(:table, locator, options, &optional_filter_block) diff --git a/lib/capybara/selector.rb b/lib/capybara/selector.rb index 0566fb3d..ec3b1598 100644 --- a/lib/capybara/selector.rb +++ b/lib/capybara/selector.rb @@ -483,6 +483,26 @@ Capybara.add_selector(:table, locator_type: [String, Symbol]) do xpath[col_conditions] end + expression_filter(:cols, valid_values: [Array]) do |xpath, cols| + raise ArgumentError, ":cols must be an Array of Arrays" unless cols.all? { |col| col.is_a? Array } + + rows = cols.transpose + xpath = xpath[XPath.descendant(:tbody).descendant(:tr).count.equals(rows.size) | (XPath.descendant(:tr).count.equals(rows.size) & ~XPath.descendant(:tbody))] + + col_conditions = rows.map do |row| + row_conditions = row.map do |cell| + XPath.self(:td)[XPath.string.n.is(cell)] + end + row_conditions = row_conditions.reverse.reduce do |cond, cell| + cell[XPath.following_sibling[cond]] + end + row_xpath = XPath.descendant(:tr)[XPath.descendant(:td)[row_conditions]] + row_xpath[XPath.descendant(:td).count.equals(row.size)] + end.reduce(:&) + + xpath[col_conditions] + end + expression_filter(:with_rows, valid_values: [Array]) do |xpath, rows| rows_conditions = rows.map do |row| if row.is_a? Hash @@ -507,7 +527,7 @@ Capybara.add_selector(:table, locator_type: [String, Symbol]) do end expression_filter(:rows, valid_values: [Array]) do |xpath, rows| - xpath = xpath[XPath.descendant(:tbody).descendant(:tr).count.equals(rows.size)] + xpath = xpath[XPath.descendant(:tbody).descendant(:tr).count.equals(rows.size) | (XPath.descendant(:tr).count.equals(rows.size) & ~XPath.descendant(:tbody))] rows_conditions = rows.map do |row| row_xpath = if row.is_a? Hash row_conditions = row.map do |header, cell| diff --git a/lib/capybara/selector/selector.rb b/lib/capybara/selector/selector.rb index 45c7fdbf..6282cd3b 100644 --- a/lib/capybara/selector/selector.rb +++ b/lib/capybara/selector/selector.rb @@ -158,6 +158,13 @@ module Capybara # * :caption (String) — Match text of associated caption # * :class ((String, Array, Regexp, XPath::Expression) — Matches the class(es) provided # * :style (String, Regexp, Hash) + # * :with_rows (Array>, Array>) - Partial match data - visibility of elements is not considered + # * :rows (Array>) — Match all s - visibility of elements is not considered + # * :with_cols (Array>, Array>) - Partial match data - visibility of elements is not considered + # * :cols (Array>) — Match all s - visibility of elements is not considered + # + # * **:table_row** - Find table row + # * Locator: Array, Hash table row contents - visibility of elements is not considered # # * **:frame** - Find frame/iframe elements # * Locator: Match id or name diff --git a/lib/capybara/spec/session/has_table_spec.rb b/lib/capybara/spec/session/has_table_spec.rb index 56500f35..76150d8f 100644 --- a/lib/capybara/spec/session/has_table_spec.rb +++ b/lib/capybara/spec/session/has_table_spec.rb @@ -43,7 +43,7 @@ Capybara::SpecHelper.spec '#has_table?' do ]) end - it 'should accept rows with array of cell values' do + it 'should accept all rows with array of cell values' do expect(@session).to have_table('Horizontal Headers', rows: [ %w[Thomas Walpole Oceanside], @@ -78,6 +78,17 @@ Capybara::SpecHelper.spec '#has_table?' do ]) end + it 'should match all cols with array of cell values' do + expect(@session).to have_table('Vertical Headers', cols: + [ + %w[Thomas Walpole Oceanside], + %w[Danilo Wilkinson Johnsonville], + %w[Vern Konopelski Everette], + ["Ratke", "Lawrence", "East Sorayashire"], + ["Palmer", "Sawayn", "West Trinidad"] + ]) + end + it "should not match if the order of cell values doesn't match" do expect(@session).not_to have_table('Vertical Headers', with_cols: [