Thursday, April 2, 2009

Recipe Details and the Kingdom of the Crystal Skull

‹prev | My Chain | next›

Today I work on the last of the recipe details scenarios, "Main site categories". At the top of each recipe page on the legacy EEE Cooks we list recipe categories (e.g. Italian, Vegetarian). If the recipe is in one of those categories, then the category is highlighted.

cstrom@jaynestown:~/repos/eee-code$ cucumber features/recipe_details.feature -n \
-s "Main site categories"
Feature: Recipe Details

So that I can accurately reproduce a recipe at home
As a web user
I want to be able to easily recognize important details
Scenario: Main site categories
Given a recipe for Mango and Tomato Salad
And site-wide categories of Italian, Asian, Latin, Breakfast, Chicken, Fish, Meat, Salad, and Vegetarian
When I view the recipe
Then the Salad and Vegetarian categories should be active


1 scenario
1 step skipped
3 steps pending (3 with no step definition)

You can use these snippets to implement pending steps which have no step definition:

Given /^a recipe for Mango and Tomato Salad$/ do
end

Given /^site\-wide categories of Italian, Asian, Latin, Breakfast, Chicken, Fish, Meat, Salad, and Vegetarian$/ do
end

Then /^the Salad and Vegetarian categories should be active$/ do
end
With 3 of 4 scenarios complete, I am in the groove. The recipe to implement the first Given step is:
  recipe = {
:title => @title,
:date => @date,
:tag_names => [ "vegetarian", "salad" ]
}
I rethink the second Given step as being better done as a Then step. If I had a Categories model, it could be a pre-condition. But in this application, it will just be links on the view—something that could described as "then I should see the site-wide categories".
(commit)

The view specs that I implement are:
  context "a vegetarian recipe" do
before(:each) do
@recipe['tag_names'] = ['vegetarian']
render("views/recipe.haml")
end
it "should highlight the vegetarian category at the top of the page" do
response.should have_selector("a",
:content => "Vegetarian",
:class => "active")
end
end

context "a vegetarian, italian recipe" do
before(:each) do
@recipe['tag_names'] = ['vegetarian', 'italian']
render("views/recipe.haml")
end
it "should highlight the vegetarian category at the top of the page" do
response.should have_selector("a",
:content => "Vegetarian",
:class => "active")
end
it "should highlight the italian category at the top of the page" do
response.should have_selector("a",
:content => "Italian",
:class => "active")
end
end
Implementation is relatively straight-forward. I use a helper to create the links in order to DRY things up a bit and to cut down on the ugly conditionals in the Haml template:
    def recipe_category_link(recipe, category)
if recipe['tag_names'] && recipe['tag_names'].include?(category.downcase)
%Q|<a class="active">#{category}</a>|
else
%Q|<a>#{category}</a>|
end
end
(commit)

Back out to the cucumber spec, the last two steps can be implemented thusly:
Then /^I should see the site\-wide categories of (.+)$/ do |category_list|
categories = category_list.
split(/\s*(,|and)\s*/).
reject{|str| str == "," || str == "and"}
response.should have_selector("#eee-categories") do |list|
categories.each do |category|
response.should have_selector("a", :content => category)
end
end
end

Then /^the Salad and Vegetarian categories should be active$/ do
response.should have_selector("a", :class => "active", :content => "Salad")
response.should have_selector("a", :class => "active", :content => "Vegetarian")
response.should_not have_selector("a", :class => "active", :content => "Fish")
end
I like throwing in a negative assertion like the Fish category should not be active. It is as if I were heckling my own code. Just like heckle, negative assertions are a nice, simple way to keep me honest.

Just like that I am done with the recipe details page. At some point I'll have to actually look at it. First though, I think I will have to get the image in there. When next my chain continues...

No comments:

Post a Comment