Skip to content

Commit

Permalink
Added tests for custom parsing, effects, and binding.
Browse files Browse the repository at this point in the history
  • Loading branch information
kevinpschaaf committed Apr 12, 2017
1 parent eb6ab63 commit 1cf955b
Showing 1 changed file with 299 additions and 13 deletions.
312 changes: 299 additions & 13 deletions test/unit/property-effects-template.html
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,17 @@

<dom-module id="x-element">
<template>
[[prop]] - [[path]]
<x-element-child id="noBinding" log></x-element>
<x-element-child id="hasBinding" prop="{{prop}}" 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();
Expand Down Expand Up @@ -138,6 +140,124 @@
</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>
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;
nodeInfo.infoIndex = templateInfo.nodeInfoList.length;
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') {
nodeInfo.infoIndex = templateInfo.nodeInfoList.length;
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='[{"prop": "prop", "prop2": "prop2"}]'></x-element>
<div>
<x-element id="standard2" prop="[[prop]]" path="[[obj.path]]"></x-element>
<x-element id="custom2" prop='[{"prop": "prop", "prop2": "prop2"}]'></x-element>
</div>
<template id="domIf" is="dom-if" if="[[prop]]" restamp>
<x-element id="standard3" prop="[[prop]]" path="[[obj.path]]"></x-element>
<x-element id="custom3" prop='[{"prop": "prop", "prop2": "prop2"}]'></x-element>
</template>
</template>
<script>
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() {
Expand Down Expand Up @@ -203,7 +323,7 @@
assert.equal(getComputedStyle($.textBinding).borderBottomWidth, '10px');
}

test('prototypical stamping', ()=> {
test('prototypical stamping', () => {
assertStampingCorrect(el, el.$);
let stamped = el.shadowRoot.querySelectorAll('x-element#first');
assert.equal(stamped.length, 1);
Expand All @@ -217,7 +337,7 @@
]);
});

test('runtime stamp template (from shadow dom)', ()=> {
test('runtime stamp template (from shadow dom)', () => {
let dom = el.stampTemplateFromShadow();
assertStampingCorrect(el, el.$);
assertStampingCorrect(el, dom.$, 'SD');
Expand All @@ -236,7 +356,7 @@
]);
});

test('runtime stamp and remove multiple templates (from shadow dom)', ()=> {
test('runtime stamp and remove multiple templates (from shadow dom)', () => {
let stamped;
// Stamp template
let dom1 = el.stampTemplateFromShadow();
Expand Down Expand Up @@ -298,7 +418,7 @@
assert.equal(stamped[0], el.$.first);
});

test('runtime stamp template (from light dom)', ()=> {
test('runtime stamp template (from light dom)', () => {
let dom = el.stampTemplateFromLight();
assertStampingCorrect(el, el.$);
assertStampingCorrect(el, dom.$, 'LD');
Expand All @@ -317,7 +437,7 @@
]);
});

test('runtime stamp and remove multiple templates (from light dom)', ()=> {
test('runtime stamp and remove multiple templates (from light dom)', () => {
let stamped;
// Stamp template
let dom1 = el.stampTemplateFromLight();
Expand Down Expand Up @@ -379,19 +499,185 @@
assert.equal(stamped[0], el.$.first);
});

test('runtime stamp and remove multiple templates (from light dom)', ()=> {
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();
assert.equal(el.$.propBinding.prop, 'prop');
assert.equal(sd.$.propBinding.prop, 'prop');
assert.equal(ld.$.propBinding.prop, 'prop');
assert.equal(el.$.pathBinding.path, 'obj.path');
assert.equal(sd.$.pathBinding.path, 'obj.path');
assert.equal(ld.$.pathBinding.path, 'obj.path');
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);
});

});

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];
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)
let 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, 'prop prop2');
assert.equal(el.$.custom2.prop, 'prop prop2');
assert.equal(el.shadowRoot.querySelector('#custom3').prop, 'prop prop2');
el.prop = false;
el.$.domIf.render();
assert.equal(el.$.standard1.prop, false);
assert.equal(el.$.standard2.prop, false);
assert.equal(el.$.standard1.path, 'obj.path');
assert.equal(el.$.standard2.path, 'obj.path');
assert.notOk(el.shadowRoot.querySelector('#standard3'));
assert.equal(el.$.custom1.prop, 'prop2');
assert.equal(el.$.custom2.prop, 'prop2');
assert.equal(el.shadowRoot.querySelector('#custom3'));
el.prop = true;
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, 'prop prop2');
assert.equal(el.$.custom2.prop, 'prop prop2');
assert.equal(el.shadowRoot.querySelector('#custom3').prop, 'prop prop2');
document.body.removeChild(el);
});
});

</script>

</body>
Expand Down

0 comments on commit 1cf955b

Please sign in to comment.