paged_scopes 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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