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')