split 3.2.0 → 4.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/.eslintrc +1 -1
- data/.github/FUNDING.yml +1 -0
- data/.github/ISSUE_TEMPLATE/bug_report.md +24 -0
- data/.github/dependabot.yml +7 -0
- data/.github/workflows/ci.yml +63 -0
- data/.rspec +1 -0
- data/.rubocop.yml +67 -1043
- data/CHANGELOG.md +174 -0
- data/CODE_OF_CONDUCT.md +3 -3
- data/CONTRIBUTING.md +1 -1
- data/Gemfile +6 -1
- data/README.md +79 -33
- data/Rakefile +6 -5
- data/lib/split/algorithms/block_randomization.rb +7 -6
- data/lib/split/algorithms/weighted_sample.rb +2 -1
- data/lib/split/algorithms/whiplash.rb +17 -18
- data/lib/split/algorithms.rb +14 -0
- data/lib/split/alternative.rb +25 -25
- data/lib/split/cache.rb +27 -0
- data/lib/split/combined_experiments_helper.rb +6 -5
- data/lib/split/configuration.rb +94 -91
- data/lib/split/dashboard/helpers.rb +9 -9
- data/lib/split/dashboard/pagination_helpers.rb +86 -0
- data/lib/split/dashboard/paginator.rb +17 -0
- data/lib/split/dashboard/public/dashboard.js +10 -0
- data/lib/split/dashboard/public/style.css +19 -2
- data/lib/split/dashboard/views/_controls.erb +13 -0
- data/lib/split/dashboard/views/_experiment.erb +2 -1
- data/lib/split/dashboard/views/index.erb +24 -5
- data/lib/split/dashboard/views/layout.erb +1 -1
- data/lib/split/dashboard.rb +47 -20
- data/lib/split/encapsulated_helper.rb +15 -8
- data/lib/split/engine.rb +7 -4
- data/lib/split/exceptions.rb +1 -0
- data/lib/split/experiment.rb +160 -122
- data/lib/split/experiment_catalog.rb +7 -8
- data/lib/split/extensions/string.rb +2 -1
- data/lib/split/goals_collection.rb +10 -10
- data/lib/split/helper.rb +56 -24
- data/lib/split/metric.rb +6 -6
- data/lib/split/persistence/cookie_adapter.rb +52 -15
- data/lib/split/persistence/dual_adapter.rb +53 -12
- data/lib/split/persistence/redis_adapter.rb +8 -4
- data/lib/split/persistence/session_adapter.rb +1 -2
- data/lib/split/persistence.rb +8 -6
- data/lib/split/redis_interface.rb +16 -31
- data/lib/split/trial.rb +48 -41
- data/lib/split/user.rb +30 -15
- data/lib/split/version.rb +2 -4
- data/lib/split/zscore.rb +2 -3
- data/lib/split.rb +39 -25
- data/spec/algorithms/block_randomization_spec.rb +6 -5
- data/spec/algorithms/weighted_sample_spec.rb +6 -5
- data/spec/algorithms/whiplash_spec.rb +4 -5
- data/spec/alternative_spec.rb +35 -36
- data/spec/cache_spec.rb +84 -0
- data/spec/combined_experiments_helper_spec.rb +18 -17
- data/spec/configuration_spec.rb +41 -45
- data/spec/dashboard/pagination_helpers_spec.rb +202 -0
- data/spec/dashboard/paginator_spec.rb +38 -0
- data/spec/dashboard_helpers_spec.rb +19 -18
- data/spec/dashboard_spec.rb +153 -48
- data/spec/encapsulated_helper_spec.rb +47 -23
- data/spec/experiment_catalog_spec.rb +14 -13
- data/spec/experiment_spec.rb +224 -111
- data/spec/goals_collection_spec.rb +18 -16
- data/spec/helper_spec.rb +539 -419
- data/spec/metric_spec.rb +14 -14
- data/spec/persistence/cookie_adapter_spec.rb +105 -27
- data/spec/persistence/dual_adapter_spec.rb +158 -66
- data/spec/persistence/redis_adapter_spec.rb +35 -27
- data/spec/persistence/session_adapter_spec.rb +2 -3
- data/spec/persistence_spec.rb +1 -2
- data/spec/redis_interface_spec.rb +25 -82
- data/spec/spec_helper.rb +38 -24
- data/spec/split_spec.rb +18 -18
- data/spec/support/cookies_mock.rb +1 -2
- data/spec/trial_spec.rb +117 -70
- data/spec/user_spec.rb +69 -27
- data/split.gemspec +26 -22
- metadata +85 -37
- data/.travis.yml +0 -41
- data/Appraisals +0 -13
- data/gemfiles/4.2.gemfile +0 -9
- data/gemfiles/5.0.gemfile +0 -10
- data/gemfiles/5.1.gemfile +0 -10
data/spec/cache_spec.rb
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
|
|
5
|
+
describe Split::Cache do
|
|
6
|
+
let(:namespace) { :test_namespace }
|
|
7
|
+
let(:key) { :test_key }
|
|
8
|
+
let(:now) { 1606189017 }
|
|
9
|
+
|
|
10
|
+
before { allow(Time).to receive(:now).and_return(now) }
|
|
11
|
+
|
|
12
|
+
describe "clear" do
|
|
13
|
+
before { Split.configuration.cache = true }
|
|
14
|
+
|
|
15
|
+
it "clears the cache" do
|
|
16
|
+
expect(Time).to receive(:now).and_return(now).exactly(2).times
|
|
17
|
+
Split::Cache.fetch(namespace, key) { Time.now }
|
|
18
|
+
Split::Cache.clear
|
|
19
|
+
Split::Cache.fetch(namespace, key) { Time.now }
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
describe "clear_key" do
|
|
24
|
+
before { Split.configuration.cache = true }
|
|
25
|
+
|
|
26
|
+
it "clears the cache" do
|
|
27
|
+
expect(Time).to receive(:now).and_return(now).exactly(3).times
|
|
28
|
+
Split::Cache.fetch(namespace, :key1) { Time.now }
|
|
29
|
+
Split::Cache.fetch(namespace, :key2) { Time.now }
|
|
30
|
+
Split::Cache.clear_key(:key1)
|
|
31
|
+
|
|
32
|
+
Split::Cache.fetch(namespace, :key1) { Time.now }
|
|
33
|
+
Split::Cache.fetch(namespace, :key2) { Time.now }
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
describe "fetch" do
|
|
38
|
+
subject { Split::Cache.fetch(namespace, key) { Time.now } }
|
|
39
|
+
|
|
40
|
+
context "when cache disabled" do
|
|
41
|
+
before { Split.configuration.cache = false }
|
|
42
|
+
|
|
43
|
+
it "returns the yield" do
|
|
44
|
+
expect(subject).to eql(now)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
it "yields every time" do
|
|
48
|
+
expect(Time).to receive(:now).and_return(now).exactly(2).times
|
|
49
|
+
Split::Cache.fetch(namespace, key) { Time.now }
|
|
50
|
+
Split::Cache.fetch(namespace, key) { Time.now }
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
context "when cache enabled" do
|
|
55
|
+
before { Split.configuration.cache = true }
|
|
56
|
+
|
|
57
|
+
it "returns the yield" do
|
|
58
|
+
expect(subject).to eql(now)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
it "yields once" do
|
|
62
|
+
expect(Time).to receive(:now).and_return(now).once
|
|
63
|
+
Split::Cache.fetch(namespace, key) { Time.now }
|
|
64
|
+
Split::Cache.fetch(namespace, key) { Time.now }
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
it "honors namespace" do
|
|
68
|
+
expect(Split::Cache.fetch(:a, key) { :a }).to eql(:a)
|
|
69
|
+
expect(Split::Cache.fetch(:b, key) { :b }).to eql(:b)
|
|
70
|
+
|
|
71
|
+
expect(Split::Cache.fetch(:a, key) { :a }).to eql(:a)
|
|
72
|
+
expect(Split::Cache.fetch(:b, key) { :b }).to eql(:b)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
it "honors key" do
|
|
76
|
+
expect(Split::Cache.fetch(namespace, :a) { :a }).to eql(:a)
|
|
77
|
+
expect(Split::Cache.fetch(namespace, :b) { :b }).to eql(:b)
|
|
78
|
+
|
|
79
|
+
expect(Split::Cache.fetch(namespace, :a) { :a }).to eql(:a)
|
|
80
|
+
expect(Split::Cache.fetch(namespace, :b) { :b }).to eql(:b)
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
@@ -1,57 +1,58 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
require "split/combined_experiments_helper"
|
|
4
5
|
|
|
5
6
|
describe Split::CombinedExperimentsHelper do
|
|
6
7
|
include Split::CombinedExperimentsHelper
|
|
7
8
|
|
|
8
|
-
describe
|
|
9
|
+
describe "ab_combined_test" do
|
|
9
10
|
let!(:config_enabled) { true }
|
|
10
|
-
let!(:combined_experiments) { [:exp_1_click, :exp_1_scroll ]}
|
|
11
|
+
let!(:combined_experiments) { [:exp_1_click, :exp_1_scroll ] }
|
|
11
12
|
let!(:allow_multiple_experiments) { true }
|
|
12
13
|
|
|
13
14
|
before do
|
|
14
15
|
Split.configuration.experiments = {
|
|
15
|
-
:
|
|
16
|
-
:
|
|
17
|
-
:
|
|
18
|
-
:
|
|
16
|
+
combined_exp_1: {
|
|
17
|
+
alternatives: [ { "control"=> 0.5 }, { "test-alt"=> 0.5 } ],
|
|
18
|
+
metric: :my_metric,
|
|
19
|
+
combined_experiments: combined_experiments
|
|
19
20
|
}
|
|
20
21
|
}
|
|
21
22
|
Split.configuration.enabled = config_enabled
|
|
22
23
|
Split.configuration.allow_multiple_experiments = allow_multiple_experiments
|
|
23
24
|
end
|
|
24
25
|
|
|
25
|
-
context
|
|
26
|
+
context "without config enabled" do
|
|
26
27
|
let!(:config_enabled) { false }
|
|
27
28
|
|
|
28
29
|
it "raises an error" do
|
|
29
|
-
expect
|
|
30
|
+
expect { ab_combined_test :combined_exp_1 }.to raise_error(Split::InvalidExperimentsFormatError)
|
|
30
31
|
end
|
|
31
32
|
end
|
|
32
33
|
|
|
33
|
-
context
|
|
34
|
+
context "multiple experiments disabled" do
|
|
34
35
|
let!(:allow_multiple_experiments) { false }
|
|
35
36
|
|
|
36
37
|
it "raises an error if multiple experiments is disabled" do
|
|
37
|
-
expect
|
|
38
|
+
expect { ab_combined_test :combined_exp_1 }.to raise_error(Split::InvalidExperimentsFormatError)
|
|
38
39
|
end
|
|
39
40
|
end
|
|
40
41
|
|
|
41
|
-
context
|
|
42
|
+
context "without combined experiments" do
|
|
42
43
|
let!(:combined_experiments) { nil }
|
|
43
44
|
|
|
44
45
|
it "raises an error" do
|
|
45
|
-
expect
|
|
46
|
+
expect { ab_combined_test :combined_exp_1 }.to raise_error(Split::InvalidExperimentsFormatError)
|
|
46
47
|
end
|
|
47
48
|
end
|
|
48
49
|
|
|
49
50
|
it "uses same alternative for all sub experiments and returns the alternative" do
|
|
50
51
|
allow(self).to receive(:get_alternative) { "test-alt" }
|
|
51
|
-
expect(self).to receive(:ab_test).with(:exp_1_click, {"control"=>0.5}, {"test-alt"=>0.5}) { "test-alt" }
|
|
52
|
-
expect(self).to receive(:ab_test).with(:exp_1_scroll, [{"control" => 0, "test-alt" => 1}])
|
|
52
|
+
expect(self).to receive(:ab_test).with(:exp_1_click, { "control"=>0.5 }, { "test-alt"=>0.5 }) { "test-alt" }
|
|
53
|
+
expect(self).to receive(:ab_test).with(:exp_1_scroll, [{ "control" => 0, "test-alt" => 1 }])
|
|
53
54
|
|
|
54
|
-
expect(ab_combined_test(
|
|
55
|
+
expect(ab_combined_test("combined_exp_1")).to eq("test-alt")
|
|
55
56
|
end
|
|
56
57
|
end
|
|
57
58
|
end
|
data/spec/configuration_spec.rb
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
-
require 'spec_helper'
|
|
3
2
|
|
|
4
|
-
|
|
3
|
+
require "spec_helper"
|
|
5
4
|
|
|
5
|
+
describe Split::Configuration do
|
|
6
6
|
before(:each) { @config = Split::Configuration.new }
|
|
7
7
|
|
|
8
8
|
it "should provide a default value for ignore_ip_addresses" do
|
|
@@ -58,17 +58,15 @@ describe Split::Configuration do
|
|
|
58
58
|
end
|
|
59
59
|
|
|
60
60
|
it "should load a metric" do
|
|
61
|
-
@config.experiments = {:
|
|
62
|
-
{:alternatives=>["control_opt", "other_opt"], :metric=>:my_metric}}
|
|
61
|
+
@config.experiments = { my_experiment: { alternatives: ["control_opt", "other_opt"], metric: :my_metric } }
|
|
63
62
|
|
|
64
63
|
expect(@config.metrics).not_to be_nil
|
|
65
64
|
expect(@config.metrics.keys).to eq([:my_metric])
|
|
66
65
|
end
|
|
67
66
|
|
|
68
67
|
it "should allow loading of experiment using experment_for" do
|
|
69
|
-
@config.experiments = {:
|
|
70
|
-
|
|
71
|
-
expect(@config.experiment_for(:my_experiment)).to eq({:alternatives=>["control_opt", ["other_opt"]]})
|
|
68
|
+
@config.experiments = { my_experiment: { alternatives: ["control_opt", "other_opt"], metric: :my_metric } }
|
|
69
|
+
expect(@config.experiment_for(:my_experiment)).to eq({ alternatives: ["control_opt", ["other_opt"]] })
|
|
72
70
|
end
|
|
73
71
|
|
|
74
72
|
context "when experiments are defined via YAML" do
|
|
@@ -82,12 +80,12 @@ describe Split::Configuration do
|
|
|
82
80
|
- Alt One
|
|
83
81
|
- Alt Two
|
|
84
82
|
resettable: false
|
|
85
|
-
|
|
83
|
+
eos
|
|
86
84
|
@config.experiments = YAML.load(experiments_yaml)
|
|
87
85
|
end
|
|
88
86
|
|
|
89
|
-
it
|
|
90
|
-
expect(@config.normalized_experiments).to eq({:
|
|
87
|
+
it "should normalize experiments" do
|
|
88
|
+
expect(@config.normalized_experiments).to eq({ my_experiment: { resettable: false, alternatives: ["Control Opt", ["Alt One", "Alt Two"]] } })
|
|
91
89
|
end
|
|
92
90
|
end
|
|
93
91
|
|
|
@@ -110,14 +108,14 @@ describe Split::Configuration do
|
|
|
110
108
|
Alt Two:
|
|
111
109
|
text: 'Alternative Two'
|
|
112
110
|
resettable: false
|
|
113
|
-
|
|
111
|
+
eos
|
|
114
112
|
@config.experiments = YAML.load(experiments_yaml)
|
|
115
113
|
end
|
|
116
114
|
|
|
117
|
-
it
|
|
115
|
+
it "should have metadata on the experiment" do
|
|
118
116
|
meta = @config.normalized_experiments[:my_experiment][:metadata]
|
|
119
117
|
expect(meta).to_not be nil
|
|
120
|
-
expect(meta[
|
|
118
|
+
expect(meta["Control Opt"]["text"]).to eq("Control Option")
|
|
121
119
|
end
|
|
122
120
|
end
|
|
123
121
|
|
|
@@ -138,25 +136,23 @@ describe Split::Configuration do
|
|
|
138
136
|
alternatives:
|
|
139
137
|
- a
|
|
140
138
|
- b
|
|
141
|
-
|
|
139
|
+
eos
|
|
142
140
|
@config.experiments = YAML.load(experiments_yaml)
|
|
143
141
|
end
|
|
144
142
|
|
|
145
143
|
it "should normalize experiments" do
|
|
146
|
-
expect(@config.normalized_experiments).to eq({:
|
|
147
|
-
[{"Alt One"=>0.1}, {"Alt Two"=>0.23}]]}, :
|
|
144
|
+
expect(@config.normalized_experiments).to eq({ my_experiment: { resettable: false, alternatives: [{ "Control Opt"=>0.67 },
|
|
145
|
+
[{ "Alt One"=>0.1 }, { "Alt Two"=>0.23 }]] }, another_experiment: { alternatives: ["a", ["b"]] } })
|
|
148
146
|
end
|
|
149
147
|
|
|
150
148
|
it "should recognize metrics" do
|
|
151
149
|
expect(@config.metrics).not_to be_nil
|
|
152
150
|
expect(@config.metrics.keys).to eq([:my_metric])
|
|
153
151
|
end
|
|
154
|
-
|
|
155
152
|
end
|
|
156
153
|
end
|
|
157
154
|
|
|
158
155
|
context "as symbols" do
|
|
159
|
-
|
|
160
156
|
context "with valid YAML" do
|
|
161
157
|
before do
|
|
162
158
|
experiments_yaml = <<-eos
|
|
@@ -166,21 +162,20 @@ describe Split::Configuration do
|
|
|
166
162
|
- Alt One
|
|
167
163
|
- Alt Two
|
|
168
164
|
:resettable: false
|
|
169
|
-
|
|
165
|
+
eos
|
|
170
166
|
@config.experiments = YAML.load(experiments_yaml)
|
|
171
167
|
end
|
|
172
168
|
|
|
173
169
|
it "should normalize experiments" do
|
|
174
|
-
expect(@config.normalized_experiments).to eq({:
|
|
170
|
+
expect(@config.normalized_experiments).to eq({ my_experiment: { resettable: false, alternatives: ["Control Opt", ["Alt One", "Alt Two"]] } })
|
|
175
171
|
end
|
|
176
172
|
end
|
|
177
173
|
|
|
178
174
|
context "with invalid YAML" do
|
|
179
|
-
|
|
180
175
|
let(:yaml) { YAML.load(input) }
|
|
181
176
|
|
|
182
177
|
context "with an empty string" do
|
|
183
|
-
let(:input) {
|
|
178
|
+
let(:input) { "" }
|
|
184
179
|
|
|
185
180
|
it "should raise an error" do
|
|
186
181
|
expect { @config.experiments = yaml }.to raise_error(Split::InvalidExperimentsFormatError)
|
|
@@ -188,7 +183,7 @@ describe Split::Configuration do
|
|
|
188
183
|
end
|
|
189
184
|
|
|
190
185
|
context "with just the YAML header" do
|
|
191
|
-
let(:input) {
|
|
186
|
+
let(:input) { "---" }
|
|
192
187
|
|
|
193
188
|
it "should raise an error" do
|
|
194
189
|
expect { @config.experiments = yaml }.to raise_error(Split::InvalidExperimentsFormatError)
|
|
@@ -200,35 +195,24 @@ describe Split::Configuration do
|
|
|
200
195
|
|
|
201
196
|
it "should normalize experiments" do
|
|
202
197
|
@config.experiments = {
|
|
203
|
-
:
|
|
204
|
-
:
|
|
205
|
-
{ :
|
|
206
|
-
{ :
|
|
207
|
-
{ :
|
|
198
|
+
my_experiment: {
|
|
199
|
+
alternatives: [
|
|
200
|
+
{ name: "control_opt", percent: 67 },
|
|
201
|
+
{ name: "second_opt", percent: 10 },
|
|
202
|
+
{ name: "third_opt", percent: 23 },
|
|
208
203
|
],
|
|
209
204
|
}
|
|
210
205
|
}
|
|
211
206
|
|
|
212
|
-
expect(@config.normalized_experiments).to eq({:
|
|
213
|
-
end
|
|
214
|
-
|
|
215
|
-
context 'redis_url configuration [DEPRECATED]' do
|
|
216
|
-
it 'should warn on set and assign to #redis' do
|
|
217
|
-
expect(@config).to receive(:warn).with(/\[DEPRECATED\]/) { nil }
|
|
218
|
-
@config.redis_url = 'example_url'
|
|
219
|
-
expect(@config.redis).to eq('example_url')
|
|
220
|
-
end
|
|
221
|
-
|
|
222
|
-
it 'should warn on get and return #redis' do
|
|
223
|
-
expect(@config).to receive(:warn).with(/\[DEPRECATED\]/) { nil }
|
|
224
|
-
@config.redis = 'example_url'
|
|
225
|
-
expect(@config.redis_url).to eq('example_url')
|
|
226
|
-
end
|
|
207
|
+
expect(@config.normalized_experiments).to eq({ my_experiment: { alternatives: [{ "control_opt"=>0.67 }, [{ "second_opt"=>0.1 }, { "third_opt"=>0.23 }]] } })
|
|
227
208
|
end
|
|
228
209
|
|
|
229
210
|
context "redis configuration" do
|
|
230
211
|
it "should default to local redis server" do
|
|
231
|
-
|
|
212
|
+
old_redis_url = ENV["REDIS_URL"]
|
|
213
|
+
ENV.delete("REDIS_URL")
|
|
214
|
+
expect(Split::Configuration.new.redis).to eq("redis://localhost:6379")
|
|
215
|
+
ENV["REDIS_URL"] = old_redis_url
|
|
232
216
|
end
|
|
233
217
|
|
|
234
218
|
it "should allow for redis url to be configured" do
|
|
@@ -238,8 +222,10 @@ describe Split::Configuration do
|
|
|
238
222
|
|
|
239
223
|
context "provided REDIS_URL environment variable" do
|
|
240
224
|
it "should use the ENV variable" do
|
|
241
|
-
|
|
225
|
+
old_redis_url = ENV["REDIS_URL"]
|
|
226
|
+
ENV["REDIS_URL"] = "env_redis_url"
|
|
242
227
|
expect(Split::Configuration.new.redis).to eq("env_redis_url")
|
|
228
|
+
ENV["REDIS_URL"] = old_redis_url
|
|
243
229
|
end
|
|
244
230
|
end
|
|
245
231
|
end
|
|
@@ -255,4 +241,14 @@ describe Split::Configuration do
|
|
|
255
241
|
end
|
|
256
242
|
end
|
|
257
243
|
|
|
244
|
+
context "persistence cookie domain" do
|
|
245
|
+
it "should default to nil" do
|
|
246
|
+
expect(@config.persistence_cookie_domain).to eq(nil)
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
it "should allow the persistence cookie domain to be configured" do
|
|
250
|
+
@config.persistence_cookie_domain = ".acme.com"
|
|
251
|
+
expect(@config.persistence_cookie_domain).to eq(".acme.com")
|
|
252
|
+
end
|
|
253
|
+
end
|
|
258
254
|
end
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
require "split/dashboard/pagination_helpers"
|
|
5
|
+
|
|
6
|
+
describe Split::DashboardPaginationHelpers do
|
|
7
|
+
include Split::DashboardPaginationHelpers
|
|
8
|
+
|
|
9
|
+
let(:url) { "/split/" }
|
|
10
|
+
|
|
11
|
+
describe "#pagination_per" do
|
|
12
|
+
context "when params empty" do
|
|
13
|
+
let(:params) { Hash[] }
|
|
14
|
+
|
|
15
|
+
it "returns the default (10)" do
|
|
16
|
+
default_per_page = Split.configuration.dashboard_pagination_default_per_page
|
|
17
|
+
expect(pagination_per).to eql default_per_page
|
|
18
|
+
expect(pagination_per).to eql 10
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
context "when params[:per] is 5" do
|
|
23
|
+
let(:params) { Hash[per: 5] }
|
|
24
|
+
|
|
25
|
+
it "returns 5" do
|
|
26
|
+
expect(pagination_per).to eql 5
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
describe "#page_number" do
|
|
32
|
+
context "when params empty" do
|
|
33
|
+
let(:params) { Hash[] }
|
|
34
|
+
|
|
35
|
+
it "returns 1" do
|
|
36
|
+
expect(page_number).to eql 1
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
context 'when params[:page] is "2"' do
|
|
41
|
+
let(:params) { Hash[page: "2"] }
|
|
42
|
+
|
|
43
|
+
it "returns 2" do
|
|
44
|
+
expect(page_number).to eql 2
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
describe "#paginated" do
|
|
50
|
+
let(:collection) { (1..20).to_a }
|
|
51
|
+
let(:params) { Hash[per: "5", page: "3"] }
|
|
52
|
+
|
|
53
|
+
it { expect(paginated(collection)).to eql [11, 12, 13, 14, 15] }
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
describe "#show_first_page_tag?" do
|
|
57
|
+
context "when page is 1" do
|
|
58
|
+
it { expect(show_first_page_tag?).to be false }
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
context "when page is 3" do
|
|
62
|
+
let(:params) { Hash[page: "3"] }
|
|
63
|
+
it { expect(show_first_page_tag?).to be true }
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
describe "#first_page_tag" do
|
|
68
|
+
it { expect(first_page_tag).to eql '<a href="/split?page=1&per=10">1</a>' }
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
describe "#show_first_ellipsis_tag?" do
|
|
72
|
+
context "when page is 1" do
|
|
73
|
+
it { expect(show_first_ellipsis_tag?).to be false }
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
context "when page is 4" do
|
|
77
|
+
let(:params) { Hash[page: "4"] }
|
|
78
|
+
it { expect(show_first_ellipsis_tag?).to be true }
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
describe "#ellipsis_tag" do
|
|
83
|
+
it { expect(ellipsis_tag).to eql "<span>...</span>" }
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
describe "#show_prev_page_tag?" do
|
|
87
|
+
context "when page is 1" do
|
|
88
|
+
it { expect(show_prev_page_tag?).to be false }
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
context "when page is 2" do
|
|
92
|
+
let(:params) { Hash[page: "2"] }
|
|
93
|
+
it { expect(show_prev_page_tag?).to be true }
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
describe "#prev_page_tag" do
|
|
98
|
+
context "when page is 2" do
|
|
99
|
+
let(:params) { Hash[page: "2"] }
|
|
100
|
+
|
|
101
|
+
it do
|
|
102
|
+
expect(prev_page_tag).to eql '<a href="/split?page=1&per=10">1</a>'
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
context "when page is 3" do
|
|
107
|
+
let(:params) { Hash[page: "3"] }
|
|
108
|
+
|
|
109
|
+
it do
|
|
110
|
+
expect(prev_page_tag).to eql '<a href="/split?page=2&per=10">2</a>'
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
describe "#show_prev_page_tag?" do
|
|
116
|
+
context "when page is 1" do
|
|
117
|
+
it { expect(show_prev_page_tag?).to be false }
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
context "when page is 2" do
|
|
121
|
+
let(:params) { Hash[page: "2"] }
|
|
122
|
+
it { expect(show_prev_page_tag?).to be true }
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
describe "#current_page_tag" do
|
|
127
|
+
context "when page is 1" do
|
|
128
|
+
let(:params) { Hash[page: "1"] }
|
|
129
|
+
it { expect(current_page_tag).to eql "<span><b>1</b></span>" }
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
context "when page is 2" do
|
|
133
|
+
let(:params) { Hash[page: "2"] }
|
|
134
|
+
it { expect(current_page_tag).to eql "<span><b>2</b></span>" }
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
describe "#show_next_page_tag?" do
|
|
139
|
+
context "when page is 2" do
|
|
140
|
+
let(:params) { Hash[page: "2"] }
|
|
141
|
+
|
|
142
|
+
context "when collection length is 20" do
|
|
143
|
+
let(:collection) { (1..20).to_a }
|
|
144
|
+
it { expect(show_next_page_tag?(collection)).to be false }
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
context "when collection length is 25" do
|
|
148
|
+
let(:collection) { (1..25).to_a }
|
|
149
|
+
it { expect(show_next_page_tag?(collection)).to be true }
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
describe "#next_page_tag" do
|
|
155
|
+
context "when page is 1" do
|
|
156
|
+
let(:params) { Hash[page: "1"] }
|
|
157
|
+
it { expect(next_page_tag).to eql '<a href="/split?page=2&per=10">2</a>' }
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
context "when page is 2" do
|
|
161
|
+
let(:params) { Hash[page: "2"] }
|
|
162
|
+
it { expect(next_page_tag).to eql '<a href="/split?page=3&per=10">3</a>' }
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
describe "#total_pages" do
|
|
167
|
+
context "when collection length is 30" do
|
|
168
|
+
let(:collection) { (1..30).to_a }
|
|
169
|
+
it { expect(total_pages(collection)).to eql 3 }
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
context "when collection length is 35" do
|
|
173
|
+
let(:collection) { (1..35).to_a }
|
|
174
|
+
it { expect(total_pages(collection)).to eql 4 }
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
describe "#show_last_ellipsis_tag?" do
|
|
179
|
+
let(:collection) { (1..30).to_a }
|
|
180
|
+
let(:params) { Hash[per: "5", page: "2"] }
|
|
181
|
+
it { expect(show_last_ellipsis_tag?(collection)).to be true }
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
describe "#show_last_page_tag?" do
|
|
185
|
+
let(:collection) { (1..30).to_a }
|
|
186
|
+
|
|
187
|
+
context "when page is 5/6" do
|
|
188
|
+
let(:params) { Hash[per: "5", page: "5"] }
|
|
189
|
+
it { expect(show_last_page_tag?(collection)).to be false }
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
context "when page is 4/6" do
|
|
193
|
+
let(:params) { Hash[per: "5", page: "4"] }
|
|
194
|
+
it { expect(show_last_page_tag?(collection)).to be true }
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
describe "#last_page_tag" do
|
|
199
|
+
let(:collection) { (1..30).to_a }
|
|
200
|
+
it { expect(last_page_tag(collection)).to eql '<a href="/split?page=3&per=10">3</a>' }
|
|
201
|
+
end
|
|
202
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
require "split/dashboard/paginator"
|
|
5
|
+
|
|
6
|
+
describe Split::DashboardPaginator do
|
|
7
|
+
context "when collection is 1..20" do
|
|
8
|
+
let(:collection) { (1..20).to_a }
|
|
9
|
+
|
|
10
|
+
context "when per 5 for page" do
|
|
11
|
+
let(:per) { 5 }
|
|
12
|
+
|
|
13
|
+
it "when page number is 1 result is [1, 2, 3, 4, 5]" do
|
|
14
|
+
result = Split::DashboardPaginator.new(collection, 1, per).paginate
|
|
15
|
+
expect(result).to eql [1, 2, 3, 4, 5]
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
it "when page number is 2 result is [6, 7, 8, 9, 10]" do
|
|
19
|
+
result = Split::DashboardPaginator.new(collection, 2, per).paginate
|
|
20
|
+
expect(result).to eql [6, 7, 8, 9, 10]
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
context "when per 10 for page" do
|
|
25
|
+
let(:per) { 10 }
|
|
26
|
+
|
|
27
|
+
it "when page number is 1 result is [1..10]" do
|
|
28
|
+
result = Split::DashboardPaginator.new(collection, 1, per).paginate
|
|
29
|
+
expect(result).to eql [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
it "when page number is 2 result is [10..20]" do
|
|
33
|
+
result = Split::DashboardPaginator.new(collection, 2, per).paginate
|
|
34
|
+
expect(result).to eql [11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -1,41 +1,42 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
require "split/dashboard/helpers"
|
|
4
5
|
|
|
5
6
|
include Split::DashboardHelpers
|
|
6
7
|
|
|
7
8
|
describe Split::DashboardHelpers do
|
|
8
|
-
describe
|
|
9
|
-
it
|
|
10
|
-
expect(confidence_level(Complex(2e-18, -0.03))).to eq(
|
|
9
|
+
describe "confidence_level" do
|
|
10
|
+
it "should handle very small numbers" do
|
|
11
|
+
expect(confidence_level(Complex(2e-18, -0.03))).to eq("Insufficient confidence")
|
|
11
12
|
end
|
|
12
13
|
|
|
13
14
|
it "should consider a z-score of 1.65 <= z < 1.96 as 90% confident" do
|
|
14
|
-
expect(confidence_level(1.65)).to eq(
|
|
15
|
-
expect(confidence_level(1.80)).to eq(
|
|
15
|
+
expect(confidence_level(1.65)).to eq("90% confidence")
|
|
16
|
+
expect(confidence_level(1.80)).to eq("90% confidence")
|
|
16
17
|
end
|
|
17
18
|
|
|
18
19
|
it "should consider a z-score of 1.96 <= z < 2.58 as 95% confident" do
|
|
19
|
-
expect(confidence_level(1.96)).to eq(
|
|
20
|
-
expect(confidence_level(2.00)).to eq(
|
|
20
|
+
expect(confidence_level(1.96)).to eq("95% confidence")
|
|
21
|
+
expect(confidence_level(2.00)).to eq("95% confidence")
|
|
21
22
|
end
|
|
22
23
|
|
|
23
24
|
it "should consider a z-score of z >= 2.58 as 99% confident" do
|
|
24
|
-
expect(confidence_level(2.58)).to eq(
|
|
25
|
-
expect(confidence_level(3.00)).to eq(
|
|
25
|
+
expect(confidence_level(2.58)).to eq("99% confidence")
|
|
26
|
+
expect(confidence_level(3.00)).to eq("99% confidence")
|
|
26
27
|
end
|
|
27
28
|
|
|
28
|
-
describe
|
|
29
|
-
it
|
|
30
|
-
expect(round(
|
|
29
|
+
describe "#round" do
|
|
30
|
+
it "can round number strings" do
|
|
31
|
+
expect(round("3.1415")).to eq BigDecimal("3.14")
|
|
31
32
|
end
|
|
32
33
|
|
|
33
|
-
it
|
|
34
|
-
expect(round(
|
|
34
|
+
it "can round number strings for precsion" do
|
|
35
|
+
expect(round("3.1415", 1)).to eq BigDecimal("3.1")
|
|
35
36
|
end
|
|
36
37
|
|
|
37
|
-
it
|
|
38
|
-
expect(round(
|
|
38
|
+
it "can handle invalid number strings" do
|
|
39
|
+
expect(round("N/A")).to be_zero
|
|
39
40
|
end
|
|
40
41
|
end
|
|
41
42
|
end
|