split 3.3.0 → 4.0.0.pre
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.eslintrc +1 -1
- data/.github/FUNDING.yml +1 -0
- data/.github/ISSUE_TEMPLATE/bug_report.md +24 -0
- data/.rspec +1 -0
- data/.rubocop.yml +71 -1044
- data/.rubocop_todo.yml +226 -0
- data/.travis.yml +18 -39
- data/Appraisals +4 -0
- data/CHANGELOG.md +110 -0
- data/CODE_OF_CONDUCT.md +3 -3
- data/Gemfile +2 -0
- data/README.md +58 -23
- data/Rakefile +2 -0
- data/gemfiles/{4.2.gemfile → 6.0.gemfile} +1 -1
- data/lib/split.rb +16 -3
- 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 +4 -3
- data/lib/split/cache.rb +28 -0
- data/lib/split/combined_experiments_helper.rb +3 -2
- data/lib/split/configuration.rb +15 -14
- data/lib/split/dashboard.rb +19 -1
- data/lib/split/dashboard/helpers.rb +3 -2
- data/lib/split/dashboard/pagination_helpers.rb +4 -4
- data/lib/split/dashboard/paginator.rb +1 -0
- data/lib/split/dashboard/public/dashboard.js +10 -0
- data/lib/split/dashboard/public/style.css +5 -0
- data/lib/split/dashboard/views/_controls.erb +13 -0
- data/lib/split/dashboard/views/layout.erb +1 -1
- data/lib/split/encapsulated_helper.rb +3 -2
- data/lib/split/engine.rb +7 -4
- data/lib/split/exceptions.rb +1 -0
- data/lib/split/experiment.rb +98 -65
- 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 +30 -10
- data/lib/split/metric.rb +2 -1
- data/lib/split/persistence.rb +4 -2
- data/lib/split/persistence/cookie_adapter.rb +1 -0
- 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/redis_interface.rb +9 -28
- data/lib/split/trial.rb +25 -17
- data/lib/split/user.rb +19 -3
- data/lib/split/version.rb +2 -4
- data/lib/split/zscore.rb +1 -0
- data/spec/alternative_spec.rb +1 -1
- data/spec/cache_spec.rb +88 -0
- data/spec/configuration_spec.rb +1 -14
- data/spec/dashboard/pagination_helpers_spec.rb +3 -1
- 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 +116 -12
- data/spec/goals_collection_spec.rb +1 -1
- data/spec/helper_spec.rb +191 -112
- data/spec/persistence/cookie_adapter_spec.rb +1 -1
- 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/trial_spec.rb +65 -19
- data/spec/user_spec.rb +28 -0
- data/split.gemspec +9 -9
- metadata +34 -28
@@ -52,7 +52,7 @@ describe Split::Persistence::CookieAdapter do
|
|
52
52
|
it "puts multiple experiments in a single cookie" do
|
53
53
|
subject["foo"] = "FOO"
|
54
54
|
subject["bar"] = "BAR"
|
55
|
-
expect(context.response.headers["Set-Cookie"]).to match(/\Asplit=%7B%22foo%22%3A%22FOO%22%2C%22bar%22%3A%22BAR%22%7D; path=\/; expires=[a-zA-Z]{3}, \d{2} [a-zA-Z]{3} \d{4} \d{2}:\d{2}:\d{2} -
|
55
|
+
expect(context.response.headers["Set-Cookie"]).to match(/\Asplit=%7B%22foo%22%3A%22FOO%22%2C%22bar%22%3A%22BAR%22%7D; path=\/; expires=[a-zA-Z]{3}, \d{2} [a-zA-Z]{3} \d{4} \d{2}:\d{2}:\d{2} [A-Z]{3}\Z/)
|
56
56
|
end
|
57
57
|
|
58
58
|
it "ensure other added cookies are not overriden" do
|
@@ -1,102 +1,194 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
|
2
|
+
|
3
|
+
require 'spec_helper'
|
3
4
|
|
4
5
|
describe Split::Persistence::DualAdapter do
|
6
|
+
let(:context) { 'some context' }
|
5
7
|
|
6
|
-
let(:
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
let(:
|
11
|
-
|
12
|
-
|
13
|
-
|
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
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
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
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
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
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
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
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
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
|
-
|
57
|
-
|
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
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
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
|
-
|
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
|
72
|
-
before{
|
73
|
-
|
74
|
-
|
75
|
-
|
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:
|
81
|
-
|
171
|
+
logged_out_adapter: logged_out_adapter
|
172
|
+
).new(context)
|
82
173
|
}.to raise_error(StandardError, /:logged_in_adapter/)
|
83
174
|
end
|
84
|
-
|
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:
|
89
|
-
|
180
|
+
logged_in_adapter: logged_in_adapter
|
181
|
+
).new(context)
|
90
182
|
}.to raise_error(StandardError, /:logged_out_adapter/)
|
91
183
|
end
|
92
|
-
|
184
|
+
|
185
|
+
it 'when no logged in detector' do
|
93
186
|
expect{
|
94
187
|
described_class.with_config(
|
95
|
-
logged_in_adapter:
|
96
|
-
logged_out_adapter:
|
97
|
-
|
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
|
@@ -60,6 +60,15 @@ describe Split::Persistence::RedisAdapter do
|
|
60
60
|
end
|
61
61
|
end
|
62
62
|
|
63
|
+
describe '#find' do
|
64
|
+
before { Split::Persistence::RedisAdapter.with_config(:lookup_by => proc{'frag'}, :namespace => 'a_namespace') }
|
65
|
+
|
66
|
+
it "should create and user from a given key" do
|
67
|
+
adapter = Split::Persistence::RedisAdapter.find(2)
|
68
|
+
expect(adapter.redis_key).to eq("a_namespace:2")
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
63
72
|
context 'functional tests' do
|
64
73
|
before { Split::Persistence::RedisAdapter.with_config(:lookup_by => 'lookup') }
|
65
74
|
|
@@ -29,75 +29,6 @@ describe Split::RedisInterface do
|
|
29
29
|
end
|
30
30
|
end
|
31
31
|
|
32
|
-
describe '#add_to_list' do
|
33
|
-
subject(:add_to_list) do
|
34
|
-
interface.add_to_list(list_name, 'y')
|
35
|
-
interface.add_to_list(list_name, 'z')
|
36
|
-
end
|
37
|
-
|
38
|
-
specify do
|
39
|
-
add_to_list
|
40
|
-
expect(Split.redis.lindex(list_name, 0)).to eq 'y'
|
41
|
-
expect(Split.redis.lindex(list_name, 1)).to eq 'z'
|
42
|
-
expect(Split.redis.llen(list_name)).to eq 2
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
|
-
describe '#set_list_index' do
|
47
|
-
subject(:set_list_index) do
|
48
|
-
interface.add_to_list(list_name, 'y')
|
49
|
-
interface.add_to_list(list_name, 'z')
|
50
|
-
interface.set_list_index(list_name, 0, 'a')
|
51
|
-
end
|
52
|
-
|
53
|
-
specify do
|
54
|
-
set_list_index
|
55
|
-
expect(Split.redis.lindex(list_name, 0)).to eq 'a'
|
56
|
-
expect(Split.redis.lindex(list_name, 1)).to eq 'z'
|
57
|
-
expect(Split.redis.llen(list_name)).to eq 2
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
describe '#list_length' do
|
62
|
-
subject(:list_length) do
|
63
|
-
interface.add_to_list(list_name, 'y')
|
64
|
-
interface.add_to_list(list_name, 'z')
|
65
|
-
interface.list_length(list_name)
|
66
|
-
end
|
67
|
-
|
68
|
-
specify do
|
69
|
-
expect(list_length).to eq 2
|
70
|
-
end
|
71
|
-
end
|
72
|
-
|
73
|
-
describe '#remove_last_item_from_list' do
|
74
|
-
subject(:remove_last_item_from_list) do
|
75
|
-
interface.add_to_list(list_name, 'y')
|
76
|
-
interface.add_to_list(list_name, 'z')
|
77
|
-
interface.remove_last_item_from_list(list_name)
|
78
|
-
end
|
79
|
-
|
80
|
-
specify do
|
81
|
-
remove_last_item_from_list
|
82
|
-
expect(Split.redis.lindex(list_name, 0)).to eq 'y'
|
83
|
-
expect(Split.redis.llen(list_name)).to eq 1
|
84
|
-
end
|
85
|
-
end
|
86
|
-
|
87
|
-
describe '#make_list_length' do
|
88
|
-
subject(:make_list_length) do
|
89
|
-
interface.add_to_list(list_name, 'y')
|
90
|
-
interface.add_to_list(list_name, 'z')
|
91
|
-
interface.make_list_length(list_name, 1)
|
92
|
-
end
|
93
|
-
|
94
|
-
specify do
|
95
|
-
make_list_length
|
96
|
-
expect(Split.redis.lindex(list_name, 0)).to eq 'y'
|
97
|
-
expect(Split.redis.llen(list_name)).to eq 1
|
98
|
-
end
|
99
|
-
end
|
100
|
-
|
101
32
|
describe '#add_to_set' do
|
102
33
|
subject(:add_to_set) do
|
103
34
|
interface.add_to_set(set_name, 'something')
|
data/spec/spec_helper.rb
CHANGED
@@ -13,17 +13,16 @@ require 'yaml'
|
|
13
13
|
|
14
14
|
Dir['./spec/support/*.rb'].each { |f| require f }
|
15
15
|
|
16
|
-
require "fakeredis"
|
17
|
-
|
18
|
-
G_fakeredis = Redis.new
|
19
|
-
|
20
16
|
module GlobalSharedContext
|
21
17
|
extend RSpec::SharedContext
|
22
18
|
let(:mock_user){ Split::User.new(double(session: {})) }
|
19
|
+
|
23
20
|
before(:each) do
|
24
21
|
Split.configuration = Split::Configuration.new
|
25
|
-
Split.redis =
|
26
|
-
Split.redis.
|
22
|
+
Split.redis = Redis.new
|
23
|
+
Split.redis.select(10)
|
24
|
+
Split.redis.flushdb
|
25
|
+
Split::Cache.clear
|
27
26
|
@ab_user = mock_user
|
28
27
|
params = nil
|
29
28
|
end
|
data/spec/trial_spec.rb
CHANGED
@@ -176,6 +176,14 @@ describe Split::Trial do
|
|
176
176
|
|
177
177
|
expect_alternative(trial, 'basket')
|
178
178
|
end
|
179
|
+
|
180
|
+
context "when alternative is not found" do
|
181
|
+
it "falls back on next_alternative" do
|
182
|
+
user[experiment.key] = 'notfound'
|
183
|
+
expect(experiment).to receive(:next_alternative).and_call_original
|
184
|
+
expect_alternative(trial, alternatives)
|
185
|
+
end
|
186
|
+
end
|
179
187
|
end
|
180
188
|
|
181
189
|
context "when user is a new participant" do
|
@@ -190,42 +198,68 @@ describe Split::Trial do
|
|
190
198
|
expect(trial.alternative.name).to_not be_empty
|
191
199
|
Split.configuration.on_trial_choose = nil
|
192
200
|
end
|
201
|
+
|
202
|
+
it "assigns user to an alternative" do
|
203
|
+
trial.choose! context
|
204
|
+
|
205
|
+
expect(alternatives).to include(user[experiment.name])
|
206
|
+
end
|
207
|
+
|
208
|
+
context "when cohorting is disabled" do
|
209
|
+
before(:each) { allow(experiment).to receive(:cohorting_disabled?).and_return(true) }
|
210
|
+
|
211
|
+
it "picks the control and does not run on_trial callbacks" do
|
212
|
+
Split.configuration.on_trial = :on_trial_callback
|
213
|
+
|
214
|
+
expect(experiment).to_not receive(:next_alternative)
|
215
|
+
expect(context).not_to receive(:on_trial_callback)
|
216
|
+
expect_alternative(trial, 'basket')
|
217
|
+
|
218
|
+
Split.configuration.enabled = true
|
219
|
+
Split.configuration.on_trial = nil
|
220
|
+
end
|
221
|
+
|
222
|
+
it "user is not assigned an alternative" do
|
223
|
+
trial.choose! context
|
224
|
+
|
225
|
+
expect(user[experiment]).to eq(nil)
|
226
|
+
end
|
227
|
+
end
|
193
228
|
end
|
194
229
|
end
|
195
230
|
|
196
231
|
describe "#complete!" do
|
197
|
-
let(:trial) { Split::Trial.new(:user => user, :experiment => experiment) }
|
198
232
|
context 'when there are no goals' do
|
233
|
+
let(:trial) { Split::Trial.new(:user => user, :experiment => experiment) }
|
199
234
|
it 'should complete the trial' do
|
200
235
|
trial.choose!
|
201
236
|
old_completed_count = trial.alternative.completed_count
|
202
237
|
trial.complete!
|
203
|
-
expect(trial.alternative.completed_count).to
|
238
|
+
expect(trial.alternative.completed_count).to eq(old_completed_count + 1)
|
204
239
|
end
|
205
240
|
end
|
206
241
|
|
207
|
-
context
|
208
|
-
let(:goals) { [
|
242
|
+
context "when there are many goals" do
|
243
|
+
let(:goals) { [ "goal1", "goal2" ] }
|
209
244
|
let(:trial) { Split::Trial.new(:user => user, :experiment => experiment, :goals => goals) }
|
210
|
-
shared_examples_for "goal completion" do
|
211
|
-
it 'should not complete the trial' do
|
212
|
-
trial.choose!
|
213
|
-
old_completed_count = trial.alternative.completed_count
|
214
|
-
trial.complete!(goal)
|
215
|
-
expect(trial.alternative.completed_count).to_not be(old_completed_count+1)
|
216
|
-
end
|
217
|
-
end
|
218
245
|
|
219
|
-
|
220
|
-
|
221
|
-
|
246
|
+
it "increments the completed count corresponding to the goals" do
|
247
|
+
trial.choose!
|
248
|
+
old_completed_counts = goals.map{ |goal| [goal, trial.alternative.completed_count(goal)] }.to_h
|
249
|
+
trial.complete!
|
250
|
+
goals.each { | goal | expect(trial.alternative.completed_count(goal)).to eq(old_completed_counts[goal] + 1) }
|
222
251
|
end
|
252
|
+
end
|
223
253
|
|
224
|
-
|
225
|
-
|
226
|
-
|
254
|
+
context "when there is 1 goal of type string" do
|
255
|
+
let(:goal) { "goal" }
|
256
|
+
let(:trial) { Split::Trial.new(:user => user, :experiment => experiment, :goals => goal) }
|
257
|
+
it "increments the completed count corresponding to the goal" do
|
258
|
+
trial.choose!
|
259
|
+
old_completed_count = trial.alternative.completed_count(goal)
|
260
|
+
trial.complete!
|
261
|
+
expect(trial.alternative.completed_count(goal)).to eq(old_completed_count + 1)
|
227
262
|
end
|
228
|
-
|
229
263
|
end
|
230
264
|
end
|
231
265
|
|
@@ -275,5 +309,17 @@ describe Split::Trial do
|
|
275
309
|
trial.choose!
|
276
310
|
end
|
277
311
|
end
|
312
|
+
|
313
|
+
context 'when experiment has winner' do
|
314
|
+
let(:trial) do
|
315
|
+
experiment.winner = 'cart'
|
316
|
+
Split::Trial.new(:user => user, :experiment => experiment)
|
317
|
+
end
|
318
|
+
|
319
|
+
it 'does not store' do
|
320
|
+
expect(user).to_not receive("[]=")
|
321
|
+
trial.choose!
|
322
|
+
end
|
323
|
+
end
|
278
324
|
end
|
279
325
|
end
|