@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
793 lines (643 loc) • 19.2 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.html">
<body>
<dom-module id="behavior-registered">
<template>
<div id="content"></div>
</template>
</dom-module>
<dom-module id="template-from-base">
<template>
<div id="from-base">should not be used</div>
</template>
</dom-module>
<script>
HTMLImports.whenReady(function() {
Polymer.setLegacyOptimizations(Boolean(window.location.search.match('legacyOptimizations')));
window.BehaviorA = {
properties: {
label: {
type: String,
observer: '_labelChanged'
},
hasOptionsA: {
readOnly: true,
notify: true
},
overridableProperty: {
value: false
},
overridablePropertyB: {
value: false
},
hasBehaviorA: {
value: true
},
computeADependency: {
value: true
},
computeA: {
computed: '_computeProp(computeADependency)'
}
},
observers: [],
_simpleProperty: 'A',
hostAttributes: {
behavior: 'A',
element: 'A',
user: 'A'
},
listeners: {
change: '_changeHandler'
},
ready: function() {
this.__readyA = true;
},
_labelChanged: function(label) {
this.__label = label;
},
_changeHandler: function(e) {
this.__change = e.detail.value;
},
_computeProp: function(a) {
return a;
}
};
window.BehaviorB = {
properties: {
disabled: {
type: Boolean,
value: false,
observer: '_disabledChanged'
},
hasOptionsB: {
readOnly: true,
notify: true
},
hasBehaviorB: {
value: true
},
overridablePropertyB: {
value: true
},
computeADependencyDependency: {
value: 'hi'
},
computeADependency: {
computed: '_computeProp(computeADependencyDependency)'
}
},
hostAttributes: {
behavior: 'B',
element: 'B',
user: 'B'
},
_simpleProperty: 'B',
_disabledChanged: function(disabled) {
this.__disabled = disabled;
},
ready: function() {
this.__readyB = true;
},
};
window.BehaviorC = {
properties: {
hasBehaviorC: {
value: true
}
},
_simpleProperty: 'C'
};
window.BehaviorD = {
properties: {
hasBehaviorD: {
value: true
}
},
_simpleProperty: 'D'
};
Polymer({
is: 'single-behavior',
behaviors: [
window.BehaviorA
],
properties: {},
observers: [],
hostAttributes: {},
listeners: {}
});
Polymer({
is: 'multi-behaviors',
behaviors: [
window.BehaviorA,
window.BehaviorB
],
hostAttributes: {
element: 'element'
},
properties: {
foo: {
type: String,
reflectToAttribute: true,
readOnly: true,
observer: '_fooChanged'
},
overridableProperty: {
value: true
}
},
_fooChanged: function(foo) {
this.__foo = foo;
},
});
Polymer({
is: 'nested-behaviors',
behaviors: [
[window.BehaviorB, [window.BehaviorC, window.BehaviorB], window.BehaviorA],
[window.BehaviorD]
]
});
window.registerBehavior1 ={
beforeRegister: function() {
this._createPropertyObserver('beforeProp', 'beforePropChanged1');
this.beforeRegisterCount++;
this.beforeRegisterBehaviors = this.behaviors;
},
registered: function() {
this._createPropertyObserver('prop', 'propChanged1');
this._createMethodObserver('propChanged2(prop)');
this.registeredCount++;
this.registeredProps = [this.prop1, this.prop2, this.prop3, this.prop4];
this.registeredBehaviors = this.behaviors;
},
prop1: true,
ready: function() {
this._ensureAttribute('attr', true);
},
beforePropChanged1: function() {
this.beforePropChangedCalled = true;
},
propChanged1: function() {
this.propChanged1Called = true;
},
propChanged2: function() {
this.propChanged2Called = true;
}
};
window.registerBehavior2 ={
prop2: true,
beforeRegister: function() {
this.beforeRegisterCount++;
},
registered: function() {
this.registeredCount++;
}
};
window.registerBehavior3 ={
prop3: true,
beforeRegister: function() {
this.beforeRegisterCount++;
},
registered: function() {
this.registeredCount++;
}
};
Polymer({
behaviors: [
window.registerBehavior1,
window.registerBehavior2,
window.registerBehavior3
],
prop4: true,
beforeRegister: function() {
this.beforeRegisterCount++;
},
registered: function() {
this.registeredCount++;
},
beforeRegisterCount: 0,
registeredCount: 0,
is: 'behavior-registered'
});
window.templateBehavior1 = {
_template: Polymer.html`<div id="from-behavior1"></div>`
};
window.templateBehavior2 = {
_template: Polymer.html`<div id="from-behavior2"></div>`
};
window.templateBehaviorFromRegistered = {
registered: function() {
this._template = Polymer.html`<div id="behavior-from-registered"></div>`;
}
};
Polymer({
is: 'template-from-registered',
registered: function() {
this._template = Polymer.html`<div id="from-registered"></div>`;
}
});
Polymer({
is: 'template-from-base',
behaviors: [
window.templateBehavior1
]
});
Polymer({
is: 'template-from-behavior',
behaviors: [
window.templateBehavior1
]
});
Polymer({
is: 'template-from-behavior-overridden',
behaviors: [
window.templateBehavior1,
window.templateBehavior2
]
});
Polymer({
is: 'template-from-behavior-registered',
behaviors: [
window.templateBehaviorFromRegistered
]
});
window.ModifyObserversBehavior = {
__barChangedCalled: 0,
beforeRegister: function() {
const observers = this.constructor.generatedFrom.observers;
this.constructor.generatedFrom.observers = observers.concat([
'_barChanged(bar)'
]);
},
_barChanged: function() {
this.__barChangedCalled++;
}
};
Polymer({
is: 'modify-observers-via-behavior',
__zonkChangedCalled: 0,
observers: [
'_zonkChanged(zonk)'
],
behaviors: [
window.ModifyObserversBehavior
],
_zonkChanged: function() {
this.__zonkChangedCalled++;
}
});
Polymer({
is: 'behavior-properties',
behaviors: [window.BehaviorA]
});
Polymer({
is: 'no-accessors-behavior',
behaviors: [{
_noAccessors: true,
properties: {
nug: String
},
foo: function() {},
bar: true
}],
_noAccessors: true,
zot: 'zot'
});
Polymer({
is: 'override-default-value',
behaviors: [
{
properties: {
foo: { value: true },
bar: { value: true}
}
},
{
properties: {
foo: { value: true },
bar: String,
zot: {value: true}
}
},
],
properties: {
foo: String,
zot: String
}
});
Polymer({
is: 'property-observer-readonly',
behaviors: [
{
observers: ['_changed(bar)'],
_changed() {}
}
],
properties: {
bar: {readOnly: true}
}
});
});
</script>
<test-fixture id="single">
<template>
<single-behavior></single-behavior>
</template>
</test-fixture>
<test-fixture id="multi">
<template>
<multi-behaviors user="user"></multi-behaviors>
</template>
</test-fixture>
<test-fixture id="nested">
<template>
<nested-behaviors></nested-behaviors>
</template>
</test-fixture>
<test-fixture id="registered">
<template>
<behavior-registered></behavior-registered>
</template>
</test-fixture>
<test-fixture id="from-registered">
<template>
<template-from-registered></template-from-registered>
</template>
</test-fixture>
<test-fixture id="from-base">
<template>
<template-from-base></template-from-base>
</template>
</test-fixture>
<test-fixture id="from-behavior">
<template>
<template-from-behavior></template-from-behavior>
</template>
</test-fixture>
<test-fixture id="from-behavior-overridden">
<template>
<template-from-behavior-overridden></template-from-behavior-overridden>
</template>
</test-fixture>
<test-fixture id="from-behavior-registered">
<template>
<template-from-behavior-registered></template-from-behavior-registered>
</template>
</test-fixture>
<test-fixture id="modify-observers-via-behavior">
<template>
<modify-observers-via-behavior></modify-observers-via-behavior>
</template>
</test-fixture>
<test-fixture id="behavior-properties">
<template>
<behavior-properties></behavior-properties>
</template>
</test-fixture>
<test-fixture id="no-accessors-behavior">
<template>
<no-accessors-behavior></no-accessors-behavior>
</template>
</test-fixture>
<test-fixture id="override-default-value">
<template>
<override-default-value></override-default-value>
</template>
</test-fixture>
<test-fixture id="property-observer-readonly">
<template>
<property-observer-readonly></property-observer-readonly>
</template>
</test-fixture>
<script>
suite('single behavior element', function() {
var el;
setup(function() {
el = fixture('single');
});
test('is on prototype', function() {
assert.equal(el.is, 'single-behavior');
});
test('instance behaviors, properties, observers, hostAttributes, listeners', function() {
assert.isOk(el.behaviors);
assert.isOk(el.properties);
assert.isOk(el.observers);
assert.isOk(el.hostAttributes);
assert.isOk(el.listeners);
});
test('ready from behavior', function() {
assert.equal(el.__readyA, true);
});
test('properties from behavior', function() {
el.label = 'foo';
assert.equal(el.__label, 'foo');
});
test('listener from behavior', function() {
el.fire('change', {value: 'bar'});
assert.equal(el.__change, 'bar');
});
test('property info from behavior', function() {
assert.equal(el._hasNotifyEffect('hasOptionsA'), true);
assert.equal(el._hasReadOnlyEffect('hasOptionsA'), true);
assert.equal(typeof el._setHasOptionsA, 'function');
});
test('compute property from behavior', function() {
assert.equal(el.computeA, true);
});
test('special properties not copied from behavior to element', function() {
const el = fixture('behavior-properties');
assert.notOk(el.properties);
assert.notOk(el.observers);
assert.notOk(el.hostAttributes);
assert.notOk(el.listeners);
});
test('properties on objects marked with `_noAccessors` are copied to class', function() {
const el = fixture('no-accessors-behavior');
assert.ok(el.foo);
assert.isTrue(el.bar);
assert.equal(el.zot, 'zot');
el.setAttribute('nug', 'nug');
assert.equal(el.nug, 'nug');
});
test('behavior default values can be overridden', function() {
const el = fixture('override-default-value');
assert.notOk(el.foo);
assert.notOk(el.bar);
assert.notOk(el.zot);
});
test('readOnly not applied when property was previously observed', function() {
const el = fixture('property-observer-readonly');
el.bar = 5;
assert.equal(el.bar, 5);
});
});
suite('behavior.registered', function() {
test('can install dynamic properties', function() {
var el = fixture('registered');
assert.ok(el.$.content);
el.prop = 42;
assert.isTrue(el.propChanged1Called);
assert.isTrue(el.propChanged2Called);
assert.isTrue(el.hasAttribute('attr'));
});
test('called once for each behavior with access to element prototype', function() {
var el = fixture('registered');
assert.equal(el.registeredCount, 4);
assert.equal(el.registeredBehaviors.length, 3);
assert.equal(el.registeredBehaviors, el.behaviors);
assert.deepEqual(el.registeredProps, [true, true, true, true]);
});
});
suite('behavior.beforeRegister', function() {
test('can install dynamic properties', function() {
var el = fixture('registered');
assert.ok(el.$.content);
el.beforeProp = 43;
assert.isTrue(el.beforePropChangedCalled);
});
test('called once for each behavior with access to element prototype', function() {
var el = fixture('registered');
assert.equal(el.beforeRegisterCount, 4);
assert.equal(el.beforeRegisterBehaviors.length, 3);
assert.equal(el.beforeRegisterBehaviors, el.behaviors);
});
test('modify element observers', function() {
var el = fixture('modify-observers-via-behavior');
el.bar = 1;
assert.equal(el.__barChangedCalled, 1);
el.zonk = 1;
assert.equal(el.__zonkChangedCalled, 1);
});
});
suite('multi-behaviors element', function() {
var el;
setup(function() {
el = fixture('multi');
});
test('ready from behaviors', function() {
assert.equal(el.__readyA, true);
assert.equal(el.__readyB, true);
});
test('properties from behaviors', function() {
el.label = 'foo';
assert.equal(el.__label, 'foo');
el.disabled = true;
assert.equal(el.__disabled, true);
});
test('properties from itself', function() {
assert.isDefined(el._setFoo, 'readOnly setter not available');
el._setFoo('bar');
assert.equal(el.__foo, 'bar', 'observer not getting called');
assert.equal(el.getAttribute('foo'), 'bar', 'not getting reflected');
});
test('listener from behaviors', function() {
el.fire('change', {value: 'bar'});
assert.equal(el.__change, 'bar');
});
test('property info from behavior A', function() {
assert.equal(el._hasNotifyEffect('hasOptionsA'), true);
assert.equal(el._hasReadOnlyEffect('hasOptionsA'), true);
assert.equal(typeof el._setHasOptionsA, 'function');
});
test('property info from behavior B', function() {
assert.equal(el._hasReadOnlyEffect('hasOptionsB'), true);
assert.equal(el._hasNotifyEffect('hasOptionsB'), true);
assert.equal(typeof el._setHasOptionsB, 'function');
});
test('computed property dependency can become a computed property', function() {
assert.equal(el.computeA, 'hi');
});
test('multi-behavior overrides ordering', function() {
assert(el.overridableProperty, 'Behavior property was not overridden by prototype');
assert(el.overridablePropertyB, 'Behavior config-property was not overridden by sub-behavior');
});
test('hostAttributes ordering', function() {
assert.equal(el.attributes.behavior.value, 'B', 'Behavior hostAttribute not overridden by younger behavior');
assert.equal(el.attributes.element.value, 'element', 'Behavior hostAttribute overridden by behavior');
assert.equal(el.attributes.user.value, 'user', 'Behavior hostAttribute overrode user attribute');
});
test('behavior is null generates warning', function() {
sinon.spy(console, 'warn');
Polymer({
is: 'behavior-null',
behaviors: [
null
]
});
document.createElement('behavior-null');
assert.equal(console.warn.callCount, 1, 'Null behaviour should generate warning');
console.warn.restore();
});
test('behavior array is unique', function() {
Polymer({
is: 'behavior-unique',
behaviors: [window.BehaviorA, window.BehaviorA]
});
assert.equal(document.createElement('behavior-unique').behaviors.length, 1);
});
test('duplicate behaviors keep first behavior', function() {
Polymer({
is: 'behavior-unique-last-behavior',
behaviors: [window.BehaviorA, window.BehaviorB, window.BehaviorC, window.BehaviorA, window.BehaviorB]
});
var behaviors = document.createElement('behavior-unique-last-behavior').behaviors;
assert.deepEqual(behaviors, [window.BehaviorC, window.BehaviorA, window.BehaviorB]);
});
});
suite('nested-behaviors element', function() {
var el;
setup(function() {
el = fixture('nested');
});
test('nested-behavior dedups', function() {
assert.equal(el.behaviors.length, 4);
});
test('nested-behavior overrides ordering', function() {
assert.ok(el.hasBehaviorA, "missing BehaviorA");
assert.ok(el.hasBehaviorB, "missing BehaviorB");
assert.ok(el.hasBehaviorC, "missing BehaviorC");
assert.ok(el.hasBehaviorD, "missing BehaviorD");
assert.equal(el._simpleProperty, 'D', 'Behavior simple property was not overridden by sub-behavior');
});
});
suite('templates from behaviors', function() {
test('template from registered callback', function() {
var el = fixture('from-registered');
assert.ok(el.shadowRoot.querySelector('#from-registered'));
});
test('template from base', function() {
var el = fixture('from-base');
assert.notOk(el.shadowRoot.querySelector('#from-base'));
assert.ok(el.shadowRoot.querySelector('#from-behavior1'));
});
test('template from behavior', function() {
var el = fixture('from-behavior');
assert.ok(el.shadowRoot.querySelector('#from-behavior1'));
});
test('template from overriding behavior', function() {
var el = fixture('from-behavior-overridden');
assert.notOk(el.shadowRoot.querySelector('#from-behavior1'));
assert.ok(el.shadowRoot.querySelector('#from-behavior2'));
});
test('template from behavior registered callback', function() {
var el = fixture('from-behavior-registered');
assert.ok(el.shadowRoot.querySelector('#behavior-from-registered'));
});
});
</script>
</body>
</html>