Phlip
2009-Mar-26 12:53 UTC
[rspec-users] [ANN] assert2-0.4.6 provides assert_xhtml, an alternative to assert_select
Rubyists:
Consider the following monstrosity, coded using assert_select:
assert_select "div#logo_box img[src=/0000/0001/logo.gif][alt=My
Company]"
Now, behold it rewritten to use assert_xhtml:
assert_xhtml do
div.logo_box! do
img :src => /logo.gif$/, :alt => ''My Company''
end
end
That sample contains more Ruby; it''s not just one big string.
Still not convinced? Oh, I forgot the <a> around the <img>! Try
this:
assert_xhtml do
div.logo_box! do
a :href => ''/'' do
img :src => /logo.gif$/, :alt => ''My Company''
end
end
end
And we had an issue with the small logo sneaking into the logo_box once.
Let''s
exclude it:
assert_xhtml do
div.logo_box do
a :href => ''/'' do
img :src => /logo.gif$/, :alt => ''My Company''
end
without!{ img :src => /mini_logo.gif/ }
end
end
Imagine adding all that to assert_select - it would get much harder to read, and
more complex. assert_xhtml uses Nokogiri::HTML::Builder notation, so anything it
can build, you can query.
Version 0.4.6 adds all the following features. To begin, enter:
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
end
end
end
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.
=== arguments ==
Call assert_xhtml(my_xml){} to interrogate your XML. When called without
an argument, the method reads @response.body.
=== without! ==
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 }
end
end
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
passes...
assert_xhtml SAMPLE_LIST do
ul{ li{ ul{ li ''Sales report''
without!{ li ''All Sales report criteria'' } } } }
end
...even though the target document contains an <li>All Sales report
criteria</li>:
<ul style=''font-size: 18''>
<li>model
<ul>
<li>Billings report</li>
<li>Sales report</li>
<li>Billings criteria</li>
<li>Common system</li>
</ul>
</li>
<li>controller
<ul>
<li>All Sales report criteria</li>
<li>All Billings reports</li>
</ul>
</li>
</ul>
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!
=== escapes ==
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
end
=== text ==
An element such as h3{ ''text'' } will match <h3> text
</h3>, with leading or
trailing blanks, but it won''t match
<h3><span>text</span></h3>. This rule
prevents runaway matches between high- and low-level elements.
The example for the next section illustrates how to mix text and attribute
specifications on the same element.
A text specification may be a /regexp/.
=== :xpath!=> ==
assert_xhtml works by throwing away structural information. If you need
more control over your structure, 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''
end
end
Note the XPath evaluates as a predicate of the target <title>, so its
parent
axis lists the familiar elements in reverse.
That code also shows the ''text'' directive, inserting text
contents directly
into the enclosing element. A future version of Nokogiri will allow the
element''s first argument to specify its text.
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'' }
end
end
end
=== :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
end
end
end
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>.
=== scope ==
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
end
end
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.
=== :class=> ==
The :class attribute is magic. This assertion passes...
assert_xhtml SAMPLE_LIST do
ul :class => :kalika do
li ''Billings report''
end
end
...despite the actual HTML contains <ul class=''kalika
goddess''>. This feature
simulates the CSS Selector notation that matches classes by their cascading
effects.
=== 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 attempts to print out...
- your reference elements, rendered as HTML
- each "near-miss" region of your sample HTML
The next version will feature much better diagnostics. Until they work, if these
diagnostics are not sufficient, put :verbose! on the lowest element you think
works, and comment out its contents...
=== RSpec ==
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]''
end
end
}
end
Good hunting!
--
Phlip
Maybe Matching Threads
- assert_xhtml - test your HTML by example
- DEPRECATION WARNING: Passing a template handler in the template name is deprecated. (rspec + haml)
- assert_select alternative
- [Security] Loofah has an HTML injection / XSS vulnerability, please upgrade to 0.4.6
- [LLVMdev] Disabling Verifier
