pinball_wizard 0.0.1.pre → 0.3.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.
- 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'])
|