rails-dom-testing 1.0.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: e4c5e946843592cec1b0b7e17a9dc5fe9b2b0ceb
4
+ data.tar.gz: 4cae76910af7476691d495a0ab84863e363824e3
5
+ SHA512:
6
+ metadata.gz: d4ead231de2ed2c888eba70d866eae3a58a7eb853c5863b67278a87e4b164beae052af93c3c6c36b51ef198964a7613b8c874ee9a3fc60ec0168484ce92b5e10
7
+ data.tar.gz: 7744ed519a6cff2f3e77395aced9a5d104e034ebddb7370897f53ad68d55f00917bfff1891fe8d4f494754f23630b7899c0afd1147c49dbbf7b2460dd8feb5a8
data/CHANGELOG.md ADDED
@@ -0,0 +1,3 @@
1
+ # 1.0.0
2
+
3
+ * First Release.
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Kasper Timm Hansen
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,66 @@
1
+ # Rails::Dom::Testing
2
+ [![Build Status](https://travis-ci.org/rails/rails-dom-testing.svg)](https://travis-ci.org/rails/rails-dom-testing)
3
+
4
+ This gem is responsible for comparing HTML doms and asserting that DOM elements are present in Rails applications.
5
+ Doms are compared via `assert_dom_equal` and `assert_dom_not_equal`.
6
+ Elements are asserted via `assert_select`, `assert_select_encoded`, `assert_select_email` and a subset of the dom can be selected with `css_select`.
7
+
8
+
9
+ ## Installation
10
+
11
+ Add this line to your application's Gemfile:
12
+
13
+ gem 'rails-dom-testing'
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install rails-dom-testing
22
+
23
+ ## Usage
24
+
25
+ ### Dom Assertions
26
+
27
+ ```ruby
28
+ assert_dom_equal '<h1>Lingua França</h1>', '<h1>Lingua França</h1>'
29
+
30
+ assert_dom_not_equal '<h1>Portuguese</h1>', '<h1>Danish</h1>'
31
+ ```
32
+
33
+ ### Selector Assertions
34
+
35
+ ```ruby
36
+ # implicitly selects from the document_root_element
37
+ css_select '.hello' # => Nokogiri::XML::NodeSet of elements with hello class
38
+
39
+ # select from a supplied node. assert_select asserts elements exist.
40
+ assert_select document_root_element.at('.hello'), '.goodbye'
41
+
42
+ # elements in CDATA encoded sections can also be selected
43
+ assert_select_encoded '#out-of-your-element'
44
+
45
+ # assert elements within an html email exists
46
+ assert_select_email '#you-got-mail'
47
+ ```
48
+
49
+ The documentation in [selector_assertions.rb](https://github.com/kaspth/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.
50
+
51
+ ## Read more
52
+
53
+ Under the hood the doms are parsed with Nokogiri and you'll generally be working with these two classes:
54
+ - [`Nokogiri::XML::Node`](http://nokogiri.org/Nokogiri/XML/Node.html)
55
+ - [`Nokogiri::XML::NodeSet`](http://nokogiri.org/Nokogiri/XML/NodeSet.html)
56
+
57
+ Read more about Nokogiri:
58
+ - [Nokogiri](http://nokogiri.org)
59
+
60
+ ## Contributing
61
+
62
+ 1. Fork it
63
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
64
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
65
+ 4. Push to the branch (`git push origin my-new-feature`)
66
+ 5. Create new Pull Request
@@ -0,0 +1 @@
1
+ require 'rails/dom/testing/assertions'
@@ -0,0 +1,18 @@
1
+ require 'active_support/concern'
2
+ require 'nokogiri'
3
+
4
+ module Rails
5
+ module Dom
6
+ module Testing
7
+ module Assertions
8
+ autoload :DomAssertions, 'rails/dom/testing/assertions/dom_assertions'
9
+ autoload :SelectorAssertions, 'rails/dom/testing/assertions/selector_assertions'
10
+
11
+ extend ActiveSupport::Concern
12
+
13
+ include DomAssertions
14
+ include SelectorAssertions
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,75 @@
1
+ module Rails
2
+ module Dom
3
+ module Testing
4
+ module Assertions
5
+ module DomAssertions
6
+ # \Test two HTML strings for equivalency (e.g., equal even when attributes are in another order)
7
+ #
8
+ # # 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)
12
+ message ||= "Expected: #{expected}\nActual: #{actual}"
13
+ assert compare_doms(expected_dom, actual_dom), message
14
+ end
15
+
16
+ # The negated form of +assert_dom_equal+.
17
+ #
18
+ # # 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)
22
+ message ||= "Expected: #{expected}\nActual: #{actual}"
23
+ assert_not compare_doms(expected_dom, actual_dom), message
24
+ end
25
+
26
+ protected
27
+
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])
33
+ end
34
+
35
+ true
36
+ end
37
+
38
+ def equal_children?(child, other_child)
39
+ return false unless child.type == other_child.type
40
+
41
+ if child.element?
42
+ child.name == other_child.name &&
43
+ equal_attribute_nodes?(child.attribute_nodes, other_child.attribute_nodes)
44
+ else
45
+ child.to_s == other_child.to_s
46
+ end
47
+ end
48
+
49
+ def equal_attribute_nodes?(nodes, other_nodes)
50
+ return false unless nodes.size == other_nodes.size
51
+
52
+ nodes = nodes.sort_by(&:name)
53
+ other_nodes = other_nodes.sort_by(&:name)
54
+
55
+ nodes.each_with_index do |attr, i|
56
+ return false unless equal_attribute?(attr, other_nodes[i])
57
+ end
58
+
59
+ true
60
+ end
61
+
62
+ def equal_attribute?(attr, other_attr)
63
+ attr.name == other_attr.name && attr.value == other_attr.value
64
+ end
65
+
66
+ private
67
+
68
+ def fragment(text)
69
+ Nokogiri::HTML::DocumentFragment.parse(text)
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,339 @@
1
+ require 'active_support/deprecation'
2
+ require_relative 'selector_assertions/html_selector'
3
+
4
+ module Rails
5
+ module Dom
6
+ module Testing
7
+ module Assertions
8
+ # Adds the +assert_select+ method for use in Rails functional
9
+ # test cases, which can be used to make assertions on the response HTML of a controller
10
+ # action. You can also call +assert_select+ within another +assert_select+ to
11
+ # make assertions on elements selected by the enclosing assertion.
12
+ #
13
+ # Use +css_select+ to select elements without making an assertions, either
14
+ # from the response HTML or elements selected by the enclosing assertion.
15
+ #
16
+ # In addition to HTML responses, you can make the following assertions:
17
+ #
18
+ # * +assert_select_encoded+ - Assertions on HTML encoded inside XML, for example for dealing with feed item descriptions.
19
+ # * +assert_select_email+ - Assertions on the HTML body of an e-mail.
20
+ module SelectorAssertions
21
+
22
+ # Select and return all matching elements.
23
+ #
24
+ # If called with a single argument, uses that argument as a selector.
25
+ # Called without an element +css_select+ selects from
26
+ # the element returned in +document_root_element+
27
+ #
28
+ # The default implementation of +document_root_element+ raises an exception explaining this.
29
+ #
30
+ # Returns an empty Nokogiri::XML::NodeSet if no match is found.
31
+ #
32
+ # If called with two arguments, uses the first argument as the root
33
+ # element and the second argument as the selector. Attempts to match the
34
+ # root element and any of its children.
35
+ # Returns an empty Nokogiri::XML::NodeSet if no match is found.
36
+ #
37
+ # The selector may be a CSS selector expression (String).
38
+ # css_select returns nil if called with an invalid css selector.
39
+ #
40
+ # # Selects all div tags
41
+ # divs = css_select("div")
42
+ #
43
+ # # Selects all paragraph tags and does something interesting
44
+ # pars = css_select("p")
45
+ # pars.each do |par|
46
+ # # Do something fun with paragraphs here...
47
+ # end
48
+ #
49
+ # # Selects all list items in unordered lists
50
+ # items = css_select("ul>li")
51
+ #
52
+ # # Selects all form tags and then all inputs inside the form
53
+ # forms = css_select("form")
54
+ # forms.each do |form|
55
+ # inputs = css_select(form, "input")
56
+ # ...
57
+ # end
58
+ def css_select(*args)
59
+ raise ArgumentError, "you at least need a selector argument" if args.empty?
60
+
61
+ root = args.size == 1 ? document_root_element : args.shift
62
+ selector = args.first
63
+
64
+ catch_invalid_selector do
65
+ nodeset(root).css(selector)
66
+ end
67
+ end
68
+
69
+ # An assertion that selects elements and makes one or more equality tests.
70
+ #
71
+ # If the first argument is an element, selects all matching elements
72
+ # starting from (and including) that element and all its children in
73
+ # depth-first order.
74
+ #
75
+ # If no element is specified +assert_select+ selects from
76
+ # the element returned in +document_root_element+
77
+ # unless +assert_select+ is called from within an +assert_select+ block.
78
+ # Override +document_root_element+ to tell +assert_select+ what to select from.
79
+ # The default implementation raises an exception explaining this.
80
+ #
81
+ # When called with a block +assert_select+ passes an array of selected elements
82
+ # to the block. Calling +assert_select+ from the block, with no element specified,
83
+ # runs the assertion on the complete set of elements selected by the enclosing assertion.
84
+ # Alternatively the array may be iterated through so that +assert_select+ can be called
85
+ # separately for each element.
86
+ #
87
+ #
88
+ # ==== Example
89
+ # If the response contains two ordered lists, each with four list elements then:
90
+ # assert_select "ol" do |elements|
91
+ # elements.each do |element|
92
+ # assert_select element, "li", 4
93
+ # end
94
+ # end
95
+ #
96
+ # will pass, as will:
97
+ # assert_select "ol" do
98
+ # assert_select "li", 8
99
+ # end
100
+ #
101
+ # The selector may be a CSS selector expression (String) or an expression
102
+ # with substitution values (Array).
103
+ # Substitution uses a custom pseudo class match. Pass in whatever attribute you want to match (enclosed in quotes) and a ? for the substitution.
104
+ # assert_select returns nil if called with an invalid css selector.
105
+ #
106
+ # assert_select "div:match('id', ?)", /\d+/
107
+ #
108
+ # === Equality Tests
109
+ #
110
+ # The equality test may be one of the following:
111
+ # * <tt>true</tt> - Assertion is true if at least one element selected.
112
+ # * <tt>false</tt> - Assertion is true if no element selected.
113
+ # * <tt>String/Regexp</tt> - Assertion is true if the text value of at least
114
+ # one element matches the string or regular expression.
115
+ # * <tt>Integer</tt> - Assertion is true if exactly that number of
116
+ # elements are selected.
117
+ # * <tt>Range</tt> - Assertion is true if the number of selected
118
+ # elements fit the range.
119
+ # If no equality test specified, the assertion is true if at least one
120
+ # element selected.
121
+ #
122
+ # To perform more than one equality tests, use a hash with the following keys:
123
+ # * <tt>:text</tt> - Narrow the selection to elements that have this text
124
+ # value (string or regexp).
125
+ # * <tt>:html</tt> - Narrow the selection to elements that have this HTML
126
+ # content (string or regexp).
127
+ # * <tt>:count</tt> - Assertion is true if the number of selected elements
128
+ # is equal to this value.
129
+ # * <tt>:minimum</tt> - Assertion is true if the number of selected
130
+ # elements is at least this value.
131
+ # * <tt>:maximum</tt> - Assertion is true if the number of selected
132
+ # elements is at most this value.
133
+ #
134
+ # If the method is called with a block, once all equality tests are
135
+ # evaluated the block is called with an array of all matched elements.
136
+ #
137
+ # # At least one form element
138
+ # assert_select "form"
139
+ #
140
+ # # Form element includes four input fields
141
+ # assert_select "form input", 4
142
+ #
143
+ # # Page title is "Welcome"
144
+ # assert_select "title", "Welcome"
145
+ #
146
+ # # Page title is "Welcome" and there is only one title element
147
+ # assert_select "title", {count: 1, text: "Welcome"},
148
+ # "Wrong title or more than one title element"
149
+ #
150
+ # # Page contains no forms
151
+ # assert_select "form", false, "This page must contain no forms"
152
+ #
153
+ # # Test the content and style
154
+ # assert_select "body div.header ul.menu"
155
+ #
156
+ # # Use substitution values
157
+ # assert_select "ol>li:match('id', ?)", /item-\d+/
158
+ #
159
+ # # All input fields in the form have a name
160
+ # assert_select "form input" do
161
+ # assert_select ":match('name', ?)", /.+/ # Not empty
162
+ # end
163
+ def assert_select(*args, &block)
164
+ @selected ||= nil
165
+
166
+ root = determine_root_from(args, @selected)
167
+ selector = HTMLSelector.new(root, args)
168
+
169
+ matches = nil
170
+ catch_invalid_selector do
171
+ matches = selector.select
172
+
173
+ assert_size_match!(matches.size, selector.equality_tests, selector.source, selector.message)
174
+ end
175
+
176
+ nest_selection(matches, &block) if block_given? && !matches.empty?
177
+
178
+ matches
179
+ end
180
+
181
+ def count_description(min, max, count) #:nodoc:
182
+ pluralize = lambda {|word, quantity| word << (quantity == 1 ? '' : 's')}
183
+
184
+ if min && max && (max != min)
185
+ "between #{min} and #{max} elements"
186
+ elsif min && max && max == min && count
187
+ "exactly #{count} #{pluralize['element', min]}"
188
+ elsif min && !(min == 1 && max == 1)
189
+ "at least #{min} #{pluralize['element', min]}"
190
+ elsif max
191
+ "at most #{max} #{pluralize['element', max]}"
192
+ end
193
+ end
194
+
195
+ # Extracts the content of an element, treats it as encoded HTML and runs
196
+ # nested assertion on it.
197
+ #
198
+ # You typically call this method within another assertion to operate on
199
+ # all currently selected elements. You can also pass an element or array
200
+ # of elements.
201
+ #
202
+ # The content of each element is un-encoded, and wrapped in the root
203
+ # element +encoded+. It then calls the block with all un-encoded elements.
204
+ #
205
+ # # Selects all bold tags from within the title of an Atom feed's entries (perhaps to nab a section name prefix)
206
+ # assert_select "feed[xmlns='http://www.w3.org/2005/Atom']" do
207
+ # # Select each entry item and then the title item
208
+ # assert_select "entry>title" do
209
+ # # Run assertions on the encoded title elements
210
+ # assert_select_encoded do
211
+ # assert_select "b"
212
+ # end
213
+ # end
214
+ # end
215
+ #
216
+ #
217
+ # # Selects all paragraph tags from within the description of an RSS feed
218
+ # assert_select "rss[version=2.0]" do
219
+ # # Select description element of each feed item.
220
+ # assert_select "channel>item>description" do
221
+ # # Run assertions on the encoded elements.
222
+ # assert_select_encoded do
223
+ # assert_select "p"
224
+ # end
225
+ # end
226
+ # end
227
+ def assert_select_encoded(element = nil, &block)
228
+ if !element && !@selected
229
+ raise ArgumentError, "Element is required when called from a nonnested assert_select"
230
+ end
231
+
232
+ content = nodeset(element || @selected).map do |elem|
233
+ elem.children.select(&:cdata?).map(&:content)
234
+ end.join
235
+
236
+ selected = Nokogiri::HTML::DocumentFragment.parse(content)
237
+ nest_selection(selected) do
238
+ if content.empty?
239
+ yield selected
240
+ else
241
+ assert_select ":root", &block
242
+ end
243
+ end
244
+ end
245
+
246
+ # Extracts the body of an email and runs nested assertions on it.
247
+ #
248
+ # You must enable deliveries for this assertion to work, use:
249
+ # ActionMailer::Base.perform_deliveries = true
250
+ #
251
+ # assert_select_email do
252
+ # assert_select "h1", "Email alert"
253
+ # end
254
+ #
255
+ # assert_select_email do
256
+ # items = assert_select "ol>li"
257
+ # items.each do
258
+ # # Work with items here...
259
+ # end
260
+ # end
261
+ def assert_select_email(&block)
262
+ deliveries = ActionMailer::Base.deliveries
263
+ assert !deliveries.empty?, "No e-mail in delivery list"
264
+
265
+ deliveries.each do |delivery|
266
+ (delivery.parts.empty? ? [delivery] : delivery.parts).each do |part|
267
+ if part["Content-Type"].to_s =~ /^text\/html\W/
268
+ root = Nokogiri::HTML::DocumentFragment.parse(part.body.to_s)
269
+ assert_select root, ":root", &block
270
+ end
271
+ end
272
+ end
273
+ end
274
+
275
+ protected
276
+
277
+ def document_root_element
278
+ raise NotImplementedError, "Implementing document_root_element makes assert_select work without needing to specify an element to select from."
279
+ end
280
+
281
+ def catch_invalid_selector
282
+ begin
283
+ yield
284
+ rescue Nokogiri::CSS::SyntaxError => e
285
+ ActiveSupport::Deprecation.warn("The assertion was not run because of an invalid css selector.\n#{e}")
286
+ return
287
+ end
288
+ end
289
+
290
+ # +equals+ must contain :minimum, :maximum and :count keys
291
+ def assert_size_match!(size, equals, css_selector, message = nil)
292
+ min, max, count = equals[:minimum], equals[:maximum], equals[:count]
293
+
294
+ message ||= %(Expected #{count_description(min, max, count)} matching "#{css_selector}", found #{size}.)
295
+ if count
296
+ assert_equal count, size, message
297
+ else
298
+ assert_operator size, :>=, min, message if min
299
+ assert_operator size, :<=, max, message if max
300
+ end
301
+ end
302
+
303
+ def determine_root_from(args, previous_selection = nil)
304
+ possible_root = args.first
305
+ if possible_root == nil
306
+ raise ArgumentError, "First argument is either selector or element to select, but nil found. Perhaps you called assert_select with an element that does not exist?"
307
+ elsif HTMLSelector.can_select_from?(possible_root)
308
+ args.shift # remove the root, so selector is the first argument
309
+ possible_root
310
+ elsif previous_selection
311
+ previous_selection
312
+ else
313
+ document_root_element
314
+ end
315
+ end
316
+
317
+ def nest_selection(selection)
318
+ # Set @selected to allow nested assert_select.
319
+ # Can be nested several levels deep.
320
+ begin
321
+ old_selected, @selected = @selected, selection
322
+ yield @selected
323
+ ensure
324
+ @selected = old_selected
325
+ end
326
+ end
327
+
328
+ def nodeset(node)
329
+ if node.is_a?(Nokogiri::XML::NodeSet)
330
+ node
331
+ else
332
+ Nokogiri::XML::NodeSet.new(node.document, [node])
333
+ end
334
+ end
335
+ end
336
+ end
337
+ end
338
+ end
339
+ end
@@ -0,0 +1,96 @@
1
+ require_relative 'substitution_context'
2
+
3
+ class HTMLSelector #:nodoc:
4
+ NO_STRIP = %w{pre script style textarea}
5
+ attr_accessor :root, :selector, :equality_tests, :message
6
+
7
+ alias :source :selector
8
+
9
+ def initialize(root, args)
10
+ @root = root
11
+ @selector = extract_selector(args)
12
+
13
+ @equality_tests = equality_tests_from(args.shift)
14
+ @message = args.shift
15
+
16
+ if args.shift
17
+ raise ArgumentError, "Not expecting that last argument, you either have too many arguments, or they're the wrong type"
18
+ end
19
+ end
20
+
21
+ class << self
22
+ def can_select_from?(selector)
23
+ selector.respond_to?(:css)
24
+ end
25
+ end
26
+
27
+ def select
28
+ filter root.css(selector, context)
29
+ end
30
+
31
+ def filter(matches)
32
+ match_with = equality_tests[:text] || equality_tests[:html]
33
+ return matches if matches.empty? || !match_with
34
+
35
+ content_mismatch = nil
36
+ text_matches = equality_tests.has_key?(:text)
37
+ regex_matching = match_with.is_a?(Regexp)
38
+
39
+ remaining = matches.reject do |match|
40
+ # Preserve markup with to_s for html elements
41
+ content = text_matches ? match.text : match.children.to_s
42
+
43
+ content.strip! unless NO_STRIP.include?(match.name)
44
+ content.sub!(/\A\n/, '') if text_matches && match.name == "textarea"
45
+
46
+ next if regex_matching ? (content =~ match_with) : (content == match_with)
47
+ content_mismatch ||= sprintf("<%s> expected but was\n<%s>.", match_with, content)
48
+ true
49
+ end
50
+
51
+ self.message ||= content_mismatch if remaining.empty?
52
+ Nokogiri::XML::NodeSet.new(matches.document, remaining)
53
+ end
54
+
55
+ def extract_selector(values)
56
+ selector = values.shift
57
+
58
+ unless selector.is_a? String
59
+ raise ArgumentError, "Expecting a selector as the first argument"
60
+ end
61
+
62
+ context.substitute!(selector, values)
63
+ end
64
+
65
+ def equality_tests_from(comparator)
66
+ comparisons = {}
67
+ case comparator
68
+ when Hash
69
+ comparisons = comparator
70
+ when String, Regexp
71
+ comparisons[:text] = comparator
72
+ when Integer
73
+ comparisons[:count] = comparator
74
+ when Range
75
+ comparisons[:minimum] = comparator.begin
76
+ comparisons[:maximum] = comparator.end
77
+ when FalseClass
78
+ comparisons[:count] = 0
79
+ when NilClass, TrueClass
80
+ comparisons[:minimum] = 1
81
+ else raise ArgumentError, "I don't understand what you're trying to match"
82
+ end
83
+
84
+ # By default we're looking for at least one match.
85
+ if comparisons[:count]
86
+ comparisons[:minimum] = comparisons[:maximum] = comparisons[:count]
87
+ else
88
+ comparisons[:minimum] ||= 1
89
+ end
90
+ comparisons
91
+ end
92
+
93
+ def context
94
+ @context ||= SubstitutionContext.new
95
+ end
96
+ end
@@ -0,0 +1,28 @@
1
+ class SubstitutionContext
2
+ def initialize(substitute = '?')
3
+ @substitute = substitute
4
+ @regexes = []
5
+ end
6
+
7
+ def add_regex(regex)
8
+ # Nokogiri doesn't like arbitrary values without quotes, hence inspect.
9
+ return regex.inspect unless regex.is_a?(Regexp)
10
+ @regexes.push(regex)
11
+ last_id.to_s # avoid implicit conversions of Fixnum to String
12
+ end
13
+
14
+ def last_id
15
+ @regexes.count - 1
16
+ end
17
+
18
+ def match(matches, attribute, id)
19
+ matches.find_all { |node| node[attribute] =~ @regexes[id] }
20
+ end
21
+
22
+ def substitute!(selector, values)
23
+ while !values.empty? && selector.index(@substitute)
24
+ selector.sub!(@substitute, add_regex(values.shift))
25
+ end
26
+ selector
27
+ end
28
+ end
@@ -0,0 +1,7 @@
1
+ module Rails
2
+ module Dom
3
+ module Testing
4
+ VERSION = "1.0.0"
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,43 @@
1
+ require 'test_helper'
2
+ require 'rails/dom/testing/assertions/dom_assertions'
3
+
4
+ class DomAssertionsTest < ActiveSupport::TestCase
5
+ Assertion = Minitest::Assertion
6
+
7
+ include Rails::Dom::Testing::Assertions::DomAssertions
8
+
9
+ def test_responds_to_assert_dom_equal
10
+ assert respond_to?(:assert_dom_equal)
11
+ end
12
+
13
+ def test_dom_equal
14
+ html = '<a></a>'
15
+ assert_dom_equal(html, html.dup)
16
+ end
17
+
18
+ def test_equal_doms_with_different_order_attributes
19
+ attributes = %{<a b="hello" c="hello"></a>}
20
+ reverse_attributes = %{<a c="hello" b="hello"></a>}
21
+ assert_dom_equal(attributes, reverse_attributes)
22
+ end
23
+
24
+ def test_dom_not_equal
25
+ assert_dom_not_equal('<a></a>', '<b></b>')
26
+ end
27
+
28
+ def test_unequal_doms_attributes_with_different_order_and_values
29
+ attributes = %{<a b="hello" c="hello"></a>}
30
+ reverse_attributes = %{<a c="hello" b="goodbye"></a>}
31
+ assert_dom_not_equal(attributes, reverse_attributes)
32
+ end
33
+
34
+ def test_custom_message_is_used_in_failures
35
+ message = "This is my message."
36
+
37
+ e = assert_raises(Assertion) do
38
+ assert_dom_equal('<a></a>', '<b></b>', message)
39
+ end
40
+
41
+ assert_equal e.message, message
42
+ end
43
+ end
@@ -0,0 +1,281 @@
1
+ # encoding: utf-8
2
+
3
+ require 'test_helper'
4
+ require 'rails/dom/testing/assertions/selector_assertions'
5
+
6
+ class AssertSelectTest < ActiveSupport::TestCase
7
+ Assertion = Minitest::Assertion
8
+
9
+ include Rails::Dom::Testing::Assertions::SelectorAssertions
10
+
11
+ def assert_failure(message, &block)
12
+ e = assert_raises(Assertion, &block)
13
+ assert_match(message, e.message) if Regexp === message
14
+ assert_equal(message, e.message) if String === message
15
+ end
16
+
17
+ #
18
+ # Test assert select.
19
+ #
20
+
21
+ def test_assert_select
22
+ render_html %Q{<div id="1"></div><div id="2"></div>}
23
+ assert_select "div", 2
24
+ assert_failure(/Expected at least 1 element matching \"p\", found 0/) { assert_select "p" }
25
+ end
26
+
27
+ def test_equality_integer
28
+ render_html %Q{<div id="1"></div><div id="2"></div>}
29
+ assert_failure(/Expected exactly 3 elements matching \"div\", found 2/) { assert_select "div", 3 }
30
+ assert_failure(/Expected exactly 0 elements matching \"div\", found 2/) { assert_select "div", 0 }
31
+ end
32
+
33
+ def test_equality_true_false
34
+ render_html %Q{<div id="1"></div><div id="2"></div>}
35
+ assert_nothing_raised { assert_select "div" }
36
+ assert_raise(Assertion) { assert_select "p" }
37
+ assert_nothing_raised { assert_select "div", true }
38
+ assert_raise(Assertion) { assert_select "p", true }
39
+ assert_raise(Assertion) { assert_select "div", false }
40
+ assert_nothing_raised { assert_select "p", false }
41
+ end
42
+
43
+ def test_equality_false_message
44
+ render_html %Q{<div id="1"></div><div id="2"></div>}
45
+ assert_failure(/Expected exactly 0 elements matching \"div\", found 2/) { assert_select "div", false }
46
+ end
47
+
48
+ def test_equality_string_and_regexp
49
+ render_html %Q{<div id="1">foo</div><div id="2">foo</div>}
50
+ assert_nothing_raised { assert_select "div", "foo" }
51
+ assert_raise(Assertion) { assert_select "div", "bar" }
52
+ assert_nothing_raised { assert_select "div", :text=>"foo" }
53
+ assert_raise(Assertion) { assert_select "div", :text=>"bar" }
54
+ assert_nothing_raised { assert_select "div", /(foo|bar)/ }
55
+ assert_raise(Assertion) { assert_select "div", /foobar/ }
56
+ assert_nothing_raised { assert_select "div", :text=>/(foo|bar)/ }
57
+ assert_raise(Assertion) { assert_select "div", :text=>/foobar/ }
58
+ assert_raise(Assertion) { assert_select "p", :text=>/foobar/ }
59
+ end
60
+
61
+ def test_equality_of_html
62
+ render_html %Q{<p>\n<em>"This is <strong>not</strong> a big problem,"</em> he said.\n</p>}
63
+ text = "\"This is not a big problem,\" he said."
64
+ html = "<em>\"This is <strong>not</strong> a big problem,\"</em> he said."
65
+ assert_nothing_raised { assert_select "p", text }
66
+ assert_raise(Assertion) { assert_select "p", html }
67
+ assert_nothing_raised { assert_select "p", :html=>html }
68
+ assert_raise(Assertion) { assert_select "p", :html=>text }
69
+ # No stripping for pre.
70
+ render_html %Q{<pre>\n<em>"This is <strong>not</strong> a big problem,"</em> he said.\n</pre>}
71
+ text = "\n\"This is not a big problem,\" he said.\n"
72
+ html = "\n<em>\"This is <strong>not</strong> a big problem,\"</em> he said.\n"
73
+ assert_nothing_raised { assert_select "pre", text }
74
+ assert_raise(Assertion) { assert_select "pre", html }
75
+ assert_nothing_raised { assert_select "pre", :html=>html }
76
+ assert_raise(Assertion) { assert_select "pre", :html=>text }
77
+ end
78
+
79
+ def test_strip_textarea
80
+ render_html %Q{<textarea>\n\nfoo\n</textarea>}
81
+ assert_select "textarea", "\nfoo\n"
82
+ render_html %Q{<textarea>\nfoo</textarea>}
83
+ assert_select "textarea", "foo"
84
+ end
85
+
86
+ def test_counts
87
+ render_html %Q{<div id="1">foo</div><div id="2">foo</div>}
88
+ assert_nothing_raised { assert_select "div", 2 }
89
+ assert_failure(/Expected exactly 3 elements matching \"div\", found 2/) do
90
+ assert_select "div", 3
91
+ end
92
+ assert_nothing_raised { assert_select "div", 1..2 }
93
+ assert_failure(/Expected between 3 and 4 elements matching \"div\", found 2/) do
94
+ assert_select "div", 3..4
95
+ end
96
+ assert_nothing_raised { assert_select "div", :count=>2 }
97
+ assert_failure(/Expected exactly 3 elements matching \"div\", found 2/) do
98
+ assert_select "div", :count=>3
99
+ end
100
+ assert_nothing_raised { assert_select "div", :minimum=>1 }
101
+ assert_nothing_raised { assert_select "div", :minimum=>2 }
102
+ assert_failure(/Expected at least 3 elements matching \"div\", found 2/) do
103
+ assert_select "div", :minimum=>3
104
+ end
105
+ assert_nothing_raised { assert_select "div", :maximum=>2 }
106
+ assert_nothing_raised { assert_select "div", :maximum=>3 }
107
+ assert_failure(/Expected at most 1 element matching \"div\", found 2/) do
108
+ assert_select "div", :maximum=>1
109
+ end
110
+ assert_nothing_raised { assert_select "div", :minimum=>1, :maximum=>2 }
111
+ assert_failure(/Expected between 3 and 4 elements matching \"div\", found 2/) do
112
+ assert_select "div", :minimum=>3, :maximum=>4
113
+ end
114
+ end
115
+
116
+ def test_substitution_values
117
+ render_html %Q{<div id="1">foo</div><div id="2">foo</div>}
118
+ assert_select "div:match('id', ?)", /\d+/ do |elements|
119
+ assert_equal 2, elements.size
120
+ end
121
+ assert_select "div" do
122
+ assert_select ":match('id', ?)", /\d+/ do |elements|
123
+ assert_equal 2, elements.size
124
+ assert_select "#1"
125
+ assert_select "#2"
126
+ end
127
+ end
128
+ end
129
+
130
+ def test_nested_assert_select
131
+ render_html %Q{<div id="1">foo</div><div id="2">foo</div>}
132
+ assert_select "div" do |elements|
133
+ assert_equal 2, elements.size
134
+ assert_select elements, "#1"
135
+ assert_select elements, "#2"
136
+ end
137
+ assert_select "div" do
138
+ assert_select "div" do |elements|
139
+ assert_equal 2, elements.size
140
+ # Testing in a group is one thing
141
+ assert_select "#1,#2"
142
+ # Testing individually is another.
143
+ assert_select "#1"
144
+ assert_select "#2"
145
+ assert_select "#3", false
146
+ end
147
+ end
148
+
149
+ assert_failure(/Expected at least 1 element matching \"#4\", found 0\./) do
150
+ assert_select "div" do
151
+ assert_select "#4"
152
+ end
153
+ end
154
+ end
155
+
156
+ def test_assert_select_text_match
157
+ render_html %Q{<div id="1"><span>foo</span></div><div id="2"><span>bar</span></div>}
158
+ assert_select "div" do
159
+ assert_nothing_raised { assert_select "div", "foo" }
160
+ assert_nothing_raised { assert_select "div", "bar" }
161
+ assert_nothing_raised { assert_select "div", /\w*/ }
162
+ assert_nothing_raised { assert_select "div", :text => /\w*/, :count=>2 }
163
+ assert_raise(Assertion) { assert_select "div", :text=>"foo", :count=>2 }
164
+ assert_nothing_raised { assert_select "div", :html=>"<span>bar</span>" }
165
+ assert_nothing_raised { assert_select "div", :html=>"<span>bar</span>" }
166
+ assert_nothing_raised { assert_select "div", :html=>/\w*/ }
167
+ assert_nothing_raised { assert_select "div", :html=>/\w*/, :count=>2 }
168
+ assert_raise(Assertion) { assert_select "div", :html=>"<span>foo</span>", :count=>2 }
169
+ end
170
+ end
171
+
172
+ #
173
+ # Test css_select.
174
+ #
175
+
176
+ def test_css_select
177
+ render_html %Q{<div id="1"></div><div id="2"></div>}
178
+ assert_equal 2, css_select("div").size
179
+ assert_equal 0, css_select("p").size
180
+ end
181
+
182
+ def test_nested_css_select
183
+ render_html %Q{<div id="1">foo</div><div id="2">foo</div>}
184
+ assert_select "div:match('id', ?)", /\d+/ do |elements|
185
+ assert_equal 1, css_select(elements[0], "div").size
186
+ assert_equal 1, css_select(elements[1], "div").size
187
+ end
188
+ assert_select "div" do
189
+ assert_equal 2, css_select("div").size
190
+ css_select("div").each do |element|
191
+ # Testing as a group is one thing
192
+ assert !css_select("#1,#2").empty?
193
+ # Testing individually is another
194
+ assert !css_select("#1").empty?
195
+ assert !css_select("#2").empty?
196
+ end
197
+ end
198
+ end
199
+
200
+ # testing invalid selectors
201
+ def test_assert_select_with_invalid_selector
202
+ render_html '<a href="http://example.com">hello</a>'
203
+ assert_deprecated do
204
+ assert_nil assert_select("[href=http://example.com]")
205
+ end
206
+ end
207
+
208
+ def test_css_select_with_invalid_selector
209
+ render_html '<a href="http://example.com">hello</a>'
210
+ assert_deprecated do
211
+ assert_nil css_select("[href=http://example.com]")
212
+ end
213
+ end
214
+
215
+ def test_feed_item_encoded
216
+ render_xml <<-EOF
217
+ <rss version="2.0">
218
+ <channel>
219
+ <item>
220
+ <description>
221
+ <![CDATA[
222
+ <p>Test 1</p>
223
+ ]]>
224
+ </description>
225
+ </item>
226
+ <item>
227
+ <description>
228
+ <![CDATA[
229
+ <p>Test 2</p>
230
+ ]]>
231
+ </description>
232
+ </item>
233
+ </channel>
234
+ </rss>
235
+ EOF
236
+ assert_select "channel item description" do
237
+
238
+ assert_select_encoded do
239
+ assert_select "p", :count=>2, :text=>/Test/
240
+ end
241
+
242
+ # Test individually.
243
+ assert_select "description" do |elements|
244
+ assert_select_encoded elements[0] do
245
+ assert_select "p", "Test 1"
246
+ end
247
+ assert_select_encoded elements[1] do
248
+ assert_select "p", "Test 2"
249
+ end
250
+ end
251
+ end
252
+
253
+ # Test that we only un-encode element itself.
254
+ assert_select "channel item" do
255
+ assert_select_encoded do
256
+ assert_select "p", 0
257
+ end
258
+ end
259
+ end
260
+
261
+ protected
262
+ def render_html(html)
263
+ fake_render(:html, html)
264
+ end
265
+
266
+ def render_xml(xml)
267
+ fake_render(:xml, xml)
268
+ end
269
+
270
+ def fake_render(content_type, content)
271
+ @html_document = if content_type == :xml
272
+ Nokogiri::XML::Document.parse(content)
273
+ else
274
+ Nokogiri::HTML::Document.parse(content)
275
+ end
276
+ end
277
+
278
+ def document_root_element
279
+ @html_document.root
280
+ end
281
+ end
@@ -0,0 +1,3 @@
1
+ require 'nokogiri'
2
+ require 'active_support/test_case'
3
+ require 'minitest/autorun'
metadata ADDED
@@ -0,0 +1,133 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rails-dom-testing
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Rafael Mendonça França
8
+ - Kasper Timm Hansen
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2014-08-18 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: nokogiri
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - "~>"
19
+ - !ruby/object:Gem::Version
20
+ version: 1.6.0
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - "~>"
26
+ - !ruby/object:Gem::Version
27
+ version: 1.6.0
28
+ - !ruby/object:Gem::Dependency
29
+ name: activesupport
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - ">="
33
+ - !ruby/object:Gem::Version
34
+ version: '0'
35
+ type: :runtime
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ version: '0'
42
+ - !ruby/object:Gem::Dependency
43
+ name: bundler
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - "~>"
47
+ - !ruby/object:Gem::Version
48
+ version: '1.3'
49
+ type: :development
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - "~>"
54
+ - !ruby/object:Gem::Version
55
+ version: '1.3'
56
+ - !ruby/object:Gem::Dependency
57
+ name: rake
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ type: :development
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ - !ruby/object:Gem::Dependency
71
+ name: minitest
72
+ requirement: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - ">="
82
+ - !ruby/object:Gem::Version
83
+ version: '0'
84
+ description: " Dom and Selector assertions for Rails applications "
85
+ email:
86
+ - rafaelmfranca@gmail.com
87
+ - kaspth@gmail.com
88
+ executables: []
89
+ extensions: []
90
+ extra_rdoc_files: []
91
+ files:
92
+ - CHANGELOG.md
93
+ - LICENSE.txt
94
+ - README.md
95
+ - lib/rails-dom-testing.rb
96
+ - lib/rails/dom/testing/assertions.rb
97
+ - lib/rails/dom/testing/assertions/dom_assertions.rb
98
+ - lib/rails/dom/testing/assertions/selector_assertions.rb
99
+ - lib/rails/dom/testing/assertions/selector_assertions/html_selector.rb
100
+ - lib/rails/dom/testing/assertions/selector_assertions/substitution_context.rb
101
+ - lib/rails/dom/testing/version.rb
102
+ - test/dom_assertions_test.rb
103
+ - test/selector_assertions_test.rb
104
+ - test/test_helper.rb
105
+ homepage: https://github.com/kaspth/rails-dom-testing
106
+ licenses:
107
+ - MIT
108
+ metadata: {}
109
+ post_install_message:
110
+ rdoc_options: []
111
+ require_paths:
112
+ - lib
113
+ required_ruby_version: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ required_rubygems_version: !ruby/object:Gem::Requirement
119
+ requirements:
120
+ - - ">="
121
+ - !ruby/object:Gem::Version
122
+ version: '0'
123
+ requirements: []
124
+ rubyforge_project:
125
+ rubygems_version: 2.2.2
126
+ signing_key:
127
+ specification_version: 4
128
+ summary: This gem can compare doms and assert certain elements exists in doms using
129
+ Nokogiri.
130
+ test_files:
131
+ - test/dom_assertions_test.rb
132
+ - test/selector_assertions_test.rb
133
+ - test/test_helper.rb