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
Apparently Analagous 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