spec_app/spec/javascripts/up/modal_spec.js.coffee in unpoly-rails-0.24.1 vs spec_app/spec/javascripts/up/modal_spec.js.coffee in unpoly-rails-0.25.0
- old
+ new
@@ -45,55 +45,235 @@
# Can't change URLs
expect(location.href).toEqual(oldHref)
describe 'up.modal.visit', ->
- it "brings its own scrollbar, padding the body on the right in order to prevent jumping", (done) ->
- promise = up.modal.visit('/foo', target: '.container')
+ describe 'preventing elements from jumping as scrollbars change', ->
- @respondWith('<div class="container">text</div>')
+ it "brings its own scrollbar, padding the body on the right", (done) ->
+ promise = up.modal.visit('/foo', target: '.container')
- promise.then ->
+ @respondWith('<div class="container">text</div>')
+
+ promise.then ->
+ $modal = $('.up-modal')
+ $viewport = $modal.find('.up-modal-viewport')
+ $body = $('body')
+ expect($modal).toExist()
+ expect($viewport.css('overflow-y')).toEqual('scroll')
+ expect($body.css('overflow-y')).toEqual('hidden')
+ expect(parseInt($body.css('padding-right'))).toBeAround(assumedScrollbarWidth, 5)
+
+ up.modal.close().then ->
+ expect($body.css('overflow-y')).toEqual('scroll')
+ expect(parseInt($body.css('padding-right'))).toBe(0)
+ done()
+
+ 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) ->
+ openPromise = up.modal.extract('.container', '<div class="container">text</div>', animation: 'fade-in', duration: 100)
$modal = $('.up-modal')
$viewport = $modal.find('.up-modal-viewport')
- $body = $('body')
- expect($modal).toExist()
- expect($viewport.css('overflow-y')).toEqual('scroll')
- expect($body.css('overflow-y')).toEqual('hidden')
- expect(parseInt($body.css('padding-right'))).toBeAround(assumedScrollbarWidth, 10)
+ expect($modal.css('overflow-y')).toEqual('scroll')
+ expect($viewport.css('overflow-y')).toEqual('hidden')
+ openPromise.then ->
+ expect($modal.css('overflow-y')).toEqual('auto')
+ expect($viewport.css('overflow-y')).toEqual('scroll')
+ closePromise = up.modal.close(animation: 'fade-out', duration: 100)
+ expect($modal.css('overflow-y')).toEqual('scroll')
+ expect($viewport.css('overflow-y')).toEqual('hidden')
+ done()
- up.modal.close().then ->
- expect($body.css('overflow-y')).toEqual('scroll')
+
+ it 'does not add right padding to the body if the body has overflow-y: hidden', (done) ->
+ restoreBody = u.temporaryCss($('body'), 'overflow-y': 'hidden')
+
+ up.modal.extract('.container', '<div class="container">text</div>').then ->
+ $body = $('body')
+ expect($('.up-modal')).toExist()
expect(parseInt($body.css('padding-right'))).toBe(0)
- done()
+ up.modal.close().then ->
+ expect(parseInt($body.css('padding-right'))).toBe(0)
+ restoreBody()
+ done()
- it 'pushes right-anchored elements away from the edge of the screen in order to prevent jumping', (done) ->
+ it 'does not add right padding to the body if the body has overflow-y: auto, but does not currently have scrollbars', (done) ->
+ restoreBody = u.temporaryCss($('body'), 'overflow-y': 'auto')
+ restoreReporter = u.temporaryCss($('.jasmine_html-reporter'), 'height': '100px', 'overflow-y': 'hidden')
- $anchoredElement = affix('div[up-anchored=right]').css
- position: 'absolute'
- top: '0'
- right: '30px'
+ up.modal.extract('.container', '<div class="container">text</div>').then ->
+ $body = $('body')
+ expect($('.up-modal')).toExist()
+ expect(parseInt($body.css('padding-right'))).toBe(0)
- promise = up.modal.visit('/foo', target: '.container')
+ up.modal.close().then ->
+ expect(parseInt($body.css('padding-right'))).toBe(0)
+ restoreReporter()
+ restoreBody()
+ done()
- @respondWith('<div class="container">text</div>')
+ it 'pushes right-anchored elements away from the edge of the screen', (done) ->
- promise.then ->
- expect(parseInt($anchoredElement.css('right'))).toBeAround(30 + assumedScrollbarWidth, 10)
+ $anchoredElement = affix('div[up-anchored=right]').css
+ position: 'absolute'
+ top: '0'
+ right: '30px'
- up.modal.close().then ->
- expect(parseInt($anchoredElement.css('right'))).toBeAround(30 , 10)
+ promise = up.modal.visit('/foo', target: '.container')
+
+ @respondWith('<div class="container">text</div>')
+
+ promise.then ->
+ expect(parseInt($anchoredElement.css('right'))).toBeAround(30 + assumedScrollbarWidth, 10)
+
+ up.modal.close().then ->
+ expect(parseInt($anchoredElement.css('right'))).toBeAround(30 , 10)
+ done()
+
+ describe 'opening a modal while another modal is open', ->
+
+ 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) ->
+ up.modal.config.closeDuration = 10
+ promise1 = up.modal.visit('/path1', target: '.container', animation: 'fade-in', duration: 100)
+ promise2 = up.modal.visit('/path2', target: '.container', animation: 'fade-in', duration: 100)
+ expect(jasmine.Ajax.requests.count()).toBe(2)
+ request1 = jasmine.Ajax.requests.at(0)
+ request2 = jasmine.Ajax.requests.at(1)
+
+ u.setTimer 10, =>
+ @respondWith('<div class="container">response1</div>', request: request1)
+ u.setTimer 10, =>
+ @respondWith('<div class="container">response2</div>', request: request2)
+ u.setTimer 50, =>
+ expect($('.up-modal').length).toBe(1)
+ expect($('.up-modal-dialog').length).toBe(1)
+ expect($('.container')).toHaveText('response2')
+ bodyPadding = parseInt($('body').css('padding-right'))
+ expect(bodyPadding).toBeAround(assumedScrollbarWidth, 10)
+ expect(bodyPadding).not.toBeAround(2 * assumedScrollbarWidth, 2 * 5)
+ done()
+
+ it 'closes the current modal and wait for its close animation to finish before starting the open animation of a second modal', (done) ->
+ up.modal.config.openAnimation = 'fade-in'
+ up.modal.config.openDuration = 5
+ up.modal.config.closeAnimation = 'fade-out'
+ up.modal.config.closeDuration = 50
+
+ events = []
+ u.each ['up:modal:open', 'up:modal:opened', 'up:modal:close', 'up:modal:closed'], (event) ->
+ up.on event, ->
+ events.push(event)
+
+ up.modal.extract('.target', '<div class="target">response1</div>')
+
+ # First modal is starting opening animation
+ expect(events).toEqual ['up:modal:open']
+ expect($('.target')).toHaveText('response1')
+
+ u.setTimer 40, ->
+ # First modal has completed opening animation
+ expect(events).toEqual ['up:modal:open', 'up:modal:opened']
+ expect($('.target')).toHaveText('response1')
+
+ up.modal.extract('.target', '<div class="target">response2</div>')
+
+ # First modal is starting close animation. Second modal waits for that.
+ expect(events).toEqual ['up:modal:open', 'up:modal:opened', 'up:modal:open', 'up:modal:close']
+ expect($('.target')).toHaveText('response1')
+
+ u.setTimer 40, ->
+
+ # Second modal is still waiting for first modal's closing animaton to finish.
+ expect(events).toEqual ['up:modal:open', 'up:modal:opened', 'up:modal:open', 'up:modal:close']
+ expect($('.target')).toHaveText('response1')
+
+ u.setTimer 100, ->
+
+ # First modal has finished closing, second modal has finished opening.
+ expect(events).toEqual ['up:modal:open', 'up:modal:opened', 'up:modal:open', 'up:modal:close', 'up:modal:closed', 'up:modal:opened']
+ expect($('.target')).toHaveText('response2')
+
+ done()
+
+ it 'closes an opening modal if a second modal starts opening before the first modal has finished its open animation', (done) ->
+ up.modal.config.openAnimation = 'fade-in'
+ up.modal.config.openDuration = 50
+ up.modal.config.closeAnimation = 'fade-out'
+ up.modal.config.closeDuration = 50
+
+ up.modal.extract('.target', '<div class="target">response1</div>')
+
+ u.setTimer 10, ->
+ # First modal is still in its opening animation
+ expect($('.target')).toHaveText('response1')
+
+ up.modal.extract('.target', '<div class="target">response2</div>')
+
+ # First modal is starting close animation. Second modal waits for that.
+ expect($('.target')).toHaveText('response1')
+
+ u.setTimer 10, ->
+ # Second modal is still waiting for first modal's closing animaton to finish.
+ expect($('.target')).toHaveText('response1')
+
+ u.setTimer 90, ->
+ # First modal has finished closing, second modal has finished opening.
+ expect($('.target')).toHaveText('response2')
+
+ done()
+
+ it 'uses the correct flavor config for the first and second modal', (done) ->
+ up.modal.config.openAnimation = 'fade-in'
+ up.modal.config.openDuration = 10
+ up.modal.config.closeAnimation = 'fade-out'
+ up.modal.config.closeDuration = 10
+ up.modal.flavor 'drawer',
+ openAnimation: 'move-from-right'
+ closeAnimation: 'move-to-right'
+
+ animations = []
+ spyOn(up, 'animate').and.callFake ($element, animation, options) ->
+ if $element.is('.up-modal-viewport')
+ animations.push
+ text: u.trim($element.find('.target').text())
+ animation: animation
+ deferred = $.Deferred()
+ u.setTimer options.duration, -> deferred.resolve()
+ deferred.promise()
+
+ up.modal.extract('.target', '<div class="target">response1</div>')
+ expect(animations).toEqual [
+ { animation: 'fade-in', text: 'response1' }
+ ]
+
+ up.modal.extract('.target', '<div class="target">response2</div>', flavor: 'drawer')
+
+ expect(animations).toEqual [
+ { animation: 'fade-in', text: 'response1' },
+ { animation: 'fade-out', text: 'response1' }
+ ]
+
+ u.setTimer 20, ->
+
+ expect(animations).toEqual [
+ { animation: 'fade-in', text: 'response1' },
+ { animation: 'fade-out', text: 'response1' },
+ { animation: 'move-from-right', text: 'response2' }
+ ]
+
+ expect($('.up-modal').attr('up-flavor')).toEqual('drawer')
+
done()
- it 'does not explode if the modal was closed before the response was received', ->
- up.modal.visit('/foo', target: '.container')
- up.modal.close()
- respond = => @respondWith('<div class="container">text</div>')
- expect(respond).not.toThrowError()
- expect($('.up-error')).not.toExist()
+ it 'does not explode if up.modal.close() was called before the response was received', ->
+ up.modal.visit('/foo', target: '.container')
+ up.modal.close()
+ respond = => @respondWith('<div class="container">text</div>')
+ expect(respond).not.toThrowError()
+ expect($('.up-error')).not.toExist()
+
describe 'up.modal.coveredUrl', ->
describeCapability 'canPushState', ->
it 'returns the URL behind the modal overlay', (done) ->
@@ -105,9 +285,31 @@
expect(up.modal.coveredUrl()).toEndWith('/foo')
up.modal.close().then ->
expect(up.modal.coveredUrl()).toBeUndefined()
done()
+ describe 'up.modal.flavor', ->
+
+ it 'registers a new modal variant with its own default configuration', ->
+ up.modal.flavor('variant', { maxWidth: 200 })
+ $link = affix('a[href="/path"][up-modal=".target"][up-flavor="variant"]')
+ Trigger.click($link)
+ @respondWith('<div class="target">new text</div>')
+ $modal = $('.up-modal')
+ $dialog = $modal.find('.up-modal-dialog')
+ expect($modal).toBeInDOM()
+ expect($modal.attr('up-flavor')).toEqual('variant')
+ expect($dialog.attr('style')).toContain('max-width: 200px')
+
+ it 'does not change the configuration of non-flavored modals', ->
+ up.modal.flavor('variant', { maxWidth: 200 })
+ $link = affix('a[href="/path"][up-modal=".target"]')
+ Trigger.click($link)
+ @respondWith('<div class="target">new text</div>')
+ $modal = $('.up-modal')
+ $dialog = $modal.find('.up-modal-dialog')
+ expect($modal).toBeInDOM()
+ expect($dialog.attr('style')).toBeBlank()
describe 'up.modal.close', ->
it 'closes a currently open modal'