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



491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
# File 'lib/assert_xpath.rb', line 491

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



400
401
402
403
404
405
406
407
# File 'lib/assert_xpath.rb', line 400

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

ERGO more documentation!



340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
# File 'lib/assert_xpath.rb', line 340

def assert_libxml(*args, &block)
  xml = args.shift || @xdoc || @response.body
  xhtml = xml.to_s

    #  CONSIDER  fix this like at the source??
  xhtml.gsub!('<![CDATA[>', '')
  xhtml.gsub!('<![CDATA[', '')
  xhtml.gsub!('//]]]]>', '')
  xhtml.gsub!(']]>', '')

  if xhtml !~ /^\<\!DOCTYPE\b/ and xhtml !~ /\<\?xml\b/
    xhtml = _doc_type[@_favorite_flavor || :html] + "\n" + xhtml if _doc_type[@_favorite_flavor]
  end  #  ERGO  document we pass HTML level into invoker

  if xhtml.index('<?xml version="1" standalone="yes"?>') == 0
    xhtml.gsub!('<?xml version="1" standalone="yes"?>', '')
    xhtml.strip!  #  ERGO  what is libxml's problem with that line???
  end

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

  xp = xhtml =~ /\<\!DOCTYPE/ ? XML::HTMLParser.new() : XML::Parser.new()
  xhtml = '<xml/>' unless xhtml.any?
  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



307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
# File 'lib/assert_xpath.rb', line 307

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!('//]>', '')
  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, diagnostic2 = 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, symbol, or hash identifying the node. ids 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



597
598
599
600
601
# File 'lib/assert_xpath.rb', line 597

def assert_tag_id(tag, id = {}, diagnostic = nil, diagnostic2 = nil, &block)
  # if id is not a hash, diagnostic might be a hash too!
#  CONSIDER  upgrade assert_tag_id to use each_element_with_attribute
  assert_xpath build_xpath(tag, id, diagnostic), diagnostic2 || 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



674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
# File 'lib/assert_xpath.rb', line 674

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



294
295
296
297
# File 'lib/assert_xpath.rb', line 294

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

#assert_xpath(*args, &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



440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
# File 'lib/assert_xpath.rb', line 440

def assert_xpath(*args, &block)
  return assert_tag_id(*args, &block) if args.length > 1 and args[1].kind_of?(Symbol) or args[1].kind_of?(Hash)
#     return assert_any_xpath(xpath, diagnostic) {
#              block.call(@xdoc) if block
#              true
#            }
  xpath, diagnostic = args
  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”



558
559
560
561
562
563
564
565
566
567
568
569
570
571
# File 'lib/assert_xpath.rb', line 558

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, diagnostic2 = nil) ⇒ Object

Negates assert_tag_id. Depends on assert_xml

Example - see: assert_xml

See: assert_tag_id



609
610
611
# File 'lib/assert_xpath.rb', line 609

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

#deny_xpath(*args) ⇒ 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



469
470
471
472
473
474
475
476
477
# File 'lib/assert_xpath.rb', line 469

def deny_xpath(*args)
  return deny_tag_id(*args) if args.length > 1 and args[1].kind_of?(Symbol) or args[1].kind_of?(Hash)
  xpath, diagnostic = args
  @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



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

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



630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
# File 'lib/assert_xpath.rb', line 630

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



225
226
227
228
# File 'lib/assert_xpath.rb', line 225

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.



237
238
239
240
241
# File 'lib/assert_xpath.rb', line 237

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



260
261
262
263
# File 'lib/assert_xpath.rb', line 260

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

#using(kode) ⇒ Object



380
381
382
383
# File 'lib/assert_xpath.rb', line 380

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

#validate_as(type) ⇒ Object

Temporarily sets the validation type to :xml, :html, or :xhtml



333
334
335
336
337
338
# File 'lib/assert_xpath.rb', line 333

def validate_as(type) # FIXME use or lose this
  @_favorite_flavor, formerly = type, @_favorite_flavor
  yield
ensure
  @_favorite_flavor = type
end