u = up.util e = up.element $ = jQuery describe 'up.fragment', -> describe 'JavaScript functions', -> describe 'up.fragment.first', -> it 'returns the first element with the given selector', -> match = fixture('.match') noMatch = fixture('.no-match') result = up.fragment.first('.match') expect(result).toBe(match) it 'returns undefined if there are no matches', -> result = up.fragment.first('.match') expect(result).toBeUndefined() it 'does not return an element that is currently destroying', -> match = fixture('.match.up-destroying') result = up.fragment.first('.match') expect(result).toBeUndefined() describe 'when given a root element for the search', -> it 'only matches descendants of that root', -> parent1 = fixture('.parent1') parent1Match = e.affix(parent1, '.match') parent2 = fixture('.parent1') parent2Match = e.affix(parent2, '.match') expect(up.fragment.first(parent1, '.match')).toBe(parent1Match) expect(up.fragment.first(parent2, '.match')).toBe(parent2Match) describe 'with { origin } option', -> it 'resolves an & in the selector string with an selector for the origin' it 'prefers to find an element in the same layer as the origin' it "returns the element in the top-most layer if there are no matches in the origin's layer" describe 'with { layer } option', -> it 'only matches elements in that layer' describe 'up.replace', -> describeCapability 'canPushState', -> beforeEach -> @$oldBefore = $fixture('.before').text('old-before') @$oldMiddle = $fixture('.middle').text('old-middle') @$oldAfter = $fixture('.after').text('old-after') @responseText = """
new-before
new-middle
new-after
""" @respond = (options = {}) -> @respondWith(@responseText, options) it 'replaces the given selector with the same selector from a freshly fetched page', asyncSpec (next) -> up.replace('.middle', '/path') next => @respond() next.after 10, => expect($('.before')).toHaveText('old-before') expect($('.middle')).toHaveText('new-middle') expect($('.after')).toHaveText('old-after') it 'returns a promise that will be fulfilled once the server response was received and the fragments were swapped', asyncSpec (next) -> resolution = jasmine.createSpy() promise = up.replace('.middle', '/path') promise.then(resolution) expect(resolution).not.toHaveBeenCalled() expect($('.middle')).toHaveText('old-middle') next => @respond() next => expect(resolution).toHaveBeenCalled() expect($('.middle')).toHaveText('new-middle') it 'allows to pass an element instead of a selector', asyncSpec (next) -> up.replace(@$oldMiddle, '/path') next => @respond() next => expect($('.before')).toHaveText('old-before') expect($('.middle')).toHaveText('new-middle') expect($('.after')).toHaveText('old-after') describe 'with { transition } option', -> it 'returns a promise that will be fulfilled once the server response was received and the swap transition has completed', asyncSpec (next) -> resolution = jasmine.createSpy() promise = up.replace('.middle', '/path', transition: 'cross-fade', duration: 200) promise.then(resolution) expect(resolution).not.toHaveBeenCalled() expect($('.middle')).toHaveText('old-middle') next => @respond() expect(resolution).not.toHaveBeenCalled() next.after 100, => expect(resolution).not.toHaveBeenCalled() next.after 200, => expect(resolution).toHaveBeenCalled() describe 'with { params } option', -> it "uses the given params as a non-GET request's payload", asyncSpec (next) -> givenParams = { 'foo-key': 'foo-value', 'bar-key': 'bar-value' } up.replace('.middle', '/path', method: 'put', params: givenParams) next => expect(@lastRequest().data()['foo-key']).toEqual(['foo-value']) expect(@lastRequest().data()['bar-key']).toEqual(['bar-value']) it "encodes the given params into the URL of a GET request", asyncSpec (next) -> givenParams = { 'foo-key': 'foo-value', 'bar-key': 'bar-value' } up.replace('.middle', '/path', method: 'get', params: givenParams) next => expect(@lastRequest().url).toMatchUrl('/path?foo-key=foo-value&bar-key=bar-value') it 'uses a HTTP method given as { method } option', asyncSpec (next) -> up.replace('.middle', '/path', method: 'put') next => expect(@lastRequest()).toHaveRequestMethod('PUT') describe 'when the server responds with an error', -> it 'replaces the first fallback instead of the given selector', asyncSpec (next) -> up.fragment.config.fallbacks = ['.fallback'] $fixture('.fallback') # can't have the example replace the Jasmine test runner UI extractSpy = up.fragment.knife.mock('extract').and.returnValue(Promise.resolve()) next => up.replace('.middle', '/path') next => @respond(status: 500) next => expect(extractSpy).toHaveBeenCalledWith('.fallback', jasmine.any(String), jasmine.any(Object)) it 'uses a target selector given as { failTarget } option', asyncSpec (next) -> next => up.replace('.middle', '/path', failTarget: '.after') next => @respond(status: 500) next => expect($('.middle')).toHaveText('old-middle') expect($('.after')).toHaveText('new-after') it 'rejects the returned promise', (done) -> $fixture('.after') promise = up.replace('.middle', '/path', failTarget: '.after') u.task => promiseState(promise).then (result) => expect(result.state).toEqual('pending') @respond(status: 500) u.task => promiseState(promise).then (result) => expect(result.state).toEqual('rejected') done() describe 'when the request times out', -> it "doesn't crash and rejects the returned promise", asyncSpec (next) -> jasmine.clock().install() # required by responseTimeout() $fixture('.target') promise = up.replace('.middle', '/path', timeout: 50) next => # See that the correct timeout value has been set on the XHR instance expect(@lastRequest().timeout).toEqual(50) next.await => # See that the promise is still pending before the timeout promiseState(promise).then (result) -> expect(result.state).toEqual('pending') next => @lastRequest().responseTimeout() next.await => promiseState(promise).then (result) -> expect(result.state).toEqual('rejected') describe 'when there is a network issue', -> it "doesn't crash and rejects the returned promise", (done) -> $fixture('.target') promise = up.replace('.middle', '/path') u.task => promiseState(promise).then (result) => expect(result.state).toEqual('pending') @lastRequest().responseError() u.task => promiseState(promise).then (result) => expect(result.state).toEqual('rejected') done() describe 'history', -> beforeEach -> up.history.config.enabled = true it 'should set the browser location to the given URL', (done) -> promise = up.replace('.middle', '/path') @respond() promise.then -> expect(location.href).toMatchUrl('/path') done() it 'does not add a history entry after non-GET requests', asyncSpec (next) -> up.replace('.middle', '/path', method: 'post') next => @respond() next => expect(location.href).toMatchUrl(@hrefBeforeExample) it 'adds a history entry after non-GET requests if the response includes a { X-Up-Method: "get" } header (will happen after a redirect)', asyncSpec (next) -> up.replace('.middle', '/requested-path', method: 'post') next => @respond(responseHeaders: 'X-Up-Method': 'GET' 'X-Up-Location': '/signaled-path' ) next => expect(location.href).toMatchUrl('/signaled-path') it 'does not a history entry after a failed GET-request', asyncSpec (next) -> up.replace('.middle', '/path', method: 'post', failTarget: '.middle') next => @respond(status: 500) next => expect(location.href).toMatchUrl(@hrefBeforeExample) it 'does not add a history entry with { history: false } option', asyncSpec (next) -> up.replace('.middle', '/path', history: false) next => @respond() next => expect(location.href).toMatchUrl(@hrefBeforeExample) it "detects a redirect's new URL when the server sets an X-Up-Location header", asyncSpec (next) -> up.replace('.middle', '/path') next => @respond(responseHeaders: { 'X-Up-Location': '/other-path' }) next => expect(location.href).toMatchUrl('/other-path') it 'adds params from a { params } option to the URL of a GET request', asyncSpec (next) -> up.replace('.middle', '/path', params: { 'foo-key': 'foo value', 'bar-key': 'bar value' }) next => @respond() next => expect(location.href).toMatchUrl('/path?foo-key=foo%20value&bar-key=bar%20value') describe 'if a URL is given as { history } option', -> it 'uses that URL as the new location after a GET request', asyncSpec (next) -> up.replace('.middle', '/path', history: '/given-path') next => @respond(failTarget: '.middle') next => expect(location.href).toMatchUrl('/given-path') it 'adds a history entry after a non-GET request', asyncSpec (next) -> up.replace('.middle', '/path', method: 'post', history: '/given-path') next => @respond(failTarget: '.middle') next => expect(location.href).toMatchUrl('/given-path') it 'does not add a history entry after a failed non-GET request', asyncSpec (next) -> up.replace('.middle', '/path', method: 'post', history: '/given-path', failTarget: '.middle') next => @respond(failTarget: '.middle', status: 500) next => expect(location.href).toMatchUrl(@hrefBeforeExample) describe 'source', -> it 'remembers the source the fragment was retrieved from', (done) -> promise = up.replace('.middle', '/path') @respond() promise.then -> expect($('.middle').attr('up-source')).toMatch(/\/path$/) done() it 'reuses the previous source for a non-GET request (since that is reloadable)', asyncSpec (next) -> @$oldMiddle.attr('up-source', '/previous-source') up.replace('.middle', '/path', method: 'post') next => @respond() next => expect($('.middle')).toHaveText('new-middle') expect(up.fragment.source('.middle')).toMatchUrl('/previous-source') describe 'if a URL is given as { source } option', -> it 'uses that URL as the source for a GET request', asyncSpec (next) -> up.replace('.middle', '/path', source: '/given-path') next => @respond() next => expect(up.fragment.source('.middle')).toMatchUrl('/given-path') it 'uses that URL as the source after a non-GET request', asyncSpec (next) -> up.replace('.middle', '/path', method: 'post', source: '/given-path') next => @respond() next => expect(up.fragment.source('.middle')).toMatchUrl('/given-path') it 'ignores the option and reuses the previous source after a failed non-GET request', asyncSpec (next) -> @$oldMiddle.attr('up-source', '/previous-source') up.replace('.middle', '/path', method: 'post', source: '/given-path', failTarget: '.middle') next => @respond(status: 500) next => expect(up.fragment.source('.middle')).toMatchUrl('/previous-source') describe 'document title', -> beforeEach -> up.history.config.enabled = true it "sets the document title to the response ", asyncSpec (next) -> $fixture('.container').text('old container text') up.replace('.container', '/path') next => @respondWith """ <html> <head> <title>Title from HTML
new container text
""" next => expect($('.container')).toHaveText('new container text') expect(document.title).toBe('Title from HTML') it "sets the document title to an 'X-Up-Title' header in the response", asyncSpec (next) -> $fixture('.container').text('old container text') up.replace('.container', '/path') next => @respondWith responseHeaders: 'X-Up-Title': 'Title from header' responseText: """
new container text
""" next => expect($('.container')).toHaveText('new container text') expect(document.title).toBe('Title from header') it "prefers the X-Up-Title header to the response ", asyncSpec (next) -> $fixture('.container').text('old container text') up.replace('.container', '/path') next => @respondWith responseHeaders: 'X-Up-Title': 'Title from header' responseText: """ <html> <head> <title>Title from HTML
new container text
""" next => expect($('.container')).toHaveText('new container text') expect(document.title).toBe('Title from header') it "sets the document title to the response with { history: false, title: true } options (bugfix)", asyncSpec (next) -> $fixture('.container').text('old container text') up.replace('.container', '/path', history: false, title: true) next => @respondWith """ <html> <head> <title>Title from HTML
new container text
""" next => expect($('.container')).toHaveText('new container text') expect(document.title).toBe('Title from HTML') it 'does not update the document title if the response has a tag inside an inline SVG image (bugfix)', asyncSpec (next) -> $fixture('.container').text('old container text') document.title = 'old document title' up.replace('.container', '/path', history: false, title: true) next => @respondWith """ <svg width="500" height="300" xmlns="http://www.w3.org/2000/svg"> <g> <title>SVG Title Demo example
new container text
""" next => expect($('.container')).toHaveText('new container text') expect(document.title).toBe('old document title') it "does not extract the title from the response or HTTP header if history isn't updated", asyncSpec (next) -> $fixture('.container').text('old container text') document.title = 'old document title' up.replace('.container', '/path', history: false) next => @respondWith responseHeaders: 'X-Up-Title': 'Title from header' responseText: """ Title from HTML
new container text
""" next => expect(document.title).toBe('old document title') it 'allows to pass an explicit title as { title } option', asyncSpec (next) -> $fixture('.container').text('old container text') up.replace('.container', '/path', title: 'Title from options') next => @respondWith """ Title from HTML
new container text
""" next => expect($('.container')).toHaveText('new container text') expect(document.title).toBe('Title from options') describe 'selector processing', -> it 'replaces multiple selectors separated with a comma', (done) -> promise = up.replace('.middle, .after', '/path') @respond() promise.then -> expect($('.before')).toHaveText('old-before') expect($('.middle')).toHaveText('new-middle') expect($('.after')).toHaveText('new-after') done() describe 'nested selector merging', -> it 'replaces a single fragment if a selector contains a subsequent selector in the current page', asyncSpec (next) -> $outer = $fixture('.outer').text('old outer text') $inner = $outer.affix('.inner').text('old inner text') replacePromise = up.replace('.outer, .inner', '/path') next => expect(@lastRequest().requestHeaders['X-Up-Target']).toEqual('.outer') @respondWith """
new outer text
new inner text
""" next => expect($('.outer')).toBeAttached() expect($('.outer').text()).toContain('new outer text') expect($('.inner')).toBeAttached() expect($('.inner').text()).toContain('new inner text') next.await => promise = promiseState(replacePromise) promise.then (result) => expect(result.state).toEqual('fulfilled') it 'does not merge selectors if a selector contains a subsequent selector, but prepends instead of replacing', asyncSpec (next) -> $outer = $fixture('.outer').text('old outer text') $inner = $outer.affix('.inner').text('old inner text') replacePromise = up.replace('.outer:before, .inner', '/path') next => expect(@lastRequest().requestHeaders['X-Up-Target']).toEqual('.outer:before, .inner') @respondWith """
new outer text
new inner text
""" next => expect($('.outer')).toBeAttached() expect($('.outer').text()).toContain('new outer text') expect($('.outer').text()).toContain('old outer text') expect($('.inner')).toBeAttached() expect($('.inner').text()).toContain('new inner text') next.await => promise = promiseState(replacePromise) promise.then (result) => expect(result.state).toEqual('fulfilled') it 'does not merge selectors if a selector contains a subsequent selector, but appends instead of replacing', asyncSpec (next) -> $outer = $fixture('.outer').text('old outer text') $inner = $outer.affix('.inner').text('old inner text') replacePromise = up.replace('.outer:after, .inner', '/path') next => expect(@lastRequest().requestHeaders['X-Up-Target']).toEqual('.outer:after, .inner') @respondWith """
new outer text
new inner text
""" next => expect($('.outer')).toBeAttached() expect($('.outer').text()).toContain('old outer text') expect($('.outer').text()).toContain('new outer text') expect($('.inner')).toBeAttached() expect($('.inner').text()).toContain('new inner text') next.await => promise = promiseState(replacePromise) promise.then (result) => expect(result.state).toEqual('fulfilled') it 'does not lose selector pseudo-classes when merging selectors (bugfix)', asyncSpec (next) -> $outer = $fixture('.outer').text('old outer text') $inner = $outer.affix('.inner').text('old inner text') replacePromise = up.replace('.outer:after, .inner', '/path') next => expect(@lastRequest().requestHeaders['X-Up-Target']).toEqual('.outer:after, .inner') it 'replaces a single fragment if a selector contains a previous selector in the current page', asyncSpec (next) -> $outer = $fixture('.outer').text('old outer text') $inner = $outer.affix('.inner').text('old inner text') replacePromise = up.replace('.outer, .inner', '/path') next => expect(@lastRequest().requestHeaders['X-Up-Target']).toEqual('.outer') @respondWith """
new outer text
new inner text
""" next => expect($('.outer')).toBeAttached() expect($('.outer').text()).toContain('new outer text') expect($('.inner')).toBeAttached() expect($('.inner').text()).toContain('new inner text') next.await => promise = promiseState(replacePromise) promise.then (result) => expect(result.state).toEqual('fulfilled') it 'does not lose a { reveal: true } option if the first selector was merged into a subsequent selector', asyncSpec (next) -> revealStub = up.viewport.knife.mock('reveal') $outer = $fixture('.outer').text('old outer text') $inner = $outer.affix('.inner').text('old inner text') up.replace('.inner, .outer', '/path', reveal: true) next => expect(@lastRequest().requestHeaders['X-Up-Target']).toEqual('.outer') @respondWith """
new outer text
new inner text
""" next => expect($('.outer')).toBeAttached() expect($('.outer').text()).toContain('new outer text') expect($('.inner')).toBeAttached() expect($('.inner').text()).toContain('new inner text') expect(revealStub).toHaveBeenCalled() it 'does not lose a { reveal: string } option if the first selector was merged into a subsequent selector', asyncSpec (next) -> revealStub = up.viewport.knife.mock('reveal') $outer = $fixture('.outer').text('old outer text') $inner = $outer.affix('.inner').text('old inner text') up.replace('.inner, .outer', '/path', reveal: '.revealee') next => expect(@lastRequest().requestHeaders['X-Up-Target']).toEqual('.outer') @respondWith """
new outer text
new inner text
revealee text
""" next => expect($('.outer')).toBeAttached() expect($('.outer').text()).toContain('new outer text') expect($('.inner')).toBeAttached() expect($('.inner').text()).toContain('new inner text') expect(revealStub).toHaveBeenCalled() revealArg = revealStub.calls.mostRecent().args[0] expect(revealArg).toMatchSelector('.revealee') it 'replaces a single fragment if the nesting differs in current page and response', asyncSpec (next) -> $outer = $fixture('.outer').text('old outer text') $inner = $outer.affix('.inner').text('old inner text') replacePromise = up.replace('.outer, .inner', '/path') next => @respondWith """
new inner text
new outer text
""" next => expect($('.outer')).toBeAttached() expect($('.outer').text()).toContain('new outer text') expect($('.inner')).not.toBeAttached() next.await => promise = promiseState(replacePromise) promise.then (result) => expect(result.state).toEqual('fulfilled') it 'does not crash if two selectors that are siblings in the current page are nested in the response', asyncSpec (next) -> $outer = $fixture('.one').text('old one text') $inner = $fixture('.two').text('old two text') replacePromise = up.replace('.one, .two', '/path') next => @respondWith """
new one text
new two text
""" next => expect($('.one')).toBeAttached() expect($('.one').text()).toContain('new one text') expect($('.two')).toBeAttached() expect($('.two').text()).toContain('new two text') next.await => promise = promiseState(replacePromise) promise.then (result) => expect(result.state).toEqual('fulfilled') it 'does not crash if selectors that siblings in the current page are inversely nested in the response', asyncSpec (next) -> $outer = $fixture('.one').text('old one text') $inner = $fixture('.two').text('old two text') replacePromise = up.replace('.one, .two', '/path') next => @respondWith """
new two text
new one text
""" next => expect($('.one')).toBeAttached() expect($('.one').text()).toContain('new one text') expect($('.two')).toBeAttached() expect($('.two').text()).toContain('new two text') next.await => promise = promiseState(replacePromise) promise.then (result) => expect(result.state).toEqual('fulfilled') it 'updates the first selector if the same element is targeted twice in a single replacement', asyncSpec (next) -> $one = $fixture('.one.alias').text('old one text') replacePromise = up.replace('.one, .alias', '/path') next => expect(@lastRequest().requestHeaders['X-Up-Target']).toEqual('.one') @respondWith """
new one text
""" next => expect($('.one')).toBeAttached() expect($('.one').text()).toContain('new one text') next.await => promise = promiseState(replacePromise) promise.then (result) => expect(result.state).toEqual('fulfilled') it 'updates the first selector if the same element is prepended or appended twice in a single replacement', asyncSpec (next) -> $one = $fixture('.one').text('old one text') replacePromise = up.replace('.one:before, .one:after', '/path') next => expect(@lastRequest().requestHeaders['X-Up-Target']).toEqual('.one:before') @respondWith """
new one text
""" next => expect($('.one')).toBeAttached() expect($('.one').text()).toMatchText('new one text old one text') next.await => promise = promiseState(replacePromise) promise.then (result) => expect(result.state).toEqual('fulfilled') it "updates the first selector if the same element is prepended, replaced and appended in a single replacement", asyncSpec (next) -> $elem = $fixture('.elem.alias1.alias2').text("old text") replacePromise = up.replace('.elem:before, .alias1, .alias2:after', '/path') next => expect(@lastRequest().requestHeaders['X-Up-Target']).toEqual('.elem:before') @respondWith """
new text
""" next => expect('.elem').toBeAttached() expect($('.elem').text()).toMatchText('new text old text') next.await => promise = promiseState(replacePromise) promise.then (result) => expect(result.state).toEqual('fulfilled') it 'replaces the body if asked to replace the "html" selector' it 'prepends instead of replacing when the target has a :before pseudo-selector', (done) -> promise = up.replace('.middle:before', '/path') @respond() promise.then -> expect($('.before')).toHaveText('old-before') expect($('.middle')).toHaveText('new-middleold-middle') expect($('.after')).toHaveText('old-after') done() it 'appends instead of replacing when the target has a :after pseudo-selector', (done) -> promise = up.replace('.middle:after', '/path') @respond() promise.then -> expect($('.before')).toHaveText('old-before') expect($('.middle')).toHaveText('old-middlenew-middle') expect($('.after')).toHaveText('old-after') done() it "lets the developer choose between replacing/prepending/appending for each selector", (done) -> promise = up.replace('.before:before, .middle, .after:after', '/path') @respond() promise.then -> expect($('.before')).toHaveText('new-beforeold-before') expect($('.middle')).toHaveText('new-middle') expect($('.after')).toHaveText('old-afternew-after') done() it 'understands non-standard CSS selector extensions such as :has(...)', (done) -> $first = $fixture('.boxx#first') $firstChild = $('old first').appendTo($first) $second = $fixture('.boxx#second') $secondChild = $('old second').appendTo($second) promise = up.replace('.boxx:has(.first-child)', '/path') @respondWith """
new first
""" promise.then -> expect($('#first span')).toHaveText('new first') expect($('#second span')).toHaveText('old second') done() describe 'when selectors are missing on the page before the request was made', -> beforeEach -> up.fragment.config.fallbacks = [] it 'tries selectors from options.fallback before making a request', asyncSpec (next) -> $fixture('.box').text('old box') up.replace('.unknown', '/path', fallback: '.box') next => @respondWith '
new box
' next => expect('.box').toHaveText('new box') it 'rejects the promise if all alternatives are exhausted', (done) -> promise = up.replace('.unknown', '/path', fallback: '.more-unknown') promise.catch (e) -> expect(e).toBeError(/Could not find target in current page/i) done() it 'considers a union selector to be missing if one of its selector-atoms are missing', asyncSpec (next) -> $fixture('.target').text('old target') $fixture('.fallback').text('old fallback') up.replace('.target, .unknown', '/path', fallback: '.fallback') next => @respondWith """
new target
new fallback
""" next => expect('.target').toHaveText('old target') expect('.fallback').toHaveText('new fallback') it 'tries a selector from up.fragment.config.fallbacks if options.fallback is missing', asyncSpec (next) -> up.fragment.config.fallbacks = ['.existing'] $fixture('.existing').text('old existing') up.replace('.unknown', '/path') next => @respondWith '
new existing
' next => expect('.existing').toHaveText('new existing') it 'does not try a selector from up.fragment.config.fallbacks and rejects the promise if options.fallback is false', (done) -> up.fragment.config.fallbacks = ['.existing'] $fixture('.existing').text('old existing') up.replace('.unknown', '/path', fallback: false).catch (e) -> expect(e).toBeError(/Could not find target in current page/i) done() describe 'when selectors are missing on the page after the request was made', -> beforeEach -> up.fragment.config.fallbacks = [] it 'tries selectors from options.fallback before swapping elements', asyncSpec (next) -> $target = $fixture('.target').text('old target') $fallback = $fixture('.fallback').text('old fallback') up.replace('.target', '/path', fallback: '.fallback') $target.remove() next => @respondWith """
new target
new fallback
""" next => expect('.fallback').toHaveText('new fallback') it 'rejects the promise if all alternatives are exhausted', (done) -> $target = $fixture('.target').text('old target') $fallback = $fixture('.fallback').text('old fallback') promise = up.replace('.target', '/path', fallback: '.fallback') $target.remove() $fallback.remove() u.task => @respondWith """
new target
new fallback
""" u.task => promiseState(promise).then (result) -> expect(result.state).toEqual('rejected') expect(result.value).toBeError(/Could not find target in current page/i) done() it 'considers a union selector to be missing if one of its selector-atoms are missing', asyncSpec (next) -> $target = $fixture('.target').text('old target') $target2 = $fixture('.target2').text('old target2') $fallback = $fixture('.fallback').text('old fallback') up.replace('.target, .target2', '/path', fallback: '.fallback') $target2.remove() next => @respondWith """
new target
new target2
new fallback
""" next => expect('.target').toHaveText('old target') expect('.fallback').toHaveText('new fallback') it 'tries a selector from up.fragment.config.fallbacks if options.fallback is missing', asyncSpec (next) -> up.fragment.config.fallbacks = ['.fallback'] $target = $fixture('.target').text('old target') $fallback = $fixture('.fallback').text('old fallback') up.replace('.target', '/path') $target.remove() next => @respondWith """
new target
new fallback
""" next => expect('.fallback').toHaveText('new fallback') it 'does not try a selector from up.fragment.config.fallbacks and rejects the promise if options.fallback is false', (done) -> up.fragment.config.fallbacks = ['.fallback'] $target = $fixture('.target').text('old target') $fallback = $fixture('.fallback').text('old fallback') promise = up.replace('.target', '/path', fallback: false) $target.remove() u.task => @respondWith """
new target
new fallback
""" promise.catch (e) -> expect(e).toBeError(/Could not find target in current page/i) done() describe 'when selectors are missing in the response', -> beforeEach -> up.fragment.config.fallbacks = [] it 'tries selectors from options.fallback before swapping elements', asyncSpec (next) -> $target = $fixture('.target').text('old target') $fallback = $fixture('.fallback').text('old fallback') up.replace('.target', '/path', fallback: '.fallback') next => @respondWith """
new fallback
""" next => expect('.target').toHaveText('old target') expect('.fallback').toHaveText('new fallback') describe 'if all alternatives are exhausted', -> it 'rejects the promise', (done) -> $target = $fixture('.target').text('old target') $fallback = $fixture('.fallback').text('old fallback') promise = up.replace('.target', '/path', fallback: '.fallback') u.task => @respondWith '
new unexpected
' promise.catch (e) -> expect(e).toBeError(/Could not find target in response/i) done() it 'shows a link to open the unexpected response', (done) -> $target = $fixture('.target').text('old target') $fallback = $fixture('.fallback').text('old fallback') promise = up.replace('.target', '/path', fallback: '.fallback') navigate = spyOn(up.browser, 'navigate') u.task => @respondWith '
new unexpected
' promise.catch (e) -> $toast = $('.up-toast') expect($toast).toBeAttached() $inspectLink = $toast.find(".up-toast-action:contains('Open response')") expect($inspectLink).toBeAttached() expect(navigate).not.toHaveBeenCalled() Trigger.clickSequence($inspectLink) u.task => expect(navigate).toHaveBeenCalledWith('/path', {}) done() it 'considers a union selector to be missing if one of its selector-atoms are missing', asyncSpec (next) -> $target = $fixture('.target').text('old target') $target2 = $fixture('.target2').text('old target2') $fallback = $fixture('.fallback').text('old fallback') up.replace('.target, .target2', '/path', fallback: '.fallback') next => @respondWith """
new target
new fallback
""" next => expect('.target').toHaveText('old target') expect('.target2').toHaveText('old target2') expect('.fallback').toHaveText('new fallback') it 'tries a selector from up.fragment.config.fallbacks if options.fallback is missing', asyncSpec (next) -> up.fragment.config.fallbacks = ['.fallback'] $target = $fixture('.target').text('old target') $fallback = $fixture('.fallback').text('old fallback') up.replace('.target', '/path') next => @respondWith '
new fallback
' next => expect('.target').toHaveText('old target') expect('.fallback').toHaveText('new fallback') it 'does not try a selector from up.fragment.config.fallbacks and rejects the promise if options.fallback is false', (done) -> up.fragment.config.fallbacks = ['.fallback'] $target = $fixture('.target').text('old target') $fallback = $fixture('.fallback').text('old fallback') promise = up.replace('.target', '/path', fallback: false) u.task => @respondWith '
new fallback
' promise.catch (e) -> expect(e).toBeError(/Could not find target in response/i) done() describe 'execution of scripts', -> beforeEach -> window.scriptTagExecuted = jasmine.createSpy('scriptTagExecuted') describe 'inline scripts', -> it 'does not execute inline script tags', (done) -> @responseText = """
new-middle
""" promise = up.replace('.middle', '/path') @respond() promise.then -> expect(window.scriptTagExecuted).not.toHaveBeenCalled() done() it 'does not crash when the new fragment contains inline script tag that is followed by another sibling (bugfix)', (done) -> @responseText = """
new-middle-before
new-middle-after
""" promise = up.replace('.middle', '/path') @respond() u.task -> promiseState(promise).then (result) -> expect(result.state).toEqual('fulfilled') expect(window.scriptTagExecuted).not.toHaveBeenCalled() done() describe 'linked scripts', -> beforeEach -> # Add a cache-buster to each path so the browser cache is guaranteed to be irrelevant @linkedScriptPath = "/assets/fixtures/linked_script.js?cache-buster=#{Math.random().toString()}" it 'does not execute linked scripts to prevent re-inclusion of javascript inserted before the closing body tag', (done) -> @responseText = """
new-middle
""" promise = up.replace('.middle', '/path') @respond() promise.then => # Must respond to this request, since jQuery makes them async: false if u.contains(@lastRequest().url, 'linked_script') @respondWith('window.scriptTagExecuted()') # Now wait for jQuery to parse out