rails-dom-testing 2.1.1 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dc48c3a21ae3839cf5bbde901d06210fe2a4d1d58e6aae4048a26c19a6baf577
4
- data.tar.gz: b082464768d961ef20cc41ec1da545691bd7e0caac93a16b8668128818855885
3
+ metadata.gz: 92842b3fcd4006daf9f8047da3d90a171ead6f39900c2d4f491d3c3ac0658845
4
+ data.tar.gz: 67b13526065e8c54acc109945f9566947f1976c601e44bb2295eaab79d7fcfe4
5
5
  SHA512:
6
- metadata.gz: 51f291660cac9014e810adbf636c5dfbf7da80db478c062191eecedebe5b77b4f795a12259b1b427fd04b548e0a9b1b524d550e514b8e8b561ae566c36471182
7
- data.tar.gz: d5ac742ae523c62d565627f9c95553780286de17ba7a43f32972a5dcf2458d316b79c80edcfd3f4361b2d77a4dcfa541976f14cbb09b249bf681bfabf2c5696e
6
+ metadata.gz: d16235fedf30e46ccc6f0fc4463f567917d12084d6d8a0b67cfce316ae7cdd5100b78d84f314e856ff56904e4ffa628628e0c2b5fee11cf1b287ddffdb04431c
7
+ data.tar.gz: 93e996ac52d49ead86b8a04ae43299303c3c02231adc0911158142e7defb49e5afc3b4584f66bb437e9983d910fc70f8bacdddfb79f648c8f1f27cd003532b87
data/README.md CHANGED
@@ -5,26 +5,6 @@ Doms are compared via `assert_dom_equal` and `assert_dom_not_equal`.
5
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
@@ -44,6 +24,9 @@ css_select '.hello' # => Nokogiri::XML::NodeSet of elements with hello class
44
24
  # select from a supplied node. assert_dom asserts elements exist.
45
25
  assert_dom document_root_element.at('.hello'), '.goodbye'
46
26
 
27
+ # select from a supplied node. assert_not_dom asserts elements do not exist.
28
+ assert_not_dom document_root_element.at('.hello'), '.goodbye'
29
+
47
30
  # elements in CDATA encoded sections can also be selected
48
31
  assert_dom_encoded '#out-of-your-element'
49
32
 
@@ -53,6 +36,44 @@ assert_dom_email '#you-got-mail'
53
36
 
54
37
  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
38
 
39
+ ### HTML versions
40
+
41
+ By default, assertions will use Nokogiri's HTML4 parser.
42
+
43
+ If `Rails::Dom::Testing.default_html_version` is set to `:html5`, then the assertions will use
44
+ Nokogiri's HTML5 parser. (If the HTML5 parser is not available on your platform, then a
45
+ `NotImplementedError` will be raised.)
46
+
47
+ When testing in a Rails application, the parser default can also be set by setting
48
+ `Rails.application.config.dom_testing_default_html_version`.
49
+
50
+ Some assertions support an `html_version:` keyword argument which can override the default for that
51
+ assertion. For example:
52
+
53
+ ``` ruby
54
+ # compare DOMs built with the HTML5 parser
55
+ assert_dom_equal(expected, actual, html_version: :html5)
56
+
57
+ # compare DOMs built with the HTML4 parser
58
+ assert_dom_not_equal(expected, actual, html_version: :html4)
59
+ ```
60
+
61
+ Please see documentation for individual assertions for more details.
62
+
63
+ ## Installation
64
+
65
+ Add this line to your application's Gemfile:
66
+
67
+ gem 'rails-dom-testing'
68
+
69
+ And then execute:
70
+
71
+ $ bundle
72
+
73
+ Or install it yourself as:
74
+
75
+ $ gem install rails-dom-testing
76
+
56
77
  ## Read more
57
78
 
58
79
  Under the hood the doms are parsed with Nokogiri, and you'll generally be working with these two classes:
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Rails
2
4
  module Dom
3
5
  module Testing
@@ -6,9 +8,32 @@ 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, strict: false)
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
38
  assert compare_doms(expected_dom, actual_dom, strict), message
14
39
  end
@@ -16,15 +41,38 @@ module Rails
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, strict: false)
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
71
  assert_not compare_doms(expected_dom, actual_dom, strict), message
24
72
  end
73
+ alias_method :refute_dom_equal, :assert_dom_not_equal
25
74
 
26
75
  protected
27
-
28
76
  def compare_doms(expected, actual, strict)
29
77
  expected_children = extract_children(expected, strict)
30
78
  actual_children = extract_children(actual, strict)
@@ -41,7 +89,7 @@ module Rails
41
89
  if strict
42
90
  node.children
43
91
  else
44
- node.children.reject{|n| n.text? && n.text.blank?}
92
+ node.children.reject { |n| n.text? && n.text.blank? }
45
93
  end
46
94
  end
47
95
 
@@ -83,9 +131,8 @@ module Rails
83
131
  end
84
132
 
85
133
  private
86
-
87
- def fragment(text)
88
- Nokogiri::HTML::DocumentFragment.parse(text)
134
+ def fragment(text, html_version: nil)
135
+ Rails::Dom::Testing.html_document_fragment(html_version: html_version).parse(text)
89
136
  end
90
137
  end
91
138
  end
@@ -1,122 +1,154 @@
1
- require 'minitest'
2
- require 'active_support'
3
- require 'active_support/core_ext/module/attribute_accessors'
4
- require_relative 'substitution_context'
5
-
6
- class HTMLSelector #:nodoc:
7
- attr_reader :css_selector, :tests, :message
8
-
9
- include Minitest::Assertions
10
-
11
- def initialize(values, previous_selection = nil, &root_fallback)
12
- @values = values
13
- @root = extract_root(previous_selection, root_fallback)
14
- extract_selectors
15
- @tests = extract_equality_tests
16
- @message = @values.shift
17
-
18
- if @message.is_a?(Hash)
19
- 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."
20
- end
21
-
22
- if @values.shift
23
- raise ArgumentError, "Not expecting that last argument, you either have too many arguments, or they're the wrong type"
24
- end
25
- end
26
-
27
- def selecting_no_body? #:nodoc:
28
- # Nokogiri gives the document a body element. Which means we can't
29
- # run an assertion expecting there to not be a body.
30
- @selector == 'body' && @tests[:count] == 0
31
- end
32
-
33
- def select
34
- filter @root.css(@selector, context)
35
- end
36
-
37
- private
38
-
39
- NO_STRIP = %w{pre script style textarea}
40
-
41
- mattr_reader(:context) { SubstitutionContext.new }
42
-
43
- def filter(matches)
44
- match_with = tests[:text] || tests[:html]
45
- return matches if matches.empty? || !match_with
46
-
47
- content_mismatch = nil
48
- text_matches = tests.has_key?(:text)
49
- regex_matching = match_with.is_a?(Regexp)
50
-
51
- remaining = matches.reject do |match|
52
- # Preserve markup with to_s for html elements
53
- content = text_matches ? match.text : match.children.to_s
54
-
55
- content.strip! unless NO_STRIP.include?(match.name)
56
- content.sub!(/\A\n/, '') if text_matches && match.name == "textarea"
57
-
58
- next if regex_matching ? (content =~ match_with) : (content == match_with)
59
- content_mismatch ||= diff(match_with, content)
60
- true
61
- end
62
-
63
- @message ||= content_mismatch if remaining.empty?
64
- Nokogiri::XML::NodeSet.new(matches.document, remaining)
65
- end
66
-
67
- def extract_root(previous_selection, root_fallback)
68
- possible_root = @values.first
69
-
70
- if possible_root == nil
71
- raise ArgumentError, 'First argument is either selector or element ' \
72
- 'to select, but nil found. Perhaps you called assert_dom with ' \
73
- 'an element that does not exist?'
74
- elsif possible_root.respond_to?(:css)
75
- @values.shift # remove the root, so selector is the first argument
76
- possible_root
77
- elsif previous_selection
78
- previous_selection
79
- else
80
- root_fallback.call
81
- end
82
- end
83
-
84
- def extract_selectors
85
- selector = @values.shift
86
-
87
- unless selector.is_a? String
88
- raise ArgumentError, "Expecting a selector as the first argument"
89
- end
90
-
91
- @css_selector = context.substitute!(selector, @values.dup, true)
92
- @selector = context.substitute!(selector, @values)
93
- end
94
-
95
- def extract_equality_tests
96
- comparisons = {}
97
- case comparator = @values.shift
98
- when Hash
99
- comparisons = comparator
100
- when String, Regexp
101
- comparisons[:text] = comparator
102
- when Integer
103
- comparisons[:count] = comparator
104
- when Range
105
- comparisons[:minimum] = comparator.begin
106
- comparisons[:maximum] = comparator.end
107
- when FalseClass
108
- comparisons[:count] = 0
109
- when NilClass, TrueClass
110
- comparisons[:minimum] = 1
111
- else raise ArgumentError, "I don't understand what you're trying to match"
112
- end
113
-
114
- # By default we're looking for at least one match.
115
- if comparisons[:count]
116
- comparisons[:minimum] = comparisons[:maximum] = comparisons[:count]
117
- else
118
- 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, refute: false, &root_fallback)
18
+ @values = values
19
+ @root = extract_root(previous_selection, root_fallback)
20
+ extract_selectors
21
+ @tests = extract_equality_tests(refute)
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
+ html_matches = tests.has_key?(:html)
55
+ regex_matching = match_with.is_a?(Regexp)
56
+
57
+ remaining = matches.reject do |match|
58
+ # Preserve markup with to_s for html elements
59
+ content = text_matches ? match.text : match.inner_html
60
+
61
+ content.strip! unless NO_STRIP.include?(match.name)
62
+ content.delete_prefix!("\n") if text_matches && match.name == "textarea"
63
+ collapse_html_whitespace!(content) unless NO_STRIP.include?(match.name) || html_matches
64
+
65
+ next if regex_matching ? (content =~ match_with) : (content == match_with)
66
+ content_mismatch ||= diff(match_with, content)
67
+ true
68
+ end
69
+
70
+ @message ||= content_mismatch if remaining.empty?
71
+ Nokogiri::XML::NodeSet.new(matches.document, remaining)
72
+ end
73
+
74
+ def extract_root(previous_selection, root_fallback)
75
+ possible_root = @values.first
76
+
77
+ if possible_root == nil
78
+ raise ArgumentError, "First argument is either selector or element " \
79
+ "to select, but nil found. Perhaps you called assert_dom with " \
80
+ "an element that does not exist?"
81
+ elsif possible_root.respond_to?(:css)
82
+ @values.shift # remove the root, so selector is the first argument
83
+ possible_root
84
+ elsif previous_selection
85
+ previous_selection
86
+ else
87
+ root_fallback.call
88
+ end
89
+ end
90
+
91
+ def extract_selectors
92
+ selector = @values.shift
93
+
94
+ unless selector.is_a? String
95
+ raise ArgumentError, "Expecting a selector as the first argument"
96
+ end
97
+
98
+ @css_selector = context.substitute!(selector, @values.dup, true)
99
+ @selector = context.substitute!(selector, @values)
100
+ end
101
+
102
+ def extract_equality_tests(refute)
103
+ comparisons = {}
104
+ case comparator = @values.shift
105
+ when Hash
106
+ comparisons = comparator
107
+ when String, Regexp
108
+ comparisons[:text] = comparator
109
+ when Integer
110
+ comparisons[:count] = comparator
111
+ when Range
112
+ comparisons[:minimum] = comparator.begin
113
+ comparisons[:maximum] = comparator.end
114
+ when FalseClass
115
+ comparisons[:count] = 0
116
+ when NilClass, TrueClass
117
+ comparisons[:minimum] = 1
118
+ else
119
+ raise ArgumentError, "I don't understand what you're trying to match"
120
+ end
121
+
122
+ if refute
123
+ if comparisons[:count] || (comparisons[:minimum] && !comparator.nil?) || comparisons[:maximum]
124
+ raise ArgumentError, "Cannot use true, false, Integer, Range, :count, :minimum and :maximum when asserting that a selector does not match"
125
+ end
126
+
127
+ comparisons[:count] = 0
128
+ end
129
+
130
+ # By default we're looking for at least one match.
131
+ if comparisons[:count]
132
+ comparisons[:minimum] = comparisons[:maximum] = comparisons[:count]
133
+ else
134
+ comparisons[:minimum] ||= 1
135
+ end
136
+
137
+ if comparisons[:minimum] && comparisons[:maximum] && comparisons[:minimum] > comparisons[:maximum]
138
+ raise ArgumentError, "Range begin or :minimum cannot be greater than Range end or :maximum"
139
+ end
140
+
141
+ @strict = comparisons[:strict]
142
+
143
+ comparisons
144
+ end
145
+
146
+ def collapse_html_whitespace!(text)
147
+ text.gsub!(/\s+/, " ")
148
+ end
149
+ end
150
+ end
151
+ end
119
152
  end
120
- comparisons
121
153
  end
122
154
  end
@@ -1,30 +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.gsub @substitute do |match|
8
- next match[0] if values.empty? || !substitutable?(values.first)
9
- matcher_for(values.shift, format_for_presentation)
10
- end
11
- end
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
12
12
 
13
- def match(matches, attribute, matcher)
14
- matches.find_all { |node| node[attribute] =~ Regexp.new(matcher) }
15
- 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
16
19
 
17
- private
18
- def matcher_for(value, format_for_presentation)
19
- # Nokogiri doesn't like arbitrary values without quotes, hence inspect.
20
- if format_for_presentation
21
- value.inspect # Avoid to_s so Regexps aren't put in quotes.
22
- else
23
- "\"#{value}\""
24
- end
25
- end
20
+ def match(matches, attribute, matcher)
21
+ matches.find_all { |node| node[attribute] =~ Regexp.new(matcher) }
22
+ end
23
+
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
26
35
 
27
- def substitutable?(value)
28
- [ Symbol, Numeric, String, Regexp ].any? { |type| value.is_a? type }
36
+ def substitutable?(value)
37
+ [ Symbol, Numeric, String, Regexp ].any? { |type| value.is_a? type }
38
+ end
39
+ end
40
+ end
41
+ end
29
42
  end
43
+ end
30
44
  end