diff --git a/render/tests/test-attributes.js b/render/tests/test-attributes.js index c6a3df31..8e706205 100644 --- a/render/tests/test-attributes.js +++ b/render/tests/test-attributes.js @@ -54,7 +54,7 @@ o.spec("attributes", function() { render(root, [a]) - o(a.dom.attributes["readonly"].nodeValue).equals("") + o(a.dom.attributes["readonly"].value).equals("") }) o("when input readonly is false, attribute is not present", function() { var a = {tag: "input", attrs: {readonly: false}} @@ -118,7 +118,7 @@ o.spec("attributes", function() { render(root, canvas) - o(canvas.dom.attributes["width"].nodeValue).equals("100%") + o(canvas.dom.attributes["width"].value).equals("100%") o(canvas.dom.width).equals(100) }) }) @@ -128,7 +128,7 @@ o.spec("attributes", function() { render(root, [a]); - o(a.dom.attributes["class"].nodeValue).equals("test") + o(a.dom.attributes["class"].value).equals("test") }) }) o.spec("contenteditable throws on untrusted children", function() { diff --git a/render/tests/test-component.js b/render/tests/test-component.js index 94863236..c391ddd2 100644 --- a/render/tests/test-component.js +++ b/render/tests/test-component.js @@ -30,7 +30,7 @@ o.spec("component", function() { render(root, [node]) o(root.firstChild.nodeName).equals("DIV") - o(root.firstChild.attributes["id"].nodeValue).equals("a") + o(root.firstChild.attributes["id"].value).equals("a") o(root.firstChild.firstChild.nodeValue).equals("b") }) o("receives arguments", function() { @@ -44,7 +44,7 @@ o.spec("component", function() { render(root, [node]) o(root.firstChild.nodeName).equals("DIV") - o(root.firstChild.attributes["id"].nodeValue).equals("a") + o(root.firstChild.attributes["id"].value).equals("a") o(root.firstChild.firstChild.nodeValue).equals("b") }) o("updates", function() { @@ -57,7 +57,7 @@ o.spec("component", function() { render(root, [{tag: component, attrs: {id: "c"}, text: "d"}]) o(root.firstChild.nodeName).equals("DIV") - o(root.firstChild.attributes["id"].nodeValue).equals("c") + o(root.firstChild.attributes["id"].value).equals("c") o(root.firstChild.firstChild.nodeValue).equals("d") }) o("updates root from null", function() { @@ -400,7 +400,7 @@ o.spec("component", function() { o(called).equals(1) o(root.firstChild.nodeName).equals("DIV") - o(root.firstChild.attributes["id"].nodeValue).equals("a") + o(root.firstChild.attributes["id"].value).equals("a") o(root.firstChild.firstChild.nodeValue).equals("b") }) o("calls oninit when returning fragment", function() { @@ -423,7 +423,7 @@ o.spec("component", function() { o(called).equals(1) o(root.firstChild.nodeName).equals("DIV") - o(root.firstChild.attributes["id"].nodeValue).equals("a") + o(root.firstChild.attributes["id"].value).equals("a") o(root.firstChild.firstChild.nodeValue).equals("b") }) o("calls oninit before view", function() { @@ -479,7 +479,7 @@ o.spec("component", function() { o(called).equals(1) o(root.firstChild.nodeName).equals("DIV") - o(root.firstChild.attributes["id"].nodeValue).equals("a") + o(root.firstChild.attributes["id"].value).equals("a") o(root.firstChild.firstChild.nodeValue).equals("b") }) o("does not calls oncreate on redraw", function() { @@ -520,7 +520,7 @@ o.spec("component", function() { o(called).equals(1) o(root.firstChild.nodeName).equals("DIV") - o(root.firstChild.attributes["id"].nodeValue).equals("a") + o(root.firstChild.attributes["id"].value).equals("a") o(root.firstChild.firstChild.nodeValue).equals("b") }) o("calls onupdate", function() { @@ -546,7 +546,7 @@ o.spec("component", function() { o(called).equals(1) o(root.firstChild.nodeName).equals("DIV") - o(root.firstChild.attributes["id"].nodeValue).equals("a") + o(root.firstChild.attributes["id"].value).equals("a") o(root.firstChild.firstChild.nodeValue).equals("b") }) o("calls onupdate when returning fragment", function() { @@ -572,7 +572,7 @@ o.spec("component", function() { o(called).equals(1) o(root.firstChild.nodeName).equals("DIV") - o(root.firstChild.attributes["id"].nodeValue).equals("a") + o(root.firstChild.attributes["id"].value).equals("a") o(root.firstChild.firstChild.nodeValue).equals("b") }) o("calls onremove", function() { diff --git a/render/tests/test-createElement.js b/render/tests/test-createElement.js index 4e3591f4..6e3dcdd7 100644 --- a/render/tests/test-createElement.js +++ b/render/tests/test-createElement.js @@ -24,8 +24,8 @@ o.spec("createElement", function() { render(root, [vnode]) o(vnode.dom.nodeName).equals("DIV") - o(vnode.dom.attributes["id"].nodeValue).equals("a") - o(vnode.dom.attributes["title"].nodeValue).equals("b") + o(vnode.dom.attributes["id"].value).equals("a") + o(vnode.dom.attributes["title"].value).equals("b") }) o("creates style", function() { var vnode = {tag: "div", attrs: {style: {backgroundColor: "red"}}} @@ -48,8 +48,8 @@ o.spec("createElement", function() { render(root, [vnode]) o(vnode.dom.nodeName).equals("DIV") - o(vnode.dom.attributes["id"].nodeValue).equals("a") - o(vnode.dom.attributes["title"].nodeValue).equals("b") + o(vnode.dom.attributes["id"].value).equals("a") + o(vnode.dom.attributes["title"].value).equals("b") o(vnode.dom.childNodes.length).equals(2) o(vnode.dom.childNodes[0].nodeName).equals("A") o(vnode.dom.childNodes[1].nodeName).equals("B") @@ -65,7 +65,7 @@ o.spec("createElement", function() { o(vnode.dom.namespaceURI).equals("http://www.w3.org/2000/svg") o(vnode.dom.firstChild.nodeName).equals("a") o(vnode.dom.firstChild.namespaceURI).equals("http://www.w3.org/2000/svg") - o(vnode.dom.firstChild.attributes["href"].nodeValue).equals("javascript:;") + o(vnode.dom.firstChild.attributes["href"].value).equals("javascript:;") o(vnode.dom.firstChild.attributes["href"].namespaceURI).equals("http://www.w3.org/1999/xlink") o(vnode.dom.childNodes[1].nodeName).equals("foreignObject") o(vnode.dom.childNodes[1].firstChild.nodeName).equals("body") @@ -75,7 +75,7 @@ o.spec("createElement", function() { var vnode = {tag: "svg", ns: "http://www.w3.org/2000/svg", attrs: {viewBox: "0 0 100 100"}} render(root, [vnode]) - o(vnode.dom.attributes["viewBox"].nodeValue).equals("0 0 100 100") + o(vnode.dom.attributes["viewBox"].value).equals("0 0 100 100") }) o("creates mathml", function() { var vnode = {tag: "math", ns: "http://www.w3.org/1998/Math/MathML", children: [{tag: "mrow", ns: "http://www.w3.org/1998/Math/MathML"}]} diff --git a/render/tests/test-event.js b/render/tests/test-event.js index 31d2d012..1f7eec2a 100644 --- a/render/tests/test-event.js +++ b/render/tests/test-event.js @@ -69,7 +69,7 @@ o.spec("event", function() { o(onevent.args[0].type).equals("click") o(onevent.args[0].target).equals(div.dom) o(div.dom).equals(updated.dom) - o(div.dom.attributes["id"].nodeValue).equals("b") + o(div.dom.attributes["id"].value).equals("b") }) o("handles ontransitionend", function() { diff --git a/render/tests/test-onbeforeupdate.js b/render/tests/test-onbeforeupdate.js index 9814122b..d8b2f2d6 100644 --- a/render/tests/test-onbeforeupdate.js +++ b/render/tests/test-onbeforeupdate.js @@ -21,7 +21,7 @@ o.spec("onbeforeupdate", function() { render(root, [vnode]) render(root, [updated]) - o(root.firstChild.attributes["id"].nodeValue).equals("a") + o(root.firstChild.attributes["id"].value).equals("a") }) o("prevents update in text", function() { @@ -65,7 +65,7 @@ o.spec("onbeforeupdate", function() { render(root, [vnode]) render(root, [updated]) - o(root.firstChild.attributes["id"].nodeValue).equals("b") + o(root.firstChild.attributes["id"].value).equals("b") }) o("accepts arguments for comparison", function() { @@ -86,7 +86,7 @@ o.spec("onbeforeupdate", function() { } o(count).equals(1) - o(root.firstChild.attributes["id"].nodeValue).equals("b") + o(root.firstChild.attributes["id"].value).equals("b") }) o("is not called on creation", function() { @@ -167,7 +167,7 @@ o.spec("onbeforeupdate", function() { render(root, [vnode]) render(root, [updated]) - o(root.firstChild.attributes["id"].nodeValue).equals("a") + o(root.firstChild.attributes["id"].value).equals("a") }) o("does not prevent update if returning true in component and true in vnode", function() { @@ -183,7 +183,7 @@ o.spec("onbeforeupdate", function() { render(root, [vnode]) render(root, [updated]) - o(root.firstChild.attributes["id"].nodeValue).equals("b") + o(root.firstChild.attributes["id"].value).equals("b") }) o("does not prevent update if returning false in component but true in vnode", function() { @@ -199,7 +199,7 @@ o.spec("onbeforeupdate", function() { render(root, [vnode]) render(root, [updated]) - o(root.firstChild.attributes["id"].nodeValue).equals("b") + o(root.firstChild.attributes["id"].value).equals("b") }) o("does not prevent update if returning true in component but false in vnode", function() { @@ -215,7 +215,7 @@ o.spec("onbeforeupdate", function() { render(root, [vnode]) render(root, [updated]) - o(root.firstChild.attributes["id"].nodeValue).equals("b") + o(root.firstChild.attributes["id"].value).equals("b") }) o("does not prevent update if returning true from component", function() { @@ -231,7 +231,7 @@ o.spec("onbeforeupdate", function() { render(root, [vnode]) render(root, [updated]) - o(root.firstChild.attributes["id"].nodeValue).equals("b") + o(root.firstChild.attributes["id"].value).equals("b") }) o("accepts arguments for comparison in component", function() { @@ -258,7 +258,7 @@ o.spec("onbeforeupdate", function() { } o(count).equals(1) - o(root.firstChild.attributes["id"].nodeValue).equals("b") + o(root.firstChild.attributes["id"].value).equals("b") }) o("is not called on component creation", function() { diff --git a/render/tests/test-onupdate.js b/render/tests/test-onupdate.js index 13f62c46..4b74d288 100644 --- a/render/tests/test-onupdate.js +++ b/render/tests/test-onupdate.js @@ -170,9 +170,9 @@ o.spec("onupdate", function() { function update(vnode) { called = true - o(vnode.dom.parentNode.attributes["id"].nodeValue).equals("11") - o(vnode.dom.attributes["id"].nodeValue).equals("22") - o(vnode.dom.childNodes[0].attributes["id"].nodeValue).equals("33") + o(vnode.dom.parentNode.attributes["id"].value).equals("11") + o(vnode.dom.attributes["id"].value).equals("22") + o(vnode.dom.childNodes[0].attributes["id"].value).equals("33") } o(called).equals(true) }) diff --git a/render/tests/test-updateElement.js b/render/tests/test-updateElement.js index 1774e61f..313fe1a9 100644 --- a/render/tests/test-updateElement.js +++ b/render/tests/test-updateElement.js @@ -21,7 +21,7 @@ o.spec("updateElement", function() { o(updated.dom).equals(vnode.dom) o(updated.dom).equals(root.firstChild) - o(updated.dom.attributes["id"].nodeValue).equals("c") + o(updated.dom.attributes["id"].value).equals("c") }) o("adds attr", function() { var vnode = {tag: "a", attrs: {id: "b"}} @@ -32,7 +32,7 @@ o.spec("updateElement", function() { o(updated.dom).equals(vnode.dom) o(updated.dom).equals(root.firstChild) - o(updated.dom.attributes["title"].nodeValue).equals("d") + o(updated.dom.attributes["title"].value).equals("d") }) o("adds attr from empty attrs", function() { var vnode = {tag: "a"} @@ -43,7 +43,7 @@ o.spec("updateElement", function() { o(updated.dom).equals(vnode.dom) o(updated.dom).equals(root.firstChild) - o(updated.dom.attributes["title"].nodeValue).equals("d") + o(updated.dom.attributes["title"].value).equals("d") }) o("removes attr", function() { var vnode = {tag: "a", attrs: {id: "b", title: "d"}} @@ -209,7 +209,7 @@ o.spec("updateElement", function() { render(root, [vnode]) render(root, [updated]) - o(updated.dom.attributes["class"].nodeValue).equals("b") + o(updated.dom.attributes["class"].value).equals("b") }) o("updates svg child", function() { var vnode = {tag: "svg", children: [{ diff --git a/test-utils/domMock.js b/test-utils/domMock.js index a827070a..2e0b4a6b 100644 --- a/test-utils/domMock.js +++ b/test-utils/domMock.js @@ -1,6 +1,38 @@ "use strict" -module.exports = function() { +/* +Known limitations: + +- `option.selected` can't be set/read when the option doesn't have a `select` parent +- `element.attributes` is just a map of attribute names => Attr objects stubs +- ... + +*/ + +/* +options: +- spy:(f: Function) => Function +*/ + +module.exports = function(options) { + options = options || {} + var spy = options.spy || function(f){return f} + var spymap = [] + function registerSpies(element, spies) { + if(options.spy) { + var i = spymap.indexOf(element) + if (i === -1) { + spymap.push(element, spies) + } else { + var existing = spymap[i + 1] + for (var k in spies) existing[k] = spies[k] + } + } + } + function getSpies(element) { + if(options.spy) return spymap[spymap.indexOf(element) + 1] + } + function isModernEvent(type) { return type === "transitionstart" || type === "transitionend" || type === "animationstart" || type === "animationend" } @@ -62,14 +94,22 @@ module.exports = function() { } function getAttribute(name) { if (this.attributes[name] == null) return null - return this.attributes[name].nodeValue + return this.attributes[name].value } function setAttribute(name, value) { - var nodeValue = String(value) + /*eslint-disable no-implicit-coercion*/ + // this is the correct kind of conversion, passing a Symbol throws in browsers too. + var nodeValue = "" + value + /*eslint-enable no-implicit-coercion*/ + this.attributes[name] = { namespaceURI: null, - get nodeValue() {return nodeValue}, - set nodeValue(value) {nodeValue = String(value)}, + get value() {return nodeValue}, + set value(value) { + /*eslint-disable no-implicit-coercion*/ + nodeValue = "" + value + /*eslint-enable no-implicit-coercion*/ + }, } } function setAttributeNS(ns, name, value) { @@ -79,6 +119,9 @@ module.exports = function() { function removeAttribute(name) { delete this.attributes[name] } + function hasAttribute(name) { + return name in this.attributes + } var declListTokenizer = /;|"(?:\\.|[^"\n])*"|'(?:\\.|[^'\n])*'/g /** * This will split a semicolon-separated CSS declaration list into an array of @@ -150,6 +193,7 @@ module.exports = function() { appendChild: appendChild, removeChild: removeChild, insertBefore: insertBefore, + hasAttribute: hasAttribute, getAttribute: getAttribute, setAttribute: setAttribute, setAttributeNS: setAttributeNS, @@ -204,7 +248,7 @@ module.exports = function() { throw new Error("setting element.style is not portable") }, get className() { - return this.attributes["class"] ? this.attributes["class"].nodeValue : "" + return this.attributes["class"] ? this.attributes["class"].value : "" }, set className(value) { if (this.namespaceURI === "http://www.w3.org/2000/svg") throw new Error("Cannot set property className of SVGElement") @@ -222,7 +266,7 @@ module.exports = function() { } }, dispatchEvent: function(e) { - if (this.nodeName === "INPUT" && this.attributes["type"] != null && this.attributes["type"].nodeValue === "checkbox" && e.type === "click") { + if (this.nodeName === "INPUT" && this.attributes["type"] != null && this.attributes["type"].value === "checkbox" && e.type === "click") { this.checked = !this.checked } @@ -256,17 +300,55 @@ module.exports = function() { enumerable: true, }) - element.value = "" - } - - if (element.nodeName === "TEXTAREA") { - var value + var value = "" + var valueSetter = spy(function(v) { + /*eslint-disable no-implicit-coercion*/ + value = v === null ? "" : "" + v + /*eslint-enable no-implicit-coercion*/ + }) Object.defineProperty(element, "value", { get: function() { - return value != null ? value : - this.firstChild ? this.firstChild.nodeValue : "" + return value + }, + set: valueSetter, + enumerable: true, + }) + + var typeSetter = spy(function(v) { + this.setAttribute("type", v) + }) + Object.defineProperty(element, "type", { + get: function() { + if (!this.hasAttribute("type")) return "text" + var type = this.getAttribute("type") + return (/^(?:radio|button|checkbox|color|date|datetime|datetime-local|email|file|hidden|month|number|password|range|research|search|submit|tel|text|url|week|image)$/) + .test(type) + ? type + : "text" + }, + set: typeSetter, + enumerable: true, + }) + registerSpies(element, { + valueSetter: valueSetter, + typeSetter: typeSetter + }) + } + + + if (element.nodeName === "TEXTAREA") { + var wasNeverSet = true + var value = "" + Object.defineProperty(element, "value", { + get: function() { + return wasNeverSet && this.firstChild ? this.firstChild.nodeValue : value + }, + set: function(v) { + wasNeverSet = false + /*eslint-disable no-implicit-coercion*/ + value = v === null ? "" : "" + v + /*eslint-enable no-implicit-coercion*/ }, - set: function(v) {value = v}, enumerable: true, }) } @@ -275,11 +357,11 @@ module.exports = function() { if (element.nodeName === "CANVAS") { Object.defineProperty(element, "width", { - get: function() {return this.attributes["width"] ? Math.floor(parseInt(this.attributes["width"].nodeValue) || 0) : 300}, + get: function() {return this.attributes["width"] ? Math.floor(parseInt(this.attributes["width"].value) || 0) : 300}, set: function(value) {this.setAttribute("width", Math.floor(Number(value) || 0).toString())}, }) Object.defineProperty(element, "height", { - get: function() {return this.attributes["height"] ? Math.floor(parseInt(this.attributes["height"].nodeValue) || 0) : 300}, + get: function() {return this.attributes["height"] ? Math.floor(parseInt(this.attributes["height"].value) || 0) : 300}, set: function(value) {this.setAttribute("height", Math.floor(Number(value) || 0).toString())}, }) } @@ -296,7 +378,7 @@ module.exports = function() { } function getOptionValue(element) { return element.attributes["value"] != null ? - element.attributes["value"].nodeValue : + element.attributes["value"].value : element.firstChild != null ? element.firstChild.nodeValue : "" } if (element.nodeName === "SELECT") { @@ -317,14 +399,14 @@ module.exports = function() { }, enumerable: true, }) - Object.defineProperty(element, "value", { - get: function() { - if (this.selectedIndex > -1) return getOptionValue(getOptions(this)[this.selectedIndex]) - return "" - }, - set: function(value) { + var valueSetter = spy(function(value) { + if (value === null) { + selectedIndex = -1 + } else { var options = getOptions(this) - var stringValue = String(value) + /*eslint-disable no-implicit-coercion*/ + var stringValue = "" + value + /*eslint-enable no-implicit-coercion*/ for (var i = 0; i < options.length; i++) { if (getOptionValue(options[i]) === stringValue) { // selectedValue = stringValue @@ -334,19 +416,37 @@ module.exports = function() { } // selectedValue = stringValue selectedIndex = -1 + } + }) + Object.defineProperty(element, "value", { + get: function() { + if (this.selectedIndex > -1) return getOptionValue(getOptions(this)[this.selectedIndex]) + return "" }, + set: valueSetter, enumerable: true, }) + registerSpies(element, { + valueSetter: valueSetter + }) } if (element.nodeName === "OPTION") { + var valueSetter = spy(function(value) { + /*eslint-disable no-implicit-coercion*/ + this.setAttribute("value", value === null ? "" : "" + value) + /*eslint-enable no-implicit-coercion*/ + }) Object.defineProperty(element, "value", { get: function() {return getOptionValue(this)}, - set: function(value) { - this.setAttribute("value", value) - }, + set: valueSetter, enumerable: true, }) + registerSpies(element, { + valueSetter: valueSetter + }) + Object.defineProperty(element, "selected", { + // TODO? handle `selected` without a parent (works in browsers) get: function() { var options = getOptions(this.parentNode) var index = options.indexOf(this) @@ -372,13 +472,19 @@ module.exports = function() { return element }, createTextNode: function(text) { - var nodeValue = String(text) + /*eslint-disable no-implicit-coercion*/ + var nodeValue = "" + text + /*eslint-enable no-implicit-coercion*/ return { nodeType: 3, nodeName: "#text", parentNode: null, get nodeValue() {return nodeValue}, - set nodeValue(value) {nodeValue = String(value)}, + set nodeValue(value) { + /*eslint-disable no-implicit-coercion*/ + nodeValue = "" + value + /*eslint-enable no-implicit-coercion*/ + }, } }, createDocumentFragment: function() { @@ -409,5 +515,7 @@ module.exports = function() { $window.document.documentElement.appendChild($window.document.body) activeElement = $window.document.body + if (options.spy) $window.__getSpies = getSpies + return $window } diff --git a/test-utils/tests/test-domMock.js b/test-utils/tests/test-domMock.js index aee68832..a71d8e8e 100644 --- a/test-utils/tests/test-domMock.js +++ b/test-utils/tests/test-domMock.js @@ -77,6 +77,27 @@ o.spec("domMock", function() { o(node.nodeValue).equals("true") }) + if (typeof Symbol === "function") { + o("doesn't work with symbols", function(){ + var threw = false + try { + $document.createTextNode(Symbol("nono")) + } catch(e) { + threw = true + } + o(threw).equals(true) + }) + o("symbols can't be used as nodeValue", function(){ + var threw = false + try { + var node = $document.createTextNode("a") + node.nodeValue = Symbol("nono") + } catch(e) { + threw = true + } + o(threw).equals(true) + }) + } }) o.spec("createDocumentFragment", function() { @@ -327,39 +348,54 @@ o.spec("domMock", function() { var div = $document.createElement("div") div.setAttribute("id", "aaa") - o(div.attributes["id"].nodeValue).equals("aaa") + o(div.attributes["id"].value).equals("aaa") o(div.attributes["id"].namespaceURI).equals(null) }) o("works w/ number", function() { var div = $document.createElement("div") div.setAttribute("id", 123) - o(div.attributes["id"].nodeValue).equals("123") + o(div.attributes["id"].value).equals("123") }) o("works w/ null", function() { var div = $document.createElement("div") div.setAttribute("id", null) - o(div.attributes["id"].nodeValue).equals("null") + o(div.attributes["id"].value).equals("null") }) o("works w/ undefined", function() { var div = $document.createElement("div") div.setAttribute("id", undefined) - o(div.attributes["id"].nodeValue).equals("undefined") + o(div.attributes["id"].value).equals("undefined") }) o("works w/ object", function() { var div = $document.createElement("div") div.setAttribute("id", {}) - o(div.attributes["id"].nodeValue).equals("[object Object]") + o(div.attributes["id"].value).equals("[object Object]") }) o("setting via attributes map stringifies", function() { var div = $document.createElement("div") div.setAttribute("id", "a") - div.attributes["id"].nodeValue = 123 + div.attributes["id"].value = 123 - o(div.attributes["id"].nodeValue).equals("123") + o(div.attributes["id"].value).equals("123") + }) + }) + o.spec("hasAttribute", function() { + o("works", function() { + var div = $document.createElement("div") + + o(div.hasAttribute("id")).equals(false) + + div.setAttribute("id", "aaa") + + o(div.hasAttribute("id")).equals(true) + + div.removeAttribute("id") + + o(div.hasAttribute("id")).equals(false) }) }) @@ -368,14 +404,14 @@ o.spec("domMock", function() { var div = $document.createElement("div") div.setAttributeNS("http://www.w3.org/1999/xlink", "href", "aaa") - o(div.attributes["href"].nodeValue).equals("aaa") + o(div.attributes["href"].value).equals("aaa") o(div.attributes["href"].namespaceURI).equals("http://www.w3.org/1999/xlink") }) o("works w/ number", function() { var div = $document.createElement("div") div.setAttributeNS("http://www.w3.org/1999/xlink", "href", 123) - o(div.attributes["href"].nodeValue).equals("123") + o(div.attributes["href"].value).equals("123") o(div.attributes["href"].namespaceURI).equals("http://www.w3.org/1999/xlink") }) }) @@ -416,18 +452,18 @@ o.spec("domMock", function() { o(div.childNodes[0].nodeName).equals("BR") o(div.childNodes[1].nodeType).equals(1) o(div.childNodes[1].nodeName).equals("A") - o(div.childNodes[1].attributes["class"].nodeValue).equals("aaa") - o(div.childNodes[1].attributes["id"].nodeValue).equals("xyz") + o(div.childNodes[1].attributes["class"].value).equals("aaa") + o(div.childNodes[1].attributes["id"].value).equals("xyz") o(div.childNodes[1].childNodes[0].nodeType).equals(3) o(div.childNodes[1].childNodes[0].nodeValue).equals("123") o(div.childNodes[1].childNodes[1].nodeType).equals(1) o(div.childNodes[1].childNodes[1].nodeName).equals("B") - o(div.childNodes[1].childNodes[1].attributes["class"].nodeValue).equals("bbb") + o(div.childNodes[1].childNodes[1].attributes["class"].value).equals("bbb") o(div.childNodes[1].childNodes[2].nodeType).equals(3) o(div.childNodes[1].childNodes[2].nodeValue).equals("234") o(div.childNodes[1].childNodes[3].nodeType).equals(1) o(div.childNodes[1].childNodes[3].nodeName).equals("BR") - o(div.childNodes[1].childNodes[3].attributes["class"].nodeValue).equals("ccc") + o(div.childNodes[1].childNodes[3].attributes["class"].value).equals("ccc") o(div.childNodes[1].childNodes[4].nodeType).equals(3) o(div.childNodes[1].childNodes[4].nodeValue).equals("345") }) @@ -628,14 +664,14 @@ o.spec("domMock", function() { a.setAttribute("href", "") o(a.href).notEquals("") - o(a.attributes["href"].nodeValue).equals("") + o(a.attributes["href"].value).equals("") }) o("is path if property is set", function() { var a = $document.createElement("a") a.href = "" o(a.href).notEquals("") - o(a.attributes["href"].nodeValue).equals("") + o(a.attributes["href"].value).equals("") }) }) o.spec("input[checked]", function() { @@ -656,7 +692,7 @@ o.spec("domMock", function() { input.setAttribute("checked", "") o(input.checked).equals(true) - o(input.attributes["checked"].nodeValue).equals("") + o(input.attributes["checked"].value).equals("") input.removeAttribute("checked") @@ -699,20 +735,95 @@ o.spec("domMock", function() { o("value" in input).equals(true) o("value" in a).equals(false) }) + o("converts null to ''", function() { + var input = $document.createElement("input") + input.value = "x" + + o(input.value).equals("x") + + input.value = null + + o(input.value).equals("") + }) + o("converts values to strings", function() { + var input = $document.createElement("input") + input.value = 5 + + o(input.value).equals("5") + + input.value = 0 + + o(input.value).equals("0") + + input.value = undefined + + o(input.value).equals("undefined") + }) + if (typeof Symbol === "function") o("throws when set to a symbol", function() { + var threw = false + var input = $document.createElement("input") + try { + input.value = Symbol("") + } catch (e) { + o(e instanceof TypeError).equals(true) + threw = true + } + + o(input.value).equals("") + o(threw).equals(true) + }) + }) + o.spec("input[type]", function(){ + o("only exists in input elements", function() { + var input = $document.createElement("input") + var a = $document.createElement("a") + + o("type" in input).equals(true) + o("type" in a).equals(false) + }) + o("is 'text' by default", function() { + var input = $document.createElement("input") + + o(input.type).equals("text") + }) + "radio|button|checkbox|color|date|datetime|datetime-local|email|file|hidden|month|number|password|range|research|search|submit|tel|text|url|week|image" + .split("|").forEach(function(type) { + o("can be set to " + type, function(){ + var input = $document.createElement("input") + input.type = type + + o(input.getAttribute("type")).equals(type) + o(input.type).equals(type) + }) + o("bad values set the attribute, but the getter corrects to 'text', " + type, function(){ + var input = $document.createElement("input") + input.type = "badbad" + type + + o(input.getAttribute("type")).equals("badbad" + type) + o(input.type).equals("text") + }) + }) }) o.spec("textarea[value]", function() { - o("reads from child if no value", function() { - var input = $document.createElement("textarea") - input.appendChild($document.createTextNode("aaa")) + o("reads from child if no value was ever set", function() { + var textarea = $document.createElement("textarea") + textarea.appendChild($document.createTextNode("aaa")) - o(input.value).equals("aaa") + o(textarea.value).equals("aaa") }) o("ignores child if value set", function() { - var input = $document.createElement("textarea") - input.value = "aaa" - input.setAttribute("value", "bbb") + var textarea = $document.createElement("textarea") + textarea.value = null + textarea.appendChild($document.createTextNode("aaa")) - o(input.value).equals("aaa") + o(textarea.value).equals("") + }) + o("textarea[value] doesn't reflect `attributes.value`", function() { + var textarea = $document.createElement("textarea") + textarea.value = "aaa" + textarea.setAttribute("value", "bbb") + + o(textarea.value).equals("aaa") }) }) o.spec("select[value] and select[selectedIndex]", function() { @@ -773,10 +884,76 @@ o.spec("domMock", function() { option2.setAttribute("value", "b") select.appendChild(option2) + var option3 = $document.createElement("option") + option3.setAttribute("value", "") + select.appendChild(option3) + + var option4 = $document.createElement("option") + option4.setAttribute("value", "null") + select.appendChild(option4) + select.value = "b" o(select.value).equals("b") o(select.selectedIndex).equals(1) + + select.value = "" + + o(select.value).equals("") + o(select.selectedIndex).equals(2) + + select.value = "null" + + o(select.value).equals("null") + o(select.selectedIndex).equals(3) + + select.value = null + + o(select.value).equals("") + o(select.selectedIndex).equals(-1) + }) + o("setting valid value works with type conversion", function() { + var select = $document.createElement("select") + + var option1 = $document.createElement("option") + option1.setAttribute("value", "0") + select.appendChild(option1) + + var option2 = $document.createElement("option") + option2.setAttribute("value", "undefined") + select.appendChild(option2) + + var option3 = $document.createElement("option") + option3.setAttribute("value", "") + select.appendChild(option3) + + select.value = 0 + + o(select.value).equals("0") + o(select.selectedIndex).equals(0) + + select.value = undefined + + o(select.value).equals("undefined") + o(select.selectedIndex).equals(1) + + if (typeof Symbol === "function") { + var threw = false + try { + select.value = Symbol("x") + } catch (e) { + threw = true + } + o(threw).equals(true) + o(select.value).equals("undefined") + o(select.selectedIndex).equals(1) + } + }) + o("option.value = null is converted to the empty string", function() { + var option = $document.createElement("option") + option.value = null + + o(option.value).equals("") }) o("setting valid value works with optgroup", function() { var select = $document.createElement("select") @@ -920,55 +1097,55 @@ o.spec("domMock", function() { var canvas = $document.createElement("canvas") canvas.width = 100 - o(canvas.attributes["width"].nodeValue).equals("100") + o(canvas.attributes["width"].value).equals("100") o(canvas.width).equals(100) canvas.height = 100 - o(canvas.attributes["height"].nodeValue).equals("100") + o(canvas.attributes["height"].value).equals("100") o(canvas.height).equals(100) }) o("setting string casts to number", function() { var canvas = $document.createElement("canvas") canvas.width = "100" - o(canvas.attributes["width"].nodeValue).equals("100") + o(canvas.attributes["width"].value).equals("100") o(canvas.width).equals(100) canvas.height = "100" - o(canvas.attributes["height"].nodeValue).equals("100") + o(canvas.attributes["height"].value).equals("100") o(canvas.height).equals(100) }) o("setting float casts to int", function() { var canvas = $document.createElement("canvas") canvas.width = 1.2 - o(canvas.attributes["width"].nodeValue).equals("1") + o(canvas.attributes["width"].value).equals("1") o(canvas.width).equals(1) canvas.height = 1.2 - o(canvas.attributes["height"].nodeValue).equals("1") + o(canvas.attributes["height"].value).equals("1") o(canvas.height).equals(1) }) o("setting percentage fails", function() { var canvas = $document.createElement("canvas") canvas.width = "100%" - o(canvas.attributes["width"].nodeValue).equals("0") + o(canvas.attributes["width"].value).equals("0") o(canvas.width).equals(0) canvas.height = "100%" - o(canvas.attributes["height"].nodeValue).equals("0") + o(canvas.attributes["height"].value).equals("0") o(canvas.height).equals(0) }) o("setting attribute works", function() { var canvas = $document.createElement("canvas") canvas.setAttribute("width", "100%") - o(canvas.attributes["width"].nodeValue).equals("100%") + o(canvas.attributes["width"].value).equals("100%") o(canvas.width).equals(100) canvas.setAttribute("height", "100%") - o(canvas.attributes["height"].nodeValue).equals("100%") + o(canvas.attributes["height"].value).equals("100%") o(canvas.height).equals(100) }) }) @@ -979,7 +1156,7 @@ o.spec("domMock", function() { el.className = "a" o(el.className).equals("a") - o(el.attributes["class"].nodeValue).equals("a") + o(el.attributes["class"].value).equals("a") }) o("setter throws in svg", function(done) { var el = $document.createElementNS("http://www.w3.org/2000/svg", "svg") @@ -991,4 +1168,69 @@ o.spec("domMock", function() { } }) }) + o.spec("spies", function() { + var $window + o.beforeEach(function() { + $window = domMock({spy: o.spy}) + }) + o("basics", function() { + o(typeof $window.__getSpies).equals("function") + o("__getSpies" in domMock()).equals(false) + }) + o("input elements have spies on value and type setters", function() { + var input = $window.document.createElement("input") + + var spies = $window.__getSpies(input) + + o(typeof spies).equals("object") + o(spies).notEquals(null) + o(typeof spies.valueSetter).equals("function") + o(typeof spies.typeSetter).equals("function") + o(spies.valueSetter.callCount).equals(0) + o(spies.typeSetter.callCount).equals(0) + + input.value = "aaa" + input.type = "radio" + + o(spies.valueSetter.callCount).equals(1) + o(spies.valueSetter.this).equals(input) + o(spies.valueSetter.args[0]).equals("aaa") + + o(spies.typeSetter.callCount).equals(1) + o(spies.typeSetter.this).equals(input) + o(spies.typeSetter.args[0]).equals("radio") + }) + o("select elements have spies on value setters", function() { + var select = $window.document.createElement("select") + + var spies = $window.__getSpies(select) + + o(typeof spies).equals("object") + o(spies).notEquals(null) + o(typeof spies.valueSetter).equals("function") + o(spies.valueSetter.callCount).equals(0) + + select.value = "aaa" + + o(spies.valueSetter.callCount).equals(1) + o(spies.valueSetter.this).equals(select) + o(spies.valueSetter.args[0]).equals("aaa") + }) + o("option elements have spies on value setters", function() { + var option = $window.document.createElement("option") + + var spies = $window.__getSpies(option) + + o(typeof spies).equals("object") + o(spies).notEquals(null) + o(typeof spies.valueSetter).equals("function") + o(spies.valueSetter.callCount).equals(0) + + option.value = "aaa" + + o(spies.valueSetter.callCount).equals(1) + o(spies.valueSetter.this).equals(option) + o(spies.valueSetter.args[0]).equals("aaa") + }) + }) })