Mithril = m = new function app(window, undefined) { var type = {}.toString var parser = /(?:(^|#|\.)([^#\.\[\]]+))|(\[.+?\])/g, attrParser = /\[(.+?)(?:=("|'|)(.*?)\2)?\]/ var voidElements = /AREA|BASE|BR|COL|COMMAND|EMBED|HR|IMG|INPUT|KEYGEN|LINK|META|PARAM|SOURCE|TR‌​ACK|WBR/ function m() { var args = arguments var hasAttrs = args[1] != null && type.call(args[1]) == "[object Object]" && !("tag" in args[1]) && !("subtree" in args[1]) var attrs = hasAttrs ? args[1] : {} var classAttrName = "class" in attrs ? "class" : "className" var cell = {tag: "div", attrs: {}} var match, classes = [] while (match = parser.exec(args[0])) { if (match[1] == "") cell.tag = match[2] else if (match[1] == "#") cell.attrs.id = match[2] else if (match[1] == ".") classes.push(match[2]) else if (match[3][0] == "[") { var pair = attrParser.exec(match[3]) cell.attrs[pair[1]] = pair[3] || (pair[2] ? "" :true) } } if (classes.length > 0) cell.attrs[classAttrName] = classes.join(" ") cell.children = hasAttrs ? args[2] : args[1] for (var attrName in attrs) { if (attrName == classAttrName) cell.attrs[attrName] = (cell.attrs[attrName] || "") + " " + attrs[attrName] else cell.attrs[attrName] = attrs[attrName] } return cell } function build(parentElement, parentTag, parentCache, parentIndex, data, cached, shouldReattach, index, editable, namespace, configs) { //`build` is a recursive function that manages creation/diffing/removal of DOM elements based on comparison between `data` and `cached` //the diff algorithm can be summarized as this: //1 - compare `data` and `cached` //2 - if they are different, copy `data` to `cached` and update the DOM based on what the difference is //3 - recursively apply this algorithm for every array and for the children of every virtual element //the `cached` data structure is essentially the same as the previous redraw's `data` data structure, with a few additions: //- `cached` always has a property called `nodes`, which is a list of DOM elements that correspond to the data represented by the respective virtual element //- in order to support attaching `nodes` as a property of `cached`, `cached` is *always* a non-primitive object, i.e. if the data was a string, then cached is a String instance. If data was `null` or `undefined`, cached is `new String("")` //- `cached also has a `configContext` property, which is the state storage object exposed by config(element, isInitialized, context) //- when `cached` is an Object, it represents a virtual element; when it's an Array, it represents a list of elements; when it's a String, Number or Boolean, it represents a text node //`parentElement` is a DOM element used for W3C DOM API calls //`parentTag` is only used for handling a corner case for textarea values //`parentCache` is used to remove nodes in some multi-node cases //`parentIndex` and `index` are used to figure out the offset of nodes. They're artifacts from before arrays started being flattened and are likely refactorable //`data` and `cached` are, respectively, the new and old nodes being diffed //`shouldReattach` is a flag indicating whether a parent node was recreated (if so, and if this node is reused, then this node must reattach itself to the new parent) //`editable` is a flag that indicates whether an ancestor is contenteditable //`namespace` indicates the closest HTML namespace as it cascades down from an ancestor //`configs` is a list of config functions to run after the topmost `build` call finishes running //there's logic that relies on the assumption that null and undefined data are equivalent to empty strings //- this prevents lifecycle surprises from procedural helpers that mix implicit and explicit return statements //- it simplifies diffing code if (data == null) data = "" if (data.subtree === "retain") return cached var cachedType = type.call(cached), dataType = type.call(data) if (cached == null || cachedType != dataType) { if (cached != null) { if (parentCache && parentCache.nodes) { var offset = index - parentIndex var end = offset + (dataType == "[object Array]" ? data : cached.nodes).length clear(parentCache.nodes.slice(offset, end), parentCache.slice(offset, end)) } else if (cached.nodes) clear(cached.nodes, cached) } cached = new data.constructor cached.nodes = [] } if (dataType == "[object Array]") { data = flatten(data) var nodes = [], intact = cached.length === data.length, subArrayCount = 0 //keys algorithm: sort elements without recreating them if keys are present //1) create a map of all existing keys, and mark all for deletion //2) add new keys to map and mark them for addition //3) if key exists in new list, change action from deletion to a move //4) for each key, handle its corresponding action as marked in previous steps //5) copy unkeyed items into their respective gaps var DELETION = 1, INSERTION = 2 , MOVE = 3 var existing = {}, unkeyed = [], shouldMaintainIdentities = false for (var i = 0; i < cached.length; i++) { if (cached[i] && cached[i].attrs && cached[i].attrs.key != null) { shouldMaintainIdentities = true existing[cached[i].attrs.key] = {action: DELETION, index: i} } } if (shouldMaintainIdentities) { for (var i = 0; i < data.length; i++) { if (data[i] && data[i].attrs) { if (data[i].attrs.key != null) { var key = data[i].attrs.key if (!existing[key]) existing[key] = {action: INSERTION, index: i} else existing[key] = {action: MOVE, index: i, from: existing[key].index, element: parentElement.childNodes[existing[key].index]} } else unkeyed.push({index: i, element: parentElement.childNodes[i]}) } } var actions = Object.keys(existing).map(function(key) {return existing[key]}) var changes = actions.sort(function(a, b) {return a.action - b.action || a.index - b.index}) var newCached = cached.slice() for (var i = 0, change; change = changes[i]; i++) { if (change.action == DELETION) { clear(cached[change.index].nodes, cached[change.index]) newCached.splice(change.index, 1) } if (change.action == INSERTION) { var dummy = window.document.createElement("div") dummy.key = data[change.index].attrs.key parentElement.insertBefore(dummy, parentElement.childNodes[change.index]) newCached.splice(change.index, 0, {attrs: {key: data[change.index].attrs.key}, nodes: [dummy]}) } if (change.action == MOVE) { if (parentElement.childNodes[change.index] !== change.element && change.element !== null) { parentElement.insertBefore(change.element, parentElement.childNodes[change.index]) } newCached[change.index] = cached[change.from] } } for (var i = 0; i < unkeyed.length; i++) { var change = unkeyed[i] parentElement.insertBefore(change.element, parentElement.childNodes[change.index]) newCached[change.index] = cached[change.index] } cached = newCached cached.nodes = [] for (var i = 0, child; child = parentElement.childNodes[i]; i++) cached.nodes.push(child) } //end key algorithm for (var i = 0, cacheCount = 0; i < data.length; i++) { //diff each item in the array var item = build(parentElement, parentTag, cached, index, data[i], cached[cacheCount], shouldReattach, index + subArrayCount || subArrayCount, editable, namespace, configs) if (item === undefined) continue if (!item.nodes.intact) intact = false var isArray = type.call(item) == "[object Array]" subArrayCount += isArray ? item.length : 1 cached[cacheCount++] = item } if (!intact) { //diff the array itself //update the list of DOM nodes by collecting the nodes from each item for (var i = 0; i < data.length; i++) { if (cached[i] != null) nodes = nodes.concat(cached[i].nodes) } //remove items from the end of the array if the new array is shorter than the old one //if errors ever happen here, the issue is most likely a bug in the construction of the `cached` data structure somewhere earlier in the program for (var i = 0, node; node = cached.nodes[i]; i++) { if (node.parentNode != null && nodes.indexOf(node) < 0) clear([node], [cached[i]]) } //add items to the end if the new array is longer than the old one for (var i = cached.nodes.length, node; node = nodes[i]; i++) { if (node.parentNode == null) parentElement.appendChild(node) } if (data.length < cached.length) cached.length = data.length cached.nodes = nodes } } else if (data != null && dataType == "[object Object]") { //if an element is different enough from the one in cache, recreate it if (data.tag != cached.tag || Object.keys(data.attrs).join() != Object.keys(cached.attrs).join() || data.attrs.id != cached.attrs.id) { clear(cached.nodes) if (cached.configContext && typeof cached.configContext.onunload == "function") cached.configContext.onunload() } if (typeof data.tag != "string") return var node, isNew = cached.nodes.length === 0 if (data.attrs.xmlns) namespace = data.attrs.xmlns else if (data.tag === "svg") namespace = "http://www.w3.org/2000/svg" else if (data.tag === "math") namespace = "http://www.w3.org/1998/Math/MathML" if (isNew) { node = namespace === undefined ? window.document.createElement(data.tag) : window.document.createElementNS(namespace, data.tag) cached = { tag: data.tag, //process children before attrs so that select.value works correctly children: build(node, data.tag, undefined, undefined, data.children, cached.children, true, 0, data.attrs.contenteditable ? node : editable, namespace, configs), attrs: setAttributes(node, data.tag, data.attrs, {}, namespace), nodes: [node] } parentElement.insertBefore(node, parentElement.childNodes[index] || null) } else { node = cached.nodes[0] setAttributes(node, data.tag, data.attrs, cached.attrs, namespace) cached.children = build(node, data.tag, undefined, undefined, data.children, cached.children, false, 0, data.attrs.contenteditable ? node : editable, namespace, configs) cached.nodes.intact = true if (shouldReattach === true && node != null) parentElement.insertBefore(node, parentElement.childNodes[index] || null) } //schedule configs to be called. They are called after `build` finishes running if (typeof data.attrs["config"] === "function") { configs.push(data.attrs["config"].bind(window, node, !isNew, cached.configContext = cached.configContext || {}, cached)) } } else if (typeof dataType != "function") { //handle text nodes var nodes if (cached.nodes.length === 0) { if (data.$trusted) { nodes = injectHTML(parentElement, index, data) } else { nodes = [window.document.createTextNode(data)] if (!parentElement.nodeName.match(voidElements)) parentElement.insertBefore(nodes[0], parentElement.childNodes[index] || null) } cached = "string number boolean".indexOf(typeof data) > -1 ? new data.constructor(data) : data cached.nodes = nodes } else if (cached.valueOf() !== data.valueOf() || shouldReattach === true) { nodes = cached.nodes if (!editable || editable !== window.document.activeElement) { if (data.$trusted) { clear(nodes, cached) nodes = injectHTML(parentElement, index, data) } else { //corner case: replacing the nodeValue of a text node that is a child of a textarea/contenteditable doesn't work //we need to update the value property of the parent textarea or the innerHTML of the contenteditable element instead if (parentTag === "textarea") parentElement.value = data else if (editable) editable.innerHTML = data else { if (nodes[0].nodeType == 1 || nodes.length > 1) { //was a trusted string clear(cached.nodes, cached) nodes = [window.document.createTextNode(data)] } parentElement.insertBefore(nodes[0], parentElement.childNodes[index] || null) nodes[0].nodeValue = data } } } cached = new data.constructor(data) cached.nodes = nodes } else cached.nodes.intact = true } return cached } function setAttributes(node, tag, dataAttrs, cachedAttrs, namespace) { var groups = {} for (var attrName in dataAttrs) { var dataAttr = dataAttrs[attrName] var cachedAttr = cachedAttrs[attrName] if (!(attrName in cachedAttrs) || (cachedAttr !== dataAttr) || node === window.document.activeElement) { cachedAttrs[attrName] = dataAttr if (attrName === "config") continue else if (typeof dataAttr == "function" && attrName.indexOf("on") == 0) { node[attrName] = autoredraw(dataAttr, node) } else if (attrName === "style" && typeof dataAttr == "object") { for (var rule in dataAttr) { if (cachedAttr == null || cachedAttr[rule] !== dataAttr[rule]) node.style[rule] = dataAttr[rule] } for (var rule in cachedAttr) { if (!(rule in dataAttr)) node.style[rule] = "" } } else if (namespace != null) { if (attrName === "href") node.setAttributeNS("http://www.w3.org/1999/xlink", "href", dataAttr) else if (attrName === "className") node.setAttribute("class", dataAttr) else node.setAttribute(attrName, dataAttr) } else if (attrName === "value" && tag === "input") { if (node.value !== dataAttr) node.value = dataAttr } else if (attrName in node && !(attrName == "list" || attrName == "style")) { node[attrName] = dataAttr } else node.setAttribute(attrName, dataAttr) } } return cachedAttrs } function clear(nodes, cached) { for (var i = nodes.length - 1; i > -1; i--) { if (nodes[i] && nodes[i].parentNode) { nodes[i].parentNode.removeChild(nodes[i]) cached = [].concat(cached) if (cached[i]) unload(cached[i]) } } if (nodes.length != 0) nodes.length = 0 } function unload(cached) { if (cached.configContext && typeof cached.configContext.onunload == "function") cached.configContext.onunload() if (cached.children) { if (type.call(cached.children) == "[object Array]") for (var i = 0; i < cached.children.length; i++) unload(cached.children[i]) else if (cached.children.tag) unload(cached.children) } } function injectHTML(parentElement, index, data) { var nextSibling = parentElement.childNodes[index] if (nextSibling) { var isElement = nextSibling.nodeType != 1 var placeholder = window.document.createElement("span") if (isElement) { parentElement.insertBefore(placeholder, nextSibling) placeholder.insertAdjacentHTML("beforebegin", data) parentElement.removeChild(placeholder) } else nextSibling.insertAdjacentHTML("beforebegin", data) } else parentElement.insertAdjacentHTML("beforeend", data) var nodes = [] while (parentElement.childNodes[index] !== nextSibling) { nodes.push(parentElement.childNodes[index]) index++ } return nodes } function flatten(data) { var flattened = [] for (var i = 0; i < data.length; i++) { var item = data[i] if (type.call(item) == "[object Array]") flattened.push.apply(flattened, flatten(item)) else flattened.push(item) } return flattened } function autoredraw(callback, object, group) { return function(e) { e = e || event m.redraw.strategy("diff") m.startComputation() try {return callback.call(object, e)} finally { if (!lastRedrawId) lastRedrawId = -1; m.endComputation() } } } var html var documentNode = { insertAdjacentHTML: function(_, data) { window.document.write(data) window.document.close() }, appendChild: function(node) { if (html === undefined) html = window.document.createElement("html") if (node.nodeName == "HTML") html = node else html.appendChild(node) if (window.document.documentElement && window.document.documentElement !== html) { window.document.replaceChild(html, window.document.documentElement) } else window.document.appendChild(html) }, insertBefore: function(node) { this.appendChild(node) }, childNodes: [] } var nodeCache = [], cellCache = {} m.render = function(root, cell, forceRecreation) { var configs = [] if (!root) throw new Error("Please ensure the DOM element exists before rendering a template into it.") var id = getCellCacheKey(root) var node = root == window.document || root == window.document.documentElement ? documentNode : root if (cellCache[id] === undefined) clear(node.childNodes) if (forceRecreation === true) reset(root) cellCache[id] = build(node, null, undefined, undefined, cell, cellCache[id], false, 0, null, undefined, configs) for (var i = 0; i < configs.length; i++) configs[i]() } function getCellCacheKey(element) { var index = nodeCache.indexOf(element) return index < 0 ? nodeCache.push(element) - 1 : index } m.trust = function(value) { value = new String(value) value.$trusted = true return value } function _prop(store) { var prop = function() { if (arguments.length) store = arguments[0] return store } prop.toJSON = function() { return store } return prop } m.prop = function (store) { if ((typeof store === 'object' || typeof store === 'function') && store !== null && typeof store.then === 'function') { var prop = _prop() newPromisedProp(prop, store).then(prop) return prop } return _prop(store) } var roots = [], modules = [], controllers = [], lastRedrawId = 0, computePostRedrawHook = null, prevented = false m.module = function(root, module) { var index = roots.indexOf(root) if (index < 0) index = roots.length var isPrevented = false if (controllers[index] && typeof controllers[index].onunload == "function") { var event = { preventDefault: function() {isPrevented = true} } controllers[index].onunload(event) } if (!isPrevented) { m.redraw.strategy("all") m.startComputation() roots[index] = root modules[index] = module controllers[index] = new module.controller m.endComputation() } } m.redraw = function(force) { var cancel = window.cancelAnimationFrame || window.clearTimeout var defer = window.requestAnimationFrame || window.setTimeout if (lastRedrawId && force !== true) { cancel(lastRedrawId) lastRedrawId = defer(redraw, 0) } else { redraw() lastRedrawId = defer(function() {lastRedrawId = null}, 0) } } m.redraw.strategy = m.prop() function redraw() { var mode = m.redraw.strategy() for (var i = 0; i < roots.length; i++) { if (controllers[i] && mode != "none") m.render(roots[i], modules[i].view(controllers[i]), mode == "all") } if (computePostRedrawHook) { computePostRedrawHook() computePostRedrawHook = null } lastRedrawId = null m.redraw.strategy("diff") } var pendingRequests = 0 m.startComputation = function() {pendingRequests++} m.endComputation = function() { pendingRequests = Math.max(pendingRequests - 1, 0) if (pendingRequests == 0) m.redraw() } m.withAttr = function(prop, withAttrCallback) { return function(e) { e = e || event var currentTarget = e.currentTarget || this withAttrCallback(prop in currentTarget ? currentTarget[prop] : currentTarget.getAttribute(prop)) } } //routing var modes = {pathname: "", hash: "#", search: "?"} var redirect = function() {}, routeParams = {}, currentRoute m.route = function() { if (arguments.length === 0) return currentRoute else if (arguments.length === 3 && typeof arguments[1] == "string") { var root = arguments[0], defaultRoute = arguments[1], router = arguments[2] redirect = function(source) { var path = currentRoute = normalizeRoute(source) if (!routeByValue(root, router, path)) { m.route(defaultRoute, true) } } var listener = m.route.mode == "hash" ? "onhashchange" : "onpopstate" window[listener] = function() { if (currentRoute != normalizeRoute(window.location[m.route.mode])) { redirect(window.location[m.route.mode]) } } computePostRedrawHook = setScroll window[listener]() } else if (arguments[0].addEventListener) { var element = arguments[0] var isInitialized = arguments[1] if (element.href.indexOf(modes[m.route.mode]) < 0) { element.href = window.location.pathname + modes[m.route.mode] + element.pathname } if (!isInitialized) { element.removeEventListener("click", routeUnobtrusive) element.addEventListener("click", routeUnobtrusive) } } else if (typeof arguments[0] == "string") { currentRoute = arguments[0] var querystring = typeof arguments[1] == "object" ? buildQueryString(arguments[1]) : null if (querystring) currentRoute += (currentRoute.indexOf("?") === -1 ? "?" : "&") + querystring var shouldReplaceHistoryEntry = (arguments.length == 3 ? arguments[2] : arguments[1]) === true if (window.history.pushState) { computePostRedrawHook = function() { window.history[shouldReplaceHistoryEntry ? "replaceState" : "pushState"](null, window.document.title, modes[m.route.mode] + currentRoute) setScroll() } redirect(modes[m.route.mode] + currentRoute) } else window.location[m.route.mode] = currentRoute } } m.route.param = function(key) {return routeParams[key]} m.route.mode = "search" function normalizeRoute(route) {return route.slice(modes[m.route.mode].length)} function routeByValue(root, router, path) { routeParams = {} var queryStart = path.indexOf("?") if (queryStart !== -1) { routeParams = parseQueryString(path.substr(queryStart + 1, path.length)) path = path.substr(0, queryStart) } for (var route in router) { if (route == path) { m.module(root, router[route]) return true } var matcher = new RegExp("^" + route.replace(/:[^\/]+?\.{3}/g, "(.*?)").replace(/:[^\/]+/g, "([^\\/]+)") + "\/?$") if (matcher.test(path)) { path.replace(matcher, function() { var keys = route.match(/:[^\/]+/g) || [] var values = [].slice.call(arguments, 1, -2) for (var i = 0; i < keys.length; i++) routeParams[keys[i].replace(/:|\./g, "")] = decodeURIComponent(values[i]) m.module(root, router[route]) }) return true } } } function routeUnobtrusive(e) { e = e || event if (e.ctrlKey || e.metaKey || e.which == 2) return e.preventDefault() m.route(e.currentTarget[m.route.mode].slice(modes[m.route.mode].length)) } function setScroll() { if (m.route.mode != "hash" && window.location.hash) window.location.hash = window.location.hash else window.scrollTo(0, 0) } function buildQueryString(object, prefix) { var str = [] for(var prop in object) { var key = prefix ? prefix + "[" + prop + "]" : prop, value = object[prop] str.push(typeof value == "object" ? buildQueryString(value, key) : encodeURIComponent(key) + "=" + encodeURIComponent(value)) } return str.join("&") } function parseQueryString(str) { var pairs = str.split("&"), params = {} for (var i = 0; i < pairs.length; i++) { var pair = pairs[i].split("=") params[decodeSpace(pair[0])] = pair[1] ? decodeSpace(pair[1]) : (pair.length === 1 ? true : "") } return params } function decodeSpace(string) { return decodeURIComponent(string.replace(/\+/g, " ")) } function reset(root) { var cacheKey = getCellCacheKey(root) clear(root.childNodes, cellCache[cacheKey]) cellCache[cacheKey] = undefined } function newPromisedProp(prop, promise) { prop.then = function () { var newProp = m.prop() return newPromisedProp(newProp, promise.then.apply(promise, arguments).then(newProp)) } prop.promise = prop prop.resolve = function (val) { prop(val) promise = promise.resolve.apply(promise, arguments) return prop } prop.reject = function () { promise = promise.reject.apply(promise, arguments) return prop } return prop } m.deferred = function () { return newPromisedProp(m.prop(), new Deferred()) } // Promiz.mithril.js | Zolmeister | MIT function Deferred(fn, er) { // states // 0: pending // 1: resolving // 2: rejecting // 3: resolved // 4: rejected var self = this, state = 0, val = 0, next = []; self['promise'] = self self['resolve'] = function (v) { if (!state) { val = v state = 1 fire() } return this } self['reject'] = function (v) { if (!state) { val = v state = 2 fire() } return this } self['then'] = function (fn, er) { var d = new Deferred(fn, er) if (state == 3) { d.resolve(val) } else if (state == 4) { d.reject(val) } else { next.push(d) } return d } var finish = function (type) { state = type || 4 next.map(function (p) { state == 3 && p.resolve(val) || p.reject(val) }) } // ref : reference to 'then' function // cb, ec, cn : successCallback, failureCallback, notThennableCallback function thennable (ref, cb, ec, cn) { if ((typeof val == 'object' || typeof val == 'function') && typeof ref == 'function') { try { // cnt protects against abuse calls from spec checker var cnt = 0 ref.call(val, function (v) { if (cnt++) return val = v cb() }, function (v) { if (cnt++) return val = v ec() }) } catch (e) { val = e ec() } } else { cn() } }; function fire() { // check if it's a thenable var ref; try { ref = val && val.then } catch (e) { val = e state = 2 return fire() } thennable(ref, function () { state = 1 fire() }, function () { state = 2 fire() }, function () { try { if (state == 1 && typeof fn == 'function') { val = fn(val) } else if (state == 2 && typeof er == 'function') { val = er(val) state = 1 } } catch (e) { val = e return finish() } if (val == self) { val = TypeError() finish() } else thennable(ref, function () { finish(3) }, finish, function () { finish(state == 1 && 3) }) }) } } m.sync = function(args) { var method = "resolve" function synchronizer(pos, resolved) { return function(value) { results[pos] = value if (!resolved) method = "reject" if (--outstanding == 0) { deferred.promise(results) deferred[method](results) } return value } } var deferred = m.deferred() var outstanding = args.length var results = new Array(outstanding) if (args.length > 0) { for (var i = 0; i < args.length; i++) { args[i].then(synchronizer(i, true), synchronizer(i, false)) } } else deferred.resolve() return deferred.promise } function identity(value) {return value} function serializeArray(array, prefix){ var idx, out = []; for(idx in array){ var formatted = (prefix ? prefix : "") + "[]"; if(prefix && typeof array[idx] === "object") formatted = formatted.replace(/\[\]$/i, "[" + idx + "]"); if(typeof array[idx] === "object" && JSON.stringify(array[idx]) === "{}"){ continue; } if(array[idx] instanceof Array) out.push(serializeArray(array[idx], formatted)); else if(typeof array[idx] === "object") out.push(serializeObject(array[idx], formatted)); else out.push(encodeURIComponent(formatted) + "=" + encodeURIComponent(array[idx])); } return out.join("&"); } function serializeObject(obj, prefix) { var key, out = []; for(key in obj){ var formatted = prefix ? prefix + "[" + key + "]" : key; if(obj[key] instanceof Array){ if(obj[key].length < 1) continue; out.push(serializeArray(obj[key], formatted)); }else if(typeof obj[key] === "object"){ if(JSON.stringify(obj[key]) === "{}") continue; out.push(serializeObject(obj[key], formatted)); }else{ out.push(encodeURIComponent(formatted) + "=" + encodeURIComponent(obj[key])); } }; return out.join("&"); } function ajax(options) { if (options.dataType && options.dataType.toLowerCase() === "jsonp") { var callbackKey = "mithril_callback_" + new Date().getTime() + "_" + (Math.round(Math.random() * 1e16)).toString(36); var script = window.document.createElement("script"); window[callbackKey] = function(resp){ delete window[callbackKey]; window.document.body.removeChild(script); options.onload({ type: "load", target: { responseText: resp } }); }; script.onerror = function(e){ delete window[callbackKey]; window.document.body.removeChild(script); options.onerror({ type: "error", target: { status: 500, responseText: JSON.stringify({ error: "Error making jsonp request" }) } }); e.preventDefault(); e.stopPropagation(); }; script.onload = function(e){ e.preventDefault(); e.stopPropagation(); }; script.src = options.url + (options.url.indexOf("?") > 0 ? "&" : "?") + (options.callbackKey ? options.callbackKey : "callback") + "=" + callbackKey + "&" + serializeObject(options.data || {}); window.document.body.appendChild(script); }else{ var xhr = new window.XMLHttpRequest xhr.open(options.method, options.url, true, options.user, options.password) xhr.onreadystatechange = function() { if (xhr.readyState === 4) { if (xhr.status >= 200 && xhr.status < 300) options.onload({type: "load", target: xhr}) else options.onerror({type: "error", target: xhr}) } } if (options.serialize == JSON.stringify && options.method != "GET") { xhr.setRequestHeader("Content-Type", "application/json; charset=utf-8"); } if (typeof options.config == "function") { var maybeXhr = options.config(xhr, options) if (maybeXhr != null) xhr = maybeXhr } xhr.send(options.method == "GET" ? "" : options.data) return xhr } } function bindData(xhrOptions, data, serialize) { if (data && Object.keys(data).length > 0) { if (xhrOptions.method == "GET") { xhrOptions.url = xhrOptions.url + (xhrOptions.url.indexOf("?") < 0 ? "?" : "&") + buildQueryString(data) } else xhrOptions.data = serialize(data) } return xhrOptions } function parameterizeUrl(url, data) { var tokens = url.match(/:[a-z]\w+/gi) if (tokens && data) { for (var i = 0; i < tokens.length; i++) { var key = tokens[i].slice(1) url = url.replace(tokens[i], data[key]) delete data[key] } } return url } m.request = function(xhrOptions) { if (xhrOptions.background !== true) m.startComputation() var deferred = m.deferred() var serialize = xhrOptions.serialize = xhrOptions.dataType && xhrOptions.dataType.toLowerCase() === "jsonp" ? identity : xhrOptions.serialize || JSON.stringify var deserialize = xhrOptions.deserialize = xhrOptions.dataType && xhrOptions.dataType.toLowerCase() === "jsonp" ? identity : xhrOptions.deserialize || JSON.parse var extract = xhrOptions.extract || function(xhr) { return xhr.responseText.length === 0 && deserialize === JSON.parse ? null : xhr.responseText } xhrOptions.url = parameterizeUrl(xhrOptions.url, xhrOptions.data) xhrOptions = bindData(xhrOptions, xhrOptions.data, serialize) xhrOptions.onload = xhrOptions.onerror = function(e) { try { e = e || event var unwrap = (e.type == "load" ? xhrOptions.unwrapSuccess : xhrOptions.unwrapError) || identity var response = unwrap(deserialize(extract(e.target, xhrOptions))) if (e.type == "load") { if (type.call(response) == "[object Array]" && xhrOptions.type) { for (var i = 0; i < response.length; i++) response[i] = new xhrOptions.type(response[i]) } else if (xhrOptions.type) response = new xhrOptions.type(response) } deferred[e.type == "load" ? "resolve" : "reject"](response) } catch (e) { if (e instanceof SyntaxError) throw new SyntaxError("Could not parse HTTP response. See http://lhorie.github.io/mithril/mithril.request.html#using-variable-data-formats") else if (type.call(e) == "[object Error]" && e.constructor !== Error) throw e else deferred.reject(e) } if (xhrOptions.background !== true) m.endComputation() } ajax(xhrOptions) return deferred.promise } //testing API m.deps = function(mock) {return window = mock} //for internal testing only, do not use `m.deps.factory` m.deps.factory = app return m }(typeof window != "undefined" ? window : {}) if (typeof module != "undefined" && module !== null) module.exports = m if (typeof define == "function" && define.amd) define(function() {return m}) ;;; function test(condition) { var duration = 0 var start = 0 var result = true test.total++ if (typeof performance != "undefined") { start = performance.now() } try { if (!condition()) throw new Error() } catch (e) { result = false console.error(e) test.failures.push(condition) } if (typeof performance != "undefined") { duration = performance.now() - start } test_obj = { name: "" + test.total, result: result, duration: duration } if (typeof window != "undefined") { if (!result) { window.global_test_results.tests.push(test_obj) } window.global_test_results.duration += duration if (result) { window.global_test_results.passed++ } else { window.global_test_results.failed++ } } } test.total = 0 test.failures = [] test.print = function(print) { for (var i = 0; i < test.failures.length; i++) { print(test.failures[i].toString()) } print("tests: " + test.total + "\nfailures: " + test.failures.length) if (test.failures.length > 0) { throw new Error(test.failures.length + " tests did not pass") } } var mock = {} mock.window = new function() { var window = {} window.document = {} window.document.childNodes = [] window.document.createElement = function(tag) { return { style: {}, childNodes: [], nodeType: 1, nodeName: tag.toUpperCase(), appendChild: window.document.appendChild, removeChild: window.document.removeChild, replaceChild: window.document.replaceChild, insertBefore: function(node, reference) { node.parentNode = this var referenceIndex = this.childNodes.indexOf(reference) if (referenceIndex < 0) this.childNodes.push(node) else { var index = this.childNodes.indexOf(node) if (index > -1) this.childNodes.splice(index, 1) this.childNodes.splice(referenceIndex, 0, node) } }, insertAdjacentHTML: function(position, html) { //todo: accept markup if (position == "beforebegin") { this.parentNode.insertBefore(window.document.createTextNode(html), this) } else if (position == "beforeend") { this.appendChild(window.document.createTextNode(html)) } }, setAttribute: function(name, value) { this[name] = value.toString() }, setAttributeNS: function(namespace, name, value) { this.namespaceURI = namespace this[name] = value.toString() }, getAttribute: function(name, value) { return this[name] } } } window.document.createElementNS = function(namespace, tag) { var element = window.document.createElement(tag) element.namespaceURI = namespace return element } window.document.createTextNode = function(text) { return {nodeValue: text.toString()} } window.document.documentElement = window.document.createElement("html") window.document.replaceChild = function(newChild, oldChild) { var index = this.childNodes.indexOf(oldChild) if (index > -1) this.childNodes.splice(index, 1, newChild) else this.childNodes.push(newChild) newChild.parentNode = this oldChild.parentNode = null } window.document.appendChild = function(child) { var index = this.childNodes.indexOf(child) if (index > -1) this.childNodes.splice(index, 1) this.childNodes.push(child) child.parentNode = this } window.document.removeChild = function(child) { var index = this.childNodes.indexOf(child) this.childNodes.splice(index, 1) child.parentNode = null } window.document.getElementsByTagName = function(name){ name = name.toLowerCase(); var out = []; var traverse = function(node){ if(node.childNodes && node.childNodes.length > 0){ node.childNodes.forEach(function(curr){ if(curr.nodeName.toLowerCase() === name) out.push(curr); traverse(curr); }); } }; traverse(window.document); return out; } window.scrollTo = function() {} window.cancelAnimationFrame = function() {} window.requestAnimationFrame = function(callback) { window.requestAnimationFrame.$callback = callback return window.requestAnimationFrame.$id++ } window.requestAnimationFrame.$id = 1 window.requestAnimationFrame.$resolve = function() { if (window.requestAnimationFrame.$callback) window.requestAnimationFrame.$callback() window.requestAnimationFrame.$callback = null } window.XMLHttpRequest = new function() { var request = function() { this.$headers = {} this.setRequestHeader = function(key, value) { this.$headers[key] = value } this.open = function(method, url) { this.method = method this.url = url } this.send = function() { this.responseText = JSON.stringify(this) this.readyState = 4 this.status = 200 request.$instances.push(this) } } request.$instances = [] return request } window.location = {search: "", pathname: "", hash: ""}, window.history = {} window.history.pushState = function(data, title, url) { window.location.pathname = window.location.search = window.location.hash = url }, window.history.replaceState = function(data, title, url) { window.location.pathname = window.location.search = window.location.hash = url } return window } function testMithril(mock) { m.deps(mock) //m test(function() {return m("div").tag === "div"}) test(function() {return m(".foo").tag === "div"}) test(function() {return m(".foo").attrs.className === "foo"}) test(function() {return m("[title=bar]").tag === "div"}) test(function() {return m("[title=bar]").attrs.title === "bar"}) test(function() {return m("[title=\'bar\']").attrs.title === "bar"}) test(function() {return m("[title=\"bar\"]").attrs.title === "bar"}) test(function() {return m("div", "test").children === "test"}) test(function() {return m("div", ["test"]).children[0] === "test"}) test(function() {return m("div", {title: "bar"}, "test").attrs.title === "bar"}) test(function() {return m("div", {title: "bar"}, "test").children === "test"}) test(function() {return m("div", {title: "bar"}, ["test"]).children[0] === "test"}) test(function() {return m("div", {title: "bar"}, m("div")).children.tag === "div"}) test(function() {return m("div", {title: "bar"}, [m("div")]).children[0].tag === "div"}) test(function() {return m("div", ["a", "b"]).children.length === 2}) test(function() {return m("div", [m("div")]).children[0].tag === "div"}) test(function() {return m("div", m("div")).children.tag === "div"}) //yes, this is expected behavior: see method signature test(function() {return m("div", [undefined]).tag === "div"}) test(function() {return m("div", [{foo: "bar"}])}) //as long as it doesn't throw errors, it's fine test(function() {return m("svg", [m("g")])}) test(function() {return m("svg", [m("a[href='http://google.com']")])}) test(function() {return m(".foo", {class: "bar"}).attrs.class == "foo bar"}) test(function() {return m(".foo", {className: "bar"}).attrs.className == "foo bar"}) //m.module test(function() { mock.requestAnimationFrame.$resolve() var root1 = mock.document.createElement("div") m.module(root1, { controller: function() {this.value = "test1"}, view: function(ctrl) {return ctrl.value} }) var root2 = mock.document.createElement("div") m.module(root2, { controller: function() {this.value = "test2"}, view: function(ctrl) {return ctrl.value} }) mock.requestAnimationFrame.$resolve() return root1.childNodes[0].nodeValue === "test1" && root2.childNodes[0].nodeValue === "test2" }) //m.withAttr test(function() { var value var handler = m.withAttr("test", function(data) {value = data}) handler({currentTarget: {test: "foo"}}) return value === "foo" }) //m.trust test(function() {return m.trust("test").valueOf() === "test"}) //m.render test(function() { var root = mock.document.createElement("div") m.render(root, "test") return root.childNodes[0].nodeValue === "test" }) test(function() { var root = mock.document.createElement("div") m.render(root, m("div", {class: "a"})) var elementBefore = root.childNodes[0] m.render(root, m("div", {class: "b"})) var elementAfter = root.childNodes[0] return elementBefore === elementAfter }) test(function() { var root = mock.document.createElement("div") m.render(root, m(".a")) var elementBefore = root.childNodes[0] m.render(root, m(".b")) var elementAfter = root.childNodes[0] return elementBefore === elementAfter }) test(function() { var root = mock.document.createElement("div") m.render(root, m("div", {id: "a"})) var elementBefore = root.childNodes[0] m.render(root, m("div", {title: "b"})) var elementAfter = root.childNodes[0] return elementBefore !== elementAfter }) test(function() { var root = mock.document.createElement("div") m.render(root, m("#a")) var elementBefore = root.childNodes[0] m.render(root, m("[title=b]")) var elementAfter = root.childNodes[0] return elementBefore !== elementAfter }) test(function() { var root = mock.document.createElement("div") m.render(root, m("#a")) var elementBefore = root.childNodes[0] m.render(root, "test") var elementAfter = root.childNodes[0] return elementBefore !== elementAfter }) test(function() { var root = mock.document.createElement("div") m.render(root, m("div", [undefined])) return root.childNodes[0].childNodes[0].nodeValue === "" }) test(function() { var root = mock.document.createElement("div") m.render(root, m("svg", [m("g")])) var g = root.childNodes[0].childNodes[0] return g.nodeName === "G" && g.namespaceURI == "http://www.w3.org/2000/svg" }) test(function() { var root = mock.document.createElement("div") m.render(root, m("svg", [m("a[href='http://google.com']")])) return root.childNodes[0].childNodes[0].nodeName === "A" }) test(function() { var root = mock.document.createElement("div") m.render(root, m("div.classname", [m("a", {href: "/first"})])) m.render(root, m("div", [m("a", {href: "/second"})])) return root.childNodes[0].childNodes.length == 1 }) test(function() { var root = mock.document.createElement("div") m.render(root, m("ul", [m("li")])) m.render(root, m("ul", [m("li"), undefined])) return root.childNodes[0].childNodes[1].nodeValue === "" }) test(function() { var root = mock.document.createElement("div") m.render(root, m("ul", [m("li"), m("li")])) m.render(root, m("ul", [m("li"), undefined])) return root.childNodes[0].childNodes.length == 2 && root.childNodes[0].childNodes[1].nodeValue === "" }) test(function() { var root = mock.document.createElement("div") m.render(root, m("ul", [m("li")])) m.render(root, m("ul", [undefined])) return root.childNodes[0].childNodes[0].nodeValue === "" }) test(function() { var root = mock.document.createElement("div") m.render(root, m("ul", [m("li")])) m.render(root, m("ul", [{}])) return root.childNodes[0].childNodes.length === 0 }) test(function() { var root = mock.document.createElement("div") m.render(root, m("ul", [m("li", [m("a")])])) m.render(root, m("ul", [{subtree: "retain"}])) return root.childNodes[0].childNodes[0].childNodes[0].nodeName === "A" }) test(function() { //https://github.com/lhorie/mithril.js/issues/43 var root = mock.document.createElement("div") m.render(root, m("a", {config: m.route}, "test")) m.render(root, m("a", {config: m.route}, "test")) return root.childNodes[0].childNodes[0].nodeValue === "test" }) test(function() { //https://github.com/lhorie/mithril.js/issues/29 var root = mock.document.createElement("div") var list = [false, false] m.render(root, list.reverse().map(function(flag, index) { return m("input[type=checkbox]", {onclick: m.withAttr("checked", function(value) {list[index] = value}), checked: flag}) })) mock.document.activeElement = root.childNodes[0] root.childNodes[0].checked = true root.childNodes[0].onclick({currentTarget: {checked: true}}) m.render(root, list.reverse().map(function(flag, index) { return m("input[type=checkbox]", {onclick: m.withAttr("checked", function(value) {list[index] = value}), checked: flag}) })) mock.document.activeElement = null return root.childNodes[0].checked === false && root.childNodes[1].checked === true }) test(function() { //https://github.com/lhorie/mithril.js/issues/44 (1) var root = mock.document.createElement("div") m.render(root, m("#foo", [null, m("#bar")])) m.render(root, m("#foo", ["test", m("#bar")])) return root.childNodes[0].childNodes[0].nodeValue === "test" }) test(function() { //https://github.com/lhorie/mithril.js/issues/44 (2) var root = mock.document.createElement("div") m.render(root, m("#foo", [null, m("#bar")])) m.render(root, m("#foo", [m("div"), m("#bar")])) return root.childNodes[0].childNodes[0].nodeName === "DIV" }) test(function() { //https://github.com/lhorie/mithril.js/issues/44 (3) var root = mock.document.createElement("div") m.render(root, m("#foo", ["test", m("#bar")])) m.render(root, m("#foo", [m("div"), m("#bar")])) return root.childNodes[0].childNodes[0].nodeName === "DIV" }) test(function() { //https://github.com/lhorie/mithril.js/issues/44 (4) var root = mock.document.createElement("div") m.render(root, m("#foo", [m("div"), m("#bar")])) m.render(root, m("#foo", ["test", m("#bar")])) return root.childNodes[0].childNodes[0].nodeValue === "test" }) test(function() { //https://github.com/lhorie/mithril.js/issues/44 (5) var root = mock.document.createElement("div") m.render(root, m("#foo", [m("#bar")])) m.render(root, m("#foo", [m("#bar"), [m("#baz")]])) return root.childNodes[0].childNodes[1].id === "baz" }) test(function() { //https://github.com/lhorie/mithril.js/issues/48 var root = mock.document m.render(root, m("html", [m("#foo")])) var result = root.childNodes[0].childNodes[0].id === "foo" root.childNodes = [mock.document.createElement("html")] return result }) test(function() { //https://github.com/lhorie/mithril.js/issues/49 var root = mock.document.createElement("div") m.render(root, m("a", "test")) m.render(root, m("a.foo", "test")) return root.childNodes[0].childNodes[0].nodeValue === "test" }) test(function() { //https://github.com/lhorie/mithril.js/issues/49 var root = mock.document.createElement("div") m.render(root, m("a.foo", "test")) m.render(root, m("a", "test")) return root.childNodes[0].childNodes[0].nodeValue === "test" }) test(function() { //https://github.com/lhorie/mithril.js/issues/49 var root = mock.document.createElement("div") m.render(root, m("a.foo", "test")) m.render(root, m("a", "test1")) return root.childNodes[0].childNodes[0].nodeValue === "test1" }) test(function() { //https://github.com/lhorie/mithril.js/issues/49 var root = mock.document.createElement("div") m.render(root, m("a", "test")) m.render(root, m("a", "test1")) return root.childNodes[0].childNodes[0].nodeValue === "test1" }) test(function() { //https://github.com/lhorie/mithril.js/issues/50 var root = mock.document.createElement("div") m.render(root, m("#foo", [[m("div", "a"), m("div", "b")], m("#bar")])) return root.childNodes[0].childNodes[1].childNodes[0].nodeValue === "b" }) test(function() { //https://github.com/lhorie/mithril.js/issues/50 var root = mock.document.createElement("div") m.render(root, m("#foo", [[m("div", "a"), m("div", "b")], m("#bar")])) m.render(root, m("#foo", [[m("div", "a"), m("div", "b"), m("div", "c")], m("#bar")])) return root.childNodes[0].childNodes[2].childNodes[0].nodeValue === "c" }) test(function() { //https://github.com/lhorie/mithril.js/issues/50 var root = mock.document.createElement("div") m.render(root, m("#foo", [[m("div", "a"), m("div", "b")], [m("div", "c"), m("div", "d")], m("#bar")])) return root.childNodes[0].childNodes[3].childNodes[0].nodeValue === "d" && root.childNodes[0].childNodes[4].id === "bar" }) test(function() { //https://github.com/lhorie/mithril.js/issues/50 var root = mock.document.createElement("div") m.render(root, m("#foo", [[m("div", "a"), m("div", "b")], "test"])) return root.childNodes[0].childNodes[1].childNodes[0].nodeValue === "b" && root.childNodes[0].childNodes[2].nodeValue === "test" }) test(function() { //https://github.com/lhorie/mithril.js/issues/50 var root = mock.document.createElement("div") m.render(root, m("#foo", [["a", "b"], "test"])) return root.childNodes[0].childNodes[1].nodeValue === "b" && root.childNodes[0].childNodes[2].nodeValue === "test" }) test(function() { //https://github.com/lhorie/mithril.js/issues/51 var root = mock.document.createElement("div") m.render(root, m("main", [m("button"), m("article", [m("section"), m("nav")])])) m.render(root, m("main", [m("button"), m("article", [m("span"), m("nav")])])) return root.childNodes[0].childNodes[1].childNodes[0].nodeName === "SPAN" }) test(function() { //https://github.com/lhorie/mithril.js/issues/51 var root = mock.document.createElement("div") m.render(root, m("main", [m("button"), m("article", [m("section"), m("nav")])])) m.render(root, m("main", [m("button"), m("article", ["test", m("nav")])])) return root.childNodes[0].childNodes[1].childNodes[0].nodeValue === "test" }) test(function() { //https://github.com/lhorie/mithril.js/issues/51 var root = mock.document.createElement("div") m.render(root, m("main", [m("button"), m("article", [m("section"), m("nav")])])) m.render(root, m("main", [m("button"), m("article", [m.trust("test"), m("nav")])])) return root.childNodes[0].childNodes[1].childNodes[0].nodeValue === "test" }) test(function() { //https://github.com/lhorie/mithril.js/issues/55 var root = mock.document.createElement("div") m.render(root, m("#a")) var elementBefore = root.childNodes[0] m.render(root, m("#b")) var elementAfter = root.childNodes[0] return elementBefore !== elementAfter }) test(function() { //https://github.com/lhorie/mithril.js/issues/56 var root = mock.document.createElement("div") m.render(root, [null, "foo"]) m.render(root, ["bar"]) return root.childNodes.length == 1 }) test(function() { //https://github.com/lhorie/mithril.js/issues/56 var root = mock.document.createElement("div") m.render(root, m("div", "foo")) return root.childNodes.length == 1 }) test(function() { var root = mock.document.createElement("div") m.render(root, m("div", [m("button"), m("ul")])) var valueBefore = root.childNodes[0].childNodes[0].nodeName m.render(root, m("div", [undefined, m("ul")])) var valueAfter = root.childNodes[0].childNodes[0].nodeValue return valueBefore === "BUTTON" && valueAfter === "" }) test(function() { var root = mock.document.createElement("div") m.render(root, m("div", [m("ul"), undefined])) var valueBefore1 = root.childNodes[0].childNodes[0].nodeName var valueBefore2 = root.childNodes[0].childNodes[1].nodeValue m.render(root, m("div", [undefined, m("ul")])) var valueAfter1 = root.childNodes[0].childNodes[0].nodeValue var valueAfter2 = root.childNodes[0].childNodes[1].nodeName return valueBefore1 === "UL" && valueAfter1 === "" && valueBefore2 === "" && valueAfter2 === "UL" }) test(function() { //https://github.com/lhorie/mithril.js/issues/79 var root = mock.document.createElement("div") m.render(root, m("div", {style: {background: "red"}})) var valueBefore = root.childNodes[0].style.background m.render(root, m("div", {style: {}})) var valueAfter = root.childNodes[0].style.background return valueBefore === "red" && valueAfter === "" }) test(function() { var root = mock.document.createElement("div") m.render(root, m("div[style='background:red']")) return root.childNodes[0].style === "background:red" }) test(function() { var root = mock.document.createElement("div") m.render(root, m("div", {style: {background: "red"}})) var valueBefore = root.childNodes[0].style.background m.render(root, m("div", {})) var valueAfter = root.childNodes[0].style.background return valueBefore === "red" && valueAfter === undefined }) test(function() { var root = mock.document.createElement("div") var module = {}, unloaded = false module.controller = function() { this.onunload = function() {unloaded = true} } module.view = function() {} m.module(root, module) m.module(root, {controller: function() {}, view: function() {}}) return unloaded === true }) test(function() { //https://github.com/lhorie/mithril.js/issues/87 var root = mock.document.createElement("div") m.render(root, m("div", [[m("a"), m("a")], m("button")])) m.render(root, m("div", [[m("a")], m("button")])) return root.childNodes[0].childNodes.length == 2 && root.childNodes[0].childNodes[1].nodeName == "BUTTON" }) test(function() { //https://github.com/lhorie/mithril.js/issues/87 var root = mock.document.createElement("div") m.render(root, m("div", [m("a"), m("b"), m("button")])) m.render(root, m("div", [m("a"), m("button")])) return root.childNodes[0].childNodes.length == 2 && root.childNodes[0].childNodes[1].nodeName == "BUTTON" }) test(function() { //https://github.com/lhorie/mithril.js/issues/99 var root = mock.document.createElement("div") m.render(root, m("div", [m("img"), m("h1")])) m.render(root, m("div", [m("a")])) return root.childNodes[0].childNodes.length == 1 && root.childNodes[0].childNodes[0].nodeName == "A" }) test(function() { //https://github.com/lhorie/mithril.js/issues/120 var root = mock.document.createElement("div") m.render(root, m("div", ["a", "b", "c", "d"])) m.render(root, m("div", [["d", "e"]])) var children = root.childNodes[0].childNodes return children.length == 2 && children[0].nodeValue == "d" && children[1].nodeValue == "e" }) test(function() { //https://github.com/lhorie/mithril.js/issues/120 var root = mock.document.createElement("div") m.render(root, m("div", [["a", "b", "c", "d"]])) m.render(root, m("div", ["d", "e"])) var children = root.childNodes[0].childNodes return children.length == 2 && children[0].nodeValue == "d" && children[1].nodeValue == "e" }) test(function() { //https://github.com/lhorie/mithril.js/issues/120 var root = mock.document.createElement("div") m.render(root, m("div", ["x", [["a"], "b", "c", "d"]])) m.render(root, m("div", ["d", ["e"]])) var children = root.childNodes[0].childNodes return children.length == 2 && children[0].nodeValue == "d" && children[1].nodeValue == "e" }) test(function() { //https://github.com/lhorie/mithril.js/issues/120 var root = mock.document.createElement("div") m.render(root, m("div", ["b"])) m.render(root, m("div", [["e"]])) var children = root.childNodes[0].childNodes return children.length == 1 && children[0].nodeValue == "e" }) test(function() { //https://github.com/lhorie/mithril.js/issues/120 var root = mock.document.createElement("div") m.render(root, m("div", ["a", ["b"]])) m.render(root, m("div", ["d", [["e"]]])) var children = root.childNodes[0].childNodes return children.length == 2 && children[0].nodeValue == "d" && children[1].nodeValue == "e" }) test(function() { //https://github.com/lhorie/mithril.js/issues/120 var root = mock.document.createElement("div") m.render(root, m("div", ["a", [["b"]]])) m.render(root, m("div", ["d", ["e"]])) var children = root.childNodes[0].childNodes return children.length == 2 && children[0].nodeValue == "d" && children[1].nodeValue == "e" }) test(function() { //https://github.com/lhorie/mithril.js/issues/120 var root = mock.document.createElement("div") m.render(root, m("div", ["a", [["b"], "c"]])) m.render(root, m("div", ["d", [[["e"]], "x"]])) var children = root.childNodes[0].childNodes return children.length == 3 && children[0].nodeValue == "d" && children[1].nodeValue == "e" }) test(function() { var root = mock.document.createElement("div") var success = false m.render(root, m("div", {config: function(elem, isInitialized, ctx) {ctx.data = 1}})) m.render(root, m("div", {config: function(elem, isInitialized, ctx) {success = ctx.data === 1}})) return success }) test(function() { var root = mock.document.createElement("div") var index = 0; var success = true; var statefulConfig = function(elem, isInitialized, ctx) {ctx.data = index++} var node = m("div", {config: statefulConfig}); m.render(root, [node, node]); index = 0; var checkConfig = function(elem, isInitialized, ctx) { success = success && (ctx.data === index++) } node = m("div", {config: checkConfig}); m.render(root, [node, node]); return success; }) test(function() { var root = mock.document.createElement("div") var parent m.render(root, m("div", m("a", { config: function(el) {parent = el.parentNode.parentNode} }))); return parent === root }) test(function() { var root = mock.document.createElement("div") var count = 0 m.render(root, m("div", m("a", { config: function(el) { var island = mock.document.createElement("div") count++ if (count > 2) throw "too much recursion..." m.render(island, m("div")) } }))); return count == 1 }) test(function() { //https://github.com/lhorie/mithril.js/issues/129 var root = mock.document.createElement("div") m.render(root, m("div", [["foo", "bar"], ["foo", "bar"], ["foo", "bar"]])); m.render(root, m("div", ["asdf", "asdf2", "asdf3"])); return true }) test(function() { //https://github.com/lhorie/mithril.js/issues/98 //insert at beginning var root = mock.document.createElement("div") m.render(root, [m("a", {key: 1}), m("a", {key: 2}), m("a", {key: 3})]) var firstBefore = root.childNodes[0] m.render(root, [m("a", {key: 4}), m("a", {key: 1}), m("a", {key: 2}), m("a", {key: 3})]) var firstAfter = root.childNodes[1] return firstBefore == firstAfter && root.childNodes[0].key == 4 && root.childNodes.length == 4 }) test(function() { //https://github.com/lhorie/mithril.js/issues/98 var root = mock.document.createElement("div") m.render(root, [m("a", {key: 1}), m("a", {key: 2}), m("a", {key: 3})]) var firstBefore = root.childNodes[0] m.render(root, [m("a", {key: 4}), m("a", {key: 1}), m("a", {key: 2})]) var firstAfter = root.childNodes[1] return firstBefore == firstAfter && root.childNodes[0].key == 4 && root.childNodes.length == 3 }) test(function() { //https://github.com/lhorie/mithril.js/issues/98 var root = mock.document.createElement("div") m.render(root, [m("a", {key: 1}), m("a", {key: 2}), m("a", {key: 3})]) var firstBefore = root.childNodes[1] m.render(root, [m("a", {key: 2}), m("a", {key: 3}), m("a", {key: 4})]) var firstAfter = root.childNodes[0] return firstBefore == firstAfter && root.childNodes[0].key === "2" && root.childNodes.length === 3 }) test(function() { //https://github.com/lhorie/mithril.js/issues/98 var root = mock.document.createElement("div") m.render(root, [m("a", {key: 1}), m("a", {key: 2}), m("a", {key: 3}), m("a", {key: 4}), m("a", {key: 5})]) var firstBefore = root.childNodes[0] var secondBefore = root.childNodes[1] var fourthBefore = root.childNodes[3] m.render(root, [m("a", {key: 4}), m("a", {key: 10}), m("a", {key: 1}), m("a", {key: 2})]) var firstAfter = root.childNodes[2] var secondAfter = root.childNodes[3] var fourthAfter = root.childNodes[0] return firstBefore === firstAfter && secondBefore === secondAfter && fourthBefore === fourthAfter && root.childNodes[1].key == "10" && root.childNodes.length === 4 }) test(function() { //https://github.com/lhorie/mithril.js/issues/98 var root = mock.document.createElement("div") m.render(root, [m("a", {key: 1}), m("a", {key: 2}), m("a", {key: 3}), m("a", {key: 4}), m("a", {key: 5})]) var firstBefore = root.childNodes[0] var secondBefore = root.childNodes[1] var fourthBefore = root.childNodes[3] m.render(root, [m("a", {key: 4}), m("a", {key: 10}), m("a", {key: 2}), m("a", {key: 1}), m("a", {key: 6}), m("a", {key: 7})]) var firstAfter = root.childNodes[3] var secondAfter = root.childNodes[2] var fourthAfter = root.childNodes[0] return firstBefore === firstAfter && secondBefore === secondAfter && fourthBefore === fourthAfter && root.childNodes[1].key == "10" && root.childNodes[4].key == "6" && root.childNodes[5].key == "7" && root.childNodes.length === 6 }) test(function() { //https://github.com/lhorie/mithril.js/issues/149 var root = mock.document.createElement("div") m.render(root, [m("a", {key: 1}), m("a", {key: 2}), m("a"), m("a", {key: 4}), m("a", {key: 5})]) var firstBefore = root.childNodes[0] var secondBefore = root.childNodes[1] var thirdBefore = root.childNodes[2] var fourthBefore = root.childNodes[3] var fifthBefore = root.childNodes[4] m.render(root, [m("a", {key: 4}), m("a", {key: 5}), m("a"), m("a", {key: 1}), m("a", {key: 2})]) var firstAfter = root.childNodes[3] var secondAfter = root.childNodes[4] var thirdAfter = root.childNodes[2] var fourthAfter = root.childNodes[0] var fifthAfter = root.childNodes[1] return firstBefore === firstAfter && secondBefore === secondAfter && thirdBefore === thirdAfter && fourthBefore === fourthAfter && fifthBefore === fifthAfter }) test(function() { //https://github.com/lhorie/mithril.js/issues/134 var root = mock.document.createElement("div") m.render(root, m("div", {contenteditable: true}, "test")) mock.document.activeElement = root.childNodes[0] m.render(root, m("div", {contenteditable: true}, "test1")) m.render(root, m("div", {contenteditable: false}, "test2")) return root.childNodes[0].childNodes[0].nodeValue === "test2" }) test(function() { //https://github.com/lhorie/mithril.js/issues/136 var root = mock.document.createElement("div") m.render(root, m("textarea", ["test"])) m.render(root, m("textarea", ["test1"])) return root.childNodes[0].value === "test1" }) test(function() { var root = mock.document.createElement("div") var unloaded = 0 m.render(root, [ m("div", { key: 1, config: function(el, init, ctx) { ctx.onunload = function() { unloaded++ } } }) ]) m.render(root, [ m("div", {key: 2}), m("div", { key: 1, config: function(el, init, ctx) { ctx.onunload = function() { unloaded++ } } }) ]) return unloaded == 0 }) test(function() { var root = mock.document.createElement("div") var unloadedParent = 0 var unloadedChild = 0 var configParent = function(el, init, ctx) { ctx.onunload = function() { unloadedParent++ } } var configChild = function(el, init, ctx) { ctx.onunload = function() { unloadedChild++ } } var unloaded = 0 m.render(root, m("div", {config: configParent}, m("a", {config: configChild}))) m.render(root, m("main", {config: configParent}, m("a", {config: configChild}))) return unloadedParent === 1 && unloadedChild === 0 }) test(function() { var root = mock.document.createElement("div") var unloadedParent = 0 var unloadedChild = 0 var configParent = function(el, init, ctx) { ctx.onunload = function() { unloadedParent++ } } var configChild = function(el, init, ctx) { ctx.onunload = function() { unloadedChild++ } } var unloaded = 0 m.render(root, m("div", {config: configParent}, m("a", {config: configChild}))) m.render(root, m("main", {config: configParent}, m("b", {config: configChild}))) return unloadedParent === 1 && unloadedChild === 1 }) test(function() { //https://github.com/lhorie/mithril.js/issues/150 var root = mock.document.createElement("div") m.render(root, [m("a"), m("div")]) m.render(root, [[], m("div")]) return root.childNodes.length == 1 && root.childNodes[0].nodeName == "DIV" }) test(function() { //https://github.com/lhorie/mithril.js/issues/156 var root = mock.document.createElement("div") m.render(root, m("div", [ ["a", "b", "c", "d"].map(function() { return [m("div"), " "] }), m("span") ])) return root.childNodes[0].childNodes[8].nodeName == "SPAN" }) test(function() { //https://github.com/lhorie/mithril.js/issues/157 var root = mock.document.createElement("div") m.render(root, m("ul", [m("li", {key: 0}), m("li", {key: 2}), m("li", {key: 4})])) m.render(root, m("ul", [m("li", {key: 0}), m("li", {key: 1}), m("li", {key: 2}), m("li", {key: 3}), m("li", {key: 4}), m("li", {key: 5})])) return root.childNodes[0].childNodes.map(function(n) {return n.key}).join("") == "012345" }) test(function() { //https://github.com/lhorie/mithril.js/issues/157 var root = mock.document.createElement("div") m.render(root, m("input", {value: "a"})) m.render(root, m("input", {value: "aa"})) return root.childNodes[0].childNodes.length == 0 }) test(function() { //https://github.com/lhorie/mithril.js/issues/157 var root = mock.document.createElement("div") m.render(root, m("br", {class: "a"})) m.render(root, m("br", {class: "aa"})) return root.childNodes[0].childNodes.length == 0 }) test(function() { //https://github.com/lhorie/mithril.js/issues/194 var root = mock.document.createElement("div") m.render(root, m("ul", [m("li", {key: 0}), m("li", {key: 1}), m("li", {key: 2}), m("li", {key: 3}), m("li", {key: 4}), m("li", {key: 5})])) m.render(root, m("ul", [m("li", {key: 0}), m("li", {key: 1}), m("li", {key: 2}), m("li", {key: 4}), m("li", {key: 5})])) return root.childNodes[0].childNodes.map(function(n) {return n.key}).join("") == "01245" }) test(function() { //https://github.com/lhorie/mithril.js/issues/206 var root = mock.document.createElement("div") m.render(root, m("div", undefined)) m.render(root, m("div", [m("div")])) return root.childNodes[0].childNodes.length == 1 }) test(function() { //https://github.com/lhorie/mithril.js/issues/206 var root = mock.document.createElement("div") m.render(root, m("div", null)) m.render(root, m("div", [m("div")])) return root.childNodes[0].childNodes.length == 1 }) test(function() { //https://github.com/lhorie/mithril.js/issues/200 var root = mock.document.createElement("div") var unloaded1 = false function unloadable1(element, isInit, context) { context.onunload = function() { unloaded1 = true } } m.render(root, [ m("div", {config: unloadable1}) ]) m.render(root, [ ]) var unloaded2 = false function unloadable2(element, isInit, context) { context.onunload = function() { unloaded2 = true } } m.render(root, [ m("div", {config: unloadable2}) ]) m.render(root, [ ]) return unloaded1 === true && unloaded2 === true }) test(function() { var root = mock.document.createElement("div") m.render(root, [m("div.blue")]) m.render(root, [m("div.green", [m("div")]), m("div.blue")]) return root.childNodes.length == 2 }) //end m.render //m.redraw test(function() { mock.requestAnimationFrame.$resolve() //setup var controller var root = mock.document.createElement("div") m.module(root, { controller: function() {controller = this}, view: function(ctrl) {return ctrl.value} }) mock.requestAnimationFrame.$resolve() var valueBefore = root.childNodes[0].nodeValue controller.value = "foo" m.redraw() mock.requestAnimationFrame.$resolve() return valueBefore === "" && root.childNodes[0].nodeValue === "foo" }) test(function() { mock.requestAnimationFrame.$resolve() //setup var count = 0 var root = mock.document.createElement("div") m.module(root, { controller: function() {}, view: function(ctrl) { count++ } }) mock.requestAnimationFrame.$resolve() //teardown m.redraw() //should run synchronously m.redraw() //rest should run asynchronously since they're spamming m.redraw() m.redraw() mock.requestAnimationFrame.$resolve() //teardown return count === 3 }) test(function() { mock.requestAnimationFrame.$resolve() //setup var count = 0 var root = mock.document.createElement("div") m.module(root, { controller: function() {}, view: function(ctrl) { count++ } }) mock.requestAnimationFrame.$resolve() //teardown m.redraw(true) //should run synchronously m.redraw(true) //forced to run synchronously m.redraw(true) m.redraw(true) mock.requestAnimationFrame.$resolve() //teardown return count === 5 }) //m.route test(function() { mock.requestAnimationFrame.$resolve() //setup mock.location.search = "?" var root = mock.document.createElement("div") m.route.mode = "search" m.route(root, "/test1", { "/test1": {controller: function() {}, view: function() {return "foo"}} }) mock.requestAnimationFrame.$resolve() //teardown return mock.location.search == "?/test1" && root.childNodes[0].nodeValue === "foo" }) test(function() { mock.requestAnimationFrame.$resolve() //setup mock.location.pathname = "/" var root = mock.document.createElement("div") m.route.mode = "pathname" m.route(root, "/test2", { "/test2": {controller: function() {}, view: function() {return "foo"}} }) mock.requestAnimationFrame.$resolve() //teardown return mock.location.pathname == "/test2" && root.childNodes[0].nodeValue === "foo" }) test(function() { mock.requestAnimationFrame.$resolve() //setup mock.location.hash = "#" var root = mock.document.createElement("div") m.route.mode = "hash" m.route(root, "/test3", { "/test3": {controller: function() {}, view: function() {return "foo"}} }) mock.requestAnimationFrame.$resolve() //teardown return mock.location.hash == "#/test3" && root.childNodes[0].nodeValue === "foo" }) test(function() { mock.requestAnimationFrame.$resolve() //setup mock.location.search = "?" var root = mock.document.createElement("div") m.route.mode = "search" m.route(root, "/test4/foo", { "/test4/:test": {controller: function() {}, view: function() {return m.route.param("test")}} }) mock.requestAnimationFrame.$resolve() //teardown return mock.location.search == "?/test4/foo" && root.childNodes[0].nodeValue === "foo" }) test(function() { mock.requestAnimationFrame.$resolve() //setup mock.location.search = "?" var module = {controller: function() {}, view: function() {return m.route.param("test")}} var root = mock.document.createElement("div") m.route.mode = "search" m.route(root, "/test5/foo", { "/": module, "/test5/:test": module }) var paramValueBefore = m.route.param("test") mock.requestAnimationFrame.$resolve() m.route("/") var paramValueAfter = m.route.param("test") mock.requestAnimationFrame.$resolve() //teardown return mock.location.search == "?/" && paramValueBefore === "foo" && paramValueAfter === undefined }) test(function() { mock.requestAnimationFrame.$resolve() //setup mock.location.search = "?" var module = {controller: function() {}, view: function() {return m.route.param("a1")}} var root = mock.document.createElement("div") m.route.mode = "search" m.route(root, "/test6/foo", { "/": module, "/test6/:a1": module }) var paramValueBefore = m.route.param("a1") mock.requestAnimationFrame.$resolve() m.route("/") var paramValueAfter = m.route.param("a1") mock.requestAnimationFrame.$resolve() //teardown return mock.location.search == "?/" && paramValueBefore === "foo" && paramValueAfter === undefined }) test(function() { //https://github.com/lhorie/mithril.js/issues/61 mock.requestAnimationFrame.$resolve() //setup mock.location.search = "?" var module = {controller: function() {}, view: function() {return m.route.param("a1")}} var root = mock.document.createElement("div") m.route.mode = "search" m.route(root, "/test7/foo", { "/": module, "/test7/:a1": module }) var routeValueBefore = m.route() mock.requestAnimationFrame.$resolve() m.route("/") var routeValueAfter = m.route() mock.requestAnimationFrame.$resolve() //teardown return routeValueBefore === "/test7/foo" && routeValueAfter === "/" }) test(function() { mock.requestAnimationFrame.$resolve() //setup mock.location.search = "?" var root = mock.document.createElement("div") m.route.mode = "search" m.route(root, "/test8/foo/SEP/bar/baz", { "/test8/:test/SEP/:path...": { controller: function() {}, view: function() { return m.route.param("test") + "_" + m.route.param("path") } } }) mock.requestAnimationFrame.$resolve() //teardown return mock.location.search == "?/test8/foo/SEP/bar/baz" && root.childNodes[0].nodeValue === "foo_bar/baz" }) test(function() { mock.requestAnimationFrame.$resolve() //setup mock.location.search = "?" var root = mock.document.createElement("div") m.route.mode = "search" m.route(root, "/test9/foo/bar/SEP/baz", { "/test9/:test.../SEP/:path": { controller: function() {}, view: function() { return m.route.param("test") + "_" + m.route.param("path") } } }) mock.requestAnimationFrame.$resolve() //teardown return mock.location.search == "?/test9/foo/bar/SEP/baz" && root.childNodes[0].nodeValue === "foo/bar_baz" }) test(function() { mock.requestAnimationFrame.$resolve() //setup mock.location.search = "?" var root = mock.document.createElement("div") m.route.mode = "search" m.route(root, "/test10/foo%20bar", { "/test10/:test": { controller: function() {}, view: function() { return m.route.param("test") } } }) mock.requestAnimationFrame.$resolve() //teardown return root.childNodes[0].nodeValue === "foo bar" }) test(function() { mock.requestAnimationFrame.$resolve() //setup mock.location.search = "?" var root = mock.document.createElement("div") m.route.mode = "search" m.route(root, "/", { "/": {controller: function() {}, view: function() {return "foo"}}, "/test11": {controller: function() {}, view: function() {return "bar"}} }) mock.requestAnimationFrame.$resolve() m.route("/test11/") mock.requestAnimationFrame.$resolve() //teardown return mock.location.search == "?/test11/" && root.childNodes[0].nodeValue === "bar" }) test(function() { mock.requestAnimationFrame.$resolve() //setup mock.location.search = "?" var root = mock.document.createElement("div") m.route.mode = "search" m.route(root, "/", { "/": {controller: function() {}, view: function() {}}, "/test12": {controller: function() {}, view: function() {}} }) mock.requestAnimationFrame.$resolve() m.route("/test12?a=foo&b=bar") mock.requestAnimationFrame.$resolve() //teardown return mock.location.search == "?/test12?a=foo&b=bar" && m.route.param("a") == "foo" && m.route.param("b") == "bar" }) test(function() { mock.requestAnimationFrame.$resolve() //setup mock.location.search = "?" var root = mock.document.createElement("div") m.route.mode = "search" m.route(root, "/", { "/": {controller: function() {}, view: function() {return "bar"}}, "/test13/:test": {controller: function() {}, view: function() {return m.route.param("test")}} }) mock.requestAnimationFrame.$resolve() m.route("/test13/foo?test=bar") mock.requestAnimationFrame.$resolve() //teardown return mock.location.search == "?/test13/foo?test=bar" && root.childNodes[0].nodeValue === "foo" }) test(function() { mock.requestAnimationFrame.$resolve() //setup mock.location.search = "?" var root = mock.document.createElement("div") m.route.mode = "search" m.route(root, "/", { "/": {controller: function() {}, view: function() {return "bar"}}, "/test14": {controller: function() {}, view: function() {return "foo"}} }) mock.requestAnimationFrame.$resolve() m.route("/test14?test&test2=") mock.requestAnimationFrame.$resolve() //teardown return mock.location.search == "?/test14?test&test2=" && m.route.param("test") === true && m.route.param("test2") === "" }) test(function() { mock.requestAnimationFrame.$resolve() //setup mock.location.search = "?" var root = mock.document.createElement("div") m.route.mode = "search" m.route(root, "/", { "/": {controller: function() {}, view: function() {}}, "/test12": {controller: function() {}, view: function() {}} }) mock.requestAnimationFrame.$resolve() m.route("/test12", {a: "foo", b: "bar"}) mock.requestAnimationFrame.$resolve() //teardown return mock.location.search == "?/test12?a=foo&b=bar" && m.route.param("a") == "foo" && m.route.param("b") == "bar" }) test(function() { mock.requestAnimationFrame.$resolve() //setup mock.location.search = "?" var root = mock.document.createElement("div") var route1, route2 m.route.mode = "search" m.route(root, "/", { "/": {controller: function() {route1 = m.route()}, view: function() {}}, "/test13": {controller: function() {route2 = m.route()}, view: function() {}} }) mock.requestAnimationFrame.$resolve() m.route("/test13") mock.requestAnimationFrame.$resolve() //teardown return route1 == "/" && route2 == "/test13" }) test(function() { mock.requestAnimationFrame.$resolve() //setup mock.location.search = "?" var root = mock.document.createElement("div") var unloaded = 0 m.route.mode = "search" m.route(root, "/", { "/": { controller: function() {}, view: function() { return m("div", { config: function(el, init, ctx) { ctx.onunload = function() { unloaded++ } } }) } }, "/test14": {controller: function() {}, view: function() {}} }) mock.requestAnimationFrame.$resolve() m.route("/test14") mock.requestAnimationFrame.$resolve() //teardown return unloaded == 1 }) test(function() { mock.requestAnimationFrame.$resolve() //setup mock.location.search = "?" var root = mock.document.createElement("div") var unloaded = 0 m.route.mode = "search" m.route(root, "/", { "/": { controller: function() {}, view: function() { return [ m("div"), m("div", { config: function(el, init, ctx) { ctx.onunload = function() { unloaded++ } } }) ] } }, "/test15": { controller: function() {}, view: function() { return [m("div")] } } }) mock.requestAnimationFrame.$resolve() m.route("/test15") mock.requestAnimationFrame.$resolve() //teardown return unloaded == 1 }) test(function() { mock.requestAnimationFrame.$resolve() //setup mock.location.search = "?" var root = mock.document.createElement("div") var unloaded = 0 m.route.mode = "search" m.route(root, "/", { "/": { controller: function() {}, view: function() { return m("div", { config: function(el, init, ctx) { ctx.onunload = function() { unloaded++ } } }) } }, "/test16": { controller: function() {}, view: function() { return m("a") } } }) mock.requestAnimationFrame.$resolve() m.route("/test16") mock.requestAnimationFrame.$resolve() //teardown return unloaded == 1 }) test(function() { mock.requestAnimationFrame.$resolve() //setup mock.location.search = "?" var root = mock.document.createElement("div") var unloaded = 0 m.route.mode = "search" m.route(root, "/", { "/": { controller: function() {}, view: function() { return [ m("div", { config: function(el, init, ctx) { ctx.onunload = function() { unloaded++ } } }) ] } }, "/test17": { controller: function() {}, view: function() { return m("a") } } }) mock.requestAnimationFrame.$resolve() m.route("/test17") mock.requestAnimationFrame.$resolve() //teardown return unloaded == 1 }) test(function() { mock.requestAnimationFrame.$resolve() //setup mock.location.search = "?" var root = mock.document.createElement("div") var unloaded = 0 m.route.mode = "search" m.route(root, "/", { "/": { controller: function() {}, view: function() { return m("div", { config: function(el, init, ctx) { ctx.onunload = function() { unloaded++ } } }) } }, "/test18": { controller: function() {}, view: function() { return [m("a")] } } }) mock.requestAnimationFrame.$resolve() m.route("/test18") mock.requestAnimationFrame.$resolve() //teardown return unloaded == 1 }) test(function() { mock.requestAnimationFrame.$resolve() //setup mock.location.search = "?" var root = mock.document.createElement("div") var unloaded = 0 m.route.mode = "search" m.route(root, "/", { "/": { controller: function() {}, view: function() { return [ m("div", { key: 1, config: function(el, init, ctx) { ctx.onunload = function() { unloaded++ } } }) ] } }, "/test20": { controller: function() {}, view: function() { return [ m("div", { key: 2, config: function(el, init, ctx) { ctx.onunload = function() { unloaded++ } } }) ] } } }) mock.requestAnimationFrame.$resolve() m.route("/test20") mock.requestAnimationFrame.$resolve() //teardown return unloaded == 1 }) test(function() { mock.requestAnimationFrame.$resolve() //setup mock.location.search = "?" var root = mock.document.createElement("div") var unloaded = 0 m.route.mode = "search" m.route(root, "/", { "/": { controller: function() {}, view: function() { return [ m("div", { key: 1, config: function(el, init, ctx) { ctx.onunload = function() { unloaded++ } } }) ] } }, "/test21": { controller: function() {}, view: function() { return [ m("div", { config: function(el, init, ctx) { ctx.onunload = function() { unloaded++ } } }) ] } } }) mock.requestAnimationFrame.$resolve() m.route("/test21") mock.requestAnimationFrame.$resolve() //teardown return unloaded == 1 }) test(function() { mock.requestAnimationFrame.$resolve() //setup mock.location.search = "?" var root = mock.document.createElement("div") m.route.mode = "search" m.route(root, "/foo", { "/foo": { controller: function() {}, view: function() { return m("div", "foo"); } }, "/bar": { controller: function() {}, view: function() { return m("div", "bar"); } }, }) mock.requestAnimationFrame.$resolve() var foo = root.childNodes[0].childNodes[0].nodeValue; m.route("/bar") mock.requestAnimationFrame.$resolve() //teardown var bar = root.childNodes[0].childNodes[0].nodeValue; return (foo === "foo" && bar === "bar") }) test(function() { mock.requestAnimationFrame.$resolve() //setup mock.location.search = "?" var root = mock.document.createElement("div") var unloaded = 0 var config = function(el, init, ctx) { ctx.onunload = function() { unloaded++ } } m.route.mode = "search" m.route(root, "/foo1", { "/foo1": { controller: function() {}, view: function() { return m("div", m("a", {config: config}, "foo")); } }, "/bar1": { controller: function() {}, view: function() { return m("main", m("a", {config: config}, "foo")); } }, }) mock.requestAnimationFrame.$resolve() m.route("/bar1") mock.requestAnimationFrame.$resolve() //teardown return unloaded == 1 }) test(function() { mock.requestAnimationFrame.$resolve() //setup mock.location.search = "?" var root = mock.document.createElement("div") var strategy m.route.mode = "search" m.route(root, "/foo1", { "/foo1": { controller: function() { strategy = m.redraw.strategy() m.redraw.strategy("none") }, view: function() { return m("div"); } } }) mock.requestAnimationFrame.$resolve() //teardown return strategy == "all" && root.childNodes.length == 0 }) test(function() { mock.requestAnimationFrame.$resolve() //setup mock.location.search = "?" var root = mock.document.createElement("div") var strategy, count = 0 var config = function(el, init) {if (!init) count++} m.route.mode = "search" m.route(root, "/foo1", { "/foo1": { controller: function() {}, view: function() { return m("div", {config: config}); } }, "/bar1": { controller: function() { strategy = m.redraw.strategy() m.redraw.strategy("redraw") }, view: function() { return m("div", {config: config}); } }, }) mock.requestAnimationFrame.$resolve() m.route("/bar1") mock.requestAnimationFrame.$resolve() //teardown return strategy == "all" && count == 1 }) test(function() { mock.requestAnimationFrame.$resolve() //setup mock.location.search = "?" var root = mock.document.createElement("div") var strategy m.route.mode = "search" m.route(root, "/foo1", { "/foo1": { controller: function() {this.number = 1}, view: function(ctrl) { return m("div", {onclick: function() { strategy = m.redraw.strategy() ctrl.number++ m.redraw.strategy("none") }}, ctrl.number); } } }) root.childNodes[0].onclick({}) return strategy == "diff" && root.childNodes[0].childNodes[0].nodeValue == "1" }) test(function() { mock.requestAnimationFrame.$resolve() //setup mock.location.search = "?" var root = mock.document.createElement("div") var count = 0 var config = function(el, init ) {if (!init) count++} m.route.mode = "search" m.route(root, "/foo1", { "/foo1": { controller: function() {}, view: function(ctrl) { return m("div", {config: config, onclick: function() { m.redraw.strategy("all") }}); } } }) root.childNodes[0].onclick({}) mock.requestAnimationFrame.$resolve() //teardown return count == 2 }) test(function() { mock.requestAnimationFrame.$resolve() //setup mock.location.search = "?" var root = mock.document.createElement("div") var value m.route(root, "/foo+bar", { "/:arg": { controller: function() {value = m.route.param("arg")}, view: function(ctrl) { return "" } } }) return value == "foo+bar" }) //end m.route //m.prop test(function() { var prop = m.prop("test") return prop() === "test" }) test(function() { var prop = m.prop("test") prop("foo") return prop() === "foo" }) test(function() { var prop = m.prop("test") return JSON.stringify(prop) === '"test"' }) test(function() { var obj = {prop: m.prop("test")} return JSON.stringify(obj) === '{"prop":"test"}' }) test(function() { var defer = m.deferred() var prop = m.prop(defer.promise) defer.resolve("test") return prop() === "test" }) test(function() { var defer = m.deferred() var prop = m.prop(defer.promise).then(function () { return "test2" }) defer.resolve("test") return prop() === "test2" }) test(function() { var prop = m.prop(null) return prop() === null }) //m.request test(function() { var prop = m.request({method: "GET", url: "test"}) mock.XMLHttpRequest.$instances.pop().onreadystatechange() return prop().method === "GET" && prop().url === "test" }) test(function() { var prop = m.request({method: "GET", url: "test"}).then(function(value) {return "foo"}) mock.XMLHttpRequest.$instances.pop().onreadystatechange() return prop() === "foo" }) test(function() { var prop = m.request({method: "POST", url: "http://domain.com:80", data: {}}).then(function(value) {return value}) mock.XMLHttpRequest.$instances.pop().onreadystatechange() return prop().url === "http://domain.com:80" }) test(function() { var prop = m.request({method: "POST", url: "http://domain.com:80/:test1", data: {test1: "foo"}}).then(function(value) {return value}) mock.XMLHttpRequest.$instances.pop().onreadystatechange() return prop().url === "http://domain.com:80/foo" }) test(function() { var error = m.prop("no error") var prop = m.request({method: "GET", url: "test", deserialize: function() {throw new Error("error occurred")}}).then(null, error) mock.XMLHttpRequest.$instances.pop().onreadystatechange() return prop().message === "error occurred" && error().message === "error occurred" }) test(function() { var error = m.prop("no error"), exception var prop = m.request({method: "GET", url: "test", deserialize: function() {throw new TypeError("error occurred")}}).then(null, error) try {mock.XMLHttpRequest.$instances.pop().onreadystatechange()} catch (e) {exception = e} m.endComputation() return prop() === undefined && error() === "no error" && exception.message == "error occurred" }) test(function() { var error = m.prop("no error") var prop = m.request({method: "POST", url: "test"}).then(null, error) var xhr = mock.XMLHttpRequest.$instances.pop() xhr.onreadystatechange() return xhr.$headers["Content-Type"] == "application/json; charset=utf-8" }) // m.request over jsonp test(function(){ // script tags cannot be appended directly on the document var body = mock.document.createElement("body"); mock.document.body = body; mock.document.appendChild(body); var _window = mock; var error = m.prop("no error"); var req = m.request({ url: "/test", dataType: "jsonp", background: true }).then(null, error); var callbackKeys = []; Object.keys(_window).forEach(function(globalKey){ if(globalKey.indexOf("mithril_callback") > -1) callbackKeys.push(globalKey); }); var foundScriptTag = false, scriptTag = null; mock.document.getElementsByTagName("script").forEach(function(script){ if(!scriptTag && script.src.indexOf(callbackKeys[0]) > -1) scriptTag = script; }); if(scriptTag){ foundScriptTag = true; delete _window[callbackKeys[0]]; mock.document.body.removeChild(scriptTag); } mock.document.removeChild(body); return callbackKeys.length === 1 && foundScriptTag; }) test(function(){ var body = mock.document.createElement("body"); mock.document.body = body; mock.document.appendChild(body); var _window = mock; var error = m.prop(false); var req = m.request({ url: "/test", dataType: "jsonp", background: true, callbackKey: "jsonpCallback" }).then(null, error); var callbackKeys = []; Object.keys(_window).forEach(function(globalKey){ if(globalKey.indexOf("mithril_callback") > -1) callbackKeys.push(globalKey); }); var scriptTag = null; mock.document.getElementsByTagName("script").forEach(function(script){ if(!scriptTag && script.src.indexOf(callbackKeys[0]) > -1) scriptTag = script; }); var correctCallback = false; if(scriptTag){ correctCallback = scriptTag.src.indexOf("jsonpCallback") > -1; delete _window[callbackKeys[0]]; mock.document.body.removeChild(scriptTag); } mock.document.removeChild(body); return correctCallback; }) test(function(){ var body = mock.document.createElement("body"); mock.document.body = body; mock.document.appendChild(body); var _window = mock; var req = m.request({ url: "/test", dataType: "jsonp", background: true }); var callbackKeys = []; Object.keys(_window).forEach(function(globalKey){ if(globalKey.indexOf("mithril_callback") > -1) callbackKeys.push(globalKey); }); var scriptTag = null; mock.document.getElementsByTagName("script").forEach(function(script){ if(!scriptTag && script.src.indexOf(callbackKeys[0]) > -1) scriptTag = script; }); var out = { foo: "bar" }; if(scriptTag && callbackKeys.length > 0){ _window[callbackKeys[0]](out); mock.document.body.removeChild(scriptTag); delete _window[callbackKeys[0]]; } mock.document.removeChild(body); return JSON.stringify(out) === JSON.stringify(req()); }) test(function(){ var body = mock.document.createElement("body"); mock.document.body = body; mock.document.appendChild(body); var _window = mock; var error = m.prop(false); var req = m.request({ url: "/test", dataType: "jsonp", data: { foo: "bar" }, background: true }); var callbackKeys = []; Object.keys(_window).forEach(function(globalKey){ if(globalKey.indexOf("mithril_callback") > -1) callbackKeys.push(globalKey); }); var scriptTag = null; mock.document.getElementsByTagName("script").forEach(function(script){ if(!scriptTag && script.src.indexOf(callbackKeys[0]) > -1) scriptTag = script; }); var correctData = false; if(scriptTag){ correctData = scriptTag.src.indexOf("foo=bar") > -1; mock.document.body.removeChild(scriptTag); delete _window[callbackKeys[0]]; } mock.document.removeChild(body); return correctData; }) //m.deferred test(function() { var value var deferred = m.deferred() deferred.promise.then(function(data) {value = data}) deferred.resolve("test") return value === "test" }) test(function() { var value var deferred = m.deferred() deferred.promise.then(function(data) {return "foo"}).then(function(data) {value = data}) deferred.resolve("test") return value === "foo" }) test(function() { var value var deferred = m.deferred() deferred.promise.then(null, function(data) {value = data}) deferred.reject("test") return value === "test" }) test(function() { var value var deferred = m.deferred() deferred.promise.then(null, function(data) {return "foo"}).then(function(data) {value = data}) deferred.reject("test") return value === "foo" }) test(function() { var value1, value2 var deferred = m.deferred() deferred.promise.then(function(data) {throw new Error}).then(function(data) {value1 = 1}, function(data) {value2 = data}) deferred.resolve("test") return value1 === undefined && value2 instanceof Error }) test(function() { var deferred1 = m.deferred() var deferred2 = m.deferred() var value1, value2 deferred1.promise.then(function(data) { value1 = data return deferred2.promise }).then(function(data) { value2 = data }) deferred1.resolve(1) deferred2.resolve(2) return value1 === 1 && value2 === 2 }) test(function() { //https://github.com/lhorie/mithril.js/issues/80 var deferred = m.deferred(), value deferred.resolve(1) deferred.promise.then(function(data) { value = data }) return value === 1 }) test(function() { //https://github.com/lhorie/mithril.js/issues/80 var deferred = m.deferred(), value deferred.reject(1) deferred.promise.then(null, function(data) { value = data }) return value === 1 }) test(function() { //https://github.com/lhorie/mithril.js/issues/80 var deferred = m.deferred(), value deferred.resolve(1) deferred.resolve(2) deferred.promise.then(function(data) { value = data }) return value === 1 }) test(function() { //https://github.com/lhorie/mithril.js/issues/80 var deferred = m.deferred(), value deferred.promise.then(function(data) { value = data }) deferred.resolve(1) deferred.resolve(2) return value === 1 }) test(function() { //https://github.com/lhorie/mithril.js/issues/80 var deferred = m.deferred(), value1, value2 deferred.promise.then(function(data) { value1 = data }, function(data) { value2 = data }) deferred.resolve(1) deferred.reject(2) return value1 === 1 && value2 === undefined }) test(function() { //https://github.com/lhorie/mithril.js/issues/80 var deferred = m.deferred(), value1, value2 deferred.promise.then(function() { value1 = data }, function(data) { value2 = data }) deferred.reject(1) deferred.resolve(2) return value1 === undefined && value2 === 1 }) test(function() { //https://github.com/lhorie/mithril.js/issues/80 var deferred = m.deferred(), value deferred.promise.then(null, function(data) { value = data }) deferred.reject(1) deferred.reject(2) return value === 1 }) test(function() { //https://github.com/lhorie/mithril.js/issues/85 var deferred = m.deferred(), value deferred.resolve() deferred.promise.then(function(data) { value = 1 }) return value === 1 }) test(function() { //https://github.com/lhorie/mithril.js/issues/85 var deferred = m.deferred(), value deferred.reject() deferred.promise.then(null, function(data) { value = 1 }) return value === 1 }) test(function() { var deferred = m.deferred(), value deferred.resolve(1) return deferred.promise() === 1 }) test(function() { var deferred = m.deferred(), value var promise = deferred.promise.then(function(data) {return data + 1}) deferred.resolve(1) return promise() === 2 }) test(function() { var deferred = m.deferred(), value deferred.reject(1) return deferred.promise() === undefined }) //m.sync test(function() { var value var deferred1 = m.deferred() var deferred2 = m.deferred() m.sync([deferred1.promise, deferred2.promise]).then(function(data) {value = data}) deferred1.resolve("test") deferred2.resolve("foo") return value[0] === "test" && value[1] === "foo" }) test(function() { var value var deferred1 = m.deferred() var deferred2 = m.deferred() m.sync([deferred1.promise, deferred2.promise]).then(function(data) {value = data}) deferred2.resolve("foo") deferred1.resolve("test") return value[0] === "test" && value[1] === "foo" }) test(function() { var value = 1 m.sync([]).then(function() {value = 2}) return value == 2 }) //m.startComputation/m.endComputation test(function() { mock.requestAnimationFrame.$resolve() var controller var root = mock.document.createElement("div") m.module(root, { controller: function() {controller = this}, view: function(ctrl) {return ctrl.value} }) mock.requestAnimationFrame.$resolve() m.startComputation() controller.value = "foo" m.endComputation() mock.requestAnimationFrame.$resolve() return root.childNodes[0].nodeValue === "foo" }) //console.log presence test(function() { return m.deps.factory.toString().indexOf("console") < 0 }) // config context test(function() { var root = mock.document.createElement("div") var success = false; m.render(root, m("div", {config: function(elem, isInitialized, ctx) {ctx.data=1}})); m.render(root, m("div", {config: function(elem, isInitialized, ctx) {success = ctx.data===1}})); return success; }) // more complex config context test(function() { var root = mock.document.createElement("div") var idx = 0; var success = true; var statefulConfig = function(elem, isInitialized, ctx) {ctx.data=idx++} var node = m("div", {config: statefulConfig}); m.render(root, [node, node]); idx = 0; var checkConfig = function(elem, isInitialized, ctx) { success = success && (ctx.data === idx++) } node = m("div", {config: checkConfig}); m.render(root, [node, node]); return success; }) } //test reporting for saucelabs if (typeof window != "undefined") { window.global_test_results = { tests: [], duration: 0, passed: 0, failed: 0 }; } //mock testMithril(mock.window); test.print(function(value) {console.log(value)})