UNPKG

41.4 kBJavaScriptView Raw
1'use strict';
2var domino = require('../lib');
3var fs = require('fs');
4var html = fs.readFileSync(__dirname + '/fixture/doc.html', 'utf8');
5
6exports = exports.domino = {};
7
8exports.matches = function() {
9 // see https://developer.mozilla.org/en-US/docs/Web/API/Element.matches
10 var d = domino.createWindow(html).document;
11 var h1 = d.getElementById('lorem');
12 h1.matches('h1').should.equal(true);
13 h1.matches('body > h1').should.equal(true); // not rooted
14 h1.matches('h1 > p').should.equal(false);
15 h1.matches('h1,h2').should.equal(true);
16 h1.matches('h2,h1').should.equal(true);
17};
18
19exports.querySelectorAll = function() {
20 var window = domino.createWindow(html);
21 var d = window.document;
22 var nodeList = d.querySelectorAll('p');
23 nodeList.should.have.property('item');
24 nodeList.should.have.length(2);
25 nodeList = d.querySelectorAll('p:not(.foo)');
26 nodeList.should.have.length(1);
27 nodeList = d.querySelectorAll('tt.foo');
28 nodeList.should.have.length(2);
29 nodeList = d.querySelectorAll('tt:not(.bar)');
30 nodeList.should.have.length(1);
31};
32
33exports.qsaOrder = function() {
34 var window = domino.createDocument('<h2></h2><h3></h3><h3></h3><h2></h2><h3></h3>');
35 window.querySelectorAll('h2, h3').map(function(el) {
36 return el.tagName;
37 })
38 .should.eql(['H2', 'H3', 'H3', 'H2', 'H3']);
39};
40
41exports.orphanQSA = function() {
42 var document = domino.createDocument('<h1>foo</h1>');
43 var p = document.createElement('p');
44 p.querySelectorAll('p').should.have.length(0);
45 p.querySelectorAll('p').should.have.length(0);
46};
47
48exports.gh20 = function() {
49 var window = domino.createWindow('');
50 var frag = window.document.createDocumentFragment();
51 frag.querySelectorAll('p').should.have.length(0);
52
53 frag.appendChild(window.document.createElement('p'));
54 frag.querySelectorAll('p').should.have.length(1);
55
56 frag.appendChild(window.document.createElement('p'));
57 frag.querySelectorAll('p').should.have.length(2);
58};
59
60exports.gh22 = function() {
61 var d=domino.createDocument("<div><h1>Hello world</h1><p>Hi</p></div>");
62 d.querySelectorAll('div').should.have.length(1);
63 d.body.querySelectorAll('div').should.have.length(1);
64 d.body.querySelectorAll('h1').should.have.length(1);
65 d.body.querySelectorAll('p').should.have.length(1);
66
67 var w=domino.createWindow("<div><h1>Hello world</h1><p>Hi</p></div>");
68 d=w.document;
69 d.querySelectorAll('div').should.have.length(1);
70 d.body.querySelectorAll('div').should.have.length(1);
71 d.body.querySelectorAll('h1').should.have.length(1);
72 d.body.querySelectorAll('p').should.have.length(1);
73};
74
75exports.gh31 = function() {
76 var document, heading1, heading2;
77
78 document = domino.createDocument("<h1>First</h1><h1>Second</h1>");
79 document.querySelectorAll('h1').should.have.length(2);
80 heading1 = document.body.querySelector('h1');
81 heading1.getElementsByTagName('h1').should.have.length(0);
82 heading1.querySelectorAll('h1').should.have.length(0);
83 heading2 = document.body.querySelector('h1 + h1');
84 heading2.querySelectorAll('h1').should.have.length(0);
85};
86
87exports.gh38 = function() {
88 var d = domino.createDocument('<table><tr><th>Header cell</th><td>Data cell</td></tr></table>');
89 var r = d.querySelector('tr');
90 r.should.have.property('cells');
91 r.cells.should.have.length(2);
92};
93
94exports.evilHandler = function() {
95 var window = domino.createDocument('<a id="a" onclick="alert(\'breakin&#39;-stuff\')">');
96 window = window; // avoid defined-but-not-used lint error
97};
98
99exports.title = function() {
100 var d = domino.createDocument(html);
101 if (d.head) { d.documentElement.removeChild(d.head); }
102 d.should.have.property('head', null);
103 d.should.have.property('title', '');
104 d.querySelectorAll('head > title').should.have.length(0);
105
106 // per the spec, if there is no <head>, then setting Document.title should
107 // be a no-op.
108 d.title = "Lorem!";
109 d.title.should.equal('');
110 d.querySelectorAll('head > title').should.have.length(0);
111
112 // but if there is a <head>, then setting Document.title should create the
113 // <title> element if necessary.
114 d.documentElement.insertBefore(d.createElement('head'), d.body);
115 (d.head === null).should.be.false();
116 d.title.should.equal('');
117 d.title = "Lorem!";
118 d.title.should.equal("Lorem!");
119 d.querySelectorAll('head > title').should.have.length(1);
120
121 // verify that setting <title> works if there's already a title
122 d.title = "ipsum";
123 d.title.should.equal("ipsum");
124 d.querySelectorAll('head > title').should.have.length(1); // still only 1!
125};
126
127exports.children = function() {
128 var d = domino.createDocument(html);
129 var c = d.body.children;
130 c.should.have.length(4);
131 c.should.have.property('0');
132 var a = Array.prototype.slice.call(c);
133 a.should.be.an.instanceof(Array);
134 a.should.have.length(4);
135 d.body.appendChild(d.createElement('p'));
136 a = Array.prototype.slice.call(c);
137 a.should.have.length(5);
138};
139
140
141exports.attributes1 = function() {
142 var d = domino.createDocument();
143 var el = d.createElement('div');
144 el.setAttribute('foo', 'foo');
145 el.setAttribute('bar', 'bar');
146 el.attributes.should.have.length(2);
147 el.attributes.item(0).value.should.equal('foo');
148 el.removeAttribute('foo');
149 el.attributes.should.have.length(1);
150 el.attributes.item(0).name.should.equal('bar');
151 el.setAttribute('baz', 'baz');
152 el.attributes.should.have.length(2);
153 el.attributes.item(1).value.should.equal('baz');
154};
155
156exports.classList = function() {
157 var d = domino.createDocument();
158 var el = d.body;
159 el.className = 'foo bar boo';
160
161 var cl = el.classList;
162 cl.should.have.length(3);
163 cl[0].should.equal('foo');
164 cl.contains('bar').should.be.ok();
165 cl.contains('baz').should.not.be.ok();
166 cl.add('baz');
167 cl.contains('baz').should.be.ok();
168 cl.should.have.length(4);
169 el.className.should.match(/baz/);
170 cl.remove('foo');
171 cl.should.have.length(3);
172 el.className.should.not.match(/foo/);
173 cl[0].should.not.equal('foo');
174};
175
176exports.attributes2 = function() {
177 var d = domino.createDocument();
178 var div = d.createElement('div');
179 div.setAttribute('onclick', 't');
180 div.attributes.should.have.property('onclick');
181 div.attributes.onclick.should.have.property('value', 't');
182 div.removeAttribute('onclick');
183 (div.attributes.onclick === undefined).should.be.true();
184};
185
186exports.jquery1_9 = function() {
187 var window = domino.createWindow(html);
188 var f = __dirname + '/fixture/jquery-1.9.1.js';
189 window._run(fs.readFileSync(f, 'utf8'), f);
190 window.$.should.be.ok();
191 window.$('.foo').should.have.length(3);
192};
193
194exports.jquery2_2 = function() {
195 var window = domino.createWindow(html);
196 window.$ = require(__dirname + '/fixture/jquery-2.2.0.js')(window);
197 window.$.should.be.ok();
198 window.$('.foo').should.have.length(3);
199 window.$.ajaxTransport("test", function() {
200 return { send: function() {}, abort: function() {} };
201 });
202 window.$.ajax({ url: 'test://', dataType: "test", timeout: 1, async: true });
203};
204
205exports.treeWalker = function() {
206 var window = domino.createWindow(html);
207 var d = window.document;
208 var root = d.getElementById('tw');
209 var tw = d.createTreeWalker(root, window.NodeFilter.SHOW_TEXT, function(n) {
210 return (n.data === 'ignore') ?
211 window.NodeFilter.FILTER_REJECT : window.NodeFilter.FILTER_ACCEPT;
212 });
213 tw.root.should.equal(root);
214 tw.currentNode.should.equal(root);
215 tw.whatToShow.should.equal(0x4);
216 tw.filter.constructor.should.equal(window.NodeFilter.constructor);
217
218 var actual = [];
219 while (tw.nextNode() !== null) {
220 actual.push(tw.currentNode);
221 }
222
223 actual.length.should.equal(4);
224 actual.should.eql([
225 root.firstChild.firstChild,
226 root.firstChild.lastChild.firstChild,
227 root.lastChild.firstChild,
228 root.lastChild.lastChild.firstChild
229 ]);
230};
231
232exports.nodeIterator = function() {
233 var window = domino.createWindow(html);
234 var d = window.document;
235 var root = d.getElementById('tw');
236 var ni = d.createNodeIterator(root, window.NodeFilter.SHOW_TEXT, function(n) {
237 return (n.data === 'ignore') ?
238 window.NodeFilter.FILTER_REJECT : window.NodeFilter.FILTER_ACCEPT;
239 });
240 ni.root.should.equal(root);
241 ni.referenceNode.should.equal(root);
242 ni.whatToShow.should.equal(0x4);
243 ni.filter.constructor.should.equal(window.NodeFilter.constructor);
244
245 var actual = [];
246 for (var n = ni.nextNode(); n ; n = ni.nextNode()) {
247 actual.push(n);
248 }
249
250 actual.length.should.equal(4);
251 actual.should.eql([
252 root.firstChild.firstChild,
253 root.firstChild.lastChild.firstChild,
254 root.lastChild.firstChild,
255 root.lastChild.lastChild.firstChild
256 ]);
257};
258
259exports.innerHTML = function() {
260 var d = domino.createDocument();
261 ['pre','textarea','listing'].forEach(function(elementName) {
262 var div = d.createElement('div');
263 var el = d.createElement(elementName);
264 el.innerHTML = "a";
265 div.appendChild(el);
266 // no extraneous newline after element tag in this case
267 div.innerHTML.should.equal('<'+elementName+'>a</'+elementName+'>');
268 el.innerHTML = "\nb";
269 // Note that this doesn't roundtrip:
270 // see https://github.com/whatwg/html/issues/944
271 div.innerHTML.should.equal('<'+elementName+'>\nb</'+elementName+'>');
272 });
273};
274
275exports.outerHTML = function() {
276 var tests = [
277 // This doesn't round trip:
278 // see https://github.com/whatwg/html/issues/944
279 //'<body><pre>\n\na\n</pre></body>',
280 '<body bgcolor="white"><h1 style="color: red">\nOne\n2 &amp; 3</h1></body>',
281 '<body data-test="<>&amp;&quot;\'"></body>'
282 ];
283 tests.forEach(function(html) {
284 var d = domino.createDocument(html);
285 // Verify round-tripping.
286 d.body.outerHTML.should.equal(html);
287 });
288};
289
290exports.largeAttribute = function() {
291 var size = 400000;
292 // work around a performance regression in node 0.4.x - 0.6.x
293 if (/^v0\.[0-6]\./.test(process.version)) { size = 50000; }
294 var html = '<body><span data-large="';
295 for (var i=0; i<size; i++) {
296 html += '&amp;';
297 }
298 html += '"></span></body>';
299 // this should not crash with a stack overflow!
300 domino.createDocument(html);
301};
302
303exports.createTextNodeWithNonString = function() {
304 var document = domino.createDocument('<html></html>');
305 var tests = [
306 [false, 'false'],
307 [NaN, 'NaN'],
308 [123, '123'],
309 [{}, '[object Object]'],
310 [[], ''],
311 [null, 'null'],
312 [undefined, 'undefined'],
313 ];
314 for(var i=0; i<tests.length; i++) {
315 var element = document.createElement('div');
316 var textNode = document.createTextNode(tests[i][0]);
317 element.appendChild(textNode);
318 element.innerHTML.should.equal(tests[i][1]);
319 }
320};
321
322exports.adoption = function() {
323 // See https://github.com/fgnass/domino/pull/36
324 var html = "<b>X<b>Y</b>Z</b>";
325 var doc = domino.createDocument(html);
326 doc.body.innerHTML.should.equal(html);
327};
328
329exports.attributeSelector = function() {
330 var html = '<h1>foo</h1><h2 id="x" title="y" lang="en" dir="ltr" ' +
331 'accessKey="z" hidden tabIndex="2">bar</h2>';
332 var doc = domino.createDocument(html);
333 var h1 = doc.querySelector('h1');
334 h1.matches('*[id]').should.equal(false);
335 h1.matches('*[title]').should.equal(false);
336 h1.matches('*[lang]').should.equal(false);
337 h1.matches('*[dir]').should.equal(false);
338 h1.matches('*[accessKey]').should.equal(false);
339 h1.matches('*[hidden]').should.equal(false);
340 h1.matches('*[tabIndex]').should.equal(false);
341
342 var h2 = doc.querySelector('h2');
343 h2.matches('*[id]').should.equal(true);
344 h2.matches('*[title]').should.equal(true);
345 h2.matches('*[lang]').should.equal(true);
346 h2.matches('*[dir]').should.equal(true);
347 h2.matches('*[accessKey]').should.equal(true);
348 h2.matches('*[hidden]').should.equal(true);
349 h2.matches('*[tabIndex]').should.equal(true);
350
351 h1.matches('*[matches]').should.equal(false);
352 h1.matches('*[querySelector]').should.equal(false);
353
354 h1.matches('*[isHTML]').should.equal(false);
355};
356
357exports.crHandling = function() {
358 var html = '<div\rid=a data-test=1\rfoo="\r"\rbar=\'\r\'\rbat=\r>\r</div\r>';
359 var doc = domino.createDocument(html);
360 var div = doc.querySelector('#a');
361 (div != null).should.be.true(); // jshint ignore:line
362 // all \r should be converted to \n
363 div.outerHTML.should.equal('<div id="a" data-test="1" foo="\n" bar="\n" bat="">\n</div>');
364};
365
366exports.eqAttr = function() {
367 var html = "<div id=a ==x><a=B></A=b></div>";
368 var doc = domino.createDocument(html);
369 var div = doc.querySelector('#a');
370 (div != null).should.be.true(); // jshint ignore:line
371 div.attributes.length.should.equal(2);
372 div.attributes.item(1).name.should.equal('=');
373 div.children.length.should.equal(1);
374 div.children[0].tagName.should.equal('A=B');
375};
376
377exports.tagNameCase = function() {
378 // See https://github.com/fgnass/domino/pull/41
379 var impl = domino.createDOMImplementation();
380 var namespace = 'http://schemas.xmlsoap.org/soap/envelope/';
381 var qualifiedName = 'Envelope';
382 var doc = impl.createDocument(namespace, qualifiedName, null);
383 doc.documentElement.tagName.should.equal(qualifiedName);
384};
385
386exports.fastAttributes = function() {
387 // test the SIMPLETAG/SIMPLEATTR path in HTMLParser
388 var html = "<div id=a b=\"x &quot;y\" c='a \rb'><\np></div>";
389 var doc = domino.createDocument(html);
390 var div = doc.querySelector('#a');
391 (div != null).should.be.true(); // jshint ignore:line
392 div.attributes.length.should.equal(3);
393 div.attributes.item(1).value.should.equal('x "y');
394 div.attributes.item(2).value.should.equal('a \nb');
395 div.children.length.should.equal(0);
396};
397
398exports.anchorElement = function() {
399 var html = "<a href='http://user:pass@example.com:1234/foo/bar?bat#baz'>!</a>";
400 var doc = domino.createDocument(html);
401 var a = doc.querySelector('a');
402 (a != null).should.be.true(); // jshint ignore:line
403 a.href.should.equal('http://user:pass@example.com:1234/foo/bar?bat#baz');
404 a.protocol.should.equal('http:');
405 a.host.should.equal('example.com:1234');
406 a.hostname.should.equal('example.com');
407 a.port.should.equal('1234');
408 a.pathname.should.equal('/foo/bar');
409 a.search.should.equal('?bat');
410 a.hash.should.equal('#baz');
411 a.username.should.equal('user');
412 a.password.should.equal('pass');
413 a.origin.should.equal('http://example.com:1234');
414 // now try mutating!
415 a.protocol = 'https:';
416 a.href.should.equal('https://user:pass@example.com:1234/foo/bar?bat#baz');
417 a.hostname = 'other.net';
418 a.href.should.equal('https://user:pass@other.net:1234/foo/bar?bat#baz');
419 a.port = 5678;
420 a.href.should.equal('https://user:pass@other.net:5678/foo/bar?bat#baz');
421 a.pathname = '/blam/';
422 a.href.should.equal('https://user:pass@other.net:5678/blam/?bat#baz');
423 a.search = '?bat&banana';
424 a.href.should.equal('https://user:pass@other.net:5678/blam/?bat&banana#baz');
425 a.hash = '#oranges';
426 a.href.should.equal('https://user:pass@other.net:5678/blam/?bat&banana#oranges');
427 a.username = 'joe';
428 a.href.should.equal('https://joe:pass@other.net:5678/blam/?bat&banana#oranges');
429 a.password = 'smith';
430 a.href.should.equal('https://joe:smith@other.net:5678/blam/?bat&banana#oranges');
431};
432
433exports.gh59 = function() {
434 var html = '<html><body><span style="display:none">foo</span></body></html>';
435 var doc = domino.createDocument(html);
436 doc.querySelectorAll('span[style]').should.have.length(1);
437 doc.querySelectorAll('span[style="display:none"]').should.have.length(1);
438 doc.querySelectorAll('span[style*="display:none"]').should.have.length(1);
439};
440
441exports.duplicateID = function() {
442 var doc = domino.createDocument('<root></root>');
443 var root = doc.documentElement;
444
445 function makeElement(name) {
446 var elt = doc.createElement(name);
447 elt.setAttribute("id", "x");
448 return elt;
449 }
450
451 var a = root.appendChild(makeElement("a"));
452 var b = root.appendChild(makeElement("b"));
453 var c = root.appendChild(makeElement("c"));
454 var d = root.appendChild(makeElement("d"));
455 doc.getElementById("x").should.equal(a);
456 root.removeChild(a);
457 doc.getElementById("x").should.equal(b);
458 root.removeChild(c);
459 root.removeChild(b);
460 doc.getElementById("x").should.equal(d);
461 root.removeChild(d);
462 (doc.getElementById("x") === null).should.be.true();
463};
464
465exports.normalize = function() {
466 var doc = domino.createDocument('<span id="x"><!---->foo</span>');
467 var span = doc.getElementById("x");
468 span.appendChild(doc.createTextNode('bar'));
469 span.outerHTML.should.equal('<span id="x"><!---->foobar</span>');
470 span.normalize();
471 span.outerHTML.should.equal('<span id="x"><!---->foobar</span>');
472 span.childNodes[1].nodeValue.should.equal('foobar');
473};
474
475exports.replaceChild = function() {
476 var impl = new domino.impl.DOMImplementation();
477 var doc = impl.createDocument();
478 var root = doc.appendChild(doc.createElement('root'));
479 root.outerHTML.should.equal('<root></root>');
480 var a = root.appendChild(doc.createElement('a'));
481 root.outerHTML.should.equal('<root><a></a></root>');
482 var b = doc.createElement('b');
483
484 function capture(f) {
485 var events = [];
486 doc._setMutationHandler(function(info) {
487 events.push(info);
488 });
489 f();
490 doc._setMutationHandler(null);
491 return events;
492 }
493
494 // Replace with unrooted
495 capture(function() {
496 root.replaceChild(b, a).should.equal(a);
497 }).should.deepEqual([
498 {
499 type: 4, // Remove
500 target: root,
501 node: a
502 },
503 {
504 type: 6, // Insert
505 target: root,
506 node: b
507 }]);
508 root.outerHTML.should.equal('<root><b></b></root>');
509
510 root.replaceChild(a, b).should.equal(b);
511 root.outerHTML.should.equal('<root><a></a></root>');
512
513 // Move node
514 var c = doc.createElement('c');
515 root.appendChild(b);
516 root.appendChild(c);
517 capture(function() {
518 root.replaceChild(c, a);
519 }).should.deepEqual([
520 {
521 type: 4, // Remove
522 target: root,
523 node: a
524 },
525 {
526 type: 5, // Move
527 target: c
528 }]);
529 root.outerHTML.should.equal('<root><c></c><b></b></root>');
530
531 // Replace under unrooted parent
532 var df = doc.createDocumentFragment();
533 var d = df.appendChild(doc.createElement('d'));
534 var e = df.appendChild(doc.createElement('e'));
535 var f = doc.createElement('f');
536 df.replaceChild(f, e);
537 df.serialize().should.equal('<d></d><f></f>');
538 d = d; // avoid defined-but-not-used warning
539
540 // Replace rooted node with document fragment
541 root.appendChild(a);
542 root.replaceChild(df, b);
543 root.outerHTML.should.equal('<root><c></c><d></d><f></f><a></a></root>');
544};
545
546exports.contains = function() {
547 // see https://developer.mozilla.org/en-US/docs/Web/API/Node/contains
548 var document = domino.createWindow(html).document;
549 var h1 = document.getElementById('lorem');
550 h1.contains(null).should.equal(false);
551 h1.contains(h1).should.equal(true);
552 h1.childNodes[0].contains(h1).should.equal(false);
553 h1.contains(h1.childNodes[0]).should.equal(true);
554 document.body.contains(h1).should.equal(true);
555 var nodeList = document.querySelectorAll('p');
556 h1.contains(nodeList[0]).should.equal(false);
557 document.body.contains(nodeList[0]).should.equal(true);
558 nodeList[0].contains(nodeList[1]).should.equal(false);
559 nodeList[1].contains(nodeList[0]).should.equal(false);
560};
561
562exports.parseImportant = function() {
563 var html = '<p style="font-family:sans-serif; text-decoration:none !important">foo</p>';
564 var doc = domino.createDocument(html);
565 var p = doc.querySelector('p');
566 p.style.fontFamily.should.equal('sans-serif');
567 p.style.textDecoration.should.equal('none');
568};
569
570exports.gh70 = function() {
571 var document = domino.createDocument('<h1 class="hello">Hello world</h1>');
572 var h1 = document.querySelector('h1');
573 var classAttr = h1.attributes.item(0);
574
575 classAttr.value.should.equal('hello');
576 classAttr.nodeValue.should.equal('hello');
577 classAttr.textContent.should.equal('hello');
578
579 classAttr.nodeValue = 'nodeValue';
580 classAttr.value.should.equal('nodeValue');
581 classAttr.nodeValue.should.equal('nodeValue');
582 classAttr.textContent.should.equal('nodeValue');
583
584 classAttr.textContent = 'textContent';
585 classAttr.value.should.equal('textContent');
586 classAttr.nodeValue.should.equal('textContent');
587 classAttr.textContent.should.equal('textContent');
588};
589
590exports.gh71 = function() {
591 var document = domino.createDocument('<h1 style="color:red !important">Hello world</h1>');
592 var h1 = document.querySelector('h1');
593 h1.style.display = 'none';
594 h1.outerHTML.should.equal('<h1 style="color: red !important; display: none;">Hello world</h1>');
595};
596
597exports.gh72 = function() {
598 var window = domino.createWindow('<h1>Hello, world!</h1>');
599 window.setTimeout.should.have.type('function');
600 window.clearTimeout.should.have.type('function');
601 window.setInterval.should.have.type('function');
602 window.clearInterval.should.have.type('function');
603};
604
605exports.navigatorID = function() {
606 var window = domino.createWindow('<h1>Hello, world!</h1>');
607 window.navigator.appCodeName.should.equal("Mozilla");
608 window.navigator.taintEnabled().should.equal(false);
609};
610
611exports.template1 = function() {
612 var document = domino.createDocument("<table><tbody><template id=row><tr><td><td>");
613 document.body.innerHTML.should.equal('<table><tbody><template id="row"><tr><td></td><td></td></tr></template></tbody></table>');
614 var t = document.getElementById("row");
615 t.should.be.an.instanceof(domino.impl.HTMLTemplateElement);
616 t.childNodes.length.should.equal(0);
617 t.content.should.be.an.instanceof(domino.impl.DocumentFragment);
618 t.content.serialize().should.equal("<tr><td></td><td></td></tr>");
619 document.querySelectorAll("td").length.should.equal(0);
620 t.content.querySelectorAll("td").length.should.equal(2);
621 t.content.ownerDocument.should.not.equal(document);
622 t.content.querySelectorAll("*").map(function(el) {
623 el.ownerDocument.should.equal(t.content.ownerDocument);
624 });
625};
626
627exports.template2 = function() {
628 // Templates go in <head> by default.
629 var document = domino.createDocument("<template>x<!--hi");
630 document.head.childNodes.length.should.equal(1);
631 document.head.children[0].tagName.should.equal("TEMPLATE");
632 var df = document.head.children[0].content;
633 df.should.be.an.instanceof(domino.impl.DocumentFragment);
634 df.ownerDocument.should.not.equal(document);
635 df.childNodes.length.should.equal(2);
636 df.childNodes[0].ownerDocument.should.equal(df.ownerDocument);
637 df.childNodes[1].ownerDocument.should.equal(df.ownerDocument);
638 document.head.innerHTML.should.equal('<template>x<!--hi--></template>');
639};
640
641// HTMLTemplateElement.innerHTML
642// see https://github.com/w3c/DOM-Parsing/issues/1
643exports.template3 = function() {
644 var document = domino.createDocument();
645 var t = document.createElement("template");
646 t.should.be.an.instanceof(domino.impl.HTMLTemplateElement);
647 t.childNodes.length.should.equal(0);
648 t.content.should.be.an.instanceof(domino.impl.DocumentFragment);
649 // This is the key line:
650 t.innerHTML = '<div>abc</div><p>def</p>';
651 t.innerHTML.should.equal('<div>abc</div><p>def</p>');
652 t.content.ownerDocument.should.not.equal(document);
653 t.content.childNodes.length.should.equal(2);
654 t.content.querySelectorAll("*").map(function(el) {
655 el.ownerDocument.should.equal(t.content.ownerDocument);
656 });
657 // Non-standard (gh #73)
658 t.content.outerHTML.should.equal('<div>abc</div><p>def</p>');
659};
660
661exports.fosterParent1 = function() {
662 var document = domino.createDocument('<table><tr>x<a>foo<td>y');
663 // In this case the "x<a>" gets foster-parented *before* the <tr>
664 document.body.innerHTML.should.equal("x<a>foo</a><table><tbody><tr><td>y</td></tr></tbody></table>");
665};
666
667exports.fosterParent2 = function() {
668 var document = domino.createDocument('foster test');
669 var thead = document.createElement("thead");
670 // exercise the "no table in open element stack" case in foster parenting
671 // algorithm.
672 thead.innerHTML = "<tr>x<a>foo<td>y";
673 // Note how "x" got placed *after* the <tr> in this case.
674 thead.outerHTML.should.equal('<thead><tr><td>y</td></tr>x<a>foo</a></thead>');
675};
676
677exports.fosterParent3 = function() {
678 var document = domino.createDocument('<body><template><table><tr>x</template><table><template><tr>y');
679 document.body.innerHTML.should.equal("<template>x<table><tbody><tr></tr></tbody></table></template><table><template><tr></tr>y</template></table>");
680 var templates = document.querySelectorAll("template");
681 templates.length.should.equal(2);
682 document.querySelectorAll("tr").length.should.equal(0);
683 Array.prototype.forEach.call(templates, function(t) {
684 t.ownerDocument.should.equal(document);
685 t.content.ownerDocument.should.not.equal(document);
686 t.content.querySelectorAll("*").map(function(el) {
687 el.ownerDocument.should.equal(t.content.ownerDocument);
688 });
689 });
690 templates[0].content.ownerDocument.should.equal(
691 templates[1].content.ownerDocument
692 );
693};
694
695exports.canvasTag = function() {
696 var document = domino.createDocument('<canvas width=23 height=45>');
697 var canvas = document.querySelector('canvas');
698 canvas.should.be.instanceof(domino.impl.HTMLElement);
699 canvas.should.be.instanceof(domino.impl.HTMLCanvasElement);
700 canvas.should.not.be.instanceof(domino.impl.HTMLUnknownElement);
701 canvas.width.should.equal(23);
702 canvas.height.should.equal(45);
703};
704
705exports.dialogTag = function() {
706 // <p> should be closed before <dialog>
707 var document = domino.createDocument('<div><p>x<dialog open returnvalue="foo">y');
708 document.body.innerHTML.should.equal('<div><p>x</p><dialog open="" returnvalue="foo">y</dialog></div>');
709 var dialog = document.querySelector('dialog');
710 dialog.should.be.instanceof(domino.impl.HTMLElement);
711 dialog.should.be.instanceof(domino.impl.HTMLDialogElement);
712 dialog.should.not.be.instanceof(domino.impl.HTMLUnknownElement);
713 dialog.open.should.equal(true);
714 dialog.returnValue.should.equal("foo");
715};
716
717exports.mainTag = function() {
718 // <p> should be closed before <main>
719 var document = domino.createDocument('<div><p>x<main>y');
720 document.body.innerHTML.should.equal('<div><p>x</p><main>y</main></div>');
721 var main = document.querySelector('main');
722 main.should.be.instanceof(domino.impl.HTMLElement);
723 main.should.not.be.instanceof(domino.impl.HTMLUnknownElement);
724};
725
726exports.menuItemTag = function() {
727 // <menuitem> should be special
728 var document = domino.createDocument('<menuitem type="checkbox" checked default>');
729 document.body.innerHTML.should.equal('<menuitem type="checkbox" checked="" default=""></menuitem>');
730 var menuitem = document.querySelector('menuitem');
731 menuitem.should.be.instanceof(domino.impl.HTMLElement);
732 menuitem.should.be.instanceof(domino.impl.HTMLMenuItemElement);
733 menuitem.should.not.be.instanceof(domino.impl.HTMLUnknownElement);
734 menuitem.type.should.equal("checkbox");
735 menuitem.checked.should.equal(true);
736 menuitem.disabled.should.equal(false);
737 menuitem.default.should.equal(true);
738};
739
740exports.rubyTags = function() {
741 var document = domino.createDocument(
742 '<p><ruby id=a>base<rt>annotation</ruby>' +
743 '<ruby id=b><rb>上<rb>手<rt>じよう<rt>ず<rtc><rt>jou<rt>zu'
744 );
745 document.body.innerHTML.should.equal(
746 '<p><ruby id="a">base<rt>annotation</rt></ruby>' +
747 '<ruby id="b"><rb>上</rb><rb>手</rb><rt>じよう</rt><rt>ず</rt><rtc><rt>jou</rt><rt>zu</rt></rtc></ruby></p>'
748 );
749 Array.prototype.forEach.call(
750 document.querySelectorAll('ruby,rb,rp,rt,rtc'), function(el) {
751 el.should.be.instanceof(domino.impl.HTMLElement);
752 el.should.not.be.instanceof(domino.impl.HTMLUnknownElement);
753 });
754};
755
756exports.sourceTag = function() {
757 var document = domino.createDocument('<base href=http://example.com><video controls><source src="foo.webm" type="video/webm">Sorry, no HTML5 video.');
758 document.body.innerHTML.should.equal('<video controls=""><source src="foo.webm" type="video/webm">Sorry, no HTML5 video.</video>');
759 var source = document.querySelector('source');
760 source.should.be.instanceof(domino.impl.HTMLElement);
761 source.should.be.instanceof(domino.impl.HTMLSourceElement);
762 source.should.not.be.instanceof(domino.impl.HTMLUnknownElement);
763 source.src.should.equal("http://example.com/foo.webm");
764 source.type.should.equal("video/webm");
765};
766
767exports.trackTag = function() {
768 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">');
769 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>');
770 var track = document.querySelector('track');
771 track.should.be.instanceof(domino.impl.HTMLElement);
772 track.should.be.instanceof(domino.impl.HTMLTrackElement);
773 track.should.not.be.instanceof(domino.impl.HTMLUnknownElement);
774 track.kind.should.equal("captions");
775 track.src.should.equal("http://example.com/en-captions.vtt");
776 track.srclang.should.equal("en");
777};
778
779exports.elementInterface = function() {
780 [
781 "acronym", "basefont", "big", "center", "nobr", "noembed", "noframes",
782 "plaintext", "rb", "rtc", "strike", "tt"
783 ].forEach(function(tag) {
784 var document = domino.createDocument('<'+tag+'>');
785 var elt = document.querySelector(tag);
786 elt.should.be.instanceof(domino.impl.HTMLElement);
787 elt.should.not.be.instanceof(domino.impl.HTMLUnknownElement);
788 });
789 [
790 "listing", "xmp", "pre"
791 ].forEach(function(tag) {
792 var document = domino.createDocument('<'+tag+'>');
793 var elt = document.querySelector(tag);
794 elt.should.be.instanceof(domino.impl.HTMLElement);
795 elt.should.be.instanceof(domino.impl.HTMLPreElement);
796 elt.should.not.be.instanceof(domino.impl.HTMLUnknownElement);
797 });
798};
799
800exports.gh79 = function() {
801 // CSS identifiers can only contain the characters [a-zA-Z0-9] and
802 // ISO 10646 characters U+00A0 and higher, plus the hyphen (-) and the
803 // underscore (_). But they *can* have escaped characters.
804 var doc = '<div id="cite_note-13.3F_It_Can\'t_Be!-3"></div>';
805 var document = domino.createDocument(doc);
806 document.body.innerHTML.should.equal(doc);
807 (function() {
808 document.querySelector("#cite_note-13.3F_It_Can't_Be!-3");
809 }).should.throw({ name: 'SyntaxError' });
810 var div = document.querySelectorAll("#cite_note-13\\.3F_It_Can\\'t_Be\\!-3");
811 div.length.should.equal(1);
812};
813
814exports.small_list = function() {
815 var doc = '<ul><li><small class=foo>x</li><li>y<ul><li>b</li><li>c</small></li></ul></li></ul>';
816 var document = domino.createDocument(doc);
817 var smalls = document.querySelectorAll('small');
818 smalls.length.should.equal(4);
819 for (var i=0; i<smalls.length; i++) {
820 smalls[i].classList.contains('foo').should.be.true();
821 }
822};
823
824exports.menuitem = function() {
825 var doc = '<menuitem id=a label=" b "><menuitem id=c> d <b> e ';
826 var document = domino.createDocument(doc);
827 document.body.innerHTML.should.equal('<menuitem id="a" label=" b "></menuitem><menuitem id="c"> d <b> e </b></menuitem>');
828 var itema = document.getElementById('a');
829 (itema != null).should.be.true(); // jshint ignore:line
830 itema.should.be.an.instanceof(domino.impl.HTMLMenuItemElement);
831 itema.label.should.equal(' b ');
832 itema.label = ' x ';
833 itema.label.should.equal(' x ');
834 itema.hasAttribute('label').should.be.true();
835 itema.getAttribute('label').should.equal(' x ');
836 itema.outerHTML.should.equal('<menuitem id="a" label=" x "></menuitem>');
837
838 var itemb = document.getElementById('c');
839 (itemb != null).should.be.true(); // jshint ignore:line
840 itemb.should.be.an.instanceof(domino.impl.HTMLMenuItemElement);
841 itemb.label.should.equal('d e');
842 itemb.label = ' y ';
843 itemb.label.should.equal(' y ');
844 itemb.hasAttribute('label').should.be.true();
845 itemb.getAttribute('label').should.equal(' y ');
846 itemb.outerHTML.should.equal('<menuitem id="c" label=" y "> d <b> e </b></menuitem>');
847};
848
849exports.createSvgElements = function() {
850 var document = domino.createDocument();
851
852 var svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
853 document.body.appendChild(svg);
854
855 svg.should.be.instanceOf(domino.impl.SVGSVGElement);
856 document.body.innerHTML.should.equal("<svg></svg>");
857};
858
859exports.gh95 = function() {
860 var document = domino.createDocument(
861 '<body><a href="foo\'s">bar</a></body>'
862 );
863 (function() {
864 document.querySelectorAll("a[href=foo's]");
865 }).should.throw({ name: 'SyntaxError' });
866 document.querySelectorAll("a[href=foo\\'s]").length.should.equal(1);
867};
868
869exports.propertyWritability = function () { // gh #89
870 var window = domino.createWindow('');
871 var document = domino.createDocument();
872
873 var assertWritable = function(object, property) {
874 var replacement = function () { };
875 object[property] = replacement;
876 object[property].should.equal(replacement, property + " should be writable");
877 };
878
879 assertWritable(window, 'HTMLElement');
880 assertWritable(document, 'importNode');
881 assertWritable(document, 'createElement');
882 assertWritable(document, 'createElementNS');
883};
884
885exports.gh90 = function() {
886 var doc = '<input type="checkbox">';
887 var document = domino.createDocument(doc);
888 document.body.innerHTML.should.equal(doc);
889
890 var input = document.querySelector('input');
891 input.checked.should.equal(false);
892
893 input.checked = true;
894 input.checked.should.equal(true);
895 input.outerHTML.should.equal('<input type="checkbox" checked="">');
896
897 input.checked = false;
898 input.checked.should.equal(false);
899 input.outerHTML.should.equal(doc);
900
901 // Now test again, using hasAttribute/hasAttributeNS directly.
902 input.hasAttribute('checked').should.equal(false);
903 input.hasAttributeNS(null, 'checked').should.equal(false);
904 input.hasAttributeNS('foo', 'checked').should.equal(false);
905
906 input.setAttribute('checked', 'bar');
907 input.setAttributeNS('foo', 'checked', 'bat');
908
909 input.hasAttribute('checked').should.equal(true);
910 input.hasAttributeNS(null, 'checked').should.equal(true);
911 input.hasAttributeNS('foo', 'checked').should.equal(true);
912
913 input.removeAttribute('checked');
914 input.removeAttributeNS('foo', 'checked');
915
916 input.hasAttribute('checked').should.equal(false);
917 input.hasAttributeNS(null, 'checked').should.equal(false);
918 input.hasAttributeNS('foo', 'checked').should.equal(false);
919};
920
921exports.gh98 = function() {
922 var doc = '<a href="/"></a>';
923 var document = domino.createDocument(doc);
924 var a = document.querySelector('a');
925 (a.style.getPropertyValue('background') === '').should.be.true();
926};
927
928exports.gh99 = function() {
929 // test '#foo' optimization in querySelectorAll
930 var window = domino.createWindow(
931 '<!DOCTYPE html><html><body></body></html>'
932 );
933 var doc = window.document;
934 var match = doc.querySelectorAll('#coordinates');
935 match.length.should.equal(0);
936 if (Array.from) {
937 Array.from(match).length.should.equal(0);
938 }
939 (match[0] === undefined).should.be.true();
940
941 // continue test, now w/ multiple elements sharing same id.
942 doc.body.innerHTML = '<p id=a>x</p><p id=a>y</p>';
943 match = doc.querySelectorAll('#a');
944 match.length.should.equal(2);
945 if (Array.from) {
946 Array.from(match).length.should.equal(2);
947 }
948 match[0].textContent.should.equal('x');
949 match[1].textContent.should.equal('y');
950};
951
952exports.gh112 = function() {
953 // attributes named 'xmlns' are fine. (gh #112)
954 var window = domino.createWindow(
955 '<!DOCTYPE html><html xmlns="http://www.w3.org/1999/xhtml"><b></b></html>'
956 );
957 var document = window.document;
958 document.documentElement.setAttribute('xmlns', 'test');
959 var b = document.querySelector('b');
960 b.innerHTML = '<test></test>';
961 var test = document.querySelector('test');
962 // Note that this seems contrary to what is implied by
963 // https://lists.w3.org/Archives/Public/www-dom/2011JulSep/0153.html
964 // but matches what modern browsers do.
965 b.namespaceURI.should.equal('http://www.w3.org/1999/xhtml');
966 test.namespaceURI.should.equal('http://www.w3.org/1999/xhtml');
967};
968
969exports.gh109 = function() {
970 var document = domino.createDocument();
971 var div = document.createElement('div');
972 div.classList.add('one', 'two');
973 div.classList.length.should.equal(2);
974 div.classList.contains('one').should.be.true();
975 div.classList.contains('two').should.be.true();
976 div.classList.remove('one', 'two');
977 div.classList.length.should.equal(0);
978 div.classList.contains('one').should.be.false();
979 div.classList.contains('two').should.be.false();
980};
981
982exports.gh111 = function() {
983 var document = domino.createDocument();
984 var div = document.createElement('div');
985 div.classList.toggle('a');
986 div.classList.toggle('b', true);
987 div.classList.length.should.equal(2);
988 div.classList.value.should.equal('a b');
989 div.classList.toggle('a');
990 div.classList.toggle('b', true);
991 div.classList.length.should.equal(1);
992 div.classList.value.should.equal('b');
993 div.classList.toggle('b', false);
994 div.classList.length.should.equal(0);
995 div.classList.value.should.equal('');
996
997 div.classList.value = ' foo\rbat \n baz a\nb\f\t';
998 div.classList.value.should.equal(' foo\rbat \n baz a\nb\f\t');
999 div.classList.length.should.equal(5);
1000 div.classList.contains('').should.be.false();
1001 div.classList.contains(' ').should.be.false();
1002 div.classList.contains('\n').should.be.false();
1003 div.classList.contains('\f').should.be.false();
1004 div.classList[0].should.equal('foo');
1005 div.classList[1].should.equal('bat');
1006 div.classList.item(2).should.equal('baz');
1007 div.classList.contains('a').should.be.true();
1008 div.classList.contains('b').should.be.true();
1009 div.classList.replace('bat', 'ball');
1010 div.classList.value.should.equal('foo ball baz a b');
1011 div.classList.length.should.equal(5);
1012 div.classList.contains('ball').should.be.true();
1013 div.classList[1].should.equal('ball');
1014};
1015
1016exports.gh126 = function() {
1017 var document = domino.createDocument();
1018 var div = document.createElement('div');
1019 div.classList.length.should.equal(0);
1020 div.classList.add();
1021 div.classList.length.should.equal(0);
1022 div.classList.add(undefined);
1023 div.classList.add(null);
1024 div.classList.length.should.equal(2);
1025 div.classList.remove();
1026 div.classList.length.should.equal(2);
1027 div.classList.contains(undefined).should.be.true();
1028 div.classList.contains('undefined').should.be.true();
1029 div.classList.contains(null).should.be.true();
1030 div.classList.contains('null').should.be.true();
1031 div.classList.remove(undefined, null);
1032 div.classList.length.should.equal(0);
1033 div.classList.contains(undefined).should.be.false();
1034 div.classList.contains('undefined').should.be.false();
1035 div.classList.contains(null).should.be.false();
1036 div.classList.contains('null').should.be.false();
1037};
1038
1039exports.arrayfrom = function() {
1040 // If your version of node supports Array.from, it should support
1041 // Array.from(node.attributes) ... even though we don't use proxies.
1042 if (typeof(Array.from) !== 'function') { return; }
1043 var d = domino.createDocument('');
1044 var e = d.createElement('span');
1045 e.setAttribute('a','1');
1046 e.setAttribute('b','2');
1047 var a = Array.from(e.attributes);
1048 a.should.have.length(2);
1049 a[0].should.have.property('name','a');
1050 a[0].should.have.property('value','1');
1051 a[1].should.have.property('name','b');
1052 a[1].should.have.property('value','2');
1053};
1054
1055exports.gh119 = function() {
1056 var document = domino.createDocument('<div></div>');
1057 var div = document.querySelector('div');
1058 div.style.flex = '1 1 0px';
1059 div.outerHTML.should.equal('<div style="flex: 1 1 0px;"></div>');
1060
1061 document = domino.createDocument('<div></div>');
1062 div = document.querySelector('div');
1063 div.style.flexFlow = 'row wrap';
1064 div.outerHTML.should.equal('<div style="flex-flow: row wrap;"></div>');
1065
1066 document = domino.createDocument('<div></div>');
1067 div = document.querySelector('div');
1068 div.style.flexBasis = '0px';
1069 div.style.flexGrow = 1;
1070 div.style.flexShrink = 1;
1071 div.style.flexDirection = 'column';
1072 div.style.flexWrap = 'wrap';
1073
1074 div.outerHTML.should.equal('<div style="flex-basis: 0px; flex-grow: 1; flex-shrink: 1; flex-direction: column; flex-wrap: wrap;"></div>');
1075};
1076
1077exports.gh121 = function() {
1078 var document = domino.createDocument('<div></div>');
1079 var div = document.querySelector('div');
1080 div.className = 'ab a';
1081 div.matches('.a').should.be.true();
1082 div.matches('[class~=a]').should.be.true();
1083 div.className = 'a ab';
1084 div.matches('.a').should.be.true();
1085 div.matches('[class~=a]').should.be.true();
1086};
1087
1088exports.gh127 = function() {
1089 var document = domino.createDocument('<a href="#foo"></a><a href="http://#foo"></a>');
1090 var aEls = document.querySelectorAll('a');
1091 aEls.length.should.equal(2);
1092 for (var i=0; i < aEls.length; i++) {
1093 aEls[i].hash.should.equal('#foo');
1094 }
1095};
1096
1097exports.gh128 = function() {
1098 var document = domino.createDocument('<a target="xInSeNsItIvEx"></a><a target="xYx"></a>');
1099 var expected = document.querySelectorAll('a')[0];
1100
1101 [
1102 { sel: '[target*="insensitive" i]' },
1103 { sel: "[target='XinsensitiveX'i]" },
1104 { sel: '[target=XINSENSITIVEX i]' },
1105 { sel: '[target=XINSENSITIVEXi]', expectFail: true },
1106 { sel: '[target^=XI i]' },
1107 { sel: '[target^=XIi]', expectFail: true },
1108 { sel: '[target^=XI]', expectFail: true },
1109 { sel: '[target$=EX i]' },
1110 { sel: '[target$="ex"i]' },
1111 { sel: '[target~="xinsensitivex"i]' },
1112 { sel: '[target|="xinsensitivex" i]' },
1113 ].forEach(function(oneCase) {
1114 var a = document.querySelectorAll('*'+oneCase.sel);
1115 if (oneCase.expectFail) {
1116 a.length.should.equal(0);
1117 } else {
1118 a.length.should.equal(1);
1119 a[0].should.be.exactly(expected);
1120 }
1121 });
1122};