unpoly-rails 0.34.2 → 0.35.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.
Potentially problematic release.
This version of unpoly-rails might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +18 -2
- data/lib/assets/javascripts/unpoly/browser.coffee +41 -48
- data/lib/assets/javascripts/unpoly/bus.coffee +2 -1
- data/lib/assets/javascripts/unpoly/dom.coffee +2 -1
- data/lib/assets/javascripts/unpoly/form.coffee +4 -18
- data/lib/assets/javascripts/unpoly/motion.coffee +2 -4
- data/lib/assets/javascripts/unpoly/protocol.coffee +15 -8
- data/lib/assets/javascripts/unpoly/util.coffee +21 -40
- data/lib/unpoly/rails/version.rb +1 -1
- data/spec_app/Gemfile.lock +1 -1
- data/spec_app/spec/javascripts/helpers/to_have_request_method.js.coffee +9 -1
- data/spec_app/spec/javascripts/up/dom_spec.js.coffee +109 -92
- data/spec_app/spec/javascripts/up/form_spec.js.coffee +7 -23
- data/spec_app/spec/javascripts/up/link_spec.js.coffee +16 -18
- data/spec_app/spec/javascripts/up/modal_spec.js.coffee +42 -46
- data/spec_app/spec/javascripts/up/motion_spec.js.coffee +249 -295
- data/spec_app/spec/javascripts/up/popup_spec.js.coffee +27 -29
- data/spec_app/spec/javascripts/up/proxy_spec.js.coffee +3 -5
- data/spec_app/spec/javascripts/up/util_spec.js.coffee +1 -0
- metadata +2 -2
data/lib/unpoly/rails/version.rb
CHANGED
data/spec_app/Gemfile.lock
CHANGED
@@ -1,7 +1,15 @@
|
|
1
|
+
u = up.util
|
2
|
+
|
1
3
|
beforeEach ->
|
2
4
|
jasmine.addMatchers
|
3
5
|
toHaveRequestMethod: (util, customEqualityTesters) ->
|
4
6
|
compare: (request, expectedMethod) ->
|
5
7
|
realMethodMatches = (request.method == expectedMethod)
|
6
|
-
|
8
|
+
formData = request.data()
|
9
|
+
if u.isFormData(formData)
|
10
|
+
wrappedMethod = formData.get('_method')
|
11
|
+
wrappedMethodMatches = (wrappedMethod == expectedMethod)
|
12
|
+
else
|
13
|
+
wrappedMethod = formData['_method']
|
14
|
+
wrappedMethodMatches = util.equals(wrappedMethod, [expectedMethod], customEqualityTesters)
|
7
15
|
pass: realMethodMatches || wrappedMethodMatches
|
@@ -99,14 +99,12 @@ describe 'up.dom', ->
|
|
99
99
|
up.replace('.middle', '/bar')
|
100
100
|
expect(jasmine.Ajax.requests.count()).toEqual(2)
|
101
101
|
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
secondReplace = -> up.replace('.middle', '/bar')
|
109
|
-
expect(secondReplace).not.toThrowError()
|
102
|
+
it "does not explode if the original request's { data } is a FormData object", ->
|
103
|
+
up.replace('.middle', '/foo', method: 'post', data: new FormData()) # POST requests are not cached
|
104
|
+
expect(jasmine.Ajax.requests.count()).toEqual(1)
|
105
|
+
@respond(responseHeaders: { 'X-Up-Location': '/bar', 'X-Up-Method': 'GET' })
|
106
|
+
secondReplace = -> up.replace('.middle', '/bar')
|
107
|
+
expect(secondReplace).not.toThrowError()
|
110
108
|
|
111
109
|
describe 'with { data } option', ->
|
112
110
|
|
@@ -331,6 +329,26 @@ describe 'up.dom', ->
|
|
331
329
|
expect($('.container')).toHaveText('new container text')
|
332
330
|
expect(document.title).toBe('Title from HTML')
|
333
331
|
|
332
|
+
it 'does not update the document title if the response has a <title> tag inside an inline SVG image (bugfix)', ->
|
333
|
+
affix('.container').text('old container text')
|
334
|
+
document.title = 'old document title'
|
335
|
+
up.replace('.container', '/path', history: false, title: true)
|
336
|
+
|
337
|
+
@respondWith """
|
338
|
+
<svg width="500" height="300" xmlns="http://www.w3.org/2000/svg">
|
339
|
+
<g>
|
340
|
+
<title>SVG Title Demo example</title>
|
341
|
+
<rect x="10" y="10" width="200" height="50" style="fill:none; stroke:blue; stroke-width:1px"/>
|
342
|
+
</g>
|
343
|
+
</svg>
|
344
|
+
|
345
|
+
<div class='container'>
|
346
|
+
new container text
|
347
|
+
</div>
|
348
|
+
"""
|
349
|
+
expect($('.container')).toHaveText('new container text')
|
350
|
+
expect(document.title).toBe('old document title')
|
351
|
+
|
334
352
|
it "does not extract the title from the response or HTTP header if history isn't updated", ->
|
335
353
|
affix('.container').text('old container text')
|
336
354
|
document.title = 'old document title'
|
@@ -903,79 +921,80 @@ describe 'up.dom', ->
|
|
903
921
|
|
904
922
|
describe 'with { transition } option', ->
|
905
923
|
|
906
|
-
|
924
|
+
it 'morphs between the old and new element', (done) ->
|
925
|
+
affix('.element').text('version 1')
|
926
|
+
up.extract('.element', '<div class="element">version 2</div>', transition: 'cross-fade', duration: 200)
|
907
927
|
|
908
|
-
|
909
|
-
|
910
|
-
|
928
|
+
$ghost1 = $('.element.up-ghost:contains("version 1")')
|
929
|
+
expect($ghost1).toHaveLength(1)
|
930
|
+
expect(u.opacity($ghost1)).toBeAround(1.0, 0.1)
|
911
931
|
|
912
|
-
|
913
|
-
|
914
|
-
|
932
|
+
$ghost2 = $('.element.up-ghost:contains("version 2")')
|
933
|
+
expect($ghost2).toHaveLength(1)
|
934
|
+
expect(u.opacity($ghost2)).toBeAround(0.0, 0.1)
|
915
935
|
|
916
|
-
|
917
|
-
expect($
|
918
|
-
expect(u.opacity($ghost2)).toBeAround(
|
936
|
+
u.setTimer 190, ->
|
937
|
+
expect(u.opacity($ghost1)).toBeAround(0.0, 0.3)
|
938
|
+
expect(u.opacity($ghost2)).toBeAround(1.0, 0.3)
|
939
|
+
done()
|
919
940
|
|
920
|
-
|
921
|
-
|
922
|
-
|
923
|
-
done()
|
941
|
+
it 'marks the old fragment and its ghost as .up-destroying during the transition', ->
|
942
|
+
affix('.element').text('version 1')
|
943
|
+
up.extract('.element', '<div class="element">version 2</div>', transition: 'cross-fade', duration: 200)
|
924
944
|
|
925
|
-
|
926
|
-
|
927
|
-
|
945
|
+
$version1 = $('.element:not(.up-ghost):contains("version 1")')
|
946
|
+
$version1Ghost = $('.element.up-ghost:contains("version 1")')
|
947
|
+
expect($version1).toHaveLength(1)
|
948
|
+
expect($version1Ghost).toHaveLength(1)
|
949
|
+
expect($version1).toHaveClass('up-destroying')
|
950
|
+
expect($version1Ghost).toHaveClass('up-destroying')
|
928
951
|
|
929
|
-
|
930
|
-
|
931
|
-
|
932
|
-
|
933
|
-
|
934
|
-
|
935
|
-
|
936
|
-
$version2 = $('.element:not(.up-ghost):contains("version 2")')
|
937
|
-
$version2Ghost = $('.element.up-ghost:contains("version 2")')
|
938
|
-
expect($version2).toHaveLength(1)
|
939
|
-
expect($version2Ghost).toHaveLength(1)
|
940
|
-
expect($version2).not.toHaveClass('up-destroying')
|
941
|
-
expect($version2Ghost).not.toHaveClass('up-destroying')
|
942
|
-
|
943
|
-
it 'cancels an existing transition by instantly jumping to the last frame', ->
|
944
|
-
affix('.element').text('version 1')
|
945
|
-
up.extract('.element', '<div class="element">version 2</div>', transition: 'cross-fade', duration: 200)
|
952
|
+
$version2 = $('.element:not(.up-ghost):contains("version 2")')
|
953
|
+
$version2Ghost = $('.element.up-ghost:contains("version 2")')
|
954
|
+
expect($version2).toHaveLength(1)
|
955
|
+
expect($version2Ghost).toHaveLength(1)
|
956
|
+
expect($version2).not.toHaveClass('up-destroying')
|
957
|
+
expect($version2Ghost).not.toHaveClass('up-destroying')
|
946
958
|
|
947
|
-
|
948
|
-
|
949
|
-
|
959
|
+
it 'cancels an existing transition by instantly jumping to the last frame', ->
|
960
|
+
affix('.element').text('version 1')
|
961
|
+
up.extract('.element', '<div class="element">version 2</div>', transition: 'cross-fade', duration: 200)
|
950
962
|
|
951
|
-
|
952
|
-
|
953
|
-
|
963
|
+
$ghost1 = $('.element.up-ghost:contains("version 1")')
|
964
|
+
expect($ghost1).toHaveLength(1)
|
965
|
+
expect($ghost1.css('opacity')).toBeAround(1.0, 0.1)
|
954
966
|
|
955
|
-
|
967
|
+
$ghost2 = $('.element.up-ghost:contains("version 2")')
|
968
|
+
expect($ghost2).toHaveLength(1)
|
969
|
+
expect($ghost2.css('opacity')).toBeAround(0.0, 0.1)
|
956
970
|
|
957
|
-
|
958
|
-
expect($ghost1).toHaveLength(0)
|
971
|
+
up.extract('.element', '<div class="element">version 3</div>', transition: 'cross-fade', duration: 200)
|
959
972
|
|
960
|
-
|
961
|
-
|
962
|
-
expect($ghost2.css('opacity')).toBeAround(1.0, 0.1)
|
973
|
+
$ghost1 = $('.element.up-ghost:contains("version 1")')
|
974
|
+
expect($ghost1).toHaveLength(0)
|
963
975
|
|
964
|
-
|
965
|
-
|
966
|
-
|
976
|
+
$ghost2 = $('.element.up-ghost:contains("version 2")')
|
977
|
+
expect($ghost2).toHaveLength(1)
|
978
|
+
expect($ghost2.css('opacity')).toBeAround(1.0, 0.1)
|
967
979
|
|
968
|
-
|
969
|
-
|
970
|
-
|
971
|
-
|
972
|
-
|
973
|
-
|
974
|
-
|
975
|
-
|
976
|
-
|
980
|
+
$ghost3 = $('.element.up-ghost:contains("version 3")')
|
981
|
+
expect($ghost3).toHaveLength(1)
|
982
|
+
expect($ghost3.css('opacity')).toBeAround(0.0, 0.1)
|
983
|
+
|
984
|
+
it 'delays the resolution of the returned promise until the transition is over', (done) ->
|
985
|
+
affix('.element').text('version 1')
|
986
|
+
resolution = jasmine.createSpy()
|
987
|
+
promise = up.extract('.element', '<div class="element">version 2</div>', transition: 'cross-fade', duration: 30)
|
988
|
+
promise.then(resolution)
|
989
|
+
expect(resolution).not.toHaveBeenCalled()
|
990
|
+
u.setTimer 80, ->
|
991
|
+
expect(resolution).toHaveBeenCalled()
|
992
|
+
done()
|
993
|
+
|
994
|
+
describe 'when animation is disabled', ->
|
977
995
|
|
978
|
-
|
996
|
+
beforeEach ->
|
997
|
+
up.motion.config.enabled = false
|
979
998
|
|
980
999
|
it 'immediately swaps the old and new elements', ->
|
981
1000
|
affix('.element').text('version 1')
|
@@ -1260,31 +1279,29 @@ describe 'up.dom', ->
|
|
1260
1279
|
expect(keptListener).toHaveBeenCalledWith($keeper, { key: 'value1' })
|
1261
1280
|
expect(keptListener).toHaveBeenCalledWith($keeper, { key: 'value2' })
|
1262
1281
|
|
1263
|
-
|
1264
|
-
|
1265
|
-
|
1266
|
-
|
1267
|
-
|
1268
|
-
|
1269
|
-
|
1270
|
-
|
1271
|
-
|
1272
|
-
|
1273
|
-
|
1274
|
-
|
1275
|
-
|
1276
|
-
|
1277
|
-
|
1278
|
-
<div class='
|
1279
|
-
|
1280
|
-
|
1281
|
-
|
1282
|
-
|
1283
|
-
|
1284
|
-
|
1285
|
-
|
1286
|
-
expect(newTextDuringTransition).toEqual('new-foo old-bar')
|
1287
|
-
done()
|
1282
|
+
it "doesn't let the discarded element appear in a transition", (done) ->
|
1283
|
+
oldTextDuringTransition = undefined
|
1284
|
+
newTextDuringTransition = undefined
|
1285
|
+
transition = ($old, $new) ->
|
1286
|
+
oldTextDuringTransition = squish($old.text())
|
1287
|
+
newTextDuringTransition = squish($new.text())
|
1288
|
+
u.resolvedDeferred()
|
1289
|
+
$container = affix('.container')
|
1290
|
+
$container.html """
|
1291
|
+
<div class='foo'>old-foo</div>
|
1292
|
+
<div class='bar' up-keep>old-bar</div>
|
1293
|
+
"""
|
1294
|
+
newHtml = """
|
1295
|
+
<div class='container'>
|
1296
|
+
<div class='foo'>new-foo</div>
|
1297
|
+
<div class='bar' up-keep>new-bar</div>
|
1298
|
+
</div>
|
1299
|
+
"""
|
1300
|
+
promise = up.extract('.container', newHtml, transition: transition)
|
1301
|
+
promise.then ->
|
1302
|
+
expect(oldTextDuringTransition).toEqual('old-foo old-bar')
|
1303
|
+
expect(newTextDuringTransition).toEqual('new-foo old-bar')
|
1304
|
+
done()
|
1288
1305
|
|
1289
1306
|
describe 'up.destroy', ->
|
1290
1307
|
|
@@ -9,15 +9,9 @@ describe 'up.form', ->
|
|
9
9
|
beforeEach ->
|
10
10
|
up.form.config.observeDelay = 0
|
11
11
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
['input', 'change']
|
16
|
-
else
|
17
|
-
# Actually we won't ever get `input` from the user in this browser,
|
18
|
-
# but we want to notice if another script manually triggers `input`
|
19
|
-
# on the element.
|
20
|
-
['input', 'change', 'keypress', 'paste', 'cut', 'click', 'propertychange']
|
12
|
+
# Actually we only need `input`, but we want to notice
|
13
|
+
# if another script manually triggers `change` on the element.
|
14
|
+
changeEvents = ['input', 'change']
|
21
15
|
|
22
16
|
describe 'when the first argument is a form field', ->
|
23
17
|
|
@@ -342,20 +336,10 @@ describe 'up.form', ->
|
|
342
336
|
beforeEach ->
|
343
337
|
@$form.affix('input[name="file-field"][type="file"]')
|
344
338
|
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
data = @lastRequest().data()
|
350
|
-
expect(u.isFormData(data)).toBe(true)
|
351
|
-
|
352
|
-
describeFallback 'canFormData', ->
|
353
|
-
|
354
|
-
it 'falls back to a vanilla form submission', ->
|
355
|
-
form = @$form.get(0)
|
356
|
-
spyOn(form, 'submit')
|
357
|
-
up.submit(@$form)
|
358
|
-
expect(form.submit).toHaveBeenCalled()
|
339
|
+
it 'transfers the form fields via FormData', ->
|
340
|
+
up.submit(@$form)
|
341
|
+
data = @lastRequest().data()
|
342
|
+
expect(u.isFormData(data)).toBe(true)
|
359
343
|
|
360
344
|
describeFallback 'canPushState', ->
|
361
345
|
|
@@ -421,24 +421,22 @@ describe 'up.link', ->
|
|
421
421
|
|
422
422
|
describe 'with [up-transition] modifier', ->
|
423
423
|
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
expect(u.opacity($
|
438
|
-
u.
|
439
|
-
|
440
|
-
expect(u.opacity($newGhost)).toBeAround(0.5, 0.15)
|
441
|
-
done()
|
424
|
+
it 'morphs between the old and new target element', (done) ->
|
425
|
+
affix('.target.old')
|
426
|
+
$link = affix('a[href="/path"][up-target=".target"][up-transition="cross-fade"][up-duration="500"][up-easing="linear"]')
|
427
|
+
Trigger.clickSequence($link)
|
428
|
+
@respondWith '<div class="target new">new text</div>'
|
429
|
+
|
430
|
+
$oldGhost = $('.target.old.up-ghost')
|
431
|
+
$newGhost = $('.target.new.up-ghost')
|
432
|
+
expect($oldGhost).toExist()
|
433
|
+
expect($newGhost).toExist()
|
434
|
+
expect(u.opacity($oldGhost)).toBeAround(1, 0.15)
|
435
|
+
expect(u.opacity($newGhost)).toBeAround(0, 0.15)
|
436
|
+
u.setTimer 250, ->
|
437
|
+
expect(u.opacity($oldGhost)).toBeAround(0.5, 0.15)
|
438
|
+
expect(u.opacity($newGhost)).toBeAround(0.5, 0.15)
|
439
|
+
done()
|
442
440
|
|
443
441
|
describe 'wih a CSS selector in the [up-fallback] attribute', ->
|
444
442
|
|
@@ -101,22 +101,20 @@ describe 'up.modal', ->
|
|
101
101
|
expect(parseInt($body.css('padding-right'))).toBe(0)
|
102
102
|
done()
|
103
103
|
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
expect($
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
expect($viewport.css('overflow-y')).toEqual('hidden')
|
119
|
-
done()
|
104
|
+
it "gives the scrollbar to .up-modal instead of .up-modal-viewport while animating, so we don't see scaled scrollbars in a zoom-in animation", (done) ->
|
105
|
+
openPromise = up.modal.extract('.container', '<div class="container">text</div>', animation: 'fade-in', duration: 100)
|
106
|
+
$modal = $('.up-modal')
|
107
|
+
$viewport = $modal.find('.up-modal-viewport')
|
108
|
+
expect($modal.css('overflow-y')).toEqual('scroll')
|
109
|
+
expect($viewport.css('overflow-y')).toEqual('hidden')
|
110
|
+
openPromise.then ->
|
111
|
+
expect($modal.css('overflow-y')).not.toEqual('scroll')
|
112
|
+
expect($viewport.css('overflow-y')).toEqual('scroll')
|
113
|
+
closePromise = up.modal.close(animation: 'fade-out', duration: 200)
|
114
|
+
u.nextFrame ->
|
115
|
+
expect($modal.css('overflow-y')).toEqual('scroll')
|
116
|
+
expect($viewport.css('overflow-y')).toEqual('hidden')
|
117
|
+
done()
|
120
118
|
|
121
119
|
it 'does not add right padding to the body if the body has overflow-y: hidden', (done) ->
|
122
120
|
restoreBody = u.temporaryCss($('body'), 'overflow-y': 'hidden')
|
@@ -185,48 +183,46 @@ describe 'up.modal', ->
|
|
185
183
|
expect(bodyPadding).not.toBeAround(2 * assumedScrollbarWidth, 2 * 5)
|
186
184
|
done()
|
187
185
|
|
188
|
-
|
186
|
+
it 'closes the current modal and wait for its close animation to finish before starting the open animation of a second modal', (done) ->
|
187
|
+
up.modal.config.openAnimation = 'fade-in'
|
188
|
+
up.modal.config.openDuration = 5
|
189
|
+
up.modal.config.closeAnimation = 'fade-out'
|
190
|
+
up.modal.config.closeDuration = 60
|
189
191
|
|
190
|
-
|
191
|
-
|
192
|
-
up.
|
193
|
-
|
194
|
-
up.modal.config.closeDuration = 60
|
192
|
+
events = []
|
193
|
+
u.each ['up:modal:open', 'up:modal:opened', 'up:modal:close', 'up:modal:closed'], (event) ->
|
194
|
+
up.on event, ->
|
195
|
+
events.push(event)
|
195
196
|
|
196
|
-
|
197
|
-
u.each ['up:modal:open', 'up:modal:opened', 'up:modal:close', 'up:modal:closed'], (event) ->
|
198
|
-
up.on event, ->
|
199
|
-
events.push(event)
|
197
|
+
up.modal.extract('.target', '<div class="target">response1</div>')
|
200
198
|
|
201
|
-
|
199
|
+
# First modal is starting opening animation
|
200
|
+
expect(events).toEqual ['up:modal:open']
|
201
|
+
expect($('.target')).toHaveText('response1')
|
202
202
|
|
203
|
-
|
204
|
-
|
203
|
+
u.setTimer 80, ->
|
204
|
+
# First modal has completed opening animation
|
205
|
+
expect(events).toEqual ['up:modal:open', 'up:modal:opened']
|
205
206
|
expect($('.target')).toHaveText('response1')
|
206
207
|
|
207
|
-
|
208
|
-
|
209
|
-
expect(events).toEqual ['up:modal:open', 'up:modal:opened']
|
210
|
-
expect($('.target')).toHaveText('response1')
|
211
|
-
|
212
|
-
# We open another modal, which will cause the first modal to start closing
|
213
|
-
up.modal.extract('.target', '<div class="target">response2</div>')
|
208
|
+
# We open another modal, which will cause the first modal to start closing
|
209
|
+
up.modal.extract('.target', '<div class="target">response2</div>')
|
214
210
|
|
215
|
-
|
211
|
+
expect($('.target')).toHaveText('response1')
|
216
212
|
|
217
|
-
|
213
|
+
u.setTimer 20, ->
|
218
214
|
|
219
|
-
|
220
|
-
|
221
|
-
|
215
|
+
# Second modal is still waiting for first modal's closing animaton to finish.
|
216
|
+
expect(events).toEqual ['up:modal:open', 'up:modal:opened', 'up:modal:close']
|
217
|
+
expect($('.target')).toHaveText('response1')
|
222
218
|
|
223
|
-
|
219
|
+
u.setTimer 200, ->
|
224
220
|
|
225
|
-
|
226
|
-
|
227
|
-
|
221
|
+
# First modal has finished closing, second modal has finished opening.
|
222
|
+
expect(events).toEqual ['up:modal:open', 'up:modal:opened', 'up:modal:close', 'up:modal:closed', 'up:modal:open', 'up:modal:opened']
|
223
|
+
expect($('.target')).toHaveText('response2')
|
228
224
|
|
229
|
-
|
225
|
+
done()
|
230
226
|
|
231
227
|
it 'closes an opening modal if a second modal starts opening before the first modal has finished its open animation', (done) ->
|
232
228
|
up.modal.config.openAnimation = 'fade-in'
|