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 +4 -4
- data/README.md +41 -20
- data/lib/rails/dom/testing/assertions/dom_assertions.rb +58 -11
- data/lib/rails/dom/testing/assertions/selector_assertions/html_selector.rb +151 -119
- data/lib/rails/dom/testing/assertions/selector_assertions/substitution_context.rb +38 -24
- data/lib/rails/dom/testing/assertions/selector_assertions.rb +311 -230
- data/lib/rails/dom/testing/assertions.rb +4 -3
- data/lib/rails/dom/testing/railtie.rb +14 -0
- data/lib/rails/dom/testing/version.rb +3 -1
- data/lib/rails/dom/testing.rb +52 -0
- data/lib/rails-dom-testing.rb +4 -1
- data/test/dom_assertions_test.rb +93 -5
- data/test/parser_selection_test.rb +75 -0
- data/test/selector_assertions_test.rb +245 -72
- data/test/test_helper.rb +21 -4
- metadata +14 -42
- data/lib/rails/dom/testing/assertions/selector_assertions/count_describable.rb +0 -28
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 92842b3fcd4006daf9f8047da3d90a171ead6f39900c2d4f491d3c3ac0658845
|
4
|
+
data.tar.gz: 67b13526065e8c54acc109945f9566947f1976c601e44bb2295eaab79d7fcfe4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
10
|
-
|
11
|
-
|
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
|
20
|
-
|
21
|
-
|
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
|
-
|
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
|
-
|
2
|
-
|
3
|
-
require
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
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
|
-
|
2
|
-
def initialize
|
3
|
-
@substitute = '?'
|
4
|
-
end
|
1
|
+
# frozen_string_literal: true
|
5
2
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
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
|
-
|
14
|
-
|
15
|
-
|
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
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
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
|
-
|
28
|
-
|
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
|