Files
webcomponentsjs/tests/ShadowDOM/js/test.js

524 lines
17 KiB
JavaScript

/**
* @license
* Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
* This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
* The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
* The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
* Code distributed by Google as part of the polymer project is also
* subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
*/
suite('Shadow DOM', function() {
var getRendererForHost = ShadowDOMPolyfill.getRendererForHost;
var unwrap = ShadowDOMPolyfill.unwrap;
function getVisualInnerHtml(el) {
el.offsetWidth;
return unwrap(el).innerHTML;
}
function normalizeInnerHtml(s) {
// IE9 - Even though the attribute name is stored as "checked" innerHTML
// upper case the name.
return s.replace(/CHECKED=""/g, 'checked=""')
}
function testRender(descr, hostInnerHtml, shadowRoots,
expectedOuterHtml, opt_beforeRender) {
test(descr, function() {
var host = document.createElement('div');
host.innerHTML = hostInnerHtml;
if (typeof shadowRoots === 'string')
shadowRoots = [shadowRoots];
shadowRoots.forEach(function(html) {
var shadowRoot = host.createShadowRoot();
shadowRoot.innerHTML = html;
});
if (opt_beforeRender)
opt_beforeRender(host);
assert.strictEqual(normalizeInnerHtml(getVisualInnerHtml(host)),
normalizeInnerHtml(expectedOuterHtml));
});
}
testRender('Empty shadow', 'abc', '', '');
testRender('Simple shadow', 'abc', 'def', 'def');
testRender('Fallback shadow', 'abc',
'<content select="xxx">fallback</content>', 'fallback');
testRender('Content', 'abc',
'<content>fallback</content>', 'abc');
testRender('Content before', 'abc',
'before<content>fallback</content>', 'beforeabc');
testRender('Content after', 'abc',
'<content>fallback</content>after', 'abcafter');
suite('content', function() {
testRender('no select', '<a href="">Link</a> <b>bold</b>',
'<content></content>',
'<a href="">Link</a> <b>bold</b>');
testRender('select ""', '<a href="">Link</a> <b>bold</b>',
'<content select=""></content>',
'<a href="">Link</a> <b>bold</b>');
testRender('select *', '<a href="">Link</a> <b>bold</b>',
'<content select="*"></content>',
'<a href="">Link</a><b>bold</b>');
testRender('select .a',
'<a class="a">a</a> <a class="b">b</a>',
'<content select=".a"></content>',
'<a class="a">a</a>');
testRender('select .b .a',
'<a class="a">a</a> <a class="b">b</a>',
'<content select=".b"></content><content select=".a"></content>',
'<a class="b">b</a><a class="a">a</a>');
});
suite('Nested shadow roots', function() {
testRender('2 levels deep', 'host', ['oldest shadow', '<shadow></shadow>'],
'oldest shadow');
testRender('4 levels deep', 'host',
['oldest shadow', '<shadow></shadow>', '<shadow></shadow>',
'<shadow></shadow>'],
'oldest shadow');
testRender('4 levels deep. A bit more interesting', 'host',
['a', 'b<shadow></shadow>c', 'd<shadow></shadow>e',
'f<shadow></shadow>g'],
'fdbaceg');
testRender('content and shadow',
'<a></a><b></b><c></c>',
[
'<content select="a"></content>',
'<shadow></shadow><content select="b"></content>',
'<content select="c"></content><shadow></shadow>'
],
'<c></c><a></a><b></b>');
testRender('content in shadow',
'<a></a><b></b><c></c>',
[
'<d></d>',
'<content select="b"></content>' +
'<shadow><content select="d"></content></shadow>',
'<content select="a"></content>' +
'<shadow>' +
'<content select="d"></content>' +
'<content select="b"></content>' +
'</shadow>',
],
'<a></a><b></b><d></d>');
});
suite('matches criteria', function() {
suite('empty select attribute', function() {
testRender('Content has no select attribute so everything should match',
'a <b>c</b> d',
'<content></content>',
'a <b>c</b> d');
testRender('Content has empty select attribute so everything should ' +
'match',
'a <b>c</b> d',
'<content select=""></content>',
'a <b>c</b> d');
testRender('Content has an all whitespace select attribute so ' +
'everything should match',
'a <b>c</b> d',
'<content select=" \n \t "></content>',
'a <b>c</b> d');
});
suite('universal selector', function() {
testRender('*',
'<a></a> <b></b> <c></c>',
'<content select="*"></content>',
'<a></a><b></b><c></c>');
testRender('With whitespace',
'<a></a> <b></b> <c></c>',
'<content select=" * "></content>',
'<a></a><b></b><c></c>');
});
suite('type selector', function() {
testRender('b',
'<a></a> <b></b> <c></c>',
'<content select="b"></content>',
'<b></b>');
testRender('case',
'<a></a> <b></b> <c></c>',
'<content select="B"></content>',
'<b></b>');
});
suite('class selector(s)', function() {
testRender('Single',
'<a class="a b"></a><a class="b a"></a><a class="b"></a>',
'<content select=".a"></content>',
'<a class="a b"></a><a class="b a"></a>');
testRender('With whitespace',
'<a class="a b"></a><a class="b a"></a><a class="b"></a>',
'<content select=" .a "></content>',
'<a class="a b"></a><a class="b a"></a>');
testRender('Multiple',
'<a class="a b"></a><a class="b a"></a><a class="b"></a>',
'<content select=".a.b"></content>',
'<a class="a b"></a><a class="b a"></a>');
});
suite('ID selector', function() {
testRender('Simple',
'<a id="a"></a><a id="b"></a>',
'<content select="#a"></content>',
'<a id="a"></a>');
testRender('Two elements with the same ID',
'<a id="a"></a><a id="a"></a>',
'<content select="#a"></content>',
'<a id="a"></a><a id="a"></a>');
});
suite('Attribute selector(s)', function() {
testRender('Simple',
'<a id="a"></a><a id="b"></a>',
'<content select="[id]"></content>',
'<a id="a"></a><a id="b"></a>');
testRender('Attribute with value',
'<a id="a"></a><a id="b"></a>',
'<content select="[id=b]"></content>',
'<a id="b"></a>');
testRender('whitespace separated list',
'<a data-test="a b c"></a><a data-test="abc"></a>',
'<content select="[data-test~=b]"></content>',
'<a data-test="a b c"></a>');
});
suite('Not selector', function() {
testRender('Type',
'<a></a><b></b>',
'<content select=":not(a)"></content>',
'<b></b>');
testRender('ID',
'<a id="a"></a><a id="b"></a>',
'<content select=":not(#a)"></content>',
'<a id="b"></a>');
testRender('Class',
'<a class="a"></a><a class="b"></a>',
'<content select=":not(.a)"></content>',
'<a class="b"></a>');
testRender('Attribute',
'<a a="a"></a><a b="b"></a>',
'<content select=":not([a])"></content>',
'<a b="b"></a>');
testRender('Attribute Value',
'<a x="a"></a><a x="b"></a>',
'<content select=":not([x=a])"></content>',
'<a x="b"></a>');
});
});
suite('Nested shadow hosts', function() {
test('Child has a shadow host', function() {
var host = document.createElement('div');
host.innerHTML = '<a>3</a>';
var a = host.firstChild;
var hostShadowRoot = host.createShadowRoot();
hostShadowRoot.innerHTML = '1<content></content>5';
var aShadowRoot = a.createShadowRoot();
aShadowRoot.innerHTML = '2<content></content>4';
assert.strictEqual(getVisualInnerHtml(host), '1<a>234</a>5');
});
test('Shadow DOM has a shadow host', function() {
var host = document.createElement('div');
host.innerHTML = '6';
var hostShadowRoot = host.createShadowRoot();
hostShadowRoot.innerHTML = '1<a>3</a>5<content></content>7';
var a = hostShadowRoot.firstChild.nextSibling;
var aShadowRoot = a.createShadowRoot();
aShadowRoot.innerHTML = '2<content></content>4';
assert.strictEqual(getVisualInnerHtml(host), '1<a>234</a>567');
});
});
suite('Tracking attributes', function() {
test('attribute selector', function() {
var host = document.createElement('div');
host.innerHTML = '<a></a>';
var a = host.firstChild;
var sr = host.createShadowRoot();
sr.innerHTML = '<content select="[foo]"></content>';
var calls = 0;
var renderer = getRendererForHost(host);
var originalRender = renderer.render;
renderer.render = function() {
calls++;
originalRender.call(this);
};
assert.equal(getVisualInnerHtml(host), '');
assert.equal(calls, 1);
a.setAttribute('foo', 'bar');
assert.equal(getVisualInnerHtml(host), '<a foo="bar"></a>');
assert.equal(calls, 2);
a.setAttribute('foo', '');
assert.equal(getVisualInnerHtml(host), '<a foo=""></a>');
assert.equal(calls, 3);
a.removeAttribute('foo');
assert.equal(getVisualInnerHtml(host), '');
assert.equal(calls, 4);
a.setAttribute('bar', '');
assert.equal(getVisualInnerHtml(host), '');
assert.equal(calls, 4);
});
test('id selector', function() {
var host = document.createElement('div');
host.innerHTML = '<a></a>';
var a = host.firstChild;
var sr = host.createShadowRoot();
sr.innerHTML = '<content select="#a"></content>';
var calls = 0;
var renderer = getRendererForHost(host);
var originalRender = renderer.render;
renderer.render = function() {
calls++;
originalRender.call(this);
};
assert.equal(getVisualInnerHtml(host), '');
assert.equal(calls, 1);
a.setAttribute('foo', 'bar');
assert.equal(getVisualInnerHtml(host), '');
assert.equal(calls, 1);
a.setAttribute('id', 'a');
var visHTML = getVisualInnerHtml(host);
// IE orders the attributes differently.
assert.isTrue(visHTML === '<a foo="bar" id="a"></a>' ||
visHTML === '<a id="a" foo="bar"></a>');
assert.equal(calls, 2);
a.removeAttribute('foo');
assert.equal(getVisualInnerHtml(host), '<a id="a"></a>');
assert.equal(calls, 2);
a.id = 'b';
assert.equal(getVisualInnerHtml(host), '');
assert.equal(calls, 3);
a.id = 'a';
assert.equal(getVisualInnerHtml(host), '<a id="a"></a>');
assert.equal(calls, 4);
a.id = null;
assert.equal(getVisualInnerHtml(host), '');
assert.equal(calls, 5);
});
test('class selector', function() {
var host = document.createElement('div');
host.innerHTML = '<a></a>';
var a = host.firstChild;
var sr = host.createShadowRoot();
sr.innerHTML = '<content select=".a"></content>';
var calls = 0;
var renderer = getRendererForHost(host);
var originalRender = renderer.render;
renderer.render = function() {
calls++;
originalRender.call(this);
};
assert.equal(getVisualInnerHtml(host), '');
assert.equal(calls, 1);
a.setAttribute('foo', 'bar');
assert.equal(getVisualInnerHtml(host), '');
assert.equal(calls, 1);
a.setAttribute('class', 'a');
var visHTML = getVisualInnerHtml(host);
// IE orders the attributes differently.
assert.isTrue(visHTML === '<a foo="bar" class="a"></a>' ||
visHTML === '<a class="a" foo="bar"></a>');
assert.equal(calls, 2);
a.removeAttribute('foo');
assert.equal(getVisualInnerHtml(host), '<a class="a"></a>');
assert.equal(calls, 2);
a.className = 'b';
assert.equal(getVisualInnerHtml(host), '');
assert.equal(calls, 3);
a.className = 'a';
assert.equal(getVisualInnerHtml(host), '<a class="a"></a>');
assert.equal(calls, 4);
a.classList.remove('a');
assert.equal(getVisualInnerHtml(host), '');
assert.equal(calls, 5);
a.classList.add('a');
assert.equal(getVisualInnerHtml(host), '<a class="a"></a>');
assert.equal(calls, 6);
a.className = null;
assert.equal(getVisualInnerHtml(host), '');
assert.equal(calls, 7);
});
});
test('invalidation', function() {
var host = document.createElement('a');
host.innerHTML = '<b></b> ';
var b = host.firstChild;
var text = host.lastChild;
var sr = host.createShadowRoot();
sr.innerHTML = '<content select="*"></content>';
var content = sr.firstChild;
var count = 0;
var renderer = ShadowDOMPolyfill.getRendererForHost(host);
var originalInvalidate = renderer.invalidate;
renderer.invalidate = function() {
count++;
return originalInvalidate.apply(this, arguments);
};
assert.equal(getVisualInnerHtml(host), '<b></b>');
b.appendChild(document.createElement('d'));
assert.equal(count, 0);
assert.equal(getVisualInnerHtml(host), '<b><d></d></b>');
b.appendChild(document.createElement('e'));
assert.equal(count, 0);
assert.equal(getVisualInnerHtml(host), '<b><d></d><e></e></b>');
var f = sr.appendChild(document.createElement('f'));
assert.equal(count, 1);
assert.equal(getVisualInnerHtml(host), '<b><d></d><e></e></b><f></f>');
f.appendChild(document.createElement('g'));
assert.equal(count, 1);
assert.equal(getVisualInnerHtml(host),
'<b><d></d><e></e></b><f><g></g></f>');
host.insertBefore(document.createElement('h'), text);
assert.equal(count, 2);
assert.equal(getVisualInnerHtml(host),
'<b><d></d><e></e></b><h></h><f><g></g></f>');
});
test('issue-235', function() {
var host = document.createElement('div');
var sr = host.createShadowRoot();
sr.innerHTML = '<a><b></b></a>';
var a = sr.firstChild;
var b = a.firstChild;
assert.equal(getVisualInnerHtml(host), '<a><b></b></a>');
var c = document.createElement('c');
a.appendChild(c);
assert.equal(a.childNodes.length, 2);
});
test('nested shadow hosts (issue 245)', function() {
var outer = document.createElement('outer');
var inner = outer.appendChild(document.createElement('inner'));
// Inner first. Order matters.
var innerShadowRoot = inner.createShadowRoot();
innerShadowRoot.textContent = 'inner';
var outerShadowRoot = outer.createShadowRoot();
outerShadowRoot.innerHTML = '<content></content>outer';
assert.equal(getVisualInnerHtml(outer), '<inner>inner</inner>outer');
});
/*
test('no mutation events during rendering', function() {
var div = document.createElement('div');
div.innerHTML = '<a>b</a>';
var sr = div.createShadowRoot();
sr.innerHTML = 'c<content></content>d';
var count = 0;
function handleEvent(e) {
count++;
}
div.addEventListener('DOMAttrModified', handleEvent, true);
div.addEventListener('DOMAttributeNameChanged', handleEvent, true);
div.addEventListener('DOMCharacterDataModified', handleEvent, true);
div.addEventListener('DOMElementNameChanged', handleEvent, true);
div.addEventListener('DOMNodeInserted', handleEvent, true);
div.addEventListener('DOMNodeInsertedIntoDocument', handleEvent, true);
div.addEventListener('DOMNodeRemoved', handleEvent, true);
div.addEventListener('DOMNodeRemovedFromDocument', handleEvent, true);
div.addEventListener('DOMSubtreeModified', handleEvent, true);
assert.equal(getVisualInnerHtml(div), 'c<a>b</a>d');
assert.equal(count, 0);
});
*/
test('moving nodes from light to shadow - issue 48', function() {
var div = document.createElement('div');
div.innerHTML = '<a></a><b></b>';
var a = div.firstChild;
var b = div.lastChild;
var sr = div.createShadowRoot();
sr.innerHTML = '<c></c>';
var c = sr.firstChild;
assert.equal(getVisualInnerHtml(div), '<c></c>');
c.appendChild(a);
assert.equal(getVisualInnerHtml(div), '<c><a></a></c>');
c.textContent = '';
assert.equal(getVisualInnerHtml(div), '<c></c>');
c.appendChild(b);
assert.equal(getVisualInnerHtml(div), '<c><b></b></c>');
});
});