'use strict'; var domino = require('../lib'); var fs = require('fs'); var html = fs.readFileSync(__dirname + '/fixture/doc.html', 'utf8'); var Window = require('../lib/Window'); exports = exports.domino = {}; function createWindow(html, address) { var document = domino.createDocument(html); if (address) { document.address = 'http://example.com/'; } return new Window(document); } exports.matches = function() { // see https://developer.mozilla.org/en-US/docs/Web/API/Element.matches var d = createWindow(html).document; var h1 = d.getElementById('lorem'); h1.matches('h1').should.equal(true); h1.matches('body > h1').should.equal(true); // not rooted h1.matches('h1 > p').should.equal(false); h1.matches('h1,h2').should.equal(true); h1.matches('h2,h1').should.equal(true); }; exports.closest = function() { // see https://developer.mozilla.org/en-US/docs/Web/API/Element/closest var html = '
\n' + '
Here is div-01\n' + '
Here is div-02\n' + '
Here is div-03
\n' + '
\n' + '
\n' + '
'; var document = createWindow(html).document; var el = document.getElementById('div-03'); var r1 = el.closest('#div-02'); r1.id.should.equal('div-02'); var r2 = el.closest('div div'); r2.id.should.equal('div-03'); var r3 = el.closest('article > div'); r3.id.should.equal('div-01'); var r4 = el.closest(':not(div)'); r4.tagName.should.equal('ARTICLE'); var r5 = el.closest('#nothere'); (r5 === null).should.be.true(); // GH #154 var disconnected = document.createElement('div'); var r6 = disconnected.closest('#nothere'); (r6 === null).should.be.true(); var fragment = document.createDocumentFragment(); fragment.appendChild(disconnected); var r7 = disconnected.closest('#nothere'); (r7 === null).should.be.true(); var r8 = disconnected.closest(':not(div)'); // This should not return DocumentFragment! Always should return an Element. (r8 === null).should.be.true(); }; exports.querySelectorAll = function() { var window = createWindow(html); var d = window.document; var nodeList = d.querySelectorAll('p'); nodeList.should.have.property('item'); nodeList.should.have.length(2); nodeList = d.querySelectorAll('p:not(.foo)'); nodeList.should.have.length(1); nodeList = d.querySelectorAll('tt.foo'); nodeList.should.have.length(2); nodeList = d.querySelectorAll('tt:not(.bar)'); nodeList.should.have.length(1); }; exports.qsaOrder = function() { var window = domino.createDocument('

'); Array.from(window.querySelectorAll('h2, h3')).map(function(el) { return el.tagName; }) .should.eql(['H2', 'H3', 'H3', 'H2', 'H3']); }; exports.orphanQSA = function() { var document = domino.createDocument('

foo

'); var p = document.createElement('p'); p.querySelectorAll('p').should.have.length(0); p.querySelectorAll('p').should.have.length(0); }; // This is https://github.com/chjj/zest/issues/22 exports.tildeQSA = function() { var document = domino.createDocument('
'); var els = document.querySelectorAll('.foo'); els.should.have.length(1); }; exports.rootQSA = function() { // the root element should be both first-child and last-child var document = domino.createDocument('foo'); var html = document.documentElement; html.matches(':first-child').should.be.true(); html.matches(':last-child').should.be.true(); html.matches(':only-child').should.be.true(); html.matches(':first-of-type').should.be.true(); html.matches(':last-of-type').should.be.true(); html.matches(':nth-child(1)').should.be.true(); html.matches(':nth-child(2)').should.be.false(); html.matches(':nth-last-child(1)').should.be.true(); html.matches(':nth-last-child(2)').should.be.false(); html.matches(':nth-of-type(1)').should.be.true(); html.matches(':nth-of-type(2)').should.be.false(); html.matches(':nth-last-of-type(1)').should.be.true(); html.matches(':nth-last-of-type(2)').should.be.false(); }; exports.escapeQSA = function() { var document = domino.createDocument('

foo'); // ensure that selector parsing can handle escaped characters document.querySelectorAll('p\\22\\27p').should.have.length(0); document.querySelectorAll('\\50').should.have.length(1); }; exports.gh20 = function() { var window = createWindow(''); var frag = window.document.createDocumentFragment(); frag.querySelectorAll('p').should.have.length(0); frag.appendChild(window.document.createElement('p')); frag.querySelectorAll('p').should.have.length(1); frag.appendChild(window.document.createElement('p')); frag.querySelectorAll('p').should.have.length(2); }; exports.gh22 = function() { var d=domino.createDocument("

Hello world

Hi

"); d.querySelectorAll('div').should.have.length(1); d.body.querySelectorAll('div').should.have.length(1); d.body.querySelectorAll('h1').should.have.length(1); d.body.querySelectorAll('p').should.have.length(1); var w=createWindow("

Hello world

Hi

"); d=w.document; d.querySelectorAll('div').should.have.length(1); d.body.querySelectorAll('div').should.have.length(1); d.body.querySelectorAll('h1').should.have.length(1); d.body.querySelectorAll('p').should.have.length(1); }; exports.gh31 = function() { var document, heading1, heading2; document = domino.createDocument("

First

Second

"); document.querySelectorAll('h1').should.have.length(2); heading1 = document.body.querySelector('h1'); heading1.getElementsByTagName('h1').should.have.length(0); heading1.querySelectorAll('h1').should.have.length(0); heading2 = document.body.querySelector('h1 + h1'); heading2.querySelectorAll('h1').should.have.length(0); }; exports.gh38 = function() { var d = domino.createDocument('
Header cellData cell
'); var r = d.querySelector('tr'); r.should.have.property('cells'); r.cells.should.have.length(2); }; exports.evilHandler = function() { var window = domino.createDocument(''); window = window; // avoid defined-but-not-used lint error }; exports.title = function() { var d = domino.createDocument(html); if (d.head) { d.documentElement.removeChild(d.head); } d.should.have.property('head', null); d.should.have.property('title', ''); d.querySelectorAll('head > title').should.have.length(0); // per the spec, if there is no , then setting Document.title should // be a no-op. d.title = "Lorem!"; d.title.should.equal(''); d.querySelectorAll('head > title').should.have.length(0); // but if there is a , then setting Document.title should create the // element if necessary. d.documentElement.insertBefore(d.createElement('head'), d.body); (d.head === null).should.be.false(); d.title.should.equal(''); d.title = "Lorem!"; d.title.should.equal("Lorem!"); d.querySelectorAll('head > title').should.have.length(1); // verify that setting <title> works if there's already a title d.title = "ipsum"; d.title.should.equal("ipsum"); d.querySelectorAll('head > title').should.have.length(1); // still only 1! }; exports.children = function() { var d = domino.createDocument(html); var c = d.body.children; c.should.have.length(4); c.should.have.property('0'); var a = Array.prototype.slice.call(c); a.should.be.an.instanceof(Array); a.should.have.length(4); d.body.appendChild(d.createElement('p')); a = Array.prototype.slice.call(c); a.should.have.length(5); }; exports.attributes1 = function() { var d = domino.createDocument(); var el = d.createElement('div'); el.setAttribute('foo', 'foo'); el.setAttribute('bar', 'bar'); el.attributes.should.have.length(2); el.attributes.item(0).value.should.equal('foo'); el.removeAttribute('foo'); el.attributes.should.have.length(1); el.attributes.item(0).name.should.equal('bar'); el.setAttribute('baz', 'baz'); el.attributes.should.have.length(2); el.attributes.item(1).value.should.equal('baz'); }; exports.classList = function() { var d = domino.createDocument(); var el = d.body; el.className = 'foo bar boo'; var cl = el.classList; cl.should.have.length(3); cl[0].should.equal('foo'); cl.contains('bar').should.be.ok(); cl.contains('baz').should.not.be.ok(); cl.add('baz'); cl.contains('baz').should.be.ok(); cl.should.have.length(4); el.className.should.match(/baz/); cl.remove('foo'); cl.should.have.length(3); el.className.should.not.match(/foo/); cl[0].should.not.equal('foo'); }; exports.attributes2 = function() { var d = domino.createDocument(); var div = d.createElement('div'); div.setAttribute('onclick', 't'); div.attributes.should.have.property('onclick'); div.attributes.onclick.should.have.property('value', 't'); div.removeAttribute('onclick'); (div.attributes.onclick === undefined).should.be.true(); }; // exports.jquery1_9 = function() { // var window = createWindow(html); // var f = __dirname + '/fixture/jquery-1.9.1.js'; // window._run(fs.readFileSync(f, 'utf8'), f); // window.$.should.be.ok(); // window.$('.foo').should.have.length(3); // }; exports.jquery2_2 = function() { var window = createWindow(html); window.$ = require(__dirname + '/fixture/jquery-2.2.0.js')(window); window.$.should.be.ok(); window.$('.foo').should.have.length(3); window.$.ajaxTransport("test", function() { return { send: function() {}, abort: function() {} }; }); window.$.ajax({ url: 'test://', dataType: "test", timeout: 1, async: true }); }; exports.jquery3 = function() { var window = createWindow(html); window.$ = require(__dirname + '/../node_modules/jquery/dist/jquery.js')(window); window.$.should.be.ok(); window.$('.foo').should.have.length(3); window.$.ajaxTransport("test", function() { return { send: function() {}, abort: function() {} }; }); window.$.ajax({ url: 'test://', dataType: "test", timeout: 1, async: true }); }; exports.treeWalker = function() { var window = createWindow(html); var d = window.document; var root = d.getElementById('tw'); var tw = d.createTreeWalker(root, window.NodeFilter.SHOW_TEXT, function(n) { return (n.data === 'ignore') ? window.NodeFilter.FILTER_REJECT : window.NodeFilter.FILTER_ACCEPT; }); tw.root.should.equal(root); tw.currentNode.should.equal(root); tw.whatToShow.should.equal(0x4); var actual = []; while (tw.nextNode() !== null) { actual.push(tw.currentNode); } actual.length.should.equal(4); actual.should.eql([ root.firstChild.firstChild, root.firstChild.lastChild.firstChild, root.lastChild.firstChild, root.lastChild.lastChild.firstChild ]); }; exports.nodeIterator = function() { var window = createWindow(html); var d = window.document; var root = d.getElementById('tw'); var ni = d.createNodeIterator(root, window.NodeFilter.SHOW_TEXT, function(n) { return (n.data === 'ignore') ? window.NodeFilter.FILTER_REJECT : window.NodeFilter.FILTER_ACCEPT; }); ni.root.should.equal(root); ni.referenceNode.should.equal(root); ni.whatToShow.should.equal(0x4); var actual = []; for (var n = ni.nextNode(); n ; n = ni.nextNode()) { actual.push(n); } actual.length.should.equal(4); actual.should.eql([ root.firstChild.firstChild, root.firstChild.lastChild.firstChild, root.lastChild.firstChild, root.lastChild.lastChild.firstChild ]); }; exports.nodeIterator2 = function() { /* jshint bitwise: false */ var document = domino.createDocument( '<a>123<b>456<script>alert(1)</script></b></a>789' ); var NodeFilter = domino.impl.NodeFilter; var ni = document.createNodeIterator( document.body, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT | NodeFilter.SHOW_TEXT, function() { return NodeFilter.FILTER_ACCEPT; }, false ); var node = ni.nextNode(); node.tagName.should.equal('BODY'); node = ni.nextNode(); node.tagName.should.equal('A'); node = ni.nextNode(); node.nodeName.should.equal('#text'); node = ni.nextNode(); node.tagName.should.equal('B'); node.insertAdjacentHTML('AfterEnd', node.innerHTML); node.parentNode.removeChild(node); node = ni.nextNode(); node.nodeName.should.equal('#text'); node = ni.nextNode(); node.tagName.should.equal('SCRIPT'); node.parentNode.removeChild(node); node = ni.nextNode(); node.nodeName.should.equal('#text'); node = ni.nextNode(); (node === null).should.be.true(); document.body.innerHTML.should.equal('<a>123456</a>789'); /* jshint bitwise: true */ }; exports.innerHTML = function() { var d = domino.createDocument(); ['pre','textarea','listing'].forEach(function(elementName) { var div = d.createElement('div'); var el = d.createElement(elementName); el.innerHTML = "a"; div.appendChild(el); // no extraneous newline after element tag in this case div.innerHTML.should.equal('<'+elementName+'>a</'+elementName+'>'); el.innerHTML = "\nb"; // Note that this doesn't roundtrip: // see https://github.com/whatwg/html/issues/944 div.innerHTML.should.equal('<'+elementName+'>\nb</'+elementName+'>'); }); }; exports.outerHTML = function() { var tests = [ // This doesn't round trip: // see https://github.com/whatwg/html/issues/944 //'<body><pre>\n\na\n</pre></body>', '<body bgcolor="white"><h1 style="color: red">\nOne\n2 & 3</h1></body>', `<body data-test="<>&"\'"></body>` ]; tests.forEach(function(html) { var d = domino.createDocument(html); // Verify round-tripping. d.body.outerHTML.should.equal(html); }); }; exports.largeAttribute = function() { var size = 400000; // work around a performance regression in node 0.4.x - 0.6.x if (/^v0\.[0-6]\./.test(process.version)) { size = 50000; } var html = '<body><span data-large="'; for (var i=0; i<size; i++) { html += '&'; } html += '"></span></body>'; // this should not crash with a stack overflow! domino.createDocument(html); }; exports.createTextNodeWithNonString = function() { var document = domino.createDocument('<html></html>'); var tests = [ [false, 'false'], [NaN, 'NaN'], [123, '123'], [{}, '[object Object]'], [[], ''], [null, 'null'], [undefined, 'undefined'], ]; for(var i=0; i<tests.length; i++) { var element = document.createElement('div'); var textNode = document.createTextNode(tests[i][0]); element.appendChild(textNode); element.innerHTML.should.equal(tests[i][1]); } }; exports.adoption = function() { // See https://github.com/fgnass/domino/pull/36 var html = "<b>X<b>Y</b>Z</b>"; var doc = domino.createDocument(html); doc.body.innerHTML.should.equal(html); }; exports.attributeSelector = function() { var html = '<h1>foo</h1><h2 id="x" title="y" lang="en" dir="ltr" ' + 'accessKey="z" hidden tabIndex="2">bar</h2>'; var doc = domino.createDocument(html); var h1 = doc.querySelector('h1'); h1.matches('*[id]').should.equal(false); h1.matches('*[title]').should.equal(false); h1.matches('*[lang]').should.equal(false); h1.matches('*[dir]').should.equal(false); h1.matches('*[accessKey]').should.equal(false); h1.matches('*[hidden]').should.equal(false); h1.matches('*[tabIndex]').should.equal(false); var h2 = doc.querySelector('h2'); h2.matches('*[id]').should.equal(true); h2.matches('*[title]').should.equal(true); h2.matches('*[lang]').should.equal(true); h2.matches('*[dir]').should.equal(true); h2.matches('*[accessKey]').should.equal(true); h2.matches('*[hidden]').should.equal(true); h2.matches('*[tabIndex]').should.equal(true); h1.matches('*[matches]').should.equal(false); h1.matches('*[querySelector]').should.equal(false); h1.matches('*[isHTML]').should.equal(false); }; exports.crHandling = function() { var html = '<div\rid=a data-test=1\rfoo="\r"\rbar=\'\r\'\rbat=\r>\r</div\r>'; var doc = domino.createDocument(html); var div = doc.querySelector('#a'); (div != null).should.be.true(); // jshint ignore:line // all \r should be converted to \n div.outerHTML.should.equal('<div id="a" data-test="1" foo="\n" bar="\n" bat="">\n</div>'); }; exports.eqAttr = function() { var html = "<div id=a ==x><a=B></A=b></div>"; var doc = domino.createDocument(html); var div = doc.querySelector('#a'); (div != null).should.be.true(); // jshint ignore:line div.attributes.length.should.equal(2); div.attributes.item(1).name.should.equal('='); div.children.length.should.equal(1); div.children[0].tagName.should.equal('A=B'); }; exports.tagNameCase = function() { // See https://github.com/fgnass/domino/pull/41 var impl = domino.createDOMImplementation(); var namespace = 'http://schemas.xmlsoap.org/soap/envelope/'; var qualifiedName = 'Envelope'; var doc = impl.createDocument(namespace, qualifiedName, null); doc.documentElement.tagName.should.equal(qualifiedName); }; exports.fastAttributes = function() { // test the SIMPLETAG/SIMPLEATTR path in HTMLParser var html = "<div id=a b=\"x "y\" c='a \rb'><\np></div>"; var doc = domino.createDocument(html); var div = doc.querySelector('#a'); (div != null).should.be.true(); // jshint ignore:line div.attributes.length.should.equal(3); div.attributes.item(1).value.should.equal('x "y'); div.attributes.item(2).value.should.equal('a \nb'); div.children.length.should.equal(0); }; exports.anchorElement = function() { var html = "<a href='http://user:pass@example.com:1234/foo/bar?bat#baz'>!</a>"; var doc = domino.createDocument(html); var a = doc.querySelector('a'); (a != null).should.be.true(); // jshint ignore:line a.href.should.equal('http://user:pass@example.com:1234/foo/bar?bat#baz'); a.protocol.should.equal('http:'); a.host.should.equal('example.com:1234'); a.hostname.should.equal('example.com'); a.port.should.equal('1234'); a.pathname.should.equal('/foo/bar'); a.search.should.equal('?bat'); a.hash.should.equal('#baz'); a.username.should.equal('user'); a.password.should.equal('pass'); a.origin.should.equal('http://example.com:1234'); // now try mutating! a.protocol = 'https:'; a.href.should.equal('https://user:pass@example.com:1234/foo/bar?bat#baz'); a.hostname = 'other.net'; a.href.should.equal('https://user:pass@other.net:1234/foo/bar?bat#baz'); a.port = 5678; a.href.should.equal('https://user:pass@other.net:5678/foo/bar?bat#baz'); a.pathname = '/blam/'; a.href.should.equal('https://user:pass@other.net:5678/blam/?bat#baz'); a.search = '?bat&banana'; a.href.should.equal('https://user:pass@other.net:5678/blam/?bat&banana#baz'); a.hash = '#oranges'; a.href.should.equal('https://user:pass@other.net:5678/blam/?bat&banana#oranges'); a.username = 'joe'; a.href.should.equal('https://joe:pass@other.net:5678/blam/?bat&banana#oranges'); a.password = 'smith'; a.href.should.equal('https://joe:smith@other.net:5678/blam/?bat&banana#oranges'); }; exports.gh59 = function() { var html = '<html><body><span style="display:none">foo</span></body></html>'; var doc = domino.createDocument(html); doc.querySelectorAll('span[style]').should.have.length(1); doc.querySelectorAll('span[style="display:none"]').should.have.length(1); doc.querySelectorAll('span[style*="display:none"]').should.have.length(1); }; exports.duplicateID = function() { var doc = domino.createDocument('<root></root>'); var root = doc.documentElement; function makeElement(name) { var elt = doc.createElement(name); elt.setAttribute("id", "x"); return elt; } var a = root.appendChild(makeElement("a")); var b = root.appendChild(makeElement("b")); var c = root.appendChild(makeElement("c")); var d = root.appendChild(makeElement("d")); doc.getElementById("x").should.equal(a); root.removeChild(a); doc.getElementById("x").should.equal(b); root.removeChild(c); root.removeChild(b); doc.getElementById("x").should.equal(d); root.removeChild(d); (doc.getElementById("x") === null).should.be.true(); }; exports.normalize = function() { var doc = domino.createDocument('<span id="x"><!---->foo</span>'); var span = doc.getElementById("x"); span.appendChild(doc.createTextNode('bar')); span.outerHTML.should.equal('<span id="x"><!---->foobar</span>'); span.normalize(); span.outerHTML.should.equal('<span id="x"><!---->foobar</span>'); span.childNodes[1].nodeValue.should.equal('foobar'); }; exports.replaceChild = function() { var impl = domino.createDOMImplementation(); var doc = impl.createDocument(); var root = doc.appendChild(doc.createElement('root')); root.outerHTML.should.equal('<root></root>'); var a = root.appendChild(doc.createElement('a')); root.outerHTML.should.equal('<root><a></a></root>'); var b = doc.createElement('b'); function capture(f) { var events = []; doc._setMutationHandler(function(info) { events.push(info); }); f(); doc._setMutationHandler(null); return events; } // Replace with unrooted capture(function() { root.replaceChild(b, a).should.equal(a); }).should.deepEqual([ { type: 4, // Remove target: root, node: a }, { type: 6, // Insert target: root, node: b }]); root.outerHTML.should.equal('<root><b></b></root>'); root.replaceChild(a, b).should.equal(b); root.outerHTML.should.equal('<root><a></a></root>'); // Move node var c = doc.createElement('c'); root.appendChild(b); root.appendChild(c); capture(function() { root.replaceChild(c, a); }).should.deepEqual([ { type: 4, // Remove target: root, node: a }, { type: 5, // Move target: c }]); root.outerHTML.should.equal('<root><c></c><b></b></root>'); // Replace under unrooted parent var df = doc.createDocumentFragment(); var d = df.appendChild(doc.createElement('d')); var e = df.appendChild(doc.createElement('e')); var f = doc.createElement('f'); df.replaceChild(f, e); df.serialize().should.equal('<d></d><f></f>'); d = d; // avoid defined-but-not-used warning // Replace rooted node with document fragment root.appendChild(a); root.replaceChild(df, b); root.outerHTML.should.equal('<root><c></c><d></d><f></f><a></a></root>'); }; exports.contains = function() { // see https://developer.mozilla.org/en-US/docs/Web/API/Node/contains var document = createWindow(html).document; var h1 = document.getElementById('lorem'); h1.contains(null).should.equal(false); h1.contains(h1).should.equal(true); h1.childNodes[0].contains(h1).should.equal(false); h1.contains(h1.childNodes[0]).should.equal(true); document.body.contains(h1).should.equal(true); var nodeList = document.querySelectorAll('p'); h1.contains(nodeList[0]).should.equal(false); document.body.contains(nodeList[0]).should.equal(true); nodeList[0].contains(nodeList[1]).should.equal(false); nodeList[1].contains(nodeList[0]).should.equal(false); }; exports.parseImportant = function() { var html = '<p style="font-family:sans-serif; text-decoration:none !important">foo</p>'; var doc = domino.createDocument(html); var p = doc.querySelector('p'); p.style.fontFamily.should.equal('sans-serif'); p.style.textDecoration.should.equal('none'); }; exports.gh70 = function() { var document = domino.createDocument('<h1 class="hello">Hello world</h1>'); var h1 = document.querySelector('h1'); var classAttr = h1.attributes.item(0); classAttr.value.should.equal('hello'); classAttr.nodeValue.should.equal('hello'); classAttr.textContent.should.equal('hello'); classAttr.nodeValue = 'nodeValue'; classAttr.value.should.equal('nodeValue'); classAttr.nodeValue.should.equal('nodeValue'); classAttr.textContent.should.equal('nodeValue'); classAttr.textContent = 'textContent'; classAttr.value.should.equal('textContent'); classAttr.nodeValue.should.equal('textContent'); classAttr.textContent.should.equal('textContent'); classAttr.innerText.should.equal('textContent'); }; exports.innerText = function() { var document = domino.createDocument('<div>Hello <h1>world!</h1></div>'); var div = document.querySelector('div'); div.innerText.should.equal('Hello world!'); // getter div.innerText = 'Hi'; // setter div.textContent.should.equal('Hi'); div.innerText.should.equal('Hi'); }; exports.gh71 = function() { var document = domino.createDocument('<h1 style="color:red !important">Hello world</h1>'); var h1 = document.querySelector('h1'); h1.style.display = 'none'; h1.outerHTML.should.equal('<h1 style="color: red !important; display: none;">Hello world</h1>'); }; exports.gh72 = function() { var window = createWindow('<h1>Hello, world!</h1>'); window.setTimeout.should.have.type('function'); window.clearTimeout.should.have.type('function'); window.setInterval.should.have.type('function'); window.clearInterval.should.have.type('function'); }; exports.navigatorID = function() { var window = createWindow('<h1>Hello, world!</h1>'); window.navigator.appCodeName.should.equal("Mozilla"); window.navigator.taintEnabled().should.equal(false); }; exports.template1 = function() { var document = domino.createDocument("<table><tbody><template id=row><tr><td><td>"); document.body.innerHTML.should.equal('<table><tbody><template id="row"><tr><td></td><td></td></tr></template></tbody></table>'); var t = document.getElementById("row"); t.should.be.an.instanceof(domino.impl.HTMLTemplateElement); t.childNodes.length.should.equal(0); t.content.should.be.an.instanceof(domino.impl.DocumentFragment); t.content.serialize().should.equal("<tr><td></td><td></td></tr>"); document.querySelectorAll("td").length.should.equal(0); t.content.querySelectorAll("td").length.should.equal(2); t.content.ownerDocument.should.not.equal(document); t.content.querySelectorAll("*").map(function(el) { el.ownerDocument.should.equal(t.content.ownerDocument); }); }; exports.template2 = function() { // Templates go in <head> by default. var document = domino.createDocument("<template>x<!--hi"); document.head.childNodes.length.should.equal(1); document.head.children[0].tagName.should.equal("TEMPLATE"); var df = document.head.children[0].content; df.should.be.an.instanceof(domino.impl.DocumentFragment); df.ownerDocument.should.not.equal(document); df.childNodes.length.should.equal(2); df.childNodes[0].ownerDocument.should.equal(df.ownerDocument); df.childNodes[1].ownerDocument.should.equal(df.ownerDocument); document.head.innerHTML.should.equal('<template>x<!--hi--></template>'); }; // HTMLTemplateElement.innerHTML // see https://github.com/w3c/DOM-Parsing/issues/1 exports.template3 = function() { var document = domino.createDocument(); var t = document.createElement("template"); t.should.be.an.instanceof(domino.impl.HTMLTemplateElement); t.childNodes.length.should.equal(0); t.content.should.be.an.instanceof(domino.impl.DocumentFragment); // This is the key line: t.innerHTML = '<div>abc</div><p>def</p>'; t.innerHTML.should.equal('<div>abc</div><p>def</p>'); t.content.ownerDocument.should.not.equal(document); t.content.childNodes.length.should.equal(2); t.content.querySelectorAll("*").map(function(el) { el.ownerDocument.should.equal(t.content.ownerDocument); }); // Non-standard (gh #73) t.content.outerHTML.should.equal('<div>abc</div><p>def</p>'); }; exports.fosterParent1 = function() { var document = domino.createDocument('<table><tr>x<a>foo<td>y'); // In this case the "x<a>" gets foster-parented *before* the <tr> document.body.innerHTML.should.equal("x<a>foo</a><table><tbody><tr><td>y</td></tr></tbody></table>"); }; exports.fosterParent2 = function() { var document = domino.createDocument('foster test'); var thead = document.createElement("thead"); // exercise the "no table in open element stack" case in foster parenting // algorithm. thead.innerHTML = "<tr>x<a>foo<td>y"; // Note how "x" got placed *after* the <tr> in this case. thead.outerHTML.should.equal('<thead><tr><td>y</td></tr>x<a>foo</a></thead>'); }; exports.fosterParent3 = function() { var document = domino.createDocument('<body><template><table><tr>x</template><table><template><tr>y'); document.body.innerHTML.should.equal("<template>x<table><tbody><tr></tr></tbody></table></template><table><template><tr></tr>y</template></table>"); var templates = document.querySelectorAll("template"); templates.length.should.equal(2); document.querySelectorAll("tr").length.should.equal(0); Array.prototype.forEach.call(templates, function(t) { t.ownerDocument.should.equal(document); t.content.ownerDocument.should.not.equal(document); t.content.querySelectorAll("*").map(function(el) { el.ownerDocument.should.equal(t.content.ownerDocument); }); }); templates[0].content.ownerDocument.should.equal( templates[1].content.ownerDocument ); }; exports.canvasTag = function() { var document = domino.createDocument('<canvas width=23 height=45>'); var canvas = document.querySelector('canvas'); canvas.should.be.instanceof(domino.impl.HTMLElement); canvas.should.be.instanceof(domino.impl.HTMLCanvasElement); canvas.should.not.be.instanceof(domino.impl.HTMLUnknownElement); canvas.width.should.equal(23); canvas.height.should.equal(45); }; exports.dialogTag = function() { // <p> should be closed before <dialog> var document = domino.createDocument('<div><p>x<dialog open returnvalue="foo">y'); document.body.innerHTML.should.equal('<div><p>x</p><dialog open="" returnvalue="foo">y</dialog></div>'); var dialog = document.querySelector('dialog'); dialog.should.be.instanceof(domino.impl.HTMLElement); dialog.should.be.instanceof(domino.impl.HTMLDialogElement); dialog.should.not.be.instanceof(domino.impl.HTMLUnknownElement); dialog.open.should.equal(true); dialog.returnValue.should.equal("foo"); }; exports.mainTag = function() { // <p> should be closed before <main> var document = domino.createDocument('<div><p>x<main>y'); document.body.innerHTML.should.equal('<div><p>x</p><main>y</main></div>'); var main = document.querySelector('main'); main.should.be.instanceof(domino.impl.HTMLElement); main.should.not.be.instanceof(domino.impl.HTMLUnknownElement); }; exports.menuItemTag = function() { // <menuitem> should be special var document = domino.createDocument('<menuitem type="checkbox" checked default>'); document.body.innerHTML.should.equal('<menuitem type="checkbox" checked="" default=""></menuitem>'); var menuitem = document.querySelector('menuitem'); menuitem.should.be.instanceof(domino.impl.HTMLElement); menuitem.should.be.instanceof(domino.impl.HTMLMenuItemElement); menuitem.should.not.be.instanceof(domino.impl.HTMLUnknownElement); menuitem.type.should.equal("checkbox"); menuitem.checked.should.equal(true); menuitem.disabled.should.equal(false); menuitem.default.should.equal(true); }; exports.rubyTags = function() { var document = domino.createDocument( '<p><ruby id=a>base<rt>annotation</ruby>' + '<ruby id=b><rb>上<rb>手<rt>じよう<rt>ず<rtc><rt>jou<rt>zu' ); document.body.innerHTML.should.equal( '<p><ruby id="a">base<rt>annotation</rt></ruby>' + '<ruby id="b"><rb>上</rb><rb>手</rb><rt>じよう</rt><rt>ず</rt><rtc><rt>jou</rt><rt>zu</rt></rtc></ruby></p>' ); Array.prototype.forEach.call( document.querySelectorAll('ruby,rb,rp,rt,rtc'), function(el) { el.should.be.instanceof(domino.impl.HTMLElement); el.should.not.be.instanceof(domino.impl.HTMLUnknownElement); }); }; exports.sourceTag = function() { var document = domino.createDocument('<base href=http://example.com><video controls><source src="foo.webm" type="video/webm">Sorry, no HTML5 video.'); document.body.innerHTML.should.equal('<video controls=""><source src="foo.webm" type="video/webm">Sorry, no HTML5 video.</video>'); var source = document.querySelector('source'); source.should.be.instanceof(domino.impl.HTMLElement); source.should.be.instanceof(domino.impl.HTMLSourceElement); source.should.not.be.instanceof(domino.impl.HTMLUnknownElement); source.src.should.equal("http://example.com/foo.webm"); source.type.should.equal("video/webm"); }; exports.trackTag = function() { var document = domino.createDocument('<base href=http://example.com><video poster="foo.jpg"><source src="foo.webm" type="video/webm"><track kind="captions" src="en-captions.vtt" srclang="en">'); document.body.innerHTML.should.equal('<video poster="foo.jpg"><source src="foo.webm" type="video/webm"><track kind="captions" src="en-captions.vtt" srclang="en"></video>'); var track = document.querySelector('track'); track.should.be.instanceof(domino.impl.HTMLElement); track.should.be.instanceof(domino.impl.HTMLTrackElement); track.should.not.be.instanceof(domino.impl.HTMLUnknownElement); track.kind.should.equal("captions"); track.src.should.equal("http://example.com/en-captions.vtt"); track.srclang.should.equal("en"); }; exports.elementInterface = function() { [ "acronym", "basefont", "big", "center", "nobr", "noembed", "noframes", "plaintext", "rb", "rtc", "strike", "tt" ].forEach(function(tag) { var document = domino.createDocument('<'+tag+'>'); var elt = document.querySelector(tag); elt.should.be.instanceof(domino.impl.HTMLElement); elt.should.not.be.instanceof(domino.impl.HTMLUnknownElement); }); [ "listing", "xmp", "pre" ].forEach(function(tag) { var document = domino.createDocument('<'+tag+'>'); var elt = document.querySelector(tag); elt.should.be.instanceof(domino.impl.HTMLElement); elt.should.be.instanceof(domino.impl.HTMLPreElement); elt.should.not.be.instanceof(domino.impl.HTMLUnknownElement); }); }; exports.gh79 = function() { // CSS identifiers can only contain the characters [a-zA-Z0-9] and // ISO 10646 characters U+00A0 and higher, plus the hyphen (-) and the // underscore (_). But they *can* have escaped characters. var doc = '<div id="cite_note-13.3F_It_Can\'t_Be!-3"></div>'; var document = domino.createDocument(doc); document.body.innerHTML.should.equal(doc); (function() { document.querySelector("#cite_note-13.3F_It_Can't_Be!-3"); }).should.throw({ name: 'SyntaxError' }); var div = document.querySelectorAll("#cite_note-13\\.3F_It_Can\\'t_Be\\!-3"); div.length.should.equal(1); }; exports.small_list = function() { var doc = '<ul><li><small class=foo>x</li><li>y<ul><li>b</li><li>c</small></li></ul></li></ul>'; var document = domino.createDocument(doc); var smalls = document.querySelectorAll('small'); smalls.length.should.equal(4); for (var i=0; i<smalls.length; i++) { smalls[i].classList.contains('foo').should.be.true(); } }; exports.menuitem = function() { var doc = '<menuitem id=a label=" b "><menuitem id=c> d <b> e '; var document = domino.createDocument(doc); document.body.innerHTML.should.equal('<menuitem id="a" label=" b "></menuitem><menuitem id="c"> d <b> e </b></menuitem>'); var itema = document.getElementById('a'); (itema != null).should.be.true(); // jshint ignore:line itema.should.be.an.instanceof(domino.impl.HTMLMenuItemElement); itema.label.should.equal(' b '); itema.label = ' x '; itema.label.should.equal(' x '); itema.hasAttribute('label').should.be.true(); itema.getAttribute('label').should.equal(' x '); itema.outerHTML.should.equal('<menuitem id="a" label=" x "></menuitem>'); var itemb = document.getElementById('c'); (itemb != null).should.be.true(); // jshint ignore:line itemb.should.be.an.instanceof(domino.impl.HTMLMenuItemElement); itemb.label.should.equal('d e'); itemb.label = ' y '; itemb.label.should.equal(' y '); itemb.hasAttribute('label').should.be.true(); itemb.getAttribute('label').should.equal(' y '); itemb.outerHTML.should.equal('<menuitem id="c" label=" y "> d <b> e </b></menuitem>'); }; exports.createSvgElements = function() { var document = domino.createDocument(); var svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); document.body.appendChild(svg); svg.should.be.instanceOf(domino.impl.SVGSVGElement); document.body.innerHTML.should.equal("<svg></svg>"); }; exports.gh95 = function() { var document = domino.createDocument( '<body><a href="foo\'s">bar</a></body>' ); (function() { document.querySelectorAll("a[href=foo's]"); }).should.throw({ name: 'SyntaxError' }); document.querySelectorAll("a[href=foo\\'s]").length.should.equal(1); }; exports.propertyWritability = function () { // gh #89 var window = createWindow(''); var document = domino.createDocument(); var assertWritable = function(object, property) { var replacement = function () { }; object[property] = replacement; object[property].should.equal(replacement, property + " should be writable"); }; assertWritable(window, 'HTMLElement'); assertWritable(document, 'importNode'); assertWritable(document, 'createElement'); assertWritable(document, 'createElementNS'); }; exports.gh90 = function() { var doc = '<input type="checkbox">'; var document = domino.createDocument(doc); document.body.innerHTML.should.equal(doc); var input = document.querySelector('input'); input.checked.should.equal(false); input.checked = true; input.checked.should.equal(true); input.outerHTML.should.equal('<input type="checkbox" checked="">'); input.checked = false; input.checked.should.equal(false); input.outerHTML.should.equal(doc); // Now test again, using hasAttribute/hasAttributeNS directly. input.hasAttribute('checked').should.equal(false); input.hasAttributeNS(null, 'checked').should.equal(false); input.hasAttributeNS('foo', 'checked').should.equal(false); input.setAttribute('checked', 'bar'); input.setAttributeNS('foo', 'checked', 'bat'); input.hasAttribute('checked').should.equal(true); input.hasAttributeNS(null, 'checked').should.equal(true); input.hasAttributeNS('foo', 'checked').should.equal(true); input.removeAttribute('checked'); input.removeAttributeNS('foo', 'checked'); input.hasAttribute('checked').should.equal(false); input.hasAttributeNS(null, 'checked').should.equal(false); input.hasAttributeNS('foo', 'checked').should.equal(false); }; exports.gh98 = function() { var doc = '<a href="/"></a>'; var document = domino.createDocument(doc); var a = document.querySelector('a'); (a.style.getPropertyValue('background') === '').should.be.true(); }; exports.gh99 = function() { // test '#foo' optimization in querySelectorAll var window = createWindow( '<!DOCTYPE html><html><body></body></html>' ); var doc = window.document; var match = doc.querySelectorAll('#coordinates'); match.length.should.equal(0); if (Array.from) { Array.from(match).length.should.equal(0); } (match[0] === undefined).should.be.true(); // continue test, now w/ multiple elements sharing same id. doc.body.innerHTML = '<p id=a>x</p><p id=a>y</p>'; match = doc.querySelectorAll('#a'); match.length.should.equal(2); if (Array.from) { Array.from(match).length.should.equal(2); } match[0].textContent.should.equal('x'); match[1].textContent.should.equal('y'); }; exports.gh112 = function() { // attributes named 'xmlns' are fine. (gh #112) var window = createWindow( '<!DOCTYPE html><html xmlns="http://www.w3.org/1999/xhtml"><b></b></html>' ); var document = window.document; document.documentElement.setAttribute('xmlns', 'test'); var b = document.querySelector('b'); b.innerHTML = '<test></test>'; var test = document.querySelector('test'); // Note that this seems contrary to what is implied by // https://lists.w3.org/Archives/Public/www-dom/2011JulSep/0153.html // but matches what modern browsers do. b.namespaceURI.should.equal('http://www.w3.org/1999/xhtml'); test.namespaceURI.should.equal('http://www.w3.org/1999/xhtml'); }; exports.gh107 = function() { var document = domino.createDocument(); document.scrollingElement.should.equal(document.documentElement); }; exports.gh109 = function() { var document = domino.createDocument(); var div = document.createElement('div'); div.classList.add('one', 'two'); div.classList.length.should.equal(2); div.classList.contains('one').should.be.true(); div.classList.contains('two').should.be.true(); div.classList.remove('one', 'two'); div.classList.length.should.equal(0); div.classList.contains('one').should.be.false(); div.classList.contains('two').should.be.false(); }; exports.gh111 = function() { var document = domino.createDocument(); var div = document.createElement('div'); div.classList.toggle('a'); div.classList.toggle('b', true); div.classList.length.should.equal(2); div.classList.value.should.equal('a b'); div.classList.toggle('a'); div.classList.toggle('b', true); div.classList.length.should.equal(1); div.classList.value.should.equal('b'); div.classList.toggle('b', false); div.classList.length.should.equal(0); div.classList.value.should.equal(''); div.classList.value = ' foo\rbat \n baz a\nb\f\t'; div.classList.value.should.equal(' foo\rbat \n baz a\nb\f\t'); div.classList.length.should.equal(5); div.classList.contains('').should.be.false(); div.classList.contains(' ').should.be.false(); div.classList.contains('\n').should.be.false(); div.classList.contains('\f').should.be.false(); div.classList[0].should.equal('foo'); div.classList[1].should.equal('bat'); div.classList.item(2).should.equal('baz'); div.classList.contains('a').should.be.true(); div.classList.contains('b').should.be.true(); div.classList.replace('bat', 'ball'); div.classList.value.should.equal('foo ball baz a b'); div.classList.length.should.equal(5); div.classList.contains('ball').should.be.true(); div.classList[1].should.equal('ball'); }; exports.gh126 = function() { var document = domino.createDocument(); var div = document.createElement('div'); div.classList.length.should.equal(0); div.classList.add(); div.classList.length.should.equal(0); div.classList.add(undefined); div.classList.add(null); div.classList.length.should.equal(2); div.classList.remove(); div.classList.length.should.equal(2); div.classList.contains(undefined).should.be.true(); div.classList.contains('undefined').should.be.true(); div.classList.contains(null).should.be.true(); div.classList.contains('null').should.be.true(); div.classList.remove(undefined, null); div.classList.length.should.equal(0); div.classList.contains(undefined).should.be.false(); div.classList.contains('undefined').should.be.false(); div.classList.contains(null).should.be.false(); div.classList.contains('null').should.be.false(); }; exports.arrayfrom = function() { // If your version of node supports Array.from, it should support // Array.from(node.attributes) ... even though we don't use proxies. if (typeof(Array.from) !== 'function') { return; } var d = domino.createDocument(''); var e = d.createElement('span'); e.setAttribute('a','1'); e.setAttribute('b','2'); var a = Array.from(e.attributes); a.should.have.length(2); a[0].should.have.property('name','a'); a[0].should.have.property('value','1'); a[1].should.have.property('name','b'); a[1].should.have.property('value','2'); }; exports.gh119 = function() { var document = domino.createDocument('<div></div>'); var div = document.querySelector('div'); div.style.flex = '1 1 0px'; div.outerHTML.should.equal('<div style="flex: 1 1 0px;"></div>'); document = domino.createDocument('<div></div>'); div = document.querySelector('div'); div.style.flexFlow = 'row wrap'; div.outerHTML.should.equal('<div style="flex-flow: row wrap;"></div>'); document = domino.createDocument('<div></div>'); div = document.querySelector('div'); div.style.flexBasis = '0px'; div.style.flexGrow = 1; div.style.flexShrink = 1; div.style.flexDirection = 'column'; div.style.flexWrap = 'wrap'; div.outerHTML.should.equal('<div style="flex-basis: 0px; flex-grow: 1; flex-shrink: 1; flex-direction: column; flex-wrap: wrap;"></div>'); }; exports.setPropertyClipBox = function() { var document = domino.createDocument('<div></div>'); var div = document.querySelector('div'); div.style['clip-path'] = 'margin-box'; div.outerHTML.should.equal('<div style="clip-path: margin-box;"></div>'); }; exports.setPropertyNull = function() { var document = domino.createDocument('<div style="color:red;"></div>'); var div = document.querySelector('div'); div.style.setProperty('--bg-color', null); div.outerHTML.should.equal('<div style="color:red;"></div>'); var document = domino.createDocument('<div style="color:green;"></div>'); var div = document.querySelector('div'); div.style.color = null; div.outerHTML.should.equal('<div style=""></div>'); }; exports.gh121 = function() { var document = domino.createDocument('<div></div>'); var div = document.querySelector('div'); div.className = 'ab a'; div.matches('.a').should.be.true(); div.matches('[class~=a]').should.be.true(); div.className = 'a ab'; div.matches('.a').should.be.true(); div.matches('[class~=a]').should.be.true(); }; exports.gh127 = function() { var document = domino.createDocument('<a href="#foo"></a><a href="http://#foo"></a>'); var aEls = document.querySelectorAll('a'); aEls.length.should.equal(2); for (var i=0; i < aEls.length; i++) { aEls[i].hash.should.equal('#foo'); } }; exports.gh128 = function() { var document = domino.createDocument('<a target="xInSeNsItIvEx"></a><a target="xYx"></a>'); var expected = document.querySelectorAll('a')[0]; [ { sel: '[target*="insensitive" i]' }, { sel: "[target='XinsensitiveX'i]" }, { sel: '[target=XINSENSITIVEX i]' }, { sel: '[target=XINSENSITIVEXi]', expectFail: true }, { sel: '[target^=XI i]' }, { sel: '[target^=XIi]', expectFail: true }, { sel: '[target^=XI]', expectFail: true }, { sel: '[target$=EX i]' }, { sel: '[target$="ex"i]' }, { sel: '[target~="xinsensitivex"i]' }, { sel: '[target|="xinsensitivex" i]' }, ].forEach(function(oneCase) { var a = document.querySelectorAll('*'+oneCase.sel); if (oneCase.expectFail) { a.length.should.equal(0); } else { a.length.should.equal(1); a[0].should.be.exactly(expected); } }); }; exports.gh129 = function() { var window = createWindow(); var document = window.document; var div = document.body.appendChild(document.createElement('div')); div.innerHTML = '<p></p><span></span>'; var span = div.firstChild.nextSibling; div.textContent = ''; (span.parentNode === null).should.equal(true); }; exports.gh135 = function() { var document = domino.createDocument('<a target="foobar"></a>'); var a = document.querySelectorAll('*[target$="aaaaaaa"]'); a.length.should.equal(0); }; exports.gh136 = function() { var document = domino.createDocument('<option> \f\tabc\n'); var o = document.querySelector('option'); o.value.should.equal('abc'); o.text.should.equal('abc'); o.textContent.should.equal(' \f\tabc\n'); o.value = ' def '; o.value.should.equal(' def '); o.text.should.equal('abc'); o.text = ' ghi '; o.text.should.equal('ghi'); o.textContent.should.equal(' ghi '); o.outerHTML.should.equal("<option value=\" def \"> ghi </option>"); }; // Examples from // https://developer.mozilla.org/en-US/docs/Web/API/Element/outerHTML exports.outerHTML1 = function() { var document = domino.createDocument( '<div id="d"><p>Content</p><p>Further Elaborated</p></div>' ); var d = document.getElementById('d'); d.outerHTML.should.equal( '<div id="d"><p>Content</p><p>Further Elaborated</p></div>' ); }; exports.outerHTML2 = function() { var document = domino.createDocument( '<div id="container"><div id="d">This is a div.</div></div>' ); var container = document.getElementById('container'); var d = document.getElementById('d'); container.firstChild.nodeName.should.equal('DIV'); d.outerHTML = "<p>This paragraph replaced the original div.</p>"; container.firstChild.nodeName.should.equal('P'); (d.parentNode === null).should.be.true(); }; exports.outerHTML3 = function() { var document = domino.createDocument(); var div = document.createElement("div"); div.outerHTML = "<div class=\"test\">test</div>"; // "Many browsers will also throw an exception" div.outerHTML.should.equal('<div></div>'); }; exports.outerHTML4 = function() { var document = domino.createDocument("<p>hello there</p>"); var p = document.getElementsByTagName("p")[0]; p.nodeName.should.equal("P"); p.outerHTML = "<div>This div replaced a paragraph.</div>"; p.nodeName.should.equal("P"); (p.parentNode === null).should.be.true(); }; // Test case taken from // https://developer.mozilla.org/en-US/docs/Web/API/Text/wholeText exports.wholeText = function() { var document = domino.createDocument( '<p>Thru-hiking is great! <strong>No insipid election coverage!</strong>' + ' However, <a href="http://en.wikipedia.org/wiki/Absentee_ballot">casting' + ' a ballot</a> is tricky.</p>' ); var para = document.querySelector('p'); para.removeChild(para.childNodes[1]); para.firstChild.wholeText.should.equal("Thru-hiking is great! However, "); }; exports.documentClone = function() { var document = domino.createDocument('throw away'); var html = document.implementation.createHTMLDocument('my title'); html.body.innerHTML = '<p>Inside <b>body'; var copy = html.cloneNode(true); // deep copy! (html === copy).should.be.false(); html.nodeType.should.equal(copy.nodeType); html.nodeName.should.equal(copy.nodeName); html.title.should.equal(copy.title); html.body.outerHTML.should.equal(copy.body.outerHTML); copy.head.ownerDocument.should.equal(copy); copy.body.ownerDocument.should.equal(copy); }; exports.insertForeignElement = function() { // Sanitization test case from DOMPurify // Verifies that prefixes are handled correctly during the // "insert a foreign element" step in HTML parsing. // https://html.spec.whatwg.org/multipage/parsing.html#insert-a-foreign-element var document = domino.createDocument( '<svg xmlns="http://www.w3.org/2000/svg">'+ '<xsl:stylesheet id="stylesheet" version="1.0" ' + 'xmlns:xsl="http://www.w3.org/1999/XSL/Transform">' + '<xsl:template match="/">' + '<iframe xmlns="http://www.w3.org/1999/xhtml" src="javascript:alert(125)">' ); var el = document.querySelector('*[match]'); el.namespaceURI.should.equal('http://www.w3.org/2000/svg'); el.localName.should.equal('xsl:template'); // Not 'template' ! el.outerHTML.should.equal( '<xsl:template match="/">' + '<iframe xmlns="http://www.w3.org/1999/xhtml" src="javascript:alert(125)">'+ '</iframe></xsl:template>' ); }; exports.bodyNL = function() { var document = domino.createDocument( '<body></body>\n' ); // This may be surprising, but it's what the spec requires. document.body.textContent.should.equal('\n'); }; exports.testIncompleteTag = function() { var document = domino.createDocument('<p>hello<'); document.body.outerHTML.should.equal('<body><p>hello<</p></body>'); }; exports.incrementalHTMLParser1 = function() { // Verify correct operation of incremental parser, fed chunks which split // up tokens. var incrParser = domino.createIncrementalHTMLParser(); var neverPause = function() { return false; }; incrParser.write('<p>hello<'); incrParser.process(neverPause); incrParser.write('b>foo&am'); incrParser.process(neverPause); incrParser.write('p;<'); incrParser.process(neverPause); incrParser.write('/p>'); incrParser.process(neverPause); incrParser.end(); // omitting the pauseFunc is equivalent to passing neverPause incrParser.process().should.equal(/*no more to do*/false); incrParser.document().outerHTML.should.equal( '<html><head></head><body><p>hello<b>foo&</b></p></body></html>' ); }; exports.incrementalHTMLParser2 = function() { // Verify correct operation of incremental parser when we only manage to // scan one token at each step. var justOneStep = function() { var counter = 1; return function() { return (counter--) <= 0; }; }; var incrParser = domino.createIncrementalHTMLParser(); incrParser.write('<p>hello<'); incrParser.write('b>foo&am'); incrParser.write('p;<'); incrParser.write('/p>'); incrParser.end(); [ '<html><head></head><body><p></p></body></html>', '<html><head></head><body><p>hello</p></body></html>', '<html><head></head><body><p>hello<b></b></p></body></html>', '<html><head></head><body><p>hello<b>foo</b></p></body></html>', '<html><head></head><body><p>hello<b>foo</b></p></body></html>', '<html><head></head><body><p>hello<b>foo</b></p></body></html>', '<html><head></head><body><p>hello<b>foo</b></p></body></html>', '<html><head></head><body><p>hello<b>foo</b></p></body></html>', '<html><head></head><body><p>hello<b>foo&</b></p></body></html>', ].forEach(function(step) { incrParser.process(justOneStep()).should.equal(/*more to do!*/true); incrParser.document().outerHTML.should.equal(step); }); incrParser.process(justOneStep()).should.equal(/*no more to do*/false); incrParser.document().outerHTML.should.equal( '<html><head></head><body><p>hello<b>foo&</b></p></body></html>' ); }; exports.shouldNotEmptyStyle = function() { var document = domino.createDocument('<div></div>'); var div = document.querySelector('div'); div.style.flex = ' '; div.outerHTML.should.equal('<div></div>'); }; exports.supportModernStyle = function() { var document = domino.createDocument('<h1 style="color:red !important">Hello world</h1>'); var h1 = document.querySelector('h1'); h1.style.inset = '0px'; h1.outerHTML.should.equal('<h1 style="color: red !important; inset: 0px;">Hello world</h1>'); }; exports.supportsNonceAttribute = function() { var document = domino.createDocument('<style>* {color: red}</style>'); var h1 = document.querySelector('style'); h1.nonce = 'randomhaash'; h1.outerHTML.should.equal('<style nonce="randomhaash">* {color: red}</style>'); }; exports.supportsHtmlElementsInNoScriptTag = function() { const document = domino.createDocument('<body><noscript>For information <em>click</em> here.</noscript></body>'); const noscript = document.querySelector('noscript'); noscript.outerHTML.should.equal('<noscript>For information <em>click</em> here.</noscript>'); };