/* global m, test, mock */

var useRealDom = true

function map(list, fn){
	return [].map.call(list, fn)
}

;(async function (config) { // eslint-disable-line max-statements
	"use strict"
	if(!useRealDom){
		m.deps(mock.window)
	} else {
		// real dom cannot test m.route/m.request !!!
		mock = window
		function onload(){
			return new Promise(s=>
				window.onload = s
			)
		}
		// await onload()  // window.onload?

		console.log('useRealDom:', useRealDom)

		window.scrollTo = function () {}

		window.requestAnimationFrame.$resolve = function(){
			return new Promise((s)=>setTimeout(s, 33))
		}
		// window.addEventListener('unhandledrejection', event => {
		// 		// Prevent error output on the console:
		// 		event.preventDefault()
		// 		console.log('Reason: ' + event.reason)
		// })
		window.XMLHttpRequest = (function () {
			function XMLHttpRequest() {
				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
					XMLHttpRequest.$instances.push(this)
				}
			}
			XMLHttpRequest.$instances = []
			return XMLHttpRequest
		})()
	}

	var nextFrame = ()=> {
		const r = mock.requestAnimationFrame.$resolve
		if(r) return r()
		else return new Promise((s => setTimeout(s, 33) ))
	}

	// ***** futurist: add more tests *****


	// test for $redrawAll===false will partial redraw for m.mount
	await test(async function () {
		var root1 = mock.document.createElement("div")
		var ctrl1 = 0
		var ctrl2 = 0
		var ctrlSub = 0
		var viewSub = 0

		var sub = {
			controller: function(){
				ctrlSub++
			},
				view: function(ctrl){
					return m('', viewSub++)
				}
			}

		var mod1 = m.mount(root1, {
			controller: function () { this.value = 0; ctrl1++ },
			view: function (ctrl) { ctrl.value++; return m(sub) }
		})

		await nextFrame()

		var root2 = mock.document.createElement("div")
		var mod2 = m.mount(root2, {
			$redrawAll: false,  // partial redraw for enter component
			controller: function () { 
				this.value = 0; ctrl2++ 
				this.onunload = function(){
					return false  // partial redraw for leave component
				}
			},
			view: function (ctrl) { return ctrl.value++ }
		})

		await nextFrame()

		m.mount(root2, null)

		await nextFrame()

		// console.log(ctrl1, ctrl2, ctrlSub, viewSub, mod1.value, mod2.value)

		return ctrlSub==1 && viewSub==3 && mod1.value==3
		
	})
	// return

	// test for attrs.$freeze for nested dom
	await test(async function () {
		var root = mock.document.createElement("div")
		var cls = 'abc'
		var ctrl = m.mount(root, m.component({
			view: function(ctrl){
				return m('div', {
					// config:(a,b,c)=>{c.$freeze=true}, 
					$freeze: true,
					className:cls, 
					key: cls
				}, [
					m('.a1', {$freeze: true, id:cls}, 'test1'),
					m('.a2', {id: cls+'a2'}, 'test2'),
				])
			}
		}))
		await nextFrame()
		var before = root.childNodes[0]
		var beforeA1 = root.childNodes[0].childNodes[0]
		var beforeA2 = root.childNodes[0].childNodes[1]
		
		cls = 'cde'
		m.redraw()

		await nextFrame()

		var after = root.childNodes[0]
		var afterA1 = root.childNodes[0].childNodes[0]
		var afterA2 = root.childNodes[0].childNodes[1]

		return before == after
		&& after.className == 'abc'
		&& beforeA1==afterA1 
		&& beforeA2!==afterA2
	})

	// test for attrs.$freeze for sub component
	await test(async function () {
		var root = mock.document.createElement("div")
		var cls = 'abc'
		var sub = {
			view: function(){
				return m('span', {$freeze:true, className: cls, id: cls}, 'sub')
			}
		}
		var ctrl = m.mount(root, m.component({
			view: function(ctrl){
				return m('div', {$freeze:true, className: cls, id: cls}, m(sub))
			}
		}))
		await nextFrame()
		var beforeDiv = root.childNodes[0]
		var beforeSpan = root.childNodes[0].childNodes[0]

		// attrs.$freeze = true
		cls = 'cde'
		m.redraw()
		await nextFrame()

		var afterDiv = root.childNodes[0]
		var afterSpan = root.childNodes[0].childNodes[0]

		return beforeDiv == afterDiv 
		&& beforeSpan == afterSpan 
		&& afterSpan.className=='abc'
	})

	// test for attrs.$freeze for root component
	await test(async function () {
		var root = mock.document.createElement("div")
		var cls = 'abc'
		var freeze = true

		var ctrl = m.mount(root, m.component({
			view: function(ctrl){
				return m('div', {$freeze:freeze, className:cls, key: cls}, 'test')
			}
		}))
		await nextFrame()
		var before = root.childNodes[0]
		
		cls = 'cde'
		m.redraw()

		await nextFrame()

		var after = root.childNodes[0]

		freeze = false
		m.redraw()
		await nextFrame()

		var third = root.childNodes[0]

		return before == after && after.className == 'abc'
		&& third != after && third.className == 'cde'
	})
	// return

	// test for ctrl._cached && ctrl._domRoot for sub component
	await test(async function () {
		var root = mock.document.createElement("div")
		var cls = 'abc'
		var sub = {
			view: function(){
				return m('span', {class: cls}, 'sub')
			}
		}
		var ctrl = m.mount(root, m.component({
			view: function(ctrl){
				return m('nav', sub)
			}
		}))
		await nextFrame()

		const c = ctrl._cached.children[0].controllers[0]

		return c._cached.tag=='span'
		&& c._domRoot.nodeName.toUpperCase()=='DIV'
	})

	// test for ctrl._cached && ctrl._domRoot for root component
	await test(async function () {
		var root = mock.document.createElement("div")
		var cls = 'abc'
		var ctrl = m.mount(root, m.component({
			view: function(ctrl){
				return m('nav', {class:cls}, 'test')
			}
		}))
		await nextFrame()

		return ctrl._cached.tag == 'nav' && ctrl._domRoot.nodeName.toUpperCase() == 'DIV'
	})

	// m
	await test(async function () { return typeof m.version() === "string" })
	await test(async function () { return m("div").tag === "div" })
	await test(async function () { return m(".foo").tag === "div" })
	await test(async function () { return m(".foo").attrs.className === "foo" })
	await test(async function () { return m("[class=a]").attrs.className === "a" })
	await test(async function () { return m("[title=bar]").tag === "div" })
	await test(async function () { return m("[title=bar]").attrs.title === "bar" })
	await test(async function () { return m("[empty]").attrs.empty === true })
	await test(async function () { return m("[title=\'bar\']").attrs.title === "bar" })
	await test(async function () { return m("[title=\"bar\"]").attrs.title === "bar" })
	await test(async function () { return m("div", "test").children[0] === "test" })

	await test(async function () {
		return m("div", "test", "test2").children[1] === "test2"
	})

	await test(async function () { return m("div", ["test"]).children[0] === "test" })

	await test(async function () {
		return m("div", {title: "bar"}, "test").attrs.title === "bar"
	})

	await test(async function () {
		return m("div", {title: "bar"}, "test").children[0] === "test"
	})

	await test(async function () {
		return m("div", {title: "bar"}, ["test"]).children[0] === "test"
	})

	await test(async function () {
		return m("div", {title: "bar"}, m("div")).children[0].tag === "div"
	})

	await test(async function () {
		return m("div", {title: "bar"}, [m("div")]).children[0].tag === "div"
	})

	// splat
	await test(async function () {
		var node = m("div", {title: "bar"}, "test0", "test1", "test2", "test3")
		return node.children[3] === "test3"
	})

	await test(async function () {
		var node = m("div", {title: "bar"}, m("div"), m("i"), m("span"))
		return node.children[2].tag === "span"
	})

	await test(async function () { return m("div", ["a", "b"]).children.length === 2 })
	await test(async function () { return m("div", [m("div")]).children[0].tag === "div" })

	// yes, this is expected behavior: see method signature
	await test(async function () { return m("div", m("div")).children[0].tag === "div" })

	await test(async function () { return m("div", [undefined]).tag === "div" })

	// as long as it doesn't throw errors, it's fine
	await test(async function () { return m("div", [{foo: "bar"}]) })

	await test(async function () { return m("svg", [m("g")]) })
	await test(async function () { return m("svg", [m("a[href='http://google.com']")]) })

	await test(async function () {
		return m(".foo", {class: "bar"}).attrs.class === "foo bar"
	})

	await test(async function () {
		return m(".foo", {className: "bar"}).attrs.className === "foo bar"
	})

	await test(async function () {
		return m(".foo", {className: ""}).attrs.className === "foo"
	})

	// https://github.com/lhorie/mithril.js/issues/382 and 512
	await test(async function () {
		return m("div", {className: ""}).attrs.className === ""
	})

	await test(async function () {
		return m("div", {class: ""}).attrs.className === undefined
	})

	await test(async function () {
		return m("div", {className: ""}).attrs.class === undefined
	})

	await test(async function () { return m("div", {class: ""}).attrs.class === "" })
	await test(async function () { return m("div", [1, 2, 3], 4).children.length === 2 })
	await test(async function () { return m("div", [1, 2, 3], 4).children[0].length === 3 })
	await test(async function () { return m("div", [1, 2, 3], 4).children[1] === 4 })
	await test(async function () { return m("div", [1, 2, 3]).children.length === 3 })

	await test(async function () {
		return m("div", [1, 2, 3], [4, 5, 6, 7]).children.length === 2
	})

	await test(async function () {
		return m("div", [1, 2, 3], [4, 5, 6, 7]).children[0].length === 3
	})

	await test(async function () {
		return m("div", [1, 2, 3], [4, 5, 6, 7]).children[1].length === 4
	})

	await test(async function () { return m("div", [1], [2], [3]).children.length === 3 })

	await test(async function () {
		// class changes shouldn't trigger dom recreation
		var v1 = m(".foo", {class: "", onclick: function () {}})
		var v2 = m(".foo", {class: "bar", onclick: function () {}})
		return Object.keys(v1.attrs).join() === Object.keys(v2.attrs).join()
	})

	await test(async function () {
		// m should proxy object first arg to m.component
		var component = {
			controller: function (args) {
				this.args = args
			},
			view: function () {
				return m("div", "testing")
			}
		}
		var args = {age: 12}
		var c1 = m(component, args).controller()
		var c2 = m.component(component, args).controller()

		return c1.args === args && c1.args === c2.args
	})

	// m.mount
	await test(async function () {
		var root = mock.document.createElement("div")
		var whatever = 1
		var app = {
			view: function () {
				return [
					whatever % 2 ? m("span", "% 2") : undefined,
					m("div", "bugs"),
					m("a")
				]
			}
		}
		m.mount(root, app)
		await mock.requestAnimationFrame.$resolve()

		whatever++
		m.redraw()
		await mock.requestAnimationFrame.$resolve()

		whatever++
		m.redraw()
		await mock.requestAnimationFrame.$resolve()

		return root.childNodes.length
	})

	await test(async function () {
		await mock.requestAnimationFrame.$resolve()

		var root1 = mock.document.createElement("div")
		var mod1 = m.mount(root1, {
			controller: function () { this.value = "test1" },
			view: function (ctrl) { return ctrl.value }
		})

		var root2 = mock.document.createElement("div")
		var mod2 = m.mount(root2, {
			controller: function () { this.value = "test2" },
			view: function (ctrl) { return ctrl.value }
		})

		await mock.requestAnimationFrame.$resolve()

		return (root1.childNodes[0].nodeValue === "test1" &&
				root2.childNodes[0].nodeValue === "test2") &&
			(mod1.value && mod1.value === "test1") &&
			(mod2.value && mod2.value === "test2")
	})

	await test(async function () {
		await mock.requestAnimationFrame.$resolve()

		var root = mock.document.createElement("div")
		var unloaded = false

		m.mount(root, {
			controller: function () {
				this.value = "test1"
				this.onunload = function () {
					unloaded = true
				}
			},
			view: function (ctrl) { return ctrl.value }
		})

		await mock.requestAnimationFrame.$resolve()

		m.mount(root, null)

		await mock.requestAnimationFrame.$resolve()

		return unloaded
	})

	await test(async function () {
		// component should pass args to both controller and view
		await mock.requestAnimationFrame.$resolve()

		var root = mock.document.createElement("div")
		var slot1, slot2, count=0
		var component = {
			controller: function (options) { slot1 = options.a },
			view: function (ctrl, options) { count++, slot2 = options.a }
		}
		m.mount(root, m.component(component, {a: 1}))

		await mock.requestAnimationFrame.$resolve()

		const ok1 = slot1 === 1 && slot2 === 1

		m.redraw()
		await nextFrame()

		const ok2 = slot1 === 1 && slot2 === 1

		return ok1 && ok2 && count==2

	})

	await test(async function () {
		// component should pass args to both controller and view for sub-component
		await mock.requestAnimationFrame.$resolve()

		var root = mock.document.createElement("div")
		var slot1, slot2, count=0
		var component = {
			controller: function (options) { slot1 = options.a },
			view: function (ctrl, options) { 
				count++
				slot2 = options.a
				return m('')
			}
		}

		var root1, root2
		// for root component, arg always undefined
		var componentRoot = {
			controller: function (arg) {
				root2 = arg===undefined
				this.a = 1 
			},
			view: function (ctrl, arg) { 
				root1 = arg===undefined
				return m('', m(component, ctrl)) 
			}
		}
		m.mount(root, componentRoot)

		await mock.requestAnimationFrame.$resolve()

		const ok1 = slot1 === 1 && slot2 === 1

		m.redraw()
		await nextFrame()

		const ok2 = slot1 === 1 && slot2 === 1

		return ok1 && ok2 && count==2 && root1 && root2

	})


	await test(async function () {
		// component should work without controller
		await mock.requestAnimationFrame.$resolve()

		var root = mock.document.createElement("div")
		var slot2
		var component = {
			view: function (ctrl, options) { slot2 = options.a }
		}
		m.mount(root, m.component(component, {a: 1}))

		await mock.requestAnimationFrame.$resolve()

		return slot2 === 1
	})

	await test(async function () {
		// component controller should only run once
		await mock.requestAnimationFrame.$resolve()

		var root = mock.document.createElement("div")
		var count1 = 0
		var count2 = 0

		var sub = {
			controller: function () {
				count1++
			},
			view: function () {
				count2++
				return m("div", "test")
			}
		}

		var component = {
			view: function () {
				return sub
			}
		}

		m.mount(root, component)

		await mock.requestAnimationFrame.$resolve()

		m.redraw(true)

		await mock.requestAnimationFrame.$resolve()

		return count1 === 1 && count2 === 2
	})

	await test(async function () {
		// sub component controller should only run once
		await mock.requestAnimationFrame.$resolve()

		var root = mock.document.createElement("div")
		var count1 = 0
		var count2 = 0
		var count3 = 0
		var count4 = 0

		var subsub = {
			controller: function () {
				count3++
			},
			view: function () {
				count4++
				return m("div", "test")
			}
		}

		var sub = {
			controller: function () {
				count1++
			},
			view: function () {
				count2++
				return subsub
			}
		}

		var component = {
			view: function () {
				return sub
			}
		}

		m.mount(root, component)

		await mock.requestAnimationFrame.$resolve()

		m.redraw(true)

		await mock.requestAnimationFrame.$resolve()

		return count1 === 1 && count2 === 2 && count3 === 1 && count4 === 2
	})

	await test(async function () {
		// keys in components should work
		await mock.requestAnimationFrame.$resolve()

		var root = mock.document.createElement("div")
		var list = [1, 2, 3]

		var sub = {
			controller: function () {},
			view: function () {
				return m("div")
			}
		}

		var component = {
			controller: function () {},
			view: function () {
				return list.map(function (i) {
					return m.component(sub, {key: i})
				})
			}
		}

		m.mount(root, component)

		var firstBefore = root.childNodes[0]

		await mock.requestAnimationFrame.$resolve()

		list.reverse()
		m.redraw(true)

		await mock.requestAnimationFrame.$resolve()

		var firstAfter = root.childNodes[2]

		return firstBefore === firstAfter
	})
	await test(async function () {
		//string keys in components should work
		await mock.requestAnimationFrame.$resolve()

		var root = mock.document.createElement("div")
		var list = [1, 2, 3]
		var component = {
			controller: function () {},
			view: function () {
				return list.map(function (i) {
					return m.component(sub, {key: "key" + i})
				})
			}
		}
		var sub = {
			controller: function () {},
			view: function () {
				return m("div")
			}
		}
		m.mount(root, component)

		var firstBefore = root.childNodes[0]

		await mock.requestAnimationFrame.$resolve()

		list.reverse()
		m.redraw(true)

		await mock.requestAnimationFrame.$resolve()

		var firstAfter = root.childNodes[2]

		return firstBefore === firstAfter
	})
	await test(async function () {
		//keys in subcomponents should work
		await mock.requestAnimationFrame.$resolve()

		var root = mock.document.createElement("div")
		var list = [1, 2, 3]

		var subsub = {
			controller: function () {},
			view: function () {
				return m("div")
			}
		}

		var sub = {
			view: function () {
				return subsub
			}
		}

		var component = {
			controller: function () {},
			view: function () {
				return list.map(function (i) {
					return m.component(sub, {key: i})
				})
			}
		}

		m.mount(root, component)

		var firstBefore = root.childNodes[0]

		await mock.requestAnimationFrame.$resolve()

		list.reverse()
		m.redraw(true)

		await mock.requestAnimationFrame.$resolve()

		var firstAfter = root.childNodes[2]

		return firstBefore === firstAfter
	})

	await test(async function () {
		// keys in components should work even if component internally messes
		// them up
		await mock.requestAnimationFrame.$resolve()

		var root = mock.document.createElement("div")
		var list = [1, 2, 3]

		var sub = {
			controller: function () {},
			view: function () {
				return m("div", {key: 1})
			}
		}

		var component = {
			controller: function () {},
			view: function () {
				return list.map(function (i) {
					return m.component(sub, {key: i})
				})
			}
		}

		m.mount(root, component)

		var firstBefore = root.childNodes[0]

		await mock.requestAnimationFrame.$resolve()

		list.reverse()
		m.redraw(true)

		await mock.requestAnimationFrame.$resolve()

		var firstAfter = root.childNodes[2]

		return firstBefore === firstAfter
	})

	await test(async function () {
		// keys in subcomponents should work even if component internally messes
		// them up
		await mock.requestAnimationFrame.$resolve()

		var root = mock.document.createElement("div")
		var list = [1, 2, 3]

		var subsub = {
			controller: function () {},
			view: function () {
				return m("div", {key: 1})
			}
		}

		var sub = {
			controller: function () {},
			view: function () {
				return subsub
			}
		}

		var component = {
			controller: function () {},
			view: function () {
				return list.map(function (i) {
					return m.component(sub, {key: i})
				})
			}
		}

		m.mount(root, component)

		var firstBefore = root.childNodes[0]

		await mock.requestAnimationFrame.$resolve()

		list.reverse()
		m.redraw(true)

		await mock.requestAnimationFrame.$resolve()

		var firstAfter = root.childNodes[2]

		return firstBefore === firstAfter
	})

	await test(async function () {
		// component identity should stay intact if components are descendants
		// of keyed elements
		await mock.requestAnimationFrame.$resolve()

		var root = mock.document.createElement("div")
		var list = [1, 2, 3]

		var sub = {
			controller: function () {},
			view: function () {
				return m("div")
			}
		}

		var component = {
			controller: function () {},
			view: function () {
				return list.map(function (i) {
					return m("div", {key: i}, sub)
				})
			}
		}

		m.mount(root, component)

		var firstBefore = root.childNodes[0].childNodes[0]

		await mock.requestAnimationFrame.$resolve()

		list.reverse()
		m.redraw(true)

		await mock.requestAnimationFrame.$resolve()

		var firstAfter = root.childNodes[2].childNodes[0]

		return firstBefore === firstAfter
	})

	await test(async function () {
		// subcomponent identity should stay intact if components are
		// descendants of keyed elements
		await mock.requestAnimationFrame.$resolve()

		var root = mock.document.createElement("div")
		var list = [1, 2, 3]

		var subsub = {
			controller: function () {},
			view: function () {
				return m("div")
			}
		}

		var sub = {
			controller: function () {},
			view: function () {
				return subsub
			}
		}

		var component = {
			controller: function () {},
			view: function () {
				return list.map(function (i) {
					return m("div", {key: i}, sub)
				})
			}
		}

		m.mount(root, component)

		var firstBefore = root.childNodes[0].childNodes[0]

		await mock.requestAnimationFrame.$resolve()

		list.reverse()
		m.redraw(true)

		await mock.requestAnimationFrame.$resolve()

		var firstAfter = root.childNodes[2].childNodes[0]

		return firstBefore === firstAfter
	})

	await test(async function () {
		// component should call onunload when removed from template
		await mock.requestAnimationFrame.$resolve()

		var root = mock.document.createElement("div")
		var list = [1, 2, 3]
		var unloaded

		var sub = {
			controller: function (opts) {
				this.onunload = function () {
					unloaded = opts.key
				}
			},
			view: function () {
				return m("div")
			}
		}

		var component = {
			controller: function () {},
			view: function () {
				return list.map(function (i) {
					return m.component(sub, {key: i})
				})
			}
		}

		m.mount(root, component)

		await mock.requestAnimationFrame.$resolve()

		list.pop()
		m.redraw(true)

		await mock.requestAnimationFrame.$resolve()

		return unloaded === 3
	})

	await test(async function () {
		// subcomponent should call onunload when removed from template
		await mock.requestAnimationFrame.$resolve()

		var root = mock.document.createElement("div")
		var list = [1, 2, 3]
		var unloaded1, unloaded2

		var subsub = {
			controller: function (opts) {
				this.onunload = function () {
					unloaded2 = opts.key
				}
			},
			view: function () {
				return m("div")
			}
		}

		var sub = {
			controller: function (opts) {
				this.onunload = function () {
					unloaded1 = opts.key
				}
			},
			view: function (ctrl, opts) {
				return m.component(subsub, {key: opts.key})
			}
		}

		var component = {
			controller: function () {},
			view: function () {
				return list.map(function (i) {
					return m.component(sub, {key: i})
				})
			}
		}

		m.mount(root, component)

		await mock.requestAnimationFrame.$resolve()

		list.pop()
		m.redraw(true)

		await mock.requestAnimationFrame.$resolve()

		return unloaded1 === 3 && unloaded2 === 3
	})

	await test(async function () {
		// calling m.redraw synchronously from controller constructor should not
		// trigger extra redraws
		await mock.requestAnimationFrame.$resolve()

		var root = mock.document.createElement("div")
		var count = 0

		var sub = {
			controller: function () {
				m.redraw()
			},
			view: function () {
				count++
				return m("div")
			}
		}

		var component = {
			controller: function () {},
			view: function () {
				return sub
			}
		}

		m.mount(root, component)

		await mock.requestAnimationFrame.$resolve()

		return count === 1
	})

	await test(async function () {
		// calling m.redraw synchronously from controller constructor should not
		// trigger extra redraws
		await mock.requestAnimationFrame.$resolve()

		var root = mock.document.createElement("div")
		var count = 0

		var subsub = {
			controller: function () {
				m.redraw()
			},
			view: function () {
				count++
				return m("div")
			}
		}

		var sub = {
			controller: function () {},
			view: function () {
				return subsub
			}
		}

		var component = {
			controller: function () {},
			view: function () {
				return sub
			}
		}

		m.mount(root, component)

		await mock.requestAnimationFrame.$resolve()

		return count === 1
	})

	await test(async function () {
		// calling preventDefault from component's onunload should prevent route
		// change
		await mock.requestAnimationFrame.$resolve()
		mock.location.search = "?"

		var root = mock.document.createElement("div")
		var loaded = false
		var testEnabled = true

		var sub = {
			controller: function () {
				this.onunload = function (e) {
					if (testEnabled) e.preventDefault()
				}
			},
			view: function () {
				return m("div")
			}
		}

		var component = {
			controller: function () {},
			view: function () {
				return sub
			}
		}

		m.route(root, "/a", {
			"/a": component,
			"/b": {
				controller: function () { loaded = true },
				view: function () {}
			}
		})

		await mock.requestAnimationFrame.$resolve()

		m.route("/b")

		await mock.requestAnimationFrame.$resolve()
		testEnabled = false

		return loaded === false
	})

	await test(async function () {
		// calling preventDefault from subcomponent's onunload should prevent
		// route change
		await mock.requestAnimationFrame.$resolve()
		mock.location.search = "?"

		var root = mock.document.createElement("div")
		var loaded = false
		var testEnabled = true

		var subsub = {
			controller: function () {
				this.onunload = function (e) {
					if (testEnabled) e.preventDefault()
				}
			},
			view: function () {
				return m("div")
			}
		}

		var sub = {
			controller: function () {},
			view: function () {
				return subsub
			}
		}

		var component = {
			controller: function () {},
			view: function () {
				return sub
			}
		}

		m.route(root, "/a", {
			"/a": component,
			"/b": {
				controller: function () { loaded = true },
				view: function () {}
			}
		})

		await mock.requestAnimationFrame.$resolve()

		m.route("/b")

		await mock.requestAnimationFrame.$resolve()
		testEnabled = false

		return loaded === false
	})

	await test(async function () {
		// calling preventDefault from non-curried component's onunload should
		// prevent route change
		await mock.requestAnimationFrame.$resolve()
		mock.location.search = "?"

		var root = mock.document.createElement("div")
		var loaded = false
		var testEnabled = true

		var sub = {
			controller: function () {
				this.onunload = function (e) {
					if (testEnabled) e.preventDefault()
				}
			},
			view: function () {
				return m("div")
			}
		}

		var component = {
			controller: function () {},
			view: function () {
				return sub
			}
		}

		m.route(root, "/a", {
			"/a": component,
			"/b": {
				controller: function () { loaded = true },
				view: function () {}
			}
		})

		await mock.requestAnimationFrame.$resolve()

		m.route("/b")

		await mock.requestAnimationFrame.$resolve()
		testEnabled = false

		return loaded === false
	})

	await test(async function () {
		// calling preventDefault from non-curried subcomponent's onunload
		// should prevent route change
		await mock.requestAnimationFrame.$resolve()
		mock.location.search = "?"

		var root = mock.document.createElement("div")
		var loaded = false
		var testEnabled = true

		var subsub = {
			controller: function () {
				this.onunload = function (e) {
					if (testEnabled) e.preventDefault()
				}
			},
			view: function () {
				return m("div")
			}
		}

		var sub = {
			controller: function () {},
			view: function () {
				return subsub
			}
		}

		var component = {
			controller: function () {},
			view: function () {
				return sub
			}
		}

		m.route(root, "/a", {
			"/a": component,
			"/b": {
				controller: function () { loaded = true },
				view: function () {}
			}
		})

		await mock.requestAnimationFrame.$resolve()

		m.route("/b")

		await mock.requestAnimationFrame.$resolve()
		testEnabled = false

		return loaded === false
	})

	await test(async function () {
		// nested components under keyed components should render
		await mock.requestAnimationFrame.$resolve()

		var root = mock.document.createElement("div")
		var count = 0

		var Reply = {
			controller: function () {},
			view: function () {
				count++
				return m(".reply")
			}
		}

		var CommentList = {
			controller: function () {},
			view: function (ctrl, props) {
				return m(".list", props.list.map(function (i) {
					return m(".comment", [
						m.component(Reply, {key: i})
					])
				}))
			}
		}

		var App = {
			controller: function () {},
			view: function () {
				return m(".outer", [
					m(".inner", m.component(CommentList, {list: [1, 2, 3]}))
				])
			}
		}

		m.mount(root, App)

		await mock.requestAnimationFrame.$resolve()

		return count === 3
	})

	await test(async function () {
		// a route change should initialize a component's controller
		await mock.requestAnimationFrame.$resolve()
		mock.location.search = "?"

		var root = mock.document.createElement("div")
		var countA = 0
		var countB = 0
		var subA = {
			controller: function (){ countA += 1 },
			view: function () { return m("div") }
		}
		var subB = {
			controller: function () { countB += 1 },
			view: function () { return m("div") }
		}
		m.route(root, "/a", {
			"/a": {
				view: function () {
					return m(".page-a", [
						m("h1"), m.component(subA, {x: 11})
					])
				}
			},
			"/b": {
				view: function () {
					return m(".page-b", [
						m("h2"), m.component(subB, {y: 22})
					])
				}
			}
		})

		await mock.requestAnimationFrame.$resolve()

		m.route("/b")

		await mock.requestAnimationFrame.$resolve()

		m.route("/a")

		await mock.requestAnimationFrame.$resolve()

		return countA === 2 && countB === 1
	})

	await test(async function () {
		var root = mock.document.createElement("div")
		var component = {}
		var unloaded = false
		component.controller = function () {
			this.onunload = function () { unloaded = true }
		}
		component.view = function () {}
		m.mount(root, component)
		m.mount(root, {controller: function () {}, view: function () {}})

		return unloaded === true
	})

	await test(async function () {
		await mock.requestAnimationFrame.$resolve()

		var root = mock.document.createElement("div")
		var initCount = 0
		var component = {}
		component.view = function () {
			return m("div", {
				config: function (el, init) {
					if (!init) initCount++
				}
			})
		}
		m.mount(root, component)

		await mock.requestAnimationFrame.$resolve()

		m.redraw()

		await mock.requestAnimationFrame.$resolve()

		return initCount === 1
	})

	await test(async function () {
		var root = mock.document.createElement("div")

		var dom = mock.document.createElement("div")

		var show = true

		var component = {
			view: function () {
				return [
					m(".foo", {
						key: 1,
						config: test,
						onclick: function () { show = !show }
					}),
					show ? m(".bar", {key: 2}) : null
				]
			}
		}

		function test(el, init) {
			if (!init) {
				root.appendChild(dom)
			}
		}

		m.mount(root, component)

		await mock.requestAnimationFrame.$resolve()

		show = false
		m.redraw()

		await mock.requestAnimationFrame.$resolve()

		show = true
		m.redraw()

		await mock.requestAnimationFrame.$resolve()

		return root.childNodes.length === 3
	})

	await test(async function () {
		var root = mock.document.createElement("div")
		var show = true
		var testcomponent = {
			controller: function () {},
			view: function () {
				return m("div", "component")
			}
		}

		var app = {
			view: function () {
				return show ? [
					m("h1", "1"),
					testcomponent
				] : [
					m("h1", "2")
				]
			}
		}

		m.mount(root, app)

		await mock.requestAnimationFrame.$resolve()

		show = false
		m.redraw()

		await mock.requestAnimationFrame.$resolve()

		show = true
		m.redraw()

		await mock.requestAnimationFrame.$resolve()

		return root.childNodes.length === 2
	})

	await test(async function () {
		// Components should not require a view
		await mock.requestAnimationFrame.$resolve()
		mock.location.search = "?"

		var Component = {
			view: function () {
				return m(".comp")
			}
		}

		var root = mock.document.createElement("div")
		m.route.mode = "search"
		m.route(root, "/foo", {
			"/foo": {
				view: function () {
					return [Component]
				}
			}
		})

		await mock.requestAnimationFrame.$resolve()

		return root.childNodes[0].nodeName === "DIV"
	})

	await test(async function () {
		// https://github.com/lhorie/mithril.js/issues/551
		var root = mock.document.createElement("div")
		var a = false
		var found = false
		var unloaded = false
		var redraws = 0

		var Comp = {
			view: function () {
				redraws++
				return m("div", {config: Comp.config}, [
					m("div", {
						onclick: function () {
							a = !a
							m.redraw(true)
							found = root.childNodes[0].childNodes[1]
						}
					}, "asd"),
					a ? m("#a", "aaa") : null,
					"test"
				])
			},
			config: function (el, init, ctx) {
				if (!init) ctx.onunload = function () { unloaded = true }
			}
		}

		var Root = {
			view: function () {
				return Comp
			}
		}

		m.mount(root, Root)

		await mock.requestAnimationFrame.$resolve()
		
		var target = root.childNodes[0].childNodes[0]
		target.onclick({currentTarget: target})

		await mock.requestAnimationFrame.$resolve()

		return !unloaded && found.id === "a" && redraws === 3
	})

	await test(async function () {
		// https://github.com/lhorie/mithril.js/issues/551
		var root = mock.document.createElement("div")
		var a = false
		var found = false
		var unloaded = false
		var redraws = 0

		var Comp = {
			view: function () {
				redraws++
				return m("div", {config: Comp.config}, [
					m("div", {
						onclick: function () {
							a = !a
							m.redraw(true)
							found = root.childNodes[0].childNodes[1]
							m.redraw.strategy("none")
						}
					}, "asd"),
					a ? m("#a", "aaa") : null,
					"test"
				])
			},
			config: function (el, init, ctx) {
				if (!init) ctx.onunload = function () { unloaded = true }
			}
		}

		var Root = {
			view: function () {
				return Comp
			}
		}

		m.mount(root, Root)

		await mock.requestAnimationFrame.$resolve()
		var target = root.childNodes[0].childNodes[0]
		target.onclick({currentTarget: target})

		await mock.requestAnimationFrame.$resolve()

		return !unloaded && found.id === "a" && redraws === 2
	})

	await test(async function () {
		var root = mock.document.createElement("div")
		var redraws = 0
		var Root = {
			view: function () {
				redraws++
				return m("div", {onclick: function () { m.redraw(true) }})
			}
		}

		m.mount(root, Root)

		await mock.requestAnimationFrame.$resolve()
		var target = root.childNodes[0]
		target.onclick({currentTarget: target})

		await mock.requestAnimationFrame.$resolve()

		return redraws === 3
	})

	await test(async function () {
		// https://github.com/lhorie/mithril.js/issues/555
		var root = mock.document.createElement("div")

		var MyComponent = {
			controller: function (args) {
				this.name = args.name
			},
			view: function (ctrl) {
				return m("div", ctrl.name)
			}
		}

		var FooPage = {
			view: function () {
				return m("div", [
					m("a[href=/]", {config: m.route}, "foo"),
					m("a[href=/bar]", {config: m.route}, "bar"),
					m.component(MyComponent, {name: "Jane"})
				])
			}
		}

		var BarPage = {
			view: function () {
				return m("div", [
					m("a[href=/]", {config: m.route}, "foo"),
					m("a[href=/bar]", {config: m.route}, "bar"),
					m.component(MyComponent, {name: "Bob"})
				])
			}
		}

		m.route(root, "/", {
			"/": FooPage,
			"/bar": BarPage
		})

		await mock.requestAnimationFrame.$resolve()

		m.route("/bar")

		await mock.requestAnimationFrame.$resolve()

		var parent = root.childNodes[0].childNodes[2].childNodes[0]
		return parent.nodeValue === "Bob"
	})

	await test(async function () {
		var root = mock.document.createElement("div")
		var redraws = 0
		var data

		var Comp = {
			controller: function () {
				this.foo = m.request({method: "GET", url: "/foo"})
			},
			view: function (ctrl) {
				redraws++
				data = ctrl.foo()
				return m("div")
			}
		}

		var Root = {
			view: function () {
				return Comp
			}
		}

		m.mount(root, Root)

		await mock.requestAnimationFrame.$resolve()
		mock.XMLHttpRequest.$instances.pop().onreadystatechange()

		await mock.requestAnimationFrame.$resolve()
		m.mount(root, null)
		await mock.requestAnimationFrame.$resolve()

		return redraws === 1 && data.url === "/foo"
	})

	await test(async function () {
		await mock.requestAnimationFrame.$resolve()
		mock.location.search = "?"

		var root = mock.document.createElement("div")
		var redraws1 = 0
		var redraws2 = 0

		var Comp1 = {
			controller: function () {
				this.foo = m.request({method: "GET", url: "/foo"})
			},
			view: function () {
				redraws1++
				return m("div")
			}
		}

		var Comp2 = {
			controller: function () {
				this.bar = m.request({method: "GET", url: "/bar"})
			},
			view: function () {
				redraws2++
				return m("div")
			}
		}

		var Root = {
			view: function () {
				return m("div", [Comp1, Comp2])
			}
		}
		m.mount(root, Root)

		await mock.requestAnimationFrame.$resolve()
		mock.XMLHttpRequest.$instances.pop().onreadystatechange()

		await mock.requestAnimationFrame.$resolve()
		mock.XMLHttpRequest.$instances.pop().onreadystatechange()

		await mock.requestAnimationFrame.$resolve()
		m.mount(root, null)
		await mock.requestAnimationFrame.$resolve()

		return redraws1 === 1 && redraws2 === 1
	})

	await test(async function () {
		var root = mock.document.createElement("div")
		var redraws1 = 0
		var redraws2 = 0

		var Comp1 = {
			controller: function () {
				this.foo = m.request({method: "GET", url: "/foo"})
			},
			view: function () {
				redraws1++
				return m("div")
			}
		}

		var Root1 = {
			view: function () {
				return Comp1
			}
		}

		var Comp2 = {
			controller: function () {
				this.bar = m.request({method: "GET", url: "/bar"})
			},
			view: function () {
				redraws2++
				return m("div")
			}
		}

		var Root2 = {
			view: function () {
				return Comp2
			}
		}

		m.route(root, "/", {
			"/": Root1,
			"/root2": Root2
		})

		await mock.requestAnimationFrame.$resolve()
		mock.XMLHttpRequest.$instances.pop().onreadystatechange()

		m.route("/root2")

		await mock.requestAnimationFrame.$resolve()
		mock.XMLHttpRequest.$instances.pop().onreadystatechange()

		await mock.requestAnimationFrame.$resolve()
		m.mount(root, null)
		await mock.requestAnimationFrame.$resolve()

		return redraws1 === 1 && redraws2 === 1
	})

	await test(async function () {
		var root = mock.document.createElement("div")

		var cond = true
		var controller1 = null
		var controller2 = null

		var Comp1 = {
			view: function (ctrl) {
				controller1 = ctrl
				return m("div")
			}
		}

		var Comp2 = {
			view: function (ctrl) {
				controller2 = ctrl
				return m("div")
			}
		}

		var Root = {
			view: function () {
				return cond ? Comp1 : Comp2
			}
		}

		m.mount(root, Root)

		await mock.requestAnimationFrame.$resolve()

		cond = false
		m.redraw(true)

		await mock.requestAnimationFrame.$resolve()

		return controller1 !== controller2
	})

	await test(async function () {
		var root = mock.document.createElement("div")

		var cond = true
		var unloaded = false

		var Comp1 = {
			view: function () {
				return m("div", {
					config: function (el, init, ctx) {
						ctx.onunload = function () { unloaded = true }
					}
				})
			}
		}

		var Comp2 = {
			view: function () {
				return m("div")
			}
		}

		var Root = {
			view: function () {
				return cond ? Comp1 : Comp2
			}
		}

		m.mount(root, Root)

		await mock.requestAnimationFrame.$resolve()

		cond = false
		m.redraw(true)

		await mock.requestAnimationFrame.$resolve()

		return unloaded
	})

	await test(async function () {
		var root = mock.document.createElement("div")

		var cond = true
		var initialized = null

		var Comp1 = {
			view: function () {
				return m("div")
			}
		}

		var Comp2 = {
			view: function () {
				return m("div", {
					config: function (el, init) { initialized = init }
				})
			}
		}

		var Root = {
			view: function () {
				return cond ? Comp1 : Comp2
			}
		}

		m.mount(root, Root)

		await mock.requestAnimationFrame.$resolve()

		cond = false
		m.redraw(true)

		await mock.requestAnimationFrame.$resolve()

		return initialized === false
	})

	await test(async function () {
		var root = mock.document.createElement("div")
		var el

		var BarComponent = {
			view: function () {
				return m("#bar", "test")
			}
		}

		var FooPage = {
			view: function (ctrl) {
				return m("div", [
					m("button", {
						onclick: function () {
							ctrl.bar = true
							m.redraw(true)
							el = root.childNodes[0].childNodes[1]
						}
					}, "click me"),
					ctrl.bar ? m.component(BarComponent) : ""
				])
			}
		}

		m.mount(root, FooPage)

		await mock.requestAnimationFrame.$resolve()
		
		root.childNodes[0].childNodes[0].onclick({})

		return el.id === "bar"
	})

	// m.withAttr
	await test(async function () {
		// the handler is called with the correct value & context when
		// callbackThis not given
		var _this = {}
		var value, context
		var handler = m.withAttr("test", function (data) {
			value = data
			/* eslint-disable consistent-this, no-invalid-this */
			context = this
			/* eslint-enable consistent-this, no-invalid-this */
		})

		handler.call(_this, {currentTarget: {test: "foo"}})
		return value === "foo" && context === _this
	})

	await test(async function () {
		// the handler is called with the correct value & context when
		// callbackThis is given
		var _this = {}
		var value, context
		var handler = m.withAttr("test", function (data) {
			value = data
			/* eslint-disable consistent-this, no-invalid-this */
			context = this
			/* eslint-enable consistent-this, no-invalid-this */
		}, _this)
		handler({currentTarget: {test: "foo"}})
		return value === "foo" && context === _this
	})

	// m.trust
	await test(async function () { return m.trust("test").valueOf() === "test" })

	// m.render
	await test(async function () {
		var root = mock.document.createElement("div")
		m.render(root, "test")
		return root.childNodes[0].nodeValue === "test"
	})

	await test(async 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
	})

	await test(async 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
	})

	await test(async 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
	})

	await test(async 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
	})

	await test(async 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
	})

	await test(async function () {
		var root = mock.document.createElement("div")
		m.render(root, m("div", [undefined]))
		return root.childNodes[0].childNodes[0].nodeValue === ""
	})

	await test(async function () {
		var root = mock.document.createElement("div")
		m.render(root, m("div", [null]))
		return root.childNodes[0].childNodes[0].nodeValue === ""
	})

	await test(async function () {
		var root = mock.document.createElement("div")
		m.render(root, m("div", [true]))
		return root.childNodes[0].childNodes[0].nodeValue === ""
	})

	await test(async function () {
		var root = mock.document.createElement("div")
		m.render(root, m("div", [false]))
		return root.childNodes[0].childNodes[0].nodeValue === ""
	})

	await test(async function () {
		var root = mock.document.createElement("div")
		m.render(root, m("svg", [m("g")]))
		await nextFrame()
		var g = root.childNodes[0].childNodes[0]
		return g.nodeName.toUpperCase() === "G" &&
			g.namespaceURI === "http://www.w3.org/2000/svg"
	})

	await test(async function () {
		var root = mock.document.createElement("div")
		m.render(root, m("svg", [m("a[href='http://google.com']")]))
		await nextFrame()
		return root.childNodes[0].childNodes[0].nodeName.toUpperCase() === "A"
	})

	await test(async 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"})]))
		await nextFrame()
		return root.childNodes[0].childNodes.length === 1
	})

	await test(async function () {
		var root = mock.document.createElement("div")
		m.render(root, m("ul", [m("li")]))
		m.render(root, m("ul", [m("li"), undefined]))
		await nextFrame()
		return root.childNodes[0].childNodes[1].nodeValue === ""
	})

	await test(async function () {
		var root = mock.document.createElement("div")
		m.render(root, m("ul", [m("li"), m("li")]))
		m.render(root, m("ul", [m("li"), undefined]))
		await nextFrame()
		return root.childNodes[0].childNodes[1].nodeValue === ""
	})

	await test(async function () {
		var root = mock.document.createElement("div")
		m.render(root, m("ul", [m("li")]))
		m.render(root, m("ul", [undefined]))
		await nextFrame()
		return root.childNodes[0].childNodes[0].nodeValue === ""
	})

	await test(async function () {
		var root = mock.document.createElement("div")
		m.render(root, m("ul", [m("li")]))
		m.render(root, m("ul", [{}]))
		await nextFrame()
		return root.childNodes[0].childNodes.length === 0
	})

	await test(async function () {
		var root = mock.document.createElement("div")
		m.render(root, m("ul", [m("li")]))
		m.render(root, m("ul", [{tag: "b", attrs: {}}]))
		await nextFrame()
		return root.childNodes[0].childNodes[0].nodeName === "B"
	})

	await test(async function () {
		var root = mock.document.createElement("div")
		m.render(root, m("ul", [m("li")]))
		/* eslint-disable no-new-wrappers */
		m.render(root, m("ul", [{tag: new String("b"), attrs: {}}]))
		await nextFrame()
		/* eslint-enable no-new-wrappers */
		return root.childNodes[0].childNodes[0].nodeName === "B"
	})

	await test(async function () {
		var root = mock.document.createElement("div")
		m.render(root, m("ul", [m("li", [m("a")])]))
		m.render(root, m("ul", [{subtree: "retain"}]))
		await mock.requestAnimationFrame.$resolve()
		return root.childNodes[0].childNodes[0].childNodes[0].nodeName === "A"
	})

	await test(async 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"))
		await mock.requestAnimationFrame.$resolve()
		return root.childNodes[0].childNodes[0].nodeValue === "test"
	})

	await test(async 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")]))
		await mock.requestAnimationFrame.$resolve()
		return root.childNodes[0].childNodes[0].nodeValue === "test"
	})

	await test(async 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")]))
		await mock.requestAnimationFrame.$resolve()
		return root.childNodes[0].childNodes[0].nodeName === "DIV"
	})

	await test(async 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")]))
		await mock.requestAnimationFrame.$resolve()
		return root.childNodes[0].childNodes[0].nodeName === "DIV"
	})

	await test(async 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")]))
		await mock.requestAnimationFrame.$resolve()
		return root.childNodes[0].childNodes[0].nodeValue === "test"
	})

	await test(async 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")]]))
		await mock.requestAnimationFrame.$resolve()
		return root.childNodes[0].childNodes[1].id === "baz"
	})

	// skipped: root length is 3, cannot render to document in real DOM:
	// <!DOCTYPE html>
	// comment
	// html
	await test(async function () {
		// https://github.com/lhorie/mithril.js/issues/48
		var root = mock.document
		m.render(root, m("html", [m("#foo")]))
		await mock.requestAnimationFrame.$resolve()
		var result = root.childNodes[0].childNodes[0].id === "foo"
		root.childNodes = [mock.document.createElement("html")]
		return result
	}, false)

	await test(async 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"))
		await mock.requestAnimationFrame.$resolve()
		return root.childNodes[0].childNodes[0].nodeValue === "test"
	})

	await test(async 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"))
		await mock.requestAnimationFrame.$resolve()
		return root.childNodes[0].childNodes[0].nodeValue === "test"
	})

	await test(async 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"))
		await mock.requestAnimationFrame.$resolve()
		return root.childNodes[0].childNodes[0].nodeValue === "test1"
	})

	await test(async 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"))
		await mock.requestAnimationFrame.$resolve()
		return root.childNodes[0].childNodes[0].nodeValue === "test1"
	})

	await test(async 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")]))
		await mock.requestAnimationFrame.$resolve()
		return root.childNodes[0].childNodes[1].childNodes[0].nodeValue === "b"
	})

	await test(async 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")
		]))

		await mock.requestAnimationFrame.$resolve()
		return root.childNodes[0].childNodes[2].childNodes[0].nodeValue === "c"
	})

	await test(async 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")
		]))

		await mock.requestAnimationFrame.$resolve()
		var parent = root.childNodes[0]
		return parent.childNodes[3].childNodes[0].nodeValue === "d" &&
			parent.childNodes[4].id === "bar"
	})

	await test(async 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"]))
		await mock.requestAnimationFrame.$resolve()
		var parent = root.childNodes[0]
		return parent.childNodes[1].childNodes[0].nodeValue === "b" &&
			parent.childNodes[2].nodeValue === "test"
	})

	await test(async function () {
		// https://github.com/lhorie/mithril.js/issues/50
		var root = mock.document.createElement("div")
		m.render(root, m("#foo", [["a", "b"], "test"]))
		await mock.requestAnimationFrame.$resolve()
		var parent = root.childNodes[0]
		return parent.childNodes[1].nodeValue === "b" &&
			parent.childNodes[2].nodeValue === "test"
	})

	await test(async 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")])
		]))
		await mock.requestAnimationFrame.$resolve()

		var node = root.childNodes[0].childNodes[1].childNodes[0]
		return node.nodeName === "SPAN"
	})

	await test(async 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")])
		]))
		await mock.requestAnimationFrame.$resolve()

		var node = root.childNodes[0].childNodes[1].childNodes[0]
		return node.nodeValue === "test"
	})

	await test(async 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")])
		]))
		await mock.requestAnimationFrame.$resolve()

		var node = root.childNodes[0].childNodes[1].childNodes[0]
		return node.nodeValue === "test"
	})

	await test(async function () {
		// https://github.com/lhorie/mithril.js/issues/55
		var root = mock.document.createElement("div")
		m.render(root, m("#a"))
		await mock.requestAnimationFrame.$resolve()
		var elementBefore = root.childNodes[0]
		m.render(root, m("#b"))
		await mock.requestAnimationFrame.$resolve()
		var elementAfter = root.childNodes[0]
		return elementBefore !== elementAfter
	})

	await test(async function () {
		// https://github.com/lhorie/mithril.js/issues/56
		var root = mock.document.createElement("div")
		m.render(root, [null, "foo"])
		m.render(root, ["bar"])
		await mock.requestAnimationFrame.$resolve()
		return root.childNodes.length === 1
	})

	await test(async function () {
		// https://github.com/lhorie/mithril.js/issues/56
		var root = mock.document.createElement("div")
		m.render(root, m("div", "foo"))
		await mock.requestAnimationFrame.$resolve()
		return root.childNodes.length === 1
	})

	await test(async 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 === ""
	})

	await test(async 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"
	})

	await test(async 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 === ""
	})

	await test(async function () {
		var root = mock.document.createElement("div")
		m.render(root, m("div[style='background:red']"))
		return useRealDom
			? root.childNodes[0].style.background === "red"
			: root.childNodes[0].style === "background:red"
	})

	// skipped: in realDOM, style value never can be undefined
	await test(async 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
	}, false)

	await test(async 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"
	})

	await test(async 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"
	})

	await test(async 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"
	})

	await test(async 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"
	})

	await test(async 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"
	})

	await test(async 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"
	})

	await test(async 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"
	})

	await test(async 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"
	})

	await test(async 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"
	})

	await test(async 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"
	})

	await test(async function () {
		var root = mock.document.createElement("div")

		var success = false

		m.render(root, m("div", {
			config: function (elem, isInit, ctx) { ctx.data = 1 }
		}))

		m.render(root, m("div", {
			config: function (elem, isInit, ctx) { success = ctx.data === 1 }
		}))

		return success
	})

	await test(async function () {
		var root = mock.document.createElement("div")

		var index = 0
		var success = true

		function statefulConfig(elem, isInit, ctx) { ctx.data = index++ }

		var node = m("div", {config: statefulConfig})
		m.render(root, [node, node])

		index = 0

		function checkConfig(elem, isInit, ctx) {
			success = success && (ctx.data === index++)
		}

		node = m("div", {config: checkConfig})
		m.render(root, [node, node])
		return success
	})

	await test(async 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
	})

	await test(async function () {
		var root = mock.document.createElement("div")
		var count = 0

		m.render(root, m("div", m("a", {
			config: function () {
				var island = mock.document.createElement("div")
				count++
				if (count > 2) throw new Error("too much recursion...")
				m.render(island, m("div"))
			}
		})))

		return count === 1
	})

	await test(async 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
	})

	await test(async 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}, 1),
			m("a", {key: 2}, 2),
			m("a", {key: 3}, 3)
		])

		var firstBefore = root.childNodes[0]

		m.render(root, [
			m("a", {key: 4}, 4),
			m("a", {key: 1}, 1),
			m("a", {key: 2}, 2),
			m("a", {key: 3}, 3)
		])

		var firstAfter = root.childNodes[1]

		return firstBefore === firstAfter &&
			root.childNodes[0].childNodes[0].nodeValue === "4" &&
			root.childNodes.length === 4
	})

	await test(async function () {
		// https://github.com/lhorie/mithril.js/issues/98
		var root = mock.document.createElement("div")

		m.render(root, [
			m("a", {key: 1}, 1),
			m("a", {key: 2}, 2),
			m("a", {key: 3}, 3)
		])

		var firstBefore = root.childNodes[0]

		m.render(root, [
			m("a", {key: 4}, 4),
			m("a", {key: 1}, 1),
			m("a", {key: 2}, 2)
		])

		var firstAfter = root.childNodes[1]

		return firstBefore === firstAfter &&
			root.childNodes[0].childNodes[0].nodeValue === "4" &&
			root.childNodes.length === 3
	})

	await test(async function () {
		// https://github.com/lhorie/mithril.js/issues/98
		var root = mock.document.createElement("div")

		m.render(root, [
			m("a", {key: 1}, 1),
			m("a", {key: 2}, 2),
			m("a", {key: 3}, 3)
		])

		var firstBefore = root.childNodes[1]

		m.render(root, [
			m("a", {key: 2}, 2),
			m("a", {key: 3}, 3),
			m("a", {key: 4}, 4)
		])

		var firstAfter = root.childNodes[0]

		return firstBefore === firstAfter &&
			root.childNodes[0].childNodes[0].nodeValue === "2" &&
			root.childNodes.length === 3
	})

	await test(async function () {
		// https://github.com/lhorie/mithril.js/issues/98
		var root = mock.document.createElement("div")

		m.render(root, [
			m("a", {key: 1}, 1),
			m("a", {key: 2}, 2),
			m("a", {key: 3}, 3),
			m("a", {key: 4}, 4),
			m("a", {key: 5}, 5)
		])

		var firstBefore = root.childNodes[0]
		var secondBefore = root.childNodes[1]
		var fourthBefore = root.childNodes[3]

		m.render(root, [
			m("a", {key: 4}, 4),
			m("a", {key: 10}, 10),
			m("a", {key: 1}, 1),
			m("a", {key: 2}, 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].childNodes[0].nodeValue === "10" &&
			root.childNodes.length === 4
	})

	await test(async function () {
		// https://github.com/lhorie/mithril.js/issues/98
		var root = mock.document.createElement("div")

		m.render(root, [
			m("a", {key: 1}, 1),
			m("a", {key: 2}, 2),
			m("a", {key: 3}, 3),
			m("a", {key: 4}, 4),
			m("a", {key: 5}, 5)
		])

		var firstBefore = root.childNodes[0]
		var secondBefore = root.childNodes[1]
		var fourthBefore = root.childNodes[3]

		m.render(root, [
			m("a", {key: 4}, 4),
			m("a", {key: 10}, 10),
			m("a", {key: 2}, 2),
			m("a", {key: 1}, 1),
			m("a", {key: 6}, 6),
			m("a", {key: 7}, 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].childNodes[0].nodeValue === "10" &&
			root.childNodes[4].childNodes[0].nodeValue === "6" &&
			root.childNodes[5].childNodes[0].nodeValue === "7" &&
			root.childNodes.length === 6
	})

	await test(async 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
	})

	await test(async function () {
		// https://github.com/lhorie/mithril.js/issues/246
		// insert at beginning with non-keyed in the middle
		var root = mock.document.createElement("div")

		m.render(root, [m("a", {key: 1}, 1)])

		var firstBefore = root.childNodes[0]

		m.render(root, [
			m("a", {key: 2}, 2),
			m("br"),
			m("a", {key: 1}, 1)
		])

		var firstAfter = root.childNodes[2]

		return firstBefore === firstAfter &&
			root.childNodes[0].childNodes[0].nodeValue === "2" &&
			root.childNodes.length === 3
	})

	await test(async function () {
		// https://github.com/lhorie/mithril.js/issues/134
		var root = mock.document.createElement("div")
		m.render(root, m("div", {contenteditable: true}, "test"))
		await mock.requestAnimationFrame.$resolve()
		mock.document.activeElement = root.childNodes[0]
		m.render(root, m("div", {contenteditable: true}, "test1"))
		m.render(root, m("div", {contenteditable: false}, "test2"))
		await mock.requestAnimationFrame.$resolve()
		return root.childNodes[0].childNodes[0].nodeValue === "test2"
	})

	await test(async 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"]))
		await mock.requestAnimationFrame.$resolve()
		return root.childNodes[0].value === "test1"
	})

	await test(async 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++
					}
				}
			})
		])

		await mock.requestAnimationFrame.$resolve()
		return unloaded === 0
	})

	await test(async function () {
		var root = mock.document.createElement("div")
		var unloadedParent = 0
		var unloadedChild = 0

		function configParent(el, init, ctx) {
			ctx.onunload = function () {
				unloadedParent++
			}
		}

		function configChild(el, init, ctx) {
			ctx.onunload = function () {
				unloadedChild++
			}
		}

		m.render(root, m("div", {config: configParent}, [
			m("a", {config: configChild})
		]))

		m.render(root, m("main", {config: configParent}, [
			m("a", {config: configChild})
		]))

		await mock.requestAnimationFrame.$resolve()
		return unloadedParent === 1 && unloadedChild === 0
	})

	await test(async function () {
		var root = mock.document.createElement("div")
		var unloadedParent = 0
		var unloadedChild = 0

		function configParent(el, init, ctx) {
			ctx.onunload = function () {
				unloadedParent++
			}
		}

		function configChild(el, init, ctx) {
			ctx.onunload = function () {
				unloadedChild++
			}
		}

		m.render(root, m("div", {config: configParent}, [
			m("a", {config: configChild})
		]))

		m.render(root, m("main", {config: configParent}, [
			m("b", {config: configChild})
		]))

		await mock.requestAnimationFrame.$resolve()
		return unloadedParent === 1 && unloadedChild === 1
	})

	await test(async 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")])

		await mock.requestAnimationFrame.$resolve()
		return root.childNodes.length === 1 &&
			root.childNodes[0].nodeName === "DIV"
	})

	await test(async 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")
		]))
		await mock.requestAnimationFrame.$resolve()
		return root.childNodes[0].childNodes[8].nodeName === "SPAN"
	})

	await test(async function () {
		// https://github.com/lhorie/mithril.js/issues/157
		var root = mock.document.createElement("div")

		m.render(root, m("ul", [
			m("li", {key: 0}, 0),
			m("li", {key: 2}, 2),
			m("li", {key: 4}, 4)
		]))

		m.render(root, m("ul", [
			m("li", {key: 0}, 0),
			m("li", {key: 1}, 1),
			m("li", {key: 2}, 2),
			m("li", {key: 3}, 3),
			m("li", {key: 4}, 4),
			m("li", {key: 5}, 5)
		]))

		await mock.requestAnimationFrame.$resolve()
		return map(root.childNodes[0].childNodes, function (n) {
			return n.childNodes[0].nodeValue
		}).join("") === "012345"
	})

	await test(async 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"}))
		await mock.requestAnimationFrame.$resolve()
		return root.childNodes[0].childNodes.length === 0
	})

	await test(async 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"}))
		await mock.requestAnimationFrame.$resolve()
		return root.childNodes[0].childNodes.length === 0
	})

	await test(async function () {
		// https://github.com/lhorie/mithril.js/issues/194
		var root = mock.document.createElement("div")

		m.render(root, m("ul", [
			m("li", {key: 0}, 0),
			m("li", {key: 1}, 1),
			m("li", {key: 2}, 2),
			m("li", {key: 3}, 3),
			m("li", {key: 4}, 4),
			m("li", {key: 5}, 5)
		]))

		m.render(root, m("ul", [
			m("li", {key: 0}, 0),
			m("li", {key: 1}, 1),
			m("li", {key: 2}, 2),
			m("li", {key: 4}, 4),
			m("li", {key: 5}, 5)
		]))

		await mock.requestAnimationFrame.$resolve()
		return map(root.childNodes[0].childNodes, function (n) {
			return n.childNodes[0].nodeValue
		}).join("") === "01245"
	})

	await test(async function () {
		// https://github.com/lhorie/mithril.js/issues/194
		var root = mock.document.createElement("div")

		m.render(root, m("ul", [
			m("li", {key: 0}, 0),
			m("li", {key: 1}, 1),
			m("li", {key: 2}, 2),
			m("li", {key: 3}, 3),
			m("li", {key: 4}, 4),
			m("li", {key: 5}, 5)
		]))

		m.render(root, m("ul", [
			m("li", {key: 1}, 1),
			m("li", {key: 2}, 2),
			m("li", {key: 3}, 3),
			m("li", {key: 4}, 4),
			m("li", {key: 5}, 5),
			m("li", {key: 6}, 6)
		]))

		m.render(root, m("ul", [
			m("li", {key: 12}, 12),
			m("li", {key: 13}, 13),
			m("li", {key: 14}, 14),
			m("li", {key: 15}, 15),
			m("li", {key: 16}, 16),
			m("li", {key: 17}, 17)
		]))

		await mock.requestAnimationFrame.$resolve()
		return map(root.childNodes[0].childNodes, function (n) {
			return n.childNodes[0].nodeValue
		}).join(",") === "12,13,14,15,16,17"
	})

	await test(async 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")]))
		await mock.requestAnimationFrame.$resolve()
		return root.childNodes[0].childNodes.length === 1
	})

	await test(async 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")]))
		await mock.requestAnimationFrame.$resolve()
		return root.childNodes[0].childNodes.length === 1
	})

	await test(async 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, [])

		await mock.requestAnimationFrame.$resolve()
		var unloaded2 = false

		function unloadable2(element, isInit, context) {
			context.onunload = function () {
				unloaded2 = true
			}
		}

		m.render(root, [m("div", {config: unloadable2})])
		m.render(root, [])
		await mock.requestAnimationFrame.$resolve()

		return unloaded1 === true && unloaded2 === true
	})

	await test(async function () {
		var root = mock.document.createElement("div")
		m.render(root, [m("div.blue")])
		m.render(root, [m("div.green", [m("div")]), m("div.blue")])
		await mock.requestAnimationFrame.$resolve()
		return root.childNodes.length === 2
	})

	await test(async function () {
		// https://github.com/lhorie/mithril.js/issues/277
		var root = mock.document.createElement("div")
		function Field() {
			this.tag = "div"
			this.attrs = {}
			this.children = "hello"
		}
		m.render(root, new Field())
		await mock.requestAnimationFrame.$resolve()
		return root.childNodes.length === 1
	})

	await test(async function () {
		var root = mock.document.createElement("div")
		m.render(root, {foo: 123})
		await mock.requestAnimationFrame.$resolve()
		return root.childNodes.length === 0
	})

	await test(async function () {
		// https://github.com/lhorie/mithril.js/issues/299
		var root = mock.document.createElement("div")

		m.render(root, m("div", [
			m("div", {key: 1}, 1),
			m("div", {key: 2}, 2),
			m("div", {key: 3}, 3),
			m("div", {key: 4}, 4),
			m("div", {key: 5}, 5),
			null, null, null, null, null, null, null, null, null, null
		]))

		m.render(root, m("div", [
			null, null,
			m("div", {key: 3}, 3),
			null, null,
			m("div", {key: 6}, 6),
			null, null,
			m("div", {key: 9}, 9),
			null, null,
			m("div", {key: 12}, 12),
			null, null,
			m("div", {key: 15}, 15)
		]))

		m.render(root, m("div", [
			m("div", {key: 1}, 1),
			m("div", {key: 2}, 2),
			m("div", {key: 3}, 3),
			m("div", {key: 4}, 4),
			m("div", {key: 5}, 5),
			null, null, null, null, null, null, null, null, null, null
		]))

		await mock.requestAnimationFrame.$resolve()
		return map(root.childNodes[0].childNodes, function (c) {
			return c.childNodes && c.childNodes.length ? c.childNodes[0].nodeValue : c.nodeValue
		}).slice(0, 5).join("") === "12345"
	})

	await test(async function () {
		// https://github.com/lhorie/mithril.js/issues/377
		var root = mock.document.createElement("div")

		m.render(root, m("div", [
			m("div", 1),
			m("div", 2), [m("div", {key: 3}, 3),
			m("div", {key: 4}, 4),
			m("div", {key: 5}, 5)],
			[m("div", {key: 6}, 6)]
		]))

		m.render(root, m("div", [
			m("div", 1),
			null,
			[m("div", {key: 3}, 3),
			m("div", {key: 4}, 4),
			m("div", {key: 5}, 5)],
			[m("div", {key: 6}, 6)]
		]))

		await mock.requestAnimationFrame.$resolve()
		return map(root.childNodes[0].childNodes, function (c) {
			return c.childNodes && c.childNodes.length ? c.childNodes[0].nodeValue : c.nodeValue
		}).join("") === "13456"
	})

	await test(async function () {
		// don't throw rendering console.log in Firefox
		var root = mock.document.createElement("div")
		/* eslint-disable no-console */
		m.render(root, m("div", [console.log()]))
		/* eslint-enable no-console */
		await mock.requestAnimationFrame.$resolve()
		return true
	})

	await test(async function () {
		var root = mock.document.createElement("div")

		m.render(root, [
			m("#div-1", {key: 1}),
			m("#div-2", {key: 2}),
			m("#div-3", {key: 3})
		])

		await mock.requestAnimationFrame.$resolve()
		root.appendChild(root.childNodes[1])

		m.render(root, [
			m("#div-1", {key: 1}),
			m("#div-3", {key: 3}),
			m("#div-2", {key: 2})
		])

		await mock.requestAnimationFrame.$resolve()
		return map(root.childNodes, function (node) {
			return node.id
		}).join() === "div-1,div-3,div-2"
	})

	await test(async function () {
		var root = mock.document.createElement("div")
		m.render(root, m("div", function () {}))
		await mock.requestAnimationFrame.$resolve()
		return root.childNodes[0].childNodes.length === 0
	})

	await test(async function () {
		var root = mock.document.createElement("div")
		m.render(root, m("div", "foo", m("a")))
		m.render(root, m("div", "test"))
		await mock.requestAnimationFrame.$resolve()
		return root.childNodes[0].childNodes.length === 1
	})

	await test(async function () {
		// if an element is preceded by a conditional, it should not lose its
		// identity
		var root = mock.document.createElement("div")
		m.render(root, m("div", [m("a"), m("input[autofocus]")]))
		await mock.requestAnimationFrame.$resolve()
		var before = root.childNodes[0].childNodes[1]
		m.render(root, m("div", [undefined, m("input[autofocus]")]))
		await mock.requestAnimationFrame.$resolve()
		var after = root.childNodes[0].childNodes[1]
		return before === after
	})

	await test(async function () {
		// unkeyed element should maintain identity if mixed w/ keyed elements
		// and identity can be inferred
		var root = mock.document.createElement("div")

		m.render(root, m("div", [
			m("a", {key: 1}),
			m("a", {key: 2}),
			m("a", {key: 3}),
			m("i")
		]))
		await mock.requestAnimationFrame.$resolve()

		var before = root.childNodes[0].childNodes[3]

		m.render(root, m("div", [
			m("b", {key: 3}),
			m("b", {key: 4}),
			m("i"),
			m("b", {key: 1})
		]))
		await mock.requestAnimationFrame.$resolve()

		var after = root.childNodes[0].childNodes[2]

		return before === after
	})

	await test(async function () {
		// unkeyed element should maintain identity if mixed w/ keyed elements
		// and text nodes and identity can be inferred
		var root = mock.document.createElement("div")

		m.render(root, m("div", [
			m("a", {key: 1}),
			m("a", {key: 2}),
			"foo",
			m("a", {key: 3}),
			m("i")
		]))
		await mock.requestAnimationFrame.$resolve()

		var before = root.childNodes[0].childNodes[4]

		m.render(root, m("div", [
			m("a", {key: 3}),
			m("a", {key: 4}),
			"bar",
			m("i"),
			m("a", {key: 1})
		]))
		await mock.requestAnimationFrame.$resolve()

		var after = root.childNodes[0].childNodes[3]

		return before === after
	})

	await test(async function () {
		var root = mock.document.createElement("div")

		m.render(root, m("div", [
			m("a", {key: 1}),
			m("a", {key: 2}),
			null,
			m("a", {key: 3}),
			m("i")
		]))
		await mock.requestAnimationFrame.$resolve()

		var before = root.childNodes[0].childNodes[4]

		m.render(root, m("div", [
			m("a", {key: 3}),
			m("a", {key: 4}),
			null,
			m("i"),
			m("a", {key: 1})
		]))
		await mock.requestAnimationFrame.$resolve()

		var after = root.childNodes[0].childNodes[3]

		return before === after
	})

	await test(async function () {
		var root = mock.document.createElement("div")

		m.render(root, m("div", [
			m("a", {key: 1}),
			m("a", {key: 2}),
			undefined,
			m("a", {key: 3}),
			m("i")
		]))
		await mock.requestAnimationFrame.$resolve()

		var before = root.childNodes[0].childNodes[4]

		m.render(root, m("div", [
			m("a", {key: 3}),
			m("a", {key: 4}),
			undefined,
			m("i"),
			m("a", {key: 1})
		]))
		await mock.requestAnimationFrame.$resolve()

		var after = root.childNodes[0].childNodes[3]

		return before === after
	})

	await test(async function () {
		var root = mock.document.createElement("div")

		m.render(root, m("div", [
			m("a", {key: 1}),
			m("a", {key: 2}),
			m.trust("a"),
			m("a", {key: 3}),
			m("i")
		]))
		await mock.requestAnimationFrame.$resolve()

		var before = root.childNodes[0].childNodes[4]

		m.render(root, m("div", [
			m("a", {key: 3}),
			m("a", {key: 4}),
			m.trust("a"),
			m("i"),
			m("a", {key: 1})
		]))
		await mock.requestAnimationFrame.$resolve()

		var after = root.childNodes[0].childNodes[3]

		return before === after
	})

	await test(async function () {
		var root = mock.document.createElement("div")
		var vdom = m("div.a", {class: undefined})
		m.render(root, vdom)
		await mock.requestAnimationFrame.$resolve()
		const node = root.childNodes[0]
		return (node.class||node.className) === "a"
	})

	await test(async function () {
		var root = mock.document.createElement("div")
		m.render(root, m(".a", [1]))
		m.render(root, m(".a", []))
		await mock.requestAnimationFrame.$resolve()
		return root.childNodes[0].childNodes.length === 0
	})
	// end m.render

	// m.redraw
	await test(async function () {
		await mock.requestAnimationFrame.$resolve() // setup
		var controller
		var root = mock.document.createElement("div")
		m.mount(root, {
			controller: function () {
				/* eslint-disable consistent-this, no-invalid-this */
				controller = this
				/* eslint-enable consistent-this, no-invalid-this */
			},
			view: function (ctrl) { return ctrl.value }
		})

		await mock.requestAnimationFrame.$resolve()

		var valueBefore = root.childNodes[0].nodeValue
		controller.value = "foo"

		m.redraw()

		await mock.requestAnimationFrame.$resolve()

		return valueBefore === "" && root.childNodes[0].nodeValue === "foo"
	})

	await test(async function () {
		await mock.requestAnimationFrame.$resolve() // setup
		var count = 0
		var root = mock.document.createElement("div")
		m.mount(root, {
			controller: function () {},
			view: function () { count++ }
		})
		await mock.requestAnimationFrame.$resolve() // teardown
		m.redraw() // should run synchronously

		m.redraw() // rest should run asynchronously since they're spamming
		m.redraw()
		m.redraw()
		await mock.requestAnimationFrame.$resolve() // teardown

		return count === 3
	})

	await test(async function () {
		await mock.requestAnimationFrame.$resolve() // setup
		var count = 0
		var root = mock.document.createElement("div")

		m.mount(root, {
			controller: function () {},
			view: function () {
				count++
			}
		})

		await mock.requestAnimationFrame.$resolve() // teardown

		m.redraw(true) // should run synchronously

		m.redraw(true) // forced to run synchronously
		m.redraw(true)
		m.redraw(true)

		await mock.requestAnimationFrame.$resolve() // teardown
		return count === 5
	})

	// m.route
	await test(async function () {
		await 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" }
			}
		})

		await mock.requestAnimationFrame.$resolve()

		var result = mock.location.search === "?/test1" &&
			root.childNodes[0].nodeValue === "foo"

		m.mount(root, null) // teardown

		return result
	})

	await test(async function () {
		await 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",
						m("a", {href: "/test2", config: m.route}, "Test2")
					]
				}
			}
		})
		await mock.requestAnimationFrame.$resolve()

		var result = mock.location.pathname === "/test2" &&
			root.childNodes[0].nodeValue === "foo" &&
			root.childNodes[1].href === "/test2"

		m.mount(root, null) // teardown

		return result
	})

	await test(async function () {
		await 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" }
			}
		})
		await mock.requestAnimationFrame.$resolve()

		var result = mock.location.hash === "#/test3" &&
			root.childNodes[0].nodeValue === "foo"

		m.mount(root, null) // teardown

		return result
	})

	await test(async function () {
		await 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") }
			}
		})
		await mock.requestAnimationFrame.$resolve()

		var result = mock.location.search === "?/test4/foo" &&
			root.childNodes[0].nodeValue === "foo"

		m.mount(root, null) // teardown

		return result
	})

	await test(async function () {
		await mock.requestAnimationFrame.$resolve() // setup
		mock.location.search = "?"

		var component = {
			controller: function () {},
			view: function () { return m.route.param("test") }
		}

		var root = mock.document.createElement("div")
		m.route.mode = "search"
		m.route(root, "/test5/foo", {
			"/": component,
			"/test5/:test": component
		})
		var paramValueBefore = m.route.param("test")
		await mock.requestAnimationFrame.$resolve()
		m.route("/")
		var paramValueAfter = m.route.param("test")
		await mock.requestAnimationFrame.$resolve()

		var result = mock.location.search === "?/" &&
			paramValueBefore === "foo" &&
			paramValueAfter === undefined

		m.mount(root, null) // teardown

		return result
	})

	await test(async function () {
		await mock.requestAnimationFrame.$resolve() // setup
		mock.location.search = "?"

		var component = {
			controller: function () {},
			view: function () { return m.route.param("a1") }
		}

		var root = mock.document.createElement("div")
		m.route.mode = "search"
		m.route(root, "/test6/foo", {
			"/": component,
			"/test6/:a1": component
		})
		var paramValueBefore = m.route.param("a1")
		await mock.requestAnimationFrame.$resolve()
		m.route("/")
		var paramValueAfter = m.route.param("a1")
		await mock.requestAnimationFrame.$resolve()

		var result = mock.location.search === "?/" &&
			paramValueBefore === "foo" &&
			paramValueAfter === undefined

		m.mount(root, null) // teardown

		return result
	})

	await test(async function () {
		// https://github.com/lhorie/mithril.js/issues/61
		await mock.requestAnimationFrame.$resolve() // setup
		mock.location.search = "?"

		var component = {
			controller: function () {},
			view: function () { return m.route.param("a1") }
		}

		var root = mock.document.createElement("div")
		m.route.mode = "search"
		m.route(root, "/test7/foo", {
			"/": component,
			"/test7/:a1": component
		})
		var routeValueBefore = m.route()
		await mock.requestAnimationFrame.$resolve()
		m.route("/")
		var routeValueAfter = m.route()
		await mock.requestAnimationFrame.$resolve()

		var result = routeValueBefore === "/test7/foo" &&
			routeValueAfter === "/"

		m.mount(root, null) // teardown

		return result
	})

	await test(async function () {
		await 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")
				}
			}
		})
		await mock.requestAnimationFrame.$resolve()

		var result = mock.location.search === "?/test8/foo/SEP/bar/baz" &&
			root.childNodes[0].nodeValue === "foo_bar/baz"

		m.mount(root, null) // teardown

		return result
	})

	await test(async function () {
		await 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")
				}
			}
		})
		await mock.requestAnimationFrame.$resolve()

		var result = mock.location.search === "?/test9/foo/bar/SEP/baz" &&
			root.childNodes[0].nodeValue === "foo/bar_baz"

		m.mount(root, null) // teardown

		return result
	})

	await test(async function () {
		await 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")
				}
			}
		})
		await mock.requestAnimationFrame.$resolve()

		var result = root.childNodes[0].nodeValue === "foo bar"

		m.mount(root, null) // teardown

		return result
	})

	await test(async function () {
		await 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" }
			}
		})
		await mock.requestAnimationFrame.$resolve()
		m.route("/test11/")
		await mock.requestAnimationFrame.$resolve()

		var result = mock.location.search === "?/test11/" &&
			root.childNodes[0].nodeValue === "bar"

		m.mount(root, null) // teardown

		return result
	})

	await test(async function () {
		await 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 () {}}
		})
		await mock.requestAnimationFrame.$resolve()
		m.route("/test12?a=foo&b=bar")
		await mock.requestAnimationFrame.$resolve()

		var result = mock.location.search === "?/test12?a=foo&b=bar" &&
			m.route.param("a") === "foo" &&
			m.route.param("b") === "bar"

		m.mount(root, null) // teardown

		return result
	})

	await test(async function () {
		await 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") }
			}
		})
		await mock.requestAnimationFrame.$resolve()
		m.route("/test13/foo?test=bar")
		await mock.requestAnimationFrame.$resolve()

		var result = mock.location.search === "?/test13/foo?test=bar" &&
			root.childNodes[0].nodeValue === "foo"

		m.mount(root, null) // teardown

		return result
	})

	await test(async function () {
		await 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" }
			}
		})
		await mock.requestAnimationFrame.$resolve()
		m.route("/test14?test&test2=")
		await mock.requestAnimationFrame.$resolve()

		var result = mock.location.search === "?/test14?test&test2=" &&
			m.route.param("test") == null &&
			m.route.param("test2") === ""

		m.mount(root, null) // teardown

		return result
	})

	await test(async function () {
		await 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 () {}}
		})
		await mock.requestAnimationFrame.$resolve()
		m.route("/test12", {a: "foo", b: "bar"})
		await mock.requestAnimationFrame.$resolve()

		var result = mock.location.search === "?/test12?a=foo&b=bar" &&
			m.route.param("a") === "foo" &&
			m.route.param("b") === "bar"

		m.mount(root, null) // teardown

		return result
	})

	await test(async function () {
		// test route params returning params object if no key is given
		await mock.requestAnimationFrame.$resolve() // setup

		var root = mock.document.createElement("div")
		m.route.mode = "search"
		m.route(root, "/", {
			"/": {controller: function () {}, view: function () {}},
			"/test12": {controller: function () {}, view: function () {}}
		})
		await mock.requestAnimationFrame.$resolve()
		m.route("/test12", {a: "foo", b: "bar"})
		await mock.requestAnimationFrame.$resolve()

		var params = m.route.param()

		var result = params.a === "foo" && params.b === "bar"

		m.mount(root, null) // teardown

		return result
	})

	await test(async function () {
		await 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 () {}
			}
		})
		await mock.requestAnimationFrame.$resolve()
		m.route("/test13")
		await mock.requestAnimationFrame.$resolve()

		var result = route1 === "/" && route2 === "/test13"

		m.mount(root, null) // teardown

		return result
	})

	await test(async function () {
		await 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 () {}}
		})
		await mock.requestAnimationFrame.$resolve()
		m.route("/test14")
		await mock.requestAnimationFrame.$resolve()

		var result = unloaded === 1

		m.mount(root, null) // teardown

		return result
	})

	await test(async function () {
		await 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")]
				}
			}
		})
		await mock.requestAnimationFrame.$resolve()
		m.route("/test15")
		await mock.requestAnimationFrame.$resolve()

		var result = unloaded === 1

		m.mount(root, null) // teardown

		return result
	})

	await test(async function () {
		await 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")
				}
			}
		})
		await mock.requestAnimationFrame.$resolve()
		m.route("/test16")
		await mock.requestAnimationFrame.$resolve()

		var result = unloaded === 1

		m.mount(root, null) // teardown

		return result
	})

	await test(async function () {
		await 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")
				}
			}
		})
		await mock.requestAnimationFrame.$resolve()
		m.route("/test17")
		await mock.requestAnimationFrame.$resolve()

		var result = unloaded === 1

		m.mount(root, null) // teardown

		return result
	})

	await test(async function () {
		await 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")]
				}
			}
		})
		await mock.requestAnimationFrame.$resolve()
		m.route("/test18")
		await mock.requestAnimationFrame.$resolve()

		var result = unloaded === 1

		m.mount(root, null) // teardown

		return result
	})

	await test(async function () {
		await 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++
								}
							}
						})
					]
				}
			}
		})
		await mock.requestAnimationFrame.$resolve()
		m.route("/test20")
		await mock.requestAnimationFrame.$resolve()

		var result = unloaded === 1

		m.mount(root, null) // teardown

		return result
	})

	await test(async function () {
		await 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++
								}
							}
						})
					]
				}
			}
		})
		await mock.requestAnimationFrame.$resolve()
		m.route("/test21")
		await mock.requestAnimationFrame.$resolve()

		var result = unloaded === 1

		m.mount(root, null) // teardown

		return result
	})

	await test(async function () {
		await 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")
				}
			}
		})
		await mock.requestAnimationFrame.$resolve()
		var foo = root.childNodes[0].childNodes[0].nodeValue
		m.route("/bar")
		await mock.requestAnimationFrame.$resolve()
		var bar = root.childNodes[0].childNodes[0].nodeValue

		var result = (foo === "foo" && bar === "bar")

		m.mount(root, null) // teardown

		return result
	})

	await test(async function () {
		await mock.requestAnimationFrame.$resolve() // setup
		mock.location.search = "?"

		var root = mock.document.createElement("div")
		var unloaded = 0
		function config(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"))
				}
			}
		})
		await mock.requestAnimationFrame.$resolve()
		m.route("/bar1")
		await mock.requestAnimationFrame.$resolve()

		var result = unloaded === 1

		m.mount(root, null) // teardown

		return result
	})

	await test(async function () {
		await 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")
				}
			}
		})
		await mock.requestAnimationFrame.$resolve()

		var result = strategy === "all" && root.childNodes.length === 0

		m.mount(root, null) // teardown

		return result
	})

	await test(async function () {
		await mock.requestAnimationFrame.$resolve() // setup
		mock.location.search = "?"

		var root = mock.document.createElement("div")
		var count = 0
		var strategy
		function config(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})
				}
			}
		})
		await mock.requestAnimationFrame.$resolve()
		m.route("/bar1")
		await mock.requestAnimationFrame.$resolve()

		var result = strategy === "all" && count === 1

		m.mount(root, null) // teardown

		return result
	})

	await test(async function () {
		await 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({})
		await mock.requestAnimationFrame.$resolve()

		var result = strategy === "diff" &&
			root.childNodes[0].childNodes[0].nodeValue === "1"

		m.mount(root, null) // teardown

		return result
	})

	await test(async function () {
		await mock.requestAnimationFrame.$resolve() // setup
		mock.location.search = "?"

		var root = mock.document.createElement("div")
		var count = 0
		function config(el, init) { if (!init) count++ }
		m.route.mode = "search"
		m.route(root, "/foo1", {
			"/foo1": {
				controller: function () {},
				view: function () {
					return m("div", {
						config: config,
						onclick: function () {
							m.redraw.strategy("all")
						}
					})
				}
			}
		})
		root.childNodes[0].onclick({})
		await mock.requestAnimationFrame.$resolve()

		var result = count === 2

		m.mount(root, null) // teardown

		return result
	})

	await test(async function () {
		await 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 () {
					return ""
				}
			}
		})
		var result = value === "foo+bar"

		m.mount(root, null) // teardown

		return result
	})

	await test(async function () {
		await 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" }
			},
			"/test22": {
				controller: function () {},
				view: function () { return "bar" }
			}
		})
		await mock.requestAnimationFrame.$resolve()
		m.route(String("/test22/"))
		await mock.requestAnimationFrame.$resolve()

		var result = mock.location.search === "?/test22/" &&
			root.childNodes[0].nodeValue === "bar"

		m.mount(root, null) // teardown

		return result
	})

	await test(async function () {
		await 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" }
			},
			"/test23": {
				controller: function () {},
				view: function () { return "bar" }
			}
		})
		await mock.requestAnimationFrame.$resolve()
		m.route(new String("/test23/")) // eslint-disable-line no-new-wrappers
		await mock.requestAnimationFrame.$resolve()

		var result = mock.location.search === "?/test23/" &&
			root.childNodes[0].nodeValue === "bar"

		m.mount(root, null) // teardown

		return result
	})

	await test(async function () {
		await mock.requestAnimationFrame.$resolve() // setup
		mock.location.search = "?"

		var root = mock.document.createElement("div")
		var value
		m.route(root, String("/foo+bar"), {
			"/:arg": {
				controller: function () { value = m.route.param("arg") },
				view: function () { return "" }
			}
		})
		var result = value === "foo+bar"

		m.mount(root, null) // teardown

		return result
	})

	await test(async function () {
		await mock.requestAnimationFrame.$resolve() // setup
		mock.location.search = "?"

		var root = mock.document.createElement("div")
		/* eslint-disable no-new-wrappers */
		var initial = new String("/foo+bar")
		/* eslint-enable no-new-wrappers */
		var value
		m.route(root, initial, {
			"/:arg": {
				controller: function () { value = m.route.param("arg") },
				view: function () {
					return ""
				}
			}
		})
		var result = value === "foo+bar"

		m.mount(root, null) // teardown

		return result
	})

	await test(async function () {
		await mock.requestAnimationFrame.$resolve()
		mock.location.search = "?"

		var root = mock.document.createElement("div")

		var a = {}
		a.controller = function () { m.route("/b") }
		a.view = function () { return "a" }

		var b = {}
		b.controller = function () {}
		b.view = function () { return "b" }

		m.route(root, "/a", {
			"/a": a,
			"/b": b
		})
		await mock.requestAnimationFrame.$resolve()

		var result = root.childNodes[0].nodeValue === "b"

		m.mount(root, null) // teardown

		return result
	})

	await test(async function () {
		await mock.requestAnimationFrame.$resolve()
		mock.location.search = "?"

		var root = mock.document.createElement("div")

		var a = {}
		a.controller = function () {
			m.route("/b?foo=1", {foo: 2})
		}
		a.view = function () { return "a" }

		var b = {}
		b.controller = function () {}
		b.view = function () { return "b" }

		m.route(root, "/", {
			"/": a,
			"/b": b
		})
		await mock.requestAnimationFrame.$resolve()

		var result = mock.location.search === "?/b?foo=2"

		m.mount(root, null) // teardown

		return result
	})

	await test(async function () {
		await mock.requestAnimationFrame.$resolve()
		mock.location.search = "?"
		mock.history.$$length = 0

		var root = mock.document.createElement("div")

		var a = {}
		a.controller = function () {}
		a.view = function () { return "a" }

		var b = {}
		b.controller = function () {}
		b.view = function () { return "b" }

		m.route(root, "/a", {
			"/a": a,
			"/b": b
		})
		await mock.requestAnimationFrame.$resolve()

		m.route("/b")

		await mock.requestAnimationFrame.$resolve()

		var result = mock.history.$$length === 1

		m.mount(root, null) // teardown

		return result
	})

	await test(async function () {
		await mock.requestAnimationFrame.$resolve()
		mock.location.search = "?"
		mock.history.$$length = 0

		var root = mock.document.createElement("div")

		var a = {}
		a.controller = function () {}
		a.view = function () { return "a" }

		var b = {}
		b.controller = function () {}
		b.view = function () { return "b" }

		m.route(root, "/a", {
			"/a": a,
			"/b": b
		})
		await mock.requestAnimationFrame.$resolve()

		m.route("/a")

		await mock.requestAnimationFrame.$resolve()

		var result = mock.history.$$length === 0

		m.mount(root, null) // teardown

		return result
	})

	await test(async function () {
		await mock.requestAnimationFrame.$resolve()
		mock.location.search = "?"

		var root = mock.document.createElement("div")
		var initCount = 0

		var a = {}
		a.controller = function () {}
		a.view = function () {
			return m("a", {config: function (el, init) {
				if (!init) initCount++
			}})
		}

		var b = {}
		b.controller = function () {}
		b.view = a.view

		m.route(root, "/a", {
			"/a": a,
			"/b": b
		})
		await mock.requestAnimationFrame.$resolve()

		m.route("/b")

		await mock.requestAnimationFrame.$resolve()

		var result = initCount === 2

		m.mount(root, null) // teardown

		return result
	})

	await test(async function () {
		await mock.requestAnimationFrame.$resolve()
		mock.location.search = "?"

		var root = mock.document.createElement("div")
		var initCount = 0

		var a = {}
		a.controller = function () {}
		a.view = function () {
			return m("a", {config: function (el, init, ctx) {
				ctx.retain = false
				if (!init) initCount++
			}})
		}

		var b = {}
		b.controller = function () {}
		b.view = a.view

		m.route(root, "/a", {
			"/a": a,
			"/b": b
		})
		await mock.requestAnimationFrame.$resolve()

		m.route("/b")

		await mock.requestAnimationFrame.$resolve()

		var result = initCount === 2

		m.mount(root, null) // teardown

		return result
	})

	await test(async function () {
		await mock.requestAnimationFrame.$resolve()
		mock.location.search = "?"

		var root = mock.document.createElement("div")
		var initCount = 0

		var a = {}
		a.controller = function () {}
		a.view = function () {
			return m("a", {config: function (el, init, ctx) {
				ctx.retain = true
				if (!init) initCount++
			}})
		}

		var b = {}
		b.controller = function () {}
		b.view = a.view

		m.route(root, "/a", {
			"/a": a,
			"/b": b
		})
		await mock.requestAnimationFrame.$resolve()

		m.route("/b")

		await mock.requestAnimationFrame.$resolve()

		var result = initCount === 1

		m.mount(root, null) // teardown

		return result
	})

	await test(async function () {
		await mock.requestAnimationFrame.$resolve()
		mock.location.search = "?"

		var root = mock.document.createElement("div")
		var initCount = 0

		var a = {}
		a.controller = function () { m.redraw.strategy("diff") }
		a.view = function () {
			return m("a", {config: function (el, init) {
				if (!init) initCount++
			}})
		}

		var b = {}
		b.controller = function () { m.redraw.strategy("diff") }
		b.view = a.view

		m.route(root, "/a", {
			"/a": a,
			"/b": b
		})
		await mock.requestAnimationFrame.$resolve()

		m.route("/b")

		await mock.requestAnimationFrame.$resolve()

		var result = initCount === 1

		m.mount(root, null) // teardown

		return result
	})

	await test(async function () {
		await mock.requestAnimationFrame.$resolve()
		mock.location.search = "?"

		var root = mock.document.createElement("div")
		var initCount = 0

		var a = {}
		a.controller = function () { m.redraw.strategy("diff") }
		a.view = function () {
			return m("a", {config: function (el, init, ctx) {
				ctx.retain = true
				if (!init) initCount++
			}})
		}

		var b = {}
		b.controller = function () { m.redraw.strategy("diff") }
		b.view = a.view

		m.route(root, "/a", {
			"/a": a,
			"/b": b
		})
		await mock.requestAnimationFrame.$resolve()

		m.route("/b")

		await mock.requestAnimationFrame.$resolve()

		var result = initCount === 1

		m.mount(root, null) // teardown

		return result
	})

	await test(async function () {
		await mock.requestAnimationFrame.$resolve()
		mock.location.search = "?"

		var root = mock.document.createElement("div")
		var initCount = 0

		var a = {}
		a.controller = function () { m.redraw.strategy("diff") }
		a.view = function () {
			return m("a", {config: function (el, init, ctx) {
				ctx.retain = false
				if (!init) initCount++
			}})
		}

		var b = {}
		b.controller = function () { m.redraw.strategy("diff") }
		b.view = a.view

		m.route(root, "/a", {
			"/a": a,
			"/b": b
		})
		await mock.requestAnimationFrame.$resolve()

		m.route("/b")

		await mock.requestAnimationFrame.$resolve()

		var result = initCount === 2

		m.mount(root, null) // teardown

		return result
	})

	await test(async function () {
		await mock.requestAnimationFrame.$resolve()
		mock.location.search = "?"

		var root = mock.document.createElement("div")
		var initCount = 0

		var a = {}
		a.controller = function () {}
		a.view = function () {
			return m("div", m("a", {config: function (el, init, ctx) {
				ctx.retain = true
				if (!init) initCount++
			}}))
		}

		var b = {}
		b.controller = function () {}
		b.view = function () {
			return m("section", m("a", {config: function (el, init, ctx) {
				ctx.retain = true
				if (!init) initCount++
			}}))
		}

		m.route(root, "/a", {
			"/a": a,
			"/b": b
		})
		await mock.requestAnimationFrame.$resolve()

		m.route("/b")

		await mock.requestAnimationFrame.$resolve()

		var result = initCount === 1

		m.mount(root, null) // teardown

		return result
	})

	await test(async function () {
		await mock.requestAnimationFrame.$resolve()
		mock.location.search = "?"

		var root = mock.document.createElement("div")
		var initCount = 0

		var a = {}
		a.controller = function () {}
		a.view = function () {
			return m("div", m("a", {config: function (el, init, ctx) {
				ctx.retain = false
				if (!init) initCount++
			}}))
		}

		var b = {}
		b.controller = function () {}
		b.view = function () {
			return m("section", m("a", {config: function (el, init, ctx) {
				ctx.retain = false
				if (!init) initCount++
			}}))
		}

		m.route(root, "/a", {
			"/a": a,
			"/b": b
		})
		await mock.requestAnimationFrame.$resolve()

		m.route("/b")

		await mock.requestAnimationFrame.$resolve()

		var result = initCount === 2

		m.mount(root, null) // teardown

		return result
	})

	await test(async function () {
		await mock.requestAnimationFrame.$resolve()
		mock.location.search = "?"

		var root = mock.document.createElement("div")
		var initCount = 0

		var a = {}
		a.controller = function () {}
		a.view = function () {
			return m("div", m("a", {config: function (el, init) {
				if (!init) initCount++
			}}))
		}

		var b = {}
		b.controller = function () {}
		b.view = function () {
			return m("section", m("a", {config: function (el, init) {
				if (!init) initCount++
			}}))
		}

		m.route(root, "/a", {
			"/a": a,
			"/b": b
		})
		await mock.requestAnimationFrame.$resolve()

		m.route("/b")

		await mock.requestAnimationFrame.$resolve()

		var result = initCount === 2

		m.mount(root, null) // teardown

		return result
	})

	await test(async function () {
		await mock.requestAnimationFrame.$resolve()
		mock.location.search = "?"

		var root = mock.document.createElement("div")
		var initCount = 0

		var a = {}
		a.controller = function () { m.redraw.strategy("diff") }
		a.view = function () {
			return m("div", m("a", {config: function (el, init, ctx) {
				ctx.retain = true
				if (!init) initCount++
			}}))
		}

		var b = {}
		b.controller = function () { m.redraw.strategy("diff") }
		b.view = function () {
			return m("section", m("a", {config: function (el, init, ctx) {
				ctx.retain = true
				if (!init) initCount++
			}}))
		}

		m.route(root, "/a", {
			"/a": a,
			"/b": b
		})
		await mock.requestAnimationFrame.$resolve()

		m.route("/b")

		await mock.requestAnimationFrame.$resolve()

		var result = initCount === 1

		m.mount(root, null) // teardown

		return result
	})

	await test(async function () {
		await mock.requestAnimationFrame.$resolve()
		mock.location.search = "?"

		var root = mock.document.createElement("div")
		var initCount = 0

		var a = {}
		a.controller = function () { m.redraw.strategy("diff") }
		a.view = function () {
			return m("div", m("a", {config: function (el, init, ctx) {
				ctx.retain = false
				if (!init) initCount++
			}}))
		}

		var b = {}
		b.controller = function () { m.redraw.strategy("diff") }
		b.view = function () {
			return m("section", m("a", {config: function (el, init, ctx) {
				ctx.retain = false
				if (!init) initCount++
			}}))
		}

		m.route(root, "/a", {
			"/a": a,
			"/b": b
		})
		await mock.requestAnimationFrame.$resolve()

		m.route("/b")

		await mock.requestAnimationFrame.$resolve()

		var result = initCount === 2

		m.mount(root, null) // teardown

		return result
	})

	await test(async function () {
		await mock.requestAnimationFrame.$resolve()
		mock.location.search = "?"

		var root = mock.document.createElement("div")
		var initCount = 0

		var a = {}
		a.controller = function () { m.redraw.strategy("diff") }
		a.view = function () {
			return m("div", m("a", {config: function (el, init) {
				if (!init) initCount++
			}}))
		}

		var b = {}
		b.controller = function () { m.redraw.strategy("diff") }
		b.view = function () {
			return m("section", m("a", {config: function (el, init) {
				if (!init) initCount++
			}}))
		}

		m.route(root, "/a", {
			"/a": a,
			"/b": b
		})
		await mock.requestAnimationFrame.$resolve()

		m.route("/b")

		await mock.requestAnimationFrame.$resolve()

		var result = initCount === 1

		m.mount(root, null) // teardown

		return result
	})

	await test(async function () {
		// retain flag should work inside component
		await mock.requestAnimationFrame.$resolve()
		mock.location.search = "?"

		var root = mock.document.createElement("div")
		var initCount = 0

		var a = {}
		a.controller = function () {}
		a.view = function () {
			return m("div", m("a", {config: function (el, init, ctx) {
				ctx.retain = true
				if (!init) initCount++
			}}))
		}

		var b = {}
		b.controller = function () { m.redraw.strategy("diff") }
		b.view = function () {
			return m("section", m("a", {config: function (el, init, ctx) {
				ctx.retain = true
				if (!init) initCount++
			}}))
		}

		m.route(root, "/a", {
			"/a": {view: function () { return m("div", a) }},
			"/b": {view: function () { return m("div", b) }}
		})
		await mock.requestAnimationFrame.$resolve()

		m.route("/b")

		await mock.requestAnimationFrame.$resolve()
		var result = initCount === 1

		m.mount(root, null) // teardown

		return result
	})

	await test(async function () {
		// https://github.com/lhorie/mithril.js/pull/571
		await mock.requestAnimationFrame.$resolve()
		mock.location.search = "?"

		var root = mock.document.createElement("div")

		var a = {}
		a.controller = function () {}
		a.view = function () {
			return m("div", {config: function (elm) {
				elm.childNodes[0].modified = true
			}}, m("div"))
		}

		var b = {}
		b.controller = function () {}
		b.view = function () {
			return m("div", m("div"))
		}

		m.route(root, "/a", {
			"/a": a,
			"/b": b
		})
		await mock.requestAnimationFrame.$resolve()

		m.route("/b")

		await mock.requestAnimationFrame.$resolve()
		var result = !root.childNodes[0].childNodes[0].modified

		m.mount(root, null) // teardown

		return result
	})
	// end m.route

	// m.route.parseQueryString
	await test(async function () {
		var str = "foo=bar&hello%5B%5D=world&hello%5B%5D=mars&hello%5B%5D=pluto"
		var args = m.route.parseQueryString(str)

		return args["hello[]"] instanceof Array &&
			args["hello[]"].indexOf("world") > -1 &&
			args["hello[]"].indexOf("mars") > -1 &&
			args["hello[]"].indexOf("pluto") > -1
	})

	await test(async function () {
		var str = "foo=bar&hello=world&hello=mars&bam=&yup"
		var args = m.route.parseQueryString(str)

		return args.foo === "bar" &&
			args.hello[0] === "world" &&
			args.hello[1] === "mars" &&
			args.bam === "" &&
			args.yup == null
	})

	await test(async function () {
		var args = m.route.parseQueryString("")
		return Object.keys(args).length === 0
	})

	// m.route.buildQueryString
	await test(async function () {
		var string = m.route.buildQueryString({
			foo: "bar",
			hello: ["world", "mars", "mars"],
			world: {
				test: 3
			},
			bam: "",
			yup: null,
			removed: undefined
		})
		var expected = "foo=bar&hello=world&hello=mars&" +
			"world%5Btest%5D=3&bam=&yup"
		return string === expected
	})

	await test(async function () {
		var string = m.route.buildQueryString({})
		return string === ""
	})

	// m.prop
	await test(async function () {
		var prop = m.prop("test")
		return prop() === "test"
	})

	await test(async function () {
		var prop = m.prop("test")
		prop("foo")
		return prop() === "foo"
	})

	await test(async function () {
		var prop = m.prop("test")
		return JSON.stringify(prop) === '"test"'
	})

	await test(async function () {
		var obj = {prop: m.prop("test")}
		return JSON.stringify(obj) === '{"prop":"test"}'
	})

	await test(async function () {
		var defer = m.deferred()
		var prop = m.prop(defer.promise)
		defer.resolve("test")

		return prop() === "test"
	})

	await test(async function () {
		var defer = m.deferred()
		var prop = m.prop(defer.promise).then(function () {
			return "test2"
		})
		defer.resolve("test")

		return prop() === "test2"
	})

	await test(async function () {
		var prop = m.prop(null)
		return prop() === null
	})

	// m.request
	await test(async function () {
		var prop = m.request({method: "GET", url: "test"})
		mock.XMLHttpRequest.$instances.pop().onreadystatechange()
		return prop().method === "GET" && prop().url === "test"
	})

	await test(async function () {
		var prop = m.request({method: "GET", url: "test"})
			.then(function () { return "foo" })
		mock.XMLHttpRequest.$instances.pop().onreadystatechange()
		return prop() === "foo"
	})

	await test(async 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"
	})

	await test(async 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"
	})

	await test(async 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"
	})

	await test(async function () {
		var error = m.prop("no error")

		var prop = m.request({
			method: "GET",
			url: "test",
			deserialize: function () { throw new Error("error occurred") }
		}).catch(error)

		mock.XMLHttpRequest.$instances.pop().onreadystatechange()
		return prop().message === "error occurred" &&
			error().message === "error occurred"
	})

	await test(async function () {
		var error = m.prop("no error")
		var 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
		}

		return prop() === undefined &&
			error() === "no error" &&
			exception.message === "error occurred"
	})

	await test(async function () {
		var error = m.prop("no error")

		m.request({method: "POST", url: "test", data: {foo: 1}})
		.then(null, error)

		var xhr = mock.XMLHttpRequest.$instances.pop()
		xhr.onreadystatechange()

		var expected = "application/json; charset=utf-8"

		return xhr.$headers["Content-Type"] === expected
	})

	await test(async function () {
		var error = m.prop("no error")
		m.request({method: "POST", url: "test"}).then(null, error)
		var xhr = mock.XMLHttpRequest.$instances.pop()
		xhr.onreadystatechange()
		return xhr.$headers["Content-Type"] === undefined
	})

	await test(async function () {
		var prop = m.request({method: "POST", url: "test", initialValue: "foo"})
		.then(function (data) { return data })

		var initialValue = prop()
		mock.XMLHttpRequest.$instances.pop().onreadystatechange()

		return initialValue === "foo"
	})

	await test(async function () {
		var prop = m.request({method: "POST", url: "test", initialValue: "foo"})
		var initialValue = prop()
		mock.XMLHttpRequest.$instances.pop().onreadystatechange()

		return initialValue === "foo"
	})

	await test(async function () {
		var prop = m.request({method: "POST", url: "test", initialValue: "foo"})
		.then(function () { return "bar" })
		mock.XMLHttpRequest.$instances.pop().onreadystatechange()
		return prop() === "bar"
	})

	await test(async function () {
		var prop = m.request({method: "GET", url: "test", data: {foo: 1}})
		mock.XMLHttpRequest.$instances.pop().onreadystatechange()
		return prop().url === "test?foo=1"
	})

	await test(async function () {
		var prop = m.request({method: "POST", url: "test", data: {foo: 1}})
		mock.XMLHttpRequest.$instances.pop().onreadystatechange()
		return prop().url === "test"
	})

	await test(async function () {
		var prop = m.request({method: "GET", url: "test", data: {foo: [1, 2]}})
		mock.XMLHttpRequest.$instances.pop().onreadystatechange()
		return prop().url === "test?foo=1&foo=2"
	})

	await test(async function () {
		var value
		var prop1 = m.request({method: "GET", url: "test", initialValue: 123})

		var val1 = prop1()
		var prop2 = prop1.then(function () { return 1 })
		var val2 = prop2()
		var prop3 = prop1.then(function (v) { value = v })
		var val3 = prop3()

		mock.XMLHttpRequest.$instances.pop().onreadystatechange()

		return val1 === 123 &&
			val2 === 123 &&
			val3 === 123 &&
			value.method === "GET" &&
			value.url === "test"
	})

	// m.request over jsonp
	await test(async function (){
		// script tags cannot be appended directly on the document
if(!useRealDom){		var body = mock.document.createElement("body")
		mock.document.body = body
		mock.document.appendChild(body)
}
		var	error = m.prop("no error")
		var data
		m.request({url: "/test", dataType: "jsonp"})
		.then(function (received) { data = received }, error)
		var callbackKey = Object.keys(mock).filter(function (globalKey){
			return globalKey.indexOf("mithril_callback") > -1
		}).pop()
		var scripts = mock.document.getElementsByTagName("script")
		var scriptTag = [].slice.call(scripts).filter(function (script){
			return script.src.indexOf(callbackKey) > -1
		}).pop()
		mock[callbackKey]({foo: "bar"})
		if(!useRealDom) mock.document.removeChild(body)
		return scriptTag.src.indexOf("/test?callback=mithril_callback") > -1 &&
			data.foo === "bar"
	})

	await test(async function (){
		// script tags cannot be appended directly on the document
if(!useRealDom){		var body = mock.document.createElement("body")
		mock.document.body = body
		mock.document.appendChild(body)
}
		var	error = m.prop("no error")
		var data
		m.request({
			url: "/test",
			dataType: "jsonp",
			callbackKey: "jsonpCallback"
		}).then(function (received) { data = received }, error)

		var callbackKey = Object.keys(mock).filter(function (globalKey){
			return globalKey.indexOf("mithril_callback") > -1
		}).pop()

		var scripts = mock.document.getElementsByTagName("script")
		var scriptTag = [].slice.call(scripts).filter(function (script){
			return script.src.indexOf(callbackKey) > -1
		}).pop()

		mock[callbackKey]({foo: "bar1"})
		if(!useRealDom) mock.document.removeChild(body)

		var url = "/test?jsonpCallback=mithril_callback"
		return scriptTag.src.indexOf(url) > -1 &&
			data.foo === "bar1"
	})

	await test(async function (){
if(!useRealDom){		var body = mock.document.createElement("body")
		mock.document.body = body
		mock.document.appendChild(body)
}
		var req = m.request({url: "/test", dataType: "jsonp"})
		var callbackKey = Object.keys(mock).filter(function (globalKey){
			return globalKey.indexOf("mithril_callback") > -1
		}).pop()
		var scripts = mock.document.getElementsByTagName("script")
		;[].slice.call(scripts).filter(function (script){
			return script.src.indexOf(callbackKey) > -1
		}).pop()
		mock[callbackKey]({foo: "bar1"})
		var out = {foo: "bar1"}
		if(!useRealDom) mock.document.removeChild(body)
		return JSON.stringify(out) === JSON.stringify(req())
	})

	await test(async function (){
if(!useRealDom){		var body = mock.document.createElement("body")
		mock.document.body = body
		mock.document.appendChild(body)
}
		m.request({url: "/test", dataType: "jsonp", data: {foo: "bar"}})
		var callbackKey = Object.keys(mock).filter(function (globalKey){
			return globalKey.indexOf("mithril_callback") > -1
		}).pop()
		var scripts = mock.document.getElementsByTagName("script")
		var scriptTag = [].slice.call(scripts).filter(function (script){
			return script.src.indexOf(callbackKey) > -1
		}).pop()
		mock[callbackKey]({foo: "bar"})
		return scriptTag.src.indexOf("foo=bar") > -1
	})

	await test(async function (){
if(!useRealDom){		var body = mock.document.createElement("body")
		mock.document.body = body
		mock.document.appendChild(body)
}
		m.request({
			url: "/test",
			dataType: "jsonp",
			method: "GET",
			data: {foo: "bar"}
		})
		var callbackKey = Object.keys(mock).filter(function (globalKey){
			return globalKey.indexOf("mithril_callback") > -1
		}).pop()
		var scripts = mock.document.getElementsByTagName("script")
		var scriptTag = [].slice.call(scripts).filter(function (script){
			return script.src.indexOf(callbackKey) > -1
		}).pop()
		mock[callbackKey]({foo: "bar"})
		if(!useRealDom) mock.document.removeChild(body)
		return scriptTag.src.match(/foo=bar/g).length === 1
	})

	// m.deferred
	await test(async function () {
		var deferred = m.deferred()
		var value
		deferred.promise.then(function (data) { value = data })
		deferred.resolve("test")
		return value === "test"
	})

	await test(async function () {
		var deferred = m.deferred()
		var value

		deferred.promise
		.then(function () { return "foo" })
		.then(function (data) { value = data })

		deferred.resolve("test")
		return value === "foo"
	})

	await test(async function () {
		var deferred = m.deferred()
		var value
		deferred.promise.then(null, function (data) { value = data })
		deferred.reject("test")
		return value === "test"
	})

	await test(async function () {
		var value
		var deferred = m.deferred()
		deferred.promise.catch(function (data) { value = data })
		deferred.reject("test")
		return value === "test"
	})

	await test(async function () {
		var deferred = m.deferred()
		var value

		deferred.promise
		.then(null, function () { return "foo" })
		.then(function (data) { value = data })

		deferred.reject("test")
		return value === "foo"
	})

	await test(async function () {
		var deferred = m.deferred()
		var value

		deferred.promise
		.catch(function () { return "foo" })
		.then(function (data) { value = data })

		deferred.reject("test")
		return value === "foo"
	})

	await test(async function () {
		var deferred = m.deferred()
		var value1, value2

		deferred.promise
		.then(function () { throw new Error() })
		.then(function () { value1 = 1 }, function (data) { value2 = data })

		deferred.resolve("test")
		return value1 === undefined && value2 instanceof Error
	})

	await test(async function () {
		// Let unchecked exceptions bubble up in order to allow meaningful error
		// messages in common cases like null reference exceptions due to typos
		//
		// An unchecked exception is defined as an object that is a subclass of
		// Error (but not a direct instance of Error itself) - basically
		// anything that can be thrown without an explicit `throw` keyword and
		// that we'd never want to programmatically manipulate. In other words,
		// an unchecked error is one where we only care about its line number
		// and where the only reasonable way to deal with it is to change the
		// buggy source code that caused the error to be thrown in the first
		// place.
		//
		// By contrast, a checked exception is defined as anything that is
		// explicitly thrown via the `throw` keyword and that can be
		// programmatically handled, for example to display a validation error
		// message on the UI. If an exception is a subclass of Error for
		// whatever reason, but it is meant to be handled as a checked exception
		// (i.e. follow the rejection rules for A+), it can be rethrown as an
		// instance of Error
		//
		// This test tests two implementation details that differ from the
		// Promises/A+ spec:
		//
		// 1) A+ requires the `then` callback to be called in a different event
		//    loop from the resolve call, i.e. it must be asynchronous (this
		//    requires a setImmediate polyfill, which cannot be implemented in a
		//    reasonable way for Mithril's purpose - the possible polyfills are
		//    either too big or too slow)
		//
		// 2) A+ swallows exceptions in a unrethrowable way, i.e. it's not
		//    possible to see default error messages on the console for runtime
		//    errors thrown from within a promise chain
		var deferred = m.deferred()
		var value1, value2, value3

		try {
			deferred.promise
				/* eslint-disable no-undef */
				// throws ReferenceError
				.then(function () { return foo.bar.baz })
				/* eslint-enable no-undef */
				.then(
					function () { value1 = 1 },
					function (data) { value2 = data }
				)
			deferred.resolve("test")
		} catch (e) {
			value3 = e
		}

		return value1 === undefined &&
			value2 === undefined &&
			(value3 instanceof ReferenceError || value3 instanceof TypeError)
	})

	await test(async 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
	})

	await test(async function () {
		// https://github.com/lhorie/mithril.js/issues/80
		var deferred = m.deferred()
		var value

		deferred.resolve(1)
		deferred.promise.then(function (data) {
			value = data
		})

		return value === 1
	})

	await test(async function () {
		// https://github.com/lhorie/mithril.js/issues/80
		var deferred = m.deferred()
		var value

		deferred.reject(1)
		deferred.promise.then(null, function (data) {
			value = data
		})

		return value === 1
	})

	await test(async function () {
		// https://github.com/lhorie/mithril.js/issues/80
		var deferred = m.deferred()
		var value
		deferred.resolve(1)
		deferred.resolve(2)
		deferred.promise.then(function (data) {
			value = data
		})
		return value === 1
	})

	await test(async function () {
		// https://github.com/lhorie/mithril.js/issues/80
		var deferred = m.deferred()
		var value
		deferred.promise.then(function (data) {
			value = data
		})
		deferred.resolve(1)
		deferred.resolve(2)
		return value === 1
	})

	await test(async function () {
		// https://github.com/lhorie/mithril.js/issues/80
		var deferred = m.deferred()
		var value1, value2
		deferred.promise.then(function (data) {
			value1 = data
		}, function (data) {
			value2 = data
		})
		deferred.resolve(1)
		deferred.reject(2)
		return value1 === 1 && value2 === undefined
	})

	await test(async function () {
		// https://github.com/lhorie/mithril.js/issues/80
		var deferred = m.deferred()
		var value1, value2
		deferred.promise.then(function (data) {
			value1 = data
		}, function (data) {
			value2 = data
		})
		deferred.reject(1)
		deferred.resolve(2)
		return value1 === undefined && value2 === 1
	})

	await test(async function () {
		// https://github.com/lhorie/mithril.js/issues/80
		var deferred = m.deferred()
		var value
		deferred.promise.then(null, function (data) {
			value = data
		})
		deferred.reject(1)
		deferred.reject(2)
		return value === 1
	})

	await test(async function () {
		// https://github.com/lhorie/mithril.js/issues/85
		var deferred = m.deferred()
		var value
		deferred.resolve()
		deferred.promise.then(function () {
			value = 1
		})
		return value === 1
	})

	await test(async function () {
		// https://github.com/lhorie/mithril.js/issues/85
		var deferred = m.deferred()
		var value
		deferred.reject()
		deferred.promise.then(null, function () {
			value = 1
		})
		return value === 1
	})

	await test(async function () {
		var deferred = m.deferred()
		deferred.resolve(1)
		return deferred.promise() === 1
	})

	await test(async function () {
		var deferred = m.deferred()
		var promise = deferred.promise.then(function (data) { return data + 1 })
		deferred.resolve(1)
		return promise() === 2
	})

	await test(async function () {
		var deferred = m.deferred()
		deferred.reject(1)
		return deferred.promise() === undefined
	})

	// m.sync
	await test(async 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"
	})

	await test(async 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"
	})

	await test(async function () {
		var value
		var deferred = m.deferred()
		m.sync([deferred.promise]).catch(function (data) { value = data })
		deferred.reject("fail")
		return value[0] === "fail"
	})

	await test(async function () {
		var value = 1
		m.sync([]).then(function () { value = 2 })
		return value === 2
	})

	await test(async function () {
		var success
		m.sync([]).then(function (value) { success = value instanceof Array })
		return success
	})

	// m.startComputation/m.endComputation
	await test(async function () {
		await mock.requestAnimationFrame.$resolve()

		var root = mock.document.createElement("div")
		var controller = m.mount(root, {
			controller: function () {},
			view: function (ctrl) { return ctrl.value }
		})

		await mock.requestAnimationFrame.$resolve()

		m.startComputation()
		controller.value = "foo"
		m.endComputation()
		await mock.requestAnimationFrame.$resolve()
		return root.childNodes[0].nodeValue === "foo"
	})

	// config context
	await test(async function () {
		var root = mock.document.createElement("div")

		var success = false

		m.render(root, m("div", {
			config: function (elem, isInit, ctx) { ctx.data = 1 }
		}))

		m.render(root, m("div", {
			config: function (elem, isInit, ctx) { success = ctx.data === 1 }
		}))

		return success
	})

	// more complex config context
	await test(async function () {
		var root = mock.document.createElement("div")

		var idx = 0
		var success = true
		function statefulConfig(elem, isInit, ctx) { ctx.data = idx++ }
		var node = m("div", {config: statefulConfig})
		m.render(root, [node, node])

		idx = 0

		function checkConfig(elem, isInit, ctx) {
			success = success && (ctx.data === idx++)
		}

		node = m("div", {config: checkConfig})
		m.render(root, [node, node])

		return success
	})

	await test(async function () {
		console.time('perf')
		var root = mock.document.createElement("div")
		var getClass = i=>i%2? 'abc' : ''
		for(let i=0; i<10000; i++){
			m.render(root, m("div", {class: getClass(i) }))
		}
		console.timeEnd('perf')
		return true
	})

	test.print(function (value) {
		var totalTime = config &&  +new Date() - config.start
		console.log(value, totalTime) // eslint-disable-line no-console
	})
})({
	start: +new Date()
})
