split 3.0.0 → 3.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. checksums.yaml +5 -5
  2. data/.eslintrc +1 -1
  3. data/.github/ISSUE_TEMPLATE/bug_report.md +24 -0
  4. data/.rspec +1 -0
  5. data/.rubocop.yml +6 -1155
  6. data/.rubocop_todo.yml +679 -0
  7. data/.travis.yml +46 -2
  8. data/Appraisals +12 -1
  9. data/CHANGELOG.md +116 -0
  10. data/CODE_OF_CONDUCT.md +3 -3
  11. data/CONTRIBUTING.md +54 -5
  12. data/Gemfile +1 -0
  13. data/LICENSE +1 -1
  14. data/README.md +209 -118
  15. data/Rakefile +1 -0
  16. data/gemfiles/4.2.gemfile +1 -1
  17. data/gemfiles/5.0.gemfile +1 -2
  18. data/gemfiles/5.1.gemfile +9 -0
  19. data/gemfiles/5.2.gemfile +9 -0
  20. data/gemfiles/6.0.gemfile +9 -0
  21. data/lib/split/algorithms/block_randomization.rb +1 -0
  22. data/lib/split/alternative.rb +6 -3
  23. data/lib/split/combined_experiments_helper.rb +37 -0
  24. data/lib/split/configuration.rb +17 -2
  25. data/lib/split/dashboard/helpers.rb +2 -2
  26. data/lib/split/dashboard/pagination_helpers.rb +86 -0
  27. data/lib/split/dashboard/paginator.rb +16 -0
  28. data/lib/split/dashboard/public/style.css +9 -0
  29. data/lib/split/dashboard/views/index.erb +5 -1
  30. data/lib/split/dashboard/views/layout.erb +1 -1
  31. data/lib/split/dashboard.rb +6 -1
  32. data/lib/split/engine.rb +6 -2
  33. data/lib/split/experiment.rb +34 -22
  34. data/lib/split/goals_collection.rb +1 -0
  35. data/lib/split/helper.rb +17 -3
  36. data/lib/split/persistence/cookie_adapter.rb +53 -15
  37. data/lib/split/persistence/dual_adapter.rb +54 -12
  38. data/lib/split/redis_interface.rb +2 -3
  39. data/lib/split/trial.rb +4 -6
  40. data/lib/split/user.rb +5 -1
  41. data/lib/split/version.rb +1 -1
  42. data/lib/split.rb +9 -1
  43. data/spec/alternative_spec.rb +12 -0
  44. data/spec/combined_experiments_helper_spec.rb +57 -0
  45. data/spec/dashboard/pagination_helpers_spec.rb +200 -0
  46. data/spec/dashboard/paginator_spec.rb +37 -0
  47. data/spec/dashboard_helpers_spec.rb +2 -2
  48. data/spec/dashboard_spec.rb +37 -16
  49. data/spec/encapsulated_helper_spec.rb +1 -1
  50. data/spec/experiment_spec.rb +45 -6
  51. data/spec/helper_spec.rb +143 -80
  52. data/spec/persistence/cookie_adapter_spec.rb +90 -23
  53. data/spec/persistence/dual_adapter_spec.rb +160 -68
  54. data/spec/split_spec.rb +7 -7
  55. data/spec/trial_spec.rb +20 -0
  56. data/spec/user_spec.rb +11 -0
  57. data/split.gemspec +17 -7
  58. metadata +53 -19
@@ -1,12 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'forwardable'
4
-
5
3
  module Split
6
4
  module Persistence
7
5
  class DualAdapter
8
- extend Forwardable
9
- def_delegators :@adapter, :keys, :[], :[]=, :delete
6
+ def self.with_config(options={})
7
+ self.config.merge!(options)
8
+ self
9
+ end
10
+
11
+ def self.config
12
+ @config ||= {}
13
+ end
10
14
 
11
15
  def initialize(context)
12
16
  if logged_in = self.class.config[:logged_in]
@@ -22,22 +26,60 @@ module Split
22
26
  raise "Please configure :logged_out_adapter"
23
27
  end
24
28
 
25
- if logged_in.call(context)
26
- @adapter = logged_in_adapter.new(context)
29
+ @fallback_to_logged_out_adapter =
30
+ self.class.config[:fallback_to_logged_out_adapter] || false
31
+ @logged_in = logged_in.call(context)
32
+ @logged_in_adapter = logged_in_adapter.new(context)
33
+ @logged_out_adapter = logged_out_adapter.new(context)
34
+ @active_adapter = @logged_in ? @logged_in_adapter : @logged_out_adapter
35
+ end
36
+
37
+ def keys
38
+ if @fallback_to_logged_out_adapter
39
+ (@logged_in_adapter.keys + @logged_out_adapter.keys).uniq
27
40
  else
28
- @adapter = logged_out_adapter.new(context)
41
+ @active_adapter.keys
29
42
  end
30
43
  end
31
44
 
32
- def self.with_config(options={})
33
- self.config.merge!(options)
34
- self
45
+ def [](key)
46
+ if @fallback_to_logged_out_adapter
47
+ @logged_in && @logged_in_adapter[key] || @logged_out_adapter[key]
48
+ else
49
+ @active_adapter[key]
50
+ end
35
51
  end
36
52
 
37
- def self.config
38
- @config ||= {}
53
+ def []=(key, value)
54
+ if @fallback_to_logged_out_adapter
55
+ @logged_in_adapter[key] = value if @logged_in
56
+ old_value = @logged_out_adapter[key]
57
+ @logged_out_adapter[key] = value
58
+
59
+ decrement_participation(key, old_value) if decrement_participation?(old_value, value)
60
+ else
61
+ @active_adapter[key] = value
62
+ end
63
+ end
64
+
65
+ def delete(key)
66
+ if @fallback_to_logged_out_adapter
67
+ @logged_in_adapter.delete(key)
68
+ @logged_out_adapter.delete(key)
69
+ else
70
+ @active_adapter.delete(key)
71
+ end
39
72
  end
40
73
 
74
+ private
75
+
76
+ def decrement_participation?(old_value, value)
77
+ !old_value.nil? && !value.nil? && old_value != value
78
+ end
79
+
80
+ def decrement_participation(key, value)
81
+ Split.redis.hincrby("#{key}:#{value}", 'participant_count', -1)
82
+ end
41
83
  end
42
84
  end
43
85
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Split
2
3
  # Simplifies the interface to Redis.
3
4
  class RedisInterface
@@ -35,9 +36,7 @@ module Split
35
36
  end
36
37
 
37
38
  def make_list_length(list_name, new_length)
38
- while list_length(list_name) > new_length
39
- remove_last_item_from_list(list_name)
40
- end
39
+ redis.ltrim(list_name, 0, new_length - 1)
41
40
  end
42
41
 
43
42
  def add_to_set(set_name, value)
data/lib/split/trial.rb CHANGED
@@ -2,7 +2,7 @@
2
2
  module Split
3
3
  class Trial
4
4
  attr_accessor :experiment
5
- attr_accessor :metadata
5
+ attr_writer :metadata
6
6
 
7
7
  def initialize(attrs = {})
8
8
  self.experiment = attrs.delete(:experiment)
@@ -68,10 +68,8 @@ module Split
68
68
  if exclude_user?
69
69
  self.alternative = @experiment.control
70
70
  else
71
- value = @user[@experiment.key]
72
- if value
73
- self.alternative = value
74
- else
71
+ self.alternative = @user[@experiment.key]
72
+ if alternative.nil?
75
73
  self.alternative = @experiment.next_alternative
76
74
 
77
75
  # Increment the number of participants since we are actually choosing a new alternative
@@ -82,7 +80,7 @@ module Split
82
80
  end
83
81
  end
84
82
 
85
- @user[@experiment.key] = alternative.name if should_store_alternative?
83
+ @user[@experiment.key] = alternative.name if !@experiment.has_winner? && should_store_alternative?
86
84
  @alternative_choosen = true
87
85
  run_callback context, Split.configuration.on_trial unless @options[:disabled] || Split.configuration.disabled?
88
86
  alternative
data/lib/split/user.rb CHANGED
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'forwardable'
2
3
 
3
4
  module Split
@@ -8,9 +9,11 @@ module Split
8
9
 
9
10
  def initialize(context, adapter=nil)
10
11
  @user = adapter || Split::Persistence.adapter.new(context)
12
+ @cleaned_up = false
11
13
  end
12
14
 
13
15
  def cleanup_old_experiments!
16
+ return if @cleaned_up
14
17
  keys_without_finished(user.keys).each do |key|
15
18
  experiment = ExperimentCatalog.find key_without_version(key)
16
19
  if experiment.nil? || experiment.has_winner? || experiment.start_time.nil?
@@ -18,6 +21,7 @@ module Split
18
21
  user.delete Experiment.finished_key(key)
19
22
  end
20
23
  end
24
+ @cleaned_up = true
21
25
  end
22
26
 
23
27
  def max_experiments_reached?(experiment_key)
@@ -38,7 +42,7 @@ module Split
38
42
 
39
43
  def active_experiments
40
44
  experiment_pairs = {}
41
- user.keys.each do |key|
45
+ keys_without_finished(user.keys).each do |key|
42
46
  Metric.possible_experiments(key_without_version(key)).each do |experiment|
43
47
  if !experiment.has_winner?
44
48
  experiment_pairs[key_without_version(key)] = user[key]
data/lib/split/version.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
  module Split
3
3
  MAJOR = 3
4
- MINOR = 0
4
+ MINOR = 4
5
5
  PATCH = 0
6
6
  VERSION = [MAJOR, MINOR, PATCH].join('.')
7
7
  end
data/lib/split.rb CHANGED
@@ -13,6 +13,7 @@ require 'split/experiment_catalog'
13
13
  require 'split/extensions/string'
14
14
  require 'split/goals_collection'
15
15
  require 'split/helper'
16
+ require 'split/combined_experiments_helper'
16
17
  require 'split/metric'
17
18
  require 'split/persistence'
18
19
  require 'split/redis_interface'
@@ -65,4 +66,11 @@ module Split
65
66
  end
66
67
  end
67
68
 
68
- Split.configure {}
69
+ # Check to see if being run in a Rails application. If so, wait until before_initialize to run configuration so Gems that create ENV variables have the chance to initialize first.
70
+ if defined?(::Rails)
71
+ class Railtie < Rails::Railtie
72
+ config.before_initialize { Split.configure {} }
73
+ end
74
+ else
75
+ Split.configure {}
76
+ end
@@ -273,6 +273,18 @@ describe Split::Alternative do
273
273
  expect(control.z_score(goal1)).to eq('N/A')
274
274
  expect(control.z_score(goal2)).to eq('N/A')
275
275
  end
276
+
277
+ it "should not blow up for Conversion Rates > 1" do
278
+ control = experiment.control
279
+ control.participant_count = 3474
280
+ control.set_completed_count(4244)
281
+
282
+ alternative2.participant_count = 3434
283
+ alternative2.set_completed_count(4358)
284
+
285
+ expect { control.z_score }.not_to raise_error
286
+ expect { alternative2.z_score }.not_to raise_error
287
+ end
276
288
  end
277
289
 
278
290
  describe "extra_info" do
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+ require 'spec_helper'
3
+ require 'split/combined_experiments_helper'
4
+
5
+ describe Split::CombinedExperimentsHelper do
6
+ include Split::CombinedExperimentsHelper
7
+
8
+ describe 'ab_combined_test' do
9
+ let!(:config_enabled) { true }
10
+ let!(:combined_experiments) { [:exp_1_click, :exp_1_scroll ]}
11
+ let!(:allow_multiple_experiments) { true }
12
+
13
+ before do
14
+ Split.configuration.experiments = {
15
+ :combined_exp_1 => {
16
+ :alternatives => [ {"control"=> 0.5}, {"test-alt"=> 0.5} ],
17
+ :metric => :my_metric,
18
+ :combined_experiments => combined_experiments
19
+ }
20
+ }
21
+ Split.configuration.enabled = config_enabled
22
+ Split.configuration.allow_multiple_experiments = allow_multiple_experiments
23
+ end
24
+
25
+ context 'without config enabled' do
26
+ let!(:config_enabled) { false }
27
+
28
+ it "raises an error" do
29
+ expect(lambda { ab_combined_test :combined_exp_1 }).to raise_error(Split::InvalidExperimentsFormatError )
30
+ end
31
+ end
32
+
33
+ context 'multiple experiments disabled' do
34
+ let!(:allow_multiple_experiments) { false }
35
+
36
+ it "raises an error if multiple experiments is disabled" do
37
+ expect(lambda { ab_combined_test :combined_exp_1 }).to raise_error(Split::InvalidExperimentsFormatError)
38
+ end
39
+ end
40
+
41
+ context 'without combined experiments' do
42
+ let!(:combined_experiments) { nil }
43
+
44
+ it "raises an error" do
45
+ expect(lambda { ab_combined_test :combined_exp_1 }).to raise_error(Split::InvalidExperimentsFormatError )
46
+ end
47
+ end
48
+
49
+ it "uses same alternative for all sub experiments and returns the alternative" do
50
+ 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}])
53
+
54
+ expect(ab_combined_test('combined_exp_1')).to eq('test-alt')
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,200 @@
1
+ require 'spec_helper'
2
+ require 'split/dashboard/pagination_helpers'
3
+
4
+ describe Split::DashboardPaginationHelpers do
5
+ include Split::DashboardPaginationHelpers
6
+
7
+ let(:url) { '/split/' }
8
+
9
+ describe '#pagination_per' do
10
+ context 'when params empty' do
11
+ let(:params) { Hash[] }
12
+
13
+ it 'returns the default (10)' do
14
+ default_per_page = Split.configuration.dashboard_pagination_default_per_page
15
+ expect(pagination_per).to eql default_per_page
16
+ expect(pagination_per).to eql 10
17
+ end
18
+ end
19
+
20
+ context 'when params[:per] is 5' do
21
+ let(:params) { Hash[per: 5] }
22
+
23
+ it 'returns 5' do
24
+ expect(pagination_per).to eql 5
25
+ end
26
+ end
27
+ end
28
+
29
+ describe '#page_number' do
30
+ context 'when params empty' do
31
+ let(:params) { Hash[] }
32
+
33
+ it 'returns 1' do
34
+ expect(page_number).to eql 1
35
+ end
36
+ end
37
+
38
+ context 'when params[:page] is "2"' do
39
+ let(:params) { Hash[page: '2'] }
40
+
41
+ it 'returns 2' do
42
+ expect(page_number).to eql 2
43
+ end
44
+ end
45
+ end
46
+
47
+ describe '#paginated' do
48
+ let(:collection) { (1..20).to_a }
49
+ let(:params) { Hash[per: '5', page: '3'] }
50
+
51
+ it { expect(paginated(collection)).to eql [11, 12, 13, 14, 15] }
52
+ end
53
+
54
+ describe '#show_first_page_tag?' do
55
+ context 'when page is 1' do
56
+ it { expect(show_first_page_tag?).to be false }
57
+ end
58
+
59
+ context 'when page is 3' do
60
+ let(:params) { Hash[page: '3'] }
61
+ it { expect(show_first_page_tag?).to be true }
62
+ end
63
+ end
64
+
65
+ describe '#first_page_tag' do
66
+ it { expect(first_page_tag).to eql '<a href="/split?page=1&per=10">1</a>' }
67
+ end
68
+
69
+ describe '#show_first_ellipsis_tag?' do
70
+ context 'when page is 1' do
71
+ it { expect(show_first_ellipsis_tag?).to be false }
72
+ end
73
+
74
+ context 'when page is 4' do
75
+ let(:params) { Hash[page: '4'] }
76
+ it { expect(show_first_ellipsis_tag?).to be true }
77
+ end
78
+ end
79
+
80
+ describe '#ellipsis_tag' do
81
+ it { expect(ellipsis_tag).to eql '<span>...</span>' }
82
+ end
83
+
84
+ describe '#show_prev_page_tag?' do
85
+ context 'when page is 1' do
86
+ it { expect(show_prev_page_tag?).to be false }
87
+ end
88
+
89
+ context 'when page is 2' do
90
+ let(:params) { Hash[page: '2'] }
91
+ it { expect(show_prev_page_tag?).to be true }
92
+ end
93
+ end
94
+
95
+ describe '#prev_page_tag' do
96
+ context 'when page is 2' do
97
+ let(:params) { Hash[page: '2'] }
98
+
99
+ it do
100
+ expect(prev_page_tag).to eql '<a href="/split?page=1&per=10">1</a>'
101
+ end
102
+ end
103
+
104
+ context 'when page is 3' do
105
+ let(:params) { Hash[page: '3'] }
106
+
107
+ it do
108
+ expect(prev_page_tag).to eql '<a href="/split?page=2&per=10">2</a>'
109
+ end
110
+ end
111
+ end
112
+
113
+ describe '#show_prev_page_tag?' do
114
+ context 'when page is 1' do
115
+ it { expect(show_prev_page_tag?).to be false }
116
+ end
117
+
118
+ context 'when page is 2' do
119
+ let(:params) { Hash[page: '2'] }
120
+ it { expect(show_prev_page_tag?).to be true }
121
+ end
122
+ end
123
+
124
+ describe '#current_page_tag' do
125
+ context 'when page is 1' do
126
+ let(:params) { Hash[page: '1'] }
127
+ it { expect(current_page_tag).to eql '<span><b>1</b></span>' }
128
+ end
129
+
130
+ context 'when page is 2' do
131
+ let(:params) { Hash[page: '2'] }
132
+ it { expect(current_page_tag).to eql '<span><b>2</b></span>' }
133
+ end
134
+ end
135
+
136
+ describe '#show_next_page_tag?' do
137
+ context 'when page is 2' do
138
+ let(:params) { Hash[page: '2'] }
139
+
140
+ context 'when collection length is 20' do
141
+ let(:collection) { (1..20).to_a }
142
+ it { expect(show_next_page_tag?(collection)).to be false }
143
+ end
144
+
145
+ context 'when collection length is 25' do
146
+ let(:collection) { (1..25).to_a }
147
+ it { expect(show_next_page_tag?(collection)).to be true }
148
+ end
149
+ end
150
+ end
151
+
152
+ describe '#next_page_tag' do
153
+ context 'when page is 1' do
154
+ let(:params) { Hash[page: '1'] }
155
+ it { expect(next_page_tag).to eql '<a href="/split?page=2&per=10">2</a>' }
156
+ end
157
+
158
+ context 'when page is 2' do
159
+ let(:params) { Hash[page: '2'] }
160
+ it { expect(next_page_tag).to eql '<a href="/split?page=3&per=10">3</a>' }
161
+ end
162
+ end
163
+
164
+ describe '#total_pages' do
165
+ context 'when collection length is 30' do
166
+ let(:collection) { (1..30).to_a }
167
+ it { expect(total_pages(collection)).to eql 3 }
168
+ end
169
+
170
+ context 'when collection length is 35' do
171
+ let(:collection) { (1..35).to_a }
172
+ it { expect(total_pages(collection)).to eql 4 }
173
+ end
174
+ end
175
+
176
+ describe '#show_last_ellipsis_tag?' do
177
+ let(:collection) { (1..30).to_a }
178
+ let(:params) { Hash[per: '5', page: '2'] }
179
+ it { expect(show_last_ellipsis_tag?(collection)).to be true }
180
+ end
181
+
182
+ describe '#show_last_page_tag?' do
183
+ let(:collection) { (1..30).to_a }
184
+
185
+ context 'when page is 5/6' do
186
+ let(:params) { Hash[per: '5', page: '5'] }
187
+ it { expect(show_last_page_tag?(collection)).to be false }
188
+ end
189
+
190
+ context 'when page is 4/6' do
191
+ let(:params) { Hash[per: '5', page: '4'] }
192
+ it { expect(show_last_page_tag?(collection)).to be true }
193
+ end
194
+ end
195
+
196
+ describe '#last_page_tag' do
197
+ let(:collection) { (1..30).to_a }
198
+ it { expect(last_page_tag(collection)).to eql '<a href="/split?page=3&per=10">3</a>' }
199
+ end
200
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+ require 'spec_helper'
3
+ require 'split/dashboard/paginator'
4
+
5
+ describe Split::DashboardPaginator do
6
+ context 'when collection is 1..20' do
7
+ let(:collection) { (1..20).to_a }
8
+
9
+ context 'when per 5 for page' do
10
+ let(:per) { 5 }
11
+
12
+ it 'when page number is 1 result is [1, 2, 3, 4, 5]' do
13
+ result = Split::DashboardPaginator.new(collection, 1, per).paginate
14
+ expect(result).to eql [1, 2, 3, 4, 5]
15
+ end
16
+
17
+ it 'when page number is 2 result is [6, 7, 8, 9, 10]' do
18
+ result = Split::DashboardPaginator.new(collection, 2, per).paginate
19
+ expect(result).to eql [6, 7, 8, 9, 10]
20
+ end
21
+ end
22
+
23
+ context 'when per 10 for page' do
24
+ let(:per) { 10 }
25
+
26
+ it 'when page number is 1 result is [1..10]' do
27
+ result = Split::DashboardPaginator.new(collection, 1, per).paginate
28
+ expect(result).to eql [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
29
+ end
30
+
31
+ it 'when page number is 2 result is [10..20]' do
32
+ result = Split::DashboardPaginator.new(collection, 2, per).paginate
33
+ expect(result).to eql [11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
34
+ end
35
+ end
36
+ end
37
+ end
@@ -27,11 +27,11 @@ describe Split::DashboardHelpers do
27
27
 
28
28
  describe '#round' do
29
29
  it 'can round number strings' do
30
- expect(round('3.1415')).to eq BigDecimal.new('3.14')
30
+ expect(round('3.1415')).to eq BigDecimal('3.14')
31
31
  end
32
32
 
33
33
  it 'can round number strings for precsion' do
34
- expect(round('3.1415', 1)).to eq BigDecimal.new('3.1')
34
+ expect(round('3.1415', 1)).to eq BigDecimal('3.1')
35
35
  end
36
36
 
37
37
  it 'can handle invalid number strings' do
@@ -29,6 +29,10 @@ describe Split::Dashboard do
29
29
  let(:red_link) { link("red") }
30
30
  let(:blue_link) { link("blue") }
31
31
 
32
+ before(:each) do
33
+ Split.configuration.beta_probability_simulations = 1
34
+ end
35
+
32
36
  it "should respond to /" do
33
37
  get '/'
34
38
  expect(last_response).to be_ok
@@ -74,17 +78,39 @@ describe Split::Dashboard do
74
78
  end
75
79
 
76
80
  describe "force alternative" do
77
- let!(:user) do
78
- Split::User.new(@app, { experiment.name => 'a' })
79
- end
81
+ context "initial version" do
82
+ let!(:user) do
83
+ Split::User.new(@app, { experiment.name => 'red' })
84
+ end
80
85
 
81
- before do
82
- allow(Split::User).to receive(:new).and_return(user)
86
+ before do
87
+ allow(Split::User).to receive(:new).and_return(user)
88
+ end
89
+
90
+ it "should set current user's alternative" do
91
+ blue_link.participant_count = 7
92
+ post "/force_alternative?experiment=#{experiment.name}", alternative: "blue"
93
+ expect(user[experiment.key]).to eq("blue")
94
+ expect(blue_link.participant_count).to eq(8)
95
+ end
83
96
  end
84
97
 
85
- it "should set current user's alternative" do
86
- post "/force_alternative?experiment=#{experiment.name}", alternative: "b"
87
- expect(user[experiment.name]).to eq("b")
98
+ context "incremented version" do
99
+ let!(:user) do
100
+ experiment.increment_version
101
+ Split::User.new(@app, { "#{experiment.name}:#{experiment.version}" => 'red' })
102
+ end
103
+
104
+ before do
105
+ allow(Split::User).to receive(:new).and_return(user)
106
+ end
107
+
108
+ it "should set current user's alternative" do
109
+ blue_link.participant_count = 7
110
+ post "/force_alternative?experiment=#{experiment.name}", alternative: "blue"
111
+ expect(user[experiment.key]).to eq("blue")
112
+ expect(blue_link.participant_count).to eq(8)
113
+ end
88
114
  end
89
115
  end
90
116
 
@@ -120,7 +146,7 @@ describe Split::Dashboard do
120
146
  it "removes winner" do
121
147
  post "/reopen?experiment=#{experiment.name}"
122
148
 
123
- expect(experiment).to_not have_winner
149
+ expect(Split::ExperimentCatalog.find(experiment.name)).to_not have_winner
124
150
  end
125
151
 
126
152
  it "keeps existing stats" do
@@ -167,19 +193,14 @@ describe Split::Dashboard do
167
193
  end
168
194
 
169
195
  it "should display the start date" do
170
- experiment_start_time = Time.parse('2011-07-07')
171
- expect(Time).to receive(:now).at_least(:once).and_return(experiment_start_time)
172
- experiment
196
+ experiment.start
173
197
 
174
198
  get '/'
175
199
 
176
- expect(last_response.body).to include('<small>2011-07-07</small>')
200
+ expect(last_response.body).to include("<small>#{experiment.start_time.strftime('%Y-%m-%d')}</small>")
177
201
  end
178
202
 
179
203
  it "should handle experiments without a start date" do
180
- experiment_start_time = Time.parse('2011-07-07')
181
- expect(Time).to receive(:now).at_least(:once).and_return(experiment_start_time)
182
-
183
204
  Split.redis.hdel(:experiment_start_times, experiment.name)
184
205
 
185
206
  get '/'
@@ -33,7 +33,7 @@ describe Split::EncapsulatedHelper do
33
33
  static <%= alt %>
34
34
  <% end %>
35
35
  ERB
36
- expect(template.result(binding)).to match /foo static \d/
36
+ expect(template.result(binding)).to match(/foo static \d/)
37
37
  end
38
38
 
39
39
  end