will_paginate 3.0.4 → 3.1.0

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