assert_xpath 0.4.0 → 0.4.2

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.
@@ -63,9 +63,9 @@ module AssertJavaScript
63
63
  # assert_xpath './/Statement[2]' do
64
64
  # assert_js_remote_function '/user/inventory/' do
65
65
  # json = assert_js_argument(2)
66
- # params = assert_params('uri?' + json[:parameters]).last
67
- # assert_equal @user.id.to_s, params[:user_id]
68
- # assert_equal @user.inventory.first.id.to_s, params[:inventory_id]
66
+ # path, query = assert_uri('path?' + json[:parameters])
67
+ # assert_equal @user.id.to_s, query[:user_id]
68
+ # assert_equal @user.inventory.first.id.to_s, query[:inventory_id]
69
69
  # end
70
70
  # end
71
71
  # end
@@ -236,8 +236,8 @@ module AssertJavaScript
236
236
 
237
237
  # Not ready for public use yet!
238
238
  #
239
- def assert_params(path_query)
240
- path, query = path_query.split('?')
239
+ def assert_uri(uri)
240
+ path, query = uri.split('?')
241
241
  params = {}
242
242
 
243
243
  if query
data/lib/assert_xpath.rb CHANGED
@@ -569,7 +569,7 @@ module AssertXPath
569
569
  # Wraps the common idiom <code>assert_xpath('descendant-or-self::./<em>my_tag</em>[ @id = "<em>my_id</em>" ]')</code>. Depends on +assert_xml+
570
570
  # * +tag+ - an XML node name, such as +div+ or +input+.
571
571
  # If this is a <code>:symbol</code>, we prefix "<code>.//</code>"
572
- # * +id+ - string or symbol uniquely identifying the node. This must not contain punctuation
572
+ # * +id+ - string, symbol, or hash identifying the node. ids must not contain punctuation
573
573
  # * +diagnostic+ - optional string to add to failure message
574
574
  # * <code>block|node|</code> - optional block containing assertions, based on
575
575
  # +assert_xpath+, which operate on this node as the XPath '.' current node.
@@ -588,9 +588,10 @@ module AssertXPath
588
588
  #
589
589
  # %transclude AssertXPathSuite#test_assert_tag_id
590
590
  #
591
- def assert_tag_id(tag, id, diagnostic = nil, &block)
591
+ def assert_tag_id(tag, id = {}, diagnostic = nil, diagnostic2 = nil, &block)
592
+ # if id is not a hash, diagnostic might be a hash too!
592
593
  # CONSIDER upgrade assert_tag_id to use each_element_with_attribute
593
- assert_xpath build_xpath(tag, id), diagnostic, &block
594
+ assert_xpath build_xpath(tag, id, diagnostic), diagnostic2 || diagnostic, &block
594
595
  end # NOTE: ids may not contain ', so we are delimiter-safe
595
596
 
596
597
  # Negates +assert_tag_id+. Depends on +assert_xml+
@@ -599,8 +600,8 @@ module AssertXPath
599
600
  #
600
601
  # See: +assert_tag_id+
601
602
  #
602
- def deny_tag_id(tag, id, diagnostic = nil)
603
- deny_xpath build_xpath(tag, id), diagnostic
603
+ def deny_tag_id(tag, id, diagnostic = nil, diagnostic2 = nil)
604
+ deny_xpath build_xpath(tag, id, diagnostic), diagnostic2 || diagnostic
604
605
  end
605
606
 
606
607
  # Pretty-print a REXML::Element or Hpricot::Elem
@@ -701,8 +702,15 @@ module AssertXPath
701
702
  return @helper.symbol_to_xpath(tag)
702
703
  end
703
704
 
704
- def build_xpath(tag, id)
705
- return symbol_to_xpath(tag) + "[ @id = '#{id}' ]"
705
+ def build_xpath(tag, id, diagnostic)
706
+ options = ( case id
707
+ when Symbol, String ; { :id => id }
708
+ when Hash ; id
709
+ end )
710
+
711
+ options.merge!(diagnostic) if diagnostic.kind_of? Hash
712
+ predicate = options.map{|k,v| "@#{k} = '#{v}'" }.join(' and ')
713
+ return symbol_to_xpath(tag) + "[ #{predicate} ]"
706
714
  end
707
715
 
708
716
  def stash_xdoc
@@ -0,0 +1,997 @@
1
+ require 'rexml/document'
2
+ require 'stringio'
3
+ require 'libxml' # FIXME soften that requirement!
4
+
5
+ RAILS_ENV = ENV.fetch('RAILS_ENV', 'test') unless defined?(RAILS_ENV)
6
+ AFE = Test::Unit::AssertionFailedError unless defined?(AFE)
7
+
8
+ =begin
9
+ #:stopdoc:
10
+ # "We had so much fun robbing the bank we forgot to take the money"
11
+ #
12
+ # ERGO comic book: programmers hunting bugs portrayed as big game hunters in jungle
13
+
14
+ # ERGO use length not size
15
+
16
+ # ERGO complain to ZenTest forum that
17
+ # assert_in_epsilon should not blot out
18
+ # its internal message if you add an external one
19
+ - test_zentest_assertions should use assert_raise_message
20
+ - we should use assert_include more
21
+ - use util_capture more!
22
+ - use Test::Rails::ControllerTestCase & HelperTestCase
23
+ - use all the assertions in ViewTest (and compete!)
24
+ - tell ZT their view assertions should nest
25
+ - take target name out of render calls if the test case name is correct!
26
+ - use path_parameters to override
27
+ - use the LayoutsController dummy object trick!
28
+ - use named routes more!
29
+
30
+ # ERGO learn FormEncodedPairParser
31
+ # ERGO RDoc a blog entry
32
+ # ERGO write deny_match, and make it work correctly!!
33
+ # ERGO link from Hpricot references to real Hpricot verbiage
34
+ # ERGO <tt> -> <code> in RDoc!!
35
+ # ERGO assert_xpath is nothing but a call to assert_any_xpath
36
+ # ERGO two %transclude directives in a row should work
37
+ # ERGO document, redundantly, that @xdoc always has bequeathed_attributes
38
+ # ERGO complain to RDoc maintainers that {}[] targets the current frame!
39
+ # monkey-patch thereof
40
+ # ERGO link out to REXML::Element
41
+ # ERGO how to RDoc format the sample for indent_xml etc?
42
+ # ERGO how to syntax hilite the sample for indent_xml etc?
43
+ # ERGO cross links in sample code
44
+ # ERGO back RDoc with complete source
45
+ # ERGO links from RDoc file reports in their contents
46
+ # ERGO is search{} defined in terms of assert_any_xpath??
47
+ # ERGO get Hpricot::search going for self-or-descendent, and take out all Hpricot::Doc cruft!
48
+ # ERGO does Hpricot#Doc[] or #Elem[] conflict with anything?
49
+ # ERGO merge the common bequeathed attributes into one module
50
+ # indent_xml should find a system built-into Hpricot?
51
+ #:startdoc:
52
+ =end
53
+
54
+
55
+ =begin rdoc
56
+ See: http://assertxpath.rubyforge.org/
57
+
58
+ %html <pre>
59
+ ___________________________
60
+ # __/ >tok tok tok< \
61
+ #< <%< <__ assert_xpath reads XHTML |
62
+ # (\\= | and queries its details! |
63
+ # | ---------------------------
64
+ #===={===
65
+ #
66
+ %html </pre>
67
+
68
+ =end
69
+
70
+
71
+ module AssertXPath
72
+
73
+ module CommonXPathExtensions #:nodoc:
74
+
75
+ def symbolic?(index)
76
+ return index.to_s if (index.kind_of? String or index.kind_of? Symbol)
77
+ end
78
+
79
+ def[](index)
80
+ if symbol = symbolic?(index)
81
+ return attributes[symbol] if attributes.has_key? symbol
82
+ raise_magic_member_not_found(symbol, caller)
83
+ end
84
+
85
+ return super # ERGO test this works?
86
+ end
87
+
88
+ def raise_magic_member_not_found(symbol, whats_caller_ERGO)
89
+ raise AFE, # ERGO merge with other raiser(s)
90
+ "missing attribute: `#{symbol}` in " +
91
+ "<#{ name } #{ attributes.keys.join(' ') }>",
92
+ whats_caller_ERGO
93
+ end
94
+
95
+ def identifiable?(str) # ERGO self. ?
96
+ return str =~ /^ [[:alpha:]] [[:alnum:]_]* $/ix
97
+ end # ERGO simplify??
98
+
99
+ # ERGO mock the YarWiki and run its tests locally!!!
100
+
101
+ def tribute(block)
102
+ stash = {} # put back the ones we changed!
103
+
104
+ if block
105
+ varz = instance_variables
106
+
107
+ attributes.each do |key, value|
108
+ if identifiable?(key) # deal if the key ain't a valid variable
109
+ key = "@#{ key }"
110
+ stash[key] = instance_variable_get(key) if varz.include?(key)
111
+ #p stash[key]
112
+ instance_variable_set key, value
113
+ end
114
+ end
115
+
116
+ return instance_eval(&block)
117
+ end
118
+ ensure # put them back!
119
+ stash.each{|key, value| instance_variable_set(key, value) }
120
+ end # this utterly sick convenience helps Ruby {@id} look like XPathic [@id]
121
+
122
+ end
123
+
124
+ # ERGO document me
125
+ def drill(&block)
126
+ if block
127
+ # ERGO harmonize with bang! version
128
+ # ERGO deal if the key ain't a valid variable
129
+
130
+ unless tribute(block) # ERGO pass in self (node)?
131
+ sib = self
132
+ nil while (sib = sib.next_sibling) and sib.node_type != :element
133
+ p sib # ERGO do tests ever get here?
134
+ q = sib and _bequeath_attributes(sib).drill(&block)
135
+ return sib if q
136
+ raise Test::Unit::AssertionFailedError.new("can't find beyond <#{xpath}>")
137
+ end
138
+ end
139
+
140
+ return self
141
+ # ERGO if block returns false/nil, find siblings until it passes.
142
+ # throw a test failure if it don't.
143
+ # ERGO axis concept
144
+ end
145
+
146
+ end
147
+
148
+ # ERGO node.descendant{ @type == 'text' and @id == 'foo' }.value
149
+ # ERGO node..a_descendant - overload ..
150
+
151
+ def _bequeath_attributes(node) #:nodoc:
152
+ return node if node.kind_of?(::Hpricot::Elem) or node.kind_of?(::Hpricot::Doc)
153
+ # ERGO shouldn't this be in a stinkin' module??
154
+ # ERGO SIMPLER!!
155
+ # ERGO document me
156
+ def node.drill(&block)
157
+ if block
158
+ # ERGO harmonize with bang! version
159
+ # ERGO deal if the key ain't a valid variable
160
+
161
+ unless tribute(block) # ERGO pass in self (node)?
162
+ sib = self
163
+ nil while (sib = sib.next_sibling) and sib.node_type != :element
164
+ q = sib and _bequeath_attributes(sib).drill(&block)
165
+ return sib if q
166
+ raise Test::Unit::AssertionFailedError.new("can't find beyond <#{ xpath }>")
167
+ end
168
+ end
169
+
170
+ return self
171
+ # ERGO if block returns false/nil, find siblings until it passes.
172
+ # throw a test failure if it don't.
173
+ # ERGO axis concept
174
+ end
175
+
176
+ return node # ERGO use this return value
177
+ end # ERGO is _ a good RPP for a "pretend private"?
178
+
179
+
180
+ module AssertXPath
181
+
182
+ Element = ::REXML::Element unless defined?(Element) #:nodoc:
183
+
184
+ class XmlHelper #:nodoc:
185
+ def libxml? ; false end # this is not a 'downcast' (bad in OO)
186
+ def rexml? ; false end # becase diverse libraries are a "boundary"
187
+ def hpricot? ; false end # situation. We can't control their contents!
188
+ end
189
+
190
+ class HpricotHelper < XmlHelper #:nodoc:
191
+ def hpricot? ; true end
192
+ def symbol_to_xpath(tag) tag.to_s end
193
+ def assert_xml(suite, *args, &block)
194
+ return suite.assert_hpricot(*args, &block)
195
+ end
196
+ end
197
+
198
+ class LibxmlHelper < XmlHelper #:nodoc:
199
+ def libxml? ; true end
200
+ def symbol_to_xpath(tag) "descendant-or-self::#{tag}" end
201
+ def assert_xml(suite, *args, &block)
202
+ return suite.assert_libxml(*args, &block)
203
+ end
204
+ end
205
+
206
+ class RexmlHelper < XmlHelper #:nodoc:
207
+ def rexml? ; true end
208
+ def symbol_to_xpath(tag) ".//#{tag}" end
209
+ def assert_xml(suite, *args, &block)
210
+ return suite.assert_rexml(*args, &block)
211
+ end
212
+ end
213
+
214
+ # Subsequent +assert_xml+ calls will use
215
+ # Hpricot[http://code.whytheluckystiff.net/hpricot/].
216
+ # (Alternately,
217
+ # +assert_hpricot+ will run one assertion in Hpricot mode.)
218
+ # Put +invoke_hpricot+ into +setup+() method, to
219
+ # run entire suites in this mode. These test cases
220
+ # explore some differences between the two assertion systems:
221
+ # %transclude AssertXPathSuite#test_assert_long_xpath
222
+ #
223
+ def invoke_hpricot
224
+ @xdoc = nil
225
+ @helper = HpricotHelper.new
226
+ end
227
+
228
+ # Subsequent +assert_xml+ calls will use
229
+ # LibXML[http://libxml.rubyforge.org/].
230
+ # (Alternately,
231
+ # +assert_libxml+ will run one assertion in Hpricot mode.)
232
+ # Put +invoke_libxml+ into +setup+() method, to
233
+ # run entire suites in this mode.
234
+ #
235
+ def invoke_libxml(favorite_flavor = :html)
236
+ @_favorite_flavor = favorite_flavor
237
+ @xdoc = nil
238
+ @helper = LibxmlHelper.new
239
+ end
240
+
241
+ def _doc_type # ERGO document all these!
242
+ { :html => '<!DOCTYPE HTML PUBLIC ' +
243
+ '"-//W3C//DTD HTML 4.01 Transitional//EN" '+
244
+ '"http://www.w3.org/TR/html4/loose.dtd">',
245
+ :xhtml => '<!DOCTYPE html PUBLIC ' +
246
+ '"-//W3C//DTD XHTML 1.0 Transitional//EN" ' +
247
+ '"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd" >',
248
+ :xml => nil
249
+ }.freeze
250
+ end
251
+ private :_doc_type
252
+
253
+ # ERGO what happens to assert_js_replace_html bearing entities??
254
+
255
+ # Subsequent +assert_xml+ calls will use REXML. See
256
+ # +invoke_hpricot+ to learn the various differences between the
257
+ # two systems
258
+ def invoke_rexml
259
+ @xdoc = nil
260
+ @helper = RexmlHelper.new
261
+ end
262
+
263
+ # %html <a name='assert_xml'></a>
264
+ #
265
+ # Prepare XML for assert_xpath <em>et al</em>
266
+ # * +xml+ - optional string containing XML. Without it, we read <code>@response.body</code>
267
+ # * <code>xpath, diagnostic, block</code> - optional arguments passed to +assert_xpath+
268
+ # Sets and returns the new secret <code>@xdoc</code> REXML::Element root
269
+ # call-seq:
270
+ # assert_xml(xml = @response.body <em>[, assert_xpath arguments]</em>) -> @xdoc, or assert_xpath's return value
271
+ #
272
+ # Assertions based on +assert_xpath+ will call this automatically if
273
+ # the secret <code>@xdoc</code> is +nil+. This implies we may freely call
274
+ # +assert_xpath+ after any method that populates <code>@response.body</code>
275
+ # -- if <code>@xdoc</code> is +nil+. When in doubt, call +assert_xml+ explicitly
276
+ #
277
+ # +assert_xml+ also translates the contents of +assert_select+ nodes. Use this to
278
+ # bridge assertions from one system to another. For example:
279
+ #
280
+ # Returns the first node in the XML
281
+ #
282
+ # Examples:
283
+ # assert_select 'div#home_page' do |home_page|
284
+ # assert_xml home_page # <-- calls home_page.to_s
285
+ # assert_xpath ".//img[ @src = '#{newb.image_uri(self)}' ]"
286
+ # deny_tag_id :form, :edit_user
287
+ # end
288
+ #
289
+ # %transclude AssertXPathSuite#test_assert_long_sick_expression
290
+ # See: AssertXPathSuite#test_assert_xml_drill
291
+ #
292
+ def assert_xml(*args, &block)
293
+ using :libxml? # prop-ulates @helper
294
+ return @helper.assert_xml(self, *args, &block)
295
+ end # ERGO take out the rescue nil!, and pass the diagnostic thru
296
+
297
+ # Processes a string of text, or the hidden <code>@response.body</code>,
298
+ # using REXML, and sets the hidden <code>@xdoc</code> node. Does
299
+ # not depend on, or change, the values of +invoke_hpricot+, +invoke_libxml+,
300
+ # or +invoke_rexml+
301
+ #
302
+ # Example:
303
+ # %transclude AssertXPathSuite#test_assert_rexml
304
+ #
305
+ def assert_rexml(*args, &block)
306
+ contents = (args.shift || @response.body).to_s
307
+ # ERGO benchmark these things
308
+
309
+ contents.gsub!('\\\'', '&apos;')
310
+ contents.gsub!('//<![CDATA[<![CDATA[', '')
311
+ contents.gsub!('//<![CDATA[', '')
312
+ contents.gsub!('//]]>', '')
313
+ contents.gsub!('//]>', '')
314
+ contents.gsub!('//]]', '')
315
+ contents.gsub!('//]', '')
316
+
317
+ begin
318
+ @xdoc = REXML::Document.new(contents)
319
+ rescue REXML::ParseException => e
320
+ raise e unless e.message =~ /attempted adding second root element to document/
321
+ @xdoc = REXML::Document.new("<xhtml>#{ contents }</xhtml>")
322
+ end
323
+
324
+ _bequeath_attributes(@xdoc)
325
+ assert_xpath(*args, &block) if args != []
326
+ return (assert_xpath('/*') rescue nil) if @xdoc
327
+ end
328
+
329
+ # Temporarily sets the validation type to :xml, :html, or :xhtml
330
+ #
331
+ def validate_as(type) # FIXME use or lose this
332
+ @_favorite_flavor, formerly = type, @_favorite_flavor
333
+ yield
334
+ ensure
335
+ @_favorite_flavor = type
336
+ end # ERGO more documentation!
337
+
338
+ def assert_libxml(*args, &block)
339
+ xml = args.shift || @xdoc || @response.body
340
+ xhtml = xml.to_s
341
+
342
+ # CONSIDER fix this like at the source??
343
+ xhtml.gsub!('<![CDATA[>', '')
344
+ xhtml.gsub!('<![CDATA[', '')
345
+ xhtml.gsub!('//]]]]>', '')
346
+ xhtml.gsub!(']]>', '')
347
+
348
+ if xhtml !~ /^\<\!DOCTYPE\b/ and xhtml !~ /\<\?xml\b/
349
+ xhtml = _doc_type[@_favorite_flavor || :html] + "\n" + xhtml if _doc_type[@_favorite_flavor]
350
+ end # ERGO document we pass HTML level into invoker
351
+
352
+ if xhtml.index('<?xml version="1" standalone="yes"?>') == 0
353
+ xhtml.gsub!('<?xml version="1" standalone="yes"?>', '')
354
+ xhtml.strip! # ERGO what is libxml's problem with that line???
355
+ end
356
+
357
+ # # FIXME blog that libxml will fully validate your ass...
358
+
359
+ xp = xhtml =~ /\<\!DOCTYPE/ ? XML::HTMLParser.new() : XML::Parser.new()
360
+ xhtml = '<xml/>' unless xhtml.any?
361
+ xp.string = xhtml
362
+ # FIXME blog we don't work with libxml-ruby 3.8.4
363
+ # XML::Parser.default_load_external_dtd = false
364
+ XML::Parser.default_pedantic_parser = false # FIXME optionalize that
365
+
366
+ #what? xp
367
+ doc = xp.parse
368
+ #what? doc
369
+ #puts doc.debug_dump
370
+ @xdoc = doc.root
371
+ # @xdoc.namespace ||= XML::NS.new('')
372
+
373
+ #pp (@xdoc.root.public_methods - public_methods).sort
374
+ return assert_xpath(*args, &block) if args.length > 0
375
+ return @xdoc
376
+ end
377
+
378
+ def using(kode)
379
+ @helper ||= RexmlHelper.new # ERGO escallate this!
380
+ return @helper.send(kode)
381
+ end
382
+
383
+ # FIXME test that the helper system withstands this effect:
384
+ # ""
385
+
386
+ # %html <a name='assert_hpricot'></a>
387
+ #
388
+ # This parses one XML string using Hpricot, so subsequent
389
+ # calls to +assert_xpath+ will use Hpricot expressions.
390
+ # This method does not depend on +invoke_hpricot+, and
391
+ # subsequent test cases will run in their suite's mode.
392
+ #
393
+ # Example:
394
+ # %transclude AssertXPathSuite#test_assert_hpricot
395
+ #
396
+ # See also: assert_hpricot[http://www.oreillynet.com/onlamp/blog/2007/08/assert_hpricot_1.html]
397
+ #
398
+ def assert_hpricot(*args, &block)
399
+ xml = args.shift || @xdoc || @response.body ## ERGO why @xdoc??
400
+ # ERGO document that callseq!
401
+ require 'hpricot'
402
+ @xdoc = Hpricot(xml.to_s) # ERGO take that to_s out of all callers
403
+ return assert_xpath(*args, &block) if args.length > 0
404
+ return @xdoc
405
+ end # ERGO reasonable error message if ill-formed
406
+
407
+ # ENCORE Bus to Julian! (-;
408
+ # ERGO why %html <a name='assert_xpath' /> screws up?
409
+
410
+ # %html <a name='assert_xpath'></a>
411
+ #
412
+ # Return the first XML node matching a query string. Depends on +assert_xml+
413
+ # to populate our secret internal REXML::Element, <code>@xdoc</code>
414
+ # * +xpath+ - a query string describing a path among XML nodes.
415
+ # See: {XPath Tutorial Roundup}[http://krow.livejournal.com/523993.html]
416
+ # * +diagnostic+ - optional string to add to failure message
417
+ # * <code>block|node|</code> - optional block containing assertions, based on +assert_xpath+,
418
+ # which operate on this node as the XPath '.' current +node+
419
+ # Returns the obtained REXML::Element +node+
420
+ #
421
+ # Examples:
422
+ #
423
+ # render :partial => 'my_partial'
424
+ #
425
+ # assert_xpath '/table' do |table|
426
+ # assert_xpath './/p[ @class = "brown_text" ]/a' do |a|
427
+ # assert_equal user.login, a.text # <-- native <code>REXML::Element#text</code> method
428
+ # assert_match /\/my_name$/, a[:href] # <-- attribute generated by +assert_xpath+
429
+ # end
430
+ # assert_equal "ring_#{ring.id}", table.id! # <-- attribute generated by +assert_xpath+, escaped with !
431
+ # end
432
+ #
433
+ # %transclude AssertXPathSuite#test_assert_xpath
434
+ #
435
+ # See: AssertXPathSuite#test_indent_xml,
436
+ # {XPath Checker}[https://addons.mozilla.org/en-US/firefox/addon/1095]
437
+ #
438
+ def assert_xpath(xpath, diagnostic = nil, &block)
439
+ # return assert_any_xpath(xpath, diagnostic) {
440
+ # block.call(@xdoc) if block
441
+ # true
442
+ # }
443
+ stash_xdoc do
444
+ xpath = symbol_to_xpath(xpath)
445
+ node = @xdoc.search(xpath).first
446
+ @xdoc = node || flunk_xpath(diagnostic, "should find xpath <#{_esc xpath}>")
447
+ @xdoc = _bequeath_attributes(@xdoc)
448
+ block.call(@xdoc) if block # ERGO tribute here?
449
+ return @xdoc
450
+ end
451
+ end
452
+
453
+ # FIXME assert_js_argument(2) gotta return nill for nada
454
+
455
+ # Negates +assert_xpath+. Depends on +assert_xml+
456
+ #
457
+ # Examples:
458
+ # assert_tag_id :td, :object_list do
459
+ # assert_xpath "table[ position() = 1 and @id = 'object_#{object1.id}' ]"
460
+ # deny_xpath "table[ position() = 2 and @id = 'object_#{object2.id}' ]"
461
+ # end # find object1 is still displayed, but object2 is not in position 2
462
+ #
463
+ # %transclude AssertXPathSuite#test_deny_xpath
464
+ #
465
+ def deny_xpath(xpath, diagnostic = nil)
466
+ @xdoc or assert_xml
467
+ xpath = symbol_to_xpath(xpath)
468
+
469
+ @xdoc.search(xpath).first and
470
+ flunk_xpath(diagnostic, "should not find: <#{_esc xpath}>")
471
+ end
472
+
473
+ # Search nodes for a matching XPath whose <code>AssertXPath::Element#inner_text</code> matches a Regular Expression. Depends on +assert_xml+
474
+ # * +xpath+ - a query string describing a path among XML nodes.
475
+ # See: {XPath Tutorial Roundup}[http://krow.livejournal.com/523993.html]
476
+ # * +matcher+ - optional Regular Expression to test node contents
477
+ # * +diagnostic+ - optional string to add to failure message
478
+ # * <code>block|node|</code> - optional block called once per match.
479
+ # If this block returns a value other than +false+ or +nil+,
480
+ # assert_any_xpath stops looping and returns the current +node+
481
+ #
482
+ # Example:
483
+ # %transclude AssertXPathSuite#test_assert_any_xpath
484
+ #
485
+ def assert_any_xpath(xpath, matcher = nil, diagnostic = nil, &block)
486
+ matcher ||= //
487
+ block ||= lambda{ true }
488
+ found_any = false
489
+ found_match = false
490
+ xpath = symbol_to_xpath(xpath)
491
+
492
+ stash_xdoc do
493
+ #assert_xpath xpath, diagnostic
494
+
495
+ if !using(:rexml?)
496
+ @xdoc.search(xpath) do |@xdoc|
497
+ found_any = true
498
+
499
+ if @xdoc.inner_text =~ matcher
500
+ found_match = true
501
+ _bequeath_attributes(@xdoc)
502
+ return @xdoc if block.call(@xdoc)
503
+ # note we only exit block if block.nil? or call returns false
504
+ end
505
+ end
506
+ else # ERGO merge!
507
+ @xdoc.each_element(xpath) do |@xdoc|
508
+ found_any = true
509
+
510
+ if @xdoc.inner_text =~ matcher
511
+ found_match = true
512
+ _bequeath_attributes(@xdoc)
513
+ return @xdoc if block.call(@xdoc)
514
+ # note we only exit block if block.nil? or call returns false
515
+ end
516
+ end
517
+ end
518
+ end
519
+
520
+ found_any or
521
+ flunk_xpath(diagnostic, "should find xpath <#{_esc xpath}>")
522
+
523
+ found_match or
524
+ flunk_xpath(
525
+ diagnostic,
526
+ "can find xpath <#{_esc xpath}> but can't find pattern <?>",
527
+ matcher
528
+ )
529
+ end
530
+
531
+ # Negates +assert_any_xpath+. Depends on +assert_xml+
532
+ #
533
+ # * +xpath+ - a query string describing a path among XML nodes. This
534
+ # must succeed - use +deny_xpath+ for simple queries that must fail
535
+ # * +matcher+ - optional Regular Expression to test node contents. If +xpath+ locates multiple nodes,
536
+ # this pattern must fail to match each node to pass the assertion.
537
+ # * +diagnostic+ - optional string to add to failure message
538
+ #
539
+ # Contrived example:
540
+ # assert_xml '<heathrow><terminal>5</terminal><lean>methods</lean></heathrow>'
541
+ #
542
+ # assert_raise_message Test::Unit::AssertionFailedError,
543
+ # /all xpath.*\.\/\/lean.*not have.*methods/ do
544
+ # deny_any_xpath :lean, /methods/
545
+ # end
546
+ #
547
+ # deny_any_xpath :lean, /denver/
548
+ #
549
+ # See: AssertXPathSuite#test_deny_any_xpath,
550
+ # {assert_raise (on Ruby) - Don't Just Say "No"}[http://www.oreillynet.com/onlamp/blog/2007/07/assert_raise_on_ruby_dont_just.html]
551
+ #
552
+ def deny_any_xpath(xpath, matcher, diagnostic = nil)
553
+ @xdoc or assert_xml
554
+ xpath = symbol_to_xpath(xpath)
555
+
556
+ assert_any_xpath xpath, nil, diagnostic do |node|
557
+ if node.inner_text =~ matcher
558
+ flunk_xpath(
559
+ diagnostic,
560
+ "all xpath <#{_esc xpath}> nodes should not have pattern <?>",
561
+ matcher
562
+ )
563
+ end
564
+ end
565
+ end
566
+
567
+ # FIXME @helper -> @_helper
568
+
569
+ # Wraps the common idiom <code>assert_xpath('descendant-or-self::./<em>my_tag</em>[ @id = "<em>my_id</em>" ]')</code>. Depends on +assert_xml+
570
+ # * +tag+ - an XML node name, such as +div+ or +input+.
571
+ # If this is a <code>:symbol</code>, we prefix "<code>.//</code>"
572
+ # * +id+ - string, symbol, or hash identifying the node. ids must not contain punctuation
573
+ # * +diagnostic+ - optional string to add to failure message
574
+ # * <code>block|node|</code> - optional block containing assertions, based on
575
+ # +assert_xpath+, which operate on this node as the XPath '.' current node.
576
+ # Returns the obtained REXML::Element +node+
577
+ #
578
+ # Examples:
579
+ #
580
+ # assert_tag_id '/span/div', "audience_#{ring.id}" do
581
+ # assert_xpath 'table/tr/td[1]' do |td|
582
+ # #...
583
+ # assert_tag_id :form, :for_sale
584
+ # end
585
+ # end
586
+ #
587
+ # %transclude AssertXPathSuite#test_assert_tag_id_and_tidy
588
+ #
589
+ # %transclude AssertXPathSuite#test_assert_tag_id
590
+ #
591
+ def assert_tag_id(tag, id = {}, diagnostic = nil, diagnostic2 = nil, &block)
592
+ # if id is not a hash, diagnostic might be a hash too!
593
+ # CONSIDER upgrade assert_tag_id to use each_element_with_attribute
594
+ assert_xpath build_xpath(tag, id, diagnostic), diagnostic2 || diagnostic, &block
595
+ end # NOTE: ids may not contain ', so we are delimiter-safe
596
+
597
+ # Negates +assert_tag_id+. Depends on +assert_xml+
598
+ #
599
+ # Example - see: +assert_xml+
600
+ #
601
+ # See: +assert_tag_id+
602
+ #
603
+ def deny_tag_id(tag, id, diagnostic = nil, diagnostic2 = nil)
604
+ deny_xpath build_xpath(tag, id, diagnostic), diagnostic2 || diagnostic
605
+ end
606
+
607
+ # Pretty-print a REXML::Element or Hpricot::Elem
608
+ # * +doc+ - optional element. Defaults to the current +assert_xml+ document
609
+ # returns: string with indented XML
610
+ #
611
+ # Use this while developing a test case, to see what
612
+ # the current <code>@xdoc</code> node contains (as populated by +assert_xml+ and
613
+ # manipulated by +assert_xpath+ <em>et al</em>)
614
+ #
615
+ # For example:
616
+ # assert_javascript 'if(x == 42) answer_great_question();'
617
+ #
618
+ # assert_js_if /x.*42/ do
619
+ # puts indent_xml # <-- temporary statement to see what to assert next!
620
+ # end
621
+ #
622
+ # See: AssertXPathSuite#test_indent_xml
623
+ #
624
+ def indent_xml(doc = @xdoc || assert_xml)
625
+ if doc.kind_of?(Hpricot::Elem) or doc.kind_of?(Hpricot::Doc)
626
+ zdoc = doc
627
+ doc = REXML::Document.new(doc.to_s.strip) rescue nil
628
+ unless doc # Hpricot didn't well-formify the HTML!
629
+ return zdoc.to_s # note: not indented, but good enough for error messages
630
+ end
631
+ end
632
+
633
+ # require 'rexml/formatters/default'
634
+ # bar = REXML::Formatters::Pretty.new
635
+ # out = String.new
636
+ # bar.write(doc, out)
637
+ # return out
638
+
639
+ return doc.to_s # ERGO reconcile with 1.8.6.111!
640
+
641
+ x = StringIO.new
642
+ doc.write(x, 2)
643
+ return x.string # CONSIDER does REXML have a simpler way?
644
+ end
645
+
646
+ # %html <a name='assert_tidy'></a>
647
+ # Thin wrapper on the Tidy command line program (the one released 2005 September)
648
+ # * +messy+ - optional string containing messy HTML. Defaults to <code>@response.body</code>.
649
+ # * +verbosity+ - optional noise level. Defaults to <code>:noisy</code>, which
650
+ # reports most errors. :verbose reports all information, and other value
651
+ # will repress all of Tidy's screams of horror regarding the quality of your HTML.
652
+ # The resulting XHTML loads into +assert_xml+. Use this to retrofit +assert_xpath+ tests
653
+ # to less-than-pristine HTML.
654
+ #
655
+ # +assert_tidy+ obeys +invoke_rexml+ and +invoke_hpricot+, to
656
+ # select its HTML parser
657
+ #
658
+ # Examples:
659
+ #
660
+ # get :adjust, :id => transaction.id # <-- fetches ill-formed HTML
661
+ # assert_tidy @response.body, :quiet # <-- upgrades it to well-formed
662
+ # assert_tag_id '//table', :payment_history do # <-- sees good XML
663
+ # #...
664
+ # end
665
+ #
666
+ # %transclude AssertXPathSuite#test_assert_tag_id_and_tidy
667
+ #
668
+ def assert_tidy(messy = @response.body, verbosity = :noisy)
669
+ scratch_html = RAILS_ROOT + '/tmp/scratch.html'
670
+ # CONSIDER a railsoid tmp file system?
671
+ # CONSIDER yield to something to respond to errors?
672
+ File.open(scratch_html, 'w'){|f| f.write(messy) }
673
+ gripes = `tidy -eq #{scratch_html} 2>&1`
674
+ gripes.split("\n")
675
+
676
+ # TODO kvetch about client_input_channel_req: channel 0 rtype keepalive@openssh.com reply 1
677
+
678
+ puts gripes if verbosity == :verbose
679
+
680
+ puts gripes.reject{|g|
681
+ g =~ / - Info\: / or
682
+ g =~ /Warning\: missing \<\!DOCTYPE\> declaration/ or
683
+ g =~ /proprietary attribute/ or
684
+ g =~ /lacks "(summary|alt)" attribute/
685
+ } if verbosity == :noisy
686
+
687
+ assert_xml `tidy -wrap 1001 -asxhtml #{ scratch_html } 2>/dev/null`
688
+ # CONSIDER that should report serious HTML deformities
689
+ end # CONSIDER how to tidy <% escaped %> eRB code??
690
+
691
+ # FIXME write a plugin for cruisecontrol.rb
692
+ # that links metrics to Gruff per project
693
+ # (and link from assert2.rf.org to rf.org/projects/assert2
694
+
695
+ private
696
+
697
+ # ERGO switch to descendant-or-self
698
+ # ERGO then update documentation of those who use this
699
+ def symbol_to_xpath(tag)
700
+ return tag unless tag.class == Symbol
701
+ @helper or using :libxml? # prop-ulates @helper
702
+ return @helper.symbol_to_xpath(tag)
703
+ end
704
+
705
+ def build_xpath(tag, id, diagnostic)
706
+ options = ( case id
707
+ when Symbol, String ; { :id => id }
708
+ when Hash ; id
709
+ end )
710
+
711
+ options.merge!(diagnostic) if diagnostic.kind_of? Hash
712
+ predicate = options.map{|k,v| "@#{k} = '#{v}'" }.join('')
713
+ return symbol_to_xpath(tag) + "[ #{predicate} ]"
714
+ end
715
+
716
+ def stash_xdoc
717
+ former_xdoc = @xdoc || assert_xml
718
+ yield ensure @xdoc = former_xdoc
719
+ end
720
+
721
+ def flunk_xpath(diagnostic, template, *args) #:nodoc:
722
+ xml = _esc(indent_xml).relevance || '(@xdoc is blank!)'
723
+ flunk build_message(diagnostic, "#{template} in\n#{xml}", *args)
724
+ end
725
+
726
+ end
727
+
728
+
729
+ def _esc(x) #:nodoc:
730
+ return x.gsub('?', '\?')
731
+ end
732
+
733
+
734
+ #####################################################
735
+
736
+ # FIXME got_libxml?
737
+
738
+ # ERGO hpricot gets its own module (REXML-free!)
739
+
740
+ def got_hpricot? # ERGO help tests pass without it
741
+ require 'hpricot'
742
+ return true
743
+ rescue MissingSourceFile
744
+ return false
745
+ end
746
+
747
+ #####################################################
748
+
749
+ # parking some tiny conveniences here,
750
+ # where even production code can get to them...
751
+ module Relevate
752
+ def relevant?
753
+ return ! blank?
754
+ end
755
+
756
+ def relevance
757
+ return to_s if relevant?
758
+ end
759
+ end
760
+
761
+ # ERGO dry these up
762
+ class String
763
+ def blank?
764
+ return strip.size == 0
765
+ end
766
+ end
767
+
768
+ class NilClass
769
+ def blank?; true; end
770
+ end
771
+
772
+ # ERGO include our test modules like this too
773
+ # ERGO seek relevant? calls that could use relevance
774
+
775
+ NilClass.send :include, Relevate
776
+ String .send :include, Relevate
777
+
778
+ #:enddoc:
779
+
780
+ # props: http://www.intertwingly.net/blog/2007/11/02/MonkeyPatch-for-Ruby-1-8-6
781
+ doc = REXML::Document.new '<doc xmlns="ns"><item name="foo"/></doc>'
782
+ if not doc.root.elements["item[@name='foo']"]
783
+ class REXML::Element
784
+ def attribute( name, namespace=nil )
785
+ prefix = nil
786
+ prefix = namespaces.index(namespace) if namespace
787
+ prefix = nil if prefix == 'xmlns'
788
+ attributes.get_attribute( "#{prefix ? prefix + ':' : ''}#{name}" )
789
+ end
790
+ end
791
+ end
792
+
793
+
794
+ # These monkey patches push Hpricot behavior closer to our customized REXML behavior
795
+ module Hpricot #:nodoc:
796
+ class Doc #:nodoc:
797
+ include AssertXPath::CommonXPathExtensions
798
+
799
+ def [](index) #:nodoc:
800
+ return root[index] if symbolic? index
801
+ super
802
+ end
803
+
804
+ def text
805
+ return root.text
806
+ end
807
+
808
+ def method_missing(*args, &block) #:nodoc:
809
+ # if got = search(symbol).first get first descendant working here!
810
+ # ERGO call root here
811
+ symbol = args.first.to_s.sub(/\!$/, '')
812
+
813
+ root.children.grep(Hpricot::Elem).each do |kid|
814
+ if kid.name == symbol
815
+ return kid.drill(&block)
816
+ # ERGO assert on return value
817
+ # ERGO pass kid in for if you want it
818
+ end
819
+ end
820
+ # ERGO raise here?
821
+ end
822
+ end
823
+
824
+ class Elem #:nodoc:
825
+ include AssertXPath::CommonXPathExtensions
826
+
827
+ def [](index) #:nodoc:
828
+ # ERGO do this conflict with anything?
829
+ if symbol = symbolic?(index)
830
+ return attributes[symbol] if attributes.has_key? symbol
831
+ raise_magic_member_not_found(symbol, caller)
832
+ end
833
+
834
+ super
835
+ end
836
+
837
+ def text # simulate REXML style - fetch child with text
838
+ return (text? ? to_s : '') + children.select(&:text?).map(&:to_s).compact.join
839
+ end
840
+
841
+ def node_type
842
+ return :element # ERGO make me less useless
843
+ end
844
+
845
+ def drill(&block)
846
+ if block
847
+ # ERGO harmonize with bang! version
848
+ # ERGO deal if the key ain't a valid variable
849
+ # ERGO get method_missing to stop returning []
850
+ unless tribute(block) # ERGO pass in self (node)?
851
+ sib = self
852
+ nil while (sib = sib.next_sibling) and sib.node_type != :element
853
+ q = sib and _bequeath_attributes(sib).drill(&block)
854
+ return sib if q
855
+ raise Test::Unit::AssertionFailedError.new("can't find beyond <#{_esc xpath}>")
856
+ end
857
+ end
858
+
859
+ return self
860
+ # ERGO if block returns false/nil, find siblings until it passes.
861
+ # throw a test failure if it don't.
862
+ # ERGO axis concept
863
+ end
864
+
865
+ def method_missing(*args, &block) #:nodoc:
866
+ symbol = args.shift.to_s.sub(/\!$/, '')
867
+
868
+ children.grep(Hpricot::Elem).each do |kid|
869
+ if kid.name == symbol
870
+ kid.tribute(block)
871
+ # ERGO assert on return value
872
+ # ERGO pass kid in for if you want it
873
+ return kid
874
+ end
875
+ end
876
+
877
+ raise_magic_member_not_found(symbol, caller) # ERGO repurpose!
878
+ end
879
+ end
880
+
881
+ end
882
+
883
+
884
+ module XML
885
+ class Node #:nodoc:
886
+ include AssertXPath::CommonXPathExtensions
887
+
888
+ def search(xpath, &block)
889
+ if block
890
+ find(xpath, "x:http://www.w3.org/1999/xhtml").each(&block)
891
+ #find(xpath, &block)
892
+ end
893
+ return [find_first(xpath, "x:http://www.w3.org/1999/xhtml")]
894
+ end
895
+ alias each_element search
896
+
897
+ def text
898
+ #p text?
899
+ find_first('text()').to_s
900
+ #text? ? content : ''
901
+ end
902
+
903
+ def inner_text(interstitial = '')
904
+ array = []
905
+ self.find( 'descendant-or-self::text()' ).each{|x| array << x }
906
+ return array.join(interstitial)
907
+ end # ERGO match??
908
+
909
+ def attributes
910
+ hash = {}
911
+ each_attr{|attr| hash[attr.name] = attr.value }
912
+ return hash # ERGO uh, was there a leaner way??
913
+ end
914
+
915
+ def [](index) #:nodoc:
916
+ return attributes[index.to_s] || super
917
+ end
918
+
919
+ def get_path(xpath)
920
+ node = find_first(xpath.to_s)
921
+ return _bequeath_attributes(node) if node
922
+ end # ERGO test that attributes are bequeathed!
923
+
924
+ alias :/ get_path
925
+
926
+ def method_missing(*args, &block) #:nodoc:
927
+ # ERGO use the define_method trick here
928
+ symbol = args.shift.to_s
929
+ symbol.sub!(/\!$/, '')
930
+
931
+ kid = if symbol == '/'
932
+ find_first('/')
933
+ else
934
+ find_first("./#{symbol}")
935
+ end
936
+ return _bequeath_attributes(kid).drill(&block) if kid
937
+ raise_magic_member_not_found(symbol, caller)
938
+ end
939
+ end
940
+
941
+ end
942
+
943
+
944
+ class REXML::Element
945
+ include AssertXPath::CommonXPathExtensions
946
+
947
+ # Semi-private method to match Hpricotic abilities
948
+ def search(xpath)
949
+ return self.each_element( xpath ){}
950
+ end
951
+
952
+ def method_missing(*args, &block) #:nodoc:
953
+ symbol = args.shift
954
+
955
+ each_element("./#{symbol}") do |kid|
956
+ return _bequeath_attributes(kid).drill(&block)
957
+ end # ERGO element/:child - def/
958
+
959
+ raise_magic_member_not_found(symbol, caller) # ERGO repurpose!
960
+ end # ERGO convert attribute chain to xpath
961
+
962
+ # Returns all text contents from a node and its descendants
963
+ #
964
+ # Example:
965
+ #
966
+ # assert_match 'can\'t be blank', assert_tag_id(:div, :errorExplanation).inner_text.strip
967
+ #
968
+ # %transclude AssertXPathSuite#test_inner_text
969
+ #
970
+ def inner_text(interstitial = '')
971
+ return self.each_element( './/text()' ){}.join(interstitial)
972
+ end # ERGO match??
973
+
974
+ def get_path(xpath)
975
+ node = each_element(xpath.to_s){}.first
976
+ return _bequeath_attributes(node) if node
977
+ end # ERGO test that attributes are bequeathed!
978
+
979
+ alias :/ get_path
980
+
981
+ # ERGO use set_backtrace to seat the backtracer to your code
982
+ # ERGO move _bequeath stuff in here!
983
+ # ERGO phase out the missing_method stuff that adds props
984
+
985
+ end
986
+
987
+
988
+ # FIXME hpricot, libxml, rexml always in alpha order
989
+
990
+ # http://www.oreillynet.com/ruby/blog/2008/01/assert_efficient_sql.html
991
+ # http://www.oreillynet.com/onlamp/blog/2007/09/big_requirements_up_front.html
992
+ # http://www.oreillynet.com/onlamp/blog/2007/08/assert_hpricot_1.html
993
+ # http://www.oreillynet.com/onlamp/blog/2007/08/xpath_checker_and_assert_xpath.html
994
+ # http://phlip.eblogs.com/2007/07/28/javascriptpureperl-for-ruby-enthusiasts/
995
+ # http://www.oreillynet.com/onlamp/blog/2007/07/assert_latest_and_greatest.html
996
+ # http://www.oreillynet.com/onlamp/blog/2007/07/assert_raise_on_ruby_dont_just.html
997
+ # http://phlip.eblogs.com/2007/01/02/growl-driven-development/
metadata CHANGED
@@ -1,55 +1,66 @@
1
1
  --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.9.4
3
+ specification_version: 1
2
4
  name: assert_xpath
3
5
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
6
+ version: 0.4.2
7
+ date: 2008-04-13 00:00:00 -07:00
8
+ summary: unit-test HTML at very high resolution
9
+ require_paths:
10
+ - lib
11
+ email: phlip2005@gmail.com
12
+ homepage: http://assertxpath.rubyforge.org/
13
+ rubyforge_project: assertxpath
14
+ description:
15
+ autorequire:
16
+ default_executable:
17
+ bindir: bin
18
+ has_rdoc: false
19
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
20
+ requirements:
21
+ - - ">"
22
+ - !ruby/object:Gem::Version
23
+ version: 0.0.0
24
+ version:
5
25
  platform: ruby
26
+ signing_key:
27
+ cert_chain:
28
+ post_install_message:
6
29
  authors:
7
30
  - Phlip
8
- autorequire:
9
- bindir: bin
10
- cert_chain: []
11
-
12
- date: 2008-03-10 00:00:00 -07:00
13
- default_executable:
14
- dependencies: []
15
-
16
- description:
17
- email: phlip2005@gmail.com
18
- executables: []
19
-
20
- extensions: []
21
-
22
- extra_rdoc_files: []
23
-
24
31
  files:
25
32
  - lib/assert_javascript.rb
33
+ - lib/assert_xpath.rb~
26
34
  - lib/jsToXml.pl
27
35
  - lib/assert_xpath.rb
28
- has_rdoc: false
29
- homepage: http://assertxpath.rubyforge.org/
30
- post_install_message:
36
+ test_files: []
37
+
31
38
  rdoc_options: []
32
39
 
33
- require_paths:
34
- - lib
35
- required_ruby_version: !ruby/object:Gem::Requirement
36
- requirements:
37
- - - ">="
38
- - !ruby/object:Gem::Version
39
- version: "0"
40
- version:
41
- required_rubygems_version: !ruby/object:Gem::Requirement
42
- requirements:
43
- - - ">="
44
- - !ruby/object:Gem::Version
45
- version: "0"
46
- version:
47
- requirements: []
40
+ extra_rdoc_files: []
48
41
 
49
- rubyforge_project: assertxpath
50
- rubygems_version: 1.0.1
51
- signing_key:
52
- specification_version: 2
53
- summary: unit-test HTML at very high resolution
54
- test_files: []
42
+ executables: []
43
+
44
+ extensions: []
45
+
46
+ requirements: []
55
47
 
48
+ dependencies:
49
+ - !ruby/object:Gem::Dependency
50
+ name: rubynode
51
+ version_requirement:
52
+ version_requirements: !ruby/object:Gem::Version::Requirement
53
+ requirements:
54
+ - - ">"
55
+ - !ruby/object:Gem::Version
56
+ version: 0.0.0
57
+ version:
58
+ - !ruby/object:Gem::Dependency
59
+ name: assert2
60
+ version_requirement:
61
+ version_requirements: !ruby/object:Gem::Version::Requirement
62
+ requirements:
63
+ - - ">"
64
+ - !ruby/object:Gem::Version
65
+ version: 0.0.0
66
+ version: