@polymer/polymer
Version:
The Polymer library makes it easy to create your own web components. Give your element some markup and properties, and then use it on a site. Polymer provides features like dynamic templates and data binding to reduce the amount of boilerplate you need to
815 lines (780 loc) • 31.9 kB
HTML
<!--
@license
Copyright (c) 2017 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
-->
<html>
<head>
<meta charset="utf-8">
<script src="../../../webcomponentsjs/webcomponents-lite.js"></script>
<script src="../../../web-component-tester/browser.js"></script>
<link rel="import" href="../../polymer-element.html">
<link rel="import" href="../../lib/mixins/gesture-event-listeners.html">
<link rel="import" href="../../lib/elements/dom-if.html">
<body>
<script>
HTMLImports.whenReady(() => {
class XElementChild extends Polymer.Element {
ready() {
super.ready();
window.lifecycleOrder.log(this, 'ready');
}
}
customElements.define('x-element-child', XElementChild);
});
</script>
<dom-module id="x-element">
<template>
[[prop]] - [[path]]
<x-element-child id="noBinding" log></x-element>
<x-element-child id="hasBinding" prop="{{prop}}" path="{{path}}" log></x-element>
<x-element-child id="events" on-click="handleClick" on-tap="handleTap"></x-element-child>
</template>
<script>
HTMLImports.whenReady(() => {
class XElement extends Polymer.Element {
static get is() { return 'x-element'; }
static get observers() { return ['propChanged(prop)', 'pathChanged(path)']; }
static get properties() { return { prop: { notify: true }, path: { notify: true } }; }
constructor() {
super();
this.propChanged = sinon.spy();
this.pathChanged = sinon.spy();
}
ready() {
super.ready();
window.lifecycleOrder.log(this, 'ready');
}
}
customElements.define('x-element', XElement);
});
</script>
</dom-module>
<dom-module id="x-runtime">
<template>
<!-- Main template content -->
<style>
x-element {
display: block;
border-bottom: 10px solid orange;
}
</style>
<x-element id="first"></x-element>
<x-element id="textBinding">[[prop]] - [[obj.path]] - [[compute(prop, obj.path)]]</x-element>
<x-element id="propBinding" prop="{{prop}}" log="proto"></x-element>
<x-element id="pathBinding" path="{{obj.path}}"></x-element>
<x-element id="compoundPropBinding" compound="[[prop]] - [[obj.path]] - [[compute(prop, obj.path)]]"></x-element>
<x-element id="events" on-click="handleClick" on-tap="handleTap"></x-element>
<template id="domIf" is="dom-if" if="[[prop]]">
<x-element id="ifElement" prop="{{prop}}" path="{{obj.path}}"></x-element>
</template>
<!-- Nested template in Shadow DOM for runtime stamping -->
<template id="templateFromShadowDom">
<x-element early="[[earlyProp]]" id="first"></x-element>
<x-element id="events" on-click="handleClick" on-tap="handleTap"></x-element>
<template id="domIf" is="dom-if" if="[[prop]]">
<x-element id="ifElementSD" prop="{{prop}}" path="{{obj.path}}"></x-element>
</template>
<span></span><span></span><span></span><span></span>
<x-element id="compoundPropBinding" compound="[[prop]] - [[obj.path]] - [[compute(prop, obj.path)]]"></x-element>
<x-element id="pathBinding" path="{{obj.path}}"></x-element>
<x-element id="propBinding" prop="{{prop}}" log="shadow"></x-element>
<x-element id="textBinding">[[prop]] - [[obj.path]] - [[compute(prop, obj.path)]]</x-element>
</template>
</template>
<!-- Template in light DOM for runtime stamping -->
<template id="templateFromLightDom">
<x-element id="first"></x-element>
<span></span><span></span><span></span><span></span>
<x-element id="compoundPropBinding" compound="[[prop]] - [[obj.path]] - [[compute(prop, obj.path)]]"></x-element>
<x-element id="pathBinding" path="{{obj.path}}"></x-element>
<x-element id="propBinding" prop="{{prop}}" log="light"></x-element>
<x-element id="textBinding">[[prop]] - [[obj.path]] - [[compute(prop, obj.path)]]</x-element>
<x-element id="events" on-click="handleClick" on-tap="handleTap"></x-element>
<template id="domIf" is="dom-if" if="[[prop]]">
<x-element id="ifElementLD" prop="{{prop}}" path="{{obj.path}}"></x-element>
</template>
</template>
<template id="templateWithDifferentProps">
<div id="bound">[[otherProp]]</div>
</template>
<script>
HTMLImports.whenReady(() => {
class XRuntime extends Polymer.GestureEventListeners(Polymer.Element) {
static get is() { return 'x-runtime'; }
static get observers() { return ['propChanged(prop)', 'pathChanged(obj.path)']; }
constructor() {
super();
this.propChanged = sinon.spy();
this.pathChanged = sinon.spy();
this.prop = 'prop';
this.obj = {path: 'obj.path'};
}
ready() {
super.setAttribute('log', '');
super.ready();
window.lifecycleOrder.log(this, 'ready');
}
compute(a, b) {
return `[${a} - ${b}]`;
}
stampTemplateFromShadow() {
let dom = this._stampTemplate(this.$.templateFromShadowDom);
this.shadowRoot.appendChild(dom);
return dom;
}
stampTemplateAndSetPropFromShadow() {
let dom = this._stampTemplate(this.$.templateFromShadowDom);
this.earlyProp = 'early';
this.shadowRoot.appendChild(dom);
return dom;
}
stampTemplateFromLight() {
let dom = this._stampTemplate(Polymer.DomModule.import(this.localName, '#templateFromLightDom'));
this.shadowRoot.appendChild(dom);
return dom;
}
stampTemplateWithDifferentProps() {
let dom = this._stampTemplate(Polymer.DomModule.import(this.localName, '#templateWithDifferentProps'));
this.shadowRoot.appendChild(dom);
return dom;
}
}
customElements.define('x-runtime', XRuntime);
});
</script>
</dom-module>
<template id="custom-template">
<x-special name="el1" special="attr1" binding="[[prop]]" on-event="handler"></x-special>
<div name="el2" special="attr2">
<div name="el3" special="attr3">
<x-special name="el4" special="attr4"></x-special>
</div>
<div></div><div></div><div></div>
<div name="el5" binding="[[prop]]" on-event="handler"></div>
<template name="el6">
<div>
<x-special name="t-el" special="t-attr" binding="[[prop]]" on-event="handler"></x-special>
</div>
</template>
</div>
<x-special name="el7" special="attr5"><x-special name="el8" special="attr6"></x-special></x-special>
</template>
<script>
HTMLImports.whenReady(() => {
class XParsing extends Polymer.Element {
static get template() { return document.getElementById('custom-template'); }
static _parseTemplateNodeAttribute(node, templateInfo, nodeInfo, name, value) {
if (name == 'special') {
nodeInfo.specialAttr = value;
node.removeAttribute('special');
node.setAttribute('had-special', '');
return true;
} else {
return super._parseTemplateNodeAttribute(node, templateInfo, nodeInfo, name, value);
}
}
static _parseTemplateNode(node, templateInfo, nodeInfo) {
let noted = super._parseTemplateNode(node, templateInfo, nodeInfo);
if (node.localName == 'x-special') {
noted = nodeInfo.specialNode = true;
}
return noted;
}
_bindTemplate(template) {
return this.templateInfoForTesting = super._bindTemplate(template);
}
}
customElements.define('x-parsing', XParsing);
class XEffects extends XParsing {
static get template() { return document.getElementById('custom-template'); }
static _parseTemplateNodeAttribute(node, templateInfo, nodeInfo, name, value) {
let noted = super._parseTemplateNodeAttribute(node, templateInfo, nodeInfo, name, value);
if (nodeInfo.specialAttr) {
this._addTemplatePropertyEffect(templateInfo, 'attr', {
fn(inst, property, props, oldProps, info, hasPaths, nodeList) {
nodeList[nodeInfo.infoIndex].specialAttr = props[property];
}
});
}
return noted;
}
static _parseTemplateNode(node, templateInfo, nodeInfo) {
let noted = super._parseTemplateNode(node, templateInfo, nodeInfo);
if (nodeInfo.specialNode) {
this._addTemplatePropertyEffect(templateInfo, 'node', {
fn(inst, property, props, oldProps, info, hasPaths, nodeList) {
nodeList[nodeInfo.infoIndex].specialNode = props[property];
}
});
}
return noted;
}
}
customElements.define('x-effects', XEffects);
});
</script>
<dom-module id="x-binding">
<template>
<x-element id="standard1" prop="[[prop]]" path="[[obj.path]]"></x-element>
<x-element id="custom1" prop='[{"a": "prop", "b": "prop2"}]'></x-element>
<div>
<x-element id="standard2" prop="[[prop]]" path="[[obj.path]]"></x-element>
<x-element id="custom2" prop='[{"a": "prop", "b": "prop2"}]'></x-element>
</div>
<template id="domIf" is="dom-if" if="[[prop2]]" restamp>
<x-element id="standard3" prop="[[prop]]" path="[[obj.path]]"></x-element>
<x-element id="custom3" prop='[{"a": "prop", "b": "prop2"}]'></x-element>
</template>
</template>
<script>
HTMLImports.whenReady(() => {
class XBinding extends Polymer.Element {
static get is() { return 'x-binding'; }
constructor() {
super();
this.prop = true;
this.obj = {path: 'obj.path'};
this.prop2 = true;
}
static _parseBindings(text, templateInfo) {
if (text.slice(0,2) == '[{' && text.slice(-2) == '}]') {
let bindingData = JSON.parse(text.slice(1,-1));
let dependencies = Object.keys(bindingData).map(n=>bindingData[n]);
return [{dependencies, bindingData}];
} else {
return super._parseBindings(text, templateInfo);
}
}
static _evaluateBinding(scope, part, path, props, oldProps, hasPaths) {
if (part.bindingData) {
return Object.keys(part.bindingData)
.map(n => scope[part.bindingData[n]] ? n : '')
.filter(c => Boolean(c))
.join(' ');
} else {
return super._evaluateBinding(scope, part, path, props, oldProps, hasPaths);
}
}
}
customElements.define(XBinding.is, XBinding);
});
</script>
</dom-module>
<script>
suite('runtime template stamping', function() {
let el;
setup(function() {
window.lifecycleOrder = {
log(el, lifecycle) {
if (this.shouldLog(el)) {
let list = this[lifecycle] = this[lifecycle] || [];
list.push(this.idFor(el));
}
},
shouldLog(el) {
let host = el.getRootNode().host;
return (!host || this.shouldLog(host)) && el.hasAttribute('log');
},
idFor(el) {
let host = el.getRootNode().host;
let id = el.getAttribute('log') || el.id;
return (host ? this.idFor(host) + '|' : '') + el.localName + (id ? '#' + id : '');
}
};
el = document.createElement('x-runtime');
document.body.appendChild(el);
});
teardown(function() {
document.body.removeChild(el);
});
function assertStampingCorrect(el, $, type) {
// Text binding
assert.equal($.textBinding.textContent, 'prop - obj.path - [prop - obj.path]');
// Property binding
assert.equal($.propBinding.prop, 'prop');
assert.equal($.propBinding.propChanged.callCount, 1);
assert.equal($.propBinding.propChanged.firstCall.args[0], 'prop');
// Path binding
assert.equal($.pathBinding.path, 'obj.path');
assert.equal($.pathBinding.pathChanged.callCount, 1);
assert.equal($.pathBinding.pathChanged.firstCall.args[0], 'obj.path');
// Compound property binding
assert.equal($.compoundPropBinding.compound, 'prop - obj.path - [prop - obj.path]');
// Observers
assert.equal(el.propChanged.callCount, 1);
assert.equal(el.propChanged.firstCall.args[0], 'prop');
assert.equal(el.pathChanged.callCount, 1);
assert.equal(el.pathChanged.firstCall.args[0], 'obj.path');
// Event handlers
el.handleClick = sinon.spy();
el.handleTap = sinon.spy();
$.events.click();
assert.equal(el.handleClick.callCount, 1);
assert.equal(el.handleTap.callCount, 1);
// Nested dom-* template
$.domIf.render();
let ifElement = el.shadowRoot.querySelector(`#ifElement${type||''}`);
assert.equal(ifElement.prop, 'prop');
assert.equal(ifElement.path, 'obj.path');
// Styling correct
assert.equal(getComputedStyle($.textBinding).borderBottomWidth, '10px');
}
test('prototypical stamping', () => {
assertStampingCorrect(el, el.$);
let stamped = el.shadowRoot.querySelectorAll('x-element#first');
assert.equal(stamped.length, 1);
assert.equal(stamped[0], el.$.first);
// Lifecycle order correct
assert.deepEqual(window.lifecycleOrder.ready, [
'x-runtime|x-element#proto|x-element-child#noBinding',
'x-runtime|x-element#proto|x-element-child#hasBinding',
'x-runtime|x-element#proto',
'x-runtime'
]);
});
test('runtime stamp template (from shadow dom)', () => {
let dom = el.stampTemplateFromShadow();
assertStampingCorrect(el, el.$);
assertStampingCorrect(el, dom.$, 'SD');
let stamped = el.shadowRoot.querySelectorAll('x-element#first');
assert.equal(stamped.length, 2);
assert.equal(stamped[0], el.$.first);
assert.equal(stamped[1], dom.$.first);
assert.deepEqual(window.lifecycleOrder.ready, [
'x-runtime|x-element#proto|x-element-child#noBinding',
'x-runtime|x-element#proto|x-element-child#hasBinding',
'x-runtime|x-element#proto',
'x-runtime',
'x-runtime|x-element#shadow|x-element-child#noBinding',
'x-runtime|x-element#shadow|x-element-child#hasBinding',
'x-runtime|x-element#shadow'
]);
});
test('runtime stamp and remove multiple templates (from shadow dom)', () => {
let stamped;
// Stamp template
let dom1 = el.stampTemplateFromShadow();
assertStampingCorrect(el, el.$);
assertStampingCorrect(el, dom1.$, 'SD');
stamped = el.shadowRoot.querySelectorAll('x-element#first');
assert.equal(stamped.length, 2);
assert.equal(stamped[0], el.$.first);
assert.equal(stamped[1], dom1.$.first);
// Unstamp
el._removeBoundDom(dom1);
for (let n in dom1.$) {
assert.notOk(dom1.$[n].parentNode, null);
}
stamped = el.shadowRoot.querySelectorAll('x-element#first');
assert.equal(stamped.length, 1);
assert.equal(stamped[0], el.$.first);
// Stamp again
let dom2 = el.stampTemplateFromShadow();
assertStampingCorrect(el, el.$);
assertStampingCorrect(el, dom2.$, 'SD');
stamped = el.shadowRoot.querySelectorAll('x-element#first');
assert.equal(stamped.length, 2);
assert.equal(stamped[0], el.$.first);
assert.equal(stamped[1], dom2.$.first);
// Stamp again
let dom3 = el.stampTemplateFromShadow();
assertStampingCorrect(el, el.$);
assertStampingCorrect(el, dom2.$, 'SD');
assertStampingCorrect(el, dom3.$, 'SD');
stamped = el.shadowRoot.querySelectorAll('x-element#first');
assert.equal(stamped.length, 3);
assert.equal(stamped[0], el.$.first);
assert.equal(stamped[1], dom2.$.first);
assert.equal(stamped[2], dom3.$.first);
assert.deepEqual(window.lifecycleOrder.ready, [
'x-runtime|x-element#proto|x-element-child#noBinding',
'x-runtime|x-element#proto|x-element-child#hasBinding',
'x-runtime|x-element#proto',
'x-runtime',
'x-runtime|x-element#shadow|x-element-child#noBinding',
'x-runtime|x-element#shadow|x-element-child#hasBinding',
'x-runtime|x-element#shadow',
'x-runtime|x-element#shadow|x-element-child#noBinding',
'x-runtime|x-element#shadow|x-element-child#hasBinding',
'x-runtime|x-element#shadow',
'x-runtime|x-element#shadow|x-element-child#noBinding',
'x-runtime|x-element#shadow|x-element-child#hasBinding',
'x-runtime|x-element#shadow'
]);
// Unstamp
el._removeBoundDom(dom2);
el._removeBoundDom(dom3);
for (let n in dom2.$) {
assert.notOk(dom1.$[n].parentNode, null);
}
for (let n in dom3.$) {
assert.notOk(dom1.$[n].parentNode, null);
}
stamped = el.shadowRoot.querySelectorAll('x-element#first');
assert.equal(stamped.length, 1);
assert.equal(stamped[0], el.$.first);
});
test('runtime stamp template and set prop before attaching (from shadow dom)', () => {
let dom = el.stampTemplateAndSetPropFromShadow();
assertStampingCorrect(el, el.$);
assertStampingCorrect(el, dom.$, 'SD');
let stamped = el.shadowRoot.querySelectorAll('x-element#first');
assert.equal(stamped.length, 2);
assert.equal(stamped[0], el.$.first);
assert.equal(stamped[1], dom.$.first);
assert.deepEqual(window.lifecycleOrder.ready, [
'x-runtime|x-element#proto|x-element-child#noBinding',
'x-runtime|x-element#proto|x-element-child#hasBinding',
'x-runtime|x-element#proto',
'x-runtime',
'x-element#shadow|x-element-child#noBinding',
'x-element#shadow|x-element-child#hasBinding',
'x-element#shadow'
]);
});
test('runtime stamp and remove multiple templates and set prop before attaching (from shadow dom)', () => {
let stamped;
// Stamp template
let dom1 = el.stampTemplateAndSetPropFromShadow();
assertStampingCorrect(el, el.$);
assertStampingCorrect(el, dom1.$, 'SD');
stamped = el.shadowRoot.querySelectorAll('x-element#first');
assert.equal(stamped.length, 2);
assert.equal(stamped[0], el.$.first);
assert.equal(stamped[1], dom1.$.first);
// Unstamp
el._removeBoundDom(dom1);
for (let n in dom1.$) {
assert.notOk(dom1.$[n].parentNode, null);
}
stamped = el.shadowRoot.querySelectorAll('x-element#first');
assert.equal(stamped.length, 1);
assert.equal(stamped[0], el.$.first);
// Stamp again
let dom2 = el.stampTemplateAndSetPropFromShadow();
assertStampingCorrect(el, el.$);
assertStampingCorrect(el, dom2.$, 'SD');
stamped = el.shadowRoot.querySelectorAll('x-element#first');
assert.equal(stamped.length, 2);
assert.equal(stamped[0], el.$.first);
assert.equal(stamped[1], dom2.$.first);
// Stamp again
let dom3 = el.stampTemplateAndSetPropFromShadow();
assertStampingCorrect(el, el.$);
assertStampingCorrect(el, dom2.$, 'SD');
assertStampingCorrect(el, dom3.$, 'SD');
stamped = el.shadowRoot.querySelectorAll('x-element#first');
assert.equal(stamped.length, 3);
assert.equal(stamped[0], el.$.first);
assert.equal(stamped[1], dom2.$.first);
assert.equal(stamped[2], dom3.$.first);
assert.deepEqual(window.lifecycleOrder.ready, [
'x-runtime|x-element#proto|x-element-child#noBinding',
'x-runtime|x-element#proto|x-element-child#hasBinding',
'x-runtime|x-element#proto',
'x-runtime',
'x-element#shadow|x-element-child#noBinding',
'x-element#shadow|x-element-child#hasBinding',
'x-element#shadow',
'x-runtime|x-element#shadow|x-element-child#noBinding',
'x-runtime|x-element#shadow|x-element-child#hasBinding',
'x-runtime|x-element#shadow',
'x-runtime|x-element#shadow|x-element-child#noBinding',
'x-runtime|x-element#shadow|x-element-child#hasBinding',
'x-runtime|x-element#shadow'
]);
// Unstamp
el._removeBoundDom(dom2);
el._removeBoundDom(dom3);
for (let n in dom2.$) {
assert.notOk(dom1.$[n].parentNode, null);
}
for (let n in dom3.$) {
assert.notOk(dom1.$[n].parentNode, null);
}
stamped = el.shadowRoot.querySelectorAll('x-element#first');
assert.equal(stamped.length, 1);
assert.equal(stamped[0], el.$.first);
});
test('runtime stamp template (from light dom)', () => {
let dom = el.stampTemplateFromLight();
assertStampingCorrect(el, el.$);
assertStampingCorrect(el, dom.$, 'LD');
let stamped = el.shadowRoot.querySelectorAll('x-element#first');
assert.equal(stamped.length, 2);
assert.equal(stamped[0], el.$.first);
assert.equal(stamped[1], dom.$.first);
assert.deepEqual(window.lifecycleOrder.ready, [
'x-runtime|x-element#proto|x-element-child#noBinding',
'x-runtime|x-element#proto|x-element-child#hasBinding',
'x-runtime|x-element#proto',
'x-runtime',
'x-runtime|x-element#light|x-element-child#noBinding',
'x-runtime|x-element#light|x-element-child#hasBinding',
'x-runtime|x-element#light'
]);
});
test('runtime stamp and remove multiple templates (from light dom)', () => {
let stamped;
// Stamp template
let dom1 = el.stampTemplateFromLight();
assertStampingCorrect(el, el.$);
assertStampingCorrect(el, dom1.$, 'LD');
stamped = el.shadowRoot.querySelectorAll('x-element#first');
assert.equal(stamped.length, 2);
assert.equal(stamped[0], el.$.first);
assert.equal(stamped[1], dom1.$.first);
// Unstamp
el._removeBoundDom(dom1);
for (let n in dom1.$) {
assert.notOk(dom1.$[n].parentNode, null);
}
// Stamp again
let dom2 = el.stampTemplateFromLight();
assertStampingCorrect(el, el.$);
assertStampingCorrect(el, dom2.$, 'LD');
stamped = el.shadowRoot.querySelectorAll('x-element#first');
assert.equal(stamped.length, 2);
assert.equal(stamped[0], el.$.first);
assert.equal(stamped[1], dom2.$.first);
// Stamp again
let dom3 = el.stampTemplateFromLight();
assertStampingCorrect(el, el.$);
assertStampingCorrect(el, dom2.$, 'LD');
assertStampingCorrect(el, dom3.$, 'LD');
stamped = el.shadowRoot.querySelectorAll('x-element#first');
assert.equal(stamped.length, 3);
assert.equal(stamped[0], el.$.first);
assert.equal(stamped[1], dom2.$.first);
assert.equal(stamped[2], dom3.$.first);
assert.deepEqual(window.lifecycleOrder.ready, [
'x-runtime|x-element#proto|x-element-child#noBinding',
'x-runtime|x-element#proto|x-element-child#hasBinding',
'x-runtime|x-element#proto',
'x-runtime',
'x-runtime|x-element#light|x-element-child#noBinding',
'x-runtime|x-element#light|x-element-child#hasBinding',
'x-runtime|x-element#light',
'x-runtime|x-element#light|x-element-child#noBinding',
'x-runtime|x-element#light|x-element-child#hasBinding',
'x-runtime|x-element#light',
'x-runtime|x-element#light|x-element-child#noBinding',
'x-runtime|x-element#light|x-element-child#hasBinding',
'x-runtime|x-element#light'
]);
// Unstamp
el._removeBoundDom(dom2);
el._removeBoundDom(dom3);
for (let n in dom2.$) {
assert.notOk(dom1.$[n].parentNode, null);
}
for (let n in dom3.$) {
assert.notOk(dom1.$[n].parentNode, null);
}
stamped = el.shadowRoot.querySelectorAll('x-element#first');
assert.equal(stamped.length, 1);
assert.equal(stamped[0], el.$.first);
});
function assertPropValues(el, prop, value, count) {
let e = el.$[prop + 'Binding'];
assert.equal(e[prop], value);
assert.equal(e[prop + 'Changed'].callCount, count);
assert.equal(e[prop + 'Changed'].getCall(count-1).args[0], value);
assert.equal(e.$.hasBinding[prop], value);
}
function assertAllPropValues(el, ld, sd, prop, value, count) {
assertPropValues(el, prop, value, count);
assertPropValues(ld, prop, value, count);
assertPropValues(sd, prop, value, count);
}
test('downward runtime binding', () => {
let sd = el.stampTemplateFromShadow();
let ld = el.stampTemplateFromLight();
assertAllPropValues(el, sd, ld, 'prop', 'prop', 1);
assertAllPropValues(el, sd, ld, 'path', 'obj.path', 1);
el.prop = 'prop+';
assertAllPropValues(el, sd, ld, 'prop', 'prop+', 2);
assertAllPropValues(el, sd, ld, 'path', 'obj.path', 1);
el.obj = {path: 'obj.path+'};
assertAllPropValues(el, sd, ld, 'prop', 'prop+', 2);
assertAllPropValues(el, sd, ld, 'path', 'obj.path+', 2);
el.set('obj.path', 'obj.path++');
assertAllPropValues(el, sd, ld, 'prop', 'prop+', 2);
assertAllPropValues(el, sd, ld, 'path', 'obj.path++', 3);
});
test('two-way runtime binding', () => {
let sd = el.stampTemplateFromShadow();
let ld = el.stampTemplateFromLight();
assertAllPropValues(el, sd, ld, 'prop', 'prop', 1);
assertAllPropValues(el, sd, ld, 'path', 'obj.path', 1);
el.$.propBinding.prop = 'prop+';
assertAllPropValues(el, sd, ld, 'prop', 'prop+', 2);
assertAllPropValues(el, sd, ld, 'path', 'obj.path', 1);
sd.$.propBinding.prop = 'prop++';
assertAllPropValues(el, sd, ld, 'prop', 'prop++', 3);
assertAllPropValues(el, sd, ld, 'path', 'obj.path', 1);
ld.$.propBinding.prop = 'prop+++';
assertAllPropValues(el, sd, ld, 'prop', 'prop+++', 4);
assertAllPropValues(el, sd, ld, 'path', 'obj.path', 1);
el.$.pathBinding.path = 'obj.path+';
assertAllPropValues(el, sd, ld, 'prop', 'prop+++', 4);
assertAllPropValues(el, sd, ld, 'path', 'obj.path+', 2);
sd.$.pathBinding.path = 'obj.path++';
assertAllPropValues(el, sd, ld, 'prop', 'prop+++', 4);
assertAllPropValues(el, sd, ld, 'path', 'obj.path++', 3);
ld.$.pathBinding.path = 'obj.path+++';
assertAllPropValues(el, sd, ld, 'prop', 'prop+++', 4);
assertAllPropValues(el, sd, ld, 'path', 'obj.path+++', 4);
});
test('accessors for non-prototypically bound properties created', () => {
// First element
let dom = el.stampTemplateWithDifferentProps();
el.otherProp = 'otherProp';
assert.equal(dom.$.bound.textContent, 'otherProp');
// Second element
let el2 = document.createElement('x-runtime');
document.body.appendChild(el2);
let dom2 = el2.stampTemplateWithDifferentProps();
el2.otherProp = 'otherProp';
assert.equal(dom2.$.bound.textContent, 'otherProp');
document.body.removeChild(el2);
});
test('prototypical stamping not affected by runtime stamping', () => {
assertStampingCorrect(el, el.$);
let stamped = el.shadowRoot.querySelectorAll('x-element#first');
assert.equal(stamped.length, 1);
assert.equal(stamped[0], el.$.first);
// Lifecycle order correct
assert.deepEqual(window.lifecycleOrder.ready, [
'x-runtime|x-element#proto|x-element-child#noBinding',
'x-runtime|x-element#proto|x-element-child#hasBinding',
'x-runtime|x-element#proto',
'x-runtime'
]);
});
});
suite('template parsing hooks', () => {
test('custom parsing', () => {
let el = document.createElement('x-parsing');
document.body.appendChild(el);
let templateInfo = el.templateInfoForTesting;
let nodeInfoList = templateInfo.nodeInfoList;
let nodeList = templateInfo.nodeList;
// The node order is depth-first bottom up but not a guarantee or generally
// important; as such, just ensure all expected nodes are there, then
// loop to
assert.sameMembers(nodeList.map(e=>e.getAttribute('name')),
['el1', 'el2', 'el3', 'el4', 'el5', 'el6', 'el7', 'el8']);
for (let i=0; i<nodeList.length; i++) {
let node = nodeList[i];
let nodeInfo = nodeInfoList[i];
let templateNodeInfo;
switch (node.getAttribute('name')) {
case 'el1':
assert.equal(nodeInfo.specialNode, true);
assert.equal(nodeInfo.specialAttr, 'attr1');
assert.equal(nodeInfo.bindings.length, 1);
assert.equal(nodeInfo.events.length, 1);
break;
case 'el2':
assert.equal(nodeInfo.specialAttr, 'attr2');
break;
case 'el3':
assert.equal(nodeInfo.specialAttr, 'attr3');
break;
case 'el4':
assert.equal(nodeInfo.specialNode, true);
assert.equal(nodeInfo.specialAttr, 'attr4');
break;
case 'el5':
assert.equal(nodeInfo.bindings.length, 1);
assert.equal(nodeInfo.events.length, 1);
break;
case 'el6':
assert.isOk(nodeInfo.templateInfo);
assert.equal(nodeInfo.templateInfo.nodeInfoList.length, 1);
templateNodeInfo = nodeInfo.templateInfo.nodeInfoList[0];
assert.equal(templateNodeInfo.bindings.length, 1);
assert.equal(templateNodeInfo.events.length, 1);
assert.equal(templateNodeInfo.specialAttr, 't-attr');
assert.equal(templateNodeInfo.specialNode, true);
break;
case 'el7':
assert.equal(nodeInfo.specialNode, true);
assert.equal(nodeInfo.specialAttr, 'attr5');
break;
case 'el8':
assert.equal(nodeInfo.specialNode, true);
assert.equal(nodeInfo.specialAttr, 'attr6');
break;
default:
throw new Error('unexpected node was recorded');
}
}
});
test('custom template effects', () => {
let el = document.createElement('x-effects');
document.body.appendChild(el);
assert.equal(Array.from(el.shadowRoot.querySelectorAll('x-special')).length, 4);
Array.from(el.shadowRoot.querySelectorAll('x-special')).forEach(e => {
assert.notOk(e.isSpecialNode);
});
el.node = 'node!';
Array.from(el.shadowRoot.querySelectorAll('x-special')).forEach(e => {
assert.equal(e.specialNode, 'node!');
});
assert.equal(Array.from(el.shadowRoot.querySelectorAll('[had-special]')).length, 6);
Array.from(el.shadowRoot.querySelectorAll('[had-special]')).forEach(e => {
assert.notOk(e.hasSpecialAttr);
});
el.attr = 'attr!';
Array.from(el.shadowRoot.querySelectorAll('[had-special]')).forEach(e => {
assert.equal(e.specialAttr, 'attr!');
});
document.body.removeChild(el);
});
test('custom template binding', () => {
let el = document.createElement('x-binding');
document.body.appendChild(el);
el.$.domIf.render();
assert.equal(el.$.standard1.prop, true);
assert.equal(el.$.standard2.prop, true);
assert.equal(el.shadowRoot.querySelector('#standard3').prop, true);
assert.equal(el.$.standard1.path, 'obj.path');
assert.equal(el.$.standard2.path, 'obj.path');
assert.equal(el.shadowRoot.querySelector('#standard3').path, 'obj.path');
assert.equal(el.$.custom1.prop, 'a b');
assert.equal(el.$.custom2.prop, 'a b');
assert.equal(el.shadowRoot.querySelector('#custom3').prop, 'a b');
el.prop = false;
assert.equal(el.$.standard1.prop, false);
assert.equal(el.$.standard2.prop, false);
assert.equal(el.shadowRoot.querySelector('#standard3').prop, false);
assert.equal(el.$.standard1.path, 'obj.path');
assert.equal(el.$.standard2.path, 'obj.path');
assert.equal(el.shadowRoot.querySelector('#standard3').path, 'obj.path');
assert.equal(el.$.custom1.prop, 'b');
assert.equal(el.$.custom2.prop, 'b');
assert.equal(el.shadowRoot.querySelector('#custom3').prop, 'b');
el.prop = true;
assert.equal(el.$.standard1.prop, true);
assert.equal(el.$.standard2.prop, true);
assert.equal(el.shadowRoot.querySelector('#standard3').prop, true);
assert.equal(el.$.standard1.path, 'obj.path');
assert.equal(el.$.standard2.path, 'obj.path');
assert.equal(el.shadowRoot.querySelector('#standard3').path, 'obj.path');
assert.equal(el.$.custom1.prop, 'a b');
assert.equal(el.$.custom2.prop, 'a b');
assert.equal(el.shadowRoot.querySelector('#custom3').prop, 'a b');
document.body.removeChild(el);
});
});
</script>
</body>
</html>