paged_scopes 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,31 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ describe "Indexing" do
4
+ in_contexts do
5
+ it "should know the index of an object in the collection" do
6
+ @articles.all.each_with_index do |article, index|
7
+ @articles.index_of(article).should == index
8
+ end
9
+ end
10
+
11
+ it "should raise an error if asked for the index of an object not in the collection" do
12
+ (Article.all - @articles.all).each do |article|
13
+ lambda { @articles.index_of(article) }.should raise_error(ActiveRecord::RecordNotFound)
14
+ end
15
+ end
16
+
17
+ it "should know the object after an object in the collection" do
18
+ articles = @articles.all
19
+ until articles.empty? do
20
+ @articles.after(articles.shift).should == articles.first
21
+ end
22
+ end
23
+
24
+ it "should know the object before an object in the collection" do
25
+ articles = @articles.all
26
+ until articles.empty? do
27
+ @articles.before(articles.pop).should == articles.last
28
+ end
29
+ end
30
+ end
31
+ end
data/spec/page_spec.rb ADDED
@@ -0,0 +1,249 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ describe "Pages" do
4
+ in_contexts do
5
+ before(:each) do
6
+ @pages = @articles.pages
7
+ @per_page = 2
8
+ @articles.stub!(:per_page).and_return(@per_page)
9
+ @articles.stub!(:page_name).and_return("Page")
10
+ end
11
+
12
+ it "should raise an error if per_page is not specified" do
13
+ @articles.stub!(:per_page).and_return(nil)
14
+ lambda { @pages.per_page }.should raise_error(RuntimeError)
15
+ end
16
+
17
+ it "should paginate using per_page from its proxy if available" do
18
+ @articles.stub!(:per_page).and_return(@per_page)
19
+ @pages.per_page.should == @per_page
20
+ end
21
+
22
+ it "should be a class" do
23
+ @pages.should be_a_kind_of(Class)
24
+ end
25
+
26
+ it "should be a class with name of the proxy's page name" do
27
+ @pages.name.should == "Page"
28
+ end
29
+
30
+ it "should know the page count" do
31
+ @pages.count.should == (@articles.all.length - 1)/@per_page + 1
32
+ end
33
+
34
+ it "should cache the page count" do
35
+ @articles.should_receive(:count).and_return(1)
36
+ 2.times { @pages.count }
37
+ end
38
+
39
+ it "should clear the count cache when reloaded" do
40
+ @articles.should_receive(:count).twice.and_return(1)
41
+ @pages.count
42
+ @pages.reload!
43
+ @pages.count
44
+ end
45
+
46
+ it "should find pages with valid numbers" do
47
+ 1.upto(@pages.count) do |number|
48
+ lambda { @pages.find(number) }.should_not raise_error
49
+ end
50
+ end
51
+
52
+ it "should cache the results of find" do
53
+ @pages.should_receive(:new).once
54
+ 2.times { @pages.find(1) }
55
+ end
56
+
57
+ it "should clear the find cache when reloaded" do
58
+ @pages.should_receive(:new).twice
59
+ @pages.find(1)
60
+ @pages.reload!
61
+ @pages.find(1)
62
+ end
63
+
64
+ it "should raise an error containing the nearest substitute page for invalid page numbers" do
65
+ [ [ -1, @pages.first], [ 0, @pages.first ], [ @pages.count + 1, @pages.last ] ].each do |number, substitute_page|
66
+ lambda { @pages.find(number) }.should raise_error do |error|
67
+ error.substitute.should == substitute_page
68
+ end
69
+ end
70
+ end
71
+
72
+ it "should be enumerable" do
73
+ @pages.metaclass.included_modules.should include(Enumerable)
74
+ @pages.should respond_to(:each)
75
+ args_for_each = []
76
+ @pages.each { |page| args_for_each << page }
77
+ args_for_each.should == (1..@pages.count).map { |number| @pages.find(number) }
78
+ end
79
+
80
+ it "should find the first page" do
81
+ @pages.first.number.should == 1
82
+ end
83
+
84
+ it "should find the last page" do
85
+ @pages.last.number.should == @pages.count
86
+ end
87
+
88
+ it "should find all pages" do
89
+ @pages.all.should == (1..@pages.count).map { |number| @pages.find(number) }
90
+ end
91
+
92
+ it "should find a page closest to a given number" do
93
+ @pages.closest_to(0 ).should == @pages.first
94
+ @pages.closest_to(@pages.count ).should == @pages.last
95
+ @pages.closest_to(@pages.count + 1).should == @pages.last
96
+ end
97
+
98
+ it "should find the page of an object in the collection" do
99
+ @articles.all.each_with_index do |article, index|
100
+ @pages.find_by_article(article).number.should == 1 + index/@pages.per_page
101
+ end
102
+ end
103
+
104
+ it "should not find the page of an object not in the collection" do
105
+ (Article.all - @articles.all).each do |article|
106
+ @pages.find_by_article(article).should be_nil
107
+ lambda { @pages.find_by_article!(article) }.should raise_error(PagedScopes::PageNotFound)
108
+ end
109
+ end
110
+
111
+ it "should not find the page of an object if the object is a new record" do
112
+ lambda { @pages.find_by_article!(Article.new) }.should raise_error(PagedScopes::PageNotFound)
113
+ end
114
+
115
+ it "should not find the page of an object if the object is not an ActiveRecord::Base instance" do
116
+ lambda { @pages.find_by_article!(Object.new) }.should raise_error(PagedScopes::PageNotFound)
117
+ end
118
+
119
+ it "should find a page from a params hash with a pages name as an id in the key" do
120
+ @pages.stub!(:name).and_return("Page")
121
+ @pages.from_params!(:page_id => "1").should == @pages.first
122
+ end
123
+
124
+ it "should find a nil page from a params hash without a pages name as an id in the key" do
125
+ @pages.stub!(:name).and_return("Page")
126
+ @pages.from_params!({}).should be_nil
127
+ end
128
+
129
+ it "should raise an error from a params hash containing an out-of-range page id in the key" do
130
+ @pages.stub!(:name).and_return("Page")
131
+ lambda { @pages.from_params!(:page_id => @pages.count + 1) }.should raise_error(PagedScopes::PageNotFound)
132
+ end
133
+ end
134
+
135
+ context "for an empty collection" do
136
+ before(:each) do
137
+ @articles = Article.scoped(:conditions => { :title => "Supercalifragilisticexpialidocious" })
138
+ @articles.all.should be_empty
139
+ @articles.per_page = 2
140
+ end
141
+
142
+ it "should have one page" do
143
+ @articles.pages.count.should == 1
144
+ end
145
+
146
+ it "should have a page numbered one" do
147
+ @articles.pages.first.number.should == 1
148
+ end
149
+
150
+ it "should have an empty page" do
151
+ @articles.pages.first.articles.all.should be_empty
152
+ end
153
+ end
154
+ end
155
+
156
+ describe "Page instance" do
157
+ in_contexts do
158
+ before(:each) do
159
+ @pages = @articles.pages
160
+ @per_page = 2
161
+ @articles.stub!(:per_page).and_return(@per_page)
162
+ end
163
+
164
+ it "should have a scope representing the objects in the page" do
165
+ @pages.each { |page| page.articles.class.should == ActiveRecord::NamedScope::Scope }
166
+ end
167
+
168
+ it "should know its number" do
169
+ @pages.find(1).number.should == 1
170
+ end
171
+
172
+ it "should parameterise to the page number" do
173
+ @pages.map(&:to_param).should == @pages.map(&:number).map(&:to_s)
174
+ end
175
+
176
+ it "should have the page number as id" do
177
+ @pages.map(&:id).should == @pages.map(&:number)
178
+ end
179
+
180
+ it "should clear the page class cache when reloaded" do
181
+ @pages.should_receive(:reload!)
182
+ @pages.first.reload!
183
+ end
184
+
185
+ it "should be found again by the page class when reloaded" do
186
+ @page = @pages.first
187
+ @pages.should_receive(:find).with(@page.number)
188
+ @page.reload!
189
+ end
190
+
191
+ it "should know whether it's first" do
192
+ pages = @pages.all
193
+ pages.shift.should be_first
194
+ pages.each { |page| page.should_not be_first }
195
+ end
196
+
197
+ it "should know whether it's last" do
198
+ pages = @pages.all
199
+ pages.pop.should be_last
200
+ pages.each { |page| page.should_not be_last }
201
+ end
202
+
203
+ it "should know the next page" do
204
+ pages = @pages.all
205
+ until pages.empty? do
206
+ pages.shift.next.should == pages.first
207
+ end
208
+ end
209
+
210
+ it "should know the previous page" do
211
+ pages = @pages.all
212
+ until pages.empty? do
213
+ pages.pop.previous.should == pages.last
214
+ end
215
+ end
216
+
217
+ it "should know the page which is offset by a specified amount" do
218
+ [ -2, 0, +2 ].each do |offset|
219
+ pages = @pages.all
220
+ pages.each_with_index do |page, index|
221
+ page.offset(offset).should == (index + offset < 0 ? nil : pages[index + offset])
222
+ end
223
+ end
224
+ end
225
+
226
+ it "should be equal based on its page number" do
227
+ @pages.find(1).should == @pages.find(1)
228
+ @pages.find(1).should_not == @pages.find(2)
229
+ end
230
+
231
+ it "should know the page count" do
232
+ @pages.first.page_count.should == @pages.count
233
+ end
234
+
235
+ it "should be sortable by page number" do
236
+ @pages.all.reverse.sort.should == @pages.all
237
+ end
238
+
239
+ it "should know whether it's full" do
240
+ @pages.each do |page|
241
+ page.articles.all.length == @per_page ? page.should(be_full) : page.should_not(be_full)
242
+ end
243
+ end
244
+
245
+ it "should be correctly paginated and ordered" do
246
+ @pages.map(&:articles).should == @articles.all.in_groups_of(@per_page, false)
247
+ end
248
+ end
249
+ end
@@ -0,0 +1,256 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ describe "Paginator" do
4
+ before(:each) do
5
+ @articles = Article.scoped({})
6
+ @articles.per_page = 2
7
+ @pages = @articles.pages
8
+ @path = lambda { |page| "/path/to/page/#{page.to_param}" }
9
+ end
10
+
11
+ it "should raise an error if the paginator path is not set" do
12
+ lambda { @pages.first.paginator.next }.should raise_error(RuntimeError)
13
+ end
14
+
15
+ it "should call the path proc with the last page when #last is called" do
16
+ @pages.each do |page|
17
+ path = lambda { |page| }
18
+ page.paginator.set_path(&path)
19
+ path.should_receive(:call).with(@pages.last).and_return("/path")
20
+ page.paginator.last.should == "/path"
21
+ end
22
+ end
23
+
24
+ it "should call the path proc with the first page when #first is called" do
25
+ @pages.each do |page|
26
+ path = lambda { |page| }
27
+ page.paginator.set_path(&path)
28
+ path.should_receive(:call).with(@pages.first).and_return "/path"
29
+ page.paginator.first.should == "/path"
30
+ end
31
+ end
32
+
33
+ context "for the first page" do
34
+ before(:each) do
35
+ @page = @pages.first
36
+ @paginator = @page.paginator
37
+ @paginator.set_path(&@path)
38
+ end
39
+
40
+ it "should call the path proc with the next page when #next is called" do
41
+ @path.should_receive(:call).with(@page.next).and_return("/path")
42
+ @paginator.next.should == "/path"
43
+ end
44
+
45
+ it "should not call the path proc when #previous is called" do
46
+ @path.should_not_receive(:call)
47
+ @paginator.previous.should be_nil
48
+ end
49
+ end
50
+
51
+ context "for the last page" do
52
+ before(:each) do
53
+ @page = @pages.last
54
+ @paginator = @page.paginator
55
+ @paginator.set_path(&@path)
56
+ end
57
+
58
+ it "should call the path proc with the previous page when #previous is called" do
59
+ @path.should_receive(:call).with(@page.previous).and_return("/path")
60
+ @paginator.previous.should == "/path"
61
+ end
62
+
63
+ it "should not call the path proc when #next is called" do
64
+ @path.should_not_receive(:call)
65
+ @paginator.next.should be_nil
66
+ end
67
+ end
68
+
69
+ context "for any other page" do
70
+ before(:each) do
71
+ @page = @pages.all.second
72
+ @paginator = @page.paginator
73
+ @paginator.set_path(&@path)
74
+ end
75
+
76
+ it "should call the path proc with the next page when #next is called" do
77
+ @path.should_receive(:call).with(@page.next).and_return("/path")
78
+ @paginator.next.should == "/path"
79
+ end
80
+
81
+ it "should call the path proc with the previous page when #previous is called" do
82
+ @path.should_receive(:call).with(@page.previous).and_return("/path")
83
+ @paginator.previous.should == "/path"
84
+ end
85
+ end
86
+
87
+ describe "window generator" do
88
+ before(:each) do
89
+ @pages.stub!(:count).and_return(14)
90
+ @count = @pages.count
91
+ end
92
+
93
+ it "should raise an error if no block is provided" do
94
+ lambda { @pages.first.paginator.window(:inner => 2) }.should raise_error(ArgumentError)
95
+ end
96
+
97
+ it "should raise an error if no inner size is provided" do
98
+ lambda { @pages.first.paginator.window({}) { |page, path| } }.should raise_error(ArgumentError)
99
+ end
100
+
101
+ it "should concatenate all the block return values into a string" do
102
+ page = @pages.find(6)
103
+ page.paginator.set_path { |page| }
104
+ links = (4..8).map { |n| "<li><a href='/path/to/page/#{6+n}'>#{6+n}</a></li>" }
105
+ links.join("\n").should == page.paginator.window(:inner => 2) { |page, path| links.shift }
106
+ end
107
+
108
+ it "should call the block with the page and the path for each page in a window surrounding the page" do
109
+ [
110
+ [ 6, 4..8 ],
111
+ [ 3, 1..5 ],
112
+ [ 1, 1..5 ],
113
+ [ @count-2, @count-4..@count ],
114
+ [ @count, @count-4..@count ]
115
+ ].each do |number, range|
116
+ page = @pages.find(number)
117
+ page.paginator.set_path(&@path)
118
+ pages, paths = [], []
119
+ range.each do |n|
120
+ pages << @pages.find(n)
121
+ paths << (n == page.number ? nil : @path.call(@pages.find(n)))
122
+ end
123
+ page.paginator.window(:inner => 2) do |pg, path, classes|
124
+ pg.should == pages.shift
125
+ path.should == paths.shift
126
+ pg == page ? classes.should(include(:selected)) : classes.should_not(include(:selected))
127
+ end
128
+ end
129
+ end
130
+
131
+ context "with an outer window" do
132
+ it "should also call the block for each page in a window from the first and last pages, and include separators between the windws if necessary" do
133
+ [
134
+ [ 6, 1..2, 6-2..6+2, @count-1..@count ],
135
+ [ 5, 1..5+2, @count-1..@count ],
136
+ [ 3, 1..5, @count-1..@count ],
137
+ [ 1, 1..5, @count-1..@count ],
138
+ [ @count-4, 1..2, @count-4-2..@count ],
139
+ [ @count-2, 1..2, @count-4..@count ],
140
+ [ @count, 1..2, @count-4..@count ]
141
+ ].each do |number, *ranges|
142
+ page = @pages.find(number)
143
+ page.paginator.set_path(&@path)
144
+ pages, paths, gaps_before, gaps_after = [], [], [], []
145
+ ranges.each do |range|
146
+ range.each do |n|
147
+ pages << @pages.find(n)
148
+ paths << (n == page.number ? nil : @path.call(@pages.find(n)))
149
+ end
150
+ end
151
+ pages.each_with_index { |pg, n| gaps_before << (pages[n-1] ? pages[n-1].number < pg.number - 1 : false) }
152
+ pages.each_with_index { |pg, n| gaps_after << (pages[n+1] ? pages[n+1].number > pg.number + 1 : false) }
153
+ page.paginator.window(:inner => 2, :outer => 2) do |pg, path, classes|
154
+ pg.should == pages.shift
155
+ path.should == paths.shift
156
+ gaps_before.shift ? classes.should(include(:gap_before)) : classes.should_not(include(:gap_before))
157
+ gaps_after.shift ? classes.should(include(:gap_after)) : classes.should_not(include(:gap_after))
158
+ end
159
+ end
160
+ end
161
+ end
162
+
163
+ [ [ :previous, 2, 1 ], [ :next, 1, 2 ] ].each do |extra, number, new_number|
164
+ it "should call the block with #{extra.inspect} and the path for the #{extra} page if #{extra.inspect} is specified as an extra" do
165
+ page = @pages.find(number)
166
+ page.paginator.set_path(&@path)
167
+ pages_paths = []
168
+ page.paginator.window(:inner => 2, :extras => [ extra ]) do |page, path, classes|
169
+ pages_paths << [ page, path ]
170
+ end
171
+ pages_paths.should include([ extra, @path.call(@pages.find(new_number)) ])
172
+ end
173
+ end
174
+
175
+ [ [ :previous, "1" ], [ :next, "@count" ] ].each do |extra, number|
176
+ it "should call the block with #{extra.inspect} and a nil path if #{extra.inspect} is specified as an extra but there is no #{extra} page" do
177
+ page = @pages.find(eval(number))
178
+ page.paginator.set_path(&@path)
179
+ pages_paths = []
180
+ page.paginator.window(:inner => 2, :extras => [ extra ]) do |page, path, classes|
181
+ pages_paths << [ page, path ]
182
+ end
183
+ pages_paths.should include([ extra, nil ])
184
+ end
185
+ end
186
+
187
+ [ :first, :last ].each do |extra|
188
+ it "should call the block with #{extra.inspect} and the path for the #{extra} page if #{extra.inspect} is specified as an extra" do
189
+ page = @pages.find(6)
190
+ page.paginator.set_path(&@path)
191
+ pages_paths = []
192
+ page.paginator.window(:inner => 2, :extras => [ extra ]) do |page, path, classes|
193
+ pages_paths << [ page, path ]
194
+ end
195
+ pages_paths.should include([ extra, @path.call(@pages.send(extra)) ])
196
+ end
197
+ end
198
+
199
+ [ [ :first, "1" ], [ :last, "@count" ] ].each do |extra, number|
200
+ it "should call the block with #{extra.inspect} and a nil path if #{extra.inspect} is specified as an extra but the current page is the #{extra} page" do
201
+ page = @pages.find(eval(number))
202
+ page.paginator.set_path(&@path)
203
+ pages_paths = []
204
+ page.paginator.window(:inner => 2, :extras => [ extra ]) do |page, path, classes|
205
+ pages_paths << [ page, path ]
206
+ end
207
+ pages_paths.should include([ extra, nil ])
208
+ end
209
+ end
210
+
211
+ it "should call the block with :first, :previous, pages, :next, :last in that order" do
212
+ page = @pages.find(6)
213
+ page.paginator.set_path(&@path)
214
+ pages = []
215
+ page.paginator.window(:inner => 1, :extras => [ :first, :previous, :next, :last ]) do |page, path, classes|
216
+ pages << page
217
+ end
218
+ pages.should == [ :first, :previous, @pages.find(5), @pages.find(6), @pages.find(7), :next, :last ]
219
+ end
220
+ end
221
+
222
+ describe "window generator for a collection with fewer pages than the window size" do
223
+ it "should list all the page" do
224
+ @pages.stub!(:count).and_return(5)
225
+ @pages.each do |page|
226
+ page.paginator.set_path(&@path)
227
+ pages = @pages.all
228
+ page.paginator.window(:inner => 2) do |pg, path, classes|
229
+ pg.should == pages.shift
230
+ if pg == page
231
+ path.should be_nil
232
+ classes.should include(:selected)
233
+ else
234
+ path.should_not be_nil
235
+ classes.should_not include(:selected)
236
+ end
237
+ classes.should_not include(:gap_before, :gap_after)
238
+ end
239
+ pages.should be_empty
240
+ end
241
+ end
242
+ end
243
+
244
+ describe "window generator for a collection with only one page" do
245
+ it "should not generate any links" do
246
+ @pages.stub!(:count).and_return(1)
247
+ page = @pages.first
248
+ page.paginator.set_path(&@path)
249
+ @path.should_not_receive(:call)
250
+ page.paginator.window(:inner => 2) do |pg, path, classes|
251
+ fail "expected block not to be called"
252
+ "<a>some link</a>"
253
+ end.should be_blank
254
+ end
255
+ end
256
+ end