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.
- 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
|
+
|