unpoly-rails 0.37.0 → 0.50.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 (88) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +127 -25
  3. data/LICENSE +1 -1
  4. data/README_RAILS.md +4 -2
  5. data/Rakefile +6 -1
  6. data/dist/unpoly.js +3192 -2198
  7. data/dist/unpoly.min.js +4 -3
  8. data/lib/assets/javascripts/unpoly/browser.coffee +51 -63
  9. data/lib/assets/javascripts/unpoly/bus.coffee +58 -33
  10. data/lib/assets/javascripts/unpoly/classes/cache.coffee +117 -0
  11. data/lib/assets/javascripts/unpoly/{dom → classes}/extract_cascade.coffee +3 -3
  12. data/lib/assets/javascripts/unpoly/{dom → classes}/extract_plan.coffee +1 -1
  13. data/lib/assets/javascripts/unpoly/classes/field_observer.coffee +57 -0
  14. data/lib/assets/javascripts/unpoly/classes/follow_variant.coffee +52 -0
  15. data/lib/assets/javascripts/unpoly/classes/motion_tracker.coffee +95 -0
  16. data/lib/assets/javascripts/unpoly/classes/record.coffee +16 -0
  17. data/lib/assets/javascripts/unpoly/classes/request.coffee +228 -0
  18. data/lib/assets/javascripts/unpoly/classes/response.coffee +138 -0
  19. data/lib/assets/javascripts/unpoly/dom.coffee +151 -142
  20. data/lib/assets/javascripts/unpoly/feedback.coffee +67 -38
  21. data/lib/assets/javascripts/unpoly/form.coffee +156 -139
  22. data/lib/assets/javascripts/unpoly/history.coffee +22 -19
  23. data/lib/assets/javascripts/unpoly/layout.coffee +108 -90
  24. data/lib/assets/javascripts/unpoly/link.coffee +159 -158
  25. data/lib/assets/javascripts/unpoly/log.coffee +5 -5
  26. data/lib/assets/javascripts/unpoly/modal.coffee +93 -81
  27. data/lib/assets/javascripts/unpoly/motion.coffee +291 -250
  28. data/lib/assets/javascripts/unpoly/popup.coffee +67 -53
  29. data/lib/assets/javascripts/unpoly/protocol.coffee +67 -16
  30. data/lib/assets/javascripts/unpoly/proxy.coffee +282 -211
  31. data/lib/assets/javascripts/unpoly/rails.coffee +3 -14
  32. data/lib/assets/javascripts/unpoly/syntax.coffee +54 -49
  33. data/lib/assets/javascripts/unpoly/tooltip.coffee +18 -25
  34. data/lib/assets/javascripts/unpoly/util.coffee +236 -477
  35. data/lib/assets/javascripts/unpoly.coffee +1 -1
  36. data/lib/unpoly/rails/inspector.rb +67 -22
  37. data/lib/unpoly/rails/version.rb +1 -1
  38. data/package.json +1 -1
  39. data/spec_app/Gemfile.lock +13 -13
  40. data/spec_app/app/assets/javascripts/integration_test.coffee +1 -0
  41. data/spec_app/app/assets/javascripts/jasmine_specs.coffee +1 -1
  42. data/spec_app/app/assets/stylesheets/jasmine_specs.sass +10 -0
  43. data/spec_app/app/controllers/binding_test_controller.rb +19 -2
  44. data/spec_app/app/controllers/method_test_controller.rb +16 -0
  45. data/spec_app/app/views/layouts/jasmine_rails/spec_runner.html.erb +20 -0
  46. data/spec_app/app/views/method_test/form_target.erb +17 -0
  47. data/spec_app/app/views/method_test/page1.erb +11 -0
  48. data/spec_app/app/views/method_test/page2.erb +6 -0
  49. data/spec_app/app/views/pages/start.erb +33 -19
  50. data/spec_app/config/initializers/assets.rb +5 -0
  51. data/spec_app/config/routes.rb +3 -0
  52. data/spec_app/spec/controllers/binding_test_controller_spec.rb +82 -27
  53. data/spec_app/spec/javascripts/helpers/agent_detector.coffee +17 -0
  54. data/spec_app/spec/javascripts/helpers/async_sequence.js.coffee +102 -0
  55. data/spec_app/spec/javascripts/helpers/last_request.js.coffee +1 -1
  56. data/spec_app/spec/javascripts/helpers/mock_ajax.js.coffee +5 -2
  57. data/spec_app/spec/javascripts/helpers/promise_state.js +18 -0
  58. data/spec_app/spec/javascripts/helpers/protect_jasmine_runner.coffee +9 -0
  59. data/spec_app/spec/javascripts/helpers/reset_history.js.coffee +22 -0
  60. data/spec_app/spec/javascripts/helpers/reset_up.js.coffee +11 -3
  61. data/spec_app/spec/javascripts/helpers/show_lib_versions.coffee +10 -0
  62. data/spec_app/spec/javascripts/helpers/to_be_error.coffee +5 -0
  63. data/spec_app/spec/javascripts/helpers/to_match_url.coffee +13 -0
  64. data/spec_app/spec/javascripts/helpers/trigger.js.coffee +13 -6
  65. data/spec_app/spec/javascripts/up/browser_spec.js.coffee +92 -33
  66. data/spec_app/spec/javascripts/up/bus_spec.js.coffee +64 -15
  67. data/spec_app/spec/javascripts/up/classes/.keep +0 -0
  68. data/spec_app/spec/javascripts/up/classes/cache_spec.js.coffee +1 -0
  69. data/spec_app/spec/javascripts/up/dom_spec.js.coffee +759 -551
  70. data/spec_app/spec/javascripts/up/feedback_spec.js.coffee +155 -82
  71. data/spec_app/spec/javascripts/up/form_spec.js.coffee +490 -349
  72. data/spec_app/spec/javascripts/up/history_spec.js.coffee +226 -179
  73. data/spec_app/spec/javascripts/up/layout_spec.js.coffee +253 -185
  74. data/spec_app/spec/javascripts/up/link_spec.js.coffee +416 -270
  75. data/spec_app/spec/javascripts/up/modal_spec.js.coffee +459 -330
  76. data/spec_app/spec/javascripts/up/motion_spec.js.coffee +198 -153
  77. data/spec_app/spec/javascripts/up/namespace_spec.js.coffee +9 -0
  78. data/spec_app/spec/javascripts/up/popup_spec.js.coffee +240 -175
  79. data/spec_app/spec/javascripts/up/protocol_spec.js.coffee +38 -0
  80. data/spec_app/spec/javascripts/up/proxy_spec.js.coffee +777 -303
  81. data/spec_app/spec/javascripts/up/rails_spec.js.coffee +24 -8
  82. data/spec_app/spec/javascripts/up/syntax_spec.js.coffee +40 -23
  83. data/spec_app/spec/javascripts/up/tooltip_spec.js.coffee +80 -66
  84. data/spec_app/spec/javascripts/up/util_spec.js.coffee +227 -201
  85. data/spec_app/vendor/asset-libs/es6-promise-4.1.6/es6-promise.auto.js +1159 -0
  86. metadata +30 -7
  87. data/spec_app/spec/javascripts/helpers/reset_path.js.coffee +0 -7
  88. data/spec_app/spec/javascripts/helpers/to_equal_url.coffee +0 -11
@@ -1,91 +1,119 @@
1
1
  describe 'up.modal', ->
2
2
 
3
3
  u = up.util
4
-
5
- describe 'JavaScript functions', ->
6
4
 
7
- assumedScrollbarWidth = 15
5
+ beforeEach ->
6
+ up.modal.config.openDuration = 5
7
+ up.modal.config.closeDuration = 5
8
+
9
+ describe 'JavaScript functions', ->
10
+ # Safari overlays the scrollbar tracker over the picture.
11
+ # The scrollbar does not take space.
12
+ assumedScrollbarWidth = if AgentDetector.isSafari() then 0 else 15
8
13
 
9
14
  describe 'up.modal.follow', ->
10
15
 
11
16
  it "loads the given link's destination in a dialog window", (done) ->
12
17
  $link = affix('a[href="/path/to"][up-modal=".middle"]').text('link')
13
18
  promise = up.modal.follow($link)
14
- expect(@lastRequest().url).toMatch /\/path\/to$/
15
- @respondWith """
19
+
20
+ u.nextFrame =>
21
+ expect(@lastRequest().url).toMatch /\/path\/to$/
22
+ @respondWith """
23
+ <div class="before">new-before</div>
24
+ <div class="middle">new-middle</div>
25
+ <div class="after">new-after</div>
26
+ """
27
+
28
+ promise.then =>
29
+ expect($('.up-modal')).toExist()
30
+ expect($('.up-modal-dialog')).toExist()
31
+ expect($('.up-modal-dialog .middle')).toExist()
32
+ expect($('.up-modal-dialog .middle')).toHaveText('new-middle')
33
+ expect($('.up-modal-dialog .before')).not.toExist()
34
+ expect($('.up-modal-dialog .after')).not.toExist()
35
+ done()
36
+
37
+ describe 'up.modal.extract', ->
38
+
39
+ it 'opens a modal by extracting the given selector from the given HTML string', (done) ->
40
+ up.history.config.enabled = true
41
+
42
+ oldHref = location.href
43
+ promise = up.modal.extract '.middle', """
16
44
  <div class="before">new-before</div>
17
45
  <div class="middle">new-middle</div>
18
46
  <div class="after">new-after</div>
19
47
  """
20
- promise.then ->
48
+
49
+ promise.then =>
21
50
  expect($('.up-modal')).toExist()
22
51
  expect($('.up-modal-dialog')).toExist()
23
52
  expect($('.up-modal-dialog .middle')).toExist()
24
53
  expect($('.up-modal-dialog .middle')).toHaveText('new-middle')
25
54
  expect($('.up-modal-dialog .before')).not.toExist()
26
55
  expect($('.up-modal-dialog .after')).not.toExist()
27
- done()
28
-
29
- describe 'up.modal.extract', ->
30
-
31
- it 'opens a modal by extracting the given selector from the given HTML string', ->
32
- oldHref = location.href
33
- up.modal.extract '.middle', """
34
- <div class="before">new-before</div>
35
- <div class="middle">new-middle</div>
36
- <div class="after">new-after</div>
37
- """
38
- expect($('.up-modal')).toExist()
39
- expect($('.up-modal-dialog')).toExist()
40
- expect($('.up-modal-dialog .middle')).toExist()
41
- expect($('.up-modal-dialog .middle')).toHaveText('new-middle')
42
- expect($('.up-modal-dialog .before')).not.toExist()
43
- expect($('.up-modal-dialog .after')).not.toExist()
44
56
 
45
- # Can't change URLs
46
- expect(location.href).toEqual(oldHref)
57
+ # Can't change URLs
58
+ expect(location.href).toEqual(oldHref)
59
+ done()
47
60
 
48
61
  describe 'up.modal.visit', ->
49
62
 
50
- it "requests the given URL and places the given selector into a modal", ->
51
- up.modal.visit '/foo', target: '.middle'
63
+ it "requests the given URL and places the given selector into a modal", (done) ->
64
+ up.history.config.enabled = true
52
65
 
53
- @respondWith """
54
- <div class="before">new-before</div>
55
- <div class="middle">new-middle</div>
56
- <div class="after">new-after</div>
57
- """
66
+ promise = up.modal.visit('/foo', target: '.middle')
58
67
 
59
- expect('.up-modal').toExist()
60
- expect('.up-modal-dialog').toExist()
61
- expect('.up-modal-dialog .middle').toExist()
62
- expect('.up-modal-dialog .middle').toHaveText('new-middle')
63
- expect('.up-modal-dialog .before').not.toExist()
64
- expect('.up-modal-dialog .after').not.toExist()
68
+ u.nextFrame =>
69
+ @respondWith """
70
+ <div class="before">new-before</div>
71
+ <div class="middle">new-middle</div>
72
+ <div class="after">new-after</div>
73
+ """
65
74
 
66
- expect(location.pathname).toEqualUrl('/foo')
75
+ promise.then =>
76
+ expect('.up-modal').toExist()
77
+ expect('.up-modal-dialog').toExist()
78
+ expect('.up-modal-dialog .middle').toExist()
79
+ expect('.up-modal-dialog .middle').toHaveText('new-middle')
80
+ expect('.up-modal-dialog .before').not.toExist()
81
+ expect('.up-modal-dialog .after').not.toExist()
82
+ expect(location.pathname).toMatchUrl('/foo')
83
+ done()
67
84
 
68
- it "doesn't create an .up-modal frame and replaces { failTarget } if the server returns a non-200 response", ->
85
+ it "doesn't create an .up-modal frame and replaces { failTarget } if the server returns a non-200 response", (done) ->
69
86
  affix('.error').text('old error')
70
87
 
71
- up.modal.visit '/foo', target: '.target', failTarget: '.error'
88
+ promise = up.modal.visit('/foo', target: '.target', failTarget: '.error')
72
89
 
73
- @respondWith
74
- status: 500
75
- responseText: """
76
- <div class="target">new target</div>
77
- <div class="error">new error</div>
78
- """
90
+ u.nextFrame =>
91
+ @respondWith
92
+ status: 500
93
+ responseText: """
94
+ <div class="target">new target</div>
95
+ <div class="error">new error</div>
96
+ """
97
+
98
+ promise.catch =>
99
+ expect('.up-modal').not.toExist()
100
+ expect('.error').toHaveText('new error')
101
+ done()
79
102
 
80
- expect('.up-modal').not.toExist()
81
- expect('.error').toHaveText('new error')
103
+ it 'always makes a request for the given selector, and does not "improve" the selector with a fallback', asyncSpec (next) ->
104
+ up.modal.visit('/foo', target: '.target', failTarget: '.error')
105
+ next =>
106
+ expect(jasmine.Ajax.requests.count()).toEqual(1)
107
+ headers = @lastRequest().requestHeaders
108
+ expect(headers['X-Up-Target']).toEqual('.target')
82
109
 
83
110
  describe 'preventing elements from jumping as scrollbars change', ->
84
111
 
85
112
  it "brings its own scrollbar, padding the body on the right", (done) ->
86
113
  promise = up.modal.visit('/foo', target: '.container')
87
114
 
88
- @respondWith('<div class="container">text</div>')
115
+ u.nextFrame =>
116
+ @respondWith('<div class="container">text</div>')
89
117
 
90
118
  promise.then ->
91
119
  $modal = $('.up-modal')
@@ -103,18 +131,21 @@ describe 'up.modal', ->
103
131
 
104
132
  it "gives the scrollbar to .up-modal instead of .up-modal-viewport while animating, so we don't see scaled scrollbars in a zoom-in animation", (done) ->
105
133
  openPromise = up.modal.extract('.container', '<div class="container">text</div>', animation: 'fade-in', duration: 100)
106
- $modal = $('.up-modal')
107
- $viewport = $modal.find('.up-modal-viewport')
108
- expect($modal.css('overflow-y')).toEqual('scroll')
109
- expect($viewport.css('overflow-y')).toEqual('hidden')
110
- openPromise.then ->
111
- expect($modal.css('overflow-y')).not.toEqual('scroll')
112
- expect($viewport.css('overflow-y')).toEqual('scroll')
113
- closePromise = up.modal.close(animation: 'fade-out', duration: 200)
114
- u.nextFrame ->
115
- expect($modal.css('overflow-y')).toEqual('scroll')
116
- expect($viewport.css('overflow-y')).toEqual('hidden')
117
- done()
134
+
135
+ u.nextFrame =>
136
+ $modal = $('.up-modal')
137
+ $viewport = $modal.find('.up-modal-viewport')
138
+ expect($modal.css('overflow-y')).toEqual('scroll')
139
+ expect($viewport.css('overflow-y')).toEqual('hidden')
140
+
141
+ openPromise.then ->
142
+ expect($modal.css('overflow-y')).not.toEqual('scroll')
143
+ expect($viewport.css('overflow-y')).toEqual('scroll')
144
+ closePromise = up.modal.close(animation: 'fade-out', duration: 200)
145
+ u.nextFrame ->
146
+ expect($modal.css('overflow-y')).toEqual('scroll')
147
+ expect($viewport.css('overflow-y')).toEqual('hidden')
148
+ done()
118
149
 
119
150
  it 'does not add right padding to the body if the body has overflow-y: hidden', (done) ->
120
151
  restoreBody = u.temporaryCss($('body'), 'overflow-y': 'hidden')
@@ -153,7 +184,8 @@ describe 'up.modal', ->
153
184
 
154
185
  promise = up.modal.visit('/foo', target: '.container')
155
186
 
156
- @respondWith('<div class="container">text</div>')
187
+ u.nextFrame =>
188
+ @respondWith('<div class="container">text</div>')
157
189
 
158
190
  promise.then ->
159
191
  expect(parseInt($anchoredElement.css('right'))).toBeAround(30 + assumedScrollbarWidth, 10)
@@ -166,28 +198,54 @@ describe 'up.modal', ->
166
198
 
167
199
  it 'does not open multiple modals or pad the body twice if the user starts loading a second modal before the first was done loading', (done) ->
168
200
  up.modal.config.closeDuration = 10
169
- promise1 = up.modal.visit('/path1', target: '.container', animation: 'fade-in', duration: 50)
170
- promise2 = up.modal.visit('/path2', target: '.container', animation: 'fade-in', duration: 50)
171
- @respondWith('<div class="container">response1</div>')
172
201
 
173
- u.setTimer 120, =>
174
- # up.modal.visit (visitAsap) will start the request only after the last modal
175
- # has finished opening and closing: 50 + 10 ms
202
+ # Load a first modal
203
+ up.modal.visit('/path1', target: '.container', animation: 'fade-in', duration: 50)
204
+
205
+ # Immediately load a second modal in the same frame.
206
+ # This will discard the first request immediately.
207
+ up.modal.visit('/path2', target: '.container', animation: 'fade-in', duration: 50)
208
+
209
+ u.nextFrame =>
210
+ # The second modal has survived
211
+ expect(jasmine.Ajax.requests.count()).toEqual(1)
212
+ expect(@lastRequest().url).toMatchUrl('/path2')
213
+
214
+ # We send the response for 2
176
215
  @respondWith('<div class="container">response2</div>')
177
- $.when(promise1, promise2).then ->
178
- expect($('.up-modal').length).toBe(1)
179
- expect($('.up-modal-dialog').length).toBe(1)
180
- expect($('.container')).toHaveText('response2')
181
- bodyPadding = parseInt($('body').css('padding-right'))
182
- expect(bodyPadding).toBeAround(assumedScrollbarWidth, 10)
183
- expect(bodyPadding).not.toBeAround(2 * assumedScrollbarWidth, 2 * 5)
184
- done()
185
216
 
186
- it 'closes the current modal and wait for its close animation to finish before starting the open animation of a second modal', (done) ->
217
+ u.setTimer 10, =>
218
+ # The second modal is now opening
219
+ up.modal.visit('/path3', target: '.container', animation: 'fade-in', duration: 50)
220
+
221
+ # Load a third modal before the second was done opening
222
+ u.nextFrame =>
223
+ # Since we're still opening the second modal, no request has been made.
224
+ expect(jasmine.Ajax.requests.count()).toEqual(1)
225
+
226
+ u.setTimer 180, =>
227
+ # Now that the second modal has opened, we make the request to /path3
228
+ expect(jasmine.Ajax.requests.count()).toEqual(2)
229
+ expect(@lastRequest().url).toMatchUrl('/path3')
230
+
231
+ @respondWith('<div class="container">response3</div>')
232
+
233
+ u.setTimer 180, =>
234
+ expect(jasmine.Ajax.requests.count()).toEqual(2)
235
+ expect($('.up-modal').length).toBe(1)
236
+ expect($('.up-modal-dialog').length).toBe(1)
237
+ expect($('.container')).toHaveText('response3')
238
+ bodyPadding = parseInt($('body').css('padding-right'))
239
+ expect(bodyPadding).toBeAround(assumedScrollbarWidth, 10)
240
+ if assumedScrollbarWidth > 0 # this test does not make sense on Safari
241
+ expect(bodyPadding).not.toBeAround(2 * assumedScrollbarWidth, 2 * 5)
242
+ done()
243
+
244
+ it 'closes the current modal and wait for its close animation to finish before starting the open animation of a second modal', asyncSpec (next) ->
187
245
  up.modal.config.openAnimation = 'fade-in'
188
- up.modal.config.openDuration = 5
246
+ up.modal.config.openDuration = 50
189
247
  up.modal.config.closeAnimation = 'fade-out'
190
- up.modal.config.closeDuration = 60
248
+ up.modal.config.closeDuration = 50
191
249
 
192
250
  events = []
193
251
  u.each ['up:modal:open', 'up:modal:opened', 'up:modal:close', 'up:modal:closed'], (event) ->
@@ -196,64 +254,60 @@ describe 'up.modal', ->
196
254
 
197
255
  up.modal.extract('.target', '<div class="target">response1</div>')
198
256
 
199
- # First modal is starting opening animation
200
- expect(events).toEqual ['up:modal:open']
201
- expect($('.target')).toHaveText('response1')
257
+ next =>
258
+ # First modal is starting opening animation (will take 50 ms)
259
+ expect(events).toEqual ['up:modal:open']
260
+ expect($('.target')).toHaveText('response1')
202
261
 
203
- u.setTimer 80, ->
204
- # First modal has completed opening animation
262
+ next.after 60, =>
263
+ # First modal has completed opening animation after 50 ms
205
264
  expect(events).toEqual ['up:modal:open', 'up:modal:opened']
206
265
  expect($('.target')).toHaveText('response1')
207
266
 
208
- # We open another modal, which will cause the first modal to start closing
267
+ # We open another modal, which will cause the first modal to start closing (will take 50 ms)
209
268
  up.modal.extract('.target', '<div class="target">response2</div>')
210
269
 
270
+ next.after 25, =>
271
+ # Second modal is still waiting for first modal's closing animaton to finish.
272
+ expect(events).toEqual ['up:modal:open', 'up:modal:opened', 'up:modal:close']
211
273
  expect($('.target')).toHaveText('response1')
212
274
 
213
- u.setTimer 20, ->
214
-
215
- # Second modal is still waiting for first modal's closing animaton to finish.
216
- expect(events).toEqual ['up:modal:open', 'up:modal:opened', 'up:modal:close']
217
- expect($('.target')).toHaveText('response1')
218
-
219
- u.setTimer 200, ->
220
-
221
- # First modal has finished closing, second modal has finished opening.
222
- expect(events).toEqual ['up:modal:open', 'up:modal:opened', 'up:modal:close', 'up:modal:closed', 'up:modal:open', 'up:modal:opened']
223
- expect($('.target')).toHaveText('response2')
275
+ # I don't know why this spec is so off with timing.
276
+ # We need to add 100ms to make it pass all of the time.
277
+ next.after (25 + 50 + 100), =>
278
+ # First modal has finished closing, second modal has finished opening.
279
+ expect(events).toEqual ['up:modal:open', 'up:modal:opened', 'up:modal:close', 'up:modal:closed', 'up:modal:open', 'up:modal:opened']
280
+ expect($('.target')).toHaveText('response2')
224
281
 
225
- done()
226
-
227
- it 'closes an opening modal if a second modal starts opening before the first modal has finished its open animation', (done) ->
228
- up.modal.config.openAnimation = 'fade-in'
229
- up.modal.config.openDuration = 50
230
- up.modal.config.closeAnimation = 'fade-out'
231
- up.modal.config.closeDuration = 50
232
-
233
- # Open the first modal
234
- up.modal.extract('.target', '<div class="target">response1</div>')
282
+ it 'closes an opening modal if a second modal starts opening before the first modal has finished its open animation', asyncSpec (next) ->
283
+ up.modal.config.openAnimation = 'fade-in'
284
+ up.modal.config.openDuration = 50
285
+ up.modal.config.closeAnimation = 'fade-out'
286
+ up.modal.config.closeDuration = 50
235
287
 
236
- u.setTimer 10, ->
237
- # First modal is still in its opening animation
238
- expect($('.target')).toHaveText('response1')
288
+ # Open the first modal
289
+ up.modal.extract('.target', '<div class="target">response1</div>')
239
290
 
240
- # Open a second modal
241
- up.modal.extract('.target', '<div class="target">response2</div>')
291
+ next.after 10, =>
292
+ # First modal is still in its opening animation
293
+ expect($('.target')).toHaveText('response1')
242
294
 
243
- # First modal is starting close animation. Second modal waits for that.
244
- expect($('.target')).toHaveText('response1')
295
+ # Open a second modal
296
+ up.modal.extract('.target', '<div class="target">response2</div>')
245
297
 
246
- u.setTimer 10, ->
247
- # Second modal is still waiting for first modal's closing animaton to finish.
248
- expect($('.target')).toHaveText('response1')
298
+ next =>
299
+ # First modal is starting close animation. Second modal waits for that.
300
+ expect($('.target')).toHaveText('response1')
249
301
 
250
- u.setTimer 150, ->
251
- # First modal has finished closing, second modal has finished opening.
252
- expect($('.target')).toHaveText('response2')
302
+ next.after 10, =>
303
+ # Second modal is still waiting for first modal's closing animaton to finish.
304
+ expect($('.target')).toHaveText('response1')
253
305
 
254
- done()
306
+ next.after 150, =>
307
+ # First modal has finished closing, second modal has finished opening.
308
+ expect($('.target')).toHaveText('response2')
255
309
 
256
- it 'uses the correct flavor config for the first and second modal', (done) ->
310
+ it 'uses the correct flavor config for the first and second modal', asyncSpec (next) ->
257
311
  up.modal.config.openAnimation = 'fade-in'
258
312
  up.modal.config.openDuration = 20
259
313
  up.modal.config.closeAnimation = 'fade-out'
@@ -268,98 +322,115 @@ describe 'up.modal', ->
268
322
  animations.push
269
323
  text: u.trim($element.find('.target').text())
270
324
  animation: animation
271
- deferred = $.Deferred()
325
+ deferred = u.newDeferred()
272
326
  u.setTimer options.duration, -> deferred.resolve()
273
327
  deferred.promise()
274
328
 
275
329
  up.modal.extract('.target', '<div class="target">response1</div>')
276
- expect(animations).toEqual [
277
- { animation: 'fade-in', text: 'response1' }
278
- ]
279
330
 
280
- u.setTimer 30, ->
331
+ next =>
332
+ expect(animations).toEqual [
333
+ { animation: 'fade-in', text: 'response1' }
334
+ ]
281
335
 
336
+ next.after 30, =>
282
337
  # first modal is now done animating
283
338
  expect(animations).toEqual [
284
339
  { animation: 'fade-in', text: 'response1' }
285
340
  ]
286
341
 
287
-
288
342
  up.modal.extract('.target', '<div class="target">response2</div>', flavor: 'custom-drawer')
343
+
344
+ next =>
345
+ expect(animations).toEqual [
346
+ { animation: 'fade-in', text: 'response1' },
347
+ { animation: 'fade-out', text: 'response1' },
348
+ ]
349
+
350
+ next.after 30, =>
289
351
  expect(animations).toEqual [
290
352
  { animation: 'fade-in', text: 'response1' },
291
353
  { animation: 'fade-out', text: 'response1' },
354
+ { animation: 'move-from-right', text: 'response2' }
292
355
  ]
293
356
 
294
- u.setTimer 30, ->
357
+ expect($('.up-modal').attr('up-flavor')).toEqual('custom-drawer')
295
358
 
296
- expect(animations).toEqual [
297
- { animation: 'fade-in', text: 'response1' },
298
- { animation: 'fade-out', text: 'response1' },
299
- { animation: 'move-from-right', text: 'response2' }
300
- ]
301
359
 
302
- expect($('.up-modal').attr('up-flavor')).toEqual('custom-drawer')
360
+ it 'never resolves the open() promise and shows no error if close() was called before the response was received', asyncSpec (next) ->
361
+ openPromise = up.modal.visit('/foo', target: '.container')
303
362
 
304
- done()
363
+ next =>
364
+ up.modal.close()
305
365
 
366
+ next =>
367
+ respond = => @respondWith('<div class="container">text</div>')
368
+ expect(respond).not.toThrowError()
306
369
 
307
- it 'does not explode if up.modal.close() was called before the response was received', ->
308
- up.modal.visit('/foo', target: '.container')
309
- up.modal.close()
310
- respond = => @respondWith('<div class="container">text</div>')
311
- expect(respond).not.toThrowError()
312
- expect($('.up-error')).not.toExist()
370
+ next.await =>
371
+ expect($('.up-toast')).not.toExist()
372
+ promise = promiseState(openPromise)
373
+ promise.then (result) => expect(result.state).toEqual('pending')
313
374
 
314
375
  describe 'up.modal.coveredUrl', ->
315
376
 
316
377
  describeCapability 'canPushState', ->
317
378
 
318
379
  it 'returns the URL behind the modal overlay', (done) ->
380
+ up.history.config.enabled = true
319
381
  up.history.replace('/foo')
320
382
  expect(up.modal.coveredUrl()).toBeMissing()
321
383
  visitPromise = up.modal.visit('/bar', target: '.container')
322
- @respondWith('<div class="container">text</div>')
323
- visitPromise.then ->
324
- expect(up.modal.coveredUrl()).toEqualUrl('/foo')
325
- up.modal.close().then ->
326
- expect(up.modal.coveredUrl()).toBeMissing()
327
- done()
384
+ u.nextFrame =>
385
+ @respondWith('<div class="container">text</div>')
386
+ visitPromise.then ->
387
+ expect(up.modal.coveredUrl()).toMatchUrl('/foo')
388
+ up.modal.close().then ->
389
+ expect(up.modal.coveredUrl()).toBeMissing()
390
+ done()
328
391
 
329
392
  describe 'up.modal.flavors', ->
330
393
 
331
- it 'allows to register new modal variants with its own default configuration', ->
394
+ it 'allows to register new modal variants with its own default configuration', asyncSpec (next) ->
332
395
  up.modal.flavors.variant = { maxWidth: 200 }
333
396
  $link = affix('a[href="/path"][up-modal=".target"][up-flavor="variant"]')
334
397
  Trigger.click($link)
335
- @respondWith('<div class="target">new text</div>')
336
- $modal = $('.up-modal')
337
- $dialog = $modal.find('.up-modal-dialog')
338
- expect($modal).toBeInDOM()
339
- expect($modal.attr('up-flavor')).toEqual('variant')
340
- expect($dialog.attr('style')).toContain('max-width: 200px')
341
-
342
- it 'does not change the configuration of non-flavored modals', ->
398
+
399
+ next =>
400
+ @respondWith('<div class="target">new text</div>')
401
+
402
+ next =>
403
+ $modal = $('.up-modal')
404
+ $dialog = $modal.find('.up-modal-dialog')
405
+ expect($modal).toBeInDOM()
406
+ expect($modal.attr('up-flavor')).toEqual('variant')
407
+ expect($dialog.attr('style')).toContain('max-width: 200px')
408
+
409
+ it 'does not change the configuration of non-flavored modals', asyncSpec (next) ->
343
410
  up.modal.flavors.variant = { maxWidth: 200 }
344
411
  $link = affix('a[href="/path"][up-modal=".target"]')
345
412
  Trigger.click($link)
346
- @respondWith('<div class="target">new text</div>')
347
- $modal = $('.up-modal')
348
- $dialog = $modal.find('.up-modal-dialog')
349
- expect($modal).toBeInDOM()
350
- expect($dialog.attr('style')).toBeBlank()
413
+
414
+ next =>
415
+ @respondWith('<div class="target">new text</div>')
416
+
417
+ next =>
418
+ $modal = $('.up-modal')
419
+ $dialog = $modal.find('.up-modal-dialog')
420
+ expect($modal).toBeInDOM()
421
+ expect($dialog.attr('style')).toBeBlank()
351
422
 
352
423
  describe 'up.modal.close', ->
353
424
 
354
425
  it 'closes a currently open modal', (done) ->
355
- up.modal.extract('.modal', '<div class="modal">Modal content</div>')
426
+ up.modal.extract('.content', '<div class="content">Modal content</div>')
356
427
 
357
- modalContent = $('.modal')
358
- expect(modalContent).toBeInDOM()
428
+ u.nextFrame =>
429
+ expect('.up-modal .content').toBeInDOM()
359
430
 
360
- up.modal.close().then ->
361
- expect(modalContent).not.toBeInDOM()
362
- done()
431
+ up.modal.close().then ->
432
+ expect('.up-modal .content').not.toBeInDOM()
433
+ done()
363
434
 
364
435
  it 'does nothing if no modal is open', (done) ->
365
436
  wasClosed = false
@@ -371,7 +442,7 @@ describe 'up.modal', ->
371
442
  done()
372
443
 
373
444
  describe 'unobtrusive behavior', ->
374
-
445
+
375
446
  describe 'a[up-modal]', ->
376
447
 
377
448
  beforeEach ->
@@ -381,43 +452,45 @@ describe 'up.modal', ->
381
452
  # actually making a request.
382
453
  @stubFollow = =>
383
454
  @$link = affix('a[href="/path"][up-modal=".target"]')
384
- @followSpy = up.modal.knife.mock('followAsap').and.returnValue(u.resolvedPromise())
385
- @defaultSpy = up.link.knife.mock('allowDefault').and.callFake((event) -> event.preventDefault())
455
+ @followSpy = up.modal.knife.mock('followAsap').and.returnValue(Promise.resolve())
456
+ @defaultSpy = spyOn(up.link, 'allowDefault').and.callFake((event) -> event.preventDefault())
386
457
 
387
- it 'opens the clicked link in a modal', (done) ->
458
+ it 'opens the clicked link in a modal', asyncSpec (next) ->
388
459
  @$link = affix('a[href="/path"][up-modal=".target"]')
389
460
  Trigger.click(@$link)
390
- lastRequest = @lastRequest()
391
- expect(lastRequest.url).toEqualUrl('/path')
392
- @respondWith '<div class="target">new content</div>'
393
- u.nextFrame =>
461
+
462
+ next =>
463
+ lastRequest = @lastRequest()
464
+ expect(lastRequest.url).toMatchUrl('/path')
465
+ @respondWith '<div class="target">new content</div>'
466
+
467
+ next =>
394
468
  expect('.up-modal').toExist()
395
469
  expect('.up-modal-content').toHaveText('new content')
396
- done()
397
470
 
398
471
  describe 'when modifier keys are held', ->
399
472
 
400
473
  # IE does not call JavaScript and always performs the default action on right clicks
401
- unless navigator.userAgent.match(/Trident/)
402
- it 'does nothing if the right mouse button is used', ->
474
+ unless AgentDetector.isIE() || AgentDetector.isEdge()
475
+ it 'does nothing if the right mouse button is used', asyncSpec (next) ->
403
476
  @stubFollow()
404
477
  Trigger.click(@$link, button: 2)
405
- expect(@followSpy).not.toHaveBeenCalled()
478
+ next => expect(@followSpy).not.toHaveBeenCalled()
406
479
 
407
- it 'does nothing if shift is pressed during the click', ->
480
+ it 'does nothing if shift is pressed during the click', asyncSpec (next) ->
408
481
  @stubFollow()
409
482
  Trigger.click(@$link, shiftKey: true)
410
- expect(@followSpy).not.toHaveBeenCalled()
483
+ next => expect(@followSpy).not.toHaveBeenCalled()
411
484
 
412
- it 'does nothing if ctrl is pressed during the click', ->
485
+ it 'does nothing if ctrl is pressed during the click', asyncSpec (next) ->
413
486
  @stubFollow()
414
487
  Trigger.click(@$link, ctrlKey: true)
415
- expect(@followSpy).not.toHaveBeenCalled()
488
+ next => expect(@followSpy).not.toHaveBeenCalled()
416
489
 
417
- it 'does nothing if meta is pressed during the click', ->
490
+ it 'does nothing if meta is pressed during the click', asyncSpec (next) ->
418
491
  @stubFollow()
419
492
  Trigger.click(@$link, metaKey: true)
420
- expect(@followSpy).not.toHaveBeenCalled()
493
+ next => expect(@followSpy).not.toHaveBeenCalled()
421
494
 
422
495
  describe 'with [up-instant] modifier', ->
423
496
 
@@ -425,48 +498,53 @@ describe 'up.modal', ->
425
498
  @stubFollow()
426
499
  @$link.attr('up-instant', '')
427
500
 
428
- it 'opens the modal on mousedown (instead of on click)', ->
501
+ it 'opens the modal on mousedown (instead of on click)', asyncSpec (next) ->
429
502
  Trigger.mousedown(@$link)
430
- expect(@followSpy.calls.mostRecent().args[0]).toEqual(@$link)
503
+ next =>
504
+ expect(@followSpy).toHaveBeenCalledWith(@$link, {})
431
505
 
432
- it 'does nothing on mouseup', ->
506
+ it 'does nothing on mouseup', asyncSpec (next) ->
433
507
  Trigger.mouseup(@$link)
434
- expect(@followSpy).not.toHaveBeenCalled()
508
+ next => expect(@followSpy).not.toHaveBeenCalled()
435
509
 
436
- it 'does nothing on click', ->
510
+ it 'does nothing on click', asyncSpec (next) ->
437
511
  Trigger.click(@$link)
438
- expect(@followSpy).not.toHaveBeenCalled()
512
+ next => expect(@followSpy).not.toHaveBeenCalled()
439
513
 
440
514
  # IE does not call JavaScript and always performs the default action on right clicks
441
- unless navigator.userAgent.match(/Trident/)
442
- it 'does nothing if the right mouse button is pressed down', ->
515
+ unless AgentDetector.isIE() || AgentDetector.isEdge()
516
+ it 'does nothing if the right mouse button is pressed down', asyncSpec (next) ->
443
517
  Trigger.mousedown(@$link, button: 2)
444
- expect(@followSpy).not.toHaveBeenCalled()
518
+ next => expect(@followSpy).not.toHaveBeenCalled()
445
519
 
446
- it 'does nothing if shift is pressed during mousedown', ->
520
+ it 'does nothing if shift is pressed during mousedown', asyncSpec (next) ->
447
521
  Trigger.mousedown(@$link, shiftKey: true)
448
- expect(@followSpy).not.toHaveBeenCalled()
522
+ next => expect(@followSpy).not.toHaveBeenCalled()
449
523
 
450
- it 'does nothing if ctrl is pressed during mousedown', ->
524
+ it 'does nothing if ctrl is pressed during mousedown', asyncSpec (next) ->
451
525
  Trigger.mousedown(@$link, ctrlKey: true)
452
- expect(@followSpy).not.toHaveBeenCalled()
526
+ next => expect(@followSpy).not.toHaveBeenCalled()
453
527
 
454
- it 'does nothing if meta is pressed during mousedown', ->
528
+ it 'does nothing if meta is pressed during mousedown', asyncSpec (next) ->
455
529
  Trigger.mousedown(@$link, metaKey: true)
456
- expect(@followSpy).not.toHaveBeenCalled()
530
+ next => expect(@followSpy).not.toHaveBeenCalled()
457
531
 
458
532
  describe 'with [up-method] modifier', ->
459
533
 
460
- it 'honours the given method', ->
534
+ it 'honours the given method', asyncSpec (next) ->
461
535
  $link = affix('a[href="/path"][up-modal=".target"][up-method="post"]')
462
536
  Trigger.click($link)
463
- expect(@lastRequest().method).toEqual 'POST'
464
537
 
465
- it 'adds a history entry and allows the user to use the back button', (done) ->
538
+ next =>
539
+ expect(@lastRequest().method).toEqual 'POST'
540
+
541
+ it 'adds a history entry and allows the user to use the back button', asyncSpec (next) ->
466
542
  up.motion.config.enabled = false
543
+ up.history.config.enabled = true
544
+ up.history.config.popTargets = ['.container']
545
+
467
546
  up.history.push('/original-path')
468
547
 
469
- up.history.config.popTargets = ['.container']
470
548
  $container = affix('.container').text('old container content')
471
549
  $link = $container.affix('a[href="/new-path"][up-modal=".target"]')
472
550
 
@@ -474,47 +552,49 @@ describe 'up.modal', ->
474
552
 
475
553
  Trigger.clickSequence($link)
476
554
 
477
- u.nextFrame =>
555
+ next =>
478
556
  @respondWith('<div class="target">modal content</div>')
557
+
558
+ next =>
479
559
  expect(location.pathname).toEqual('/new-path')
480
560
  expect('.up-modal .target').toHaveText('modal content')
481
561
 
482
562
  history.back()
483
- u.setTimer (waitForBrowser = 70), =>
484
- @respondWith('<div class="container">restored container content</div>')
485
- u.nextFrame =>
486
- expect(location.pathname).toEqual('/original-path')
487
- expect('.container').toHaveText('restored container content')
488
- expect('.up-modal').not.toExist()
489
- done()
490
563
 
491
- it 'allows to open a modal after closing a previous modul with the escape key (bugfix)', (done) ->
564
+ next.after (waitForBrowser = 70), =>
565
+ @respondWith('<div class="container">restored container content</div>')
566
+
567
+ next =>
568
+ expect(location.pathname).toEqual('/original-path')
569
+ expect('.container').toHaveText('restored container content')
570
+ expect('.up-modal').not.toExist()
571
+
572
+ it 'allows to open a modal after closing a previous modal with the escape key (bugfix)', asyncSpec (next) ->
492
573
  up.motion.config.enabled = false
493
574
 
494
575
  $link1 = affix('a[href="/path1"][up-modal=".target"]')
495
576
  $link2 = affix('a[href="/path2"][up-modal=".target"]')
496
577
  Trigger.clickSequence($link1)
497
578
 
498
- u.nextFrame =>
579
+ next =>
499
580
  @respondWith('<div class="target">content 1</div>')
500
581
 
501
- u.nextFrame =>
502
- expect(up.modal.isOpen()).toBe(true)
582
+ next =>
583
+ expect(up.modal.isOpen()).toBe(true)
503
584
 
504
- escapeEvent = $.Event('keydown', keyCode: 27)
505
- $('body').trigger(escapeEvent)
585
+ escapeEvent = $.Event('keydown', keyCode: 27)
586
+ $('body').trigger(escapeEvent)
506
587
 
507
- u.nextFrame =>
508
- expect(up.modal.isOpen()).toBe(false)
588
+ next =>
589
+ expect(up.modal.isOpen()).toBe(false)
509
590
 
510
- Trigger.clickSequence($link2)
591
+ Trigger.clickSequence($link2)
511
592
 
512
- u.nextFrame =>
513
- @respondWith('<div class="target">content 1</div>')
593
+ next =>
594
+ @respondWith('<div class="target">content 1</div>')
514
595
 
515
- u.nextFrame =>
516
- expect(up.modal.isOpen()).toBe(true)
517
- done()
596
+ next =>
597
+ expect(up.modal.isOpen()).toBe(true)
518
598
 
519
599
 
520
600
  describe '[up-drawer]', ->
@@ -522,35 +602,39 @@ describe 'up.modal', ->
522
602
  beforeEach ->
523
603
  up.motion.config.enabled = false
524
604
 
525
- it 'slides in a drawer that covers the full height of the screen', (done) ->
605
+ it 'slides in a drawer that covers the full height of the screen', asyncSpec (next) ->
526
606
  $link = affix('a[href="/foo"][up-drawer=".target"]').text('label')
527
607
  up.hello($link)
528
608
  Trigger.clickSequence($link)
529
- u.nextFrame =>
609
+
610
+ next =>
530
611
  @respondWith '<div class="target">new text</div>'
612
+
613
+ next =>
531
614
  expect(up.modal.isOpen()).toBe(true)
532
615
  expect($('.up-modal').attr('up-flavor')).toEqual('drawer')
533
616
  windowHeight = u.clientSize().height
534
617
  modalHeight = $('.up-modal-content').outerHeight()
535
618
  expect(modalHeight).toEqual(windowHeight)
536
619
  expect($('.up-modal-content').offset()).toEqual(top: 0, left: 0)
537
- done()
538
620
 
539
- it 'puts the drawer on the right if the opening link sits in the right 50% of the screen', (done) ->
621
+ it 'puts the drawer on the right if the opening link sits in the right 50% of the screen', asyncSpec (next) ->
540
622
  $link = affix('a[href="/foo"][up-drawer=".target"]').text('label')
541
623
  $link.css
542
624
  position: 'absolute'
543
625
  right: '0'
544
626
  up.hello($link)
545
627
  Trigger.clickSequence($link)
546
- u.nextFrame =>
628
+
629
+ next =>
547
630
  @respondWith '<div class="target">new text</div>'
631
+
632
+ next =>
548
633
  expect(up.modal.isOpen()).toBe(true)
549
634
  windowWidth = u.clientSize().width
550
635
  modalWidth = $('.up-modal-content').outerWidth()
551
636
  scrollbarWidth = u.scrollbarWidth()
552
637
  expect($('.up-modal-content').offset().left).toBeAround(windowWidth - modalWidth - scrollbarWidth, 1.0)
553
- done()
554
638
 
555
639
  describe '[up-close]', ->
556
640
 
@@ -562,190 +646,235 @@ describe 'up.modal', ->
562
646
  up.on 'click', backgroundClicked
563
647
 
564
648
  describe 'when clicked inside a modal', ->
565
-
566
- it 'closes the open modal and halts the event chain', (done) ->
649
+
650
+ it 'closes the open modal and halts the event chain', asyncSpec (next) ->
567
651
  up.modal.extract('.target', '<div class="target"><a up-close>text</a></div>', animation: false)
568
- $link = $('.up-modal a[up-close]') # link is within the modal
569
- Trigger.clickSequence($link)
570
- u.nextFrame ->
652
+
653
+ next =>
654
+ $link = $('.up-modal a[up-close]') # link is within the modal
655
+ Trigger.clickSequence($link)
656
+
657
+ next =>
571
658
  expect(up.modal.isOpen()).toBe(false)
572
659
  expect(backgroundClicked).not.toHaveBeenCalled()
573
- done()
574
660
 
575
661
  describe 'when no modal is open', ->
576
662
 
577
- it 'does nothing and allows the event chain to continue', (done) ->
663
+ it 'does nothing and allows the event chain to continue', asyncSpec (next) ->
578
664
  $link = affix('a[up-close]') # link is outside the modal
579
665
  up.hello($link)
580
666
  Trigger.clickSequence($link)
581
- u.nextFrame ->
667
+
668
+ next =>
582
669
  expect(backgroundClicked).toHaveBeenCalled()
583
- done()
584
670
 
585
671
  describe 'closing', ->
586
672
 
587
- it 'closes the modal on close icon click', (done) ->
673
+ it 'closes the modal on close icon click', asyncSpec (next) ->
588
674
  up.modal.extract('.modal', '<div class="modal">Modal content</div>', animation: false)
589
675
 
590
- $closeIcon = $('.up-modal-close')
676
+ next =>
677
+ $closeIcon = $('.up-modal-close')
678
+ Trigger.clickSequence($closeIcon)
591
679
 
592
- Trigger.clickSequence($closeIcon)
593
- u.nextFrame ->
680
+ next =>
594
681
  expect(up.modal.isOpen()).toBe(false)
595
- done()
596
682
 
597
- it 'closes the modal on backdrop click', (done) ->
683
+ it 'closes the modal on backdrop click', asyncSpec (next) ->
598
684
  up.modal.extract('.modal', '<div class="modal">Modal content</div>', animation: false)
599
685
 
600
- $backdrop = $('.up-modal-backdrop')
686
+ next =>
687
+ $backdrop = $('.up-modal-backdrop')
688
+ Trigger.clickSequence($backdrop)
601
689
 
602
- Trigger.clickSequence($backdrop)
603
- u.nextFrame ->
690
+ next =>
604
691
  expect(up.modal.isOpen()).toBe(false)
605
- done()
606
692
 
607
- it "does not close the modal when clicking on an element outside the modal's DOM hierarchy", (done) ->
693
+ it "does not close the modal when clicking on an element outside the modal's DOM hierarchy", asyncSpec (next) ->
608
694
  $container = affix('.container')
609
695
  up.modal.extract('.modal', '<div class="modal">Modal content</div>', animation: false)
610
696
 
611
- Trigger.clickSequence($container)
612
- u.nextFrame ->
697
+ next =>
698
+ Trigger.clickSequence($container)
699
+
700
+ next =>
613
701
  expect(up.modal.isOpen()).toBe(true)
614
- done()
615
702
 
616
- it 'closes the modal when the user presses the escape key', (done) ->
703
+ it 'closes the modal when the user presses the escape key', asyncSpec (next) ->
617
704
  wasClosed = false
618
- up.modal.extract('.modal', '<div class="modal">Modal content</div>', animation: false)
619
705
  up.on 'up:modal:close', ->
620
706
  wasClosed = true
621
707
 
622
- escapeEvent = $.Event('keydown', keyCode: 27)
623
- $('body').trigger(escapeEvent)
624
- u.nextFrame ->
708
+ up.modal.extract('.modal', '<div class="modal">Modal content</div>', animation: false)
709
+
710
+ next =>
711
+ escapeEvent = $.Event('keydown', keyCode: 27)
712
+ $('body').trigger(escapeEvent)
713
+
714
+ next =>
625
715
  expect(wasClosed).toBe(true)
626
- done()
627
716
 
628
717
  describe 'when opened with { closable: false }', ->
629
718
 
630
- it 'does not render a close icon', ->
719
+ it 'does not render a close icon', asyncSpec (next) ->
631
720
  up.modal.extract('.modal', '<div class="modal">Modal content</div>', animation: false, closable: false)
632
721
 
633
- modal = $('.up-modal')
634
- expect(modal).not.toContainElement('.up-modal-close')
722
+ next =>
723
+ expect('.up-modal').not.toContainElement('.up-modal-close')
635
724
 
636
- it 'does not close the modal on backdrop click', (done) ->
725
+ it 'does not close the modal on backdrop click', asyncSpec (next) ->
637
726
  up.modal.extract('.modal', '<div class="modal">Modal content</div>', animation: false, closable: false)
638
727
 
639
- $backdrop = $('.up-modal-backdrop')
728
+ next =>
729
+ $backdrop = $('.up-modal-backdrop')
730
+ Trigger.clickSequence($backdrop)
640
731
 
641
- Trigger.clickSequence($backdrop)
642
- u.nextFrame ->
732
+ next =>
643
733
  expect(up.modal.isOpen()).toBe(true)
644
- done()
645
734
 
646
- it 'does not close the modal when the user presses the escape key', (done) ->
735
+ it 'does not close the modal when the user presses the escape key', asyncSpec (next) ->
647
736
  up.modal.extract('.modal', '<div class="modal">Modal content</div>', animation: false, closable: false)
648
737
 
649
- escapeEvent = $.Event('keydown', keyCode: 27)
650
- $('body').trigger(escapeEvent)
651
- u.nextFrame ->
738
+ next =>
739
+ escapeEvent = $.Event('keydown', keyCode: 27)
740
+ $('body').trigger(escapeEvent)
741
+
742
+ next =>
652
743
  expect(up.modal.isOpen()).toBe(true)
653
- done()
654
744
 
655
745
  describe 'when replacing content', ->
656
746
 
657
747
  beforeEach ->
658
748
  up.motion.config.enabled = false
659
749
 
660
- it 'prefers to replace a selector within the modal', ->
750
+ it 'prefers to replace a selector within the modal', asyncSpec (next) ->
661
751
  $outside = affix('.foo').text('old outside')
662
752
  up.modal.visit('/path', target: '.foo')
663
- @respondWith("<div class='foo'>old inside</div>")
664
- up.extract('.foo', "<div class='foo'>new text</div>")
665
- expect($outside).toBeInDOM()
666
- expect($outside).toHaveText('old outside')
667
- expect($('.up-modal-content')).toHaveText('new text')
668
753
 
669
- it 'auto-closes the modal when a replacement from inside the modal affects a selector behind the modal', ->
754
+ next =>
755
+ @respondWith("<div class='foo'>old inside</div>")
756
+
757
+ next =>
758
+ up.extract('.foo', "<div class='foo'>new text</div>")
759
+
760
+ next =>
761
+ expect($outside).toBeInDOM()
762
+ expect($outside).toHaveText('old outside')
763
+ expect($('.up-modal-content')).toHaveText('new text')
764
+
765
+ it 'auto-closes the modal when a replacement from inside the modal affects a selector behind the modal', asyncSpec (next) ->
670
766
  affix('.outside').text('old outside')
671
767
  up.modal.visit('/path', target: '.inside')
672
- @respondWith("<div class='inside'>old inside</div>")
673
- up.extract('.outside', "<div class='outside'>new outside</div>", origin: $('.inside'))
674
- expect($('.outside')).toHaveText('new outside')
675
- expect($('.up-modal')).not.toExist()
676
768
 
677
- it 'does not restore the covered URL when auto-closing', (done) ->
769
+ next =>
770
+ @respondWith("<div class='inside'>old inside</div>")
771
+
772
+ next =>
773
+ up.extract('.outside', "<div class='outside'>new outside</div>", origin: $('.inside'))
774
+
775
+ next =>
776
+ expect($('.outside')).toHaveText('new outside')
777
+ expect($('.up-modal')).not.toExist()
778
+
779
+ it 'does not restore the covered URL when auto-closing (since it would override the URL from the triggering update)', asyncSpec (next) ->
780
+ up.history.config.enabled = true
678
781
  up.motion.config.enabled = true
679
782
  up.modal.config.openDuration = 0
680
783
  up.modal.config.closeDuration = 20
681
784
 
682
785
  affix('.outside').text('old outside')
683
- whenModalOpen = up.modal.visit('/path', target: '.inside')
684
- @respondWith("<div class='inside'>old inside</div>") # Populate modal
786
+ up.modal.visit('/path', target: '.inside')
787
+
788
+ next =>
789
+ @respondWith("<div class='inside'>old inside</div>") # Populate modal
685
790
 
686
- whenModalOpen.then ->
791
+ next =>
687
792
  up.extract('.outside', "<div class='outside'>new outside</div>",
688
793
  origin: $('.inside'), history: '/new-location') # Provoke auto-close
689
794
 
690
- u.setTimer 50, ->
691
- expect(location.href).toEqualUrl '/new-location'
692
- done()
795
+ next =>
796
+ expect(location.href).toMatchUrl '/new-location'
693
797
 
694
- it 'does not auto-close the modal when a replacement from inside the modal affects a selector inside the modal', ->
798
+ it 'does not auto-close the modal when a replacement from inside the modal affects a selector inside the modal', asyncSpec (next) ->
695
799
  affix('.outside').text('old outside')
696
800
  up.modal.visit('/path', target: '.inside')
697
- @respondWith("<div class='inside'>old inside</div>")
698
- up.extract('.inside', "<div class='inside'>new inside</div>", origin: $('.inside'))
699
- expect($('.inside')).toHaveText('new inside')
700
- expect($('.up-modal')).toExist()
701
801
 
702
- it 'does not auto-close the modal when a replacement from outside the modal affects a selector outside the modal', ->
802
+ next =>
803
+ @respondWith("<div class='inside'>old inside</div>")
804
+
805
+ next =>
806
+ up.extract('.inside', "<div class='inside'>new inside</div>", origin: $('.inside'))
807
+
808
+ next =>
809
+ expect($('.inside')).toHaveText('new inside')
810
+ expect($('.up-modal')).toExist()
811
+
812
+ it 'does not auto-close the modal when a replacement from outside the modal affects a selector outside the modal', asyncSpec (next) ->
703
813
  affix('.outside').text('old outside')
704
814
  up.modal.visit('/path', target: '.inside')
705
- @respondWith("<div class='inside'>old inside</div>")
706
- up.extract('.outside', "<div class='outside'>new outside</div>", origin: $('.outside'))
707
- expect($('.outside')).toHaveText('new outside')
708
- expect($('.up-modal')).toExist()
709
815
 
710
- it 'does not auto-close the modal when a replacement from outside the modal affects a selector inside the modal', ->
816
+ next =>
817
+ @respondWith("<div class='inside'>old inside</div>")
818
+
819
+ next =>
820
+ up.extract('.outside', "<div class='outside'>new outside</div>", origin: $('.outside'))
821
+
822
+ next =>
823
+ expect($('.outside')).toHaveText('new outside')
824
+ expect($('.up-modal')).toExist()
825
+
826
+ it 'does not auto-close the modal when a replacement from outside the modal affects a selector inside the modal', asyncSpec (next) ->
711
827
  affix('.outside').text('old outside')
712
828
  up.modal.visit('/path', target: '.inside')
713
- @respondWith("<div class='inside'>old inside</div>")
714
- up.extract('.inside', "<div class='inside'>new inside</div>", origin: $('.outside'))
715
- expect($('.inside')).toHaveText('new inside')
716
- expect($('.up-modal')).toExist()
717
829
 
718
- it 'does not auto-close the modal when the new fragment is within a popup', ->
830
+ next =>
831
+ @respondWith("<div class='inside'>old inside</div>")
832
+
833
+ next =>
834
+ up.extract('.inside', "<div class='inside'>new inside</div>", origin: $('.outside'))
835
+
836
+ next =>
837
+ expect($('.inside')).toHaveText('new inside')
838
+ expect($('.up-modal')).toExist()
839
+
840
+ it 'does not auto-close the modal when the new fragment is within a popup', asyncSpec (next) ->
719
841
  up.modal.visit('/modal', target: '.modal-content')
720
- @respondWith("<div class='modal-content'></div>")
721
- up.popup.attach('.modal-content', url: '/popup', target: '.popup-content')
722
- @respondWith("<div class='popup-content'></div>")
723
- expect($('.up-modal')).toExist()
724
- expect($('.up-popup')).toExist()
725
842
 
726
- it 'does not close the modal when a clicked [up-target] link within the modal links to cached content (bugfix)', (done) ->
843
+ next =>
844
+ @respondWith("<div class='modal-content'></div>")
845
+
846
+ next =>
847
+ up.popup.attach('.modal-content', url: '/popup', target: '.popup-content')
727
848
 
849
+ next =>
850
+ @respondWith("<div class='popup-content'></div>")
851
+
852
+ next =>
853
+ expect($('.up-modal')).toExist()
854
+ expect($('.up-popup')).toExist()
855
+
856
+ it 'does not close the modal when a clicked [up-target] link within the modal links to cached content (bugfix)', asyncSpec (next) ->
728
857
  up.modal.extract '.content', """
729
858
  <div class="content">
730
859
  <a href="/foo" up-target=".content">link</a>
731
860
  </div>
732
861
  """
733
- $link = $('.up-modal .content a')
734
- expect($link).toExist()
735
- whenPreloaded = up.proxy.preload($link)
736
-
737
- @respondWith """
738
- <div class="content">
739
- new text
740
- </div>
741
- """
742
862
 
743
- whenPreloaded.then ->
863
+ next =>
864
+ $link = $('.up-modal .content a')
865
+ expect($link).toExist()
866
+ up.proxy.preload($link)
744
867
 
745
- Trigger.clickSequence($link)
868
+ next =>
869
+ @respondWith """
870
+ <div class="content">
871
+ new text
872
+ </div>
873
+ """
746
874
 
747
- u.nextFrame ->
748
- expect($('.up-modal')).toExist()
749
- expect($('.up-modal .content')).toHaveText('new text')
875
+ next =>
876
+ Trigger.clickSequence('.up-modal .content a')
750
877
 
751
- done()
878
+ next =>
879
+ expect($('.up-modal')).toExist()
880
+ expect($('.up-modal .content')).toHaveText('new text')