assert_xpath 0.4.0 → 0.4.2

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