Skip to content

Instantly share code, notes, and snippets.

@mcmire
Created May 4, 2018 18:51
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mcmire/2c09ae3adba0bd8fa1f9ad65daef513a to your computer and use it in GitHub Desktop.
Save mcmire/2c09ae3adba0bd8fa1f9ad65daef513a to your computer and use it in GitHub Desktop.
Table matchers for RSpec
module FeatureSpecs
def have_row(expected_row)
HaveRowMatcher.new(self, expected_row)
end
class HaveRowMatcher
def initialize(context, expected_row)
@context = context
@expected_row = expected_row
end
def description
"should have row containing #{expected_row}"
end
def matches?(element)
@element = element
table = Table.new(context, element)
@actual_rows =
if expected_row.is_a?(Hash)
table.to_array_of_hashes
else
table.to_array_of_arrays
end
@actual_rows.include?(expected_row)
end
def failure_message
"Expected table to have row containing:\n" +
expected_row.pretty_inspect + "\n" +
"Full table:\n" +
actual_rows.pretty_inspect
end
def failure_message_when_negated
"Did not expect table to have row containing:\n" +
expected_row.pretty_inspect
end
protected
attr_reader :context, :expected_row, :element, :actual_rows
end
end
require_relative "../mixins/wait_for"
module FeatureSpecs
def have_table(selector, content:)
HaveTableMatcher.new(selector, content: content)
end
class HaveTableMatcher
include WaitFor
def initialize(selector, content:)
@selector = selector
@expected_content = content
end
def matches?(parent_element)
@element = parent_element.find(selector)
@table = Table.new(element)
wait_for("table to have content") do
actual_content == expected_content
end
rescue FeatureSpecs::WaitedTooLongError
false
end
def failure_message
"Expected to see table with content:" + "\n\n" +
expected_content.pretty_inspect + "\n\n" +
"However, the table contained:" + "\n\n" +
actual_content.pretty_inspect + "\n\n" +
"Diff:" + "\n" +
differ.diff(actual_content, expected_content)
end
private
attr_reader :selector, :expected_content, :element, :table
def actual_content
if expected_content[0].is_a?(Hash)
table.to_array_of_hashes
else
table.to_array_of_arrays
end
end
def differ
@_differ ||= RSpec::Support::Differ.new
end
end
end
module FeatureSpecs
class Table
def initialize(element)
@element = element
end
def find_body_row!(&block)
if row_element = find_body_row(&block)
row_element
else
raise "Couldn't find row element!"
end
end
def find_body_row(&block)
if row = body_rows.detect(&block)
row.element
end
end
def to_array_of_arrays
header_and_body_rows.map(&:to_array)
end
def to_array_of_hashes
body_rows.map(&:to_hash)
end
private
attr_reader :element
def header_and_body_rows
[header_row] + body_rows
end
def body_rows
@_body_rows ||= trs[1..-1].map { |tr| BodyTableRow.new(header_row, tr) }
end
def header_row
@_header_row ||= TableRow.new(trs[0])
end
def trs
@_trs ||= element.all('tr')
end
end
class TableRow
attr_reader :element
def initialize(element)
@element = element
end
def [](index)
to_array[index]
end
def to_array
@_to_array ||= cells.map { |cell| cell.text.strip.squish }
end
private
def cells
@_cells ||= element.all('th, td')
end
end
class BodyTableRow < TableRow
def initialize(header_row, element)
super(element)
@header_row = header_row
end
def [](key)
if key.is_a?(String)
to_hash[key]
else
to_array[key]
end
end
def to_hash
@_to_hash ||= header_row.to_array.zip(to_array).to_h
end
private
attr_reader :header_row
end
end
module FeatureSpecs
WaitedTooLongError = Class.new(Timeout::Error)
module WaitFor
def wait_for(condition_description)
Timeout.timeout(Capybara.default_max_wait_time) do
begin
sleep(0.1)
value = yield
end until value
value
end
rescue Timeout::Error
raise WaitedTooLongError.new(
"Timed out while waiting for #{condition_description}"
)
end
end
include WaitFor
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment