From 5e133126c31b4d980cfa8ed4b8811b0b476cfcaf Mon Sep 17 00:00:00 2001 From: Thomas Walpole Date: Tue, 19 Mar 2019 11:39:34 -0700 Subject: [PATCH] Refactor :table selector filters --- lib/capybara/selector.rb | 134 +++++++++----------- lib/capybara/selector/xpath_extensions.rb | 8 ++ lib/capybara/spec/session/has_table_spec.rb | 8 +- 3 files changed, 75 insertions(+), 75 deletions(-) diff --git a/lib/capybara/selector.rb b/lib/capybara/selector.rb index ec3b1598..0d93ecf6 100644 --- a/lib/capybara/selector.rb +++ b/lib/capybara/selector.rb @@ -459,101 +459,93 @@ Capybara.add_selector(:table, locator_type: [String, Symbol]) do expression_filter(:with_cols, valid_values: [Array]) do |xpath, cols| col_conditions = cols.map do |col| if col.is_a? Hash - col.reduce(nil) do |xp, (header, cell)| - header_xp = XPath.descendant(:th)[XPath.string.n.is(header)] - cell_xp = XPath.descendant(:tr)[header_xp].descendant(:td) - next cell_xp[XPath.string.n.is(cell)] unless xp - - table_ancestor = XPath.ancestor(:table)[1] - xp = XPath::Expression.new(:join, table_ancestor, xp) - cell_xp[XPath.string.n.is(cell) & XPath.position.equals(xp.preceding_sibling(:td).count.plus(1))] + col.reduce(nil) do |xp, (header, cell_str)| + header = XPath.descendant(:th)[XPath.string.n.is(header)] + td = XPath.descendant(:tr)[header].descendant(:td) + cell_condition = XPath.string.n.is(cell_str) + cell_condition &= prev_col_position?(XPath.ancestor(:table)[1].join(xp)) if xp + td[cell_condition] end else - cells_xp = col.reduce(nil) do |xp, cell| - cell_conditions = [XPath.string.n.is(cell)] - if xp - prev_row_xp = XPath::Expression.new(:join, XPath.ancestor(:tr)[1].preceding_sibling(:tr), xp) - cell_conditions << XPath.position.equals(prev_row_xp.preceding_sibling(:td).count.plus(1)) + cells_xp = col.reduce(nil) do |prev_cell, cell_str| + cell_condition = XPath.string.n.is(cell_str) + + if prev_cell + prev_cell = XPath.ancestor(:tr)[1].preceding_sibling(:tr).join(prev_cell) + cell_condition &= prev_col_position?(prev_cell) end - XPath.descendant(:td)[cell_conditions.reduce :&] + + XPath.descendant(:td)[cell_condition] end - XPath::Expression.new(:join, XPath.descendant(:tr), cells_xp) + XPath.descendant(:tr).join(cells_xp) end end.reduce(:&) 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 } + 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] + col_conditions = rows.map { |row| match_row(row, match_size: true) }.reduce(:&) + xpath[match_row_count(rows.size)][col_conditions] end expression_filter(:with_rows, valid_values: [Array]) do |xpath, rows| - rows_conditions = rows.map do |row| - if row.is_a? Hash - row_conditions = 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.string.n.is(cell) & XPath.position.equals(header_xp.preceding_sibling.count.plus(1)) - ] - end.reduce(:&) - XPath.descendant(:tr)[row_conditions] - else - 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 - XPath.descendant(:tr)[XPath.descendant(:td)[row_conditions]] - end - end.reduce(:&) + rows_conditions = rows.map { |row| match_row(row) }.reduce(:&) xpath[rows_conditions] end expression_filter(:rows, valid_values: [Array]) do |xpath, rows| - 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| - header_xp = XPath.ancestor(:table)[1].descendant(:tr)[1].descendant(:th)[XPath.string.n.is(header)] - XPath.descendant(:td)[ - XPath.string.n.is(cell) & XPath.position.equals(header_xp.preceding_sibling.count.plus(1)) - ] - end.reduce(:&) - XPath.descendant(:tr)[row_conditions] - else - 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 - XPath.descendant(:tr)[XPath.descendant(:td)[row_conditions]] - end - row_xpath[XPath.descendant(:td).count.equals(row.size)] - end.reduce(:&) - xpath[rows_conditions] + rows_conditions = rows.map { |row| match_row(row, match_size: true) }.reduce(:&) + xpath[match_row_count(rows.size)][rows_conditions] end describe_expression_filters do |caption: nil, **| " with caption \"#{caption}\"" if caption end + + def prev_col_position?(cell) + XPath.position.equals(cell_position(cell)) + end + + def cell_position(cell) + cell.preceding_sibling(:td).count.plus(1) + end + + def match_row(row, match_size: false) + xp = XPath.descendant(:tr)[ + if row.is_a? Hash + row_match_cells_to_headers(row) + else + XPath.descendant(:td)[row_match_ordered_cells(row)] + end + ] + xp = xp[XPath.descendant(:td).count.equals(row.size)] if match_size + xp + end + + def match_row_count(size) + XPath.descendant(:tbody).descendant(:tr).count.equals(size) | (XPath.descendant(:tr).count.equals(size) & ~XPath.descendant(:tbody)) + end + + 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.string.n.is(cell) & 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)] + end + row_conditions.reverse.reduce do |cond, cell| + cell[XPath.following_sibling[cond]] + end + end end Capybara.add_selector(:table_row, locator_type: [Array, Hash]) do diff --git a/lib/capybara/selector/xpath_extensions.rb b/lib/capybara/selector/xpath_extensions.rb index 8990949b..fe0ff3dd 100644 --- a/lib/capybara/selector/xpath_extensions.rb +++ b/lib/capybara/selector/xpath_extensions.rb @@ -7,3 +7,11 @@ module XPath end end end + +module XPath + module DSL + def join(*expressions) + XPath::Expression.new(:join, *[self, expressions].flatten) + end + end +end diff --git a/lib/capybara/spec/session/has_table_spec.rb b/lib/capybara/spec/session/has_table_spec.rb index 76150d8f..1ddeb145 100644 --- a/lib/capybara/spec/session/has_table_spec.rb +++ b/lib/capybara/spec/session/has_table_spec.rb @@ -49,8 +49,8 @@ Capybara::SpecHelper.spec '#has_table?' do %w[Thomas Walpole Oceanside], %w[Danilo Wilkinson Johnsonville], %w[Vern Konopelski Everette], - ["Ratke", "Lawrence", "East Sorayashire"], - ["Palmer", "Sawayn", "West Trinidad"] + ['Ratke', 'Lawrence', 'East Sorayashire'], + ['Palmer', 'Sawayn', 'West Trinidad'] ]) end @@ -84,8 +84,8 @@ Capybara::SpecHelper.spec '#has_table?' do %w[Thomas Walpole Oceanside], %w[Danilo Wilkinson Johnsonville], %w[Vern Konopelski Everette], - ["Ratke", "Lawrence", "East Sorayashire"], - ["Palmer", "Sawayn", "West Trinidad"] + ['Ratke', 'Lawrence', 'East Sorayashire'], + ['Palmer', 'Sawayn', 'West Trinidad'] ]) end