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'