Switch branches/tags
Nothing to show
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Failed to load latest commit information.


The assert{ 2.0 } project asks a simple question:

  How the leanest possible assertions 
  can yield the maximum diagnostics?

Its latest assertion, assert_xhtml, answers this question for HTML.

To begin, grab it with:

  gem install nokogiri assert2

==require 'assert2/xhtml'==

All assert{ 2.0 } dependencies are optional. If you have Nokogiri 
(>=1.2.2), you can test Rails views like this:

     user = users(:Moses)
     get :edit_user, :id =>  user.id

     assert_xhtml do

       form :action =>  '/users' do
         fieldset do
           legend 'Personal Information'
           label 'First name'
           input :type =>  'text',
                 :name =>  'user[first_name]'
                 :value =>  user.first_name


That's a Rails functional test on a form. The assertion expects the form
to target the given action, and contain a fieldset, a legend, a label, and 
a populated text input field. The assertion forgives any other details, 
such as intervening structural tags, excess spaces, or extra attributes; 
and complains if any required detail is missing, out of order, or ill-formed.

The DSL inside that block is Nokogiri::HTML::Builder notation. Generally
speaking, anything Nokogiri can build, you can specify.


Call assert_xhtml(my_xml){} to interrogate your XML. When called without
an argument, the method reads @response.body.


Every assert* has a matching deny* method. assert_xhtml recognizes the
special element without! as a request to fail if the given elements
do indeed appear in your output:

    get :info, :record_id => record.id
    assert_xhtml do
      div :class => :content do
        without!{ div :class => :download }

That assertion will fail if the outer <div class='content'> tag does not
exist, or if any inner <div class='download'> does exist.

The without! element respects your document layout. This assertion 

    assert_xhtml SAMPLE_LIST do
      ul{ li{ ul{ li 'Sales report'
          without!{ li 'All Sales report criteria' } } } }

...even though the target document contains an <li>All Sales report

    <ul style='font-size: 18'>
          <li>Billings report</li>
          <li>Sales report</li>
          <li>Billings criteria</li>
          <li>Common system</li>
          <li>All Sales report criteria</li>
          <li>All Billings reports</li>

The two <li> elements appear in different <ul> lists, so the assertion 
does not associate them.

The committee does not yet know what without!{ without!{} } does, so please
do not rely on its current behavior, whatever that is!


Certain elements, such as <select> and <id>, have the same names as internal
methods. If you experience a bizarre error message, such as "wrong argument 
type Hash (expected Array)", add a ! to the end of the element, like this:

    assert_xhtml do
      h2 'Sites'
      select! :id => 'sites',
              :name => 'sites[]',
              :multiple => :multiple,
              :size => SaleController::LIST_SIZE


An element such as h3{ 'text' } will match <h3> text </h3>, with leading and
trailing blanks, but it won't match <h3><span>text</span></h3>. This rule
prevents runaway matches between high- and low-level elements.

A text specification may be a /regexp/, and an element may contain both text 
and attributes:

  h2 /Sites/, :style => /right/

The next section illustrates the text() directive.


assert_xhtml works by throwing away structural information. If your document
structure is more unruly and wild, it might need more constraints. Use an :xpath!
attribute to apply raw XPath specifications to your target elements.

This assertion detect the rather pedestrian fact that your <title>
element remains inside your <html><head> block - and it did not
escape and rampage off to somewhere else:

    assert_xhtml do
      title :xpath! => 'parent::head/parent::html' do
        text 'Chamber of Commerce - Info - Hope Orphanage'

(That code also shows the 'text' directive, inserting text contents directly
into the enclosing element. Sometimes the directive is the most convenient
way to specify the content.)

An :xpath! of a number evaluates to the 1-based index of an item in its
parent. This assertion forces list items to appear in the correct order:

    assert_xhtml do
      ul :style => 'font-size: 18' do
        li 'model' do
          li(:xpath! => 1){ text 'Sales report'  }
          li(:xpath! => 2){ text 'Billings report' }
          li(:xpath! => 3){ text 'Billings criteria' }

===:verbose! => true===

Sometimes when an assertion fails, you can't tell why. To see each 
context the assertion considers, add :verbose! => true to the lowest 
element you know works, and run the tests:

    assert_xhtml SAMPLE_FORM do
      fieldset do
        li :verbose! => true do
          label 'First name', :for => :user_first_name

The verbose option works as "spew", not as a diagnostic, and it reports 
each considered element's contents.

Because XPath evaluates the <label>, in our example, before the <li>, you
might need to comment the <label> out to see a successful spew on the <li>.


assert_xhtml{} yields its block to Nokogiri::HTML::Builder, which turns
every method call into an HTML element. This freedom comes at a price -
you can't easily call your own methods!

Use this scope trick to pass your outer scope into the specification:

     get :edit_user, :id => users(:Moses).id
     scope = self

     assert_xhtml do
       form :action => '/users' do
         input :value => scope.users(:Moses).first_name

Notice we could improve that test by declaring a variable,
user = users(:Moses), in the outer scope, and simply passing
the user variable itself into the specification.


The :class attribute is magic. This assertion passes...

    assert_xhtml SAMPLE_LIST do
      ul :class => :kalika do
        li 'Billings report'

...despite the actual HTML contains <ul class='kalika goddess'>. This feature
simulates the CSS Selector notation that matches classes by their cascading 

===class & ID shortcuts===

Nokogiri expands div.rad.thing! to <div class='rad' id='thing'/>. That 
means you don't need to write div :class => 'rad', :id => 'thing' (or 
ul :class => :kalika). You can then put other arguments after the shortcut,
and the <div> in our example receives them, too.

===diagnostic message===

When this assertion fails, it prints out...

 - your reference elements, rendered as HTML
 - the best "near-miss" region of your sample HTML

Note that the diagnostic displays without! and xpath! tags literally.

If your assertion fails, and the diagnostic does not display a relevant
near-miss, the best recourse is to wrap your target element with a known
container element. Don't do this:

  assert_xhtml do
    li 'my one lonely list item'

Do this:

  assert_xhtml do
    ul.my_unordered_list! do
      li 'my one lonely list item'

That is guaranteed to match the first <ul id='my_unordered_list'> in your


The matching "specification", in RSpec language, is be_html_with{}.
Its syntax and behavior are the same:

  it 'should have a cute form' do
    render '/users/new'

    response.body.should be_html_with{
      form :action => '/users' do
        fieldset do
          legend 'Personal Information'
          label 'First nome'
          input :type => 'text', :name => 'user[first_name]'

Good hunting!