RSpecsters:

I like nested XPath expressions, because the outer XPath assertion can clip the diagnostic of the inner XPath assertion. You don't get the whole page spewed into your face when you fault!

This specification, for example, tests Yury Kotlyarov's user login page, from:

  http://github.com/yura/howto-rspec-custom-matchers/tree/master

  it 'should have xpathic tags' do
    render '/users/new'

    response.body.should be_xml_with{
      xpath :form, :action => '/users' do
        xpath :fieldset do
          xpath :'legend[ contains(., "Personal Information") ]' and
          xpath :'label[ contains(., "First name") ]' and
          xpath :input, :type => 'text', :name => 'user[first_name]'
        end
      end
    }
  end

That tests this (otherwise innocuous) new.html.erb:

<form action="/users">
  <fieldset>
    <legend>Personal Information</legend>
    <ol>
      <li id="control_user_first_name">
        <label for="user_first_name">First name</label>
        <input type="text" name="user[first_name]" id="user_first_name" />
      </li>
    </ol>
  </fieldset>
</form>

If that code had any major complex <%= erb %> activity, the tests^W specifications would keep it honest.

Let's change the specification, to simulate a bug, and try it:

    xpath :input, :type => 'text', :name => 'user[first_nome]' # was _name

That provides this mind-blast of errata:

'/users/new should have xpathic tags' FAILED
xpath: "descendant-or-self::inp...@type = $type and @name = $name]"
arguments: {"name"=>"user[first_nome]", "type"=>"text"}

xml context:

<fieldset>
  <legend>
    Personal Information
  </legend>
  <ol>
    <li id='control_user_first_name'>
      <label for='user_first_name'>
        First name
      </label>
      <input name='user[first_name]' type='text' id='user_first_name'/>
    </li>
  </ol>
</fieldset>

assert{ ( ( xpath(:"legend[ contains(., \"Personal Information\") ]") ) and
     ( ( ( xpath(:"label[ contains(., \"First name\") ]") ) and
    ( xpath(:input, { :type => "text", :name => "user[first_nome]" }) ) ) ) ) }
--> nil - should pass

xpath(:"legend[ contains(., \"Personal Information\") ]")
  --> <legend> ... </>

xpath(:"label[ contains(., \"First name\") ]")
  --> <label for='user_first_name'> ... </>

xpath(:input, { :type => "text", :name => "user[first_nome]" })
  --> nil

./spec/views/users/new.html.erb_spec.rb:63:
script/spec:5:

Finished in 0.116823 seconds

2 examples, 1 failure

Note that the error message restricted itself to the XHTML inside the <form> tag. This is a major benefit when diagnosing a huge page that failed. (But also note that your HTML, like your code, should come in small reusable snippets, such as partials, and that these should get tested directly!)

Soon I will upgrade this system to use Nokogiri instead of (>cough<) REXML.

Now, while I go and put the "simple" matcher that does this onto Twitter, YouTube, Mingle, Facebook, Mindfuck, Reddit, Tumblog, LinkedIn, and Gist, you all can just read it below my sig.

--
  Phlip
  http://www.zeroplayer.com/

require File.dirname(__FILE__) + "/../../spec_helper"
require 'assert2/xpath'
require 'spec/matchers/wrap_expectation'

Spec::Runner.configure do |c|
  c.include Test::Unit::Assertions
end  #  TODO blog this

describe "/users/new" do

  it "should have user form" do
    render '/users/new'
    response.should have_form('/users') do
      with_field_set 'Personal Information' do
        with_text_field 'First name', 'user', 'first_name'
      end
    end
  end

  class BeXmlWith

    def initialize(scope, &block)
      @scope, @block = scope, block
    end

    def matches?(stwing, &block)
      waz_xdoc = @xdoc

      @scope.wrap_expectation self do
        @scope.assert_xhtml stwing
        return (block || @block || proc{}).call
      end
    ensure
      @xdoc = waz_xdoc
    end

    attr_accessor :failure_message

    def negative_failure_message
      "yack yack yack"
    end
  end

  def be_xml_with(&block)
    BeXmlWith.new(self, &block)
  end

  def be_xml_with_(&block)
    waz_xdoc = @xdoc
    simple_matcher 'yo' do |given, matcher|
      wrap_expectation matcher do
        assert_xhtml given  #  this works
        block.call  #  crashes with a nil.first error!
      end
    end
  ensure
    @xdoc = waz_xdoc
  end

  it 'should have xpathic tags' do
    render '/users/new'

    response.body.should be_xml_with{
      xpath :form, :action => '/users' do
        xpath :fieldset do
          xpath :'legend[ contains(., "Personal Information") ]' and
          xpath :'label[ contains(., "First name") ]' and
          xpath :input, :type => 'text', :name => 'user[first_name]'
        end
      end
    }
  end

end

_______________________________________________
rspec-users mailing list
rspec-users@rubyforge.org
http://rubyforge.org/mailman/listinfo/rspec-users

Reply via email to