@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
1,332 lines (1,133 loc) • 68.8 kB
HTML
<!doctype 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.html">
<link rel="import" href="property-effects-elements.html">
<body>
<dom-repeat id="class-repeat" items='["class1", "class2"]'>
<template>
<div class$=[[item]] clazz$="[[item]]">[[item]]</div>
</template>
</dom-repeat>
<script>
suite('single-element binding effects', function() {
var el;
setup(function() {
el = document.createElement('x-basic');
document.body.appendChild(el);
});
teardown(function() {
document.body.removeChild(el);
});
test('undefined input value', function() {
assert.equal(el.$.boundInput.value, '', 'undefined input value not blank');
el.text = 'this is a test';
assert.equal(el.$.boundInput.value, 'this is a test', 'binding to input didn\'t go');
el.text = undefined;
assert.equal(el.$.boundInput.value, '', 'undefined input value not blank');
});
test('undefined textarea value', function() {
assert.equal(el.$.boundTextArea.value, '', 'undefined textarea value not blank');
el.text = 'this is a test';
assert.equal(el.$.boundTextArea.value, 'this is a test', 'binding to textarea didn\'t go');
el.text = undefined;
assert.equal(el.$.boundTextArea.value, '', 'undefined textarea value not blank');
});
test('id is bindable', function() {
assert.equal(el.root.querySelector('span[idtest]').id, 'span', 'id bound to <span> not found');
});
test('textContent binding updates', function() {
el.text = 'this is a test';
assert.equal(el.$.boundText.textContent, 'this is a test', 'Value not propagated to textContent');
});
test('textContent binding to undefined is empty string', function() {
el.text = 'this is a test';
el.text = undefined;
assert.equal(el.$.boundText.textContent, '', 'undefined bound to textContent should be empty string');
});
test('textContent binding to null is empty string', function() {
el.text = null;
assert.equal(el.$.boundText.textContent, '', 'null bound to textContent should be empty string');
});
test('textContent binding to zero is empty correct', function() {
el.text = 0;
assert.equal(el.$.boundText.textContent, '0', 'zero bound to textContent should be empty string');
});
test('camel-case binding updates', function() {
el.value = 41;
assert.equal(el.$.boundChild.camelCase, 41, 'Value not propagated to camelCase property');
});
test('annotation binding updates', function() {
el.value = 42;
assert.equal(el.$.boundChild.value, 42, 'Value not propagated to bound child');
});
test('negated annotation binding updates', function() {
el.bool = true;
assert.equal(el.$.boundChild.negvalue, false, 'Value not negated');
el.bool = false;
assert.equal(el.$.boundChild.negvalue, true, 'Value not negated');
});
test('observer called', function() {
assert.equal(el.observerCounts.valueChanged, 1, 'observer not called once for default value at configure');
el.value = 43;
assert.equal(el.observerCounts.valueChanged, 2, 'observer not called after property change');
});
test('observer called only once for null', function() {
el.clearObserverCounts();
assert.equal(el.observerCounts.valueChanged, 0);
el.value = {};
assert.equal(el.observerCounts.valueChanged, 1);
el.value = null;
assert.equal(el.observerCounts.valueChanged, 2);
el.value = null;
assert.equal(el.observerCounts.valueChanged, 2);
el.value = {};
assert.equal(el.observerCounts.valueChanged, 3);
el.value = null;
assert.equal(el.observerCounts.valueChanged, 4);
el.value = null;
assert.equal(el.observerCounts.valueChanged, 4);
});
test('computed value updates', function() {
el.value = 44;
assert.equal(el.computedvalue, 45, 'Computed value not correct');
assert.equal(el.$.boundChild.computedvalue, 45, 'Computed value not propagated to bound child');
});
test('computed value readOnly from imperative set', function() {
el.value = 44;
// Should have no effect
el.computedvalue = 99;
assert.equal(el.computedvalue, 45, 'Computed value not correct');
assert.equal(el.$.boundChild.computedvalue, 45, 'Computed value not propagated to bound child');
});
test('computed values to same method updates', function() {
el.value = 44;
el.valuetwo = 144;
assert.equal(el.computedvalue, 45, 'Computed value not correct');
assert.equal(el.computedvaluetwo, 145, 'Computed value not correct');
assert.equal(el.$.boundChild.computedvalue, 45, 'Computed value not propagated to bound child');
});
test('computed value using computing fn from behavior', function() {
el.value = 44;
assert.equal(el.$.boundChild.computedFromBehavior, 'computed:44');
el.computeFromBehavior = function(value) {
return 'new:' + value;
};
assert.equal(el.$.boundChild.computedFromBehavior, 'new:44');
});
test('notification sent', function() {
var notified = 0;
el.addEventListener('notifyingvalue-changed', function(e) {
assert.equal(e.detail.value, 45);
notified++;
});
el.addEventListener('camel-notifying-value-changed', function(e) {
assert.equal(e.detail.value, 45);
notified++;
});
el.notifyingvalue = 45;
el.camelNotifyingValue = 45;
assert.equal(notified, 2, 'Notification events not sent');
});
test('computed observer called', function() {
el.clearObserverCounts();
el.value = 46;
assert.equal(el.observerCounts.computedvalueChanged, 1, 'observer not called');
});
test('NaN does not loop observers', function() {
el.clearObserverCounts();
el.addEventListener('notifierwithoutcomputing-changed', function() {
if (el.observerCounts.notifierWithoutComputingChanged >= 3) {
throw new Error('infinite loop!');
}
});
el.notifierWithoutComputing = NaN;
assert.equal(el.observerCounts.notifierWithoutComputingChanged, 1,
'NaN was not handled as expected');
});
test('computed notification sent', function() {
var notified = 0;
el.addEventListener('computednotifyingvalue-changed', function(e) {
assert.equal(e.detail.value, 49);
notified++;
});
el.notifyingvalue = 47;
assert.equal(notified, 1, 'Notification event not sent');
});
test('computed property with multiple dependencies', function() {
var notified = 0;
el.addEventListener('computed-from-multiple-values-changed', function() {
notified++;
});
el.sum1 = 10;
el.sum2 = 20;
el.divide = 2;
assert.equal(el.computedFromMultipleValues, 15, 'Computed value wrong');
assert.equal(notified, 1, 'Notification event not sent');
assert.equal(el.observerCounts.computedFromMultipleValuesChanged, 1, 'observer not called');
});
test('computed annotation with literals', function() {
el.bool = true;
assert.equal(el.$.boundChild.computedFromMixedLiterals, '3foo', 'Wrong result from mixed literal arg computation');
assert.equal(el.$.boundChild.computedFromPureLiterals, '3foo', 'Wrong result from pure literal arg computation');
assert.equal(el.$.boundChild.computedFromTrickyFunction, '3foo', 'Wrong result from tricky function with pure literal arg computation');
assert.equal(el.$.boundChild.computedFromTrickyLiterals, '3tricky,\'zot\'', 'Wrong result from tricky literal arg computation');
assert.equal(el.$.boundChild.computedFromTrickyLiterals2, '3tricky,\'zot\'', 'Wrong result from tricky literal arg computation');
assert.equal(el.$.boundChild.computedFromTrickyLiterals3, '3tricky,\'zot\'', 'Wrong result from tricky literal arg computation');
assert.equal(el.$.computedContent.textContent, '3tricky,\'zot\'', 'Wrong textContent from tricky literal arg computation');
assert.equal(el.$.computedContent2.textContent, '(3', 'Wrong textContent from tricky literal arg computation');
});
test('computed annotation with no args', function() {
assert.equal(el.$.boundChild.computedFromNoArgs, 'no args!', 'Wrong content when computed has no args');
});
test('no read-only observer called with assignment', function() {
el.readonlyvalue = 46;
assert.equal(el.observerCounts.readonlyvalueChanged, 0, 'observer should not be called for readOnly prop assignment');
});
test('read-only observer called with _setReadonlyvalue', function() {
el._setReadonlyvalue(46);
assert.equal(el.observerCounts.readonlyvalueChanged, 1, 'observer should be called');
assert(el.readonlyvalue == 46, 'value should be changed but was not');
});
test('no read-only notification sent with assignment', function() {
var notified = 0;
el.addEventListener('readonlyvalue-changed', function() {
notified++;
});
el.readonlyvalue = 47;
assert.equal(notified, 0, 'Notification should not be called for readOnly prop assignment');
});
test('read-only notification sent with _setReadonlyvalue', function() {
var notified = 0;
el.addEventListener('readonlyvalue-changed', function(e) {
assert.equal(e.detail.value, 47);
notified++;
});
el._setReadonlyvalue(47);
assert.equal(notified, 1, 'Notification event not sent');
});
test('multiple dependency observer called once', function() {
el.setProperties({
dep1: true,
dep2: {},
dep3: 42
});
assert.equal(el.observerCounts.multipleDepChangeHandler, 1, 'observer not called once');
});
test('setProperties does not set readOnly by default', function() {
assert.equal(el.observerCounts.valueChanged, 1);
assert.equal(el.observerCounts.readonlyvalueChanged, 0);
el.setProperties({
value: 'shouldChange',
nofx: 'shouldChange',
readonlyvalue: 'shouldNotChange'
});
assert.equal(el.value, 'shouldChange');
assert.equal(el.nofx, 'shouldChange');
assert.equal(el.readonlyvalue, undefined);
assert.equal(el.observerCounts.valueChanged, 2);
assert.equal(el.observerCounts.readonlyvalueChanged, 0);
});
test('setProperties sets readOnly using `setPrivate` arg', function() {
assert.equal(el.observerCounts.valueChanged, 1);
assert.equal(el.observerCounts.readonlyvalueChanged, 0);
el.setProperties({
value: 'shouldChange',
nofx: 'shouldChange',
readonlyvalue: 'shouldChange'
}, true);
assert.equal(el.value, 'shouldChange');
assert.equal(el.nofx, 'shouldChange');
assert.equal(el.readonlyvalue, 'shouldChange');
assert.equal(el.observerCounts.valueChanged, 2);
assert.equal(el.observerCounts.readonlyvalueChanged, 1);
});
test('annotated computed property', function() {
el.value = 20;
el.add = 40;
el.divide = 3;
assert.equal(el.$.boundChild.computedInline, 20, 'computedInline not correct');
assert.equal(el.$.boundChild.computedInline2, 20, 'computedInline2 not correct');
assert.equal(el.$.boundChild.computedInline3, 20, 'computedInline3 not correct');
assert.equal(el.$.boundChild.negComputedInline, false, 'negComputedInline not correct');
});
test('annotated computed attribute', function() {
el.value = 20;
el.add = 40;
el.divide = 3;
assert.equal(el.$.boundChild.getAttribute('computedattribute'), 20, 'computed attribute not correct');
assert.equal(el.$.boundChild.getAttribute('computedattribute2'), 20, 'computed attribute not correct');
});
test('annotated style attribute binding', function() {
el.boundStyle = 'padding: 37px;';
assert.equal(getComputedStyle(el.$.boundChild).paddingTop, '37px', 'style attribute binding not correct');
});
test('annotated dataset attribute binding', function() {
if (el.$.boundChild.datast) { // IE10, sigh
el.dataSetId = 'yeah';
assert.equal(el.$.boundChild.dataset.id, 'yeah', 'dataset.id dataset property not set correctly');
assert.equal(el.$.boundChild.getAttribute('data-id'), 'yeah', 'data-id attribute not set correctly');
}
});
test('custom notification event to property', function() {
el.$.boundChild.customEventValue = 42;
el.fire('custom', null, {node: el.$.boundChild});
assert.equal(el.customEventValue, 42, 'custom bound property incorrect');
assert.equal(el.observerCounts.customEventValueChanged, 1, 'custom bound property observer not called');
});
test('custom notification bubbling event to property', function() {
const child = document.createElement('div');
el.$.boundChild.appendChild(child);
el.$.boundChild.customEventValue = 42;
child.dispatchEvent(new Event('custom', {bubbles: true}));
assert.equal(el.customEventValue, 42, 'custom bound property incorrect');
assert.equal(el.observerCounts.customEventValueChanged, 1, 'custom bound property observer not called');
});
test('custom notification event to path', function() {
el.clearObserverCounts();
el.$.boundChild.customEventObjectValue = 84;
el.$.boundChild.dispatchEvent(new Event('change'));
assert.equal(el.customEventObject.value, 84, 'custom bound path incorrect');
assert.equal(el.observerCounts.customEventObjectValueChanged, 1, 'custom bound path observer not called');
});
test('computed property with negative number', function() {
assert.equal(el.$.boundChild.computedNegativeNumber, -1);
});
test('computed property with negative literal', function() {
assert.equal(el.$.boundChild.computedNegativeLiteral, undefined);
});
test('computed binding with wildcard', function() {
el.a = 5;
el.b = {value: 10};
assert.equal(el.$.boundChild.computedWildcard, 15);
});
test('binding with dash', function() {
el.objectWithDash = {
'binding-with-dash': 'yes'
};
assert.equal(el.$.boundWithDash.textContent, 'yes');
});
test('class attribute without template scope not erased', function() {
var el = document.querySelector('.class1');
assert.notEqual(el, null, 'class without template scope is undefined');
var el2 = document.querySelector('.class2');
assert.notEqual(el2, null, 'class without template scope is undefined');
});
test('property effect for native property (title)', function() {
// NOTE: This will FAIL on browser on which `title` is an own property so
// just let this pass.
if (document.createElement('div').hasOwnProperty('title')) {
this.skip();
}
assert.isTrue(el.titleChanged.notCalled);
el.title = 'a title';
assert.isTrue(el.titleChanged.calledOnce);
assert.equal(el.titleChanged.firstCall.args[0], 'a title');
});
test('property effect added at instance time', function() {
el.earlyBoundObserver = sinon.spy();
el.lateBoundObserver = sinon.spy();
el.earlyBound = 'early';
el._createPropertyObserver('earlyBound', 'earlyBoundObserver');
el._createPropertyObserver('lateBound', 'lateBoundObserver');
el._flushProperties();
el.lateBound = 'late';
assert.equal(el.earlyBound, 'early');
assert.equal(el.earlyBoundObserver.callCount, 1);
assert.equal(el.earlyBoundObserver.firstCall.args[0], 'early');
assert.equal(el.lateBound, 'late');
assert.equal(el.lateBoundObserver.callCount, 1);
assert.equal(el.lateBoundObserver.firstCall.args[0], 'late');
});
test('does not parse bindings inside <script>', function() {
assert.include(el.$.scriptWithBinding.textContent, "{{binding}}");
});
test('does not parse bindings inside <style>', function() {
var style = el.shadowRoot.querySelector('style');
// native shadow dom
if (style) {
assert.include(style.textContent, "[[binding]]");
// shady dom
} else {
assert.include(document.querySelector('[scope="x-basic"]').textContent, "[[binding]]");
}
});
test('only calls dynamic functions once', function() {
el.dynamicFn = function() {
assert.isFalse(this._dynamicFnCalled);
this._dynamicFnCalled = true;
};
});
suite('observer inheritance', function() {
setup(function() {
el = document.createElement('sub-observer-element');
document.body.appendChild(el);
});
test('does not invoke observer twice', function() {
assert.equal(el.__observerCalled, 1);
});
});
suite('automated attribute capitalization detection', function() {
let el;
setup(function() {
el = document.createElement('svg-element');
document.body.appendChild(el);
});
teardown(function() {
document.body.removeChild(el);
});
test('can handle capitalized HTML attribute', function() {
assert.equal(el.$.svg.getAttribute('viewBox'), el.value);
});
});
suite('can work with strict binding parser', function() {
setup(function() {
document.body.removeChild(el);
el = document.createElement('x-basic-strict-binding-parser');
document.body.appendChild(el);
});
test('binding with slash', function() {
el.objectWithSlash = {
'binding/with/slash': 'yes'
};
assert.equal(el.$.boundWithSlash.textContent, 'yes');
});
test('json should not be a binding', function() {
assert.equal(el.$.jsonContent.textContent, '[["Jan", 31],["Feb", 28],["Mar", 31]]');
});
test('binding with non-English unicode', function() {
el.objectWithNonEnglishUnicode = {
商品名: 'yes'
};
assert.equal(el.$.nonEnglishUnicode.textContent, 'yes');
});
test('binding with booleans', function() {
el.otherValue = 10;
assert.equal(el.$.booleanTrue.textContent, 'foo(field, true): 10');
assert.equal(el.$.booleanFalse.textContent, 'foo(field, false): 20');
});
suite('equivalent behavior as regex', function() {
// Loop over the suite "single-element binding effects" (parent of the parent of this suite)
// And make sure that the tests there also pass on the binding-parser
//
// t.title is the name of the test and t.fn contains the test body
this.parent.parent.tests.forEach(t => {
test(t.title, t.fn);
});
});
});
});
suite('computed bindings with dynamic functions', function() {
var el;
setup(function() {
sinon.spy(console, 'warn');
});
teardown(function() {
document.body.removeChild(el);
console.warn.restore();
});
test('annotated computation with dynamic function', function() {
el = document.createElement('x-bind-computed-property');
document.body.appendChild(el);
assert.equal(el.$.check.textContent, 'translated: Hello World.');
el.translator = function(message) {
return 'changed: ' + message;
};
assert.equal(el.$.check.textContent, 'changed: Hello World.');
assert.equal(console.warn.callCount, 0);
});
test('annotated computation / late resolved dynamic function', function() {
el = document.createElement('x-bind-computed-property-late-translator');
document.body.appendChild(el);
assert.equal(el.$.check.textContent.trim(), '');
el.translator = function(message) {
return 'translated: ' + message;
};
assert.equal(el.$.check.textContent, 'translated: Hello');
assert.equal(console.warn.callCount, 0);
});
test('method observer with dynamic function', function() {
Polymer({
is: 'x-method-observer-with-dynamic-function',
properties: {
translateMessage: {
type: Function
},
message: {
type: String,
value: 'Hello'
}
},
observers: ['translateMessage(message)']
});
el = document.createElement('x-method-observer-with-dynamic-function');
document.body.appendChild(el);
el.translateMessage = sinon.spy();
assert.equal(el.translateMessage.callCount, 1);
assert.equal(console.warn.callCount, 0);
});
test('observer with dynamic function', function() {
Polymer({
is: 'x-observer-with-dynamic-function',
properties: {
messageChanged: {
type: Function
},
message: {
type: String,
value: 'Hello',
observer: 'messageChanged'
}
}
});
el = document.createElement('x-observer-with-dynamic-function');
document.body.appendChild(el);
el.messageChanged = sinon.spy();
assert.equal(el.messageChanged.callCount, 1);
assert.equal(console.warn.callCount, 0);
});
test('computed property with dynamic function', function() {
Polymer({
is: 'x-computed-property-with-dynamic-function',
properties: {
computedValue: {
computed: "translateMessage('Hello')"
},
translateMessage: {
type: Function
}
}
});
el = document.createElement('x-computed-property-with-dynamic-function');
document.body.appendChild(el);
assert.equal(el.computedValue, undefined);
var called = 0;
el.translateMessage = function(message) {
called += 1;
return 'translated: ' + message;
};
assert.equal(called, 1);
assert.equal(el.computedValue, 'translated: Hello');
assert.equal(console.warn.callCount, 0);
});
test('ensure annotator can pass dynamic fn to parent props', function(done) {
el = document.createElement('x-child-template-with-dynamic-fn');
document.body.appendChild(el);
setTimeout(function() {
var check = el.root.querySelector('p');
assert.equal(check.textContent, 'text');
assert.equal(console.warn.callCount, 0);
done();
});
});
});
suite('2-way binding effects between elements', function() {
var el;
setup(function() {
el = document.createElement('x-compose');
document.body.appendChild(el);
});
teardown(function() {
document.body.removeChild(el);
});
test('binding to non-notifying property', function() {
el.boundvalue = 42;
assert.equal(el.$.basic1.value, 42, 'binding to child not updated');
el.$.basic1.value = 43;
assert.equal(el.boundvalue, 42, 'binding to non-notifying property updated and should not have been');
});
test('observer for property bound to non-notifying property', function() {
el.$.basic1.value = 44;
assert.equal(el.observerCounts.boundvalueChanged, 0, 'observer for property bound to non-notifying property called and should not have been');
});
test('binding to non-notifying computed property', function() {
el.boundcomputedvalue = 42;
el.$.basic1.value = 43;
assert.equal(el.boundcomputedvalue, 42, 'binding to non-notifying computed property updated and should not have been');
});
test('observer for property bound to non-notifying computed property', function() {
el.$.basic1.value = 44;
assert.equal(el.observerCounts.boundcomputedvalueChanged, 0, 'observer for property bound to non-notifying computed property called and should not have been');
});
test('computed value readOnly from downward binding', function() {
el.$.basic3.value = 10;
assert.equal(el.$.basic3.computedvalue, 11);
// should have no effect
el.value = 99;
assert.equal(el.$.basic3.computedvalue, 11);
});
test('computed value readOnly from upward notification', function() {
assert.equal(el.computedValue, 30);
// should have no effect
el.$.basic3.notifyingvalue = 10;
assert.equal(el.computedValue, 30);
});
test('binding to notifying property', function() {
el.boundnotifyingvalue = 42;
assert.equal(el.$.basic1.notifyingvalue, 42, 'binding to child not updated');
assert.equal(el.$.basic1.camelNotifyingValue, 42, 'camel-case binding to child not updated');
el.$.basic1.notifyingvalue = 43;
assert.equal(el.boundnotifyingvalue, 43, 'binding to notifying property not updated');
el.$.basic1.camelNotifyingValue = -43;
assert.equal(el.boundnotifyingvalue, -43, 'camel-case binding to notifying property not updated');
});
test('binding to notifying property with default', function() {
assert.equal(el.boundnotifyingvalueWithDefault, 99);
});
test('observer for property bound to notifying property', function() {
el.$.basic1.notifyingvalue = 45;
assert.equal(el.observerCounts.boundnotifyingvalueChanged, 1, 'observer for property bound to notifying property not called');
});
test('binding to notifying computed property', function() {
el.$.basic1.notifyingvalue = 43;
assert.equal(el.boundcomputednotifyingvalue, 45, 'binding to notifying computed property not updated');
});
test('observer for property bound to notifying computed property', function() {
el.$.basic1.notifyingvalue = 45;
assert.equal(el.observerCounts.boundcomputednotifyingvalueChanged, 1, 'observer for property bound to non-notifying computed property not called');
});
test('no change for binding into read-only property', function() {
el.$.basic1._setReadonlyvalue(45);
el.$.basic1.clearObserverCounts();
el.boundreadonlyvalue = 46;
assert.equal(el.$.basic1.observerCounts.readonlyvalueChanged, 0, 'observer for read-only property should not be called from change to bound value');
assert.equal(el.$.basic1.readonlyvalue, 45, 'read-only property should not change from change to bound value');
});
test('change for binding out of read-only property', function() {
el.$.basic1._setReadonlyvalue(46);
assert.equal(el.observerCounts.boundreadonlyvalueChanged, 1, 'observer for property bound to read-only property should be called from change to bound value');
assert.equal(el.boundreadonlyvalue, 46, 'property bound to read-only property should change from change to bound value');
});
test('negated binding update negates value for parent', function() {
assert.equal(el.negatedValue, false);
assert.equal(el.$.basic4.notifyingvalue, true);
el.$.basic4.notifyingvalue = false;
assert.equal(el.negatedValue, true);
});
test('custom xxx-changed event notifies correctly', function() {
assert.equal(el.boundCustomNotifyingValue, undefined);
assert.equal(el.observerCounts.boundCustomNotifyingValueChanged, 0);
el.$.basic1.fireCustomNotifyingEvent();
assert.equal(el.boundCustomNotifyingValue, 'changed!');
assert.equal(el.observerCounts.boundCustomNotifyingValueChanged, 1);
});
});
suite('handling notifying evevnts', function() {
test('handle notification event and set property with observer when connected', function() {
var el = document.createElement('x-handle-notify-event');
document.body.appendChild(el);
assert.ok(el.shadowRoot.querySelector('#before'), 'element not found before default notifying element');
assert.ok(el.shadowRoot.querySelector('#later'), 'element not found after default notifying element');
assert.isTrue(el.readySpy.calledOnce, 'ready called more than once');
assert.isTrue(el.handleNotify.calledOnce, 'listener called more than once');
assert.isTrue(el.propChanged.calledOnce, 'observer called more than once');
assert.isTrue(el.handleNotify.calledBefore(el.propChanged), 'observer called before event');
assert.isTrue(el.afterSettingProp.calledBefore(el.propChanged), 'accessor side effect processed during notifying event (before clients ready)');
assert.isTrue(el.propChanged.calledBefore(el.readySpy), 'observer called before ready');
document.body.removeChild(el);
});
test('handle notification event and set property with observer when *not* connected and _enableProperties called', function() {
var el = document.createElement('x-handle-notify-event');
el._enableProperties();
assert.ok(el.shadowRoot.querySelector('#before'), 'element not found before default notifying element');
assert.ok(el.shadowRoot.querySelector('#later'), 'element not found after default notifying element');
assert.isTrue(el.readySpy.calledOnce, 'ready called more than once');
assert.isTrue(el.handleNotify.calledOnce, 'listener called more than once');
assert.isTrue(el.propChanged.calledOnce, 'observer called more than once');
assert.isTrue(el.handleNotify.calledBefore(el.propChanged), 'observer called before event');
assert.isTrue(el.afterSettingProp.calledBefore(el.propChanged), 'accessor side effect processed during notifying event (before clients ready)');
assert.isTrue(el.propChanged.calledBefore(el.readySpy), 'observer called before ready');
});
});
suite('1-way binding effects between elements', function() {
var el;
setup(function() {
el = document.createElement('x-compose');
document.body.appendChild(el);
});
teardown(function() {
document.body.removeChild(el);
});
test('one-way binding to non-notifying property', function() {
el.boundvalue = 42;
assert.equal(el.$.basic1.value, 42, 'binding to child not updated');
el.$.basic2.value = 43;
assert.equal(el.boundvalue, 42, 'binding to non-notifying property updated and should not have been');
});
test('observer for property one-way-bound to non-notifying property', function() {
el.$.basic2.value = 44;
assert.equal(el.observerCounts.boundvalueChanged, 0, 'observer for property one-way-bound to non-notifying property called and should not have been');
});
test('one-way binding to non-notifying computed property', function() {
el.boundcomputedvalue = 42;
el.$.basic2.value = 43;
assert.equal(el.boundcomputedvalue, 42, 'binding to non-notifying computed property updated and should not have been');
});
test('observer for property one-way-bound to non-notifying computed property', function() {
el.$.basic2.value = 44;
assert.equal(el.observerCounts.boundcomputedvalueChanged, 0, 'observer for property bound to non-notifying computed property called and should not have been');
});
test('one-way binding to notifying property', function() {
el.boundnotifyingvalue = 42;
assert.equal(el.$.basic2.notifyingvalue, 42, 'binding to child not updated');
el.$.basic2.notifyingvalue = 43;
assert.equal(el.boundnotifyingvalue, 42, 'binding to notifying property updated and should not have been');
});
test('observer for property one-way-bound to notifying property', function() {
el.$.basic2.notifyingvalue = 45;
assert.equal(el.observerCounts.boundnotifyingvalueChanged, 0, 'observer for property bound to notifying property called and should not have been');
});
test('one-way binding to notifying computed property', function() {
el.boundcomputednotifyingvalue = 42;
el.$.basic2.notifyingvalue = 43;
assert.equal(el.boundcomputednotifyingvalue, 42, 'binding to notifying computed property updated and should not have been');
});
test('observer for property one-way-bound to notifying computed property', function() {
el.$.basic2.notifyingvalue = 45;
assert.equal(el.observerCounts.boundcomputednotifyingvalueChanged, 0, 'observer for property bound to non-notifying computed property called and should not have been');
});
});
suite('reflection to attribute', function() {
var el;
setup(function() {
el = document.createElement('x-reflect');
document.body.appendChild(el);
});
teardown(function() {
document.body.removeChild(el);
});
test('reflect object', function() {
var obj = {foo: 'bar', array: [1, '2', {3:3}]};
el.reflectedobject = obj;
assert.equal(el.getAttribute('reflectedobject'), '{"foo":"bar","array":[1,"2",{"3":3}]}');
// Ensure object wasn't re-deserialized
assert.equal(el.reflectedobject, obj);
el.reflectedobject = null;
assert(!el.hasAttribute('reflectedobject'));
});
test('reflect array', function() {
var arr = [1, '2', {3:3}, {'four': 'four'}];
el.reflectedarray = arr;
assert.equal(el.getAttribute('reflectedarray'), '[1,"2",{"3":3},{"four":"four"}]');
// Ensure array wasn't re-deserialized
assert.equal(el.reflectedarray, arr);
el.reflectedarray = null;
assert(!el.hasAttribute('reflectedarray'));
});
test('reflect string', function() {
var str = '"polymer is grrrrreat, ain\'t it?"';
el.reflectedstring = str;
assert.equal(el.getAttribute('reflectedstring'), str);
assert.equal(el.reflectedstring, str);
el.reflectedstring = '';
assert.equal(el.getAttribute('reflectedstring'), '');
assert.equal(el.reflectedstring, '');
el.reflectedstring = null;
assert(!el.hasAttribute('reflectedstring'));
assert.equal(el.reflectedstring, null);
});
test('reflect number', function() {
el.reflectedNumber = 765;
assert.equal(el.getAttribute('reflected-number'), '765');
assert.equal(el.reflectedNumber, 765);
el.reflectedNumber = 765.4321;
assert.equal(el.getAttribute('reflected-number'), '765.4321');
assert.equal(el.reflectedNumber, 765.4321);
el.reflectedNumber = null;
assert(!el.hasAttribute('reflected-number'));
assert.equal(el.reflectedNumber, null);
});
test('reflect boolean', function() {
el.reflectedboolean = true;
assert(el.hasAttribute('reflectedboolean'));
assert.equal(el.getAttribute('reflectedboolean'), '');
assert.equal(el.reflectedboolean, true);
el.reflectedboolean = false;
assert(!el.hasAttribute('reflectedboolean'));
assert.equal(el.reflectedboolean, false);
el.reflectedboolean = true;
el.reflectedboolean = null;
assert(!el.hasAttribute('reflectedboolean'));
assert.equal(el.reflectedboolean, null);
});
test('reflect date', function() {
var date = new Date('Fri Jan 23 2015 17:40:29 GMT-0800 (PST)');
el.reflecteddate = date;
assert(el.hasAttribute('reflecteddate'));
assert.equal(Date.parse(el.getAttribute('reflecteddate')),
el.reflecteddate.getTime());
assert.equal(el.reflecteddate, date);
el.reflecteddate = null;
assert(!el.hasAttribute('reflecteddate'));
assert.equal(el.reflecteddate, null);
});
test('reflect wrong type', function() {
el.reflectedstring = true;
assert(el.hasAttribute('reflectedstring'));
assert.equal(el.getAttribute('reflectedstring'), '');
// Ensure value wasn't re-deserialized
assert.strictEqual(el.reflectedstring, true);
});
});
suite('binding to attribute', function() {
var el;
setup(function() {
el = document.createElement('x-basic');
document.body.appendChild(el);
});
teardown(function() {
document.body.removeChild(el);
});
test('bind object to attribute', function() {
el.attrvalue = {foo: 'bar', array: [1, '2', {3:3}]};
assert.equal(el.$.boundChild.getAttribute('attrvalue'),
'{"foo":"bar","array":[1,"2",{"3":3}]}');
el.attrvalue = null;
assert(!el.$.boundChild.hasAttribute('attrvalue'));
});
test('bind array to attribute', function() {
el.attrvalue = [1, '2', {3:3}, {'four': 'four'}];
assert.equal(el.$.boundChild.getAttribute('attrvalue'),
'[1,"2",{"3":3},{"four":"four"}]');
el.attrvalue = null;
assert(!el.$.boundChild.hasAttribute('attrvalue'));
});
test('bind string to attribute', function() {
el.attrvalue = '"polymer is grrrrreat, ain\'t it?"';
assert.equal(el.$.boundChild.getAttribute('attrvalue'),
'"polymer is grrrrreat, ain\'t it?"');
el.attrvalue = '';
assert.equal(el.$.boundChild.getAttribute('attrvalue'), '');
el.attrvalue = null;
assert(!el.$.boundChild.hasAttribute('attrvalue'));
});
test('bind number to attribute', function() {
el.attrvalue = 765;
assert.equal(el.$.boundChild.getAttribute('attrvalue'), '765');
el.attrvalue = 765.4321;
assert.equal(el.$.boundChild.getAttribute('attrvalue'), '765.4321');
el.attrvalue = null;
assert(!el.$.boundChild.hasAttribute('attrvalue'));
});
test('bind boolean to attribute', function() {
el.attrvalue = true;
assert(el.$.boundChild.hasAttribute('attrvalue'));
assert.equal(el.$.boundChild.getAttribute('attrvalue'), '');
el.attrvalue = false;
assert(!el.$.boundChild.hasAttribute('attrvalue'));
el.attrvalue = true;
el.attrvalue = null;
assert(!el.$.boundChild.hasAttribute('attrvalue'));
});
test('bind date to attribute', function() {
el.attrvalue = new Date('Fri Jan 23 2015 17:40:29 GMT-0800 (PST)');
assert(el.$.boundChild.hasAttribute('attrvalue'));
assert.equal(Date.parse(el.$.boundChild.getAttribute('attrvalue')),
el.attrvalue.getTime());
el.attrvalue = null;
assert(!el.$.boundChild.hasAttribute('attrvalue'));
});
test('bind to value attribute on input should not fail', function() {
var el = document.createElement('x-input-value');
document.body.appendChild(el);
el.inputValue = "the value";
assert.equal(el.$.input.value, "the value", "The value of the input is not propagated");
assert.equal(el.$.input.value$, undefined, "value$ should be removed from input");
document.body.removeChild(el);
});
});
suite('avoid non-bubbling event gotchas', function() {
var el;
var container;
setup(function() {
container = document.createElement('div');
document.body.appendChild(container);
container.innerHTML = '<x-notifies3></x-notifies3>';
el = container.firstChild;
});
teardown(function() {
document.body.removeChild(container);
});
test('avoid non-bubbling event gotchas', function() {
el.$.notifies2.$.notifies1.notifies = 'runtimeValue';
assert.equal(el.$.notifies2.$.notifies1.notifies, 'runtimeValue');
assert.equal(el.$.notifies2.shouldChange, 'runtimeValue');
assert.notEqual(el.shouldNotChange, 'runtimeValue');
});
test('avoid non-bubbling event gotchas at ready time', function() {
assert.equal(el.$.notifies2.$.notifies1.notifies, 'readyValue');
assert.equal(el.$.notifies2.shouldChange, 'readyValue');
assert.notEqual(el.shouldNotChange, 'readyValue');
});
});
suite('warnings', function() {
var el;
var warn; //eslint-disable-line no-unused-vars
setup(function() {
sinon.spy(console, 'warn');
el = document.createElement('x-basic');
document.body.appendChild(el);
});
teardown(function() {
console.warn.restore();
document.body.removeChild(el);
});
test('undefined observer', function() {
el.noObserver = 42;
assert.isTrue(console.warn.calledOnce, 'no warning for undefined observer');
});
test('undefined complex observer', function() {
el.noComplexObserver = {};
assert.isTrue(console.warn.calledOnce, 'no warning for undefined complex observer');
});
test('undefined computed function', function() {
el.noComputed = 99;
assert.isTrue(console.warn.calledOnce, 'no warning for undefined computed function');
});
test('undefined inline computed function', function() {
el.noInlineComputed = 99;
assert.isTrue(console.warn.calledOnce, 'no warning for undefined computed function');
});
test('binding to a bad attribute warns', function() {
document.createElement('x-bind-bad-attribute-name');
assert.isTrue(console.warn.calledOnce, 'no warning for setting a bad attribute');
});
});
suite('binding corner cases', function() {
// IE can create adjacent text nodes that split bindings; this test
// ensures the code that addresses this is functional
test('text binding after entity', function() {
var el = document.createElement('x-entity-and-binding');
document.body.appendChild(el);
assert.equal(el.$.binding.textContent, 'binding');
document.body.removeChild(el);
});
test('bind to isAttached', function() {
var el = document.createElement('x-bind-is-attached');
sinon.spy(el, '_isAttachedChanged');
document.body.appendChild(el);
assert.equal(el.$.check.textContent, 'true');
assert.isTrue(el._isAttachedChanged.calledOnce);
document.body.removeChild(el);
});
});
suite('binding interop', function() {
test('do not set same value to a non-Polymer element', function() {
var el = document.createElement('x-interop');
document.body.appendChild(el);
assert.equal(el.$.raw.value, 10);
assert.equal(el.$.raw.valueChanged.callCount, 1);
assert.equal(el.$.raw.valueChanged.firstCall.args[0], 10);
// Should set value
el.value++;
assert.equal(el.$.raw.value, 11);
assert.equal(el.$.raw.valueChanged.callCount, 2);
assert.equal(el.$.raw.valueChanged.secondCall.args[0], 11);
// Notifies up to host, should not be re-set down to element
el.$.raw.increment();
assert.equal(el.$.raw.value, 12);
assert.equal(el.$.raw.valueChanged.callCount, 2);
document.body.removeChild(el);
});
test('path notification of object bound to textContent of a Polymer element', function() {
var el = document.createElement('x-interop');
document.body.appendChild(el);
// Initial state
assert.equal(el.$.polymer.textContent, '1,2,3');
// Push
el.push('array', 4);
assert.equal(el.$.polymer.textContent, '1,2,3,4');
document.body.removeChild(el);
});
test('path notification of array bound to setter of a Polymer element', function() {
var el = document.createElement('x-interop');
document.body.appendChild(el);
// Initial state
assert.equal(el.$.polymer.array, el.array);
assert.equal(el.$.polymer.arrayChanged.callCount, 1);
assert.equal(el.$.polymer.arrayChanged.firstCall.args[0], el.array);
// Push
el.push('array', 4);
assert.equal(el.$.polymer.array, el.array);
// Gets called twice, once for splice info, once for length :(
assert.equal(el.$.polymer.arrayChanged.callCount, 3);
assert.equal(el.$.polymer.arrayChanged.secondCall.args[0], el.array);
assert.equal(el.$.polymer.arrayChanged.thirdCall.args[0], el.array);
document.body.removeChild(el);
});
test('path notification of array in compound binding to property of Polymer element', function() {
var el = document.createElement('x-interop');
document.body.appendChild(el);
// Initial state
assert.equal(el.$.polymer.compound, '**1,2,3**');
assert.equal(el.$.polymer.compoundChanged.callCount, 1);
assert.equal(el.$.polymer.compoundChanged.firstCall.args[0], '**1,2,3**');
// Push
el.push('array', 4);
assert.equal(el.$.polymer.compound, '**1,2,3,4**');
assert.equal(el.$.polymer.compoundChanged.callCount, 2);
assert.equal(el.$.polymer.compoundChanged.secondCall.args[0], '**1,2,3,4**');
document.body.removeChild(el);
});
test('path notification of array bound to setter of a non-Polymer element', function() {
var el = document.createElement('x-interop');
document.body.appendChild(el);
// Initial state
assert.equal(el.$.raw.textContent, '1,2,3');
assert.equal(el.$.raw.arrayChanged.callCount, 1);
assert.equal(el.$.raw.arrayChanged.firstCall.args[0], el.array);
// Push
el.push('array', 4);
assert.equal(el.$.raw.textContent, '1,2,3,4');
// Array change notifies once for splice info, once for length, and
// since the property is a Array, it passes the dirty check and will go
// through twice; this test also happens to not have a getter, so that
// would cause the dirty check to pass even if it wasn't an Object
assert.equal(el.$.raw.arrayChanged.callCount, 3);
assert.equal(el.$.raw.arrayChanged.secondCall.args[0], el.array);
assert.equal(el.$.raw.arrayChanged.thirdCall.args[0], el.array);
document.body.removeChild(el);
});
test('path notification of array in compound binding to property of non-Polymer element', function() {
var el = document.createElement('x-interop');
document.body.appendChild(el);
// Initial state
assert.equal(el.$.raw.compound, '**1,2,3**');
// Raw elements with compound bindings will first see the
// literals, then the properties, hence 2 sets initially
assert.equal(el.$.raw.compoundChanged.callCount, 2);
assert.equal(el.$.raw.compoundChanged.firstCall.args[0], '****');
assert.equal(el.$.raw.compoundChanged.secondCall.args[0], '**1,2,3**');
// Push
el.push('array', 4);
assert.equal(el.$.raw.compound, '**1,2,3,4**');
assert.equal(el.$.raw.compoundChanged.callCount, 3);
assert.equal(el.$.raw.compoundChanged.thirdCall.args[0], '**1,2,3,4**');
document.body.removeChild(el);
});
});
suite('compound binding / string interpolation', function() {
var el;
setup(function() {
el = document.createElement('x-basic');
document.body.appendChild(el);
});
teardown(function() {
document.body.removeChild(el);
});
test('compound adjacent property bindings', function() {
// Adjacent compound binding with no literal do not override the default
assert.equal(el.$.boundProps.prop1, 'default');
assert.isTrue(el.$.boundProps.prop1Changed.calledOnce);
el.cpnd2 = 'cpnd2';
assert.equal(el.$.boundProps.prop1, 'cpnd2');
el.cpnd1 = 'cpnd1';
el.cpnd3 = {prop: 'cpnd3'};
assert.equal(el.$.boundProps.prop1, 'cpnd1cpnd2cpnd3');
el.cpnd4 = 'cpnd4';
assert.equal(el.$.boundProps.prop1, 'cpnd1cpnd2cpnd3literalComputedcpnd4');
el.cpnd5 = 'cpnd5';
assert.equal(el.$.boundProps.prop1, 'cpnd1cpnd2cpnd3literalComputedcpnd5cpnd4');
});
test('compound property bindings with literals', function() {
assert.equal(el.$.boundProps.prop2, 'literal1 literal2 literal3 literal4');
assert.isTrue(el.$.boundProps.prop2Changed.calledOnce);
el.cpnd1 = 'cpnd1';
el.cpnd2 = 'cpnd2';
el.cpnd3 = {prop: 'cpnd3'};
el.cpnd4 = 'cpnd4';
el.cpnd5 = 'cpnd5';
assert.equal(el.$.boundProps.prop2, 'literal1 cpnd1 literal2 cpnd2cpnd3 literal3 literalComputedcpnd5cpnd4 literal4');
el.cpnd1 = null;
el.cpnd2 = undefined;
el.cpnd3 = {};
el.cpnd4 = '';
el.cpnd5 = '';
assert.equal(el.$.boundProps.prop2, 'literal1 literal2 literal3 literalComputed literal4');
});
test('compound adjacent attribute bindings', function() {
// Adjacent compound binding with no literal do not override the default
assert.equal(el.$.boundChild.getAttribute('compoundAttr1'), null);
el.cpnd2 = 'cpnd2';
assert.equal(el.$.boundChild.getAttribute('compoundAttr1'), 'cpnd2');
el.cpnd1 = 'cpnd1';
el.cpnd3 = {prop: 'cpnd3'};
assert.equal(el.$.boundChild.getAttribute('compoundAttr1'), 'cpnd1cpnd2cpnd3');
el.cpnd4 = 'cpnd4';
assert.equal(el.$.boundChild.getAttribute('compoundAttr1'), 'cpnd1cpnd2cpnd3literalComputedcpnd4');
el.cpnd5 = 'cpnd5';
assert.equal(el.$.boundChild.getAttribute('compoundAttr1'), 'cpnd1cpnd2cpnd3literalComputedcpnd5cpnd4');
});
test('compound property attribute with literals', function() {
assert.equal(el.$.boundChild.getAttribute('compoundAttr2'), 'literal1 literal2 literal3 literal4');
el.cpnd1 = 'cpnd1';
el.cpnd2 = 'cpnd2';
el.cpnd3 = {prop: 'cpnd3'};
el.cpnd4 = 'cpnd4';
el.cpnd5 = 'cpnd5';
assert.equal(el.$.boundChild.getAttribute('compoundAttr2'), 'literal1 cpnd1 literal2 cpnd2cpnd3 literal3 literalComputedcpnd5cpnd4 literal4');
el.cpnd1 = null;
el.cpnd2 = undefined;
el.cpnd3 = {};
el.cpnd4 = '';
el.cpnd5 = '';
assert.equal(el.$.boundChild.getAttribute('compoundAttr2'), 'literal1 literal2 literal3 literalComputed literal4');
});
test('compound property attribute with {} and [] in text', function() {
el.cpnd1 = 'cpnd1';
assert.equal(el.$.boundChild.getAttribute('compoundAttr3'), '[yes/no]: cpnd1, Hello {0} username world');
});
test('compound adjacent textNode bindings', function() {
// The single space is due to the gambit to prevent empty text nodes
// from being omitted by IE during importNode from the template; it will
// only be there when in the virgin state after cloning the template
assert.equal(el.$.compound1.textContent, ' ');
el.cpnd2 = 'cpnd2';
assert.equal(el.$.compound1.textContent, 'cpnd2');
el.cpnd1 = 'cpnd1';
el.cpnd3 = {prop: 'cpnd3'};
assert.equal(el.$.compound1.textContent, 'cpnd1cpnd2cpnd3');
el.cpnd4 = 'cpnd4';
assert.equal(el.$.compound1.textContent, 'cpnd1cpnd2cpnd3literalComputedcpnd4');
el.cpnd5 = 'cpnd5';
assert.equal(el.$.compound1.textContent, 'cpnd1cpnd2cpnd3literalComputedcpnd5cpnd4');
// Once the binding evaluates back to '', it will in fact be ''
el.computeCompound = function() { return ''; };
el.cpnd1 = null;
el.cpnd2 = '';
el.cpnd3 = {prop: null};
el.cpnd4 = null;
el.cpnd5 = '';
assert.equal(el.$.compound1.textContent, '');
});
test('compound textNode bindings with literals', function() {
assert.equal(el.$.compound2.textContent.trim(), 'literal1 literal2 literal3 literal4');
el.cpnd1 = 'cpnd1';
el.cpnd2 = 'cpnd2';
el.cpnd3 = {prop: 'cpnd3'};
el.cpnd4 = 'cpnd4';
el.cpnd5 = 'cpnd5';
assert.equal(el.$.compound2.textContent.trim(), 'literal1 cpnd1 literal2 cpnd2cpnd3 literal3 literalComputedcpnd5cpnd4 literal4');
el.cpnd1 = null;
el.cpnd2 = undefined;
el.cpnd3 = {};
el.cpnd4 = '';
el.cpnd5 = '';
assert.equal(el.$.compound2.textContent.trim(), 'literal1 literal2 literal3 literalComputed literal4');
});
test('malformed bindings ignored', function() {
el.bool = true;
assert.isTrue(el.$.boundChild.textContent.indexOf('really.long.identifier.in.malformed.binding.should.be.ignored') >= 0, true);
assert.isTrue(el.$.boundChild.textContent.indexOf('really.long.l