rails-dom-testing 2.1.1 → 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
2
  SHA256:
3
- metadata.gz: dc48c3a21ae3839cf5bbde901d06210fe2a4d1d58e6aae4048a26c19a6baf577
4
- data.tar.gz: b082464768d961ef20cc41ec1da545691bd7e0caac93a16b8668128818855885
3
+ metadata.gz: d8720c7b560a8e3acaa5ae87a7dbfb587fe47c156d8f49b9d319b2c089a3d987
4
+ data.tar.gz: f482fcae81574f5fedc71d6e46ce372f8f273eac9df50a40d565696db1a02209
5
5
  SHA512:
6
- metadata.gz: 51f291660cac9014e810adbf636c5dfbf7da80db478c062191eecedebe5b77b4f795a12259b1b427fd04b548e0a9b1b524d550e514b8e8b561ae566c36471182
7
- data.tar.gz: d5ac742ae523c62d565627f9c95553780286de17ba7a43f32972a5dcf2458d316b79c80edcfd3f4361b2d77a4dcfa541976f14cbb09b249bf681bfabf2c5696e
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,122 +1,132 @@
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, &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
119
130
  end
120
- comparisons
121
131
  end
122
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