Module: AssertXPath

Included in:
AssertJavaScript
Defined in:
lib/assert_xpath.rb,
lib/assert_xpath.rb

Defined Under Namespace

Modules: CommonXPathExtensions Classes: HpricotHelper, LibxmlHelper, RexmlHelper, XmlHelper

Constant Summary collapse

Element =

:nodoc:

::REXML::Element

Instance Method Summary collapse

Instance Method Details

#assert_any_xpath(xpath, matcher = nil, diagnostic = nil, &block) ⇒ Object

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: %transclude AssertXPathSuite#test_assert_any_xpath



462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
# File 'lib/assert_xpath.rb', line 462

def assert_any_xpath(xpath, matcher = nil, diagnostic = nil, &block)
  matcher ||= //
  block   ||= lambda{ true }
  found_any = false
  found_match = false
  xpath = symbol_to_xpath(xpath)

  stash_xdoc do
    #assert_xpath xpath, diagnostic

    if !using(:rexml?)
      @xdoc.search(xpath) do |@xdoc|
        found_any = true
        
        if @xdoc.inner_text =~ matcher
          found_match = true
          _bequeath_attributes(@xdoc)
          return @xdoc  if block.call(@xdoc)
          #  note we only exit block if block.nil? or call returns false
        end
      end
    else  # ERGO      merge!
      @xdoc.each_element(xpath) do |@xdoc|
        found_any = true
        
        if @xdoc.inner_text =~ matcher
          found_match = true
          _bequeath_attributes(@xdoc)
          return @xdoc  if block.call(@xdoc)
          #  note we only exit block if block.nil? or call returns false
        end
      end
    end
  end

  found_any or
    flunk_xpath(diagnostic, "should find xpath <#{_esc xpath}>")
    
  found_match or
    flunk_xpath(
        diagnostic, 
        "can find xpath <#{_esc xpath}> but can't find pattern <?>",
        matcher
        )
end

#assert_hpricot(*args, &block) ⇒ Object

%html <a name=‘assert_hpricot’></a>

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: %transclude AssertXPathSuite#test_assert_hpricot

See also: assert_hpricot



375
376
377
378
379
380
381
382
# File 'lib/assert_xpath.rb', line 375

def assert_hpricot(*args, &block)
  xml = args.shift || @xdoc || @response.body  ## ERGO why @xdoc??
#  ERGO  document that callseq!
  require 'hpricot'
  @xdoc = Hpricot(xml.to_s)  #  ERGO  take that to_s out of all callers
  return assert_xpath(*args, &block)  if args.length > 0
  return @xdoc
end

#assert_libxml(*args, &block) ⇒ Object



326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
# File 'lib/assert_xpath.rb', line 326

def assert_libxml(*args, &block)
  xml = args.shift || @xdoc || @response.body
  require 'xml/libxml'
  xp = XML::Parser.new()
  xhtml = xml.to_s
  
  if xhtml !~ /^\<\!DOCTYPE\b/ and xhtml !~ /\<\?xml\b/
    xhtml = _doc_type[@_favorite_flavor || :html] + "\n" + xhtml
  end  #  ERGO  document we pass HTML level into invoker

  # # FIXME blog that libxml will fully validate your ass...

  xp.string = xhtml
#  FIXME  blog we don't work with libxml-ruby 3.8.4
#   XML::Parser.default_load_external_dtd = false
  XML::Parser.default_pedantic_parser = false # FIXME optionalize that

#what? xp
  doc = xp.parse
#what? doc
#puts doc.debug_dump
  @xdoc = doc.root
#    @xdoc.namespace ||= XML::NS.new('')
  
#pp (@xdoc.root.public_methods - public_methods).sort
  return assert_xpath(*args, &block)  if args.length > 0
  return @xdoc
end

#assert_rexml(*args, &block) ⇒ Object

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_hpricot, invoke_libxml, or invoke_rexml

Example: %transclude AssertXPathSuite#test_assert_rexml



304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
# File 'lib/assert_xpath.rb', line 304

def assert_rexml(*args, &block)
  contents = (args.shift || @response.body).to_s
#  ERGO  benchmark these things

  contents.gsub!('\\\'', '&apos;')
  contents.gsub!('//<![CDATA[<![CDATA[', '')
  contents.gsub!('//<![CDATA[', '')
  contents.gsub!('//]]>', '')
  contents.gsub!('//]>', '')

  begin
    @xdoc = REXML::Document.new(contents)
  rescue REXML::ParseException => e
    raise e  unless e.message =~ /attempted adding second root element to document/
    @xdoc = REXML::Document.new("<xhtml>#{ contents }</xhtml>")
  end
  
  _bequeath_attributes(@xdoc)
  assert_xpath(*args, &block)  if args != []
  return (assert_xpath('/*') rescue nil)  if @xdoc
end

#assert_tag_id(tag, id, diagnostic = nil, &block) ⇒ Object

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

%transclude AssertXPathSuite#test_assert_tag_id_and_tidy

%transclude AssertXPathSuite#test_assert_tag_id



566
567
568
569
# File 'lib/assert_xpath.rb', line 566

def assert_tag_id(tag, id, diagnostic = nil, &block)
#  CONSIDER  upgrade assert_tag_id to use each_element_with_attribute
  assert_xpath build_xpath(tag, id), diagnostic, &block
end

#assert_tidy(messy = @response.body, verbosity = :noisy) ⇒ Object

%html <a name=‘assert_tidy’></a> 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, which reports most errors. :verbose reports all information, and 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

%transclude AssertXPathSuite#test_assert_tag_id_and_tidy



642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
# File 'lib/assert_xpath.rb', line 642

def assert_tidy(messy = @response.body, verbosity = :noisy)
  scratch_html = RAILS_ROOT + '/tmp/scratch.html'
  #  CONSIDER  a railsoid tmp file system?
  #  CONSIDER  yield to something to respond to errors?
  File.open(scratch_html, 'w'){|f|  f.write(messy)  }
  gripes = `tidy -eq #{scratch_html} 2>&1`
  gripes.split("\n")
  
  #  TODO  kvetch about client_input_channel_req: channel 0 rtype [email protected] reply 1
  
  puts gripes if verbosity == :verbose
  
  puts gripes.reject{|g|
    g =~ / - Info\: /                                  or
    g =~ /Warning\: missing \<\!DOCTYPE\> declaration/ or
    g =~ /proprietary attribute/                       or
    g =~ /lacks "(summary|alt)" attribute/
  } if verbosity == :noisy
  
  assert_xml `tidy -wrap 1001 -asxhtml #{ scratch_html } 2>/dev/null`
    #  CONSIDER  that should report serious HTML deformities
end

#assert_xml(*args, &block) ⇒ Object

%html <a name=‘assert_xml’></a>

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 call-seq:

assert_xml(xml = @response.body <em>[, assert_xpath arguments]</em>) -> @xdoc, or assert_xpath's return value

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

%transclude AssertXPathSuite#test_assert_long_sick_expression See: AssertXPathSuite#test_assert_xml_drill



291
292
293
294
# File 'lib/assert_xpath.rb', line 291

def assert_xml(*args, &block)
  using :libxml? # prop-ulates @helper
  return @helper.assert_xml(self, *args, &block)
end

#assert_xpath(xpath, diagnostic = nil, &block) ⇒ Object

%html <a name=‘assert_xpath’></a>

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.,   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

%transclude AssertXPathSuite#test_assert_xpath

See: AssertXPathSuite#test_indent_xml, XPath Checker



415
416
417
418
419
420
421
422
423
424
425
426
427
428
# File 'lib/assert_xpath.rb', line 415

def assert_xpath(xpath, diagnostic = nil, &block)
#     return assert_any_xpath(xpath, diagnostic) {
#              block.call(@xdoc) if block
#              true
#            }
  stash_xdoc do
    xpath = symbol_to_xpath(xpath)
    node  = @xdoc.search(xpath).first
    @xdoc = node || flunk_xpath(diagnostic, "should find xpath <#{_esc xpath}>")
    @xdoc = _bequeath_attributes(@xdoc)
    block.call(@xdoc) if block  #  ERGO  tribute here?
    return @xdoc
  end
end

#deny_any_xpath(xpath, matcher, diagnostic = nil) ⇒ Object

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”



529
530
531
532
533
534
535
536
537
538
539
540
541
542
# File 'lib/assert_xpath.rb', line 529

def deny_any_xpath(xpath, matcher, diagnostic = nil)
  @xdoc or assert_xml
  xpath = symbol_to_xpath(xpath)

  assert_any_xpath xpath, nil, diagnostic do |node|
    if node.inner_text =~ matcher
      flunk_xpath(
          diagnostic, 
          "all xpath <#{_esc xpath}> nodes should not have pattern <?>",
          matcher
          )
    end
  end
end

#deny_tag_id(tag, id, diagnostic = nil) ⇒ Object

Negates assert_tag_id. Depends on assert_xml

Example - see: assert_xml

See: assert_tag_id



577
578
579
# File 'lib/assert_xpath.rb', line 577

def deny_tag_id(tag, id, diagnostic = nil)
  deny_xpath build_xpath(tag, id), diagnostic
end

#deny_xpath(xpath, diagnostic = nil) ⇒ Object

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

%transclude AssertXPathSuite#test_deny_xpath



442
443
444
445
446
447
448
# File 'lib/assert_xpath.rb', line 442

def deny_xpath(xpath, diagnostic = nil)
  @xdoc or assert_xml
  xpath = symbol_to_xpath(xpath)

  @xdoc.search(xpath).first and
    flunk_xpath(diagnostic, "should not find: <#{_esc xpath}>")
end

#drill(&block) ⇒ Object

ERGO document me



124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
# File 'lib/assert_xpath.rb', line 124

def drill(&block)
  if block
        #  ERGO  harmonize with bang! version
      #  ERGO  deal if the key ain't a valid variable
    
    unless tribute(block)  #  ERGO  pass in self (node)?
      sib = self
      nil while (sib = sib.next_sibling) and sib.node_type != :element
p sib #  ERGO  do tests ever get here?
      q = sib and _bequeath_attributes(sib).drill(&block)
      return sib  if q
      raise Test::Unit::AssertionFailedError.new("can't find beyond <#{xpath}>")
    end
  end
  
  return self
  #  ERGO  if block returns false/nil, find siblings until it passes.
  #        throw a test failure if it don't.
  #  ERGO  axis concept
end

#indent_xml(doc = @xdoc || assert_xml) ⇒ Object

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



598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
# File 'lib/assert_xpath.rb', line 598

def indent_xml(doc = @xdoc || assert_xml)
  if doc.kind_of?(Hpricot::Elem) or doc.kind_of?(Hpricot::Doc)
    zdoc = doc
    doc = REXML::Document.new(doc.to_s.strip) rescue nil
    unless doc  #  Hpricot didn't well-formify the HTML!
      return zdoc.to_s  #  note: not indented, but good enough for error messages
    end
  end

# require 'rexml/formatters/default'
# bar = REXML::Formatters::Pretty.new
# out = String.new
# bar.write(doc, out)
# return out

  return doc.to_s  #  ERGO  reconcile with 1.8.6.111!

  x = StringIO.new
  doc.write(x, 2)
  return x.string  #  CONSIDER  does REXML have a simpler way?
end

#invoke_hpricotObject

Subsequent assert_xml calls will use Hpricot. (Alternately, assert_hpricot will run one assertion in Hpricot mode.) Put invoke_hpricot into setup() method, to run entire suites in this mode. These test cases explore some differences between the two assertion systems: %transclude AssertXPathSuite#test_assert_long_xpath



222
223
224
225
# File 'lib/assert_xpath.rb', line 222

def invoke_hpricot
  @xdoc = nil
  @helper = HpricotHelper.new
end

#invoke_libxml(favorite_flavor = :html) ⇒ Object

Subsequent assert_xml calls will use LibXML. (Alternately, assert_libxml will run one assertion in Hpricot mode.) Put invoke_libxml into setup() method, to run entire suites in this mode.



234
235
236
237
238
# File 'lib/assert_xpath.rb', line 234

def invoke_libxml(favorite_flavor = :html)
  @_favorite_flavor = favorite_flavor    
  @xdoc   = nil
  @helper = LibxmlHelper.new
end

#invoke_rexmlObject

Subsequent assert_xml calls will use REXML. See invoke_hpricot to learn the various differences between the two systems



257
258
259
260
# File 'lib/assert_xpath.rb', line 257

def invoke_rexml
  @xdoc   = nil
  @helper = RexmlHelper.new
end

#using(kode) ⇒ Object



355
356
357
358
# File 'lib/assert_xpath.rb', line 355

def using(kode)
  @helper ||= RexmlHelper.new  #  ERGO  escallate this!
  return @helper.send(kode)
end