rails-dom-testing 2.0.3 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 0af31466148a2f967def0233933062ca454b1bcb
4
- data.tar.gz: 825ff206fc31a115d41c567614331e779ccc8d92
2
+ SHA256:
3
+ metadata.gz: d8720c7b560a8e3acaa5ae87a7dbfb587fe47c156d8f49b9d319b2c089a3d987
4
+ data.tar.gz: f482fcae81574f5fedc71d6e46ce372f8f273eac9df50a40d565696db1a02209
5
5
  SHA512:
6
- metadata.gz: 51291b7d5bf443fa4df3b9fa38ef92b84ecef5e434a77b9681f2b63bd3188aaf59d13b722e08a27e1f3a21fb1a2872d42a05a92e838c072d0059983edc4b6878
7
- data.tar.gz: af4705e0a6acb6d1009ad3cdec42ab3dfc3eedb9e4a8ec28a4b112bbd0f1e53badfd8fa853a7146644d445c876b71b89fb4dd269e875e366c5d33f5850c55767
6
+ metadata.gz: bb404e501783f50dfd614101ee141ccc08665e2410fbd3c2e62c6859e4ed2edd2097169373b86d6deaefb63192385cf895d1ae0c512c69f65de760d38a8b6542
7
+ data.tar.gz: afc3f0247a26905b8aeb37fd94a9a0bd79f09b2ed334b539129f9566d3c25aed44e0e3a6a34f73c4ad0c6c485a24ae30e205b0aac14ff7e60b942be5d32d7663
data/README.md CHANGED
@@ -2,29 +2,9 @@
2
2
 
3
3
  This gem is responsible for comparing HTML doms and asserting that DOM elements are present in Rails applications.
4
4
  Doms are compared via `assert_dom_equal` and `assert_dom_not_equal`.
5
- Elements are asserted via `assert_select`, `assert_select_encoded`, `assert_select_email` and a subset of the dom can be selected with `css_select`.
5
+ Elements are asserted via `assert_dom`, `assert_dom_encoded`, `assert_dom_email` and a subset of the dom can be selected with `css_select`.
6
6
  The gem is developed for Rails 4.2 and above, and will not work on previous versions.
7
7
 
8
- ## Nokogiri::CSS::SyntaxError exceptions when upgrading to Rails 4.2:
9
-
10
- Nokogiri is slightly stricter about the format of CSS selectors than the previous implementation.
11
-
12
- Check the 4.2 release notes [section on `assert_select`](http://edgeguides.rubyonrails.org/4_2_release_notes.html#assert-select) for help.
13
-
14
- ## Installation
15
-
16
- Add this line to your application's Gemfile:
17
-
18
- gem 'rails-dom-testing'
19
-
20
- And then execute:
21
-
22
- $ bundle
23
-
24
- Or install it yourself as:
25
-
26
- $ gem install rails-dom-testing
27
-
28
8
  ## Usage
29
9
 
30
10
  ### Dom Assertions
@@ -41,21 +21,59 @@ assert_dom_not_equal '<h1>Portuguese</h1>', '<h1>Danish</h1>'
41
21
  # implicitly selects from the document_root_element
42
22
  css_select '.hello' # => Nokogiri::XML::NodeSet of elements with hello class
43
23
 
44
- # select from a supplied node. assert_select asserts elements exist.
45
- assert_select document_root_element.at('.hello'), '.goodbye'
24
+ # select from a supplied node. assert_dom asserts elements exist.
25
+ assert_dom document_root_element.at('.hello'), '.goodbye'
46
26
 
47
27
  # elements in CDATA encoded sections can also be selected
48
- assert_select_encoded '#out-of-your-element'
28
+ assert_dom_encoded '#out-of-your-element'
49
29
 
50
30
  # assert elements within an html email exists
51
- assert_select_email '#you-got-mail'
31
+ assert_dom_email '#you-got-mail'
52
32
  ```
53
33
 
54
34
  The documentation in [selector_assertions.rb](https://github.com/rails/rails-dom-testing/blob/master/lib/rails/dom/testing/assertions/selector_assertions.rb) goes into a lot more detail of how selector assertions can be used.
55
35
 
36
+ ### HTML versions
37
+
38
+ By default, assertions will use Nokogiri's HTML4 parser.
39
+
40
+ If `Rails::Dom::Testing.default_html_version` is set to `:html5`, then the assertions will use
41
+ Nokogiri's HTML5 parser. (If the HTML5 parser is not available on your platform, then a
42
+ `NotImplementedError` will be raised.)
43
+
44
+ When testing in a Rails application, the parser default can also be set by setting
45
+ `Rails.application.config.dom_testing_default_html_version`.
46
+
47
+ Some assertions support an `html_version:` keyword argument which can override the default for that
48
+ assertion. For example:
49
+
50
+ ``` ruby
51
+ # compare DOMs built with the HTML5 parser
52
+ assert_dom_equal(expected, actual, html_version: :html5)
53
+
54
+ # compare DOMs built with the HTML4 parser
55
+ assert_dom_not_equal(expected, actual, html_version: :html4)
56
+ ```
57
+
58
+ Please see documentation for individual assertions for more details.
59
+
60
+ ## Installation
61
+
62
+ Add this line to your application's Gemfile:
63
+
64
+ gem 'rails-dom-testing'
65
+
66
+ And then execute:
67
+
68
+ $ bundle
69
+
70
+ Or install it yourself as:
71
+
72
+ $ gem install rails-dom-testing
73
+
56
74
  ## Read more
57
75
 
58
- Under the hood the doms are parsed with Nokogiri and you'll generally be working with these two classes:
76
+ Under the hood the doms are parsed with Nokogiri, and you'll generally be working with these two classes:
59
77
  - [`Nokogiri::XML::Node`](http://www.rubydoc.info/github/sparklemotion/nokogiri/Nokogiri/XML/Node)
60
78
  - [`Nokogiri::XML::NodeSet`](http://www.rubydoc.info/github/sparklemotion/nokogiri/Nokogiri/XML/NodeSet)
61
79
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Rails
2
4
  module Dom
3
5
  module Testing
@@ -6,44 +8,107 @@ module Rails
6
8
  # \Test two HTML strings for equivalency (e.g., equal even when attributes are in another order)
7
9
  #
8
10
  # # assert that the referenced method generates the appropriate HTML string
9
- # assert_dom_equal '<a href="http://www.example.com">Apples</a>', link_to("Apples", "http://www.example.com")
10
- def assert_dom_equal(expected, actual, message = nil)
11
- expected_dom, actual_dom = fragment(expected), fragment(actual)
11
+ # assert_dom_equal(
12
+ # '<a href="http://www.example.com">Apples</a>',
13
+ # link_to("Apples", "http://www.example.com"),
14
+ # )
15
+ #
16
+ # By default, the matcher will not pay attention to whitespace in text nodes (e.g., spaces
17
+ # and newlines). If you want stricter matching with exact matching for whitespace, pass
18
+ # <tt>strict: true</tt>:
19
+ #
20
+ # # these assertions will both pass
21
+ # assert_dom_equal "<div>\nfoo\n\</div>", "<div>foo</div>", strict: false
22
+ # assert_dom_not_equal "<div>\nfoo\n\</div>", "<div>foo</div>", strict: true
23
+ #
24
+ # The DOMs are created using an HTML parser specified by
25
+ # Rails::Dom::Testing.default_html_version (either :html4 or :html5).
26
+ #
27
+ # When testing in a Rails application, the parser default can also be set by setting
28
+ # +Rails.application.config.dom_testing_default_html_version+.
29
+ #
30
+ # If you want to specify the HTML parser just for a particular assertion, pass
31
+ # <tt>html_version: :html4</tt> or <tt>html_version: :html5</tt> keyword arguments:
32
+ #
33
+ # assert_dom_equal expected, actual, html_version: :html5
34
+ #
35
+ def assert_dom_equal(expected, actual, message = nil, strict: false, html_version: nil)
36
+ expected_dom, actual_dom = fragment(expected, html_version: html_version), fragment(actual, html_version: html_version)
12
37
  message ||= "Expected: #{expected}\nActual: #{actual}"
13
- assert compare_doms(expected_dom, actual_dom), message
38
+ assert compare_doms(expected_dom, actual_dom, strict), message
14
39
  end
15
40
 
16
41
  # The negated form of +assert_dom_equal+.
17
42
  #
18
43
  # # assert that the referenced method does not generate the specified HTML string
19
- # assert_dom_not_equal '<a href="http://www.example.com">Apples</a>', link_to("Oranges", "http://www.example.com")
20
- def assert_dom_not_equal(expected, actual, message = nil)
21
- expected_dom, actual_dom = fragment(expected), fragment(actual)
44
+ # assert_dom_not_equal(
45
+ # '<a href="http://www.example.com">Apples</a>',
46
+ # link_to("Oranges", "http://www.example.com"),
47
+ # )
48
+ #
49
+ # By default, the matcher will not pay attention to whitespace in text nodes (e.g., spaces
50
+ # and newlines). If you want stricter matching with exact matching for whitespace, pass
51
+ # <tt>strict: true</tt>:
52
+ #
53
+ # # these assertions will both pass
54
+ # assert_dom_equal "<div>\nfoo\n\</div>", "<div>foo</div>", strict: false
55
+ # assert_dom_not_equal "<div>\nfoo\n\</div>", "<div>foo</div>", strict: true
56
+ #
57
+ # The DOMs are created using an HTML parser specified by
58
+ # Rails::Dom::Testing.default_html_version (either :html4 or :html5).
59
+ #
60
+ # When testing in a Rails application, the parser default can also be set by setting
61
+ # +Rails.application.config.dom_testing_default_html_version+.
62
+ #
63
+ # If you want to specify the HTML parser just for a particular assertion, pass
64
+ # <tt>html_version: :html4</tt> or <tt>html_version: :html5</tt> keyword arguments:
65
+ #
66
+ # assert_dom_not_equal expected, actual, html_version: :html5
67
+ #
68
+ def assert_dom_not_equal(expected, actual, message = nil, strict: false, html_version: nil)
69
+ expected_dom, actual_dom = fragment(expected, html_version: html_version), fragment(actual, html_version: html_version)
22
70
  message ||= "Expected: #{expected}\nActual: #{actual}"
23
- assert_not compare_doms(expected_dom, actual_dom), message
71
+ assert_not compare_doms(expected_dom, actual_dom, strict), message
24
72
  end
25
73
 
26
74
  protected
75
+ def compare_doms(expected, actual, strict)
76
+ expected_children = extract_children(expected, strict)
77
+ actual_children = extract_children(actual, strict)
78
+ return false unless expected_children.size == actual_children.size
27
79
 
28
- def compare_doms(expected, actual)
29
- return false unless expected.children.size == actual.children.size
30
-
31
- expected.children.each_with_index do |child, i|
32
- return false unless equal_children?(child, actual.children[i])
80
+ expected_children.each_with_index do |child, i|
81
+ return false unless equal_children?(child, actual_children[i], strict)
33
82
  end
34
83
 
35
84
  true
36
85
  end
37
86
 
38
- def equal_children?(child, other_child)
87
+ def extract_children(node, strict)
88
+ if strict
89
+ node.children
90
+ else
91
+ node.children.reject { |n| n.text? && n.text.blank? }
92
+ end
93
+ end
94
+
95
+ def equal_children?(child, other_child, strict)
39
96
  return false unless child.type == other_child.type
40
97
 
41
98
  if child.element?
42
99
  child.name == other_child.name &&
43
100
  equal_attribute_nodes?(child.attribute_nodes, other_child.attribute_nodes) &&
44
- compare_doms(child, other_child)
101
+ compare_doms(child, other_child, strict)
45
102
  else
103
+ equal_child?(child, other_child, strict)
104
+ end
105
+ end
106
+
107
+ def equal_child?(child, other_child, strict)
108
+ if strict
46
109
  child.to_s == other_child.to_s
110
+ else
111
+ child.to_s.split == other_child.to_s.split
47
112
  end
48
113
  end
49
114
 
@@ -65,9 +130,8 @@ module Rails
65
130
  end
66
131
 
67
132
  private
68
-
69
- def fragment(text)
70
- Nokogiri::HTML::DocumentFragment.parse(text)
133
+ def fragment(text, html_version: nil)
134
+ Rails::Dom::Testing.html_document_fragment(html_version: html_version).parse(text)
71
135
  end
72
136
  end
73
137
  end
@@ -1,114 +1,132 @@
1
- require 'active_support/core_ext/module/attribute_accessors'
2
- require_relative 'substitution_context'
3
-
4
- class HTMLSelector #:nodoc:
5
- attr_reader :css_selector, :tests, :message
6
-
7
- def initialize(values, previous_selection = nil, &root_fallback)
8
- @values = values
9
- @root = extract_root(previous_selection, root_fallback)
10
- extract_selectors
11
- @tests = extract_equality_tests
12
- @message = @values.shift
13
-
14
- if @values.shift
15
- raise ArgumentError, "Not expecting that last argument, you either have too many arguments, or they're the wrong type"
16
- end
17
- end
18
-
19
- def selecting_no_body? #:nodoc:
20
- # Nokogiri gives the document a body element. Which means we can't
21
- # run an assertion expecting there to not be a body.
22
- @selector == 'body' && @tests[:count] == 0
23
- end
24
-
25
- def select
26
- filter @root.css(@selector, context)
27
- end
28
-
29
- private
30
-
31
- NO_STRIP = %w{pre script style textarea}
32
-
33
- mattr_reader(:context) { SubstitutionContext.new }
34
-
35
- def filter(matches)
36
- match_with = tests[:text] || tests[:html]
37
- return matches if matches.empty? || !match_with
38
-
39
- content_mismatch = nil
40
- text_matches = tests.has_key?(:text)
41
- regex_matching = match_with.is_a?(Regexp)
42
-
43
- remaining = matches.reject do |match|
44
- # Preserve markup with to_s for html elements
45
- content = text_matches ? match.text : match.children.to_s
46
-
47
- content.strip! unless NO_STRIP.include?(match.name)
48
- content.sub!(/\A\n/, '') if text_matches && match.name == "textarea"
49
-
50
- next if regex_matching ? (content =~ match_with) : (content == match_with)
51
- content_mismatch ||= sprintf("<%s> expected but was\n<%s>.", match_with, content)
52
- true
53
- end
54
-
55
- @message ||= content_mismatch if remaining.empty?
56
- Nokogiri::XML::NodeSet.new(matches.document, remaining)
57
- end
58
-
59
- def extract_root(previous_selection, root_fallback)
60
- possible_root = @values.first
61
-
62
- if possible_root == nil
63
- raise ArgumentError, 'First argument is either selector or element ' \
64
- 'to select, but nil found. Perhaps you called assert_select with ' \
65
- 'an element that does not exist?'
66
- elsif possible_root.respond_to?(:css)
67
- @values.shift # remove the root, so selector is the first argument
68
- possible_root
69
- elsif previous_selection
70
- previous_selection
71
- else
72
- root_fallback.call
73
- end
74
- end
75
-
76
- def extract_selectors
77
- selector = @values.shift
78
-
79
- unless selector.is_a? String
80
- raise ArgumentError, "Expecting a selector as the first argument"
81
- end
82
-
83
- @css_selector = context.substitute!(selector, @values.dup, true)
84
- @selector = context.substitute!(selector, @values)
85
- end
86
-
87
- def extract_equality_tests
88
- comparisons = {}
89
- case comparator = @values.shift
90
- when Hash
91
- comparisons = comparator
92
- when String, Regexp
93
- comparisons[:text] = comparator
94
- when Integer
95
- comparisons[:count] = comparator
96
- when Range
97
- comparisons[:minimum] = comparator.begin
98
- comparisons[:maximum] = comparator.end
99
- when FalseClass
100
- comparisons[:count] = 0
101
- when NilClass, TrueClass
102
- comparisons[:minimum] = 1
103
- else raise ArgumentError, "I don't understand what you're trying to match"
104
- end
105
-
106
- # By default we're looking for at least one match.
107
- if comparisons[:count]
108
- comparisons[:minimum] = comparisons[:maximum] = comparisons[:count]
109
- else
110
- comparisons[:minimum] ||= 1
1
+ # frozen_string_literal: true
2
+
3
+ require "minitest"
4
+
5
+ require_relative "substitution_context"
6
+
7
+ module Rails
8
+ module Dom
9
+ module Testing
10
+ module Assertions
11
+ module SelectorAssertions
12
+ class HTMLSelector # :nodoc:
13
+ attr_reader :css_selector, :tests, :message
14
+
15
+ include Minitest::Assertions
16
+
17
+ def initialize(values, previous_selection = nil, &root_fallback)
18
+ @values = values
19
+ @root = extract_root(previous_selection, root_fallback)
20
+ extract_selectors
21
+ @tests = extract_equality_tests
22
+ @message = @values.shift
23
+
24
+ if @message.is_a?(Hash)
25
+ raise ArgumentError, "Last argument was a Hash, which would be used for the assertion message. You probably want this to be a String, or you have the wrong type of arguments."
26
+ end
27
+
28
+ if @values.shift
29
+ raise ArgumentError, "Not expecting that last argument, you either have too many arguments, or they're the wrong type"
30
+ end
31
+ end
32
+
33
+ def selecting_no_body? # :nodoc:
34
+ # Nokogiri gives the document a body element. Which means we can't
35
+ # run an assertion expecting there to not be a body.
36
+ @selector == "body" && @tests[:count] == 0
37
+ end
38
+
39
+ def select
40
+ filter @root.css(@selector, context)
41
+ end
42
+
43
+ private
44
+ NO_STRIP = %w{pre script style textarea}
45
+
46
+ mattr_reader(:context) { SubstitutionContext.new }
47
+
48
+ def filter(matches)
49
+ match_with = tests[:text] || tests[:html]
50
+ return matches if matches.empty? || !match_with
51
+
52
+ content_mismatch = nil
53
+ text_matches = tests.has_key?(:text)
54
+ regex_matching = match_with.is_a?(Regexp)
55
+
56
+ remaining = matches.reject do |match|
57
+ # Preserve markup with to_s for html elements
58
+ content = text_matches ? match.text : match.children.to_s
59
+
60
+ content.strip! unless NO_STRIP.include?(match.name)
61
+ content.delete_prefix!("\n") if text_matches && match.name == "textarea"
62
+
63
+ next if regex_matching ? (content =~ match_with) : (content == match_with)
64
+ content_mismatch ||= diff(match_with, content)
65
+ true
66
+ end
67
+
68
+ @message ||= content_mismatch if remaining.empty?
69
+ Nokogiri::XML::NodeSet.new(matches.document, remaining)
70
+ end
71
+
72
+ def extract_root(previous_selection, root_fallback)
73
+ possible_root = @values.first
74
+
75
+ if possible_root == nil
76
+ raise ArgumentError, "First argument is either selector or element " \
77
+ "to select, but nil found. Perhaps you called assert_dom with " \
78
+ "an element that does not exist?"
79
+ elsif possible_root.respond_to?(:css)
80
+ @values.shift # remove the root, so selector is the first argument
81
+ possible_root
82
+ elsif previous_selection
83
+ previous_selection
84
+ else
85
+ root_fallback.call
86
+ end
87
+ end
88
+
89
+ def extract_selectors
90
+ selector = @values.shift
91
+
92
+ unless selector.is_a? String
93
+ raise ArgumentError, "Expecting a selector as the first argument"
94
+ end
95
+
96
+ @css_selector = context.substitute!(selector, @values.dup, true)
97
+ @selector = context.substitute!(selector, @values)
98
+ end
99
+
100
+ def extract_equality_tests
101
+ comparisons = {}
102
+ case comparator = @values.shift
103
+ when Hash
104
+ comparisons = comparator
105
+ when String, Regexp
106
+ comparisons[:text] = comparator
107
+ when Integer
108
+ comparisons[:count] = comparator
109
+ when Range
110
+ comparisons[:minimum] = comparator.begin
111
+ comparisons[:maximum] = comparator.end
112
+ when FalseClass
113
+ comparisons[:count] = 0
114
+ when NilClass, TrueClass
115
+ comparisons[:minimum] = 1
116
+ else raise ArgumentError, "I don't understand what you're trying to match"
117
+ end
118
+
119
+ # By default we're looking for at least one match.
120
+ if comparisons[:count]
121
+ comparisons[:minimum] = comparisons[:maximum] = comparisons[:count]
122
+ else
123
+ comparisons[:minimum] ||= 1
124
+ end
125
+ comparisons
126
+ end
127
+ end
128
+ end
129
+ end
111
130
  end
112
- comparisons
113
131
  end
114
132
  end
@@ -1,33 +1,44 @@
1
- class SubstitutionContext
2
- def initialize
3
- @substitute = '?'
4
- end
1
+ # frozen_string_literal: true
5
2
 
6
- def substitute!(selector, values, format_for_presentation = false)
7
- selector = selector.dup
3
+ module Rails
4
+ module Dom
5
+ module Testing
6
+ module Assertions
7
+ module SelectorAssertions
8
+ class SubstitutionContext # :nodoc:
9
+ def initialize
10
+ @substitute = "?"
11
+ end
8
12
 
9
- while !values.empty? && substitutable?(values.first) && selector.index(@substitute)
10
- selector.sub! @substitute, matcher_for(values.shift, format_for_presentation)
11
- end
13
+ def substitute!(selector, values, format_for_presentation = false)
14
+ selector.gsub @substitute do |match|
15
+ next match[0] if values.empty? || !substitutable?(values.first)
16
+ matcher_for(values.shift, format_for_presentation)
17
+ end
18
+ end
12
19
 
13
- selector
14
- end
20
+ def match(matches, attribute, matcher)
21
+ matches.find_all { |node| node[attribute] =~ Regexp.new(matcher) }
22
+ end
15
23
 
16
- def match(matches, attribute, matcher)
17
- matches.find_all { |node| node[attribute] =~ Regexp.new(matcher) }
18
- end
24
+ private
25
+ def matcher_for(value, format_for_presentation)
26
+ # Nokogiri doesn't like arbitrary values without quotes, hence inspect.
27
+ if format_for_presentation
28
+ value.inspect # Avoid to_s so Regexps aren't put in quotes.
29
+ elsif value.is_a?(Regexp)
30
+ "\"#{value}\""
31
+ else
32
+ value.to_s.inspect
33
+ end
34
+ end
19
35
 
20
- private
21
- def matcher_for(value, format_for_presentation)
22
- # Nokogiri doesn't like arbitrary values without quotes, hence inspect.
23
- if format_for_presentation
24
- value.inspect # Avoid to_s so Regexps aren't put in quotes.
25
- else
26
- value.to_s.inspect
36
+ def substitutable?(value)
37
+ [ Symbol, Numeric, String, Regexp ].any? { |type| value.is_a? type }
38
+ end
39
+ end
40
+ end
27
41
  end
28
42
  end
29
-
30
- def substitutable?(value)
31
- value.is_a?(String) || value.is_a?(Regexp)
32
- end
43
+ end
33
44
  end