Live Preview with RSpec on Rails
Today I’m waiting at Così Cafe for our train to Washington DC for Agile 2007, so it’s time to add the next part of the “preview” feature. I have an internet connection, so I can afford to be a little more aggressive with the story I tackle next, because Google knows all. I think I’ll try to build the real-time preview I imagined earlier this morning. Now I don’t think I can spec-drive the real-time aspect of the preview, but I can certainly spec-drive showing the preview in a nearby window while I’m typing in the posting content text area. To get a sense for how that would look, I do some quick UI design.
In order to make room, I shrink the “content” text area and put the live preview, as it’s called, below it. This looks reasonable to me. In the process, I make one design decision: the <div> that contains the live preview text has the ID content-live-preview, which I’ll need to spec-drive the RJS I’ll have to write.
Speaking of spec-driving RJS, I have no idea how to do that, so I need to read a little about that before I get started.
Unfortunately, after reading Rails Recipes, I’m convinced that I need to implement this first before I can learn how to spec-drive it for next time. I find this happens a lot with Rails: it’s almost too simple. I paste an implementation into my view and inspect it manually. When I do that, it becomes clear that I need an action to handle the live preview, so although I didn’t drive the controller behavior with an automated test or spec, I’m certain I need something. Now I think I can spec-drive the controller behavior. Since this is familiar territory, this should go smoothly. Let’s see.
According to Rails Recipes_, all I need to do in my action is render the default template without a layout, which means I will need to move the live preview markup to a new @livepreview.rhtml@ template. I’ll do that first, to make sure it will be wired together properly.
After a few false starts, I have these specs:
describe "Live Preview for weblog postings" do
it "should have a section to display the live preview" do
assigns[:posting] = Posting.new(:content => "Some @textile@ content.")
render "/postings/live_preview"
response.should have_tag("div#content-live-preview")
end
it "should be able to handle nil content" do
assigns[:posting] = Posting.new(:content => nil)
render "/postings/live_preview"
response.should have_tag("div#content-live-preview")
end
end
This is the live_preview.rhtml template:
<div id="content-live-preview" class="entry"> <%= textilize(@posting.content) if @posting.content %> </div>
That decidedly doesn’t work. When I consult Rails Recipes, I realize I should display the request parameter posting[content] and not the Ruby object @posting.content. For this, I need to learn how to stub the request parameters in the view. Evidently this is as simple as assigning to a params Hash. I fix the specs to match this new information.
describe "Live Preview for weblog postings" do
it "should have a section to display the live preview" do
params[:posting] = { :content => "Some @textile@ content." }
render "/postings/live_preview"
response.should have_tag("div#content-live-preview")
end
it "should be able to handle nil content" do
params[:posting] = { :content => nil }
render "/postings/live_preview"
response.should have_tag("div#content-live-preview")
end
end
I’m on a green bar, but the UI doesn’t work. Firebug tells me that my controller live_preview method works, but the corresponding <div> is not updating. I don’t know how to spec-drive this, so I need to hack for a while. It’s 14.44; let’s see how long I need to hack.
Fortunately, only about five minutes. I had one <div> too many.
After fixing that, and making a few small changes, it looks like I’m done. I would have preferred to spec-drive the observe_field code, but at least I have less untested code than I used to. Now I can remove the preview button and the code that went with it. It served its purpose; now it can go to its rest.