elabs_matchers 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (97) hide show
  1. data/.gitignore +2 -1
  2. data/Gemfile.lock +37 -26
  3. data/README.md +57 -47
  4. data/development.txt +26 -0
  5. data/doc/ElabsMatchers/Helpers/Fixtures.html +33 -23
  6. data/doc/ElabsMatchers/Helpers/NormalizeKeys.html +222 -0
  7. data/doc/ElabsMatchers/Helpers/ReloadRecord.html +302 -0
  8. data/doc/ElabsMatchers/Helpers/SelectYearAndMonth.html +251 -0
  9. data/doc/ElabsMatchers/Helpers.html +29 -22
  10. data/doc/ElabsMatchers/Matchers/Allow/AllowMatcher.html +707 -0
  11. data/doc/ElabsMatchers/Matchers/Allow.html +229 -0
  12. data/doc/ElabsMatchers/Matchers/ContainHash/ContainHashMatcher.html +506 -0
  13. data/doc/ElabsMatchers/Matchers/ContainHash.html +222 -0
  14. data/doc/ElabsMatchers/Matchers/HaveAttribute/HaveAttributeMatcher.html +656 -0
  15. data/doc/ElabsMatchers/Matchers/HaveAttribute.html +239 -0
  16. data/doc/ElabsMatchers/Matchers/HaveFields/HaveFieldsMatcher.html +572 -0
  17. data/doc/ElabsMatchers/Matchers/HaveFields.html +220 -0
  18. data/doc/ElabsMatchers/Matchers/HaveFlash/HaveFlashMatcher.html +654 -0
  19. data/doc/ElabsMatchers/Matchers/HaveFlash.html +306 -0
  20. data/doc/ElabsMatchers/Matchers/HaveFormErrorsOn/HaveFormErrorsOnMatcher.html +678 -0
  21. data/doc/ElabsMatchers/Matchers/HaveFormErrorsOn.html +236 -0
  22. data/doc/ElabsMatchers/Matchers/HaveHeader/HaveHeaderMatcher.html +568 -0
  23. data/doc/ElabsMatchers/Matchers/HaveHeader.html +219 -0
  24. data/doc/ElabsMatchers/Matchers/HaveImage/HaveImageMatcher.html +592 -0
  25. data/doc/ElabsMatchers/Matchers/HaveImage.html +219 -0
  26. data/doc/ElabsMatchers/Matchers/HaveTableRow/HaveTableRowMatcher.html +654 -0
  27. data/doc/ElabsMatchers/Matchers/HaveTableRow.html +236 -0
  28. data/doc/ElabsMatchers/Matchers/OnlyInclude/OnlyIncludeMatcher.html +494 -0
  29. data/doc/ElabsMatchers/Matchers/OnlyInclude.html +221 -0
  30. data/doc/ElabsMatchers/Matchers/Persist/PersistMatcher.html +586 -0
  31. data/doc/ElabsMatchers/Matchers/Persist.html +235 -0
  32. data/doc/ElabsMatchers/Matchers.html +29 -22
  33. data/doc/ElabsMatchers.html +1144 -21
  34. data/doc/_index.html +161 -79
  35. data/doc/class_list.html +12 -6
  36. data/doc/css/full_list.css +4 -2
  37. data/doc/css/style.css +50 -44
  38. data/doc/file.README.html +71 -73
  39. data/doc/file_list.html +12 -6
  40. data/doc/frames.html +20 -5
  41. data/doc/index.html +71 -73
  42. data/doc/js/app.js +52 -43
  43. data/doc/js/full_list.js +9 -9
  44. data/doc/js/jquery.js +4 -16
  45. data/doc/method_list.html +603 -77
  46. data/doc/top-level-namespace.html +24 -103
  47. data/elabs_matchers.gemspec +4 -3
  48. data/history.txt +15 -0
  49. data/lib/elabs_matchers/extensions/module.rb +24 -0
  50. data/lib/elabs_matchers/helpers/fixtures.rb +2 -2
  51. data/lib/elabs_matchers/helpers/{common.rb → normalize_keys.rb} +3 -4
  52. data/lib/elabs_matchers/helpers/{orm.rb → reload_record.rb} +9 -6
  53. data/lib/elabs_matchers/helpers/{capybara.rb → select_year_and_month.rb} +5 -3
  54. data/lib/elabs_matchers/matchers/allow.rb +130 -0
  55. data/lib/elabs_matchers/matchers/contain_hash.rb +58 -0
  56. data/lib/elabs_matchers/matchers/have_attribute.rb +62 -0
  57. data/lib/elabs_matchers/matchers/have_fields.rb +45 -0
  58. data/lib/elabs_matchers/matchers/have_flash.rb +65 -0
  59. data/lib/elabs_matchers/matchers/have_form_errors_on.rb +79 -0
  60. data/lib/elabs_matchers/matchers/have_header.rb +53 -0
  61. data/lib/elabs_matchers/matchers/have_image.rb +69 -0
  62. data/lib/elabs_matchers/matchers/have_table_row.rb +113 -0
  63. data/lib/elabs_matchers/matchers/only_include.rb +41 -0
  64. data/lib/elabs_matchers/matchers/persist.rb +42 -0
  65. data/lib/elabs_matchers/version.rb +1 -1
  66. data/lib/elabs_matchers.rb +90 -10
  67. data/spec/elabs_matchers/helpers/fixtures_spec.rb +1 -1
  68. data/spec/elabs_matchers/helpers/{common_spec.rb → normalize_keys_spec.rb} +2 -2
  69. data/spec/elabs_matchers/helpers/{orm_spec.rb → reload_record_spec.rb} +3 -2
  70. data/spec/elabs_matchers/helpers/select_year_and_month_spec.rb +32 -0
  71. data/spec/elabs_matchers/matchers/{rspec/allow_spec.rb → allow_spec.rb} +5 -4
  72. data/spec/elabs_matchers/matchers/contain_hash_spec.rb +86 -0
  73. data/spec/elabs_matchers/matchers/have_attribute_spec.rb +65 -0
  74. data/spec/elabs_matchers/matchers/have_fields_spec.rb +46 -0
  75. data/spec/elabs_matchers/matchers/have_flash_spec.rb +97 -0
  76. data/spec/elabs_matchers/matchers/have_form_errors_on_spec.rb +93 -0
  77. data/spec/elabs_matchers/matchers/have_header_spec.rb +54 -0
  78. data/spec/elabs_matchers/matchers/have_image_spec.rb +49 -0
  79. data/spec/elabs_matchers/matchers/have_table_row_spec.rb +119 -0
  80. data/spec/elabs_matchers/matchers/only_include_spec.rb +32 -0
  81. data/spec/elabs_matchers/matchers/persist_spec.rb +21 -0
  82. data/{lib/elabs_matchers/orm → spec/fixtures}/post.rb +0 -0
  83. data/spec/spec_helper.rb +19 -8
  84. data/todo.txt +6 -0
  85. metadata +104 -48
  86. data/lib/elabs_matchers/cucumber/common.rb +0 -57
  87. data/lib/elabs_matchers/helpers/session.rb +0 -81
  88. data/lib/elabs_matchers/matchers/capybara/common.rb +0 -245
  89. data/lib/elabs_matchers/matchers/rspec/allow.rb +0 -112
  90. data/lib/elabs_matchers/matchers/rspec/common.rb +0 -59
  91. data/lib/elabs_matchers/matchers/rspec/orm.rb +0 -29
  92. data/spec/elabs_matchers/cucumber/common_spec.rb +0 -49
  93. data/spec/elabs_matchers/helpers/capybara_spec.rb +0 -32
  94. data/spec/elabs_matchers/helpers/session_spec.rb +0 -35
  95. data/spec/elabs_matchers/matchers/capybara/common_spec.rb +0 -202
  96. data/spec/elabs_matchers/matchers/rspec/common_spec.rb +0 -74
  97. data/spec/elabs_matchers/matchers/rspec/orm_spec.rb +0 -16
@@ -0,0 +1,58 @@
1
+ module ElabsMatchers
2
+ module Matchers
3
+ module ContainHash
4
+ rspec
5
+
6
+ class ContainHashMatcher < Struct.new(:expected, :actual)
7
+ def matches?(actual)
8
+ @actual = actual
9
+ contains?(expected, actual)
10
+ end
11
+
12
+ def failure_message_for_should
13
+ "Expected #{expected.inspect} to exist in #{actual.inspect} but it did not."
14
+ end
15
+
16
+ def failure_message_for_should_not
17
+ "Expected #{expected.inspect} to not exist in #{actual.inspect} but it did."
18
+ end
19
+
20
+ private
21
+
22
+ def contains?(expected, actual)
23
+ return false unless actual.is_a?(expected.class)
24
+
25
+ if expected.is_a?(Array)
26
+ expected.all? do |expected_value|
27
+ actual.any? do |actual_value|
28
+ contains?(expected_value, actual_value)
29
+ end
30
+ end
31
+ elsif expected.is_a?(Hash)
32
+ expected.all? do |key, value|
33
+ contains?(value, actual[key])
34
+ end
35
+ else
36
+ actual == expected
37
+ end
38
+ end
39
+
40
+ end
41
+
42
+ ##
43
+ #
44
+ # Asserts if the hash contains the supplied hash.
45
+ # Works with nested hashes and array of hashes.
46
+ #
47
+ # @param [Hash] hash Key => value pairs seperated by comma
48
+ #
49
+ # Example:
50
+ # { "foo" => ["quox", { "bar" => "baz"}]}.should contain_hash({ "foo" => [{ "bar" => "baz" }]})
51
+ # { "foo" => "bar", "baz" => "quox" }.should_not contain_hash({ "baz" => "bar" })
52
+
53
+ def contain_hash(hash)
54
+ ContainHashMatcher.new(hash)
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,62 @@
1
+ module ElabsMatchers
2
+ module Matchers
3
+ module HaveAttribute
4
+ rspec :type => :request
5
+
6
+ class HaveAttributeMatcher < Struct.new(:label, :value)
7
+ attr_reader :page
8
+
9
+ def matches?(page)
10
+ @page = page
11
+ page.has_selector?(selector_type, selector)
12
+ end
13
+
14
+ def does_not_match?(page)
15
+ @page = page
16
+ page.has_no_selector?(selector_type, selector)
17
+ end
18
+
19
+ def failure_message_for_should
20
+ attributes = page.all(:css, "li.wrapper").map(&:text).to_sentence
21
+ "expected there to be an attribute #{label}: #{value}, but the only attributes were: #{attributes}."
22
+ end
23
+
24
+ def failure_message_for_should_not
25
+ "expected there to be no attribute #{label}: #{value}, but there was."
26
+ end
27
+
28
+ private
29
+
30
+ def selector_type
31
+ ElabsMatchers.attribute_selector_type
32
+ end
33
+
34
+ def selector
35
+ if ElabsMatchers.attribute_selector
36
+ ElabsMatchers.attribute_selector[label, value]
37
+ else
38
+ XPath.generate do |x|
39
+ x.descendant(:p)[x.attr(:class).contains("wrapper")][x.child(:strong).contains(label)][x.contains(value)]
40
+ end
41
+ end
42
+ end
43
+ end
44
+
45
+ ##
46
+ #
47
+ # Asserts if the supplied show_for* attribute exists or not
48
+ #
49
+ # @param [String] label The name of the attribute.
50
+ # @param [String] value The value of the attribute.
51
+ #
52
+ # Example:
53
+ # page.should have_attribute("Status", "Pending")
54
+ #
55
+ # * https://github.com/plataformatec/show_for
56
+
57
+ def have_attribute(label, value)
58
+ HaveAttributeMatcher.new(label, value)
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,45 @@
1
+ module ElabsMatchers
2
+ module Matchers
3
+ module HaveFields
4
+ rspec :type => :request
5
+
6
+ class HaveFieldsMatcher < Struct.new(:fields)
7
+ attr_reader :page
8
+
9
+ def matches?(page)
10
+ @page = page
11
+ fields.all? { |label, value| page.has_field?(label, :with => value) }
12
+ end
13
+
14
+ def does_not_match?(page)
15
+ @page = page
16
+ fields.all? { |label, value| page.has_no_field?(label, :with => value) }
17
+ end
18
+
19
+ def failure_message_for_should
20
+ field_values = page.all("input, textarea").map { |input| input[:value] }
21
+
22
+ "expected page to have the fields #{fields.inspect}, but it didn't.
23
+ The fields on the page had the following values: #{field_values.to_sentence}."
24
+ end
25
+
26
+ def failure_message_for_should_not
27
+ "expected page not to have the fields #{fields.inspect}, but it did."
28
+ end
29
+ end
30
+
31
+ ##
32
+ #
33
+ # Asserts if the supplied fields exists or not
34
+ #
35
+ # @param [Hash] fields A hash containing pairs of field name => value
36
+ #
37
+ # Example:
38
+ # page.should have_fields("Author" => "Adam", "Year" => "2011")
39
+
40
+ def have_fields(fields)
41
+ HaveFieldsMatcher.new(fields)
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,65 @@
1
+ module ElabsMatchers
2
+ module Matchers
3
+ module HaveFlash
4
+ rspec :type => :request
5
+
6
+ class HaveFlashMatcher < Struct.new(:type, :message)
7
+ attr_reader :page
8
+
9
+ def matches?(page)
10
+ @page = page
11
+ page.has_selector?(selector_type, selector, :text => message)
12
+ end
13
+
14
+ def does_not_match?(page)
15
+ @page = page
16
+ page.has_no_selector?(selector_type, selector, :text => message)
17
+ end
18
+
19
+ def failure_message_for_should
20
+ "Expected flash #{type} to be '#{message}' but was '#{page.find(selector_type, selector).text}'."
21
+ end
22
+
23
+ def failure_message_for_should_not
24
+ "Expected flash #{type} to not be '#{message}' but it was."
25
+ end
26
+
27
+ private
28
+
29
+ def selector_type
30
+ ElabsMatchers.send(:"flash_#{type}_selector_type")
31
+ end
32
+
33
+ def selector
34
+ ElabsMatchers.send(:"flash_#{type}_selector")
35
+ end
36
+ end
37
+
38
+ ##
39
+ #
40
+ # Asserts if the supplied flash notice exists or not
41
+ #
42
+ # @param [String] message The content of the flash notice
43
+ #
44
+ # Example:
45
+ # page.should have_flash_notice("Success")
46
+
47
+ def have_flash_notice(message)
48
+ HaveFlashMatcher.new(:notice, message)
49
+ end
50
+
51
+ ##
52
+ #
53
+ # Asserts if the supplied flash alert exists or not
54
+ #
55
+ # @param [String] message The content of the flash alert
56
+ #
57
+ # Example:
58
+ # page.should have_flash_alert("Error")
59
+
60
+ def have_flash_alert(message)
61
+ HaveFlashMatcher.new(:alert, message)
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,79 @@
1
+ module ElabsMatchers
2
+ module Matchers
3
+ module HaveFormErrorsOn
4
+ rspec :type => :request
5
+
6
+ class HaveFormErrorsOnMatcher < Struct.new(:field, :message)
7
+ attr_reader :page
8
+
9
+ def matches?(page)
10
+ @page = page
11
+
12
+ if page.has_field?(field)
13
+ page.find_field(field).has_selector?(selector_type, selector, :text => message)
14
+ end
15
+ end
16
+
17
+ def does_not_match?(page)
18
+ @page = page
19
+ page.has_no_field?(field) || page.find_field(field).has_no_selector?(selector_type, selector, :text => message)
20
+ end
21
+
22
+ def failure_message_for_should
23
+ if page.has_field?(field)
24
+ error = page.find_field(field).all(selector_type, selector).first
25
+ if not error
26
+ "Expected field '#{field}' to have an error, but it didn't."
27
+ elsif error.text != message
28
+ "Expected error message on #{field} to be '#{message}' but was '#{error.text}'."
29
+ end
30
+ else
31
+ "No such field #{field}."
32
+ end
33
+ end
34
+
35
+ def failure_message_for_should_not
36
+ "Expected error message on '#{field}' not to be '#{message}' but it was."
37
+ end
38
+
39
+ private
40
+
41
+ def selector_type
42
+ :xpath
43
+ end
44
+
45
+ def selector
46
+ if ElabsMatchers.form_errors_on_selector
47
+ ElabsMatchers.form_errors_on_selector
48
+ else
49
+ outside_input_container_xpath = %Q{ancestor::*[contains(@class, 'field_with_errors') and contains(@class, 'error')]}
50
+ error_as_sibling_xpath = %Q{..//span[contains(@class,'error')]}
51
+ input_nested_in_label_xpath = %Q{..//..//label/following-sibling::*[1]/self::span[@class='error']}
52
+ twitter_bootstrap_xpath = %Q{ancestor::*[contains(concat(' ', @class, ' '), ' control-group ') and contains(concat(' ', @class, ' '), ' error ')]}
53
+
54
+ [
55
+ outside_input_container_xpath,
56
+ error_as_sibling_xpath,
57
+ input_nested_in_label_xpath,
58
+ twitter_bootstrap_xpath
59
+ ].join(" | ")
60
+ end
61
+ end
62
+ end
63
+
64
+ ##
65
+ #
66
+ # Asserts if the supplied flash alert exists or not
67
+ #
68
+ # @param [String] field The label content associated with the field. Any selector which works with has_field? works here too.
69
+ # @param [String] message The error message expected.
70
+ #
71
+ # Example:
72
+ # page.should have_form_errors_on("Name", "Can't be blank")
73
+
74
+ def have_form_errors_on(field, message)
75
+ HaveFormErrorsOnMatcher.new(field, message)
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,53 @@
1
+ module ElabsMatchers
2
+ module Matchers
3
+ module HaveHeader
4
+ rspec :type => :request
5
+
6
+ class HaveHeaderMatcher < Struct.new(:text)
7
+ attr_reader :page
8
+
9
+ def matches?(page)
10
+ @page = page
11
+ page.has_selector?(selector_type, selector, :text => text)
12
+ end
13
+
14
+ def does_not_match?(page)
15
+ @page = page
16
+ page.has_no_selector?(selector_type, selector, :text => text)
17
+ end
18
+
19
+ def failure_message_for_should
20
+ headers = page.all(selector_type, selector).map { |h| "'#{h.text}'" }.to_sentence
21
+ "Expected header to be '#{text}' but it had the headers #{headers}."
22
+ end
23
+
24
+ def failure_message_for_should_not
25
+ "Expected header not to be '#{text}' but it was."
26
+ end
27
+
28
+ private
29
+
30
+ def selector
31
+ ElabsMatchers.header_selector
32
+ end
33
+
34
+ def selector_type
35
+ ElabsMatchers.header_selector_type
36
+ end
37
+ end
38
+
39
+ ##
40
+ #
41
+ # Asserts if the supplied header exists or not
42
+ #
43
+ # @param [String] text The content of the header
44
+ #
45
+ # Example:
46
+ # page.should have_header("Elabs")
47
+
48
+ def have_header(text)
49
+ HaveHeaderMatcher.new(text)
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,69 @@
1
+ module ElabsMatchers
2
+ module Matchers
3
+ module HaveImage
4
+ rspec :type => :request
5
+
6
+ class HaveImageMatcher < Struct.new(:value)
7
+ attr_reader :page
8
+
9
+ def matches?(page)
10
+ @page = page
11
+ page.has_selector?(selector_type, selector)
12
+ end
13
+
14
+ def does_not_match?(page)
15
+ @page = page
16
+ page.has_no_selector?(selector_type, selector)
17
+ end
18
+
19
+ def failure_message_for_should
20
+ images = page.all("img").map do |image|
21
+ hash = {}
22
+ hash[:alt] = image[:alt] if image[:alt].present?
23
+ hash[:src] = image[:src] if image[:src].present?
24
+ hash[:id] = image[:id] if image[:id].present?
25
+ hash[:class] = image[:class] if image[:class].present?
26
+ hash
27
+ end.to_sentence
28
+
29
+ if ElabsMatchers.image_selector.nil?
30
+ "expected image alt to be '#{value}' but only had the images: #{images}."
31
+ else
32
+ "expected to find image '#{value}' but only had the images: #{images}."
33
+ end
34
+ end
35
+
36
+ def failure_message_for_should_not
37
+ "expected image not to be '#{value}' but it was."
38
+ end
39
+
40
+ private
41
+
42
+ def selector_type
43
+ ElabsMatchers.image_selector_type
44
+ end
45
+
46
+ def selector
47
+ if ElabsMatchers.image_selector
48
+ ElabsMatchers.image_selector[value]
49
+ else
50
+ "img[alt=\"#{value}\"]"
51
+ end
52
+ end
53
+ end
54
+
55
+ ##
56
+ #
57
+ # Asserts if the supplied image exists or not
58
+ #
59
+ # @param [String] value The alt attribute content of the image
60
+ #
61
+ # Example:
62
+ # page.should have_image("Logo")
63
+
64
+ def have_image(value)
65
+ HaveImageMatcher.new(value)
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,113 @@
1
+ module ElabsMatchers
2
+ module Matchers
3
+ module HaveTableRow
4
+ rspec :type => :request
5
+
6
+ class HaveTableRowMatcher < Struct.new(:table_name, :row)
7
+ attr_reader :page
8
+
9
+ def matches?(page)
10
+ @page = page
11
+ table and table.has_selector?(selector_type, selector)
12
+ end
13
+
14
+ def does_not_match?(page)
15
+ @page = page
16
+ !table or table.has_no_selector?(selector_type, selector)
17
+ end
18
+
19
+ def failure_message_for_should
20
+ "Expected #{row.inspect} to be included in the table #{table_name}, but it wasn't:\n\n#{ascii_table}"
21
+ end
22
+
23
+ def failure_message_for_should_not
24
+ "Expected there to be no table #{table_name} with row #{row.inspect}, but there was.\n\n#{ascii_table}"
25
+ end
26
+
27
+ private
28
+
29
+ def selector_type
30
+ :xpath
31
+ end
32
+
33
+ def selector
34
+ if ElabsMatchers.table_row_selector
35
+ ElabsMatchers.table_row_selector[row, table]
36
+ else
37
+ exps = row.map do |header, value|
38
+ col_index = table.all("th").index { |th| th.text.include?(header.to_s) }
39
+ col_index = if col_index then col_index + 1 else 0 end
40
+
41
+ XPath.generate do |x|
42
+ cell = x.child(:td, :th)[col_index.to_s.to_sym]
43
+
44
+ if value.blank?
45
+ cell["not(node())".to_sym].or(cell.descendant(:input)["string-length(normalize-space(@value))=0".to_sym])
46
+ else
47
+ cell[x.contains(value).or(x.descendant(:input)[x.attr(:value).contains(value)])]
48
+ end
49
+ end
50
+ end
51
+
52
+ XPath.descendant["tr"][exps.reduce(:&)]
53
+ end
54
+ end
55
+
56
+ def table
57
+ table_xpath = XPath::HTML.table(table_name)
58
+
59
+ if page.has_xpath?(table_xpath)
60
+ page.find(:xpath, table_xpath)
61
+ end
62
+ end
63
+
64
+ def ascii_table
65
+ if table
66
+ column_lengths = []
67
+ table.all("tr").map do |tr|
68
+ tr.all("td,th").each_with_index do |td, i|
69
+ size = td_content(td).strip.size
70
+ if (column_lengths[i] || 0) < size
71
+ column_lengths[i] = size
72
+ end
73
+ end
74
+ end
75
+
76
+ table.all("tr").map do |tr|
77
+ middle = []
78
+ space = if tr.all("td,th").first.tag_name == "th" then "_" else " " end
79
+ wall = "|"
80
+
81
+ tr.all("td,th").each_with_index do |td, i|
82
+ middle << td_content(td).strip.ljust(column_lengths[i], space)
83
+ end
84
+
85
+ [wall, space, middle.join(space + wall + space), space, wall].join
86
+ end.join("\n")
87
+ end
88
+ end
89
+
90
+ def td_content(td)
91
+ text = td.text.presence
92
+ text ||= td.find("input, textarea")[:value] if td.has_css?("input")
93
+ text || ""
94
+ end
95
+ end
96
+
97
+
98
+ ##
99
+ #
100
+ # Asserts if the supplied table row exists in the table
101
+ #
102
+ # @param [String] table_name The tables's caption text.
103
+ # @param [Hash] row A hash representing the column name and value in key-value form.
104
+ #
105
+ # Example:
106
+ # table.should have_table_row("Posts", "Title" => "First", :year => "2012")
107
+
108
+ def have_table_row(table_name, row)
109
+ HaveTableRowMatcher.new(table_name, row)
110
+ end
111
+ end
112
+ end
113
+ end