split 3.4.0 → 4.0.1
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 +4 -4
- data/.github/FUNDING.yml +1 -0
- data/.github/dependabot.yml +7 -0
- data/.github/workflows/ci.yml +71 -0
- data/.rubocop.yml +177 -1
- data/.rubocop_todo.yml +40 -493
- data/CHANGELOG.md +41 -0
- data/Gemfile +1 -0
- data/README.md +26 -6
- data/Rakefile +1 -0
- data/gemfiles/{4.2.gemfile → 6.1.gemfile} +1 -1
- data/gemfiles/7.0.gemfile +9 -0
- data/lib/split/algorithms/block_randomization.rb +1 -0
- data/lib/split/algorithms/weighted_sample.rb +2 -1
- data/lib/split/algorithms/whiplash.rb +3 -2
- data/lib/split/alternative.rb +1 -0
- data/lib/split/cache.rb +28 -0
- data/lib/split/combined_experiments_helper.rb +1 -0
- data/lib/split/configuration.rb +8 -12
- data/lib/split/dashboard/helpers.rb +1 -0
- data/lib/split/dashboard/pagination_helpers.rb +1 -0
- data/lib/split/dashboard/paginator.rb +1 -0
- data/lib/split/dashboard/public/dashboard.js +10 -0
- data/lib/split/dashboard/public/style.css +5 -0
- data/lib/split/dashboard/views/_controls.erb +13 -0
- data/lib/split/dashboard.rb +17 -2
- data/lib/split/encapsulated_helper.rb +3 -2
- data/lib/split/engine.rb +5 -4
- data/lib/split/exceptions.rb +1 -0
- data/lib/split/experiment.rb +82 -60
- data/lib/split/experiment_catalog.rb +1 -3
- data/lib/split/extensions/string.rb +1 -0
- data/lib/split/goals_collection.rb +1 -0
- data/lib/split/helper.rb +26 -7
- data/lib/split/metric.rb +2 -1
- data/lib/split/persistence/cookie_adapter.rb +6 -1
- data/lib/split/persistence/redis_adapter.rb +5 -0
- data/lib/split/persistence/session_adapter.rb +1 -0
- data/lib/split/persistence.rb +4 -2
- data/lib/split/redis_interface.rb +8 -28
- data/lib/split/trial.rb +20 -10
- data/lib/split/user.rb +15 -3
- data/lib/split/version.rb +2 -4
- data/lib/split/zscore.rb +1 -0
- data/lib/split.rb +9 -3
- data/spec/alternative_spec.rb +1 -1
- data/spec/cache_spec.rb +88 -0
- data/spec/configuration_spec.rb +17 -15
- data/spec/dashboard_spec.rb +45 -5
- data/spec/encapsulated_helper_spec.rb +1 -1
- data/spec/experiment_spec.rb +78 -13
- data/spec/goals_collection_spec.rb +1 -1
- data/spec/helper_spec.rb +68 -32
- data/spec/persistence/cookie_adapter_spec.rb +1 -1
- data/spec/persistence/redis_adapter_spec.rb +9 -0
- data/spec/redis_interface_spec.rb +0 -69
- data/spec/spec_helper.rb +5 -6
- data/spec/trial_spec.rb +45 -19
- data/spec/user_spec.rb +34 -3
- data/split.gemspec +7 -7
- metadata +27 -35
- data/.travis.yml +0 -60
data/lib/split.rb
CHANGED
@@ -1,10 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require 'redis'
|
3
4
|
|
4
5
|
require 'split/algorithms/block_randomization'
|
5
6
|
require 'split/algorithms/weighted_sample'
|
6
7
|
require 'split/algorithms/whiplash'
|
7
8
|
require 'split/alternative'
|
9
|
+
require 'split/cache'
|
8
10
|
require 'split/configuration'
|
9
11
|
require 'split/encapsulated_helper'
|
10
12
|
require 'split/exceptions'
|
@@ -35,9 +37,9 @@ module Split
|
|
35
37
|
# `Redis::DistRedis`, or `Redis::Namespace`.
|
36
38
|
def redis=(server)
|
37
39
|
@redis = if server.is_a?(String)
|
38
|
-
Redis.new(:
|
40
|
+
Redis.new(url: server)
|
39
41
|
elsif server.is_a?(Hash)
|
40
|
-
Redis.new(server
|
42
|
+
Redis.new(server)
|
41
43
|
elsif server.respond_to?(:smembers)
|
42
44
|
server
|
43
45
|
else
|
@@ -64,11 +66,15 @@ module Split
|
|
64
66
|
self.configuration ||= Configuration.new
|
65
67
|
yield(configuration)
|
66
68
|
end
|
69
|
+
|
70
|
+
def cache(namespace, key, &block)
|
71
|
+
Split::Cache.fetch(namespace, key, &block)
|
72
|
+
end
|
67
73
|
end
|
68
74
|
|
69
75
|
# 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
76
|
if defined?(::Rails)
|
71
|
-
class Railtie < Rails::Railtie
|
77
|
+
class Split::Railtie < Rails::Railtie
|
72
78
|
config.before_initialize { Split.configure {} }
|
73
79
|
end
|
74
80
|
else
|
data/spec/alternative_spec.rb
CHANGED
@@ -126,7 +126,7 @@ describe Split::Alternative do
|
|
126
126
|
|
127
127
|
it "should save to redis" do
|
128
128
|
alternative.save
|
129
|
-
expect(Split.redis.exists('basket_text:Basket')).to be true
|
129
|
+
expect(Split.redis.exists?('basket_text:Basket')).to be true
|
130
130
|
end
|
131
131
|
|
132
132
|
it "should increment participation count" do
|
data/spec/cache_spec.rb
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
describe Split::Cache do
|
5
|
+
|
6
|
+
let(:namespace) { :test_namespace }
|
7
|
+
let(:key) { :test_key }
|
8
|
+
let(:now) { 1606189017 }
|
9
|
+
|
10
|
+
before { allow(Time).to receive(:now).and_return(now) }
|
11
|
+
|
12
|
+
describe 'clear' do
|
13
|
+
|
14
|
+
before { Split.configuration.cache = true }
|
15
|
+
|
16
|
+
it 'clears the cache' do
|
17
|
+
expect(Time).to receive(:now).and_return(now).exactly(2).times
|
18
|
+
Split::Cache.fetch(namespace, key) { Time.now }
|
19
|
+
Split::Cache.clear
|
20
|
+
Split::Cache.fetch(namespace, key) { Time.now }
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe 'clear_key' do
|
25
|
+
before { Split.configuration.cache = true }
|
26
|
+
|
27
|
+
it 'clears the cache' do
|
28
|
+
expect(Time).to receive(:now).and_return(now).exactly(3).times
|
29
|
+
Split::Cache.fetch(namespace, :key1) { Time.now }
|
30
|
+
Split::Cache.fetch(namespace, :key2) { Time.now }
|
31
|
+
Split::Cache.clear_key(:key1)
|
32
|
+
|
33
|
+
Split::Cache.fetch(namespace, :key1) { Time.now }
|
34
|
+
Split::Cache.fetch(namespace, :key2) { Time.now }
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe 'fetch' do
|
39
|
+
|
40
|
+
subject { Split::Cache.fetch(namespace, key) { Time.now } }
|
41
|
+
|
42
|
+
context 'when cache disabled' do
|
43
|
+
|
44
|
+
before { Split.configuration.cache = false }
|
45
|
+
|
46
|
+
it 'returns the yield' do
|
47
|
+
expect(subject).to eql(now)
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'yields every time' do
|
51
|
+
expect(Time).to receive(:now).and_return(now).exactly(2).times
|
52
|
+
Split::Cache.fetch(namespace, key) { Time.now }
|
53
|
+
Split::Cache.fetch(namespace, key) { Time.now }
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
context 'when cache enabled' do
|
58
|
+
|
59
|
+
before { Split.configuration.cache = true }
|
60
|
+
|
61
|
+
it 'returns the yield' do
|
62
|
+
expect(subject).to eql(now)
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'yields once' do
|
66
|
+
expect(Time).to receive(:now).and_return(now).once
|
67
|
+
Split::Cache.fetch(namespace, key) { Time.now }
|
68
|
+
Split::Cache.fetch(namespace, key) { Time.now }
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'honors namespace' do
|
72
|
+
expect(Split::Cache.fetch(:a, key) { :a }).to eql(:a)
|
73
|
+
expect(Split::Cache.fetch(:b, key) { :b }).to eql(:b)
|
74
|
+
|
75
|
+
expect(Split::Cache.fetch(:a, key) { :a }).to eql(:a)
|
76
|
+
expect(Split::Cache.fetch(:b, key) { :b }).to eql(:b)
|
77
|
+
end
|
78
|
+
|
79
|
+
it 'honors key' do
|
80
|
+
expect(Split::Cache.fetch(namespace, :a) { :a }).to eql(:a)
|
81
|
+
expect(Split::Cache.fetch(namespace, :b) { :b }).to eql(:b)
|
82
|
+
|
83
|
+
expect(Split::Cache.fetch(namespace, :a) { :a }).to eql(:a)
|
84
|
+
expect(Split::Cache.fetch(namespace, :b) { :b }).to eql(:b)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
data/spec/configuration_spec.rb
CHANGED
@@ -212,23 +212,12 @@ describe Split::Configuration do
|
|
212
212
|
expect(@config.normalized_experiments).to eq({:my_experiment=>{:alternatives=>[{"control_opt"=>0.67}, [{"second_opt"=>0.1}, {"third_opt"=>0.23}]]}})
|
213
213
|
end
|
214
214
|
|
215
|
-
context 'redis_url configuration [DEPRECATED]' do
|
216
|
-
it 'should warn on set and assign to #redis' do
|
217
|
-
expect(@config).to receive(:warn).with(/\[DEPRECATED\]/) { nil }
|
218
|
-
@config.redis_url = 'example_url'
|
219
|
-
expect(@config.redis).to eq('example_url')
|
220
|
-
end
|
221
|
-
|
222
|
-
it 'should warn on get and return #redis' do
|
223
|
-
expect(@config).to receive(:warn).with(/\[DEPRECATED\]/) { nil }
|
224
|
-
@config.redis = 'example_url'
|
225
|
-
expect(@config.redis_url).to eq('example_url')
|
226
|
-
end
|
227
|
-
end
|
228
|
-
|
229
215
|
context "redis configuration" do
|
230
216
|
it "should default to local redis server" do
|
231
|
-
|
217
|
+
old_redis_url = ENV['REDIS_URL']
|
218
|
+
ENV.delete('REDIS_URL')
|
219
|
+
expect(Split::Configuration.new.redis).to eq("redis://localhost:6379")
|
220
|
+
ENV['REDIS_URL'] = old_redis_url
|
232
221
|
end
|
233
222
|
|
234
223
|
it "should allow for redis url to be configured" do
|
@@ -238,8 +227,10 @@ describe Split::Configuration do
|
|
238
227
|
|
239
228
|
context "provided REDIS_URL environment variable" do
|
240
229
|
it "should use the ENV variable" do
|
230
|
+
old_redis_url = ENV['REDIS_URL']
|
241
231
|
ENV['REDIS_URL'] = "env_redis_url"
|
242
232
|
expect(Split::Configuration.new.redis).to eq("env_redis_url")
|
233
|
+
ENV['REDIS_URL'] = old_redis_url
|
243
234
|
end
|
244
235
|
end
|
245
236
|
end
|
@@ -255,4 +246,15 @@ describe Split::Configuration do
|
|
255
246
|
end
|
256
247
|
end
|
257
248
|
|
249
|
+
context "persistence cookie domain" do
|
250
|
+
it "should default to nil" do
|
251
|
+
expect(@config.persistence_cookie_domain).to eq(nil)
|
252
|
+
end
|
253
|
+
|
254
|
+
it "should allow the persistence cookie domain to be configured" do
|
255
|
+
@config.persistence_cookie_domain = '.acme.com'
|
256
|
+
expect(@config.persistence_cookie_domain).to eq('.acme.com')
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
258
260
|
end
|
data/spec/dashboard_spec.rb
CHANGED
@@ -6,8 +6,16 @@ require 'split/dashboard'
|
|
6
6
|
describe Split::Dashboard do
|
7
7
|
include Rack::Test::Methods
|
8
8
|
|
9
|
+
class TestDashboard < Split::Dashboard
|
10
|
+
include Split::Helper
|
11
|
+
|
12
|
+
get '/my_experiment' do
|
13
|
+
ab_test(params[:experiment], 'blue', 'red')
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
9
17
|
def app
|
10
|
-
@app ||=
|
18
|
+
@app ||= TestDashboard
|
11
19
|
end
|
12
20
|
|
13
21
|
def link(color)
|
@@ -90,8 +98,17 @@ describe Split::Dashboard do
|
|
90
98
|
it "should set current user's alternative" do
|
91
99
|
blue_link.participant_count = 7
|
92
100
|
post "/force_alternative?experiment=#{experiment.name}", alternative: "blue"
|
93
|
-
|
94
|
-
|
101
|
+
|
102
|
+
get "/my_experiment?experiment=#{experiment.name}"
|
103
|
+
expect(last_response.body).to include("blue")
|
104
|
+
end
|
105
|
+
|
106
|
+
it "should not modify an existing user" do
|
107
|
+
blue_link.participant_count = 7
|
108
|
+
post "/force_alternative?experiment=#{experiment.name}", alternative: "blue"
|
109
|
+
|
110
|
+
expect(user[experiment.key]).to eq("red")
|
111
|
+
expect(blue_link.participant_count).to eq(7)
|
95
112
|
end
|
96
113
|
end
|
97
114
|
|
@@ -108,8 +125,9 @@ describe Split::Dashboard do
|
|
108
125
|
it "should set current user's alternative" do
|
109
126
|
blue_link.participant_count = 7
|
110
127
|
post "/force_alternative?experiment=#{experiment.name}", alternative: "blue"
|
111
|
-
|
112
|
-
|
128
|
+
|
129
|
+
get "/my_experiment?experiment=#{experiment.name}"
|
130
|
+
expect(last_response.body).to include("blue")
|
113
131
|
end
|
114
132
|
end
|
115
133
|
end
|
@@ -161,6 +179,28 @@ describe Split::Dashboard do
|
|
161
179
|
end
|
162
180
|
end
|
163
181
|
|
182
|
+
describe "update cohorting" do
|
183
|
+
it "calls enable of cohorting when action is enable" do
|
184
|
+
post "/update_cohorting?experiment=#{experiment.name}", { "cohorting_action": "enable" }
|
185
|
+
|
186
|
+
expect(experiment.cohorting_disabled?).to eq false
|
187
|
+
end
|
188
|
+
|
189
|
+
it "calls disable of cohorting when action is disable" do
|
190
|
+
post "/update_cohorting?experiment=#{experiment.name}", { "cohorting_action": "disable" }
|
191
|
+
|
192
|
+
expect(experiment.cohorting_disabled?).to eq true
|
193
|
+
end
|
194
|
+
|
195
|
+
it "calls neither enable or disable cohorting when passed invalid action" do
|
196
|
+
previous_value = experiment.cohorting_disabled?
|
197
|
+
|
198
|
+
post "/update_cohorting?experiment=#{experiment.name}", { "cohorting_action": "other" }
|
199
|
+
|
200
|
+
expect(experiment.cohorting_disabled?).to eq previous_value
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
164
204
|
it "should reset an experiment" do
|
165
205
|
red_link.participant_count = 5
|
166
206
|
blue_link.participant_count = 7
|
@@ -21,7 +21,7 @@ describe Split::EncapsulatedHelper do
|
|
21
21
|
end
|
22
22
|
|
23
23
|
it "calls the block with selected alternative" do
|
24
|
-
expect{|block| ab_test('link_color', 'red', 'red', &block) }.to yield_with_args('red',
|
24
|
+
expect{|block| ab_test('link_color', 'red', 'red', &block) }.to yield_with_args('red', {})
|
25
25
|
end
|
26
26
|
|
27
27
|
context "inside a view" do
|
data/spec/experiment_spec.rb
CHANGED
@@ -35,15 +35,9 @@ describe Split::Experiment do
|
|
35
35
|
expect(experiment.resettable).to be_truthy
|
36
36
|
end
|
37
37
|
|
38
|
-
it "should be resettable when loading from configuration" do
|
39
|
-
allow(Split.configuration).to receive(:experiment_for).with('some_experiment') { { alternatives: %w(a b) } }
|
40
|
-
|
41
|
-
expect(Split::Experiment.new('some_experiment')).to be_resettable
|
42
|
-
end
|
43
|
-
|
44
38
|
it "should save to redis" do
|
45
39
|
experiment.save
|
46
|
-
expect(Split.redis.exists('basket_text')).to be true
|
40
|
+
expect(Split.redis.exists?('basket_text')).to be true
|
47
41
|
end
|
48
42
|
|
49
43
|
it "should save the start time to redis" do
|
@@ -91,7 +85,7 @@ describe Split::Experiment do
|
|
91
85
|
it "should not create duplicates when saving multiple times" do
|
92
86
|
experiment.save
|
93
87
|
experiment.save
|
94
|
-
expect(Split.redis.exists('basket_text')).to be true
|
88
|
+
expect(Split.redis.exists?('basket_text')).to be true
|
95
89
|
expect(Split.redis.lrange('basket_text', 0, -1)).to eq(['{"Basket":1}', '{"Cart":1}'])
|
96
90
|
end
|
97
91
|
|
@@ -124,6 +118,23 @@ describe Split::Experiment do
|
|
124
118
|
experiment = Split::Experiment.new('basket_text', :alternatives => ['Basket', "Cart"], :resettable => false)
|
125
119
|
expect(experiment.resettable).to be_falsey
|
126
120
|
end
|
121
|
+
|
122
|
+
context 'from configuration' do
|
123
|
+
let(:experiment_name) { :my_experiment }
|
124
|
+
let(:experiments) do
|
125
|
+
{
|
126
|
+
experiment_name => {
|
127
|
+
:alternatives => ['Control Opt', 'Alt one']
|
128
|
+
}
|
129
|
+
}
|
130
|
+
end
|
131
|
+
|
132
|
+
before { Split.configuration.experiments = experiments }
|
133
|
+
|
134
|
+
it 'assigns default values to the experiment' do
|
135
|
+
expect(Split::Experiment.new(experiment_name).resettable).to eq(true)
|
136
|
+
end
|
137
|
+
end
|
127
138
|
end
|
128
139
|
|
129
140
|
describe 'persistent configuration' do
|
@@ -140,10 +151,23 @@ describe Split::Experiment do
|
|
140
151
|
|
141
152
|
describe '#metadata' do
|
142
153
|
let(:experiment) { Split::Experiment.new('basket_text', :alternatives => ['Basket', "Cart"], :algorithm => Split::Algorithms::Whiplash, :metadata => meta) }
|
154
|
+
let(:meta) { { a: 'b' }}
|
155
|
+
|
156
|
+
before do
|
157
|
+
experiment.save
|
158
|
+
end
|
159
|
+
|
160
|
+
it "should delete the key when metadata is removed" do
|
161
|
+
experiment.metadata = nil
|
162
|
+
experiment.save
|
163
|
+
|
164
|
+
expect(Split.redis.exists?(experiment.metadata_key)).to be_falsey
|
165
|
+
end
|
166
|
+
|
143
167
|
context 'simple hash' do
|
144
168
|
let(:meta) { { 'basket' => 'a', 'cart' => 'b' } }
|
169
|
+
|
145
170
|
it "should persist metadata in redis" do
|
146
|
-
experiment.save
|
147
171
|
e = Split::ExperimentCatalog.find('basket_text')
|
148
172
|
expect(e).to eq(experiment)
|
149
173
|
expect(e.metadata).to eq(meta)
|
@@ -153,7 +177,6 @@ describe Split::Experiment do
|
|
153
177
|
context 'nested hash' do
|
154
178
|
let(:meta) { { 'basket' => { 'one' => 'two' }, 'cart' => 'b' } }
|
155
179
|
it "should persist metadata in redis" do
|
156
|
-
experiment.save
|
157
180
|
e = Split::ExperimentCatalog.find('basket_text')
|
158
181
|
expect(e).to eq(experiment)
|
159
182
|
expect(e.metadata).to eq(meta)
|
@@ -186,7 +209,7 @@ describe Split::Experiment do
|
|
186
209
|
experiment.save
|
187
210
|
|
188
211
|
experiment.delete
|
189
|
-
expect(Split.redis.exists('link_color')).to be false
|
212
|
+
expect(Split.redis.exists?('link_color')).to be false
|
190
213
|
expect(Split::ExperimentCatalog.find('link_color')).to be_nil
|
191
214
|
end
|
192
215
|
|
@@ -212,8 +235,14 @@ describe Split::Experiment do
|
|
212
235
|
experiment.delete
|
213
236
|
expect(experiment.start_time).to be_nil
|
214
237
|
end
|
215
|
-
end
|
216
238
|
|
239
|
+
it "should default cohorting back to false" do
|
240
|
+
experiment.disable_cohorting
|
241
|
+
expect(experiment.cohorting_disabled?).to eq(true)
|
242
|
+
experiment.delete
|
243
|
+
expect(experiment.cohorting_disabled?).to eq(false)
|
244
|
+
end
|
245
|
+
end
|
217
246
|
|
218
247
|
describe 'winner' do
|
219
248
|
it "should have no winner initially" do
|
@@ -222,12 +251,17 @@ describe Split::Experiment do
|
|
222
251
|
end
|
223
252
|
|
224
253
|
describe 'winner=' do
|
225
|
-
it
|
254
|
+
it 'should allow you to specify a winner' do
|
226
255
|
experiment.save
|
227
256
|
experiment.winner = 'red'
|
228
257
|
expect(experiment.winner.name).to eq('red')
|
229
258
|
end
|
230
259
|
|
260
|
+
it 'should call the on_experiment_winner_choose hook' do
|
261
|
+
expect(Split.configuration.on_experiment_winner_choose).to receive(:call)
|
262
|
+
experiment.winner = 'green'
|
263
|
+
end
|
264
|
+
|
231
265
|
context 'when has_winner state is memoized' do
|
232
266
|
before { expect(experiment).to_not have_winner }
|
233
267
|
|
@@ -376,6 +410,22 @@ describe Split::Experiment do
|
|
376
410
|
end
|
377
411
|
end
|
378
412
|
|
413
|
+
describe "#cohorting_disabled?" do
|
414
|
+
it "returns false when nothing has been configured" do
|
415
|
+
expect(experiment.cohorting_disabled?).to eq false
|
416
|
+
end
|
417
|
+
|
418
|
+
it "returns true when enable_cohorting is performed" do
|
419
|
+
experiment.enable_cohorting
|
420
|
+
expect(experiment.cohorting_disabled?).to eq false
|
421
|
+
end
|
422
|
+
|
423
|
+
it "returns false when nothing has been configured" do
|
424
|
+
experiment.disable_cohorting
|
425
|
+
expect(experiment.cohorting_disabled?).to eq true
|
426
|
+
end
|
427
|
+
end
|
428
|
+
|
379
429
|
describe 'changing an existing experiment' do
|
380
430
|
def same_but_different_alternative
|
381
431
|
Split::ExperimentCatalog.find_or_create('link_color', 'blue', 'yellow', 'orange')
|
@@ -398,6 +448,21 @@ describe Split::Experiment do
|
|
398
448
|
expect(same_experiment_again.version).to eq(1)
|
399
449
|
end
|
400
450
|
|
451
|
+
context "when metadata is changed" do
|
452
|
+
it "should increase version" do
|
453
|
+
experiment.save
|
454
|
+
experiment.metadata = { 'foo' => 'bar' }
|
455
|
+
|
456
|
+
expect { experiment.save }.to change { experiment.version }.by(1)
|
457
|
+
end
|
458
|
+
|
459
|
+
it "does not increase version" do
|
460
|
+
experiment.metadata = nil
|
461
|
+
experiment.save
|
462
|
+
expect { experiment.save }.to change { experiment.version }.by(0)
|
463
|
+
end
|
464
|
+
end
|
465
|
+
|
401
466
|
context 'when experiment configuration is changed' do
|
402
467
|
let(:reset_manually) { false }
|
403
468
|
|
data/spec/helper_spec.rb
CHANGED
@@ -229,13 +229,15 @@ describe Split::Helper do
|
|
229
229
|
|
230
230
|
context "when user already has experiment" do
|
231
231
|
let(:mock_user){ Split::User.new(self, {'test_0' => 'test-alt'}) }
|
232
|
-
|
232
|
+
|
233
|
+
before do
|
233
234
|
Split.configure do |config|
|
234
235
|
config.allow_multiple_experiments = 'control'
|
235
236
|
end
|
237
|
+
|
236
238
|
Split::ExperimentCatalog.find_or_initialize('test_0', 'control', 'test-alt').save
|
237
239
|
Split::ExperimentCatalog.find_or_initialize('test_1', 'control', 'test-alt').save
|
238
|
-
|
240
|
+
end
|
239
241
|
|
240
242
|
it "should restore previously selected alternative" do
|
241
243
|
expect(ab_user.active_experiments.size).to eq 1
|
@@ -243,6 +245,16 @@ describe Split::Helper do
|
|
243
245
|
expect(ab_test(:test_0, {'control' => 1}, {"test-alt" => 100})).to eq 'test-alt'
|
244
246
|
end
|
245
247
|
|
248
|
+
it "should select the correct alternatives after experiment resets" do
|
249
|
+
experiment = Split::ExperimentCatalog.find(:test_0)
|
250
|
+
experiment.reset
|
251
|
+
mock_user[experiment.key] = 'test-alt'
|
252
|
+
|
253
|
+
expect(ab_user.active_experiments.size).to eq 1
|
254
|
+
expect(ab_test(:test_0, {'control' => 100}, {"test-alt" => 1})).to eq 'test-alt'
|
255
|
+
expect(ab_test(:test_0, {'control' => 0}, {"test-alt" => 100})).to eq 'test-alt'
|
256
|
+
end
|
257
|
+
|
246
258
|
it "lets override existing choice" do
|
247
259
|
pending "this requires user store reset on first call not depending on whelther it is current trial"
|
248
260
|
@params = { 'ab_test' => { 'test_1' => 'test-alt' } }
|
@@ -265,33 +277,63 @@ describe Split::Helper do
|
|
265
277
|
end
|
266
278
|
|
267
279
|
describe 'metadata' do
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
:
|
272
|
-
|
273
|
-
|
280
|
+
context 'is defined' do
|
281
|
+
before do
|
282
|
+
Split.configuration.experiments = {
|
283
|
+
:my_experiment => {
|
284
|
+
:alternatives => ["one", "two"],
|
285
|
+
:resettable => false,
|
286
|
+
:metadata => { 'one' => 'Meta1', 'two' => 'Meta2' }
|
287
|
+
}
|
274
288
|
}
|
275
|
-
|
276
|
-
end
|
289
|
+
end
|
277
290
|
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
291
|
+
it 'should be passed to helper block' do
|
292
|
+
@params = { 'ab_test' => { 'my_experiment' => 'two' } }
|
293
|
+
expect(ab_test('my_experiment')).to eq 'two'
|
294
|
+
expect(ab_test('my_experiment') do |alternative, meta|
|
295
|
+
meta
|
296
|
+
end).to eq('Meta2')
|
297
|
+
end
|
298
|
+
|
299
|
+
it 'should pass control metadata helper block if library disabled' do
|
300
|
+
Split.configure do |config|
|
301
|
+
config.enabled = false
|
302
|
+
end
|
303
|
+
|
304
|
+
expect(ab_test('my_experiment')).to eq 'one'
|
305
|
+
expect(ab_test('my_experiment') do |_, meta|
|
306
|
+
meta
|
307
|
+
end).to eq('Meta1')
|
308
|
+
end
|
284
309
|
end
|
285
310
|
|
286
|
-
|
287
|
-
|
288
|
-
|
311
|
+
context 'is not defined' do
|
312
|
+
before do
|
313
|
+
Split.configuration.experiments = {
|
314
|
+
:my_experiment => {
|
315
|
+
:alternatives => ["one", "two"],
|
316
|
+
:resettable => false,
|
317
|
+
:metadata => nil
|
318
|
+
}
|
319
|
+
}
|
320
|
+
end
|
321
|
+
|
322
|
+
it 'should be passed to helper block' do
|
323
|
+
expect(ab_test('my_experiment') do |alternative, meta|
|
324
|
+
meta
|
325
|
+
end).to eq({})
|
289
326
|
end
|
290
327
|
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
328
|
+
it 'should pass control metadata helper block if library disabled' do
|
329
|
+
Split.configure do |config|
|
330
|
+
config.enabled = false
|
331
|
+
end
|
332
|
+
|
333
|
+
expect(ab_test('my_experiment') do |_, meta|
|
334
|
+
meta
|
335
|
+
end).to eq({})
|
336
|
+
end
|
295
337
|
end
|
296
338
|
end
|
297
339
|
|
@@ -1096,15 +1138,9 @@ describe Split::Helper do
|
|
1096
1138
|
end
|
1097
1139
|
|
1098
1140
|
it "should increment the counter for the specified-goal completed alternative" do
|
1099
|
-
expect(
|
1100
|
-
|
1101
|
-
|
1102
|
-
}).not_to change {
|
1103
|
-
Split::Alternative.new(@alternative_name, @experiment_name).completed_count(@goal2)
|
1104
|
-
}
|
1105
|
-
}).to change {
|
1106
|
-
Split::Alternative.new(@alternative_name, @experiment_name).completed_count(@goal1)
|
1107
|
-
}.by(1)
|
1141
|
+
expect{ ab_finished({"link_color" => ["purchase"]}) }
|
1142
|
+
.to change{ Split::Alternative.new(@alternative_name, @experiment_name).completed_count(@goal2) }.by(0)
|
1143
|
+
.and change{ Split::Alternative.new(@alternative_name, @experiment_name).completed_count(@goal1) }.by(1)
|
1108
1144
|
end
|
1109
1145
|
end
|
1110
1146
|
end
|
@@ -52,7 +52,7 @@ describe Split::Persistence::CookieAdapter do
|
|
52
52
|
it "puts multiple experiments in a single cookie" do
|
53
53
|
subject["foo"] = "FOO"
|
54
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} -
|
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} [A-Z]{3}\Z/)
|
56
56
|
end
|
57
57
|
|
58
58
|
it "ensure other added cookies are not overriden" do
|
@@ -60,6 +60,15 @@ describe Split::Persistence::RedisAdapter do
|
|
60
60
|
end
|
61
61
|
end
|
62
62
|
|
63
|
+
describe '#find' do
|
64
|
+
before { Split::Persistence::RedisAdapter.with_config(:lookup_by => proc{'frag'}, :namespace => 'a_namespace') }
|
65
|
+
|
66
|
+
it "should create and user from a given key" do
|
67
|
+
adapter = Split::Persistence::RedisAdapter.find(2)
|
68
|
+
expect(adapter.redis_key).to eq("a_namespace:2")
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
63
72
|
context 'functional tests' do
|
64
73
|
before { Split::Persistence::RedisAdapter.with_config(:lookup_by => 'lookup') }
|
65
74
|
|