will_paginate 3.0.4 → 3.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,140 @@
1
+ require 'spec_helper'
2
+
3
+ begin
4
+ require 'will_paginate/mongoid'
5
+ rescue LoadError => error
6
+ warn "Error running Mongoid specs: #{error.message}"
7
+ mongoid_loaded = false
8
+ else
9
+ Mongoid.connect_to 'will_paginate_test'
10
+
11
+ class MongoidModel
12
+ include Mongoid::Document
13
+ end
14
+
15
+ mongoid_loaded = true
16
+ end
17
+
18
+ describe WillPaginate::Mongoid do
19
+ before(:all) do
20
+ MongoidModel.delete_all
21
+ 4.times { MongoidModel.create! }
22
+ end
23
+
24
+ let(:criteria) { MongoidModel.criteria }
25
+
26
+ describe "#page" do
27
+ it "should forward to the paginate method" do
28
+ criteria.expects(:paginate).with(:page => 2).returns("itself")
29
+ criteria.page(2).should == "itself"
30
+ end
31
+
32
+ it "should not override per_page if set earlier in the chain" do
33
+ criteria.paginate(:per_page => 10).page(1).per_page.should == 10
34
+ criteria.paginate(:per_page => 20).page(1).per_page.should == 20
35
+ end
36
+ end
37
+
38
+ describe "#per_page" do
39
+ it "should set the limit if given an argument" do
40
+ criteria.per_page(10).options[:limit].should == 10
41
+ end
42
+
43
+ it "should return the current limit if no argument is given" do
44
+ criteria.per_page.should == nil
45
+ criteria.per_page(10).per_page.should == 10
46
+ end
47
+
48
+ it "should be interchangable with limit" do
49
+ criteria.limit(15).per_page.should == 15
50
+ end
51
+
52
+ it "should be nil'able" do
53
+ criteria.per_page(nil).per_page.should be_nil
54
+ end
55
+ end
56
+
57
+ describe "#paginate" do
58
+ it "should use criteria" do
59
+ criteria.paginate.should be_instance_of(::Mongoid::Criteria)
60
+ end
61
+
62
+ it "should not override page number if set earlier in the chain" do
63
+ criteria.page(3).paginate.current_page.should == 3
64
+ end
65
+
66
+ it "should limit according to per_page parameter" do
67
+ criteria.paginate(:per_page => 10).options.should include(:limit => 10)
68
+ end
69
+
70
+ it "should skip according to page and per_page parameters" do
71
+ criteria.paginate(:page => 2, :per_page => 5).options.should include(:skip => 5)
72
+ end
73
+
74
+ specify "first fallback value for per_page option is the current limit" do
75
+ criteria.limit(12).paginate.options.should include(:limit => 12)
76
+ end
77
+
78
+ specify "second fallback value for per_page option is WillPaginate.per_page" do
79
+ criteria.paginate.options.should include(:limit => WillPaginate.per_page)
80
+ end
81
+
82
+ specify "page should default to 1" do
83
+ criteria.paginate.options.should include(:skip => 0)
84
+ end
85
+
86
+ it "should convert strings to integers" do
87
+ criteria.paginate(:page => "2", :per_page => "3").options.should include(:limit => 3)
88
+ end
89
+
90
+ describe "collection compatibility" do
91
+ describe "#total_count" do
92
+ it "should be calculated correctly" do
93
+ criteria.paginate(:per_page => 1).total_entries.should == 4
94
+ criteria.paginate(:per_page => 3).total_entries.should == 4
95
+ end
96
+
97
+ it "should be cached" do
98
+ criteria.expects(:count).once.returns(123)
99
+ criteria.paginate
100
+ 2.times { criteria.total_entries.should == 123 }
101
+ end
102
+ end
103
+
104
+ it "should calculate total_pages" do
105
+ criteria.paginate(:per_page => 1).total_pages.should == 4
106
+ criteria.paginate(:per_page => 3).total_pages.should == 2
107
+ criteria.paginate(:per_page => 10).total_pages.should == 1
108
+ end
109
+
110
+ it "should return per_page" do
111
+ criteria.paginate(:per_page => 1).per_page.should == 1
112
+ criteria.paginate(:per_page => 5).per_page.should == 5
113
+ end
114
+
115
+ describe "#current_page" do
116
+ it "should return current_page" do
117
+ criteria.paginate(:page => 1).current_page.should == 1
118
+ criteria.paginate(:page => 3).current_page.should == 3
119
+ end
120
+
121
+ it "should be casted to PageNumber" do
122
+ page = criteria.paginate(:page => 1).current_page
123
+ (page.instance_of? WillPaginate::PageNumber).should be
124
+ end
125
+ end
126
+
127
+ it "should return offset" do
128
+ criteria.paginate(:page => 1).offset.should == 0
129
+ criteria.paginate(:page => 2, :per_page => 5).offset.should == 5
130
+ criteria.paginate(:page => 3, :per_page => 10).offset.should == 20
131
+ end
132
+
133
+ it "should not pollute plain mongoid criterias" do
134
+ %w(total_entries total_pages current_page).each do |method|
135
+ criteria.should_not respond_to(method)
136
+ end
137
+ end
138
+ end
139
+ end
140
+ end if mongoid_loaded
@@ -1,3 +1,3 @@
1
1
  class Admin < User
2
- has_many :companies, :finder_sql => 'SELECT * FROM companies'
2
+ has_many :companies
3
3
  end
@@ -1,13 +1,9 @@
1
1
  class Developer < User
2
- has_and_belongs_to_many :projects, :include => :topics, :order => 'projects.name', :join_table => 'developers_projects'
2
+ has_and_belongs_to_many :projects, :join_table => 'developers_projects'
3
3
 
4
- def self.with_poor_ones(&block)
5
- with_scope :find => { :conditions => ['salary <= ?', 80000], :order => 'salary' } do
6
- yield
7
- end
8
- end
9
-
10
- scope :poor, :conditions => ['salary <= ?', 80000], :order => 'salary'
4
+ scope :poor, lambda {
5
+ where(['salary <= ?', 80000]).order('salary')
6
+ }
11
7
 
12
8
  def self.per_page() 10 end
13
9
  end
@@ -1,5 +1,5 @@
1
1
  class Project < ActiveRecord::Base
2
- has_and_belongs_to_many :developers, :uniq => true, :join_table => 'developers_projects'
2
+ has_and_belongs_to_many :developers, :join_table => 'developers_projects'
3
3
 
4
4
  has_many :topics
5
5
  # :finder_sql => 'SELECT * FROM topics WHERE (topics.project_id = #{id})',
@@ -7,9 +7,7 @@ class Project < ActiveRecord::Base
7
7
 
8
8
  has_many :replies, :through => :topics do
9
9
  def only_recent(params = {})
10
- scoped.where(['replies.created_at > ?', 15.minutes.ago])
10
+ where(['replies.created_at > ?', 15.minutes.ago])
11
11
  end
12
12
  end
13
-
14
- has_many :unique_replies, :through => :topics, :source => :replies, :uniq => true
15
13
  end
@@ -1,9 +1,8 @@
1
1
  class Reply < ActiveRecord::Base
2
- belongs_to :topic, :include => [:replies]
3
-
4
- scope :recent,
5
- :conditions => ['replies.created_at > ?', 15.minutes.ago],
6
- :order => 'replies.created_at DESC'
2
+ scope :recent, lambda {
3
+ where(['replies.created_at > ?', 15.minutes.ago]).
4
+ order('replies.created_at DESC')
5
+ }
7
6
 
8
7
  validates_presence_of :content
9
8
  end
@@ -1,7 +1,8 @@
1
1
  class Topic < ActiveRecord::Base
2
- has_many :replies, :dependent => :destroy, :order => 'replies.created_at DESC'
2
+ has_many :replies, :dependent => :destroy
3
3
  belongs_to :project
4
4
 
5
- scope :mentions_activerecord, :conditions => ['topics.title LIKE ?', '%ActiveRecord%']
6
- scope :distinct, :select => "DISTINCT #{table_name}.*"
5
+ scope :mentions_activerecord, lambda {
6
+ where(['topics.title LIKE ?', '%ActiveRecord%'])
7
+ }
7
8
  end
@@ -0,0 +1,27 @@
1
+ require 'stringio'
2
+
3
+ class DeprecationMatcher
4
+ def initialize(message)
5
+ @message = message
6
+ end
7
+
8
+ def matches?(block)
9
+ @actual = hijack_stderr(&block)
10
+ PhraseMatcher.new("DEPRECATION WARNING: #{@message}").matches?(@actual)
11
+ end
12
+
13
+ def failure_message
14
+ "expected deprecation warning #{@message.inspect}, got #{@actual.inspect}"
15
+ end
16
+
17
+ private
18
+
19
+ def hijack_stderr
20
+ err = $stderr
21
+ $stderr = StringIO.new
22
+ yield
23
+ $stderr.string.rstrip
24
+ ensure
25
+ $stderr = err
26
+ end
27
+ end
@@ -0,0 +1,19 @@
1
+ class PhraseMatcher
2
+ def initialize(string)
3
+ @string = string
4
+ @pattern = /\b#{Regexp.escape string}\b/
5
+ end
6
+
7
+ def matches?(actual)
8
+ @actual = actual.to_s
9
+ @actual =~ @pattern
10
+ end
11
+
12
+ def failure_message
13
+ "expected #{@actual.inspect} to contain phrase #{@string.inspect}"
14
+ end
15
+
16
+ def negative_failure_message
17
+ "expected #{@actual.inspect} not to contain phrase #{@string.inspect}"
18
+ end
19
+ end
@@ -0,0 +1,36 @@
1
+ class QueryCountMatcher
2
+ def initialize(num)
3
+ @expected_count = num
4
+ end
5
+
6
+ def matches?(block)
7
+ run(block)
8
+
9
+ if @expected_count.respond_to? :include?
10
+ @expected_count.include? @count
11
+ else
12
+ @count == @expected_count
13
+ end
14
+ end
15
+
16
+ def run(block)
17
+ $query_count = 0
18
+ $query_sql = []
19
+ block.call
20
+ ensure
21
+ @queries = $query_sql.dup
22
+ @count = $query_count
23
+ end
24
+
25
+ def performed_queries
26
+ @queries
27
+ end
28
+
29
+ def failure_message
30
+ "expected #{@expected_count} queries, got #{@count}\n#{@queries.join("\n")}"
31
+ end
32
+
33
+ def negative_failure_message
34
+ "expected query count not to be #{@expected_count}"
35
+ end
36
+ end
@@ -3,23 +3,41 @@ require 'will_paginate/page_number'
3
3
 
4
4
  describe WillPaginate::PageNumber do
5
5
  describe "valid" do
6
- subject { described_class.new('12', 'page') }
6
+ def num
7
+ WillPaginate::PageNumber.new('12', 'page')
8
+ end
9
+
10
+ it "== 12" do
11
+ num.should eq(12)
12
+ end
7
13
 
8
- it { should eq(12) }
9
- its(:inspect) { should eq('page 12') }
10
- it { should be_a(WillPaginate::PageNumber) }
11
- it { should be_instance_of(WillPaginate::PageNumber) }
12
- it { should be_a(Numeric) }
13
- it { should be_a(Fixnum) }
14
- it { should_not be_instance_of(Fixnum) }
14
+ it "inspects to 'page 12'" do
15
+ num.inspect.should eq('page 12')
16
+ end
17
+
18
+ it "is a PageNumber" do
19
+ (num.instance_of? WillPaginate::PageNumber).should be
20
+ end
21
+
22
+ it "is a kind of Numeric" do
23
+ (num.is_a? Numeric).should be
24
+ end
25
+
26
+ it "is a kind of Fixnum" do
27
+ (num.is_a? Fixnum).should be
28
+ end
29
+
30
+ it "isn't directly a Fixnum" do
31
+ (num.instance_of? Fixnum).should_not be
32
+ end
15
33
 
16
34
  it "passes the PageNumber=== type check" do |variable|
17
- (WillPaginate::PageNumber === subject).should be
35
+ (WillPaginate::PageNumber === num).should be
18
36
  end
19
37
 
20
38
  it "passes the Numeric=== type check" do |variable|
21
- (Numeric === subject).should be
22
- (Fixnum === subject).should be
39
+ (Numeric === num).should be
40
+ (Fixnum === num).should be
23
41
  end
24
42
  end
25
43
 
data/spec/spec_helper.rb CHANGED
@@ -1,11 +1,13 @@
1
1
  require 'rspec'
2
- require File.expand_path('../view_helpers/view_example_group', __FILE__)
2
+ require 'view_helpers/view_example_group'
3
3
  begin
4
4
  require 'ruby-debug'
5
5
  rescue LoadError
6
6
  # no debugger available
7
7
  end
8
8
 
9
+ Dir[File.expand_path('../matchers/*_matcher.rb', __FILE__)].each { |matcher| require matcher }
10
+
9
11
  RSpec.configure do |config|
10
12
  config.include Module.new {
11
13
  protected
@@ -17,55 +19,28 @@ RSpec.configure do |config|
17
19
  def have_deprecation(msg)
18
20
  DeprecationMatcher.new(msg)
19
21
  end
20
- }
21
-
22
- config.mock_with :mocha
23
- end
24
-
25
- class PhraseMatcher
26
- def initialize(string)
27
- @string = string
28
- @pattern = /\b#{Regexp.escape string}\b/
29
- end
30
-
31
- def matches?(actual)
32
- @actual = actual.to_s
33
- @actual =~ @pattern
34
- end
35
-
36
- def failure_message
37
- "expected #{@actual.inspect} to contain phrase #{@string.inspect}"
38
- end
39
-
40
- def negative_failure_message
41
- "expected #{@actual.inspect} not to contain phrase #{@string.inspect}"
42
- end
43
- end
44
-
45
- require 'stringio'
46
22
 
47
- class DeprecationMatcher
48
- def initialize(message)
49
- @message = message
50
- end
51
-
52
- def matches?(block)
53
- @actual = hijack_stderr(&block)
54
- PhraseMatcher.new("DEPRECATION WARNING: #{@message}").matches?(@actual)
55
- end
23
+ def run_queries(num)
24
+ QueryCountMatcher.new(num)
25
+ end
56
26
 
57
- def failure_message
58
- "expected deprecation warning #{@message.inspect}, got #{@actual.inspect}"
59
- end
27
+ def ignore_deprecation
28
+ ActiveSupport::Deprecation.silence { yield }
29
+ end
60
30
 
61
- private
31
+ def show_queries(&block)
32
+ counter = QueryCountMatcher.new(nil)
33
+ counter.run block
34
+ ensure
35
+ queries = counter.performed_queries
36
+ if queries.any?
37
+ puts queries
38
+ else
39
+ puts "no queries"
40
+ end
41
+ end
42
+ }
62
43
 
63
- def hijack_stderr
64
- err = $stderr
65
- $stderr = StringIO.new
66
- yield
67
- $stderr.string.rstrip
68
- ensure
69
- $stderr = err
70
- end
44
+ config.mock_with :mocha
45
+ config.backtrace_clean_patterns << /view_example_group/
71
46
  end
@@ -1,6 +1,8 @@
1
+ # encoding: utf-8
1
2
  require 'spec_helper'
2
3
  require 'active_support/rescuable' # needed for Ruby 1.9.1
3
4
  require 'action_controller'
5
+ require 'action_view'
4
6
  require 'will_paginate/view_helpers/action_view'
5
7
  require 'will_paginate/collection'
6
8
 
@@ -19,6 +21,7 @@ describe WillPaginate::ActionView do
19
21
 
20
22
  before(:all) do
21
23
  I18n.load_path.concat WillPaginate::I18n.load_path
24
+ I18n.enforce_available_locales = false
22
25
  end
23
26
 
24
27
  before(:each) do
@@ -47,12 +50,23 @@ describe WillPaginate::ActionView do
47
50
  paginate do |pagination|
48
51
  assert_select 'a[href]', 3 do |elements|
49
52
  validate_page_numbers [2,3,2], elements
50
- assert_select elements.last, ':last-child', "Next &#8594;"
53
+ text(elements[2]).should == 'Next →'
54
+ end
55
+ assert_select 'span', 1 do |spans|
56
+ spans[0]['class'].should == 'previous_page disabled'
57
+ text(spans[0]).should == '← Previous'
51
58
  end
52
- assert_select 'span', 1
53
- assert_select 'span.disabled:first-child', '&#8592; Previous'
54
59
  assert_select 'em.current', '1'
55
- pagination.first.inner_text.should == '&#8592; Previous 1 2 3 Next &#8594;'
60
+ text(pagination[0]).should == ' Previous 1 2 3 Next '
61
+ end
62
+ end
63
+
64
+ it "should override existing page param value" do
65
+ request.params :page => 1
66
+ paginate do |pagination|
67
+ assert_select 'a[href]', 3 do |elements|
68
+ validate_page_numbers [2,3,2], elements
69
+ end
56
70
  end
57
71
  end
58
72
 
@@ -65,15 +79,12 @@ describe WillPaginate::ActionView do
65
79
  assert_select 'a[href]', 4 do |elements|
66
80
  validate_page_numbers [1,1,3,3], elements
67
81
  # test rel attribute values:
68
- assert_select elements[1], 'a', '1' do |link|
69
- link.first['rel'].should == 'prev start'
70
- end
71
- assert_select elements.first, 'a', "Prev" do |link|
72
- link.first['rel'].should == 'prev start'
73
- end
74
- assert_select elements.last, 'a', "Next" do |link|
75
- link.first['rel'].should == 'next'
76
- end
82
+ text(elements[0]).should == 'Prev'
83
+ elements[0]['rel'].should == 'prev start'
84
+ text(elements[1]).should == '1'
85
+ elements[1]['rel'].should == 'prev start'
86
+ text(elements[3]).should == 'Next'
87
+ elements[3]['rel'].should == 'next'
77
88
  end
78
89
  assert_select '.current', '2'
79
90
  end
@@ -116,8 +127,8 @@ describe WillPaginate::ActionView do
116
127
  <a href="/foo/bar?page=2" class="next_page" rel="next">Next &#8594;</a></div>
117
128
  HTML
118
129
  expected.strip!.gsub!(/\s{2,}/, ' ')
119
- expected_dom = HTML::Document.new(expected).root
120
-
130
+ expected_dom = parse_html_document(expected).root
131
+
121
132
  html_document.root.should == expected_dom
122
133
  end
123
134
 
@@ -127,7 +138,8 @@ describe WillPaginate::ActionView do
127
138
 
128
139
  assert_select 'a[href]', 1 do |links|
129
140
  query = links.first['href'].split('?', 2)[1]
130
- query.split('&amp;').sort.should == %w(page=2 tag=%3Cbr%3E)
141
+ parts = query.gsub('&amp;', '&').split('&').sort
142
+ parts.should == %w(page=2 tag=%3Cbr%3E)
131
143
  end
132
144
  end
133
145
 
@@ -180,6 +192,15 @@ describe WillPaginate::ActionView do
180
192
  paginate
181
193
  assert_links_match /foo\[bar\]=baz/
182
194
  end
195
+
196
+ it "doesn't allow tampering with host, port, protocol" do
197
+ request.params :host => 'disney.com', :port => '99', :protocol => 'ftp'
198
+ paginate
199
+ assert_links_match %r{^/foo/bar}
200
+ assert_no_links_match /disney/
201
+ assert_no_links_match /99/
202
+ assert_no_links_match /ftp/
203
+ end
183
204
 
184
205
  it "should not preserve parameters on POST" do
185
206
  request.post
@@ -294,13 +315,12 @@ describe WillPaginate::ActionView do
294
315
  end
295
316
 
296
317
  it "renders using ActionView helpers on a custom object" do
297
- helper = Object.new
298
- class << helper
318
+ helper = Class.new {
299
319
  attr_reader :controller
300
320
  include ActionView::Helpers::UrlHelper
301
321
  include Routes.url_helpers
302
322
  include WillPaginate::ActionView
303
- end
323
+ }.new
304
324
  helper.default_url_options[:controller] = 'dummy'
305
325
 
306
326
  collection = WillPaginate::Collection.new(2, 1, 3)
@@ -313,22 +333,21 @@ describe WillPaginate::ActionView do
313
333
  end
314
334
 
315
335
  it "renders using ActionDispatch helper on a custom object" do
316
- helper = Object.new
317
- class << helper
336
+ helper = Class.new {
318
337
  include ActionDispatch::Routing::UrlFor
319
338
  include Routes.url_helpers
320
339
  include WillPaginate::ActionView
321
- end
322
- helper.default_url_options[:host] = 'example.com'
323
- helper.default_url_options[:controller] = 'dummy'
324
- # helper.default_url_options[:only_path] = true
340
+ }.new
341
+ helper.default_url_options.update \
342
+ :only_path => true,
343
+ :controller => 'dummy'
325
344
 
326
345
  collection = WillPaginate::Collection.new(2, 1, 3)
327
346
  @render_output = helper.will_paginate(collection)
328
347
 
329
348
  assert_select 'a[href]', 4 do |links|
330
349
  urls = links.map {|l| l['href'] }.uniq
331
- urls.should == ['http://example.com/dummy/page/1', 'http://example.com/dummy/page/3']
350
+ urls.should == ['/dummy/page/1', '/dummy/page/3']
332
351
  end
333
352
  end
334
353
 
@@ -338,6 +357,11 @@ describe WillPaginate::ActionView do
338
357
  I18n.available_locales # triggers loading existing translations
339
358
  I18n.backend.store_translations(:en, data)
340
359
  end
360
+
361
+ # Normalizes differences between HTML::Document and Nokogiri::HTML
362
+ def text(node)
363
+ node.inner_text.gsub('&#8594;', '→').gsub('&#8592;', '←')
364
+ end
341
365
  end
342
366
 
343
367
  class AdditionalLinkAttributesRenderer < WillPaginate::ActionView::LinkRenderer
@@ -359,7 +383,7 @@ class DummyController
359
383
  include Routes.url_helpers
360
384
 
361
385
  def initialize
362
- @request = DummyRequest.new
386
+ @request = DummyRequest.new(self)
363
387
  end
364
388
 
365
389
  def params
@@ -380,13 +404,19 @@ end
380
404
 
381
405
  class DummyRequest
382
406
  attr_accessor :symbolized_path_parameters
407
+ alias :path_parameters :symbolized_path_parameters
383
408
 
384
- def initialize
409
+ def initialize(controller)
410
+ @controller = controller
385
411
  @get = true
386
412
  @params = {}
387
413
  @symbolized_path_parameters = { :controller => 'foo', :action => 'bar' }
388
414
  end
389
-
415
+
416
+ def routes
417
+ @controller._routes
418
+ end
419
+
390
420
  def get?
391
421
  @get
392
422
  end
@@ -10,6 +10,7 @@ describe WillPaginate::ViewHelpers do
10
10
  before(:all) do
11
11
  # make sure default translations aren't loaded
12
12
  I18n.load_path.clear
13
+ I18n.enforce_available_locales = false
13
14
  end
14
15
 
15
16
  before(:each) do
@@ -32,6 +33,18 @@ describe WillPaginate::ViewHelpers do
32
33
  collection = mock 'Collection', :total_pages => 1
33
34
  will_paginate(collection).should be_nil
34
35
  end
36
+
37
+ it "should call html_safe on result" do
38
+ collection = WillPaginate::Collection.new(1, 2, 4)
39
+
40
+ html = mock 'HTML'
41
+ html.expects(:html_safe).returns(html)
42
+ renderer = mock 'Renderer'
43
+ renderer.stubs(:prepare)
44
+ renderer.expects(:to_html).returns(html)
45
+
46
+ will_paginate(collection, :renderer => renderer).should eql(html)
47
+ end
35
48
  end
36
49
 
37
50
  describe "pagination_options" do