split 3.0.0 → 3.4.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 +5 -5
- data/.eslintrc +1 -1
- data/.github/ISSUE_TEMPLATE/bug_report.md +24 -0
- data/.rspec +1 -0
- data/.rubocop.yml +6 -1155
- data/.rubocop_todo.yml +679 -0
- data/.travis.yml +46 -2
- data/Appraisals +12 -1
- data/CHANGELOG.md +116 -0
- data/CODE_OF_CONDUCT.md +3 -3
- data/CONTRIBUTING.md +54 -5
- data/Gemfile +1 -0
- data/LICENSE +1 -1
- data/README.md +209 -118
- data/Rakefile +1 -0
- data/gemfiles/4.2.gemfile +1 -1
- data/gemfiles/5.0.gemfile +1 -2
- data/gemfiles/5.1.gemfile +9 -0
- data/gemfiles/5.2.gemfile +9 -0
- data/gemfiles/6.0.gemfile +9 -0
- data/lib/split/algorithms/block_randomization.rb +1 -0
- data/lib/split/alternative.rb +6 -3
- data/lib/split/combined_experiments_helper.rb +37 -0
- data/lib/split/configuration.rb +17 -2
- data/lib/split/dashboard/helpers.rb +2 -2
- data/lib/split/dashboard/pagination_helpers.rb +86 -0
- data/lib/split/dashboard/paginator.rb +16 -0
- data/lib/split/dashboard/public/style.css +9 -0
- data/lib/split/dashboard/views/index.erb +5 -1
- data/lib/split/dashboard/views/layout.erb +1 -1
- data/lib/split/dashboard.rb +6 -1
- data/lib/split/engine.rb +6 -2
- data/lib/split/experiment.rb +34 -22
- data/lib/split/goals_collection.rb +1 -0
- data/lib/split/helper.rb +17 -3
- data/lib/split/persistence/cookie_adapter.rb +53 -15
- data/lib/split/persistence/dual_adapter.rb +54 -12
- data/lib/split/redis_interface.rb +2 -3
- data/lib/split/trial.rb +4 -6
- data/lib/split/user.rb +5 -1
- data/lib/split/version.rb +1 -1
- data/lib/split.rb +9 -1
- data/spec/alternative_spec.rb +12 -0
- data/spec/combined_experiments_helper_spec.rb +57 -0
- data/spec/dashboard/pagination_helpers_spec.rb +200 -0
- data/spec/dashboard/paginator_spec.rb +37 -0
- data/spec/dashboard_helpers_spec.rb +2 -2
- data/spec/dashboard_spec.rb +37 -16
- data/spec/encapsulated_helper_spec.rb +1 -1
- data/spec/experiment_spec.rb +45 -6
- data/spec/helper_spec.rb +143 -80
- data/spec/persistence/cookie_adapter_spec.rb +90 -23
- data/spec/persistence/dual_adapter_spec.rb +160 -68
- data/spec/split_spec.rb +7 -7
- data/spec/trial_spec.rb +20 -0
- data/spec/user_spec.rb +11 -0
- data/split.gemspec +17 -7
- metadata +53 -19
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'forwardable'
|
|
4
|
-
|
|
5
3
|
module Split
|
|
6
4
|
module Persistence
|
|
7
5
|
class DualAdapter
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
def self.with_config(options={})
|
|
7
|
+
self.config.merge!(options)
|
|
8
|
+
self
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def self.config
|
|
12
|
+
@config ||= {}
|
|
13
|
+
end
|
|
10
14
|
|
|
11
15
|
def initialize(context)
|
|
12
16
|
if logged_in = self.class.config[:logged_in]
|
|
@@ -22,22 +26,60 @@ module Split
|
|
|
22
26
|
raise "Please configure :logged_out_adapter"
|
|
23
27
|
end
|
|
24
28
|
|
|
25
|
-
|
|
26
|
-
|
|
29
|
+
@fallback_to_logged_out_adapter =
|
|
30
|
+
self.class.config[:fallback_to_logged_out_adapter] || false
|
|
31
|
+
@logged_in = logged_in.call(context)
|
|
32
|
+
@logged_in_adapter = logged_in_adapter.new(context)
|
|
33
|
+
@logged_out_adapter = logged_out_adapter.new(context)
|
|
34
|
+
@active_adapter = @logged_in ? @logged_in_adapter : @logged_out_adapter
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def keys
|
|
38
|
+
if @fallback_to_logged_out_adapter
|
|
39
|
+
(@logged_in_adapter.keys + @logged_out_adapter.keys).uniq
|
|
27
40
|
else
|
|
28
|
-
@
|
|
41
|
+
@active_adapter.keys
|
|
29
42
|
end
|
|
30
43
|
end
|
|
31
44
|
|
|
32
|
-
def
|
|
33
|
-
|
|
34
|
-
|
|
45
|
+
def [](key)
|
|
46
|
+
if @fallback_to_logged_out_adapter
|
|
47
|
+
@logged_in && @logged_in_adapter[key] || @logged_out_adapter[key]
|
|
48
|
+
else
|
|
49
|
+
@active_adapter[key]
|
|
50
|
+
end
|
|
35
51
|
end
|
|
36
52
|
|
|
37
|
-
def
|
|
38
|
-
@
|
|
53
|
+
def []=(key, value)
|
|
54
|
+
if @fallback_to_logged_out_adapter
|
|
55
|
+
@logged_in_adapter[key] = value if @logged_in
|
|
56
|
+
old_value = @logged_out_adapter[key]
|
|
57
|
+
@logged_out_adapter[key] = value
|
|
58
|
+
|
|
59
|
+
decrement_participation(key, old_value) if decrement_participation?(old_value, value)
|
|
60
|
+
else
|
|
61
|
+
@active_adapter[key] = value
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def delete(key)
|
|
66
|
+
if @fallback_to_logged_out_adapter
|
|
67
|
+
@logged_in_adapter.delete(key)
|
|
68
|
+
@logged_out_adapter.delete(key)
|
|
69
|
+
else
|
|
70
|
+
@active_adapter.delete(key)
|
|
71
|
+
end
|
|
39
72
|
end
|
|
40
73
|
|
|
74
|
+
private
|
|
75
|
+
|
|
76
|
+
def decrement_participation?(old_value, value)
|
|
77
|
+
!old_value.nil? && !value.nil? && old_value != value
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def decrement_participation(key, value)
|
|
81
|
+
Split.redis.hincrby("#{key}:#{value}", 'participant_count', -1)
|
|
82
|
+
end
|
|
41
83
|
end
|
|
42
84
|
end
|
|
43
85
|
end
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
1
2
|
module Split
|
|
2
3
|
# Simplifies the interface to Redis.
|
|
3
4
|
class RedisInterface
|
|
@@ -35,9 +36,7 @@ module Split
|
|
|
35
36
|
end
|
|
36
37
|
|
|
37
38
|
def make_list_length(list_name, new_length)
|
|
38
|
-
|
|
39
|
-
remove_last_item_from_list(list_name)
|
|
40
|
-
end
|
|
39
|
+
redis.ltrim(list_name, 0, new_length - 1)
|
|
41
40
|
end
|
|
42
41
|
|
|
43
42
|
def add_to_set(set_name, value)
|
data/lib/split/trial.rb
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
module Split
|
|
3
3
|
class Trial
|
|
4
4
|
attr_accessor :experiment
|
|
5
|
-
|
|
5
|
+
attr_writer :metadata
|
|
6
6
|
|
|
7
7
|
def initialize(attrs = {})
|
|
8
8
|
self.experiment = attrs.delete(:experiment)
|
|
@@ -68,10 +68,8 @@ module Split
|
|
|
68
68
|
if exclude_user?
|
|
69
69
|
self.alternative = @experiment.control
|
|
70
70
|
else
|
|
71
|
-
|
|
72
|
-
if
|
|
73
|
-
self.alternative = value
|
|
74
|
-
else
|
|
71
|
+
self.alternative = @user[@experiment.key]
|
|
72
|
+
if alternative.nil?
|
|
75
73
|
self.alternative = @experiment.next_alternative
|
|
76
74
|
|
|
77
75
|
# Increment the number of participants since we are actually choosing a new alternative
|
|
@@ -82,7 +80,7 @@ module Split
|
|
|
82
80
|
end
|
|
83
81
|
end
|
|
84
82
|
|
|
85
|
-
@user[@experiment.key] = alternative.name if should_store_alternative?
|
|
83
|
+
@user[@experiment.key] = alternative.name if !@experiment.has_winner? && should_store_alternative?
|
|
86
84
|
@alternative_choosen = true
|
|
87
85
|
run_callback context, Split.configuration.on_trial unless @options[:disabled] || Split.configuration.disabled?
|
|
88
86
|
alternative
|
data/lib/split/user.rb
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
1
2
|
require 'forwardable'
|
|
2
3
|
|
|
3
4
|
module Split
|
|
@@ -8,9 +9,11 @@ module Split
|
|
|
8
9
|
|
|
9
10
|
def initialize(context, adapter=nil)
|
|
10
11
|
@user = adapter || Split::Persistence.adapter.new(context)
|
|
12
|
+
@cleaned_up = false
|
|
11
13
|
end
|
|
12
14
|
|
|
13
15
|
def cleanup_old_experiments!
|
|
16
|
+
return if @cleaned_up
|
|
14
17
|
keys_without_finished(user.keys).each do |key|
|
|
15
18
|
experiment = ExperimentCatalog.find key_without_version(key)
|
|
16
19
|
if experiment.nil? || experiment.has_winner? || experiment.start_time.nil?
|
|
@@ -18,6 +21,7 @@ module Split
|
|
|
18
21
|
user.delete Experiment.finished_key(key)
|
|
19
22
|
end
|
|
20
23
|
end
|
|
24
|
+
@cleaned_up = true
|
|
21
25
|
end
|
|
22
26
|
|
|
23
27
|
def max_experiments_reached?(experiment_key)
|
|
@@ -38,7 +42,7 @@ module Split
|
|
|
38
42
|
|
|
39
43
|
def active_experiments
|
|
40
44
|
experiment_pairs = {}
|
|
41
|
-
user.keys.each do |key|
|
|
45
|
+
keys_without_finished(user.keys).each do |key|
|
|
42
46
|
Metric.possible_experiments(key_without_version(key)).each do |experiment|
|
|
43
47
|
if !experiment.has_winner?
|
|
44
48
|
experiment_pairs[key_without_version(key)] = user[key]
|
data/lib/split/version.rb
CHANGED
data/lib/split.rb
CHANGED
|
@@ -13,6 +13,7 @@ require 'split/experiment_catalog'
|
|
|
13
13
|
require 'split/extensions/string'
|
|
14
14
|
require 'split/goals_collection'
|
|
15
15
|
require 'split/helper'
|
|
16
|
+
require 'split/combined_experiments_helper'
|
|
16
17
|
require 'split/metric'
|
|
17
18
|
require 'split/persistence'
|
|
18
19
|
require 'split/redis_interface'
|
|
@@ -65,4 +66,11 @@ module Split
|
|
|
65
66
|
end
|
|
66
67
|
end
|
|
67
68
|
|
|
68
|
-
|
|
69
|
+
# Check to see if being run in a Rails application. If so, wait until before_initialize to run configuration so Gems that create ENV variables have the chance to initialize first.
|
|
70
|
+
if defined?(::Rails)
|
|
71
|
+
class Railtie < Rails::Railtie
|
|
72
|
+
config.before_initialize { Split.configure {} }
|
|
73
|
+
end
|
|
74
|
+
else
|
|
75
|
+
Split.configure {}
|
|
76
|
+
end
|
data/spec/alternative_spec.rb
CHANGED
|
@@ -273,6 +273,18 @@ describe Split::Alternative do
|
|
|
273
273
|
expect(control.z_score(goal1)).to eq('N/A')
|
|
274
274
|
expect(control.z_score(goal2)).to eq('N/A')
|
|
275
275
|
end
|
|
276
|
+
|
|
277
|
+
it "should not blow up for Conversion Rates > 1" do
|
|
278
|
+
control = experiment.control
|
|
279
|
+
control.participant_count = 3474
|
|
280
|
+
control.set_completed_count(4244)
|
|
281
|
+
|
|
282
|
+
alternative2.participant_count = 3434
|
|
283
|
+
alternative2.set_completed_count(4358)
|
|
284
|
+
|
|
285
|
+
expect { control.z_score }.not_to raise_error
|
|
286
|
+
expect { alternative2.z_score }.not_to raise_error
|
|
287
|
+
end
|
|
276
288
|
end
|
|
277
289
|
|
|
278
290
|
describe "extra_info" do
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require 'spec_helper'
|
|
3
|
+
require 'split/combined_experiments_helper'
|
|
4
|
+
|
|
5
|
+
describe Split::CombinedExperimentsHelper do
|
|
6
|
+
include Split::CombinedExperimentsHelper
|
|
7
|
+
|
|
8
|
+
describe 'ab_combined_test' do
|
|
9
|
+
let!(:config_enabled) { true }
|
|
10
|
+
let!(:combined_experiments) { [:exp_1_click, :exp_1_scroll ]}
|
|
11
|
+
let!(:allow_multiple_experiments) { true }
|
|
12
|
+
|
|
13
|
+
before do
|
|
14
|
+
Split.configuration.experiments = {
|
|
15
|
+
:combined_exp_1 => {
|
|
16
|
+
:alternatives => [ {"control"=> 0.5}, {"test-alt"=> 0.5} ],
|
|
17
|
+
:metric => :my_metric,
|
|
18
|
+
:combined_experiments => combined_experiments
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
Split.configuration.enabled = config_enabled
|
|
22
|
+
Split.configuration.allow_multiple_experiments = allow_multiple_experiments
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
context 'without config enabled' do
|
|
26
|
+
let!(:config_enabled) { false }
|
|
27
|
+
|
|
28
|
+
it "raises an error" do
|
|
29
|
+
expect(lambda { ab_combined_test :combined_exp_1 }).to raise_error(Split::InvalidExperimentsFormatError )
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
context 'multiple experiments disabled' do
|
|
34
|
+
let!(:allow_multiple_experiments) { false }
|
|
35
|
+
|
|
36
|
+
it "raises an error if multiple experiments is disabled" do
|
|
37
|
+
expect(lambda { ab_combined_test :combined_exp_1 }).to raise_error(Split::InvalidExperimentsFormatError)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
context 'without combined experiments' do
|
|
42
|
+
let!(:combined_experiments) { nil }
|
|
43
|
+
|
|
44
|
+
it "raises an error" do
|
|
45
|
+
expect(lambda { ab_combined_test :combined_exp_1 }).to raise_error(Split::InvalidExperimentsFormatError )
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
it "uses same alternative for all sub experiments and returns the alternative" do
|
|
50
|
+
allow(self).to receive(:get_alternative) { "test-alt" }
|
|
51
|
+
expect(self).to receive(:ab_test).with(:exp_1_click, {"control"=>0.5}, {"test-alt"=>0.5}) { "test-alt" }
|
|
52
|
+
expect(self).to receive(:ab_test).with(:exp_1_scroll, [{"control" => 0, "test-alt" => 1}])
|
|
53
|
+
|
|
54
|
+
expect(ab_combined_test('combined_exp_1')).to eq('test-alt')
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,200 @@
|
|
|
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 the default (10)' do
|
|
14
|
+
default_per_page = Split.configuration.dashboard_pagination_default_per_page
|
|
15
|
+
expect(pagination_per).to eql default_per_page
|
|
16
|
+
expect(pagination_per).to eql 10
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
context 'when params[:per] is 5' do
|
|
21
|
+
let(:params) { Hash[per: 5] }
|
|
22
|
+
|
|
23
|
+
it 'returns 5' do
|
|
24
|
+
expect(pagination_per).to eql 5
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
describe '#page_number' do
|
|
30
|
+
context 'when params empty' do
|
|
31
|
+
let(:params) { Hash[] }
|
|
32
|
+
|
|
33
|
+
it 'returns 1' do
|
|
34
|
+
expect(page_number).to eql 1
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
context 'when params[:page] is "2"' do
|
|
39
|
+
let(:params) { Hash[page: '2'] }
|
|
40
|
+
|
|
41
|
+
it 'returns 2' do
|
|
42
|
+
expect(page_number).to eql 2
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
describe '#paginated' do
|
|
48
|
+
let(:collection) { (1..20).to_a }
|
|
49
|
+
let(:params) { Hash[per: '5', page: '3'] }
|
|
50
|
+
|
|
51
|
+
it { expect(paginated(collection)).to eql [11, 12, 13, 14, 15] }
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
describe '#show_first_page_tag?' do
|
|
55
|
+
context 'when page is 1' do
|
|
56
|
+
it { expect(show_first_page_tag?).to be false }
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
context 'when page is 3' do
|
|
60
|
+
let(:params) { Hash[page: '3'] }
|
|
61
|
+
it { expect(show_first_page_tag?).to be true }
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
describe '#first_page_tag' do
|
|
66
|
+
it { expect(first_page_tag).to eql '<a href="/split?page=1&per=10">1</a>' }
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
describe '#show_first_ellipsis_tag?' do
|
|
70
|
+
context 'when page is 1' do
|
|
71
|
+
it { expect(show_first_ellipsis_tag?).to be false }
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
context 'when page is 4' do
|
|
75
|
+
let(:params) { Hash[page: '4'] }
|
|
76
|
+
it { expect(show_first_ellipsis_tag?).to be true }
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
describe '#ellipsis_tag' do
|
|
81
|
+
it { expect(ellipsis_tag).to eql '<span>...</span>' }
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
describe '#show_prev_page_tag?' do
|
|
85
|
+
context 'when page is 1' do
|
|
86
|
+
it { expect(show_prev_page_tag?).to be false }
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
context 'when page is 2' do
|
|
90
|
+
let(:params) { Hash[page: '2'] }
|
|
91
|
+
it { expect(show_prev_page_tag?).to be true }
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
describe '#prev_page_tag' do
|
|
96
|
+
context 'when page is 2' do
|
|
97
|
+
let(:params) { Hash[page: '2'] }
|
|
98
|
+
|
|
99
|
+
it do
|
|
100
|
+
expect(prev_page_tag).to eql '<a href="/split?page=1&per=10">1</a>'
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
context 'when page is 3' do
|
|
105
|
+
let(:params) { Hash[page: '3'] }
|
|
106
|
+
|
|
107
|
+
it do
|
|
108
|
+
expect(prev_page_tag).to eql '<a href="/split?page=2&per=10">2</a>'
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
describe '#show_prev_page_tag?' do
|
|
114
|
+
context 'when page is 1' do
|
|
115
|
+
it { expect(show_prev_page_tag?).to be false }
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
context 'when page is 2' do
|
|
119
|
+
let(:params) { Hash[page: '2'] }
|
|
120
|
+
it { expect(show_prev_page_tag?).to be true }
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
describe '#current_page_tag' do
|
|
125
|
+
context 'when page is 1' do
|
|
126
|
+
let(:params) { Hash[page: '1'] }
|
|
127
|
+
it { expect(current_page_tag).to eql '<span><b>1</b></span>' }
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
context 'when page is 2' do
|
|
131
|
+
let(:params) { Hash[page: '2'] }
|
|
132
|
+
it { expect(current_page_tag).to eql '<span><b>2</b></span>' }
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
describe '#show_next_page_tag?' do
|
|
137
|
+
context 'when page is 2' do
|
|
138
|
+
let(:params) { Hash[page: '2'] }
|
|
139
|
+
|
|
140
|
+
context 'when collection length is 20' do
|
|
141
|
+
let(:collection) { (1..20).to_a }
|
|
142
|
+
it { expect(show_next_page_tag?(collection)).to be false }
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
context 'when collection length is 25' do
|
|
146
|
+
let(:collection) { (1..25).to_a }
|
|
147
|
+
it { expect(show_next_page_tag?(collection)).to be true }
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
describe '#next_page_tag' do
|
|
153
|
+
context 'when page is 1' do
|
|
154
|
+
let(:params) { Hash[page: '1'] }
|
|
155
|
+
it { expect(next_page_tag).to eql '<a href="/split?page=2&per=10">2</a>' }
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
context 'when page is 2' do
|
|
159
|
+
let(:params) { Hash[page: '2'] }
|
|
160
|
+
it { expect(next_page_tag).to eql '<a href="/split?page=3&per=10">3</a>' }
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
describe '#total_pages' do
|
|
165
|
+
context 'when collection length is 30' do
|
|
166
|
+
let(:collection) { (1..30).to_a }
|
|
167
|
+
it { expect(total_pages(collection)).to eql 3 }
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
context 'when collection length is 35' do
|
|
171
|
+
let(:collection) { (1..35).to_a }
|
|
172
|
+
it { expect(total_pages(collection)).to eql 4 }
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
describe '#show_last_ellipsis_tag?' do
|
|
177
|
+
let(:collection) { (1..30).to_a }
|
|
178
|
+
let(:params) { Hash[per: '5', page: '2'] }
|
|
179
|
+
it { expect(show_last_ellipsis_tag?(collection)).to be true }
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
describe '#show_last_page_tag?' do
|
|
183
|
+
let(:collection) { (1..30).to_a }
|
|
184
|
+
|
|
185
|
+
context 'when page is 5/6' do
|
|
186
|
+
let(:params) { Hash[per: '5', page: '5'] }
|
|
187
|
+
it { expect(show_last_page_tag?(collection)).to be false }
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
context 'when page is 4/6' do
|
|
191
|
+
let(:params) { Hash[per: '5', page: '4'] }
|
|
192
|
+
it { expect(show_last_page_tag?(collection)).to be true }
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
describe '#last_page_tag' do
|
|
197
|
+
let(:collection) { (1..30).to_a }
|
|
198
|
+
it { expect(last_page_tag(collection)).to eql '<a href="/split?page=3&per=10">3</a>' }
|
|
199
|
+
end
|
|
200
|
+
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
|
|
@@ -27,11 +27,11 @@ describe Split::DashboardHelpers do
|
|
|
27
27
|
|
|
28
28
|
describe '#round' do
|
|
29
29
|
it 'can round number strings' do
|
|
30
|
-
expect(round('3.1415')).to eq BigDecimal
|
|
30
|
+
expect(round('3.1415')).to eq BigDecimal('3.14')
|
|
31
31
|
end
|
|
32
32
|
|
|
33
33
|
it 'can round number strings for precsion' do
|
|
34
|
-
expect(round('3.1415', 1)).to eq BigDecimal
|
|
34
|
+
expect(round('3.1415', 1)).to eq BigDecimal('3.1')
|
|
35
35
|
end
|
|
36
36
|
|
|
37
37
|
it 'can handle invalid number strings' do
|
data/spec/dashboard_spec.rb
CHANGED
|
@@ -29,6 +29,10 @@ describe Split::Dashboard do
|
|
|
29
29
|
let(:red_link) { link("red") }
|
|
30
30
|
let(:blue_link) { link("blue") }
|
|
31
31
|
|
|
32
|
+
before(:each) do
|
|
33
|
+
Split.configuration.beta_probability_simulations = 1
|
|
34
|
+
end
|
|
35
|
+
|
|
32
36
|
it "should respond to /" do
|
|
33
37
|
get '/'
|
|
34
38
|
expect(last_response).to be_ok
|
|
@@ -74,17 +78,39 @@ describe Split::Dashboard do
|
|
|
74
78
|
end
|
|
75
79
|
|
|
76
80
|
describe "force alternative" do
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
81
|
+
context "initial version" do
|
|
82
|
+
let!(:user) do
|
|
83
|
+
Split::User.new(@app, { experiment.name => 'red' })
|
|
84
|
+
end
|
|
80
85
|
|
|
81
|
-
|
|
82
|
-
|
|
86
|
+
before do
|
|
87
|
+
allow(Split::User).to receive(:new).and_return(user)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
it "should set current user's alternative" do
|
|
91
|
+
blue_link.participant_count = 7
|
|
92
|
+
post "/force_alternative?experiment=#{experiment.name}", alternative: "blue"
|
|
93
|
+
expect(user[experiment.key]).to eq("blue")
|
|
94
|
+
expect(blue_link.participant_count).to eq(8)
|
|
95
|
+
end
|
|
83
96
|
end
|
|
84
97
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
98
|
+
context "incremented version" do
|
|
99
|
+
let!(:user) do
|
|
100
|
+
experiment.increment_version
|
|
101
|
+
Split::User.new(@app, { "#{experiment.name}:#{experiment.version}" => 'red' })
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
before do
|
|
105
|
+
allow(Split::User).to receive(:new).and_return(user)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
it "should set current user's alternative" do
|
|
109
|
+
blue_link.participant_count = 7
|
|
110
|
+
post "/force_alternative?experiment=#{experiment.name}", alternative: "blue"
|
|
111
|
+
expect(user[experiment.key]).to eq("blue")
|
|
112
|
+
expect(blue_link.participant_count).to eq(8)
|
|
113
|
+
end
|
|
88
114
|
end
|
|
89
115
|
end
|
|
90
116
|
|
|
@@ -120,7 +146,7 @@ describe Split::Dashboard do
|
|
|
120
146
|
it "removes winner" do
|
|
121
147
|
post "/reopen?experiment=#{experiment.name}"
|
|
122
148
|
|
|
123
|
-
expect(experiment).to_not have_winner
|
|
149
|
+
expect(Split::ExperimentCatalog.find(experiment.name)).to_not have_winner
|
|
124
150
|
end
|
|
125
151
|
|
|
126
152
|
it "keeps existing stats" do
|
|
@@ -167,19 +193,14 @@ describe Split::Dashboard do
|
|
|
167
193
|
end
|
|
168
194
|
|
|
169
195
|
it "should display the start date" do
|
|
170
|
-
|
|
171
|
-
expect(Time).to receive(:now).at_least(:once).and_return(experiment_start_time)
|
|
172
|
-
experiment
|
|
196
|
+
experiment.start
|
|
173
197
|
|
|
174
198
|
get '/'
|
|
175
199
|
|
|
176
|
-
expect(last_response.body).to include(
|
|
200
|
+
expect(last_response.body).to include("<small>#{experiment.start_time.strftime('%Y-%m-%d')}</small>")
|
|
177
201
|
end
|
|
178
202
|
|
|
179
203
|
it "should handle experiments without a start date" do
|
|
180
|
-
experiment_start_time = Time.parse('2011-07-07')
|
|
181
|
-
expect(Time).to receive(:now).at_least(:once).and_return(experiment_start_time)
|
|
182
|
-
|
|
183
204
|
Split.redis.hdel(:experiment_start_times, experiment.name)
|
|
184
205
|
|
|
185
206
|
get '/'
|