unpoly-rails 0.24.1 → 0.25.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 (30) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +31 -0
  3. data/README_RAILS.md +8 -1
  4. data/dist/unpoly.css +22 -10
  5. data/dist/unpoly.js +406 -196
  6. data/dist/unpoly.min.css +1 -1
  7. data/dist/unpoly.min.js +3 -3
  8. data/lib/assets/javascripts/unpoly/flow.js.coffee +36 -19
  9. data/lib/assets/javascripts/unpoly/form.js.coffee +1 -2
  10. data/lib/assets/javascripts/unpoly/link.js.coffee +2 -2
  11. data/lib/assets/javascripts/unpoly/modal.js.coffee +174 -81
  12. data/lib/assets/javascripts/unpoly/navigation.js.coffee +3 -1
  13. data/lib/assets/javascripts/unpoly/popup.js.coffee +62 -37
  14. data/lib/assets/javascripts/unpoly/proxy.js.coffee +1 -0
  15. data/lib/assets/javascripts/unpoly/syntax.js.coffee +12 -4
  16. data/lib/assets/javascripts/unpoly/util.js.coffee +55 -13
  17. data/lib/assets/stylesheets/unpoly/modal.css.sass +28 -12
  18. data/lib/unpoly/rails/inspector.rb +26 -0
  19. data/lib/unpoly/rails/version.rb +1 -1
  20. data/spec_app/Gemfile.lock +1 -1
  21. data/spec_app/app/controllers/binding_test_controller.rb +6 -0
  22. data/spec_app/spec/controllers/binding_test_controller_spec.rb +82 -11
  23. data/spec_app/spec/javascripts/up/flow_spec.js.coffee +21 -7
  24. data/spec_app/spec/javascripts/up/form_spec.js.coffee +15 -0
  25. data/spec_app/spec/javascripts/up/link_spec.js.coffee +11 -10
  26. data/spec_app/spec/javascripts/up/modal_spec.js.coffee +232 -30
  27. data/spec_app/spec/javascripts/up/motion_spec.js.coffee +33 -27
  28. data/spec_app/spec/javascripts/up/popup_spec.js.coffee +72 -0
  29. data/spec_app/spec/javascripts/up/syntax_spec.js.coffee +51 -13
  30. metadata +2 -2
@@ -47,50 +47,230 @@ describe 'up.modal', ->
47
47
 
48
48
  describe 'up.modal.visit', ->
49
49
 
50
- it "brings its own scrollbar, padding the body on the right in order to prevent jumping", (done) ->
51
- promise = up.modal.visit('/foo', target: '.container')
50
+ describe 'preventing elements from jumping as scrollbars change', ->
52
51
 
53
- @respondWith('<div class="container">text</div>')
52
+ it "brings its own scrollbar, padding the body on the right", (done) ->
53
+ promise = up.modal.visit('/foo', target: '.container')
54
54
 
55
- promise.then ->
55
+ @respondWith('<div class="container">text</div>')
56
+
57
+ promise.then ->
58
+ $modal = $('.up-modal')
59
+ $viewport = $modal.find('.up-modal-viewport')
60
+ $body = $('body')
61
+ expect($modal).toExist()
62
+ expect($viewport.css('overflow-y')).toEqual('scroll')
63
+ expect($body.css('overflow-y')).toEqual('hidden')
64
+ expect(parseInt($body.css('padding-right'))).toBeAround(assumedScrollbarWidth, 5)
65
+
66
+ up.modal.close().then ->
67
+ expect($body.css('overflow-y')).toEqual('scroll')
68
+ expect(parseInt($body.css('padding-right'))).toBe(0)
69
+ done()
70
+
71
+ 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) ->
72
+ openPromise = up.modal.extract('.container', '<div class="container">text</div>', animation: 'fade-in', duration: 100)
56
73
  $modal = $('.up-modal')
57
74
  $viewport = $modal.find('.up-modal-viewport')
58
- $body = $('body')
59
- expect($modal).toExist()
60
- expect($viewport.css('overflow-y')).toEqual('scroll')
61
- expect($body.css('overflow-y')).toEqual('hidden')
62
- expect(parseInt($body.css('padding-right'))).toBeAround(assumedScrollbarWidth, 10)
63
-
64
- up.modal.close().then ->
65
- expect($body.css('overflow-y')).toEqual('scroll')
75
+ expect($modal.css('overflow-y')).toEqual('scroll')
76
+ expect($viewport.css('overflow-y')).toEqual('hidden')
77
+ openPromise.then ->
78
+ expect($modal.css('overflow-y')).toEqual('auto')
79
+ expect($viewport.css('overflow-y')).toEqual('scroll')
80
+ closePromise = up.modal.close(animation: 'fade-out', duration: 100)
81
+ expect($modal.css('overflow-y')).toEqual('scroll')
82
+ expect($viewport.css('overflow-y')).toEqual('hidden')
83
+ done()
84
+
85
+
86
+ it 'does not add right padding to the body if the body has overflow-y: hidden', (done) ->
87
+ restoreBody = u.temporaryCss($('body'), 'overflow-y': 'hidden')
88
+
89
+ up.modal.extract('.container', '<div class="container">text</div>').then ->
90
+ $body = $('body')
91
+ expect($('.up-modal')).toExist()
66
92
  expect(parseInt($body.css('padding-right'))).toBe(0)
67
93
 
68
- done()
94
+ up.modal.close().then ->
95
+ expect(parseInt($body.css('padding-right'))).toBe(0)
96
+ restoreBody()
97
+ done()
69
98
 
70
- it 'pushes right-anchored elements away from the edge of the screen in order to prevent jumping', (done) ->
99
+ it 'does not add right padding to the body if the body has overflow-y: auto, but does not currently have scrollbars', (done) ->
100
+ restoreBody = u.temporaryCss($('body'), 'overflow-y': 'auto')
101
+ restoreReporter = u.temporaryCss($('.jasmine_html-reporter'), 'height': '100px', 'overflow-y': 'hidden')
71
102
 
72
- $anchoredElement = affix('div[up-anchored=right]').css
73
- position: 'absolute'
74
- top: '0'
75
- right: '30px'
103
+ up.modal.extract('.container', '<div class="container">text</div>').then ->
104
+ $body = $('body')
105
+ expect($('.up-modal')).toExist()
106
+ expect(parseInt($body.css('padding-right'))).toBe(0)
76
107
 
77
- promise = up.modal.visit('/foo', target: '.container')
108
+ up.modal.close().then ->
109
+ expect(parseInt($body.css('padding-right'))).toBe(0)
110
+ restoreReporter()
111
+ restoreBody()
112
+ done()
78
113
 
79
- @respondWith('<div class="container">text</div>')
114
+ it 'pushes right-anchored elements away from the edge of the screen', (done) ->
80
115
 
81
- promise.then ->
82
- expect(parseInt($anchoredElement.css('right'))).toBeAround(30 + assumedScrollbarWidth, 10)
116
+ $anchoredElement = affix('div[up-anchored=right]').css
117
+ position: 'absolute'
118
+ top: '0'
119
+ right: '30px'
120
+
121
+ promise = up.modal.visit('/foo', target: '.container')
122
+
123
+ @respondWith('<div class="container">text</div>')
124
+
125
+ promise.then ->
126
+ expect(parseInt($anchoredElement.css('right'))).toBeAround(30 + assumedScrollbarWidth, 10)
127
+
128
+ up.modal.close().then ->
129
+ expect(parseInt($anchoredElement.css('right'))).toBeAround(30 , 10)
130
+ done()
131
+
132
+ describe 'opening a modal while another modal is open', ->
133
+
134
+ 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) ->
135
+ up.modal.config.closeDuration = 10
136
+ promise1 = up.modal.visit('/path1', target: '.container', animation: 'fade-in', duration: 100)
137
+ promise2 = up.modal.visit('/path2', target: '.container', animation: 'fade-in', duration: 100)
138
+ expect(jasmine.Ajax.requests.count()).toBe(2)
139
+ request1 = jasmine.Ajax.requests.at(0)
140
+ request2 = jasmine.Ajax.requests.at(1)
141
+
142
+ u.setTimer 10, =>
143
+ @respondWith('<div class="container">response1</div>', request: request1)
144
+ u.setTimer 10, =>
145
+ @respondWith('<div class="container">response2</div>', request: request2)
146
+ u.setTimer 50, =>
147
+ expect($('.up-modal').length).toBe(1)
148
+ expect($('.up-modal-dialog').length).toBe(1)
149
+ expect($('.container')).toHaveText('response2')
150
+ bodyPadding = parseInt($('body').css('padding-right'))
151
+ expect(bodyPadding).toBeAround(assumedScrollbarWidth, 10)
152
+ expect(bodyPadding).not.toBeAround(2 * assumedScrollbarWidth, 2 * 5)
153
+ done()
154
+
155
+ it 'closes the current modal and wait for its close animation to finish before starting the open animation of a second modal', (done) ->
156
+ up.modal.config.openAnimation = 'fade-in'
157
+ up.modal.config.openDuration = 5
158
+ up.modal.config.closeAnimation = 'fade-out'
159
+ up.modal.config.closeDuration = 50
160
+
161
+ events = []
162
+ u.each ['up:modal:open', 'up:modal:opened', 'up:modal:close', 'up:modal:closed'], (event) ->
163
+ up.on event, ->
164
+ events.push(event)
165
+
166
+ up.modal.extract('.target', '<div class="target">response1</div>')
167
+
168
+ # First modal is starting opening animation
169
+ expect(events).toEqual ['up:modal:open']
170
+ expect($('.target')).toHaveText('response1')
171
+
172
+ u.setTimer 40, ->
173
+ # First modal has completed opening animation
174
+ expect(events).toEqual ['up:modal:open', 'up:modal:opened']
175
+ expect($('.target')).toHaveText('response1')
176
+
177
+ up.modal.extract('.target', '<div class="target">response2</div>')
178
+
179
+ # First modal is starting close animation. Second modal waits for that.
180
+ expect(events).toEqual ['up:modal:open', 'up:modal:opened', 'up:modal:open', 'up:modal:close']
181
+ expect($('.target')).toHaveText('response1')
182
+
183
+ u.setTimer 40, ->
184
+
185
+ # Second modal is still waiting for first modal's closing animaton to finish.
186
+ expect(events).toEqual ['up:modal:open', 'up:modal:opened', 'up:modal:open', 'up:modal:close']
187
+ expect($('.target')).toHaveText('response1')
188
+
189
+ u.setTimer 100, ->
190
+
191
+ # First modal has finished closing, second modal has finished opening.
192
+ expect(events).toEqual ['up:modal:open', 'up:modal:opened', 'up:modal:open', 'up:modal:close', 'up:modal:closed', 'up:modal:opened']
193
+ expect($('.target')).toHaveText('response2')
194
+
195
+ done()
196
+
197
+ it 'closes an opening modal if a second modal starts opening before the first modal has finished its open animation', (done) ->
198
+ up.modal.config.openAnimation = 'fade-in'
199
+ up.modal.config.openDuration = 50
200
+ up.modal.config.closeAnimation = 'fade-out'
201
+ up.modal.config.closeDuration = 50
202
+
203
+ up.modal.extract('.target', '<div class="target">response1</div>')
204
+
205
+ u.setTimer 10, ->
206
+ # First modal is still in its opening animation
207
+ expect($('.target')).toHaveText('response1')
208
+
209
+ up.modal.extract('.target', '<div class="target">response2</div>')
210
+
211
+ # First modal is starting close animation. Second modal waits for that.
212
+ expect($('.target')).toHaveText('response1')
213
+
214
+ u.setTimer 10, ->
215
+ # Second modal is still waiting for first modal's closing animaton to finish.
216
+ expect($('.target')).toHaveText('response1')
217
+
218
+ u.setTimer 90, ->
219
+ # First modal has finished closing, second modal has finished opening.
220
+ expect($('.target')).toHaveText('response2')
221
+
222
+ done()
223
+
224
+ it 'uses the correct flavor config for the first and second modal', (done) ->
225
+ up.modal.config.openAnimation = 'fade-in'
226
+ up.modal.config.openDuration = 10
227
+ up.modal.config.closeAnimation = 'fade-out'
228
+ up.modal.config.closeDuration = 10
229
+ up.modal.flavor 'drawer',
230
+ openAnimation: 'move-from-right'
231
+ closeAnimation: 'move-to-right'
232
+
233
+ animations = []
234
+ spyOn(up, 'animate').and.callFake ($element, animation, options) ->
235
+ if $element.is('.up-modal-viewport')
236
+ animations.push
237
+ text: u.trim($element.find('.target').text())
238
+ animation: animation
239
+ deferred = $.Deferred()
240
+ u.setTimer options.duration, -> deferred.resolve()
241
+ deferred.promise()
242
+
243
+ up.modal.extract('.target', '<div class="target">response1</div>')
244
+ expect(animations).toEqual [
245
+ { animation: 'fade-in', text: 'response1' }
246
+ ]
247
+
248
+ up.modal.extract('.target', '<div class="target">response2</div>', flavor: 'drawer')
249
+
250
+ expect(animations).toEqual [
251
+ { animation: 'fade-in', text: 'response1' },
252
+ { animation: 'fade-out', text: 'response1' }
253
+ ]
254
+
255
+ u.setTimer 20, ->
256
+
257
+ expect(animations).toEqual [
258
+ { animation: 'fade-in', text: 'response1' },
259
+ { animation: 'fade-out', text: 'response1' },
260
+ { animation: 'move-from-right', text: 'response2' }
261
+ ]
262
+
263
+ expect($('.up-modal').attr('up-flavor')).toEqual('drawer')
83
264
 
84
- up.modal.close().then ->
85
- expect(parseInt($anchoredElement.css('right'))).toBeAround(30 , 10)
86
265
  done()
87
266
 
88
- it 'does not explode if the modal was closed before the response was received', ->
89
- up.modal.visit('/foo', target: '.container')
90
- up.modal.close()
91
- respond = => @respondWith('<div class="container">text</div>')
92
- expect(respond).not.toThrowError()
93
- expect($('.up-error')).not.toExist()
267
+
268
+ it 'does not explode if up.modal.close() was called before the response was received', ->
269
+ up.modal.visit('/foo', target: '.container')
270
+ up.modal.close()
271
+ respond = => @respondWith('<div class="container">text</div>')
272
+ expect(respond).not.toThrowError()
273
+ expect($('.up-error')).not.toExist()
94
274
 
95
275
  describe 'up.modal.coveredUrl', ->
96
276
 
@@ -107,6 +287,28 @@ describe 'up.modal', ->
107
287
  expect(up.modal.coveredUrl()).toBeUndefined()
108
288
  done()
109
289
 
290
+ describe 'up.modal.flavor', ->
291
+
292
+ it 'registers a new modal variant with its own default configuration', ->
293
+ up.modal.flavor('variant', { maxWidth: 200 })
294
+ $link = affix('a[href="/path"][up-modal=".target"][up-flavor="variant"]')
295
+ Trigger.click($link)
296
+ @respondWith('<div class="target">new text</div>')
297
+ $modal = $('.up-modal')
298
+ $dialog = $modal.find('.up-modal-dialog')
299
+ expect($modal).toBeInDOM()
300
+ expect($modal.attr('up-flavor')).toEqual('variant')
301
+ expect($dialog.attr('style')).toContain('max-width: 200px')
302
+
303
+ it 'does not change the configuration of non-flavored modals', ->
304
+ up.modal.flavor('variant', { maxWidth: 200 })
305
+ $link = affix('a[href="/path"][up-modal=".target"]')
306
+ Trigger.click($link)
307
+ @respondWith('<div class="target">new text</div>')
308
+ $modal = $('.up-modal')
309
+ $dialog = $modal.find('.up-modal-dialog')
310
+ expect($modal).toBeInDOM()
311
+ expect($dialog.attr('style')).toBeBlank()
110
312
 
111
313
  describe 'up.modal.close', ->
112
314
 
@@ -10,17 +10,29 @@ describe 'up.motion', ->
10
10
 
11
11
  it 'animates the given element', (done) ->
12
12
  $element = affix('.element').text('content')
13
- opacity = -> Number($element.css('opacity'))
14
13
  up.animate($element, 'fade-in', duration: 200, easing: 'linear')
15
14
 
16
15
  u.setTimer 0, ->
17
- expect(opacity()).toBeAround(0.0, 0.25)
16
+ expect(u.opacity($element)).toBeAround(0.0, 0.25)
18
17
  u.setTimer 100, ->
19
- expect(opacity()).toBeAround(0.5, 0.25)
18
+ expect(u.opacity($element)).toBeAround(0.5, 0.25)
20
19
  u.setTimer 200, ->
21
- expect(opacity()).toBeAround(1.0, 0.25)
20
+ expect(u.opacity($element)).toBeAround(1.0, 0.25)
22
21
  done()
23
22
 
23
+ it 'returns a promise that is resolved when the animation completed', (done) ->
24
+ $element = affix('.element').text('content')
25
+ resolveSpy = jasmine.createSpy('resolve')
26
+
27
+ promise = up.animate($element, 'fade-in', duration: 100, easing: 'linear')
28
+ promise.then(resolveSpy)
29
+
30
+ u.setTimer 50, ->
31
+ expect(resolveSpy).not.toHaveBeenCalled()
32
+ u.setTimer 70, ->
33
+ expect(resolveSpy).toHaveBeenCalled()
34
+ done()
35
+
24
36
  it 'cancels an existing animation on the element by instantly jumping to the last frame', ->
25
37
  $element = affix('.element').text('content')
26
38
  up.animate($element, { 'font-size': '40px' }, duration: 10000, easing: 'linear')
@@ -74,17 +86,15 @@ describe 'up.motion', ->
74
86
  it 'restores existing transitions on the element', ->
75
87
  $element = affix('.element').text('content')
76
88
  $element.css('transition': 'font-size 3s ease')
77
- oldTransition = $element.css('transition')
78
- expect(oldTransition).toContain('font-size') # be paranoid
89
+ oldTransitionProperty = $element.css('transition-property')
90
+ expect(oldTransitionProperty).toContain('font-size') # be paranoid
79
91
  up.animate($element, 'fade-in', duration: 10000)
80
92
  up.motion.finish($element)
81
- expect(Number($element.css('opacity'))).toEqual(1)
82
- currentTransition = $element.css('transition')
83
- expect(currentTransition).toEqual(oldTransition)
84
- expect(currentTransition).toContain('font-size')
85
- expect(currentTransition).not.toContain('opacity')
86
- expect(currentTransition).not.toContain('none')
87
- expect(currentTransition).not.toContain('all')
93
+ expect(u.opacity($element)).toEqual(1)
94
+ currentTransitionProperty = $element.css('transition-property')
95
+ expect(currentTransitionProperty).toEqual(oldTransitionProperty)
96
+ expect(currentTransitionProperty).toContain('font-size')
97
+ expect(currentTransitionProperty).not.toContain('opacity')
88
98
 
89
99
  it 'cancels an existing transition on the element by instantly jumping to the last frame', ->
90
100
  $old = affix('.old').text('old content')
@@ -136,17 +146,15 @@ describe 'up.motion', ->
136
146
  up.animate($element1, 'fade-in', duration: 3000)
137
147
  up.animate($element2, 'fade-in', duration: 3000)
138
148
 
139
- opacity = ($element) -> Number($element.css('opacity'))
140
-
141
- expect(opacity($element1)).toBeAround(0.0, 0.1)
142
- expect(opacity($element2)).toBeAround(0.0, 0.1)
149
+ expect(u.opacity($element1)).toBeAround(0.0, 0.1)
150
+ expect(u.opacity($element2)).toBeAround(0.0, 0.1)
143
151
 
144
152
  up.motion.finish()
145
153
 
146
154
  $element1 = $('.element1')
147
155
  $element2 = $('.element2')
148
- expect(opacity($element1)).toBe(1.0)
149
- expect(opacity($element2)).toBe(1.0)
156
+ expect(u.opacity($element1)).toBe(1.0)
157
+ expect(u.opacity($element2)).toBe(1.0)
150
158
 
151
159
  describe 'up.morph', ->
152
160
 
@@ -222,19 +230,17 @@ describe 'up.motion', ->
222
230
  height: '23px'
223
231
  )
224
232
 
225
- opacity = ($element) -> Number($element.css('opacity'))
226
-
227
233
  u.setTimer 0, ->
228
- expect(opacity($newGhost)).toBeAround(0.0, 0.25)
229
- expect(opacity($oldGhost)).toBeAround(1.0, 0.25)
234
+ expect(u.opacity($newGhost)).toBeAround(0.0, 0.25)
235
+ expect(u.opacity($oldGhost)).toBeAround(1.0, 0.25)
230
236
 
231
237
  u.setTimer 80, ->
232
- expect(opacity($newGhost)).toBeAround(0.4, 0.25)
233
- expect(opacity($oldGhost)).toBeAround(0.6, 0.25)
238
+ expect(u.opacity($newGhost)).toBeAround(0.4, 0.25)
239
+ expect(u.opacity($oldGhost)).toBeAround(0.6, 0.25)
234
240
 
235
241
  u.setTimer 140, ->
236
- expect(opacity($newGhost)).toBeAround(0.7, 0.25)
237
- expect(opacity($oldGhost)).toBeAround(0.3, 0.25)
242
+ expect(u.opacity($newGhost)).toBeAround(0.7, 0.25)
243
+ expect(u.opacity($oldGhost)).toBeAround(0.3, 0.25)
238
244
 
239
245
  u.setTimer 250, ->
240
246
  # Once our two ghosts have rendered their visual effect,
@@ -43,6 +43,78 @@ describe 'up.popup', ->
43
43
  expect(respond).not.toThrowError()
44
44
  expect($('.up-error')).not.toExist()
45
45
 
46
+ describe 'with { html } option', ->
47
+
48
+ it 'extracts the selector from the given HTML string', ->
49
+ $span = affix('span')
50
+ up.popup.attach($span, target: '.container', html: "<div class='container'>container contents</div>")
51
+ expect($('.up-popup')).toHaveText('container contents')
52
+
53
+ describe 'opening a popup while another modal is open', ->
54
+
55
+ it 'does not open multiple popups or pad the body twice if the user starts loading a second popup before the first was done loading', (done) ->
56
+ $span = affix('span')
57
+ up.popup.config.closeDuration = 10
58
+ promise1 = up.popup.attach($span, url: '/path1', target: '.container', animation: 'fade-in', duration: 100)
59
+ promise2 = up.popup.attach($span, url: '/path2', target: '.container', animation: 'fade-in', duration: 100)
60
+ expect(jasmine.Ajax.requests.count()).toBe(2)
61
+ request1 = jasmine.Ajax.requests.at(0)
62
+ request2 = jasmine.Ajax.requests.at(1)
63
+
64
+ u.setTimer 10, =>
65
+ @respondWith('<div class="container">response1</div>', request: request1)
66
+ u.setTimer 10, =>
67
+ @respondWith('<div class="container">response2</div>', request: request2)
68
+ u.setTimer 30, =>
69
+ expect($('.up-popup').length).toBe(1)
70
+ expect($('.container')).toHaveText('response2')
71
+ done()
72
+
73
+ it 'closes the current popup and wait for its close animation to finish before starting the open animation of a second popup', (done) ->
74
+ $span = affix('span')
75
+ up.popup.config.openAnimation = 'fade-in'
76
+ up.popup.config.openDuration = 5
77
+ up.popup.config.closeAnimation = 'fade-out'
78
+ up.popup.config.closeDuration = 50
79
+
80
+ events = []
81
+ u.each ['up:popup:open', 'up:popup:opened', 'up:popup:close', 'up:popup:closed'], (event) ->
82
+ up.on event, ->
83
+ events.push(event)
84
+
85
+ up.popup.attach($span, { target: '.target', html: '<div class="target">response1</div>' })
86
+
87
+ # First popup is starting opening animation
88
+ expect(events).toEqual ['up:popup:open']
89
+ expect($('.target')).toHaveText('response1')
90
+
91
+ u.setTimer 30, ->
92
+ # First popup has completed opening animation
93
+ expect(events).toEqual ['up:popup:open', 'up:popup:opened']
94
+ expect($('.target')).toHaveText('response1')
95
+
96
+ up.popup.attach($span, { target: '.target', html: '<div class="target">response2</div>' })
97
+
98
+ # First popup is starting close animation. Second popup waits for that.
99
+ expect(events).toEqual ['up:popup:open', 'up:popup:opened', 'up:popup:open', 'up:popup:close']
100
+ expect($('.target')).toHaveText('response1')
101
+
102
+ u.setTimer 15, ->
103
+
104
+ # Second popup is still waiting for first popup's closing animaton to finish.
105
+ expect(events).toEqual ['up:popup:open', 'up:popup:opened', 'up:popup:open', 'up:popup:close']
106
+ expect($('.target')).toHaveText('response1')
107
+
108
+ u.setTimer 100, ->
109
+
110
+ # First popup has finished closing, second popup has finished opening.
111
+ expect(events).toEqual ['up:popup:open', 'up:popup:opened', 'up:popup:open', 'up:popup:close', 'up:popup:closed', 'up:popup:opened']
112
+ expect($('.target')).toHaveText('response2')
113
+
114
+ done()
115
+
116
+
117
+
46
118
  describe 'up.popup.coveredUrl', ->
47
119
 
48
120
  describeCapability 'canPushState', ->
@@ -15,15 +15,28 @@ describe 'up.syntax', ->
15
15
  expect(observeClass).not.toHaveBeenCalledWith('container')
16
16
  expect(observeClass).toHaveBeenCalledWith('child')
17
17
 
18
- destructor = jasmine.createSpy('destructor')
19
- up.compiler '.child', ($element) ->
20
- destructor
21
-
22
- up.hello(affix('.container .child'))
23
- expect(destructor).not.toHaveBeenCalled()
24
-
25
- up.destroy('.container')
26
- expect(destructor).toHaveBeenCalled()
18
+ describe 'destructors', ->
19
+
20
+ it 'allows initializers to return a function that is called when the compiled element is removed', ->
21
+ destructor = jasmine.createSpy('destructor')
22
+ up.compiler '.child', ($element) ->
23
+ destructor
24
+
25
+ up.hello(affix('.container .child'))
26
+ expect(destructor).not.toHaveBeenCalled()
27
+
28
+ up.destroy('.container')
29
+ expect(destructor).toHaveBeenCalled()
30
+
31
+ it 'does not throw an error if both container and child have a destructor, and the container gets destroyed', ->
32
+ up.compiler '.container', ($element) ->
33
+ return (->)
34
+
35
+ up.compiler '.child', ($element) ->
36
+ return (->)
37
+
38
+ destruction = -> up.destroy('.container')
39
+ expect(destruction).not.toThrowError()
27
40
 
28
41
  it 'parses an up-data attribute as JSON and passes the parsed object as a second argument to the initializer', ->
29
42
 
@@ -101,7 +114,7 @@ describe 'up.syntax', ->
101
114
  up.compiler '.element', { priority: 3 }, -> traces.push('bam')
102
115
  up.compiler '.element', { priority: -1 }, -> traces.push('qux')
103
116
  up.hello(affix('.element'))
104
- expect(traces).toEqual ['qux', 'baz', 'foo', 'bar', 'bam']
117
+ expect(traces).toEqual ['bam', 'bar', 'foo', 'baz', 'qux']
105
118
 
106
119
  it 'considers priority-less compilers to be priority zero', ->
107
120
  traces = []
@@ -109,7 +122,7 @@ describe 'up.syntax', ->
109
122
  up.compiler '.element', -> traces.push('bar')
110
123
  up.compiler '.element', { priority: -1 }, -> traces.push('baz')
111
124
  up.hello(affix('.element'))
112
- expect(traces).toEqual ['baz', 'bar', 'foo']
125
+ expect(traces).toEqual ['foo', 'bar', 'baz']
113
126
 
114
127
  it 'runs two compilers with the same priority in the order in which they were registered', ->
115
128
  traces = []
@@ -126,7 +139,7 @@ describe 'up.syntax', ->
126
139
  up.compiler '.element', { priority: -1000 }, -> traces.push('bar')
127
140
  up.macro '.element', -> traces.push('baz')
128
141
  up.hello(affix('.element'))
129
- expect(traces).toEqual ['baz', 'bar' , 'foo']
142
+ expect(traces).toEqual ['baz', 'foo' , 'bar']
130
143
 
131
144
  it 'allows to macros to have priorities of their own', ->
132
145
  traces = []
@@ -135,8 +148,33 @@ describe 'up.syntax', ->
135
148
  up.macro '.element', { priority: 0 }, -> traces.push('baz')
136
149
  up.macro '.element', { priority: 3 }, -> traces.push('bam')
137
150
  up.macro '.element', { priority: -1 }, -> traces.push('qux')
151
+ up.compiler '.element', { priority: 999 }, -> traces.push('ccc')
152
+ up.hello(affix('.element'))
153
+ expect(traces).toEqual ['bam', 'bar', 'foo', 'baz', 'qux', 'ccc']
154
+
155
+ it 'runs two macros with the same priority in the order in which they were registered', ->
156
+ traces = []
157
+ up.macro '.element', { priority: 1 }, -> traces.push('foo')
158
+ up.macro '.element', { priority: 1 }, -> traces.push('bar')
138
159
  up.hello(affix('.element'))
139
- expect(traces).toEqual ['qux', 'baz', 'foo', 'bar', 'bam']
160
+ expect(traces).toEqual ['foo', 'bar']
161
+
162
+ it 'allows users to use the built-in [up-expand] from their own macros', ->
163
+ up.macro '.element', ($element) ->
164
+ $element.attr('up-expand', '')
165
+ $element = affix('.element a[href="/foo"][up-target=".target"]')
166
+ up.hello($element)
167
+ expect($element.attr('up-target')).toEqual('.target')
168
+ expect($element.attr('up-href')).toEqual('/foo')
169
+
170
+ it 'allows users to use the built-in [up-dash] from their own macros', ->
171
+ up.macro '.element', ($element) ->
172
+ $element.attr('up-dash', '.target')
173
+ $element = affix('a.element[href="/foo"]')
174
+ up.hello($element)
175
+ expect($element.attr('up-target')).toEqual('.target')
176
+ expect($element.attr('up-preload')).toEqual('')
177
+ expect($element.attr('up-instant')).toEqual('')
140
178
 
141
179
  describe 'up.hello', ->
142
180
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: unpoly-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.24.1
4
+ version: 0.25.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Henning Koch
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-04-07 00:00:00.000000000 Z
11
+ date: 2016-04-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails