[rspec-users] Ordering in view specs using have_tag and with_tag

Wincent Colaiuta win at wincent.com
Tue May 22 07:48:42 EDT 2007

El 22/5/2007, a las 2:27, David Chelimsky escribió:

> You don't have to know too much about Rails internals. assert_select
> is nicely decoupled from most of it. You will have to put on your
> regexp fu.
> Take a look at actionpack/test/controller/selector_assertions.rb and
> actionpack/lib/action_controller/assertions/selector_assertions.rb.

Ok, I've had a look at the both the Rails code in actionpack/lib/ 
action_controller/assertions/selector_assertions.rb and the RSpec  
implementations of have_tag and with_tag in lib/spec/rails/matchers/ 
assert_select.rb (actionpack/test/controller/selector_assertions.rb  
doesn't exist in either Rails 1.2.3 or trunk; did you mean actionpack/ 

I don't necessarily have it fully grokked yet, but my observations of  
the RSpec side of things thus far are:

1. Each time you invoke "have_tag" you create a new AssertSelect  
instance under the covers

2. "with_tag" invokes "have_tag" behind the scenes, so that means a  
new AssertSelect instance gets created there two

3. I presume the method that ends up getting called when you do  
"should have_tag" is AssertSelect#matches?

4. If and only if the response text is a String,  
AssertSelect#matches? creates a new HTML::Document instance from it  
and prepends it to the arguments list

5. Finally, AssertSelect#matches? sends an "assert_select" message to  
the @spec_scope (which is really "self", ie. the matcher).

So that's the way things are by the time Rails receives the message.  
Observations on the Rails side:

1. It appears that assert_select and friends are included into the  
object being tested via actionpack/lib/action_controller/assertions.rb.

2. assert_select uses an instance variable, @selected, to allow  
nesting of assert_select; so perhaps using instance variables may be  
the way to preserve state between assert_select calls and track the  
ordering of the elements.

The only problem is, as I said I haven't fully grokked this yet so I  
am not sure what the lifetime of the objects in question are and how  
long the instance variables will stick around. It seems that a new  
AssertSelect instance is created for every assertion, every assertion  
has its own matcher, and the matcher receives the assert_select  
message, so I am not sure how to achieve the kind of inter-matcher or  
inter-assertion dependency which would allow ordering comparisons to  
be made.

In other words, I don't know if this can be tackled purely from the  
Rails side of things. One thing which may prove useful is that  
assert_select returns an array of matches, and also passes that array  
into any block (if called with a block). The array contains  
HTML::Node instances, and these nodes have a position attribute (the  
position of the node within the byte stream); so making comparisons  
of order externally is possible and could be down already right now  
with no changes (although not transparently; you'd have to explicitly  
compare element positions in your specs).

There are two more questions that grow out of this:

1. What would be the ideal "scope" for these order restrictions to  
take effect? Should multiple have_tag calls within the same example  
(the same "it" block) be expected to appear in order? Or should it  
apply to have_tags calls within the same context (the same "describe"  
block)? Should calling "render" again have the effect of "resetting"  

2. If this can be implemented, should it apply as a default (might  
break existing specs), or should it be made optional (pass an  
additional parameter to indicate that ordering is important?); an  
alternative would be to add two additional methods, have_tags and  
with_tags, which would expect an array of selectors rather than a  
single selector, and would expect them to succeed in order (I almost  
think that this is the best solution).

Any ideas?


More information about the rspec-users mailing list