split 3.2.0 → 3.3.0

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