unpoly-rails 0.56.7 → 0.57.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.

Files changed (67) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +74 -1
  3. data/dist/unpoly.js +1569 -793
  4. data/dist/unpoly.min.js +4 -4
  5. data/lib/assets/javascripts/unpoly.coffee +2 -0
  6. data/lib/assets/javascripts/unpoly/browser.coffee.erb +25 -41
  7. data/lib/assets/javascripts/unpoly/bus.coffee.erb +20 -6
  8. data/lib/assets/javascripts/unpoly/classes/cache.coffee +23 -13
  9. data/lib/assets/javascripts/unpoly/classes/compile_pass.coffee +87 -0
  10. data/lib/assets/javascripts/unpoly/classes/focus_tracker.coffee +29 -0
  11. data/lib/assets/javascripts/unpoly/classes/follow_variant.coffee +7 -4
  12. data/lib/assets/javascripts/unpoly/classes/record.coffee +1 -1
  13. data/lib/assets/javascripts/unpoly/classes/request.coffee +38 -45
  14. data/lib/assets/javascripts/unpoly/classes/response.coffee +16 -1
  15. data/lib/assets/javascripts/unpoly/classes/store/memory.coffee +26 -0
  16. data/lib/assets/javascripts/unpoly/classes/store/session.coffee +59 -0
  17. data/lib/assets/javascripts/unpoly/cookie.coffee +56 -0
  18. data/lib/assets/javascripts/unpoly/dom.coffee.erb +67 -39
  19. data/lib/assets/javascripts/unpoly/feedback.coffee +2 -2
  20. data/lib/assets/javascripts/unpoly/form.coffee.erb +23 -12
  21. data/lib/assets/javascripts/unpoly/history.coffee +2 -2
  22. data/lib/assets/javascripts/unpoly/layout.coffee.erb +118 -99
  23. data/lib/assets/javascripts/unpoly/link.coffee.erb +12 -5
  24. data/lib/assets/javascripts/unpoly/log.coffee +6 -5
  25. data/lib/assets/javascripts/unpoly/modal.coffee.erb +9 -2
  26. data/lib/assets/javascripts/unpoly/motion.coffee.erb +2 -6
  27. data/lib/assets/javascripts/unpoly/namespace.coffee.erb +2 -2
  28. data/lib/assets/javascripts/unpoly/params.coffee.erb +522 -0
  29. data/lib/assets/javascripts/unpoly/popup.coffee.erb +3 -3
  30. data/lib/assets/javascripts/unpoly/proxy.coffee +42 -34
  31. data/lib/assets/javascripts/unpoly/{syntax.coffee → syntax.coffee.erb} +59 -117
  32. data/lib/assets/javascripts/unpoly/{util.coffee → util.coffee.erb} +206 -171
  33. data/lib/unpoly/rails/version.rb +1 -1
  34. data/package.json +1 -1
  35. data/spec_app/Gemfile.lock +1 -1
  36. data/spec_app/app/assets/javascripts/integration_test.coffee +0 -4
  37. data/spec_app/app/assets/stylesheets/integration_test.sass +7 -1
  38. data/spec_app/app/controllers/pages_controller.rb +4 -0
  39. data/spec_app/app/views/form_test/basics/new.erb +34 -5
  40. data/spec_app/app/views/form_test/submission_result.erb +2 -2
  41. data/spec_app/app/views/form_test/uploads/new.erb +15 -2
  42. data/spec_app/app/views/hash_test/unpoly.erb +30 -0
  43. data/spec_app/app/views/pages/start.erb +2 -1
  44. data/spec_app/spec/javascripts/helpers/parse_form_data.js.coffee +17 -2
  45. data/spec_app/spec/javascripts/helpers/reset_up.js.coffee +5 -0
  46. data/spec_app/spec/javascripts/helpers/to_be_error.coffee +1 -1
  47. data/spec_app/spec/javascripts/helpers/to_match_selector.coffee +5 -0
  48. data/spec_app/spec/javascripts/up/browser_spec.js.coffee +8 -8
  49. data/spec_app/spec/javascripts/up/bus_spec.js.coffee +58 -20
  50. data/spec_app/spec/javascripts/up/classes/cache_spec.js.coffee +78 -0
  51. data/spec_app/spec/javascripts/up/classes/focus_tracker_spec.coffee +31 -0
  52. data/spec_app/spec/javascripts/up/classes/request_spec.coffee +50 -0
  53. data/spec_app/spec/javascripts/up/classes/store/memory_spec.js.coffee +67 -0
  54. data/spec_app/spec/javascripts/up/classes/store/session_spec.js.coffee +113 -0
  55. data/spec_app/spec/javascripts/up/dom_spec.js.coffee +133 -45
  56. data/spec_app/spec/javascripts/up/form_spec.js.coffee +13 -13
  57. data/spec_app/spec/javascripts/up/layout_spec.js.coffee +110 -26
  58. data/spec_app/spec/javascripts/up/link_spec.js.coffee +1 -1
  59. data/spec_app/spec/javascripts/up/modal_spec.js.coffee +1 -0
  60. data/spec_app/spec/javascripts/up/motion_spec.js.coffee +52 -51
  61. data/spec_app/spec/javascripts/up/namespace_spec.js.coffee +2 -2
  62. data/spec_app/spec/javascripts/up/params_spec.coffee +768 -0
  63. data/spec_app/spec/javascripts/up/proxy_spec.js.coffee +75 -36
  64. data/spec_app/spec/javascripts/up/syntax_spec.js.coffee +48 -15
  65. data/spec_app/spec/javascripts/up/util_spec.js.coffee +148 -131
  66. metadata +17 -5
  67. data/spec_app/spec/javascripts/up/classes/.keep +0 -0
@@ -40,33 +40,33 @@ describe 'up.form', ->
40
40
  it 'debounces the callback when the { delay } option is given', asyncSpec (next) ->
41
41
  $input = affix('input[value="old-value"]')
42
42
  callback = jasmine.createSpy('change callback')
43
- up.observe($input, { delay: 100 }, callback)
43
+ up.observe($input, { delay: 200 }, callback)
44
44
  $input.val('new-value-1')
45
45
  $input.trigger(eventName)
46
46
 
47
- next.after 50, ->
48
- # 50 ms after change 1: We're still waiting for the 100ms delay to expire
47
+ next.after 100, ->
48
+ # 100 ms after change 1: We're still waiting for the 200ms delay to expire
49
49
  expect(callback.calls.count()).toEqual(0)
50
50
 
51
- next.after 100, ->
52
- # 150 ms after change 1: The 100ms delay has expired
51
+ next.after 200, ->
52
+ # 300 ms after change 1: The 200ms delay has expired
53
53
  expect(callback.calls.count()).toEqual(1)
54
54
  expect(callback.calls.mostRecent().args[0]).toEqual('new-value-1')
55
55
  $input.val('new-value-2')
56
56
  $input.trigger(eventName)
57
57
 
58
- next.after 40, ->
59
- # 40 ms after change 2: We change again, resetting the delay
58
+ next.after 80, ->
59
+ # 80 ms after change 2: We change again, resetting the delay
60
60
  expect(callback.calls.count()).toEqual(1)
61
61
  $input.val('new-value-3')
62
62
  $input.trigger(eventName)
63
63
 
64
- next.after 85, ->
65
- # 125 ms after change 2, which was superseded by change 3
66
- # 85 ms after change 3
64
+ next.after 170, ->
65
+ # 250 ms after change 2, which was superseded by change 3
66
+ # 170 ms after change 3
67
67
  expect(callback.calls.count()).toEqual(1)
68
68
 
69
- next.after 65, ->
69
+ next.after 130, ->
70
70
  # 190 ms after change 2, which was superseded by change 3
71
71
  # 150 ms after change 3
72
72
  expect(callback.calls.count()).toEqual(2)
@@ -561,8 +561,8 @@ describe 'up.form', ->
561
561
  it 'transfers the form fields via FormData', asyncSpec (next) ->
562
562
  up.submit(@$form)
563
563
  next =>
564
- data = @lastRequest().data()
565
- expect(u.isFormData(data)).toBe(true)
564
+ rawData = @lastRequest().params
565
+ expect(u.isFormData(rawData)).toBe(true)
566
566
 
567
567
  describeFallback 'canPushState', ->
568
568
 
@@ -17,17 +17,27 @@ describe 'up.layout', ->
17
17
 
18
18
  @$elements = []
19
19
  @$container = $('<div class="container">').prependTo($body)
20
+ @$container.css(opacity: 0.2) # reduce flashing during test runs
20
21
 
21
22
  @clientHeight = u.clientSize().height
22
23
 
23
- for height in [@clientHeight, '50px', '5000px']
24
- $element = $('<div>').css(height: height)
24
+ elementPlans = [
25
+ { height: @clientHeight, backgroundColor: 'yellow' }, # [0]
26
+ { height: '50px', backgroundColor: 'cyan' }, # [1]
27
+ { height: '5000px', backgroundColor: 'orange' } # [2]
28
+ ]
29
+
30
+ for elementPlan in elementPlans
31
+ $element = $('<div>').css(elementPlan)
25
32
  $element.appendTo(@$container)
26
33
  @$elements.push($element)
27
34
 
28
35
  afterEach ->
29
36
  @$container.remove()
30
37
 
38
+ $documentViewport = ->
39
+ $(up.browser.documentViewportSelector())
40
+
31
41
  it 'reveals the given element', asyncSpec (next) ->
32
42
  up.reveal(@$elements[0])
33
43
 
@@ -37,7 +47,7 @@ describe 'up.layout', ->
37
47
  # ---------------------
38
48
  # [1] ch+0 ...... ch+49
39
49
  # [2] ch+50 ... ch+5049
40
- expect($(document).scrollTop()).toBe(0)
50
+ expect($documentViewport().scrollTop()).toBe(0)
41
51
 
42
52
  up.reveal(@$elements[1])
43
53
 
@@ -47,7 +57,7 @@ describe 'up.layout', ->
47
57
  # [1] ch+0 ...... ch+49
48
58
  # ---------------------
49
59
  # [2] ch+50 ... ch+5049
50
- expect($(document).scrollTop()).toBe(50)
60
+ expect($documentViewport().scrollTop()).toBe(50)
51
61
 
52
62
  up.reveal(@$elements[2])
53
63
 
@@ -57,7 +67,7 @@ describe 'up.layout', ->
57
67
  # ---------------------
58
68
  # [2] ch+50 ... ch+5049
59
69
  # ---------------------
60
- expect($(document).scrollTop()).toBe(@clientHeight + 50)
70
+ expect($documentViewport().scrollTop()).toBe(@clientHeight + 50)
61
71
 
62
72
  it "includes the element's top margin in the revealed area", asyncSpec (next) ->
63
73
  @$elements[1].css('margin-top': '20px')
@@ -163,6 +173,36 @@ describe 'up.layout', ->
163
173
  # ----------------
164
174
  expect($(document).scrollTop()).toBe(@clientHeight + 50 - 100 - 50)
165
175
 
176
+ it 'scrolls far enough so the element is not obstructed by an element fixed to the top with margin, padding, border and non-zero top properties', asyncSpec (next) ->
177
+ $topNav = affix('[up-fixed=top]').css(
178
+ position: 'fixed',
179
+ top: '29px',
180
+ margin: '16px',
181
+ border: '7px solid rgba(0, 0, 0, 0.1)',
182
+ padding: '5px'
183
+ left: '0',
184
+ right: '0'
185
+ height: '100px'
186
+ )
187
+
188
+ up.reveal(@$elements[2], viewport: @viewport)
189
+
190
+ next =>
191
+ # [0] 00000 ...... ch-1 [F] 0 ...... 99+props
192
+ # [1] ch+0 ...... ch+49
193
+ # --------------------- ---------------------
194
+ # [2] ch+50 ... ch+5049
195
+ # ---------------------
196
+
197
+ expect($(document).scrollTop()).toBe(
198
+ @clientHeight + # scroll past @$elements[0]
199
+ 50 - # scroll past @$elements[1]
200
+ 100 - # obstruction height
201
+ 29 - # obstruction's top property
202
+ (1 * 16) - # top margin (bottom margin is not a visual obstruction)
203
+ (2 * 7) - # obstruction top and bottom borders
204
+ (2 * 5) # obstruction top and bottom paddings
205
+ )
166
206
 
167
207
  it 'scrolls far enough so the element is not obstructed by an element fixed to the bottom', asyncSpec (next) ->
168
208
  $bottomNav = affix('[up-fixed=bottom]').css(
@@ -206,6 +246,36 @@ describe 'up.layout', ->
206
246
  # [F] 0 ............ 99
207
247
  expect($(document).scrollTop()).toBe(@clientHeight + 50)
208
248
 
249
+ it 'scrolls far enough so the element is not obstructed by an element fixed to the bottom with margin, padding, border and non-zero bottom properties', asyncSpec (next) ->
250
+ $bottomNav = affix('[up-fixed=bottom]').css(
251
+ position: 'fixed',
252
+ bottom: '29px',
253
+ margin: '16px',
254
+ border: '7px solid rgba(0, 0, 0, 0.2)',
255
+ padding: '5px',
256
+ left: '0',
257
+ right: '0'
258
+ height: '100px'
259
+ )
260
+
261
+ up.reveal(@$elements[1])
262
+
263
+ next =>
264
+ # ---------------------
265
+ # [0] 0 .......... ch-1
266
+ # [1] ch+0 ...... ch+49
267
+ # ---------------------
268
+ # [2] ch+50 ... ch+5049
269
+ # [F] 0 ...... 99+props
270
+ expect($(document).scrollTop()).toBe(
271
+ 50 + # height of elements[1]
272
+ 100 + # obstruction height
273
+ 29 + # obstruction's bottom property
274
+ (1 * 16) + # bottom margin (top margin is not a visual obstruction)
275
+ (2 * 7) + # obstruction top and bottom borders
276
+ (2 * 5) # obstruction top and bottom paddings
277
+ )
278
+
209
279
  it 'does not crash when called with a CSS selector (bugfix)', (done) ->
210
280
  promise = up.reveal('.container')
211
281
  promiseState(promise).then (result) ->
@@ -364,35 +434,31 @@ describe 'up.layout', ->
364
434
  # Viewing 100 to 199
365
435
  expect($viewport.scrollTop()).toBe(100)
366
436
 
367
- describe 'revealHash', ->
437
+ describe 'up.layout.revealHash', ->
368
438
 
369
- it 'reveals an element with an ID matching the hash in the location', asyncSpec (next) ->
439
+ it 'reveals an element with an ID matching the given #hash', asyncSpec (next) ->
370
440
  revealSpy = up.layout.knife.mock('reveal')
371
441
  $match = affix('div#hash')
372
- location.hash = '#hash'
373
- up.layout.revealHash()
374
- next => expect(revealSpy).toHaveBeenCalledWith($match)
442
+ up.layout.revealHash('#hash')
443
+ next => expect(revealSpy).toHaveBeenCalledWith($match, top: true)
375
444
 
376
- it 'reveals a named anchor matching the hash in the location', asyncSpec (next) ->
445
+ it 'reveals a named anchor matching the given #hash', asyncSpec (next) ->
377
446
  revealSpy = up.layout.knife.mock('reveal')
378
447
  $match = affix('a[name="hash"]')
379
- location.hash = '#hash'
380
- up.layout.revealHash()
381
- next => expect(revealSpy).toHaveBeenCalledWith($match)
448
+ up.layout.revealHash('#hash')
449
+ next => expect(revealSpy).toHaveBeenCalledWith($match, top: true)
382
450
 
383
- it 'does nothing and returns a fulfilled promise if no element or anchor matches the hash in the location', (done) ->
451
+ it 'does nothing and returns a fulfilled promise if no element or anchor matches the given #hash', (done) ->
384
452
  revealSpy = up.layout.knife.mock('reveal')
385
- location.hash = '#hash'
386
- promise = up.layout.revealHash()
453
+ promise = up.layout.revealHash('#hash')
387
454
  expect(revealSpy).not.toHaveBeenCalled()
388
455
  promiseState(promise).then (result) ->
389
456
  expect(result.state).toEqual('fulfilled')
390
457
  done()
391
458
 
392
- it 'does nothing and returns a fulfilled promise if the location has no hash', (done) ->
459
+ it 'does nothing and returns a fulfilled promise if no #hash is given', (done) ->
393
460
  revealSpy = up.layout.knife.mock('reveal')
394
- location.hash = ''
395
- promise = up.layout.revealHash()
461
+ promise = up.layout.revealHash('')
396
462
  expect(revealSpy).not.toHaveBeenCalled()
397
463
  promiseState(promise).then (result) ->
398
464
  expect(result.state).toEqual('fulfilled')
@@ -416,12 +482,30 @@ describe 'up.layout', ->
416
482
  $viewport = affix('.viewport')
417
483
  expect(up.layout.viewportOf($viewport)).toEqual($viewport)
418
484
 
419
- it 'finds the document if no better viewport matches', ->
420
- # This actually tests that the hierarchy returned by `$.parent`
421
- # is $element => ... => $('body') => $('html') => $(document)
422
- up.layout.config.viewports = ['.other-viewport']
423
- $element = affix('div')
424
- expect(up.layout.viewportOf($element)).toEqual($(document))
485
+ describe 'when no configured viewport matches', ->
486
+
487
+ afterEach ->
488
+ @resetBodyCss?()
489
+ @resetHtmlCss?()
490
+
491
+ it 'falls back to the scrolling element', ->
492
+ $element = affix('.element').css(height: '3000px')
493
+ $result = up.layout.viewportOf($element)
494
+ expect($result).toMatchSelector(up.browser.documentViewportSelector())
495
+
496
+ it 'falls back to the scrolling element if <body> is configured to scroll (fix for Edge)', ->
497
+ $element = affix('.element').css(height: '3000px')
498
+ @resetHtmlCss = u.writeTemporaryStyle('html', 'overflow-y': 'hidden')
499
+ @resetBodyCss = u.writeTemporaryStyle('body', 'overflow-y': 'scroll')
500
+ $result = up.layout.viewportOf($element)
501
+ expect($result).toMatchSelector(up.browser.documentViewportSelector())
502
+
503
+ it 'falls back to the scrolling element if <html> is configured to scroll (fix for Edge)', ->
504
+ $element = affix('.element').css(height: '3000px')
505
+ @resetHtmlCss = u.writeTemporaryStyle('html', 'overflow-y': 'scroll')
506
+ @resetBodyCss = u.writeTemporaryStyle('body', 'overflow-y': 'hidden')
507
+ $result = up.layout.viewportOf($element)
508
+ expect($result).toMatchSelector(up.browser.documentViewportSelector())
425
509
 
426
510
  describe 'up.layout.restoreScroll', ->
427
511
 
@@ -216,7 +216,7 @@ describe 'up.link', ->
216
216
  expect($('.target')).toHaveText('restored text from two')
217
217
  expect(location.pathname).toEqual('/two')
218
218
 
219
- it 'does adds additional history entries when linking to the current URL, but with a different hash', asyncSpec (next) ->
219
+ it 'does add additional history entries when linking to the current URL, but with a different hash', asyncSpec (next) ->
220
220
  up.history.config.enabled = true
221
221
 
222
222
  # By default, up.history will replace the <body> tag when
@@ -381,6 +381,7 @@ describe 'up.modal', ->
381
381
  it 'returns the URL behind the modal overlay', (done) ->
382
382
  up.history.config.enabled = true
383
383
  up.history.replace('/foo')
384
+
384
385
  expect(up.modal.coveredUrl()).toBeMissing()
385
386
  visitPromise = up.modal.visit('/bar', target: '.container')
386
387
  u.nextFrame =>
@@ -10,12 +10,12 @@ describe 'up.motion', ->
10
10
  $element = affix('.element').text('content')
11
11
  up.animate($element, 'fade-in', duration: 200, easing: 'linear')
12
12
 
13
- u.setTimer 5, ->
14
- expect($element).toHaveOpacity(0.0, 0.25)
13
+ u.setTimer 1, ->
14
+ expect($element).toHaveOpacity(0.0, 0.15)
15
15
  u.setTimer 100, ->
16
- expect($element).toHaveOpacity(0.5, 0.25)
17
- u.setTimer 200, ->
18
- expect($element).toHaveOpacity(1.0, 0.25)
16
+ expect($element).toHaveOpacity(0.5, 0.3)
17
+ u.setTimer 260, ->
18
+ expect($element).toHaveOpacity(1.0, 0.15)
19
19
  done()
20
20
 
21
21
  it 'returns a promise that is fulfilled when the animation has completed', (done) ->
@@ -41,6 +41,49 @@ describe 'up.motion', ->
41
41
  next =>
42
42
  expect($element.css('font-size')).toEqual('40px')
43
43
 
44
+ it 'pauses an existing CSS transitions and restores it once the Unpoly animation is done', asyncSpec (next) ->
45
+ $element = affix('.element').text('content').css
46
+ backgroundColor: 'yellow'
47
+ fontSize: '10px'
48
+ height: '20px'
49
+
50
+ expect(parseFloat($element.css('fontSize'))).toBeAround(10, 0.1)
51
+ expect(parseFloat($element.css('height'))).toBeAround(20, 0.1)
52
+
53
+ next.after 10, =>
54
+ $element.css
55
+ transition: 'font-size 500ms linear, height 500ms linear'
56
+ fontSize: '100px'
57
+ height: '200px'
58
+
59
+ next.after 250, =>
60
+ # Original CSS transition should now be ~50% done
61
+ @fontSizeBeforeAnimate = parseFloat($element.css('fontSize'))
62
+ @heightBeforeAnimate = parseFloat($element.css('height'))
63
+
64
+ expect(@fontSizeBeforeAnimate).toBeAround(0.5 * (100 - 10), 20)
65
+ expect(@heightBeforeAnimate).toBeAround(0.5 * (200 - 20), 40)
66
+
67
+ up.animate($element, 'fade-in', duration: 500, easing: 'linear')
68
+
69
+ next.after 250, =>
70
+ # Original CSS transition should remain paused at ~50%
71
+ # Unpoly animation should now be ~50% done
72
+ expect(parseFloat($element.css('fontSize'))).toBeAround(@fontSizeBeforeAnimate, 10)
73
+ expect(parseFloat($element.css('height'))).toBeAround(@heightBeforeAnimate, 10)
74
+ expect(parseFloat($element.css('opacity'))).toBeAround(0.5, 0.3)
75
+
76
+ next.after 250, =>
77
+ # Unpoly animation should now be done
78
+ # The original transition resumes. For technical reasons it will take
79
+ # its full duration for the remaining frames of the transition.
80
+ expect(parseFloat($element.css('opacity'))).toBeAround(1.0, 0.3)
81
+
82
+ next.after (500 + (tolerance = 125)), =>
83
+ expect(parseFloat($element.css('fontSize'))).toBeAround(100, 20)
84
+ expect(parseFloat($element.css('height'))).toBeAround(200, 40)
85
+
86
+
44
87
  describe 'when up.animate() is called from inside an animation function', ->
45
88
 
46
89
  it 'animates', (done) ->
@@ -101,6 +144,7 @@ describe 'up.motion', ->
101
144
  animateDone.then(callback)
102
145
  next => expect(callback).toHaveBeenCalled()
103
146
 
147
+
104
148
  describe 'up.motion.finish', ->
105
149
 
106
150
  describe 'when called with an element or selector', ->
@@ -158,49 +202,6 @@ describe 'up.motion', ->
158
202
  expect(currentTransitionProperty).toContain('font-size')
159
203
  expect(currentTransitionProperty).not.toContain('opacity')
160
204
 
161
- it 'pauses an existing CSS transitions and restores it once the Unpoly animation is done', asyncSpec (next) ->
162
- $element = affix('.element').text('content').css
163
- backgroundColor: 'yellow'
164
- fontSize: '10px'
165
- height: '20px'
166
-
167
- expect(parseFloat($element.css('fontSize'))).toBeAround(10, 0.1)
168
- expect(parseFloat($element.css('height'))).toBeAround(20, 0.1)
169
-
170
- next.after 10, =>
171
- $element.css
172
- transition: 'font-size 500ms linear, height 500ms linear'
173
- fontSize: '100px'
174
- height: '200px'
175
-
176
- next.after 250, =>
177
- # Original CSS transition should now be ~50% done
178
- @fontSizeBeforeAnimate = parseFloat($element.css('fontSize'))
179
- @heightBeforeAnimate = parseFloat($element.css('height'))
180
-
181
- expect(@fontSizeBeforeAnimate).toBeAround(0.5 * (100 - 10), 20)
182
- expect(@heightBeforeAnimate).toBeAround(0.5 * (200 - 20), 40)
183
-
184
- up.animate($element, 'fade-in', duration: 500, easing: 'linear')
185
-
186
- next.after 250, =>
187
- # Original CSS transition should remain paused at ~50%
188
- # Unpoly animation should now be ~50% done
189
- expect(parseFloat($element.css('fontSize'))).toBeAround(@fontSizeBeforeAnimate, 2)
190
- expect(parseFloat($element.css('height'))).toBeAround(@heightBeforeAnimate, 2)
191
- expect(parseFloat($element.css('opacity'))).toBeAround(0.5, 0.3)
192
-
193
- next.after 250, =>
194
- # Unpoly animation should now be done
195
- # The original transition resumes. For technical reasons it will take
196
- # its full duration for the remaining frames of the transition.
197
- expect(parseFloat($element.css('opacity'))).toBeAround(1.0, 0.3)
198
-
199
- next.after (500 + (tolerance = 125)), =>
200
- expect(parseFloat($element.css('fontSize'))).toBeAround(100, 20)
201
- expect(parseFloat($element.css('height'))).toBeAround(200, 40)
202
-
203
-
204
205
  it 'cancels an existing transition on the old element by instantly jumping to the last frame', asyncSpec (next) ->
205
206
  $v1 = affix('.element').text('v1')
206
207
  $v2 = affix('.element').text('v2')
@@ -473,7 +474,7 @@ describe 'up.motion', ->
473
474
  transition = ($old, $new, options) ->
474
475
  up.morph($old, $new, 'cross-fade', options)
475
476
 
476
- up.morph($old, $new, transition, duration: 200, easing: 'linear')
477
+ up.morph($old, $new, transition, duration: 400, easing: 'linear')
477
478
 
478
479
  next =>
479
480
  expect(u.measure($old)).toEqual(oldDims)
@@ -482,11 +483,11 @@ describe 'up.motion', ->
482
483
  expect(u.opacity($old)).toBeAround(1.0, 0.25)
483
484
  expect(u.opacity($new)).toBeAround(0.0, 0.25)
484
485
 
485
- next.after 100, =>
486
+ next.after 200, =>
486
487
  expect(u.opacity($old)).toBeAround(0.5, 0.25)
487
488
  expect(u.opacity($new)).toBeAround(0.5, 0.25)
488
489
 
489
- next.after (100 + (tolerance = 110)), =>
490
+ next.after (200 + (tolerance = 110)), =>
490
491
  expect(u.opacity($new)).toBeAround(1.0, 0.25)
491
492
  expect($old).toBeDetached()
492
493
  expect($new).toBeAttached()
@@ -1,9 +1,9 @@
1
1
  describe 'window.up namespace', ->
2
2
 
3
- describe 'renamedModule()', ->
3
+ describe 'deprecateRenamedModule()', ->
4
4
 
5
5
  it 'prints a warning and forwards the call to the new module', ->
6
- warnSpy = spyOn(up.log, 'warn')
6
+ warnSpy = spyOn(up, 'warn')
7
7
  value = up.flow
8
8
  expect(warnSpy).toHaveBeenCalled()
9
9
  expect(value).toBe(up.dom)
@@ -0,0 +1,768 @@
1
+ describe 'up.params', ->
2
+
3
+ u = up.util
4
+
5
+ describe 'JavaScript functions', ->
6
+
7
+ encodeBrackets = (str) ->
8
+ str = str.replace(/\[/g, '%5B')
9
+ str = str.replace(/\]/g, '%5D')
10
+ str
11
+
12
+ beforeEach ->
13
+ jasmine.addMatchers
14
+ toEqualAfterEncodingBrackets: (util, customEqualityTesters) ->
15
+ compare: (actual, expected) ->
16
+ pass: actual == encodeBrackets(expected)
17
+
18
+ describe 'up.params.toQuery', ->
19
+
20
+ # encodedOpeningBracket = '%5B'
21
+ # encodedClosingBracket = '%5D'
22
+ encodedSpace = '%20'
23
+
24
+ it 'returns the query section for the given object', ->
25
+ string = up.params.toQuery('foo-key': 'foo value', 'bar-key': 'bar value')
26
+ expect(string).toEqual("foo-key=foo#{encodedSpace}value&bar-key=bar#{encodedSpace}value")
27
+
28
+ it 'returns the query section for the given array with { name } and { value } keys', ->
29
+ string = up.params.toQuery([
30
+ { name: 'foo-key', value: 'foo value' },
31
+ { name: 'bar-key', value: 'bar value' }
32
+ ])
33
+ expect(string).toEqual("foo-key=foo#{encodedSpace}value&bar-key=bar#{encodedSpace}value")
34
+
35
+ it 'returns a given query string', ->
36
+ string = up.params.toQuery('foo=bar')
37
+ expect(string).toEqual('foo=bar')
38
+
39
+ it 'returns an empty string for an empty object', ->
40
+ string = up.params.toQuery({})
41
+ expect(string).toEqual('')
42
+
43
+ it 'returns an empty string for an empty string', ->
44
+ string = up.params.toQuery('')
45
+ expect(string).toEqual('')
46
+
47
+ it 'returns an empty string for undefined', ->
48
+ string = up.params.toQuery(undefined)
49
+ expect(string).toEqual('')
50
+
51
+ it 'URL-encodes characters in the key and value', ->
52
+ string = up.params.toQuery({ 'äpfel': 'bäume' })
53
+ expect(string).toEqual('%C3%A4pfel=b%C3%A4ume')
54
+
55
+ it "sets a blank value after the equal sign if a key's value is a blank string", ->
56
+ string = up.params.toQuery({'foo': ''})
57
+ expect(string).toEqual('foo=')
58
+
59
+ it 'omits non-primitive values (like Files) from the given params', ->
60
+ # I would like to construct a File, but IE11 does not support the constructor
61
+ blob = new Blob([])
62
+ string = up.params.toQuery(string: 'foo', blob: blob)
63
+ expect(string).toEqual('string=foo')
64
+
65
+ it "omits an equal sign if a key's value is null", ->
66
+ string = up.params.toQuery({'foo': null})
67
+ expect(string).toEqual('foo')
68
+
69
+ it 'URL-encodes plus characters', ->
70
+ string = up.params.toQuery({ 'my+key': 'my+value' })
71
+ expect(string).toEqual('my%2Bkey=my%2Bvalue')
72
+
73
+ describeCapability 'canInspectFormData', ->
74
+
75
+ it 'converts a FormData object to a query string', ->
76
+ fd = new FormData()
77
+ fd.append('key1', 'value1')
78
+ fd.append('key2', 'value2')
79
+ string = up.params.toQuery(fd)
80
+ expect(string).toEqual('key1=value1&key2=value2')
81
+
82
+ describe 'up.params.toArray', ->
83
+
84
+ it 'normalized null to an empty array', ->
85
+ array = up.params.toArray(null)
86
+ expect(array).toEqual([])
87
+
88
+ it 'normalized undefined to an empty array', ->
89
+ array = up.params.toArray(undefined)
90
+ expect(array).toEqual([])
91
+
92
+ it 'normalizes an object hash to an array of objects with { name } and { value } keys', ->
93
+ array = up.params.toArray(
94
+ 'foo-key': 'foo-value'
95
+ 'bar-key': 'bar-value'
96
+ )
97
+ expect(array).toEqual([
98
+ { name: 'foo-key', value: 'foo-value' },
99
+ { name: 'bar-key', value: 'bar-value' },
100
+ ])
101
+
102
+ it 'returns a given array without modification', ->
103
+ array = up.params.toArray([
104
+ { name: 'foo-key', value: 'foo-value' },
105
+ { name: 'bar-key', value: 'bar-value' },
106
+ ])
107
+ expect(array).toEqual([
108
+ { name: 'foo-key', value: 'foo-value' },
109
+ { name: 'bar-key', value: 'bar-value' },
110
+ ])
111
+
112
+ it 'does not URL-encode special characters in keys or values', ->
113
+ array = up.params.toArray(
114
+ 'äpfel': 'bäume'
115
+ )
116
+ expect(array).toEqual([
117
+ { name: 'äpfel', value: 'bäume' },
118
+ ])
119
+
120
+ it 'does not URL-encode spaces in keys or values', ->
121
+ array = up.params.toArray(
122
+ 'my key': 'my value'
123
+ )
124
+ expect(array).toEqual([
125
+ { name: 'my key', value: 'my value' },
126
+ ])
127
+
128
+ it 'does not URL-encode ampersands in keys or values', ->
129
+ array = up.params.toArray(
130
+ 'my&key': 'my&value'
131
+ )
132
+ expect(array).toEqual([
133
+ { name: 'my&key', value: 'my&value' },
134
+ ])
135
+
136
+ it 'does not URL-encode equal signs in keys or values', ->
137
+ array = up.params.toArray(
138
+ 'my=key': 'my=value'
139
+ )
140
+ expect(array).toEqual([
141
+ { name: 'my=key', value: 'my=value' },
142
+ ])
143
+
144
+ describeCapability 'canInspectFormData', ->
145
+
146
+ it 'converts a FormData object to an array', ->
147
+ fd = new FormData()
148
+ fd.append('key1', 'value1')
149
+ fd.append('key2', 'value2')
150
+ array = up.params.toArray(fd)
151
+ expect(array).toEqual([
152
+ { name: 'key1', value: 'value1' },
153
+ { name: 'key2', value: 'value2' },
154
+ ])
155
+
156
+
157
+ describe 'up.params.toFormData', ->
158
+
159
+ describeCapability 'canInspectFormData', ->
160
+
161
+ it 'converts undefined to an empty FormData object', ->
162
+ params = undefined
163
+ formData = up.params.toFormData(params)
164
+ expect(up.params.toArray(formData)).toEqual []
165
+
166
+ it 'converts null to an empty FormData object', ->
167
+ params = null
168
+ formData = up.params.toFormData(params)
169
+ expect(up.params.toArray(formData)).toEqual []
170
+
171
+ it 'converts an object to a FormData object', ->
172
+ params = {
173
+ key1: 'value1',
174
+ key2: 'value2'
175
+ }
176
+ formData = up.params.toFormData(params)
177
+ expect(up.params.toArray(formData)).toEqual [
178
+ { name: 'key1', value: 'value1' },
179
+ { name: 'key2', value: 'value2' },
180
+ ]
181
+
182
+ it 'returns a FormData object unchanged', ->
183
+ params = new FormData()
184
+ formData = up.params.toFormData(params)
185
+ expect(formData).toBe(params)
186
+
187
+ it 'converts a query string to a FormData object', ->
188
+ params = 'key1=value1&key2=value2'
189
+ formData = up.params.toFormData(params)
190
+ expect(up.params.toArray(formData)).toEqual [
191
+ { name: 'key1', value: 'value1' },
192
+ { name: 'key2', value: 'value2' },
193
+ ]
194
+
195
+ it 'unescapes percent-encoded characters from a query string', ->
196
+ params = 'my%20key=my%20value'
197
+ formData = up.params.toFormData(params)
198
+ expect(up.params.toArray(formData)).toEqual [
199
+ { name: 'my key', value: 'my value' }
200
+ ]
201
+
202
+
203
+ describe 'up.params.toObject', ->
204
+
205
+ it "parses flat key/value pairs", ->
206
+ expect(up.params.toObject("xfoo")).toEqual("xfoo": null)
207
+ expect(up.params.toObject("foo=")).toEqual("foo": "")
208
+ expect(up.params.toObject("foo=bar")).toEqual("foo": "bar")
209
+ expect(up.params.toObject("foo=\"bar\"")).toEqual("foo": "\"bar\"")
210
+
211
+ expect(up.params.toObject("foo=bar&foo=quux")).toEqual("foo": "quux")
212
+ expect(up.params.toObject("foo&foo=")).toEqual("foo": "")
213
+ expect(up.params.toObject("foo=1&bar=2")).toEqual("foo": "1", "bar": "2")
214
+ expect(up.params.toObject("&foo=1&&bar=2")).toEqual("foo": "1", "bar": "2")
215
+ expect(up.params.toObject("foo&bar=")).toEqual("foo": null, "bar": "")
216
+ expect(up.params.toObject("foo=bar&baz=")).toEqual("foo": "bar", "baz": "")
217
+
218
+ it 'URL-decodes keys and values', ->
219
+ expect(up.params.toObject("my%20weird%20field=q1%212%22%27w%245%267%2Fz8%29%3F")).toEqual("my weird field": "q1!2\"'w$5&7/z8)?")
220
+ expect(up.params.toObject("a=b&pid%3D1234=1023")).toEqual("pid=1234": "1023", "a": "b")
221
+ # expect(-> up.params.toObject("foo%81E=1")).toThrowError() # invalid byte sequence in UTF-8
222
+
223
+ it 'ignores keys that would overwrite an Object prototype property', ->
224
+ obj = up.params.toObject("foo=bar&hasOwnProperty=baz")
225
+ expect(obj['foo']).toEqual('bar')
226
+ expect(u.isFunction obj['hasOwnProperty']).toBe(true)
227
+
228
+ describe 'up.params.add', ->
229
+
230
+ describe '(with object)', ->
231
+
232
+ it 'adds a single key and value', ->
233
+ obj = { foo: 'one' }
234
+ obj = up.params.add(obj, 'bar', 'two')
235
+ expect(obj).toEqual { foo: 'one', bar: 'two' }
236
+
237
+ describe '(with array)', ->
238
+
239
+ it 'adds a single key and value', ->
240
+ obj = [{ name: 'foo', value: 'one' }]
241
+ obj = up.params.add(obj, 'bar', 'two')
242
+ expect(obj).toEqual [{ name: 'foo', value: 'one' }, { name: 'bar', value: 'two' }]
243
+
244
+ describe '(with query string)', ->
245
+
246
+ it 'adds a new key/value pair to the end of a query', ->
247
+ query = 'foo=one'
248
+ query = up.params.add(query, 'bar', 'two')
249
+ expect(query).toEqual('foo=one&bar=two')
250
+
251
+ it 'does not add superfluous ampersands if the previous query was a blank string', ->
252
+ query = ''
253
+ query = up.params.add(query, 'bar', 'two')
254
+ expect(query).toEqual('bar=two')
255
+
256
+ it 'escapes special characters in the new key and value', ->
257
+ query = 'foo=one'
258
+ query = up.params.add(query, 'bär', 'twö')
259
+ expect(query).toEqual('foo=one&b%C3%A4r=tw%C3%B6')
260
+
261
+ describe '(with FormData)', ->
262
+
263
+ describeCapability 'canInspectFormData', ->
264
+
265
+ it 'adds a single entry', ->
266
+ formData = new FormData()
267
+ formData.append('key1', 'value1')
268
+ up.params.add(formData, 'key2', 'value2')
269
+ expect(up.params.toArray(formData)).toEqual [
270
+ { name: 'key1', value: 'value1' },
271
+ { name: 'key2', value: 'value2' },
272
+ ]
273
+
274
+ describe '(with missing params)', ->
275
+
276
+ it 'returns an object with only the new key and value', ->
277
+ obj = undefined
278
+ obj = up.params.add(obj, 'bar', 'two')
279
+ expect(obj).toEqual { bar: 'two' }
280
+
281
+
282
+ describe 'up.params.get', ->
283
+
284
+ describe '(with object)', ->
285
+
286
+ it 'returns the value for the given name', ->
287
+ obj = { foo: 'one', bar: 'two' }
288
+ value = up.params.get(obj, 'bar')
289
+ expect(value).toEqual('two')
290
+
291
+ it 'returns undefined if no value is set for the given name', ->
292
+ obj = { foo: 'one' }
293
+ value = up.params.get(obj, 'bar')
294
+ expect(value).toBeUndefined()
295
+
296
+ it 'returns undefined for names that are also a basic object property', ->
297
+ obj = {}
298
+ value = up.params.get(obj, 'hasOwnProperty')
299
+ expect(value).toBeUndefined()
300
+
301
+ describe '(with array)', ->
302
+
303
+ it 'returns the value of the first entry with the given name', ->
304
+ array = [
305
+ { name: 'foo', value: 'one' }
306
+ { name: 'bar', value: 'two' }
307
+ { name: 'foo', value: 'three' }
308
+ ]
309
+ value = up.params.get(array, 'foo')
310
+ expect(value).toEqual('one')
311
+
312
+ it 'returns undefined if there is no entry with the given name', ->
313
+ array = [
314
+ { name: 'foo', value: 'one' }
315
+ ]
316
+ value = up.params.get(array, 'bar')
317
+ expect(value).toBeUndefined()
318
+
319
+ describe '(with query string)', ->
320
+
321
+ it 'returns the query param with the given name', ->
322
+ query = 'foo=one&bar=two'
323
+ value = up.params.get(query, 'bar')
324
+ expect(value).toEqual('two')
325
+
326
+ it 'returns undefined if there is no query param with the given name', ->
327
+ query = 'foo=one'
328
+ query = up.params.get(query, 'bar')
329
+ expect(query).toBeUndefined()
330
+
331
+ it 'unescapes percent-encoded characters in the returned value', ->
332
+ query = 'foo=one%20two'
333
+ value = up.params.get(query, 'foo')
334
+ expect(value).toEqual('one two')
335
+
336
+ describe '(with FormData)', ->
337
+
338
+ describeCapability 'canInspectFormData', ->
339
+
340
+ it 'returns the first entry with the given name', ->
341
+ formData = new FormData()
342
+ formData.append('key1', 'value1')
343
+ formData.append('key2', 'value2')
344
+ value = up.params.get(formData, 'key2')
345
+ expect(value).toEqual('value2')
346
+
347
+ it 'returns undefined if there is no entry with the given name', ->
348
+ formData = new FormData()
349
+ value = up.params.get(formData, 'key')
350
+ expect(value).toBeUndefined()
351
+
352
+ describe '(with missing params)', ->
353
+
354
+ it 'returns undefined', ->
355
+ params = undefined
356
+ value = up.params.get(params, 'foo')
357
+ expect(value).toBeUndefined()
358
+
359
+ describe 'up.params.merge', ->
360
+
361
+ describe '(with object)', ->
362
+
363
+ it 'merges a flat object', ->
364
+ obj = { a: '1', b: '2' }
365
+ other = { c: '3', d: '4'}
366
+ obj = up.params.merge(obj, other)
367
+ expect(obj).toEqual({ a: '1', b: '2', c: '3', d: '4' })
368
+
369
+ it 'merges an array', ->
370
+ obj = { a: '1', b: '2' }
371
+ other = [
372
+ { name: 'c', value: '3' },
373
+ { name: 'd', value: '4' }
374
+ ]
375
+ obj = up.params.merge(obj, other)
376
+ expect(obj).toEqual({ a: '1', b: '2', c: '3', d: '4' })
377
+
378
+ it 'merges a query string', ->
379
+ obj = { a: '1', b: '2' }
380
+ other = 'c=3&d=4'
381
+ obj = up.params.merge(obj, other)
382
+ expect(obj).toEqual({ a: '1', b: '2', c: '3', d: '4' })
383
+
384
+ it 'does not change or crash when merged with undefined', ->
385
+ obj = { a: '1', b: '2' }
386
+ obj = up.params.merge(obj, undefined)
387
+ expect(obj).toEqual({ a: '1', b: '2' })
388
+
389
+ describeCapability 'canInspectFormData', ->
390
+
391
+ it 'merges a FormData object', ->
392
+ obj = { a: '1', b: '2' }
393
+ formData = new FormData()
394
+ formData.append('c', '3')
395
+ formData.append('d', '4')
396
+ merged = up.params.merge(obj, formData)
397
+ expect(merged).toEqual({ a: '1', b: '2', c: '3', d: '4' })
398
+
399
+ describe '(with array)', ->
400
+
401
+ it 'merges a flat object', ->
402
+ array = [
403
+ { name: 'a', value: '1' },
404
+ { name: 'b', value: '2' }
405
+ ]
406
+ other = { c: '3', d: '4'}
407
+ array = up.params.merge(array, other)
408
+ expect(array).toEqual [
409
+ { name: 'a', value: '1' },
410
+ { name: 'b', value: '2' },
411
+ { name: 'c', value: '3' },
412
+ { name: 'd', value: '4' }
413
+ ]
414
+
415
+ it 'merges another array', ->
416
+ array = [
417
+ { name: 'a', value: '1' },
418
+ { name: 'b', value: '2' }
419
+ ]
420
+ other = [
421
+ { name: 'c', value: '3' },
422
+ { name: 'd', value: '4' }
423
+ ]
424
+ array = up.params.merge(array, other)
425
+ expect(array).toEqual [
426
+ { name: 'a', value: '1' },
427
+ { name: 'b', value: '2' },
428
+ { name: 'c', value: '3' },
429
+ { name: 'd', value: '4' }
430
+ ]
431
+
432
+ it 'merges a query string', ->
433
+ array = [
434
+ { name: 'a', value: '1' },
435
+ { name: 'b', value: '2' }
436
+ ]
437
+ other = 'c=3&d=4'
438
+ array = up.params.merge(array, other)
439
+ expect(array).toEqual [
440
+ { name: 'a', value: '1' },
441
+ { name: 'b', value: '2' },
442
+ { name: 'c', value: '3' },
443
+ { name: 'd', value: '4' }
444
+ ]
445
+
446
+ it 'does not change or crash when merged with undefined', ->
447
+ array = [
448
+ { name: 'a', value: '1' },
449
+ { name: 'b', value: '2' }
450
+ ]
451
+ array = up.params.merge(array, undefined)
452
+ expect(array).toEqual [
453
+ { name: 'a', value: '1' },
454
+ { name: 'b', value: '2' }
455
+ ]
456
+
457
+ describeCapability 'canInspectFormData', ->
458
+
459
+ it 'merges a FormData object', ->
460
+ array = [
461
+ { name: 'a', value: '1' },
462
+ { name: 'b', value: '2' }
463
+ ]
464
+ formData = new FormData()
465
+ formData.append('c', '3')
466
+ formData.append('d', '4')
467
+ merged = up.params.merge(array, formData)
468
+ expect(merged).toEqual [
469
+ { name: 'a', value: '1' },
470
+ { name: 'b', value: '2' },
471
+ { name: 'c', value: '3' },
472
+ { name: 'd', value: '4' }
473
+ ]
474
+
475
+
476
+ describe '(with query)', ->
477
+
478
+ it 'merges a flat object', ->
479
+ query = 'a=1&b=2'
480
+ other = { c: '3', d: '4'}
481
+ query = up.params.merge(query, other)
482
+ expect(query).toEqual('a=1&b=2&c=3&d=4')
483
+
484
+ it 'merges an array', ->
485
+ query = 'a=1&b=2'
486
+ other = [
487
+ { name: 'c', value: '3' },
488
+ { name: 'd', value: '4' }
489
+ ]
490
+ query = up.params.merge(query, other)
491
+ expect(query).toEqual('a=1&b=2&c=3&d=4')
492
+
493
+ it 'merges another query string', ->
494
+ query = 'a=1&b=2'
495
+ other = 'c=3&d=4'
496
+ query = up.params.merge(query, other)
497
+ expect(query).toEqual('a=1&b=2&c=3&d=4')
498
+
499
+ it 'does not change or crash when merged with undefined', ->
500
+ query = 'a=1&b=2'
501
+ query = up.params.merge(query, undefined)
502
+ expect(query).toEqual('a=1&b=2')
503
+
504
+ describeCapability 'canInspectFormData', ->
505
+
506
+ it 'merges a FormData object', ->
507
+ query = 'a=1&b=2'
508
+ formData = new FormData()
509
+ formData.append('c', '3')
510
+ formData.append('d', '4')
511
+ merged = up.params.merge(query, formData)
512
+ expect(merged).toEqual('a=1&b=2&c=3&d=4')
513
+
514
+ describe 'up.params.buildURL', ->
515
+
516
+ it 'composes a URL from a base URL (without query section) and a query section', ->
517
+ base = 'http://foo.bar/path'
518
+ query = 'key=value'
519
+ expect(up.params.buildURL(base, query)).toEqual('http://foo.bar/path?key=value')
520
+
521
+ it 'accepts other forms of params (instead of query sections)', ->
522
+ base = 'http://foo.bar/path'
523
+ params = { key: 'value' }
524
+ expect(up.params.buildURL(base, params)).toEqual('http://foo.bar/path?key=value')
525
+
526
+ it 'adds more params to a base URL that already has a query section', ->
527
+ base = 'http://foo.bar/path?key1=value1'
528
+ params = { key2: 'value2' }
529
+ expect(up.params.buildURL(base, params)).toEqual('http://foo.bar/path?key1=value1&key2=value2')
530
+
531
+ it 'does not add a question mark to the base URL if the given params are blank', ->
532
+ base = 'http://foo.bar/path'
533
+ params = ''
534
+ expect(up.params.buildURL(base, params)).toEqual('http://foo.bar/path')
535
+
536
+ describe 'up.params.fromURL', ->
537
+
538
+ it 'returns the query section from an URL, without leading question mark', ->
539
+ url = 'http://foo.bar/path?key=value'
540
+ expect(up.params.fromURL(url)).toEqual('key=value')
541
+
542
+ it 'returns undefined if the URL has no query section', ->
543
+ url = 'http://foo.bar/path'
544
+ expect(up.params.fromURL(url)).toBeUndefined()
545
+
546
+
547
+ describe 'up.params.fromForm', ->
548
+
549
+ it 'serializes a form with multiple inputs', ->
550
+ $form = affix('form')
551
+ $form.append('<input name="key1" value="value1">')
552
+ $form.append('<input name="key2" value="value2">')
553
+
554
+ params = up.params.fromForm($form)
555
+ expect(params).toEqual [
556
+ { name: 'key1', value: 'value1' },
557
+ { name: 'key2', value: 'value2' },
558
+ ]
559
+
560
+ it 'serializes an <input type="text"> with its default [value]', ->
561
+ $form = affix('form')
562
+ $form.append('<input type="text" name="key" value="value-from-attribute">')
563
+
564
+ params = up.params.fromForm($form)
565
+ expect(params).toEqual [
566
+ { name: 'key', value: 'value-from-attribute' }
567
+ ]
568
+
569
+ it 'serializes an <input type="text"> that had its value property changed by a script', ->
570
+ $form = affix('form')
571
+ $input = $('<input type="text" name="key" value="value-from-attribute">').appendTo($form)
572
+ $input[0].value = 'value-from-script'
573
+
574
+ params = up.params.fromForm($form)
575
+ expect(params).toEqual [
576
+ { name: 'key', value: 'value-from-script' }
577
+ ]
578
+
579
+ it 'serializes an <input type="hidden"> with its default [value]', ->
580
+ $form = affix('form')
581
+ $form.append('<input type="hidden" name="key" value="value-from-attribute">')
582
+
583
+ params = up.params.fromForm($form)
584
+ expect(params).toEqual [
585
+ { name: 'key', value: 'value-from-attribute' }
586
+ ]
587
+
588
+ it 'serializes an <input type="hidden"> that had its value property changed by a script', ->
589
+ $form = affix('form')
590
+ $input = $('<input type="hidden" name="key" value="value-from-attribute">').appendTo($form)
591
+ $input[0].value = 'value-from-script'
592
+
593
+ params = up.params.fromForm($form)
594
+ expect(params).toEqual [
595
+ { name: 'key', value: 'value-from-script' }
596
+ ]
597
+
598
+ it 'seralizes a <select> with its default selected option', ->
599
+ $form = affix('form')
600
+ $select = $('<select name="key"></select>').appendTo($form)
601
+ $option1 = $('<option value="value1">').appendTo($select)
602
+ $option2 = $('<option value="value2" selected>').appendTo($select)
603
+ $option3 = $('<option value="value3">').appendTo($select)
604
+
605
+ params = up.params.fromForm($form)
606
+ expect(params).toEqual [
607
+ { name: 'key', value: 'value2' }
608
+ ]
609
+
610
+ it 'seralizes a <select> that had its selection changed by a script', ->
611
+ $form = affix('form')
612
+ $select = $('<select name="key"></select>').appendTo($form)
613
+ $option1 = $('<option value="value1">').appendTo($select)
614
+ $option2 = $('<option value="value2" selected>').appendTo($select)
615
+ $option3 = $('<option value="value3">').appendTo($select)
616
+
617
+ $option2[0].selected = false
618
+ $option3[0].selected = true
619
+
620
+ params = up.params.fromForm($form)
621
+ expect(params).toEqual [
622
+ { name: 'key', value: 'value3' }
623
+ ]
624
+
625
+ it 'serializes a <select multiple> with multiple selected options into multiple params', ->
626
+ $form = affix('form')
627
+ $select = $('<select name="key" multiple></select>').appendTo($form)
628
+ $option1 = $('<option value="value1">').appendTo($select)
629
+ $option2 = $('<option value="value2" selected>').appendTo($select)
630
+ $option3 = $('<option value="value3" selected>').appendTo($select)
631
+
632
+ params = up.params.fromForm($form)
633
+ expect(params).toEqual [
634
+ { name: 'key', value: 'value2' },
635
+ { name: 'key', value: 'value3' }
636
+ ]
637
+
638
+ it 'serializes an <input type="file">'
639
+
640
+ it 'serializes an <input type="file" multiple> into multiple params'
641
+
642
+ it 'includes an <input type="checkbox"> that was [checked] by default', ->
643
+ $form = affix('form')
644
+ $input = $('<input type="checkbox" name="key" value="value" checked>').appendTo($form)
645
+
646
+ params = up.params.fromForm($form)
647
+ expect(params).toEqual [
648
+ { name: 'key', value: 'value' }
649
+ ]
650
+
651
+ it 'includes an <input type="checkbox"> that was checked by a script', ->
652
+ $form = affix('form')
653
+ $input = $('<input type="checkbox" name="key" value="value">').appendTo($form)
654
+ $input[0].checked = true
655
+
656
+ params = up.params.fromForm($form)
657
+ expect(params).toEqual [
658
+ { name: 'key', value: 'value' }
659
+ ]
660
+
661
+ it 'excludes an <input type="checkbox"> that is unchecked', ->
662
+ $form = affix('form')
663
+ $input = $('<input type="checkbox" name="key" value="value">').appendTo($form)
664
+ params = up.params.fromForm($form)
665
+ expect(params).toEqual []
666
+
667
+ it 'includes a checked <input type="radio"> in a radio button group that was [checked] by default', ->
668
+ $form = affix('form')
669
+ $button1 = $('<input type="radio" name="key" value="value1">').appendTo($form)
670
+ $button2 = $('<input type="radio" name="key" value="value2" checked>').appendTo($form)
671
+ $button3 = $('<input type="radio" name="key" value="value3">').appendTo($form)
672
+
673
+ params = up.params.fromForm($form)
674
+ expect(params).toEqual [
675
+ { name: 'key', value: 'value2' }
676
+ ]
677
+
678
+ it 'includes a checked <input type="radio"> in a radio button group that was checked by a script', ->
679
+ $form = affix('form')
680
+ $button1 = $('<input type="radio" name="key" value="value1">').appendTo($form)
681
+ $button2 = $('<input type="radio" name="key" value="value2" checked>').appendTo($form)
682
+ $button3 = $('<input type="radio" name="key" value="value3">').appendTo($form)
683
+
684
+ $button2[0].checked = false
685
+ $button3[0].checked = true
686
+
687
+ params = up.params.fromForm($form)
688
+ expect(params).toEqual [
689
+ { name: 'key', value: 'value3' }
690
+ ]
691
+
692
+ it 'excludes an radio button group if no button is selected', ->
693
+ $form = affix('form')
694
+ $button1 = $('<input type="radio" name="key" value="value1">').appendTo($form)
695
+ $button2 = $('<input type="radio" name="key" value="value2">').appendTo($form)
696
+
697
+ params = up.params.fromForm($form)
698
+ expect(params).toEqual []
699
+
700
+ it 'excludes an <input> that is [disabled] by default', ->
701
+ $form = affix('form')
702
+ $input = $('<input type="text" name="key" value="value" disabled>').appendTo($form)
703
+
704
+ params = up.params.fromForm($form)
705
+ expect(params).toEqual []
706
+
707
+ it 'excludes an <input> that was disabled by a script', ->
708
+ $form = affix('form')
709
+ $input = $('<input type="text" name="key" value="value">').appendTo($form)
710
+ $input[0].disabled = true
711
+
712
+ params = up.params.fromForm($form)
713
+ expect(params).toEqual []
714
+
715
+ it 'excludes an <input> without a [name] attribute', ->
716
+ $form = affix('form')
717
+ $input = $('<input type="text" value="value">').appendTo($form)
718
+
719
+ params = up.params.fromForm($form)
720
+ expect(params).toEqual []
721
+
722
+ it 'includes an <input readonly>', ->
723
+ $form = affix('form')
724
+ $input = $('<input type="text" name="key" value="value" readonly>').appendTo($form)
725
+
726
+ params = up.params.fromForm($form)
727
+ expect(params).toEqual [
728
+ { name: 'key', value: 'value' }
729
+ ]
730
+
731
+ it 'includes the focused submit button', ->
732
+ $form = affix('form')
733
+ $input = $('<input type="text" name="input-key" value="input-value">').appendTo($form)
734
+ $submit1 = $('<button type="submit" name="submit1-key" value="submit1-value">').appendTo($form)
735
+ $submit2 = $('<input type="submit" name="submit2-key" value="submit2-value">').appendTo($form)
736
+ $submit3 = $('<input type="submit" name="submit3-key" value="submit3-value">').appendTo($form)
737
+
738
+ $submit2.focus()
739
+
740
+ params = up.params.fromForm($form)
741
+ expect(params).toEqual [
742
+ { name: 'input-key', value: 'input-value' },
743
+ { name: 'submit2-key', value: 'submit2-value' }
744
+ ]
745
+
746
+ it 'includes a the first submit button if no button is focused', ->
747
+ $form = affix('form')
748
+ $input = $('<input type="text" name="input-key" value="input-value">').appendTo($form)
749
+ $submit1 = $('<button type="submit" name="submit1-key" value="submit1-value">').appendTo($form)
750
+ $submit2 = $('<input type="submit" name="submit2-key" value="submit2-value">').appendTo($form)
751
+
752
+ params = up.params.fromForm($form)
753
+ expect(params).toEqual [
754
+ { name: 'input-key', value: 'input-value' },
755
+ { name: 'submit1-key', value: 'submit1-value' }
756
+ ]
757
+
758
+ it 'excludes a submit button without a [name] attribute', ->
759
+ $form = affix('form')
760
+ $input = $('<input type="text" name="input-key" value="input-value">').appendTo($form)
761
+ $submit = $('<button type="submit" value="submit-value">').appendTo($form)
762
+
763
+ params = up.params.fromForm($form)
764
+ expect(params).toEqual [
765
+ { name: 'input-key', value: 'input-value' }
766
+ ]
767
+
768
+