split 3.3.2 → 4.0.0.pre2

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 (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