upjs-rails 0.10.5 → 0.11.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.
@@ -155,21 +155,49 @@ up.motion = (->
155
155
 
156
156
  GHOSTING_PROMISE_KEY = 'up-ghosting-promise'
157
157
 
158
- withGhosts = ($old, $new, block) ->
159
- oldCopy = null
160
- newCopy = null
158
+ withGhosts = ($old, $new, options, block) ->
159
+
160
+ oldCopy = undefined
161
+ newCopy = undefined
162
+ oldScrollTop = undefined
163
+ newScrollTop = undefined
164
+
165
+ $viewport = up.layout.viewportOf($old)
161
166
 
162
167
  u.temporaryCss $new, display: 'none', ->
163
- oldCopy = prependGhost($old)
168
+ # Within this block, $new is hidden but $old is visible
169
+ oldCopy = prependCopy($old, $viewport)
164
170
  oldCopy.$ghost.addClass('up-destroying')
165
171
  oldCopy.$bounds.addClass('up-destroying')
172
+ # Remember the previous scroll position in case we will reveal $new below.
173
+ oldScrollTop = $viewport.scrollTop()
174
+ # $viewport.scrollTop(oldScrollTop + 1)
175
+
166
176
  u.temporaryCss $old, display: 'none', ->
167
- newCopy = prependGhost($new)
168
- # $old should take up space in the page flow until the transition ends
169
- $old.css(visibility: 'hidden')
170
-
171
- showNew = u.temporaryCss($new, display: 'none')
177
+ # Within this block, $old is hidden but $new is visible
178
+ up.reveal($new, viewport: $viewport) if options.reveal
179
+ newCopy = prependCopy($new, $viewport)
180
+ newScrollTop = $viewport.scrollTop()
181
+
182
+ # Since we have scrolled the viewport (containing both $old and $new),
183
+ # we must shift the old copy so it looks like it it is still sitting
184
+ # in the same position.
185
+ oldCopy.moveTop(newScrollTop - oldScrollTop)
186
+
187
+ # Hide $old since we no longer need it.
188
+ $old.hide()
189
+
190
+ # We will let $new take up space in the element flow, but hide it.
191
+ # The user will only see the two animated ghosts until the transition
192
+ # is over.
193
+ showNew = u.temporaryCss($new, visibility: 'hidden')
194
+
172
195
  promise = block(oldCopy.$ghost, newCopy.$ghost)
196
+
197
+ # Make a way to look at $old and $new and see if an animation is
198
+ # already in progress. If someone attempted a new animation on the
199
+ # same elements, the stored promises would be resolved by the second
200
+ # animation call, making the transition jump to the last frame instantly.
173
201
  $old.data(GHOSTING_PROMISE_KEY, promise)
174
202
  $new.data(GHOSTING_PROMISE_KEY, promise)
175
203
 
@@ -179,9 +207,6 @@ up.motion = (->
179
207
  oldCopy.$bounds.remove()
180
208
  newCopy.$bounds.remove()
181
209
  # Now that the transition is over we show $new again.
182
- # Since we expect $old to be removed in a heartbeat,
183
- # $new should take up space
184
- $old.css(display: 'none')
185
210
  showNew()
186
211
 
187
212
  promise
@@ -207,11 +232,11 @@ up.motion = (->
207
232
  u.debug('Canceling existing ghosting on %o', $element)
208
233
  existingGhosting.resolve?()
209
234
 
210
- assertIsDeferred = (object, origin) ->
235
+ assertIsDeferred = (object, source) ->
211
236
  if u.isDeferred(object)
212
237
  object
213
238
  else
214
- u.error("Did not return a promise with .then and .resolve methods: %o", origin)
239
+ u.error("Did not return a promise with .then and .resolve methods: %o", source)
215
240
 
216
241
  ###*
217
242
  Performs an animated transition between two elements.
@@ -252,25 +277,31 @@ up.motion = (->
252
277
  The timing function that controls the transition's acceleration.
253
278
  See [W3C documentation](http://www.w3.org/TR/css3-transitions/#transition-timing-function)
254
279
  for a list of pre-defined timing functions.
280
+ @param {Boolean} [options.reveal=false]
281
+ Whether to reveal the new element by scrolling its parent viewport.
255
282
  @return {Promise}
256
283
  A promise for the transition's end.
257
284
  ###
258
285
  morph = (source, target, transitionOrName, options) ->
259
286
  if up.browser.canCssAnimation()
260
- options = animateOptions(options)
287
+ parsedOptions = u.only(options, 'reveal')
288
+ parsedOptions = u.extend(parsedOptions, animateOptions(options))
261
289
  $old = $(source)
262
290
  $new = $(target)
291
+
263
292
  finish($old)
264
293
  finish($new)
265
- if transitionOrName == 'none' or transitionOrName == false
266
- # don't create ghosts if we aren't really transitioning
267
- none()
268
- else if transition = u.presence(transitionOrName, u.isFunction) || transitions[transitionOrName]
269
- withGhosts $old, $new, ($oldGhost, $newGhost) ->
270
- assertIsDeferred(transition($oldGhost, $newGhost, options), transitionOrName)
271
- else if animation = animations[transitionOrName]
294
+
295
+ if transitionOrName == 'none' || transitionOrName == false || animation = animations[transitionOrName]
272
296
  $old.hide()
273
- animate($new, animation, options)
297
+ # Since we can not longer rely on withGhosts to process options.reveal
298
+ # in this branch, we need to do it ourselves.
299
+ up.reveal($new) if options.reveal
300
+ animate($new, animation || 'none', options)
301
+ else if transition = u.presence(transitionOrName, u.isFunction) || transitions[transitionOrName]
302
+ withGhosts $old, $new, parsedOptions, ($oldGhost, $newGhost) ->
303
+ transitionPromise = transition($oldGhost, $newGhost, parsedOptions)
304
+ assertIsDeferred(transitionPromise, transitionOrName)
274
305
  else if u.isString(transitionOrName) && transitionOrName.indexOf('/') >= 0
275
306
  parts = transitionOrName.split('/')
276
307
  transition = ($old, $new, options) ->
@@ -278,7 +309,7 @@ up.motion = (->
278
309
  animate($old, parts[0], options),
279
310
  animate($new, parts[1], options)
280
311
  )
281
- morph($old, $new, transition, options)
312
+ morph($old, $new, transition, parsedOptions)
282
313
  else
283
314
  u.error("Unknown transition %o", transitionOrName)
284
315
  else
@@ -289,7 +320,7 @@ up.motion = (->
289
320
  ###*
290
321
  @private
291
322
  ###
292
- prependGhost = ($element) ->
323
+ prependCopy = ($element, $viewport) ->
293
324
  elementDims = u.measure($element, relative: true, inner: true)
294
325
 
295
326
  $ghost = $element.clone()
@@ -313,16 +344,29 @@ up.motion = (->
313
344
  $bounds.css(position: 'absolute')
314
345
  $bounds.css(elementDims)
315
346
 
347
+ top = elementDims.top
348
+
349
+ moveTop = (diff) ->
350
+ if diff != 0
351
+ top += diff
352
+ $bounds.css(top: top)
353
+
316
354
  $ghost.appendTo($bounds)
317
355
  $bounds.insertBefore($element)
318
356
 
319
- # Make sure that we don't shift a child element with margins
320
- # that can no longer collapse against a previous sibling
321
- diff = $ghost.offset().top - $element.offset().top
322
- $bounds.css(top: elementDims.top - diff) if diff != 0
357
+ # In theory, $ghost should now sit over $element perfectly.
358
+ # However, $element might collapse its margin against a previous sibling
359
+ # element, and $ghost does not have the same sibling.
360
+ # So we manually correct $ghost's top position so it aligns with $element.
361
+ moveTop($element.offset().top - $ghost.offset().top)
362
+
363
+ $fixedElements = up.layout.fixedChildren($ghost)
364
+ for fixedElement in $fixedElements
365
+ u.fixedToAbsolute(fixedElement, $viewport)
323
366
 
324
367
  $ghost: $ghost
325
368
  $bounds: $bounds
369
+ moveTop: moveTop
326
370
 
327
371
  ###*
328
372
  Defines a named transition.
@@ -543,7 +587,7 @@ up.motion = (->
543
587
  defaults: config.update
544
588
  none: none
545
589
  when: resolvableWhen
546
- prependGhost: prependGhost
590
+ prependCopy: prependCopy
547
591
 
548
592
  )()
549
593
 
@@ -254,8 +254,12 @@ up.popup = (->
254
254
  @ujs
255
255
  ###
256
256
  up.on('click', '[up-close]', (event, $element) ->
257
- if $element.closest('.up-popup')
257
+ if $element.closest('.up-popup').length
258
258
  close()
259
+ # Only prevent the default when we actually closed a popup.
260
+ # This way we can have buttons that close a popup when within a popup,
261
+ # but link to a destination if not.
262
+ event.preventDefault()
259
263
  )
260
264
 
261
265
  # The framework is reset between tests
@@ -189,6 +189,8 @@ up.proxy = (->
189
189
  else
190
190
  promise = load(request)
191
191
  set(request, promise)
192
+ # Don't cache failed requests
193
+ promise.fail -> remove(request)
192
194
 
193
195
  if pending && !options.preload
194
196
  # This will actually make `pendingCount` higher than the actual
@@ -529,24 +529,38 @@ up.util = (->
529
529
  if existingAnimation = $(this).data(ANIMATION_PROMISE_KEY)
530
530
  existingAnimation.resolve()
531
531
 
532
- measure = ($element, options) ->
533
- coordinates = if options?.relative
534
- $element.position()
532
+ measure = ($element, opts) ->
533
+ opts = options(opts, relative: false, inner: false, full: false)
534
+
535
+ if opts.relative
536
+ if opts.relative == true
537
+ coordinates = $element.position()
538
+ else
539
+ $context = $(opts.relative)
540
+ elementCoords = $element.offset()
541
+ if $context.is(document)
542
+ # The document is always at the origin
543
+ coordinates = elementCoords
544
+ else
545
+ contextCoords = $context.offset()
546
+ coordinates =
547
+ left: elementCoords.left - contextCoords.left
548
+ top: elementCoords.top - contextCoords.top
535
549
  else
536
- $element.offset()
550
+ coordinates = $element.offset()
537
551
 
538
552
  box =
539
553
  left: coordinates.left
540
554
  top: coordinates.top
541
555
 
542
- if options?.inner
556
+ if opts.inner
543
557
  box.width = $element.width()
544
558
  box.height = $element.height()
545
559
  else
546
560
  box.width = $element.outerWidth()
547
561
  box.height = $element.outerHeight()
548
562
 
549
- if options?.full
563
+ if opts.full
550
564
  viewport = clientSize()
551
565
  box.right = viewport.width - (box.left + box.width)
552
566
  box.bottom = viewport.height - (box.top + box.height)
@@ -838,6 +852,33 @@ up.util = (->
838
852
  parent.insertBefore(wrappedNode, wrapper)
839
853
  parent.removeChild(wrapper)
840
854
 
855
+ offsetParent = ($element) ->
856
+ $match = undefined
857
+ while ($element = $element.parent()) && $element.length
858
+ position = $element.css('position')
859
+ console.log("Iteration element is %o with position %o", $element, position)
860
+ if position == 'absolute' || position == 'relative' || $element.is('body')
861
+ $match = $element
862
+ break
863
+ $match
864
+
865
+ fixedToAbsolute = (element, $viewport) ->
866
+ $element = $(element)
867
+ $futureOffsetParent = offsetParent($element)
868
+ # To get a fixed elements distance from the edge of the screen,
869
+ # use position(), not offset(). offset() would include the current
870
+ # scrollTop of the viewport.
871
+ elementCoords = $element.position()
872
+ futureParentCoords = $futureOffsetParent.offset()
873
+ $element.css
874
+ position: 'absolute'
875
+ left: elementCoords.left - futureParentCoords.left
876
+ top: elementCoords.top - futureParentCoords.top + $viewport.scrollTop()
877
+ right: ''
878
+ bottom: ''
879
+
880
+ offsetParent: offsetParent
881
+ fixedToAbsolute: fixedToAbsolute
841
882
  presentAttr: presentAttr
842
883
  createElement: createElement
843
884
  normalizeUrl: normalizeUrl
@@ -3,3 +3,4 @@ defaults = up.layout.defaults()
3
3
  up.layout.defaults
4
4
  fixedTop: defaults.fixedTop.concat(['.navbar-fixed-top'])
5
5
  fixedBottom: defaults.fixedBottom.concat(['.navbar-fixed-bottom'])
6
+ anchoredRight: defaults.anchoredRight.concat(['.navbar-fixed-top', '.navbar-fixed-bottom', '.footer'])
@@ -1,19 +1,19 @@
1
1
  module Upjs
2
2
  module Rails
3
- module CurrentLocation
3
+ module RequestEchoHeaders
4
4
 
5
5
  def self.included(base)
6
- base.before_filter :set_header_for_current_location
6
+ base.before_filter :set_up_request_echo_headers
7
7
  end
8
8
 
9
9
  private
10
10
 
11
- def set_header_for_current_location
11
+ def set_up_request_echo_headers
12
12
  headers['X-Up-Location'] = request.original_url
13
13
  headers['X-Up-Method'] = request.method
14
14
  end
15
15
 
16
- ActionController::Base.include(self)
16
+ ActionController::Base.send(:include, self)
17
17
 
18
18
  end
19
19
  end
@@ -6,7 +6,7 @@ module Upjs
6
6
  headers['X-Up-Selector'].present?
7
7
  end
8
8
 
9
- ActionDispatch::Request.include(self)
9
+ ActionDispatch::Request.send(:include, self)
10
10
 
11
11
  end
12
12
  end
@@ -0,0 +1,28 @@
1
+ # See
2
+ # https://github.com/rails/turbolinks/search?q=request_method&ref=cmdform
3
+ # https://github.com/rails/turbolinks/blob/83d4b3d2c52a681f07900c28adb28bc8da604733/README.md#initialization
4
+ module Upjs
5
+ module Rails
6
+ module RequestMethod
7
+
8
+ COOKIE_NAME = '_up_request_method'
9
+
10
+ def self.included(base)
11
+ base.before_filter :set_up_request_method_cookie
12
+ end
13
+
14
+ private
15
+
16
+ def set_up_request_method_cookie
17
+ if request.get?
18
+ cookies.delete(COOKIE_NAME)
19
+ else
20
+ cookies[COOKIE_NAME] = request.request_method
21
+ end
22
+ end
23
+
24
+ ActionController::Base.send(:include, self)
25
+
26
+ end
27
+ end
28
+ end
@@ -1,5 +1,5 @@
1
1
  module Upjs
2
2
  module Rails
3
- VERSION = '0.10.5'
3
+ VERSION = '0.11.0'
4
4
  end
5
5
  end
data/lib/upjs-rails.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  require "upjs/rails/version"
2
2
  require "upjs/rails/engine"
3
- require "upjs/rails/current_location"
4
- require "upjs/rails/request"
3
+ require "upjs/rails/request_echo_headers"
4
+ require "upjs/rails/request_method_cookie"
5
+ require "upjs/rails/request_ext"
@@ -0,0 +1,5 @@
1
+ # We have a lot of specs that measure the distance of elements
2
+ # from the screen edge, so we can't have the Jasmine runner to have
3
+ # any margins on the body.
4
+ beforeEach ->
5
+ $('body').css('margin-top': 0)
@@ -124,11 +124,12 @@ describe 'up.flow', ->
124
124
  @revealedHTML = $revealedElement.get(0).outerHTML
125
125
  u.resolvedPromise()
126
126
 
127
- it 'reveals an old element before it is being replaced', (done) ->
127
+ it 'reveals a new element before it is being replaced', (done) ->
128
128
  @request = up.replace('.middle', '/path', reveal: true)
129
129
  @respond()
130
130
  @request.then =>
131
- expect(up.reveal).toHaveBeenCalledWith(@oldMiddle)
131
+ expect(up.reveal).not.toHaveBeenCalledWith(@oldMiddle)
132
+ expect(@revealedHTML).toContain('new-middle')
132
133
  done()
133
134
 
134
135
  it 'reveals a new element that is being appended', (done) ->
@@ -16,11 +16,8 @@ describe 'up.history', ->
16
16
 
17
17
  it 'sets an [up-href] attribute to the previous URL and sets the up-restore-scroll attribute to "true"', ->
18
18
  up.history.push('/one')
19
- console.log("url is now %o, previous was %o", up.history.url(), up.history.previousUrl())
20
19
  up.history.push('/two')
21
- console.log("url is now %o, previous was %o", up.history.url(), up.history.previousUrl())
22
20
  $element = up.ready(affix('a[href="/three"][up-back]').text('text'))
23
- console.log("url is now %o, previous was %o", up.history.url(), up.history.previousUrl())
24
21
  expect($element.attr('href')).toEndWith('/three')
25
22
  expect($element.attr('up-href')).toEndWith('/one')
26
23
  expect($element.attr('up-restore-scroll')).toBe('')
@@ -17,8 +17,6 @@ describe 'up.layout', ->
17
17
  beforeEach ->
18
18
  $body = $('body')
19
19
 
20
- @restoreMargin = u.temporaryCss($body, 'margin-top': 0)
21
-
22
20
  @$elements = []
23
21
  @$container = $('<div class="container">').prependTo($body)
24
22
 
@@ -31,7 +29,6 @@ describe 'up.layout', ->
31
29
 
32
30
  afterEach ->
33
31
  @$container.remove()
34
- @restoreMargin()
35
32
 
36
33
  it 'reveals the given element', ->
37
34
  up.reveal(@$elements[0])
@@ -8,6 +8,8 @@ describe 'up.modal', ->
8
8
 
9
9
  describe 'up.modal.open', ->
10
10
 
11
+ assumedScrollbarWidth = 15
12
+
11
13
  it "loads the given link's destination in a dialog window", (done) ->
12
14
  $link = affix('a[href="/path/to"][up-modal=".middle"]').text('link')
13
15
  promise = up.modal.open($link)
@@ -49,7 +51,7 @@ describe 'up.modal', ->
49
51
  expect($modal).toExist()
50
52
  expect($modal.css('overflow-y')).toEqual('scroll')
51
53
  expect($body.css('overflow-y')).toEqual('hidden')
52
- expect(parseInt($body.css('padding-right'))).toBeAround(15, 10)
54
+ expect(parseInt($body.css('padding-right'))).toBeAround(assumedScrollbarWidth, 10)
53
55
 
54
56
  up.modal.close().then ->
55
57
  expect($body.css('overflow-y')).toEqual('scroll')
@@ -57,6 +59,30 @@ describe 'up.modal', ->
57
59
 
58
60
  done()
59
61
 
62
+ it 'pushes right-anchored elements away from the edge of the screen in order to prevent jumping', (done) ->
63
+
64
+ $anchoredElement = affix('div[up-anchored=right]').css
65
+ position: 'absolute'
66
+ top: '0'
67
+ right: '30px'
68
+
69
+ promise = up.modal.open(url: '/foo', target: '.container')
70
+
71
+ @lastRequest().respondWith
72
+ status: 200
73
+ contentType: 'text/html'
74
+ responseText:
75
+ """
76
+ <div class="container">text</div>
77
+ """
78
+
79
+ promise.then ->
80
+ expect(parseInt($anchoredElement.css('right'))).toBeAround(30 + assumedScrollbarWidth, 10)
81
+
82
+ up.modal.close().then ->
83
+ expect(parseInt($anchoredElement.css('right'))).toBeAround(30 , 10)
84
+ done()
85
+
60
86
  describe 'up.modal.close', ->
61
87
 
62
88
  it 'should have tests'
@@ -37,7 +37,7 @@ describe 'up.motion', ->
37
37
 
38
38
  if up.browser.canCssAnimation()
39
39
 
40
- it 'transitions between two element using ghosts', (done) ->
40
+ it 'transitions between two element by animating two copies while keeping the originals in the background', (done) ->
41
41
 
42
42
  $old = affix('.old').text('old content').css(
43
43
  position: 'absolute'
@@ -53,7 +53,7 @@ describe 'up.motion', ->
53
53
  width: '22px',
54
54
  height: '23px'
55
55
  )
56
- up.morph($old, $new, 'cross-fade', duration: 100, easing: 'linear')
56
+ up.morph($old, $new, 'cross-fade', duration: 200, easing: 'linear')
57
57
 
58
58
  # The actual animation will be performed on Ghosts since
59
59
  # two element usually cannot exist in the DOM at the same time
@@ -76,14 +76,12 @@ describe 'up.motion', ->
76
76
 
77
77
  # The actual elements are hidden, but $old will take up its original
78
78
  # space until the animation completes.
79
- expect($old.css(['display', 'visibility'])).toEqual(
79
+ expect($old.css('display')).toEqual('none')
80
+
81
+ expect($new.css(['display', 'visibility'])).toEqual(
80
82
  display: 'block',
81
83
  visibility: 'hidden'
82
84
  )
83
- expect($new.css(['display', 'visibility'])).toEqual(
84
- display: 'none',
85
- visibility: 'visible'
86
- )
87
85
 
88
86
  # Ghosts will hover over $old and $new using absolute positioning,
89
87
  # matching the coordinates of the original elements.
@@ -108,15 +106,15 @@ describe 'up.motion', ->
108
106
  expect(opacity($newGhost)).toBeAround(0.0, 0.25)
109
107
  expect(opacity($oldGhost)).toBeAround(1.0, 0.25)
110
108
 
111
- @setTimer 40, ->
109
+ @setTimer 80, ->
112
110
  expect(opacity($newGhost)).toBeAround(0.4, 0.25)
113
111
  expect(opacity($oldGhost)).toBeAround(0.6, 0.25)
114
112
 
115
- @setTimer 70, ->
113
+ @setTimer 140, ->
116
114
  expect(opacity($newGhost)).toBeAround(0.7, 0.25)
117
115
  expect(opacity($oldGhost)).toBeAround(0.3, 0.25)
118
116
 
119
- @setTimer 125, ->
117
+ @setTimer 250, ->
120
118
  # Once our two ghosts have rendered their visual effect,
121
119
  # we remove them from the DOM.
122
120
  expect($newGhost).not.toBeInDOM()
@@ -124,10 +122,7 @@ describe 'up.motion', ->
124
122
 
125
123
  # The old element is still in the DOM, but hidden.
126
124
  # Morphing does *not* remove the target element.
127
- expect($old.css(['display', 'visibility'])).toEqual(
128
- display: 'none',
129
- visibility: 'hidden'
130
- )
125
+ expect($old.css('display')).toEqual('none')
131
126
  expect($new.css(['display', 'visibility'])).toEqual(
132
127
  display: 'block',
133
128
  visibility: 'visible'
@@ -150,6 +145,39 @@ describe 'up.motion', ->
150
145
  # Check that it's a different ghosts
151
146
  expect($ghost2).not.toEqual($ghost1)
152
147
 
148
+ describe 'with { reveal: true } option', ->
149
+
150
+ it 'reveals the new element while making the old element within the same viewport appear as if it would keep its scroll position', ->
151
+ $container = affix('.container[up-viewport]').css
152
+ 'width': '200px'
153
+ 'height': '200px'
154
+ 'overflow-y': 'scroll'
155
+ 'position': 'fixed'
156
+ 'left': 0,
157
+ 'top': 0
158
+ $old = affix('.old').appendTo($container).css(height: '600px')
159
+ $container.scrollTop(300)
160
+
161
+ $new = affix('.new').insertBefore($old).css(height: '600px')
162
+
163
+ up.morph($old, $new, 'cross-fade', duration: 50, reveal: true)
164
+
165
+ $oldGhost = $('.old.up-ghost')
166
+ $newGhost = $('.new.up-ghost')
167
+
168
+ # Container is scrolled up due to { reveal: true } option.
169
+ # Since $old and $new are sitting in the same viewport with a
170
+ # single shares scrollbar This will make the ghost for $old jump.
171
+ expect($container.scrollTop()).toEqual(0)
172
+
173
+ # See that the ghost for $new is aligned with the top edge
174
+ # of the viewport.
175
+ expect($newGhost.offset().top).toEqual(0)
176
+
177
+ # The ghost for $old is shifted upwards to make it looks like it
178
+ # was at the scroll position before we revealed $new.
179
+ expect($oldGhost.offset().top).toEqual(-300)
180
+
153
181
  else
154
182
 
155
183
  it "doesn't animate and hides the first element instead", ->
@@ -171,14 +199,14 @@ describe 'up.motion', ->
171
199
 
172
200
  it 'should have tests'
173
201
 
174
- describe 'up.motion.prependGhost', ->
202
+ describe 'up.motion.prependCopy', ->
175
203
 
176
204
  afterEach ->
177
205
  $('.up-bounds, .up-ghost, .fixture').remove()
178
206
 
179
207
  it 'clones the given element into a .up-ghost-bounds container and inserts it as a sibling before the element', ->
180
208
  $element = affix('.element').text('element text')
181
- up.motion.prependGhost($element)
209
+ up.motion.prependCopy($element)
182
210
  $bounds = $element.prev()
183
211
  expect($bounds).toExist()
184
212
  expect($bounds).toHaveClass('up-bounds')
@@ -190,13 +218,13 @@ describe 'up.motion', ->
190
218
  it 'removes <script> tags from the cloned element', ->
191
219
  $element = affix('.element')
192
220
  $('<script></script>').appendTo($element)
193
- up.motion.prependGhost($element)
221
+ up.motion.prependCopy($element)
194
222
  $ghost = $('.up-ghost')
195
223
  expect($ghost.find('script')).not.toExist()
196
224
 
197
225
  it 'absolutely positions the ghost over the given element', ->
198
226
  $element = affix('.element')
199
- up.motion.prependGhost($element)
227
+ up.motion.prependCopy($element)
200
228
  $ghost = $('.up-ghost')
201
229
  expect($ghost.offset()).toEqual($element.offset())
202
230
  expect($ghost.width()).toEqual($element.width())
@@ -204,25 +232,43 @@ describe 'up.motion', ->
204
232
 
205
233
  it 'accurately positions the ghost over an element with margins', ->
206
234
  $element = affix('.element').css(margin: '40px')
207
- up.motion.prependGhost($element)
235
+ up.motion.prependCopy($element)
208
236
  $ghost = $('.up-ghost')
209
237
  expect($ghost.offset()).toEqual($element.offset())
210
238
 
211
239
  it "doesn't change the position of a child whose margins no longer collapse", ->
212
240
  $element = affix('.element')
213
241
  $child = $('<div class="child"></div>').css(margin: '40px').appendTo($element)
214
- up.motion.prependGhost($element)
242
+ up.motion.prependCopy($element)
215
243
  $clonedChild = $('.up-ghost .child')
216
244
  expect($clonedChild.offset()).toEqual($child.offset())
217
245
 
218
246
  it 'correctly positions the ghost over an element within a scrolled body', ->
219
- $body = $('body').css(margin: 0)
247
+ $body = $('body')
220
248
  $element1 = $('<div class="fixture"></div>').css(height: '75px').prependTo($body)
221
249
  $element2 = $('<div class="fixture"></div>').css(height: '100px').insertAfter($element1)
222
250
  $body.scrollTop(17)
223
- { $bounds, $ghost } = up.motion.prependGhost($element2)
251
+ { $bounds, $ghost } = up.motion.prependCopy($element2)
224
252
  expect($bounds.css('position')).toBe('absolute')
225
253
  expect($bounds.css('top')).toEqual('75px')
226
254
  expect($ghost.css('position')).toBe('static')
227
255
 
228
256
  it 'correctly positions the ghost over an element within a viewport with overflow-y: scroll'
257
+
258
+ it 'converts fixed elements within the copies to absolutely positioning', ->
259
+ $element = affix('.element').css
260
+ position: 'absolute'
261
+ top: '50px'
262
+ left: '50px'
263
+ $fixedChild = $('<div class="fixed-child" up-fixed></div>').css
264
+ position: 'fixed'
265
+ left: '77px'
266
+ top: '77px'
267
+ $fixedChild.appendTo($element)
268
+ up.motion.prependCopy($element, $('body'))
269
+ $fixedChildGhost = $('.up-ghost .fixed-child')
270
+ expect($fixedChildGhost.css(['position', 'left', 'top'])).toEqual
271
+ position: 'absolute',
272
+ left: '27px',
273
+ top: '27px'
274
+