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