Tuesday, June 30, 2009

I pick up with adding the 10 most recent meals to the site's RSS feed. First, I have to pull back the list of the 10 most recent IDs from CouchDB. Thankfully, I did something similar for the homepage, so I can re-use here:

With that example safely passing, I need to ensure that the full details are pulled back for those 10 meals. Again, I copy from the site's home page example to stub out the RestClient call that pulls back the 10 meal IDs. Then I specify that those 10 IDs need to be used to look up the full meal details for each corresponding meal. This leads to something of a longish example:

That passes without any changes thanks to the RSpec driven work inside the Sinatra application.

To verify complete implementation of the next step, that the 10 most recent meals should be included in the RSS feed, I first check the old feed on the legacy Rails site:

<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"> <channel> <title>EEE Cooks: Meals</title> <link>http://www.eeecooks.com/meals/rss</link> <description>Meals from a Family Cookbook</description> <language>en-us</language> <item> <title>Star Wars: The Dinner</title> <description><p>Inspired by one of the many <a href="http://www.cooksillustrated.com/">magazines</a> that Robin reads, we do a little grilling today. It&#8217;s been a while, so we were pleasantly surprised that the grill even started up, let alone that we were able to cook up such yummy food.</p></description> <pubDate>Sun, 13 Jul 2008 00:00:00 +0000</pubDate> <link>http://www.eeecooks.com/meals/2008/07/13</link> <guid>http://www.eeecooks.com/meals/2008/07/13</guid> </item> <item> <title>You Each Take a Side</title>...

First, up, there should be 10 <item> children of <channel>, each with a <title> child. I should also verify that the RSS item titles contain the expected values (e.g. "Meal 0", "Meal 1", etc). I can specify this in the example with:

Monday, June 29, 2009

Moving on to the next Cucumber feature, I need to implement RSS feeds. First up is the RSS feed for the meals:

It is nice that, once a project has reached a certain point, one or two steps are already defined when starting on a new feature or scenario. The "Given" step of 20 yummy meals is already working, so I am able to get right down to business of accessing the RSS feed. Since I have yet to define that action in the Sinatra application, I am done with my outside, Cucumber work and am ready to dive into the inside, driven by RSpec work.

In the legacy Rails app (and the legacy baked-from-XML version before it), the RSS feed was named main.rss. To keep disruption to a minimum, I will keep that name, which means that I first drive RSS implementation with this example:

describe "GET /main.rss" do it "should respond OK" do get "/main.rss" last_response.should be_ok end end

Implementation of this simple example is predictably simple:

get '/main.rss' do ""end

Before switching to a RSS builder library, I implement a simple string bare bones RSS feed. This is a simpler step than going straight for the RSS builder. Simpler, smaller steps make for fewer things that can go wrong moving to subsequent steps, making for a smoother overall development process.

The example that I use to drive the bare bones RSS feed is:

it "should be the meals rss feed" do get "/main.rss" last_response. should have_selector("channel title", :content => "EEE Cooks: Meals") end

And the bare bones RSS feed that implements this example is:

get '/main.rss' do "<channel><title>EEE Cooks: Title</title></channel>"end

With that example passing, I will give the built-in ruby library RSS:Maker a try for building the real RSS feed. I convert the bare bones string version to:

Sunday, June 28, 2009

I start today with a note about yesterday's work: when URL encoding in Sinatra, use Rack::Utils.escape. It works and it will encode in the same way that Rack::Test would expect things to be encoded. Maybe an obvious suggestion, but with the myriad of URL encoding options available in the Ruby world, it seemed one worth noting.

Yesterday, I used the subject and url query parameters in that link to pre-populate the feedback form. Today, I need to ensure that the url parameter, when submitted by the feedback form to the "send us email" action does something. Specifically, it ought to include the URL in the email that we receive.

Unfortunately, because the email body is only one attribute to the Pony.mail call, I have to specify the entire body of the email message. I can use RSpec's hash_including so that I do not have to specify all of the Pony.mail attributes, but I still need the entire message body:

it "should include the URL (if supplied) in the email" do message = <From: fromEmail: email

With that, I am ready to work my way back out to the Cucumber scenario to mark it as complete:

I implement the last step, that the form's subject should be pre-filled, with:

Then /^I should see a subject of "([^\"]*)"$/ do |subject| response.should have_selector("input", :value => subject)end

And now, I have the entire meal feedback scenario complete:

cstrom@jaynestown:~/repos/eee-code$ cucumber features -n \ -s "Give feedback to the authors on a yummy meal"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: Give feedback to the authors on a yummy meal Given a "Yummy" meal enjoyed on 2009-06-27 When I view the "Yummy" meal And I click "Send us feedback on this meal" Then I should see a feedback form And I should see a subject of "[Meal] Yummy"

1 scenario5 passed steps

After driving-by-example a similar feedback link in the recipe.haml template, I can mark the recipe-feedback scenario as complete as well:

cstrom@jaynestown:~/repos/eee-code$ cucumber features -n \> -s "Send compliments to the chef on a delicious recipe"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: Send compliments to the chef on a delicious recipe Given a recipe for Curried Shrimp When I view the recipe And I click "Send us feedback on this recipe" Then I should see a feedback form And I should see a subject of "[Recipe] Curried Shrimp"

The date does not really matter, but I opt for the former since it reads OK in the scenario. After picking and choosing the remainder of the scenario steps, I end up with:

Scenario: Give feedback to the authors on a yummy meal

Given a "Yummy" meal enjoyed on 2009-06-27 When I view the "Yummy" meal And I click "Send us feedback on this meal" Then I should see a feedback form And I should see a subject of "[Meal] Yummy"

And something similar for the recipe feedback scenario:

Scenario: Send compliments to the chef on a delicious recipe

Given a "Yummy" recipe from 2009-06-27 When I view the recipe And I click "Send us feedback on this recipe" Then I should see a feedback form And I should see a subject of "[Recipe] Yummy"

One other thing that ought to happen in both scenarios is that the recipe/meal should supply the feedback form with its URL so that the email that we receive includes the URL. We prefer having the URL so that we can click it to fix any mistakes. I do not include that in the scenario since it is written from the user's perspective—mixing perspectives can lead to unnecessary conflicts. Instead I will try to remember to do this as I work inside the code.

First up, I run the scenario to see what else needs to be implemented:

cstrom@jaynestown:~/repos/eee-code$ cucumber features -n \ -s "Give feedback to the authors on a yummy meal"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: Give feedback to the authors on a yummy meal Given a "Yummy" meal enjoyed on 2009-06-27 When I view the "Yummy" meal And I click "Send us feedback on this meal" Could not find link with text or title or id "Send us feedback on this meal" (Webrat::NotFoundError) features/site.feature:60:in `And I click "Send us feedback on this meal"' Then I should see a feedback form And I should see a subject of "[Meal] Yummy"

1 scenario1 failed step1 skipped step1 undefined step2 passed steps

You can implement step definitions for missing steps with these snippets:

Then /^I should see a subject of "([^\"]*)"$/ do |arg1| pendingend

Not too bad, I just need to get two step defined.

To drive the meal template's inclusion of the subject and URL I just this rather ugly bit of RSpec:

it "should include the recipe's URL in the feedback link" do render("/views/meal.haml") response.should have_selector("a", :href => "/feedback?url=http%3A%2F%2Fexample.org%2Frecipe-1&subject=%5BMeal%5D+Meal+Title", :content => "Send us feedback on this meal") end

I need to escape the URL and subject, so the example needs to account for this. Once that is implemented, I have only one more step to go:

I will pick up tomorrow with that last step and doing the same for recipes.

Friday, June 26, 2009

Having built my feedback form and the action to which it submits, I am ready to work my way back out to the Cucumber scenario driving this particular feature:

Telling Webrat to click the "submit" button is not specific enough. Webrat needs a value or id attribute. So I will rewrite that step as:

When I click the "Send Comments" button

I can define that step with:

When /^click the "([^\"]+)" button$/ do |button_text| click_button button_textend

When I run this scenario, I find:

Scenario: Give feedback to the authors of this fantastic site Given 25 yummy meals When I view the site's homepage And I click "Send us comments" Then I should see a feedback form When I fill out the form with effusive praise And click the "Send Comments" button Could not find button "Send Comments" (Webrat::NotFoundError) features/site.feature:53:in `And click the "Send Comments" button' Then I should see a thank you note

Hunh. Cannot find the "Send Comments" button? I was sure I had that in the Haml template:

Scenario: Give feedback to the authors of this fantastic site Given 25 yummy meals When I view the site's homepage And I click "Send us comments" Then I should see a feedback form When I fill out the form with effusive praise And I click the "Send Comments" button PATH_INFO must start with / (Rack::Lint::LintError) (eval):7:in `get' features/site.feature:53:in `And I click the "Send Comments" button' Then I should see a thank you note

This error I do not quite understand. If I am working in the top level URL space of a site (e.g. http://eeecooks.com/feedback), then I expect any form submitted without an absolute URL to be relative to the top-level namespace (i.e.email should be submitted to http://eeecooks.com/email). No matter, I change:

%form{:action => "email"}

to:

%form{:action => "/email"}

And finally that step is passing. The last step is to verify that the user now sees a "thanks for the feedback" message. It is a step so simple that it almost seems not worth bothering. Still, it cannot hurt to define, especially since the step is so easy to define:

Then /^I should see a thank you note$/ do response.should have_selector("h1", :content => "Thank You")end

Finally, I get to see my simple feedback scenario is all of its glory:

Scenario: Give feedback to the authors of this fantastic site Given 25 yummy meals When I view the site's homepage And I click "Send us comments" Then I should see a feedback form When I fill out the form with effusive praise And I click the "Send Comments" button Then I should see a thank you note expected following output to contain a <h1>Thank You</h1> tag: <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd"> <html><body><h1>Not Found</h1></body></html> (Spec::Expectations::ExpectationNotMetError) features/site.feature:54:in `Then I should see a thank you note'

Aaarrrgh! Not found?! How can that be?

After much soul searching and questioning of my ability as a developer, I finally ask myself how could it not be found? It is clearly defined in my Sinatra application:

The answer, of course, is in the first word of that code snippet: post. The /email action is only defined for POST actions, not GETs. Changing the form action definition in the Haml template will resolve everything:

%form{:action => "/email", :method => "post"}

Finally, I get:

cstrom@jaynestown:~/repos/eee-code$ cucumber features -n \ -s "Give feedback to the authors of this fantastic site"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: Give feedback to the authors of this fantastic site Given 25 yummy meals When I view the site's homepage And I click "Send us comments" Then I should see a feedback form When I fill out the form with effusive praisesendmail: 553 jaynestown.mail.rr.com does not exist And I click the "Send Comments" button Then I should see a thank you note

1 scenario7 passed steps

Oops. An actual email is trying to go out there. If I had sendmail configured correctly, I would be spamming myself right now.

Cucumber is an awesome full-stack testing tool, but, at least in this case, I do not want to test the entire stack. What I want to do is stub out the Pony.mail call right before the form is submitted:

Scenario: Give feedback to the authors of this fantastic site Given 25 yummy meals When I view the site's homepage And I click "Send us comments" Then I should see a feedback form When I fill out the form with effusive praise And I click the "Send Comments" button undefined method `stub!' for Pony:Module (NoMethodError) features/site.feature:53:in `And I click the "Send Comments" button' Then I should see a thank you note

Thursday, June 25, 2009

Following up on yesterday's simple form-by-example, today I get to fill out that form with Webrat.

Yay! Good clean fun.

In the site feedback Cucumber scenario, I am seeing the feedback form. Next up is "When I fill out the form with effusive praise", which I can define with:

When /^I fill out the form with effusive praise$/ do fill_in "Name", :with => "Bob" fill_in "Email", :with => "foo@example.org" fill_in "Subject", :with => "Your site is awesome!" fill_in "Message", :with => "The recipes are delicious and your use of CouchDB is impressive."end

That's it! Webrat is just awesome.

This works because I have wisely wrapped my <label> tags around the input fields:

For the uninitiated, <label> tags not only cue Webrat which form fields to complete, but also activate the HTML form field when the label text is clicked—especially handy when clicking those tiny radio buttons or checkboxes.

I could also have added id attributes to the various form fields (e.g.<input id="email-input" type="text"…/>) and then done the label with a for attribute (e.g.<label for="email-input">Email</label>). That would have the same effect in most browsers, but would have the added benefit of working in Internet Explorer (wrapping <label> tags does not work in Internet Explorer), but why make life easier for Internet Explorer users?

With that step marked as complete, it is time to do something in response to submitting the feedback form. I drive-by-example a simple "Thanks for the feedback" page. Before I am really done, I need to actually do something with the feedback. In the various legacy versions of EEE Cooks, all that we do with the feedback is send an email. According to the Sinatra FAQ, that means Pony:

Wednesday, June 24, 2009

The next Cucumber scenario to implement is a somewhat simple feedback form. The very first incarnation of the site was static HTML baked from XML (even many of the search pages were baked). As such, we could not easily do in-page comments (pre-dated prototype.js and jQuery).

Even today, we still prefer the feedback form rather than on-page comments. It is amazing how rude some people can be when commenting on a family cookbook.

The scenario so far:

cstrom@jaynestown:~/repos/eee-code$ cucumber features -n \ -s "Give feedback to the authors of this fantastic site"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: Give feedback to the authors of this fantastic site Given 25 yummy meals When I view the site's homepage And I click "Send us comments" Could not find link with text or title or id "Send us comments" (Webrat::NotFoundError) features/site.feature:50:in `And I click "Send us comments"' Then I should see a feedback form When I fill out the form with effusive praise And click the submit button Then I should see a thank you note

1 scenario1 failed step4 undefined steps2 passed steps

There is nothing in the way of behavior in the addition of a link to a feedback form. Still there is some semantic information in how this should be added to the homepage, so an example is in order. The feedback link should be included as part of an "About Us" section in the right-hand column of the homepage:

I implement this, including the link to the feedback form with the following Haml:

.home-section %h1 About Us %p We are a young family living in the Baltimore area. One day we decided to put our recipe box online. This is our family cookbook %p %a{:href => "/feedback"} Send us comments

That gets the "Send us comments" step passing:

The next steps in the Cucumber scenario describe the feeback page, which does not yet exist. That means it is time to start working my way back into the code. First, the Sinatra application needs to respond to a feedback URL:

describe "GET /feedback" do it "should respond OK" do get "/feedback" last_response.should be_ok end end

Initially, I implement that with an empty string response (as the simplest thing that can work):

get '/feedback' do ""end

I quickly replace that with a call to haml :feedback, which will need to be driven by example. The first example requires a name field for the feedback form:

it "should include a field for the user's name" do render("/views/feedback.haml") response.should have_selector("label", :content => "Name") end

The first time I run the spec, I get an error:

1)Errno::ENOENT in 'feedback.haml should include a field for the user's name'No such file or directory - ./views/feedback.haml/home/cstrom/repos/eee-code/spec/spec_helper.rb:25:in `read'/home/cstrom/repos/eee-code/spec/spec_helper.rb:25:in `render'./spec/views/feedback.haml_spec.rb:5

1)'feedback.haml should include a field for the user's name' FAILEDexpected following output to contain a Name tag:<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">

./spec/views/feedback.haml_spec.rb:6:

Finished in 0.008351 seconds

Finally, I am done changing the message, now I can make it pass:

%label Name %input{:type => "text", :name => "name"}

I follow a similar path to get email address, subject and message fields on this form.

I will call it a night at that point. I will pick up tomorrow implementing the form handler and moving closer to being complete with this scenario.

Before shutting down, I will mark my progress by defining the should-see-a-feedback-form step:

Then /^I should see a feedback form$/ do response.should have_selector("h1", :content => "Feedback") response.should have_selector("form")end

Tuesday, June 23, 2009

With a little bit of cleanup behind, it's on to the next Cucumber scenario—exploring food categories from the homepage. The whole scenario:

Nice! Out of 12 steps in this scenario, 4 are already passing and 2 more are defined, but not yet reached.

The first undefined step in the scenario, clicking on the Italian category on the homepage, describes something missing on the homepage—category links. So, it is time for an RSpec example for the Haml template:

it "should link to the listing of Italian recipes" do render("/views/index.haml") response.should have_selector("a", :content => "Italian") end

I implement that with a call to the categories helper, which links to the Italian category, as well as the other major categories on the site.

That is sufficient to implement the next step, "When I click the Italian category". Hey, that seems vaguely familiar. Have I already done something similar? In fact, I have:

When /^I click on the Italian category$/ do click_link "Italian"end

Hmm… I do not particularly care for the "on" that is currently in there. I think that it reads better without it, so I remove it from the text of an earlier scenario and from the step definition. While I am re-working that step anyway, I note that I will soon need to verify that the user can click the "Breakfast" category. To get the step passing for both categories, I use a Regexp matcher:

When /^I click the (\w+) category$/ do |category| click_link categoryend

With that step passing, I am up to the "Then I should see 20 recipes" step. Again, this seems familiar. Looking through the old recipe search feature step definitions, I find:

Upon further inspection, I note that there were no matches on the Italian recipe category. The source of the trouble was the implementation of the "Given 50 Italian recipes" step. That step, as shown in the Cucumber output, was defined for the recipe search feature. It was defined without categories. The text after the number ("Italian") was used to build the recipe title, not to assign categories. Since that step made no use of the categories / tag_names, I am free to make use of it now:

After making similar adjustments for the next several steps, I am on the last step of this scenario—when I search for a category that only has 10 recipes (i.e. is less that the search page size), then I should see no more result pages. I define this step with the assumption that, in such a case, the "Next" link should be inactive:

Then /^I should see no more pages of results$/ do response.should have_selector(".inactive", :content => "Next »")end

And, just like that, I have another scenario complete:

There are still a few more scenarios in need of implementation, but I am getting very close to deploying.

Monday, June 22, 2009

Today I continue working on affordances that I have built up in the legacy version of the site over the years. I made progress yesterday, but still have a few remaining:

Meals on odd numbered days have right-aligned thumbnails on the homepage (added "depth")

Text links to older meals at the bottom of the meals section of the homepage

Category links on all pages (I only have them on the meal and recipe pages)

Even/Odd Meal Thumbnail Alignment

I will accomplish the actual alignment via CSS (as opposed to an align attribute on the thumbnail's <img> tag). To get the odd/even day of the month to drive the alignment of the thumbnail, I only need a simple modulo 2 of the day:

%div{:class => "meal meal#{date.day % 2}"}

No tests are needed for this—there is no semantic information or behavior being driven. Just a little something to break up the monotony of the homepage.

Older Meals

In addition to the 10 meal summaries on the homepage, we also provide 3 additional links to older meals. In the Sinatra application, I am already pulling back 13 meals:

Sunday, June 21, 2009

Before moving onto the next Cucumber scenario, I take a moment to see if the previous scenario missed anything. I think it likely covered just about all of the necessary behavior needed. But the legacy Rails site (and the previous incarnation as an XML-based CMS) built up many affordances that I would prefer not to lose in translation.

The homepage in Webrat save_and_open_page form:

The homepage on the legacy Rails site:

Naked CSS and missing images aside, it looks as though I am missing:

The date of the meals

A "Read more..." link for the meal

Only linking to new recipes, not the entire menu

Meals on odd numbered days have right-aligned thumbnails (added "depth")

Text links to older meals at the bottom of the meals section

Working through that list one at a time, first up is this RSpec example:

it "should include a pretty date for each meal" do render("/views/index.haml") response.should have_selector(".meals", :content => "May 15") end

In that code snippet, I store a regular expression built from the meal's current date. I use that regular expression to select all recipes from the menu that match the current date. As long as there are new recipes, a "pipe" delimiter is inserted between the link to the current meal and to the new recipes. Finally, the wiki helper converts the wiki recipe text to HTML links.

I will stop there for today and pick up with the last two missing affordances tomorrow.

Saturday, June 20, 2009

The next step in the exploration-from-the-homepage Cucumber scenario is returning back to the homepage. Amazingly, I have made it through three and a half months of my chain without a site-wide layout. That will need to change in order to get this next step passing.

Haml layouts in Sinatra are designated by creating a layout.haml file. The following simple layout should do the trick:

Before defining the click-logo-to-return-home step, I re-run the whole Cucumber scenario to make sure the layout not break anything. Unfortunately, the earlier step that checked the number of meal thumbnail images on the homepage is now failing:

The thumbnails are still there, so what gives? The failing Cucumber scenario is currently defined as:

Then /^the prominently displayed meals should include a thumbnail image$/ do response.should have_selector("img", :count => 10)end

Ah, rather than verifying that there are 10 meal thumbnail images, I am trying to verify that there are 10 images on the entire homepage. The addition of the site logo has upped the number of images on the homepage to 11. A CSS selector can provide the necessary context to only check meal thumbnails:

The additional context in the scenario step gets it passing, so now I can try defining the click-the-logo step:

When /^I click the site logo$/ do click_link "/"end

That link is now on the page, thanks to the newly added layout, so this step is passing.

The next step is simply verifying that the user is back on the homepage after clicking the site logo. At this point, the distinguishing characteristic of the homepage is the inclusion of 10 meal summaries. So I use that to verify this step:

Then /^I should see the homepage$/ do response.should have_selector(".meals h2", :count => 10)end

Now that the user has gone from homepage to meal, to recipe, and back to the homepage, the next step is clicking on the recipe text under the 6th meal on the homepage. The primary reason for choosing the 6th meal/recipe was that this was the first that was vegetarian (the first 5 were Italian). Subsequent steps will verify that it behaves like vegetarian recipes should (i.e. the vegetarian category link is highlighted).

In this scenario, each meal has exactly one menu item—a recipe created just for that meal. Recipe names follow an obvious convention such that the recipe for the 6th meal should be named "Recipe for Meal 5" (meal indexes start at zero). The user should be able to click on the 6th recipe by clicking that text:

When /^I click the recipe link under the 6th meal$/ do click_link "Recipe for Meal 5"end

The second to last step in this scenario—verifying that the user is now on the recipe page—was already defined. After a small tweak to ensure that it applies for all recipes, I am onto the final step in this scenario, verifying that the vegetarian category is highlighted on this recipe page.

For a previous recipe, I defined a should-have-the-italian-category scenario step:

Friday, June 19, 2009

Today, I continue working my way through the navigation from the home page Cucumber scenario. So far, I have been able to verify that meals are listed on the homepage and that the user can click through the meals onto recipe pages. Next in the scenario is verifying that the user can click on some category links before returning to the home page.

To verify the ability of the user to click on the Italian category on the recipe page, I ought to be able to do something as simple as:

When /^I click on the Italian category$/ do click_link "Italian"end

I ought to be able to do something like that, but...

cucumber -n features \ -s "Quickly scanning meals and recipes accessible from the home page"... When I click on the recipe in the menu Then I should see the recipe page And the Italian category should be highlighted When I click on the Italian category Could not find link with text or title or id "Italian" (Webrat::NotFoundError) features/site.feature:23:in `When I click on the Italian category'...

Hunh. I just spent yesterday DRYing up the category links. Did I mess something up? To answer that question, I'll use Webrat's save_and_open_page helper method to dump a copy of the page to the browser. After adding this to the Cucumber step:

When /^I click on the Italian category$/ do save_and_open_page() click_link "Italian"end

I find this in the browser:

Hmmm... the categories, including Italian, are there, but they do not look like normal hyperlinks. Viewing the source, I see:

Ah, no href attribute. It would seem that Webrat does not view <a> tags without href attributes as links any more than Firefox does. Ah well, looks as though I have a little more work to do in the code before I can mark this step as complete.

When I click on a category link, such as "Italian", the user should be taken to the list of recipes that fall into that category. Fortunately, I already have a couchdb-lucene search results page that holds this information. To link the categories at the top of the pages to those search results, I start with this RSpec example:

it "should link to the category search results" do recipe_category_link({}, "Italian"). should have_selector("a", :href => "/recipes/search?q=tag_names:italian") end

To get that example to pass, I add href attributes to the recipe_category_link helper:

Thursday, June 18, 2009

I'm on vacation, but the don't-break-the-chain gods recognize no holidays and never spend a day at the beach. Lest they gaze upon me with disfavor, I will continue to do at least a little work each day on my chain to stay in their good graces. I have no idea what the wrath of the don't-break-the-chain gods is like and I do not intend to find out.

I noted last night that the code for the category links at the top of most pages was starting to feel repetitive. On the legacy Rails site, the category links look like:

The "Vegetarian" category is in red to indicate that the user is on a vegetarian meal, recipe, or list.

There is much repetition in there—9 calls of the recipe_category_link helper, which is responsible for highlighting the link when appropriate. What's worse is that the following is in the meal Haml template (the above is in the recipe template):

I have definitely reached the refactor portion of the Red-Green-Refactor behavior driven development cycle. I could DRY the code up by either factoring the duplication out into a shared partial or into a helper. I opt for the helper since recipe_category_link is already a helper—might as well keep them close should any later work need to be done.

The specs for the categories helper end up reading:

categories- should link to the italian category- should be able to highlight the link to the italian category- should link to the fish category- should link to the vegetarian category- should link to all recipes

Since there is so much repetition in the the category listing, the individual RSpec examples are all very much the same. The "vegetarian" example reads:

it "should link to the vegetarian category" do categories({}). should have_selector("#eee-categories a", :content => "Vegetarian") end

The other examples replace "vegetarian" with the appropriate other category.

All eleven errors are caused by the inclusion of the recipe_category_link helper last night. I am not able to stub out the method, so I seed some dummy data in the before(:each) block to eliminate the errors:

assigns[:recipes] = []

This prevents the recipe_category_link from acting upon a nil@recipes array when building the category links:

Next up, it is back to the Cucumber scenario. Specifically, I need to mark a few scenario steps as complete in the navigation-from-the-homepage Cucumber scenario. Yesterday, I was able to verify that a user could navigate from the homepage to a recent meal. I also ensured that site-wide categories were displaying on the same meal page.

Now, I need to ensure that the user can navigate from the meal down to a recipe listed on the menu. Cucumber tells me that I can implement the missing steps with:

When /^I click on the recipe in the menu$/ do pendingend

Then /^I should see the recipe page$/ do pendingend

As mentioned, the scenario so far involves clicking on the most recently prepared meal, then clicking on the recipe on that meal's menu. The meals created for this scenario have titles: "Meal 0", "Meal 1", "Meal 2", etc. In turn, the recipes for each meal are named simply: "Recipe for Meal 0", "Recipe for Meal 1", "Recipe for Meal 2", etc. So, to click on the recipe for the first meal, I need to look for a link to "Recipe for Meal 0":

When /^I click on the recipe in the menu$/ do click_link "Recipe for Meal 0"end

Similarly, to verify that I am on the appropriate recipe page, I check the <h1> tag:

Then /^I should see the recipe page$/ do response.should have_selector("h1", :content => "Recipe for Meal 0")end

The next step in the scenario, that the Italian category should be highlighted for this Italian recipe, was implemented last night. The step was written for the meal (the meal is Italian because it has an Italian recipe on the menu), but applies to category links on the recipe page just as it does on the meal page.

And just like that, I am done with 14 of the 21 steps in the scenario:

Before moving onto the next steps, I think there are some DRY violations in the category links that I need to address. Tomorrow.

Tuesday, June 16, 2009

The next couple of items in the navigation-from-the-homepage Cucumber involve clicking through to a meal:

The next undefined step is "When I click on the first meal". Hmm... I think I forgot to link to the meals from the homepage. Looking closer I find an example that superficially describes linking to meals, but actually only verifies that it includes the title text:

it "should link to the meal titles" do render("/views/index.haml") response.should have_selector("h2", :content => "Bar") end

I rename that example as "should include meal titles". Then I create the real example for linking to meals:

it "should link to the the meal titles" do render("/views/index.haml") response.should have_selector("a", :href => "/meals/2009/05/15", :content => "Bar") end

Once I get that passing by adding the appropriate %aHaml tag, it is time to move back to the Cucumber scenario. I can mark the next two steps as passing at this point. I can both click a meal link and verify that I am on the meal page:

When /^I click on the first meal$/ do click_link "Meal 0"end

Then /^I should see the meal page$/ do response.should have_selector("h1", :content => "Meal 0")end

Those two steps pass, putting me half way through the exploration-from-the-homepage scenario:

The next "then" step states that "the Italian category should be highlighted". I already have something similar for the recipe page:

To determine if a meal is Italian, each of the recipes on the menu need to be checked. If any of them are Italian, then the meal will be considered Italian. So I'll need to load the recipes and I'll need to update the recipe_category_link to handle more than one recipe.

First up, loading a recipe from wiki text. For text similar to "[recipe:2009/06/16]", CouchDB should be queried and the results parsed:

describe "wiki_recipe" do before(:each) do @json = '{"_id":"2009-06-16-recipe","title":"Recipe for Foo"}' end it "should lookup a recipe from recipe wiki text" do RestClient. should_receive(:get). with(/2009-06-16/). and_return(@json)

wiki_recipe(" [recipe:2009/06/16]") end it "should return a recipe from recipe wiki text" do RestClient. stub!(:get). and_return(@json)

When viewing a meal, I load all recipes into an instance variable in the Sinatra app:

@recipes = @meal['menu'].map { |m| wiki_recipe(m) }.compact

With the Sinatra app using wiki_recipe to lookup recipes, I need to get recipe_category_link to use multiple recipes to determine whether or not to highlight category links. The example uses two "recipes", one with an "italian" category, which should highlight the link:

Astute readers will note that the recipe_category_link omits the href attribute. It is not much of a link without that attribute. Fortunately, subsequent steps in this scenario cover this omission. Something for another day.

Monday, June 15, 2009

Today, I continue working on the navigation from the homepage Cucumber scenario. I implemented view code to present meal and recipe information last night. I was able to mark one more step in the scenario as complete. Tonight I need to ensure that the homepage is displaying recipe links (as it should after yesterday's effort). The step that will verify that things are working as desired:

Then /^the prominently displayed meals should include the recipe titles$/ do response.should have_selector(".menu-items", :content => "Recipe for Meal 1")end

When I run the scenario, however, I find:

cstrom@jaynestown:~/repos/eee-code$ cucumber -n 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 Resource not found (RestClient::ResourceNotFound) /usr/lib/ruby/1.8/net/http.rb:543:in `start' ./helpers.rb:41:in `recipe_link' ./helpers.rb:27:in `wiki' ./helpers.rb:27:in `gsub!' ./helpers.rb:27:in `wiki' /home/cstrom/repos/eee-code/views/index.haml:7:in `render' /home/cstrom/repos/eee-code/views/index.haml:3:in `each' /home/cstrom/repos/eee-code/views/index.haml:3:in `render' ./features/support/../../eee.rb:30:in `GET /' (eval):7:in `get' features/site.feature:13:in `When I view the site's homepage'

Ah, somehow I am requesting the wrong URL. I ought to be requesting the ID from the DB, not a nested resource as I have done here. Specifically, I should be looking for /eee-test/2009-06-11-recipe. So where am I going wrong?

The request is being made by the wiki helper, which, in turn, uses the recipe_link helper to convert wiki text like [recipe:2009/06/11/recipe] into links to the recipe page—by looking the recipe up in the CouchDB database. In the current Cucumber scenario, I am generating wiki text with slashes (as I think I did in the legacy application). I have the feeling that, when I coded the helper in the first place, I accounted only for explicit IDs (e.g. 2009-06-11-recipe).

So it is back into the helper specification to add this example:

it "should be able to link with \"slash\" dates (e.g. 2009/06/11/recipe)" do RestClient. should_receive(:get). with(/2009-06-11-recipe/). and_return(@json)

recipe_link("2009/06/11/recipe") end

I am testing implementation details here rather than behavior. Should I ever change from RestClient to CouchRest, I will have to return here to update the spec—even though no behavior will change. I accept this as the price to keeping my unit tests at the unit level. Hopefully I will not be changing CouchDB client libraries often.

At any rate, this example can drive change in the actual code. This version of the recipe_link helper will work with links with slashes or dashes:

Sunday, June 14, 2009

Today, I continue work on the navigation-from-the-homepage Cucumber scenario. Yesterday, I was able to include meal summaries on the homepage. The next two steps in the scenario are additional "thens" (post-conditions) verifying that a meal thumbnail and menu items are also included on the homepage.

Nothing new needs to be added to the Sinatra application to include either of these pieces of information. This means that work today begins with the Haml specification to drive the new data presentation.

The example of how I'd like the thumbnail to display:

it "should include a thumbnail image of the meal" do render("/views/index.haml") response.should have_selector("img", :src => "/images/2009-05-15/image1.jpg", :width => "200", :height => "150") end

I already have an image_link helper, so I can make this example pass with a simple call to that helper:

= image_link meal, :width => 200, :height => 150

Next, I need two examples describing the presentation of each meal's menu items on the homepage. They should be wikified and comma separated:

The content is the base64 encoded content of good old 1x1 spacer.gif. It does not matter much what the content is for the purposes of the Cucumber scenario—just that the filename ends with "jpg" (implementation detail of the image_link helper). With images attached to each meal, I can verify this Cucumber step with:

Then /^the prominently displayed meals should include a thumbnail image$/ do response.should have_selector("img", :count => 10)end

That's a good stopping point for tonight. Tomorrow, I will mark off the meals-should-include-recipe-titles step as complete. I already have enough code to implement that step, but have a kink or two that needs working out before I consider it really done.