pinball_wizard 0.0.1.pre → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -13
- data/.gitignore +9 -0
- data/CHANGELOG.md +6 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +32 -0
- data/LICENSE +21 -0
- data/README.md +336 -0
- data/Rakefile +4 -0
- data/bower.json +14 -0
- data/dist/css_tagger.js +42 -0
- data/dist/css_tagger.min.js +3 -0
- data/dist/pinball_wizard.js +227 -0
- data/gulpfile.js +40 -0
- data/karma.conf.js +66 -0
- data/lib/pinball_wizard.rb +5 -0
- data/lib/pinball_wizard/configuration.rb +17 -0
- data/lib/pinball_wizard/dsl.rb +38 -0
- data/lib/pinball_wizard/feature.rb +52 -0
- data/lib/pinball_wizard/helpers/hash.rb +26 -0
- data/lib/pinball_wizard/null_feature.rb +7 -0
- data/lib/pinball_wizard/railtie.rb +9 -0
- data/lib/pinball_wizard/registry.rb +44 -0
- data/lib/pinball_wizard/version.rb +3 -0
- data/lib/pinball_wizard/view_helpers/rails.rb +13 -0
- data/lib/pinball_wizard/view_helpers/sinatra/slim.rb +15 -0
- data/package.json +45 -0
- data/pinball_wizard.gemspec +20 -0
- data/spec/pinball_wizard/dsl_spec.rb +73 -0
- data/spec/pinball_wizard/feature_spec.rb +89 -0
- data/spec/pinball_wizard/registry_spec.rb +56 -0
- data/src/css_tagger.coffee +44 -0
- data/src/pinball_wizard.coffee +157 -0
- data/test/spec/css_tagger_spec.coffee +50 -0
- data/test/spec/pinball_wizard_spec.coffee +268 -0
- data/test/test-main.coffee +25 -0
- metadata +47 -10
@@ -0,0 +1,44 @@
|
|
1
|
+
define ->
|
2
|
+
|
3
|
+
# Add .use-{feature-name} to <html> right away to prevent flickering.
|
4
|
+
# Works with ?pinball=feature_name and activated via optimizely.
|
5
|
+
|
6
|
+
# NOTE: underscores are converted to -. e.g. my_feature -> use-my-feature
|
7
|
+
|
8
|
+
# This component is designed to be minified and put in an inline <script> in
|
9
|
+
# the document <head>. Above CSS (so that it runs right away).
|
10
|
+
|
11
|
+
# Usage
|
12
|
+
# <script type="text/javascript">
|
13
|
+
# (
|
14
|
+
# function (...) { # minified script }
|
15
|
+
# )(document.documentElement, window.pinball, , window.location.search)
|
16
|
+
# </script>
|
17
|
+
|
18
|
+
(ele, pinballQueue, searchQuery) ->
|
19
|
+
classNames = []
|
20
|
+
|
21
|
+
add = (name) ->
|
22
|
+
classNames.push 'use-' + name.split('_').join('-')
|
23
|
+
|
24
|
+
for entry in pinballQueue
|
25
|
+
continue unless entry.length
|
26
|
+
|
27
|
+
switch entry[0]
|
28
|
+
when 'activate'
|
29
|
+
add entry[1]
|
30
|
+
|
31
|
+
when 'add'
|
32
|
+
for feature, state of entry[1]
|
33
|
+
add feature if state == 'active'
|
34
|
+
|
35
|
+
matches = searchQuery.match(/pinball=([a-z-_,]+)/i)
|
36
|
+
if matches && matches.length > 1
|
37
|
+
featureNames = (matches[1] + '').split(',')
|
38
|
+
|
39
|
+
for feature in featureNames
|
40
|
+
add feature
|
41
|
+
|
42
|
+
ele.className += ' ' + classNames.join(' ') if ele
|
43
|
+
|
44
|
+
return
|
@@ -0,0 +1,157 @@
|
|
1
|
+
'use strict'
|
2
|
+
|
3
|
+
define ->
|
4
|
+
|
5
|
+
features = {}
|
6
|
+
subscribers = {}
|
7
|
+
|
8
|
+
showLog = false
|
9
|
+
logPrefix = '[PinballWizard]'
|
10
|
+
|
11
|
+
_log = (message, args...) ->
|
12
|
+
if showLog && window.console && window.console.log
|
13
|
+
console.log("#{logPrefix} #{message}", args...)
|
14
|
+
return
|
15
|
+
|
16
|
+
_notifySubscribersOnActivate = (name) ->
|
17
|
+
subscribers[name] ?= []
|
18
|
+
for subscriber in subscribers[name]
|
19
|
+
_notifySubscriberOnActivate(subscriber, name)
|
20
|
+
|
21
|
+
_notifySubscriberOnActivate = (subscriber, name) ->
|
22
|
+
_log 'Notify subscriber that %s is active', name
|
23
|
+
subscriber.onActivate()
|
24
|
+
|
25
|
+
_notifySubscribersOnDeactivate = (name) ->
|
26
|
+
subscribers[name] ?= []
|
27
|
+
for subscriber in subscribers[name]
|
28
|
+
subscriber.onDeactivate()
|
29
|
+
|
30
|
+
# Support ?pinball=name1,name2,debug
|
31
|
+
_urlValueMatches = (value) ->
|
32
|
+
for v in _urlValues()
|
33
|
+
return true if value == v
|
34
|
+
false
|
35
|
+
|
36
|
+
_urlValues = (search = window.location.search) ->
|
37
|
+
pairs = search.substr(1).split('&')
|
38
|
+
for pair in pairs
|
39
|
+
[key, value] = pair.split('=')
|
40
|
+
if key == 'pinball' and value?
|
41
|
+
return value.split(',')
|
42
|
+
[]
|
43
|
+
urlValues = _urlValues() # Memoize
|
44
|
+
|
45
|
+
cssClassName = (name, prefix = 'use-') ->
|
46
|
+
prefix + name.split('_').join('-')
|
47
|
+
|
48
|
+
addCSSClassName = (name, ele = document.documentElement) ->
|
49
|
+
cN = cssClassName(name)
|
50
|
+
if ele.className.indexOf(cN) < 0
|
51
|
+
ele.className += ' ' + cN
|
52
|
+
|
53
|
+
removeCSSClassName = (name, ele = document.documentElement) ->
|
54
|
+
cN = cssClassName(name)
|
55
|
+
if ele.className.indexOf(cN) >= 0
|
56
|
+
ele.className = ele.className.replace cN, ''
|
57
|
+
|
58
|
+
add = (list) ->
|
59
|
+
for name, state of list
|
60
|
+
features[name] = state
|
61
|
+
_log "Added %s: %s.", name, state
|
62
|
+
|
63
|
+
if isActive(name)
|
64
|
+
activate(name, "automatic. added as '#{state}'")
|
65
|
+
else if _urlValueMatches(name, urlValues)
|
66
|
+
activate(name, 'URL')
|
67
|
+
|
68
|
+
get = (name) ->
|
69
|
+
features[name]
|
70
|
+
|
71
|
+
update = (name, state) ->
|
72
|
+
features[name] = state
|
73
|
+
|
74
|
+
activate = (name, sourceName = null) ->
|
75
|
+
state = get(name)
|
76
|
+
source = if sourceName? then " (source: #{sourceName})" else ''
|
77
|
+
switch state
|
78
|
+
when undefined
|
79
|
+
_log "Attempted to activate %s, but it was not found%s.", name, source
|
80
|
+
when 'inactive'
|
81
|
+
_log "Activate %s%s.", name, source
|
82
|
+
update(name, 'active')
|
83
|
+
addCSSClassName(name)
|
84
|
+
_notifySubscribersOnActivate(name)
|
85
|
+
when 'active'
|
86
|
+
_log "Attempted to activate %s, but it is already active%s.", name, source
|
87
|
+
else
|
88
|
+
_log "Attempted to activate %s, but it is %s%s.", name, state, source
|
89
|
+
|
90
|
+
deactivate = (name, source = null) ->
|
91
|
+
state = get(name)
|
92
|
+
source = if sourceName? then " (source: #{sourceName})" else ''
|
93
|
+
switch state
|
94
|
+
when undefined
|
95
|
+
_log "Attempted to deactivate %s, but it was not found%s.", name, source
|
96
|
+
when 'active'
|
97
|
+
_log "Dectivate %s%s.", name, source
|
98
|
+
update(name, 'inactive')
|
99
|
+
removeCSSClassName(name)
|
100
|
+
_notifySubscribersOnDeactivate(name)
|
101
|
+
else
|
102
|
+
_log "Attempted to deactivate %s, but it is %s%s.", name, state, source
|
103
|
+
|
104
|
+
isActive = (name) ->
|
105
|
+
get(name) == 'active'
|
106
|
+
|
107
|
+
_buildSubscriber = (onActivate, onDeactivate) ->
|
108
|
+
onActivate: if onActivate? then onActivate else ->
|
109
|
+
onDeactivate: if onDeactivate? then onDeactivate else ->
|
110
|
+
|
111
|
+
# If the feature is already active, the callback is invoked immediately.
|
112
|
+
subscribe = (name, onActivate, onDeactivate) ->
|
113
|
+
_log 'Added subscriber to %s', name
|
114
|
+
subscriber = _buildSubscriber(onActivate, onDeactivate)
|
115
|
+
subscribers[name] ?= []
|
116
|
+
subscribers[name].push(subscriber)
|
117
|
+
_notifySubscriberOnActivate(subscriber, name) if isActive(name)
|
118
|
+
|
119
|
+
push = (params) ->
|
120
|
+
method = params.shift()
|
121
|
+
@[method].apply(@, params)
|
122
|
+
|
123
|
+
state = ->
|
124
|
+
features
|
125
|
+
|
126
|
+
reset = ->
|
127
|
+
features = {}
|
128
|
+
|
129
|
+
debug = ->
|
130
|
+
showLog = true
|
131
|
+
|
132
|
+
# Exports
|
133
|
+
exports = {
|
134
|
+
add
|
135
|
+
get
|
136
|
+
activate
|
137
|
+
deactivate
|
138
|
+
isActive
|
139
|
+
subscribe
|
140
|
+
push
|
141
|
+
state
|
142
|
+
reset
|
143
|
+
debug
|
144
|
+
cssClassName
|
145
|
+
addCSSClassName
|
146
|
+
removeCSSClassName
|
147
|
+
_urlValues
|
148
|
+
}
|
149
|
+
|
150
|
+
# Initialize
|
151
|
+
if window?.pinball
|
152
|
+
debug() if _urlValueMatches('debug')
|
153
|
+
while window.pinball.length
|
154
|
+
exports.push window.pinball.shift()
|
155
|
+
window.pinball = exports
|
156
|
+
|
157
|
+
exports
|
@@ -0,0 +1,50 @@
|
|
1
|
+
define ['css_tagger'], (tagger) ->
|
2
|
+
|
3
|
+
describe 'css_tagger', ->
|
4
|
+
it 'should add classes from the pinball async function queue', ->
|
5
|
+
ele = document.createElement 'div'
|
6
|
+
pinballQueue = [
|
7
|
+
['activate','my_feature']
|
8
|
+
]
|
9
|
+
|
10
|
+
tagger ele, pinballQueue, ''
|
11
|
+
expect(ele.className).toEqual ' use-my-feature'
|
12
|
+
|
13
|
+
it 'should add classes from query params', ->
|
14
|
+
ele = document.createElement 'div'
|
15
|
+
tagger ele, [], '?pinball=feature_a,feature_b&other=param'
|
16
|
+
expect(ele.className).toEqual ' use-feature-a use-feature-b'
|
17
|
+
|
18
|
+
it 'should not interfere with existing class names', ->
|
19
|
+
ele = document.createElement 'div'
|
20
|
+
ele.className = 'foo-bar'
|
21
|
+
tagger ele, [], '?pinball=feature_a,feature_b&other=param'
|
22
|
+
expect(ele.className).toEqual 'foo-bar use-feature-a use-feature-b'
|
23
|
+
|
24
|
+
it 'should add classes from added pinball features', ->
|
25
|
+
ele = document.createElement 'div'
|
26
|
+
pinballQueue = [
|
27
|
+
[
|
28
|
+
'add'
|
29
|
+
feature_a: 'active'
|
30
|
+
feature_b: 'inactive'
|
31
|
+
feature_c: 'active'
|
32
|
+
]
|
33
|
+
]
|
34
|
+
|
35
|
+
tagger ele, pinballQueue, ''
|
36
|
+
expect(ele.className).toEqual ' use-feature-a use-feature-c'
|
37
|
+
|
38
|
+
it 'should add classes from queue and query params', ->
|
39
|
+
ele = document.createElement 'div'
|
40
|
+
pinballQueue = [
|
41
|
+
[
|
42
|
+
'add'
|
43
|
+
feature_a: 'active'
|
44
|
+
],
|
45
|
+
['activate', 'feature_b'],
|
46
|
+
['something-odd']
|
47
|
+
]
|
48
|
+
|
49
|
+
tagger ele, pinballQueue, '?pinball=feature_c'
|
50
|
+
expect(ele.className).toEqual ' use-feature-a use-feature-b use-feature-c'
|
@@ -0,0 +1,268 @@
|
|
1
|
+
define ['pinball_wizard'], (pinball) ->
|
2
|
+
|
3
|
+
beforeEach ->
|
4
|
+
pinball.reset()
|
5
|
+
|
6
|
+
describe 'initialize', ->
|
7
|
+
it 'is defined', ->
|
8
|
+
expect(pinball).toBeDefined()
|
9
|
+
|
10
|
+
describe '#reset', ->
|
11
|
+
it 'removes all features', ->
|
12
|
+
pinball.add
|
13
|
+
a: 'active'
|
14
|
+
pinball.reset()
|
15
|
+
expect(pinball.state()).toEqual({})
|
16
|
+
|
17
|
+
describe '#add', ->
|
18
|
+
it 'activates if active', ->
|
19
|
+
pinball.add
|
20
|
+
a: 'active'
|
21
|
+
expect(pinball.isActive('a')).toEqual(true)
|
22
|
+
|
23
|
+
it 'does not activate if inactive', ->
|
24
|
+
pinball.add
|
25
|
+
a: 'inactive'
|
26
|
+
expect(pinball.isActive('a')).toEqual(false)
|
27
|
+
|
28
|
+
it 'does not activate if disabled', ->
|
29
|
+
pinball.add
|
30
|
+
a: 'disabled: reason'
|
31
|
+
expect(pinball.isActive('a')).toEqual(false)
|
32
|
+
|
33
|
+
describe 'with a ?pinball=feature,feature url param', ->
|
34
|
+
|
35
|
+
originalPathname = null
|
36
|
+
|
37
|
+
beforeEach ->
|
38
|
+
originalPathname = window.location.pathname
|
39
|
+
|
40
|
+
afterEach ->
|
41
|
+
window.history.replaceState(null, null, originalPathname)
|
42
|
+
|
43
|
+
it 'is not active when mismatched', ->
|
44
|
+
urlParam = '?pinball=foo,bar'
|
45
|
+
window.history.replaceState(null, null, window.location.pathname + urlParam)
|
46
|
+
pinball.add
|
47
|
+
a: 'inactive'
|
48
|
+
b: 'inactive'
|
49
|
+
expect(pinball.isActive('a')).toEqual(false)
|
50
|
+
expect(pinball.isActive('b')).toEqual(false)
|
51
|
+
|
52
|
+
it 'is active when matching', ->
|
53
|
+
urlParam = '?pinball=a,b'
|
54
|
+
# Mock a different url
|
55
|
+
window.history.replaceState(null, null, window.location.pathname + urlParam)
|
56
|
+
pinball.add
|
57
|
+
a: 'inactive'
|
58
|
+
b: 'inactive'
|
59
|
+
expect(pinball.isActive('a')).toEqual(true)
|
60
|
+
expect(pinball.isActive('b')).toEqual(true)
|
61
|
+
|
62
|
+
describe '#state', ->
|
63
|
+
it 'displays a list based on state', ->
|
64
|
+
pinball.add
|
65
|
+
a: 'active'
|
66
|
+
b: 'inactive'
|
67
|
+
c: 'disabled: reason'
|
68
|
+
|
69
|
+
expect(pinball.state()).toEqual({
|
70
|
+
a: 'active'
|
71
|
+
b: 'inactive'
|
72
|
+
c: 'disabled: reason'
|
73
|
+
})
|
74
|
+
|
75
|
+
describe '#activate', ->
|
76
|
+
it 'makes an inactive feature active', ->
|
77
|
+
pinball.add
|
78
|
+
a: 'inactive'
|
79
|
+
pinball.activate 'a'
|
80
|
+
|
81
|
+
expect(pinball.get('a')).toEqual('active')
|
82
|
+
|
83
|
+
it 'does not make a disabled feature active', ->
|
84
|
+
pinball.add
|
85
|
+
a: 'disabled'
|
86
|
+
pinball.activate 'a'
|
87
|
+
|
88
|
+
expect(pinball.get('a')).toEqual('disabled')
|
89
|
+
|
90
|
+
describe '#deactivate', ->
|
91
|
+
it 'makes an active feature inactive', ->
|
92
|
+
pinball.add
|
93
|
+
a: 'active'
|
94
|
+
pinball.deactivate 'a'
|
95
|
+
|
96
|
+
expect(pinball.get('a')).toEqual('inactive')
|
97
|
+
|
98
|
+
describe '#isActive', ->
|
99
|
+
beforeEach ->
|
100
|
+
pinball.add
|
101
|
+
a: 'inactive'
|
102
|
+
|
103
|
+
it 'is true after activating', ->
|
104
|
+
pinball.activate 'a'
|
105
|
+
expect(pinball.isActive('a')).toEqual(true)
|
106
|
+
|
107
|
+
describe '#subscribe', ->
|
108
|
+
callback = null
|
109
|
+
beforeEach ->
|
110
|
+
callback = jasmine.createSpy('callback')
|
111
|
+
|
112
|
+
describe 'when the activate callback should be called', ->
|
113
|
+
it 'calls after activating', ->
|
114
|
+
pinball.add
|
115
|
+
a: 'inactive'
|
116
|
+
pinball.subscribe 'a', callback
|
117
|
+
pinball.activate 'a'
|
118
|
+
expect(callback).toHaveBeenCalled()
|
119
|
+
|
120
|
+
it 'calls it once on multiple activations', ->
|
121
|
+
pinball.add
|
122
|
+
a: 'inactive'
|
123
|
+
pinball.subscribe 'a', callback
|
124
|
+
pinball.activate 'a'
|
125
|
+
pinball.activate 'a'
|
126
|
+
pinball.activate 'a'
|
127
|
+
expect(callback.calls.count()).toEqual(1)
|
128
|
+
|
129
|
+
it 'calls it twice when toggling activations', ->
|
130
|
+
pinball.add
|
131
|
+
a: 'inactive'
|
132
|
+
pinball.subscribe 'a', callback
|
133
|
+
pinball.activate 'a'
|
134
|
+
pinball.deactivate 'a'
|
135
|
+
pinball.activate 'a'
|
136
|
+
expect(callback.calls.count()).toEqual(2)
|
137
|
+
|
138
|
+
it 'calls when subscribing then adding and then activating a feature', ->
|
139
|
+
pinball.subscribe 'a', callback
|
140
|
+
pinball.add
|
141
|
+
a: 'inactive'
|
142
|
+
pinball.activate 'a'
|
143
|
+
expect(callback).toHaveBeenCalled()
|
144
|
+
|
145
|
+
it 'calls when subscribing to an already active feature', ->
|
146
|
+
pinball.add
|
147
|
+
a: 'active'
|
148
|
+
pinball.subscribe 'a', callback
|
149
|
+
expect(callback).toHaveBeenCalled()
|
150
|
+
|
151
|
+
describe 'when the activate callback should not be called', ->
|
152
|
+
it 'does not call when the feature is missing', ->
|
153
|
+
pinball.subscribe 'a', callback
|
154
|
+
pinball.activate 'a'
|
155
|
+
expect(callback).not.toHaveBeenCalled()
|
156
|
+
|
157
|
+
it 'does not call when the feature is disabled', ->
|
158
|
+
pinball.add
|
159
|
+
a: 'disabled: reason'
|
160
|
+
pinball.subscribe 'a', callback
|
161
|
+
pinball.activate 'a'
|
162
|
+
expect(callback).not.toHaveBeenCalled()
|
163
|
+
|
164
|
+
describe 'when the deactivate callback should be called', ->
|
165
|
+
it 'calls after deactivate', ->
|
166
|
+
pinball.add
|
167
|
+
a: 'inactive'
|
168
|
+
pinball.subscribe 'a', null, callback
|
169
|
+
pinball.activate 'a'
|
170
|
+
pinball.deactivate 'a'
|
171
|
+
expect(callback).toHaveBeenCalled()
|
172
|
+
|
173
|
+
it 'calls it once on multiple deactivations', ->
|
174
|
+
pinball.add
|
175
|
+
a: 'inactive'
|
176
|
+
pinball.subscribe 'a', null, callback
|
177
|
+
pinball.activate 'a'
|
178
|
+
pinball.deactivate 'a'
|
179
|
+
pinball.deactivate 'a'
|
180
|
+
expect(callback.calls.count()).toEqual(1)
|
181
|
+
|
182
|
+
it 'calls it twice when toggling deactivations', ->
|
183
|
+
pinball.add
|
184
|
+
a: 'inactive'
|
185
|
+
pinball.subscribe 'a', null, callback
|
186
|
+
pinball.activate 'a'
|
187
|
+
pinball.deactivate 'a'
|
188
|
+
pinball.activate 'a'
|
189
|
+
pinball.deactivate 'a'
|
190
|
+
expect(callback.calls.count()).toEqual(2)
|
191
|
+
|
192
|
+
it 'calls when subscribing, adding adding an active then deactivating', ->
|
193
|
+
pinball.subscribe 'a', null, callback
|
194
|
+
pinball.add
|
195
|
+
a: 'active'
|
196
|
+
pinball.deactivate 'a'
|
197
|
+
expect(callback).toHaveBeenCalled()
|
198
|
+
|
199
|
+
it 'calls when subscribing then adding and then deactivating a feature', ->
|
200
|
+
pinball.subscribe 'a', null, callback
|
201
|
+
pinball.add
|
202
|
+
a: 'inactive'
|
203
|
+
pinball.activate 'a'
|
204
|
+
pinball.deactivate 'a'
|
205
|
+
expect(callback).toHaveBeenCalled()
|
206
|
+
|
207
|
+
describe 'when the deactivate callback should not be called', ->
|
208
|
+
it 'does not call when subscribing then adding an active feature', ->
|
209
|
+
pinball.subscribe 'a', null, callback
|
210
|
+
pinball.add
|
211
|
+
a: 'active'
|
212
|
+
expect(callback).not.toHaveBeenCalled()
|
213
|
+
|
214
|
+
it 'does not call when the feature is missing', ->
|
215
|
+
pinball.subscribe 'a', null, callback
|
216
|
+
pinball.activate 'a'
|
217
|
+
pinball.deactivate 'a'
|
218
|
+
expect(callback).not.toHaveBeenCalled()
|
219
|
+
|
220
|
+
it 'does not call when the feature is disabled', ->
|
221
|
+
pinball.add
|
222
|
+
a: 'disabled'
|
223
|
+
pinball.subscribe 'a', null, callback
|
224
|
+
pinball.activate 'a'
|
225
|
+
pinball.deactivate 'a'
|
226
|
+
expect(callback).not.toHaveBeenCalled()
|
227
|
+
|
228
|
+
describe '#push', ->
|
229
|
+
it 'calls the function with the first entry and the args for the rest', ->
|
230
|
+
spyOn(pinball, 'activate')
|
231
|
+
pinball.push ['activate','my-feature']
|
232
|
+
expect(pinball.activate).toHaveBeenCalledWith('my-feature')
|
233
|
+
|
234
|
+
describe '#cssClassName', ->
|
235
|
+
it 'builds the name with the prefix', ->
|
236
|
+
expect(pinball.cssClassName('my_feature')).toEqual 'use-my-feature'
|
237
|
+
|
238
|
+
describe '#addCSSClassName', ->
|
239
|
+
it 'appends', ->
|
240
|
+
ele = document.createElement 'div'
|
241
|
+
pinball.addCSSClassName('my_feature', ele)
|
242
|
+
expect(ele.className).toEqual ' use-my-feature'
|
243
|
+
|
244
|
+
it 'does not append twice', ->
|
245
|
+
ele = document.createElement 'div'
|
246
|
+
pinball.addCSSClassName('my_feature', ele)
|
247
|
+
pinball.addCSSClassName('my_feature', ele)
|
248
|
+
expect(ele.className).toEqual ' use-my-feature'
|
249
|
+
|
250
|
+
describe '#removeCSSClassName', ->
|
251
|
+
it 'removes it', ->
|
252
|
+
ele = document.createElement 'div'
|
253
|
+
pinball.addCSSClassName('my_feature', ele)
|
254
|
+
pinball.removeCSSClassName('my_feature', ele)
|
255
|
+
expect(ele.className).toEqual ''
|
256
|
+
|
257
|
+
describe '#_urlValues', ->
|
258
|
+
it 'pulls out the parts', ->
|
259
|
+
urlParam = '?pinball=a,b'
|
260
|
+
expect(pinball._urlValues(urlParam)).toEqual(['a','b'])
|
261
|
+
|
262
|
+
it 'is empty for blank values', ->
|
263
|
+
urlParam = '?pinball'
|
264
|
+
expect(pinball._urlValues(urlParam)).toEqual([])
|
265
|
+
|
266
|
+
it 'works with other keys/values', ->
|
267
|
+
urlParam = '?foo=bar&pinball=a,b&bar'
|
268
|
+
expect(pinball._urlValues(urlParam)).toEqual(['a','b'])
|