Sunday, April 26, 2009

Pagination, Page 5

‹prev | My Chain | next›

The next few steps in the search pagination scenario ought to be easy to implement—I believe they are already functional:



I already have the "should see X number of results" implemented (shown in blue). While working inside the actual code, I am reasonably sure that I got the next / previous buttons working as well. Still it is best to make sure...

The next and previous links are rendered as spans when it is not possible for the user to follow them. Therefore, I verify that they are span tags:
Then /^I should not be able to go to a (.+) page$/ do |link_text|
response.should have_selector(".pagination span",
:content => link_text.capitalize())
end
Similarly, I implement the "click the next / previous page" steps as:
When /^I click the (.+) page$/ do |link_text|
click_link(link_text)
end
This is very similar to the step that describes clicking on the numbered pages (e.g. when I click page 3):
When /^I click page (\d+)$/ do |page|
click_link(page)
end
I do not mind duplication in specs / tests. I much prefer it to awkwardly written text.

With that, I have everything but the boundary conditions passing:



Before working through the internals, I can implement the remaining steps:
When /^I visit page "?(.+)"?$/ do |page|
visit(@query + "&page=#{page}")
end

Then /^I should see page (.+)$/ do |page|
response.should have_selector(".pagination span.current",
:content => page)
end
When I run this I get a big old failure, which is to be expected since I am testing boundary conditions:



To handle boundary conditions, I exploit that "foo".to_i, nil.to_i, and "-1".to_i all evaluate to an integer. The example that I use to describe the desired behavior:
    it "should display page 1 when passing a bad page number" do
RestClient.should_receive(:get).
with(/skip=0/).
and_return('{"total_rows":30,"skip":0,"limit":20,"rows":[]}')

get "/recipes/search?q=title:eggs&page=foo"
end
I implement this by refactoring the page and skip calculation in /recipes/search:
get '/recipes/search' do
page = params[:page].to_i
skip = (page < 2) ? 0 : ((page - 1) * 20) + 1
data = RestClient.get "#{@@db}/_fti?limit=20&skip=#{skip}&q=#{params[:q]}"
@results = JSON.parse(data)
@query = params[:q]

if @results['rows'].size == 0 && page > 1
redirect("/recipes/search?q=#{@query}")
return
end

haml :search
end
One final thing that I need to do is mark the current page (so that the scenario knows that it is staying on page 1 when moving past boundaries). In the spec helper test, the example that describes this behavior is:
  it "should mark the current page" do
pagination(@query, @results).
should have_selector("span.current", :content => "1")
end
The code block inside the pagination helper that implements this is:
      links << (1..last_page).map do |page|
if page == current_page
%Q|<span class="current">#{page}</span>|
else
%Q|<a href="#{link}&page=#{page}">#{page}</a>|
end
end
Back out in the feature, I am now greeted with this:



Yay! The whole scenario done!
(commit)

Next up, sorting and some additional boundary conditions.

No comments:

Post a Comment