diff --git a/docs/change-log.md b/docs/change-log.md index f49329ec..87343422 100644 --- a/docs/change-log.md +++ b/docs/change-log.md @@ -29,7 +29,7 @@ - API: Event handlers may also be objects with `handleEvent` methods ([#1939](https://github.com/MithrilJS/mithril.js/issues/1939)). - API: `m.route.link` accepts an optional `options` object ([#1930](https://github.com/MithrilJS/mithril.js/pull/1930)) - API: `m.request` supports `timeout` as attr - ([#1966](https://github.com/MithrilJS/mithril.js/pull/1966)) - +- Mocks: add limited support for the DOMParser API #### Bug fixes diff --git a/test-utils/domMock.js b/test-utils/domMock.js index a4c7c598..99bfff9a 100644 --- a/test-utils/domMock.js +++ b/test-utils/domMock.js @@ -2,7 +2,7 @@ /* Known limitations: - +- the innerHTML setter and the DOMParser only support a small subset of the true HTML/XML syntax. - `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 - ... @@ -183,9 +183,43 @@ module.exports = function(options) { res.unshift(declList) return res } - + function parseMarkup(value, root, voidElements, xmlns) { + var depth = 0, stack = [root] + value.replace(/<([a-z0-9\-]+?)((?:\s+?[^=]+?=(?:"[^"]*?"|'[^']*?'|[^\s>]*))*?)(\s*\/)?>|<\/([a-z0-9\-]+?)>|([^<]+)/g, function(match, startTag, attrs, selfClosed, endTag, text) { + if (startTag) { + var element = xmlns == null ? $window.document.createElement(startTag) : $window.document.createElementNS(xmlns, startTag) + attrs.replace(/\s+?([^=]+?)=(?:"([^"]*?)"|'([^']*?)'|([^\s>]*))/g, function(match, key, doubleQuoted, singleQuoted, unquoted) { + var keyParts = key.split(":") + var name = keyParts.pop() + var ns = keyParts[0] + var value = doubleQuoted || singleQuoted || unquoted || "" + if (ns != null) element.setAttributeNS(ns, name, value) + else element.setAttribute(name, value) + }) + stack[depth].appendChild(element) + if (!selfClosed && voidElements.indexOf(startTag.toLowerCase()) < 0) stack[++depth] = element + } + else if (endTag) { + depth-- + } + else if (text) { + stack[depth].appendChild($window.document.createTextNode(text)) // FIXME handle html entities + } + }) + } + function DOMParser() {} + DOMParser.prototype.parseFromString = function(src, mime) { + if (mime !== "image/svg+xml") throw new Error("The DOMParser mock only supports the \"image/svg+xml\" MIME type") + var match = src.match(/^(.*)<\/svg>$/) + if (!match) throw new Error("Please provide a bare SVG tag with the xmlns as only attribute") + var value = match[1] + var root = $window.document.createElementNS("http://www.w3.org/2000/svg", "svg") + parseMarkup(value, root, [], "http://www.w3.org/2000/svg") + return {documentElement: root} + } var activeElement var $window = { + DOMParser: DOMParser, document: { createElement: function(tag) { var cssText = "" @@ -244,30 +278,18 @@ module.exports = function(options) { if (value !== "") this.appendChild($window.document.createTextNode(value)) }, set innerHTML(value) { + var voidElements = ["area", "base", "br", "col", "command", "embed", "hr", "img", "input", "keygen", "link", "meta", "param", "source", "track", "wbr"] while (this.firstChild) this.removeChild(this.firstChild) - - var stack = [this], depth = 0, voidElements = ["area", "base", "br", "col", "command", "embed", "hr", "img", "input", "keygen", "link", "meta", "param", "source", "track", "wbr"] - value.replace(/<([a-z0-9\-]+?)((?:\s+?[^=]+?=(?:"[^"]*?"|'[^']*?'|[^\s>]*))*?)(\s*\/)?>|<\/([a-z0-9\-]+?)>|([^<]+)/g, function(match, startTag, attrs, selfClosed, endTag, text) { - if (startTag) { - var element = $window.document.createElement(startTag) - attrs.replace(/\s+?([^=]+?)=(?:"([^"]*?)"|'([^']*?)'|([^\s>]*))/g, function(match, key, doubleQuoted, singleQuoted, unquoted) { - var keyParts = key.split(":") - var name = keyParts.pop() - var ns = keyParts[0] - var value = doubleQuoted || singleQuoted || unquoted || "" - if (ns != null) element.setAttributeNS(ns, name, value) - else element.setAttribute(name, value) - }) - stack[depth].appendChild(element) - if (!selfClosed && voidElements.indexOf(startTag.toLowerCase()) < 0) stack[++depth] = element - } - else if (endTag) { - depth-- - } - else if (text) { - stack[depth].appendChild($window.document.createTextNode(text)) // FIXME handle html entities - } - }) + var match = value.match(/^(.*)<\/svg>$/), root, ns + if (match) { + var value = match[1] + root = $window.document.createElementNS("http://www.w3.org/2000/svg", "svg") + ns = "http://www.w3.org/2000/svg" + this.appendChild(root) + } else { + root = this + } + parseMarkup(value, root, voidElements, ns) }, get style() { return style diff --git a/test-utils/tests/test-domMock.js b/test-utils/tests/test-domMock.js index f05d24c9..5540bb86 100644 --- a/test-utils/tests/test-domMock.js +++ b/test-utils/tests/test-domMock.js @@ -4,9 +4,10 @@ var o = require("../../ospec/ospec") var domMock = require("../../test-utils/domMock") o.spec("domMock", function() { - var $document + var $document, $window o.beforeEach(function() { - $document = domMock().document + $window = domMock() + $document = $window.document }) o.spec("createElement", function() { @@ -497,6 +498,45 @@ o.spec("domMock", function() { o(a.parentNode).equals(null) }) + o("empty SVG document", function() { + var div = $document.createElement("div") + div.innerHTML = "" + + o(typeof div.firstChild).notEquals(undefined) + o(div.firstChild.nodeName).equals("svg") + o(div.firstChild.namespaceURI).equals("http://www.w3.org/2000/svg") + o(div.firstChild.childNodes.length).equals(0) + }) + o("text elements", function() { + var div = $document.createElement("div") + div.innerHTML = + "" + + "hello" + + " " + + "world" + + "" + + o(div.firstChild.nodeName).equals("svg") + o(div.firstChild.namespaceURI).equals("http://www.w3.org/2000/svg") + + var nodes = div.firstChild.childNodes + o(nodes.length).equals(3) + o(nodes[0].nodeName).equals("text") + o(nodes[0].namespaceURI).equals("http://www.w3.org/2000/svg") + o(nodes[0].childNodes.length).equals(1) + o(nodes[0].childNodes[0].nodeName).equals("#text") + o(nodes[0].childNodes[0].nodeValue).equals("hello") + o(nodes[1].nodeName).equals("text") + o(nodes[1].namespaceURI).equals("http://www.w3.org/2000/svg") + o(nodes[1].childNodes.length).equals(1) + o(nodes[1].childNodes[0].nodeName).equals("#text") + o(nodes[1].childNodes[0].nodeValue).equals(" ") + o(nodes[2].nodeName).equals("text") + o(nodes[2].namespaceURI).equals("http://www.w3.org/2000/svg") + o(nodes[2].childNodes.length).equals(1) + o(nodes[2].childNodes[0].nodeName).equals("#text") + o(nodes[2].childNodes[0].nodeValue).equals("world") + }) }) o.spec("focus", function() { o("body is active by default", function() { @@ -1792,4 +1832,62 @@ o.spec("domMock", function() { o(spies.valueSetter.args[0]).equals("aaa") }) }) + o.spec("DOMParser for SVG", function(){ + var $DOMParser + o.beforeEach(function() { + $DOMParser = $window.DOMParser + }) + o("basics", function(){ + o(typeof $DOMParser).equals("function") + + var parser = new $DOMParser() + + o(parser instanceof $DOMParser).equals(true) + o(typeof parser.parseFromString).equals("function") + }) + o("empty document", function() { + var parser = new $DOMParser() + var doc = parser.parseFromString( + "", + "image/svg+xml" + ) + + o(typeof doc.documentElement).notEquals(undefined) + o(doc.documentElement.nodeName).equals("svg") + o(doc.documentElement.namespaceURI).equals("http://www.w3.org/2000/svg") + o(doc.documentElement.childNodes.length).equals(0) + }) + o("text elements", function() { + var parser = new $DOMParser() + var doc = parser.parseFromString( + "" + + "hello" + + " " + + "world" + + "", + "image/svg+xml" + ) + + o(doc.documentElement.nodeName).equals("svg") + o(doc.documentElement.namespaceURI).equals("http://www.w3.org/2000/svg") + + var nodes = doc.documentElement.childNodes + o(nodes.length).equals(3) + o(nodes[0].nodeName).equals("text") + o(nodes[0].namespaceURI).equals("http://www.w3.org/2000/svg") + o(nodes[0].childNodes.length).equals(1) + o(nodes[0].childNodes[0].nodeName).equals("#text") + o(nodes[0].childNodes[0].nodeValue).equals("hello") + o(nodes[1].nodeName).equals("text") + o(nodes[1].namespaceURI).equals("http://www.w3.org/2000/svg") + o(nodes[1].childNodes.length).equals(1) + o(nodes[1].childNodes[0].nodeName).equals("#text") + o(nodes[1].childNodes[0].nodeValue).equals(" ") + o(nodes[2].nodeName).equals("text") + o(nodes[2].namespaceURI).equals("http://www.w3.org/2000/svg") + o(nodes[2].childNodes.length).equals(1) + o(nodes[2].childNodes[0].nodeName).equals("#text") + o(nodes[2].childNodes[0].nodeValue).equals("world") + }) + }) })