Merge branch 'master' into strict-no-warnings

This commit is contained in:
Kevin Schaaf
2019-02-05 11:49:45 -08:00
28 changed files with 3768 additions and 3569 deletions

View File

@@ -40,7 +40,7 @@ The Polymer team heavily uses (and loves!) GitHub for all of our software manage
If you find an issue, please do file it on the repository. The [Polymer/polymer issues](https://github.com/polymer/polymer/issues) should be used only for issues on the Polymer library itself - bugs somewhere in the core codebase.
For issues with elements the team maintains, please file directly on the element's repository. If you're not sure if a bug stems from the element or the library, air toward filing it on the element and we'll move the issue if necessary.
For issues with elements the team maintains, please file directly on the element's repository. If you're not sure if a bug stems from the element or the library, err toward filing it on the element and we'll move the issue if necessary.
Please file issues using the issue template provided, filling out as many fields as possible. We love examples for addressing issues - issues with a jsBin, Plunkr, jsFiddle, or glitch.me repro will be much easier for us to work on quickly. You can start with [this jsbin](http://jsbin.com/luhaxab/edit) which sets up the basics to demonstrate a Polymer element. If you need your repro to run in IE11, you can start from [this glitch](https://glitch.com/edit/#!/polymer-repro?path=my-element.html:2:0), which serves the source via polyserve for automatic transpilation, although you must sign up for a glitch.me account to ensure your code persists for more than 5 days (note the glitch.me _editing environment_ is not compatible with IE11, however the "live" view link of the running code should work).

View File

@@ -33,7 +33,7 @@ let ShadyDOM = {
window.ShadyDOM = ShadyDOM;
let WebComponents = {};
var WebComponents = {};
window.WebComponents = WebComponents;
/** @type {Element} */

View File

@@ -70,6 +70,7 @@ const header =
`/**
* @fileoverview Generated typings for Polymer mixins
* @externs
* @suppress {checkPrototypalTypes}
*
* @license
* Copyright (c) 2017 The Polymer Project Authors. All rights reserved.

View File

@@ -9,8 +9,9 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
*/
import { LegacyElementMixin } from './legacy-element-mixin.js';
import { legacyOptimizations } from '../utils/settings.js';
let metaProps = {
const lifecycleProps = {
attached: true,
detached: true,
ready: true,
@@ -18,10 +19,51 @@ let metaProps = {
beforeRegister: true,
registered: true,
attributeChanged: true,
// meta objects
behaviors: true
listeners: true,
hostAttributes: true
};
const excludeOnInfo = {
attached: true,
detached: true,
ready: true,
created: true,
beforeRegister: true,
registered: true,
attributeChanged: true,
behaviors: true,
_noAccessors: true
};
const excludeOnBehaviors = Object.assign({
listeners: true,
hostAttributes: true,
properties: true,
observers: true,
}, excludeOnInfo);
function copyProperties(source, target, excludeProps) {
const noAccessors = source._noAccessors;
const propertyNames = Object.getOwnPropertyNames(source);
for (let i = 0; i < propertyNames.length; i++) {
let p = propertyNames[i];
if (p in excludeProps) {
continue;
}
if (noAccessors) {
target[p] = source[p];
} else {
let pd = Object.getOwnPropertyDescriptor(source, p);
if (pd) {
// ensure property is configurable so that a later behavior can
// re-configure it.
pd.configurable = true;
Object.defineProperty(target, p, pd);
}
}
}
}
/**
* Applies a "legacy" behavior or array of behaviors to the provided class.
*
@@ -38,28 +80,7 @@ let metaProps = {
* @suppress {invalidCasts, checkTypes}
*/
export function mixinBehaviors(behaviors, klass) {
if (!behaviors) {
klass = /** @type {HTMLElement} */(klass); // eslint-disable-line no-self-assign
return klass;
}
// NOTE: ensure the behavior is extending a class with
// legacy element api. This is necessary since behaviors expect to be able
// to access 1.x legacy api.
klass = LegacyElementMixin(klass);
if (!Array.isArray(behaviors)) {
behaviors = [behaviors];
}
let superBehaviors = klass.prototype.behaviors;
// get flattened, deduped list of behaviors *not* already on super class
behaviors = flattenBehaviors(behaviors, null, superBehaviors);
// mixin new behaviors
klass = _mixinBehaviors(behaviors, klass);
if (superBehaviors) {
behaviors = superBehaviors.concat(behaviors);
}
// Set behaviors on prototype for BC...
klass.prototype.behaviors = behaviors;
return klass;
return GenerateClassFromInfo({}, LegacyElementMixin(klass), behaviors);
}
// NOTE:
@@ -92,15 +113,20 @@ export function mixinBehaviors(behaviors, klass) {
// If lifecycle is called (super then me), order is
// (1) C.created, (2) A.created, (3) B.created, (4) element.created
// (again same as 1.x)
function _mixinBehaviors(behaviors, klass) {
function applyBehaviors(proto, behaviors, lifecycle) {
for (let i=0; i<behaviors.length; i++) {
let b = behaviors[i];
if (b) {
klass = Array.isArray(b) ? _mixinBehaviors(b, klass) :
GenerateClassFromInfo(b, klass);
applyInfo(proto, behaviors[i], lifecycle, excludeOnBehaviors);
}
}
function applyInfo(proto, info, lifecycle, excludeProps) {
copyProperties(info, proto, excludeProps);
for (let p in lifecycleProps) {
if (info[p]) {
lifecycle[p] = lifecycle[p] || [];
lifecycle[p].push(info[p]);
}
}
return klass;
}
/**
@@ -129,24 +155,99 @@ function flattenBehaviors(behaviors, list, exclude) {
return list;
}
/* Note about construction and extension of legacy classes.
[Changed in Q4 2018 to optimize performance.]
When calling `Polymer` or `mixinBehaviors`, the generated class below is
made. The list of behaviors was previously made into one generated class per
behavior, but this is no longer the case as behaviors are now called
manually. Note, there may *still* be multiple generated classes in the
element's prototype chain if extension is used with `mixinBehaviors`.
The generated class is directly tied to the info object and behaviors
used to create it. That list of behaviors is filtered so it's only the
behaviors not active on the superclass. In order to call through to the
entire list of lifecycle methods, it's important to call `super`.
The element's `properties` and `observers` are controlled via the finalization
mechanism provided by `PropertiesMixin`. `Properties` and `observers` are
collected by manually traversing the prototype chain and merging.
To limit changes, the `_registered` method is called via `_initializeProperties`
and not `_finalizeClass`.
*/
/**
* @param {!PolymerInit} info Polymer info object
* @param {function(new:HTMLElement)} Base base class to extend with info object
* @param {Object} behaviors behaviors to copy into the element
* @return {function(new:HTMLElement)} Generated class
* @suppress {checkTypes}
* @private
*/
function GenerateClassFromInfo(info, Base) {
function GenerateClassFromInfo(info, Base, behaviors) {
// manages behavior and lifecycle processing (filled in after class definition)
let behaviorList;
const lifecycle = {};
/** @private */
class PolymerGenerated extends Base {
// explicitly not calling super._finalizeClass
static _finalizeClass() {
// if calling via a subclass that hasn't been generated, pass through to super
if (!this.hasOwnProperty(window.JSCompiler_renameProperty('generatedFrom', this))) {
super._finalizeClass();
} else {
// interleave properties and observers per behavior and `info`
if (behaviorList) {
for (let i=0, b; i < behaviorList.length; i++) {
b = behaviorList[i];
if (b.properties) {
this.createProperties(b.properties);
}
if (b.observers) {
this.createObservers(b.observers, b.properties);
}
}
}
if (info.properties) {
this.createProperties(info.properties);
}
if (info.observers) {
this.createObservers(info.observers, info.properties);
}
// make sure to prepare the element template
this._prepareTemplate();
}
}
static get properties() {
return info.properties;
const properties = {};
if (behaviorList) {
for (let i=0; i < behaviorList.length; i++) {
Object.assign(properties, behaviorList[i].properties);
}
}
Object.assign(properties, info.properties);
return properties;
}
static get observers() {
return info.observers;
let observers = [];
if (behaviorList) {
for (let i=0, b; i < behaviorList.length; i++) {
b = behaviorList[i];
if (b.observers) {
observers = observers.concat(b.observers);
}
}
}
if (info.observers) {
observers = observers.concat(info.observers);
}
return observers;
}
/**
@@ -154,8 +255,11 @@ function GenerateClassFromInfo(info, Base) {
*/
created() {
super.created();
if (info.created) {
info.created.call(this);
const list = lifecycle.created;
if (list) {
for (let i=0; i < list.length; i++) {
list[i].call(this);
}
}
}
@@ -163,19 +267,39 @@ function GenerateClassFromInfo(info, Base) {
* @return {void}
*/
_registered() {
super._registered();
/* NOTE: `beforeRegister` is called here for bc, but the behavior
is different than in 1.x. In 1.0, the method was called *after*
mixing prototypes together but *before* processing of meta-objects.
However, dynamic effects can still be set here and can be done either
in `beforeRegister` or `registered`. It is no longer possible to set
`is` in `beforeRegister` as you could in 1.x.
is different than in 1.x. In 1.0, the method was called *after*
mixing prototypes together but *before* processing of meta-objects.
However, dynamic effects can still be set here and can be done either
in `beforeRegister` or `registered`. It is no longer possible to set
`is` in `beforeRegister` as you could in 1.x.
*/
if (info.beforeRegister) {
info.beforeRegister.call(Object.getPrototypeOf(this));
}
if (info.registered) {
info.registered.call(Object.getPrototypeOf(this));
// only proceed if the generated class' prototype has not been registered.
const generatedProto = PolymerGenerated.prototype;
if (!generatedProto.hasOwnProperty('__hasRegisterFinished')) {
generatedProto.__hasRegisterFinished = true;
// ensure superclass is registered first.
super._registered();
// copy properties onto the generated class lazily if we're optimizing,
if (legacyOptimizations) {
copyPropertiesToProto(generatedProto);
}
// make sure legacy lifecycle is called on the *element*'s prototype
// and not the generated class prototype; if the element has been
// extended, these are *not* the same.
const proto = Object.getPrototypeOf(this);
let list = lifecycle.beforeRegister;
if (list) {
for (let i=0; i < list.length; i++) {
list[i].call(proto);
}
}
list = lifecycle.registered;
if (list) {
for (let i=0; i < list.length; i++) {
list[i].call(proto);
}
}
}
}
@@ -184,9 +308,15 @@ function GenerateClassFromInfo(info, Base) {
*/
_applyListeners() {
super._applyListeners();
if (info.listeners) {
for (let l in info.listeners) {
this._addMethodEventListenerToNode(this, l, info.listeners[l]);
const list = lifecycle.listeners;
if (list) {
for (let i=0; i < list.length; i++) {
const listeners = list[i];
if (listeners) {
for (let l in listeners) {
this._addMethodEventListenerToNode(this, l, listeners[l]);
}
}
}
}
}
@@ -198,9 +328,13 @@ function GenerateClassFromInfo(info, Base) {
* @return {void}
*/
_ensureAttributes() {
if (info.hostAttributes) {
for (let a in info.hostAttributes) {
this._ensureAttribute(a, info.hostAttributes[a]);
const list = lifecycle.hostAttributes;
if (list) {
for (let i=list.length-1; i >= 0; i--) {
const hostAttributes = list[i];
for (let a in hostAttributes) {
this._ensureAttribute(a, hostAttributes[a]);
}
}
}
super._ensureAttributes();
@@ -211,8 +345,11 @@ function GenerateClassFromInfo(info, Base) {
*/
ready() {
super.ready();
if (info.ready) {
info.ready.call(this);
let list = lifecycle.ready;
if (list) {
for (let i=0; i < list.length; i++) {
list[i].call(this);
}
}
}
@@ -221,8 +358,11 @@ function GenerateClassFromInfo(info, Base) {
*/
attached() {
super.attached();
if (info.attached) {
info.attached.call(this);
let list = lifecycle.attached;
if (list) {
for (let i=0; i < list.length; i++) {
list[i].call(this);
}
}
}
@@ -231,8 +371,11 @@ function GenerateClassFromInfo(info, Base) {
*/
detached() {
super.detached();
if (info.detached) {
info.detached.call(this);
let list = lifecycle.detached;
if (list) {
for (let i=0; i < list.length; i++) {
list[i].call(this);
}
}
}
@@ -246,26 +389,45 @@ function GenerateClassFromInfo(info, Base) {
* @return {void}
*/
attributeChanged(name, old, value) {
super.attributeChanged(name, old, value);
if (info.attributeChanged) {
info.attributeChanged.call(this, name, old, value);
}
}
}
PolymerGenerated.generatedFrom = info;
for (let p in info) {
// NOTE: cannot copy `metaProps` methods onto prototype at least because
// `super.ready` must be called and is not included in the user fn.
if (!(p in metaProps)) {
let pd = Object.getOwnPropertyDescriptor(info, p);
if (pd) {
Object.defineProperty(PolymerGenerated.prototype, p, pd);
super.attributeChanged();
let list = lifecycle.attributeChanged;
if (list) {
for (let i=0; i < list.length; i++) {
list[i].call(this, name, old, value);
}
}
}
}
// apply behaviors, note actual copying is done lazily at first instance creation
if (behaviors) {
// NOTE: ensure the behavior is extending a class with
// legacy element api. This is necessary since behaviors expect to be able
// to access 1.x legacy api.
if (!Array.isArray(behaviors)) {
behaviors = [behaviors];
}
let superBehaviors = Base.prototype.behaviors;
// get flattened, deduped list of behaviors *not* already on super class
behaviorList = flattenBehaviors(behaviors, null, superBehaviors);
PolymerGenerated.prototype.behaviors = superBehaviors ?
superBehaviors.concat(behaviors) : behaviorList;
}
const copyPropertiesToProto = (proto) => {
if (behaviorList) {
applyBehaviors(proto, behaviorList, lifecycle);
}
applyInfo(proto, info, lifecycle, excludeOnInfo);
};
// copy properties if we're not optimizing
if (!legacyOptimizations) {
copyPropertiesToProto(PolymerGenerated.prototype);
}
PolymerGenerated.generatedFrom = info;
return PolymerGenerated;
}
@@ -341,15 +503,12 @@ function GenerateClassFromInfo(info, Base) {
*/
export const Class = function(info, mixin) {
if (!info) {
console.warn(`Polymer's Class function requires \`info\` argument`);
console.warn('Polymer.Class requires `info` argument');
}
const baseWithBehaviors = info.behaviors ?
// note: mixinBehaviors ensures `LegacyElementMixin`.
mixinBehaviors(info.behaviors, HTMLElement) :
LegacyElementMixin(HTMLElement);
const baseWithMixin = mixin ? mixin(baseWithBehaviors) : baseWithBehaviors;
const klass = GenerateClassFromInfo(info, baseWithMixin);
let klass = mixin ? mixin(LegacyElementMixin(HTMLElement)) :
LegacyElementMixin(HTMLElement);
klass = GenerateClassFromInfo(info, klass, info.behaviors);
// decorate klass with registration info
klass.is = info.is;
klass.is = klass.prototype.is = info.is;
return klass;
};

View File

@@ -11,6 +11,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
import { Class } from './class.js';
import { Polymer } from '../../polymer-legacy.js';
import { dedupingMixin } from '../utils/mixin.js';
import { templatize } from '../utils/templatize.js';
const UndefinedArgumentError = class extends Error {
constructor(message, arg) {
@@ -148,5 +149,14 @@ Polymer.Class = (info, mixin) => Class(info,
LegacyDataMixin(superClass)
);
// Apply LegacyDataMixin to Templatizer instances as well, and defer
// runtime switch to the root's host (_methodHost)
templatize.mixin =
dedupingMixin(superClass => class extends LegacyDataMixin(superClass) {
get _legacyUndefinedCheck() {
return this._methodHost && this._methodHost._legacyUndefinedCheck;
}
});
console.info('LegacyDataMixin will be applied to all legacy elements.\n' +
'Set `_legacyUndefinedCheck: true` on element class to enable.');

View File

@@ -76,11 +76,6 @@ export const LegacyElementMixin = dedupingMixin((base) => {
this.__boundListeners;
/** @type {?Object<string, ?Function>} */
this._debouncers;
// Ensure listeners are applied immediately so that they are
// added before declarative event listeners. This allows an element to
// decorate itself via an event prior to any declarative listeners
// seeing the event. Note, this ensures compatibility with 1.x ordering.
this._applyListeners();
}
/**
@@ -183,12 +178,18 @@ export const LegacyElementMixin = dedupingMixin((base) => {
_initializeProperties() {
let proto = Object.getPrototypeOf(this);
if (!proto.hasOwnProperty('__hasRegisterFinished')) {
proto.__hasRegisterFinished = true;
this._registered();
// backstop in case the `_registered` implementation does not set this
proto.__hasRegisterFinished = true;
}
super._initializeProperties();
this.root = /** @type {HTMLElement} */(this);
this.created();
// Ensure listeners are applied immediately so that they are
// added before declarative event listeners. This allows an element to
// decorate itself via an event prior to any declarative listeners
// seeing the event. Note, this ensures compatibility with 1.x ordering.
this._applyListeners();
}
/**
@@ -380,6 +381,7 @@ export const LegacyElementMixin = dedupingMixin((base) => {
* @return {!DocumentFragment} Document fragment containing the imported
* template content.
* @override
* @suppress {missingProperties} go/missingfnprops
*/
instanceTemplate(template) {
let content = this.constructor._contentForTemplate(template);

View File

@@ -1,4 +1,5 @@
/**
* @suppress {checkPrototypalTypes}
@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
@@ -17,6 +18,10 @@ const HOST_DIR_REPLACMENT = ':host([dir="$1"])';
const EL_DIR = /([\s\w-#\.\[\]\*]*):dir\((ltr|rtl)\)/g;
const EL_DIR_REPLACMENT = ':host([dir="$2"]) $1';
const DIR_CHECK = /:dir\((?:ltr|rtl)\)/;
const SHIM_SHADOW = Boolean(window['ShadyDOM'] && window['ShadyDOM']['inUse']);
/**
* @type {!Array<!Polymer_DirMixin>}
*/
@@ -80,10 +85,12 @@ function takeRecords() {
*/
export const DirMixin = dedupingMixin((base) => {
if (!observer) {
getRTL();
observer = new MutationObserver(updateDirection);
observer.observe(document.documentElement, {attributes: true, attributeFilter: ['dir']});
if (!SHIM_SHADOW) {
if (!observer) {
getRTL();
observer = new MutationObserver(updateDirection);
observer.observe(document.documentElement, {attributes: true, attributeFilter: ['dir']});
}
}
/**
@@ -101,12 +108,17 @@ export const DirMixin = dedupingMixin((base) => {
class Dir extends elementBase {
/**
* @override
* @param {string} cssText .
* @param {string} baseURI .
* @return {string} .
* @suppress {missingProperties} Interfaces in closure do not inherit statics, but classes do
*/
static _processStyleText(cssText, baseURI) {
cssText = super._processStyleText(cssText, baseURI);
cssText = this._replaceDirInCssText(cssText);
if (!SHIM_SHADOW && DIR_CHECK.test(cssText)) {
cssText = this._replaceDirInCssText(cssText);
this.__activateDir = true;
}
return cssText;
}
@@ -120,9 +132,6 @@ export const DirMixin = dedupingMixin((base) => {
let replacedText = text;
replacedText = replacedText.replace(HOST_DIR, HOST_DIR_REPLACMENT);
replacedText = replacedText.replace(EL_DIR, EL_DIR_REPLACMENT);
if (text !== replacedText) {
this.__activateDir = true;
}
return replacedText;
}

View File

@@ -1,4 +1,5 @@
/**
* @suppress {checkPrototypalTypes}
@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
@@ -53,7 +54,9 @@ export const DisableUpgradeMixin = dedupingMixin((base) => {
*/
class DisableUpgradeClass extends superClass {
/** @override */
/**
* @suppress {missingProperties} go/missingfnprops
*/
static get observedAttributes() {
return super.observedAttributes.concat(DISABLED_ATTR);
}

View File

@@ -1,4 +1,5 @@
/**
* @suppress {checkPrototypalTypes}
@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
@@ -9,7 +10,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
*/
import '../utils/boot.js';
import { rootPath, strictTemplatePolicy, allowTemplateFromDomModule } from '../utils/settings.js';
import { rootPath, strictTemplatePolicy, allowTemplateFromDomModule, legacyOptimizations } from '../utils/settings.js';
import { dedupingMixin } from '../utils/mixin.js';
import { stylesFromTemplate, stylesFromModuleImports } from '../utils/style-gather.js';
import { pathFromUrl, resolveCss, resolveUrl } from '../utils/resolve-url.js';
@@ -23,6 +24,8 @@ import { PropertiesMixin } from './properties-mixin.js';
*/
export const version = '3.0.5';
const builtCSS = window.ShadyCSS && window.ShadyCSS['cssBuild'];
/**
* Element class mixin that provides the core API for Polymer's meta-programming
* features including template stamping, data-binding, attribute deserialization,
@@ -193,7 +196,6 @@ export const ElementMixin = dedupingMixin(base => {
* disables the effect, the setter would fail unexpectedly.
* Based on feedback, we may want to try to make effects more malleable
* and/or provide an advanced api for manipulating them.
* Also consider adding warnings when an effect cannot be changed.
*
* @param {!PolymerElement} proto Element class prototype to add accessors
* and effects to
@@ -215,17 +217,27 @@ export const ElementMixin = dedupingMixin(base => {
// setup where multiple triggers for setting a property)
// While we do have `hasComputedEffect` this is set on the property's
// dependencies rather than itself.
if (info.computed && !proto._hasReadOnlyEffect(name)) {
proto._createComputedProperty(name, info.computed, allProps);
if (info.computed) {
if (proto._hasReadOnlyEffect(name)) {
console.warn(`Cannot redefine computed property '${name}'.`);
} else {
proto._createComputedProperty(name, info.computed, allProps);
}
}
if (info.readOnly && !proto._hasReadOnlyEffect(name)) {
proto._createReadOnlyProperty(name, !info.computed);
} else if (info.readOnly === false && proto._hasReadOnlyEffect(name)) {
console.warn(`Cannot make readOnly property '${name}' non-readOnly.`);
}
if (info.reflectToAttribute && !proto._hasReflectEffect(name)) {
proto._createReflectedProperty(name);
} else if (info.reflectToAttribute === false && proto._hasReflectEffect(name)) {
console.warn(`Cannot make reflected property '${name}' non-reflected.`);
}
if (info.notify && !proto._hasNotifyEffect(name)) {
proto._createNotifyingProperty(name);
} else if (info.notify === false && proto._hasNotifyEffect(name)) {
console.warn(`Cannot make notify property '${name}' non-notify.`);
}
// always add observer
if (info.observer) {
@@ -246,31 +258,33 @@ export const ElementMixin = dedupingMixin(base => {
* @private
*/
function processElementStyles(klass, template, is, baseURI) {
const templateStyles = template.content.querySelectorAll('style');
const stylesWithImports = stylesFromTemplate(template);
// insert styles from <link rel="import" type="css"> at the top of the template
const linkedStyles = stylesFromModuleImports(is);
const firstTemplateChild = template.content.firstElementChild;
for (let idx = 0; idx < linkedStyles.length; idx++) {
let s = linkedStyles[idx];
s.textContent = klass._processStyleText(s.textContent, baseURI);
template.content.insertBefore(s, firstTemplateChild);
}
// keep track of the last "concrete" style in the template we have encountered
let templateStyleIndex = 0;
// ensure all gathered styles are actually in this template.
for (let i = 0; i < stylesWithImports.length; i++) {
let s = stylesWithImports[i];
let templateStyle = templateStyles[templateStyleIndex];
// if the style is not in this template, it's been "included" and
// we put a clone of it in the template before the style that included it
if (templateStyle !== s) {
s = s.cloneNode(true);
templateStyle.parentNode.insertBefore(s, templateStyle);
} else {
templateStyleIndex++;
if (!builtCSS) {
const templateStyles = template.content.querySelectorAll('style');
const stylesWithImports = stylesFromTemplate(template);
// insert styles from <link rel="import" type="css"> at the top of the template
const linkedStyles = stylesFromModuleImports(is);
const firstTemplateChild = template.content.firstElementChild;
for (let idx = 0; idx < linkedStyles.length; idx++) {
let s = linkedStyles[idx];
s.textContent = klass._processStyleText(s.textContent, baseURI);
template.content.insertBefore(s, firstTemplateChild);
}
// keep track of the last "concrete" style in the template we have encountered
let templateStyleIndex = 0;
// ensure all gathered styles are actually in this template.
for (let i = 0; i < stylesWithImports.length; i++) {
let s = stylesWithImports[i];
let templateStyle = templateStyles[templateStyleIndex];
// if the style is not in this template, it's been "included" and
// we put a clone of it in the template before the style that included it
if (templateStyle !== s) {
s = s.cloneNode(true);
templateStyle.parentNode.insertBefore(s, templateStyle);
} else {
templateStyleIndex++;
}
s.textContent = klass._processStyleText(s.textContent, baseURI);
}
s.textContent = klass._processStyleText(s.textContent, baseURI);
}
if (window.ShadyCSS) {
window.ShadyCSS.prepareTemplate(template, is);
@@ -323,26 +337,25 @@ export const ElementMixin = dedupingMixin(base => {
* find the template.
* @return {void}
* @protected
* @override
* @suppress {missingProperties} Interfaces in closure do not inherit statics, but classes do
*/
static _finalizeClass() {
super._finalizeClass();
if (this.hasOwnProperty(
JSCompiler_renameProperty('is', this)) && this.is) {
register(this.prototype);
}
const observers = ownObservers(this);
if (observers) {
this.createObservers(observers, this._properties);
}
this._prepareTemplate();
}
static _prepareTemplate() {
// note: create "working" template that is finalized at instance time
let template = /** @type {PolymerElementConstructor} */ (this).template;
if (template) {
if (typeof template === 'string') {
console.error('template getter must return HTMLTemplateElement');
template = null;
} else {
} else if (!legacyOptimizations) {
template = template.cloneNode(true);
}
}
@@ -353,9 +366,9 @@ export const ElementMixin = dedupingMixin(base => {
/**
* Override of PropertiesChanged createProperties to create accessors
* and property effects for all of the properties.
* @param {!Object} props .
* @return {void}
* @protected
* @override
*/
static createProperties(props) {
for (let p in props) {
@@ -514,10 +527,9 @@ export const ElementMixin = dedupingMixin(base => {
*
* @return {void}
* @override
* @suppress {invalidCasts}
* @suppress {invalidCasts,missingProperties} go/missingfnprops
*/
_initializeProperties() {
instanceCount++;
this.constructor.finalize();
// note: finalize template when we have access to `localName` to
// avoid dependence on `is` for polyfilling styling.
@@ -723,12 +735,15 @@ export const ElementMixin = dedupingMixin(base => {
}
/**
* Overrides `PropertyAccessors` to add map of dynamic functions on
* Overrides `PropertyEffects` to add map of dynamic functions on
* template info, for consumption by `PropertyEffects` template binding
* code. This map determines which method templates should have accessors
* created for them.
*
* @override
* @param {!HTMLTemplateElement} template Template
* @param {!TemplateInfo} templateInfo Template metadata for current template
* @param {!NodeInfo} nodeInfo Node metadata for current template.
* @return {boolean} .
* @suppress {missingProperties} Interfaces in closure do not inherit statics, but classes do
*/
static _parseTemplateContent(template, templateInfo, nodeInfo) {
@@ -736,48 +751,37 @@ export const ElementMixin = dedupingMixin(base => {
return super._parseTemplateContent(template, templateInfo, nodeInfo);
}
/**
* Overrides `PropertyEffects` to warn on use of undeclared properties in
* template.
*
* @param {Object} templateInfo Template metadata to add effect to
* @param {string} prop Property that should trigger the effect
* @param {Object=} effect Effect metadata object
* @return {void}
* @protected
*/
static _addTemplatePropertyEffect(templateInfo, prop, effect) {
// Warn if properties are used in template without being declared.
// Properties must be listed in `properties` to be included in
// `observedAttributes` since CE V1 reads that at registration time, and
// since we want to keep template parsing lazy, we can't automatically
// add undeclared properties used in templates to `observedAttributes`.
// The warning is only enabled in `legacyOptimizations` mode, since
// we don't want to spam existing users who might have adopted the
// shorthand when attribute deserialization is not important.
if (legacyOptimizations && !(prop in this._properties)) {
console.warn(`Property '${prop}' used in template but not declared in 'properties'; ` +
`attribute will not be observed.`);
}
return super._addTemplatePropertyEffect(templateInfo, prop, effect);
}
}
return PolymerElement;
});
/**
* Total number of Polymer element instances created.
* @type {number}
*/
export let instanceCount = 0;
/**
* Array of Polymer element classes that have been finalized.
* @type {!Array<!PolymerElementConstructor>}
*/
export const registrations = [];
/**
* @param {!PolymerElementConstructor} prototype Element prototype to log
* @private
*/
function _regLog(prototype) {
console.log('[' + prototype.is + ']: registered');
}
/**
* Registers a class prototype for telemetry purposes.
* @param {!PolymerElementConstructor} prototype Element prototype to register
* @protected
*/
export function register(prototype) {
registrations.push(prototype);
}
/**
* Logs all elements registered with an `is` to the console.
* @public
*/
export function dumpRegistrations() {
registrations.forEach(_regLog);
}
/**
* When using the ShadyCSS scoping and custom property shim, causes all
* shimmed `styles` (via `custom-style`) in the document (and its subtree)

View File

@@ -10,6 +10,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
import '../utils/boot.js';
import { dedupingMixin } from '../utils/mixin.js';
import { register, incrementInstanceCount } from '../utils/telemetry.js';
import { PropertiesChanged } from './properties-changed.js';
/**
@@ -114,8 +115,12 @@ export const PropertiesMixin = dedupingMixin(superClass => {
* @suppress {missingProperties} Interfaces in closure do not inherit statics, but classes do
*/
static get observedAttributes() {
const props = this._properties;
return props ? Object.keys(props).map(p => this.attributeNameForProperty(p)) : [];
if (!this.hasOwnProperty('__observedAttributes')) {
register(this.prototype);
const props = this._properties;
this.__observedAttributes = props ? Object.keys(props).map(p => this.attributeNameForProperty(p)) : [];
}
return this.__observedAttributes;
}
/**
@@ -189,6 +194,7 @@ export const PropertiesMixin = dedupingMixin(superClass => {
* @return {void}
*/
_initializeProperties() {
incrementInstanceCount();
this.constructor.finalize();
super._initializeProperties();
}

View File

@@ -1,4 +1,5 @@
/**
* @suppress {checkPrototypalTypes}
@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
@@ -969,6 +970,19 @@ function parseArg(rawArg) {
return a;
}
function getArgValue(data, props, path) {
let value = get(data, path);
// when data is not stored e.g. `splices`, get the value from changedProps
// TODO(kschaaf): Note, this can cause a rare issue where the wildcard
// info.value could pull a stale value out of changedProps during a reentrant
// change that sets the value back to undefined.
// https://github.com/Polymer/polymer/issues/5479
if (value === undefined) {
value = props[path];
}
return value;
}
// data api
/**
@@ -984,11 +998,8 @@ function parseArg(rawArg) {
* @private
*/
function notifySplices(inst, array, path, splices) {
let splicesPath = path + '.splices';
inst.notifyPath(splicesPath, { indexSplices: splices });
inst.notifyPath(path + '.splices', { indexSplices: splices });
inst.notifyPath(path + '.length', array.length);
// Null here to allow potentially large splice records to be GC'ed.
inst.__data[splicesPath] = {indexSplices: null};
}
/**
@@ -2134,6 +2145,7 @@ export const PropertyEffects = dedupingMixin(superClass => {
* @param {string} property Property name
* @return {void}
* @protected
* @suppress {missingProperties} go/missingfnprops
*/
_createReflectedProperty(property) {
let attr = this.constructor.attributeNameForProperty(property);
@@ -2186,37 +2198,23 @@ export const PropertyEffects = dedupingMixin(superClass => {
*/
_marshalArgs(args, path, props) {
const data = this.__data;
let values = [];
const values = [];
for (let i=0, l=args.length; i<l; i++) {
let arg = args[i];
let name = arg.name;
let v;
if (arg.literal) {
v = arg.value;
} else {
if (arg.structured) {
v = get(data, name);
// when data is not stored e.g. `splices`
if (v === undefined) {
v = props[name];
}
let {name, structured, wildcard, value, literal} = args[i];
if (!literal) {
if (wildcard) {
const matches = isDescendant(name, path);
const pathValue = getArgValue(data, props, matches ? path : name);
value = {
path: matches ? path : name,
value: pathValue,
base: matches ? get(data, name) : pathValue
};
} else {
v = data[name];
value = structured ? getArgValue(data, props, name) : data[name];
}
}
if (arg.wildcard) {
// Only send the actual path changed info if the change that
// caused the observer to run matched the wildcard
let baseChanged = (name.indexOf(path + '.') === 0);
let matches = (path.indexOf(name) === 0 && !baseChanged);
values[i] = {
path: matches ? path : name,
value: matches ? props[path] : v,
base: v
};
} else {
values[i] = v;
}
values[i] = value;
}
return values;
}
@@ -2396,6 +2394,7 @@ export const PropertyEffects = dedupingMixin(superClass => {
* @return {!TemplateInfo} Template metadata object; for `runtimeBinding`,
* this is an instance of the prototypical template info
* @protected
* @suppress {missingProperties} go/missingfnprops
*/
_bindTemplate(template, instanceBinding) {
let templateInfo = this.constructor._parseTemplate(template);
@@ -2531,7 +2530,6 @@ export const PropertyEffects = dedupingMixin(superClass => {
* with one or more metadata objects capturing the source(s) of the
* binding.
*
* @override
* @param {Node} node Node to parse
* @param {TemplateInfo} templateInfo Template metadata for current template
* @param {NodeInfo} nodeInfo Node metadata for current template node
@@ -2564,7 +2562,6 @@ export const PropertyEffects = dedupingMixin(superClass => {
* with one or more metadata objects capturing the source(s) of the
* binding.
*
* @override
* @param {Element} node Node to parse
* @param {TemplateInfo} templateInfo Template metadata for current template
* @param {NodeInfo} nodeInfo Node metadata for current template node
@@ -2593,6 +2590,11 @@ export const PropertyEffects = dedupingMixin(superClass => {
// Initialize attribute bindings with any literal parts
let literal = literalFromParts(parts);
if (literal && kind == 'attribute') {
// Ensure a ShadyCSS template scoped style is not removed
// when a class$ binding's initial literal value is set.
if (name == 'class' && node.hasAttribute('class')) {
literal += ' ' + node.getAttribute(name);
}
node.setAttribute(name, literal);
}
// Clear attribute before removing, since IE won't allow removing
@@ -2623,7 +2625,6 @@ export const PropertyEffects = dedupingMixin(superClass => {
* binding the properties that a nested template depends on to the template
* as `_host_<property>`.
*
* @override
* @param {Node} node Node to parse
* @param {TemplateInfo} templateInfo Template metadata for current template
* @param {NodeInfo} nodeInfo Node metadata for current template node

View File

@@ -11,6 +11,9 @@ import '../utils/boot.js';
import { dedupingMixin } from '../utils/mixin.js';
const walker = document.createTreeWalker(document, NodeFilter.SHOW_ALL,
null, false);
// 1.x backwards-compatible auto-wrapper for template type extensions
// This is a clear layering violation and gives favored-nation status to
// dom-if and dom-repeat templates. This is a conceit we're choosing to keep
@@ -45,7 +48,8 @@ function findTemplateNode(root, nodeInfo) {
if (parent) {
// note: marginally faster than indexing via childNodes
// (http://jsperf.com/childnodes-lookup)
for (let n=parent.firstChild, i=0; n; n=n.nextSibling) {
walker.currentNode = parent;
for (let n=walker.firstChild(), i=0; n; n=walker.nextSibling()) {
if (nodeInfo.parentIndex === i++) {
return n;
}
@@ -234,7 +238,8 @@ export const TemplateStamp = dedupingMixin(
// For ShadyDom optimization, indicating there is an insertion point
templateInfo.hasInsertionPoint = true;
}
if (element.firstChild) {
walker.currentNode = element;
if (walker.firstChild()) {
noted = this._parseTemplateChildNodes(element, templateInfo, nodeInfo) || noted;
}
if (element.hasAttributes && element.hasAttributes()) {
@@ -260,7 +265,8 @@ export const TemplateStamp = dedupingMixin(
if (root.localName === 'script' || root.localName === 'style') {
return;
}
for (let node=root.firstChild, parentIndex=0, next; node; node=next) {
walker.currentNode = root;
for (let node=walker.firstChild(), parentIndex=0, next; node; node=next) {
// Wrap templates
if (node.localName == 'template') {
node = wrapTemplateExtension(node);
@@ -269,12 +275,13 @@ export const TemplateStamp = dedupingMixin(
// text nodes to be inexplicably split =(
// note that root.normalize() should work but does not so we do this
// manually.
next = node.nextSibling;
walker.currentNode = node;
next = walker.nextSibling();
if (node.nodeType === Node.TEXT_NODE) {
let /** Node */ n = next;
while (n && (n.nodeType === Node.TEXT_NODE)) {
node.textContent += n.textContent;
next = n.nextSibling;
next = walker.nextSibling();
root.removeChild(n);
n = next;
}
@@ -289,7 +296,8 @@ export const TemplateStamp = dedupingMixin(
childInfo.infoIndex = templateInfo.nodeInfoList.push(/** @type {!NodeInfo} */(childInfo)) - 1;
}
// Increment if not removed
if (node.parentNode) {
walker.currentNode = node;
if (walker.parentNode()) {
parentIndex++;
}
}

View File

@@ -23,8 +23,7 @@ export const useNativeCustomElements = !(window.customElements.polyfillWrapFlush
* `rootPath` to provide a stable application mount path when
* using client side routing.
*/
export let rootPath = undefined ||
pathFromUrl(document.baseURI || window.location.href);
export let rootPath = pathFromUrl(document.baseURI || window.location.href);
/**
* Sets the global rootPath property used by `ElementMixin` and
@@ -113,10 +112,31 @@ export let allowTemplateFromDomModule = false;
/**
* Sets `lookupTemplateFromDomModule` globally for all elements
*
* @param {boolean} allowDomModule enable or disable template lookup
* @param {boolean} allowDomModule enable or disable template lookup
* globally
* @return {void}
*/
export const setAllowTemplateFromDomModule = function(allowDomModule) {
allowTemplateFromDomModule = allowDomModule;
};
/**
* Setting to skip processing style includes and re-writing urls in css styles.
* Normally "included" styles are pulled into the element and all urls in styles
* are re-written to be relative to the containing script url.
* If no includes or relative urls are used in styles, these steps can be
* skipped as an optimization.
*/
export let legacyOptimizations = false;
/**
* Sets `legacyOptimizations` globally for all elements to enable optimizations
* when only legacy based elements are used.
*
* @param {boolean} useLegacyOptimizations enable or disable legacy optimizations
* includes and url rewriting
* @return {void}
*/
export const setLegacyOptimizations = function(useLegacyOptimizations) {
legacyOptimizations = useLegacyOptimizations;
};

50
lib/utils/telemetry.js Normal file
View File

@@ -0,0 +1,50 @@
/**
@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
*/
/**
* Total number of Polymer element instances created.
* @type {number}
*/
export let instanceCount = 0;
export function incrementInstanceCount() {
instanceCount++;
}
/**
* Array of Polymer element classes that have been finalized.
* @type {!Array<!PolymerElementConstructor>}
*/
export const registrations = [];
/**
* @param {!PolymerElementConstructor} prototype Element prototype to log
* @private
*/
function _regLog(prototype) {
console.log('[' + prototype.is + ']: registered');
}
/**
* Registers a class prototype for telemetry purposes.
* @param {!PolymerElementConstructor} prototype Element prototype to register
* @protected
*/
export function register(prototype) {
registrations.push(prototype);
}
/**
* Logs all elements registered with an `is` to the console.
* @public
*/
export function dumpRegistrations() {
registrations.forEach(_regLog);
}

View File

@@ -112,6 +112,7 @@ class TemplateInstanceBase extends templateInstanceBase {
constructor(props) {
super();
this._configureProperties(props);
/** @type {!StampedTemplate} */
this.root = this._stampTemplate(this.__dataHost);
// Save list of stamped children
let children = this.children = [];
@@ -337,6 +338,12 @@ function createTemplatizerClass(template, templateInfo, options) {
*/
let templatizerBase = options.mutableData ?
MutableTemplateInstanceBase : TemplateInstanceBase;
// Affordance for global mixins onto TemplatizeInstance
if (templatize.mixin) {
templatizerBase = templatize.mixin(templatizerBase);
}
/**
* Anonymous class created by the templatize
* @constructor

4706
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -12,7 +12,7 @@
"@polymer/gen-typescript-declarations": "^1.5.1",
"@polymer/iron-component-page": "^3.0.0-pre.12",
"@polymer/test-fixture": "^3.0.0-pre.12",
"@webcomponents/webcomponentsjs": "^2.1.3",
"@webcomponents/webcomponentsjs": "^2.2.6",
"babel-eslint": "^7.2.3",
"babel-preset-minify": "^0.2.0",
"del": "^3.0.0",
@@ -27,11 +27,11 @@
"gulp-replace": "^0.6.1",
"gulp-size": "^3.0.0",
"gulp-vulcanize": "^7.0.0",
"lazypipe": "^1.0.1",
"lazypipe": "^1.0.2",
"merge-stream": "^1.0.1",
"parse5": "^4.0.0",
"polymer-build": "^3.1.0",
"polymer-cli": "^1.8.1",
"polymer-cli": "^1.9.4",
"through2": "^2.0.0",
"typescript": "^2.9.2",
"wct-browser-legacy": "^1.0.2"
@@ -65,7 +65,7 @@
"type-detect": "1.0.0"
},
"dependencies": {
"@webcomponents/shadycss": "^1.5.2"
"@webcomponents/shadycss": "^1.8.0"
},
"files": [
"externs",

View File

@@ -56,6 +56,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
'unit/attributes.html',
'unit/async.html',
'unit/behaviors.html',
'unit/behaviors.html?legacyOptimizations=true',
'unit/polymer.element.html',
'unit/polymer.properties-mixin.html',
'unit/polymer.properties-mixin-with-property-accessors.html',
@@ -83,13 +84,14 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
'unit/disable-upgrade.html',
'unit/shady-unscoped-style.html',
'unit/html-tag.html',
'unit/legacy-data.html'
'unit/legacy-data.html',
// 'unit/multi-style.html'
'unit/class-properties.html'
];
function combinations(suites, flags) {
return flags.map((f) => {
return f ? suites.map(s => `${s}?${f}`) : suites;
return f ? suites.map(s => `${s}${s.match(/\?/) ? '&' : '?'}${f}`) : suites;
}).reduce((arr, s) => arr.concat(s), []);
}

View File

@@ -14,6 +14,10 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
<script src="../../node_modules/@webcomponents/webcomponentsjs/webcomponents-bundle.js"></script>
<script src="wct-browser-config.js"></script>
<script src="../../node_modules/wct-browser-legacy/browser.js"></script>
<script type="module">
import {setLegacyOptimizations} from '../../lib/utils/settings.js';
setLegacyOptimizations(Boolean(window.location.search.match('legacyOptimizations')));
</script>
<script type="module" src="../../polymer-legacy.js"></script>
<body>
@@ -30,316 +34,408 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
</dom-module>
<script type="module">
import { Polymer, html } from '../../polymer-legacy.js';
window.BehaviorA = {
properties: {
import { Polymer, html } from '../../polymer-legacy.js';
window.BehaviorA = {
properties: {
label: {
type: String,
observer: '_labelChanged'
},
label: {
type: String,
observer: '_labelChanged'
},
hasOptionsA: {
readOnly: true,
notify: true
},
hasOptionsA: {
readOnly: true,
notify: true
},
overridableProperty: {
value: false
},
overridableProperty: {
value: false
},
overridablePropertyB: {
value: false
},
overridablePropertyB: {
value: false
},
hasBehaviorA: {
value: true
},
hasBehaviorA: {
value: true
},
computeADependency: {
value: true
},
computeADependency: {
value: true
},
computeA: {
computed: '_computeProp(computeADependency)'
}
},
computeA: {
computed: '_computeProp(computeADependency)'
}
},
_simpleProperty: 'A',
observers: [],
hostAttributes: {
behavior: 'A',
element: 'A',
user: 'A'
},
_simpleProperty: 'A',
listeners: {
change: '_changeHandler'
},
hostAttributes: {
behavior: 'A',
element: 'A',
user: 'A'
},
ready: function() {
this.__readyA = true;
},
listeners: {
change: '_changeHandler'
},
_labelChanged: function(label) {
this.__label = label;
},
ready: function() {
this.__readyA = true;
},
_changeHandler: function(e) {
this.__change = e.detail.value;
},
_labelChanged: function(label) {
this.__label = label;
},
_computeProp: function(a) {
return a;
}
};
_changeHandler: function(e) {
this.__change = e.detail.value;
},
window.BehaviorB = {
properties: {
_computeProp: function(a) {
return a;
}
};
disabled: {
type: Boolean,
value: false,
observer: '_disabledChanged'
},
window.BehaviorB = {
properties: {
hasOptionsB: {
readOnly: true,
notify: true
},
disabled: {
type: Boolean,
value: false,
observer: '_disabledChanged'
},
hasBehaviorB: {
value: true
},
hasOptionsB: {
readOnly: true,
notify: true
},
overridablePropertyB: {
value: true
},
hasBehaviorB: {
value: true
},
computeADependencyDependency: {
value: 'hi'
},
overridablePropertyB: {
value: true
},
computeADependency: {
computed: '_computeProp(computeADependencyDependency)'
}
computeADependencyDependency: {
value: 'hi'
},
},
computeADependency: {
computed: '_computeProp(computeADependencyDependency)'
}
hostAttributes: {
behavior: 'B',
element: 'B',
user: 'B'
},
},
_simpleProperty: 'B',
hostAttributes: {
behavior: 'B',
element: 'B',
user: 'B'
},
_disabledChanged: function(disabled) {
this.__disabled = disabled;
},
_simpleProperty: 'B',
ready: function() {
this.__readyB = true;
},
};
_disabledChanged: function(disabled) {
this.__disabled = disabled;
},
window.BehaviorC = {
ready: function() {
this.__readyB = true;
},
};
properties: {
window.BehaviorC = {
hasBehaviorC: {
value: true
}
properties: {
},
hasBehaviorC: {
value: true
}
_simpleProperty: 'C'
};
},
window.BehaviorD = {
_simpleProperty: 'C'
};
properties: {
window.BehaviorD = {
hasBehaviorD: {
value: true
}
properties: {
},
_simpleProperty: 'D'
hasBehaviorD: {
value: true
}
};
},
_simpleProperty: 'D'
Polymer({
is: 'single-behavior',
};
behaviors: [
window.BehaviorA
],
Polymer({
is: 'single-behavior',
properties: {},
observers: [],
hostAttributes: {},
listeners: {}
});
behaviors: [
window.BehaviorA
],
Polymer({
is: 'multi-behaviors',
properties: {},
observers: [],
hostAttributes: {},
listeners: {}
});
behaviors: [
window.BehaviorA,
window.BehaviorB
],
Polymer({
is: 'multi-behaviors',
hostAttributes: {
element: 'element'
},
behaviors: [
window.BehaviorA,
window.BehaviorB
],
properties: {
hostAttributes: {
element: 'element'
},
foo: {
type: String,
reflectToAttribute: true,
readOnly: true,
observer: '_fooChanged'
},
properties: {
overridableProperty: {
value: true
}
foo: {
type: String,
reflectToAttribute: true,
readOnly: true,
observer: '_fooChanged'
},
},
overridableProperty: {
value: true
}
_fooChanged: function(foo) {
this.__foo = foo;
},
});
},
Polymer({
is: 'nested-behaviors',
_fooChanged: function(foo) {
this.__foo = foo;
},
});
behaviors: [
[window.BehaviorB, [window.BehaviorC, window.BehaviorB], window.BehaviorA],
[window.BehaviorD]
]
});
Polymer({
is: 'nested-behaviors',
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;
}
};
behaviors: [
[window.BehaviorB, [window.BehaviorC, window.BehaviorB], window.BehaviorA],
[window.BehaviorD]
]
});
window.registerBehavior2 ={
prop2: true,
beforeRegister: function() {
this.beforeRegisterCount++;
},
registered: function() {
this.registeredCount++;
}
};
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.registerBehavior3 ={
prop3: true,
beforeRegister: function() {
this.beforeRegisterCount++;
},
registered: function() {
this.registeredCount++;
}
};
window.registerBehavior2 ={
prop2: 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++;
},
window.registerBehavior3 ={
prop3: true,
beforeRegister: function() {
this.beforeRegisterCount++;
},
registered: function() {
this.registeredCount++;
}
};
beforeRegisterCount: 0,
registeredCount: 0,
Polymer({
behaviors: [
window.registerBehavior1,
window.registerBehavior2,
window.registerBehavior3
],
prop4: true,
beforeRegister: function() {
this.beforeRegisterCount++;
},
registered: function() {
this.registeredCount++;
},
is: 'behavior-registered'
});
beforeRegisterCount: 0,
registeredCount: 0,
window.templateBehavior1 = {
_template: html`<div id="from-behavior1"></div>`
};
is: 'behavior-registered'
});
window.templateBehavior2 = {
_template: html`<div id="from-behavior2"></div>`
};
window.templateBehavior1 = {
_template: html`<div id="from-behavior1"></div>`
};
window.templateBehaviorFromRegistered = {
registered: function() {
this._template = html`<div id="behavior-from-registered"></div>`;
}
};
window.templateBehavior2 = {
_template: html`<div id="from-behavior2"></div>`
};
Polymer({
is: 'template-from-registered',
registered: function() {
this._template = html`<div id="from-registered"></div>`;
}
});
window.templateBehaviorFromRegistered = {
registered: function() {
this._template = html`<div id="behavior-from-registered"></div>`;
}
};
Polymer({
is: 'template-from-base',
behaviors: [
window.templateBehavior1
]
});
Polymer({
is: 'template-from-registered',
registered: function() {
this._template = html`<div id="from-registered"></div>`;
}
});
Polymer({
is: 'template-from-behavior',
behaviors: [
window.templateBehavior1
]
});
Polymer({
is: 'template-from-base',
behaviors: [
window.templateBehavior1
]
});
Polymer({
is: 'template-from-behavior-overridden',
behaviors: [
window.templateBehavior1,
window.templateBehavior2
]
});
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}
}
});
Polymer({
is: 'template-from-behavior-registered',
behaviors: [
window.templateBehaviorFromRegistered
]
});
</script>
<test-fixture id="single">
@@ -365,7 +461,7 @@ Polymer({
<behavior-registered></behavior-registered>
</template>
</test-fixture>
<test-fixture id="from-registered">
<template>
<template-from-registered></template-from-registered>
@@ -396,6 +492,36 @@ Polymer({
</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 type="module">
import { Polymer } from '../../polymer-legacy.js';
@@ -442,6 +568,36 @@ suite('single behavior element', 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() {
@@ -479,6 +635,14 @@ suite('behavior.beforeRegister', function() {
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);
});
});
@@ -602,8 +766,8 @@ suite('templates from behaviors', function() {
test('template from base', function() {
var el = fixture('from-base');
assert.ok(el.shadowRoot.querySelector('#from-base'));
assert.notOk(el.shadowRoot.querySelector('#from-behavior1'));
assert.notOk(el.shadowRoot.querySelector('#from-base'));
assert.ok(el.shadowRoot.querySelector('#from-behavior1'));
});
test('template from behavior', function() {

View File

@@ -0,0 +1,39 @@
<!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="../../node_modules/@webcomponents/webcomponentsjs/webcomponents-bundle.js"></script>
<script src="wct-browser-config.js"></script>
<script src="../../node_modules/wct-browser-legacy/browser.js"></script>
</head>
<body>
<script type="module">
import {Polymer} from '../../polymer-legacy.js';
class Example {
ready() {}
foo() {}
}
Example.prototype.is = 'x-example';
suite('Polymer function w/Class', function() {
test('Polymer call works with a class', function() {
assert.doesNotThrow(() => Polymer(Example.prototype));
});
test('methods are copied from class input', function() {
const def = customElements.get('x-example');
assert(def.prototype.ready, 'ready should be defined');
assert(def.prototype.foo, 'foo should be defined');
});
});
</script>
</body>
</html>

View File

@@ -13,8 +13,6 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
<script src="../../node_modules/@webcomponents/webcomponentsjs/webcomponents-bundle.js"></script>
<script src="wct-browser-config.js"></script>
<script src="../../node_modules/wct-browser-legacy/browser.js"></script>
<script type="module" src="../../polymer-legacy.js"></script>
<script type="module" src="../../lib/mixins/dir-mixin.js"></script>
</head>
<body>
<!--
@@ -219,6 +217,9 @@ suite(':dir', function() {
});
test('elements with dir attribute explicitly set will not change', function() {
if (window.ShadyDOM && window.ShadyDOM.inUse) {
this.skip();
}
let inner = fixture('preset');
assert.equal(document.documentElement.getAttribute('dir'), 'rtl');
assertComputed(inner.shadowRoot.querySelector('.special > div'), 'rgb(255, 255, 255)', 'color');
@@ -247,6 +248,9 @@ suite(':dir', function() {
});
test('complicated setup', function() {
if (window.ShadyDOM && window.ShadyDOM.inUse) {
this.skip();
}
let el = fixture('complicated');
assertComputed(el.$.direct, '10px');
assertComputed(el.$.indirect, '9px');

View File

@@ -93,13 +93,17 @@ Polymer({
type: String
}
},
listeners: {
foo: 'fooHandler'
},
created() {
this.hasCreated = true;
this.prop = 'enabled!';
},
ready() {
this.enabled = true;
}
},
fooHandler() {}
});
Polymer({
@@ -241,30 +245,39 @@ suite('disable-upgrade-legacy', function() {
assert.ok(el.$.enabledEl.enabled);
assert.ok(el.$.enabledEl.$.element);
assert.equal(el.$.enabledEl.$.element.textContent, 'enabled!');
el.$.enabledEl.fooHandler = sinon.spy();
el.$.enabledEl.fire('foo');
assert.equal(el.$.enabledEl.fooHandler.callCount, 1);
assert.notOk(el.$.disabledEl.hasCreated);
assert.notOk(el.$.disabledEl.enabled);
assert.notOk(el.$.disabledEl.$);
el.$.disabledEl.fooHandler = sinon.spy();
el.$.disabledEl.fire('foo');
assert.equal(el.$.disabledEl.fooHandler.callCount, 0);
assert.notOk(el.$.disabledBoundEl.hasCreated);
assert.notOk(el.$.disabledBoundEl.enabled);
assert.notOk(el.$.disabledBoundEl.$);
el.$.disabledBoundEl.fooHandler = sinon.spy();
el.$.disabledBoundEl.fire('foo');
assert.equal(el.$.disabledBoundEl.fooHandler.callCount, 0);
});
test('elements upgrade when `disable-upgrade` removed', function() {
assert.notOk(el.$.disabledEl.hasCreated);
assert.notOk(el.$.disabledEl.enabled);
assert.notOk(el.$.disabledEl.$);
assert.notOk(el.$.disabledBoundEl.hasCreated);
assert.notOk(el.$.disabledBoundEl.enabled);
assert.notOk(el.$.disabledBoundEl.$);
el.enable();
assert.ok(el.$.disabledEl.hasCreated);
assert.ok(el.$.disabledEl.enabled);
assert.ok(el.$.disabledEl.$.element);
assert.equal(el.$.disabledEl.$.element.textContent, 'enabled!');
el.$.disabledEl.fooHandler = sinon.spy();
el.$.disabledEl.fire('foo');
assert.equal(el.$.disabledEl.fooHandler.callCount, 1);
assert.ok(el.$.disabledBoundEl.hasCreated);
assert.ok(el.$.disabledBoundEl.enabled);
assert.ok(el.$.disabledBoundEl.$.element);
assert.equal(el.$.disabledBoundEl.$.element.textContent, 'enabled!');
el.$.disabledBoundEl.fooHandler = sinon.spy();
el.$.disabledBoundEl.fire('foo');
assert.equal(el.$.disabledBoundEl.fooHandler.callCount, 1);
});

View File

@@ -24,6 +24,9 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
<div id="child"
computed-single="[[computeSingle(inlineSingleDep)]]"
computed-multi="[[computeMulti(inlineMultiDep1, inlineMultiDep2)]]">
<dom-if if>
<template><div id="ifChild" computed-multi="[[computeMulti(inlineMultiIfDep1, inlineMultiIfDep2)]]"></div></template>
</dom-if>
</div>
</template>
<script type="module">
@@ -41,6 +44,8 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
inlineSingleDep: String,
inlineMultiDep1: String,
inlineMultiDep2: String,
inlineMultiIfDep1: String,
inlineMultiIfDep2: String,
computedSingle: {
computed: 'computeSingle(computedSingleDep)'
},
@@ -128,8 +133,20 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
</template>
</test-fixture>
<script>
(function() {
<test-fixture id="declarative-multi-if-one-computed-inline">
<template>
<x-data inline-multi-if-dep1="b"></x-data>
</template>
</test-fixture>
<test-fixture id="declarative-multi-if-all-computed-inline">
<template>
<x-data inline-multi-if-dep1="b" inline-multi-if-dep2="c"></x-data>
</template>
</test-fixture>
<script type="module">
import {flush} from '../../lib/utils/flush.js';
let el;
@@ -156,6 +173,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
el._legacyUndefinedCheck = check;
Object.assign(el, props);
document.body.appendChild(el);
flush();
}
teardown(() => {
@@ -172,6 +190,8 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
const inlineSingleDep = 'inlineSingleDep';
const inlineMultiDep1 = 'inlineMultiDep1';
const inlineMultiDep2 = 'inlineMultiDep2';
const inlineMultiIfDep1 = 'inlineMultiIfDep1';
const inlineMultiIfDep2 = 'inlineMultiIfDep2';
suite('check disabled', () => {
test('no arguments defined', () => {
@@ -240,6 +260,16 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
assertEffects({computeMulti: 1});
assert.equal(el.$.child.computedMulti, '[inlineMultiDep1,inlineMultiDep2]');
});
test('one inline computeMulti argument defined in dom-if', () => {
setupElement(false, {inlineMultiIfDep1});
assertEffects({computeMulti: 1});
assert.equal(el.$$('#ifChild').computedMulti, '[inlineMultiIfDep1,undefined]');
});
test('all inline computeMulti argument defined in dom-if', () => {
setupElement(false, {inlineMultiIfDep1, inlineMultiIfDep2});
assertEffects({computeMulti: 1});
assert.equal(el.$$('#ifChild').computedMulti, '[inlineMultiIfDep1,inlineMultiIfDep2]');
});
});
suite('warn', () => {
@@ -309,6 +339,16 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
assertEffects({computeMulti: 1});
assert.equal(el.$.child.computedMulti, '[inlineMultiDep1,inlineMultiDep2]');
});
test('one inline computeMulti argument defined in dom-if', () => {
setupElement(true, {inlineMultiIfDep1});
assertEffects({warn: 1});
assert.equal(el.$$('#ifChild').computedMulti, undefined);
});
test('all inline computeMulti argument defined in dom-if', () => {
setupElement(true, {inlineMultiIfDep1, inlineMultiIfDep2});
assertEffects({computeMulti: 1});
assert.equal(el.$$('#ifChild').computedMulti, '[inlineMultiIfDep1,inlineMultiIfDep2]');
});
});
});
@@ -361,8 +401,21 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
});
test('inline all computeMulti defined', () => {
el = fixture('declarative-multi-all-computed-inline');
assertEffects({computeMulti: 1});
assert.equal(el.$.child.computedMulti, '[b,c]');
});
test('one inline computeMulti argument defined in dom-if', () => {
el = fixture('declarative-multi-if-one-computed-inline');
flush();
assertEffects({computeMulti: 0, warn: 1});
assert.equal(el.$$('#ifChild').computedMulti, undefined);
});
test('all inline computeMulti argument defined in dom-if', () => {
el = fixture('declarative-multi-if-all-computed-inline');
flush();
assertEffects({computeMulti: 1});
assert.equal(el.$$('#ifChild').computedMulti, '[b,c]');
});
});
});
@@ -377,7 +430,6 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
});
});
})();
</script>
</body>
</html>

View File

@@ -18,323 +18,419 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
<body>
<script type="module">
window.LifeCycleBehavior1 = {
_calledCreated: 0,
_calledAttached: 0,
_calledAttributeChanged: 0,
window.LifeCycleBehavior1 = {
properties: {
foo: {
type: String
}
},
properties: {
foo: {
type: String
}
},
created() {
this._calledCreated++;
},
created() {
this.__createdList = this.__createdList || [];
this.__createdList.push('1');
},
attached() {
this._calledAttached++;
},
attached() {
this.__attachedList = this.__attachedList || [];
this.__attachedList.push('1');
},
attributeChanged() {
this._calledAttributeChanged++;
}
};
attributeChanged() {
this.__attributeChangedList = this.__attributeChangedList || [];
this.__attributeChangedList.push('1');
}
};
window.LifeCycleBehavior2 = {
created() {
this._calledCreated++;
},
window.LifeCycleBehavior2 = {
created() {
this.__createdList = this.__createdList || [];
this.__createdList.push('2');
},
attached() {
this._calledAttached++;
},
attached() {
this.__attachedList = this.__attachedList || [];
this.__attachedList.push('2');
}
};
};
window.LifeCycleBehavior3 = {
created() {
this.__createdList = this.__createdList || [];
this.__createdList.push('3');
}
};
window.BehaviorA = {
properties: {
window.LifeCycleBehavior4 = {
created() {
this.__createdList = this.__createdList || [];
this.__createdList.push('4');
}
};
label: {
type: String,
observer: '_labelChanged'
},
window.BehaviorA = {
properties: {
hasOptionsA: {
readOnly: true,
notify: true
},
label: {
type: String,
observer: '_labelChanged'
},
overridableProperty: {
value: false
},
hasOptionsA: {
readOnly: true,
notify: true
},
overridablePropertyB: {
value: false
},
overridableProperty: {
value: false
},
hasBehaviorA: {
value: true
},
overridablePropertyB: {
value: false
},
computeADependency: {
value: true
},
hasBehaviorA: {
value: true
},
computeA: {
computed: '_computeProp(computeADependency)'
}
},
computeADependency: {
value: true
},
_simpleProperty: 'A',
computeA: {
computed: '_computeProp(computeADependency)'
}
},
hostAttributes: {
behavior: 'A',
element: 'A',
user: 'A'
},
_simpleProperty: 'A',
listeners: {
change: '_changeHandler'
},
hostAttributes: {
behavior: 'A',
element: 'A',
user: 'A'
},
ready: function() {
this.__readyA = true;
},
listeners: {
change: '_changeHandler'
},
_labelChanged: function(label) {
this.__label = label;
},
ready: function() {
this.__readyA = true;
},
_changeHandler: function(e) {
this.__change = e.detail.value;
},
_labelChanged: function(label) {
this.__label = label;
},
_computeProp: function(a) {
return a;
}
};
_changeHandler: function(e) {
this.__change = e.detail.value;
},
window.BehaviorB = {
properties: {
_computeProp: function(a) {
return a;
}
};
disabled: {
type: Boolean,
value: false,
observer: '_disabledChanged'
},
window.BehaviorB = {
properties: {
hasOptionsB: {
readOnly: true,
notify: true
},
disabled: {
type: Boolean,
value: false,
observer: '_disabledChanged'
},
hasBehaviorB: {
value: true
},
hasOptionsB: {
readOnly: true,
notify: true
},
overridablePropertyB: {
value: true
},
hasBehaviorB: {
value: true
},
computeADependencyDependency: {
value: 'hi'
},
overridablePropertyB: {
value: true
},
computeADependency: {
computed: '_computeProp(computeADependencyDependency)'
}
computeADependencyDependency: {
value: 'hi'
},
},
computeADependency: {
computed: '_computeProp(computeADependencyDependency)'
}
hostAttributes: {
behavior: 'B',
element: 'B',
user: 'B'
},
},
_simpleProperty: 'B',
hostAttributes: {
behavior: 'B',
element: 'B',
user: 'B'
},
_disabledChanged: function(disabled) {
this.__disabled = disabled;
},
_simpleProperty: 'B',
ready: function() {
this.__readyB = true;
},
_disabledChanged: function(disabled) {
this.__disabled = disabled;
},
};
ready: function() {
this.__readyB = true;
},
window.BehaviorC = {
};
properties: {
window.BehaviorC = {
hasBehaviorC: {
value: true
}
properties: {
},
hasBehaviorC: {
value: true
}
_simpleProperty: 'C'
};
},
window.BehaviorD = {
_simpleProperty: 'C'
};
properties: {
window.BehaviorD = {
hasBehaviorD: {
value: true
}
properties: {
},
_simpleProperty: 'D'
hasBehaviorD: {
value: true
}
};
</script>
},
_simpleProperty: 'D'
};
</script>
<dom-module id="single-behavior">
<script type="module">
import { mixinBehaviors } from '../../lib/legacy/class.js';
import { PolymerElement } from '../../polymer-element.js';
customElements.define('single-behavior',
mixinBehaviors(window.BehaviorA, PolymerElement));
</script>
import { mixinBehaviors } from '../../lib/legacy/class.js';
import { PolymerElement } from '../../polymer-element.js';
customElements.define('single-behavior',
mixinBehaviors(window.BehaviorA, PolymerElement));
</script>
</dom-module>
<dom-module id="lifecycle-behavior">
<script type="module">
import { mixinBehaviors } from '../../lib/legacy/class.js';
import { PolymerElement } from '../../polymer-element.js';
customElements.define('lifecycle-behavior',
mixinBehaviors([window.LifeCycleBehavior1,
window.LifeCycleBehavior2], PolymerElement));
</script>
import { mixinBehaviors } from '../../lib/legacy/class.js';
import { PolymerElement } from '../../polymer-element.js';
customElements.define('lifecycle-behavior',
mixinBehaviors([window.LifeCycleBehavior1,
window.LifeCycleBehavior2], PolymerElement));
</script>
</dom-module>
<dom-module id="multi-behaviors">
<script type="module">
import { mixinBehaviors } from '../../lib/legacy/class.js';
import { PolymerElement } from '../../polymer-element.js';
customElements.define('multi-behaviors',
class extends mixinBehaviors(
[window.BehaviorA, window.BehaviorB], PolymerElement) {
import { mixinBehaviors } from '../../lib/legacy/class.js';
import { PolymerElement } from '../../polymer-element.js';
customElements.define('multi-behaviors',
class extends mixinBehaviors(
[window.BehaviorA, window.BehaviorB], PolymerElement) {
static get properties() {
return {
foo: {
type: String,
reflectToAttribute: true,
readOnly: true,
observer: '_fooChanged'
},
static get properties() {
return {
foo: {
type: String,
reflectToAttribute: true,
readOnly: true,
observer: '_fooChanged'
},
overridableProperty: {
value: true
}
};
}
overridableProperty: {
value: true
}
};
}
constructor() {
super();
}
constructor() {
super();
}
_fooChanged(foo) {
this.__foo = foo;
}
_fooChanged(foo) {
this.__foo = foo;
}
_ensureAttributes() {
this._ensureAttribute('element', 'element');
super._ensureAttributes();
}
});
</script>
_ensureAttributes() {
this._ensureAttribute('element', 'element');
super._ensureAttributes();
}
});
</script>
</dom-module>
<dom-module id="nested-behaviors">
<script type="module">
import { mixinBehaviors } from '../../lib/legacy/class.js';
import { PolymerElement } from '../../polymer-element.js';
customElements.define('nested-behaviors',
class extends mixinBehaviors(
[
[window.BehaviorB, [window.BehaviorC, window.BehaviorB], window.BehaviorA],
[window.BehaviorD]
], PolymerElement) {
});
</script>
</dom-module>
<script type="module">
import { mixinBehaviors } from '../../lib/legacy/class.js';
import { Polymer } from '../../polymer-legacy.js';
import { PolymerElement } from '../../polymer-element.js';
customElements.define('nested-behaviors',
class extends mixinBehaviors([window.BehaviorD, window.LifeCycleBehavior1], mixinBehaviors(
[
[window.BehaviorB, [window.BehaviorC, window.BehaviorB], window.BehaviorA, window.LifeCycleBehavior2],
], PolymerElement)) {
});
var base = Polymer({
is: 'sup-element',
behaviors: [
window.LifeCycleBehavior1,
window.LifeCycleBehavior2
],
created: function() {
this.__createdList.push('sup');
}
});
class extended extends mixinBehaviors([window.LifeCycleBehavior3,
window.LifeCycleBehavior4], base) {
created() {
super.created();
this.__createdList.push('sub');
}
}
customElements.define('extended-behaviors', extended);
</script>
<dom-module id="behavior-registered">
<template>
<div id="content"></div>
</template>
<script type="module">
import { mixinBehaviors } from '../../lib/legacy/class.js';
import { PolymerElement } from '../../polymer-element.js';
import { mixinBehaviors } from '../../lib/legacy/class.js';
import { PolymerElement } from '../../polymer-element.js';
window.registerBehavior1 ={
registered: function() {
this._createPropertyObserver('prop', 'propChanged1');
this._createMethodObserver('propChanged2(prop)');
this.registeredCount++;
this.registeredProps = [this.prop1, this.prop2, this.prop3];
this.registeredBehaviors = this.behaviors;
},
prop1: true,
ready: function() {
this._ensureAttribute('attr', true);
},
propChanged1: function() {
this.propChanged1Called = true;
},
propChanged2: function() {
this.propChanged2Called = true;
}
};
window.registerBehavior1 ={
registeredCount: 0,
registered: function() {
this._createPropertyObserver('prop', 'propChanged1');
this._createMethodObserver('propChanged2(prop)');
this.registeredCount++;
this.registeredProps = [this.prop1, this.prop2, this.prop3];
this.registeredBehaviors = this.behaviors;
},
prop1: true,
ready: function() {
this._ensureAttribute('attr', true);
},
propChanged1: function() {
this.propChanged1Called = true;
},
propChanged2: function() {
this.propChanged2Called = true;
}
};
window.registerBehavior2 ={
prop2: true,
registered: function() {
this.registeredCount++;
}
};
window.registerBehavior2 ={
prop2: true,
registered: function() {
this.registeredCount++;
}
};
window.registerBehavior3 ={
prop3: true,
registered: function() {
this.registeredCount++;
}
};
window.registerBehavior3 ={
prop3: true,
registered: function() {
this.registeredCount++;
}
};
class BehaviorRegistered extends mixinBehaviors([
window.registerBehavior1,
window.registerBehavior2,
window.registerBehavior3
], PolymerElement) {
class BehaviorRegistered extends mixinBehaviors([
window.registerBehavior1,
window.registerBehavior2,
window.registerBehavior3
], PolymerElement) {
static get is() { return 'behavior-registered';}
static get is() { return 'behavior-registered';}
_initializeProperties() {
super._initializeProperties();
this.registeredCount++;
}
}
_initializeProperties() {
super._initializeProperties();
this.registeredCount++;
}
}
BehaviorRegistered.prototype.registeredCount = 0;
customElements.define(BehaviorRegistered.is, BehaviorRegistered);
customElements.define(BehaviorRegistered.is, BehaviorRegistered);
class BehaviorRegisteredExt extends BehaviorRegistered {
static get is() { return 'behavior-registered-ext';}
}
class BehaviorRegisteredExt extends BehaviorRegistered {
static get is() { return 'behavior-registered-ext';}
}
BehaviorRegisteredExt.prototype.registeredCount = 0;
customElements.define(BehaviorRegisteredExt.is, BehaviorRegisteredExt);
</script>
customElements.define(BehaviorRegisteredExt.is, BehaviorRegisteredExt);
</script>
</dom-module>
<script type="module">
import { mixinBehaviors } from '../../lib/legacy/class.js';
import { PolymerElement } from '../../polymer-element.js';
var klass = mixinBehaviors([
{
__fooChangedCalled: 0,
beforeRegister: function() {
this.constructor.generatedFrom.observers = [
'_fooChanged(foo)'
];
},
_fooChanged: function() {
this.__fooChangedCalled++;
}
}
], PolymerElement);
customElements.define('before-register-observers', klass);
</script>
<script type="module">
import { mixinBehaviors } from '../../lib/legacy/class.js';
import { PolymerElement } from '../../polymer-element.js';
const Base = mixinBehaviors([{
registered() {
this.usedExtendedProto = this.canUseExtendedProto;
}
}], PolymerElement);
customElements.define('registered-proto', mixinBehaviors([
{canUseExtendedProto: true}
], Base));
</script>
<script type="module">
import { mixinBehaviors } from '../../lib/legacy/class.js';
import { PolymerElement } from '../../polymer-element.js';
class Base extends mixinBehaviors([{
properties: {b1: String} }], PolymerElement) {
static get properties() {
return { e1: String};
}
}
class El extends mixinBehaviors([{
properties: {b2: String}}], Base) {
static get properties() {
return { e2: String};
}
}
customElements.define('extended-observed-attributes', El);
</script>
<test-fixture id="single">
<template>
<single-behavior></single-behavior>
@@ -355,7 +451,13 @@ customElements.define(BehaviorRegisteredExt.is, BehaviorRegisteredExt);
<test-fixture id="nested">
<template>
<nested-behaviors></nested-behaviors>
<nested-behaviors foo="foo"></nested-behaviors>
</template>
</test-fixture>
<test-fixture id="extended-behaviors">
<template>
<extended-behaviors></extended-behaviors>
</template>
</test-fixture>
@@ -365,11 +467,31 @@ customElements.define(BehaviorRegisteredExt.is, BehaviorRegisteredExt);
</template>
</test-fixture>
<test-fixture id="before-register-observers">
<template>
<before-register-observers></before-register-observers>
</template>
</test-fixture>
<test-fixture id="registered-ext">
<template>
<behavior-registered-ext></behavior-registered-ext>
</template>
</test-fixture>
<test-fixture id="registered-proto">
<template>
<registered-proto></registered-proto>
</template>
</test-fixture>
<test-fixture id="extended-observed-attributes">
<template>
<extended-observed-attributes b1="b1" e1="e1" b2="b2" e2="e2"></extended-observed-attributes>
</template>
</test-fixture>
<script type="module">
import { Polymer } from '../../polymer-legacy.js';
import { mixinBehaviors } from '../../lib/legacy/class.js';
@@ -412,7 +534,7 @@ suite('single behavior element', function() {
});
suite('behavior.registered', function() {
suite('behavior.registered/beforeRegister', function() {
test('can install dynamic properties', function() {
var el = fixture('registered');
assert.ok(el.$.content);
@@ -438,6 +560,17 @@ suite('behavior.registered', function() {
assert.deepEqual(el.registeredProps, [true, true, true]);
});
test('add observers via behavior in beforeRegister', function() {
var el = fixture('before-register-observers');
el.foo = 1;
assert.equal(el.__fooChangedCalled, 1);
});
test('registered called on class prototype when extended', function() {
var el = fixture('registered-proto');
assert.isTrue(el.usedExtendedProto);
});
});
suite('behavior lifecycle', function() {
@@ -449,9 +582,9 @@ suite('behavior lifecycle', function() {
});
test('lifecycle', function() {
assert.equal(el._calledCreated, 2, 'created call count wrong');
assert.equal(el._calledAttached, 2, 'attached call count wrong');
assert.equal(el._calledAttributeChanged, 1, 'attributeChanged call count wrong');
assert.deepEqual(el.__createdList, ['1', '2'], 'created list wrong');
assert.deepEqual(el.__attachedList, ['1', '2'], 'attached list wrong');
assert.deepEqual(el.__attributeChangedList, ['1'], 'attributeChanged list wrong');
});
});
@@ -554,7 +687,13 @@ suite('nested-behaviors element', function() {
});
test('nested-behavior dedups', function() {
assert.equal(el.behaviors.length, 4);
assert.equal(el.behaviors.length, 6);
});
test('nested-behavior lifecycle', function() {
assert.deepEqual(el.__createdList, ['2', '1'], 'created list wrong');
assert.deepEqual(el.__attachedList, ['2', '1'], 'attached list wrong');
assert.deepEqual(el.__attributeChangedList, ['1'], 'attributeChanged list wrong');
});
test('nested-behavior overrides ordering', function() {
@@ -566,6 +705,29 @@ suite('nested-behaviors element', function() {
});
});
suite('extended-behaviors', function() {
var el;
setup(function() {
el = fixture('extended-behaviors');
});
test('lifecycle', function() {
assert.deepEqual(el.__createdList, ['1', '2', 'sup', '3', '4', 'sub'], 'created list wrong');
});
test('observedAttributes when extended', function() {
const el = fixture('extended-observed-attributes');
assert.equal(el.b1, 'b1');
assert.equal(el.e1, 'e1');
assert.equal(el.b2, 'b2');
assert.equal(el.e2, 'e2');
});
});
</script>
</body>

View File

@@ -162,6 +162,9 @@ Polymer({
'aChanged(a.*)',
'xChanged(x.*)',
'yChanged(y.*)',
'abChanged(a.b.*)',
'abcChanged(a.b.c.*)',
'abcxChanged(a.b.c.*, x)'
],
created: function() {
@@ -190,6 +193,9 @@ Polymer({
this.$.forward.resetObservers();
this.$.pe.resetObservers();
}
this.abChanged = sinon.spy();
this.abcChanged = sinon.spy();
this.abcxChanged = sinon.spy();
},
computeFromPaths: function(a, b, c) {
@@ -245,6 +251,148 @@ Polymer({
this.objChanged = sinon.spy();
}
});
Polymer({
is: 'x-reentry-true',
properties: {
prop: {
type: Boolean,
computed: 'computeProp(trigger, value)'
},
value: {
type: Boolean
},
trigger: {
type: Boolean,
observer: 'triggerChanged',
value: true
}
},
observers: ['propChanged(prop.*)'],
created() {
this.propChanged = sinon.spy();
},
computeProp(trigger, value) {
return Boolean(value);
},
triggerChanged() {
this.value = true;
}
});
Polymer({
is: 'x-reentry-undefined',
properties: {
prop: {
type: Boolean,
computed: 'computeProp(trigger, value)'
},
value: {
type: Boolean
},
trigger: {
type: Boolean,
observer: 'triggerChanged',
value: true
}
},
observers: ['propChanged(prop.*)'],
created() {
this.propChanged = sinon.spy();
},
computeProp(trigger, value) {
return value ? undefined : true;
},
triggerChanged() {
this.value = true;
}
});
Polymer({
is: 'x-reentry-undefined-path',
properties: {
prop: {
type: Object
},
value: {
type: Boolean
},
trigger: {
type: Boolean,
value: true
}
},
observers: [
'computeProp(trigger, value)',
'triggerChanged(trigger)',
'propChanged(prop.sub.*)'
],
created() {
this.propChanged = sinon.spy();
},
computeProp(trigger, value) {
if (value) {
this.set('prop.sub', undefined);
} else {
this.prop = {sub: true};
}
},
triggerChanged() {
this.value = true;
}
});
Polymer({
is: 'x-reentry-splices',
properties: {
array: {
type: Array,
value() { return []; }
}
},
observers: [
'changeArray(array.splices.*)',
'arrayChanged(array.splices.*)'
],
created() {
this.arrayChanged = sinon.spy();
},
changeArray() {
if (this.array.length === 0) {
this.push('array', 'one');
} else if (this.array.length === 1) {
this.push('array', 'two');
}
}
});
Polymer({
is: 'x-path-client',
observers: ['objChanged(obj.*)'],
@@ -273,4 +421,4 @@ Polymer({
created: function() {
this.objChanged = sinon.spy();
}
});
});

View File

@@ -341,6 +341,112 @@ suite('basic path bindings', function() {
assert.equal(el.$.compose.$.basic1.othervalue, 98);
});
suite('ancestors and descendants', function() {
test('change base', function() {
let c = {}, b = {c}, a = {b};
el.a = a;
assert.equal(el.aChanged.callCount, 1);
assert.deepEqual(el.aChanged.firstCall.args[0], {path: 'a', base: a, value: a});
assert.equal(el.abChanged.callCount, 1);
assert.deepEqual(el.abChanged.firstCall.args[0], {path: 'a.b', base: b, value: b});
assert.equal(el.abcChanged.callCount, 1);
assert.deepEqual(el.abcChanged.firstCall.args[0], {path: 'a.b.c', base: c, value: c});
assert.equal(el.abcxChanged.callCount, 1);
assert.deepEqual(el.abcxChanged.firstCall.args[0], {path: 'a.b.c', base: c, value: c});
});
test('change subpath level 1', function() {
let c = {}, b = {c}, a = {b};
el.a = a;
el.resetObservers();
c = {};
b = {c};
el.set('a.b', b);
assert.equal(el.aChanged.callCount, 1);
assert.deepEqual(el.aChanged.firstCall.args[0], {path: 'a.b', base: a, value: b});
assert.equal(el.abChanged.callCount, 1);
assert.deepEqual(el.abChanged.firstCall.args[0], {path: 'a.b', base: b, value: b});
assert.equal(el.abcChanged.callCount, 1);
assert.deepEqual(el.abcChanged.firstCall.args[0], {path: 'a.b.c', base: c, value: c});
assert.equal(el.abcxChanged.callCount, 1);
assert.deepEqual(el.abcxChanged.firstCall.args[0], {path: 'a.b.c', base: c, value: c});
});
test('change subpath level 2', function() {
let c = {}, b = {c}, a = {b};
el.a = a;
el.resetObservers();
c = {};
el.set('a.b.c', c);
assert.equal(el.aChanged.callCount, 1);
assert.deepEqual(el.aChanged.firstCall.args[0], {path: 'a.b.c', base: a, value: c});
assert.equal(el.abChanged.callCount, 1);
assert.deepEqual(el.abChanged.firstCall.args[0], {path: 'a.b.c', base: b, value: c});
assert.equal(el.abcChanged.callCount, 1);
assert.deepEqual(el.abcChanged.firstCall.args[0], {path: 'a.b.c', base: c, value: c});
assert.equal(el.abcxChanged.callCount, 1);
assert.deepEqual(el.abcxChanged.firstCall.args[0], {path: 'a.b.c', base: c, value: c});
});
test('change subpath of base only', function() {
let c = {}, b = {c}, a = {b};
el.a = a;
el.resetObservers();
let d = {};
el.set('a.d', d);
assert.equal(el.aChanged.callCount, 1);
assert.deepEqual(el.aChanged.firstCall.args[0], {path: 'a.d', base: a, value: d});
assert.equal(el.abChanged.callCount, 0);
assert.equal(el.abcChanged.callCount, 0);
assert.equal(el.abcxChanged.callCount, 0);
});
test('change subpath of level 1 only', function() {
let c = {}, b = {c}, a = {b};
el.a = a;
el.resetObservers();
let e = {};
el.set('a.b.e', e);
assert.equal(el.aChanged.callCount, 1);
assert.deepEqual(el.aChanged.firstCall.args[0], {path: 'a.b.e', base: a, value: e});
assert.equal(el.abChanged.callCount, 1);
assert.deepEqual(el.abChanged.firstCall.args[0], {path: 'a.b.e', base: b, value: e});
assert.equal(el.abcChanged.callCount, 0);
assert.equal(el.abcxChanged.callCount, 0);
});
test('change subpath of level 2', function() {
let c = {}, b = {c}, a = {b};
el.a = a;
el.resetObservers();
let f = {};
el.set('a.b.c.f', f);
assert.equal(el.aChanged.callCount, 1);
assert.deepEqual(el.aChanged.firstCall.args[0], {path: 'a.b.c.f', base: a, value: f});
assert.equal(el.abChanged.callCount, 1);
assert.deepEqual(el.abChanged.firstCall.args[0], {path: 'a.b.c.f', base: b, value: f});
assert.equal(el.abcChanged.callCount, 1);
assert.deepEqual(el.abcChanged.firstCall.args[0], {path: 'a.b.c.f', base: c, value: f});
assert.equal(el.abcxChanged.callCount, 1);
assert.deepEqual(el.abcxChanged.firstCall.args[0], {path: 'a.b.c.f', base: c, value: f});
});
test('change non-wildcard argument', function() {
let c = {}, b = {c}, a = {b};
el.a = a;
el.resetObservers();
let x = el.x = {};
assert.equal(el.aChanged.callCount, 0);
assert.equal(el.abChanged.callCount, 0);
assert.equal(el.abcChanged.callCount, 0);
assert.equal(el.abcxChanged.callCount, 1);
assert.deepEqual(el.abcxChanged.firstCall.args[0], {path: 'a.b.c', base: c, value: c});
assert.equal(el.abcxChanged.firstCall.args[1], x);
});
});
});
suite('path effects', function() {
@@ -1099,6 +1205,63 @@ suite('corner cases', function() {
assert.equal(host.objChanged.secondCall.args[0].value, 1);
});
test('reentry after wildcard observer queued', function() {
let el = document.createElement('x-reentry-true');
document.body.appendChild(el);
assert.equal(el.propChanged.callCount, 2);
assert.equal(el.propChanged.firstCall.args[0].base, true);
assert.equal(el.propChanged.firstCall.args[0].value, true);
assert.equal(el.propChanged.secondCall.args[0].base, true);
assert.equal(el.propChanged.secondCall.args[0].value, true);
document.body.removeChild(el);
});
// TODO(kschaaf): address code in `getArgValue` that looks in changedProps
// when `undefined` for splices, which breaks this test (latent issue)
// https://github.com/Polymer/polymer/issues/5479
test.skip('reentry after prop goes back to undefined', function() {
let el = document.createElement('x-reentry-undefined');
document.body.appendChild(el);
assert.equal(el.propChanged.callCount, 2);
assert.equal(el.propChanged.firstCall.args[0].base, undefined);
assert.equal(el.propChanged.firstCall.args[0].value, undefined);
assert.equal(el.propChanged.secondCall.args[0].base, undefined);
assert.equal(el.propChanged.secondCall.args[0].value, undefined);
document.body.removeChild(el);
});
// TODO(kschaaf): address code in `getArgValue` that looks in changedProps
// when `undefined` for splices, which breaks this test (latent issue)
// https://github.com/Polymer/polymer/issues/5479
test.skip('reentry after path goes back to undefined', function() {
let el = document.createElement('x-reentry-undefined-path');
document.body.appendChild(el);
assert.equal(el.propChanged.callCount, 2);
assert.equal(el.propChanged.firstCall.args[0].base, undefined);
assert.equal(el.propChanged.firstCall.args[0].value, undefined);
assert.equal(el.propChanged.secondCall.args[0].base, undefined);
assert.equal(el.propChanged.secondCall.args[0].value, undefined);
document.body.removeChild(el);
});
test('reentry from array splices', function() {
let el = document.createElement('x-reentry-splices');
document.body.appendChild(el);
assert.equal(el.arrayChanged.callCount, 3);
assert.deepEqual(el.arrayChanged.getCalls()[0].args[0].value, {
indexSplices: [{
index: 1, addedCount: 1, removed: [], object: el.array, type: 'splice'
}]
});
assert.deepEqual(el.arrayChanged.getCalls()[1].args[0].value, {
indexSplices: [{
index: 0, addedCount: 1, removed: [], object: el.array, type: 'splice'
}]
});
assert.deepEqual(el.arrayChanged.getCalls()[2].args[0].value, undefined);
document.body.removeChild(el);
});
test('element without notify effects still notifies paths (1.x guarantee)', function() {
let host = document.createElement('x-path-host');
let listener = sinon.spy();

View File

@@ -25,8 +25,8 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
<script type="module">
import './property-effects-elements.js';
import { Polymer } from '../../lib/legacy/polymer-fn.js';
import { setSanitizeDOMValue, sanitizeDOMValue } from '../../lib/utils/settings.js';
import { Polymer, html } from '../../polymer-legacy.js';
import { setSanitizeDOMValue, sanitizeDOMValue, setLegacyOptimizations } from '../../lib/utils/settings.js';
import { PropertyEffects } from '../../lib/mixins/property-effects.js';
suite('single-element binding effects', function() {
@@ -1872,6 +1872,110 @@ suite('a la carte usage of API', function() {
document.body.removeChild(el);
});
});
suite('warn on legacy differences', () => {
setup(function() {
sinon.spy(console, 'warn');
});
teardown(function() {
setLegacyOptimizations(false);
console.warn.restore();
});
test('warn if non-declared property used in binding', () => {
setLegacyOptimizations(true);
Polymer({
is: 'x-warn-undeclared-binding',
_template: html`
<div attr$="[[undeclaredToAttr]]"
prop="[[undeclaredToProp]]">
[[undeclaredToText]]</div>`
});
const el = document.createElement('x-warn-undeclared-binding');
document.body.appendChild(el);
document.body.removeChild(el);
assert.equal(console.warn.callCount, 3);
});
test('warn when re-declaring a computed property', () => {
Polymer({
is: 'x-warn-redeclared-computed',
behaviors: [{
properties: {
a: { computed: 'compute(x)' }
}
}, {
properties: {
a: { computed: 'compute(y)' }
}
}]
});
const el = document.createElement('x-warn-redeclared-computed');
document.body.appendChild(el);
document.body.removeChild(el);
assert.equal(console.warn.callCount, 1);
});
test('warn when disabling readOnly on a readOnly property', () => {
Polymer({
is: 'x-warn-disable-readonly',
behaviors: [{
properties: {
a: { readOnly: true }
}
}, {
properties: {
a: { readOnly: false }
}
}]
});
const el = document.createElement('x-warn-disable-readonly');
document.body.appendChild(el);
document.body.removeChild(el);
assert.equal(console.warn.callCount, 1);
});
test('warn when disabling reflect on a reflect property', () => {
Polymer({
is: 'x-warn-disable-reflect',
behaviors: [{
properties: {
a: { reflectToAttribute: true }
}
}, {
properties: {
a: { reflectToAttribute: false }
}
}]
});
const el = document.createElement('x-warn-disable-reflect');
document.body.appendChild(el);
document.body.removeChild(el);
assert.equal(console.warn.callCount, 1);
});
test('warn when disabling notify on a notify property', () => {
Polymer({
is: 'x-warn-disable-notify',
behaviors: [{
properties: {
a: { notify: true }
}
}, {
properties: {
a: { notify: false }
}
}]
});
const el = document.createElement('x-warn-disable-notify');
document.body.appendChild(el);
document.body.removeChild(el);
assert.equal(console.warn.callCount, 1);
});
});
</script>

View File

@@ -153,6 +153,18 @@ Polymer({
</script>
</dom-module>
<dom-module id="x-class-literal">
<template>
<div id="scope" class$="a [[b]] c" class="d e">Trivial</div>
</template>
<script type="module">
import { Polymer } from '../../polymer-legacy.js';
Polymer({
is: 'x-class-literal'
});
</script>
</dom-module>
<dom-module id="x-styled">
@@ -993,6 +1005,16 @@ suite('scoped-styling', function() {
assertComputed(e, '5px', 'padding-top');
});
test('initial literal values in class are preserved when a class$ binding is present', function() {
var e = document.createElement('x-class-literal');
document.body.appendChild(e);
var el = e.$.scope;
assert.isTrue(el.classList.contains('a'));
assert.isTrue(el.classList.contains('c'));
assert.isTrue(el.classList.contains('d'));
assert.isTrue(el.classList.contains('e'));
});
});
suite('double including style sheets', function() {
@@ -1144,9 +1166,9 @@ if (window.ShadyDOM) {
el.shadowRoot.appendChild(div);
flush();
div.className = 'foo';
assert.isTrue(div.classList.contains('foo'));
assert.isTrue(div.classList.contains('style-scope'));
assert.isTrue(div.classList.contains('x-scope-class'));
assert.isTrue(div.classList.contains('foo'), 'foo should be in classList');
assert.isTrue(div.classList.contains('style-scope'), 'style-scope should be in classList');
assert.isTrue(div.classList.contains('x-scope-class'), 'x-scope-class should be in classList');
el.shadowRoot.appendChild(div);
flush();
document.body.removeChild(el);