split 3.2.0 → 3.3.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 861fb73c59529de9a24f3a78ddd0b5cbe257eb8b
4
- data.tar.gz: dd67eac616dd9435c3a0ca448de504b3824bccb8
2
+ SHA256:
3
+ metadata.gz: ac2527f0dab7b1f6c2de3e21b02fd933eef302046e7cdd4b8f42da20a3cdf1ba
4
+ data.tar.gz: 6332eabde240b1c408ff434850b7ac520b3081bfd5fe76f7343259a8eae039b6
5
5
  SHA512:
6
- metadata.gz: d078b157629b473b654b751c18fe11ddae08486f38bce2fef2bdd71b7a8536ce048204adaf9b1e242e6cf76f1c33650db91994e34a23734ecaf4357663b750cf
7
- data.tar.gz: aa994978184569f624b9d5bb3503b93b498ef4601bb1989fe2e16eabee07a0450adc9555c02c251d70d9a73dce65e40bac0e05bf071dc8d229e4ab87c0b817ec
6
+ metadata.gz: 331863e4c78e8dabf7eb23551608be301cdd33d4c14186ab47def00485b028c05784f70009fd66d1b3e240963d1cd45b61d2233b651fd0bcbcb79e2871d2a6ad
7
+ data.tar.gz: 2c8723105824b9dfc315f88de450be5c18aa32d20e2253a25ca9fcbb6b65b082697c0782719198a2f7b401e66824597a50c3f97dece30a3c9ceccb3fcd62fa95
@@ -5,12 +5,15 @@ rvm:
5
5
  - 2.1
6
6
  - 2.2.0
7
7
  - 2.2.2
8
- - 2.4.2
8
+ - 2.4.3
9
+ - 2.5.1
9
10
 
10
11
  gemfile:
11
12
  - gemfiles/4.2.gemfile
12
13
  - gemfiles/5.0.gemfile
13
14
  - gemfiles/5.1.gemfile
15
+ - gemfiles/5.2.gemfile
16
+
14
17
 
15
18
  matrix:
16
19
  exclude:
@@ -30,6 +33,15 @@ matrix:
30
33
  gemfile: gemfiles/5.0.gemfile
31
34
  - rvm: 2.2.0
32
35
  gemfile: gemfiles/5.1.gemfile
36
+ - rvm: 1.9.3
37
+ gemfile: gemfiles/5.2.gemfile
38
+ - rvm: 2.0
39
+ gemfile: gemfiles/5.2.gemfile
40
+ - rvm: 2.1
41
+ gemfile: gemfiles/5.2.gemfile
42
+ - rvm: 2.2.0
43
+ gemfile: gemfiles/5.2.gemfile
44
+
33
45
 
34
46
  before_install:
35
47
  - gem update --system && gem install bundler
data/Appraisals CHANGED
@@ -4,10 +4,12 @@ end
4
4
 
5
5
  appraise "5.0" do
6
6
  gem "rails", "~> 5.0"
7
- gem "sinatra", git: "https://github.com/sinatra/sinatra"
8
7
  end
9
8
 
10
9
  appraise "5.1" do
11
10
  gem "rails", "~> 5.1"
12
- gem "sinatra", git: "https://github.com/sinatra/sinatra"
11
+ end
12
+
13
+ appraise "5.2" do
14
+ gem "rails", "~> 5.2"
13
15
  end
data/README.md CHANGED
@@ -5,6 +5,7 @@
5
5
  [![Code Climate](https://codeclimate.com/github/splitrb/split/badges/gpa.svg)](https://codeclimate.com/github/splitrb/split)
6
6
  [![Test Coverage](https://codeclimate.com/github/splitrb/split/badges/coverage.svg)](https://codeclimate.com/github/splitrb/split/coverage)
7
7
  [![standard-readme compliant](https://img.shields.io/badge/readme%20style-standard-brightgreen.svg?style=flat-square)](https://github.com/RichardLitt/standard-readme)
8
+ [![Open Source Helpers](https://www.codetriage.com/splitrb/split/badges/users.svg)](https://www.codetriage.com/splitrb/split)
8
9
 
9
10
  > 📈 The Rack Based A/B testing framework http://libraries.io/rubygems/split
10
11
 
@@ -388,7 +389,7 @@ run Rack::URLMap.new \
388
389
  "/split" => Split::Dashboard.new
389
390
  ```
390
391
 
391
- However, if you are using Rails 3: You can mount this inside your app routes by first adding this to the Gemfile:
392
+ However, if you are using Rails 3 or higher: You can mount this inside your app routes by first adding this to the Gemfile:
392
393
 
393
394
  ```ruby
394
395
  gem 'split', require: 'split/dashboard'
@@ -475,7 +476,7 @@ Split.configure do |config|
475
476
  # IP config
476
477
  config.ignore_ip_addresses << '81.19.48.130' # or regex: /81\.19\.48\.[0-9]+/
477
478
 
478
- # or provide your own filter functionality, the default is proc{ |request| is_robot? || is_ignored_ip_address? }
479
+ # or provide your own filter functionality, the default is proc{ |request| is_robot? || is_ignored_ip_address? || is_preview? }
479
480
  config.ignore_filter = -> (request) { CustomExcludeLogic.excludes?(request) }
480
481
  end
481
482
  ```
@@ -662,8 +663,8 @@ Once you finish one of the goals, the test is considered to be completed, and fi
662
663
  **Bad Example**: Test both how button color affects signup *and* how it affects login, at the same time. THIS WILL NOT WORK.
663
664
 
664
665
  #### Combined Experiments
665
- If you want to test how how button color affects signup *and* how it affects login, at the same time. Use combined tests
666
- Configure like so
666
+ If you want to test how button color affects signup *and* how it affects login at the same time, use combined experiments.
667
+ Configure like so:
667
668
  ```ruby
668
669
  Split.configuration.experiments = {
669
670
  :button_color_experiment => {
@@ -934,4 +935,4 @@ Please note that this project is released with a [Contributor Code of Conduct](C
934
935
 
935
936
  ## Copyright
936
937
 
937
- [MIT License](LICENSE) © 2017 [Andrew Nesbitt](https://github.com/andrew).
938
+ [MIT License](LICENSE) © 2018 [Andrew Nesbitt](https://github.com/andrew).
@@ -5,6 +5,5 @@ source "https://rubygems.org"
5
5
  gem "appraisal"
6
6
  gem "codeclimate-test-reporter"
7
7
  gem "rails", "~> 5.0"
8
- gem "sinatra", git: "https://github.com/sinatra/sinatra"
9
8
 
10
9
  gemspec path: "../"
@@ -5,6 +5,5 @@ source "https://rubygems.org"
5
5
  gem "appraisal"
6
6
  gem "codeclimate-test-reporter"
7
7
  gem "rails", "~> 5.1"
8
- gem "sinatra", git: "https://github.com/sinatra/sinatra"
9
8
 
10
9
  gemspec path: "../"
@@ -0,0 +1,9 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "appraisal"
6
+ gem "codeclimate-test-reporter"
7
+ gem "rails", "~> 5.2"
8
+
9
+ gemspec path: "../"
@@ -74,6 +74,7 @@ module Split
74
74
  'bitlybot' => 'bit.ly bot',
75
75
  'bot@linkfluence.net' => 'Linkfluence bot',
76
76
  'facebookexternalhit' => 'facebook bot',
77
+ 'Facebot' => 'Facebook crawler',
77
78
  'Feedfetcher-Google' => 'Google Feedfetcher',
78
79
  'https://developers.google.com/+/web/snippet' => 'Google+ Snippet Fetcher',
79
80
  'LinkedInBot' => 'LinkedIn bot',
@@ -3,6 +3,7 @@ require 'sinatra/base'
3
3
  require 'split'
4
4
  require 'bigdecimal'
5
5
  require 'split/dashboard/helpers'
6
+ require 'split/dashboard/pagination_helpers'
6
7
 
7
8
  module Split
8
9
  class Dashboard < Sinatra::Base
@@ -14,6 +15,7 @@ module Split
14
15
  set :method_override, true
15
16
 
16
17
  helpers Split::DashboardHelpers
18
+ helpers Split::DashboardPaginationHelpers
17
19
 
18
20
  get '/' do
19
21
  # Display experiments without a winner at the top of the dashboard
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+ require 'split/dashboard/paginator'
3
+
4
+ module Split
5
+ module DashboardPaginationHelpers
6
+ DEFAULT_PER = 10
7
+
8
+ def pagination_per
9
+ @pagination_per ||= (params[:per] || DEFAULT_PER).to_i
10
+ end
11
+
12
+ def page_number
13
+ @page_number ||= (params[:page] || 1).to_i
14
+ end
15
+
16
+ def paginated(collection)
17
+ Split::DashboardPaginator.new(collection, page_number, pagination_per).paginate
18
+ end
19
+
20
+ def pagination(collection)
21
+ html = []
22
+ html << first_page_tag if show_first_page_tag?
23
+ html << ellipsis_tag if show_first_ellipsis_tag?
24
+ html << prev_page_tag if show_prev_page_tag?
25
+ html << current_page_tag
26
+ html << next_page_tag if show_next_page_tag?(collection)
27
+ html << ellipsis_tag if show_last_ellipsis_tag?(collection)
28
+ html << last_page_tagcollection if show_last_page_tag?(collection)
29
+ html.join
30
+ end
31
+
32
+ private
33
+
34
+ def show_first_page_tag?
35
+ page_number > 2
36
+ end
37
+
38
+ def first_page_tag
39
+ %Q(<a href="#{url.chop}?page=1&per=#{pagination_per}">1</a>)
40
+ end
41
+
42
+ def show_first_ellipsis_tag?
43
+ page_number >= 4
44
+ end
45
+
46
+ def ellipsis_tag
47
+ '<span>...</span>'
48
+ end
49
+
50
+ def show_prev_page_tag?
51
+ page_number > 1
52
+ end
53
+
54
+ def prev_page_tag
55
+ %Q(<a href="#{url.chop}?page=#{page_number - 1}&per=#{pagination_per}">#{page_number - 1}</a>)
56
+ end
57
+
58
+ def current_page_tag
59
+ "<span><b>#{page_number}</b></span>"
60
+ end
61
+
62
+ def show_next_page_tag?(collection)
63
+ (page_number * pagination_per) < collection.count
64
+ end
65
+
66
+ def next_page_tag
67
+ %Q(<a href="#{url.chop}?page=#{page_number + 1}&per=#{pagination_per}">#{page_number + 1}</a>)
68
+ end
69
+
70
+ def show_last_ellipsis_tag?(collection)
71
+ (total_pages(collection) - page_number) >= 3
72
+ end
73
+
74
+ def total_pages(collection)
75
+ collection.count / pagination_per + ((collection.count % pagination_per).zero? ? 0 : 1)
76
+ end
77
+
78
+ def show_last_page_tag?(collection)
79
+ page_number < (total_pages(collection) - 1)
80
+ end
81
+
82
+ def last_page_tag(collection)
83
+ total = total_pages(collection)
84
+ %Q(<a href="#{url.chop}?page=#{total}&per=#{pagination_per}">#{total}</a>)
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+ module Split
3
+ class DashboardPaginator
4
+ def initialize(collection, page_number, per)
5
+ @collection = collection
6
+ @page_number = page_number
7
+ @per = per
8
+ end
9
+
10
+ def paginate
11
+ to = @page_number * @per
12
+ from = to - @per
13
+ @collection[from...to]
14
+ end
15
+ end
16
+ end
@@ -317,3 +317,12 @@ a.button.green:focus, button.green:focus, input[type="submit"].green:focus {
317
317
  }
318
318
 
319
319
 
320
+ .pagination {
321
+ text-align: center;
322
+ font-size: 15px;
323
+ }
324
+
325
+ .pagination a, .paginaton span {
326
+ display: inline-block;
327
+ padding: 5px;
328
+ }
@@ -6,7 +6,7 @@
6
6
  <input type="button" id="toggle-active" value="Hide active" />
7
7
  <input type="button" id="clear-filter" value="Clear filters" />
8
8
 
9
- <% @experiments.each do |experiment| %>
9
+ <% paginated(@experiments).each do |experiment| %>
10
10
  <% if experiment.goals.empty? %>
11
11
  <%= erb :_experiment, :locals => {:goal => nil, :experiment => experiment} %>
12
12
  <% else %>
@@ -16,6 +16,10 @@
16
16
  <% end %>
17
17
  <% end %>
18
18
  <% end %>
19
+
20
+ <div class="pagination">
21
+ <%= pagination(@experiments) %>
22
+ </div>
19
23
  <% else %>
20
24
  <p class="intro">No experiments have started yet, you need to define them in your code and introduce them to your users.</p>
21
25
  <p class="intro">Check out the <a href='https://github.com/splitrb/split#readme'>Readme</a> for more help getting started.</p>
@@ -122,13 +122,17 @@ module Split
122
122
  end
123
123
 
124
124
  def exclude_visitor?
125
- instance_eval(&Split.configuration.ignore_filter) || is_ignored_ip_address? || is_robot?
125
+ instance_exec(request, &Split.configuration.ignore_filter) || is_ignored_ip_address? || is_robot? || is_preview?
126
126
  end
127
127
 
128
128
  def is_robot?
129
129
  defined?(request) && request.user_agent =~ Split.configuration.robot_regex
130
130
  end
131
131
 
132
+ def is_preview?
133
+ defined?(request) && defined?(request.headers) && request.headers['x-purpose'] == 'preview'
134
+ end
135
+
132
136
  def is_ignored_ip_address?
133
137
  return false if Split.configuration.ignore_ip_addresses.empty?
134
138
 
@@ -6,6 +6,7 @@ module Split
6
6
  class CookieAdapter
7
7
 
8
8
  def initialize(context)
9
+ @context = context
9
10
  @request, @response = context.request, context.response
10
11
  @cookies = @request.cookies
11
12
  @expires = Time.now + cookie_length_config
@@ -30,13 +31,43 @@ module Split
30
31
  private
31
32
 
32
33
  def set_cookie(value = {})
33
- @response.set_cookie :split.to_s, default_options.merge(value: JSON.generate(value))
34
+ cookie_key = :split.to_s
35
+ cookie_value = default_options.merge(value: JSON.generate(value))
36
+ if action_dispatch?
37
+ # The "send" is necessary when we call ab_test from the controller
38
+ # and thus @context is a rails controller, because then "cookies" is
39
+ # a private method.
40
+ @context.send(:cookies)[cookie_key] = cookie_value
41
+ else
42
+ set_cookie_via_rack(cookie_key, cookie_value)
43
+ end
34
44
  end
35
45
 
36
46
  def default_options
37
47
  { expires: @expires, path: '/' }
38
48
  end
39
49
 
50
+ def set_cookie_via_rack(key, value)
51
+ delete_cookie_header!(@response.header, key, value)
52
+ Rack::Utils.set_cookie_header!(@response.header, key, value)
53
+ end
54
+
55
+ # Use Rack::Utils#make_delete_cookie_header after Rack 2.0.0
56
+ def delete_cookie_header!(header, key, value)
57
+ cookie_header = header['Set-Cookie']
58
+ case cookie_header
59
+ when nil, ''
60
+ cookies = []
61
+ when String
62
+ cookies = cookie_header.split("\n")
63
+ when Array
64
+ cookies = cookie_header
65
+ end
66
+
67
+ cookies.reject! { |cookie| cookie =~ /\A#{Rack::Utils.escape(key)}=/ }
68
+ header['Set-Cookie'] = cookies.join("\n")
69
+ end
70
+
40
71
  def hash
41
72
  @hash ||= begin
42
73
  if cookies = @cookies[:split.to_s]
@@ -55,6 +86,9 @@ module Split
55
86
  Split.configuration.persistence_cookie_length
56
87
  end
57
88
 
89
+ def action_dispatch?
90
+ defined?(Rails) && @response.is_a?(ActionDispatch::Response)
91
+ end
58
92
  end
59
93
  end
60
94
  end
@@ -35,9 +35,7 @@ module Split
35
35
  end
36
36
 
37
37
  def make_list_length(list_name, new_length)
38
- while list_length(list_name) > new_length
39
- remove_last_item_from_list(list_name)
40
- end
38
+ redis.ltrim(list_name, 0, new_length - 1)
41
39
  end
42
40
 
43
41
  def add_to_set(set_name, value)
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
  module Split
3
3
  MAJOR = 3
4
- MINOR = 2
4
+ MINOR = 3
5
5
  PATCH = 0
6
6
  VERSION = [MAJOR, MINOR, PATCH].join('.')
7
7
  end
@@ -0,0 +1,198 @@
1
+ require 'spec_helper'
2
+ require 'split/dashboard/pagination_helpers'
3
+
4
+ describe Split::DashboardPaginationHelpers do
5
+ include Split::DashboardPaginationHelpers
6
+
7
+ let(:url) { '/split/' }
8
+
9
+ describe '#pagination_per' do
10
+ context 'when params empty' do
11
+ let(:params) { Hash[] }
12
+
13
+ it 'returns 10' do
14
+ expect(pagination_per).to eql 10
15
+ end
16
+ end
17
+
18
+ context 'when params[:per] is 5' do
19
+ let(:params) { Hash[per: 5] }
20
+
21
+ it 'returns 5' do
22
+ expect(pagination_per).to eql 5
23
+ end
24
+ end
25
+ end
26
+
27
+ describe '#page_number' do
28
+ context 'when params empty' do
29
+ let(:params) { Hash[] }
30
+
31
+ it 'returns 1' do
32
+ expect(page_number).to eql 1
33
+ end
34
+ end
35
+
36
+ context 'when params[:page] is "2"' do
37
+ let(:params) { Hash[page: '2'] }
38
+
39
+ it 'returns 2' do
40
+ expect(page_number).to eql 2
41
+ end
42
+ end
43
+ end
44
+
45
+ describe '#paginated' do
46
+ let(:collection) { (1..20).to_a }
47
+ let(:params) { Hash[per: '5', page: '3'] }
48
+
49
+ it { expect(paginated(collection)).to eql [11, 12, 13, 14, 15] }
50
+ end
51
+
52
+ describe '#show_first_page_tag?' do
53
+ context 'when page is 1' do
54
+ it { expect(show_first_page_tag?).to be false }
55
+ end
56
+
57
+ context 'when page is 3' do
58
+ let(:params) { Hash[page: '3'] }
59
+ it { expect(show_first_page_tag?).to be true }
60
+ end
61
+ end
62
+
63
+ describe '#first_page_tag' do
64
+ it { expect(first_page_tag).to eql '<a href="/split?page=1&per=10">1</a>' }
65
+ end
66
+
67
+ describe '#show_first_ellipsis_tag?' do
68
+ context 'when page is 1' do
69
+ it { expect(show_first_ellipsis_tag?).to be false }
70
+ end
71
+
72
+ context 'when page is 4' do
73
+ let(:params) { Hash[page: '4'] }
74
+ it { expect(show_first_ellipsis_tag?).to be true }
75
+ end
76
+ end
77
+
78
+ describe '#ellipsis_tag' do
79
+ it { expect(ellipsis_tag).to eql '<span>...</span>' }
80
+ end
81
+
82
+ describe '#show_prev_page_tag?' do
83
+ context 'when page is 1' do
84
+ it { expect(show_prev_page_tag?).to be false }
85
+ end
86
+
87
+ context 'when page is 2' do
88
+ let(:params) { Hash[page: '2'] }
89
+ it { expect(show_prev_page_tag?).to be true }
90
+ end
91
+ end
92
+
93
+ describe '#prev_page_tag' do
94
+ context 'when page is 2' do
95
+ let(:params) { Hash[page: '2'] }
96
+
97
+ it do
98
+ expect(prev_page_tag).to eql '<a href="/split?page=1&per=10">1</a>'
99
+ end
100
+ end
101
+
102
+ context 'when page is 3' do
103
+ let(:params) { Hash[page: '3'] }
104
+
105
+ it do
106
+ expect(prev_page_tag).to eql '<a href="/split?page=2&per=10">2</a>'
107
+ end
108
+ end
109
+ end
110
+
111
+ describe '#show_prev_page_tag?' do
112
+ context 'when page is 1' do
113
+ it { expect(show_prev_page_tag?).to be false }
114
+ end
115
+
116
+ context 'when page is 2' do
117
+ let(:params) { Hash[page: '2'] }
118
+ it { expect(show_prev_page_tag?).to be true }
119
+ end
120
+ end
121
+
122
+ describe '#current_page_tag' do
123
+ context 'when page is 1' do
124
+ let(:params) { Hash[page: '1'] }
125
+ it { expect(current_page_tag).to eql '<span><b>1</b></span>' }
126
+ end
127
+
128
+ context 'when page is 2' do
129
+ let(:params) { Hash[page: '2'] }
130
+ it { expect(current_page_tag).to eql '<span><b>2</b></span>' }
131
+ end
132
+ end
133
+
134
+ describe '#show_next_page_tag?' do
135
+ context 'when page is 2' do
136
+ let(:params) { Hash[page: '2'] }
137
+
138
+ context 'when collection length is 20' do
139
+ let(:collection) { (1..20).to_a }
140
+ it { expect(show_next_page_tag?(collection)).to be false }
141
+ end
142
+
143
+ context 'when collection length is 25' do
144
+ let(:collection) { (1..25).to_a }
145
+ it { expect(show_next_page_tag?(collection)).to be true }
146
+ end
147
+ end
148
+ end
149
+
150
+ describe '#next_page_tag' do
151
+ context 'when page is 1' do
152
+ let(:params) { Hash[page: '1'] }
153
+ it { expect(next_page_tag).to eql '<a href="/split?page=2&per=10">2</a>' }
154
+ end
155
+
156
+ context 'when page is 2' do
157
+ let(:params) { Hash[page: '2'] }
158
+ it { expect(next_page_tag).to eql '<a href="/split?page=3&per=10">3</a>' }
159
+ end
160
+ end
161
+
162
+ describe '#total_pages' do
163
+ context 'when collection length is 30' do
164
+ let(:collection) { (1..30).to_a }
165
+ it { expect(total_pages(collection)).to eql 3 }
166
+ end
167
+
168
+ context 'when collection length is 35' do
169
+ let(:collection) { (1..35).to_a }
170
+ it { expect(total_pages(collection)).to eql 4 }
171
+ end
172
+ end
173
+
174
+ describe '#show_last_ellipsis_tag?' do
175
+ let(:collection) { (1..30).to_a }
176
+ let(:params) { Hash[per: '5', page: '2'] }
177
+ it { expect(show_last_ellipsis_tag?(collection)).to be true }
178
+ end
179
+
180
+ describe '#show_last_page_tag?' do
181
+ let(:collection) { (1..30).to_a }
182
+
183
+ context 'when page is 5/6' do
184
+ let(:params) { Hash[per: '5', page: '5'] }
185
+ it { expect(show_last_page_tag?(collection)).to be false }
186
+ end
187
+
188
+ context 'when page is 4/6' do
189
+ let(:params) { Hash[per: '5', page: '4'] }
190
+ it { expect(show_last_page_tag?(collection)).to be true }
191
+ end
192
+ end
193
+
194
+ describe '#last_page_tag' do
195
+ let(:collection) { (1..30).to_a }
196
+ it { expect(last_page_tag(collection)).to eql '<a href="/split?page=3&per=10">3</a>' }
197
+ end
198
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+ require 'spec_helper'
3
+ require 'split/dashboard/paginator'
4
+
5
+ describe Split::DashboardPaginator do
6
+ context 'when collection is 1..20' do
7
+ let(:collection) { (1..20).to_a }
8
+
9
+ context 'when per 5 for page' do
10
+ let(:per) { 5 }
11
+
12
+ it 'when page number is 1 result is [1, 2, 3, 4, 5]' do
13
+ result = Split::DashboardPaginator.new(collection, 1, per).paginate
14
+ expect(result).to eql [1, 2, 3, 4, 5]
15
+ end
16
+
17
+ it 'when page number is 2 result is [6, 7, 8, 9, 10]' do
18
+ result = Split::DashboardPaginator.new(collection, 2, per).paginate
19
+ expect(result).to eql [6, 7, 8, 9, 10]
20
+ end
21
+ end
22
+
23
+ context 'when per 10 for page' do
24
+ let(:per) { 10 }
25
+
26
+ it 'when page number is 1 result is [1..10]' do
27
+ result = Split::DashboardPaginator.new(collection, 1, per).paginate
28
+ expect(result).to eql [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
29
+ end
30
+
31
+ it 'when page number is 2 result is [10..20]' do
32
+ result = Split::DashboardPaginator.new(collection, 2, per).paginate
33
+ expect(result).to eql [11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
34
+ end
35
+ end
36
+ end
37
+ end
@@ -699,6 +699,14 @@ describe Split::Helper do
699
699
  end
700
700
  end
701
701
 
702
+ describe 'when user is previewing' do
703
+ before(:each) do
704
+ @request = OpenStruct.new(headers: { 'x-purpose' => 'preview' })
705
+ end
706
+
707
+ it_behaves_like "a disabled test"
708
+ end
709
+
702
710
  describe 'versioned experiments' do
703
711
  it "should use version zero if no version is present" do
704
712
  alternative_name = ab_test('link_color', 'blue', 'red')
@@ -3,41 +3,104 @@ require "spec_helper"
3
3
  require 'rack/test'
4
4
 
5
5
  describe Split::Persistence::CookieAdapter do
6
+ subject { described_class.new(context) }
6
7
 
7
- let(:env) { Rack::MockRequest.env_for("http://example.com:8080/") }
8
- let(:request) { Rack::Request.new(env) }
9
- let(:response) { Rack::MockResponse.new(200, {}, "") }
10
- let(:context) { double(request: request, response: response) }
11
- subject { Split::Persistence::CookieAdapter.new(context) }
8
+ shared_examples "sets cookies correctly" do
9
+ describe "#[] and #[]=" do
10
+ it "set and return the value for given key" do
11
+ subject["my_key"] = "my_value"
12
+ expect(subject["my_key"]).to eq("my_value")
13
+ end
12
14
 
13
- describe "#[] and #[]=" do
14
- it "should set and return the value for given key" do
15
- subject["my_key"] = "my_value"
16
- expect(subject["my_key"]).to eq("my_value")
15
+ it "handles invalid JSON" do
16
+ context.request.cookies[:split] = {
17
+ :value => '{"foo":2,',
18
+ :expires => Time.now
19
+ }
20
+ expect(subject["my_key"]).to be_nil
21
+ subject["my_key"] = "my_value"
22
+ expect(subject["my_key"]).to eq("my_value")
23
+ end
17
24
  end
18
- end
19
25
 
20
- describe "#delete" do
21
- it "should delete the given key" do
22
- subject["my_key"] = "my_value"
23
- subject.delete("my_key")
24
- expect(subject["my_key"]).to be_nil
26
+ describe "#delete" do
27
+ it "should delete the given key" do
28
+ subject["my_key"] = "my_value"
29
+ subject.delete("my_key")
30
+ expect(subject["my_key"]).to be_nil
31
+ end
25
32
  end
26
- end
27
33
 
28
- describe "#keys" do
29
- it "should return an array of the session's stored keys" do
30
- subject["my_key"] = "my_value"
31
- subject["my_second_key"] = "my_second_value"
32
- expect(subject.keys).to match(["my_key", "my_second_key"])
34
+ describe "#keys" do
35
+ it "should return an array of the session's stored keys" do
36
+ subject["my_key"] = "my_value"
37
+ subject["my_second_key"] = "my_second_value"
38
+ expect(subject.keys).to match(["my_key", "my_second_key"])
39
+ end
33
40
  end
34
41
  end
35
42
 
36
- it "handles invalid JSON" do
37
- context.request.cookies[:split] = { :value => '{"foo":2,', :expires => Time.now }
38
- expect(subject["my_key"]).to be_nil
39
- subject["my_key"] = "my_value"
40
- expect(subject["my_key"]).to eq("my_value")
43
+
44
+ context "when using Rack" do
45
+ let(:env) { Rack::MockRequest.env_for("http://example.com:8080/") }
46
+ let(:request) { Rack::Request.new(env) }
47
+ let(:response) { Rack::MockResponse.new(200, {}, "") }
48
+ let(:context) { double(request: request, response: response, cookies: CookiesMock.new) }
49
+
50
+ include_examples "sets cookies correctly"
51
+
52
+ it "puts multiple experiments in a single cookie" do
53
+ subject["foo"] = "FOO"
54
+ subject["bar"] = "BAR"
55
+ expect(context.response.headers["Set-Cookie"]).to match(/\Asplit=%7B%22foo%22%3A%22FOO%22%2C%22bar%22%3A%22BAR%22%7D; path=\/; expires=[a-zA-Z]{3}, \d{2} [a-zA-Z]{3} \d{4} \d{2}:\d{2}:\d{2} -0000\Z/)
56
+ end
57
+
58
+ it "ensure other added cookies are not overriden" do
59
+ context.response.set_cookie 'dummy', 'wow'
60
+ subject["foo"] = "FOO"
61
+ expect(context.response.headers["Set-Cookie"]).to include("dummy=wow")
62
+ expect(context.response.headers["Set-Cookie"]).to include("split=")
63
+ end
41
64
  end
42
65
 
66
+ context "when @context is an ActionController::Base" do
67
+ before :context do
68
+ require "rails"
69
+ require "action_controller/railtie"
70
+ end
71
+
72
+ let(:context) do
73
+ controller = controller_class.new
74
+ if controller.respond_to?(:set_request!)
75
+ controller.set_request!(ActionDispatch::Request.new({}))
76
+ else # Before rails 5.0
77
+ controller.send(:"request=", ActionDispatch::Request.new({}))
78
+ end
79
+
80
+ response = ActionDispatch::Response.new(200, {}, '').tap do |res|
81
+ res.request = controller.request
82
+ end
83
+
84
+ if controller.respond_to?(:set_response!)
85
+ controller.set_response!(response)
86
+ else # Before rails 5.0
87
+ controller.send(:set_response!, response)
88
+ end
89
+ controller
90
+ end
91
+
92
+ let(:controller_class) { Class.new(ActionController::Base) }
93
+
94
+ include_examples "sets cookies correctly"
95
+
96
+ it "puts multiple experiments in a single cookie" do
97
+ subject["foo"] = "FOO"
98
+ subject["bar"] = "BAR"
99
+ expect(subject.keys).to eq(["foo", "bar"])
100
+ expect(subject["foo"]).to eq("FOO")
101
+ expect(subject["bar"]).to eq("BAR")
102
+ cookie_jar = context.request.env["action_dispatch.cookies"]
103
+ expect(cookie_jar['split']).to eq("{\"foo\":\"FOO\",\"bar\":\"BAR\"}")
104
+ end
105
+ end
43
106
  end
@@ -15,19 +15,19 @@ RSpec.describe Split do
15
15
  Split.redis = 'redis://localhost:6379'
16
16
  expect(Split.redis).to be_a(Redis)
17
17
 
18
- client = Split.redis.client
19
- expect(client.host).to eq("localhost")
20
- expect(client.port).to eq(6379)
18
+ client = Split.redis.connection
19
+ expect(client[:host]).to eq("localhost")
20
+ expect(client[:port]).to eq(6379)
21
21
  end
22
22
 
23
23
  it 'accepts an options hash' do
24
24
  Split.redis = {host: 'localhost', port: 6379, db: 12}
25
25
  expect(Split.redis).to be_a(Redis)
26
26
 
27
- client = Split.redis.client
28
- expect(client.host).to eq("localhost")
29
- expect(client.port).to eq(6379)
30
- expect(client.db).to eq(12)
27
+ client = Split.redis.connection
28
+ expect(client[:host]).to eq("localhost")
29
+ expect(client[:port]).to eq(6379)
30
+ expect(client[:db]).to eq(12)
31
31
  end
32
32
 
33
33
  it 'accepts a valid Redis instance' do
@@ -35,10 +35,10 @@ Gem::Specification.new do |s|
35
35
  s.add_dependency 'simple-random', '>= 0.9.3'
36
36
 
37
37
  s.add_development_dependency 'bundler', '~> 1.14'
38
- s.add_development_dependency 'simplecov', '~> 0.12'
38
+ s.add_development_dependency 'simplecov', '~> 0.15'
39
39
  s.add_development_dependency 'rack-test', '~> 0.6'
40
40
  s.add_development_dependency 'rake', '~> 12'
41
- s.add_development_dependency 'rspec', '~> 3.4'
41
+ s.add_development_dependency 'rspec', '~> 3.7'
42
42
  s.add_development_dependency 'pry', '~> 0.10'
43
- s.add_development_dependency 'fakeredis', '~> 0.6.0'
43
+ s.add_development_dependency 'fakeredis', '~> 0.7'
44
44
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: split
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.2.0
4
+ version: 3.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Nesbitt
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-09-21 00:00:00.000000000 Z
11
+ date: 2018-08-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: redis
@@ -72,14 +72,14 @@ dependencies:
72
72
  requirements:
73
73
  - - "~>"
74
74
  - !ruby/object:Gem::Version
75
- version: '0.12'
75
+ version: '0.15'
76
76
  type: :development
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
- version: '0.12'
82
+ version: '0.15'
83
83
  - !ruby/object:Gem::Dependency
84
84
  name: rack-test
85
85
  requirement: !ruby/object:Gem::Requirement
@@ -114,14 +114,14 @@ dependencies:
114
114
  requirements:
115
115
  - - "~>"
116
116
  - !ruby/object:Gem::Version
117
- version: '3.4'
117
+ version: '3.7'
118
118
  type: :development
119
119
  prerelease: false
120
120
  version_requirements: !ruby/object:Gem::Requirement
121
121
  requirements:
122
122
  - - "~>"
123
123
  - !ruby/object:Gem::Version
124
- version: '3.4'
124
+ version: '3.7'
125
125
  - !ruby/object:Gem::Dependency
126
126
  name: pry
127
127
  requirement: !ruby/object:Gem::Requirement
@@ -142,14 +142,14 @@ dependencies:
142
142
  requirements:
143
143
  - - "~>"
144
144
  - !ruby/object:Gem::Version
145
- version: 0.6.0
145
+ version: '0.7'
146
146
  type: :development
147
147
  prerelease: false
148
148
  version_requirements: !ruby/object:Gem::Requirement
149
149
  requirements:
150
150
  - - "~>"
151
151
  - !ruby/object:Gem::Version
152
- version: 0.6.0
152
+ version: '0.7'
153
153
  description:
154
154
  email:
155
155
  - andrewnez@gmail.com
@@ -175,6 +175,7 @@ files:
175
175
  - gemfiles/4.2.gemfile
176
176
  - gemfiles/5.0.gemfile
177
177
  - gemfiles/5.1.gemfile
178
+ - gemfiles/5.2.gemfile
178
179
  - lib/split.rb
179
180
  - lib/split/algorithms/block_randomization.rb
180
181
  - lib/split/algorithms/weighted_sample.rb
@@ -184,6 +185,8 @@ files:
184
185
  - lib/split/configuration.rb
185
186
  - lib/split/dashboard.rb
186
187
  - lib/split/dashboard/helpers.rb
188
+ - lib/split/dashboard/pagination_helpers.rb
189
+ - lib/split/dashboard/paginator.rb
187
190
  - lib/split/dashboard/public/dashboard-filtering.js
188
191
  - lib/split/dashboard/public/dashboard.js
189
192
  - lib/split/dashboard/public/jquery-1.11.1.min.js
@@ -219,6 +222,8 @@ files:
219
222
  - spec/alternative_spec.rb
220
223
  - spec/combined_experiments_helper_spec.rb
221
224
  - spec/configuration_spec.rb
225
+ - spec/dashboard/pagination_helpers_spec.rb
226
+ - spec/dashboard/paginator_spec.rb
222
227
  - spec/dashboard_helpers_spec.rb
223
228
  - spec/dashboard_spec.rb
224
229
  - spec/encapsulated_helper_spec.rb
@@ -265,7 +270,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
265
270
  version: 2.0.0
266
271
  requirements: []
267
272
  rubyforge_project: split
268
- rubygems_version: 2.6.4
273
+ rubygems_version: 2.7.3
269
274
  signing_key:
270
275
  specification_version: 4
271
276
  summary: Rack based split testing framework
@@ -276,6 +281,8 @@ test_files:
276
281
  - spec/alternative_spec.rb
277
282
  - spec/combined_experiments_helper_spec.rb
278
283
  - spec/configuration_spec.rb
284
+ - spec/dashboard/pagination_helpers_spec.rb
285
+ - spec/dashboard/paginator_spec.rb
279
286
  - spec/dashboard_helpers_spec.rb
280
287
  - spec/dashboard_spec.rb
281
288
  - spec/encapsulated_helper_spec.rb