unpoly-rails 0.34.2 → 0.35.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of unpoly-rails might be problematic. Click here for more details.

@@ -4,6 +4,6 @@ module Unpoly
4
4
  # The current version of the unpoly-rails gem.
5
5
  # This version number is also used for releases of the Unpoly
6
6
  # frontend code.
7
- VERSION = '0.34.2'
7
+ VERSION = '0.35.0'
8
8
  end
9
9
  end
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ..
3
3
  specs:
4
- unpoly-rails (0.34.1)
4
+ unpoly-rails (0.34.2)
5
5
  rails (>= 3)
6
6
 
7
7
  GEM
@@ -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
- wrappedMethodMatches = util.equals(request.data()['_method'], [expectedMethod], customEqualityTesters)
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
- describeCapability 'canFormData', ->
103
-
104
- it "does not explode if the original request's { data } is a FormData object", ->
105
- up.replace('.middle', '/foo', method: 'post', data: new FormData()) # POST requests are not cached
106
- expect(jasmine.Ajax.requests.count()).toEqual(1)
107
- @respond(responseHeaders: { 'X-Up-Location': '/bar', 'X-Up-Method': 'GET' })
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
- describeCapability 'canCssTransition', ->
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
- it 'morphs between the old and new element', (done) ->
909
- affix('.element').text('version 1')
910
- up.extract('.element', '<div class="element">version 2</div>', transition: 'cross-fade', duration: 200)
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
- $ghost1 = $('.element.up-ghost:contains("version 1")')
913
- expect($ghost1).toHaveLength(1)
914
- expect(u.opacity($ghost1)).toBeAround(1.0, 0.1)
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
- $ghost2 = $('.element.up-ghost:contains("version 2")')
917
- expect($ghost2).toHaveLength(1)
918
- expect(u.opacity($ghost2)).toBeAround(0.0, 0.1)
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
- u.setTimer 190, ->
921
- expect(u.opacity($ghost1)).toBeAround(0.0, 0.3)
922
- expect(u.opacity($ghost2)).toBeAround(1.0, 0.3)
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
- it 'marks the old fragment and its ghost as .up-destroying during the transition', ->
926
- affix('.element').text('version 1')
927
- up.extract('.element', '<div class="element">version 2</div>', transition: 'cross-fade', duration: 200)
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
- $version1 = $('.element:not(.up-ghost):contains("version 1")')
930
- $version1Ghost = $('.element.up-ghost:contains("version 1")')
931
- expect($version1).toHaveLength(1)
932
- expect($version1Ghost).toHaveLength(1)
933
- expect($version1).toHaveClass('up-destroying')
934
- expect($version1Ghost).toHaveClass('up-destroying')
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
- $ghost1 = $('.element.up-ghost:contains("version 1")')
948
- expect($ghost1).toHaveLength(1)
949
- expect($ghost1.css('opacity')).toBeAround(1.0, 0.1)
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
- $ghost2 = $('.element.up-ghost:contains("version 2")')
952
- expect($ghost2).toHaveLength(1)
953
- expect($ghost2.css('opacity')).toBeAround(0.0, 0.1)
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
- up.extract('.element', '<div class="element">version 3</div>', transition: 'cross-fade', duration: 200)
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
- $ghost1 = $('.element.up-ghost:contains("version 1")')
958
- expect($ghost1).toHaveLength(0)
971
+ up.extract('.element', '<div class="element">version 3</div>', transition: 'cross-fade', duration: 200)
959
972
 
960
- $ghost2 = $('.element.up-ghost:contains("version 2")')
961
- expect($ghost2).toHaveLength(1)
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
- $ghost3 = $('.element.up-ghost:contains("version 3")')
965
- expect($ghost3).toHaveLength(1)
966
- expect($ghost3.css('opacity')).toBeAround(0.0, 0.1)
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
- it 'delays the resolution of the returned promise until the transition is over', (done) ->
969
- affix('.element').text('version 1')
970
- resolution = jasmine.createSpy()
971
- promise = up.extract('.element', '<div class="element">version 2</div>', transition: 'cross-fade', duration: 30)
972
- promise.then(resolution)
973
- expect(resolution).not.toHaveBeenCalled()
974
- u.setTimer 80, ->
975
- expect(resolution).toHaveBeenCalled()
976
- done()
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
- describeFallback 'canCssTransition', ->
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
- describeCapability 'canCssTransition', ->
1264
-
1265
- it "doesn't let the discarded element appear in a transition", (done) ->
1266
- oldTextDuringTransition = undefined
1267
- newTextDuringTransition = undefined
1268
- transition = ($old, $new) ->
1269
- oldTextDuringTransition = squish($old.text())
1270
- newTextDuringTransition = squish($new.text())
1271
- u.resolvedDeferred()
1272
- $container = affix('.container')
1273
- $container.html """
1274
- <div class='foo'>old-foo</div>
1275
- <div class='bar' up-keep>old-bar</div>
1276
- """
1277
- newHtml = """
1278
- <div class='container'>
1279
- <div class='foo'>new-foo</div>
1280
- <div class='bar' up-keep>new-bar</div>
1281
- </div>
1282
- """
1283
- promise = up.extract('.container', newHtml, transition: transition)
1284
- promise.then ->
1285
- expect(oldTextDuringTransition).toEqual('old-foo old-bar')
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
- changeEvents = if up.browser.canInputEvent()
13
- # Actually we only need `input`, but we want to notice
14
- # if another script manually triggers `change` on the element.
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
- describeCapability 'canFormData', ->
346
-
347
- it 'transfers the form fields via FormData', ->
348
- up.submit(@$form)
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
- describeCapability 'canCssTransition', ->
425
-
426
- it 'morphs between the old and new target element', (done) ->
427
- affix('.target.old')
428
- $link = affix('a[href="/path"][up-target=".target"][up-transition="cross-fade"][up-duration="500"][up-easing="linear"]')
429
- Trigger.clickSequence($link)
430
- @respondWith '<div class="target new">new text</div>'
431
-
432
- $oldGhost = $('.target.old.up-ghost')
433
- $newGhost = $('.target.new.up-ghost')
434
- expect($oldGhost).toExist()
435
- expect($newGhost).toExist()
436
- expect(u.opacity($oldGhost)).toBeAround(1, 0.15)
437
- expect(u.opacity($newGhost)).toBeAround(0, 0.15)
438
- u.setTimer 250, ->
439
- expect(u.opacity($oldGhost)).toBeAround(0.5, 0.15)
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
- describeCapability 'canCssTransition', ->
105
-
106
- 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) ->
107
- openPromise = up.modal.extract('.container', '<div class="container">text</div>', animation: 'fade-in', duration: 100)
108
- $modal = $('.up-modal')
109
- $viewport = $modal.find('.up-modal-viewport')
110
- expect($modal.css('overflow-y')).toEqual('scroll')
111
- expect($viewport.css('overflow-y')).toEqual('hidden')
112
- openPromise.then ->
113
- expect($modal.css('overflow-y')).not.toEqual('scroll')
114
- expect($viewport.css('overflow-y')).toEqual('scroll')
115
- closePromise = up.modal.close(animation: 'fade-out', duration: 200)
116
- u.nextFrame ->
117
- expect($modal.css('overflow-y')).toEqual('scroll')
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
- describeCapability 'canCssTransition', ->
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
- it 'closes the current modal and wait for its close animation to finish before starting the open animation of a second modal', (done) ->
191
- up.modal.config.openAnimation = 'fade-in'
192
- up.modal.config.openDuration = 5
193
- up.modal.config.closeAnimation = 'fade-out'
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
- events = []
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
- up.modal.extract('.target', '<div class="target">response1</div>')
199
+ # First modal is starting opening animation
200
+ expect(events).toEqual ['up:modal:open']
201
+ expect($('.target')).toHaveText('response1')
202
202
 
203
- # First modal is starting opening animation
204
- expect(events).toEqual ['up:modal:open']
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
- u.setTimer 80, ->
208
- # First modal has completed opening animation
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
- expect($('.target')).toHaveText('response1')
211
+ expect($('.target')).toHaveText('response1')
216
212
 
217
- u.setTimer 20, ->
213
+ u.setTimer 20, ->
218
214
 
219
- # Second modal is still waiting for first modal's closing animaton to finish.
220
- expect(events).toEqual ['up:modal:open', 'up:modal:opened', 'up:modal:close']
221
- expect($('.target')).toHaveText('response1')
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
- u.setTimer 200, ->
219
+ u.setTimer 200, ->
224
220
 
225
- # First modal has finished closing, second modal has finished opening.
226
- expect(events).toEqual ['up:modal:open', 'up:modal:opened', 'up:modal:close', 'up:modal:closed', 'up:modal:open', 'up:modal:opened']
227
- expect($('.target')).toHaveText('response2')
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
- done()
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'