have_selector should accept a block #387

Closed
dwilkie opened this Issue Jun 2, 2011 · 7 comments

3 participants

@dwilkie

I have the following view which I can't spec out properly in a view spec:

file: "products/index.html.haml"

#products
  = render @products

And this is my view spec:

require 'spec_helper'

describe "products/index.html.haml" do
  let(:products) {[mock_model(Product)]}

  before do
    stub_template "products/product.html.haml" => "" 
    render
  end

  it "should render the products" do
    rendered.should have_selector(#products) do
    rendered.should render_template products
  end
end

The problem here is that have_selector does not accept a block so there is no way to assert that the partial is rendered inside the #products div. Since Capybara matchers don't work within View specs you cannot use within either.

@dchelimsky
RSpec member

have_selector is not an rspec matcher: it comes from either webrat or capybara. Capybara matchers do work, btw, in view specs as of capybara 1.0.0.beta1 and rspec-rails 2.6.

There's no support for specifying that a template is rendered within an html element - those are two different concerns. Capy supports arbitrarily complex selectors, however, so you could do this (assuming the #product-37 div was in the products template):

rendered.should have_selector("#products #product-37")
@dchelimsky dchelimsky closed this Jun 2, 2011
@dwilkie

Thanks David. I'm running capybara 1.0.0.beta1 and rspec-rails 2.6, how can I enable Capybara matchers in the view Specs? FYI I tried something like this:

      within "#products" do
        rendered.should render_template products
      end

and got:

 NoMethodError:
   undefined method `within' for #<RSpec::Core::ExampleGroup::Nested_1::Nested_1::Nested_1::Nested_1:0xab2b86c>
@dchelimsky
RSpec member

within is not supported in view specs, but this is something you'll need to report to the capybara tracker - i'll be glad to work w/ Jonas to make it happen if he wants to, but view specs are a very different animal for capybara and I'm (personally) happy just to have the matchers at all.

Again - rendered.should render_template(products) inside any sort of css selector is probably not gonna happen.

@dwilkie

Feel free to disagree with me here but something seems fundamentally wrong here.

When writing request specs, Integration tests or Cucumber features for acceptance testing the developer should not have to worry about the semantics of the markup.

For example, the developer should not have to worry that #product_37 was rendered within #products within a request spec or integration test and yet currently the only way you can assert this in Rspec is within a request spec using, for example, Capybara's within method. Although as you suggested you could use something like rendered.should have_selector("#products #product_37"), this breaks the advantage of isolating your view specs when you do something like render @products and have a partial: _product.html.erb (a common Rails practice).

Some may argue that specifying the semantics of the markup is not good practice but I believe in some cases it is important. For example for jQuery purposes you might want to ensure that #products_37 was rendered within #products because your javascript hooks into #products #div. For me, this kind of assertion definitely belongs in a view spec and it seems others agree (see for example: http://blog.carbonfive.com/2011/03/02/a-look-at-specifying-views-in-rspec/). Test-unit allows your to do this with assert_select but Rspec does not.

Maybe it would be good to bring jnicklas into this discussion to see what he thinks.

@dmonopoly

Hey, I've been going through the RSpec book, but instead of following with Webrat as it suggests, I tried Capybara.

On ~p333, I came across a problem: Capybara's have_selector matcher does not support nesting.

My code was this (from the book):

    require 'spec_helper'
    describe "messages/new.html.erb" do
      let(:message) do
        mock_model("Message").as_new_record.as_null_object
      end
      before do
        assign :message, message
      end
      it "renders a form to create a message" do
        render
        rendered.should have_selector("form",
          :method => "post",
          :action => messages_path
        ) do |form|
          form.should have_selector("input", :type => "submit") # this should fail because no submit button is in the view, but it silently passes
        end
      end

This doesn't work with Capybara, but would work correctly if with Webrat. In any case, shouldn't Capybara support nesting just like Webrat does?
And I'm wondering about the workaround for this with Capybara...

@dchelimsky
RSpec member

@dmonopoly - submit an issue in the Capybara tracker if you think Capybara should support this. As for a workaround:

rendered.should have_selector("form input[@type='submit']")
@dwilkie

@dchelimsky - submitted issue to Capybara: jnicklas/capybara#390

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment