Fix bugs and add tests #145

Merged
BehindTheMath merged 11 commits from fix/bugs-and-add-tests into master 2018-04-09 22:36:33 -05:00
22 changed files with 478 additions and 153 deletions

View File

@@ -445,10 +445,14 @@ Enables verbose mode. Useful to debug page layout differences.
When set to true, clicking on a link that points to the current URL will trigger a full page reload. When set to true, clicking on a link that points to the current URL will trigger a full page reload.
The default is `false`, so clicking on such a link will do nothing. When set to `false`, clicking on such a link will cause Pjax to load the
current page like any page.
If you want to add some custom behavior, add a click listener to the link, If you want to add some custom behavior, add a click listener to the link,
and set `preventDefault` to true, to prevent Pjax from receiving the event. and set `preventDefault` to true, to prevent Pjax from receiving the event.
Note: this must be done before Pjax is instantiated. Otherwise, Pjax's
event handler will be called first, and preventDefault() won't be called yet.
Here is some sample code: Here is some sample code:
```js ```js
@@ -465,6 +469,8 @@ Here is some sample code:
} }
}) })
} }
var pjax = new Pjax()
``` ```
(Note that if `cacheBust` is set to true, the code that checks if the href (Note that if `cacheBust` is set to true, the code that checks if the href

View File

@@ -11,7 +11,7 @@
<h1>Index</h1> <h1>Index</h1>
Hello. Hello.
Go to <a href="page2.html" class="js-Pjax">Page 2</a> or <a href="page3.html" class="js-Pjax">Page 3</a> and view your console to see Pjax events. Go to <a href="page2.html" class="js-Pjax">Page 2</a> or <a href="page3.html" class="js-Pjax">Page 3</a> and view your console to see Pjax events.
Clicking on <a href="index.html">this page</a> will just reload the page entirely. Clicking on <a href="index.html">this page</a> will do nothing.
<h2>Manual URL loading</h2> <h2>Manual URL loading</h2>

14
index.d.ts vendored
View File

@@ -168,9 +168,23 @@ declare namespace Pjax {
* will not work, due to the query string appended to force a cache bust). * will not work, due to the query string appended to force a cache bust).
*/ */
currentUrlFullReload: boolean; currentUrlFullReload: boolean;
/**
* Hold the information to make an XHR request.
*/
requestOptions?: {
requestUrl?: string;
requestMethod?: string;
requestParams?: IRequestParams[];
}
} }
export type Switch = (oldEl: Element, newEl: Element, options?: IOptions, switchesOptions?: StringKeyedObject) => void; export type Switch = (oldEl: Element, newEl: Element, options?: IOptions, switchesOptions?: StringKeyedObject) => void;
export interface IRequestParams {
name: string,
value: string
}
} }
interface StringKeyedObject<T = any> { interface StringKeyedObject<T = any> {

View File

@@ -13,6 +13,7 @@ module.exports = function(el) {
script.type = "text/javascript" script.type = "text/javascript"
/* istanbul ignore if */
if (src !== "") { if (src !== "") {
script.src = src script.src = src
script.async = false // force synchronous loading of peripheral JS script.async = false // force synchronous loading of peripheral JS
@@ -23,6 +24,7 @@ module.exports = function(el) {
script.appendChild(document.createTextNode(code)) script.appendChild(document.createTextNode(code))
} }
catch (e) { catch (e) {
/* istanbul ignore next */
// old IEs have funky script nodes // old IEs have funky script nodes
script.text = code script.text = code
} }
@@ -31,7 +33,7 @@ module.exports = function(el) {
// execute // execute
parent.appendChild(script) parent.appendChild(script)
// avoid pollution only in head or body tags // avoid pollution only in head or body tags
if (["head", "body"].indexOf(parent.tagName.toLowerCase()) > 0) { if (parent instanceof HTMLHeadElement || parent instanceof HTMLBodyElement) {
parent.removeChild(script) parent.removeChild(script)
} }

View File

@@ -9,16 +9,7 @@ module.exports = function(options) {
options.switches = options.switches || {} options.switches = options.switches || {}
options.switchesOptions = options.switchesOptions || {} options.switchesOptions = options.switchesOptions || {}
options.history = options.history || true options.history = options.history || true
options.analytics = (typeof options.analytics === "function" || options.analytics === false) ? options.analytics = (typeof options.analytics === "function" || options.analytics === false) ? options.analytics : defaultAnalytics
options.analytics :
function() {
if (window._gaq) {
_gaq.push(["_trackPageview"])
}
if (window.ga) {
ga("send", "pageview", {page: location.pathname, title: document.title})
}
}
options.scrollTo = (typeof options.scrollTo === "undefined") ? 0 : options.scrollTo options.scrollTo = (typeof options.scrollTo === "undefined") ? 0 : options.scrollTo
options.scrollRestoration = (typeof options.scrollRestoration !== "undefined") ? options.scrollRestoration : true options.scrollRestoration = (typeof options.scrollRestoration !== "undefined") ? options.scrollRestoration : true
options.cacheBust = (typeof options.cacheBust === "undefined") ? true : options.cacheBust options.cacheBust = (typeof options.cacheBust === "undefined") ? true : options.cacheBust
@@ -38,3 +29,13 @@ module.exports = function(options) {
return options return options
} }
/* istanbul ignore next */
function defaultAnalytics() {
if (window._gaq) {
_gaq.push(["_trackPageview"])
}
if (window.ga) {
ga("send", "pageview", {page: location.pathname, title: document.title})
}
}

View File

@@ -1,9 +1,13 @@
var on = require("../events/on") var on = require("../events/on")
var clone = require("../util/clone") var clone = require("../util/clone")
var attrClick = "data-pjax-click-state" var attrState = "data-pjax-state"
var formAction = function(el, event) { var formAction = function(el, event) {
if (isDefaultPrevented(event)) {
return
}
// Since loadUrl modifies options and we may add our own modifications below, // Since loadUrl modifies options and we may add our own modifications below,
// clone it so the changes don't persist // clone it so the changes don't persist
var options = clone(this.options) var options = clone(this.options)
@@ -19,27 +23,9 @@ var formAction = function(el, event) {
var virtLinkElement = document.createElement("a") var virtLinkElement = document.createElement("a")
virtLinkElement.setAttribute("href", options.requestOptions.requestUrl) virtLinkElement.setAttribute("href", options.requestOptions.requestUrl)
// Ignore external links. var attrValue = checkIfShouldAbort(virtLinkElement, options)
if (virtLinkElement.protocol !== window.location.protocol || virtLinkElement.host !== window.location.host) { if (attrValue) {
el.setAttribute(attrClick, "external") el.setAttribute(attrState, attrValue)
return
}
// Ignore click if we are on an anchor on the same page
if (virtLinkElement.pathname === window.location.pathname && virtLinkElement.hash.length > 0) {
el.setAttribute(attrClick, "anchor-present")
return
}
// Ignore empty anchor "foo.html#"
if (virtLinkElement.href === window.location.href.split("#")[0] + "#") {
el.setAttribute(attrClick, "anchor-empty")
return
}
// if declared as a full reload, just normally submit the form
if (options.currentUrlFullReload) {
el.setAttribute(attrClick, "reload")
return return
} }
@@ -59,12 +45,34 @@ var formAction = function(el, event) {
} }
} }
el.setAttribute(attrClick, "submit") el.setAttribute(attrState, "submit")
options.triggerElement = el options.triggerElement = el
this.loadUrl(virtLinkElement.href, options) this.loadUrl(virtLinkElement.href, options)
} }
function checkIfShouldAbort(virtLinkElement, options) {
// Ignore external links.
if (virtLinkElement.protocol !== window.location.protocol || virtLinkElement.host !== window.location.host) {
return "external"
}
// Ignore click if we are on an anchor on the same page
if (virtLinkElement.hash && virtLinkElement.href.replace(virtLinkElement.hash, "") === window.location.href.replace(location.hash, "")) {
return "anchor"
}
// Ignore empty anchor "foo.html#"
if (virtLinkElement.href === window.location.href.split("#")[0] + "#") {
return "anchor-empty"
}
// if declared as a full reload, just normally submit the form
if (options.currentUrlFullReload && virtLinkElement.href === window.location.href.split("#")[0]) {
return "reload"
}
}
var isDefaultPrevented = function(event) { var isDefaultPrevented = function(event) {
return event.defaultPrevented || event.returnValue === false return event.defaultPrevented || event.returnValue === false
} }
@@ -72,19 +80,13 @@ var isDefaultPrevented = function(event) {
module.exports = function(el) { module.exports = function(el) {
var that = this var that = this
on(el, "submit", function(event) { el.setAttribute(attrState, "")
if (isDefaultPrevented(event)) {
return
}
on(el, "submit", function(event) {
formAction.call(that, el, event) formAction.call(that, el, event)
}) })
on(el, "keyup", function(event) { on(el, "keyup", function(event) {
if (isDefaultPrevented(event)) {
return
}
if (event.keyCode === 13) { if (event.keyCode === 13) {
formAction.call(that, el, event) formAction.call(that, el, event)
} }

View File

@@ -1,44 +1,20 @@
var on = require("../events/on") var on = require("../events/on")
var clone = require("../util/clone") var clone = require("../util/clone")
var attrClick = "data-pjax-click-state" var attrState = "data-pjax-state"
var attrKey = "data-pjax-keyup-state"
var linkAction = function(el, event) { var linkAction = function(el, event) {
if (isDefaultPrevented(event)) {
return
}
// Since loadUrl modifies options and we may add our own modifications below, // Since loadUrl modifies options and we may add our own modifications below,
// clone it so the changes don't persist // clone it so the changes don't persist
var options = clone(this.options) var options = clone(this.options)
// Dont break browser special behavior on links (like page in new window) var attrValue = checkIfShouldAbort(el, event)
if (event.which > 1 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) { if (attrValue) {
el.setAttribute(attrClick, "modifier") el.setAttribute(attrState, attrValue)
return
}
// we do test on href now to prevent unexpected behavior if for some reason
// user have href that can be dynamically updated
// Ignore external links.
if (el.protocol !== window.location.protocol || el.host !== window.location.host) {
el.setAttribute(attrClick, "external")
return
}
// Ignore click if we are on an anchor on the same page
if (el.pathname === window.location.pathname && el.hash.length > 0) {
el.setAttribute(attrClick, "anchor-present")
return
}
// Ignore anchors on the same page (keep native behavior)
if (el.hash && el.href.replace(el.hash, "") === window.location.href.replace(location.hash, "")) {
el.setAttribute(attrClick, "anchor")
return
}
// Ignore empty anchor "foo.html#"
if (el.href === window.location.href.split("#")[0] + "#") {
el.setAttribute(attrClick, "anchor-empty")
return return
} }
@@ -49,17 +25,42 @@ var linkAction = function(el, event) {
this.options.currentUrlFullReload && this.options.currentUrlFullReload &&
el.href === window.location.href.split("#")[0] el.href === window.location.href.split("#")[0]
) { ) {
el.setAttribute(attrClick, "reload") el.setAttribute(attrState, "reload")
this.reload() this.reload()
return return
} }
el.setAttribute(attrClick, "load") el.setAttribute(attrState, "load")
options.triggerElement = el options.triggerElement = el
this.loadUrl(el.href, options) this.loadUrl(el.href, options)
} }
function checkIfShouldAbort(el, event) {
// Dont break browser special behavior on links (like page in new window)
if (event.which > 1 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) {
return "modifier"
}
// we do test on href now to prevent unexpected behavior if for some reason
// user have href that can be dynamically updated
// Ignore external links.
if (el.protocol !== window.location.protocol || el.host !== window.location.host) {
return "external"
}
// Ignore anchors on the same page (keep native behavior)
if (el.hash && el.href.replace(el.hash, "") === window.location.href.replace(location.hash, "")) {
return "anchor"
}
// Ignore empty anchor "foo.html#"
if (el.href === window.location.href.split("#")[0] + "#") {
return "anchor-empty"
}
}
var isDefaultPrevented = function(event) { var isDefaultPrevented = function(event) {
return event.defaultPrevented || event.returnValue === false return event.defaultPrevented || event.returnValue === false
} }
@@ -67,25 +68,13 @@ var isDefaultPrevented = function(event) {
module.exports = function(el) { module.exports = function(el) {
var that = this var that = this
on(el, "click", function(event) { el.setAttribute(attrState, "")
if (isDefaultPrevented(event)) {
return
}
on(el, "click", function(event) {
linkAction.call(that, el, event) linkAction.call(that, el, event)
}) })
on(el, "keyup", function(event) { on(el, "keyup", function(event) {
if (isDefaultPrevented(event)) {
return
}
// Dont break browser special behavior on links (like page in new window)
if (event.which > 1 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) {
el.setAttribute(attrKey, "modifier")
return
}
if (event.keyCode === 13) { if (event.keyCode === 13) {
linkAction.call(that, el, event) linkAction.call(that, el, event)
} }

View File

@@ -1,15 +1,17 @@
var attrState = "data-pjax-state"
robinnorth commented 2018-04-09 15:06:00 -05:00 (Migrated from github.com)
Review

This, and line 12 could be abstracted to use var attrState = "data-pjax-state", as in attach-link

This, and line 12 could be abstracted to use `var attrState = "data-pjax-state"`, as in `attach-link`
BehindTheMath commented 2018-04-09 15:18:15 -05:00 (Migrated from github.com)
Review

True.

True.
module.exports = function(el) { module.exports = function(el) {
switch (el.tagName.toLowerCase()) { switch (el.tagName.toLowerCase()) {
case "a": case "a":
// only attach link if el does not already have link attached // only attach link if el does not already have link attached
if (!el.hasAttribute("data-pjax-click-state")) { if (!el.hasAttribute(attrState)) {
this.attachLink(el) this.attachLink(el)
} }
break break
case "form": case "form":
// only attach link if el does not already have link attached // only attach link if el does not already have link attached
if (!el.hasAttribute("data-pjax-click-state")) { if (!el.hasAttribute(attrState)) {
this.attachForm(el) this.attachForm(el)
} }
break break

View File

@@ -15,7 +15,7 @@ module.exports = function(location, options, callback) {
if (request.status === 200) { if (request.status === 200) {
callback(request.responseText, request, location) callback(request.responseText, request, location)
} }
else { else if (request.status !== 0) {
callback(null, request, location) callback(null, request, location)
} }
} }
@@ -65,7 +65,7 @@ module.exports = function(location, options, callback) {
// Send the proper header information for POST forms // Send the proper header information for POST forms
if (requestPayload && requestMethod === "POST") { if (requestPayload && requestMethod === "POST") {
request.setRequestHeader("Content-type", "application/x-www-form-urlencoded") request.setRequestHeader("Content-Type", "application/x-www-form-urlencoded")
} }
request.send(requestPayload) request.send(requestPayload)

View File

@@ -8,7 +8,14 @@ module.exports = {
innerHTML: function(oldEl, newEl) { innerHTML: function(oldEl, newEl) {
oldEl.innerHTML = newEl.innerHTML oldEl.innerHTML = newEl.innerHTML
oldEl.className = newEl.className
if (newEl.className === "") {
oldEl.removeAttribute("class")
}
else {
oldEl.className = newEl.className
}
this.onSwitch() this.onSwitch()
}, },

View File

@@ -1,4 +1,5 @@
module.exports = function(obj) { module.exports = function(obj) {
/* istanbul ignore if */
if (null === obj || "object" !== typeof obj) { if (null === obj || "object" !== typeof obj) {
return obj return obj
} }

View File

@@ -1,6 +1,6 @@
module.exports = function(target) { module.exports = function(target) {
if (target == null) { if (target == null) {
return target return null
} }
var to = Object(target) var to = Object(target)

View File

@@ -14,8 +14,8 @@ tape("test evalScript method", function(t) {
t.equal(document.body.className, "executed", "script has been properly executed") t.equal(document.body.className, "executed", "script has been properly executed")
script.innerHTML = "document.write('failure')" script.innerHTML = "document.write('failure')"
document.body.text = "document.write hasn't been executed" var bodyText = "document.write hasn't been executed"
var bodyText = document.body.text document.body.text = bodyText
evalScript(script) evalScript(script)
t.equal(document.body.text, bodyText, "document.write hasn't been executed") t.equal(document.body.text, bodyText, "document.write hasn't been executed")

View File

@@ -2,7 +2,7 @@ var tape = require("tape")
var executeScripts = require("../../lib/execute-scripts") var executeScripts = require("../../lib/execute-scripts")
tape("test executeScripts method", function(t) { tape("test executeScripts method when the script tag is inside a container", function(t) {
document.body.className = "" document.body.className = ""
var container = document.createElement("div") var container = document.createElement("div")
@@ -14,3 +14,16 @@ tape("test executeScripts method", function(t) {
t.end() t.end()
}) })
tape("test executeScripts method with just a script tag", function(t) {
document.body.className = ""
var script = document.createElement("script")
script.innerHTML = "document.body.className = 'executed correctly';"
t.equal(document.body.className, "", "script hasn't been executed yet")
executeScripts(script)
t.equal(document.body.className, "executed correctly", "script has been properly executed")
t.end()
})

View File

@@ -4,20 +4,18 @@ var on = require("../../../lib/events/on")
var trigger = require("../../../lib/events/trigger") var trigger = require("../../../lib/events/trigger")
var attachForm = require("../../../lib/proto/attach-form") var attachForm = require("../../../lib/proto/attach-form")
var form = document.createElement("form") var attr = "data-pjax-state"
var attr = "data-pjax-click-state"
var preventDefault = function(e) { e.preventDefault() }
tape("test attach form prototype method", function(t) { tape("test attach form prototype method", function(t) {
t.plan(7) var form = document.createElement("form")
var loadUrlCalled = false
attachForm.call({ attachForm.call({
options: {}, options: {
reload: function() { currentUrlFullReload: true
t.equal(form.getAttribute(attr), "reload", "triggering a simple reload will just submit the form")
}, },
loadUrl: function() { loadUrl: function() {
t.equal(form.getAttribute(attr), "submit", "triggering a post to the next page") loadUrlCalled = true
} }
}, form) }, form)
@@ -29,50 +27,57 @@ tape("test attach form prototype method", function(t) {
form.action = internalUri + "#anchor" form.action = internalUri + "#anchor"
trigger(form, "submit") trigger(form, "submit")
t.equal(form.getAttribute(attr), "anchor-present", "internal anchor stop behavior") t.equal(form.getAttribute(attr), "anchor", "internal anchor stop behavior")
window.location.hash = "#anchor" window.location.hash = "#anchor"
form.action = internalUri + "#another-anchor" form.action = internalUri + "#another-anchor"
trigger(form, "submit") trigger(form, "submit")
t.notEqual(form.getAttribute(attr), "anchor", "differents anchors stop behavior") t.equal(form.getAttribute(attr), "anchor", "different anchors stop behavior")
window.location.hash = "" window.location.hash = ""
form.action = internalUri + "#" form.action = internalUri + "#"
trigger(form, "submit") trigger(form, "submit")
t.equal(form.getAttribute(attr), "anchor-empty", "empty anchor stop behavior") t.equal(form.getAttribute(attr), "anchor-empty", "empty anchor stop behavior")
form.action = internalUri form.action = window.location.href
trigger(form, "submit") trigger(form, "submit")
// see reload defined above t.equal(form.getAttribute(attr), "reload", "submitting when currentUrlFullReload is true will submit normally, without XHR")
t.equal(loadUrlCalled, false, "loadUrl() not called")
form.action = window.location.protocol + "//" + window.location.host + "/internal" form.action = window.location.protocol + "//" + window.location.host + "/internal"
form.method = "POST" form.method = "POST"
trigger(form, "submit") trigger(form, "submit")
// see post defined above t.equal(form.getAttribute(attr), "submit", "triggering a POST request to the next page")
t.equal(loadUrlCalled, true, "loadUrl() called correctly")
loadUrlCalled = false
form.setAttribute(attr, "")
form.action = window.location.protocol + "//" + window.location.host + "/internal" form.action = window.location.protocol + "//" + window.location.host + "/internal"
form.method = "GET" form.method = "GET"
trigger(form, "submit") trigger(form, "submit")
// see post defined above t.equal(form.getAttribute(attr), "submit", "triggering a GET request to the next page")
t.equal(loadUrlCalled, true, "loadUrl() called correctly")
t.end() t.end()
}) })
tape("test attach form preventDefaulted events", function(t) { tape("test attach form preventDefaulted events", function(t) {
var callbacked = false var loadUrlCalled = false
var form = document.createElement("form") var form = document.createElement("form")
// This needs to be before the call to attachForm()
on(form, "submit", function(event) { event.preventDefault() })
attachForm.call({ attachForm.call({
options: {}, options: {},
loadUrl: function() { loadUrl: function() {
callbacked = true loadUrlCalled = true
} }
}, form) }, form)
form.action = "#" form.action = "#"
on(form, "submit", preventDefault)
trigger(form, "submit") trigger(form, "submit")
t.equal(callbacked, false, "events that are preventDefaulted should not fire callback") t.equal(loadUrlCalled, false, "events that are preventDefaulted should not fire callback")
t.end() t.end()
}) })
@@ -93,3 +98,57 @@ tape("test options are not modified by attachForm", function(t) {
t.end() t.end()
}) })
tape("test submit triggered by keyboard", function(t) {
var form = document.createElement("form")
var pjax = {
options: {},
loadUrl: function() {
t.equal(form.getAttribute(attr), "submit", "triggering a internal link actually submits the form")
}
}
t.plan(2)
attachForm.call(pjax, form)
form.action = window.location.protocol + "//" + window.location.host + "/internal"
trigger(form, "keyup", {keyCode: 14})
t.equal(form.getAttribute(attr), "", "keycode other than 13 doesn't trigger anything")
trigger(form, "keyup", {keyCode: 13})
// see loadUrl defined above
t.end()
})
tape("test form elements parsed correctly", function(t) {
t.plan(1)
var form = document.createElement("form")
var input = document.createElement("input")
input.name = "input"
input.value = "value"
form.appendChild(input)
var params = [{
name: "input",
value: "value"
}]
var pjax = {
options: {},
loadUrl: function(href, options) {
t.same(options.requestOptions.requestParams, params, "form elements parsed correctly")
}
}
attachForm.call(pjax, form)
form.action = window.location.protocol + "//" + window.location.host + "/internal"
trigger(form, "submit")
// see loadUrl defined above
t.end()
})

View File

@@ -4,27 +4,22 @@ var on = require("../../../lib/events/on")
var trigger = require("../../../lib/events/trigger") var trigger = require("../../../lib/events/trigger")
var attachLink = require("../../../lib/proto/attach-link") var attachLink = require("../../../lib/proto/attach-link")
var a = document.createElement("a") var attr = "data-pjax-state"
var attr = "data-pjax-click-state"
var preventDefault = function(e) { e.preventDefault() }
tape("test attach link prototype method", function(t) { tape("test attach link prototype method", function(t) {
t.plan(7) var a = document.createElement("a")
var loadUrlCalled = false
attachLink.call({ attachLink.call({
options: {}, options: {},
reload: function() {
t.equal(a.getAttribute(attr), "reload", "triggering exact same url reload the page")
},
loadUrl: function() { loadUrl: function() {
t.equal(a.getAttribute(attr), "load", "triggering a internal link actually load the page") loadUrlCalled = true
} }
}, a) }, a)
var internalUri = window.location.protocol + "//" + window.location.host + window.location.pathname + window.location.search var internalUri = window.location.protocol + "//" + window.location.host + window.location.pathname + window.location.search
a.href = internalUri a.href = internalUri
on(a, "click", preventDefault) // to avoid link to be open (break testing env)
trigger(a, "click", {metaKey: true}) trigger(a, "click", {metaKey: true})
t.equal(a.getAttribute(attr), "modifier", "event key modifier stop behavior") t.equal(a.getAttribute(attr), "modifier", "event key modifier stop behavior")
@@ -32,46 +27,47 @@ tape("test attach link prototype method", function(t) {
trigger(a, "click") trigger(a, "click")
t.equal(a.getAttribute(attr), "external", "external url stop behavior") t.equal(a.getAttribute(attr), "external", "external url stop behavior")
window.location.hash = "#anchor"
a.href = internalUri + "#anchor" a.href = internalUri + "#anchor"
trigger(a, "click") trigger(a, "click")
t.equal(a.getAttribute(attr), "anchor-present", "internal anchor stop behavior") t.equal(a.getAttribute(attr), "anchor", "internal anchor stop behavior")
window.location.hash = "#anchor"
a.href = internalUri + "#another-anchor" a.href = internalUri + "#another-anchor"
trigger(a, "click") trigger(a, "click")
t.notEqual(a.getAttribute(attr), "anchor", "differents anchors stop behavior") t.equal(a.getAttribute(attr), "anchor", "different anchors stop behavior")
window.location.hash = "" window.location.hash = ""
a.href = internalUri + "#" a.href = internalUri + "#"
trigger(a, "click") trigger(a, "click")
t.equal(a.getAttribute(attr), "anchor-empty", "empty anchor stop behavior") t.equal(a.getAttribute(attr), "anchor-empty", "empty anchor stop behavior")
a.href = internalUri
trigger(a, "click")
// see reload defined above
a.href = window.location.protocol + "//" + window.location.host + "/internal" a.href = window.location.protocol + "//" + window.location.host + "/internal"
trigger(a, "click") trigger(a, "click")
// see loadUrl defined above t.equals(a.getAttribute(attr), "load", "triggering an internal link sets the state attribute to 'load'")
t.equals(loadUrlCalled, true, "triggering an internal link actually loads the page")
t.end() t.end()
}) })
tape("test attach link preventDefaulted events", function(t) { tape("test attach link preventDefaulted events", function(t) {
var callbacked = false var loadUrlCalled = false
var a = document.createElement("a") var a = document.createElement("a")
// This needs to be before the call to attachLink()
on(a, "click", function(event) {
event.preventDefault()
})
attachLink.call({ attachLink.call({
options: {}, options: {},
loadUrl: function() { loadUrl: function() {
callbacked = true loadUrlCalled = true
} }
}, a) }, a)
a.href = "#" a.href = "#"
on(a, "click", preventDefault)
trigger(a, "click") trigger(a, "click")
t.equal(callbacked, false, "events that are preventDefaulted should not fire callback") t.equal(loadUrlCalled, false, "events that are preventDefaulted should not fire callback")
t.end() t.end()
}) })
@@ -92,3 +88,56 @@ tape("test options are not modified by attachLink", function(t) {
t.end() t.end()
}) })
tape("test link triggered by keyboard", function(t) {
var a = document.createElement("a")
var pjax = {
options: {},
loadUrl: function() {
t.equal(a.getAttribute(attr), "load", "triggering a internal link actually loads the page")
}
}
t.plan(3)
attachLink.call(pjax, a)
a.href = window.location.protocol + "//" + window.location.host + "/internal"
trigger(a, "keyup", {keyCode: 14})
t.equal(a.getAttribute(attr), "", "keycode other than 13 doesn't trigger anything")
trigger(a, "keyup", {keyCode: 13, metaKey: true})
t.equal(a.getAttribute(attr), "modifier", "event key modifier stop behavior")
trigger(a, "keyup", {keyCode: 13})
// see loadUrl defined above
t.end()
})
tape("test link with the same URL as the current one, when currentUrlFullReload set to true", function(t) {
var a = document.createElement("a")
var pjax = {
options: {
currentUrlFullReload: true
},
reload: function() {
t.pass("this.reload() was called correctly")
},
loadUrl: function() {
t.fail("loadUrl() was called wrongly")
}
}
t.plan(2)
attachLink.call(pjax, a)
a.href = window.location.href
trigger(a, "click")
t.equal(a.getAttribute(attr), "reload", "reload stop behavior")
t.end()
})

View File

@@ -1,7 +1,8 @@
var tape = require("tape") var tape = require("tape")
var parseElement = require("../../../lib/proto/parse-element") var parseElement = require("../../../lib/proto/parse-element")
var protoMock = {
var pjax = {
attachLink: function() { return true }, attachLink: function() { return true },
attachForm: function() { return true } attachForm: function() { return true }
} }
@@ -9,13 +10,18 @@ var protoMock = {
tape("test parse element prototype method", function(t) { tape("test parse element prototype method", function(t) {
t.doesNotThrow(function() { t.doesNotThrow(function() {
var a = document.createElement("a") var a = document.createElement("a")
parseElement.call(protoMock, a) parseElement.call(pjax, a)
}, "<a> element can be parsed") }, "<a> element can be parsed")
t.doesNotThrow(function() { t.doesNotThrow(function() {
var form = document.createElement("form") var form = document.createElement("form")
parseElement.call(protoMock, form) parseElement.call(pjax, form)
}, "<form> element can be parsed") }, "<form> element can be parsed")
t.throws(function() {
var el = document.createElement("div")
parseElement.call(pjax, el)
}, "<div> element cannot be parsed")
t.end() t.end()
}) })

View File

@@ -57,3 +57,81 @@ tape("request headers are sent properly", function(t) {
}) })
}) })
tape("HTTP status codes other than 200 are handled properly", function(t) {
var url = "https://httpbin.org/status/400"
sendRequest(url, {}, function(responseText, request) {
t.equals(responseText, null, "responseText is null")
t.equals(request.status, 400, "HTTP status code is correct")
t.end()
})
})
tape.skip("XHR error is handled properly", function(t) {
var url = "https://encrypted.google.com/foobar"
sendRequest(url, {}, function(responseText) {
t.equals(responseText, null, "responseText is null")
t.end()
})
})
tape("POST body data is sent properly", function(t) {
var url = "https://httpbin.org/post"
var params = [{
name: "test",
value: "1"
}];
var options = {
requestOptions: {
requestMethod: "POST",
requestParams: params
}
}
sendRequest(url, options, function(responseText) {
var response = JSON.parse(responseText)
t.same(response.form[params[0].name], params[0].value, "requestParams were sent properly")
t.equals(response.headers["Content-Type"], "application/x-www-form-urlencoded", "Content-Type header was set properly")
t.end()
})
})
tape("GET query data is sent properly", function(t) {
var url = "https://httpbin.org/get"
var params = [{
name: "test",
value: "1"
}];
var options = {
requestOptions: {
requestParams: params
}
}
sendRequest(url, options, function(responseText) {
var response = JSON.parse(responseText)
t.same(response.args[params[0].name], params[0].value, "requestParams were sent properly")
t.end()
})
})
tape("XHR timeout is handled properly", function(t) {
var url = "https://httpbin.org/delay/5"
var options = {
timeout: 1000
}
sendRequest(url, options, function(responseText) {
t.equals(responseText, null, "responseText is null")
t.end()
})
})

View File

@@ -1,18 +1,20 @@
var tape = require("tape") var tape = require("tape")
var switchesSelectors = require("../../lib/switches-selectors.js") var switchesSelectors = require("../../lib/switches-selectors.js")
var noop = require("../../lib/util/noop")
var pjax = {
onSwitch: function() {
console.log("Switched")
},
state: {},
log: noop
}
// @author darylteo // @author darylteo
tape("test switchesSelectors", function(t) { tape("test switchesSelectors", function(t) {
// switchesSelectors relies on a higher level function callback // switchesSelectors relies on a higher level function callback
// should really be passed in instead so I'll leave it here as a TODO: // should really be passed in instead so I'll leave it here as a TODO:
var pjax = {
onSwitch: function() {
console.log("Switched")
},
state: {}
}
var tmpEl = document.implementation.createHTMLDocument() var tmpEl = document.implementation.createHTMLDocument()
// a div container is used because swapping the containers // a div container is used because swapping the containers
@@ -40,3 +42,33 @@ tape("test switchesSelectors", function(t) {
t.end() t.end()
}) })
tape("test switchesSelectors when number of elements don't match", function(t) {
var newTempDoc = document.implementation.createHTMLDocument()
var originalTempDoc = document.implementation.createHTMLDocument()
// a div container is used because swapping the containers
// will generate a new element, so things get weird
// using "body" generates a lot of testling cruft that I don't
// want so let's avoid that
var container = originalTempDoc.createElement("div")
container.innerHTML = "<p>Original text</p><span>No change</span>"
originalTempDoc.body.appendChild(container)
var container2 = newTempDoc.createElement("div")
container2.innerHTML = "<p>New text</p><p>More new text</p><span>New span</span>"
newTempDoc.body.appendChild(container2)
var switchSelectorsFn = switchesSelectors.bind(pjax,
{}, // switches
{}, // switchesOptions
["p"], // selectors,
newTempDoc, // fromEl
originalTempDoc, // toEl,
{} // options
)
t.throws(switchSelectorsFn, null, "error was thrown properly since number of elements don't match")
t.end()
})

View File

@@ -2,6 +2,60 @@ var tape = require("tape")
var switches = require("../../lib/switches") var switches = require("../../lib/switches")
var noop = require("../../lib/util/noop") var noop = require("../../lib/util/noop")
tape("test outerHTML switch", function(t) {
var outerHTML = switches.outerHTML
var doc = document.implementation.createHTMLDocument()
var container = doc.createElement("div")
container.innerHTML = "<p id='p'>Original Text</p>"
doc.body.appendChild(container)
var p = doc.createElement("p")
p.innerHTML = "New Text"
outerHTML.bind({
onSwitch: noop
})(doc.querySelector("p"), p)
t.equals(doc.querySelector("p").innerHTML, "New Text", "Elements correctly switched")
t.notEquals(doc.querySelector("p").id, "p", "other attributes overwritten correctly")
t.end()
})
tape("test innerHTML switch", function(t) {
var innerHTML = switches.innerHTML
var doc = document.implementation.createHTMLDocument()
var container = doc.createElement("div")
container.innerHTML = "<p id='p'>Original Text</p>"
doc.body.appendChild(container)
var p = doc.createElement("p")
p.innerHTML = "New Text"
p.className = "p"
innerHTML.bind({
onSwitch: noop
})(doc.querySelector("p"), p)
t.equals(doc.querySelector("p").innerHTML, "New Text", "Elements correctly switched")
t.equals(doc.querySelector("p").className, "p", "classname set correctly")
t.equals(doc.querySelector("p").id, "p", "other attributes set correctly")
p.removeAttribute("class")
innerHTML.bind({
onSwitch: noop
})(doc.querySelector("p"), p)
t.equals(doc.querySelector("p").className, "", "classname set correctly")
t.end()
})
tape("test replaceNode switch", function(t) { tape("test replaceNode switch", function(t) {
var replaceNode = switches.replaceNode var replaceNode = switches.replaceNode

View File

@@ -4,13 +4,14 @@ var extend = require("../../../lib/util/extend")
tape("test extend method", function(t) { tape("test extend method", function(t) {
var obj = {one: 1, two: 2} var obj = {one: 1, two: 2}
var extended = extend({}, obj, {two: "two", three: 3}) var extended = extend({}, obj, {two: "two", three: 3})
t.notEqual(obj, extended, "extended object isn't the original object") t.notEqual(obj, extended, "extended object isn't the original object")
t.notSame(obj, extended, "extended object doesn't have the same values as original object") t.notSame(obj, extended, "extended object doesn't have the same values as original object")
t.notSame(obj.two, extended.two, "extended object value overwrites value from original object") t.notSame(obj.two, extended.two, "extended object value overwrites value from original object")
extended = extend(null)
t.equals(extended, null, "passing null returns null")
t.end() t.end()
}) })

9
tests/lib/util/noop.js Normal file
View File

@@ -0,0 +1,9 @@
var tape = require("tape")
var noop = require("../../../lib/util/noop")
tape("test noop function", function(t) {
t.equal(typeof noop, "function", "noop is a function")
t.equal(noop(), undefined, "noop() returns nothing")
t.end()
})