Sunday, August 30, 2009

Small Steps / Mini Calendar

‹prev | My Chain | next›

With half a brain, I was able to drive by example a start on the homepage mini-calendar. I left off with the mini-calendar showing a numeric date and little else. This was all done in the Sinatra action:
get %r{/mini/.*} do
url = "#{@@db}/_design/meals/_view/count_by_month?group=true\&limit=1\&descending=true"
data = RestClient.get url
@last_month = JSON.parse(data)['rows'].first['key']

"<h1>#{@last_month}</h1>"
end
Although not an MVC framework, I have done my view code separately in Haml templates. So first up today is to write some examples for the mini-calendar view. As can be seen from the mini action, there will be a @last_month (with a meal) instance variable, so my examples will all expect that to be set. In RSpec:
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper' )

describe "mini_calendar.haml" do
before(:each) do
assigns[:last_month] = "2009-08"
end
end
The first example will describe the date heading:
  it "should show a human readable month and year" do
render("views/mini_calendar.haml")
response.should have_selector("h1", :content => "August 2009")
end
Running this example, of course, I find failure due to the lack of the template:
cstrom@jaynestown:~/repos/eee-code$ spec ./spec/views/mini_calendar.haml_spec.rb 
F

1)
Errno::ENOENT in 'mini_calendar.haml should show a human readable month and year'
No such file or directory - ./views/mini_calendar.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/mini_calendar.haml_spec.rb:9:

Finished in 0.00705 seconds

1 example, 1 failure
Once again I find myself in the familiar, comfortable confines of the change-the-message or make-it-pass cycle. I can change the above message by creating the template. The new failure message becomes:
cstrom@jaynestown:~/repos/eee-code$ spec ./spec/views/mini_calendar.haml_spec.rb 
F

1)
'mini_calendar.haml should show a human readable month and year' FAILED
expected following output to contain a <h1>August 2009</h1> tag:
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">

./spec/views/mini_calendar.haml_spec.rb:10:

Finished in 0.007892 seconds

1 example, 1 failure
Finally, I can make the example pass by displaying the current month inside an <h1> tag:
%h1
= month_text @last_month
The reason for the change-the-message or make-it-pass cycle is to ensure that every step is built on validated assumptions from the previous step. There is nothing worse than spending 10 minutes trying to solve a problem only to realize that you are solving the wrong problem (like creating the template with the wrong name).

I do notice a problem with my naming convention. The Haml template will always show the month requested, not the "latest month". The current step in the current Cucumber scenario drove a default date—the last month with a meal. But as mentioned, this template is displaying the requested month. So I change the expected instance variable in the examples from @last_month to @month:
describe "mini_calendar.haml" do
before(:each) do
assigns[:month] = "2009-08"
end

it "should show a human readable month and year" do
render("views/mini_calendar.haml")
response.should have_selector("h1", :content => "August 2009")
end
end
Oddly enough, that example still passes:
cstrom@jaynestown:~/repos/eee-code$ spec ./spec/views/mini_calendar.haml_spec.rb
.

Finished in 0.008105 seconds

1 example, 0 failures
The reason for the non-failure has to do with the month_text helper method:
    def month_text(date_frag)
Date.parse("#{date_frag}-01").strftime("%B %Y")
end
Since the @last_month variable is not being set in the example, date_frag argument is now nil. This means that Date.parse is now trying to parse the string "-01". How can that return an actual date? Somehow it does:
>> Date.parse("-01").to_s
=> "2009-08-01"
This example will fail in two days (once the example month is different than the current month). For the time being, I will create another example to fail as expected:
  it "should show a human readable month and year for other months" do
assigns[:month] = "2009-01"
render("views/mini_calendar.haml")
response.should have_selector("h1", :content => "January 2009")
end
That example does fail:
cstrom@jaynestown:~/repos/eee-code$ spec ./spec/views/mini_calendar.haml_spec.rb 
.F

1)
'mini_calendar.haml should show a human readable month and year for other months' FAILED
expected following output to contain a <h1>January 2009</h1> tag:
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
<html><body><h1>
August 2009
</h1></body></html>
./spec/views/mini_calendar.haml_spec.rb:16:

Finished in 0.010549 seconds

2 examples, 1 failure
Making that example pass is a simple matter of using the new @month instance variable:
%h1
= month_text @month
This was a nice demonstration of the value in taking small steps in the change-the-message or make-it-pass cycle. I could have easily made the changes without the small steps. I might even have made the change without introducing a subtle defect. By taking small steps, I know that I have not introduced a subtle defect.

With that, I can work my way back out to the Cucumber scenario to mark the current step as complete:
Then /^I should see the calendar for (.+)$/ do |date|
response.should have_selector("h1", :content => date)
end
Cucumber had not suggested the RegExp for the date—rather I saw an opportunity for re-use coming very soon. And, indeed, in addition to one newly passing step, I have some blue ones as well:



I spend a little more time driving days in the mini-calendar. Tomorrow I ought to be able to link to meals from the calendar and mark off another step or two in my scenario.

No comments:

Post a Comment