"use strict"

var o = require("../../ospec/ospec")
var components = require("../../test-utils/components")
var domMock = require("../../test-utils/domMock")
var vdom = require("../../render/render")

o.spec("component", function() {
	var $window, root, render
	o.beforeEach(function() {
		$window = domMock()
		root = $window.document.createElement("div")

		render = vdom($window).render
	})

	components.forEach(function(cmp){
		o.spec(cmp.kind, function(){
			var createComponent = cmp.create

			o.spec("basics", function() {
				o("works", function() {
					var component = createComponent({
						view: function() {
							return {tag: "div", attrs: {id: "a"}, text: "b"}
						}
					})
					var node = {tag: component}

					render(root, [node])

					o(root.firstChild.nodeName).equals("DIV")
					o(root.firstChild.attributes["id"].value).equals("a")
					o(root.firstChild.firstChild.nodeValue).equals("b")
				})
				o("receives arguments", function() {
					var component = createComponent({
						view: function(vnode) {
							return {tag: "div", attrs: vnode.attrs, text: vnode.text}
						}
					})
					var node = {tag: component, attrs: {id: "a"}, text: "b"}

					render(root, [node])

					o(root.firstChild.nodeName).equals("DIV")
					o(root.firstChild.attributes["id"].value).equals("a")
					o(root.firstChild.firstChild.nodeValue).equals("b")
				})
				o("updates", function() {
					var component = createComponent({
						view: function(vnode) {
							return {tag: "div", attrs: vnode.attrs, text: vnode.text}
						}
					})
					render(root, [{tag: component, attrs: {id: "a"}, text: "b"}])
					render(root, [{tag: component, attrs: {id: "c"}, text: "d"}])

					o(root.firstChild.nodeName).equals("DIV")
					o(root.firstChild.attributes["id"].value).equals("c")
					o(root.firstChild.firstChild.nodeValue).equals("d")
				})
				o("updates root from null", function() {
					var visible = false
					var component = createComponent({
						view: function() {
							return visible ? {tag: "div"} : null
						}
					})
					render(root, [{tag: component}])
					visible = true
					render(root, [{tag: component}])

					o(root.firstChild.nodeName).equals("DIV")
				})
				o("updates root from primitive", function() {
					var visible = false
					var component = createComponent({
						view: function() {
							return visible ? {tag: "div"} : false
						}
					})
					render(root, [{tag: component}])
					visible = true
					render(root, [{tag: component}])

					o(root.firstChild.nodeName).equals("DIV")
				})
				o("updates root to null", function() {
					var visible = true
					var component = createComponent({
						view: function() {
							return visible ? {tag: "div"} : null
						}
					})
					render(root, [{tag: component}])
					visible = false
					render(root, [{tag: component}])

					o(root.childNodes.length).equals(0)
				})
				o("updates root to primitive", function() {
					var visible = true
					var component = createComponent({
						view: function() {
							return visible ? {tag: "div"} : false
						}
					})
					render(root, [{tag: component}])
					visible = false
					render(root, [{tag: component}])

					o(root.firstChild.nodeValue).equals("")
				})
				o("updates root from null to null", function() {
					var component = createComponent({
						view: function() {
							return null
						}
					})
					render(root, [{tag: component}])
					render(root, [{tag: component}])

					o(root.childNodes.length).equals(0)
				})
				o("removes", function() {
					var component = createComponent({
						view: function() {
							return {tag: "div"}
						}
					})
					var div = {tag: "div", key: 2}
					render(root, [{tag: component, key: 1}, div])
					render(root, [{tag: "div", key: 2}])

					o(root.childNodes.length).equals(1)
					o(root.firstChild).equals(div.dom)
				})
				o("svg works when creating across component boundary", function() {
					var component = createComponent({
						view: function() {
							return {tag: "g"}
						}
					})
					render(root, [{tag: "svg", children: [{tag: component}]}])

					o(root.firstChild.firstChild.namespaceURI).equals("http://www.w3.org/2000/svg")
				})
				o("svg works when updating across component boundary", function() {
					var component = createComponent({
						view: function() {
							return {tag: "g"}
						}
					})
					render(root, [{tag: "svg", children: [{tag: component}]}])
					render(root, [{tag: "svg", children: [{tag: component}]}])

					o(root.firstChild.firstChild.namespaceURI).equals("http://www.w3.org/2000/svg")
				})
			})
			o.spec("return value", function() {
				o("can return fragments", function() {
					var component = createComponent({
						view: function() {
							return [
								{tag: "label"},
								{tag: "input"},
							]
						}
					})
					render(root, [{tag: component}])

					o(root.childNodes.length).equals(2)
					o(root.childNodes[0].nodeName).equals("LABEL")
					o(root.childNodes[1].nodeName).equals("INPUT")
				})
				o("can return string", function() {
					var component = createComponent({
						view: function() {
							return "a"
						}
					})
					render(root, [{tag: component}])

					o(root.firstChild.nodeType).equals(3)
					o(root.firstChild.nodeValue).equals("a")
				})
				o("can return falsy string", function() {
					var component = createComponent({
						view: function() {
							return ""
						}
					})
					render(root, [{tag: component}])

					o(root.firstChild.nodeType).equals(3)
					o(root.firstChild.nodeValue).equals("")
				})
				o("can return number", function() {
					var component = createComponent({
						view: function() {
							return 1
						}
					})
					render(root, [{tag: component}])

					o(root.firstChild.nodeType).equals(3)
					o(root.firstChild.nodeValue).equals("1")
				})
				o("can return falsy number", function() {
					var component = createComponent({
						view: function() {
							return 0
						}
					})
					render(root, [{tag: component}])

					o(root.firstChild.nodeType).equals(3)
					o(root.firstChild.nodeValue).equals("0")
				})
				o("can return boolean", function() {
					var component = createComponent({
						view: function() {
							return true
						}
					})
					render(root, [{tag: component}])

					o(root.firstChild.nodeType).equals(3)
					o(root.firstChild.nodeValue).equals("true")
				})
				o("can return falsy boolean", function() {
					var component = createComponent({
						view: function() {
							return false
						}
					})
					render(root, [{tag: component}])

					o(root.firstChild.nodeType).equals(3)
					o(root.firstChild.nodeValue).equals("")
				})
				o("can return null", function() {
					var component = createComponent({
						view: function() {
							return null
						}
					})
					render(root, [{tag: component}])

					o(root.childNodes.length).equals(0)
				})
				o("can return undefined", function() {
					var component = createComponent({
						view: function() {
							return undefined
						}
					})
					render(root, [{tag: component}])

					o(root.childNodes.length).equals(0)
				})
				o("throws a custom error if it returns itself when created", function() {
					// A view that returns its vnode would otherwise trigger an infinite loop
					var threw = false
					var component = createComponent({
						view: function(vnode) {
							return vnode
						}
					})
					try {
						render(root, [{tag: component}])
					}
					catch (e) {
						threw = true
						o(e instanceof Error).equals(true)
						// Call stack exception is a RangeError
						o(e instanceof RangeError).equals(false)
					}
					o(threw).equals(true)
				})
				o("throws a custom error if it returns itself when updated", function() {
					// A view that returns its vnode would otherwise trigger an infinite loop
					var threw = false
					var init = true
					var oninit = o.spy()
					var component = createComponent({
						oninit: oninit,
						view: function(vnode) {
							if (init) return init = false
							else return vnode
						}
					})
					render(root, [{tag: component}])

					o(root.firstChild.nodeType).equals(3)
					o(root.firstChild.nodeValue).equals("")

					try {
						render(root, [{tag: component}])
					}
					catch (e) {
						threw = true
						o(e instanceof Error).equals(true)
						// Call stack exception is a RangeError
						o(e instanceof RangeError).equals(false)
					}
					o(threw).equals(true)
					o(oninit.callCount).equals(1)
				})
				o("can update when returning fragments", function() {
					var component = createComponent({
						view: function() {
							return [
								{tag: "label"},
								{tag: "input"},
							]
						}
					})
					render(root, [{tag: component}])
					render(root, [{tag: component}])

					o(root.childNodes.length).equals(2)
					o(root.childNodes[0].nodeName).equals("LABEL")
					o(root.childNodes[1].nodeName).equals("INPUT")
				})
				o("can update when returning primitive", function() {
					var component = createComponent({
						view: function() {
							return "a"
						}
					})
					render(root, [{tag: component}])
					render(root, [{tag: component}])

					o(root.firstChild.nodeType).equals(3)
					o(root.firstChild.nodeValue).equals("a")
				})
				o("can update when returning null", function() {
					var component = createComponent({
						view: function() {
							return null
						}
					})
					render(root, [{tag: component}])
					render(root, [{tag: component}])

					o(root.childNodes.length).equals(0)
				})
				o("can remove when returning fragments", function() {
					var component = createComponent({
						view: function() {
							return [
								{tag: "label"},
								{tag: "input"},
							]
						}
					})
					var div = {tag: "div", key: 2}
					render(root, [{tag: component, key: 1}, div])

					render(root, [{tag: "div", key: 2}])

					o(root.childNodes.length).equals(1)
					o(root.firstChild).equals(div.dom)
				})
				o("can remove when returning primitive", function() {
					var component = createComponent({
						view: function() {
							return "a"
						}
					})
					var div = {tag: "div", key: 2}
					render(root, [{tag: component, key: 1}, div])

					render(root, [{tag: "div", key: 2}])

					o(root.childNodes.length).equals(1)
					o(root.firstChild).equals(div.dom)
				})
			})
			o.spec("lifecycle", function() {
				o("calls oninit", function() {
					var called = 0
					var component = createComponent({
						oninit: function(vnode) {
							called++

							o(vnode.tag).equals(component)
							o(vnode.dom).equals(undefined)
							o(root.childNodes.length).equals(0)
						},
						view: function() {
							return {tag: "div", attrs: {id: "a"}, text: "b"}
						}
					})
					var node = {tag: component}

					render(root, [node])

					o(called).equals(1)
					o(root.firstChild.nodeName).equals("DIV")
					o(root.firstChild.attributes["id"].value).equals("a")
					o(root.firstChild.firstChild.nodeValue).equals("b")
				})
				o("calls oninit when returning fragment", function() {
					var called = 0
					var component = createComponent({
						oninit: function(vnode) {
							called++

							o(vnode.tag).equals(component)
							o(vnode.dom).equals(undefined)
							o(root.childNodes.length).equals(0)
						},
						view: function() {
							return [{tag: "div", attrs: {id: "a"}, text: "b"}]
						}
					})
					var node = {tag: component}

					render(root, [node])

					o(called).equals(1)
					o(root.firstChild.nodeName).equals("DIV")
					o(root.firstChild.attributes["id"].value).equals("a")
					o(root.firstChild.firstChild.nodeValue).equals("b")
				})
				o("calls oninit before view", function() {
					var viewCalled = false

					render(root, createComponent({
						tag: {
							view: function() {
								viewCalled = true
								return [{tag: "div", attrs: {id: "a"}, text: "b"}]
							},
							oninit: function() {
								o(viewCalled).equals(false)
							},
						}
					}))
				})
				o("does not calls oninit on redraw", function() {
					var init = o.spy()
					var component = createComponent({
						view: function() {
							return {tag: "div", attrs: {id: "a"}, text: "b"}
						},
						oninit: init,
					})

					function view() {
						return {tag: component}
					}

					render(root, view())
					render(root, view())

					o(init.callCount).equals(1)
				})
				o("calls oncreate", function() {
					var called = 0
					var component = createComponent({
						oncreate: function(vnode) {
							called++

							o(vnode.dom).notEquals(undefined)
							o(vnode.dom).equals(root.firstChild)
							o(root.childNodes.length).equals(1)
						},
						view: function() {
							return {tag: "div", attrs: {id: "a"}, text: "b"}
						}
					})
					var node = {tag: component}

					render(root, [node])

					o(called).equals(1)
					o(root.firstChild.nodeName).equals("DIV")
					o(root.firstChild.attributes["id"].value).equals("a")
					o(root.firstChild.firstChild.nodeValue).equals("b")
				})
				o("does not calls oncreate on redraw", function() {
					var create = o.spy()
					var component = createComponent({
						view: function() {
							return {tag: "div", attrs: {id: "a"}, text: "b"}
						},
						oncreate: create,
					})

					function view() {
						return {tag: component}
					}

					render(root, view())
					render(root, view())

					o(create.callCount).equals(1)
				})
				o("calls oncreate when returning fragment", function() {
					var called = 0
					var component = createComponent({
						oncreate: function(vnode) {
							called++

							o(vnode.dom).notEquals(undefined)
							o(vnode.dom).equals(root.firstChild)
							o(root.childNodes.length).equals(1)
						},
						view: function() {
							return [{tag: "div", attrs: {id: "a"}, text: "b"}]
						}
					})
					var node = {tag: component}

					render(root, [node])

					o(called).equals(1)
					o(root.firstChild.nodeName).equals("DIV")
					o(root.firstChild.attributes["id"].value).equals("a")
					o(root.firstChild.firstChild.nodeValue).equals("b")
				})
				o("calls onupdate", function() {
					var called = 0
					var component = createComponent({
						onupdate: function(vnode) {
							called++

							o(vnode.dom).notEquals(undefined)
							o(vnode.dom).equals(root.firstChild)
							o(root.childNodes.length).equals(1)
						},
						view: function() {
							return {tag: "div", attrs: {id: "a"}, text: "b"}
						}
					})

					render(root, [{tag: component}])

					o(called).equals(0)

					render(root, [{tag: component}])

					o(called).equals(1)
					o(root.firstChild.nodeName).equals("DIV")
					o(root.firstChild.attributes["id"].value).equals("a")
					o(root.firstChild.firstChild.nodeValue).equals("b")
				})
				o("calls onupdate when returning fragment", function() {
					var called = 0
					var component = createComponent({
						onupdate: function(vnode) {
							called++

							o(vnode.dom).notEquals(undefined)
							o(vnode.dom).equals(root.firstChild)
							o(root.childNodes.length).equals(1)
						},
						view: function() {
							return [{tag: "div", attrs: {id: "a"}, text: "b"}]
						}
					})

					render(root, [{tag: component}])

					o(called).equals(0)

					render(root, [{tag: component}])

					o(called).equals(1)
					o(root.firstChild.nodeName).equals("DIV")
					o(root.firstChild.attributes["id"].value).equals("a")
					o(root.firstChild.firstChild.nodeValue).equals("b")
				})
				o("calls onremove", function() {
					var called = 0
					var component = createComponent({
						onremove: function(vnode) {
							called++

							o(vnode.dom).notEquals(undefined)
							o(vnode.dom).equals(root.firstChild)
							o(root.childNodes.length).equals(1)
						},
						view: function() {
							return {tag: "div", attrs: {id: "a"}, text: "b"}
						}
					})

					render(root, [{tag: component}])

					o(called).equals(0)

					render(root, [])

					o(called).equals(1)
					o(root.childNodes.length).equals(0)
				})
				o("calls onremove when returning fragment", function() {
					var called = 0
					var component = createComponent({
						onremove: function(vnode) {
							called++

							o(vnode.dom).notEquals(undefined)
							o(vnode.dom).equals(root.firstChild)
							o(root.childNodes.length).equals(1)
						},
						view: function() {
							return [{tag: "div", attrs: {id: "a"}, text: "b"}]
						}
					})

					render(root, [{tag: component}])

					o(called).equals(0)

					render(root, [])

					o(called).equals(1)
					o(root.childNodes.length).equals(0)
				})
				o("calls onbeforeremove", function() {
					var called = 0
					var component = createComponent({
						onbeforeremove: function(vnode) {
							called++

							o(vnode.dom).notEquals(undefined)
							o(vnode.dom).equals(root.firstChild)
							o(root.childNodes.length).equals(1)
						},
						view: function() {
							return {tag: "div", attrs: {id: "a"}, text: "b"}
						}
					})

					render(root, [{tag: component}])

					o(called).equals(0)

					render(root, [])

					o(called).equals(1)
					o(root.childNodes.length).equals(0)
				})
				o("calls onbeforeremove when returning fragment", function() {
					var called = 0
					var component = createComponent({
						onbeforeremove: function(vnode) {
							called++

							o(vnode.dom).notEquals(undefined)
							o(vnode.dom).equals(root.firstChild)
							o(root.childNodes.length).equals(1)
						},
						view: function() {
							return [{tag: "div", attrs: {id: "a"}, text: "b"}]
						}
					})

					render(root, [{tag: component}])

					o(called).equals(0)

					render(root, [])

					o(called).equals(1)
					o(root.childNodes.length).equals(0)
				})
				o("does not recycle when there's an onupdate", function() {
					var component = createComponent({
						onupdate: function() {},
						view: function() {
							return {tag: "div"}
						}
					})
					var vnode = {tag: component, key: 1}
					var updated = {tag: component, key: 1}

					render(root, [vnode])
					render(root, [])
					render(root, [updated])

					o(vnode.dom).notEquals(updated.dom)
				})
				o("lifecycle timing megatest (for a single component)", function() {
					var methods = {
						view: o.spy(function() {
							return ""
						})
					}
					var attrs = {}
					var hooks = [
						"oninit", "oncreate", "onbeforeupdate",
						"onupdate", "onbeforeremove", "onremove"
					]
					hooks.forEach(function(hook) {
						// the `attrs` hooks are called before  the component ones
						attrs[hook] = o.spy(function() {
							o(attrs[hook].callCount).equals(methods[hook].callCount + 1)
						})
						methods[hook] = o.spy(function() {
							o(attrs[hook].callCount).equals(methods[hook].callCount)
						})
					})

					var component = createComponent(methods)

					o(methods.view.callCount).equals(0)
					o(methods.oninit.callCount).equals(0)
					o(methods.oncreate.callCount).equals(0)
					o(methods.onbeforeupdate.callCount).equals(0)
					o(methods.onupdate.callCount).equals(0)
					o(methods.onbeforeremove.callCount).equals(0)
					o(methods.onremove.callCount).equals(0)

					hooks.forEach(function(hook) {
						o(attrs[hook].callCount).equals(methods[hook].callCount)(hook)
					})

					render(root, [{tag: component, attrs: attrs}])

					o(methods.view.callCount).equals(1)
					o(methods.oninit.callCount).equals(1)
					o(methods.oncreate.callCount).equals(1)
					o(methods.onbeforeupdate.callCount).equals(0)
					o(methods.onupdate.callCount).equals(0)
					o(methods.onbeforeremove.callCount).equals(0)
					o(methods.onremove.callCount).equals(0)

					hooks.forEach(function(hook) {
						o(attrs[hook].callCount).equals(methods[hook].callCount)(hook)
					})

					render(root, [{tag: component, attrs: attrs}])

					o(methods.view.callCount).equals(2)
					o(methods.oninit.callCount).equals(1)
					o(methods.oncreate.callCount).equals(1)
					o(methods.onbeforeupdate.callCount).equals(1)
					o(methods.onupdate.callCount).equals(1)
					o(methods.onbeforeremove.callCount).equals(0)
					o(methods.onremove.callCount).equals(0)

					hooks.forEach(function(hook) {
						o(attrs[hook].callCount).equals(methods[hook].callCount)(hook)
					})

					render(root, [])

					o(methods.view.callCount).equals(2)
					o(methods.oninit.callCount).equals(1)
					o(methods.oncreate.callCount).equals(1)
					o(methods.onbeforeupdate.callCount).equals(1)
					o(methods.onupdate.callCount).equals(1)
					o(methods.onbeforeremove.callCount).equals(1)
					o(methods.onremove.callCount).equals(1)

					hooks.forEach(function(hook) {
						o(attrs[hook].callCount).equals(methods[hook].callCount)(hook)
					})
				})
				o("lifecycle timing megatest (for a single component with the state overwritten)", function() {
					var methods = {
						view: o.spy(function(vnode) {
							o(vnode.state).equals(1)
							return ""
						})
					}
					var attrs = {}
					var hooks = [
						"oninit", "oncreate", "onbeforeupdate",
						"onupdate", "onbeforeremove", "onremove"
					]
					hooks.forEach(function(hook) {
						// the `attrs` hooks are called before  the component ones
						attrs[hook] = o.spy(function(vnode) {
							o(vnode.state).equals(1)
							o(attrs[hook].callCount).equals(methods[hook].callCount + 1)
						})
						methods[hook] = o.spy(function(vnode) {
							o(vnode.state).equals(1)
							o(attrs[hook].callCount).equals(methods[hook].callCount)
						})
					})

					var attrsOninit = attrs.oninit
					var methodsOninit = methods.oninit
					attrs.oninit = o.spy(function(vnode){
						vnode.state = 1
						return attrsOninit.call(this, vnode)
					})
					methods.oninit = o.spy(function(vnode){
						vnode.state = 1
						return methodsOninit.call(this, vnode)
					})

					var component = createComponent(methods)

					o(methods.view.callCount).equals(0)
					o(methods.oninit.callCount).equals(0)
					o(methods.oncreate.callCount).equals(0)
					o(methods.onbeforeupdate.callCount).equals(0)
					o(methods.onupdate.callCount).equals(0)
					o(methods.onbeforeremove.callCount).equals(0)
					o(methods.onremove.callCount).equals(0)

					hooks.forEach(function(hook) {
						o(attrs[hook].callCount).equals(methods[hook].callCount)(hook)
					})

					render(root, [{tag: component, attrs: attrs}])

					o(methods.view.callCount).equals(1)
					o(methods.oninit.callCount).equals(1)
					o(methods.oncreate.callCount).equals(1)
					o(methods.onbeforeupdate.callCount).equals(0)
					o(methods.onupdate.callCount).equals(0)
					o(methods.onbeforeremove.callCount).equals(0)
					o(methods.onremove.callCount).equals(0)

					hooks.forEach(function(hook) {
						o(attrs[hook].callCount).equals(methods[hook].callCount)(hook)
					})

					render(root, [{tag: component, attrs: attrs}])

					o(methods.view.callCount).equals(2)
					o(methods.oninit.callCount).equals(1)
					o(methods.oncreate.callCount).equals(1)
					o(methods.onbeforeupdate.callCount).equals(1)
					o(methods.onupdate.callCount).equals(1)
					o(methods.onbeforeremove.callCount).equals(0)
					o(methods.onremove.callCount).equals(0)

					hooks.forEach(function(hook) {
						o(attrs[hook].callCount).equals(methods[hook].callCount)(hook)
					})

					render(root, [])

					o(methods.view.callCount).equals(2)
					o(methods.oninit.callCount).equals(1)
					o(methods.oncreate.callCount).equals(1)
					o(methods.onbeforeupdate.callCount).equals(1)
					o(methods.onupdate.callCount).equals(1)
					o(methods.onbeforeremove.callCount).equals(1)
					o(methods.onremove.callCount).equals(1)

					hooks.forEach(function(hook) {
						o(attrs[hook].callCount).equals(methods[hook].callCount)(hook)
					})
				})
				o("hook state and arguments validation", function(){
					var methods = {
						view: o.spy(function(vnode) {
							o(this).equals(vnode.state)
							return ""
						})
					}
					var attrs = {}
					var hooks = [
						"oninit", "oncreate", "onbeforeupdate",
						"onupdate", "onbeforeremove", "onremove"
					]
					hooks.forEach(function(hook) {
						attrs[hook] = o.spy(function(vnode){
							o(this).equals(vnode.state)(hook)
						})
						methods[hook] = o.spy(function(vnode){
							o(this).equals(vnode.state)
						})
					})

					var component = createComponent(methods)

					render(root, [{tag: component, attrs: attrs}])
					render(root, [{tag: component, attrs: attrs}])
					render(root, [])

					hooks.forEach(function(hook) {
						o(attrs[hook].this).equals(methods.view.this)(hook)
						o(methods[hook].this).equals(methods.view.this)(hook)
					})

					o(methods.view.args.length).equals(1)
					o(methods.oninit.args.length).equals(1)
					o(methods.oncreate.args.length).equals(1)
					o(methods.onbeforeupdate.args.length).equals(2)
					o(methods.onupdate.args.length).equals(1)
					o(methods.onbeforeremove.args.length).equals(1)
					o(methods.onremove.args.length).equals(1)

					hooks.forEach(function(hook) {
						o(methods[hook].args.length).equals(attrs[hook].args.length)(hook)
					})
				})
				o("recycled components get a fresh state", function() {
					var step = 0
					var firstState
					var view = o.spy(function(vnode) {
						if (step === 0) {
							firstState = vnode.state
						} else {
							o(vnode.state).notEquals(firstState)
						}
						return {tag: "div"}
					})
					var component = createComponent({view: view})

					render(root, [{tag: "div", children: [{tag: component, key: 1}]}])
					var child = root.firstChild.firstChild
					render(root, [])
					step = 1
					render(root, [{tag: "div", children: [{tag: component, key: 1}]}])

					o(child).equals(root.firstChild.firstChild)
					o(view.callCount).equals(2)
				})
			})
			o.spec("state", function() {
				o("initializes state", function() {
					var data = {a: 1}
					var component = createComponent(createComponent({
						data: data,
						oninit: init,
						view: function() {
							return ""
						}
					}))

					render(root, [{tag: component}])

					function init(vnode) {
						o(vnode.state.data).equals(data)
					}
				})
				o("state proxies to the component object/prototype", function() {
					var body = {a: 1}
					var data = [body]
					var component = createComponent(createComponent({
						data: data,
						oninit: init,
						view: function() {
							return ""
						}
					}))

					render(root, [{tag: component}])

					function init(vnode) {
						o(vnode.state.data).equals(data)
						o(vnode.state.data[0]).equals(body)
					}
				})
			})
		})
	})
	o.spec("Tests specific to certain component kinds", function() {
		o.spec("state", function() {
			o("POJO", function() {
				var data = {}
				var component = {
					data: data,
					oninit: init,
					view: function() {
						return ""
					}
				}

				render(root, [{tag: component}])

				function init(vnode) {
					o(vnode.state.data).equals(data)

					//inherits state via prototype
					component.x = 1
					o(vnode.state.x).equals(1)
				}
			})
			o("Constructible", function() {
				var oninit = o.spy()
				var component = o.spy(function(vnode){
					o(vnode.state).equals(undefined)
					o(oninit.callCount).equals(0)
				})
				var view = o.spy(function(){
					o(this instanceof component).equals(true)
					return ""
				})
				component.prototype.view = view
				component.prototype.oninit = oninit

				render(root, [{tag: component, attrs: {oninit: oninit}}])
				render(root, [{tag: component, attrs: {oninit: oninit}}])
				render(root, [])

				o(component.callCount).equals(1)
				o(oninit.callCount).equals(2)
				o(view.callCount).equals(2)
			})
			o("Closure", function() {
				var state
				var oninit = o.spy()
				var view = o.spy(function() {
					o(this).equals(state)
					return ""
				})
				var component = o.spy(function(vnode) {
					o(vnode.state).equals(undefined)
					o(oninit.callCount).equals(0)
					return state = {
						view: view
					}
				})

				render(root, [{tag: component, attrs: {oninit: oninit}}])
				render(root, [{tag: component, attrs: {oninit: oninit}}])
				render(root, [])

				o(component.callCount).equals(1)
				o(oninit.callCount).equals(1)
				o(view.callCount).equals(2)
			})
		})
	})
})
