Saturday, May 2, 2009

Sorting, Page 2

‹prev | My Chain | next›

With sorting and reverse sorting working, up next is propagating that sorting through pagination.

The setup for the existing pagination helper specs reads:
describe "pagination" do
before(:each) do
@query = 'foo'
@results = { 'total_rows' => 41, 'limit' => 20, 'skip' => 0}
end
...
end
To describe pagination with sorting, I add a new context to the pagination description. The context adds a couchdb-lucene sort_order attribute to the results set from the parent pagination block:
  context "with sorting applied" do
before(:each) do
@results["sort_order"] = [{ "field" => "sort_foo",
"reverse" => false}]
end
...
end
With that, an example of a link to a second page of sorted results looks like:
    it "should have a link to other pages with sorting applied" do
pagination(@query, @results).
should have_selector("a",
:content => "2",
:href => "/recipes/search?q=foo&sort=sort_foo&page=2")
end
To get that example working, I add a simple conditional to the pagination helper that adds the sort parameter under these conditions:
      if results['sort_order']
link += "&sort=#{results['sort_order'].first['field']}"
end
A similar example and conditional get reverse sorting working with pagination. Then it is back out to the Cucumber scenario, where I make a disturbing discovery.

In text order, the first 20 (of the 50 total in the Cucumber scenario) recipes in descending order are, "delicious recipe 9", "delicious recipe 8", 7, 6, 50, 5, 49, 48, 47, 46, 45, 44, 43, 42, 41, 40, 4, 39, "delicious recipe 38", and "delicious recipe 37". The first two on the next page should be "delicious recipe 36" and "delicious recipe 35", so I define this step to describe page 2 of descending title sorted results:
Then /^the results should be ordered by name in descending order$/ do
response.should have_selector("tr:nth-child(2) a",
:content => "delicious recipe 36")
response.should have_selector("tr:nth-child(3) a",
:content => "delicious recipe 35")
Sadly, when I run the cucumber scenario, I have skipped 36:
cstrom@jaynestown:~/repos/eee-code$ cucumber -n features -s "Sorting (name, date, preparation time, number of ingredients)"
Feature: Search for recipes

So that I can find one recipe among many
As a web user
I want to be able search recipes

Scenario: Sorting (name, date, preparation time, number of ingredients)
Given 50 "delicious" recipes with ascending names, dates, preparation times, and number of ingredients
And a 0.5 second wait to allow the search index to be updated
When I search for "delicious"
Then I should see 20 results
When I click the "Name" column header
Then the results should be ordered by name in ascending order
When I click the "Name" column header
Then the results should be ordered by name in descending order
When I click the next page
Then I should see page 2
And the results should be ordered by name in descending order
expected following output to contain a <tr:nth-child(2) a>delicious recipe 36</tr:nth-child(2) a> tag:
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
<html><body>
<table>
<tr>
<th>
<a href="/recipes/search?q=delicious&sort=sort_title" id="sort-by-name">Name</a>
<a href="/recipes/search?q=delicious&sort=sort_date" id="sort-by-date">Date</a>
</th>
<th>Date</th>
</tr>
<tr class="row0">
<td>
<a href="/recipes/id-35-delicious">delicious recipe 35</a>
</td>
<td>2008-06-02</td>
</tr>
<tr class="row1">
<td>
<a href="/recipes/id-34-delicious">delicious recipe 34</a>
</td>
<td>2008-06-01</td>
</tr>
...
Damn. A fence post problem. I added a one to the couchdb-lucene "skip" parameter that was not needed:
  skip = (page < 2) ? 0 : ((page - 1) * 20) + 1
Removing the 1 removes the fencepost from the calculation and fixes the Cucumber scenario, but requires a bit of unit-level spec clean-up.
(commit)

This was a legit bug that I introduced. If I had not had the Cucumber integration test, I would have introduced a bug into live code, so outside-in testing really saved me today!

No comments:

Post a Comment