UNPKG

@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

616 lines (513 loc) 17.4 kB
<!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-element.html"> <body> <dom-module id="my-element"> <template> <div id="content">{{prop}}</div> </template> <script> HTMLImports.whenReady(function() { class MyElement extends Polymer.Element { static get is() { return 'my-element'; } static get properties() { return { prop: { value: 'base', observer: '_propObserver' }, computedPropDep: { value: true }, computedProp: { computed: '_compute(computedPropDep)' }, accessor: String, noStomp: String }; } static get observers() { return [ '_methodObserver(prop)' ]; } constructor() { super(); this._propObserver = sinon.spy(); this._methodObserver = sinon.spy(); this._tapListener = sinon.spy(); this._calledConstructor++; } get noStomp() { return 'noStomp'; } connectedCallback() { super.connectedCallback(); this._calledConnectedCallback++; } attributeChangedCallback() { super.attributeChangedCallback.apply(this, arguments); this._callAttributeChangedCallback++; } ready() { super.ready(); this._ensureAttribute('tabIndex', 0); this.addEventListener('click', (e) => this._tapListener(e)); this._calledReady++; } _compute(value) { return value; } } MyElement.prototype._calledConstructor = 0; MyElement.prototype._calledConnectedCallback = 0; MyElement.prototype._calledReady = 0; MyElement.prototype._callAttributeChangedCallback = 0; customElements.define(MyElement.is, MyElement); window.MyElement = MyElement; }); </script> </dom-module> <dom-module id="sub-element"> <script> HTMLImports.whenReady(function() { class SubElement extends window.MyElement { static get is() { return 'sub-element'; } static get properties() { return { prop: { value: 'sub' }, prop2: { value: 'prop2', observer: '_prop2Observer' }, computedPropDep: { computed: '_compute(prop2)' } }; } static get observers() { return [ '_subMethodObserver(prop, prop2)' ]; } constructor() { super(); this._calledConstructor++; this._prop2Observer = sinon.spy(); this._subMethodObserver = sinon.spy(); } connectedCallback() { super.connectedCallback(); this._calledConnectedCallback++; } ready() { super.ready(); this._calledReady++; this._ensureAttribute('role', 'button'); this.addEventListener('click', (e) => this._tapListener(e)); } } customElements.define(SubElement.is, SubElement); window.SubElement = SubElement; }); </script> </dom-module> <dom-module id="sub-mixin-element"> <script> HTMLImports.whenReady(function() { function MyMixin(Base) { return class extends Base { static get properties() { return { prop: { value: 'mixin' }, mixin: { value: 'mixin', observer: '_mixinObserver' } }; } static get observers() { return [ '_mixinMethodObserver(mixin, prop, prop2)' ]; } constructor() { super(); this._calledConstructor++; this._mixinObserver = sinon.spy(); this._mixinMethodObserver = sinon.spy(); } connectedCallback() { super.connectedCallback(); this._calledConnectedCallback++; } ready() { super.ready(); this._calledReady++; this._ensureAttribute('mixinattr', 'mixinattr'); this.addEventListener('click', (e) => this._tapListener(e)); } }; } class SubMixinElement extends MyMixin(window.SubElement) { static get is() { return 'sub-mixin-element'; } static get properties() { return { prop3: { value: 'prop3', observer: '_prop3Observer' } }; } static get observers() { return [ '_subMixinMethodObserver(mixin, prop, prop2, prop3)' ]; } constructor() { super(); this._calledConstructor++; this._prop3Observer = sinon.spy(); this._subMixinMethodObserver = sinon.spy(); } connectedCallback() { super.connectedCallback(); this._calledConnectedCallback++; } ready() { super.ready(); this._calledReady++; this._ensureAttribute('submixinattr', 'submixinattr'); this.addEventListener('click', (e) => this._tapListener(e)); } } customElements.define(SubMixinElement.is, SubMixinElement); window.SubMixinElement = SubMixinElement; }); </script> </dom-module> <script> HTMLImports.whenReady(function() { class SubNewTemplate extends window.MyElement { static get template() { return ` <h1>Sub template</h1> <div id="subContent">{{prop2}}</div>`; } static get properties() { return { prop2: { value: 'prop2', } }; } } customElements.define('sub-new-template', SubNewTemplate); }); </script> <template id="no-dom-module"> <style> :host { display: block; border: 10px solid rgb(123, 123, 123); } </style> <div id="child">Content</div> </template> <script> HTMLImports.whenReady(function() { class NoDomModule extends Polymer.Element { static get is() {return 'no-dom-module';} static get template() { return document.body.querySelector('template#no-dom-module'); } } customElements.define('no-dom-module', NoDomModule); }); </script> <dom-module id="sub-extended-template"> <script> HTMLImports.whenReady(function() { // Setting up property bind listeners in both base and sub class // is required to catch regression bug class ElementWithBindListeners extends window.MyElement { static get is() { return 'element-with-bind-listeners'; } } class SubExtendedTemplate extends ElementWithBindListeners { static get is() { return 'sub-extended-template'; } static get template() { if (!this.hasOwnProperty('_template')) { const baseClass = Object.getPrototypeOf(this.prototype).constructor; this._template = baseClass.template.cloneNode(true); const newElement = document.createElement('div'); newElement.innerHTML = '<div id="subContent" imaginary-prop$="{{prop}}""></div>'; this._template.content.appendChild(newElement.firstChild); } return this._template; } } customElements.define(ElementWithBindListeners.is, ElementWithBindListeners); customElements.define(SubExtendedTemplate.is, SubExtendedTemplate); }); </script> </dom-module> <test-fixture id="my-element-attr"> <template> <my-element prop="attr" foo="foo"></my-element> </template> </test-fixture> <test-fixture id="sub-element-attr"> <template> <sub-element prop="attr" prop2="attr"></sub-element> </template> </test-fixture> <test-fixture id="sub-mixin-element-attr"> <template> <sub-mixin-element prop="attr" prop2="attr" prop3="attr"></sub-mixin-element> </template> </test-fixture> <script> suite('class extends Polymer.Element', function() { var el; setup(function() { el = document.createElement('my-element'); document.body.appendChild(el); }); teardown(function() { document.body.removeChild(el); }); test('instanceof', function() { assert.instanceOf(el, HTMLElement); assert.instanceOf(el, Polymer.Element); assert.instanceOf(el, window.MyElement); }); test('lifecycle', function() { assert.equal(el._calledConstructor, 1); assert.equal(el._calledConnectedCallback, 1); assert.equal(el._calledReady, 1); assert.equal(el._callAttributeChangedCallback, 0); }); test('template', function() { assert.equal(el.prop, 'base'); assert.equal(el.$.content.textContent, el.prop); }); test('observers', function() { assert.isTrue(el._propObserver.calledOnce); assert.isTrue(el._propObserver.calledWith('base')); assert.isTrue(el._methodObserver.calledOnce); assert.isTrue(el._methodObserver.calledWith('base')); }); test('listeners', function() { el.dispatchEvent(new CustomEvent('click')); assert.equal(el._tapListener.callCount, 1); }); test('properties', function() { assert.equal(el.prop, 'base'); assert.equal(el.computedPropDep, true); assert.equal(el.computedProp, true); }); test('attributes', function() { const fixtureEl = fixture('my-element-attr'); assert.equal(fixtureEl.prop, 'attr'); assert.equal(fixtureEl._callAttributeChangedCallback, 1); assert.isTrue(fixtureEl.hasAttribute('tabindex')); }); test('reflecting attributes', function() { const fixtureEl = fixture('my-element-attr'); fixtureEl.prop = 'propValue'; fixtureEl._propertyToAttribute('prop'); assert.equal(fixtureEl.getAttribute('prop'), 'propValue'); }); }); suite('subclass', function() { let el; setup(function() { el = document.createElement('sub-element'); document.body.appendChild(el); }); teardown(function() { document.body.removeChild(el); }); test('instanceof', function() { assert.instanceOf(el, HTMLElement); assert.instanceOf(el, Polymer.Element); assert.instanceOf(el, window.MyElement); assert.instanceOf(el, window.SubElement); }); test('lifecycle', function() { assert.equal(el._calledConstructor, 2); assert.equal(el._calledConnectedCallback, 2); assert.equal(el._calledReady, 2); assert.equal(el._callAttributeChangedCallback, 0); }); test('template', function() { assert.equal(el.prop, 'sub'); assert.equal(el.$.content.textContent, el.prop); }); test('observers', function() { assert.isTrue(el._propObserver.calledOnce); assert.isTrue(el._propObserver.calledWith('sub')); assert.isTrue(el._methodObserver.calledOnce); assert.isTrue(el._methodObserver.calledWith('sub')); // sub assert.isTrue(el._prop2Observer.calledOnce); assert.isTrue(el._prop2Observer.calledWith('prop2')); assert.isTrue(el._subMethodObserver.calledOnce); assert.isTrue(el._subMethodObserver.calledWith('sub', 'prop2')); }); test('listeners', function() { el.dispatchEvent(new CustomEvent('click')); assert.equal(el._tapListener.callCount, 2); }); test('properties', function() { assert.equal(el.prop, 'sub'); assert.equal(el.prop2, 'prop2'); assert.equal(el.computedPropDep, 'prop2'); assert.equal(el.computedProp, 'prop2', 'computedProp dependency cannot itself be a computed property'); }); test('attributes', function() { const fixtureEl = fixture('sub-element-attr'); assert.equal(fixtureEl.prop, 'attr'); assert.equal(fixtureEl.prop2, 'attr'); assert.equal(fixtureEl._callAttributeChangedCallback, 2); assert.isTrue(fixtureEl.hasAttribute('tabindex')); assert.equal(fixtureEl.getAttribute('role'), 'button'); }); }); suite('mixin', function() { let el; setup(function() { el = document.createElement('sub-mixin-element'); document.body.appendChild(el); }); teardown(function() { document.body.removeChild(el); }); test('instanceof', function() { assert.instanceOf(el, HTMLElement); assert.instanceOf(el, Polymer.Element); assert.instanceOf(el, window.MyElement); assert.instanceOf(el, window.SubElement); assert.instanceOf(el, window.SubMixinElement); }); test('lifecycle', function() { assert.equal(el._calledConstructor, 4); assert.equal(el._calledConnectedCallback, 4); assert.equal(el._calledReady, 4); assert.equal(el._callAttributeChangedCallback, 0); }); test('template', function() { assert.equal(el.prop, 'mixin'); assert.equal(el.$.content.textContent, el.prop); }); test('observers', function() { assert.isTrue(el._propObserver.calledOnce); assert.isTrue(el._propObserver.calledWith('mixin')); assert.isTrue(el._methodObserver.calledOnce); assert.isTrue(el._methodObserver.calledWith('mixin')); // sub assert.isTrue(el._prop2Observer.calledOnce); assert.isTrue(el._prop2Observer.calledWith('prop2')); assert.isTrue(el._subMethodObserver.calledOnce); assert.isTrue(el._subMethodObserver.calledWith('mixin', 'prop2')); // mixin assert.isTrue(el._mixinObserver.calledOnce); assert.isTrue(el._mixinObserver.calledWith('mixin')); assert.isTrue(el._mixinMethodObserver.calledOnce); assert.isTrue(el._mixinMethodObserver.calledWith('mixin', 'mixin', 'prop2')); // submixin assert.isTrue(el._prop3Observer.calledOnce); assert.isTrue(el._prop3Observer.calledWith('prop3')); assert.isTrue(el._subMixinMethodObserver.calledOnce); assert.isTrue(el._subMixinMethodObserver.calledWith('mixin', 'mixin', 'prop2', 'prop3')); }); test('listeners', function() { el.dispatchEvent(new CustomEvent('click')); assert.equal(el._tapListener.callCount, 4); }); test('attributes', function() { const fixtureEl = fixture('sub-mixin-element-attr'); assert.equal(fixtureEl.prop, 'attr'); assert.equal(fixtureEl.prop2, 'attr'); assert.equal(fixtureEl.prop3, 'attr'); assert.equal(fixtureEl._callAttributeChangedCallback, 3); assert.isTrue(fixtureEl.hasAttribute('tabindex')); assert.equal(fixtureEl.getAttribute('role'), 'button'); assert.equal(fixtureEl.getAttribute('mixinattr'), 'mixinattr'); assert.equal(fixtureEl.getAttribute('submixinattr'), 'submixinattr'); }); }); suite('subclass new template', function() { let el; setup(function() { el = document.createElement('sub-new-template'); document.body.appendChild(el); }); teardown(function() { document.body.removeChild(el); }); test('template', function() { assert.equal(el.prop2, 'prop2'); assert.equal(el.$.subContent.textContent, el.prop2); assert.notOk(el.$.content); }); suite('use without dom-module', function() { let el; setup(function() { el = document.createElement('no-dom-module'); document.body.appendChild(el); }); teardown(function() { document.body.removeChild(el); }); test('template', function() { assert.ok(el.$.child); assert.equal(el.$.child.textContent, 'Content'); assert.equal(window.ShadyCSS.getComputedStyleValue(el, 'border-top-width'), '10px'); }); }); }); suite('extend sub-class template', function() { let el, baseEl; setup(function() { el = document.createElement('sub-extended-template'); document.body.appendChild(el); baseEl = document.createElement('element-with-bind-listeners'); document.body.appendChild(baseEl); }); teardown(function() { document.body.removeChild(el); document.body.removeChild(baseEl); }); test('has original template', function() { assert.equal(el.prop, 'base'); assert.equal(el.$.content.textContent, el.prop); }); test('has new element', function() { assert.ok(el.$.subContent); assert.equal(el.$.subContent.getAttribute('imaginary-prop'), 'base'); }); test('base doesnt have new element', function() { assert.notOk(baseEl.$.subContent); }); }); </script> </body> </html>