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.
Files changed (58) hide show
  1. checksums.yaml +5 -5
  2. data/.eslintrc +1 -1
  3. data/.github/ISSUE_TEMPLATE/bug_report.md +24 -0
  4. data/.rspec +1 -0
  5. data/.rubocop.yml +6 -1155
  6. data/.rubocop_todo.yml +679 -0
  7. data/.travis.yml +46 -2
  8. data/Appraisals +12 -1
  9. data/CHANGELOG.md +116 -0
  10. data/CODE_OF_CONDUCT.md +3 -3
  11. data/CONTRIBUTING.md +54 -5
  12. data/Gemfile +1 -0
  13. data/LICENSE +1 -1
  14. data/README.md +209 -118
  15. data/Rakefile +1 -0
  16. data/gemfiles/4.2.gemfile +1 -1
  17. data/gemfiles/5.0.gemfile +1 -2
  18. data/gemfiles/5.1.gemfile +9 -0
  19. data/gemfiles/5.2.gemfile +9 -0
  20. data/gemfiles/6.0.gemfile +9 -0
  21. data/lib/split/algorithms/block_randomization.rb +1 -0
  22. data/lib/split/alternative.rb +6 -3
  23. data/lib/split/combined_experiments_helper.rb +37 -0
  24. data/lib/split/configuration.rb +17 -2
  25. data/lib/split/dashboard/helpers.rb +2 -2
  26. data/lib/split/dashboard/pagination_helpers.rb +86 -0
  27. data/lib/split/dashboard/paginator.rb +16 -0
  28. data/lib/split/dashboard/public/style.css +9 -0
  29. data/lib/split/dashboard/views/index.erb +5 -1
  30. data/lib/split/dashboard/views/layout.erb +1 -1
  31. data/lib/split/dashboard.rb +6 -1
  32. data/lib/split/engine.rb +6 -2
  33. data/lib/split/experiment.rb +34 -22
  34. data/lib/split/goals_collection.rb +1 -0
  35. data/lib/split/helper.rb +17 -3
  36. data/lib/split/persistence/cookie_adapter.rb +53 -15
  37. data/lib/split/persistence/dual_adapter.rb +54 -12
  38. data/lib/split/redis_interface.rb +2 -3
  39. data/lib/split/trial.rb +4 -6
  40. data/lib/split/user.rb +5 -1
  41. data/lib/split/version.rb +1 -1
  42. data/lib/split.rb +9 -1
  43. data/spec/alternative_spec.rb +12 -0
  44. data/spec/combined_experiments_helper_spec.rb +57 -0
  45. data/spec/dashboard/pagination_helpers_spec.rb +200 -0
  46. data/spec/dashboard/paginator_spec.rb +37 -0
  47. data/spec/dashboard_helpers_spec.rb +2 -2
  48. data/spec/dashboard_spec.rb +37 -16
  49. data/spec/encapsulated_helper_spec.rb +1 -1
  50. data/spec/experiment_spec.rb +45 -6
  51. data/spec/helper_spec.rb +143 -80
  52. data/spec/persistence/cookie_adapter_spec.rb +90 -23
  53. data/spec/persistence/dual_adapter_spec.rb +160 -68
  54. data/spec/split_spec.rb +7 -7
  55. data/spec/trial_spec.rb +20 -0
  56. data/spec/user_spec.rb +11 -0
  57. data/split.gemspec +17 -7
  58. metadata +53 -19
@@ -35,6 +35,12 @@ 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
+
38
44
  it "should save to redis" do
39
45
  experiment.save
40
46
  expect(Split.redis.exists('basket_text')).to be true
@@ -86,7 +92,7 @@ describe Split::Experiment do
86
92
  experiment.save
87
93
  experiment.save
88
94
  expect(Split.redis.exists('basket_text')).to be true
89
- expect(Split.redis.lrange('basket_text', 0, -1)).to eq(['Basket', "Cart"])
95
+ expect(Split.redis.lrange('basket_text', 0, -1)).to eq(['{"Basket":1}', '{"Cart":1}'])
90
96
  end
91
97
 
92
98
  describe 'new record?' do
@@ -213,12 +219,41 @@ describe Split::Experiment do
213
219
  it "should have no winner initially" do
214
220
  expect(experiment.winner).to be_nil
215
221
  end
222
+ end
216
223
 
224
+ describe 'winner=' do
217
225
  it "should allow you to specify a winner" do
218
226
  experiment.save
219
227
  experiment.winner = 'red'
220
228
  expect(experiment.winner.name).to eq('red')
221
229
  end
230
+
231
+ context 'when has_winner state is memoized' do
232
+ before { expect(experiment).to_not have_winner }
233
+
234
+ it 'should keep has_winner state consistent' do
235
+ experiment.winner = 'red'
236
+ expect(experiment).to have_winner
237
+ end
238
+ end
239
+ end
240
+
241
+ describe 'reset_winner' do
242
+ before { experiment.winner = 'green' }
243
+
244
+ it 'should reset the winner' do
245
+ experiment.reset_winner
246
+ expect(experiment.winner).to be_nil
247
+ end
248
+
249
+ context 'when has_winner state is memoized' do
250
+ before { expect(experiment).to have_winner }
251
+
252
+ it 'should keep has_winner state consistent' do
253
+ experiment.reset_winner
254
+ expect(experiment).to_not have_winner
255
+ end
256
+ end
222
257
  end
223
258
 
224
259
  describe 'has_winner?' do
@@ -235,6 +270,12 @@ describe Split::Experiment do
235
270
  expect(experiment).to_not have_winner
236
271
  end
237
272
  end
273
+
274
+ it 'memoizes has_winner state' do
275
+ expect(experiment).to receive(:winner).once
276
+ expect(experiment).to_not have_winner
277
+ expect(experiment).to_not have_winner
278
+ end
238
279
  end
239
280
 
240
281
  describe 'reset' do
@@ -414,9 +455,7 @@ describe Split::Experiment do
414
455
  }
415
456
 
416
457
  context "saving experiment" do
417
- def same_but_different_goals
418
- Split::ExperimentCatalog.find_or_create({'link_color' => ["purchase", "refund"]}, 'blue', 'red', 'green')
419
- end
458
+ let(:same_but_different_goals) { Split::ExperimentCatalog.find_or_create({'link_color' => ["purchase", "refund"]}, 'blue', 'red', 'green') }
420
459
 
421
460
  before { experiment.save }
422
461
 
@@ -425,7 +464,7 @@ describe Split::Experiment do
425
464
  end
426
465
 
427
466
  it "should reset an experiment if it is loaded with different goals" do
428
- same_experiment = same_but_different_goals
467
+ same_but_different_goals
429
468
  expect(Split::ExperimentCatalog.find("link_color").goals).to eq(["purchase", "refund"])
430
469
  end
431
470
 
@@ -458,7 +497,7 @@ describe Split::Experiment do
458
497
  expect(experiment.alternatives[0].p_winner).to be_within(0.04).of(0.50)
459
498
  end
460
499
 
461
- it "should calculate the probability of being the winning alternative separately for each goal" do
500
+ it "should calculate the probability of being the winning alternative separately for each goal", :skip => true do
462
501
  experiment = Split::ExperimentCatalog.find_or_create({'link_color3' => ["purchase", "refund"]}, 'blue', 'red', 'green')
463
502
  goal1 = experiment.goals[0]
464
503
  goal2 = experiment.goals[1]
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
- # TODO: persist alternative weights
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
@@ -285,98 +296,126 @@ describe Split::Helper do
285
296
  end
286
297
 
287
298
  describe 'ab_finished' do
288
- before(:each) do
289
- @experiment_name = 'link_color'
290
- @alternatives = ['blue', 'red']
291
- @experiment = Split::ExperimentCatalog.find_or_create(@experiment_name, *@alternatives)
292
- @alternative_name = ab_test(@experiment_name, *@alternatives)
293
- @previous_completion_count = Split::Alternative.new(@alternative_name, @experiment_name).completed_count
294
- end
299
+ context 'for an experiment that the user participates in' do
300
+ before(:each) do
301
+ @experiment_name = 'link_color'
302
+ @alternatives = ['blue', 'red']
303
+ @experiment = Split::ExperimentCatalog.find_or_create(@experiment_name, *@alternatives)
304
+ @alternative_name = ab_test(@experiment_name, *@alternatives)
305
+ @previous_completion_count = Split::Alternative.new(@alternative_name, @experiment_name).completed_count
306
+ end
295
307
 
296
- it 'should increment the counter for the completed alternative' do
297
- ab_finished(@experiment_name)
298
- new_completion_count = Split::Alternative.new(@alternative_name, @experiment_name).completed_count
299
- expect(new_completion_count).to eq(@previous_completion_count + 1)
300
- end
308
+ it 'should increment the counter for the completed alternative' do
309
+ ab_finished(@experiment_name)
310
+ new_completion_count = Split::Alternative.new(@alternative_name, @experiment_name).completed_count
311
+ expect(new_completion_count).to eq(@previous_completion_count + 1)
312
+ end
301
313
 
302
- it "should set experiment's finished key if reset is false" do
303
- ab_finished(@experiment_name, {:reset => false})
304
- expect(ab_user[@experiment.key]).to eq(@alternative_name)
305
- expect(ab_user[@experiment.finished_key]).to eq(true)
306
- end
314
+ it "should set experiment's finished key if reset is false" do
315
+ ab_finished(@experiment_name, {:reset => false})
316
+ expect(ab_user[@experiment.key]).to eq(@alternative_name)
317
+ expect(ab_user[@experiment.finished_key]).to eq(true)
318
+ end
307
319
 
308
- it 'should not increment the counter if reset is false and the experiment has been already finished' do
309
- 2.times { ab_finished(@experiment_name, {:reset => false}) }
310
- new_completion_count = Split::Alternative.new(@alternative_name, @experiment_name).completed_count
311
- expect(new_completion_count).to eq(@previous_completion_count + 1)
312
- end
320
+ it 'should not increment the counter if reset is false and the experiment has been already finished' do
321
+ 2.times { ab_finished(@experiment_name, {:reset => false}) }
322
+ new_completion_count = Split::Alternative.new(@alternative_name, @experiment_name).completed_count
323
+ expect(new_completion_count).to eq(@previous_completion_count + 1)
324
+ end
313
325
 
314
- it 'should not increment the counter for an experiment that the user is not participating in' do
315
- ab_test('button_size', 'small', 'big')
326
+ it 'should not increment the counter for an ended experiment' do
327
+ e = Split::ExperimentCatalog.find_or_create('button_size', 'small', 'big')
328
+ e.winner = 'small'
329
+ a = ab_test('button_size', 'small', 'big')
330
+ expect(a).to eq('small')
331
+ expect(lambda {
332
+ ab_finished('button_size')
333
+ }).not_to change { Split::Alternative.new(a, 'button_size').completed_count }
334
+ end
316
335
 
317
- # So, user should be participating in the link_color experiment and
318
- # receive the control for button_size. As the user is not participating in
319
- # the button size experiment, finishing it should not increase the
320
- # completion count for that alternative.
321
- expect(lambda {
322
- ab_finished('button_size')
323
- }).not_to change { Split::Alternative.new('small', 'button_size').completed_count }
324
- end
336
+ it "should clear out the user's participation from their session" do
337
+ expect(ab_user[@experiment.key]).to eq(@alternative_name)
338
+ ab_finished(@experiment_name)
339
+ expect(ab_user.keys).to be_empty
340
+ end
325
341
 
326
- it 'should not increment the counter for an ended experiment' do
327
- e = Split::ExperimentCatalog.find_or_create('button_size', 'small', 'big')
328
- e.winner = 'small'
329
- a = ab_test('button_size', 'small', 'big')
330
- expect(a).to eq('small')
331
- expect(lambda {
332
- ab_finished('button_size')
333
- }).not_to change { Split::Alternative.new(a, 'button_size').completed_count }
334
- end
342
+ it "should not clear out the users session if reset is false" do
343
+ expect(ab_user[@experiment.key]).to eq(@alternative_name)
344
+ ab_finished(@experiment_name, {:reset => false})
345
+ expect(ab_user[@experiment.key]).to eq(@alternative_name)
346
+ expect(ab_user[@experiment.finished_key]).to eq(true)
347
+ end
335
348
 
336
- it "should clear out the user's participation from their session" do
337
- expect(ab_user[@experiment.key]).to eq(@alternative_name)
338
- ab_finished(@experiment_name)
339
- expect(ab_user.keys).to be_empty
340
- end
349
+ it "should reset the users session when experiment is not versioned" do
350
+ expect(ab_user[@experiment.key]).to eq(@alternative_name)
351
+ ab_finished(@experiment_name)
352
+ expect(ab_user.keys).to be_empty
353
+ end
341
354
 
342
- it "should not clear out the users session if reset is false" do
343
- expect(ab_user[@experiment.key]).to eq(@alternative_name)
344
- ab_finished(@experiment_name, {:reset => false})
345
- expect(ab_user[@experiment.key]).to eq(@alternative_name)
346
- expect(ab_user[@experiment.finished_key]).to eq(true)
347
- end
355
+ it "should reset the users session when experiment is versioned" do
356
+ @experiment.increment_version
357
+ @alternative_name = ab_test(@experiment_name, *@alternatives)
348
358
 
349
- it "should reset the users session when experiment is not versioned" do
350
- expect(ab_user[@experiment.key]).to eq(@alternative_name)
351
- ab_finished(@experiment_name)
352
- expect(ab_user.keys).to be_empty
353
- end
359
+ expect(ab_user[@experiment.key]).to eq(@alternative_name)
360
+ ab_finished(@experiment_name)
361
+ expect(ab_user.keys).to be_empty
362
+ end
354
363
 
355
- it "should reset the users session when experiment is versioned" do
356
- @experiment.increment_version
357
- @alternative_name = ab_test(@experiment_name, *@alternatives)
364
+ context "when on_trial_complete is set" do
365
+ before { Split.configuration.on_trial_complete = :some_method }
366
+ it "should call the method" do
367
+ expect(self).to receive(:some_method)
368
+ ab_finished(@experiment_name)
369
+ end
358
370
 
359
- expect(ab_user[@experiment.key]).to eq(@alternative_name)
360
- ab_finished(@experiment_name)
361
- expect(ab_user.keys).to be_empty
371
+ it "should not call the method without alternative" do
372
+ ab_user[@experiment.key] = nil
373
+ expect(self).not_to receive(:some_method)
374
+ ab_finished(@experiment_name)
375
+ end
376
+ end
362
377
  end
363
378
 
364
- it "should do nothing where the experiment was not started by this user" do
365
- ab_user = nil
366
- expect(lambda { ab_finished('some_experiment_not_started_by_the_user') }).not_to raise_exception
379
+ context 'for an experiment that the user is excluded from' do
380
+ before do
381
+ alternative = ab_test('link_color', 'blue', 'red')
382
+ expect(Split::Alternative.new(alternative, 'link_color').participant_count).to eq(1)
383
+ alternative = ab_test('button_size', 'small', 'big')
384
+ expect(Split::Alternative.new(alternative, 'button_size').participant_count).to eq(0)
385
+ end
386
+
387
+ it 'should not increment the completed counter' do
388
+ # So, user should be participating in the link_color experiment and
389
+ # receive the control for button_size. As the user is not participating in
390
+ # the button size experiment, finishing it should not increase the
391
+ # completion count for that alternative.
392
+ expect(lambda {
393
+ ab_finished('button_size')
394
+ }).not_to change { Split::Alternative.new('small', 'button_size').completed_count }
395
+ end
367
396
  end
368
397
 
369
- context "when on_trial_complete is set" do
370
- before { Split.configuration.on_trial_complete = :some_method }
371
- it "should call the method" do
372
- expect(self).to receive(:some_method)
373
- ab_finished(@experiment_name)
398
+ context 'for an experiment that the user does not participate in' do
399
+ before do
400
+ Split::ExperimentCatalog.find_or_create(:not_started_experiment, 'control', 'alt')
401
+ end
402
+ it 'should not raise an exception' do
403
+ expect { ab_finished(:not_started_experiment) }.not_to raise_exception
374
404
  end
375
405
 
376
- it "should not call the method without alternative" do
377
- ab_user[@experiment.key] = nil
378
- expect(self).not_to receive(:some_method)
379
- ab_finished(@experiment_name)
406
+ it 'should not change the user state when reset is false' do
407
+ expect { ab_finished(:not_started_experiment, reset: false) }.not_to change { ab_user.keys}.from([])
408
+ end
409
+
410
+ it 'should not change the user state when reset is true' do
411
+ expect(self).not_to receive(:reset!)
412
+ ab_finished(:not_started_experiment)
413
+ end
414
+
415
+ it 'should not increment the completed counter' do
416
+ ab_finished(:not_started_experiment)
417
+ expect(Split::Alternative.new('control', :not_started_experiment).completed_count).to eq(0)
418
+ expect(Split::Alternative.new('alt', :not_started_experiment).completed_count).to eq(0)
380
419
  end
381
420
  end
382
421
  end
@@ -516,6 +555,17 @@ describe Split::Helper do
516
555
  expect(active_experiments.first[0]).to eq "link_color"
517
556
  end
518
557
 
558
+ it 'should show versioned tests properly' do
559
+ 10.times { experiment.reset }
560
+
561
+ alternative = ab_test(experiment.name, 'blue', 'red')
562
+ ab_finished(experiment.name, reset: false)
563
+
564
+ expect(experiment.version).to eq(10)
565
+ expect(active_experiments.count).to eq 1
566
+ expect(active_experiments).to eq({'link_color' => alternative })
567
+ end
568
+
519
569
  it 'should show multiple tests' do
520
570
  Split.configure do |config|
521
571
  config.allow_multiple_experiments = true
@@ -533,7 +583,7 @@ describe Split::Helper do
533
583
  end
534
584
  e = Split::ExperimentCatalog.find_or_create('def', '4', '5', '6')
535
585
  e.winner = '4'
536
- alternative = ab_test('def', '4', '5', '6')
586
+ ab_test('def', '4', '5', '6')
537
587
  another_alternative = ab_test('ghi', '7', '8', '9')
538
588
  expect(active_experiments.count).to eq 1
539
589
  expect(active_experiments.first[0]).to eq "ghi"
@@ -552,6 +602,11 @@ describe Split::Helper do
552
602
  expect(alternative).to eq experiment.control.name
553
603
  end
554
604
 
605
+ it 'should not create a experiment' do
606
+ ab_test('link_color', 'blue', 'red')
607
+ expect(Split::Experiment.new('link_color')).to be_a_new_record
608
+ end
609
+
555
610
  it "should not increment the participation count" do
556
611
 
557
612
  previous_red_count = Split::Alternative.new('red', 'link_color').participant_count
@@ -687,6 +742,14 @@ describe Split::Helper do
687
742
  end
688
743
  end
689
744
 
745
+ describe 'when user is previewing' do
746
+ before(:each) do
747
+ @request = OpenStruct.new(headers: { 'x-purpose' => 'preview' })
748
+ end
749
+
750
+ it_behaves_like "a disabled test"
751
+ end
752
+
690
753
  describe 'versioned experiments' do
691
754
  it "should use version zero if no version is present" do
692
755
  alternative_name = ab_test('link_color', 'blue', 'red')
@@ -996,8 +1059,8 @@ describe Split::Helper do
996
1059
 
997
1060
  it 'should handle multiple experiments correctly' do
998
1061
  experiment2 = Split::ExperimentCatalog.find_or_create('link_color2', 'blue', 'red')
999
- alternative_name = ab_test('link_color', 'blue', 'red')
1000
- alternative_name2 = ab_test('link_color2', 'blue', 'red')
1062
+ ab_test('link_color', 'blue', 'red')
1063
+ ab_test('link_color2', 'blue', 'red')
1001
1064
  ab_finished('link_color2')
1002
1065
 
1003
1066
  experiment2.alternatives.each do |alt|
@@ -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
- let(:context) { double(:cookies => CookiesMock.new) }
7
- 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
8
14
 
9
- describe "#[] and #[]=" do
10
- it "should set and return the value for given key" do
11
- subject["my_key"] = "my_value"
12
- 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
13
24
  end
14
- end
15
25
 
16
- describe "#delete" do
17
- it "should delete the given key" do
18
- subject["my_key"] = "my_value"
19
- subject.delete("my_key")
20
- 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
21
32
  end
22
- end
23
33
 
24
- describe "#keys" do
25
- it "should return an array of the session's stored keys" do
26
- subject["my_key"] = "my_value"
27
- subject["my_second_key"] = "my_second_value"
28
- 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
29
40
  end
30
41
  end
31
42
 
32
- it "handles invalid JSON" do
33
- context.cookies[:split] = { :value => '{"foo":2,', :expires => Time.now }
34
- expect(subject["my_key"]).to be_nil
35
- subject["my_key"] = "my_value"
36
- 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
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