spec_app/spec/javascripts/up/modal_spec.js.coffee in unpoly-rails-0.37.0 vs spec_app/spec/javascripts/up/modal_spec.js.coffee in unpoly-rails-0.50.0

- old
+ new

@@ -1,93 +1,121 @@ describe 'up.modal', -> u = up.util - - describe 'JavaScript functions', -> - assumedScrollbarWidth = 15 + beforeEach -> + up.modal.config.openDuration = 5 + up.modal.config.closeDuration = 5 + describe 'JavaScript functions', -> + # Safari overlays the scrollbar tracker over the picture. + # The scrollbar does not take space. + assumedScrollbarWidth = if AgentDetector.isSafari() then 0 else 15 + describe 'up.modal.follow', -> it "loads the given link's destination in a dialog window", (done) -> $link = affix('a[href="/path/to"][up-modal=".middle"]').text('link') promise = up.modal.follow($link) - expect(@lastRequest().url).toMatch /\/path\/to$/ - @respondWith """ + + u.nextFrame => + expect(@lastRequest().url).toMatch /\/path\/to$/ + @respondWith """ + <div class="before">new-before</div> + <div class="middle">new-middle</div> + <div class="after">new-after</div> + """ + + promise.then => + expect($('.up-modal')).toExist() + expect($('.up-modal-dialog')).toExist() + expect($('.up-modal-dialog .middle')).toExist() + expect($('.up-modal-dialog .middle')).toHaveText('new-middle') + expect($('.up-modal-dialog .before')).not.toExist() + expect($('.up-modal-dialog .after')).not.toExist() + done() + + describe 'up.modal.extract', -> + + it 'opens a modal by extracting the given selector from the given HTML string', (done) -> + up.history.config.enabled = true + + oldHref = location.href + promise = up.modal.extract '.middle', """ <div class="before">new-before</div> <div class="middle">new-middle</div> <div class="after">new-after</div> """ - promise.then -> + + promise.then => expect($('.up-modal')).toExist() expect($('.up-modal-dialog')).toExist() expect($('.up-modal-dialog .middle')).toExist() expect($('.up-modal-dialog .middle')).toHaveText('new-middle') expect($('.up-modal-dialog .before')).not.toExist() expect($('.up-modal-dialog .after')).not.toExist() + + # Can't change URLs + expect(location.href).toEqual(oldHref) done() - describe 'up.modal.extract', -> - - it 'opens a modal by extracting the given selector from the given HTML string', -> - oldHref = location.href - up.modal.extract '.middle', """ - <div class="before">new-before</div> - <div class="middle">new-middle</div> - <div class="after">new-after</div> - """ - expect($('.up-modal')).toExist() - expect($('.up-modal-dialog')).toExist() - expect($('.up-modal-dialog .middle')).toExist() - expect($('.up-modal-dialog .middle')).toHaveText('new-middle') - expect($('.up-modal-dialog .before')).not.toExist() - expect($('.up-modal-dialog .after')).not.toExist() - - # Can't change URLs - expect(location.href).toEqual(oldHref) - describe 'up.modal.visit', -> - it "requests the given URL and places the given selector into a modal", -> - up.modal.visit '/foo', target: '.middle' + it "requests the given URL and places the given selector into a modal", (done) -> + up.history.config.enabled = true - @respondWith """ - <div class="before">new-before</div> - <div class="middle">new-middle</div> - <div class="after">new-after</div> - """ + promise = up.modal.visit('/foo', target: '.middle') - expect('.up-modal').toExist() - expect('.up-modal-dialog').toExist() - expect('.up-modal-dialog .middle').toExist() - expect('.up-modal-dialog .middle').toHaveText('new-middle') - expect('.up-modal-dialog .before').not.toExist() - expect('.up-modal-dialog .after').not.toExist() + u.nextFrame => + @respondWith """ + <div class="before">new-before</div> + <div class="middle">new-middle</div> + <div class="after">new-after</div> + """ - expect(location.pathname).toEqualUrl('/foo') + promise.then => + expect('.up-modal').toExist() + expect('.up-modal-dialog').toExist() + expect('.up-modal-dialog .middle').toExist() + expect('.up-modal-dialog .middle').toHaveText('new-middle') + expect('.up-modal-dialog .before').not.toExist() + expect('.up-modal-dialog .after').not.toExist() + expect(location.pathname).toMatchUrl('/foo') + done() - it "doesn't create an .up-modal frame and replaces { failTarget } if the server returns a non-200 response", -> + it "doesn't create an .up-modal frame and replaces { failTarget } if the server returns a non-200 response", (done) -> affix('.error').text('old error') - up.modal.visit '/foo', target: '.target', failTarget: '.error' + promise = up.modal.visit('/foo', target: '.target', failTarget: '.error') - @respondWith - status: 500 - responseText: """ - <div class="target">new target</div> - <div class="error">new error</div> - """ + u.nextFrame => + @respondWith + status: 500 + responseText: """ + <div class="target">new target</div> + <div class="error">new error</div> + """ - expect('.up-modal').not.toExist() - expect('.error').toHaveText('new error') + promise.catch => + expect('.up-modal').not.toExist() + expect('.error').toHaveText('new error') + done() + it 'always makes a request for the given selector, and does not "improve" the selector with a fallback', asyncSpec (next) -> + up.modal.visit('/foo', target: '.target', failTarget: '.error') + next => + expect(jasmine.Ajax.requests.count()).toEqual(1) + headers = @lastRequest().requestHeaders + expect(headers['X-Up-Target']).toEqual('.target') + describe 'preventing elements from jumping as scrollbars change', -> it "brings its own scrollbar, padding the body on the right", (done) -> promise = up.modal.visit('/foo', target: '.container') - @respondWith('<div class="container">text</div>') + u.nextFrame => + @respondWith('<div class="container">text</div>') promise.then -> $modal = $('.up-modal') $viewport = $modal.find('.up-modal-viewport') $body = $('body') @@ -101,23 +129,26 @@ 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') - expect($modal.css('overflow-y')).toEqual('scroll') - expect($viewport.css('overflow-y')).toEqual('hidden') - openPromise.then -> - expect($modal.css('overflow-y')).not.toEqual('scroll') - expect($viewport.css('overflow-y')).toEqual('scroll') - closePromise = up.modal.close(animation: 'fade-out', duration: 200) - u.nextFrame -> - expect($modal.css('overflow-y')).toEqual('scroll') - expect($viewport.css('overflow-y')).toEqual('hidden') - done() + u.nextFrame => + $modal = $('.up-modal') + $viewport = $modal.find('.up-modal-viewport') + expect($modal.css('overflow-y')).toEqual('scroll') + expect($viewport.css('overflow-y')).toEqual('hidden') + + openPromise.then -> + expect($modal.css('overflow-y')).not.toEqual('scroll') + expect($viewport.css('overflow-y')).toEqual('scroll') + closePromise = up.modal.close(animation: 'fade-out', duration: 200) + u.nextFrame -> + expect($modal.css('overflow-y')).toEqual('scroll') + expect($viewport.css('overflow-y')).toEqual('hidden') + done() + 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') @@ -151,11 +182,12 @@ top: '0' right: '30px' promise = up.modal.visit('/foo', target: '.container') - @respondWith('<div class="container">text</div>') + u.nextFrame => + @respondWith('<div class="container">text</div>') promise.then -> expect(parseInt($anchoredElement.css('right'))).toBeAround(30 + assumedScrollbarWidth, 10) up.modal.close().then -> @@ -164,98 +196,120 @@ 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: 50) - promise2 = up.modal.visit('/path2', target: '.container', animation: 'fade-in', duration: 50) - @respondWith('<div class="container">response1</div>') - u.setTimer 120, => - # up.modal.visit (visitAsap) will start the request only after the last modal - # has finished opening and closing: 50 + 10 ms + # Load a first modal + up.modal.visit('/path1', target: '.container', animation: 'fade-in', duration: 50) + + # Immediately load a second modal in the same frame. + # This will discard the first request immediately. + up.modal.visit('/path2', target: '.container', animation: 'fade-in', duration: 50) + + u.nextFrame => + # The second modal has survived + expect(jasmine.Ajax.requests.count()).toEqual(1) + expect(@lastRequest().url).toMatchUrl('/path2') + + # We send the response for 2 @respondWith('<div class="container">response2</div>') - $.when(promise1, promise2).then -> - 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) -> + u.setTimer 10, => + # The second modal is now opening + up.modal.visit('/path3', target: '.container', animation: 'fade-in', duration: 50) + + # Load a third modal before the second was done opening + u.nextFrame => + # Since we're still opening the second modal, no request has been made. + expect(jasmine.Ajax.requests.count()).toEqual(1) + + u.setTimer 180, => + # Now that the second modal has opened, we make the request to /path3 + expect(jasmine.Ajax.requests.count()).toEqual(2) + expect(@lastRequest().url).toMatchUrl('/path3') + + @respondWith('<div class="container">response3</div>') + + u.setTimer 180, => + expect(jasmine.Ajax.requests.count()).toEqual(2) + expect($('.up-modal').length).toBe(1) + expect($('.up-modal-dialog').length).toBe(1) + expect($('.container')).toHaveText('response3') + bodyPadding = parseInt($('body').css('padding-right')) + expect(bodyPadding).toBeAround(assumedScrollbarWidth, 10) + if assumedScrollbarWidth > 0 # this test does not make sense on Safari + 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', asyncSpec (next) -> up.modal.config.openAnimation = 'fade-in' - up.modal.config.openDuration = 5 + up.modal.config.openDuration = 50 up.modal.config.closeAnimation = 'fade-out' - up.modal.config.closeDuration = 60 + 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') + next => + # First modal is starting opening animation (will take 50 ms) + expect(events).toEqual ['up:modal:open'] + expect($('.target')).toHaveText('response1') - u.setTimer 80, -> - # First modal has completed opening animation + next.after 60, => + # First modal has completed opening animation after 50 ms expect(events).toEqual ['up:modal:open', 'up:modal:opened'] expect($('.target')).toHaveText('response1') - # We open another modal, which will cause the first modal to start closing + # We open another modal, which will cause the first modal to start closing (will take 50 ms) up.modal.extract('.target', '<div class="target">response2</div>') + next.after 25, => + # Second modal is still waiting for first modal's closing animaton to finish. + expect(events).toEqual ['up:modal:open', 'up:modal:opened', 'up:modal:close'] expect($('.target')).toHaveText('response1') - u.setTimer 20, -> + # I don't know why this spec is so off with timing. + # We need to add 100ms to make it pass all of the time. + next.after (25 + 50 + 100), => + # First modal has finished closing, second modal has finished opening. + expect(events).toEqual ['up:modal:open', 'up:modal:opened', 'up:modal:close', 'up:modal:closed', 'up:modal:open', 'up:modal:opened'] + expect($('.target')).toHaveText('response2') - # Second modal is still waiting for first modal's closing animaton to finish. - expect(events).toEqual ['up:modal:open', 'up:modal:opened', 'up:modal:close'] - expect($('.target')).toHaveText('response1') + it 'closes an opening modal if a second modal starts opening before the first modal has finished its open animation', asyncSpec (next) -> + up.modal.config.openAnimation = 'fade-in' + up.modal.config.openDuration = 50 + up.modal.config.closeAnimation = 'fade-out' + up.modal.config.closeDuration = 50 - u.setTimer 200, -> + # Open the first modal + up.modal.extract('.target', '<div class="target">response1</div>') - # First modal has finished closing, second modal has finished opening. - expect(events).toEqual ['up:modal:open', 'up:modal:opened', 'up:modal:close', 'up:modal:closed', 'up:modal:open', 'up:modal:opened'] - expect($('.target')).toHaveText('response2') + next.after 10, => + # First modal is still in its opening animation + expect($('.target')).toHaveText('response1') - done() + # Open a second modal + up.modal.extract('.target', '<div class="target">response2</div>') - 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 + next => + # First modal is starting close animation. Second modal waits for that. + expect($('.target')).toHaveText('response1') - # Open the first modal - up.modal.extract('.target', '<div class="target">response1</div>') + next.after 10, => + # Second modal is still waiting for first modal's closing animaton to finish. + expect($('.target')).toHaveText('response1') - u.setTimer 10, -> - # First modal is still in its opening animation - expect($('.target')).toHaveText('response1') + next.after 150, => + # First modal has finished closing, second modal has finished opening. + expect($('.target')).toHaveText('response2') - # Open a second modal - 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 150, -> - # 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) -> + it 'uses the correct flavor config for the first and second modal', asyncSpec (next) -> up.modal.config.openAnimation = 'fade-in' up.modal.config.openDuration = 20 up.modal.config.closeAnimation = 'fade-out' up.modal.config.closeDuration = 20 up.modal.flavor 'custom-drawer', @@ -266,102 +320,119 @@ 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() + deferred = u.newDeferred() 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' } - ] - u.setTimer 30, -> + next => + expect(animations).toEqual [ + { animation: 'fade-in', text: 'response1' } + ] + next.after 30, => # first modal is now done animating expect(animations).toEqual [ { animation: 'fade-in', text: 'response1' } ] - up.modal.extract('.target', '<div class="target">response2</div>', flavor: 'custom-drawer') + + next => expect(animations).toEqual [ { animation: 'fade-in', text: 'response1' }, { animation: 'fade-out', text: 'response1' }, ] - u.setTimer 30, -> + next.after 30, => + expect(animations).toEqual [ + { animation: 'fade-in', text: 'response1' }, + { animation: 'fade-out', text: 'response1' }, + { animation: 'move-from-right', text: 'response2' } + ] - 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('custom-drawer') - expect($('.up-modal').attr('up-flavor')).toEqual('custom-drawer') - done() + it 'never resolves the open() promise and shows no error if close() was called before the response was received', asyncSpec (next) -> + openPromise = up.modal.visit('/foo', target: '.container') + next => + up.modal.close() - 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() + next => + respond = => @respondWith('<div class="container">text</div>') + expect(respond).not.toThrowError() + next.await => + expect($('.up-toast')).not.toExist() + promise = promiseState(openPromise) + promise.then (result) => expect(result.state).toEqual('pending') + describe 'up.modal.coveredUrl', -> describeCapability 'canPushState', -> it 'returns the URL behind the modal overlay', (done) -> + up.history.config.enabled = true up.history.replace('/foo') expect(up.modal.coveredUrl()).toBeMissing() visitPromise = up.modal.visit('/bar', target: '.container') - @respondWith('<div class="container">text</div>') - visitPromise.then -> - expect(up.modal.coveredUrl()).toEqualUrl('/foo') - up.modal.close().then -> - expect(up.modal.coveredUrl()).toBeMissing() - done() + u.nextFrame => + @respondWith('<div class="container">text</div>') + visitPromise.then -> + expect(up.modal.coveredUrl()).toMatchUrl('/foo') + up.modal.close().then -> + expect(up.modal.coveredUrl()).toBeMissing() + done() describe 'up.modal.flavors', -> - it 'allows to register new modal variants with its own default configuration', -> + it 'allows to register new modal variants with its own default configuration', asyncSpec (next) -> up.modal.flavors.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', -> + next => + @respondWith('<div class="target">new text</div>') + + next => + $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', asyncSpec (next) -> up.modal.flavors.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() + next => + @respondWith('<div class="target">new text</div>') + + next => + $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', (done) -> - up.modal.extract('.modal', '<div class="modal">Modal content</div>') + up.modal.extract('.content', '<div class="content">Modal content</div>') - modalContent = $('.modal') - expect(modalContent).toBeInDOM() + u.nextFrame => + expect('.up-modal .content').toBeInDOM() - up.modal.close().then -> - expect(modalContent).not.toBeInDOM() - done() + up.modal.close().then -> + expect('.up-modal .content').not.toBeInDOM() + done() it 'does nothing if no modal is open', (done) -> wasClosed = false up.on 'up:modal:close', -> wasClosed = true @@ -369,190 +440,203 @@ up.modal.close().then -> expect(wasClosed).toBe(false) done() describe 'unobtrusive behavior', -> - + describe 'a[up-modal]', -> beforeEach -> up.motion.config.enabled = false # Some examples only want to check if follow() has been called, without # actually making a request. @stubFollow = => @$link = affix('a[href="/path"][up-modal=".target"]') - @followSpy = up.modal.knife.mock('followAsap').and.returnValue(u.resolvedPromise()) - @defaultSpy = up.link.knife.mock('allowDefault').and.callFake((event) -> event.preventDefault()) + @followSpy = up.modal.knife.mock('followAsap').and.returnValue(Promise.resolve()) + @defaultSpy = spyOn(up.link, 'allowDefault').and.callFake((event) -> event.preventDefault()) - it 'opens the clicked link in a modal', (done) -> + it 'opens the clicked link in a modal', asyncSpec (next) -> @$link = affix('a[href="/path"][up-modal=".target"]') Trigger.click(@$link) - lastRequest = @lastRequest() - expect(lastRequest.url).toEqualUrl('/path') - @respondWith '<div class="target">new content</div>' - u.nextFrame => + + next => + lastRequest = @lastRequest() + expect(lastRequest.url).toMatchUrl('/path') + @respondWith '<div class="target">new content</div>' + + next => expect('.up-modal').toExist() expect('.up-modal-content').toHaveText('new content') - done() describe 'when modifier keys are held', -> # IE does not call JavaScript and always performs the default action on right clicks - unless navigator.userAgent.match(/Trident/) - it 'does nothing if the right mouse button is used', -> + unless AgentDetector.isIE() || AgentDetector.isEdge() + it 'does nothing if the right mouse button is used', asyncSpec (next) -> @stubFollow() Trigger.click(@$link, button: 2) - expect(@followSpy).not.toHaveBeenCalled() + next => expect(@followSpy).not.toHaveBeenCalled() - it 'does nothing if shift is pressed during the click', -> + it 'does nothing if shift is pressed during the click', asyncSpec (next) -> @stubFollow() Trigger.click(@$link, shiftKey: true) - expect(@followSpy).not.toHaveBeenCalled() + next => expect(@followSpy).not.toHaveBeenCalled() - it 'does nothing if ctrl is pressed during the click', -> + it 'does nothing if ctrl is pressed during the click', asyncSpec (next) -> @stubFollow() Trigger.click(@$link, ctrlKey: true) - expect(@followSpy).not.toHaveBeenCalled() + next => expect(@followSpy).not.toHaveBeenCalled() - it 'does nothing if meta is pressed during the click', -> + it 'does nothing if meta is pressed during the click', asyncSpec (next) -> @stubFollow() Trigger.click(@$link, metaKey: true) - expect(@followSpy).not.toHaveBeenCalled() + next => expect(@followSpy).not.toHaveBeenCalled() describe 'with [up-instant] modifier', -> beforeEach -> @stubFollow() @$link.attr('up-instant', '') - it 'opens the modal on mousedown (instead of on click)', -> + it 'opens the modal on mousedown (instead of on click)', asyncSpec (next) -> Trigger.mousedown(@$link) - expect(@followSpy.calls.mostRecent().args[0]).toEqual(@$link) + next => + expect(@followSpy).toHaveBeenCalledWith(@$link, {}) - it 'does nothing on mouseup', -> + it 'does nothing on mouseup', asyncSpec (next) -> Trigger.mouseup(@$link) - expect(@followSpy).not.toHaveBeenCalled() + next => expect(@followSpy).not.toHaveBeenCalled() - it 'does nothing on click', -> + it 'does nothing on click', asyncSpec (next) -> Trigger.click(@$link) - expect(@followSpy).not.toHaveBeenCalled() + next => expect(@followSpy).not.toHaveBeenCalled() # IE does not call JavaScript and always performs the default action on right clicks - unless navigator.userAgent.match(/Trident/) - it 'does nothing if the right mouse button is pressed down', -> + unless AgentDetector.isIE() || AgentDetector.isEdge() + it 'does nothing if the right mouse button is pressed down', asyncSpec (next) -> Trigger.mousedown(@$link, button: 2) - expect(@followSpy).not.toHaveBeenCalled() + next => expect(@followSpy).not.toHaveBeenCalled() - it 'does nothing if shift is pressed during mousedown', -> + it 'does nothing if shift is pressed during mousedown', asyncSpec (next) -> Trigger.mousedown(@$link, shiftKey: true) - expect(@followSpy).not.toHaveBeenCalled() + next => expect(@followSpy).not.toHaveBeenCalled() - it 'does nothing if ctrl is pressed during mousedown', -> + it 'does nothing if ctrl is pressed during mousedown', asyncSpec (next) -> Trigger.mousedown(@$link, ctrlKey: true) - expect(@followSpy).not.toHaveBeenCalled() + next => expect(@followSpy).not.toHaveBeenCalled() - it 'does nothing if meta is pressed during mousedown', -> + it 'does nothing if meta is pressed during mousedown', asyncSpec (next) -> Trigger.mousedown(@$link, metaKey: true) - expect(@followSpy).not.toHaveBeenCalled() + next => expect(@followSpy).not.toHaveBeenCalled() describe 'with [up-method] modifier', -> - it 'honours the given method', -> + it 'honours the given method', asyncSpec (next) -> $link = affix('a[href="/path"][up-modal=".target"][up-method="post"]') Trigger.click($link) - expect(@lastRequest().method).toEqual 'POST' - it 'adds a history entry and allows the user to use the back button', (done) -> + next => + expect(@lastRequest().method).toEqual 'POST' + + it 'adds a history entry and allows the user to use the back button', asyncSpec (next) -> up.motion.config.enabled = false + up.history.config.enabled = true + up.history.config.popTargets = ['.container'] + up.history.push('/original-path') - up.history.config.popTargets = ['.container'] $container = affix('.container').text('old container content') $link = $container.affix('a[href="/new-path"][up-modal=".target"]') expect(location.pathname).toEqual('/original-path') Trigger.clickSequence($link) - u.nextFrame => + next => @respondWith('<div class="target">modal content</div>') + + next => expect(location.pathname).toEqual('/new-path') expect('.up-modal .target').toHaveText('modal content') history.back() - u.setTimer (waitForBrowser = 70), => - @respondWith('<div class="container">restored container content</div>') - u.nextFrame => - expect(location.pathname).toEqual('/original-path') - expect('.container').toHaveText('restored container content') - expect('.up-modal').not.toExist() - done() - it 'allows to open a modal after closing a previous modul with the escape key (bugfix)', (done) -> + next.after (waitForBrowser = 70), => + @respondWith('<div class="container">restored container content</div>') + + next => + expect(location.pathname).toEqual('/original-path') + expect('.container').toHaveText('restored container content') + expect('.up-modal').not.toExist() + + it 'allows to open a modal after closing a previous modal with the escape key (bugfix)', asyncSpec (next) -> up.motion.config.enabled = false $link1 = affix('a[href="/path1"][up-modal=".target"]') $link2 = affix('a[href="/path2"][up-modal=".target"]') Trigger.clickSequence($link1) - u.nextFrame => + next => @respondWith('<div class="target">content 1</div>') - u.nextFrame => - expect(up.modal.isOpen()).toBe(true) + next => + expect(up.modal.isOpen()).toBe(true) - escapeEvent = $.Event('keydown', keyCode: 27) - $('body').trigger(escapeEvent) + escapeEvent = $.Event('keydown', keyCode: 27) + $('body').trigger(escapeEvent) - u.nextFrame => - expect(up.modal.isOpen()).toBe(false) + next => + expect(up.modal.isOpen()).toBe(false) - Trigger.clickSequence($link2) + Trigger.clickSequence($link2) - u.nextFrame => - @respondWith('<div class="target">content 1</div>') + next => + @respondWith('<div class="target">content 1</div>') - u.nextFrame => - expect(up.modal.isOpen()).toBe(true) - done() + next => + expect(up.modal.isOpen()).toBe(true) describe '[up-drawer]', -> beforeEach -> up.motion.config.enabled = false - it 'slides in a drawer that covers the full height of the screen', (done) -> + it 'slides in a drawer that covers the full height of the screen', asyncSpec (next) -> $link = affix('a[href="/foo"][up-drawer=".target"]').text('label') up.hello($link) Trigger.clickSequence($link) - u.nextFrame => + + next => @respondWith '<div class="target">new text</div>' + + next => expect(up.modal.isOpen()).toBe(true) expect($('.up-modal').attr('up-flavor')).toEqual('drawer') windowHeight = u.clientSize().height modalHeight = $('.up-modal-content').outerHeight() expect(modalHeight).toEqual(windowHeight) expect($('.up-modal-content').offset()).toEqual(top: 0, left: 0) - done() - it 'puts the drawer on the right if the opening link sits in the right 50% of the screen', (done) -> + it 'puts the drawer on the right if the opening link sits in the right 50% of the screen', asyncSpec (next) -> $link = affix('a[href="/foo"][up-drawer=".target"]').text('label') $link.css position: 'absolute' right: '0' up.hello($link) Trigger.clickSequence($link) - u.nextFrame => + + next => @respondWith '<div class="target">new text</div>' + + next => expect(up.modal.isOpen()).toBe(true) windowWidth = u.clientSize().width modalWidth = $('.up-modal-content').outerWidth() scrollbarWidth = u.scrollbarWidth() expect($('.up-modal-content').offset().left).toBeAround(windowWidth - modalWidth - scrollbarWidth, 1.0) - done() describe '[up-close]', -> backgroundClicked = undefined @@ -560,192 +644,237 @@ up.motion.config.enabled = false backgroundClicked = jasmine.createSpy('background clicked') up.on 'click', backgroundClicked describe 'when clicked inside a modal', -> - - it 'closes the open modal and halts the event chain', (done) -> + + it 'closes the open modal and halts the event chain', asyncSpec (next) -> up.modal.extract('.target', '<div class="target"><a up-close>text</a></div>', animation: false) - $link = $('.up-modal a[up-close]') # link is within the modal - Trigger.clickSequence($link) - u.nextFrame -> + + next => + $link = $('.up-modal a[up-close]') # link is within the modal + Trigger.clickSequence($link) + + next => expect(up.modal.isOpen()).toBe(false) expect(backgroundClicked).not.toHaveBeenCalled() - done() describe 'when no modal is open', -> - it 'does nothing and allows the event chain to continue', (done) -> + it 'does nothing and allows the event chain to continue', asyncSpec (next) -> $link = affix('a[up-close]') # link is outside the modal up.hello($link) Trigger.clickSequence($link) - u.nextFrame -> + + next => expect(backgroundClicked).toHaveBeenCalled() - done() describe 'closing', -> - it 'closes the modal on close icon click', (done) -> + it 'closes the modal on close icon click', asyncSpec (next) -> up.modal.extract('.modal', '<div class="modal">Modal content</div>', animation: false) - $closeIcon = $('.up-modal-close') + next => + $closeIcon = $('.up-modal-close') + Trigger.clickSequence($closeIcon) - Trigger.clickSequence($closeIcon) - u.nextFrame -> + next => expect(up.modal.isOpen()).toBe(false) - done() - it 'closes the modal on backdrop click', (done) -> + it 'closes the modal on backdrop click', asyncSpec (next) -> up.modal.extract('.modal', '<div class="modal">Modal content</div>', animation: false) - $backdrop = $('.up-modal-backdrop') + next => + $backdrop = $('.up-modal-backdrop') + Trigger.clickSequence($backdrop) - Trigger.clickSequence($backdrop) - u.nextFrame -> + next => expect(up.modal.isOpen()).toBe(false) - done() - it "does not close the modal when clicking on an element outside the modal's DOM hierarchy", (done) -> + it "does not close the modal when clicking on an element outside the modal's DOM hierarchy", asyncSpec (next) -> $container = affix('.container') up.modal.extract('.modal', '<div class="modal">Modal content</div>', animation: false) - Trigger.clickSequence($container) - u.nextFrame -> + next => + Trigger.clickSequence($container) + + next => expect(up.modal.isOpen()).toBe(true) - done() - it 'closes the modal when the user presses the escape key', (done) -> + it 'closes the modal when the user presses the escape key', asyncSpec (next) -> wasClosed = false - up.modal.extract('.modal', '<div class="modal">Modal content</div>', animation: false) up.on 'up:modal:close', -> wasClosed = true - escapeEvent = $.Event('keydown', keyCode: 27) - $('body').trigger(escapeEvent) - u.nextFrame -> + up.modal.extract('.modal', '<div class="modal">Modal content</div>', animation: false) + + next => + escapeEvent = $.Event('keydown', keyCode: 27) + $('body').trigger(escapeEvent) + + next => expect(wasClosed).toBe(true) - done() describe 'when opened with { closable: false }', -> - it 'does not render a close icon', -> + it 'does not render a close icon', asyncSpec (next) -> up.modal.extract('.modal', '<div class="modal">Modal content</div>', animation: false, closable: false) - modal = $('.up-modal') - expect(modal).not.toContainElement('.up-modal-close') + next => + expect('.up-modal').not.toContainElement('.up-modal-close') - it 'does not close the modal on backdrop click', (done) -> + it 'does not close the modal on backdrop click', asyncSpec (next) -> up.modal.extract('.modal', '<div class="modal">Modal content</div>', animation: false, closable: false) - $backdrop = $('.up-modal-backdrop') + next => + $backdrop = $('.up-modal-backdrop') + Trigger.clickSequence($backdrop) - Trigger.clickSequence($backdrop) - u.nextFrame -> + next => expect(up.modal.isOpen()).toBe(true) - done() - it 'does not close the modal when the user presses the escape key', (done) -> + it 'does not close the modal when the user presses the escape key', asyncSpec (next) -> up.modal.extract('.modal', '<div class="modal">Modal content</div>', animation: false, closable: false) - escapeEvent = $.Event('keydown', keyCode: 27) - $('body').trigger(escapeEvent) - u.nextFrame -> + next => + escapeEvent = $.Event('keydown', keyCode: 27) + $('body').trigger(escapeEvent) + + next => expect(up.modal.isOpen()).toBe(true) - done() describe 'when replacing content', -> beforeEach -> up.motion.config.enabled = false - it 'prefers to replace a selector within the modal', -> + it 'prefers to replace a selector within the modal', asyncSpec (next) -> $outside = affix('.foo').text('old outside') up.modal.visit('/path', target: '.foo') - @respondWith("<div class='foo'>old inside</div>") - up.extract('.foo', "<div class='foo'>new text</div>") - expect($outside).toBeInDOM() - expect($outside).toHaveText('old outside') - expect($('.up-modal-content')).toHaveText('new text') - it 'auto-closes the modal when a replacement from inside the modal affects a selector behind the modal', -> + next => + @respondWith("<div class='foo'>old inside</div>") + + next => + up.extract('.foo', "<div class='foo'>new text</div>") + + next => + expect($outside).toBeInDOM() + expect($outside).toHaveText('old outside') + expect($('.up-modal-content')).toHaveText('new text') + + it 'auto-closes the modal when a replacement from inside the modal affects a selector behind the modal', asyncSpec (next) -> affix('.outside').text('old outside') up.modal.visit('/path', target: '.inside') - @respondWith("<div class='inside'>old inside</div>") - up.extract('.outside', "<div class='outside'>new outside</div>", origin: $('.inside')) - expect($('.outside')).toHaveText('new outside') - expect($('.up-modal')).not.toExist() - it 'does not restore the covered URL when auto-closing', (done) -> + next => + @respondWith("<div class='inside'>old inside</div>") + + next => + up.extract('.outside', "<div class='outside'>new outside</div>", origin: $('.inside')) + + next => + expect($('.outside')).toHaveText('new outside') + expect($('.up-modal')).not.toExist() + + it 'does not restore the covered URL when auto-closing (since it would override the URL from the triggering update)', asyncSpec (next) -> + up.history.config.enabled = true up.motion.config.enabled = true up.modal.config.openDuration = 0 up.modal.config.closeDuration = 20 affix('.outside').text('old outside') - whenModalOpen = up.modal.visit('/path', target: '.inside') - @respondWith("<div class='inside'>old inside</div>") # Populate modal + up.modal.visit('/path', target: '.inside') - whenModalOpen.then -> + next => + @respondWith("<div class='inside'>old inside</div>") # Populate modal + + next => up.extract('.outside', "<div class='outside'>new outside</div>", origin: $('.inside'), history: '/new-location') # Provoke auto-close - u.setTimer 50, -> - expect(location.href).toEqualUrl '/new-location' - done() + next => + expect(location.href).toMatchUrl '/new-location' - it 'does not auto-close the modal when a replacement from inside the modal affects a selector inside the modal', -> + it 'does not auto-close the modal when a replacement from inside the modal affects a selector inside the modal', asyncSpec (next) -> affix('.outside').text('old outside') up.modal.visit('/path', target: '.inside') - @respondWith("<div class='inside'>old inside</div>") - up.extract('.inside', "<div class='inside'>new inside</div>", origin: $('.inside')) - expect($('.inside')).toHaveText('new inside') - expect($('.up-modal')).toExist() - it 'does not auto-close the modal when a replacement from outside the modal affects a selector outside the modal', -> + next => + @respondWith("<div class='inside'>old inside</div>") + + next => + up.extract('.inside', "<div class='inside'>new inside</div>", origin: $('.inside')) + + next => + expect($('.inside')).toHaveText('new inside') + expect($('.up-modal')).toExist() + + it 'does not auto-close the modal when a replacement from outside the modal affects a selector outside the modal', asyncSpec (next) -> affix('.outside').text('old outside') up.modal.visit('/path', target: '.inside') - @respondWith("<div class='inside'>old inside</div>") - up.extract('.outside', "<div class='outside'>new outside</div>", origin: $('.outside')) - expect($('.outside')).toHaveText('new outside') - expect($('.up-modal')).toExist() - it 'does not auto-close the modal when a replacement from outside the modal affects a selector inside the modal', -> + next => + @respondWith("<div class='inside'>old inside</div>") + + next => + up.extract('.outside', "<div class='outside'>new outside</div>", origin: $('.outside')) + + next => + expect($('.outside')).toHaveText('new outside') + expect($('.up-modal')).toExist() + + it 'does not auto-close the modal when a replacement from outside the modal affects a selector inside the modal', asyncSpec (next) -> affix('.outside').text('old outside') up.modal.visit('/path', target: '.inside') - @respondWith("<div class='inside'>old inside</div>") - up.extract('.inside', "<div class='inside'>new inside</div>", origin: $('.outside')) - expect($('.inside')).toHaveText('new inside') - expect($('.up-modal')).toExist() - it 'does not auto-close the modal when the new fragment is within a popup', -> + next => + @respondWith("<div class='inside'>old inside</div>") + + next => + up.extract('.inside', "<div class='inside'>new inside</div>", origin: $('.outside')) + + next => + expect($('.inside')).toHaveText('new inside') + expect($('.up-modal')).toExist() + + it 'does not auto-close the modal when the new fragment is within a popup', asyncSpec (next) -> up.modal.visit('/modal', target: '.modal-content') - @respondWith("<div class='modal-content'></div>") - up.popup.attach('.modal-content', url: '/popup', target: '.popup-content') - @respondWith("<div class='popup-content'></div>") - expect($('.up-modal')).toExist() - expect($('.up-popup')).toExist() - it 'does not close the modal when a clicked [up-target] link within the modal links to cached content (bugfix)', (done) -> + next => + @respondWith("<div class='modal-content'></div>") + next => + up.popup.attach('.modal-content', url: '/popup', target: '.popup-content') + + next => + @respondWith("<div class='popup-content'></div>") + + next => + expect($('.up-modal')).toExist() + expect($('.up-popup')).toExist() + + it 'does not close the modal when a clicked [up-target] link within the modal links to cached content (bugfix)', asyncSpec (next) -> up.modal.extract '.content', """ <div class="content"> <a href="/foo" up-target=".content">link</a> </div> """ - $link = $('.up-modal .content a') - expect($link).toExist() - whenPreloaded = up.proxy.preload($link) - @respondWith """ - <div class="content"> - new text - </div> - """ + next => + $link = $('.up-modal .content a') + expect($link).toExist() + up.proxy.preload($link) - whenPreloaded.then -> + next => + @respondWith """ + <div class="content"> + new text + </div> + """ - Trigger.clickSequence($link) + next => + Trigger.clickSequence('.up-modal .content a') - u.nextFrame -> - expect($('.up-modal')).toExist() - expect($('.up-modal .content')).toHaveText('new text') - - done() + next => + expect($('.up-modal')).toExist() + expect($('.up-modal .content')).toHaveText('new text')