upjs-rails 0.17.0 → 0.18.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 +4 -4
- data/CHANGELOG.md +46 -1
- data/dist/up.js +929 -374
- data/dist/up.min.js +2 -2
- data/lib/assets/javascripts/up/browser.js.coffee +31 -14
- data/lib/assets/javascripts/up/bus.js.coffee +87 -22
- data/lib/assets/javascripts/up/flow.js.coffee +119 -43
- data/lib/assets/javascripts/up/form.js.coffee +188 -57
- data/lib/assets/javascripts/up/link.js.coffee +57 -21
- data/lib/assets/javascripts/up/modal.js.coffee +77 -63
- data/lib/assets/javascripts/up/motion.js.coffee +10 -9
- data/lib/assets/javascripts/up/popup.js.coffee +54 -40
- data/lib/assets/javascripts/up/proxy.js.coffee +46 -17
- data/lib/assets/javascripts/up/rails.js.coffee +22 -4
- data/lib/assets/javascripts/up/syntax.js.coffee +2 -2
- data/lib/assets/javascripts/up/util.js.coffee +100 -16
- data/lib/upjs/rails/inspector.rb +3 -3
- data/lib/upjs/rails/version.rb +1 -1
- data/spec_app/Gemfile.lock +1 -4
- data/spec_app/app/controllers/test_controller.rb +2 -2
- data/spec_app/spec/controllers/test_controller_spec.rb +5 -5
- data/spec_app/spec/javascripts/helpers/browser_switches.js.coffee +9 -0
- data/spec_app/spec/javascripts/helpers/knife.js.coffee +0 -1
- data/spec_app/spec/javascripts/helpers/reset_path.js.coffee +4 -5
- data/spec_app/spec/javascripts/helpers/to_be_present.js.coffee +5 -0
- data/spec_app/spec/javascripts/helpers/to_have_request_method.js.coffee +8 -0
- data/spec_app/spec/javascripts/up/bus_spec.js.coffee +26 -0
- data/spec_app/spec/javascripts/up/flow_spec.js.coffee +203 -91
- data/spec_app/spec/javascripts/up/form_spec.js.coffee +244 -49
- data/spec_app/spec/javascripts/up/history_spec.js.coffee +8 -2
- data/spec_app/spec/javascripts/up/link_spec.js.coffee +83 -30
- data/spec_app/spec/javascripts/up/modal_spec.js.coffee +23 -17
- data/spec_app/spec/javascripts/up/motion_spec.js.coffee +4 -4
- data/spec_app/spec/javascripts/up/navigation_spec.js.coffee +1 -1
- data/spec_app/spec/javascripts/up/popup_spec.js.coffee +26 -16
- data/spec_app/spec/javascripts/up/proxy_spec.js.coffee +45 -13
- data/spec_app/spec/javascripts/up/rails_spec.js.coffee +48 -0
- data/spec_app/spec/javascripts/up/util_spec.js.coffee +48 -0
- metadata +5 -2
@@ -0,0 +1,9 @@
|
|
1
|
+
window.describeCapability = (capability, examples) ->
|
2
|
+
if up.browser[capability]()
|
3
|
+
examples()
|
4
|
+
|
5
|
+
window.describeFallback = (capability, examples) ->
|
6
|
+
describe "in a browser without #{capability}", ->
|
7
|
+
beforeEach ->
|
8
|
+
spyOn(up.browser, capability).and.returnValue(false)
|
9
|
+
examples()
|
@@ -1,9 +1,8 @@
|
|
1
1
|
beforeEach ->
|
2
|
-
@
|
3
|
-
@
|
2
|
+
@hrefBeforeExample = location.href
|
3
|
+
@titleBeforeExample = document.title
|
4
4
|
|
5
5
|
afterEach ->
|
6
6
|
if up.browser.canPushState()
|
7
|
-
history.replaceState?({}, @
|
8
|
-
document.title = @
|
9
|
-
|
7
|
+
history.replaceState?({}, @titleBeforeExample, @hrefBeforeExample)
|
8
|
+
document.title = @titleBeforeExample
|
@@ -0,0 +1,8 @@
|
|
1
|
+
beforeEach ->
|
2
|
+
jasmine.addMatchers
|
3
|
+
toHaveRequestMethod: (util, customEqualityTesters) ->
|
4
|
+
compare: (request, expectedMethod) ->
|
5
|
+
console.log("real is %o, wrapped is %o", request.method, request.data()['_method'])
|
6
|
+
realMethodMatches = (request.method == expectedMethod)
|
7
|
+
wrappedMethodMatches = util.equals(request.data()['_method'], [expectedMethod], customEqualityTesters)
|
8
|
+
pass: realMethodMatches || wrappedMethodMatches
|
@@ -47,6 +47,32 @@ describe 'up.bus', ->
|
|
47
47
|
$('.child').click()
|
48
48
|
expect(observeArgs).toHaveBeenCalledWith('child', {})
|
49
49
|
|
50
|
+
describe 'up.off', ->
|
51
|
+
|
52
|
+
it 'unregisters an event listener previously registered through up.on', ->
|
53
|
+
$child = affix('.child')
|
54
|
+
clickSpy = jasmine.createSpy()
|
55
|
+
up.on 'click', '.child', clickSpy
|
56
|
+
$('.child').click()
|
57
|
+
up.off 'click', '.child', clickSpy
|
58
|
+
$('.child').click()
|
59
|
+
expect(clickSpy.calls.count()).toEqual(1)
|
60
|
+
|
61
|
+
it 'throws an error if the given event listener was not registered through up.on', ->
|
62
|
+
someFunction = ->
|
63
|
+
offing = -> up.off 'click', '.child', someFunction
|
64
|
+
expect(offing).toThrowError(/(not|never) registered/i)
|
65
|
+
|
66
|
+
it 'reduces the internally tracked list of event listeners (bugfix for memory leak)', ->
|
67
|
+
getCount = -> up.bus.knife.get('Object.keys(liveUpDescriptions).length')
|
68
|
+
oldCount = getCount()
|
69
|
+
expect(oldCount).toBeGreaterThan(0)
|
70
|
+
clickSpy = jasmine.createSpy()
|
71
|
+
up.on 'click', '.child', clickSpy
|
72
|
+
expect(getCount()).toBe(oldCount + 1)
|
73
|
+
up.off 'click', '.child', clickSpy
|
74
|
+
expect(getCount()).toBe(oldCount)
|
75
|
+
|
50
76
|
describe 'up.emit', ->
|
51
77
|
|
52
78
|
it 'triggers an event on the document', ->
|
@@ -6,7 +6,7 @@ describe 'up.flow', ->
|
|
6
6
|
|
7
7
|
describe 'up.replace', ->
|
8
8
|
|
9
|
-
|
9
|
+
describeCapability 'canPushState', ->
|
10
10
|
|
11
11
|
beforeEach ->
|
12
12
|
|
@@ -21,7 +21,7 @@ describe 'up.flow', ->
|
|
21
21
|
<div class="after">new-after</div>
|
22
22
|
"""
|
23
23
|
|
24
|
-
@respond = -> @respondWith(@responseText)
|
24
|
+
@respond = (options = {}) -> @respondWith(@responseText, options)
|
25
25
|
|
26
26
|
it 'replaces the given selector with the same selector from a freshly fetched page', (done) ->
|
27
27
|
promise = up.replace('.middle', '/path')
|
@@ -31,20 +31,133 @@ describe 'up.flow', ->
|
|
31
31
|
expect($('.middle')).toHaveText('new-middle')
|
32
32
|
expect($('.after')).toHaveText('old-after')
|
33
33
|
done()
|
34
|
-
|
35
|
-
it 'should set the browser location to the given URL', (done) ->
|
36
|
-
promise = up.replace('.middle', '/path')
|
37
|
-
@respond()
|
38
|
-
promise.then ->
|
39
|
-
expect(window.location.pathname).toBe('/path')
|
40
|
-
done()
|
41
34
|
|
42
|
-
it
|
43
|
-
|
44
|
-
@
|
45
|
-
|
46
|
-
|
47
|
-
|
35
|
+
it 'sends an X-Up-Target HTTP headers along with the request', ->
|
36
|
+
up.replace('.middle', '/path')
|
37
|
+
request = @lastRequest()
|
38
|
+
console.log(request.requestHeaders)
|
39
|
+
expect(request.requestHeaders['X-Up-Target']).toEqual('.middle')
|
40
|
+
|
41
|
+
describe 'with { data } option', ->
|
42
|
+
|
43
|
+
it "uses the given params as a non-GET request's payload", ->
|
44
|
+
givenParams = { 'foo-key': 'foo-value', 'bar-key': 'bar-value' }
|
45
|
+
up.replace('.middle', '/path', method: 'put', data: givenParams)
|
46
|
+
expect(@lastRequest().data()['foo-key']).toEqual(['foo-value'])
|
47
|
+
expect(@lastRequest().data()['bar-key']).toEqual(['bar-value'])
|
48
|
+
|
49
|
+
it "encodes the given params into the URL of a GET request", ->
|
50
|
+
givenParams = { 'foo-key': 'foo value', 'bar-key': 'bar value' }
|
51
|
+
up.replace('.middle', '/path', method: 'get', data: givenParams)
|
52
|
+
expect(@lastRequest().url).toEndWith('/path?foo-key=foo+value&bar-key=bar+value')
|
53
|
+
|
54
|
+
it 'uses a HTTP method given as { method } option', ->
|
55
|
+
up.replace('.middle', '/path', method: 'put')
|
56
|
+
expect(@lastRequest()).toHaveRequestMethod('PUT')
|
57
|
+
|
58
|
+
describe 'if the server responds with a non-200 status code', ->
|
59
|
+
|
60
|
+
it 'replaces the <body> instead of the given selector', ->
|
61
|
+
implantSpy = up.flow.knife.mock('implant') # can't have the example replace the Jasmine test runner UI
|
62
|
+
up.replace('.middle', '/path')
|
63
|
+
@respond(status: 500)
|
64
|
+
expect(implantSpy).toHaveBeenCalledWith('body', jasmine.any(String), jasmine.any(Object))
|
65
|
+
|
66
|
+
it 'uses a target selector given as { failTarget } option', ->
|
67
|
+
up.replace('.middle', '/path', failTarget: '.after')
|
68
|
+
@respond(status: 500)
|
69
|
+
expect($('.middle')).toHaveText('old-middle')
|
70
|
+
expect($('.after')).toHaveText('new-after')
|
71
|
+
|
72
|
+
describe 'history', ->
|
73
|
+
|
74
|
+
it 'should set the browser location to the given URL', (done) ->
|
75
|
+
promise = up.replace('.middle', '/path')
|
76
|
+
@respond()
|
77
|
+
promise.then ->
|
78
|
+
expect(location.href).toEndWith('/path')
|
79
|
+
done()
|
80
|
+
|
81
|
+
it 'does not add a history entry after non-GET requests', ->
|
82
|
+
promise = up.replace('.middle', '/path', method: 'post')
|
83
|
+
@respond()
|
84
|
+
expect(location.href).toEndWith(@hrefBeforeExample)
|
85
|
+
|
86
|
+
it 'adds a history entry after non-GET requests if the response includes a { X-Up-Method: "get" } header (will happen after a redirect)', ->
|
87
|
+
promise = up.replace('.middle', '/path', method: 'post')
|
88
|
+
@respond(responseHeaders: { 'X-Up-Method': 'get' })
|
89
|
+
expect(location.href).toEndWith('/path')
|
90
|
+
|
91
|
+
it 'does not a history entry after a failed GET-request', ->
|
92
|
+
promise = up.replace('.middle', '/path', method: 'post', failTarget: '.middle')
|
93
|
+
@respond(status: 500)
|
94
|
+
expect(location.href).toEndWith(@hrefBeforeExample)
|
95
|
+
|
96
|
+
it 'does not add a history entry with { history: false } option', ->
|
97
|
+
promise = up.replace('.middle', '/path', history: false)
|
98
|
+
@respond()
|
99
|
+
expect(location.href).toEndWith(@hrefBeforeExample)
|
100
|
+
|
101
|
+
it "detects a redirect's new URL when the server sets an X-Up-Location header", ->
|
102
|
+
promise = up.replace('.middle', '/path')
|
103
|
+
@respond(responseHeaders: { 'X-Up-Location': '/other-path' })
|
104
|
+
expect(location.href).toEndWith('/other-path')
|
105
|
+
|
106
|
+
it 'adds params from a { data } option to the URL of a GET request', ->
|
107
|
+
promise = up.replace('.middle', '/path', data: { 'foo-key': 'foo value', 'bar-key': 'bar value' })
|
108
|
+
@respond()
|
109
|
+
console.log("EXPECTATION COMING UP AGAINST %o", location.pathname)
|
110
|
+
expect(location.href).toEndWith('/path?foo-key=foo%20value&bar-key=bar%20value')
|
111
|
+
|
112
|
+
describe 'if a URL is given as { history } option', ->
|
113
|
+
|
114
|
+
it 'uses that URL as the new location after a GET request', ->
|
115
|
+
promise = up.replace('.middle', '/path', history: '/given-path')
|
116
|
+
@respond(failTarget: '.middle')
|
117
|
+
expect(location.href).toEndWith('/given-path')
|
118
|
+
|
119
|
+
it 'adds a history entry after a non-GET request', ->
|
120
|
+
promise = up.replace('.middle', '/path', method: 'post', history: '/given-path')
|
121
|
+
@respond(failTarget: '.middle')
|
122
|
+
expect(location.href).toEndWith('/given-path')
|
123
|
+
|
124
|
+
it 'does not add a history entry after a failed non-GET request', ->
|
125
|
+
promise = up.replace('.middle', '/path', method: 'post', history: '/given-path', failTarget: '.middle')
|
126
|
+
@respond(failTarget: '.middle', status: 500)
|
127
|
+
expect(location.href).toEndWith(@hrefBeforeExample)
|
128
|
+
|
129
|
+
describe 'source', ->
|
130
|
+
|
131
|
+
it 'remembers the source the fragment was retrieved from', (done) ->
|
132
|
+
promise = up.replace('.middle', '/path')
|
133
|
+
@respond()
|
134
|
+
promise.then ->
|
135
|
+
expect($('.middle').attr('up-source')).toMatch(/\/path$/)
|
136
|
+
done()
|
137
|
+
|
138
|
+
it 'reuses the previous source for a non-GET request (since that is reloadable)', ->
|
139
|
+
@oldMiddle.attr('up-source', '/previous-source')
|
140
|
+
up.replace('.middle', '/path', method: 'post')
|
141
|
+
@respond()
|
142
|
+
expect($('.middle')).toHaveText('new-middle')
|
143
|
+
expect(up.flow.source('.middle')).toEndWith('/previous-source')
|
144
|
+
|
145
|
+
describe 'if a URL is given as { source } option', ->
|
146
|
+
|
147
|
+
it 'uses that URL as the source for a GET request', ->
|
148
|
+
promise = up.replace('.middle', '/path', source: '/given-path')
|
149
|
+
@respond()
|
150
|
+
expect(up.flow.source('.middle')).toEndWith('/given-path')
|
151
|
+
|
152
|
+
it 'uses that URL as the source after a non-GET request', ->
|
153
|
+
promise = up.replace('.middle', '/path', method: 'post', source: '/given-path')
|
154
|
+
@respond()
|
155
|
+
expect(up.flow.source('.middle')).toEndWith('/given-path')
|
156
|
+
|
157
|
+
it 'still reuses the previous URL after a failed non-GET request', ->
|
158
|
+
promise = up.replace('.middle', '/path', method: 'post', source: '/given-path', failTarget: '.middle')
|
159
|
+
@respond(status: 500)
|
160
|
+
expect(up.flow.source('.middle')).toEndWith(@hrefBeforeExample)
|
48
161
|
|
49
162
|
it 'understands non-standard CSS selector extensions such as :has(...)', (done) ->
|
50
163
|
$first = affix('.boxx#first')
|
@@ -64,82 +177,79 @@ describe 'up.flow', ->
|
|
64
177
|
expect($('#second span')).toHaveText('old second')
|
65
178
|
done()
|
66
179
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
180
|
+
describe 'document title', ->
|
181
|
+
|
182
|
+
it "sets the document title to a 'title' tag in the response", ->
|
183
|
+
affix('.container').text('old container text')
|
184
|
+
up.replace('.container', '/path')
|
185
|
+
@respondWith """
|
186
|
+
<html>
|
187
|
+
<head>
|
188
|
+
<title>Title from HTML</title>
|
189
|
+
</head>
|
190
|
+
<body>
|
191
|
+
<div class='container'>
|
192
|
+
new container text
|
193
|
+
</div>
|
194
|
+
</body>
|
195
|
+
</html>
|
196
|
+
"""
|
197
|
+
expect($('.container')).toHaveText('new container text')
|
198
|
+
expect(document.title).toBe('Title from HTML')
|
199
|
+
|
200
|
+
it "sets the document title to an 'X-Up-Title' header in the response", ->
|
201
|
+
affix('.container').text('old container text')
|
202
|
+
up.replace('.container', '/path')
|
203
|
+
@respondWith
|
204
|
+
responseHeaders:
|
205
|
+
'X-Up-Title': 'Title from header'
|
206
|
+
responseText: """
|
94
207
|
<div class='container'>
|
95
208
|
new container text
|
96
209
|
</div>
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
expect($('.container')).toHaveText('new container text')
|
101
|
-
expect(document.title).toBe('Title from HTML')
|
102
|
-
|
103
|
-
it "sets the document title to an 'X-Up-Title' header in the response", ->
|
104
|
-
affix('.container').text('old container text')
|
105
|
-
up.replace('.container', '/path')
|
106
|
-
@respondWith
|
107
|
-
responseHeaders:
|
108
|
-
'X-Up-Title': 'Title from header'
|
109
|
-
responseText: """
|
110
|
-
<div class='container'>
|
111
|
-
new container text
|
112
|
-
</div>
|
113
|
-
"""
|
114
|
-
expect($('.container')).toHaveText('new container text')
|
115
|
-
expect(document.title).toBe('Title from header')
|
116
|
-
|
117
|
-
it 'prepends instead of replacing when the target has a :before pseudo-selector', (done) ->
|
118
|
-
promise = up.replace('.middle:before', '/path')
|
119
|
-
@respond()
|
120
|
-
promise.then ->
|
121
|
-
expect($('.before')).toHaveText('old-before')
|
122
|
-
expect($('.middle')).toHaveText('new-middleold-middle')
|
123
|
-
expect($('.after')).toHaveText('old-after')
|
124
|
-
done()
|
210
|
+
"""
|
211
|
+
expect($('.container')).toHaveText('new container text')
|
212
|
+
expect(document.title).toBe('Title from header')
|
125
213
|
|
126
|
-
|
127
|
-
promise = up.replace('.middle:after', '/path')
|
128
|
-
@respond()
|
129
|
-
promise.then ->
|
130
|
-
expect($('.before')).toHaveText('old-before')
|
131
|
-
expect($('.middle')).toHaveText('old-middlenew-middle')
|
132
|
-
expect($('.after')).toHaveText('old-after')
|
133
|
-
done()
|
214
|
+
describe 'selector processing', ->
|
134
215
|
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
216
|
+
it 'replaces multiple selectors separated with a comma', (done) ->
|
217
|
+
promise = up.replace('.middle, .after', '/path')
|
218
|
+
@respond()
|
219
|
+
promise.then ->
|
220
|
+
expect($('.before')).toHaveText('old-before')
|
221
|
+
expect($('.middle')).toHaveText('new-middle')
|
222
|
+
expect($('.after')).toHaveText('new-after')
|
223
|
+
done()
|
224
|
+
|
225
|
+
it 'replaces the body if asked to replace the "html" selector'
|
226
|
+
|
227
|
+
it 'prepends instead of replacing when the target has a :before pseudo-selector', (done) ->
|
228
|
+
promise = up.replace('.middle:before', '/path')
|
229
|
+
@respond()
|
230
|
+
promise.then ->
|
231
|
+
expect($('.before')).toHaveText('old-before')
|
232
|
+
expect($('.middle')).toHaveText('new-middleold-middle')
|
233
|
+
expect($('.after')).toHaveText('old-after')
|
234
|
+
done()
|
235
|
+
|
236
|
+
it 'appends instead of replacing when the target has a :after pseudo-selector', (done) ->
|
237
|
+
promise = up.replace('.middle:after', '/path')
|
238
|
+
@respond()
|
239
|
+
promise.then ->
|
240
|
+
expect($('.before')).toHaveText('old-before')
|
241
|
+
expect($('.middle')).toHaveText('old-middlenew-middle')
|
242
|
+
expect($('.after')).toHaveText('old-after')
|
243
|
+
done()
|
244
|
+
|
245
|
+
it "lets the developer choose between replacing/prepending/appending for each selector", (done) ->
|
246
|
+
promise = up.replace('.before:before, .middle, .after:after', '/path')
|
247
|
+
@respond()
|
248
|
+
promise.then ->
|
249
|
+
expect($('.before')).toHaveText('new-beforeold-before')
|
250
|
+
expect($('.middle')).toHaveText('new-middle')
|
251
|
+
expect($('.after')).toHaveText('old-afternew-after')
|
252
|
+
done()
|
143
253
|
|
144
254
|
it 'executes only those script-tags in the response that get inserted into the DOM', (done) ->
|
145
255
|
window.scriptTagExecuted = jasmine.createSpy('scriptTagExecuted')
|
@@ -272,7 +382,9 @@ describe 'up.flow', ->
|
|
272
382
|
expect($('.up-insertion')).not.toExist()
|
273
383
|
done()
|
274
384
|
|
275
|
-
|
385
|
+
it 'uses a { failTransition } option if the request failed'
|
386
|
+
|
387
|
+
describeFallback 'canPushState', ->
|
276
388
|
|
277
389
|
it 'makes a full page load', ->
|
278
390
|
spyOn(up.browser, 'loadPage')
|
@@ -360,8 +472,8 @@ describe 'up.flow', ->
|
|
360
472
|
expect(destructor).toHaveBeenCalled()
|
361
473
|
|
362
474
|
describe 'up.reload', ->
|
363
|
-
|
364
|
-
|
475
|
+
|
476
|
+
describeCapability 'canPushState', ->
|
365
477
|
|
366
478
|
it 'reloads the given selector from the closest known source URL', (done) ->
|
367
479
|
affix('.container[up-source="/source"] .element').find('.element').text('old text')
|
@@ -377,8 +489,8 @@ describe 'up.flow', ->
|
|
377
489
|
<div class="element">new text</div>
|
378
490
|
</div>
|
379
491
|
"""
|
380
|
-
|
381
|
-
|
492
|
+
|
493
|
+
describeFallback 'canPushState', ->
|
382
494
|
|
383
495
|
it 'makes a page load from the closest known source URL', ->
|
384
496
|
affix('.container[up-source="/source"] .element').find('.element').text('old text')
|
@@ -7,13 +7,13 @@ describe 'up.form', ->
|
|
7
7
|
describe 'up.observe', ->
|
8
8
|
|
9
9
|
changeEvents = if up.browser.canInputEvent()
|
10
|
-
# Actually we only need `input`, but we want to notice
|
11
|
-
# if another script manually triggers `change` on the element.
|
10
|
+
# Actually we only need `input`, but we want to notice
|
11
|
+
# if another script manually triggers `change` on the element.
|
12
12
|
['input', 'change']
|
13
13
|
else
|
14
|
-
# Actually we won't ever get `input` from the user in this browser,
|
15
|
-
# but we want to notice if another script manually triggers `input`
|
16
|
-
# on the element.
|
14
|
+
# Actually we won't ever get `input` from the user in this browser,
|
15
|
+
# but we want to notice if another script manually triggers `input`
|
16
|
+
# on the element.
|
17
17
|
['input', 'change', 'keypress', 'paste', 'cut', 'click', 'propertychange']
|
18
18
|
|
19
19
|
u.each changeEvents, (eventName) ->
|
@@ -83,27 +83,24 @@ describe 'up.form', ->
|
|
83
83
|
done()
|
84
84
|
|
85
85
|
describe 'up.submit', ->
|
86
|
-
|
87
|
-
|
86
|
+
|
87
|
+
describeCapability 'canPushState', ->
|
88
88
|
|
89
89
|
beforeEach ->
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
90
|
+
@$form = affix('form[action="/path/to"][method="put"][up-target=".response"]')
|
91
|
+
@$form.append('<input name="field1" value="value1">')
|
92
|
+
@$form.append('<input name="field2" value="value2">')
|
94
93
|
affix('.response').text('old-text')
|
95
|
-
|
96
|
-
@promise = up.submit($form)
|
97
|
-
|
94
|
+
@promise = up.submit(@$form)
|
98
95
|
@request = @lastRequest()
|
96
|
+
|
97
|
+
it 'submits the given form and replaces the target with the response', ->
|
99
98
|
expect(@request.url).toMatch /\/path\/to$/
|
100
|
-
expect(@request
|
101
|
-
expect(@request.data()).toEqual
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
it 'submits the given form and replaces the target with the response', (done) ->
|
106
|
-
|
99
|
+
expect(@request).toHaveRequestMethod('PUT')
|
100
|
+
expect(@request.data()['field1']).toEqual(['value1'])
|
101
|
+
expect(@request.data()['field2']).toEqual(['value2'])
|
102
|
+
expect(@request.requestHeaders['X-Up-Target']).toEqual('.response')
|
103
|
+
|
107
104
|
@respondWith """
|
108
105
|
text-before
|
109
106
|
|
@@ -113,15 +110,10 @@ describe 'up.form', ->
|
|
113
110
|
|
114
111
|
text-after
|
115
112
|
"""
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
expect($('body')).not.toHaveText('text-after')
|
121
|
-
done()
|
122
|
-
|
123
|
-
it 'places the response into the form if the submission returns a 5xx status code', (done) ->
|
124
|
-
@request.respondWith
|
113
|
+
|
114
|
+
it "places the response into the form and doesn't update the browser URL if the submission returns a 5xx status code", ->
|
115
|
+
up.submit(@$form)
|
116
|
+
@respondWith
|
125
117
|
status: 500
|
126
118
|
contentType: 'text/html'
|
127
119
|
responseText:
|
@@ -134,32 +126,47 @@ describe 'up.form', ->
|
|
134
126
|
|
135
127
|
text-after
|
136
128
|
"""
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
@request.respondWith
|
129
|
+
expect(up.browser.url()).toEqual(@hrefBeforeExample)
|
130
|
+
expect($('.response')).toHaveText('old-text')
|
131
|
+
expect($('form')).toHaveText('error-messages')
|
132
|
+
expect($('body')).not.toHaveText('text-before')
|
133
|
+
expect($('body')).not.toHaveText('text-after')
|
134
|
+
|
135
|
+
it 'respects X-Up-Method and X-Up-Location response headers so the server can show that it redirected to a GET URL', ->
|
136
|
+
up.submit(@$form)
|
137
|
+
@respondWith
|
148
138
|
status: 200
|
149
139
|
contentType: 'text/html'
|
150
|
-
responseHeaders:
|
140
|
+
responseHeaders:
|
141
|
+
'X-Up-Location': '/other-path'
|
142
|
+
'X-Up-Method': 'GET'
|
151
143
|
responseText:
|
152
144
|
"""
|
153
145
|
<div class="response">
|
154
146
|
new-text
|
155
147
|
</div>
|
156
148
|
"""
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
149
|
+
|
150
|
+
expect(up.browser.url()).toEndWith('/other-path')
|
151
|
+
|
152
|
+
describe 'with { history } option', ->
|
153
|
+
|
154
|
+
it 'uses the given URL as the new browser location if the request succeeded', ->
|
155
|
+
up.submit(@$form, history: '/given-path')
|
156
|
+
@respondWith('<div class="response">new-text</div>')
|
157
|
+
expect(up.browser.url()).toEndWith('/given-path')
|
158
|
+
|
159
|
+
it 'keeps the current browser location if the request failed', ->
|
160
|
+
up.submit(@$form, history: '/given-path', failTarget: '.response')
|
161
|
+
@respondWith('<div class="response">new-text</div>', status: 500)
|
162
|
+
expect(up.browser.url()).toEqual(@hrefBeforeExample)
|
163
|
+
|
164
|
+
it 'keeps the current browser location if the option is set to false', ->
|
165
|
+
up.submit(@$form, history: false)
|
166
|
+
@respondWith('<div class="response">new-text</div>')
|
167
|
+
expect(up.browser.url()).toEqual(@hrefBeforeExample)
|
168
|
+
|
169
|
+
describeFallback 'canPushState', ->
|
163
170
|
|
164
171
|
it 'submits the given form', ->
|
165
172
|
$form = affix('form[action="/path/to"][method="put"][up-target=".response"]')
|
@@ -221,7 +228,7 @@ describe 'up.form', ->
|
|
221
228
|
|
222
229
|
request = @lastRequest()
|
223
230
|
expect(request.requestHeaders['X-Up-Validate']).toEqual('user')
|
224
|
-
expect(request.requestHeaders['X-Up-
|
231
|
+
expect(request.requestHeaders['X-Up-Target']).toEqual(".field-group:has([name='user'])")
|
225
232
|
|
226
233
|
@respondWith """
|
227
234
|
<div class="field-group has-error">
|
@@ -274,3 +281,191 @@ describe 'up.form', ->
|
|
274
281
|
$labels = $('#registration label')
|
275
282
|
expect($labels[0]).not.toHaveText('Validation message')
|
276
283
|
expect($labels[1]).toHaveText('Validation message')
|
284
|
+
|
285
|
+
describe '[up-toggle]', ->
|
286
|
+
|
287
|
+
describe 'on a select', ->
|
288
|
+
|
289
|
+
beforeEach ->
|
290
|
+
@$select = affix('select[up-toggle=".target"]')
|
291
|
+
@$blankOption = @$select.affix('option').text('<Please select something>').val('')
|
292
|
+
@$fooOption = @$select.affix('option[value="foo"]').text('Foo')
|
293
|
+
@$barOption = @$select.affix('option[value="bar"]').text('Bar')
|
294
|
+
@$bazOption = @$select.affix('option[value="baz"]').text('Baz')
|
295
|
+
|
296
|
+
it "shows the target element iff its up-show-for attribute contains the select value", ->
|
297
|
+
$target = affix('.target[up-show-for="something bar other"]')
|
298
|
+
up.hello(@$select)
|
299
|
+
expect($target).toBeHidden()
|
300
|
+
@$select.val('bar').change()
|
301
|
+
expect($target).toBeVisible()
|
302
|
+
|
303
|
+
it "shows the target element iff its up-hide-for attribute doesn't contain the select value", ->
|
304
|
+
$target = affix('.target[up-hide-for="something bar other"]')
|
305
|
+
up.hello(@$select)
|
306
|
+
expect($target).toBeVisible()
|
307
|
+
@$select.val('bar').change()
|
308
|
+
expect($target).toBeHidden()
|
309
|
+
|
310
|
+
it "shows the target element iff it has neither up-show-for nor up-hide-for and the select value is present", ->
|
311
|
+
$target = affix('.target')
|
312
|
+
up.hello(@$select)
|
313
|
+
expect($target).toBeHidden()
|
314
|
+
@$select.val('bar').change()
|
315
|
+
expect($target).toBeVisible()
|
316
|
+
|
317
|
+
it "shows the target element iff its up-show-for attribute contains a value ':present' and the select value is present", ->
|
318
|
+
$target = affix('.target[up-show-for=":present"]')
|
319
|
+
up.hello(@$select)
|
320
|
+
expect($target).toBeHidden()
|
321
|
+
@$select.val('bar').change()
|
322
|
+
expect($target).toBeVisible()
|
323
|
+
|
324
|
+
it "shows the target element iff its up-show-for attribute contains a value ':blank' and the select value is blank", ->
|
325
|
+
$target = affix('.target[up-show-for=":blank"]')
|
326
|
+
up.hello(@$select)
|
327
|
+
expect($target).toBeVisible()
|
328
|
+
@$select.val('bar').change()
|
329
|
+
expect($target).toBeHidden()
|
330
|
+
|
331
|
+
describe 'on a checkbox', ->
|
332
|
+
|
333
|
+
beforeEach ->
|
334
|
+
@$checkbox = affix('input[type="checkbox"][value="1"][up-toggle=".target"]')
|
335
|
+
|
336
|
+
it "shows the target element iff its up-show-for attribute is :checked and the checkbox is checked", ->
|
337
|
+
$target = affix('.target[up-show-for=":checked"]')
|
338
|
+
up.hello(@$checkbox)
|
339
|
+
expect($target).toBeHidden()
|
340
|
+
@$checkbox.prop('checked', true).change()
|
341
|
+
expect($target).toBeVisible()
|
342
|
+
|
343
|
+
it "shows the target element iff its up-show-for attribute is :unchecked and the checkbox is unchecked", ->
|
344
|
+
$target = affix('.target[up-show-for=":unchecked"]')
|
345
|
+
up.hello(@$checkbox)
|
346
|
+
expect($target).toBeVisible()
|
347
|
+
@$checkbox.prop('checked', true).change()
|
348
|
+
expect($target).toBeHidden()
|
349
|
+
|
350
|
+
it "shows the target element iff its up-hide-for attribute is :checked and the checkbox is unchecked", ->
|
351
|
+
$target = affix('.target[up-hide-for=":checked"]')
|
352
|
+
up.hello(@$checkbox)
|
353
|
+
expect($target).toBeVisible()
|
354
|
+
@$checkbox.prop('checked', true).change()
|
355
|
+
expect($target).toBeHidden()
|
356
|
+
|
357
|
+
it "shows the target element iff its up-hide-for attribute is :unchecked and the checkbox is checked", ->
|
358
|
+
$target = affix('.target[up-hide-for=":unchecked"]')
|
359
|
+
up.hello(@$checkbox)
|
360
|
+
expect($target).toBeHidden()
|
361
|
+
@$checkbox.prop('checked', true).change()
|
362
|
+
expect($target).toBeVisible()
|
363
|
+
|
364
|
+
it "shows the target element iff it has neither up-show-for nor up-hide-for and the checkbox is checked", ->
|
365
|
+
$target = affix('.target')
|
366
|
+
up.hello(@$checkbox)
|
367
|
+
expect($target).toBeHidden()
|
368
|
+
@$checkbox.prop('checked', true).change()
|
369
|
+
expect($target).toBeVisible()
|
370
|
+
|
371
|
+
describe 'on a group of radio buttons', ->
|
372
|
+
|
373
|
+
beforeEach ->
|
374
|
+
@$buttons = affix('.radio-buttons')
|
375
|
+
@$blankButton = @$buttons.affix('input[type="radio"][name="group"][up-toggle=".target"]').val('')
|
376
|
+
@$fooButton = @$buttons.affix('input[type="radio"][name="group"][up-toggle=".target"]').val('foo')
|
377
|
+
@$barButton = @$buttons.affix('input[type="radio"][name="group"][up-toggle=".target"]').val('bar')
|
378
|
+
@$bazkButton = @$buttons.affix('input[type="radio"][name="group"][up-toggle=".target"]').val('baz')
|
379
|
+
|
380
|
+
it "shows the target element iff its up-show-for attribute contains the selected button value", ->
|
381
|
+
$target = affix('.target[up-show-for="something bar other"]')
|
382
|
+
up.hello(@$buttons)
|
383
|
+
expect($target).toBeHidden()
|
384
|
+
@$barButton.prop('checked', true).change()
|
385
|
+
expect($target).toBeVisible()
|
386
|
+
|
387
|
+
it "shows the target element iff its up-hide-for attribute doesn't contain the selected button value", ->
|
388
|
+
$target = affix('.target[up-hide-for="something bar other"]')
|
389
|
+
up.hello(@$buttons)
|
390
|
+
expect($target).toBeVisible()
|
391
|
+
@$barButton.prop('checked', true).change()
|
392
|
+
expect($target).toBeHidden()
|
393
|
+
|
394
|
+
it "shows the target element iff it has neither up-show-for nor up-hide-for and the selected button value is present", ->
|
395
|
+
$target = affix('.target')
|
396
|
+
up.hello(@$buttons)
|
397
|
+
expect($target).toBeHidden()
|
398
|
+
@$barButton.prop('checked', true).change()
|
399
|
+
expect($target).toBeVisible()
|
400
|
+
|
401
|
+
it "shows the target element iff its up-show-for attribute contains a value ':present' and the selected button value is present", ->
|
402
|
+
$target = affix('.target[up-show-for=":present"]')
|
403
|
+
up.hello(@$buttons)
|
404
|
+
expect($target).toBeHidden()
|
405
|
+
@$blankButton.prop('checked', true).change()
|
406
|
+
expect($target).toBeHidden()
|
407
|
+
@$barButton.prop('checked', true).change()
|
408
|
+
expect($target).toBeVisible()
|
409
|
+
|
410
|
+
it "shows the target element iff its up-show-for attribute contains a value ':blank' and the selected button value is blank", ->
|
411
|
+
$target = affix('.target[up-show-for=":blank"]')
|
412
|
+
up.hello(@$buttons)
|
413
|
+
expect($target).toBeVisible()
|
414
|
+
@$blankButton.prop('checked', true).change()
|
415
|
+
expect($target).toBeVisible()
|
416
|
+
@$barButton.prop('checked', true).change()
|
417
|
+
expect($target).toBeHidden()
|
418
|
+
|
419
|
+
it "shows the target element iff its up-show-for attribute contains a value ':checked' and any button is checked", ->
|
420
|
+
$target = affix('.target[up-show-for=":checked"]')
|
421
|
+
up.hello(@$buttons)
|
422
|
+
expect($target).toBeHidden()
|
423
|
+
@$blankButton.prop('checked', true).change()
|
424
|
+
expect($target).toBeVisible()
|
425
|
+
|
426
|
+
it "shows the target element iff its up-show-for attribute contains a value ':unchecked' and no button is checked", ->
|
427
|
+
$target = affix('.target[up-show-for=":unchecked"]')
|
428
|
+
up.hello(@$buttons)
|
429
|
+
expect($target).toBeVisible()
|
430
|
+
@$blankButton.prop('checked', true).change()
|
431
|
+
expect($target).toBeHidden()
|
432
|
+
|
433
|
+
describe 'on a text input', ->
|
434
|
+
|
435
|
+
beforeEach ->
|
436
|
+
@$textInput = affix('input[type="text"][up-toggle=".target"]')
|
437
|
+
|
438
|
+
it "shows the target element iff its up-show-for attribute contains the input value", ->
|
439
|
+
$target = affix('.target[up-show-for="something bar other"]')
|
440
|
+
up.hello(@$textInput)
|
441
|
+
expect($target).toBeHidden()
|
442
|
+
@$textInput.val('bar').change()
|
443
|
+
expect($target).toBeVisible()
|
444
|
+
|
445
|
+
it "shows the target element iff its up-hide-for attribute doesn't contain the input value", ->
|
446
|
+
$target = affix('.target[up-hide-for="something bar other"]')
|
447
|
+
up.hello(@$textInput)
|
448
|
+
expect($target).toBeVisible()
|
449
|
+
@$textInput.val('bar').change()
|
450
|
+
expect($target).toBeHidden()
|
451
|
+
|
452
|
+
it "shows the target element iff it has neither up-show-for nor up-hide-for and the input value is present", ->
|
453
|
+
$target = affix('.target')
|
454
|
+
up.hello(@$textInput)
|
455
|
+
expect($target).toBeHidden()
|
456
|
+
@$textInput.val('bar').change()
|
457
|
+
expect($target).toBeVisible()
|
458
|
+
|
459
|
+
it "shows the target element iff its up-show-for attribute contains a value ':present' and the input value is present", ->
|
460
|
+
$target = affix('.target[up-show-for=":present"]')
|
461
|
+
up.hello(@$textInput)
|
462
|
+
expect($target).toBeHidden()
|
463
|
+
@$textInput.val('bar').change()
|
464
|
+
expect($target).toBeVisible()
|
465
|
+
|
466
|
+
it "shows the target element iff its up-show-for attribute contains a value ':blank' and the input value is blank", ->
|
467
|
+
$target = affix('.target[up-show-for=":blank"]')
|
468
|
+
up.hello(@$textInput)
|
469
|
+
expect($target).toBeVisible()
|
470
|
+
@$textInput.val('bar').change()
|
471
|
+
expect($target).toBeHidden()
|