Fix templatizer memorization bug. Add x-repeat tests.

This commit is contained in:
Kevin Schaaf
2015-04-17 17:17:45 -07:00
parent 4369e724ed
commit 6a77bee2d2
3 changed files with 538 additions and 7 deletions

View File

@@ -31,8 +31,6 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
// `archetype` is the prototype of the anonymous
// class created by the templatizer
var archetype = Object.create(Polymer.Base);
var rootDataHost = this._getRootDataHost();
archetype._rootDataHost = rootDataHost;
// normally Annotations.parseAnnotations(template) but
// archetypes do special caching
this.customPrepAnnotations(archetype, template);
@@ -40,8 +38,6 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
archetype._prepEffects();
// forward parent properties to archetype
this._prepParentProperties(archetype);
// late-binds archetype.listen to host.listen; h.l doesn't exist yet
archetype.listen = rootDataHost.listen.bind(rootDataHost);
// boilerplate code
archetype._notifyPath = this._notifyPathImpl;
@@ -126,9 +122,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
var parentProp = '_parent_' + prop;
var effects = [{
kind: 'function',
effect: { function: function(prop, source, value) {
this._forwardParentProp(prop, value);
}.bind(this, prop)}
effect: { function: this._createForwardPropEffector(prop) }
}];
Polymer.Bind._createAccessors(proto, parentProp, effects);
}
@@ -136,11 +130,19 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
// Instance setup
if (this._templatized != this) {
Polymer.Bind.prepareInstance(this._templatized);
this._templatized._forwardParentProp =
this._forwardParentProp.bind(this);
}
this._extendTemplate(this._templatized, proto);
}
},
_createForwardPropEffector: function(prop) {
return function(source, value) {
this._forwardParentProp(prop, value);
};
},
// Similar to Polymer.Base.extend, but retains any previously set instance
// values (_propertySet back on instance once accessor is installed)
_extendTemplate: function(template, proto) {
@@ -186,6 +188,9 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
},
_constructorImpl: function(model, host) {
var rootDataHost = host._getRootDataHost();
this.listen = rootDataHost.listen.bind(rootDataHost);
this._rootDataHost = rootDataHost;
this._setupConfigure(model);
this._pushHost(host);
this.root = this.instanceTemplate(this._template);

View File

@@ -0,0 +1,251 @@
<script>
window.data = [
{
prop: 'prop-1',
items: [
{
prop: 'prop-1-1',
items: [
{ prop: 'prop-1-1-1' },
{ prop: 'prop-1-1-2' },
{ prop: 'prop-1-1-3' }
]
},
{
prop: 'prop-1-2',
items: [
{ prop: 'prop-1-1-1' },
{ prop: 'prop-1-1-2' },
{ prop: 'prop-1-1-3' }
]
},
{
prop: 'prop-1-3',
items: [
{ prop: 'prop-1-1-1' },
{ prop: 'prop-1-1-2' },
{ prop: 'prop-1-1-3' }
]
},
]
},
{
prop: 'prop-2',
items: [
{
prop: 'prop-2-1',
items: [
{ prop: 'prop-2-1-1' },
{ prop: 'prop-2-1-2' },
{ prop: 'prop-2-1-3' }
]
},
{
prop: 'prop-2-2',
items: [
{ prop: 'prop-2-2-1' },
{ prop: 'prop-2-2-2' },
{ prop: 'prop-2-2-3' }
]
},
{
prop: 'prop-2-3',
items: [
{ prop: 'prop-2-3-1' },
{ prop: 'prop-2-3-2' },
{ prop: 'prop-2-3-3' }
]
},
]
},
{
prop: 'prop-3',
items: [
{
prop: 'prop-3-1',
items: [
{ prop: 'prop-3-1-1' },
{ prop: 'prop-3-1-2' },
{ prop: 'prop-3-1-3' }
]
},
{
prop: 'prop-3-2',
items: [
{ prop: 'prop-3-2-1' },
{ prop: 'prop-3-2-2' },
{ prop: 'prop-3-2-3' }
]
},
{
prop: 'prop-3-3',
items: [
{ prop: 'prop-3-3-1' },
{ prop: 'prop-3-3-2' },
{ prop: 'prop-3-3-3' }
]
},
]
}
];
</script>
<dom-module id="x-foo">
<template>
<x-bar id="bar"
prop="{{prop}}"
item-prop="{{itemProp}}"
parent-prop="{{parentProp}}"
parent-item-prop="{{parentItemProp}}"
parent-parent-prop="{{parentParentProp}}"
parent-parent-item-prop="{{parentParentItemProp}}"
parent-parent-parent-prop="{{parentParentParentProp}}"
parent-parent-parent-item-prop="{{parentParentParentItemProp}}">
</x-bar>
</template>
</dom-module>
<script>
Polymer({
is: 'x-foo',
properties: {
prop: {
notify: true
},
itemProp: {
notify: true
},
parentProp: {
notify: true,
},
parentItemProp: {
notify: true
},
parentParentProp: {
notify: true,
},
parentParentItemProp: {
notify: true
},
parentParentParentProp: {
notify: true,
},
parentParentParentItemProp: {
notify: true
}
}
});
Polymer({
is: 'x-bar',
properties: {
prop: {
notify: true
},
itemProp: {
notify: true
},
parentProp: {
notify: true,
},
parentItemProp: {
notify: true
},
parentParentProp: {
notify: true,
},
parentParentItemProp: {
notify: true
},
parentParentParentProp: {
notify: true,
},
parentParentParentItemProp: {
notify: true
}
}
});
</script>
<dom-module id="x-nested-repeat">
<template>
<template id="repeater" is="x-repeat" items="{{items}}">
<x-foo prop="{{prop}}"
item-prop="{{item.prop}}"
parent-prop="{{parent.prop}}"
parent-item-prop="{{parent.item.prop}}">
</x-foo>
<template is="x-repeat" items="{{item.items}}">
<x-foo prop="{{prop}}"
item-prop="{{item.prop}}"
parent-prop="{{parent.prop}}"
parent-item-prop="{{parent.item.prop}}"
parent-parent-prop="{{parent.parent.prop}}"
parent-parent-item-prop="{{parent.parent.item.prop}}">
</x-foo>
<template is="x-repeat" items="{{item.items}}">
<x-foo prop="{{prop}}"
item-prop="{{item.prop}}"
parent-prop="{{parent.prop}}"
parent-item-prop="{{parent.item.prop}}"
parent-parent-prop="{{parent.parent.prop}}"
parent-parent-item-prop="{{parent.parent.item.prop}}"
parent-parent-parent-prop="{{parent.parent.parent.prop}}"
parent-parent-parent-item-prop="{{parent.parent.parent.item.prop}}">
</x-foo>
</template>
</template>
</template>
</template>
</dom-module>
<script>
Polymer({
is: 'x-nested-repeat'
});
</script>
<dom-module id="x-nested-repeat-configured">
<template>
<template id="repeater" is="x-repeat" items="{{items}}">
<x-foo prop="{{prop}}"
item-prop="{{item.prop}}"
parent-prop="{{parent.prop}}"
parent-item-prop="{{parent.item.prop}}">
</x-foo>
<template is="x-repeat" items="{{item.items}}">
<x-foo prop="{{prop}}"
item-prop="{{item.prop}}"
parent-prop="{{parent.prop}}"
parent-item-prop="{{parent.item.prop}}"
parent-parent-prop="{{parent.parent.prop}}"
parent-parent-item-prop="{{parent.parent.item.prop}}">
</x-foo>
<template is="x-repeat" items="{{item.items}}">
<x-foo prop="{{prop}}"
item-prop="{{item.prop}}"
parent-prop="{{parent.prop}}"
parent-item-prop="{{parent.item.prop}}"
parent-parent-prop="{{parent.parent.prop}}"
parent-parent-item-prop="{{parent.parent.item.prop}}"
parent-parent-parent-prop="{{parent.parent.parent.prop}}"
parent-parent-parent-item-prop="{{parent.parent.parent.item.prop}}">
</x-foo>
</template>
</template>
</template>
</template>
</dom-module>
<script>
Polymer({
is: 'x-nested-repeat-configured',
properties: {
items: {
value: window.data
},
prop: {
value: 'outer',
},
item: {
value: function() { return {prop: 'outerItem'}; }
}
}
});
</script>

275
test/unit/x-repeat.html Normal file
View File

@@ -0,0 +1,275 @@
<!doctype html>
<!--
@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
-->
<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.html">
<link rel="import" href="x-repeat-elements.html">
</head>
<body>
<x-nested-repeat-configured id="configured"></x-nested-repeat-configured>
<template is="x-autobind" id="unconfigured">
<x-nested-repeat id="unconfigured1" items="{{items}}"></x-nested-repeat>
<x-nested-repeat id="unconfigured2" items="{{items}}"></x-nested-repeat>
</template>
<script>
/*
Expected:
stamped[0] ... 1
stamped[1] ... 1-1
stamped[2] ... 1-1-1
stamped[3] ... 1-1-2
stamped[4] ... 1-1-3
stamped[5] ... 2
...
stamped[13] .. 2
...
stamped[36] .. 3-3-1
stamped[37] .. 3-3-2
stamped[38] .. 3-3-3
*/
suite('nested pre-configured x-repeat', function() {
test('basic rendering, downward item binding', function() {
var stamped = Polymer.dom(configured.root).querySelectorAll('*:not(template)');
assert.equal(stamped.length, 3 + 3*3 + 3*3*3, 'total stamped count incorrect');
assert.equal(stamped[0].itemProp, 'prop-1');
assert.equal(stamped[0].$.bar.itemProp, 'prop-1');
assert.equal(stamped[1].itemProp, 'prop-1-1');
assert.equal(stamped[1].$.bar.itemProp, 'prop-1-1');
assert.equal(stamped[2].itemProp, 'prop-1-1-1');
assert.equal(stamped[2].$.bar.itemProp, 'prop-1-1-1');
assert.equal(stamped[3].itemProp, 'prop-1-1-2');
assert.equal(stamped[3].$.bar.itemProp, 'prop-1-1-2');
assert.equal(stamped[4].itemProp, 'prop-1-1-3');
assert.equal(stamped[4].$.bar.itemProp, 'prop-1-1-3');
assert.equal(stamped[13].itemProp, 'prop-2');
assert.equal(stamped[13].$.bar.itemProp, 'prop-2');
assert.equal(stamped[36].itemProp, 'prop-3-3-1');
assert.equal(stamped[36].$.bar.itemProp, 'prop-3-3-1');
assert.equal(stamped[37].itemProp, 'prop-3-3-2');
assert.equal(stamped[37].$.bar.itemProp, 'prop-3-3-2');
assert.equal(stamped[38].itemProp, 'prop-3-3-3');
assert.equal(stamped[38].$.bar.itemProp, 'prop-3-3-3');
});
test('parent scope binding', function() {
var stamped = Polymer.dom(configured.root).querySelectorAll('*:not(template)');
assert.equal(stamped[0].parentProp, 'outer');
assert.equal(stamped[0].parentItemProp, 'outerItem');
assert.equal(stamped[1].parentItemProp, 'prop-1');
assert.equal(stamped[1].parentParentProp, 'outer');
assert.equal(stamped[1].parentParentItemProp, 'outerItem');
assert.equal(stamped[2].parentItemProp, 'prop-1-1');
assert.equal(stamped[2].parentParentItemProp, 'prop-1');
assert.equal(stamped[2].parentParentParentProp, 'outer');
assert.equal(stamped[2].parentParentParentItemProp, 'outerItem');
assert.equal(stamped[38].parentItemProp, 'prop-3-3');
assert.equal(stamped[38].parentParentItemProp, 'prop-3');
assert.equal(stamped[38].parentParentParentProp, 'outer');
assert.equal(stamped[38].parentParentParentItemProp, 'outerItem');
});
test('parent scope downward notification', function() {
var stamped = Polymer.dom(configured.root).querySelectorAll('*:not(template)');
configured.prop = 'yes';
assert.equal(stamped[0].parentProp, 'yes');
assert.equal(stamped[1].parentParentProp, 'yes');
assert.equal(stamped[2].parentParentParentProp, 'yes');
assert.equal(stamped[38].parentParentParentProp, 'yes');
configured.setPathValue('item.prop', 'yay');
assert.equal(stamped[0].parentItemProp, 'yay');
assert.equal(stamped[1].parentParentItemProp, 'yay');
assert.equal(stamped[2].parentParentParentItemProp, 'yay');
assert.equal(stamped[38].parentParentParentItemProp, 'yay');
});
test('parent upward upward notification', function() {
var stamped = Polymer.dom(configured.root).querySelectorAll('*:not(template)');
stamped[38].parentParentParentProp = 'nice';
assert.equal(configured.prop, 'nice');
assert.equal(stamped[0].parentProp, 'nice');
assert.equal(stamped[1].parentParentProp, 'nice');
assert.equal(stamped[2].parentParentParentProp, 'nice');
assert.equal(stamped[37].parentParentParentProp, 'nice');
stamped[38].parentParentParentItemProp = 'cool';
assert.equal(configured.item.prop, 'cool');
assert.equal(stamped[0].parentItemProp, 'cool');
assert.equal(stamped[1].parentParentItemProp, 'cool');
assert.equal(stamped[2].parentParentParentItemProp, 'cool');
assert.equal(stamped[37].parentParentParentItemProp, 'cool');
});
test('anonymous scope binding', function() {
var stamped = Polymer.dom(configured.root).querySelectorAll('*:not(template)');
stamped[1].$.bar.prop = 'changed';
assert.equal(stamped[1].prop, 'changed');
assert.equal(stamped[2].parentProp, 'changed');
assert.equal(stamped[3].parentProp, 'changed');
assert.equal(stamped[4].parentProp, 'changed');
});
});
suite('nested un-configured x-repeat', function() {
test('basic rendering, downward item binding', function(done) {
unconfigured.items = window.data;
setTimeout(function() {
var stamped = Polymer.dom(unconfigured1.root).querySelectorAll('*:not(template)');
assert.equal(stamped.length, 3 + 3*3 + 3*3*3, 'total stamped count incorrect');
assert.equal(stamped[0].itemProp, 'prop-1');
assert.equal(stamped[0].$.bar.itemProp, 'prop-1');
assert.equal(stamped[1].itemProp, 'prop-1-1');
assert.equal(stamped[1].$.bar.itemProp, 'prop-1-1');
assert.equal(stamped[2].itemProp, 'prop-1-1-1');
assert.equal(stamped[2].$.bar.itemProp, 'prop-1-1-1');
assert.equal(stamped[3].itemProp, 'prop-1-1-2');
assert.equal(stamped[3].$.bar.itemProp, 'prop-1-1-2');
assert.equal(stamped[4].itemProp, 'prop-1-1-3');
assert.equal(stamped[4].$.bar.itemProp, 'prop-1-1-3');
assert.equal(stamped[13].itemProp, 'prop-2');
assert.equal(stamped[13].$.bar.itemProp, 'prop-2');
assert.equal(stamped[36].itemProp, 'prop-3-3-1');
assert.equal(stamped[36].$.bar.itemProp, 'prop-3-3-1');
assert.equal(stamped[37].itemProp, 'prop-3-3-2');
assert.equal(stamped[37].$.bar.itemProp, 'prop-3-3-2');
assert.equal(stamped[38].itemProp, 'prop-3-3-3');
assert.equal(stamped[38].$.bar.itemProp, 'prop-3-3-3');
done();
});
});
test('parent scope binding', function() {
var stamped = Polymer.dom(unconfigured1.root).querySelectorAll('*:not(template)');
unconfigured1.prop = 'outer';
unconfigured1.item = {prop: 'outerItem'}
assert.equal(stamped[0].parentProp, 'outer');
assert.equal(stamped[0].parentItemProp, 'outerItem');
assert.equal(stamped[1].parentItemProp, 'prop-1');
assert.equal(stamped[1].parentParentProp, 'outer');
assert.equal(stamped[1].parentParentItemProp, 'outerItem');
assert.equal(stamped[2].parentItemProp, 'prop-1-1');
assert.equal(stamped[2].parentParentItemProp, 'prop-1');
assert.equal(stamped[2].parentParentParentProp, 'outer');
assert.equal(stamped[2].parentParentParentItemProp, 'outerItem');
assert.equal(stamped[38].parentItemProp, 'prop-3-3');
assert.equal(stamped[38].parentParentItemProp, 'prop-3');
assert.equal(stamped[38].parentParentParentProp, 'outer');
assert.equal(stamped[38].parentParentParentItemProp, 'outerItem');
});
test('parent scope downward notification', function() {
var stamped = Polymer.dom(unconfigured1.root).querySelectorAll('*:not(template)');
unconfigured1.prop = 'yes';
assert.equal(stamped[0].parentProp, 'yes');
assert.equal(stamped[1].parentParentProp, 'yes');
assert.equal(stamped[2].parentParentParentProp, 'yes');
assert.equal(stamped[38].parentParentParentProp, 'yes');
unconfigured1.setPathValue('item.prop', 'yay');
assert.equal(stamped[0].parentItemProp, 'yay');
assert.equal(stamped[1].parentParentItemProp, 'yay');
assert.equal(stamped[2].parentParentParentItemProp, 'yay');
assert.equal(stamped[38].parentParentParentItemProp, 'yay');
});
test('parent upward upward notification', function() {
var stamped = Polymer.dom(unconfigured1.root).querySelectorAll('*:not(template)');
stamped[38].parentParentParentProp = 'nice';
assert.equal(unconfigured1.prop, 'nice');
assert.equal(stamped[0].parentProp, 'nice');
assert.equal(stamped[1].parentParentProp, 'nice');
assert.equal(stamped[2].parentParentParentProp, 'nice');
assert.equal(stamped[37].parentParentParentProp, 'nice');
stamped[38].parentParentParentItemProp = 'cool';
assert.equal(unconfigured1.item.prop, 'cool');
assert.equal(stamped[0].parentItemProp, 'cool');
assert.equal(stamped[1].parentParentItemProp, 'cool');
assert.equal(stamped[2].parentParentParentItemProp, 'cool');
assert.equal(stamped[37].parentParentParentItemProp, 'cool');
});
test('anonymous scope binding', function() {
var stamped = Polymer.dom(unconfigured1.root).querySelectorAll('*:not(template)');
stamped[1].$.bar.prop = 'changed';
assert.equal(stamped[1].prop, 'changed');
assert.equal(stamped[2].parentProp, 'changed');
assert.equal(stamped[3].parentProp, 'changed');
assert.equal(stamped[4].parentProp, 'changed');
});
});
suite('array notification between two x-repeats', function() {
test('change to item from one x-repeat to other', function() {
var stamped1 = Polymer.dom(unconfigured1.root).querySelectorAll('*:not(template)');
var stamped2 = Polymer.dom(unconfigured2.root).querySelectorAll('*:not(template)');
assert.equal(stamped1[0].itemProp, 'prop-1');
assert.equal(stamped2[0].itemProp, 'prop-1');
stamped1[0].$.bar.itemProp = 'changed';
assert.equal(stamped2[0].itemProp, 'changed');
stamped2[0].$.bar.itemProp = 'back';
assert.equal(stamped1[0].itemProp, 'back');
assert.equal(stamped1[1].itemProp, 'prop-1-1');
assert.equal(stamped2[1].itemProp, 'prop-1-1');
stamped1[1].$.bar.itemProp = 'changed';
assert.equal(stamped2[1].itemProp, 'changed');
stamped2[1].$.bar.itemProp = 'back';
assert.equal(stamped1[1].itemProp, 'back');
assert.equal(stamped1[2].itemProp, 'prop-1-1-1');
assert.equal(stamped2[2].itemProp, 'prop-1-1-1');
stamped1[2].$.bar.itemProp = 'changed';
assert.equal(stamped2[2].itemProp, 'changed');
stamped2[2].$.bar.itemProp = 'back';
assert.equal(stamped1[2].itemProp, 'back');
assert.equal(stamped1[38].itemProp, 'prop-3-3-3');
assert.equal(stamped2[38].itemProp, 'prop-3-3-3');
stamped1[38].$.bar.itemProp = 'changed';
assert.equal(stamped2[38].itemProp, 'changed');
stamped2[38].$.bar.itemProp = 'back';
assert.equal(stamped1[38].itemProp, 'back');
});
test('change to non-item scope doesn\'t affect other x-repeat', function() {
var stamped1 = Polymer.dom(unconfigured1.root).querySelectorAll('*:not(template)');
var stamped2 = Polymer.dom(unconfigured2.root).querySelectorAll('*:not(template)');
unconfigured1.prop = 'foo';
unconfigured2.prop = 'bar';
assert.equal(stamped1[0].parentProp, 'foo');
assert.equal(stamped1[1].parentParentProp, 'foo');
assert.equal(stamped1[2].parentParentParentProp, 'foo');
assert.equal(stamped2[0].parentProp, 'bar');
assert.equal(stamped2[1].parentParentProp, 'bar');
assert.equal(stamped2[2].parentParentParentProp, 'bar');
stamped1[1].$.bar.prop = 'bar';
});
});
</script>
</body>
</html>