initial commit (work in progress)

This commit is contained in:
Leo Horie 2016-04-20 20:02:37 -04:00
parent 13fdb60f66
commit 559369016d
83 changed files with 10461 additions and 0 deletions

12
test-utils/README.md Normal file
View file

@ -0,0 +1,12 @@
# Test utils
Utilities for testing Mithril
Version: 1.0
License: MIT
## About
- pushStateMock - mock for `history.pushState` and `location`
- ajaxMock - mock for XMLHttpRequest and JSONP transporters
- parseURL - helper function for URL parsing

77
test-utils/ajaxMock.js Normal file
View file

@ -0,0 +1,77 @@
"use strict"
var callAsync = require("../test-utils/callAsync")
var parseURL = require("../test-utils/parseURL")
var parseQueryString = require("../querystring/parse")
module.exports = function() {
var routes = {}
var callback = "callback"
var serverErrorHandler = function() {
return {status: 500, responseText: "server error"}
}
var $window = {
XMLHttpRequest: function XMLHttpRequest() {
var args = {}
this.setRequestHeader = function(header, value) {}
this.open = function(method, url, async, user, password) {
var urlData = parseURL(url, {protocol: "http:", hostname: "localhost", port: "", pathname: "/"})
args.method = method
args.pathname = urlData.pathname
args.search = urlData.search
args.async = async != null ? async : true
args.user = user
args.password = password
}
this.send = function(body) {
var self = this
var handler = routes[args.method + " " + args.pathname] || serverErrorHandler
var data = handler({url: args.pathname, query: args.search || {}, body: body || null})
self.readyState = 4
self.status = data.status
self.responseText = data.responseText
if (args.async === true) {
var s = new Date
callAsync(function() {
if (typeof self.onreadystatechange === "function") self.onreadystatechange()
})
}
}
},
document: {
createElement: function(tag) {
return {nodeName: tag.toUpperCase(), parentNode: null}
},
documentElement: {
appendChild: function(element) {
element.parentNode = this
if (element.nodeName === "SCRIPT") {
var urlData = parseURL(element.src, {protocol: "http:", hostname: "localhost", port: "", pathname: "/"})
var handler = routes["GET " + urlData.pathname] || serverErrorHandler
var data = handler({url: urlData.pathname, query: urlData.search, body: null})
var query = parseQueryString(urlData.search)
callAsync(function() {
if (data.status === 200) {
new Function("$window", "with ($window) return " + data.responseText).call($window, $window)
}
else if (typeof element.onerror === "function") {
element.onerror({type: "error"})
}
})
}
},
removeChild: function(element) {
element.parentNode = null
},
},
},
$defineRoutes: function(rules) {
routes = rules
},
$defineJSONPCallbackKey: function(key) {
callback = key
},
}
return $window
}

3
test-utils/callAsync.js Normal file
View file

@ -0,0 +1,3 @@
"use strict"
module.exports = typeof process === "object" ? process.nextTick : window.setImmediate || window.setTimeout

219
test-utils/domMock.js Normal file
View file

@ -0,0 +1,219 @@
"use strict"
module.exports = function() {
function appendChild(child) {
var ancestor = this
while (ancestor !== child && ancestor !== null) ancestor = ancestor.parentNode
if (ancestor === child) throw new Error("Node cannot be inserted at the specified point in the hierarchy")
if (child.nodeType == null) throw new Error("Argument is not a DOM element")
var index = this.childNodes.indexOf(child)
if (index > -1) this.childNodes.splice(index, 1)
if (child.nodeType === 11) {
while (child.firstChild != null) this.appendChild(child.firstChild)
child.childNodes = []
}
else {
this.childNodes.push(child)
if (child.parentNode != null && child.parentNode !== this) child.parentNode.removeChild(child)
child.parentNode = this
}
}
function removeChild(child) {
var index = this.childNodes.indexOf(child)
if (index > -1) {
this.childNodes.splice(index, 1)
child.parentNode = null
}
else throw new TypeError("Failed to execute 'removeChild'")
}
function insertBefore(child, reference) {
var ancestor = this
while (ancestor !== child && ancestor !== null) ancestor = ancestor.parentNode
if (ancestor === child) throw new Error("Node cannot be inserted at the specified point in the hierarchy")
if (child.nodeType == null) throw new Error("Argument is not a DOM element")
var refIndex = this.childNodes.indexOf(reference)
var index = this.childNodes.indexOf(child)
if (reference !== null && refIndex < 0) throw new TypeError("Invalid argument")
if (index > -1) this.childNodes.splice(index, 1)
if (reference === null) this.appendChild(child)
else {
if (child.nodeType === 11) {
this.childNodes.splice.apply(this.childNodes, [refIndex, 0].concat(child.childNodes))
while (child.firstChild) {
var subchild = child.firstChild
child.removeChild(subchild)
subchild.parentNode = this
}
child.childNodes = []
}
else {
this.childNodes.splice(refIndex, 0, child)
if (child.parentNode != null && child.parentNode !== this) child.parentNode.removeChild(child)
child.parentNode = this
}
}
}
function setAttribute(name, value) {
var nodeValue = String(value)
this.attributes[name] = {
namespaceURI: null,
get nodeValue() {return nodeValue},
set nodeValue(value) {nodeValue = String(value)},
}
}
function setAttributeNS(ns, name, value) {
this.setAttribute(name, value)
this.attributes[name].namespaceURI = ns
}
function removeAttribute(name) {
delete this.attributes[name]
}
var activeElement
var $window = {
document: {
createElement: function(tag, is) {
var style = {}
var element = {
nodeType: 1,
nodeName: tag.toUpperCase(),
namespaceURI: "http://www.w3.org/1999/xhtml",
appendChild: appendChild,
removeChild: removeChild,
insertBefore: insertBefore,
setAttribute: setAttribute,
setAttributeNS: setAttributeNS,
removeAttribute: removeAttribute,
parentNode: null,
childNodes: [],
attributes: {},
get firstChild() {
return this.childNodes[0] || null
},
get nextSibling() {
if (this.parentNode == null) return null
var index = this.parentNode.childNodes.indexOf(this)
if (index < 0) throw new TypeError("Parent's childNodes is out of sync")
return this.parentNode.childNodes[index + 1] || null
},
set textContent(value) {
this.childNodes = []
if (value !== "") this.appendChild($window.document.createTextNode(value))
},
set innerHTML(value) {
while (this.firstChild) this.removeChild(this.firstChild)
var stack = [this], depth = 0, voidElements = ["area", "base", "br", "col", "command", "embed", "hr", "img", "input", "keygen", "link", "meta", "param", "source", "track", "wbr"]
value.replace(/<([a-z0-9\-]+?)((?:\s+?[^=]+?=(?:"[^"]*?"|'[^']*?'|[^\s>]*))*?)(\s*\/)?>|<\/([a-z0-9\-]+?)>|([^<]+)/g, function(match, startTag, attrs, selfClosed, endTag, text) {
if (startTag) {
var element = $window.document.createElement(startTag)
attrs.replace(/\s+?([^=]+?)=(?:"([^"]*?)"|'([^']*?)'|([^\s>]*))/g, function(match, key, doubleQuoted, singleQuoted, unquoted) {
var keyParts = key.split(":")
var name = keyParts.pop()
var ns = keyParts[0]
var value = doubleQuoted || singleQuoted || unquoted || ""
if (ns != null) element.setAttributeNS(ns, name, value)
else element.setAttribute(name, value)
})
stack[depth].appendChild(element)
if (!selfClosed && voidElements.indexOf(startTag.toLowerCase()) < 0) stack[++depth] = element
}
else if (endTag) {
depth--
}
else if (text) {
stack[depth].appendChild($window.document.createTextNode(text)) // FIXME handle html entities
}
})
},
get style() {
return style
},
set style(value) {
if (typeof value === "string") {
for (var key in style) style[key] = ""
var rules = value.split(";")
for (var i = 0; i < rules.length; i++) {
var rule = rules[i]
var colonIndex = rule.indexOf(":")
if (colonIndex > -1) {
var key = rule.slice(0, colonIndex).trim().replace(/-\D/g, function(match) {return match[1].toUpperCase()})
var value = rule.slice(colonIndex + 1).trim()
style[key] = value
}
}
}
},
focus: function() {activeElement = this},
dispatchEvent: function(e) {
e.target = this
if (typeof this["on" + e.type] === "function") this["on" + e.type](e)
},
}
if (element.nodeName === "A") {
var href
Object.defineProperty(element, "href", {
get: function() {return this.attributes["href"] === undefined ? "" : "[FIXME implement]"},
set: function(value) {this.setAttribute("href", value)},
enumerable: true,
})
}
if (element.nodeName === "INPUT") {
var checked
Object.defineProperty(element, "checked", {
get: function() {return checked === undefined ? this.attributes["checked"] !== undefined : checked},
set: function(value) {checked = Boolean(value)},
enumerable: true,
})
}
return element
},
createElementNS: function(ns, tag, is) {
var element = this.createElement(tag, is)
element.nodeName = tag
element.namespaceURI = ns
return element
},
createTextNode: function(text) {
var nodeValue = String(text)
return {
nodeType: 3,
nodeName: "#text",
parentNode: null,
get nodeValue() {return nodeValue},
set nodeValue(value) {nodeValue = String(value)},
}
},
createDocumentFragment: function() {
return {
nodeType: 11,
nodeName: "#document-fragment",
appendChild: appendChild,
insertBefore: insertBefore,
removeChild: removeChild,
parentNode: null,
childNodes: [],
get firstChild() {
return this.childNodes[0] || null
},
}
},
createEvent: function() {
return {
initEvent: function(type) {this.type = type},
}
},
get activeElement() {return activeElement},
},
}
$window.document.documentElement = $window.document.createElement("html")
$window.document.documentElement.appendChild($window.document.createElement("head"))
$window.document.body = $window.document.createElement("body")
$window.document.documentElement.appendChild($window.document.body)
activeElement = $window.document.body
return $window
}

47
test-utils/parseURL.js Normal file
View file

@ -0,0 +1,47 @@
"use strict"
module.exports = function parseURL(url, root) {
var data = {}
var protocolIndex = url.indexOf("://")
var pathnameIndex = protocolIndex > - 1 ? url.indexOf("/", protocolIndex + 3) : url.indexOf("/")
var searchIndex = url.indexOf("?")
var hashIndex = url.indexOf("#")
if ((pathnameIndex > searchIndex && searchIndex > -1) || (pathnameIndex > hashIndex && hashIndex > -1)) pathnameIndex = -1
if (searchIndex > hashIndex && hashIndex > -1) searchIndex = -1
var pathnameEnd = searchIndex > -1 ? searchIndex : hashIndex > -1 ? hashIndex : url.length
if (protocolIndex > -1) {
//it's a full URL
if (pathnameIndex < 0) pathnameIndex = url.length
var portIndex = url.indexOf(":", protocolIndex + 1)
if (portIndex < 0) portIndex = pathnameIndex
data.protocol = url.slice(0, protocolIndex + 1)
data.hostname = url.slice(protocolIndex + 3, portIndex)
data.port = url.slice(portIndex + 1, pathnameIndex)
data.pathname = url.slice(pathnameIndex, pathnameEnd) || "/"
}
else {
data.protocol = root.protocol
data.hostname = root.hostname
data.port = root.port
if (pathnameIndex === 0) {
//it's an absolute path
data.pathname = url.slice(pathnameIndex, pathnameEnd) || "/"
}
else if (searchIndex !== 0 && hashIndex !== 0) {
//it's a relative path
var slashIndex = root.pathname.lastIndexOf("/")
var path = slashIndex > -1 ? root.pathname.slice(0, slashIndex + 1) : "./"
var normalized = url.slice(0, pathnameEnd).replace(/^\.$/, root.pathname.slice(slashIndex + 1)).replace(/^\.\//, "")
var dotdot = /\/[^\/]+?\/\.{2}/g
var pathname = path + normalized
pathname = path + normalized
while (dotdot.test(pathname)) pathname = pathname.replace(dotdot, "")
pathname = pathname.replace(/\/\.\//g, "/").replace(/^(\/\.{2})+/, "") || "/"
data.pathname = pathname
}
}
var searchEnd = hashIndex > -1 ? hashIndex : url.length
data.search = searchIndex > -1 ? url.slice(searchIndex, searchEnd) : ""
data.hash = hashIndex > -1 ? url.slice(hashIndex) : ""
return data
}

169
test-utils/pushStateMock.js Normal file
View file

@ -0,0 +1,169 @@
"use strict"
var parseURL = require("../test-utils/parseURL")
module.exports = function() {
var protocol = "http:"
var hostname = "localhost"
var port = ""
var pathname = "/"
var search = ""
var hash = ""
var past = [], future = []
function getURL() {
if (protocol === "file:") return protocol + "//" + pathname + search + hash
return protocol + "//" + hostname + prefix(":", port) + pathname + search + hash
}
function setURL(value) {
var data = parseURL(value, {protocol: protocol, hostname: hostname, port: port, pathname: pathname})
var isNew = false
if (data.protocol != null && data.protocol !== protocol) protocol = data.protocol, isNew = true
if (data.hostname != null && data.hostname !== hostname) hostname = data.hostname, isNew = true
if (data.port != null && data.port !== port) port = data.port, isNew = true
if (data.pathname != null && data.pathname !== pathname) pathname = data.pathname, isNew = true
if (data.search != null && data.search !== search) search = data.search, isNew = true
if (data.hash != null && data.hash !== hash) {
hash = data.hash
if (!isNew) hashchange()
}
return isNew
}
function prefix(prefix, value) {
if (value === "") return ""
return (value.charAt(0) !== prefix ? prefix : "") + value
}
function hashchange() {
if (typeof $window.onhashchange === "function") $window.onhashchange({type: "hashchange"})
}
function popstate() {
if (typeof $window.onpopstate === "function") $window.onpopstate({type: "popstate"})
}
function unload() {
if (typeof $window.onunload === "function") $window.onunload({type: "unload"})
}
var $window = {
location: {
get protocol() {
return protocol
},
get hostname() {
return hostname
},
get port() {
return port
},
get pathname() {
return pathname
},
get search() {
return search
},
get hash() {
return hash
},
get origin() {
if (protocol === "file:") return "null"
return protocol + "//" + hostname + prefix(":", port)
},
get host() {
if (protocol === "file:") return ""
return hostname + prefix(":", port)
},
get href() {
return getURL()
},
set protocol(value) {
throw new Error("Protocol is read-only")
},
set hostname(value) {
unload()
past.push({url: getURL(), isNew: true})
future = []
hostname = value
},
set port(value) {
if (protocol === "file:") throw new Error("Port is read-only under `file://` protocol")
unload()
past.push({url: getURL(), isNew: true})
future = []
port = value
},
set pathname(value) {
if (protocol === "file:") throw new Error("Pathname is read-only under `file://` protocol")
unload()
past.push({url: getURL(), isNew: true})
future = []
pathname = prefix("/", value)
},
set search(value) {
unload()
past.push({url: getURL(), isNew: true})
future = []
search = prefix("?", value)
},
set hash(value) {
var oldHash = hash
past.push({url: getURL(), isNew: false})
future = []
hash = prefix("#", value)
if (oldHash != hash) hashchange()
},
set origin(value) {
console.warn("Origin is writable but ignored")
},
set host(value) {
console.warn("Host is writable but ignored in Chrome")
},
set href(value) {
var url = getURL()
var isNew = setURL(value)
if (isNew) {
setURL(url)
unload()
setURL(value)
}
past.push({url: url, isNew: isNew})
future = []
},
},
history: {
pushState: function(data, title, url) {
past.push({url: getURL(), isNew: false})
future = []
setURL(url)
},
replaceState: function(data, title, url) {
future = []
setURL(url)
},
back: function() {
var entry = past.pop()
if (entry != null) {
if (entry.isNew) unload()
future.push({url: getURL(), isNew: false})
setURL(entry.url)
if (!entry.isNew) popstate()
}
},
forward: function() {
var entry = future.pop()
if (entry != null) {
if (entry.isNew) unload()
past.push({url: getURL(), isNew: false})
setURL(entry.url)
if (!entry.isNew) popstate()
}
},
},
scrollTo: function(x, y) {},
onpopstate: null,
onhashchange: null,
onunload: null,
}
return $window
}

View file

@ -0,0 +1,23 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<script src="../../module/module.js"></script>
<script src="../../ospec/ospec.js"></script>
<script src="../../querystring/parse.js"></script>
<script src="../../test-utils/callAsync.js"></script>
<script src="../../test-utils/parseURL.js"></script>
<script src="../../test-utils/pushStateMock.js"></script>
<script src="../../test-utils/ajaxMock.js"></script>
<script src="../../test-utils/domMock.js"></script>
<script src="test-parseURL.js"></script>
<script src="test-pushStateMock.js"></script>
<script src="test-ajaxMock.js"></script>
<script src="test-domMock.js"></script>
<script>require("../../ospec/ospec").run()</script>
</body>
</html>

View file

@ -0,0 +1,147 @@
"use strict"
var o = require("../../ospec/ospec")
var ajaxMock = require("../../test-utils/ajaxMock")
var parseQueryString = require("../../querystring/parse")
o.spec("ajaxMock", function() {
var $window, ajax
o.beforeEach(function() {
$window = ajaxMock()
})
o.spec("xhr", function() {
o("works", function(done, timeout) {
$window.$defineRoutes({
"GET /item": function(request) {
o(request.url).equals("/item")
return {status: 200, responseText: "test"}
}
})
var xhr = new $window.XMLHttpRequest()
xhr.open("GET", "/item")
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
o(xhr.status).equals(200)
o(xhr.responseText).equals("test")
done()
}
}
xhr.send()
})
o("works w/ search", function(done, timeout) {
$window.$defineRoutes({
"GET /item": function(request) {
o(request.query).equals("?a=b")
return {status: 200, responseText: "test"}
}
})
var xhr = new $window.XMLHttpRequest()
xhr.open("GET", "/item?a=b")
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
done()
}
}
xhr.send()
})
o("works w/ body", function(done, timeout) {
$window.$defineRoutes({
"POST /item": function(request) {
o(request.body).equals("a=b")
return {status: 200, responseText: "test"}
}
})
var xhr = new $window.XMLHttpRequest()
xhr.open("POST", "/item")
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
done()
}
}
xhr.send("a=b")
})
o("handles routing error", function(done, timeout) {
var xhr = new $window.XMLHttpRequest()
xhr.open("GET", "/nonexistent")
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
o(xhr.status).equals(500)
done()
}
}
xhr.send("a=b")
})
})
o.spec("jsonp", function() {
o("works", function(done) {
$window.$defineRoutes({
"GET /test": function(request) {
var queryData = parseQueryString(request.query)
return {status: 200, responseText: queryData["callback"] + "(" + JSON.stringify({a: 1}) + ")"}
}
})
$window["cb"] = finish
var script = $window.document.createElement("script")
script.src = "/test?callback=cb"
$window.document.documentElement.appendChild(script)
function finish(data) {
o(data).deepEquals({a: 1})
done()
}
})
o("works w/ custom callback key", function(done) {
$window.$defineRoutes({
"GET /test": function(request) {
var queryData = parseQueryString(request.query)
return {status: 200, responseText: queryData["cb"] + "(" + JSON.stringify({a: 2}) + ")"}
}
})
$window.$defineJSONPCallbackKey("cb")
$window["customcb"] = finish2
var script = $window.document.createElement("script")
script.src = "/test?cb=customcb"
$window.document.documentElement.appendChild(script)
function finish2(data) {
o(data).deepEquals({a: 2})
done()
}
})
o("works with other querystring params", function(done, timeout) {
$window.$defineRoutes({
"GET /test": function(request) {
var queryData = parseQueryString(request.query)
return {status: 200, responseText: queryData["callback"] + "(" + JSON.stringify({a: 3}) + ")"}
}
})
$window["cbwithinparams"] = finish
var script = $window.document.createElement("script")
script.src = "/test?a=b&callback=cbwithinparams&c=d"
$window.document.documentElement.appendChild(script)
function finish(data) {
o(data).deepEquals({a: 3})
done()
}
})
o("handles error", function(done) {
var script = $window.document.createElement("script")
script.onerror = finish
script.src = "/test?cb=nonexistent"
$window.document.documentElement.appendChild(script)
function finish(e) {
o(e.type).equals("error")
done()
}
})
})
})

View file

@ -0,0 +1,580 @@
"use strict"
var o = require("../../ospec/ospec")
var domMock = require("../../test-utils/domMock")
o.spec("domMock", function() {
var $document
o.beforeEach(function() {
$document = domMock().document
//$document = document //TODO clean up
})
o.spec("createElement", function() {
o("works", function() {
var node = $document.createElement("div")
o(node.nodeType).equals(1)
o(node.nodeName).equals("DIV")
o(node.namespaceURI).equals("http://www.w3.org/1999/xhtml")
o(node.parentNode).equals(null)
o(node.childNodes.length).equals(0)
o(node.firstChild).equals(null)
o(node.nextSibling).equals(null)
})
})
o.spec("createElementNS", function() {
o("works", function() {
var node = $document.createElementNS("http://www.w3.org/2000/svg", "svg")
o(node.nodeType).equals(1)
o(node.nodeName).equals("svg")
o(node.namespaceURI).equals("http://www.w3.org/2000/svg")
o(node.parentNode).equals(null)
o(node.childNodes.length).equals(0)
o(node.firstChild).equals(null)
o(node.nextSibling).equals(null)
})
})
o.spec("createTextNode", function() {
o("works", function() {
var node = $document.createTextNode("abc")
o(node.nodeType).equals(3)
o(node.nodeName).equals("#text")
o(node.parentNode).equals(null)
o(node.nodeValue).equals("abc")
})
o("works w/ number", function() {
var node = $document.createTextNode(123)
o(node.nodeValue).equals("123")
})
o("works w/ null", function() {
var node = $document.createTextNode(null)
o(node.nodeValue).equals("null")
})
o("works w/ undefined", function() {
var node = $document.createTextNode(undefined)
o(node.nodeValue).equals("undefined")
})
o("works w/ object", function() {
var node = $document.createTextNode({})
o(node.nodeValue).equals("[object Object]")
})
o("does not unescape HTML", function() {
var node = $document.createTextNode("<a>&amp;</a>")
o(node.nodeValue).equals("<a>&amp;</a>")
})
o("nodeValue casts to string", function() {
var node = $document.createTextNode("a")
node.nodeValue = true
o(node.nodeValue).equals("true")
})
})
o.spec("createDocumentFragment", function() {
o("works", function() {
var node = $document.createDocumentFragment()
o(node.nodeType).equals(11)
o(node.nodeName).equals("#document-fragment")
o(node.parentNode).equals(null)
o(node.childNodes.length).equals(0)
o(node.firstChild).equals(null)
})
})
o.spec("appendChild", function() {
o("works", function() {
var parent = $document.createElement("div")
var child = $document.createElement("a")
parent.appendChild(child)
o(parent.childNodes.length).equals(1)
o(parent.childNodes[0]).equals(child)
o(parent.firstChild).equals(child)
o(child.parentNode).equals(parent)
})
o("moves existing", function() {
var parent = $document.createElement("div")
var a = $document.createElement("a")
var b = $document.createElement("b")
parent.appendChild(a)
parent.appendChild(b)
parent.appendChild(a)
o(parent.childNodes.length).equals(2)
o(parent.childNodes[0]).equals(b)
o(parent.childNodes[1]).equals(a)
o(parent.firstChild).equals(b)
o(parent.firstChild.nextSibling).equals(a)
o(a.parentNode).equals(parent)
o(b.parentNode).equals(parent)
})
o("removes from old parent", function() {
var parent = $document.createElement("div")
var source = $document.createElement("span")
var a = $document.createElement("a")
var b = $document.createElement("b")
parent.appendChild(a)
source.appendChild(b)
parent.appendChild(b)
o(source.childNodes.length).equals(0)
})
o("transfers from fragment", function() {
var parent = $document.createElement("div")
var a = $document.createDocumentFragment("a")
var b = $document.createElement("b")
var c = $document.createElement("c")
a.appendChild(b)
a.appendChild(c)
parent.appendChild(a)
o(parent.childNodes.length).equals(2)
o(parent.childNodes[0]).equals(b)
o(parent.childNodes[1]).equals(c)
o(parent.firstChild).equals(b)
o(parent.firstChild.nextSibling).equals(c)
o(a.childNodes.length).equals(0)
o(a.firstChild).equals(null)
o(a.parentNode).equals(null)
o(b.parentNode).equals(parent)
o(c.parentNode).equals(parent)
})
o("throws if appended to self", function(done) {
var div = $document.createElement("div")
try {div.appendChild(div)}
catch (e) {done()}
})
o("throws if appended to child", function(done) {
var parent = $document.createElement("div")
var child = $document.createElement("a")
parent.appendChild(child)
try {child.appendChild(parent)}
catch (e) {done()}
})
o("throws if child is not element", function(done) {
var parent = $document.createElement("div")
var child = 1
try {parent.appendChild(child)}
catch (e) {done()}
})
})
o.spec("removeChild", function() {
o("works", function() {
var parent = $document.createElement("div")
var child = $document.createElement("a")
parent.appendChild(child)
parent.removeChild(child)
o(parent.childNodes.length).equals(0)
o(parent.firstChild).equals(null)
o(child.parentNode).equals(null)
})
o("throws if not a child", function(done) {
var parent = $document.createElement("div")
var child = $document.createElement("a")
try {parent.removeChild(child)}
catch (e) {done()}
})
})
o.spec("insertBefore", function() {
o("works", function() {
var parent = $document.createElement("div")
var a = $document.createElement("a")
var b = $document.createElement("b")
parent.appendChild(a)
parent.insertBefore(b, a)
o(parent.childNodes.length).equals(2)
o(parent.childNodes[0]).equals(b)
o(parent.childNodes[1]).equals(a)
o(parent.firstChild).equals(b)
o(parent.firstChild.nextSibling).equals(a)
o(a.parentNode).equals(parent)
o(b.parentNode).equals(parent)
})
o("moves existing", function() {
var parent = $document.createElement("div")
var a = $document.createElement("a")
var b = $document.createElement("b")
parent.appendChild(a)
parent.appendChild(b)
parent.insertBefore(b, a)
o(parent.childNodes.length).equals(2)
o(parent.childNodes[0]).equals(b)
o(parent.childNodes[1]).equals(a)
o(parent.firstChild).equals(b)
o(parent.firstChild.nextSibling).equals(a)
o(a.parentNode).equals(parent)
o(b.parentNode).equals(parent)
})
o("removes from old parent", function() {
var parent = $document.createElement("div")
var source = $document.createElement("span")
var a = $document.createElement("a")
var b = $document.createElement("b")
parent.appendChild(a)
source.appendChild(b)
parent.insertBefore(b, a)
o(source.childNodes.length).equals(0)
})
o("transfers from fragment", function() {
var parent = $document.createElement("div")
var ref = $document.createElement("span")
var a = $document.createDocumentFragment("a")
var b = $document.createElement("b")
var c = $document.createElement("c")
parent.appendChild(ref)
a.appendChild(b)
a.appendChild(c)
parent.insertBefore(a, ref)
o(parent.childNodes.length).equals(3)
o(parent.childNodes[0]).equals(b)
o(parent.childNodes[1]).equals(c)
o(parent.childNodes[2]).equals(ref)
o(parent.firstChild).equals(b)
o(parent.firstChild.nextSibling).equals(c)
o(parent.firstChild.nextSibling.nextSibling).equals(ref)
o(a.childNodes.length).equals(0)
o(a.firstChild).equals(null)
o(a.parentNode).equals(null)
o(b.parentNode).equals(parent)
o(c.parentNode).equals(parent)
})
o("appends if second arg is null", function() {
var parent = $document.createElement("div")
var a = $document.createElement("a")
var b = $document.createElement("b")
parent.appendChild(a)
parent.insertBefore(b, null)
o(parent.childNodes.length).equals(2)
o(parent.childNodes[0]).equals(a)
o(parent.childNodes[1]).equals(b)
o(parent.firstChild).equals(a)
o(parent.firstChild.nextSibling).equals(b)
o(a.parentNode).equals(parent)
})
o("throws if appended to self", function(done) {
var div = $document.createElement("div")
var a = $document.createElement("a")
div.appendChild(a)
try {div.isnertBefore(div, a)}
catch (e) {done()}
})
o("throws if appended to child", function(done) {
var parent = $document.createElement("div")
var a = $document.createElement("a")
var b = $document.createElement("b")
parent.appendChild(a)
a.appendChild(b)
try {a.insertBefore(parent, b)}
catch (e) {done()}
})
o("throws if child is not element", function(done) {
var parent = $document.createElement("div")
var a = $document.createElement("a")
parent.appendChild(a)
try {parent.insertBefore(1, a)}
catch (e) {done()}
})
o("throws if inserted before itself", function(done) {
var parent = $document.createElement("div")
var a = $document.createElement("a")
try {parent.insertBefore(a, a)}
catch (e) {done()}
})
o("throws if second arg is undefined", function(done) {
var parent = $document.createElement("div")
var a = $document.createElement("a")
try {parent.insertBefore(a)}
catch (e) {done()}
})
o("throws if reference is not child", function(done) {
var parent = $document.createElement("div")
var a = $document.createElement("a")
var b = $document.createElement("b")
try {parent.insertBefore(a, b)}
catch (e) {done()}
})
})
o.spec("setAttribute", function() {
o("works", function() {
var div = $document.createElement("div")
div.setAttribute("id", "aaa")
o(div.attributes["id"].nodeValue).equals("aaa")
o(div.attributes["id"].namespaceURI).equals(null)
})
o("works w/ number", function() {
var div = $document.createElement("div")
div.setAttribute("id", 123)
o(div.attributes["id"].nodeValue).equals("123")
})
o("works w/ null", function() {
var div = $document.createElement("div")
div.setAttribute("id", null)
o(div.attributes["id"].nodeValue).equals("null")
})
o("works w/ undefined", function() {
var div = $document.createElement("div")
div.setAttribute("id", undefined)
o(div.attributes["id"].nodeValue).equals("undefined")
})
o("works w/ object", function() {
var div = $document.createElement("div")
div.setAttribute("id", {})
o(div.attributes["id"].nodeValue).equals("[object Object]")
})
})
o.spec("setAttributeNS", function() {
o("works", function() {
var div = $document.createElement("div")
div.setAttributeNS("http://www.w3.org/1999/xlink", "href", "aaa")
o(div.attributes["href"].nodeValue).equals("aaa")
o(div.attributes["href"].namespaceURI).equals("http://www.w3.org/1999/xlink")
})
o("works w/ number", function() {
var div = $document.createElement("div")
div.setAttributeNS("http://www.w3.org/1999/xlink", "href", 123)
o(div.attributes["href"].nodeValue).equals("123")
o(div.attributes["href"].namespaceURI).equals("http://www.w3.org/1999/xlink")
})
})
o.spec("removeAttribute", function() {
o("works", function() {
var div = $document.createElement("div")
div.setAttribute("id", "aaa")
div.removeAttribute("id")
o("id" in div.attributes).equals(false)
})
})
o.spec("textContent", function() {
o("works", function() {
var div = $document.createElement("div")
var a = $document.createElement("a")
div.textContent = "aaa"
o(div.childNodes.length).equals(1)
o(div.firstChild.nodeType).equals(3)
o(div.firstChild.nodeValue).equals("aaa")
})
o("works with empty string", function() {
var div = $document.createElement("div")
var a = $document.createElement("a")
div.textContent = ""
o(div.childNodes.length).equals(0)
})
})
o.spec("innerHTML", function() {
o("works", function() {
var div = $document.createElement("div")
div.innerHTML = "<br /><a class='aaa' id='xyz'>123<b class=\"bbb\"></b>234<br class=ccc>345</a>"
o(div.childNodes.length).equals(2)
o(div.childNodes[0].nodeType).equals(1)
o(div.childNodes[0].nodeName).equals("BR")
o(div.childNodes[1].nodeType).equals(1)
o(div.childNodes[1].nodeName).equals("A")
o(div.childNodes[1].attributes["class"].nodeValue).equals("aaa")
o(div.childNodes[1].attributes["id"].nodeValue).equals("xyz")
o(div.childNodes[1].childNodes[0].nodeType).equals(3)
o(div.childNodes[1].childNodes[0].nodeValue).equals("123")
o(div.childNodes[1].childNodes[1].nodeType).equals(1)
o(div.childNodes[1].childNodes[1].nodeName).equals("B")
o(div.childNodes[1].childNodes[1].attributes["class"].nodeValue).equals("bbb")
o(div.childNodes[1].childNodes[2].nodeType).equals(3)
o(div.childNodes[1].childNodes[2].nodeValue).equals("234")
o(div.childNodes[1].childNodes[3].nodeType).equals(1)
o(div.childNodes[1].childNodes[3].nodeName).equals("BR")
o(div.childNodes[1].childNodes[3].attributes["class"].nodeValue).equals("ccc")
o(div.childNodes[1].childNodes[4].nodeType).equals(3)
o(div.childNodes[1].childNodes[4].nodeValue).equals("345")
})
o("headers work", function() {
var div = $document.createElement("div")
div.innerHTML = "<h1></h1><h2></h2><h3></h3><h4></h4><h5></h5><h6></h6>"
o(div.childNodes.length).equals(6)
o(div.childNodes[0].nodeType).equals(1)
o(div.childNodes[0].nodeName).equals("H1")
o(div.childNodes[1].nodeType).equals(1)
o(div.childNodes[1].nodeName).equals("H2")
o(div.childNodes[2].nodeType).equals(1)
o(div.childNodes[2].nodeName).equals("H3")
o(div.childNodes[3].nodeType).equals(1)
o(div.childNodes[3].nodeName).equals("H4")
o(div.childNodes[4].nodeType).equals(1)
o(div.childNodes[4].nodeName).equals("H5")
o(div.childNodes[5].nodeType).equals(1)
o(div.childNodes[5].nodeName).equals("H6")
})
o("detaches old elements", function() {
var div = $document.createElement("div")
var a = $document.createElement("a")
div.appendChild(a)
div.innerHTML = "<b></b>"
o(a.parentNode).equals(null)
})
})
o.spec("focus", function() {
o("body is active by default", function() {
o($document.documentElement.nodeName).equals("HTML")
o($document.body.nodeName).equals("BODY")
o($document.documentElement.firstChild.nodeName).equals("HEAD")
o($document.documentElement).equals($document.body.parentNode)
o($document.activeElement).equals($document.body)
})
o("focus changes activeElement", function() {
var input = $document.createElement("input")
$document.body.appendChild(input)
input.focus()
o($document.activeElement).equals(input)
$document.body.removeChild(input)
})
})
o.spec("style", function() {
o("has style property", function() {
var div = $document.createElement("div")
o(typeof div.style).equals("object")
})
o("setting style string works", function() {
var div = $document.createElement("div")
div.style = "background-color: red; border-bottom: 1px solid red;"
o(div.style.backgroundColor).equals("red")
o(div.style.borderBottom).equals("1px solid red")
})
o("removing via setting style string works", function() {
var div = $document.createElement("div")
div.style = "background: red;"
div.style = ""
o(div.style.background).equals("")
})
})
o.spec("events", function() {
var spy, div, e
o.beforeEach(function() {
spy = o.spy()
div = $document.createElement("div")
e = $document.createEvent("MouseEvents")
e.initEvent("click", true, true)
$document.body.appendChild(div)
})
o.afterEach(function() {
$document.body.removeChild(div)
})
o("click fires onclick", function() {
div.onclick = spy
div.dispatchEvent(e)
o(spy.callCount).equals(1)
o(spy.this).equals(div)
o(spy.args[0].type).equals("click")
o(spy.args[0].target).equals(div)
})
o("click without onclick doesn't throw", function(done) {
div.dispatchEvent(e)
done()
})
})
o.spec("attributes", function() {
o.spec("link href", function() {
o("is empty string if no attribute", function() {
var a = $document.createElement("a")
o(a.href).equals("")
o(a.attributes["href"]).equals(undefined)
})
o("is path if attribute is set", function() {
var a = $document.createElement("a")
a.setAttribute("href", "")
o(a.href).notEquals("")
o(a.attributes["href"].nodeValue).equals("")
})
o("is path if property is set", function() {
var a = $document.createElement("a")
a.href = ""
o(a.href).notEquals("")
o(a.attributes["href"].nodeValue).equals("")
})
})
o.spec("input checked", function() {
o("only exists in input elements", function() {
var input = $document.createElement("input")
var a = $document.createElement("a")
o("checked" in input).equals(true)
o("checked" in a).equals(false)
})
o("tracks attribute value when unset", function() {
var input = $document.createElement("input")
input.setAttribute("type", "checkbox")
o(input.checked).equals(false)
o(input.attributes["checked"]).equals(undefined)
input.setAttribute("checked", "")
o(input.checked).equals(true)
o(input.attributes["checked"].nodeValue).equals("")
input.removeAttribute("checked")
o(input.checked).equals(false)
o(input.attributes["checked"]).equals(undefined)
})
o("does not track attribute value when set", function() {
var input = $document.createElement("input")
input.setAttribute("type", "checkbox")
input.checked = true
o(input.checked).equals(true)
o(input.attributes["checked"]).equals(undefined)
input.checked = false
input.setAttribute("checked", "")
input.checked = true
input.removeAttribute("checked")
o(input.checked).equals(true)
})
})
})
})

View file

@ -0,0 +1,150 @@
"use strict"
var o = require("../../ospec/ospec")
var parseURL = require("../../test-utils/parseURL")
o.spec("parseURL", function() {
var root = {protocol: "http:", hostname: "localhost", port: "", pathname: "/"}
o.spec("full URL", function() {
o("parses full URL", function() {
var data = parseURL("http://www.google.com:80/test?a=b#c")
o(data.protocol).equals("http:")
o(data.hostname).equals("www.google.com")
o(data.port).equals("80")
o(data.pathname).equals("/test")
o(data.search).equals("?a=b")
o(data.hash).equals("#c")
})
o("parses full URL omitting optionals", function() {
var data = parseURL("http://www.google.com")
o(data.protocol).equals("http:")
o(data.hostname).equals("www.google.com")
o(data.port).equals("")
o(data.pathname).equals("/")
o(data.search).equals("")
o(data.hash).equals("")
})
})
o.spec("absolute path", function() {
o("parses absolute path", function() {
var data = parseURL("/test?a=b#c", root)
o(data.protocol).equals(root.protocol)
o(data.hostname).equals(root.hostname)
o(data.port).equals(root.port)
o(data.pathname).equals("/test")
o(data.search).equals("?a=b")
o(data.hash).equals("#c")
})
o("parses absolute path omitting optionals", function() {
var data = parseURL("/test?a=b#c", root)
o(data.protocol).equals(root.protocol)
o(data.hostname).equals(root.hostname)
o(data.port).equals(root.port)
o(data.pathname).equals("/test")
o(data.search).equals("?a=b")
o(data.hash).equals("#c")
})
})
o.spec("relative path", function() {
o("parses relative URL", function() {
var data = parseURL("test?a=b#c", root)
o(data.protocol).equals(root.protocol)
o(data.hostname).equals(root.hostname)
o(data.port).equals(root.port)
o(data.pathname).equals("/test")
o(data.search).equals("?a=b")
o(data.hash).equals("#c")
})
o("parses relative URL omitting optionals", function() {
var data = parseURL("test", root)
o(data.protocol).equals(root.protocol)
o(data.hostname).equals(root.hostname)
o(data.port).equals(root.port)
o(data.pathname).equals("/test")
o(data.search).equals("")
o(data.hash).equals("")
})
o("parses relative URL with dot", function() {
var data = parseURL("././test?a=b#c", root)
o(data.protocol).equals(root.protocol)
o(data.hostname).equals(root.hostname)
o(data.port).equals(root.port)
o(data.pathname).equals("/test")
o(data.search).equals("?a=b")
o(data.hash).equals("#c")
})
o("parses relative URL with dotdot", function() {
var data = parseURL("foo/bar/../../test?a=b#c", root)
o(data.protocol).equals(root.protocol)
o(data.hostname).equals(root.hostname)
o(data.port).equals(root.port)
o(data.pathname).equals("/test")
o(data.search).equals("?a=b")
o(data.hash).equals("#c")
})
o("clamps invalid dotdot", function() {
var data = parseURL("../../test?a=b#c", root)
o(data.protocol).equals(root.protocol)
o(data.hostname).equals(root.hostname)
o(data.port).equals(root.port)
o(data.pathname).equals("/test")
o(data.search).equals("?a=b")
o(data.hash).equals("#c")
})
o("clamps invalid dotdot after dot", function() {
var data = parseURL("./../../test?a=b#c", root)
o(data.protocol).equals(root.protocol)
o(data.hostname).equals(root.hostname)
o(data.port).equals(root.port)
o(data.pathname).equals("/test")
o(data.search).equals("?a=b")
o(data.hash).equals("#c")
})
o("clamps invalid dotdot after valid path", function() {
var data = parseURL("a/../../test?a=b#c", root)
o(data.protocol).equals(root.protocol)
o(data.hostname).equals(root.hostname)
o(data.port).equals(root.port)
o(data.pathname).equals("/test")
o(data.search).equals("?a=b")
o(data.hash).equals("#c")
})
})
o.spec("edge cases", function() {
o("handles hash w/ question mark", function() {
var data = parseURL("http://www.google.com/test#a?c")
o(data.pathname).equals("/test")
o(data.search).equals("")
o(data.hash).equals("#a?c")
})
o("handles hash w/ slash", function() {
var data = parseURL("http://www.google.com/test#a/c")
o(data.pathname).equals("/test")
o(data.search).equals("")
o(data.hash).equals("#a/c")
})
o("handles hash w/ colon", function() {
var data = parseURL("http://www.google.com/test#a:c")
o(data.pathname).equals("/test")
o(data.search).equals("")
o(data.hash).equals("#a:c")
})
o("handles search w/ slash", function() {
var data = parseURL("http://www.google.com/test?a/c")
o(data.pathname).equals("/test")
o(data.search).equals("?a/c")
o(data.hash).equals("")
})
o("handles search w/ slash", function() {
var data = parseURL("http://www.google.com/test?a:c")
o(data.pathname).equals("/test")
o(data.search).equals("?a:c")
o(data.hash).equals("")
})
o("handles pathname w/ colon", function() {
var data = parseURL("http://www.google.com/a:b")
o(data.pathname).equals("/a:b")
})
})
})

View file

@ -0,0 +1,520 @@
"use strict"
var o = require("../../ospec/ospec")
var pushStateMock = require("../../test-utils/pushStateMock")
o.spec("pushStateMock", function() {
var $window
o.beforeEach(function() {
$window = pushStateMock()
})
o.spec("initial state", function() {
o("has url on page load", function() {
o($window.location.href).equals("http://localhost/")
})
})
o.spec("set href", function() {
o("changes url on location.href change", function() {
var old = $window.location.href
$window.location.href = "http://localhost/a"
o(old).equals("http://localhost/")
o($window.location.href).equals("http://localhost/a")
})
o("changes url on relative location.href change", function() {
var old = $window.location.href
$window.location.href = "a"
o(old).equals("http://localhost/")
o($window.location.href).equals("http://localhost/a")
o($window.location.pathname).equals("/a")
})
o("changes url on dotdot location.href change", function() {
$window.location.href = "a"
var old = $window.location.href
$window.location.href = ".."
o(old).equals("http://localhost/a")
o($window.location.href).equals("http://localhost/")
o($window.location.pathname).equals("/")
})
o("changes url on deep dotdot location.href change", function() {
$window.location.href = "a/b/c"
var old = $window.location.href
$window.location.href = ".."
o(old).equals("http://localhost/a/b/c")
o($window.location.href).equals("http://localhost/a")
o($window.location.pathname).equals("/a")
})
o("does not change url on dotdot location.href change from root", function() {
var old = $window.location.href
$window.location.href = ".."
o(old).equals("http://localhost/")
o($window.location.href).equals("http://localhost/")
o($window.location.pathname).equals("/")
})
o("changes url on dot relative location.href change", function() {
var old = $window.location.href
$window.location.href = "a"
$window.location.href = "./b"
o(old).equals("http://localhost/")
o($window.location.href).equals("http://localhost/b")
o($window.location.pathname).equals("/b")
})
o("does not change url on dot location.href change", function() {
var old = $window.location.href
$window.location.href = "a"
$window.location.href = "."
o(old).equals("http://localhost/")
o($window.location.href).equals("http://localhost/a")
o($window.location.pathname).equals("/a")
})
o("changes url on hash-only location.href change", function() {
var old = $window.location.href
$window.location.href = "#a"
o(old).equals("http://localhost/")
o($window.location.href).equals("http://localhost/#a")
o($window.location.hash).equals("#a")
})
o("changes url on search-only location.href change", function() {
var old = $window.location.href
$window.location.href = "?a"
o(old).equals("http://localhost/")
o($window.location.href).equals("http://localhost/?a")
o($window.location.search).equals("?a")
})
o("changes hash on location.href change", function() {
var old = $window.location.href
$window.location.href = "http://localhost/a#b"
o(old).equals("http://localhost/")
o($window.location.href).equals("http://localhost/a#b")
o($window.location.hash).equals("#b")
})
o("changes search on location.href change", function() {
var old = $window.location.href
$window.location.href = "http://localhost/a?b"
o(old).equals("http://localhost/")
o($window.location.href).equals("http://localhost/a?b")
o($window.location.search).equals("?b")
})
o("changes search and hash on location.href change", function() {
var old = $window.location.href
$window.location.href = "http://localhost/a?b#c"
o(old).equals("http://localhost/")
o($window.location.href).equals("http://localhost/a?b#c")
o($window.location.search).equals("?b")
o($window.location.hash).equals("#c")
})
o("handles search with search and hash", function() {
var old = $window.location.href
$window.location.href = "http://localhost/a?b?c#d"
o(old).equals("http://localhost/")
o($window.location.href).equals("http://localhost/a?b?c#d")
o($window.location.search).equals("?b?c")
o($window.location.hash).equals("#d")
})
o("handles hash with search and hash", function() {
var old = $window.location.href
$window.location.href = "http://localhost/a#b?c#d"
o(old).equals("http://localhost/")
o($window.location.href).equals("http://localhost/a#b?c#d")
o($window.location.search).equals("")
o($window.location.hash).equals("#b?c#d")
})
})
o.spec("set search", function() {
o("changes url on location.search change", function() {
var old = $window.location.href
$window.location.search = "?b"
o(old).equals("http://localhost/")
o($window.location.href).equals("http://localhost/?b")
o($window.location.search).equals("?b")
})
})
o.spec("set hash", function() {
o("changes url on location.hash change", function() {
var old = $window.location.href
$window.location.hash = "#b"
o(old).equals("http://localhost/")
o($window.location.href).equals("http://localhost/#b")
o($window.location.hash).equals("#b")
})
})
o.spec("pushState", function() {
o("changes url on pushstate", function() {
var old = $window.location.href
$window.history.pushState(null, null, "http://localhost/a")
o(old).equals("http://localhost/")
o($window.location.href).equals("http://localhost/a")
})
o("changes search on pushstate", function() {
var old = $window.location.href
$window.history.pushState(null, null, "http://localhost/?a")
o(old).equals("http://localhost/")
o($window.location.href).equals("http://localhost/?a")
o($window.location.search).equals("?a")
})
o("changes search on relative pushstate", function() {
var old = $window.location.href
$window.history.pushState(null, null, "?a")
o(old).equals("http://localhost/")
o($window.location.href).equals("http://localhost/?a")
o($window.location.search).equals("?a")
})
o("changes hash on pushstate", function() {
var old = $window.location.href
$window.history.pushState(null, null, "http://localhost/#a")
o(old).equals("http://localhost/")
o($window.location.href).equals("http://localhost/#a")
o($window.location.hash).equals("#a")
})
o("changes hash on relative pushstate", function() {
var old = $window.location.href
$window.history.pushState(null, null, "#a")
o(old).equals("http://localhost/")
o($window.location.href).equals("http://localhost/#a")
o($window.location.hash).equals("#a")
})
})
o.spec("onpopstate", function() {
o("history.back() without history does not trigger onpopstate", function() {
$window.onpopstate = o.spy()
$window.history.back()
o($window.onpopstate.callCount).equals(0)
})
o("history.back() after pushstate triggers onpopstate", function() {
$window.onpopstate = o.spy()
$window.history.pushState(null, null, "http://localhost/a")
$window.history.back()
o($window.onpopstate.callCount).equals(1)
o($window.onpopstate.args[0].type).equals("popstate")
})
o("history.back() after relative pushstate triggers onpopstate", function() {
$window.onpopstate = o.spy()
$window.history.pushState(null, null, "a")
$window.history.back()
o($window.onpopstate.callCount).equals(1)
})
o("history.back() after search pushstate triggers onpopstate", function() {
$window.onpopstate = o.spy()
$window.history.pushState(null, null, "http://localhost/?a")
$window.history.back()
o($window.onpopstate.callCount).equals(1)
})
o("history.back() after relative search pushstate triggers onpopstate", function() {
$window.onpopstate = o.spy()
$window.history.pushState(null, null, "?a")
$window.history.back()
o($window.onpopstate.callCount).equals(1)
})
o("history.back() after hash pushstate triggers onpopstate", function() {
$window.onpopstate = o.spy()
$window.history.pushState(null, null, "http://localhost/#a")
$window.history.back()
o($window.onpopstate.callCount).equals(1)
})
o("history.back() after relative hash pushstate triggers onpopstate", function() {
$window.onpopstate = o.spy()
$window.history.pushState(null, null, "#a")
$window.history.back()
o($window.onpopstate.callCount).equals(1)
})
o("history.back() after replacestate does not trigger onpopstate", function() {
$window.onpopstate = o.spy()
$window.history.replaceState(null, null, "http://localhost/a")
$window.history.back()
o($window.onpopstate.callCount).equals(0)
})
o("history.back() after relative replacestate does not trigger onpopstate", function() {
$window.onpopstate = o.spy()
$window.history.replaceState(null, null, "a")
$window.history.back()
o($window.onpopstate.callCount).equals(0)
})
o("history.back() after relative search replacestate does not trigger onpopstate", function() {
$window.onpopstate = o.spy()
$window.history.replaceState(null, null, "?a")
$window.history.back()
o($window.onpopstate.callCount).equals(0)
})
o("history.back() after relative hash replacestate does not trigger onpopstate", function() {
$window.onpopstate = o.spy()
$window.history.replaceState(null, null, "#a")
$window.history.back()
o($window.onpopstate.callCount).equals(0)
})
o("history.forward() after pushstate triggers onpopstate", function() {
$window.onpopstate = o.spy()
$window.history.pushState(null, null, "http://localhost/a")
$window.history.back()
$window.history.forward()
o($window.onpopstate.callCount).equals(2)
})
o("history.forward() after relative pushstate triggers onpopstate", function() {
$window.onpopstate = o.spy()
$window.history.pushState(null, null, "a")
$window.history.back()
$window.history.forward()
o($window.onpopstate.callCount).equals(2)
})
o("history.forward() after search pushstate triggers onpopstate", function() {
$window.onpopstate = o.spy()
$window.history.pushState(null, null, "http://localhost/?a")
$window.history.back()
$window.history.forward()
o($window.onpopstate.callCount).equals(2)
})
o("history.forward() after relative search pushstate triggers onpopstate", function() {
$window.onpopstate = o.spy()
$window.history.pushState(null, null, "?a")
$window.history.back()
$window.history.forward()
o($window.onpopstate.callCount).equals(2)
})
o("history.forward() after hash pushstate triggers onpopstate", function() {
$window.onpopstate = o.spy()
$window.history.pushState(null, null, "http://localhost/#a")
$window.history.back()
$window.history.forward()
o($window.onpopstate.callCount).equals(2)
})
o("history.forward() after relative hash pushstate triggers onpopstate", function() {
$window.onpopstate = o.spy()
$window.history.pushState(null, null, "#a")
$window.history.back()
$window.history.forward()
o($window.onpopstate.callCount).equals(2)
})
o("history.back() without history does not trigger onpopstate", function() {
$window.onpopstate = o.spy()
$window.history.back()
o($window.onpopstate.callCount).equals(0)
})
o("history.forward() without history does not trigger onpopstate", function() {
$window.onpopstate = o.spy()
$window.history.forward()
o($window.onpopstate.callCount).equals(0)
})
o("history navigation without history does not trigger onpopstate", function() {
$window.onpopstate = o.spy()
$window.history.back()
$window.history.forward()
o($window.onpopstate.callCount).equals(0)
})
o("reverse history navigation without history does not trigger onpopstate", function() {
$window.onpopstate = o.spy()
$window.history.forward()
$window.history.back()
o($window.onpopstate.callCount).equals(0)
})
o("onpopstate has correct url during call", function(done) {
$window.location.href = "a"
$window.onpopstate = function() {
o($window.location.href).equals("http://localhost/a")
done()
}
$window.history.pushState(null, null, "b")
$window.history.back()
})
})
o.spec("onhashchance", function() {
o("onhashchange triggers on location.href change", function() {
$window.onhashchange = o.spy()
$window.location.href = "http://localhost/#a"
o($window.onhashchange.callCount).equals(1)
o($window.onhashchange.args[0].type).equals("hashchange")
})
o("onhashchange triggers on relative location.href change", function() {
$window.onhashchange = o.spy()
$window.location.href = "#a"
o($window.onhashchange.callCount).equals(1)
})
o("onhashchange triggers on location.hash change", function() {
$window.onhashchange = o.spy()
$window.location.hash = "#a"
o($window.onhashchange.callCount).equals(1)
})
o("onhashchange does not trigger on page change", function() {
$window.onhashchange = o.spy()
$window.location.href = "http://localhost/a"
o($window.onhashchange.callCount).equals(0)
})
o("onhashchange does not trigger on page change with different hash", function() {
$window.location.href = "http://localhost/#a"
$window.onhashchange = o.spy()
$window.location.href = "http://localhost/a#b"
o($window.onhashchange.callCount).equals(0)
})
o("onhashchange does not trigger on page change with same hash", function() {
$window.location.href = "http://localhost/#b"
$window.onhashchange = o.spy()
$window.location.href = "http://localhost/a#b"
o($window.onhashchange.callCount).equals(0)
})
o("onhashchange triggers on history.back()", function() {
$window.location.href = "#a"
$window.onhashchange = o.spy()
$window.history.back()
o($window.onhashchange.callCount).equals(1)
})
o("onhashchange triggers on history.forward()", function() {
$window.location.href = "#a"
$window.onhashchange = o.spy()
$window.history.back()
$window.history.forward()
o($window.onhashchange.callCount).equals(2)
})
o("onhashchange does not trigger on history.back() that causes page change with different hash", function() {
$window.location.href = "#a"
$window.location.href = "a#b"
$window.onhashchange = o.spy()
$window.history.back()
o($window.onhashchange.callCount).equals(0)
})
o("onhashchange does not trigger on history.back() that causes page change with same hash", function() {
$window.location.href = "#a"
$window.location.href = "a#a"
$window.onhashchange = o.spy()
$window.history.back()
o($window.onhashchange.callCount).equals(0)
})
o("onhashchange does not trigger on history.forward() that causes page change with different hash", function() {
$window.location.href = "#a"
$window.location.href = "a#b"
$window.onhashchange = o.spy()
$window.history.back()
$window.history.forward()
o($window.onhashchange.callCount).equals(0)
})
o("onhashchange does not trigger on history.forward() that causes page change with same hash", function() {
$window.location.href = "#a"
$window.location.href = "a#b"
$window.onhashchange = o.spy()
$window.history.back()
$window.history.forward()
o($window.onhashchange.callCount).equals(0)
})
})
o.spec("onunload", function() {
o("onunload triggers on location.href change", function() {
$window.onunload = o.spy()
$window.location.href = "http://localhost/a"
o($window.onunload.callCount).equals(1)
o($window.onunload.args[0].type).equals("unload")
})
o("onunload triggers on relative location.href change", function() {
$window.onunload = o.spy()
$window.location.href = "a"
o($window.onunload.callCount).equals(1)
})
o("onunload triggers on search change via location.href", function() {
$window.onunload = o.spy()
$window.location.href = "http://localhost/?a"
o($window.onunload.callCount).equals(1)
})
o("onunload triggers on relative search change via location.href", function() {
$window.onunload = o.spy()
$window.location.href = "?a"
o($window.onunload.callCount).equals(1)
})
o("onunload does not trigger on hash change via location.href", function() {
$window.onunload = o.spy()
$window.location.href = "http://localhost/#a"
o($window.onunload.callCount).equals(0)
})
o("onunload does not trigger on relative hash change via location.href", function() {
$window.onunload = o.spy()
$window.location.href = "#a"
o($window.onunload.callCount).equals(0)
})
o("onunload does not trigger on hash-only history.back()", function() {
$window.location.href = "#a"
$window.onunload = o.spy()
$window.history.back()
o($window.onunload.callCount).equals(0)
})
o("onunload does not trigger on hash-only history.forward()", function() {
$window.location.href = "#a"
$window.history.back()
$window.onunload = o.spy()
$window.history.forward()
o($window.onunload.callCount).equals(0)
})
o("onunload has correct url during call via location.href change", function(done) {
$window.onunload = function() {
o($window.location.href).equals("http://localhost/")
done()
}
$window.location.href = "a"
})
o("onunload has correct url during call via location.search change", function(done) {
$window.onunload = function() {
o($window.location.href).equals("http://localhost/")
done()
}
$window.location.search = "?a"
})
})
})