rails-dom-testing 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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