unpoly-rails 0.24.1 → 0.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


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

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