split 3.0.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 +5 -5
- data/.eslintrc +1 -1
- data/.github/FUNDING.yml +1 -0
- data/.github/ISSUE_TEMPLATE/bug_report.md +24 -0
- data/.github/dependabot.yml +7 -0
- data/.github/workflows/ci.yml +71 -0
- data/.rspec +1 -0
- data/.rubocop.yml +71 -1044
- data/.rubocop_todo.yml +226 -0
- data/Appraisals +12 -1
- data/CHANGELOG.md +157 -0
- data/CODE_OF_CONDUCT.md +3 -3
- data/CONTRIBUTING.md +54 -5
- data/Gemfile +2 -0
- data/LICENSE +1 -1
- data/README.md +232 -121
- data/Rakefile +2 -0
- data/gemfiles/5.0.gemfile +1 -2
- data/gemfiles/{4.2.gemfile → 5.1.gemfile} +2 -2
- data/gemfiles/5.2.gemfile +9 -0
- data/gemfiles/6.0.gemfile +9 -0
- data/gemfiles/6.1.gemfile +9 -0
- data/gemfiles/7.0.gemfile +9 -0
- data/lib/split/algorithms/block_randomization.rb +2 -0
- data/lib/split/algorithms/weighted_sample.rb +2 -1
- data/lib/split/algorithms/whiplash.rb +3 -2
- data/lib/split/alternative.rb +7 -3
- data/lib/split/cache.rb +28 -0
- data/lib/split/combined_experiments_helper.rb +38 -0
- data/lib/split/configuration.rb +24 -13
- data/lib/split/dashboard/helpers.rb +3 -2
- data/lib/split/dashboard/pagination_helpers.rb +87 -0
- data/lib/split/dashboard/paginator.rb +17 -0
- data/lib/split/dashboard/public/dashboard.js +10 -0
- data/lib/split/dashboard/public/style.css +14 -0
- data/lib/split/dashboard/views/_controls.erb +13 -0
- data/lib/split/dashboard/views/index.erb +5 -1
- data/lib/split/dashboard/views/layout.erb +1 -1
- data/lib/split/dashboard.rb +21 -1
- data/lib/split/encapsulated_helper.rb +3 -2
- data/lib/split/engine.rb +7 -2
- data/lib/split/exceptions.rb +1 -0
- data/lib/split/experiment.rb +103 -69
- data/lib/split/experiment_catalog.rb +1 -3
- data/lib/split/extensions/string.rb +1 -0
- data/lib/split/goals_collection.rb +2 -0
- data/lib/split/helper.rb +42 -9
- data/lib/split/metric.rb +2 -1
- data/lib/split/persistence/cookie_adapter.rb +58 -15
- data/lib/split/persistence/dual_adapter.rb +54 -12
- 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 +9 -30
- data/lib/split/trial.rb +25 -17
- data/lib/split/user.rb +20 -4
- data/lib/split/version.rb +2 -4
- data/lib/split/zscore.rb +1 -0
- data/lib/split.rb +17 -3
- data/spec/alternative_spec.rb +13 -1
- data/spec/cache_spec.rb +88 -0
- data/spec/combined_experiments_helper_spec.rb +57 -0
- data/spec/configuration_spec.rb +17 -15
- 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 +78 -17
- data/spec/encapsulated_helper_spec.rb +2 -2
- data/spec/experiment_spec.rb +117 -13
- data/spec/goals_collection_spec.rb +1 -1
- data/spec/helper_spec.rb +211 -112
- data/spec/persistence/cookie_adapter_spec.rb +90 -23
- data/spec/persistence/dual_adapter_spec.rb +160 -68
- 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/split_spec.rb +7 -7
- data/spec/trial_spec.rb +65 -19
- data/spec/user_spec.rb +45 -3
- data/split.gemspec +20 -10
- metadata +61 -35
- data/.travis.yml +0 -16
data/spec/helper_spec.rb
CHANGED
|
@@ -35,6 +35,18 @@ describe Split::Helper do
|
|
|
35
35
|
expect(lambda { ab_test({'link_color' => "purchase"}, 'blue', 'red') }).not_to raise_error
|
|
36
36
|
end
|
|
37
37
|
|
|
38
|
+
it "raises an appropriate error when processing combined expirements" do
|
|
39
|
+
Split.configuration.experiments = {
|
|
40
|
+
:combined_exp_1 => {
|
|
41
|
+
:alternatives => [ { name: "control", percent: 50 }, { name: "test-alt", percent: 50 } ],
|
|
42
|
+
:metric => :my_metric,
|
|
43
|
+
:combined_experiments => [:combined_exp_1_sub_1]
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
Split::ExperimentCatalog.find_or_create('combined_exp_1')
|
|
47
|
+
expect(lambda { ab_test('combined_exp_1')}).to raise_error(Split::InvalidExperimentsFormatError )
|
|
48
|
+
end
|
|
49
|
+
|
|
38
50
|
it "should assign a random alternative to a new user when there are an equal number of alternatives assigned" do
|
|
39
51
|
ab_test('link_color', 'blue', 'red')
|
|
40
52
|
expect(['red', 'blue']).to include(ab_user['link_color'])
|
|
@@ -171,8 +183,7 @@ describe Split::Helper do
|
|
|
171
183
|
ab_test('link_color', {'blue' => 0.01}, 'red' => 0.2)
|
|
172
184
|
experiment = Split::ExperimentCatalog.find('link_color')
|
|
173
185
|
expect(experiment.alternatives.map(&:name)).to eq(['blue', 'red'])
|
|
174
|
-
|
|
175
|
-
# expect(experiment.alternatives.collect{|a| a.weight}).to eq([0.01, 0.2])
|
|
186
|
+
expect(experiment.alternatives.collect{|a| a.weight}).to match_array([0.01, 0.2])
|
|
176
187
|
end
|
|
177
188
|
|
|
178
189
|
it "should only let a user participate in one experiment at a time" do
|
|
@@ -218,13 +229,15 @@ describe Split::Helper do
|
|
|
218
229
|
|
|
219
230
|
context "when user already has experiment" do
|
|
220
231
|
let(:mock_user){ Split::User.new(self, {'test_0' => 'test-alt'}) }
|
|
221
|
-
|
|
232
|
+
|
|
233
|
+
before do
|
|
222
234
|
Split.configure do |config|
|
|
223
235
|
config.allow_multiple_experiments = 'control'
|
|
224
236
|
end
|
|
237
|
+
|
|
225
238
|
Split::ExperimentCatalog.find_or_initialize('test_0', 'control', 'test-alt').save
|
|
226
239
|
Split::ExperimentCatalog.find_or_initialize('test_1', 'control', 'test-alt').save
|
|
227
|
-
|
|
240
|
+
end
|
|
228
241
|
|
|
229
242
|
it "should restore previously selected alternative" do
|
|
230
243
|
expect(ab_user.active_experiments.size).to eq 1
|
|
@@ -232,6 +245,16 @@ describe Split::Helper do
|
|
|
232
245
|
expect(ab_test(:test_0, {'control' => 1}, {"test-alt" => 100})).to eq 'test-alt'
|
|
233
246
|
end
|
|
234
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
|
+
|
|
235
258
|
it "lets override existing choice" do
|
|
236
259
|
pending "this requires user store reset on first call not depending on whelther it is current trial"
|
|
237
260
|
@params = { 'ab_test' => { 'test_1' => 'test-alt' } }
|
|
@@ -254,129 +277,187 @@ describe Split::Helper do
|
|
|
254
277
|
end
|
|
255
278
|
|
|
256
279
|
describe 'metadata' do
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
:
|
|
261
|
-
|
|
262
|
-
|
|
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
|
+
}
|
|
263
288
|
}
|
|
264
|
-
|
|
265
|
-
|
|
289
|
+
end
|
|
290
|
+
|
|
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
|
|
266
303
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
end).to eq('Meta1')
|
|
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
|
|
273
309
|
end
|
|
274
310
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
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
|
+
}
|
|
278
320
|
end
|
|
279
321
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
322
|
+
it 'should be passed to helper block' do
|
|
323
|
+
expect(ab_test('my_experiment') do |alternative, meta|
|
|
324
|
+
meta
|
|
325
|
+
end).to eq({})
|
|
326
|
+
end
|
|
327
|
+
|
|
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
|
|
284
337
|
end
|
|
285
338
|
end
|
|
286
339
|
|
|
287
340
|
describe 'ab_finished' do
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
341
|
+
context 'for an experiment that the user participates in' do
|
|
342
|
+
before(:each) do
|
|
343
|
+
@experiment_name = 'link_color'
|
|
344
|
+
@alternatives = ['blue', 'red']
|
|
345
|
+
@experiment = Split::ExperimentCatalog.find_or_create(@experiment_name, *@alternatives)
|
|
346
|
+
@alternative_name = ab_test(@experiment_name, *@alternatives)
|
|
347
|
+
@previous_completion_count = Split::Alternative.new(@alternative_name, @experiment_name).completed_count
|
|
348
|
+
end
|
|
295
349
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
350
|
+
it 'should increment the counter for the completed alternative' do
|
|
351
|
+
ab_finished(@experiment_name)
|
|
352
|
+
new_completion_count = Split::Alternative.new(@alternative_name, @experiment_name).completed_count
|
|
353
|
+
expect(new_completion_count).to eq(@previous_completion_count + 1)
|
|
354
|
+
end
|
|
301
355
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
356
|
+
it "should set experiment's finished key if reset is false" do
|
|
357
|
+
ab_finished(@experiment_name, {:reset => false})
|
|
358
|
+
expect(ab_user[@experiment.key]).to eq(@alternative_name)
|
|
359
|
+
expect(ab_user[@experiment.finished_key]).to eq(true)
|
|
360
|
+
end
|
|
307
361
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
362
|
+
it 'should not increment the counter if reset is false and the experiment has been already finished' do
|
|
363
|
+
2.times { ab_finished(@experiment_name, {:reset => false}) }
|
|
364
|
+
new_completion_count = Split::Alternative.new(@alternative_name, @experiment_name).completed_count
|
|
365
|
+
expect(new_completion_count).to eq(@previous_completion_count + 1)
|
|
366
|
+
end
|
|
313
367
|
|
|
314
|
-
|
|
315
|
-
|
|
368
|
+
it 'should not increment the counter for an ended experiment' do
|
|
369
|
+
e = Split::ExperimentCatalog.find_or_create('button_size', 'small', 'big')
|
|
370
|
+
e.winner = 'small'
|
|
371
|
+
a = ab_test('button_size', 'small', 'big')
|
|
372
|
+
expect(a).to eq('small')
|
|
373
|
+
expect(lambda {
|
|
374
|
+
ab_finished('button_size')
|
|
375
|
+
}).not_to change { Split::Alternative.new(a, 'button_size').completed_count }
|
|
376
|
+
end
|
|
316
377
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
ab_finished('button_size')
|
|
323
|
-
}).not_to change { Split::Alternative.new('small', 'button_size').completed_count }
|
|
324
|
-
end
|
|
378
|
+
it "should clear out the user's participation from their session" do
|
|
379
|
+
expect(ab_user[@experiment.key]).to eq(@alternative_name)
|
|
380
|
+
ab_finished(@experiment_name)
|
|
381
|
+
expect(ab_user.keys).to be_empty
|
|
382
|
+
end
|
|
325
383
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
ab_finished('button_size')
|
|
333
|
-
}).not_to change { Split::Alternative.new(a, 'button_size').completed_count }
|
|
334
|
-
end
|
|
384
|
+
it "should not clear out the users session if reset is false" do
|
|
385
|
+
expect(ab_user[@experiment.key]).to eq(@alternative_name)
|
|
386
|
+
ab_finished(@experiment_name, {:reset => false})
|
|
387
|
+
expect(ab_user[@experiment.key]).to eq(@alternative_name)
|
|
388
|
+
expect(ab_user[@experiment.finished_key]).to eq(true)
|
|
389
|
+
end
|
|
335
390
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
391
|
+
it "should reset the users session when experiment is not versioned" do
|
|
392
|
+
expect(ab_user[@experiment.key]).to eq(@alternative_name)
|
|
393
|
+
ab_finished(@experiment_name)
|
|
394
|
+
expect(ab_user.keys).to be_empty
|
|
395
|
+
end
|
|
341
396
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
expect(ab_user[@experiment.key]).to eq(@alternative_name)
|
|
346
|
-
expect(ab_user[@experiment.finished_key]).to eq(true)
|
|
347
|
-
end
|
|
397
|
+
it "should reset the users session when experiment is versioned" do
|
|
398
|
+
@experiment.increment_version
|
|
399
|
+
@alternative_name = ab_test(@experiment_name, *@alternatives)
|
|
348
400
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
end
|
|
401
|
+
expect(ab_user[@experiment.key]).to eq(@alternative_name)
|
|
402
|
+
ab_finished(@experiment_name)
|
|
403
|
+
expect(ab_user.keys).to be_empty
|
|
404
|
+
end
|
|
354
405
|
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
406
|
+
context "when on_trial_complete is set" do
|
|
407
|
+
before { Split.configuration.on_trial_complete = :some_method }
|
|
408
|
+
it "should call the method" do
|
|
409
|
+
expect(self).to receive(:some_method)
|
|
410
|
+
ab_finished(@experiment_name)
|
|
411
|
+
end
|
|
358
412
|
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
413
|
+
it "should not call the method without alternative" do
|
|
414
|
+
ab_user[@experiment.key] = nil
|
|
415
|
+
expect(self).not_to receive(:some_method)
|
|
416
|
+
ab_finished(@experiment_name)
|
|
417
|
+
end
|
|
418
|
+
end
|
|
362
419
|
end
|
|
363
420
|
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
421
|
+
context 'for an experiment that the user is excluded from' do
|
|
422
|
+
before do
|
|
423
|
+
alternative = ab_test('link_color', 'blue', 'red')
|
|
424
|
+
expect(Split::Alternative.new(alternative, 'link_color').participant_count).to eq(1)
|
|
425
|
+
alternative = ab_test('button_size', 'small', 'big')
|
|
426
|
+
expect(Split::Alternative.new(alternative, 'button_size').participant_count).to eq(0)
|
|
427
|
+
end
|
|
428
|
+
|
|
429
|
+
it 'should not increment the completed counter' do
|
|
430
|
+
# So, user should be participating in the link_color experiment and
|
|
431
|
+
# receive the control for button_size. As the user is not participating in
|
|
432
|
+
# the button size experiment, finishing it should not increase the
|
|
433
|
+
# completion count for that alternative.
|
|
434
|
+
expect(lambda {
|
|
435
|
+
ab_finished('button_size')
|
|
436
|
+
}).not_to change { Split::Alternative.new('small', 'button_size').completed_count }
|
|
437
|
+
end
|
|
367
438
|
end
|
|
368
439
|
|
|
369
|
-
context
|
|
370
|
-
before
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
440
|
+
context 'for an experiment that the user does not participate in' do
|
|
441
|
+
before do
|
|
442
|
+
Split::ExperimentCatalog.find_or_create(:not_started_experiment, 'control', 'alt')
|
|
443
|
+
end
|
|
444
|
+
it 'should not raise an exception' do
|
|
445
|
+
expect { ab_finished(:not_started_experiment) }.not_to raise_exception
|
|
374
446
|
end
|
|
375
447
|
|
|
376
|
-
it
|
|
377
|
-
ab_user[
|
|
378
|
-
|
|
379
|
-
|
|
448
|
+
it 'should not change the user state when reset is false' do
|
|
449
|
+
expect { ab_finished(:not_started_experiment, reset: false) }.not_to change { ab_user.keys}.from([])
|
|
450
|
+
end
|
|
451
|
+
|
|
452
|
+
it 'should not change the user state when reset is true' do
|
|
453
|
+
expect(self).not_to receive(:reset!)
|
|
454
|
+
ab_finished(:not_started_experiment)
|
|
455
|
+
end
|
|
456
|
+
|
|
457
|
+
it 'should not increment the completed counter' do
|
|
458
|
+
ab_finished(:not_started_experiment)
|
|
459
|
+
expect(Split::Alternative.new('control', :not_started_experiment).completed_count).to eq(0)
|
|
460
|
+
expect(Split::Alternative.new('alt', :not_started_experiment).completed_count).to eq(0)
|
|
380
461
|
end
|
|
381
462
|
end
|
|
382
463
|
end
|
|
@@ -516,6 +597,17 @@ describe Split::Helper do
|
|
|
516
597
|
expect(active_experiments.first[0]).to eq "link_color"
|
|
517
598
|
end
|
|
518
599
|
|
|
600
|
+
it 'should show versioned tests properly' do
|
|
601
|
+
10.times { experiment.reset }
|
|
602
|
+
|
|
603
|
+
alternative = ab_test(experiment.name, 'blue', 'red')
|
|
604
|
+
ab_finished(experiment.name, reset: false)
|
|
605
|
+
|
|
606
|
+
expect(experiment.version).to eq(10)
|
|
607
|
+
expect(active_experiments.count).to eq 1
|
|
608
|
+
expect(active_experiments).to eq({'link_color' => alternative })
|
|
609
|
+
end
|
|
610
|
+
|
|
519
611
|
it 'should show multiple tests' do
|
|
520
612
|
Split.configure do |config|
|
|
521
613
|
config.allow_multiple_experiments = true
|
|
@@ -533,7 +625,7 @@ describe Split::Helper do
|
|
|
533
625
|
end
|
|
534
626
|
e = Split::ExperimentCatalog.find_or_create('def', '4', '5', '6')
|
|
535
627
|
e.winner = '4'
|
|
536
|
-
|
|
628
|
+
ab_test('def', '4', '5', '6')
|
|
537
629
|
another_alternative = ab_test('ghi', '7', '8', '9')
|
|
538
630
|
expect(active_experiments.count).to eq 1
|
|
539
631
|
expect(active_experiments.first[0]).to eq "ghi"
|
|
@@ -552,6 +644,11 @@ describe Split::Helper do
|
|
|
552
644
|
expect(alternative).to eq experiment.control.name
|
|
553
645
|
end
|
|
554
646
|
|
|
647
|
+
it 'should not create a experiment' do
|
|
648
|
+
ab_test('link_color', 'blue', 'red')
|
|
649
|
+
expect(Split::Experiment.new('link_color')).to be_a_new_record
|
|
650
|
+
end
|
|
651
|
+
|
|
555
652
|
it "should not increment the participation count" do
|
|
556
653
|
|
|
557
654
|
previous_red_count = Split::Alternative.new('red', 'link_color').participant_count
|
|
@@ -687,6 +784,14 @@ describe Split::Helper do
|
|
|
687
784
|
end
|
|
688
785
|
end
|
|
689
786
|
|
|
787
|
+
describe 'when user is previewing' do
|
|
788
|
+
before(:each) do
|
|
789
|
+
@request = OpenStruct.new(headers: { 'x-purpose' => 'preview' })
|
|
790
|
+
end
|
|
791
|
+
|
|
792
|
+
it_behaves_like "a disabled test"
|
|
793
|
+
end
|
|
794
|
+
|
|
690
795
|
describe 'versioned experiments' do
|
|
691
796
|
it "should use version zero if no version is present" do
|
|
692
797
|
alternative_name = ab_test('link_color', 'blue', 'red')
|
|
@@ -996,8 +1101,8 @@ describe Split::Helper do
|
|
|
996
1101
|
|
|
997
1102
|
it 'should handle multiple experiments correctly' do
|
|
998
1103
|
experiment2 = Split::ExperimentCatalog.find_or_create('link_color2', 'blue', 'red')
|
|
999
|
-
|
|
1000
|
-
|
|
1104
|
+
ab_test('link_color', 'blue', 'red')
|
|
1105
|
+
ab_test('link_color2', 'blue', 'red')
|
|
1001
1106
|
ab_finished('link_color2')
|
|
1002
1107
|
|
|
1003
1108
|
experiment2.alternatives.each do |alt|
|
|
@@ -1033,15 +1138,9 @@ describe Split::Helper do
|
|
|
1033
1138
|
end
|
|
1034
1139
|
|
|
1035
1140
|
it "should increment the counter for the specified-goal completed alternative" do
|
|
1036
|
-
expect(
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
}).not_to change {
|
|
1040
|
-
Split::Alternative.new(@alternative_name, @experiment_name).completed_count(@goal2)
|
|
1041
|
-
}
|
|
1042
|
-
}).to change {
|
|
1043
|
-
Split::Alternative.new(@alternative_name, @experiment_name).completed_count(@goal1)
|
|
1044
|
-
}.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)
|
|
1045
1144
|
end
|
|
1046
1145
|
end
|
|
1047
1146
|
end
|
|
@@ -1,39 +1,106 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
require "spec_helper"
|
|
3
|
+
require 'rack/test'
|
|
3
4
|
|
|
4
5
|
describe Split::Persistence::CookieAdapter do
|
|
6
|
+
subject { described_class.new(context) }
|
|
5
7
|
|
|
6
|
-
|
|
7
|
-
|
|
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
|
|
8
14
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
|
13
24
|
end
|
|
14
|
-
end
|
|
15
25
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
|
21
32
|
end
|
|
22
|
-
end
|
|
23
33
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
|
29
40
|
end
|
|
30
41
|
end
|
|
31
42
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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} [A-Z]{3}\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
|
|
37
64
|
end
|
|
38
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
|
|
39
106
|
end
|