diff --git a/lib/assets/javascripts/turbograft/turbolinks.coffee b/lib/assets/javascripts/turbograft/turbolinks.coffee index e728d070..c23359d2 100644 --- a/lib/assets/javascripts/turbograft/turbolinks.coffee +++ b/lib/assets/javascripts/turbograft/turbolinks.coffee @@ -1,14 +1,15 @@ xhr = null +activeDocument = document installDocumentReadyPageEventTriggers = -> - document.addEventListener 'DOMContentLoaded', ( -> + activeDocument.addEventListener 'DOMContentLoaded', ( -> triggerEvent 'page:change' triggerEvent 'page:update' ), true installJqueryAjaxSuccessPageUpdateTrigger = -> if typeof jQuery isnt 'undefined' - jQuery(document).on 'ajaxSuccess', (event, xhr, settings) -> + jQuery(activeDocument).on 'ajaxSuccess', (event, xhr, settings) -> return unless jQuery.trim xhr.responseText triggerEvent 'page:update' @@ -20,20 +21,20 @@ browserSupportsPushState = window.history and window.history.pushState and window.history.replaceState and historyStateIsDefined window.triggerEvent = (name, data) -> - event = document.createEvent 'Events' + event = activeDocument.createEvent 'Events' event.data = data if data event.initEvent name, true, true - document.dispatchEvent event + activeDocument.dispatchEvent event window.triggerEventFor = (name, node, data) -> - event = document.createEvent 'Events' + event = activeDocument.createEvent 'Events' event.data = data if data event.initEvent name, true, true node.dispatchEvent event popCookie = (name) -> - value = document.cookie.match(new RegExp(name+"=(\\w+)"))?[1].toUpperCase() or '' - document.cookie = name + '=; expires=Thu, 01-Jan-70 00:00:01 GMT; path=/' + value = activeDocument.cookie.match(new RegExp(name+"=(\\w+)"))?[1].toUpperCase() or '' + activeDocument.cookie = name + '=; expires=Thu, 01-Jan-70 00:00:01 GMT; path=/' value requestMethodIsSafe = @@ -42,7 +43,7 @@ requestMethodIsSafe = browserSupportsTurbolinks = browserSupportsPushState and requestMethodIsSafe browserSupportsCustomEvents = - document.addEventListener and document.createEvent + activeDocument.addEventListener and activeDocument.createEvent if browserSupportsCustomEvents installDocumentReadyPageEventTriggers() @@ -61,7 +62,6 @@ removeNode = (node) -> # TODO: clean up everything above me ^ # TODO: decide on the public API class window.Turbolinks - createDocument = null currentState = null referer = null @@ -83,7 +83,7 @@ class window.Turbolinks if url? url = (new ComponentUrl(url)).absolute triggerEvent('page:before-full-refresh', url: url) - document.location.href = url + activeDocument.location.href = url return @pushState: (state, title, url) -> @@ -92,6 +92,10 @@ class window.Turbolinks @replaceState: (state, title, url) -> window.history.replaceState(state, title, url) + @document: (documentToUse) -> + activeDocument = documentToUse if documentToUse + activeDocument + fetchReplacement = (url, options) -> triggerEvent 'page:fetch', url: url.absolute @@ -138,7 +142,7 @@ class window.Turbolinks reflectNewUrl url if options.updatePushState updateBody(upstreamDocument, xhr, options) else - turbohead = new TurboGraft.TurboHead(document, upstreamDocument) + turbohead = new TurboGraft.TurboHead(activeDocument, upstreamDocument) if turbohead.hasAssetConflicts() return Turbolinks.fullPageNavigate(url) reflectNewUrl url if options.updatePushState @@ -162,7 +166,7 @@ class window.Turbolinks triggerEvent 'page:load', nodes changePage = (title, body, csrfToken, runScripts, options = {}) -> - document.title = title if title + activeDocument.title = title if title options.onlyKeys ?= [] options.exceptKeys ?= [] @@ -180,7 +184,7 @@ class window.Turbolinks deleteRefreshNeverNodes(body) triggerEvent 'page:before-replace' - replaceNode(body, document.body) + replaceNode(body, activeDocument.body) CSRFToken.update csrfToken if csrfToken? setAutofocusElement() executeScriptTags() if runScripts @@ -193,14 +197,14 @@ class window.Turbolinks getNodesMatchingRefreshKeys = (keys) -> matchingNodes = [] for key in keys - for node in TurboGraft.querySelectorAllTGAttribute(document, 'refresh', key) + for node in TurboGraft.querySelectorAllTGAttribute(activeDocument, 'refresh', key) matchingNodes.push(node) return matchingNodes getNodesWithRefreshAlways = -> matchingNodes = [] - for node in TurboGraft.querySelectorAllTGAttribute(document, 'refresh-always') + for node in TurboGraft.querySelectorAllTGAttribute(activeDocument, 'refresh-always') matchingNodes.push(node) return matchingNodes @@ -213,8 +217,8 @@ class window.Turbolinks false setAutofocusElement = -> - autofocusElement = (list = document.querySelectorAll 'input[autofocus], textarea[autofocus]')[list.length - 1] - if autofocusElement and document.activeElement isnt autofocusElement + autofocusElement = (list = activeDocument.querySelectorAll 'input[autofocus], textarea[autofocus]')[list.length - 1] + if autofocusElement and activeDocument.activeElement isnt autofocusElement autofocusElement.focus() deleteRefreshNeverNodes = (body) -> @@ -263,7 +267,7 @@ class window.Turbolinks persistStaticElements = (body) -> allNodesToKeep = [] - nodes = TurboGraft.querySelectorAllTGAttribute(document, 'tg-static') + nodes = TurboGraft.querySelectorAllTGAttribute(activeDocument, 'tg-static') allNodesToKeep.push(node) for node in nodes keepNodes(body, allNodesToKeep) @@ -273,22 +277,22 @@ class window.Turbolinks allNodesToKeep = [] for key in keys - for node in TurboGraft.querySelectorAllTGAttribute(document, 'refresh', key) + for node in TurboGraft.querySelectorAllTGAttribute(activeDocument, 'refresh', key) allNodesToKeep.push(node) keepNodes(body, allNodesToKeep) return executeScriptTags = -> - scripts = Array::slice.call document.body.querySelectorAll 'script:not([data-turbolinks-eval="false"])' + scripts = Array::slice.call activeDocument.body.querySelectorAll 'script:not([data-turbolinks-eval="false"])' for script in scripts when script.type in ['', 'text/javascript'] executeScriptTag(script) return executeScriptTag = (script) -> - copy = document.createElement 'script' + copy = activeDocument.createElement 'script' copy.setAttribute attr.name, attr.value for attr in script.attributes - copy.appendChild document.createTextNode script.innerHTML + copy.appendChild activeDocument.createTextNode script.innerHTML { parentNode, nextSibling } = script parentNode.removeChild script parentNode.insertBefore copy, nextSibling @@ -306,16 +310,16 @@ class window.Turbolinks reflectRedirectedUrl = (xhr) -> if location = xhr.getResponseHeader 'X-XHR-Redirected-To' location = new ComponentUrl location - preservedHash = if location.hasNoHash() then document.location.hash else '' + preservedHash = if location.hasNoHash() then activeDocument.location.hash else '' Turbolinks.replaceState currentState, '', location.href + preservedHash return rememberReferer = -> - referer = document.location.href + referer = activeDocument.location.href @rememberCurrentUrl: -> - Turbolinks.replaceState { turbolinks: true, url: document.location.href }, '', document.location.href + Turbolinks.replaceState { turbolinks: true, url: activeDocument.location.href }, '', activeDocument.location.href @rememberCurrentState: -> currentState = window.history.state @@ -324,8 +328,8 @@ class window.Turbolinks window.scrollTo page.positionX, page.positionY resetScrollPosition = -> - if document.location.hash - document.location.href = document.location.href + if activeDocument.location.hash + activeDocument.location.href = activeDocument.location.href else window.scrollTo 0, 0 @@ -346,10 +350,10 @@ class window.Turbolinks @rememberCurrentUrl() @rememberCurrentState() - document.addEventListener 'click', Click.installHandlerLast, true + activeDocument.addEventListener 'click', Click.installHandlerLast, true bypassOnLoadPopstate -> window.addEventListener 'popstate', installHistoryChangeHandler, false else - @visit = (url) -> document.location.href = url + @visit = (url) -> activeDocument.location.href = url diff --git a/test/example/config/initializers/assets.rb b/test/example/config/initializers/assets.rb index eb9bd64d..a3b53a6a 100644 --- a/test/example/config/initializers/assets.rb +++ b/test/example/config/initializers/assets.rb @@ -43,4 +43,5 @@ fixtures/js/order_testing/b.js fixtures/js/order_testing/c.js fixtures/js/routes.self.js + fixtures/js/asset-fixtures.self.js ) diff --git a/test/javascripts/fixtures/_test_head.html.erb b/test/javascripts/fixtures/_test_head.html.erb deleted file mode 100644 index eeab85ca..00000000 --- a/test/javascripts/fixtures/_test_head.html.erb +++ /dev/null @@ -1,19 +0,0 @@ - -<%= javascript_include_tag *@suite.spec_assets, debug: @suite.config.expand_assets %> - diff --git a/test/javascripts/fixtures/js/order_testing/a.js b/test/javascripts/fixtures/js/order_testing/a.js index 995b6894..1ed1fe35 100644 --- a/test/javascripts/fixtures/js/order_testing/a.js +++ b/test/javascripts/fixtures/js/order_testing/a.js @@ -1 +1 @@ -window.actualExecutionOrder.push('a') +window.parent.actualExecutionOrder.push('a') diff --git a/test/javascripts/fixtures/js/order_testing/b.js b/test/javascripts/fixtures/js/order_testing/b.js index 611250c7..b4b55791 100644 --- a/test/javascripts/fixtures/js/order_testing/b.js +++ b/test/javascripts/fixtures/js/order_testing/b.js @@ -1 +1 @@ -window.actualExecutionOrder.push('b') +window.parent.actualExecutionOrder.push('b') diff --git a/test/javascripts/fixtures/js/order_testing/c.js b/test/javascripts/fixtures/js/order_testing/c.js index e70e619e..8fe0f870 100644 --- a/test/javascripts/fixtures/js/order_testing/c.js +++ b/test/javascripts/fixtures/js/order_testing/c.js @@ -1 +1 @@ -window.actualExecutionOrder.push('c') +window.parent.actualExecutionOrder.push('c') diff --git a/test/javascripts/fixtures/js/routes.coffee b/test/javascripts/fixtures/js/routes.coffee index 89d6caa2..eee026f0 100644 --- a/test/javascripts/fixtures/js/routes.coffee +++ b/test/javascripts/fixtures/js/routes.coffee @@ -469,7 +469,7 @@ window.ROUTES = { Hi - + """ @@ -485,7 +485,7 @@ window.ROUTES = { Hi - + """ diff --git a/test/javascripts/test_helper.js b/test/javascripts/test_helper.js deleted file mode 100644 index 6e8f2684..00000000 --- a/test/javascripts/test_helper.js +++ /dev/null @@ -1,16 +0,0 @@ -//= require support/sinon -//= require support/chai -//= require support/sinon-chai -//= require fixtures/js/routes -//= require vendor/promise-polyfill/promise -//= require application - -expect = chai.expect; -assert = chai.assert; -spy = sinon.spy; -mock = sinon.mock; -stub = sinon.stub; - -mocha.setup('tdd') -sinon.assert.expose(chai.assert, {prefix: ''}); -chai.config.truncateThreshold = 9999; diff --git a/test/javascripts/test_helper.js.erb b/test/javascripts/test_helper.js.erb new file mode 100644 index 00000000..cb474a47 --- /dev/null +++ b/test/javascripts/test_helper.js.erb @@ -0,0 +1,31 @@ +//= require support/sinon +//= require support/chai +//= require support/sinon-chai +//= require_self +//= require fixtures/js/routes +//= require vendor/promise-polyfill/promise +//= require application + +ASSET_FIXTURES = { + 'foo.js': "<%= asset_path('fixtures/js/foo.js') %>", + 'bar.js': "<%= asset_path('fixtures/js/bar.js') %>", + 'a.js': "<%= asset_path('fixtures/js/order_testing/a.js') %>", + 'b.js': "<%= asset_path('fixtures/js/order_testing/b.js') %>", + 'c.js': "<%= asset_path('fixtures/js/order_testing/c.js') %>", + 'foo.css': "<%= asset_path('fixtures/css/foo.css') %>", + 'bar.css': "<%= asset_path('fixtures/css/bar.css') %>", + 'a.css': "<%= asset_path('fixtures/css/order_testing/a.css') %>", + 'b.css': "<%= asset_path('fixtures/css/order_testing/b.css') %>", + 'c.css': "<%= asset_path('fixtures/css/order_testing/c.css') %>", + 'd.css': "<%= asset_path('fixtures/css/order_testing/d.css') %>" +}; + +expect = chai.expect; +assert = chai.assert; +spy = sinon.spy; +mock = sinon.mock; +stub = sinon.stub; + +mocha.setup('tdd') +sinon.assert.expose(chai.assert, {prefix: ''}); +chai.config.truncateThreshold = 9999; diff --git a/test/javascripts/turbolinks_test.coffee b/test/javascripts/turbolinks_test.coffee index 3613a9d0..d90dfff3 100644 --- a/test/javascripts/turbolinks_test.coffee +++ b/test/javascripts/turbolinks_test.coffee @@ -1,4 +1,7 @@ describe 'Turbolinks', -> + baseHTML = '' + iframe = null + testDocument = null sandbox = null pushStateStub = null replaceStateStub = null @@ -22,7 +25,7 @@ describe 'Turbolinks', -> ] hasScript = (filename) -> - document.querySelectorAll("script[src=\"#{ASSET_FIXTURES[filename]}\"]").length > 0 + testDocument.querySelectorAll("script[src=\"#{ASSET_FIXTURES[filename]}\"]").length > 0 assetFixturePath = (filename) -> path = ASSET_FIXTURES[filename] @@ -44,39 +47,47 @@ describe 'Turbolinks', -> ) scriptsInHead = -> - [].slice.call(document.head.children) + [].slice.call(testDocument.head.children) .filter((node) -> node.nodeName == 'SCRIPT') .map((node) -> node.getAttribute('src')) linksInHead = -> - [].slice.call(document.head.children) + [].slice.call(testDocument.head.children) .filter((node) -> node.nodeName == 'LINK') .map((node) -> node.getAttribute('href')) resetPage = -> - document.head.innerHTML = "" - document.body.innerHTML = """ + testDocument.head.innerHTML = "" + testDocument.body.innerHTML = """
""" + setupIframe = -> + iframe = document.createElement('iframe') + document.body.appendChild(iframe) + iframe.contentDocument.write(baseHTML) + iframe.contentDocument + startFromFixture = (route) -> fixtureHTML = ROUTES[route][2] - document.documentElement.innerHTML = fixtureHTML + testDocument.documentElement.innerHTML = fixtureHTML urlFor = (slug) -> window.location.origin + slug visit = ({options, url}, callback) -> - $(document).one('page:load', (event) -> + $(testDocument).one('page:load', (event) -> setTimeout((-> callback(event) if callback), 0) ) Turbolinks.visit('/' + url, options) beforeEach -> + testDocument = setupIframe() unless iframe + Turbolinks.document(testDocument) sandbox = sinon.sandbox.create() pushStateStub = sandbox.stub(Turbolinks, 'pushState') replaceStateStub = sandbox.stub(Turbolinks, 'replaceState') - sandbox.stub(Turbolinks, 'fullPageNavigate', -> $(document).trigger('page:load')) + sandbox.stub(Turbolinks, 'fullPageNavigate', -> $(testDocument).trigger('page:load')) sandbox.useFakeServer() Object.keys(ROUTES).forEach (url) -> @@ -91,7 +102,7 @@ describe 'Turbolinks', -> afterEach -> sandbox.restore() - $(document).off(TURBO_EVENTS.join(' ')) + $(testDocument).off(TURBO_EVENTS.join(' ')) $("#turbo-area").remove() it 'is defined', -> @@ -100,14 +111,14 @@ describe 'Turbolinks', -> describe '#visit', -> it 'subsequent visits abort previous XHRs', (done) -> pageReceive = stub() - $(document).on('page:receive', pageReceive) + $(testDocument).on('page:receive', pageReceive) visit url: 'noScriptsOrLinkInHead', -> true visit url: 'noScriptsOrLinkInHead', -> assert(pageReceive.calledOnce, 'page:receive should only be emitted once!') done() it 'returns if pageChangePrevented', (done) -> - $(document).one 'page:before-change', (event) -> + $(testDocument).one 'page:before-change', (event) -> event.preventDefault() assert.equal('/noScriptsOrLinkInHead', event.originalEvent.data) assert.equal(0, sandbox.server.requests.length) @@ -190,7 +201,7 @@ describe 'Turbolinks', -> describe 'link tags', -> it 'dispatches page:after-link-inserted event when inserting a link on navigation', (done) -> linkTagInserted = sinon.spy() - $(document).on 'page:after-link-inserted', linkTagInserted + $(testDocument).on 'page:after-link-inserted', linkTagInserted visit url: 'singleLinkInHead', -> assertLinks(['foo.css']) @@ -217,7 +228,7 @@ describe 'Turbolinks', -> describe 'script tags', -> it 'dispatches page:after-script-inserted event when inserting a script on navigation', (done) -> scriptTagInserted = sinon.spy() - $(document).on 'page:after-script-inserted', scriptTagInserted + $(testDocument).on 'page:after-script-inserted', scriptTagInserted visit url: 'singleScriptInHead', -> assert.equal(scriptTagInserted.callCount, 1) done() @@ -259,7 +270,6 @@ describe 'Turbolinks', -> describe 'executes scripts in the order they are present in the dom of the upstream document', -> beforeEach -> window.actualExecutionOrder = [] - afterEach -> delete window.actualExecutionOrder it 'works in order ABC', (done) -> expectedScriptOrder = ['a', 'b', 'c'] @@ -351,9 +361,9 @@ describe 'Turbolinks', -> it 'uses just the part of the response body we supply', (done) -> visit url: 'noScriptsOrLinkInHead', options: {partialReplace: true, onlyKeys: ['turbo-area']}, -> - assert.equal("Hi there!", document.title) - assert.notInclude(document.body.textContent, 'YOLO') - assert.include(document.body.textContent, 'Hi bob') + assert.equal("Hi there!", testDocument.title) + assert.notInclude(testDocument.body.textContent, 'YOLO') + assert.include(testDocument.body.textContent, 'Hi bob') done() it 'triggers the page:load event with a list of nodes that are new (freshly replaced)', (done) -> @@ -369,7 +379,7 @@ describe 'Turbolinks', -> it 'does not trigger the page:before-partial-replace event more than once', (done) -> handler = stub() - $(document).on 'page:before-partial-replace', handler + $(testDocument).on 'page:before-partial-replace', handler visit url: 'noScriptsOrLinkInHead', options: {partialReplace: true, onlyKeys: ['turbo-area']}, -> assert(handler.calledOnce) @@ -377,7 +387,7 @@ describe 'Turbolinks', -> it 'refreshes only outermost nodes of dom subtrees with refresh keys', (done) -> visit url: 'responseWithRefreshAlways', -> - $(document).on 'page:before-partial-replace', (ev) -> + $(testDocument).on 'page:before-partial-replace', (ev) -> nodes = ev.originalEvent.data assert.equal(2, nodes.length) assert.equal('div2', nodes[0].id) @@ -406,9 +416,9 @@ describe 'Turbolinks', -> it 'does not update document if the request was canceled', -> resolver({isCanceled: true}) loadPromise = Turbolinks.loadPage('/foo', xhr) - .then -> assert.notInclude(document.body.innerHTML, SUCCESS_HTML_CONTENT) + .then -> assert.notInclude(testDocument.body.innerHTML, SUCCESS_HTML_CONTENT) it 'updates the document if the request was not canceled', -> resolver() loadPromise = Turbolinks.loadPage('/foo', xhr) - .then -> assert.include(document.body.innerHTML, SUCCESS_HTML_CONTENT) + .then -> assert.include(testDocument.body.innerHTML, SUCCESS_HTML_CONTENT) diff --git a/test/teaspoon_env.rb b/test/teaspoon_env.rb index 06eae1fc..18c343a9 100644 --- a/test/teaspoon_env.rb +++ b/test/teaspoon_env.rb @@ -13,7 +13,6 @@ config.suite do |suite| suite.use_framework :mocha - suite.boot_partial = '/test_head' suite.matcher = "{test/javascripts,app/assets}/**/*_test.{js,js.coffee,coffee,coffee.erb}" suite.helper = "test_helper" suite.stylesheets = ["teaspoon"]