split 3.3.2 → 4.0.0.pre2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) hide show
  1. checksums.yaml +4 -4
  2. data/.eslintrc +1 -1
  3. data/.github/FUNDING.yml +1 -0
  4. data/.github/ISSUE_TEMPLATE/bug_report.md +24 -0
  5. data/.github/dependabot.yml +7 -0
  6. data/.github/workflows/ci.yml +61 -0
  7. data/.rspec +1 -0
  8. data/.rubocop.yml +71 -1044
  9. data/.rubocop_todo.yml +226 -0
  10. data/Appraisals +1 -1
  11. data/CHANGELOG.md +62 -0
  12. data/CODE_OF_CONDUCT.md +3 -3
  13. data/Gemfile +2 -0
  14. data/README.md +40 -18
  15. data/Rakefile +2 -0
  16. data/gemfiles/6.0.gemfile +1 -1
  17. data/lib/split/algorithms/block_randomization.rb +2 -0
  18. data/lib/split/algorithms/weighted_sample.rb +2 -1
  19. data/lib/split/algorithms/whiplash.rb +3 -2
  20. data/lib/split/alternative.rb +4 -3
  21. data/lib/split/cache.rb +28 -0
  22. data/lib/split/combined_experiments_helper.rb +2 -1
  23. data/lib/split/configuration.rb +13 -14
  24. data/lib/split/dashboard/helpers.rb +1 -0
  25. data/lib/split/dashboard/pagination_helpers.rb +3 -3
  26. data/lib/split/dashboard/paginator.rb +1 -0
  27. data/lib/split/dashboard/public/dashboard.js +10 -0
  28. data/lib/split/dashboard/public/style.css +5 -0
  29. data/lib/split/dashboard/views/_controls.erb +13 -0
  30. data/lib/split/dashboard/views/layout.erb +1 -1
  31. data/lib/split/dashboard.rb +19 -1
  32. data/lib/split/encapsulated_helper.rb +3 -2
  33. data/lib/split/engine.rb +7 -4
  34. data/lib/split/exceptions.rb +1 -0
  35. data/lib/split/experiment.rb +98 -65
  36. data/lib/split/experiment_catalog.rb +1 -3
  37. data/lib/split/extensions/string.rb +1 -0
  38. data/lib/split/goals_collection.rb +2 -0
  39. data/lib/split/helper.rb +28 -8
  40. data/lib/split/metric.rb +2 -1
  41. data/lib/split/persistence/cookie_adapter.rb +6 -1
  42. data/lib/split/persistence/dual_adapter.rb +54 -12
  43. data/lib/split/persistence/redis_adapter.rb +5 -0
  44. data/lib/split/persistence/session_adapter.rb +1 -0
  45. data/lib/split/persistence.rb +4 -2
  46. data/lib/split/redis_interface.rb +9 -28
  47. data/lib/split/trial.rb +21 -11
  48. data/lib/split/user.rb +20 -4
  49. data/lib/split/version.rb +2 -4
  50. data/lib/split/zscore.rb +1 -0
  51. data/lib/split.rb +9 -3
  52. data/spec/alternative_spec.rb +1 -1
  53. data/spec/cache_spec.rb +88 -0
  54. data/spec/configuration_spec.rb +17 -15
  55. data/spec/dashboard/pagination_helpers_spec.rb +3 -1
  56. data/spec/dashboard_helpers_spec.rb +2 -2
  57. data/spec/dashboard_spec.rb +78 -17
  58. data/spec/encapsulated_helper_spec.rb +2 -2
  59. data/spec/experiment_spec.rb +116 -12
  60. data/spec/goals_collection_spec.rb +1 -1
  61. data/spec/helper_spec.rb +186 -112
  62. data/spec/persistence/cookie_adapter_spec.rb +1 -1
  63. data/spec/persistence/dual_adapter_spec.rb +160 -68
  64. data/spec/persistence/redis_adapter_spec.rb +9 -0
  65. data/spec/redis_interface_spec.rb +0 -69
  66. data/spec/spec_helper.rb +5 -6
  67. data/spec/trial_spec.rb +45 -19
  68. data/spec/user_spec.rb +45 -3
  69. data/split.gemspec +8 -9
  70. metadata +28 -36
  71. data/.travis.yml +0 -66
  72. data/gemfiles/4.2.gemfile +0 -9
data/spec/helper_spec.rb CHANGED
@@ -183,8 +183,7 @@ describe Split::Helper do
183
183
  ab_test('link_color', {'blue' => 0.01}, 'red' => 0.2)
184
184
  experiment = Split::ExperimentCatalog.find('link_color')
185
185
  expect(experiment.alternatives.map(&:name)).to eq(['blue', 'red'])
186
- # TODO: persist alternative weights
187
- # 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])
188
187
  end
189
188
 
190
189
  it "should only let a user participate in one experiment at a time" do
@@ -230,13 +229,15 @@ describe Split::Helper do
230
229
 
231
230
  context "when user already has experiment" do
232
231
  let(:mock_user){ Split::User.new(self, {'test_0' => 'test-alt'}) }
233
- before{
232
+
233
+ before do
234
234
  Split.configure do |config|
235
235
  config.allow_multiple_experiments = 'control'
236
236
  end
237
+
237
238
  Split::ExperimentCatalog.find_or_initialize('test_0', 'control', 'test-alt').save
238
239
  Split::ExperimentCatalog.find_or_initialize('test_1', 'control', 'test-alt').save
239
- }
240
+ end
240
241
 
241
242
  it "should restore previously selected alternative" do
242
243
  expect(ab_user.active_experiments.size).to eq 1
@@ -244,6 +245,16 @@ describe Split::Helper do
244
245
  expect(ab_test(:test_0, {'control' => 1}, {"test-alt" => 100})).to eq 'test-alt'
245
246
  end
246
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
+
247
258
  it "lets override existing choice" do
248
259
  pending "this requires user store reset on first call not depending on whelther it is current trial"
249
260
  @params = { 'ab_test' => { 'test_1' => 'test-alt' } }
@@ -266,129 +277,187 @@ describe Split::Helper do
266
277
  end
267
278
 
268
279
  describe 'metadata' do
269
- before do
270
- Split.configuration.experiments = {
271
- :my_experiment => {
272
- :alternatives => ["one", "two"],
273
- :resettable => false,
274
- :metadata => { 'one' => 'Meta1', 'two' => 'Meta2' }
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
+ }
275
288
  }
276
- }
277
- end
289
+ end
278
290
 
279
- it 'should be passed to helper block' do
280
- @params = { 'ab_test' => { 'my_experiment' => 'one' } }
281
- expect(ab_test('my_experiment')).to eq 'one'
282
- expect(ab_test('my_experiment') do |alternative, meta|
283
- meta
284
- end).to eq('Meta1')
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
285
309
  end
286
310
 
287
- it 'should pass empty hash to helper block if library disabled' do
288
- Split.configure do |config|
289
- config.enabled = false
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({})
290
326
  end
291
327
 
292
- expect(ab_test('my_experiment')).to eq 'one'
293
- expect(ab_test('my_experiment') do |_, meta|
294
- meta
295
- end).to eq({})
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
296
337
  end
297
338
  end
298
339
 
299
340
  describe 'ab_finished' 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
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
307
349
 
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
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
313
355
 
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
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
319
361
 
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
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
325
367
 
326
- it 'should not increment the counter for an experiment that the user is not participating in' do
327
- ab_test('button_size', 'small', 'big')
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
328
377
 
329
- # So, user should be participating in the link_color experiment and
330
- # receive the control for button_size. As the user is not participating in
331
- # the button size experiment, finishing it should not increase the
332
- # completion count for that alternative.
333
- expect(lambda {
334
- ab_finished('button_size')
335
- }).not_to change { Split::Alternative.new('small', 'button_size').completed_count }
336
- 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
337
383
 
338
- it 'should not increment the counter for an ended experiment' do
339
- e = Split::ExperimentCatalog.find_or_create('button_size', 'small', 'big')
340
- e.winner = 'small'
341
- a = ab_test('button_size', 'small', 'big')
342
- expect(a).to eq('small')
343
- expect(lambda {
344
- ab_finished('button_size')
345
- }).not_to change { Split::Alternative.new(a, 'button_size').completed_count }
346
- 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
347
390
 
348
- it "should clear out the user's participation from their session" do
349
- expect(ab_user[@experiment.key]).to eq(@alternative_name)
350
- ab_finished(@experiment_name)
351
- expect(ab_user.keys).to be_empty
352
- end
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
353
396
 
354
- it "should not clear out the users session if reset is false" do
355
- expect(ab_user[@experiment.key]).to eq(@alternative_name)
356
- ab_finished(@experiment_name, {:reset => false})
357
- expect(ab_user[@experiment.key]).to eq(@alternative_name)
358
- expect(ab_user[@experiment.finished_key]).to eq(true)
359
- 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)
360
400
 
361
- it "should reset the users session when experiment is not versioned" do
362
- expect(ab_user[@experiment.key]).to eq(@alternative_name)
363
- ab_finished(@experiment_name)
364
- expect(ab_user.keys).to be_empty
365
- 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
366
405
 
367
- it "should reset the users session when experiment is versioned" do
368
- @experiment.increment_version
369
- @alternative_name = ab_test(@experiment_name, *@alternatives)
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
370
412
 
371
- expect(ab_user[@experiment.key]).to eq(@alternative_name)
372
- ab_finished(@experiment_name)
373
- expect(ab_user.keys).to be_empty
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
374
419
  end
375
420
 
376
- it "should do nothing where the experiment was not started by this user" do
377
- ab_user = nil
378
- expect(lambda { ab_finished('some_experiment_not_started_by_the_user') }).not_to raise_exception
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
379
438
  end
380
439
 
381
- context "when on_trial_complete is set" do
382
- before { Split.configuration.on_trial_complete = :some_method }
383
- it "should call the method" do
384
- expect(self).to receive(:some_method)
385
- ab_finished(@experiment_name)
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
386
446
  end
387
447
 
388
- it "should not call the method without alternative" do
389
- ab_user[@experiment.key] = nil
390
- expect(self).not_to receive(:some_method)
391
- ab_finished(@experiment_name)
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)
392
461
  end
393
462
  end
394
463
  end
@@ -528,6 +597,17 @@ describe Split::Helper do
528
597
  expect(active_experiments.first[0]).to eq "link_color"
529
598
  end
530
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
+
531
611
  it 'should show multiple tests' do
532
612
  Split.configure do |config|
533
613
  config.allow_multiple_experiments = true
@@ -545,7 +625,7 @@ describe Split::Helper do
545
625
  end
546
626
  e = Split::ExperimentCatalog.find_or_create('def', '4', '5', '6')
547
627
  e.winner = '4'
548
- alternative = ab_test('def', '4', '5', '6')
628
+ ab_test('def', '4', '5', '6')
549
629
  another_alternative = ab_test('ghi', '7', '8', '9')
550
630
  expect(active_experiments.count).to eq 1
551
631
  expect(active_experiments.first[0]).to eq "ghi"
@@ -1021,8 +1101,8 @@ describe Split::Helper do
1021
1101
 
1022
1102
  it 'should handle multiple experiments correctly' do
1023
1103
  experiment2 = Split::ExperimentCatalog.find_or_create('link_color2', 'blue', 'red')
1024
- alternative_name = ab_test('link_color', 'blue', 'red')
1025
- alternative_name2 = ab_test('link_color2', 'blue', 'red')
1104
+ ab_test('link_color', 'blue', 'red')
1105
+ ab_test('link_color2', 'blue', 'red')
1026
1106
  ab_finished('link_color2')
1027
1107
 
1028
1108
  experiment2.alternatives.each do |alt|
@@ -1058,15 +1138,9 @@ describe Split::Helper do
1058
1138
  end
1059
1139
 
1060
1140
  it "should increment the counter for the specified-goal completed alternative" do
1061
- expect(lambda {
1062
- expect(lambda {
1063
- ab_finished({"link_color" => ["purchase"]})
1064
- }).not_to change {
1065
- Split::Alternative.new(@alternative_name, @experiment_name).completed_count(@goal2)
1066
- }
1067
- }).to change {
1068
- Split::Alternative.new(@alternative_name, @experiment_name).completed_count(@goal1)
1069
- }.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)
1070
1144
  end
1071
1145
  end
1072
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} -0000\Z/)
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
@@ -1,102 +1,194 @@
1
1
  # frozen_string_literal: true
2
- require "spec_helper"
2
+
3
+ require 'spec_helper'
3
4
 
4
5
  describe Split::Persistence::DualAdapter do
6
+ let(:context) { 'some context' }
5
7
 
6
- let(:context){ "some context" }
7
-
8
- let(:just_adapter){ Class.new }
9
- let(:selected_adapter_instance){ double }
10
- let(:selected_adapter){
11
- c = Class.new
12
- expect(c).to receive(:new){ selected_adapter_instance }
13
- c
14
- }
15
- let(:not_selected_adapter){
16
- c = Class.new
17
- expect(c).not_to receive(:new)
18
- c
19
- }
20
-
21
- shared_examples_for "forwarding calls" do
22
- it "#[]=" do
23
- expect(selected_adapter_instance).to receive(:[]=).with('my_key', 'my_value')
24
- expect_any_instance_of(not_selected_adapter).not_to receive(:[]=)
25
- subject["my_key"] = "my_value"
26
- end
8
+ let(:logged_in_adapter_instance) { double }
9
+ let(:logged_in_adapter) do
10
+ Class.new.tap { |c| allow(c).to receive(:new) { logged_in_adapter_instance } }
11
+ end
12
+ let(:logged_out_adapter_instance) { double }
13
+ let(:logged_out_adapter) do
14
+ Class.new.tap { |c| allow(c).to receive(:new) { logged_out_adapter_instance } }
15
+ end
27
16
 
28
- it "#[]" do
29
- expect(selected_adapter_instance).to receive(:[]).with('my_key'){'my_value'}
30
- expect_any_instance_of(not_selected_adapter).not_to receive(:[])
31
- expect(subject["my_key"]).to eq('my_value')
32
- end
17
+ context 'when fallback_to_logged_out_adapter is false' do
18
+ context 'when logged in' do
19
+ subject do
20
+ described_class.with_config(
21
+ logged_in: lambda { |context| true },
22
+ logged_in_adapter: logged_in_adapter,
23
+ logged_out_adapter: logged_out_adapter,
24
+ fallback_to_logged_out_adapter: false
25
+ ).new(context)
26
+ end
27
+
28
+ it '#[]=' do
29
+ expect(logged_in_adapter_instance).to receive(:[]=).with('my_key', 'my_value')
30
+ expect_any_instance_of(logged_out_adapter).not_to receive(:[]=)
31
+ subject['my_key'] = 'my_value'
32
+ end
33
+
34
+ it '#[]' do
35
+ expect(logged_in_adapter_instance).to receive(:[]).with('my_key') { 'my_value' }
36
+ expect_any_instance_of(logged_out_adapter).not_to receive(:[])
37
+ expect(subject['my_key']).to eq('my_value')
38
+ end
33
39
 
34
- it "#delete" do
35
- expect(selected_adapter_instance).to receive(:delete).with('my_key'){'my_value'}
36
- expect_any_instance_of(not_selected_adapter).not_to receive(:delete)
37
- expect(subject.delete("my_key")).to eq('my_value')
40
+ it '#delete' do
41
+ expect(logged_in_adapter_instance).to receive(:delete).with('my_key') { 'my_value' }
42
+ expect_any_instance_of(logged_out_adapter).not_to receive(:delete)
43
+ expect(subject.delete('my_key')).to eq('my_value')
44
+ end
45
+
46
+ it '#keys' do
47
+ expect(logged_in_adapter_instance).to receive(:keys) { ['my_value'] }
48
+ expect_any_instance_of(logged_out_adapter).not_to receive(:keys)
49
+ expect(subject.keys).to eq(['my_value'])
50
+ end
38
51
  end
39
52
 
40
- it "#keys" do
41
- expect(selected_adapter_instance).to receive(:keys){'my_value'}
42
- expect_any_instance_of(not_selected_adapter).not_to receive(:keys)
43
- expect(subject.keys).to eq('my_value')
53
+ context 'when logged out' do
54
+ subject do
55
+ described_class.with_config(
56
+ logged_in: lambda { |context| false },
57
+ logged_in_adapter: logged_in_adapter,
58
+ logged_out_adapter: logged_out_adapter,
59
+ fallback_to_logged_out_adapter: false
60
+ ).new(context)
61
+ end
62
+
63
+ it '#[]=' do
64
+ expect_any_instance_of(logged_in_adapter).not_to receive(:[]=)
65
+ expect(logged_out_adapter_instance).to receive(:[]=).with('my_key', 'my_value')
66
+ subject['my_key'] = 'my_value'
67
+ end
68
+
69
+ it '#[]' do
70
+ expect_any_instance_of(logged_in_adapter).not_to receive(:[])
71
+ expect(logged_out_adapter_instance).to receive(:[]).with('my_key') { 'my_value' }
72
+ expect(subject['my_key']).to eq('my_value')
73
+ end
74
+
75
+ it '#delete' do
76
+ expect_any_instance_of(logged_in_adapter).not_to receive(:delete)
77
+ expect(logged_out_adapter_instance).to receive(:delete).with('my_key') { 'my_value' }
78
+ expect(subject.delete('my_key')).to eq('my_value')
79
+ end
80
+
81
+ it '#keys' do
82
+ expect_any_instance_of(logged_in_adapter).not_to receive(:keys)
83
+ expect(logged_out_adapter_instance).to receive(:keys) { ['my_value', 'my_value2'] }
84
+ expect(subject.keys).to eq(['my_value', 'my_value2'])
85
+ end
44
86
  end
45
87
  end
46
88
 
47
- context "when logged in" do
48
- subject {
49
- described_class.with_config(
50
- logged_in: lambda { |context| true },
51
- logged_in_adapter: selected_adapter,
52
- logged_out_adapter: not_selected_adapter
89
+ context 'when fallback_to_logged_out_adapter is true' do
90
+ context 'when logged in' do
91
+ subject do
92
+ described_class.with_config(
93
+ logged_in: lambda { |context| true },
94
+ logged_in_adapter: logged_in_adapter,
95
+ logged_out_adapter: logged_out_adapter,
96
+ fallback_to_logged_out_adapter: true
53
97
  ).new(context)
54
- }
98
+ end
55
99
 
56
- it_should_behave_like "forwarding calls"
57
- end
100
+ it '#[]=' do
101
+ expect(logged_in_adapter_instance).to receive(:[]=).with('my_key', 'my_value')
102
+ expect(logged_out_adapter_instance).to receive(:[]=).with('my_key', 'my_value')
103
+ expect(logged_out_adapter_instance).to receive(:[]).with('my_key') { nil }
104
+ subject['my_key'] = 'my_value'
105
+ end
106
+
107
+ it '#[]' do
108
+ expect(logged_in_adapter_instance).to receive(:[]).with('my_key') { 'my_value' }
109
+ expect_any_instance_of(logged_out_adapter).not_to receive(:[])
110
+ expect(subject['my_key']).to eq('my_value')
111
+ end
112
+
113
+ it '#delete' do
114
+ expect(logged_in_adapter_instance).to receive(:delete).with('my_key') { 'my_value' }
115
+ expect(logged_out_adapter_instance).to receive(:delete).with('my_key') { 'my_value' }
116
+ expect(subject.delete('my_key')).to eq('my_value')
117
+ end
118
+
119
+ it '#keys' do
120
+ expect(logged_in_adapter_instance).to receive(:keys) { ['my_value'] }
121
+ expect(logged_out_adapter_instance).to receive(:keys) { ['my_value', 'my_value2'] }
122
+ expect(subject.keys).to eq(['my_value', 'my_value2'])
123
+ end
124
+ end
58
125
 
59
- context "when not logged in" do
60
- subject {
61
- described_class.with_config(
62
- logged_in: lambda { |context| false },
63
- logged_in_adapter: not_selected_adapter,
64
- logged_out_adapter: selected_adapter
126
+ context 'when logged out' do
127
+ subject do
128
+ described_class.with_config(
129
+ logged_in: lambda { |context| false },
130
+ logged_in_adapter: logged_in_adapter,
131
+ logged_out_adapter: logged_out_adapter,
132
+ fallback_to_logged_out_adapter: true
65
133
  ).new(context)
66
- }
134
+ end
135
+
136
+ it '#[]=' do
137
+ expect_any_instance_of(logged_in_adapter).not_to receive(:[]=)
138
+ expect(logged_out_adapter_instance).to receive(:[]=).with('my_key', 'my_value')
139
+ expect(logged_out_adapter_instance).to receive(:[]).with('my_key') { nil }
140
+ subject['my_key'] = 'my_value'
141
+ end
142
+
143
+ it '#[]' do
144
+ expect_any_instance_of(logged_in_adapter).not_to receive(:[])
145
+ expect(logged_out_adapter_instance).to receive(:[]).with('my_key') { 'my_value' }
146
+ expect(subject['my_key']).to eq('my_value')
147
+ end
148
+
149
+ it '#delete' do
150
+ expect(logged_in_adapter_instance).to receive(:delete).with('my_key') { 'my_value' }
151
+ expect(logged_out_adapter_instance).to receive(:delete).with('my_key') { 'my_value' }
152
+ expect(subject.delete('my_key')).to eq('my_value')
153
+ end
67
154
 
68
- it_should_behave_like "forwarding calls"
155
+ it '#keys' do
156
+ expect(logged_in_adapter_instance).to receive(:keys) { ['my_value'] }
157
+ expect(logged_out_adapter_instance).to receive(:keys) { ['my_value', 'my_value2'] }
158
+ expect(subject.keys).to eq(['my_value', 'my_value2'])
159
+ end
160
+ end
69
161
  end
70
162
 
71
- describe "when errors in config" do
72
- before{
73
- described_class.config.clear
74
- }
75
- let(:some_proc){ ->{} }
76
- it "when no logged in adapter" do
163
+ describe 'when errors in config' do
164
+ before { described_class.config.clear }
165
+ let(:some_proc) { ->{} }
166
+
167
+ it 'when no logged in adapter' do
77
168
  expect{
78
169
  described_class.with_config(
79
170
  logged_in: some_proc,
80
- logged_out_adapter: just_adapter
81
- ).new(context)
171
+ logged_out_adapter: logged_out_adapter
172
+ ).new(context)
82
173
  }.to raise_error(StandardError, /:logged_in_adapter/)
83
174
  end
84
- it "when no logged out adapter" do
175
+
176
+ it 'when no logged out adapter' do
85
177
  expect{
86
178
  described_class.with_config(
87
179
  logged_in: some_proc,
88
- logged_in_adapter: just_adapter
89
- ).new(context)
180
+ logged_in_adapter: logged_in_adapter
181
+ ).new(context)
90
182
  }.to raise_error(StandardError, /:logged_out_adapter/)
91
183
  end
92
- it "when no logged in detector" do
184
+
185
+ it 'when no logged in detector' do
93
186
  expect{
94
187
  described_class.with_config(
95
- logged_in_adapter: just_adapter,
96
- logged_out_adapter: just_adapter
97
- ).new(context)
188
+ logged_in_adapter: logged_in_adapter,
189
+ logged_out_adapter: logged_out_adapter
190
+ ).new(context)
98
191
  }.to raise_error(StandardError, /:logged_in$/)
99
192
  end
100
193
  end
101
-
102
194
  end