rails-dom-testing 2.1.0 → 2.2.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: d3af7ffd9f30304ed5cc33f9d2483fc9ba0827f6aade8d3e3bcef4410cb41956
4
- data.tar.gz: f1ba5e86a04619e0c9943ce4543dca021b1f2f8593ce2fa18b2e2e66318ed2aa
3
+ metadata.gz: d8720c7b560a8e3acaa5ae87a7dbfb587fe47c156d8f49b9d319b2c089a3d987
4
+ data.tar.gz: f482fcae81574f5fedc71d6e46ce372f8f273eac9df50a40d565696db1a02209
5
5
  SHA512:
6
- metadata.gz: 89242830f894040fa4bfca0777099dbd41f45b48745fa155ce0084e9abd60d66d61ab3f8be307bdbe8fd543b0cac1970b0fd7b2e7205430e425866719d832774
7
- data.tar.gz: f3398b23f429cc591c053750557135b7e5fc68cb9ba6b107b4f605c3ff2ca3e389261e060fa9a51755bc3d8b428cf7954588bf496b205fa84e10faf434dfe915
6
+ metadata.gz: bb404e501783f50dfd614101ee141ccc08665e2410fbd3c2e62c6859e4ed2edd2097169373b86d6deaefb63192385cf895d1ae0c512c69f65de760d38a8b6542
7
+ data.tar.gz: afc3f0247a26905b8aeb37fd94a9a0bd79f09b2ed334b539129f9566d3c25aed44e0e3a6a34f73c4ad0c6c485a24ae30e205b0aac14ff7e60b942be5d32d7663
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
@@ -53,6 +33,44 @@ assert_dom_email '#you-got-mail'
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
76
  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,37 @@ 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
25
73
 
26
74
  protected
27
-
28
75
  def compare_doms(expected, actual, strict)
29
76
  expected_children = extract_children(expected, strict)
30
77
  actual_children = extract_children(actual, strict)
@@ -41,7 +88,7 @@ module Rails
41
88
  if strict
42
89
  node.children
43
90
  else
44
- node.children.reject{|n| n.text? && n.text.blank?}
91
+ node.children.reject { |n| n.text? && n.text.blank? }
45
92
  end
46
93
  end
47
94
 
@@ -83,9 +130,8 @@ module Rails
83
130
  end
84
131
 
85
132
  private
86
-
87
- def fragment(text)
88
- 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)
89
135
  end
90
136
  end
91
137
  end
@@ -1,120 +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
- include Minitest::Assertions
8
-
9
- def initialize(values, previous_selection = nil, &root_fallback)
10
- @values = values
11
- @root = extract_root(previous_selection, root_fallback)
12
- extract_selectors
13
- @tests = extract_equality_tests
14
- @message = @values.shift
15
-
16
- if @message.is_a?(Hash)
17
- 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."
18
- end
19
-
20
- if @values.shift
21
- raise ArgumentError, "Not expecting that last argument, you either have too many arguments, or they're the wrong type"
22
- end
23
- end
24
-
25
- def selecting_no_body? #:nodoc:
26
- # Nokogiri gives the document a body element. Which means we can't
27
- # run an assertion expecting there to not be a body.
28
- @selector == 'body' && @tests[:count] == 0
29
- end
30
-
31
- def select
32
- filter @root.css(@selector, context)
33
- end
34
-
35
- private
36
-
37
- NO_STRIP = %w{pre script style textarea}
38
-
39
- mattr_reader(:context) { SubstitutionContext.new }
40
-
41
- def filter(matches)
42
- match_with = tests[:text] || tests[:html]
43
- return matches if matches.empty? || !match_with
44
-
45
- content_mismatch = nil
46
- text_matches = tests.has_key?(:text)
47
- regex_matching = match_with.is_a?(Regexp)
48
-
49
- remaining = matches.reject do |match|
50
- # Preserve markup with to_s for html elements
51
- content = text_matches ? match.text : match.children.to_s
52
-
53
- content.strip! unless NO_STRIP.include?(match.name)
54
- content.sub!(/\A\n/, '') if text_matches && match.name == "textarea"
55
-
56
- next if regex_matching ? (content =~ match_with) : (content == match_with)
57
- content_mismatch ||= diff(match_with, content)
58
- true
59
- end
60
-
61
- @message ||= content_mismatch if remaining.empty?
62
- Nokogiri::XML::NodeSet.new(matches.document, remaining)
63
- end
64
-
65
- def extract_root(previous_selection, root_fallback)
66
- possible_root = @values.first
67
-
68
- if possible_root == nil
69
- raise ArgumentError, 'First argument is either selector or element ' \
70
- 'to select, but nil found. Perhaps you called assert_dom with ' \
71
- 'an element that does not exist?'
72
- elsif possible_root.respond_to?(:css)
73
- @values.shift # remove the root, so selector is the first argument
74
- possible_root
75
- elsif previous_selection
76
- previous_selection
77
- else
78
- root_fallback.call
79
- end
80
- end
81
-
82
- def extract_selectors
83
- selector = @values.shift
84
-
85
- unless selector.is_a? String
86
- raise ArgumentError, "Expecting a selector as the first argument"
87
- end
88
-
89
- @css_selector = context.substitute!(selector, @values.dup, true)
90
- @selector = context.substitute!(selector, @values)
91
- end
92
-
93
- def extract_equality_tests
94
- comparisons = {}
95
- case comparator = @values.shift
96
- when Hash
97
- comparisons = comparator
98
- when String, Regexp
99
- comparisons[:text] = comparator
100
- when Integer
101
- comparisons[:count] = comparator
102
- when Range
103
- comparisons[:minimum] = comparator.begin
104
- comparisons[:maximum] = comparator.end
105
- when FalseClass
106
- comparisons[:count] = 0
107
- when NilClass, TrueClass
108
- comparisons[:minimum] = 1
109
- else raise ArgumentError, "I don't understand what you're trying to match"
110
- end
111
-
112
- # By default we're looking for at least one match.
113
- if comparisons[:count]
114
- comparisons[:minimum] = comparisons[:maximum] = comparisons[:count]
115
- else
116
- 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
117
130
  end
118
- comparisons
119
131
  end
120
132
  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