unpoly-rails 0.56.7 → 0.57.0

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

Potentially problematic release.


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

Files changed (67) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +74 -1
  3. data/dist/unpoly.js +1569 -793
  4. data/dist/unpoly.min.js +4 -4
  5. data/lib/assets/javascripts/unpoly.coffee +2 -0
  6. data/lib/assets/javascripts/unpoly/browser.coffee.erb +25 -41
  7. data/lib/assets/javascripts/unpoly/bus.coffee.erb +20 -6
  8. data/lib/assets/javascripts/unpoly/classes/cache.coffee +23 -13
  9. data/lib/assets/javascripts/unpoly/classes/compile_pass.coffee +87 -0
  10. data/lib/assets/javascripts/unpoly/classes/focus_tracker.coffee +29 -0
  11. data/lib/assets/javascripts/unpoly/classes/follow_variant.coffee +7 -4
  12. data/lib/assets/javascripts/unpoly/classes/record.coffee +1 -1
  13. data/lib/assets/javascripts/unpoly/classes/request.coffee +38 -45
  14. data/lib/assets/javascripts/unpoly/classes/response.coffee +16 -1
  15. data/lib/assets/javascripts/unpoly/classes/store/memory.coffee +26 -0
  16. data/lib/assets/javascripts/unpoly/classes/store/session.coffee +59 -0
  17. data/lib/assets/javascripts/unpoly/cookie.coffee +56 -0
  18. data/lib/assets/javascripts/unpoly/dom.coffee.erb +67 -39
  19. data/lib/assets/javascripts/unpoly/feedback.coffee +2 -2
  20. data/lib/assets/javascripts/unpoly/form.coffee.erb +23 -12
  21. data/lib/assets/javascripts/unpoly/history.coffee +2 -2
  22. data/lib/assets/javascripts/unpoly/layout.coffee.erb +118 -99
  23. data/lib/assets/javascripts/unpoly/link.coffee.erb +12 -5
  24. data/lib/assets/javascripts/unpoly/log.coffee +6 -5
  25. data/lib/assets/javascripts/unpoly/modal.coffee.erb +9 -2
  26. data/lib/assets/javascripts/unpoly/motion.coffee.erb +2 -6
  27. data/lib/assets/javascripts/unpoly/namespace.coffee.erb +2 -2
  28. data/lib/assets/javascripts/unpoly/params.coffee.erb +522 -0
  29. data/lib/assets/javascripts/unpoly/popup.coffee.erb +3 -3
  30. data/lib/assets/javascripts/unpoly/proxy.coffee +42 -34
  31. data/lib/assets/javascripts/unpoly/{syntax.coffee → syntax.coffee.erb} +59 -117
  32. data/lib/assets/javascripts/unpoly/{util.coffee → util.coffee.erb} +206 -171
  33. data/lib/unpoly/rails/version.rb +1 -1
  34. data/package.json +1 -1
  35. data/spec_app/Gemfile.lock +1 -1
  36. data/spec_app/app/assets/javascripts/integration_test.coffee +0 -4
  37. data/spec_app/app/assets/stylesheets/integration_test.sass +7 -1
  38. data/spec_app/app/controllers/pages_controller.rb +4 -0
  39. data/spec_app/app/views/form_test/basics/new.erb +34 -5
  40. data/spec_app/app/views/form_test/submission_result.erb +2 -2
  41. data/spec_app/app/views/form_test/uploads/new.erb +15 -2
  42. data/spec_app/app/views/hash_test/unpoly.erb +30 -0
  43. data/spec_app/app/views/pages/start.erb +2 -1
  44. data/spec_app/spec/javascripts/helpers/parse_form_data.js.coffee +17 -2
  45. data/spec_app/spec/javascripts/helpers/reset_up.js.coffee +5 -0
  46. data/spec_app/spec/javascripts/helpers/to_be_error.coffee +1 -1
  47. data/spec_app/spec/javascripts/helpers/to_match_selector.coffee +5 -0
  48. data/spec_app/spec/javascripts/up/browser_spec.js.coffee +8 -8
  49. data/spec_app/spec/javascripts/up/bus_spec.js.coffee +58 -20
  50. data/spec_app/spec/javascripts/up/classes/cache_spec.js.coffee +78 -0
  51. data/spec_app/spec/javascripts/up/classes/focus_tracker_spec.coffee +31 -0
  52. data/spec_app/spec/javascripts/up/classes/request_spec.coffee +50 -0
  53. data/spec_app/spec/javascripts/up/classes/store/memory_spec.js.coffee +67 -0
  54. data/spec_app/spec/javascripts/up/classes/store/session_spec.js.coffee +113 -0
  55. data/spec_app/spec/javascripts/up/dom_spec.js.coffee +133 -45
  56. data/spec_app/spec/javascripts/up/form_spec.js.coffee +13 -13
  57. data/spec_app/spec/javascripts/up/layout_spec.js.coffee +110 -26
  58. data/spec_app/spec/javascripts/up/link_spec.js.coffee +1 -1
  59. data/spec_app/spec/javascripts/up/modal_spec.js.coffee +1 -0
  60. data/spec_app/spec/javascripts/up/motion_spec.js.coffee +52 -51
  61. data/spec_app/spec/javascripts/up/namespace_spec.js.coffee +2 -2
  62. data/spec_app/spec/javascripts/up/params_spec.coffee +768 -0
  63. data/spec_app/spec/javascripts/up/proxy_spec.js.coffee +75 -36
  64. data/spec_app/spec/javascripts/up/syntax_spec.js.coffee +48 -15
  65. data/spec_app/spec/javascripts/up/util_spec.js.coffee +148 -131
  66. metadata +17 -5
  67. data/spec_app/spec/javascripts/up/classes/.keep +0 -0
@@ -0,0 +1,31 @@
1
+ #describe 'up.FocusTracker', ->
2
+ #
3
+ # describe '#lastField', ->
4
+ #
5
+ # it 'returns undefined if no field is focused', ->
6
+ # tracker = new up.FocusTracker()
7
+ # expect(tracker.lastField()).toBeUndefined()
8
+ #
9
+ # it 'returns a <input type="text"> after it was focused', ->
10
+ # tracker = new up.FocusTracker()
11
+ # $form = affix('form')
12
+ # $input = $form.affix('input[type=text]')
13
+ # $input.focus()
14
+ # expect(tracker.lastField()).toEqual($input[0])
15
+ #
16
+ # it 'returns a <select> after it was focused', ->
17
+ # tracker = new up.FocusTracker()
18
+ # $form = affix('form')
19
+ # $select = $form.affix('select')
20
+ # $option = $select.affix('option')
21
+ # $select.focus()
22
+ # expect(tracker.lastField()).toEqual($select[0])
23
+ #
24
+ # it 'returns the field that was most recently focused after a series of focus/blur'
25
+ #
26
+ # it 'returns undefined after a field was focused, but then detached'
27
+ #
28
+ # it 'returns a previously focused field for some time after it was blurred, so we can retrieve the last field after the user submits'
29
+ #
30
+ # it 'returns undefined after a field was blurred and some time has passed'
31
+ #
@@ -0,0 +1,50 @@
1
+ describe 'up.Request', ->
2
+
3
+ describe '#url', ->
4
+
5
+ it 'returns the given URL', ->
6
+ request = new up.Request(url: 'http://host.com/foo')
7
+ expect(request.url).toEqual('http://host.com/foo')
8
+
9
+ it 'does not include a hash anchor of the constructed URL', ->
10
+ request = new up.Request(url: 'http://host.com/foo#hash')
11
+ expect(request.url).toEqual('http://host.com/foo')
12
+
13
+ it "merges { params } for HTTP methods that don't allow a payload", ->
14
+ request = new up.Request(url: 'http://host.com/foo?urlKey=urlValue', params: { paramsKey: 'paramsValue' }, method: 'get')
15
+ expect(request.url).toEqual('http://host.com/foo?urlKey=urlValue&paramsKey=paramsValue')
16
+
17
+ it 'excludes { params } for HTTP methods that allow a payload', ->
18
+ request = new up.Request(url: 'http://host.com/foo?key=value', method: 'post')
19
+ expect(request.url).toEqual('http://host.com/foo')
20
+
21
+ describe '#method', ->
22
+
23
+ it 'defaults to "GET"', ->
24
+ request = new up.Request(url: 'http://host.com/foo')
25
+ expect(request.method).toEqual('GET')
26
+
27
+ describe '#hash', ->
28
+
29
+ it 'returns the hash anchor from the constructed URL', ->
30
+ request = new up.Request(url: 'http://host.com/foo#hash')
31
+ expect(request.hash).toEqual('#hash')
32
+
33
+ it 'returns undefined if the constructed URL had no hash anchor', ->
34
+ request = new up.Request(url: 'http://host.com/foo')
35
+ expect(request.hash).toBeUndefined()
36
+
37
+ describe '#params', ->
38
+
39
+ it 'returns the constructed params for HTTP methods that allow a payload', ->
40
+ params = { key: 'value' }
41
+ request = new up.Request(url: 'http://host.com/foo', params: params, method: 'post')
42
+ expect(request.params).toEqual(params)
43
+
44
+ it "returns undefined for HTTP methods that don't allow a payload", ->
45
+ request = new up.Request(url: 'http://host.com/foo', params: { key: 'value' }, method: 'get')
46
+ expect(request.params).toBeUndefined()
47
+
48
+ it 'returns the merged { params } and params from the URL for HTTP methods that allow a payload', ->
49
+ request = new up.Request(url: 'http://host.com/foo?urlKey=urlValue', params: { paramsKey: 'paramsValue' }, method: 'post')
50
+ expect(request.params).toEqual(paramsKey: 'paramsValue', urlKey: 'urlValue')
@@ -0,0 +1,67 @@
1
+ describe 'up.store.Memory', ->
2
+
3
+ describe '#get', ->
4
+
5
+ it 'returns an item that was previously set', ->
6
+ store = new up.store.Memory()
7
+ store.set('foo', 'value of foo')
8
+ store.set('bar', 'value of bar')
9
+
10
+ expect(store.get('foo')).toEqual('value of foo')
11
+ expect(store.get('bar')).toEqual('value of bar')
12
+
13
+ it 'returns undefined if no item with that key was set', ->
14
+ store = new up.store.Memory()
15
+ store.set('foo', 'value of foo')
16
+
17
+ expect(store.get('bar')).toBeUndefined()
18
+
19
+ describe '#keys', ->
20
+
21
+ it 'returns an array of keys in the store', ->
22
+ store = new up.store.Memory()
23
+ store.set('foo', 'value of foo')
24
+ store.set('bar', 'value of bar')
25
+
26
+ expect(store.keys().sort()).toEqual ['bar', 'foo']
27
+
28
+ it 'does not return keys for entries that were removed (bugfix)', ->
29
+ store = new up.store.Memory()
30
+ store.set('foo', 'value of foo')
31
+ store.set('bar', 'value of bar')
32
+ store.remove('bar')
33
+
34
+ expect(store.keys().sort()).toEqual ['foo']
35
+
36
+ describe '#values', ->
37
+
38
+ it 'returns an array of values in the store', ->
39
+ store = new up.store.Memory()
40
+ store.set('foo', 'value of foo')
41
+ store.set('bar', 'value of bar')
42
+
43
+ expect(store.values().sort()).toEqual ['value of bar', 'value of foo']
44
+
45
+ describe '#clear', ->
46
+
47
+ it 'removes all keys from the store', ->
48
+ store = new up.store.Memory()
49
+ store.set('foo', 'value of foo')
50
+ store.set('bar', 'value of bar')
51
+
52
+ store.clear()
53
+
54
+ expect(store.get('foo')).toBeUndefined()
55
+ expect(store.get('bar')).toBeUndefined()
56
+
57
+ describe '#remove', ->
58
+
59
+ it 'removes the given key from the store', ->
60
+ store = new up.store.Memory()
61
+ store.set('foo', 'value of foo')
62
+ store.set('bar', 'value of bar')
63
+
64
+ store.remove('foo')
65
+
66
+ expect(store.get('foo')).toBeUndefined()
67
+ expect(store.get('bar')).toEqual('value of bar')
@@ -0,0 +1,113 @@
1
+ describe 'up.store.Session', ->
2
+
3
+ u = up.util
4
+
5
+ afterEach ->
6
+ sessionStorage.removeItem('spec')
7
+
8
+ describe '#get', ->
9
+
10
+ it 'returns an item that was previously set', ->
11
+ store = new up.store.Session('spec')
12
+ store.set('foo', 'value of foo')
13
+ store.set('bar', 'value of bar')
14
+
15
+ expect(store.get('foo')).toEqual('value of foo')
16
+ expect(store.get('bar')).toEqual('value of bar')
17
+
18
+ it 'returns undefined if no item with that key was set', ->
19
+ store = new up.store.Session('spec')
20
+ store.set('foo', 'value of foo')
21
+
22
+ expect(store.get('bar')).toBeUndefined()
23
+
24
+ it 'does not read keys from a store with anotther root key', ->
25
+ store = new up.store.Session('spec.v1')
26
+ store.set('foo', 'value of foo')
27
+ expect(store.get('foo')).toEqual('value of foo')
28
+
29
+ store = new up.store.Session('spec.v2')
30
+ expect(store.get('foo')).toBeUndefined()
31
+
32
+ describe '#set', ->
33
+
34
+ it 'stores the given item in window.sessionStorage where it survives a follow without Unpoly', ->
35
+ store = new up.store.Session('spec')
36
+ store.set('foo', 'value of foo')
37
+
38
+ expect(window.sessionStorage.getItem('spec')).toContain('foo')
39
+
40
+ it 'stores boolean values across sessions', ->
41
+ store1 = new up.store.Session('spec')
42
+ store1.set('foo', true)
43
+ store1.set('bar', false)
44
+
45
+ store2 = new up.store.Session('spec')
46
+ expect(store2.get('foo')).toEqual(true)
47
+ expect(store2.get('bar')).toEqual(false)
48
+
49
+ it 'stores number values across sessions', ->
50
+ store1 = new up.store.Session('spec')
51
+ store1.set('foo', 123)
52
+
53
+ store2 = new up.store.Session('spec')
54
+ expect(store2.get('foo')).toEqual(123)
55
+
56
+ it 'stores structured values across sessions', ->
57
+ store1 = new up.store.Session('spec')
58
+ store1.set('foo', { bar: ['baz', 'bam'] })
59
+
60
+ store2 = new up.store.Session('spec')
61
+ storedValue = store2.get('foo')
62
+ expect(u.isObject(storedValue)).toBe(true)
63
+ expect(storedValue).toEqual { bar: ['baz', 'bam'] }
64
+
65
+ describe '#keys', ->
66
+
67
+ it 'returns an array of keys in the store', ->
68
+ store = new up.store.Session('spec')
69
+ store.set('foo', 'value of foo')
70
+ store.set('bar', 'value of bar')
71
+
72
+ expect(store.keys().sort()).toEqual ['bar', 'foo']
73
+
74
+ it 'does not return keys for entries that were removed (bugfix)', ->
75
+ store = new up.store.Session('spec')
76
+ store.set('foo', 'value of foo')
77
+ store.set('bar', 'value of bar')
78
+ store.remove('bar')
79
+
80
+ expect(store.keys().sort()).toEqual ['foo']
81
+
82
+ describe '#values', ->
83
+
84
+ it 'returns an array of values in the store', ->
85
+ store = new up.store.Session('spec')
86
+ store.set('foo', 'value of foo')
87
+ store.set('bar', 'value of bar')
88
+
89
+ expect(store.values().sort()).toEqual ['value of bar', 'value of foo']
90
+
91
+ describe '#clear', ->
92
+
93
+ it 'removes all keys from the store', ->
94
+ store = new up.store.Session('spec')
95
+ store.set('foo', 'value of foo')
96
+ store.set('bar', 'value of bar')
97
+
98
+ store.clear()
99
+
100
+ expect(store.get('foo')).toBeUndefined()
101
+ expect(store.get('bar')).toBeUndefined()
102
+
103
+ describe '#remove', ->
104
+
105
+ it 'removes the given key from the store', ->
106
+ store = new up.store.Session('spec')
107
+ store.set('foo', 'value of foo')
108
+ store.set('bar', 'value of bar')
109
+
110
+ store.remove('foo')
111
+
112
+ expect(store.get('foo')).toBeUndefined()
113
+ expect(store.get('bar')).toEqual('value of bar')
@@ -52,7 +52,7 @@ describe 'up.dom', ->
52
52
 
53
53
  it 'returns a promise that will be fulfilled once the server response was received and the swap transition has completed', asyncSpec (next) ->
54
54
  resolution = jasmine.createSpy()
55
- promise = up.replace('.middle', '/path', transition: 'cross-fade', duration: 50)
55
+ promise = up.replace('.middle', '/path', transition: 'cross-fade', duration: 200)
56
56
  promise.then(resolution)
57
57
  expect(resolution).not.toHaveBeenCalled()
58
58
  expect($('.middle')).toHaveText('old-middle')
@@ -61,17 +61,17 @@ describe 'up.dom', ->
61
61
  @respond()
62
62
  expect(resolution).not.toHaveBeenCalled()
63
63
 
64
- next.after 20, =>
64
+ next.after 100, =>
65
65
  expect(resolution).not.toHaveBeenCalled()
66
66
 
67
- next.after 80, =>
67
+ next.after 200, =>
68
68
  expect(resolution).toHaveBeenCalled()
69
69
 
70
- describe 'with { data } option', ->
70
+ describe 'with { params } option', ->
71
71
 
72
72
  it "uses the given params as a non-GET request's payload", asyncSpec (next) ->
73
73
  givenParams = { 'foo-key': 'foo-value', 'bar-key': 'bar-value' }
74
- up.replace('.middle', '/path', method: 'put', data: givenParams)
74
+ up.replace('.middle', '/path', method: 'put', params: givenParams)
75
75
 
76
76
  next =>
77
77
  expect(@lastRequest().data()['foo-key']).toEqual(['foo-value'])
@@ -79,7 +79,7 @@ describe 'up.dom', ->
79
79
 
80
80
  it "encodes the given params into the URL of a GET request", asyncSpec (next) ->
81
81
  givenParams = { 'foo-key': 'foo-value', 'bar-key': 'bar-value' }
82
- up.replace('.middle', '/path', method: 'get', data: givenParams)
82
+ up.replace('.middle', '/path', method: 'get', params: givenParams)
83
83
  next => expect(@lastRequest().url).toMatchUrl('/path?foo-key=foo-value&bar-key=bar-value')
84
84
 
85
85
  it 'uses a HTTP method given as { method } option', asyncSpec (next) ->
@@ -201,8 +201,8 @@ describe 'up.dom', ->
201
201
  next => @respond(responseHeaders: { 'X-Up-Location': '/other-path' })
202
202
  next => expect(location.href).toMatchUrl('/other-path')
203
203
 
204
- it 'adds params from a { data } option to the URL of a GET request', asyncSpec (next) ->
205
- up.replace('.middle', '/path', data: { 'foo-key': 'foo value', 'bar-key': 'bar value' })
204
+ it 'adds params from a { params } option to the URL of a GET request', asyncSpec (next) ->
205
+ up.replace('.middle', '/path', params: { 'foo-key': 'foo value', 'bar-key': 'bar value' })
206
206
  next => @respond()
207
207
  next => expect(location.href).toMatchUrl('/path?foo-key=foo%20value&bar-key=bar%20value')
208
208
 
@@ -1269,6 +1269,8 @@ describe 'up.dom', ->
1269
1269
  describe 'with { reveal } option', ->
1270
1270
 
1271
1271
  beforeEach ->
1272
+ up.history.config.enabled = true
1273
+
1272
1274
  @revealedHTML = []
1273
1275
  @revealedText = []
1274
1276
  @revealOptions = {}
@@ -1345,13 +1347,13 @@ describe 'up.dom', ->
1345
1347
  up.replace('.middle', '/path', failTarget: '.fail-target', reveal: false, failReveal: '&', origin: $origin)
1346
1348
 
1347
1349
  next =>
1348
- @respondWith
1349
- status: 500
1350
- responseText: """
1351
- <div class="fail-target">
1352
- new fail target text
1353
- </div>
1354
- """
1350
+ @respondWith
1351
+ status: 500
1352
+ responseText: """
1353
+ <div class="fail-target">
1354
+ new fail target text
1355
+ </div>
1356
+ """
1355
1357
 
1356
1358
  next =>
1357
1359
  expect(@revealedText).toEqual ['origin text']
@@ -1370,8 +1372,71 @@ describe 'up.dom', ->
1370
1372
 
1371
1373
  describe 'when there is an anchor #hash in the URL', ->
1372
1374
 
1373
- it 'scrolls to the top of a child with the ID of that #hash', asyncSpec (next) ->
1375
+ it 'scrolls to the top of an element with the ID of that #hash', asyncSpec (next) ->
1376
+ up.replace('.middle', '/path#hash', reveal: true)
1377
+ @responseText =
1378
+ """
1379
+ <div class="middle">
1380
+ <div id="hash"></div>
1381
+ </div>
1382
+ """
1383
+
1384
+ next =>
1385
+ @respond()
1386
+
1387
+ next =>
1388
+ expect(@revealedHTML).toEqual ['<div id="hash"></div>']
1389
+ expect(@revealOptions).toEqual jasmine.objectContaining(top: true)
1390
+
1391
+ it "scrolls to the top of an <a> element with the name of that hash", asyncSpec (next) ->
1374
1392
  up.replace('.middle', '/path#three', reveal: true)
1393
+ @responseText =
1394
+ """
1395
+ <div class="middle">
1396
+ <a name="three"></a>
1397
+ </div>
1398
+ """
1399
+
1400
+ next =>
1401
+ @respond()
1402
+
1403
+ next =>
1404
+ expect(@revealedHTML).toEqual ['<a name="three"></a>']
1405
+ expect(@revealOptions).toEqual jasmine.objectContaining(top: true)
1406
+
1407
+ it "scrolls to a hash that includes a dot character ('.') (bugfix)", asyncSpec (next) ->
1408
+ up.replace('.middle', '/path#foo.bar', reveal: true)
1409
+ @responseText =
1410
+ """
1411
+ <div class="middle">
1412
+ <a name="foo.bar"></a>
1413
+ </div>
1414
+ """
1415
+
1416
+ next =>
1417
+ @respond()
1418
+
1419
+ next =>
1420
+ expect(@revealedHTML).toEqual ['<a name="foo.bar"></a>']
1421
+ expect(@revealOptions).toEqual jasmine.objectContaining(top: true)
1422
+
1423
+ it 'does not scroll if { reveal: false } is also set', asyncSpec (next) ->
1424
+ up.replace('.middle', '/path#hash', reveal: false)
1425
+ @responseText =
1426
+ """
1427
+ <div class="middle">
1428
+ <div id="hash"></div>
1429
+ </div>
1430
+ """
1431
+
1432
+ next =>
1433
+ @respond()
1434
+
1435
+ next =>
1436
+ expect(@revealMock).not.toHaveBeenCalled()
1437
+
1438
+ it 'reveals multiple consecutive #hash targets with the same URL (bugfix)', asyncSpec (next) ->
1439
+ up.replace('.middle', '/path#two', reveal: true)
1375
1440
  @responseText =
1376
1441
  """
1377
1442
  <div class="middle">
@@ -1383,25 +1448,26 @@ describe 'up.dom', ->
1383
1448
 
1384
1449
  next =>
1385
1450
  @respond()
1451
+ up.replace('.middle', '/path#three', reveal: true)
1452
+ # response is already cached
1386
1453
 
1387
1454
  next =>
1388
- expect(@revealedHTML).toEqual ['<div id="three">three</div>']
1389
- expect(@revealOptions).toEqual jasmine.objectContaining(top: true)
1455
+ expect(@revealedText).toEqual ['two', 'three']
1390
1456
 
1391
- it "reveals the entire element if it has no child with the ID of that #hash", asyncSpec (next) ->
1392
- up.replace('.middle', '/path#four', reveal: true)
1457
+ it "does not scroll if there is no element with the ID of that #hash", asyncSpec (next) ->
1458
+ up.replace('.middle', '/path#hash', reveal: true)
1459
+ @responseText =
1460
+ """
1461
+ <div class="middle">
1462
+ </div>
1463
+ """
1393
1464
 
1394
1465
  next =>
1395
- @responseText =
1396
- """
1397
- <div class="middle">
1398
- new-middle
1399
- </div>
1400
- """
1401
1466
  @respond()
1402
1467
 
1403
1468
  next =>
1404
- expect(@revealedText).toEqual ['new-middle']
1469
+ expect(@revealMock).not.toHaveBeenCalled()
1470
+
1405
1471
 
1406
1472
  it 'reveals a new element that is being appended', (done) ->
1407
1473
  promise = up.replace('.middle:after', '/path', reveal: true)
@@ -1621,7 +1687,7 @@ describe 'up.dom', ->
1621
1687
 
1622
1688
  it 'calls destructors when the replaced element is a singleton element like <body> (bugfix)', asyncSpec (next) ->
1623
1689
  # shouldSwapElementsDirectly() is true for body, but can't have the example replace the Jasmine test runner UI
1624
- up.motion.knife.mock('isSingletonElement').and.callFake ($element) -> $element.is('.container')
1690
+ up.util.knife.mock('isSingletonElement').and.callFake ($element) -> $element.is('.container')
1625
1691
  destructor = jasmine.createSpy('destructor')
1626
1692
  up.compiler '.container', -> destructor
1627
1693
  $container = affix('.container')
@@ -1696,7 +1762,7 @@ describe 'up.dom', ->
1696
1762
 
1697
1763
  it 'morphs between the old and new element', asyncSpec (next) ->
1698
1764
  affix('.element.v1').text('version 1')
1699
- up.extract('.element', '<div class="element v2">version 2</div>', transition: 'cross-fade', duration: 100, easing: 'linear')
1765
+ up.extract('.element', '<div class="element v2">version 2</div>', transition: 'cross-fade', duration: 200, easing: 'linear')
1700
1766
 
1701
1767
  $old = undefined
1702
1768
  $new = undefined
@@ -1706,23 +1772,23 @@ describe 'up.dom', ->
1706
1772
  $new = $('.element.v2')
1707
1773
 
1708
1774
  expect($old).toHaveLength(1)
1709
- expect(u.opacity($old)).toBeAround(1.0, 0.2)
1775
+ expect(u.opacity($old)).toBeAround(1.0, 0.15)
1710
1776
 
1711
1777
  expect($new).toHaveLength(1)
1712
- expect(u.opacity($new)).toBeAround(0.0, 0.2)
1778
+ expect(u.opacity($new)).toBeAround(0.0, 0.15)
1713
1779
 
1714
- next.after 50, =>
1715
- expect(u.opacity($old)).toBeAround(0.5, 0.2)
1716
- expect(u.opacity($new)).toBeAround(0.5, 0.2)
1780
+ next.after 100, =>
1781
+ expect(u.opacity($old)).toBeAround(0.5, 0.3)
1782
+ expect(u.opacity($new)).toBeAround(0.5, 0.3)
1717
1783
 
1718
- next.after (50 + 30), =>
1784
+ next.after (100 + 70), =>
1719
1785
  expect(u.opacity($new)).toBeAround(1.0, 0.1)
1720
1786
  expect($old).toBeDetached()
1721
1787
 
1722
1788
 
1723
1789
  it 'ignores a { transition } option when replacing a singleton element like <body>', asyncSpec (next) ->
1724
1790
  # shouldSwapElementsDirectly() is true for body, but can't have the example replace the Jasmine test runner UI
1725
- up.motion.knife.mock('isSingletonElement').and.callFake ($element) -> $element.is('.container')
1791
+ up.util.knife.mock('isSingletonElement').and.callFake ($element) -> $element.is('.container')
1726
1792
 
1727
1793
  affix('.container').text('old text')
1728
1794
 
@@ -1814,7 +1880,7 @@ describe 'up.dom', ->
1814
1880
  promise.then(resolution)
1815
1881
  expect(resolution).not.toHaveBeenCalled()
1816
1882
 
1817
- u.setTimer 40, ->
1883
+ u.setTimer 20, ->
1818
1884
  expect(resolution).not.toHaveBeenCalled()
1819
1885
 
1820
1886
  u.setTimer 200, ->
@@ -1991,7 +2057,6 @@ describe 'up.dom', ->
1991
2057
 
1992
2058
  it "only emits an event up:fragment:kept, but not an event up:fragment:inserted", asyncSpec (next) ->
1993
2059
  insertedListener = jasmine.createSpy('subscriber to up:fragment:inserted')
1994
- up.on('up:fragment:inserted', insertedListener)
1995
2060
  keptListener = jasmine.createSpy('subscriber to up:fragment:kept')
1996
2061
  up.on('up:fragment:kept', keptListener)
1997
2062
  up.on 'up:fragment:inserted', insertedListener
@@ -2323,15 +2388,15 @@ describe 'up.dom', ->
2323
2388
 
2324
2389
  it 'runs an animation before removal with { animate } option', asyncSpec (next) ->
2325
2390
  $element = affix('.element')
2326
- up.destroy($element, animation: 'fade-out', duration: 150, easing: 'linear')
2391
+ up.destroy($element, animation: 'fade-out', duration: 200, easing: 'linear')
2327
2392
 
2328
2393
  next ->
2329
- expect($element).toHaveOpacity(1.0, 0.2)
2394
+ expect($element).toHaveOpacity(1.0, 0.15)
2330
2395
 
2331
- next.after 75, ->
2332
- expect($element).toHaveOpacity(0.5, 0.2)
2396
+ next.after 100, ->
2397
+ expect($element).toHaveOpacity(0.5, 0.3)
2333
2398
 
2334
- next.after (75 + 40), ->
2399
+ next.after (100 + 75), ->
2335
2400
  expect($element).toBeDetached()
2336
2401
 
2337
2402
  it 'calls destructors for custom elements', (done) ->
@@ -2477,8 +2542,31 @@ describe 'up.dom', ->
2477
2542
  next =>
2478
2543
  expect(up.browser.navigate).toHaveBeenCalledWith('/source', jasmine.anything())
2479
2544
 
2545
+ describe 'up.dom.layerOf', ->
2546
+
2547
+ it 'returns "popup" for an element in a popup over the page', ->
2548
+ $popup = affix('.up-popup')
2549
+ $element = $popup.affix('.element')
2550
+ expect(up.dom.layerOf($element)).toEqual('popup')
2551
+
2552
+ it 'returns "popup" for an element in a popup over a modal', ->
2553
+ $modal = affix('.up-modal')
2554
+ $popupInModal = $modal.affix('.up-popup')
2555
+ $element = $popupInModal.affix('.element')
2556
+ expect(up.dom.layerOf($element)).toEqual('popup')
2557
+
2558
+ it 'returns "modal" for an element in a modal', ->
2559
+ $modal = affix('.up-modal')
2560
+ $element = $modal.affix('.element')
2561
+ expect(up.dom.layerOf($element)).toEqual('modal')
2562
+
2563
+ it 'returns "page" for an element below a modal or popup', ->
2564
+ $element = affix('.element')
2565
+ expect(up.dom.layerOf($element)).toEqual('page')
2480
2566
 
2481
- describe 'up.reset', ->
2567
+ it 'returns undefined for an empty jQuery collection', ->
2568
+ expect(up.dom.layerOf($())).toBeUndefined()
2482
2569
 
2483
- it 'should have tests'
2570
+ it 'returns undefined for undefined', ->
2571
+ expect(up.dom.layerOf(undefined)).toBeUndefined()
2484
2572