Wednesday, June 10, 2009

RSpec Between Meals

‹prev | My Chain | next›

First up tonight, I need to link to more human readable text when navigating between meals. So I add the following RSpec example to drive this:
  it "should link to easily human readable previous meal" do
render("/views/meal.haml")
response.should have_selector("a", :content => "May 15, 2009")
end
I can make this example pass with some help from Date's strftime:
%div.navigation

=link_to_adjacent_view_date(@meal['date'], @meals_by_date, :previous => true) { |d| Date.parse(d).strftime("%B %e, %Y") }
|
=link_to_adjacent_view_date(@meal['date'], @meals_by_date) { |d| d.strftime("%B %e, %Y") }
(my link_to_adjacent_view_date takes an optional block argument that produces the link text).

With that I have reached the refactor portion of the red-green-refactor cycle. I target these two examples for refactoring:
  it "should link to previous meal" do
render("/views/meal.haml")
response.should have_selector("a", :href => "/meals/2009/05/15")
end

it "should link to easily human readable previous meal" do
render("/views/meal.haml")
response.should have_selector("a", :content => "May 15, 2009")
end
I can combine those into a single, easier to read spec:
  it "should link to previous meal" do
render("/views/meal.haml")
response.should have_selector("a",
:href => "/meals/2009/05/15",
:content => "May 15, 2009")
end
So, is time to move back out to the Cucumber scenario? Not quite, the current failing step is trying to click on the meal name, not the date as I have just implemented.

The difficulty with this is that the link_to_adjacent_view_date helper was implemented to yield just the date for presentation. In this case, I need the title as well. That information is in the CouchDB view from last night, which has data that looks like:
{"total_rows":7,"offset":0,"rows":[
{"id":"2002-04-21","key":"2002/04/21","value":["2002-04-21","Sausage Vegetable Grits"]},
{"id":"2002-04-23","key":"2002/04/23","value":["2002-04-23","Black Bean Chili and Fried Grits"]},
{"id":"2002-07-06","key":"2002/07/06","value":["2002-07-06","Mac 'n' Cheese"]},
{"id":"2003-02-09","key":"2003/02/09","value":["2003-02-09","What To Do with Leftover Bubba's"]},
{"id":"2003-05-26","key":"2003/05/26","value":["2003-05-26","Biscuits and Gravy"]},
{"id":"2003-09-24","key":"2003/09/24","value":["2003-09-24","Almost French Onion Soup"]},
{"id":"2006-05-03","key":"2006/05/03","value":["2006-05-03","The Bestest Dinner Ever"]}
]}
The key is the date of meal, the value is a two element array of the id and title. So, yielding both key and value ought to allow me to retain the current date linking behavior (with some modifications), but also support the title linking behavior.

Before I implement this, I have to ask, do I really need the meal text? I already have navigation between meals working—the user can click on the date link. The user, eh? Thinking back to the feature that is driving this inside work, the user is described as "a person interested in exploring meals and how they drive certain recipes". I think such a user is much more likely to be engaged in exploration if the meal titles are shown. It is still worthwhile keeping the dates—if nothing else, they provide a clue to the user as to how the between meal links work (i.e. they are date ordered).

Phew! Confident that I am justified in adding this next bit of behavior, I will need to modify the example of the current behavior:
describe "link_to_adjacent_view_date" do
context "couchdb view by_month" do
before(:each) do
@count_by_month = [{"key" => "2009-04", "value" => 3},
{"key" => "2009-05", "value" => 3}]
end

it "should link to block text + date, if block is given" do
link_to_adjacent_view_date("2009-04", @count_by_month) do |date|
"foo #{date} bar"
end.
should have_selector("a",
:href => "/meals/2009/05",
:content => "foo 2009-05 bar")
end
end
end
That example reads, given the CouchDB view (@count_by_month), linking to a date adjacent to the supplied date, I should be able to incorporate the date into my own date string (here, surrounding the date with the words "foo" and "bar").

Now I need to support two arguments, instead of one, so the example is updated to read:
    it "should link to the CouchDB view's key and value, if block is given" do
link_to_adjacent_view_date("2009-04", @count_by_month) do |date, value|
"foo #{date} bar #{value} baz"
end.
should have_selector("a",
:href => "/meals/2009/05",
:content => "foo 2009-05 bar 3 baz")
end
Now, I expect the link_to_adjacent_view_date helper to yield with the CouchDB value, in addition to the date (key) that it had been yielding. That example verifies that the value ("3" from the context above) is usable as I expect it to be.

Running the spec fails now:
cstrom@jaynestown:~/repos/eee-code$ spec ./spec/eee_helpers_spec.rb 
...............................F..............

1)
'link_to_adjacent_view_date couchdb view by_month should link to the CouchDB view's key and value, if block is given' FAILED
expected following output to contain a <a href='/meals/2009/05'>foo 2009-05 bar 3 baz</a> tag:
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
<html><body><a href="/meals/2009/05">foo 2009-05 bar baz</a></body></html>
./spec/eee_helpers_spec.rb:241:

Finished in 0.044204 seconds

46 examples, 1 failure
I can make that pass by changing link_to_adjacent_view_date to yield the key and value to build the link text:
        link_text = block_given? ?
yield(next_result['key'], next_result['value']) :
next_result['key']
I have to update all instances of link_to_adjacent_view_date that use a block to expect two parameters (even if the second one is not used). With all specs passing, it is back to the meal spec to use the meal title in the link text:
  it "should link to previous meal title" do
render("/views/meal.haml")
response.should have_selector("a",
:href => "/meals/2009/05/15",
:content => "Foo")
end
(where "Foo" is the title a mock CouchDB view, defined in the spec setup)

To make that example pass, I change this line in the Haml template:
=link_to_adjacent_view_date(@meal['date'], @meals_by_date, :previous => true) { |d,v| Date.parse(d).strftime("%B %e, %Y") }
to read:
  =link_to_adjacent_view_date(@meal['date'], @meals_by_date, :previous => true) { |d,v| Date.parse(d).strftime("#{v[1]} (%B %e, %Y)") }
OK, now I am ready to work my way back out to the navigation-between-meals Cucumber scenario:



Yay! All passing. I am now assured that quick navigation between meals will work.
(commit)

I currently reside at the following Cucumber milemarker:
26 scenarios
1 skipped step
42 undefined steps
166 passed steps
Tomorrow, I will pick up with another scenario and try to get the number of undefined steps down under 40. I'm getting close.

No comments:

Post a Comment