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

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
+