Wednesday, April 1, 2009

Implementing Recipe Details, Part 3

‹prev | My Chain | next›

With all of my inside / view specs passing, I continue my chain by moving back out to cucumber:
cstrom@jaynestown:~/repos/eee-code$ cucumber features/recipe_details.feature -n \
-s "Viewing a recipe with non-active prep time"
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: Viewing a recipe with non-active prep time
Given a recipe for Crockpot Lentil Andouille Soup
When I view the recipe
Then I should see 15 minutes of prep time
And I should see that it requires 5 hours of non-active cook time


Pending Notes:
Then /^I should see 15 minutes of prep time$/ (TODO) # features/step_definitions/recipe_details.rb:67

1 scenario
2 steps passed
2 steps pending (1 with no step definition)
Implementing this and the next steps is as easy as this:
Then /^I should see 15 minutes of prep time$/ do
response.should contain("Preparation Time: 15 minutes")
end

Then /^I should see that it requires 5 hours of non\-active cook time$/ do
response.should contain("Inactive Time: 5 hours")
end
(commit)

Up next is the "Viewing a list of tools used to prepare the recipe" scenario:
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: Viewing a list of tools used to prepare the recipe
Given a recipe for Chicken Noodle Soup
When I view the recipe
Then I should see that it requires a bowl, a colander, a cutting board, a pot and a skimmer to prepare


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

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

Given /^a recipe for Chicken Noodle Soup$/ do
end

Then /^I should see that it requires a bowl, a colander, a cutting board, a pot and a skimmer to prepare$/ do
end
To implement the Given step, I again copy and paste other Given steps, this time adding the legacy tools data structure:
  recipe = {
:title => @title,
:date => @date,
:tools => [
{
"title" => "Bowl",
"asin" => nil,
"amazon_title" => nil
},
{
"title" => "Colander",
"asin" => nil,
"amazon_title" => nil
},
{
"title" => "Cutting Board",
"asin" => nil,
"amazon_title" => nil
},
{
"title" => "Pot",
"asin" => nil,
"amazon_title" => nil
},
{
"title" => "Skimmer",
"asin" => nil,
"amazon_title" => nil
},
]
}
The ASIN is a unique code that Amazon.com uses to identify its products. It is used on the legacy EEE Cooks site to illustrate and to make a little money to support the site via referral fees. I honestly don't recall the need for the amazon_title attribute. I plan to treat it as expendable.

(commit)

Now it is time to move back into the view specs. I finish with two tools in the before(:each) block—one to implement the simplest possible way (hard coding) and the other to force a dynamic solution. The specs are:
  context "a recipe requiring a colander and a pot" do
before(:each) do
colander = {
'title' => "Colander",
'asin' => "ASIN-1234"
}
pot = {
'title' => "Pot",
'asin' => "ASIN-5678"
}

@recipe['tools'] = [colander, pot]
render("views/recipe.haml")
end
it "should contain a link to the colander on Amazon" do
response.should have_selector("a", :content => "Colander",
:href => "http://www.amazon.com/exec/obidos/ASIN/ASIN-1234/eeecooks-20")
end
it "should contain a link to the pot on Amazon" do
response.should have_selector("a", :content => "Pot",
:href => "http://www.amazon.com/exec/obidos/ASIN/ASIN-5678/eeecooks-20")
end
end
As with the ingredient preparations, I also need a boundary condition case for when there are no tools used in a recipe. The boundary condition is represented as:
  context "a recipe with no tools or appliances" do
before(:each) do
@recipe[:tools] = nil
end

it "should not render an ingredient preparations" do
render("views/recipe.haml")
response.should_not have_selector(".eee-recipe-tools")
end
end
Finally, the Haml code that makes this pass is:
- if @recipe['tools']
.eee-recipe-tools
%h2= "Tools and Appliances Used"
%ul
- @recipe['tools'].each do |tool|
%li
%a{:href => amazon_url(tool['asin'])}
= tool['title']
(commit)

Completing the circle, I work my way back outside to define the Then I should see that it requires a Bowl, a Colander, a Cutting Board, a Pot and a Skimmer to prepare step:
Then /^I should see that it requires (.+) to prepare$/ do |tool_list|
tools = tool_list.
split(/\s*(,|and)\s*/).
reject{|str| str == "," || str == "and"}

tools.each do |tool|
response.should contain(tool.sub(/an? /, ''))
end
end
I take the string list of tools and split on commas and the word "and". The RegExp grouping that is needed to accomplish this has the unfortunate side-effect of including the commas and "ands" in the resulting array, so they need to be explicitly rejected. Finally, the response is checked to verify that it contains each tool.
(commit)

And that is it! I completed working my way outside of one scenario and then in-and-out for another scenario all in one night. I am definitely picking up speed. It will be time for a VPS decision real soon now.

No comments:

Post a Comment