Saturday, June 13, 2009

One Step Closer to a Homepage

‹prev | My Chain | next›

Today, I pick up midway through a step in the Cucumber scenario describing site exploration from the homepage. Before I can move back out to the scenario to check off the step as complete, I need to elaborate some more on the current step: "I should see the 10 most recent meals prominently displayed". The meal titles are already presented on the homepage, but I need to display the summary as well.

The example (in RSpec format) that describes the presentation of the titles on the homepage:
describe "index.haml" do
before(:each) do
assigns[:meals] = [{ "key" => "2009-05-15",
"value" => ['2009-05-15', "Foo"] },
{ "key" => "2009-05-31",
"value" => ["2009-05-31", "Bar"] }]
end

it "should link to the meal titles" do
render("/views/index.haml")
response.should have_selector("h2", :content => "Bar")
end
end
The @meals instance variable is assigned by the Sinatra controller, which retrieves the data structure from a CouchDB view. When I first created the view, I only needed the date and title in the value. Now I need the summary as well. Peeking ahead in the Cucumber scenario, I see that I will need the meal image and menu items as well:
  Scenario: Quickly scanning meals and recipes accessible from the home page
Given 25 yummy meals
And 1 delicious recipe for each meal
And the first 5 recipes are Italian
And the second 10 recipes are Vegetarian
When I view the site's homepage
Then I should see the 10 most recent meals prominently displayed
And the prominently displayed meals should include a thumbnail image
And the prominently displayed meals should include the recipe titles

...
So I need nearly the entire meal. Do I pull the entire meal into the CouchDB view or do I look up each meal individually? I think I will go with the latter. My convention with CouchDB views has been to include only the date. When I implemented the between meal navigation, I added the title. If I switch now to include an entire object, I may end up causing confusion down the line.

So I need to stub out the initial RestClient.get to the CouchDB view to pull back 13 meals (ten for prominent display, 3 more for text links only). The example usage then attempts to verify that the detailed meal information is only request ten times (for prominent display). Thirteen meals make for a long example block:
      it "should pull back full details for the first 10 meals" do
RestClient.
stub!(:get).
and_return('{"rows": [' +
'{"key":"2009-06-10","value":["2009-06-10","Foo"]},' +
'{"key":"2009-06-09","value":["2009-06-09","Foo"]},' +
'{"key":"2009-06-08","value":["2009-06-08","Foo"]},' +
'{"key":"2009-06-07","value":["2009-06-07","Foo"]},' +
'{"key":"2009-06-06","value":["2009-06-06","Foo"]},' +
'{"key":"2009-06-05","value":["2009-06-05","Foo"]},' +
'{"key":"2009-06-04","value":["2009-06-04","Foo"]},' +
'{"key":"2009-06-03","value":["2009-06-03","Foo"]},' +
'{"key":"2009-06-02","value":["2009-06-02","Foo"]},' +
'{"key":"2009-06-01","value":["2009-06-01","Foo"]},' +
'{"key":"2009-05-31","value":["2009-05-31","Foo"]},' +
'{"key":"2009-05-30","value":["2009-05-30","Foo"]},' +
'{"key":"2009-05-29","value":["2009-05-29","Foo"]}' +
']}')

RestClient.
should_receive(:get).
with(/2009-0/).
exactly(10).times.
and_return('{"title":"foo",' +
'"summary":"foo summary",' +
'"menu":[]}')

get "/"

end
end
With the example now failing, I use a range to inject 10 IDs into a meal array for the Haml template:
get '/' do
url = "#{@@db}/_design/meals/_view/by_date?limit=13"
data = RestClient.get url
@meal_view = JSON.parse(data)['rows']

@meals = @meal_view[0...10].inject([]) do |memo, couch_rec|
data = RestClient.get "#{@@db}/#{couch_rec['key']}"
meal = JSON.parse(data)
memo + [meal]
end


haml :index
end
With the Sinatra application behaving as desired, now I need to get the Haml template to present the new data. After updating the before block and the first example to work with the list of full meals, I add two examples describing the summary, checking for presence and wiki-fication:
  it "should include a summary of the meals" do
render("/views/index.haml")
response.should have_selector("p", :content => "Bar summary")
end

it "should wikify the summary" do
assigns[:meals][0]["summary"] = "Foo *bar* baz"
render("/views/index.haml")
response.should have_selector("p strong",
:content => "bar")

end
One line of Haml is needed to make both example pass:
.meals
%h1 Meals
- @meals.each do |meal|
%h2= meal["title"]
%p= wiki(meal["summary"])
With that, I am ready to check this of my Cucumber list by defining the step:
Then /^I should see the 10 most recent meals prominently displayed$/ do
response.should have_selector("h2", :count => 10)
response.should have_selector("h2", :content => "Meal 0")
response.should have_selector("h2", :content => "Meal 9")
response.should_not have_selector("h2", :content => "Meal 10")
end
Running the Cucumber scenario, I find:
cstrom@jaynestown:~/repos/eee-code$ cucumber -ni features \
-s "Quickly scanning meals and recipes accessible from the home page"
Sinatra::Test is deprecated; use Rack::Test instead.
Feature: Site

So that I may explore many wonderful recipes and see the meals in which they were served
As someone interested in cooking
I want to be able to easily explore this awesome site

Scenario: Quickly scanning meals and recipes accessible from the home page
Given 25 yummy meals
And 1 delicious recipe for each meal
And the first 5 recipes are Italian
And the second 10 recipes are Vegetarian
When I view the site's homepage
Then I should see the 10 most recent meals prominently displayed
expected following output to contain a <h2>Meal 0</h2> tag:
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
<html><body><div class="meals">
<h1>Meals</h1>
<h2>Meal 24</h2>
<p></p>
<p>meal summary</p>
<h2>Meal 23</h2>
<p></p>
<p>meal summary</p>
<h2>Meal 22</h2>
<p></p>
<p>meal summary</p>
<h2>Meal 21</h2>
<p></p>
<p>meal summary</p>
<h2>Meal 20</h2>
<p></p>
<p>meal summary</p>
<h2>Meal 19</h2>
<p></p>
...
Aw man!

After recovering from the initial disappointment, I look closer at the failure to discover that the meals are being returned from the CouchDB view in the opposite order that I wanted. This is a hazard of extensive stubbing in my unit tests. Thankfully, Cucumber caught the problem for me. Adding a descending=true to my GET of the CouchDB view should resolve the problem:
get '/' do
url = "#{@@db}/_design/meals/_view/by_date?limit=13&descending=true"
data = RestClient.get url
@meal_view = JSON.parse(data)['rows']

@meals = @meal_view[0...10].inject([]) do |memo, couch_rec|
data = RestClient.get "#{@@db}/#{couch_rec['key']}"
meal = JSON.parse(data)
memo + [meal]
end

haml :index
end
And running the scenario again, I find that everything is working as desired:



One step closer to a working homepage. Yay!

No comments:

Post a Comment