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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +74 -1
- data/dist/unpoly.js +1569 -793
- data/dist/unpoly.min.js +4 -4
- data/lib/assets/javascripts/unpoly.coffee +2 -0
- data/lib/assets/javascripts/unpoly/browser.coffee.erb +25 -41
- data/lib/assets/javascripts/unpoly/bus.coffee.erb +20 -6
- data/lib/assets/javascripts/unpoly/classes/cache.coffee +23 -13
- data/lib/assets/javascripts/unpoly/classes/compile_pass.coffee +87 -0
- data/lib/assets/javascripts/unpoly/classes/focus_tracker.coffee +29 -0
- data/lib/assets/javascripts/unpoly/classes/follow_variant.coffee +7 -4
- data/lib/assets/javascripts/unpoly/classes/record.coffee +1 -1
- data/lib/assets/javascripts/unpoly/classes/request.coffee +38 -45
- data/lib/assets/javascripts/unpoly/classes/response.coffee +16 -1
- data/lib/assets/javascripts/unpoly/classes/store/memory.coffee +26 -0
- data/lib/assets/javascripts/unpoly/classes/store/session.coffee +59 -0
- data/lib/assets/javascripts/unpoly/cookie.coffee +56 -0
- data/lib/assets/javascripts/unpoly/dom.coffee.erb +67 -39
- data/lib/assets/javascripts/unpoly/feedback.coffee +2 -2
- data/lib/assets/javascripts/unpoly/form.coffee.erb +23 -12
- data/lib/assets/javascripts/unpoly/history.coffee +2 -2
- data/lib/assets/javascripts/unpoly/layout.coffee.erb +118 -99
- data/lib/assets/javascripts/unpoly/link.coffee.erb +12 -5
- data/lib/assets/javascripts/unpoly/log.coffee +6 -5
- data/lib/assets/javascripts/unpoly/modal.coffee.erb +9 -2
- data/lib/assets/javascripts/unpoly/motion.coffee.erb +2 -6
- data/lib/assets/javascripts/unpoly/namespace.coffee.erb +2 -2
- data/lib/assets/javascripts/unpoly/params.coffee.erb +522 -0
- data/lib/assets/javascripts/unpoly/popup.coffee.erb +3 -3
- data/lib/assets/javascripts/unpoly/proxy.coffee +42 -34
- data/lib/assets/javascripts/unpoly/{syntax.coffee → syntax.coffee.erb} +59 -117
- data/lib/assets/javascripts/unpoly/{util.coffee → util.coffee.erb} +206 -171
- data/lib/unpoly/rails/version.rb +1 -1
- data/package.json +1 -1
- data/spec_app/Gemfile.lock +1 -1
- data/spec_app/app/assets/javascripts/integration_test.coffee +0 -4
- data/spec_app/app/assets/stylesheets/integration_test.sass +7 -1
- data/spec_app/app/controllers/pages_controller.rb +4 -0
- data/spec_app/app/views/form_test/basics/new.erb +34 -5
- data/spec_app/app/views/form_test/submission_result.erb +2 -2
- data/spec_app/app/views/form_test/uploads/new.erb +15 -2
- data/spec_app/app/views/hash_test/unpoly.erb +30 -0
- data/spec_app/app/views/pages/start.erb +2 -1
- data/spec_app/spec/javascripts/helpers/parse_form_data.js.coffee +17 -2
- data/spec_app/spec/javascripts/helpers/reset_up.js.coffee +5 -0
- data/spec_app/spec/javascripts/helpers/to_be_error.coffee +1 -1
- data/spec_app/spec/javascripts/helpers/to_match_selector.coffee +5 -0
- data/spec_app/spec/javascripts/up/browser_spec.js.coffee +8 -8
- data/spec_app/spec/javascripts/up/bus_spec.js.coffee +58 -20
- data/spec_app/spec/javascripts/up/classes/cache_spec.js.coffee +78 -0
- data/spec_app/spec/javascripts/up/classes/focus_tracker_spec.coffee +31 -0
- data/spec_app/spec/javascripts/up/classes/request_spec.coffee +50 -0
- data/spec_app/spec/javascripts/up/classes/store/memory_spec.js.coffee +67 -0
- data/spec_app/spec/javascripts/up/classes/store/session_spec.js.coffee +113 -0
- data/spec_app/spec/javascripts/up/dom_spec.js.coffee +133 -45
- data/spec_app/spec/javascripts/up/form_spec.js.coffee +13 -13
- data/spec_app/spec/javascripts/up/layout_spec.js.coffee +110 -26
- data/spec_app/spec/javascripts/up/link_spec.js.coffee +1 -1
- data/spec_app/spec/javascripts/up/modal_spec.js.coffee +1 -0
- data/spec_app/spec/javascripts/up/motion_spec.js.coffee +52 -51
- data/spec_app/spec/javascripts/up/namespace_spec.js.coffee +2 -2
- data/spec_app/spec/javascripts/up/params_spec.coffee +768 -0
- data/spec_app/spec/javascripts/up/proxy_spec.js.coffee +75 -36
- data/spec_app/spec/javascripts/up/syntax_spec.js.coffee +48 -15
- data/spec_app/spec/javascripts/up/util_spec.js.coffee +148 -131
- metadata +17 -5
- 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:
|
43
|
+
up.observe($input, { delay: 200 }, callback)
|
44
44
|
$input.val('new-value-1')
|
45
45
|
$input.trigger(eventName)
|
46
46
|
|
47
|
-
next.after
|
48
|
-
#
|
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
|
52
|
-
#
|
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
|
59
|
-
#
|
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
|
65
|
-
#
|
66
|
-
#
|
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
|
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
|
-
|
565
|
-
expect(u.isFormData(
|
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
|
-
|
24
|
-
|
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($(
|
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($(
|
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($(
|
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
|
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
|
-
|
373
|
-
|
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
|
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
|
-
|
380
|
-
|
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
|
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
|
-
|
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
|
459
|
+
it 'does nothing and returns a fulfilled promise if no #hash is given', (done) ->
|
393
460
|
revealSpy = up.layout.knife.mock('reveal')
|
394
|
-
|
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
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
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
|
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
|
14
|
-
expect($element).toHaveOpacity(0.0, 0.
|
13
|
+
u.setTimer 1, ->
|
14
|
+
expect($element).toHaveOpacity(0.0, 0.15)
|
15
15
|
u.setTimer 100, ->
|
16
|
-
expect($element).toHaveOpacity(0.5, 0.
|
17
|
-
u.setTimer
|
18
|
-
expect($element).toHaveOpacity(1.0, 0.
|
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:
|
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
|
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 (
|
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 '
|
3
|
+
describe 'deprecateRenamedModule()', ->
|
4
4
|
|
5
5
|
it 'prints a warning and forwards the call to the new module', ->
|
6
|
-
warnSpy = spyOn(up
|
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
|
+
|