diff --git a/.clang-format b/.clang-format new file mode 100644 index 00000000..62cdf4c6 --- /dev/null +++ b/.clang-format @@ -0,0 +1,12 @@ +BasedOnStyle: Google +AlignAfterOpenBracket: AlwaysBreak +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: None +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +BinPackArguments: false +# This breaks async functions sometimes, see +# https://github.com/Polymer/polymer-analyzer/pull/393 +# BinPackParameters: false diff --git a/src/demo/ts-element.ts b/src/demo/ts-element.ts index 3b1d7cbc..8cf0c926 100644 --- a/src/demo/ts-element.ts +++ b/src/demo/ts-element.ts @@ -1,11 +1,10 @@ import {html, LitElement, property} from '../lit-element.js'; class TSElement extends LitElement { - @property() message = 'Hi'; @property( - {attribute : 'more-info', converter: (value: string) => `[${value}]`}) + {attribute: 'more-info', converter: (value: string) => `[${value}]`}) extra = ''; render() { diff --git a/src/env.d.ts b/src/env.d.ts index 080f5f39..781982ef 100644 --- a/src/env.d.ts +++ b/src/env.d.ts @@ -20,7 +20,7 @@ interface ShadowRoot { adoptedStyleSheets: CSSStyleSheet[]; } -declare var ShadowRoot: {prototype: ShadowRoot; new () : ShadowRoot;} +declare var ShadowRoot: {prototype: ShadowRoot; new (): ShadowRoot;} interface CSSStyleSheet { replaceSync(cssText: string): void; diff --git a/src/lib/css-tag.ts b/src/lib/css-tag.ts index 5e61112a..eb180902 100644 --- a/src/lib/css-tag.ts +++ b/src/lib/css-tag.ts @@ -10,19 +10,20 @@ found at http://polymer.github.io/PATENTS.txt */ export const supportsAdoptingStyleSheets = - ('adoptedStyleSheets' in Document.prototype) && ('replace' in CSSStyleSheet.prototype); + ('adoptedStyleSheets' in Document.prototype) && + ('replace' in CSSStyleSheet.prototype); const constructionToken = Symbol(); export class CSSResult { - _styleSheet?: CSSStyleSheet|null; readonly cssText: string; constructor(cssText: string, safeToken: symbol) { if (safeToken !== constructionToken) { - throw new Error('CSSResult is not constructable. Use `unsafeCSS` or `css` instead.'); + throw new Error( + 'CSSResult is not constructable. Use `unsafeCSS` or `css` instead.'); } this.cssText = cssText; } @@ -66,7 +67,7 @@ const textFromCSSResult = (value: CSSResult) => { throw new Error( `Value passed to 'css' function must be a 'css' function result: ${ value}. Use 'unsafeCSS' to pass non-literal values, but - take care to ensure page security.` ); + take care to ensure page security.`); } }; @@ -76,10 +77,9 @@ const textFromCSSResult = (value: CSSResult) => { * used. To incorporate non-literal values `unsafeCSS` may be used inside a * template string part. */ -export const css = - (strings: TemplateStringsArray, ...values: CSSResult[]) => { - const cssText = values.reduce( - (acc, v, idx) => acc + textFromCSSResult(v) + strings[idx + 1], - strings[0]); - return new CSSResult(cssText, constructionToken); - }; +export const css = (strings: TemplateStringsArray, ...values: CSSResult[]) => { + const cssText = values.reduce( + (acc, v, idx) => acc + textFromCSSResult(v) + strings[idx + 1], + strings[0]); + return new CSSResult(cssText, constructionToken); +}; diff --git a/src/lib/decorators.ts b/src/lib/decorators.ts index ee9f179c..bc72e65e 100644 --- a/src/lib/decorators.ts +++ b/src/lib/decorators.ts @@ -68,12 +68,12 @@ const standardCustomElement = * * @param tagName the name of the custom element to define */ -export const customElement = (tagName: string) => ( - classOrDescriptor: Constructor|ClassDescriptor) => - (typeof classOrDescriptor === 'function') - ? legacyCustomElement(tagName, - classOrDescriptor as Constructor) - : standardCustomElement(tagName, classOrDescriptor as ClassDescriptor); +export const customElement = (tagName: string) => + (classOrDescriptor: Constructor|ClassDescriptor) => + (typeof classOrDescriptor === 'function') ? + legacyCustomElement( + tagName, classOrDescriptor as Constructor) : + standardCustomElement(tagName, classOrDescriptor as ClassDescriptor); const standardProperty = (options: PropertyDeclaration, element: ClassElement) => { @@ -93,10 +93,10 @@ const standardProperty = // must return some kind of descriptor, so return a descriptor for an // unused prototype field. The finisher calls createProperty(). return { - kind : 'field', - key : Symbol(), - placement : 'own', - descriptor : {}, + kind: 'field', + key: Symbol(), + placement: 'own', + descriptor: {}, // When @babel/plugin-proposal-decorators implements initializers, // do this instead of the initializer below. See: // https://github.com/babel/babel/issues/9260 extras: [ @@ -118,10 +118,11 @@ const standardProperty = } }; -const legacyProperty = (options: PropertyDeclaration, proto: Object, - name: PropertyKey) => { - (proto.constructor as typeof UpdatingElement).createProperty(name!, options); -}; +const legacyProperty = + (options: PropertyDeclaration, proto: Object, name: PropertyKey) => { + (proto.constructor as typeof UpdatingElement) + .createProperty(name!, options); + }; /** * A property decorator which creates a LitElement property which reflects a @@ -132,35 +133,36 @@ const legacyProperty = (options: PropertyDeclaration, proto: Object, */ export function property(options?: PropertyDeclaration) { return (protoOrDescriptor: Object|ClassElement, name?: PropertyKey): any => - (name !== undefined) - ? legacyProperty(options!, protoOrDescriptor as Object, name) - : standardProperty(options!, - protoOrDescriptor as ClassElement); + (name !== undefined) ? + legacyProperty(options!, protoOrDescriptor as Object, name) : + standardProperty(options!, protoOrDescriptor as ClassElement); } /** * A property decorator that converts a class property into a getter that * executes a querySelector on the element's renderRoot. */ -export const query = _query((target: NodeSelector, selector: string) => - target.querySelector(selector)); +export const query = _query( + (target: NodeSelector, selector: string) => target.querySelector(selector)); /** * A property decorator that converts a class property into a getter * that executes a querySelectorAll on the element's renderRoot. */ -export const queryAll = _query((target: NodeSelector, selector: string) => - target.querySelectorAll(selector)); +export const queryAll = _query( + (target: NodeSelector, selector: string) => + target.querySelectorAll(selector)); const legacyQuery = - (descriptor: PropertyDescriptor, proto: Object, - name: PropertyKey) => { Object.defineProperty(proto, name, descriptor); }; + (descriptor: PropertyDescriptor, proto: Object, name: PropertyKey) => { + Object.defineProperty(proto, name, descriptor); + }; const standardQuery = (descriptor: PropertyDescriptor, element: ClassElement) => ({ - kind : 'method', - placement : 'prototype', - key : element.key, + kind: 'method', + placement: 'prototype', + key: element.key, descriptor, }); @@ -173,17 +175,20 @@ const standardQuery = (descriptor: PropertyDescriptor, element: ClassElement) => * element. */ function _query(queryFn: (target: NodeSelector, selector: string) => T) { - return (selector: string) => (protoOrDescriptor: Object|ClassElement, - name?: PropertyKey): any => { - const descriptor = { - get(this: LitElement) { return queryFn(this.renderRoot!, selector); }, - enumerable : true, - configurable : true, - }; - return (name !== undefined) - ? legacyQuery(descriptor, protoOrDescriptor as Object, name) - : standardQuery(descriptor, protoOrDescriptor as ClassElement); - }; + return (selector: string) => + (protoOrDescriptor: Object|ClassElement, + name?: PropertyKey): any => { + const descriptor = { + get(this: LitElement) { + return queryFn(this.renderRoot!, selector); + }, + enumerable: true, + configurable: true, + }; + return (name !== undefined) ? + legacyQuery(descriptor, protoOrDescriptor as Object, name) : + standardQuery(descriptor, protoOrDescriptor as ClassElement); + }; } const standardEventOptions = @@ -191,15 +196,16 @@ const standardEventOptions = return { ...element, finisher(clazz: typeof UpdatingElement) { - Object.assign(clazz.prototype[element.key as keyof UpdatingElement], - options); + Object.assign( + clazz.prototype[element.key as keyof UpdatingElement], options); } }; }; const legacyEventOptions = - (options: AddEventListenerOptions, proto: any, - name: PropertyKey) => { Object.assign(proto[name], options); }; + (options: AddEventListenerOptions, proto: any, name: PropertyKey) => { + Object.assign(proto[name], options); + }; /** * Adds event listener options to a method used as an event listener in a @@ -234,7 +240,7 @@ export const eventOptions = (options: AddEventListenerOptions) => // TODO(kschaaf): unclear why it was only failing on this decorator and not // the others ((protoOrDescriptor: Object|ClassElement, name?: string) => - (name !== undefined) - ? legacyEventOptions(options, protoOrDescriptor as Object, name) - : standardEventOptions(options, - protoOrDescriptor as ClassElement)) as any; + (name !== undefined) ? + legacyEventOptions(options, protoOrDescriptor as Object, name) : + standardEventOptions(options, protoOrDescriptor as ClassElement)) as + any; diff --git a/src/lib/updating-element.ts b/src/lib/updating-element.ts index c55b0c3a..f753dc08 100644 --- a/src/lib/updating-element.ts +++ b/src/lib/updating-element.ts @@ -34,7 +34,6 @@ declare global { * Converts property values to and from attribute values. */ export interface ComplexAttributeConverter { - /** * Function called to convert an attribute value to a property * value. @@ -55,7 +54,6 @@ type AttributeConverter = * Defines options for a property accessor. */ export interface PropertyDeclaration { - /** * Indicates how and whether the property becomes an observed attribute. * If the value is `false`, the property is not added to `observedAttributes`. @@ -132,26 +130,26 @@ export const defaultConverter: ComplexAttributeConverter = { toAttribute(value: any, type?: any) { switch (type) { - case Boolean: - return value ? '' : null; - case Object: - case Array: - // if the value is `null` or `undefined` pass this through - // to allow removing/no change behavior. - return value == null ? value : JSON.stringify(value); + case Boolean: + return value ? '' : null; + case Object: + case Array: + // if the value is `null` or `undefined` pass this through + // to allow removing/no change behavior. + return value == null ? value : JSON.stringify(value); } return value; }, fromAttribute(value: any, type?: any) { switch (type) { - case Boolean: - return value !== null; - case Number: - return value === null ? null : Number(value); - case Object: - case Array: - return JSON.parse(value); + case Boolean: + return value !== null; + case Number: + return value === null ? null : Number(value); + case Object: + case Array: + return JSON.parse(value); } return value; } @@ -172,11 +170,11 @@ export const notEqual: HasChanged = (value: unknown, old: unknown): boolean => { }; const defaultPropertyDeclaration: PropertyDeclaration = { - attribute : true, - type : String, - converter : defaultConverter, - reflect : false, - hasChanged : notEqual + attribute: true, + type: String, + converter: defaultConverter, + reflect: false, + hasChanged: notEqual }; const microtaskPromise = Promise.resolve(true); @@ -196,7 +194,6 @@ type UpdateState = typeof STATE_HAS_UPDATED|typeof STATE_UPDATE_REQUESTED| * should be supplied by subclassers to render updates as desired. */ export abstract class UpdatingElement extends HTMLElement { - /* * Due to closure compiler ES6 compilation bugs, @nocollapse is required on * all static methods and properties with initializers. Reference: @@ -261,8 +258,8 @@ export abstract class UpdatingElement extends HTMLElement { // NOTE: Workaround IE11 not supporting Map constructor argument. const superProperties = Object.getPrototypeOf(this)._classProperties; if (superProperties !== undefined) { - superProperties.forEach((v: any, k: PropertyKey) => - this._classProperties!.set(k, v)); + superProperties.forEach( + (v: any, k: PropertyKey) => this._classProperties!.set(k, v)); } } } @@ -274,9 +271,9 @@ export abstract class UpdatingElement extends HTMLElement { * an update. * @nocollapse */ - static createProperty(name: PropertyKey, - options: - PropertyDeclaration = defaultPropertyDeclaration) { + static createProperty( + name: PropertyKey, + options: PropertyDeclaration = defaultPropertyDeclaration) { // Note, since this can be called by the `@property` decorator which // is called before `finalize`, we ensure storage exists for property // metadata. @@ -292,16 +289,17 @@ export abstract class UpdatingElement extends HTMLElement { } const key = typeof name === 'symbol' ? Symbol() : `__${name}`; Object.defineProperty(this.prototype, name, { - get(): any { return (this as any)[key]; }, + get(): any { + return (this as any)[key]; + }, set(this: UpdatingElement, value: any) { const oldValue = (this as any)[name]; (this as any)[key] = value; - this.requestUpdate(name, oldValue); - }, - configurable : true, - enumerable : true - } - ); + this.requestUpdate(name, oldValue); + }, + configurable: true, + enumerable: true + }); } /** @@ -332,9 +330,9 @@ export abstract class UpdatingElement extends HTMLElement { // support symbols in properties (IE11 does not support this) const propKeys = [ ...Object.getOwnPropertyNames(props), - ...(typeof Object.getOwnPropertySymbols === 'function') - ? Object.getOwnPropertySymbols(props) - : [] + ...(typeof Object.getOwnPropertySymbols === 'function') ? + Object.getOwnPropertySymbols(props) : + [] ]; // This for/of is ok because propKeys is an array for (const p of propKeys) { @@ -349,15 +347,14 @@ export abstract class UpdatingElement extends HTMLElement { * Returns the property name for the given attribute `name`. * @nocollapse */ - private static _attributeNameForProperty(name: PropertyKey, - options: PropertyDeclaration) { + private static _attributeNameForProperty( + name: PropertyKey, options: PropertyDeclaration) { const attribute = options.attribute; - return attribute === false - ? undefined - : (typeof attribute === 'string' - ? attribute - : (typeof name === 'string' ? name.toLowerCase() - : undefined)); + return attribute === false ? + undefined : + (typeof attribute === 'string' ? + attribute : + (typeof name === 'string' ? name.toLowerCase() : undefined)); } /** @@ -366,8 +363,8 @@ export abstract class UpdatingElement extends HTMLElement { * option for the property if present or a strict identity check. * @nocollapse */ - private static _valueHasChanged(value: unknown, old: unknown, - hasChanged: HasChanged = notEqual) { + private static _valueHasChanged( + value: unknown, old: unknown, hasChanged: HasChanged = notEqual) { return hasChanged(value, old); } @@ -377,8 +374,8 @@ export abstract class UpdatingElement extends HTMLElement { * `converter` or `converter.fromAttribute` property option. * @nocollapse */ - private static _propertyValueFromAttribute(value: string, - options: PropertyDeclaration) { + private static _propertyValueFromAttribute( + value: string, options: PropertyDeclaration) { const type = options.type; const converter = options.converter || defaultConverter; const fromAttribute = @@ -394,8 +391,8 @@ export abstract class UpdatingElement extends HTMLElement { * This uses the property's `reflect` and `type.toAttribute` property options. * @nocollapse */ - private static _propertyValueToAttribute(value: unknown, - options: PropertyDeclaration) { + private static _propertyValueToAttribute( + value: unknown, options: PropertyDeclaration) { if (options.reflect === undefined) { return; } @@ -433,7 +430,9 @@ export abstract class UpdatingElement extends HTMLElement { * Performs element initialization. By default captures any pre-set values for * registered properties. */ - protected initialize() { this._saveInstanceProperties(); } + protected initialize() { + this._saveInstanceProperties(); + } /** * Fixes any properties set on the instance before upgrade time. @@ -492,7 +491,8 @@ export abstract class UpdatingElement extends HTMLElement { * reserving the possibility of making non-breaking feature additions * when disconnecting at some point in the future. */ - disconnectedCallback() {} + disconnectedCallback() { + } /** * Synchronizes property values when attributes change. @@ -573,8 +573,8 @@ export abstract class UpdatingElement extends HTMLElement { const ctor = this.constructor as typeof UpdatingElement; const options = ctor._classProperties!.get(name) || defaultPropertyDeclaration; - if (ctor._valueHasChanged(this[name as keyof this], oldValue, - options.hasChanged)) { + if (ctor._valueHasChanged( + this[name as keyof this], oldValue, options.hasChanged)) { // track old value when changing. this._changedProperties.set(name, oldValue); // add to reflecting properties set @@ -631,7 +631,9 @@ export abstract class UpdatingElement extends HTMLElement { return (this._updateState & STATE_UPDATE_REQUESTED); } - protected get hasUpdated() { return (this._updateState & STATE_HAS_UPDATED); } + protected get hasUpdated() { + return (this._updateState & STATE_HAS_UPDATED); + } /** * Performs an element update. @@ -682,7 +684,9 @@ export abstract class UpdatingElement extends HTMLElement { * @returns {Promise} The Promise returns a boolean that indicates if the * update resolved without triggering another update. */ - get updateComplete() { return this._updatePromise; } + get updateComplete() { + return this._updatePromise; + } /** * Controls whether or not `update` should be called when the element requests @@ -723,7 +727,8 @@ export abstract class UpdatingElement extends HTMLElement { * * * @param _changedProperties Map of changed properties with old values */ - protected updated(_changedProperties: PropertyValues) {} + protected updated(_changedProperties: PropertyValues) { + } /** * Invoked when the element is first updated. Implement to perform one time @@ -734,5 +739,6 @@ export abstract class UpdatingElement extends HTMLElement { * * * @param _changedProperties Map of changed properties with old values */ - protected firstUpdated(_changedProperties: PropertyValues) {} + protected firstUpdated(_changedProperties: PropertyValues) { + } } diff --git a/src/lit-element.ts b/src/lit-element.ts index a2d8b4f5..558ec603 100644 --- a/src/lit-element.ts +++ b/src/lit-element.ts @@ -22,14 +22,27 @@ export {html, svg, TemplateResult, SVGTemplateResult} from 'lit-html/lit-html'; import {supportsAdoptingStyleSheets, CSSResult} from './lib/css-tag.js'; export * from './lib/css-tag.js'; -export interface CSSResultArray extends Array {} +declare global { + interface Window { + litElementVersions: string[]; + } +} + +// IMPORTANT: do not change the property name or the assignment expression. +// This line will be used in regexes to search for LitElement usage. +// TODO(justinfagnani): inject version number at build time +(window['litElementVersions'] || (window['litElementVersions'] = [])) + .push('2.0.0'); + +export interface CSSResultArray extends Array {} /** * Minimal implementation of Array.prototype.flat * @param arr the array to flatten * @param result the accumlated result */ -function arrayFlat(styles: CSSResultArray, result: CSSResult[] = []): CSSResult[] { +function arrayFlat( + styles: CSSResultArray, result: CSSResult[] = []): CSSResult[] { for (let i = 0, length = styles.length; i < length; i++) { const value = styles[i]; if (Array.isArray(value)) { @@ -42,10 +55,10 @@ function arrayFlat(styles: CSSResultArray, result: CSSResult[] = []): CSSResult[ } /** Deeply flattens styles array. Uses native flat if available. */ -const flattenStyles = (styles: CSSResultArray): CSSResult[] => styles.flat ? styles.flat(Infinity) : arrayFlat(styles); +const flattenStyles = (styles: CSSResultArray): CSSResult[] => + styles.flat ? styles.flat(Infinity) : arrayFlat(styles); export class LitElement extends UpdatingElement { - /** * Ensure this class is marked as `finalized` as an optimization ensuring * it will not needlessly try to `finalize`. @@ -66,7 +79,7 @@ export class LitElement extends UpdatingElement { * Array of styles to apply to the element. The styles should be defined * using the `css` tag function. */ - static styles?: CSSResult | CSSResultArray; + static styles?: CSSResult|CSSResultArray; private static _styles: CSSResult[]|undefined; @@ -75,9 +88,10 @@ export class LitElement extends UpdatingElement { super.finalize(); // Prepare styling that is stamped at first render time. Styling // is built from user provided `styles` or is inherited from the superclass. - this._styles = this.hasOwnProperty(JSCompiler_renameProperty('styles', this)) ? - this._getUniqueStyles() : - this._styles || []; + this._styles = + this.hasOwnProperty(JSCompiler_renameProperty('styles', this)) ? + this._getUniqueStyles() : + this._styles || []; } /** @nocollapse */ @@ -142,7 +156,7 @@ export class LitElement extends UpdatingElement { * @returns {Element|DocumentFragment} Returns a node into which to render. */ protected createRenderRoot(): Element|ShadowRoot { - return this.attachShadow({mode : 'open'}); + return this.attachShadow({mode: 'open'}); } /** @@ -197,8 +211,10 @@ export class LitElement extends UpdatingElement { const templateResult = this.render() as any; if (templateResult instanceof TemplateResult) { (this.constructor as typeof LitElement) - .render(templateResult, this.renderRoot!, - {scopeName : this.localName!, eventContext : this}); + .render( + templateResult, + this.renderRoot!, + {scopeName: this.localName!, eventContext: this}); } // When native Shadow DOM is used but adoptedStyles are not supported, // insert styling after rendering to ensure adoptedStyles have highest @@ -218,5 +234,6 @@ export class LitElement extends UpdatingElement { * a lit-html TemplateResult. Setting properties inside this method will *not* * trigger the element to update. */ - protected render(): TemplateResult|void {} + protected render(): TemplateResult|void { + } } diff --git a/src/test/lib/decorators_test.ts b/src/test/lib/decorators_test.ts index 97272f1d..c63b7a2f 100644 --- a/src/test/lib/decorators_test.ts +++ b/src/test/lib/decorators_test.ts @@ -13,14 +13,7 @@ */ import {eventOptions, property} from '../../lib/decorators.js'; -import { - customElement, - html, - LitElement, - PropertyValues, - query, - queryAll -} from '../../lit-element.js'; +import {customElement, html, LitElement, PropertyValues, query, queryAll} from '../../lit-element.js'; import {generateElementName} from '../test-helpers.js'; let hasOptions; @@ -61,8 +54,8 @@ const supportsPassive = (function() { } }; f.contentDocument!.addEventListener(event, fn, options); - f.contentDocument!.removeEventListener(event, fn, - options as AddEventListenerOptions); + f.contentDocument!.removeEventListener( + event, fn, options as AddEventListenerOptions); document.body.removeChild(f); return hasPassive; })(); @@ -103,16 +96,15 @@ suite('decorators', () => { const fromAttribute = (value: any) => parseInt(value); const toAttribute = (value: any) => `${value}-attr`; class E extends LitElement { - - @property({attribute : false}) noAttr = 'noAttr'; - @property({attribute : true}) atTr = 'attr'; - @property({attribute : 'custom', reflect: true}) + @property({attribute: false}) noAttr = 'noAttr'; + @property({attribute: true}) atTr = 'attr'; + @property({attribute: 'custom', reflect: true}) customAttr = 'customAttr'; @property({hasChanged}) hasChanged = 10; - @property({converter : fromAttribute}) fromAttribute = 1; - @property({reflect : true, converter: {toAttribute}}) toAttribute = 1; + @property({converter: fromAttribute}) fromAttribute = 1; + @property({reflect: true, converter: {toAttribute}}) toAttribute = 1; @property({ - attribute : 'all-attr', + attribute: 'all-attr', hasChanged, converter: {fromAttribute, toAttribute}, reflect: true @@ -126,7 +118,9 @@ suite('decorators', () => { super.update(changed); } - render() { return html``; } + render() { + return html``; + } } customElements.define(generateElementName(), E); const el = new E(); @@ -187,11 +181,10 @@ suite('decorators', () => { test('can decorate user accessor with @property', async () => { class E extends LitElement { - _foo?: number; updatedContent?: number; - @property({reflect : true, type: Number}) + @property({reflect: true, type: Number}) get foo() { return this._foo as number; } @@ -202,7 +195,9 @@ suite('decorators', () => { this.requestUpdate('foo', old); } - updated() { this.updatedContent = this.foo; } + updated() { + this.updatedContent = this.foo; + } } customElements.define(generateElementName(), E); const el = new E(); @@ -224,12 +219,11 @@ suite('decorators', () => { const fromAttribute = (value: any) => parseInt(value); const toAttribute = (value: any) => `${value}-attr`; class E extends LitElement { - @property({hasChanged}) hasChanged = 10; - @property({converter : fromAttribute}) fromAttribute = 1; - @property({reflect : true, converter: {toAttribute}}) toAttribute = 1; + @property({converter: fromAttribute}) fromAttribute = 1; + @property({reflect: true, converter: {toAttribute}}) toAttribute = 1; @property({ - attribute : 'all-attr', + attribute: 'all-attr', hasChanged, converter: {fromAttribute, toAttribute}, reflect: true @@ -240,9 +234,9 @@ suite('decorators', () => { static get properties() { return { - noAttr : {attribute : false}, - atTr : {attribute : true}, - customAttr : {attribute : 'custom', reflect : true}, + noAttr: {attribute: false}, + atTr: {attribute: true}, + customAttr: {attribute: 'custom', reflect: true}, }; } @@ -262,7 +256,9 @@ suite('decorators', () => { super.update(changed); } - render() { return html``; } + render() { + return html``; + } } customElements.define(generateElementName(), E); const el = new E(); @@ -325,7 +321,6 @@ suite('decorators', () => { suite('@query', () => { @customElement(generateElementName() as keyof HTMLElementTagNameMap) class C extends LitElement { - @query('#blah') blah?: HTMLDivElement; @query('span') nope?: HTMLSpanElement; @@ -359,7 +354,6 @@ suite('decorators', () => { suite('@queryAll', () => { @customElement(generateElementName() as keyof HTMLElementTagNameMap) class C extends LitElement { - @queryAll('div') divs!: NodeList; @queryAll('span') spans!: NodeList; @@ -408,7 +402,7 @@ suite('decorators', () => { `; } - @eventOptions({capture : true}) + @eventOptions({capture: true}) onClick(e: Event) { this.eventPhase = e.eventPhase; } @@ -428,7 +422,6 @@ suite('decorators', () => { } @customElement(generateElementName() as keyof HTMLElementTagNameMap) class C extends LitElement { - clicked = 0; render() { @@ -437,7 +430,7 @@ suite('decorators', () => { `; } - @eventOptions({once : true}) + @eventOptions({once: true}) onClick() { this.clicked++; } @@ -458,7 +451,6 @@ suite('decorators', () => { } @customElement(generateElementName() as keyof HTMLElementTagNameMap) class C extends LitElement { - defaultPrevented?: boolean; render() { @@ -467,7 +459,7 @@ suite('decorators', () => { `; } - @eventOptions({passive : true}) + @eventOptions({passive: true}) onClick(e: Event) { try { e.preventDefault(); diff --git a/src/test/lib/updating-element_test.ts b/src/test/lib/updating-element_test.ts index 1ca40766..ebcea360 100644 --- a/src/test/lib/updating-element_test.ts +++ b/src/test/lib/updating-element_test.ts @@ -13,12 +13,7 @@ */ import {property} from '../../lib/decorators.js'; -import { - ComplexAttributeConverter, - PropertyDeclarations, - PropertyValues, - UpdatingElement -} from '../../lib/updating-element.js'; +import {ComplexAttributeConverter, PropertyDeclarations, PropertyValues, UpdatingElement} from '../../lib/updating-element.js'; import {generateElementName} from '../test-helpers.js'; const assert = chai.assert; @@ -40,7 +35,9 @@ suite('UpdatingElement', () => { test('`requestUpdate` waits until update', async () => { class E extends UpdatingElement { updateCount = 0; - updated() { this.updateCount++; } + updated() { + this.updateCount++; + } } customElements.define(generateElementName(), E); const el = new E(); @@ -58,7 +55,9 @@ suite('UpdatingElement', () => { async () => { class E extends UpdatingElement { updateCount = 0; - updated() { this.updateCount++; } + updated() { + this.updateCount++; + } } customElements.define(generateElementName(), E); const el = new E(); @@ -76,13 +75,16 @@ suite('UpdatingElement', () => { test('`shouldUpdate` controls update', async () => { class E extends UpdatingElement { - needsUpdate = true; updateCount = 0; - shouldUpdate() { return this.needsUpdate; } + shouldUpdate() { + return this.needsUpdate; + } - updated() { this.updateCount++; } + updated() { + this.updateCount++; + } } customElements.define(generateElementName(), E); const el = new E(); @@ -107,17 +109,17 @@ suite('UpdatingElement', () => { class E extends UpdatingElement { static get properties() { return { - noAttr : {attribute : false}, - atTr : {attribute : true}, - customAttr : {attribute : 'custom', reflect : true}, - hasChanged : {hasChanged}, - fromAttribute : {converter : fromAttribute}, - toAttribute : {reflect : true, converter : {toAttribute}}, - all : { - attribute : 'all-attr', + noAttr: {attribute: false}, + atTr: {attribute: true}, + customAttr: {attribute: 'custom', reflect: true}, + hasChanged: {hasChanged}, + fromAttribute: {converter: fromAttribute}, + toAttribute: {reflect: true, converter: {toAttribute}}, + all: { + attribute: 'all-attr', hasChanged, - converter : {fromAttribute, toAttribute}, - reflect : true + converter: {fromAttribute, toAttribute}, + reflect: true }, }; } @@ -195,7 +197,7 @@ suite('UpdatingElement', () => { }); test('property option `converter` can use `type` info', async () => { - const FooType = {name : 'FooType'}; + const FooType = {name: 'FooType'}; // Make test work on IE where these are undefined. if (!('name' in String)) { (String as any).name = (String as any).name || 'String'; @@ -205,20 +207,20 @@ suite('UpdatingElement', () => { } const converter: ComplexAttributeConverter = { - fromAttribute : - (_value: any, - type: any) => { return `fromAttribute: ${String(type.name)}`; }, - toAttribute : - (_value: any, - type: any) => { return `toAttribute: ${String(type.name)}`; } + fromAttribute: (_value: any, type: any) => { + return `fromAttribute: ${String(type.name)}`; + }, + toAttribute: (_value: any, type: any) => { + return `toAttribute: ${String(type.name)}`; + } }; class E extends UpdatingElement { static get properties() { return { - num : {type : Number, converter, reflect : true}, - str : {type : String, converter, reflect : true}, - foo : {type : FooType, converter, reflect : true} + num: {type: Number, converter, reflect: true}, + str: {type: String, converter, reflect: true}, + foo: {type: FooType, converter, reflect: true} }; } @@ -260,26 +262,26 @@ suite('UpdatingElement', () => { class E extends UpdatingElement { static get properties() { return { - bool : {type : Boolean}, - num : {type : Number}, - str : {type : String}, - obj : {type : Object}, - arr : {type : Array}, - reflectBool : {type : Boolean, reflect : true}, - reflectNum : {type : Number, reflect : true}, - reflectStr : {type : String, reflect : true}, - reflectObj : {type : Object, reflect : true}, - reflectArr : {type : Array, reflect : true}, - defaultBool : {type : Boolean}, - defaultNum : {type : Number}, - defaultStr : {type : String}, - defaultObj : {type : Object}, - defaultArr : {type : Array}, - defaultReflectBool : {type : Boolean, reflect : true}, - defaultReflectNum : {type : Number, reflect : true}, - defaultReflectStr : {type : String, reflect : true}, - defaultReflectObj : {type : Object, reflect : true}, - defaultReflectArr : {type : Array, reflect : true}, + bool: {type: Boolean}, + num: {type: Number}, + str: {type: String}, + obj: {type: Object}, + arr: {type: Array}, + reflectBool: {type: Boolean, reflect: true}, + reflectNum: {type: Number, reflect: true}, + reflectStr: {type: String, reflect: true}, + reflectObj: {type: Object, reflect: true}, + reflectArr: {type: Array, reflect: true}, + defaultBool: {type: Boolean}, + defaultNum: {type: Number}, + defaultStr: {type: String}, + defaultObj: {type: Object}, + defaultArr: {type: Array}, + defaultReflectBool: {type: Boolean, reflect: true}, + defaultReflectNum: {type: Number, reflect: true}, + defaultReflectStr: {type: String, reflect: true}, + defaultReflectObj: {type: Object, reflect: true}, + defaultReflectArr: {type: Array, reflect: true}, }; } @@ -296,13 +298,13 @@ suite('UpdatingElement', () => { defaultBool = false; defaultNum = 0; defaultStr = ''; - defaultObj = {defaultObj : false}; - defaultArr = [ 1 ]; + defaultObj = {defaultObj: false}; + defaultArr = [1]; defaultReflectBool = false; defaultReflectNum = 0; defaultReflectStr = 'defaultReflectStr'; - defaultReflectObj = {defaultReflectObj : true}; - defaultReflectArr = [ 1, 2 ]; + defaultReflectObj = {defaultReflectObj: true}; + defaultReflectArr = [1, 2]; } const name = generateElementName(); customElements.define(name, E); @@ -317,23 +319,23 @@ suite('UpdatingElement', () => { assert.equal(el.bool, true); assert.equal(el.num, 2); assert.equal(el.str, 'str'); - assert.deepEqual(el.obj, {obj : true}); - assert.deepEqual(el.arr, [ 1 ]); + assert.deepEqual(el.obj, {obj: true}); + assert.deepEqual(el.arr, [1]); assert.equal(el.reflectBool, true); assert.equal(el.reflectNum, 3); assert.equal(el.reflectStr, 'reflectStr'); - assert.deepEqual(el.reflectObj, {reflectObj : true}); - assert.deepEqual(el.reflectArr, [ 1, 2 ]); + assert.deepEqual(el.reflectObj, {reflectObj: true}); + assert.deepEqual(el.reflectArr, [1, 2]); assert.equal(el.defaultBool, true); assert.equal(el.defaultNum, 4); assert.equal(el.defaultStr, 'defaultStr'); - assert.deepEqual(el.defaultObj, {defaultObj : true}); - assert.deepEqual(el.defaultArr, [ 1, 2, 3 ]); + assert.deepEqual(el.defaultObj, {defaultObj: true}); + assert.deepEqual(el.defaultArr, [1, 2, 3]); assert.equal(el.defaultReflectBool, false); assert.equal(el.defaultReflectNum, 0); assert.equal(el.defaultReflectStr, 'defaultReflectStr'); - assert.deepEqual(el.defaultReflectObj, {defaultReflectObj : true}); - assert.deepEqual(el.defaultReflectArr, [ 1, 2 ]); + assert.deepEqual(el.defaultReflectObj, {defaultReflectObj: true}); + assert.deepEqual(el.defaultReflectArr, [1, 2]); el.removeAttribute('bool'); el.removeAttribute('num'); el.removeAttribute('str'); @@ -377,55 +379,53 @@ suite('UpdatingElement', () => { assert.deepEqual(el.defaultReflectArr, null); }); - test('attributes removed when a reflecting property\'s value becomes null', - async () => { - class E extends UpdatingElement { - static get properties() { - return { - bool : {type : Boolean, reflect : true}, - num : {type : Number, reflect : true}, - str : {type : String, reflect : true}, - obj : {type : Object, reflect : true}, - arr : {type : Array, reflect : true} - }; - } - - bool?: any; - num?: any; - str?: any; - obj?: any; - arr?: any; - } - const name = generateElementName(); - customElements.define(name, E); - container.innerHTML = - `<${name} bool num="2" str="str" obj='{"obj": true}' + test( + 'attributes removed when a reflecting property\'s value becomes null', + async () => { + class E extends UpdatingElement { + static get properties() { + return { + bool: {type: Boolean, reflect: true}, + num: {type: Number, reflect: true}, + str: {type: String, reflect: true}, + obj: {type: Object, reflect: true}, + arr: {type: Array, reflect: true} + }; + } + + bool?: any; + num?: any; + str?: any; + obj?: any; + arr?: any; + } + const name = generateElementName(); + customElements.define(name, E); + container.innerHTML = + `<${name} bool num="2" str="str" obj='{"obj": true}' arr='[1]'> `; - const el = container.firstChild as E; - await el.updateComplete; - el.bool = false; - el.num = null; - el.str = null; - el.obj = null; - el.arr = null; - await el.updateComplete; - assert.isFalse(el.hasAttribute('bool')); - assert.isFalse(el.hasAttribute('num')); - assert.isFalse(el.hasAttribute('str')); - assert.isFalse(el.hasAttribute('obj')); - assert.isFalse(el.hasAttribute('arr')); - }); + const el = container.firstChild as E; + await el.updateComplete; + el.bool = false; + el.num = null; + el.str = null; + el.obj = null; + el.arr = null; + await el.updateComplete; + assert.isFalse(el.hasAttribute('bool')); + assert.isFalse(el.hasAttribute('num')); + assert.isFalse(el.hasAttribute('str')); + assert.isFalse(el.hasAttribute('obj')); + assert.isFalse(el.hasAttribute('arr')); + }); test( 'if a `reflect: true` returns `undefined`, the attribute does not change', async () => { class E extends UpdatingElement { static get properties() { - return { - foo : {reflect : true}, - obj : {type : Object, reflect : true} - }; + return {foo: {reflect: true}, obj: {type: Object, reflect: true}}; } foo?: any; @@ -439,10 +439,10 @@ suite('UpdatingElement', () => { el.setAttribute('foo', 'foo'); el.setAttribute('obj', '{"obj": 1}'); assert.equal(el.foo, 'foo'); - assert.deepEqual(el.obj, {obj : 1}); + assert.deepEqual(el.obj, {obj: 1}); await el.updateComplete; el.foo = 'foo2'; - el.obj = {obj : 2}; + el.obj = {obj: 2}; await el.updateComplete; assert.equal(el.getAttribute('foo'), 'foo2'); assert.equal(el.getAttribute('obj'), '{"obj":2}'); @@ -452,7 +452,7 @@ suite('UpdatingElement', () => { assert.equal(el.getAttribute('foo'), 'foo2'); assert.equal(el.getAttribute('obj'), '{"obj":2}'); el.foo = 'foo3'; - el.obj = {obj : 3}; + el.obj = {obj: 3}; await el.updateComplete; assert.equal(el.getAttribute('foo'), 'foo3'); assert.equal(el.getAttribute('obj'), '{"obj":3}'); @@ -464,16 +464,14 @@ suite('UpdatingElement', () => { const fromAttribute = (value: any) => parseInt(value); const toAttribute = (value: any) => `${value}-attr`; class E extends UpdatingElement { - - @property({attribute : false}) noAttr = 'noAttr'; - @property({attribute : true}) atTr = 'attr'; - @property({attribute : 'custom', reflect: true}) - customAttr = 'customAttr'; + @property({attribute: false}) noAttr = 'noAttr'; + @property({attribute: true}) atTr = 'attr'; + @property({attribute: 'custom', reflect: true}) customAttr = 'customAttr'; @property({hasChanged}) hasChanged = 10; - @property({converter : fromAttribute}) fromAttribute = 1; - @property({reflect : true, converter: {toAttribute}}) toAttribute = 1; + @property({converter: fromAttribute}) fromAttribute = 1; + @property({reflect: true, converter: {toAttribute}}) toAttribute = 1; @property({ - attribute : 'all-attr', + attribute: 'all-attr', hasChanged, converter: {fromAttribute, toAttribute}, reflect: true @@ -546,9 +544,8 @@ suite('UpdatingElement', () => { test('property options via decorator do not modify superclass', async () => { class E extends UpdatingElement { - static get properties() { - return {foo : {type : Number, reflect : true}}; + return {foo: {type: Number, reflect: true}}; } foo = 1; @@ -558,8 +555,7 @@ suite('UpdatingElement', () => { const el1 = new E(); class F extends E { - - @property({type : Number}) foo = 2; + @property({type: Number}) foo = 2; } customElements.define(generateElementName(), F); @@ -582,12 +578,11 @@ suite('UpdatingElement', () => { const fromAttribute = (value: any) => parseInt(value); const toAttribute = (value: any) => `${value}-attr`; class E extends UpdatingElement { - @property({hasChanged}) hasChanged = 10; - @property({converter : fromAttribute}) fromAttribute = 1; - @property({reflect : true, converter: {toAttribute}}) toAttribute = 1; + @property({converter: fromAttribute}) fromAttribute = 1; + @property({reflect: true, converter: {toAttribute}}) toAttribute = 1; @property({ - attribute : 'all-attr', + attribute: 'all-attr', hasChanged, converter: {fromAttribute, toAttribute}, reflect: true @@ -598,9 +593,9 @@ suite('UpdatingElement', () => { static get properties() { return { - noAttr : {attribute : false}, - atTr : {attribute : true}, - customAttr : {attribute : 'custom', reflect : true}, + noAttr: {attribute: false}, + atTr: {attribute: true}, + customAttr: {attribute: 'custom', reflect: true}, }; } @@ -680,26 +675,26 @@ suite('UpdatingElement', () => { test('attributes deserialize from html', async () => { const fromAttribute = (value: any) => parseInt(value); const toAttributeOnly = (value: any) => - typeof value === 'string' && value.indexOf(`-attr`) > 0 - ? value - : `${value}-attr`; + typeof value === 'string' && value.indexOf(`-attr`) > 0 ? + value : + `${value}-attr`; const toAttribute = (value: any) => `${value}-attr`; class E extends UpdatingElement { static get properties() { return { - noAttr : {attribute : false}, - atTr : {attribute : true}, - customAttr : {attribute : 'custom', reflect : true}, - fromAttribute : {converter : fromAttribute}, - toAttribute : - {reflect : true, converter : {toAttribute : toAttributeOnly}}, - all : { - attribute : 'all-attr', - converter : {fromAttribute, toAttribute}, - reflect : true + noAttr: {attribute: false}, + atTr: {attribute: true}, + customAttr: {attribute: 'custom', reflect: true}, + fromAttribute: {converter: fromAttribute}, + toAttribute: + {reflect: true, converter: {toAttribute: toAttributeOnly}}, + all: { + attribute: 'all-attr', + converter: {fromAttribute, toAttribute}, + reflect: true }, - obj : {type : Object}, - arr : {type : Array} + obj: {type: Object}, + arr: {type: Array} }; } @@ -735,8 +730,8 @@ suite('UpdatingElement', () => { assert.equal(el.getAttribute('toattribute'), '7-attr'); assert.equal(el.all, 11); assert.equal(el.getAttribute('all-attr'), '11-attr'); - assert.deepEqual(el.obj, {foo : true, bar : 5, baz : 'hi'}); - assert.deepEqual(el.arr, [ 1, 2, 3, 4 ]); + assert.deepEqual(el.obj, {foo: true, bar: 5, baz: 'hi'}); + assert.deepEqual(el.arr, [1, 2, 3, 4]); }); if (Object.getOwnPropertySymbols) { @@ -744,8 +739,9 @@ suite('UpdatingElement', () => { const zug = Symbol(); class E extends UpdatingElement { - - static get properties() { return {foo : {}, [zug] : {}}; } + static get properties() { + return {foo: {}, [zug]: {}}; + } updateCount = 0; foo = 5; [zug] = 6; @@ -778,13 +774,12 @@ suite('UpdatingElement', () => { const zug = Symbol(); class E extends UpdatingElement { - static get properties() { return { - [zug] : { - attribute : 'zug', - reflect : true, - converter : (value: string) => Number(value) + 100 + [zug]: { + attribute: 'zug', + reflect: true, + converter: (value: string) => Number(value) + 100 } }; } @@ -819,10 +814,10 @@ suite('UpdatingElement', () => { class E extends UpdatingElement { static get properties(): PropertyDeclarations { return { - noAttr : {attribute : false}, - atTr : {attribute : true}, - customAttr : {}, - hasChanged : {}, + noAttr: {attribute: false}, + atTr: {attribute: true}, + customAttr: {}, + hasChanged: {}, }; } @@ -843,10 +838,10 @@ suite('UpdatingElement', () => { class F extends E { static get properties(): PropertyDeclarations { return { - customAttr : {attribute : 'custom', reflect : true}, - hasChanged : {hasChanged}, - fromAttribute : {}, - toAttribute : {}, + customAttr: {attribute: 'custom', reflect: true}, + hasChanged: {hasChanged}, + fromAttribute: {}, + toAttribute: {}, }; } @@ -858,13 +853,13 @@ suite('UpdatingElement', () => { class G extends F { static get properties(): PropertyDeclarations { return { - fromAttribute : {converter : fromAttribute}, - toAttribute : {reflect : true, converter : {toAttribute}}, - all : { - attribute : 'all-attr', + fromAttribute: {converter: fromAttribute}, + toAttribute: {reflect: true, converter: {toAttribute}}, + all: { + attribute: 'all-attr', hasChanged, - converter : {fromAttribute, toAttribute}, - reflect : true + converter: {fromAttribute, toAttribute}, + reflect: true }, }; } @@ -931,10 +926,7 @@ suite('UpdatingElement', () => { test('superclass properties not affected by subclass', async () => { class E extends UpdatingElement { static get properties(): PropertyDeclarations { - return { - foo : {attribute : 'zug', reflect : true}, - bar : {reflect : true} - }; + return {foo: {attribute: 'zug', reflect: true}, bar: {reflect: true}}; } foo = 5; @@ -944,7 +936,7 @@ suite('UpdatingElement', () => { class F extends E { static get properties(): PropertyDeclarations { - return {foo : {attribute : false}, nug : {}}; + return {foo: {attribute: false}, nug: {}}; } foo = 6; @@ -980,9 +972,9 @@ suite('UpdatingElement', () => { class E extends UpdatingElement { static get properties() { return { - foo : { - reflect : true, - converter : {toAttribute : (value: any) => `${value}${suffix}`} + foo: { + reflect: true, + converter: {toAttribute: (value: any) => `${value}${suffix}`} } }; } @@ -1002,7 +994,7 @@ suite('UpdatingElement', () => { test('Attributes reflect with type: Boolean', async () => { class E extends UpdatingElement { static get properties() { - return {bar : {type : Boolean, reflect : true}}; + return {bar: {type: Boolean, reflect: true}}; } bar = true; @@ -1022,12 +1014,16 @@ suite('UpdatingElement', () => { test('updates when properties change', async () => { class E extends UpdatingElement { - static get properties() { return {foo : {}}; } + static get properties() { + return {foo: {}}; + } foo = 'one'; updatedText = ''; - updated() { this.updatedText = `${this.foo}`; } + updated() { + this.updatedText = `${this.foo}`; + } } customElements.define(generateElementName(), E); const el = new E(); @@ -1041,7 +1037,9 @@ suite('UpdatingElement', () => { test('updates when properties and attributes change', async () => { class E extends UpdatingElement { - static get properties() { return {value : {}, attrValue : {}}; } + static get properties() { + return {value: {}, attrValue: {}}; + } value = '1'; attrValue = 'attr'; @@ -1082,12 +1080,16 @@ suite('UpdatingElement', () => { test('updates changes when attributes change', async () => { class E extends UpdatingElement { - static get properties() { return {foo : {}}; } + static get properties() { + return {foo: {}}; + } foo = 'one'; updatedText = ''; - updated() { this.updatedText = `${this.foo}`; } + updated() { + this.updatedText = `${this.foo}`; + } } customElements.define(generateElementName(), E); const el = new E(); @@ -1105,11 +1107,15 @@ suite('UpdatingElement', () => { updatedText = ''; - static get properties() { return {foo : {}, bar : {}}; } + static get properties() { + return {foo: {}, bar: {}}; + } foo = 0; - get bar() { return this.__bar; } + get bar() { + return this.__bar; + } set bar(value) { const old = this.bar; @@ -1117,7 +1123,9 @@ suite('UpdatingElement', () => { this.requestUpdate('bar', old); } - updated() { this.updatedText = `${this.foo}${this.bar}`; } + updated() { + this.updatedText = `${this.foo}${this.bar}`; + } } customElements.define(generateElementName(), E); const el = new E(); @@ -1129,68 +1137,70 @@ suite('UpdatingElement', () => { assert.equal(el.updatedText, '020'); }); - test('User defined accessor can use property options via `requestUpdate`', - async () => { - const fromAttribute = (value: any) => parseInt(value); - const toAttribute = (value: any) => `${value}-attr`; - const hasChanged = (value: any, old: any) => isNaN(old) || value > old; - class E extends UpdatingElement { - - updateCount = 0; - __bar: any; - - static get properties() { - return { - bar : { - attribute : 'attr-bar', - reflect : true, - converter : {fromAttribute, toAttribute}, - hasChanged - } - }; - } - - constructor() { - super(); - this.bar = 5; - } - - update(changed: PropertyValues) { - super.update(changed); - this.updateCount++; - } - - get bar() { return this.__bar; } - - set bar(value) { - const old = this.bar; - this.__bar = Number(value); - this.requestUpdate('bar', old); - } - } - customElements.define(generateElementName(), E); - const el = new E(); - container.appendChild(el); - await el.updateComplete; - assert.equal(el.updateCount, 1); - assert.equal(el.bar, 5); - assert.equal(el.getAttribute('attr-bar'), `5-attr`); - el.setAttribute('attr-bar', '7'); - await el.updateComplete; - assert.equal(el.updateCount, 2); - assert.equal(el.bar, 7); - assert.equal(el.getAttribute('attr-bar'), `7`); - el.bar = 4; - await el.updateComplete; - assert.equal(el.updateCount, 2); - assert.equal(el.bar, 4); - assert.equal(el.getAttribute('attr-bar'), `7`); - el.setAttribute('attr-bar', '3'); - await el.updateComplete; - assert.equal(el.updateCount, 2); - assert.equal(el.bar, 3); - assert.equal(el.getAttribute('attr-bar'), `3`); - }); + test( + 'User defined accessor can use property options via `requestUpdate`', + async () => { + const fromAttribute = (value: any) => parseInt(value); + const toAttribute = (value: any) => `${value}-attr`; + const hasChanged = (value: any, old: any) => isNaN(old) || value > old; + class E extends UpdatingElement { + updateCount = 0; + __bar: any; + + static get properties() { + return { + bar: { + attribute: 'attr-bar', + reflect: true, + converter: {fromAttribute, toAttribute}, + hasChanged + } + }; + } + + constructor() { + super(); + this.bar = 5; + } + + update(changed: PropertyValues) { + super.update(changed); + this.updateCount++; + } + + get bar() { + return this.__bar; + } + + set bar(value) { + const old = this.bar; + this.__bar = Number(value); + this.requestUpdate('bar', old); + } + } + customElements.define(generateElementName(), E); + const el = new E(); + container.appendChild(el); + await el.updateComplete; + assert.equal(el.updateCount, 1); + assert.equal(el.bar, 5); + assert.equal(el.getAttribute('attr-bar'), `5-attr`); + el.setAttribute('attr-bar', '7'); + await el.updateComplete; + assert.equal(el.updateCount, 2); + assert.equal(el.bar, 7); + assert.equal(el.getAttribute('attr-bar'), `7`); + el.bar = 4; + await el.updateComplete; + assert.equal(el.updateCount, 2); + assert.equal(el.bar, 4); + assert.equal(el.getAttribute('attr-bar'), `7`); + el.setAttribute('attr-bar', '3'); + await el.updateComplete; + assert.equal(el.updateCount, 2); + assert.equal(el.bar, 3); + assert.equal(el.getAttribute('attr-bar'), `3`); + }); test( 'User defined accessor not overwritten by subclass, but subclass property options respected', @@ -1199,10 +1209,12 @@ suite('UpdatingElement', () => { __foo?: number; static get properties(): PropertyDeclarations { - return {bar : {hasChanged : () => false}, foo : {}}; + return {bar: {hasChanged: () => false}, foo: {}}; } - get foo() { return this.__foo; } + get foo() { + return this.__foo; + } set foo(value) { const old = this.foo; @@ -1214,10 +1226,12 @@ suite('UpdatingElement', () => { __bar?: string; static get properties(): PropertyDeclarations { - return {bar : {}, foo : {reflect : true}}; + return {bar: {}, foo: {reflect: true}}; } - get bar() { return this.__bar; } + get bar() { + return this.__bar; + } set bar(value) { const old = this.foo; @@ -1234,9 +1248,8 @@ suite('UpdatingElement', () => { }; class G extends F { - static get properties(): PropertyDeclarations { - return {bar : {hasChanged, reflect : true}, foo : {hasChanged}}; + return {bar: {hasChanged, reflect: true}, foo: {hasChanged}}; } } @@ -1259,7 +1272,6 @@ suite('UpdatingElement', () => { test('`firstUpdated` called when element first updates', async () => { class E extends UpdatingElement { - @property() foo = 1; wasUpdatedCount = 0; @@ -1297,7 +1309,6 @@ suite('UpdatingElement', () => { '`firstUpdated` called when element first updates even if first `shouldUpdate` returned false', async () => { class E extends UpdatingElement { - @property() foo = 1; triedToUpdatedCount = 0; @@ -1342,7 +1353,9 @@ suite('UpdatingElement', () => { test('update lifecycle order', async () => { class E extends UpdatingElement { - static get properties() { return {foo : {type : Number}}; } + static get properties() { + return {foo: {type: Number}}; + } info: Array = []; @@ -1357,9 +1370,13 @@ suite('UpdatingElement', () => { this.info.push('after-update'); } - firstUpdated() { this.info.push('firstUpdated'); } + firstUpdated() { + this.info.push('firstUpdated'); + } - updated() { this.info.push('updated'); } + updated() { + this.info.push('updated'); + } } customElements.define(generateElementName(), E); const el = new E(); @@ -1367,15 +1384,20 @@ suite('UpdatingElement', () => { await el.updateComplete; el.info.push('updateComplete'); assert.deepEqual(el.info, [ - 'shouldUpdate', 'before-update', 'after-update', 'firstUpdated', - 'updated', 'updateComplete' + 'shouldUpdate', + 'before-update', + 'after-update', + 'firstUpdated', + 'updated', + 'updateComplete' ]); }); test('setting properties in update does not trigger update', async () => { class E extends UpdatingElement { - - static get properties() { return {foo : {}}; } + static get properties() { + return {foo: {}}; + } promiseFulfilled = false; foo = 0; updateCount = 0; @@ -1387,7 +1409,9 @@ suite('UpdatingElement', () => { super.update(props); } - updated() { this.updatedText = `${this.foo}`; } + updated() { + this.updatedText = `${this.foo}`; + } } customElements.define(generateElementName(), E); const el = new E(); @@ -1407,9 +1431,8 @@ suite('UpdatingElement', () => { 'setting properties in update reflects to attribute and is included in `changedProperties`', async () => { class E extends UpdatingElement { - static get properties() { - return {foo : {}, bar : {}, zot : {reflect : true}}; + return {foo: {}, bar: {}, zot: {reflect: true}}; } changedProperties: PropertyValues|undefined = undefined; @@ -1456,13 +1479,12 @@ suite('UpdatingElement', () => { // cannot have default values. These will be overwritten by instance values. test('can make properties for native accessors', async () => { class E extends UpdatingElement { - static get properties() { return { - id : {reflect : true}, - name : {reflect : true}, - title : {reflect : true}, - foo : {} + id: {reflect: true}, + name: {reflect: true}, + title: {reflect: true}, + foo: {} }; } @@ -1479,7 +1501,9 @@ suite('UpdatingElement', () => { this.changedProperties = changedProperties; } - updated() { this.updatedText = `${this.id}-${this.title}-${this.foo}`; } + updated() { + this.updatedText = `${this.id}-${this.title}-${this.foo}`; + } } customElements.define(generateElementName(), E); const el = new E() as any; @@ -1507,8 +1531,8 @@ suite('UpdatingElement', () => { _bar?: String; static get properties() { return { - foo : {type : String, reflect : true}, - bar : {type : String, reflect : true} + foo: {type: String, reflect: true}, + bar: {type: String, reflect: true} }; } constructor() { @@ -1521,18 +1545,24 @@ suite('UpdatingElement', () => { this._foo = value; this.requestUpdate('foo', old); } - get foo() { return this._foo as string; } + get foo() { + return this._foo as string; + } set bar(value: string) { const old = this._bar; this._bar = value; this.requestUpdate('bar', old); } - get bar() { return this._bar as string; } + get bar() { + return this._bar as string; + } update(changedProperties: PropertyValues) { this._updateCount++; super.update(changedProperties); } - updated() { this.updatedText = `${this.foo}-${this.bar}`; } + updated() { + this.updatedText = `${this.foo}-${this.bar}`; + } } customElements.define(generateElementName(), E); @@ -1573,7 +1603,9 @@ suite('UpdatingElement', () => { _oldFoo?: any; _foo?: number; updatedText = ''; - static get properties() { return {foo : {type : Number}}; } + static get properties() { + return {foo: {type: Number}}; + } constructor() { super(); this.foo = 0; @@ -1584,19 +1616,25 @@ suite('UpdatingElement', () => { this._foo = Math.min(v, 10); this.requestUpdate('foo', old); } - get foo(): number { return this._foo as number; } + get foo(): number { + return this._foo as number; + } update(changedProperties: PropertyValues) { this._oldFoo = changedProperties.get('foo'); super.update(changedProperties); } - updated() { this.updatedText = `${this.foo}`; } + updated() { + this.updatedText = `${this.foo}`; + } } customElements.define(generateElementName(), Sup); // Sub implements an accessor that rounds down in the getter class Sub extends Sup { _subSetCount?: number; - static get properties() { return {foo : {type : Number}}; } + static get properties() { + return {foo: {type: Number}}; + } set foo(v: number) { this._subSetCount = (this._subSetCount || 0) + 1; super.foo = v; @@ -1625,7 +1663,7 @@ suite('UpdatingElement', () => { sup.foo = 20; await sup.updateComplete; - assert.equal(sup.foo, 10); // (user getter implements a max of 10) + assert.equal(sup.foo, 10); // (user getter implements a max of 10) assert.equal(sup._oldFoo, 5); assert.equal(sup._supSetCount, 3); assert.equal(sup.updatedText, '10'); @@ -1656,7 +1694,7 @@ suite('UpdatingElement', () => { sub.foo = 7.5; await sub.updateComplete; - assert.equal(sub.foo, 7); // (sub setter rounds down) + assert.equal(sub.foo, 7); // (sub setter rounds down) assert.equal(sub._oldFoo, 5); assert.equal(sub._supSetCount, 3); assert.equal(sub._subSetCount, 3); @@ -1664,88 +1702,101 @@ suite('UpdatingElement', () => { sub.foo = 20; await sub.updateComplete; - assert.equal(sub.foo, 10); // (super user getter maxes at 10) + assert.equal(sub.foo, 10); // (super user getter maxes at 10) assert.equal(sub._oldFoo, 7); assert.equal(sub._supSetCount, 4); assert.equal(sub._subSetCount, 4); assert.equal(sub.updatedText, '10'); }); - test('Using `noAccessor` to set property options for extended user accessors', - async () => { - // Sup implements an accessor that clamps to a maximum in the setter - class Sup extends UpdatingElement { - _supSetCount?: number; - _oldFoo?: any; - _foo?: number; - updatedText = ''; - static get properties() { return {foo : {type : Number}}; } - constructor() { - super(); - this.foo = 0; - } - set foo(v: number) { - this._supSetCount = (this._supSetCount || 0) + 1; - const old = this.foo; - this._foo = Math.min(v, 10); - this.requestUpdate('foo', old); - } - get foo(): number { return this._foo as number; } - update(changedProperties: PropertyValues) { - this._oldFoo = changedProperties.get('foo'); - super.update(changedProperties); - } - updated() { this.updatedText = `${this.foo}`; } - } - customElements.define(generateElementName(), Sup); - - // Sub implements an accessor that rounds down in the getter - class Sub extends Sup { - static get properties() { - return {foo : {type : Number, reflect : true, noAccessor : true}}; - } - } - customElements.define(generateElementName(), Sub); - - const sub = new Sub(); - container.appendChild(sub); - await sub.updateComplete; - assert.equal(sub.foo, 0); - assert.equal(sub._oldFoo, undefined); - assert.equal(sub._supSetCount, 1); - assert.equal(sub.updatedText, '0'); - - sub.foo = 5; - await sub.updateComplete; - assert.equal(sub.foo, 5); - assert.equal(sub._oldFoo, 0); - assert.equal(sub._supSetCount, 2); - assert.equal(sub.updatedText, '5'); - assert.equal(sub.getAttribute('foo'), '5'); - }); + test( + 'Using `noAccessor` to set property options for extended user accessors', + async () => { + // Sup implements an accessor that clamps to a maximum in the setter + class Sup extends UpdatingElement { + _supSetCount?: number; + _oldFoo?: any; + _foo?: number; + updatedText = ''; + static get properties() { + return {foo: {type: Number}}; + } + constructor() { + super(); + this.foo = 0; + } + set foo(v: number) { + this._supSetCount = (this._supSetCount || 0) + 1; + const old = this.foo; + this._foo = Math.min(v, 10); + this.requestUpdate('foo', old); + } + get foo(): number { + return this._foo as number; + } + update(changedProperties: PropertyValues) { + this._oldFoo = changedProperties.get('foo'); + super.update(changedProperties); + } + updated() { + this.updatedText = `${this.foo}`; + } + } + customElements.define(generateElementName(), Sup); + + // Sub implements an accessor that rounds down in the getter + class Sub extends Sup { + static get properties() { + return {foo: {type: Number, reflect: true, noAccessor: true}}; + } + } + customElements.define(generateElementName(), Sub); + + const sub = new Sub(); + container.appendChild(sub); + await sub.updateComplete; + assert.equal(sub.foo, 0); + assert.equal(sub._oldFoo, undefined); + assert.equal(sub._supSetCount, 1); + assert.equal(sub.updatedText, '0'); + + sub.foo = 5; + await sub.updateComplete; + assert.equal(sub.foo, 5); + assert.equal(sub._oldFoo, 0); + assert.equal(sub._supSetCount, 2); + assert.equal(sub.updatedText, '5'); + assert.equal(sub.getAttribute('foo'), '5'); + }); test('attribute-based property storage', async () => { class E extends UpdatingElement { _updateCount = 0; updatedText = ''; static get properties() { - return {foo : {type : String}, bar : {type : String}}; + return {foo: {type: String}, bar: {type: String}}; } set foo(value: string|null) { this.setAttribute('foo', value as string); this.requestUpdate(); } - get foo() { return this.getAttribute('foo') || 'defaultFoo'; } + get foo() { + return this.getAttribute('foo') || 'defaultFoo'; + } set bar(value: string|null) { this.setAttribute('bar', value as string); this.requestUpdate(); } - get bar() { return this.getAttribute('bar') || 'defaultBar'; } + get bar() { + return this.getAttribute('bar') || 'defaultBar'; + } update(changedProperties: PropertyValues) { this._updateCount++; super.update(changedProperties); } - updated() { this.updatedText = `${this.foo}-${this.bar}`; } + updated() { + this.updatedText = `${this.foo}-${this.bar}`; + } } customElements.define(generateElementName(), E); @@ -1782,12 +1833,20 @@ suite('UpdatingElement', () => { _updateCount = 0; updatedText = ''; static get properties() { - return {foo : {type : String}, bar : {type : String}}; + return {foo: {type: String}, bar: {type: String}}; + } + set foo(value: string|null) { + this.setAttribute('foo', value as string); + } + get foo() { + return this.getAttribute('foo') || 'defaultFoo'; + } + set bar(value: string|null) { + this.setAttribute('bar', value as string); + } + get bar() { + return this.getAttribute('bar') || 'defaultBar'; } - set foo(value: string|null) { this.setAttribute('foo', value as string); } - get foo() { return this.getAttribute('foo') || 'defaultFoo'; } - set bar(value: string|null) { this.setAttribute('bar', value as string); } - get bar() { return this.getAttribute('bar') || 'defaultBar'; } attributeChangedCallback(name: string, old: string, value: string) { super.attributeChangedCallback(name, old, value); this.requestUpdate(name, old); @@ -1796,7 +1855,9 @@ suite('UpdatingElement', () => { this._updateCount++; super.update(changedProperties); } - updated() { this.updatedText = `${this.foo}-${this.bar}`; } + updated() { + this.updatedText = `${this.foo}-${this.bar}`; + } } customElements.define(generateElementName(), E); @@ -1832,8 +1893,9 @@ suite('UpdatingElement', () => { 'setting properties in `updated` does trigger update and does not block updateComplete', async () => { class E extends UpdatingElement { - - static get properties() { return {foo : {}}; } + static get properties() { + return {foo: {}}; + } foo = 0; updateCount = 0; fooMax = 2; @@ -1868,8 +1930,9 @@ suite('UpdatingElement', () => { 'setting properties in `updated` can await until updateComplete returns true', async () => { class E extends UpdatingElement { - - static get properties() { return {foo : {}}; } + static get properties() { + return {foo: {}}; + } foo = 0; updateCount = 0; @@ -1894,8 +1957,9 @@ suite('UpdatingElement', () => { test('`updateComplete` can block properties set in `updated`', async () => { class E extends UpdatingElement { - - static get properties() { return {foo : {}}; } + static get properties() { + return {foo: {}}; + } foo = 1; updateCount = 0; fooMax = 10; @@ -1926,8 +1990,9 @@ suite('UpdatingElement', () => { test('can await promise in `updateComplete`', async () => { class E extends UpdatingElement { - - static get properties() { return {foo : {}}; } + static get properties() { + return {foo: {}}; + } promiseFulfilled = false; get updateComplete() { @@ -1951,8 +2016,9 @@ suite('UpdatingElement', () => { test('`requestUpdate` resolved at `updateComplete` time', async () => { class E extends UpdatingElement { - - static get properties() { return {foo : {}}; } + static get properties() { + return {foo: {}}; + } promiseFulfilled = false; get updateComplete() { @@ -1980,8 +2046,9 @@ suite('UpdatingElement', () => { test('can await sub-element `updateComplete`', async () => { class E extends UpdatingElement { - - static get properties() { return {foo : {}}; } + static get properties() { + return {foo: {}}; + } promiseFulfilled = false; foo = 'hi'; updatedText = ''; @@ -1994,16 +2061,17 @@ suite('UpdatingElement', () => { }, 1))); } - updated() { this.updatedText = this.foo; } + updated() { + this.updatedText = this.foo; + } } customElements.define('x-1224', E); class F extends UpdatingElement { - inner: E|null = null; firstUpdated() { - this.attachShadow({mode : 'open'}); + this.attachShadow({mode: 'open'}); this.inner = document.createElement('x-1224') as E; this.shadowRoot!.appendChild(this.inner); } @@ -2033,7 +2101,9 @@ suite('UpdatingElement', () => { const objectValue = {}; (el as any).zug = objectValue; class E extends UpdatingElement { - static get properties() { return {foo : {}, bar : {}, zug : {}}; } + static get properties() { + return {foo: {}, bar: {}, zug: {}}; + } foo = ''; bar = true; @@ -2047,7 +2117,6 @@ suite('UpdatingElement', () => { }); test('can override performUpdate()', async () => { - let resolve: (() => void)|undefined; class A extends UpdatingElement { @@ -2127,7 +2196,6 @@ suite('UpdatingElement', () => { test('update does not occur before element is connected', async () => { class A extends UpdatingElement { - updatedCalledCount = 0; @property() foo = 5; diff --git a/src/test/lit-element_styling_test.ts b/src/test/lit-element_styling_test.ts index 25bc2b0d..1a34a58d 100644 --- a/src/test/lit-element_styling_test.ts +++ b/src/test/lit-element_styling_test.ts @@ -14,19 +14,9 @@ import '@webcomponents/shadycss/apply-shim.min.js'; -import { - css, - unsafeCSS, - CSSResult, - html as htmlWithStyles, - LitElement, -} from '../lit-element.js'; - -import { - generateElementName, - getComputedStyleValue, - nextFrame -} from './test-helpers.js'; +import {css, CSSResult, html as htmlWithStyles, LitElement, unsafeCSS} from '../lit-element.js'; + +import {generateElementName, getComputedStyleValue, nextFrame} from './test-helpers.js'; const assert = chai.assert; @@ -155,71 +145,71 @@ suite('Styling', () => { assert.equal(getComputedStyleValue(div!, 'border-top-width').trim(), '8px'); }); - test('elements with custom properties can move between elements', - async () => { - customElements.define('x-inner1', class extends LitElement { - render() { - return htmlWithStyles` + test( + 'elements with custom properties can move between elements', async () => { + customElements.define('x-inner1', class extends LitElement { + render() { + return htmlWithStyles`
Testing...
`; - } - }); - const name1 = generateElementName(); - customElements.define(name1, class extends LitElement { - inner: Element|null = null; - - render() { - return htmlWithStyles` + } + }); + const name1 = generateElementName(); + customElements.define(name1, class extends LitElement { + inner: Element|null = null; + + render() { + return htmlWithStyles` `; - } - - firstUpdated() { - this.inner = this.shadowRoot!.querySelector('x-inner1'); - } - }); - const name2 = generateElementName(); - customElements.define(name2, class extends LitElement { - render() { - return htmlWithStyles` + } + + firstUpdated() { + this.inner = this.shadowRoot!.querySelector('x-inner1'); + } + }); + const name2 = generateElementName(); + customElements.define(name2, class extends LitElement { + render() { + return htmlWithStyles` `; - } - }); - const el = document.createElement(name1) as LitElement; - const el2 = document.createElement(name2); - container.appendChild(el); - container.appendChild(el2); - let div: Element|null; - - // Workaround for Safari 9 Promise timing bugs. - await el.updateComplete; - - await nextFrame(); - const inner = el.shadowRoot!.querySelector('x-inner1'); - div = inner!.shadowRoot!.querySelector('div'); - assert.equal(getComputedStyleValue(div!, 'border-top-width').trim(), - '2px'); - el2!.shadowRoot!.appendChild(inner!); - - // Workaround for Safari 9 Promise timing bugs. - await el.updateComplete; - - await nextFrame(); - assert.equal(getComputedStyleValue(div!, 'border-top-width').trim(), - '8px'); - }); + } + }); + const el = document.createElement(name1) as LitElement; + const el2 = document.createElement(name2); + container.appendChild(el); + container.appendChild(el2); + let div: Element|null; + + // Workaround for Safari 9 Promise timing bugs. + await el.updateComplete; + + await nextFrame(); + const inner = el.shadowRoot!.querySelector('x-inner1'); + div = inner!.shadowRoot!.querySelector('div'); + assert.equal( + getComputedStyleValue(div!, 'border-top-width').trim(), '2px'); + el2!.shadowRoot!.appendChild(inner!); + + // Workaround for Safari 9 Promise timing bugs. + await el.updateComplete; + + await nextFrame(); + assert.equal( + getComputedStyleValue(div!, 'border-top-width').trim(), '8px'); + }); test('@apply renders in nested elements', async () => { customElements.define('x-inner2', class extends LitElement { @@ -262,8 +252,8 @@ suite('Styling', () => { await nextFrame(); const div = el.shadowRoot!.querySelector( 'x-inner2')!.shadowRoot!.querySelector('div'); - assert.equal(getComputedStyleValue(div!, 'border-top-width').trim(), - '10px'); + assert.equal( + getComputedStyleValue(div!, 'border-top-width').trim(), '10px'); }); test( @@ -323,13 +313,13 @@ suite('Styling', () => { assert.equal( getComputedStyleValue(firstApplied!, 'border-top-width').trim(), '2px'); - assert.equal(getComputedStyleValue(firstApplied!, 'margin-top').trim(), - '10px'); + assert.equal( + getComputedStyleValue(firstApplied!, 'margin-top').trim(), '10px'); assert.equal( getComputedStyleValue(el.applied!, 'border-top-width').trim(), '10px'); - assert.equal(getComputedStyleValue(el.applied!, 'margin-top').trim(), - '2px'); + assert.equal( + getComputedStyleValue(el.applied!, 'margin-top').trim(), '2px'); }); }); @@ -374,38 +364,43 @@ suite('Static get styles', () => { const div = el.shadowRoot!.querySelector('div'); assert.equal(getComputedStyleValue(div!, 'border-top-width').trim(), '2px'); const span = el.shadowRoot!.querySelector('span'); - assert.equal(getComputedStyleValue(span!, 'border-top-width').trim(), - '3px'); + assert.equal( + getComputedStyleValue(span!, 'border-top-width').trim(), '3px'); }); - // Test this in Shadow DOM without `adoptedStyleSheets` only since it's easily detectable in that case. - const testShadowDOMStyleCount = (!window.ShadyDOM || !window.ShadyDOM.inUse) && !('adoptedStyleSheets' in Document.prototype); - (testShadowDOMStyleCount ? test : test.skip)('when an array is returned from `static get styles`, one style is generated per array item', async () => { - const name = generateElementName(); - customElements.define(name, class extends LitElement { - static get styles() { - return [ - css`div { + // Test this in Shadow DOM without `adoptedStyleSheets` only since it's easily + // detectable in that case. + const testShadowDOMStyleCount = + (!window.ShadyDOM || !window.ShadyDOM.inUse) && + !('adoptedStyleSheets' in Document.prototype); + (testShadowDOMStyleCount ? test : test.skip)( + 'when an array is returned from `static get styles`, one style is generated per array item', + async () => { + const name = generateElementName(); + customElements.define(name, class extends LitElement { + static get styles() { + return [ + css`div { border: 2px solid blue; }`, - css`span { + css`span { display: block; border: 3px solid blue; }` - ]; - } + ]; + } - render() { - return htmlWithStyles` + render() { + return htmlWithStyles`
Testing1
Testing2`; - } - }); - const el = document.createElement(name); - container.appendChild(el); - await (el as LitElement).updateComplete; - assert.equal(el.shadowRoot!.querySelectorAll('style').length, 2); - }); + } + }); + const el = document.createElement(name); + container.appendChild(el); + await (el as LitElement).updateComplete; + assert.equal(el.shadowRoot!.querySelectorAll('style').length, 2); + }); test('static get styles can be a single CSSResult', async () => { const name = generateElementName(); @@ -455,40 +450,47 @@ suite('Static get styles', () => { const div = el.shadowRoot!.querySelector('div'); assert.equal(getComputedStyleValue(div!, 'border-top-width').trim(), '2px'); const span = el.shadowRoot!.querySelector('span'); - assert.equal(getComputedStyleValue(span!, 'border-top-width').trim(), - '3px'); + assert.equal( + getComputedStyleValue(span!, 'border-top-width').trim(), '3px'); }); test('`css` get styles throws when unsafe values are used', async () => { - assert.throws(() => { css`div { border: ${`2px solid blue;` as any}}`; }); + assert.throws(() => { + css`div { border: ${`2px solid blue;` as any}}`; + }); }); test('`CSSResult` cannot be constructed', async () => { // Note, this is done for security, instead use `css` or `unsafeCSS` - assert.throws(() => { new CSSResult('throw', Symbol()); }); + assert.throws(() => { + new CSSResult('throw', Symbol()); + }); }); - test('Any value can be used in `css` when included with `unsafeCSS`', async () => { - const name = generateElementName(); - const someVar = `2px solid blue`; - customElements.define(name, class extends LitElement { - static get styles() { - return css`div { + test( + 'Any value can be used in `css` when included with `unsafeCSS`', + async () => { + const name = generateElementName(); + const someVar = `2px solid blue`; + customElements.define(name, class extends LitElement { + static get styles() { + return css`div { border: ${unsafeCSS(someVar)}; }`; - } + } - render() { - return htmlWithStyles` + render() { + return htmlWithStyles`
Testing
`; - } - }); - const el = document.createElement(name); - container.appendChild(el); - await (el as LitElement).updateComplete; - const div = el.shadowRoot!.querySelector('div'); - assert.equal(getComputedStyleValue(div!, 'border-top-width').trim(), '2px'); - }); + } + }); + const el = document.createElement(name); + container.appendChild(el); + await (el as LitElement).updateComplete; + const div = el.shadowRoot!.querySelector('div'); + assert.equal( + getComputedStyleValue(div!, 'border-top-width').trim(), '2px'); + }); test('styles in render compose with `static get styles`', async () => { const name = generateElementName(); @@ -527,8 +529,8 @@ suite('Static get styles', () => { assert.equal(getComputedStyleValue(div!, 'border-top-width').trim(), '2px'); assert.equal(getComputedStyleValue(div!, 'padding-top').trim(), '4px'); const span = el.shadowRoot!.querySelector('span'); - assert.equal(getComputedStyleValue(span!, 'border-top-width').trim(), - '3px'); + assert.equal( + getComputedStyleValue(span!, 'border-top-width').trim(), '3px'); }); test('`static get styles` applies last instance of style', async () => { @@ -540,7 +542,9 @@ suite('Static get styles', () => { border: 3px solid blue; }`; customElements.define(name, class extends LitElement { - static get styles() { return [ s1, s2, s1 ]; } + static get styles() { + return [s1, s2, s1]; + } render() { return htmlWithStyles` @@ -577,7 +581,9 @@ suite('Static get styles', () => { ], ]; customElements.define(name, class extends LitElement { - static get styles() { return [ styles ]; } + static get styles() { + return [styles]; + } render() { return htmlWithStyles` @@ -594,10 +600,14 @@ suite('Static get styles', () => { const level2 = el.shadowRoot!.querySelector('.level2'); const level3 = el.shadowRoot!.querySelector('.level3'); const level4 = el.shadowRoot!.querySelector('.level4'); - assert.equal(getComputedStyleValue(level1!, 'border-top-width').trim(), '1px'); - assert.equal(getComputedStyleValue(level2!, 'border-top-width').trim(), '2px'); - assert.equal(getComputedStyleValue(level3!, 'border-top-width').trim(), '3px'); - assert.equal(getComputedStyleValue(level4!, 'border-top-width').trim(), '4px'); + assert.equal( + getComputedStyleValue(level1!, 'border-top-width').trim(), '1px'); + assert.equal( + getComputedStyleValue(level2!, 'border-top-width').trim(), '2px'); + assert.equal( + getComputedStyleValue(level3!, 'border-top-width').trim(), '3px'); + assert.equal( + getComputedStyleValue(level4!, 'border-top-width').trim(), '4px'); }); test('`styles` can be a static field', async () => { @@ -625,14 +635,15 @@ suite('Static get styles', () => { const div = el.shadowRoot!.querySelector('div'); assert.equal(getComputedStyleValue(div!, 'border-top-width').trim(), '2px'); const span = el.shadowRoot!.querySelector('span'); - assert.equal(getComputedStyleValue(span!, 'border-top-width').trim(), - '3px'); + assert.equal( + getComputedStyleValue(span!, 'border-top-width').trim(), '3px'); }); test('can extend and augment `styles`', async () => { const base = generateElementName(); customElements.define(base, class extends LitElement { - static get styles() { return css`div { + static get styles() { + return css`div { border: 2px solid blue; }`; } @@ -689,20 +700,20 @@ suite('Static get styles', () => { container.appendChild(el); await (el as LitElement).updateComplete; const span = el.shadowRoot!.querySelector('span'); - assert.equal(getComputedStyleValue(span!, 'border-top-width').trim(), - '3px'); + assert.equal( + getComputedStyleValue(span!, 'border-top-width').trim(), '3px'); el = document.createElement(subsub); container.appendChild(el); await (el as LitElement).updateComplete; const p = el.shadowRoot!.querySelector('p'); - assert.equal(getComputedStyleValue(p!, 'border-top-width').trim(), - '4px'); + assert.equal(getComputedStyleValue(p!, 'border-top-width').trim(), '4px'); }); test('can extend and override `styles`', async () => { const base = generateElementName(); customElements.define(base, class extends LitElement { - static get styles() { return css`div { + static get styles() { + return css`div { border: 2px solid blue; }`; } @@ -715,20 +726,20 @@ suite('Static get styles', () => { const sub = generateElementName(); customElements.define(sub, class extends customElements.get(base) { - static get styles() { return css`div { + static get styles() { + return css`div { border: 3px solid blue; }`; } - }); const subsub = generateElementName(); customElements.define(subsub, class extends customElements.get(sub) { - static get styles() { return css`div { + static get styles() { + return css`div { border: 4px solid blue; }`; } - }); let el = document.createElement(base); container.appendChild(el); @@ -739,14 +750,12 @@ suite('Static get styles', () => { container.appendChild(el); await (el as LitElement).updateComplete; div = el.shadowRoot!.querySelector('div'); - assert.equal(getComputedStyleValue(div!, 'border-top-width').trim(), - '3px'); + assert.equal(getComputedStyleValue(div!, 'border-top-width').trim(), '3px'); el = document.createElement(subsub); container.appendChild(el); await (el as LitElement).updateComplete; div = el.shadowRoot!.querySelector('div'); - assert.equal(getComputedStyleValue(div!, 'border-top-width').trim(), - '4px'); + assert.equal(getComputedStyleValue(div!, 'border-top-width').trim(), '4px'); }); test('elements should inherit `styles` by default', async () => { @@ -766,16 +775,21 @@ suite('Static get styles', () => { container.appendChild(el); await (el as LitElement).updateComplete; const div = el.shadowRoot!.querySelector('div'); - assert.equal(getComputedStyle(div!).getPropertyValue('border-top-width').trim(), '4px'); + assert.equal( + getComputedStyle(div!).getPropertyValue('border-top-width').trim(), + '4px'); }); - test('`CSSResult` allows for String type coercion via toString()', async () => { - const cssModule = css`.my-module { color: yellow; }`; - // Coercion allows for reusage of css-tag outcomes in regular strings. - // Example use case: apply cssModule as global page styles at document.body level. - const bodyStyles = `${cssModule}`; - assert.equal(bodyStyles, '.my-module { color: yellow; }'); - }); + test( + '`CSSResult` allows for String type coercion via toString()', + async () => { + const cssModule = css`.my-module { color: yellow; }`; + // Coercion allows for reusage of css-tag outcomes in regular strings. + // Example use case: apply cssModule as global page styles at + // document.body level. + const bodyStyles = `${cssModule}`; + assert.equal(bodyStyles, '.my-module { color: yellow; }'); + }); }); suite('ShadyDOM', () => { @@ -796,31 +810,32 @@ suite('ShadyDOM', () => { } }); - test('properties in styles render with initial value and cannot be changed', - async () => { - let border = `6px solid blue`; - const name = generateElementName(); - customElements.define(name, class extends LitElement { - render() { - return htmlWithStyles` + test( + 'properties in styles render with initial value and cannot be changed', + async () => { + let border = `6px solid blue`; + const name = generateElementName(); + customElements.define(name, class extends LitElement { + render() { + return htmlWithStyles`
Testing...
`; - } - }); - const el = document.createElement(name) as LitElement; - container.appendChild(el); - await el.updateComplete; - const div = el.shadowRoot!.querySelector('div'); - assert.equal(getComputedStyleValue(div!, 'border-top-width').trim(), - '6px'); - border = `4px solid orange`; - el.requestUpdate(); - await el.updateComplete; - assert.equal(getComputedStyleValue(div!, 'border-top-width').trim(), - '6px'); - }); + } + }); + const el = document.createElement(name) as LitElement; + container.appendChild(el); + await el.updateComplete; + const div = el.shadowRoot!.querySelector('div'); + assert.equal( + getComputedStyleValue(div!, 'border-top-width').trim(), '6px'); + border = `4px solid orange`; + el.requestUpdate(); + await el.updateComplete; + assert.equal( + getComputedStyleValue(div!, 'border-top-width').trim(), '6px'); + }); }); diff --git a/src/test/lit-element_test.ts b/src/test/lit-element_test.ts index 72534a6d..89b9dc2e 100644 --- a/src/test/lit-element_test.ts +++ b/src/test/lit-element_test.ts @@ -12,15 +12,9 @@ * http://polymer.github.io/PATENTS.txt */ -import { - html, - LitElement, -} from '../lit-element.js'; +import {html, LitElement} from '../lit-element.js'; -import { - generateElementName, - stripExpressionDelimeters -} from './test-helpers.js'; +import {generateElementName, stripExpressionDelimeters} from './test-helpers.js'; const assert = chai.assert; @@ -42,15 +36,17 @@ suite('LitElement', () => { const rendered = `hello world`; const name = generateElementName(); customElements.define(name, class extends LitElement { - render() { return html`${rendered}`; } + render() { + return html`${rendered}`; + } }); const el = document.createElement(name); container.appendChild(el); await new Promise((resolve) => { setTimeout(() => { assert.ok(el.shadowRoot); - assert.equal(stripExpressionDelimeters(el.shadowRoot!.innerHTML), - rendered); + assert.equal( + stripExpressionDelimeters(el.shadowRoot!.innerHTML), rendered); resolve(); }); }); @@ -60,9 +56,13 @@ suite('LitElement', () => { const rendered = `hello world`; const name = generateElementName(); customElements.define(name, class extends LitElement { - render() { return html`${rendered}`; } + render() { + return html`${rendered}`; + } - createRenderRoot() { return this; } + createRenderRoot() { + return this; + } }); const el = document.createElement(name); container.appendChild(el); @@ -74,7 +74,9 @@ suite('LitElement', () => { test('renders when created via constructor', async () => { const rendered = `hello world`; class E extends LitElement { - render() { return html`${rendered}`; } + render() { + return html`${rendered}`; + } } customElements.define(generateElementName(), E); const el = new E(); @@ -93,7 +95,9 @@ suite('LitElement', () => { render() { const attr = 'attr'; const prop = 'prop'; - const event = function(this: E, e: Event) { this._event = e; }; + const event = function(this: E, e: Event) { + this._event = e; + }; return html `
`; } @@ -114,9 +118,13 @@ suite('LitElement', () => { class E extends LitElement { event?: Event; - render() { return html`
`; } + render() { + return html`
`; + } - onTest(e: Event) { this.event = e; } + onTest(e: Event) { + this.event = e; + } } customElements.define(generateElementName(), E); const el = new E(); @@ -130,22 +138,24 @@ suite('LitElement', () => { test('can set properties and attributes on sub-element', async () => { class E extends LitElement { - static get properties() { - return {foo : {}, attr : {}, bool : {type : Boolean}}; + return {foo: {}, attr: {}, bool: {type: Boolean}}; } foo = 'hi'; bool = false; - render() { return html`${this.foo}`; } + render() { + return html`${this.foo}`; + } } customElements.define('x-2448', E); class F extends LitElement { - inner: E|null = null; - static get properties() { return {bar : {}, bool : {type : Boolean}}; } + static get properties() { + return {bar: {}, bool: {type: Boolean}}; + } bar = 'outer'; bool = false; @@ -154,7 +164,9 @@ suite('LitElement', () => { this.bool}">`; } - firstUpdated() { this.inner = this.shadowRoot!.querySelector('x-2448'); } + firstUpdated() { + this.inner = this.shadowRoot!.querySelector('x-2448'); + } get updateComplete() { return super.updateComplete.then(() => this.inner!.updateComplete); @@ -176,4 +188,8 @@ suite('LitElement', () => { assert.equal(el.inner!.getAttribute('attr'), 'test'); assert.equal(el.inner!.bool, true); }); + + test('adds a version number', () => { + assert.equal(window['litElementVersions'].length, 1); + }); }); diff --git a/src/test/test-helpers.ts b/src/test/test-helpers.ts index ca99eb45..a33e4328 100644 --- a/src/test/test-helpers.ts +++ b/src/test/test-helpers.ts @@ -22,5 +22,5 @@ export const nextFrame = () => new Promise((resolve) => requestAnimationFrame(resolve)); export const getComputedStyleValue = (element: Element, property: string) => - window.ShadyCSS ? window.ShadyCSS.getComputedStyleValue(element, property) - : getComputedStyle(element).getPropertyValue(property); + window.ShadyCSS ? window.ShadyCSS.getComputedStyleValue(element, property) : + getComputedStyle(element).getPropertyValue(property);