Module: AssertXPath
Description
See: assertxpath.rubyforge.org/
___________________________
# __/ >tok tok tok< \
#< <%< <__ assert_xpath reads XHTML |
# (\\= | and queries its details! |
# | ---------------------------
#===={===
#
Public Instance methods
assert_any_xpath (xpath, matcher = nil, diagnostic = nil, &block)
Search nodes for a matching XPath whose AssertXPath::Element#inner_text matches a Regular Expression. Depends on assert_xml
- xpath - a query string describing a path among XML nodes. See: XPath Tutorial Roundup
- matcher - optional Regular Expression to test node contents
- diagnostic - optional string to add to failure message
- block|node| - optional block called once per match. If this block returns a value other than false or nil, assert_any_xpath stops looping and returns the current node
Example:
def test_assert_any_xpath assert_xml '<yo><i>we</i><i zone="phone">find_me</i></yo>' assert_any_xpath '/yo/i', /find_me/ assert_any_xpath :i, /find_me/ assert_any_xpath :i, /we/ assert_equal 'wefind_me', @xdoc.inner_text once = 0 assert_any_xpath :i, /find_me/ do |i| assert_equal '<i zone=\'phone\'>find_me</i>', indent_xml.strip assert_equal 'phone', i[:zone] # ERGO i.zone should raise if not found once += 1 false # stops the loop end assert_equal 1, once once = 0 node = assert_any_xpath :i do assert_equal '<i>we</i>', indent_xml.strip once += 1 true end assert_equal 1, once assert_equal 'we', node.text twice = 0 assert_any_xpath :i, /e/ do |node| assert_match /we|find_me/, node.text twice += 1 false # false means keep looping end assert_equal 2, twice twice = 0 assert_any_xpath :i do |node| assert_match /we|find_me/, node.text twice += 1 false # false means keep looping end assert_equal 2, twice assert_raise_message AFE, /should find.*frootloop/ do assert_any_xpath :frootloop end assert_raise_message AFE, /can find.*i.*but can't find.*don't find me/ do assert_any_xpath :i, /don't find me/ end end
# File lib/assert_xpath.rb, line 506 506: def assert_any_xpath(xpath, matcher = nil, diagnostic = nil, &block) 507: matcher ||= // 508: block ||= lambda{ true } 509: found_any = false 510: found_match = false 511: xpath = symbol_to_xpath(xpath) 512: 513: stash_xdoc do # ERGO check that stash_xdoc does _not_ mess with @hdoc 514: #assert_xpath xpath, diagnostic 515: 516: if @hdoc 517: @xdoc.search(xpath) do |@xdoc| 518: found_any = true 519: 520: if @xdoc.inner_text =~ matcher 521: found_match = true 522: _bequeath_attributes(@xdoc) 523: return @xdoc if block.call(@xdoc) 524: # note we only exit block if block.nil? or call returns false 525: end 526: end 527: else # ERGO merge! 528: @xdoc.each_element(xpath) do |@xdoc| 529: found_any = true 530: 531: if @xdoc.inner_text =~ matcher 532: found_match = true 533: _bequeath_attributes(@xdoc) 534: return @xdoc if block.call(@xdoc) 535: # note we only exit block if block.nil? or call returns false 536: end 537: end 538: end 539: end 540: 541: found_any or 542: flunk_xpath(diagnostic, "should find xpath <#{_esc xpath}>") 543: 544: found_match or 545: flunk_xpath( 546: diagnostic, 547: "can find xpath <#{_esc xpath}> but can't find pattern <?>", 548: matcher 549: ) 550: end
assert_hpricot (*args, &block)
This parses one XML string using Hpricot, so subsequent calls to assert_xpath will use Hpricot expressions. This method does not depend on invoke_hpricot, and subsequent test cases will run in their suite‘s mode.
Example:
def test_assert_hpricot assert_hpricot '<yo><i>nope</i><i class="foo">b xml</i></yo>' assert_kind_of Hpricot::Doc, @hdoc called_once = false @hdoc.search('//i'){ called_once = true } assert called_once assert_equal @xdoc, @hdoc end
See also: assert_hpricot
# File lib/assert_xpath.rb, line 420 420: def assert_hpricot(*args, &block) 421: xml = args.shift 422: xml ||= @xdoc || @response.body 423: # ERGO document that callseq! 424: require 'hpricot' 425: @xdoc = @hdoc = Hpricot(xml.to_s) # ERGO take that to_s out of all callers 426: return assert_xpath(*args, &block) if args.length > 0 427: return @xdoc 428: end
assert_rexml (*args, &block)
Processes a string of text, or the hidden @response.body, using REXML, and sets the hidden @xdoc node. Does not depend on, or change, the values of invoke_rexml or invoke_hpricot
Example:
def test_assert_rexml x = assert_rexml('<x id="42" />') assert_kind_of ::REXML::Element, x assert_equal '42', x[:id] x = assert_rexml('<x id="43" />') assert_equal '43', x[:id] assert_raise_message(AFE, /missing attribute.*v/){ x[:v] } end
# File lib/assert_xpath.rb, line 386 386: def assert_rexml(*args, &block) 387: contents = (args.shift || @response.body).to_s 388: # ERGO benchmark these things 389: 390: contents.gsub!('\\\'', ''') 391: contents.gsub!('//<![CDATA[<![CDATA[', '') 392: contents.gsub!('//<![CDATA[', '') 393: contents.gsub!('//]]>', '') 394: contents.gsub!('//]>', '') 395: 396: begin 397: @xdoc = REXML::Document.new(contents) 398: rescue REXML::ParseException => e 399: raise e unless e.message =~ /attempted adding second root element to document/ 400: @xdoc = REXML::Document.new("<xhtml>#{ contents }</xhtml>") 401: end 402: 403: _bequeath_attributes(@xdoc) 404: assert_xpath(*args, &block) if args != [] 405: return (assert_xpath('/*') rescue nil) if @xdoc 406: end
assert_tag_id (tag, id, diagnostic = nil, &block)
Wraps the common idiom assert_xpath(‘descendant-or-self::./my_tag[ @id = "my_id" ]’). Depends on assert_xml
- tag - an XML node name, such as div or input. If this is a :symbol, we prefix ".//"
- id - string or symbol uniquely identifying the node. This must not contain punctuation
- diagnostic - optional string to add to failure message
- block|node| - optional block containing assertions, based on assert_xpath, which operate on this node as the XPath ’.’ current node.
Returns the obtained REXML::Element node
Examples:
assert_tag_id '/span/div', "audience_#{ring.id}" do
assert_xpath 'table/tr/td[1]' do |td|
#...
assert_tag_id :form, :for_sale
end
end
def test_assert_tag_id_and_tidy assert_tidy '<html><form id="for_sale">' + ' <input type="text" id="item" value="boots">' + '</html>', :quiet # about the two missing end tags! assert_tag_id :form, :for_sale do input = assert_tag_id('input', :item) assert_equal 'text', input[:type] assert_equal 'boots', input[:value] end end
def test_assert_tag_id assert_xml '<html><form id="for_sale"> <input type="text" id="item" value="boots" /> </form></html>' assert_tag_id :form, :for_sale do input = assert_tag_id('input', :item) assert_equal 'text' , input[:type] assert_equal 'boots', input[:value] end end
# File lib/assert_xpath.rb, line 610 610: def assert_tag_id(tag, id, diagnostic = nil, &block) 611: # CONSIDER upgrade assert_tag_id to use each_element_with_attribute 612: assert_xpath build_xpath(tag, id), diagnostic, &block 613: end
assert_tidy (messy = @response.body, verbosity = :noisy)
Thin wrapper on the Tidy command line program (the one released 2005 September)
- messy - optional string containing messy HTML. Defaults to @response.body.
- verbosity - optional noise level. Defaults to :noisy - any other value will repress all of Tidy‘s screams of horror regarding the quality of your HTML.
The resulting XHTML loads into assert_xml. Use this to retrofit assert_xpath tests to less-than-pristine HTML.
assert_tidy obeys invoke_rexml and invoke_hpricot, to select its HTML parser
Examples:
get :adjust, :id => transaction.id # <-- fetches ill-formed HTML assert_tidy @response.body, :quiet # <-- upgrades it to well-formed assert_tag_id '//table', :payment_history do # <-- sees good XML #... end
def test_assert_tag_id_and_tidy assert_tidy '<html><form id="for_sale">' + ' <input type="text" id="item" value="boots">' + '</html>', :quiet # about the two missing end tags! assert_tag_id :form, :for_sale do input = assert_tag_id('input', :item) assert_equal 'text', input[:type] assert_equal 'boots', input[:value] end end
# File lib/assert_xpath.rb, line 685 685: def assert_tidy(messy = @response.body, verbosity = :noisy) 686: scratch_html = RAILS_ROOT + '/tmp/scratch.html' 687: # CONSIDER a railsoid tmp file system? 688: # CONSIDER yield to something to respond to errors? 689: File.open(scratch_html, 'w'){|f| f.write(messy) } 690: gripes = `tidy -eq #{scratch_html} 2>&1` 691: gripes.split("\n") 692: 693: exclude, inclued = gripes.partition do |g| 694: g =~ / - Info\: / or 695: g =~ /Warning\: missing \<\!DOCTYPE\> declaration/ or 696: g =~ /proprietary attribute/ or 697: g =~ /lacks "(summary|alt)" attribute/ 698: end 699: 700: puts inclued if verbosity == :noisy 701: # inclued.map{|i| puts Regexp.escape(i) } 702: assert_xml `tidy -wrap 1001 -asxhtml #{scratch_html} 2>/dev/null` 703: # CONSIDER that should report serious HTML deformities 704: end
assert_xml(xml = @response.body [, assert_xpath arguments]) → @xdoc, or assert_xpath's return value
Prepare XML for assert_xpath et al
- xml - optional string containing XML. Without it, we read @response.body
- xpath, diagnostic, block - optional arguments passed to assert_xpath
Sets and returns the new secret @xdoc REXML::Element root
Assertions based on assert_xpath will call this automatically if the secret @xdoc is nil. This implies we may freely call assert_xpath after any method that populates @response.body — if @xdoc is nil. When in doubt, call assert_xml explicitly
assert_xml also translates the contents of assert_select nodes. Use this to bridge assertions from one system to another. For example:
Returns the first node in the XML
Examples:
assert_select 'div#home_page' do |home_page|
assert_xml home_page # <-- calls home_page.to_s
assert_xpath ".//img[ @src = '#{newb.image_uri(self)}' ]"
deny_tag_id :form, :edit_user
end
def test_assert_long_sick_expression assert_xml '<a><b>c</b><b>d<e g="h">f</e><e g="i">j</e></b></a>' a = assert_xpath('a') assert_match /^c/, (a / :b).text # ERGO more accurate? assert_match /^c/, (a / 'b').text assert_match /^d/, (a / 'b[2]').text assert_match /^f/, (a / 'b/e').text assert_equal 'h', (a / 'b/e')[:g] unless @use_hpricot assert_equal 'h', (a / 'b/e').first[:g] if @use_hpricot end
See: AssertXPathSuite#test_assert_xml_drill
# File lib/assert_xpath.rb, line 373 373: def assert_xml(*args, &block) 374: return assert_hpricot(*args, &block) if @use_hpricot 375: return assert_rexml(*args, &block) 376: end
assert_xpath (xpath, diagnostic = nil, &block)
Return the first XML node matching a query string. Depends on assert_xml to populate our secret internal REXML::Element, @xdoc
- xpath - a query string describing a path among XML nodes. See: XPath Tutorial Roundup
- diagnostic - optional string to add to failure message
- block|node| - optional block containing assertions, based on assert_xpath, which operate on this node as the XPath ’.’ current node
Returns the obtained REXML::Element node
Examples:
render :partial => 'my_partial'
assert_xpath '/table' do |table|
assert_xpath './/p[ @class = "brown_text" ]/a' do |a|
assert_equal user.login, a.text # <-- native <code>REXML::Element#text</code> method
assert_match /\/my_name$/, a[:href] # <-- attribute generated by +assert_xpath+
end
assert_equal "ring_#{ring.id}", table.id! # <-- attribute generated by +assert_xpath+, escaped with !
end
def test_assert_xpath assert_xml '<bob><marley walkin="like he talk it" /></bob>' assert_xpath :bob assert_equal 'like he talk it', assert_xpath(:marley)[:walkin] assert_xpath :marley do deny_xpath :bob, 'we should not see <bob>, because he\'s above us' assert_xpath '/bob', 'REXML can see <bob> because / re-roots the search' unless @use_hpricot end assert_xpath '/bob/marley' assert_xpath 'bob/marley' bob = assert_xpath(:bob) that = self block_was_called = false bob.marley do that.assert_equal 'like he talk it', @walkin # <-- an attribute block_was_called = true true end assert block_was_called, 'child-methods should call their blocks' end
See: AssertXPathSuite#test_indent_xml, XPath Checker
# File lib/assert_xpath.rb, line 461 461: def assert_xpath(xpath, diagnostic = nil, &block) 462: # return assert_any_xpath(xpath, diagnostic) { 463: # block.call(@xdoc) if block 464: # true 465: # } 466: stash_xdoc do 467: xpath = symbol_to_xpath(xpath) 468: node = @xdoc.search(xpath).first 469: @xdoc = node || flunk_xpath(diagnostic, "should find xpath <#{_esc xpath}>") 470: @xdoc = _bequeath_attributes(@xdoc) 471: block.call(@xdoc) if block # ERGO tribute here? 472: return @xdoc 473: end 474: end
deny_any_xpath (xpath, matcher, diagnostic = nil)
Negates assert_any_xpath. Depends on assert_xml
- xpath - a query string describing a path among XML nodes. This must succeed - use deny_xpath for simple queries that must fail
- matcher - optional Regular Expression to test node contents. If xpath locates multiple nodes, this pattern must fail to match each node to pass the assertion.
- diagnostic - optional string to add to failure message
Contrived example:
assert_xml '<heathrow><terminal>5</terminal><lean>methods</lean></heathrow>'
assert_raise_message Test::Unit::AssertionFailedError,
/all xpath.*\.\/\/lean.*not have.*methods/ do
deny_any_xpath :lean, /methods/
end
deny_any_xpath :lean, /denver/
See: AssertXPathSuite#test_deny_any_xpath, assert_raise (on Ruby) - Don't Just Say "No"
# File lib/assert_xpath.rb, line 573 573: def deny_any_xpath(xpath, matcher, diagnostic = nil) 574: @xdoc or assert_xml 575: xpath = symbol_to_xpath(xpath) 576: 577: assert_any_xpath xpath, nil, diagnostic do |node| 578: if node.inner_text =~ matcher 579: flunk_xpath( 580: diagnostic, 581: "all xpath <#{_esc xpath}> nodes should not have pattern <?>", 582: matcher 583: ) 584: end 585: end 586: end
deny_tag_id (tag, id, diagnostic = nil)
Negates assert_tag_id. Depends on assert_xml
Example - see: assert_xml
See: assert_tag_id
# File lib/assert_xpath.rb, line 621 621: def deny_tag_id(tag, id, diagnostic = nil) 622: deny_xpath build_xpath(tag, id), diagnostic 623: end
deny_xpath (xpath, diagnostic = nil)
Negates assert_xpath. Depends on assert_xml
Examples:
assert_tag_id :td, :object_list do
assert_xpath "table[ position() = 1 and @id = 'object_#{object1.id}' ]"
deny_xpath "table[ position() = 2 and @id = 'object_#{object2.id}' ]"
end # find object1 is still displayed, but object2 is not in position 2
def test_deny_xpath assert_xml '<kiwi><eland/></kiwi>' deny_xpath 'koodoo' assert_raise_message AFE, /should not find.*eland/ do deny_xpath './/eland' end assert_raise_message AFE, /should not find.*eland/ do deny_xpath :eland end assert_raise_message AFE, /should not find.*eland/ do deny_xpath '//eland' end # Note: deny_xpath 'eland' will fail # with Hpricot and pass with REXML if @use_hpricot assert_raise_message AFE, /should not find.*eland/ do deny_xpath 'eland' end else deny_xpath 'eland' # REXML requires // end end
# File lib/assert_xpath.rb, line 486 486: def deny_xpath(xpath, diagnostic = nil) 487: @xdoc or assert_xml 488: xpath = symbol_to_xpath(xpath) 489: 490: @xdoc.search(xpath).first and 491: flunk_xpath(diagnostic, "should not find: <#{_esc xpath}>") 492: end
drill (&block)
ERGO document me
# File lib/assert_xpath.rb, line 124 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
indent_xml (doc = @xdoc || assert_xml)
Pretty-print a REXML::Element or Hpricot::Elem
- doc - optional element. Defaults to the current assert_xml document
returns: string with indented XML
Use this while developing a test case, to see what the current @xdoc node contains (as populated by assert_xml and manipulated by assert_xpath et al)
For example:
assert_javascript 'if(x == 42) answer_great_question();'
assert_js_if /x.*42/ do
puts indent_xml # <-- temporary statement to see what to assert next!
end
See: AssertXPathSuite#test_indent_xml
# File lib/assert_xpath.rb, line 642 642: def indent_xml(doc = @xdoc || assert_xml) 643: if doc.kind_of?(Hpricot::Elem) or doc.kind_of?(Hpricot::Doc) 644: zdoc = doc 645: doc = REXML::Document.new(doc.to_s.strip) rescue nil 646: unless doc # Hpricot didn't well-formify the HTML! 647: return zdoc.to_s # note: not indented, but good enough for error messages 648: end 649: end 650: 651: # require 'rexml/formatters/default' 652: # bar = REXML::Formatters::Pretty.new 653: # out = String.new 654: # bar.write(doc, out) 655: # return out 656: 657: return doc.to_s # FIXME reconcile with 1.8.6.111! 658: 659: x = StringIO.new 660: doc.write(x, 2) 661: return x.string # CONSIDER does REXML have a simpler way? 662: end
invoke_hpricot ()
This activates a secret variable, @use_hpricot, so subsequent assert_xml calls will use Hpricot. Use assert_hpricot to run one test case in Hpricot mode, and use invoke_hpricot, from your setup() method, to run entire suites in this mode. These test cases explore some differences between the two assertion systems:
def test_assert_long_xpath assert_xml '<anna><marie><candy><lights>' + ' <since><imp>' + ' <pulp lay="things" />' + ' </imp></since>' + '</lights></candy></marie></anna>' assert_xpath '/anna/marie/candy/lights/since/imp/pulp[ @lay = "things" ]' anna = assert_xpath('/anna') pulp = anna/:marie/:candy/:lights/:since/:imp/:pulp assert_equal pulp.first[:lay], 'things' if @use_hpricot assert_equal pulp[:lay], 'things' unless @use_hpricot assert anna.marie.candy.lights.since.imp.pulp{ @lay == 'things' } end
# File lib/assert_xpath.rb, line 330 330: def invoke_hpricot 331: @hdoc = @xdoc = nil 332: @use_hpricot = true 333: end
invoke_rexml ()
This passivates a secret variable, @use_hpricot, so subsequent assert_xml calls will use REXML. See invoke_hpricot to learn the various differences between the two systems
# File lib/assert_xpath.rb, line 339 339: def invoke_rexml 340: @hdoc = @xdoc = nil 341: @use_hpricot = false 342: end