describe 'up.popup', ->
u = up.util
describe 'JavaScript functions', ->
describe 'up.popup.attach', ->
beforeEach ->
jasmine.addMatchers
toSitBelow: (util, customEqualityTesters) ->
compare: ($popup, $link) ->
popupDims = $popup.get(0).getBoundingClientRect()
linkDims = $link.get(0).getBoundingClientRect()
pass:
Math.abs(popupDims.right - linkDims.right) < 1.0 && Math.abs(popupDims.top - linkDims.bottom) < 1.0
beforeEach ->
@restoreBodyHeight = u.writeTemporaryStyle('body', minHeight: '3000px')
afterEach ->
@restoreBodyHeight()
it "loads this link's destination in a popup positioned under the given link", asyncSpec (next) ->
$container = affix('.container')
$container.css
position: 'absolute'
left: '100px'
top: '50px'
$link = $container.affix('a[href="/path/to"][up-popup=".middle"]').text('link')
up.popup.attach($link)
next =>
expect(@lastRequest().url).toMatch /\/path\/to$/
@respondWith """
new-before
new-middle
new-after
"""
next =>
$popup = $('.up-popup')
expect($popup).toExist()
expect($popup.find('.middle')).toHaveText('new-middle')
expect($popup.find('.before')).not.toExist()
expect($popup.find('.after')).not.toExist()
expect($popup.css('position')).toEqual('absolute')
expect($popup).toSitBelow($link)
it 'always makes a request for the given selector, and does not "improve" the selector with a fallback', asyncSpec (next) ->
$container = affix('.container')
$link = $container.affix('a[href="/path/to"][up-popup=".content"]').text('link')
up.popup.attach($link)
next =>
expect(jasmine.Ajax.requests.count()).toEqual(1)
headers = @lastRequest().requestHeaders
expect(headers['X-Up-Target']).toEqual('.content')
it 'gives the popup { position: "fixed" } if the given link is fixed', asyncSpec (next) ->
# Let's test the harder case where the document is scrolled
up.layout.scroll(document, 50)
$container = affix('.container')
$container.css
position: 'fixed'
left: '100px'
top: '50px'
$link = $container.affix('a[href="/path/to"][up-popup=".content"]').text('link')
up.popup.attach($link)
next =>
@respondWith('popup-content
')
next =>
$popup = $('.up-popup')
expect($popup.css('position')).toEqual('fixed')
expect($popup).toSitBelow($link)
it 'never resolves the open() promise and shows no error if close() was called before the response was received', asyncSpec (next) ->
$span = affix('span')
openPromise = up.popup.attach($span, url: '/foo', target: '.container')
next =>
up.popup.close()
next =>
respond = => @respondWith('text
')
expect(respond).not.toThrowError()
next.await =>
expect($('.up-toast')).not.toExist()
promise = promiseState(openPromise)
promise.then (result) => expect(result.state).toEqual('pending')
describe 'with { html } option', ->
it 'extracts the selector from the given HTML string', asyncSpec (next) ->
$span = affix('span')
next.await up.popup.attach($span, target: '.container', html: "container contents
")
next => expect($('.up-popup')).toHaveText('container contents')
describe 'opening a popup while another modal is open', ->
it 'closes the current popup and wait for its close animation to finish before starting the open animation of a second popup', asyncSpec (next) ->
$span = affix('span')
up.popup.config.openAnimation = 'fade-in'
up.popup.config.openDuration = 5
up.popup.config.closeAnimation = 'fade-out'
up.popup.config.closeDuration = 60
events = []
u.each ['up:popup:open', 'up:popup:opened', 'up:popup:close', 'up:popup:closed'], (event) ->
up.on event, -> events.push(event)
up.popup.attach($span, { target: '.target', html: 'response1
' })
next =>
# First popup is starting opening animation
expect(events).toEqual ['up:popup:open']
expect($('.target')).toHaveText('response1')
next.after 80, ->
# First popup has completed opening animation
expect(events).toEqual ['up:popup:open', 'up:popup:opened']
expect($('.target')).toHaveText('response1')
# We open another popup, which will cause the first modal to start closing
up.popup.attach($span, { target: '.target', html: 'response2
' })
next.after 20, ->
# Second popup is still waiting for first popup's closing animation to finish.
expect(events).toEqual ['up:popup:open', 'up:popup:opened', 'up:popup:close']
expect($('.target')).toHaveText('response1')
next.after 200, ->
# First popup has finished closing, second popup has finished opening.
expect(events).toEqual ['up:popup:open', 'up:popup:opened', 'up:popup:close', 'up:popup:closed', 'up:popup:open', 'up:popup:opened']
expect($('.target')).toHaveText('response2')
describe 'up.popup.coveredUrl', ->
describeCapability 'canPushState', ->
it 'returns the URL behind the popup', asyncSpec (next) ->
up.history.config.enabled = true
up.history.replace('/foo')
expect(up.popup.coveredUrl()).toBeMissing()
$popupLink = affix('a[href="/bar"][up-popup=".container"][up-history="true"]')
Trigger.clickSequence($popupLink)
next =>
@respondWith('text
')
expect(up.popup.coveredUrl()).toMatchUrl('/foo')
next.await up.popup.close()
next =>
expect(up.popup.coveredUrl()).toBeMissing()
describe 'up.popup.close', ->
it 'should have tests'
describe 'up.popup.source', ->
it 'should have tests'
describe 'unobtrusive behavior', ->
describe 'a[up-popup]', ->
beforeEach ->
@stubAttach = =>
@$link = affix('a[href="/path"][up-popup=".target"]')
@attachSpy = up.popup.knife.mock('attachAsap').and.returnValue(Promise.resolve())
@defaultSpy = spyOn(up.link, 'allowDefault').and.callFake((event) -> event.preventDefault())
it 'opens the clicked link in a popup', asyncSpec (next) ->
@stubAttach()
Trigger.click(@$link)
next => expect(@attachSpy).toHaveBeenCalledWith(@$link, {})
# IE does not call JavaScript and always performs the default action on right clicks
unless AgentDetector.isIE() || AgentDetector.isEdge()
it 'does nothing if the right mouse button is used', asyncSpec (next) ->
@stubAttach()
Trigger.click(@$link, button: 2)
next => expect(@attachSpy).not.toHaveBeenCalled()
it 'does nothing if shift is pressed during the click', asyncSpec (next) ->
@stubAttach()
Trigger.click(@$link, shiftKey: true)
next => expect(@attachSpy).not.toHaveBeenCalled()
it 'does nothing if ctrl is pressed during the click', asyncSpec (next) ->
@stubAttach()
Trigger.click(@$link, ctrlKey: true)
next => expect(@attachSpy).not.toHaveBeenCalled()
it 'does nothing if meta is pressed during the click', asyncSpec (next) ->
@stubAttach()
Trigger.click(@$link, metaKey: true)
next => expect(@attachSpy).not.toHaveBeenCalled()
it 'closes an existing popup before opening the new popup', asyncSpec (next) ->
up.popup.config.openDuration = 0
up.popup.config.closeDuration = 0
$link1 = affix('a[href="/path1"][up-popup=".target"]')
$link2 = affix('a[href="/path2"][up-popup=".target"]')
events = []
u.each ['up:popup:open', 'up:popup:opened', 'up:popup:close', 'up:popup:closed'], (event) ->
up.on event, -> events.push(event)
Trigger.click($link1)
next =>
expect(events).toEqual ['up:popup:open']
@respondWith('text1
')
next =>
expect(events).toEqual ['up:popup:open', 'up:popup:opened']
Trigger.click($link2)
next =>
expect(events).toEqual ['up:popup:open', 'up:popup:opened', 'up:popup:close', 'up:popup:closed', 'up:popup:open']
@respondWith('text1
')
next =>
expect(events).toEqual ['up:popup:open', 'up:popup:opened', 'up:popup:close', 'up:popup:closed', 'up:popup:open', 'up:popup:opened']
describe 'with [up-instant] modifier', ->
beforeEach ->
@stubAttach()
@$link.attr('up-instant', '')
it 'opens the modal on mousedown (instead of on click)', asyncSpec (next) ->
Trigger.mousedown(@$link)
next => expect(@attachSpy.calls.mostRecent().args[0]).toEqual(@$link)
it 'does nothing on mouseup', asyncSpec (next) ->
Trigger.mouseup(@$link)
next => expect(@attachSpy).not.toHaveBeenCalled()
it 'does nothing on click', asyncSpec (next) ->
Trigger.click(@$link)
next => expect(@attachSpy).not.toHaveBeenCalled()
# IE does not call JavaScript and always performs the default action on right clicks
unless AgentDetector.isIE() || AgentDetector.isEdge()
it 'does nothing if the right mouse button is pressed down', asyncSpec (next) ->
Trigger.mousedown(@$link, button: 2)
next => expect(@attachSpy).not.toHaveBeenCalled()
it 'does nothing if shift is pressed during mousedown', asyncSpec (next) ->
Trigger.mousedown(@$link, shiftKey: true)
next => expect(@attachSpy).not.toHaveBeenCalled()
it 'does nothing if ctrl is pressed during mousedown', asyncSpec (next) ->
Trigger.mousedown(@$link, ctrlKey: true)
next => expect(@attachSpy).not.toHaveBeenCalled()
it 'does nothing if meta is pressed during mousedown', asyncSpec (next) ->
Trigger.mousedown(@$link, metaKey: true)
next => expect(@attachSpy).not.toHaveBeenCalled()
describe 'with [up-method] modifier', ->
it 'honours the given method', asyncSpec (next) ->
$link = affix('a[href="/path"][up-popup=".target"][up-method="post"]')
Trigger.click($link)
next =>
expect(@lastRequest().method).toEqual 'POST'
describe '[up-close]', ->
backgroundClicked = undefined
beforeEach ->
up.motion.config.enabled = false
backgroundClicked = jasmine.createSpy('background clicked')
up.on 'click', backgroundClicked
describe 'when clicked inside a popup', ->
it 'closes the open popup and halts the event chain', asyncSpec (next) ->
$opener = affix('a')
up.popup.attach($opener, html: 'text
', target: '.target')
next =>
$popup = affix('.up-popup')
$closer = $popup.affix('a[up-close]') # link is within the popup
up.hello($closer)
Trigger.clickSequence($closer)
next =>
expect(up.popup.isOpen()).toBe(false)
expect(backgroundClicked).not.toHaveBeenCalled()
describe 'when clicked inside a popup when a modal is open', ->
it 'closes the popup, but not the modal', asyncSpec (next) ->
up.modal.extract '.modalee', ''
next =>
$modalee = $('.up-modal .modalee')
$opener = $modalee.affix('a')
up.popup.attach($opener, html: '', target: '.popupee')
next =>
$popupee = $('.up-popup .popupee')
$closer = $popupee.affix('a[up-close]') # link is within the popup
up.hello($closer)
Trigger.clickSequence($closer)
next =>
expect(up.popup.isOpen()).toBe(false)
expect(up.modal.isOpen()).toBe(true)
expect(backgroundClicked).not.toHaveBeenCalled()
describe 'when no popup is open', ->
it 'does nothing and allows the event chain to continue', asyncSpec (next) ->
$link = affix('a[up-close]') # link is outside the popup
up.hello($link)
Trigger.clickSequence($link)
next =>
expect(up.popup.isOpen()).toBe(false)
expect(backgroundClicked).toHaveBeenCalled()
describe 'when replacing content', ->
beforeEach ->
up.motion.config.enabled = false
it 'prefers to replace a selector within the popup', asyncSpec (next) ->
$outside = affix('.foo').text('old outside')
$link = affix('.link')
up.popup.attach($link, target: '.foo', html: "old inside
")
next =>
up.extract('.foo', "new text
")
next =>
expect($outside).toBeInDOM()
expect($outside).toHaveText('old outside')
expect($('.up-popup')).toHaveText('new text')
it 'auto-closes the popup when a replacement from inside the popup affects a selector behind the popup', asyncSpec (next) ->
affix('.outside').text('old outside')
$link = affix('.link')
up.popup.attach($link, target: '.inside', html: "old inside
")
next =>
up.extract('.outside', "new outside
", origin: $('.inside'))
next =>
expect($('.outside')).toHaveText('new outside')
expect($('.up-popup')).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.popup.config.openDuration = 0
up.popup.config.closeDuration = 20
up.popup.config.history = true
affix('.outside').text('old outside')
$link = affix('.link')
up.popup.attach($link, url: '/path', target: '.inside')
next =>
@respondWith("old inside
") # Populate pop-up
next =>
up.extract('.outside', "new outside
",
origin: $('.inside'), history: '/new-location') # Provoke auto-close
next =>
expect(location.href).toMatchUrl '/new-location'
it 'does not auto-close the popup when a replacement from inside the popup affects a selector inside the popup', asyncSpec (next) ->
affix('.outside').text('old outside')
$link = affix('.link')
up.popup.attach($link, html: "old inside
", target: '.inside')
next =>
up.extract('.inside', "new inside
", origin: $('.inside'))
next =>
expect($('.inside')).toHaveText('new inside')
expect($('.up-popup')).toExist()
it 'does not auto-close the popup when a replacement from outside the popup affects a selector outside the popup', asyncSpec (next) ->
affix('.outside').text('old outside')
$link = affix('.link')
up.popup.attach($link, target: '.inside', html: "old inside
")
next =>
up.extract('.outside', "new outside
", origin: $('.outside'))
next =>
expect($('.outside')).toHaveText('new outside')
expect($('.up-popup')).toExist()
it 'does not auto-close the popup when a replacement from outside the popup affects a selector inside the popup', asyncSpec (next) ->
affix('.outside').text('old outside')
$link = affix('.link')
up.popup.attach($link, target: '.inside', html: "old inside
")
next =>
up.extract('.inside', "new inside
", origin: $('.outside'))
next =>
expect($('.inside')).toHaveText('new inside')
expect($('.up-popup')).toExist()
describe 'when clicking on the body', ->
beforeEach ->
up.motion.config.enabled = false
it 'closes the popup', asyncSpec (next) ->
affix('.outside').text('old outside')
$link = affix('.link')
up.popup.attach($link, target: '.inside', html: "inside
")
next =>
expect(up.popup.isOpen()).toBe(true)
Trigger.clickSequence($('body'))
next =>
expect(up.popup.isOpen()).toBe(false)
it 'closes the popup when a an [up-instant] link removes its parent (and thus a click event never bubbles up to the document)', asyncSpec (next) ->
$parent = affix('.parent')
$parentReplacingLink = $parent.affix('a[href="/foo"][up-target=".parent"][up-instant]')
$popupOpener = affix('.link')
up.popup.attach($popupOpener, target: '.inside', html: "inside
")
next =>
expect(up.popup.isOpen()).toBe(true)
Trigger.clickSequence($parentReplacingLink)
next =>
expect(up.popup.isOpen()).toBe(false)
it 'closes the popup when the user clicks on an [up-target] link outside the popup', asyncSpec (next) ->
$target = affix('.target')
$outsideLink = affix('a[href="/foo"][up-target=".target"]')
$popupOpener = affix('.link')
up.popup.attach($popupOpener, target: '.inside', html: "inside
")
next =>
expect(up.popup.isOpen()).toBe(true)
Trigger.clickSequence($outsideLink)
next =>
expect(up.popup.isOpen()).toBe(false)
it 'closes the popup when the user clicks on an [up-instant] link outside the popup', asyncSpec (next) ->
$target = affix('.target')
$outsideLink = affix('a[href="/foo"][up-target=".target"][up-instant]')
$popupOpener = affix('.link')
up.popup.attach($popupOpener, target: '.inside', html: "inside
")
next =>
expect(up.popup.isOpen()).toBe(true)
Trigger.clickSequence($outsideLink)
next =>
expect(up.popup.isOpen()).toBe(false)
it 'does not close the popup if #preventDefault() is called on up:popup:close event', asyncSpec (next) ->
affix('.outside').text('old outside')
$link = affix('.link')
up.popup.attach($link, target: '.inside', html: "inside
")
up.on 'up:popup:close', (e) -> e.preventDefault()
next =>
expect(up.popup.isOpen()).toBe(true)
Trigger.clickSequence($('body'))
next =>
expect(up.popup.isOpen()).toBe(true)
# Since there isn't anyone who could handle the rejection inside
# the event handler, our handler mutes the rejection.
expect(window).not.toHaveUnhandledRejections()
it 'does not close the popup if a link outside the popup is followed with the up.follow function (bugfix)', asyncSpec (next) ->
$target = affix('.target')
$outsideLink = affix('a[href="/foo"][up-target=".target"]')
$popupOpener = affix('.link')
up.popup.attach($popupOpener, target: '.inside', html: "inside
")
next =>
expect(up.popup.isOpen()).toBe(true)
up.follow($outsideLink)
next =>
expect(up.popup.isOpen()).toBe(true)
it 'does not close the popup if a form outside the popup is followed with the up.submit function (bugfix)', asyncSpec (next) ->
$target = affix('.target')
$outsideForm = affix('form[action="/foo"][up-target=".target"]')
$popupOpener = affix('.link')
up.popup.attach($popupOpener, target: '.inside', html: "inside
")
next =>
expect(up.popup.isOpen()).toBe(true)
up.submit($outsideForm)
next =>
expect(up.popup.isOpen()).toBe(true)