/*
* Copyright 2015 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import static com.google.common.truth.Truth.assertThat;
import static com.google.javascript.jscomp.PolymerPassErrors.POLYMER_DESCRIPTOR_NOT_VALID;
import static com.google.javascript.jscomp.PolymerPassErrors.POLYMER_INVALID_DECLARATION;
import static com.google.javascript.jscomp.PolymerPassErrors.POLYMER_INVALID_EXTENDS;
import static com.google.javascript.jscomp.PolymerPassErrors.POLYMER_INVALID_PROPERTY;
import static com.google.javascript.jscomp.PolymerPassErrors.POLYMER_MISSING_IS;
import static com.google.javascript.jscomp.PolymerPassErrors.POLYMER_SHORTHAND_NOT_SUPPORTED;
import static com.google.javascript.jscomp.PolymerPassErrors.POLYMER_UNANNOTATED_BEHAVIOR;
import static com.google.javascript.jscomp.PolymerPassErrors.POLYMER_UNEXPECTED_PARAMS;
import static com.google.javascript.jscomp.PolymerPassErrors.POLYMER_UNQUALIFIED_BEHAVIOR;
import static com.google.javascript.jscomp.TypeValidator.TYPE_MISMATCH_WARNING;
import static com.google.javascript.jscomp.testing.NodeSubject.assertNode;
import com.google.javascript.jscomp.NodeUtil.Visitor;
import com.google.javascript.rhino.Node;
/**
* Unit tests for PolymerPass
* @author jlklein@google.com (Jeremy Klein)
*/
public class PolymerPassTest extends Es6CompilerTestCase {
private static final String EXTERNS =
LINE_JOINER.join(
"/** @constructor */",
"var HTMLElement = function() {};",
"/** @constructor @extends {HTMLElement} */",
"var HTMLInputElement = function() {};",
"/** @constructor @extends {HTMLElement} */",
"var PolymerElement = function() {};",
"/** @type {!Object} */",
"PolymerElement.prototype.$;",
"PolymerElement.prototype.created = function() {};",
"PolymerElement.prototype.ready = function() {};",
"PolymerElement.prototype.attached = function() {};",
"PolymerElement.prototype.domReady = function() {};",
"PolymerElement.prototype.detached = function() {};",
"/**",
" * Call the callback after a timeout. Calling job again with the same name",
" * resets the timer but will not result in additional calls to callback.",
" *",
" * @param {string} name",
" * @param {Function} callback",
" * @param {number} timeoutMillis The minimum delay in milliseconds before",
" * calling the callback.",
" */",
"PolymerElement.prototype.job = function(name, callback, timeoutMillis) {};",
"/**",
" * @param a {!Object}",
" * @return {!function()}",
" */",
"var Polymer = function(a) {};",
"var alert = function(msg) {};");
private static final String INPUT_EXTERNS =
LINE_JOINER.join(
"/** @constructor */",
"var HTMLElement = function() {};",
"/** @constructor @extends {HTMLElement} */",
"var HTMLInputElement = function() {};",
"/** @constructor @extends {HTMLElement} */",
"var PolymerElement = function() {};",
"/** @constructor @extends {HTMLInputElement} */",
"var PolymerInputElement = function() {};",
"/** @type {!Object} */",
"PolymerInputElement.prototype.$;",
"PolymerInputElement.prototype.created = function() {};",
"PolymerInputElement.prototype.ready = function() {};",
"PolymerInputElement.prototype.attached = function() {};",
"PolymerInputElement.prototype.domReady = function() {};",
"PolymerInputElement.prototype.detached = function() {};",
"/**",
" * Call the callback after a timeout. Calling job again with the same name",
" * resets the timer but will not result in additional calls to callback.",
" *",
" * @param {string} name",
" * @param {Function} callback",
" * @param {number} timeoutMillis The minimum delay in milliseconds before",
" * calling the callback.",
" */",
"PolymerInputElement.prototype.job = function(name, callback, timeoutMillis) {};",
"/** @type {!Object} */",
"PolymerElement.prototype.$;",
"PolymerElement.prototype.created = function() {};",
"PolymerElement.prototype.ready = function() {};",
"PolymerElement.prototype.attached = function() {};",
"PolymerElement.prototype.domReady = function() {};",
"PolymerElement.prototype.detached = function() {};",
"/**",
" * Call the callback after a timeout. Calling job again with the same name",
" * resets the timer but will not result in additional calls to callback.",
" *",
" * @param {string} name",
" * @param {Function} callback",
" * @param {number} timeoutMillis The minimum delay in milliseconds before",
" * calling the callback.",
" */",
"PolymerElement.prototype.job = function(name, callback, timeoutMillis) {};",
"/**",
" * @param a {!Object}",
" * @return {!function()}",
" */",
"var Polymer = function(a) {};",
"var alert = function(msg) {};",
"/** @interface */",
"var PolymerXInputElementInterface = function() {};");
private static final String READONLY_EXTERNS =
EXTERNS
+ LINE_JOINER.join(
"/** @interface */",
"var Polymera_BInterface = function() {};",
"/** @type {!Array<string>} */",
"Polymera_BInterface.prototype.pets;",
"/** @private {string} */",
"Polymera_BInterface.prototype.name_;",
"/** @param {!Array<string>} pets **/",
"Polymera_BInterface.prototype._setPets;");
private static final String BEHAVIOR_READONLY_EXTERNS =
EXTERNS
+ LINE_JOINER.join(
"/** @interface */",
"var PolymerAInterface = function() {};",
"/** @type {boolean} */",
"PolymerAInterface.prototype.isFun;",
"/** @type {!Array} */",
"PolymerAInterface.prototype.pets;",
"/** @type {string} */",
"PolymerAInterface.prototype.name;",
"/** @param {boolean} isFun **/",
"PolymerAInterface.prototype._setIsFun;");
public PolymerPassTest() {
super(EXTERNS);
}
@Override
protected CompilerPass getProcessor(Compiler compiler) {
return new PolymerPass(compiler);
}
@Override
protected void setUp() throws Exception {
super.setUp();
allowExternsChanges(true);
enableTypeCheck();
enableCheckAccessControls(false);
runTypeCheckAfterProcessing = true;
parseTypeInfo = true;
}
@Override
protected int getNumRepetitions() {
return 1;
}
public void testVarTarget() {
test(
LINE_JOINER.join(
"var X = Polymer({",
" is: 'x-element',",
"});"),
LINE_JOINER.join(
"/** @constructor @extends {PolymerElement} @implements {PolymerXInterface} */",
"var X = function() {};",
"X = Polymer(/** @lends {X.prototype} */ {",
" is: 'x-element',",
"});"));
}
public void testLetTarget() {
disableTypeCheck();
testEs6(
LINE_JOINER.join(
"let X = Polymer({",
" is: 'x-element',",
"});"),
LINE_JOINER.join(
"/**",
" * @constructor",
" * @implements {PolymerXInterface}",
" * @extends {PolymerElement}",
" */",
"var X = function() {};",
"X = Polymer(/** @lends {X.prototype} */ {is:'x-element'});"));
}
public void testConstTarget() {
disableTypeCheck();
testErrorEs6(
LINE_JOINER.join(
"const X = Polymer({",
" is: 'x-element',",
"});"), POLYMER_INVALID_DECLARATION);
}
public void testDefaultTypeNameTarget() {
test(
LINE_JOINER.join(
"Polymer({",
" is: 'x',",
"});"),
LINE_JOINER.join(
"/**",
" * @implements {PolymerXElementInterface}",
" * @constructor @extends {PolymerElement}",
" */",
"var XElement = function() {};",
"Polymer(/** @lends {XElement.prototype} */ {",
" is: 'x',",
"});"));
}
public void testPathAssignmentTarget() {
test(
LINE_JOINER.join(
"var x = {};",
"x.Z = Polymer({",
" is: 'x-element',",
"});"),
LINE_JOINER.join(
"var x = {};",
"/** @constructor @extends {PolymerElement} @implements {Polymerx_ZInterface} */",
"x.Z = function() {};",
"x.Z = Polymer(/** @lends {x.Z.prototype} */ {",
" is: 'x-element',",
"});"));
}
public void testComputedPropName() {
disableTypeCheck(); // TypeCheck cannot grab a name from a complicated computedPropName
testEs6("var X = Polymer({is:'x-element', [name + (() => 42)]: function() {return 42;}});",
LINE_JOINER.join(
"/** @constructor @extends {PolymerElement} @implements {PolymerXInterface} */",
"var X = function() {}",
"",
"X = Polymer(/** @lends {X.prototype} */{",
" is: 'x-element',",
" /** @this {X} */",
" [name + (()=>42)]: function() {return 42;},",
"});"));
}
/**
* Since 'x' is a global name, the type system understands
* 'x.Z' as a type name, so there is no need to extract the
* type to the global namespace.
*/
public void testIIFEExtractionInGlobalNamespace() {
test(
LINE_JOINER.join(
"var x = {};",
"(function() {",
" x.Z = Polymer({",
" is: 'x-element',",
" sayHi: function() { alert('hi'); },",
" });",
"})()"),
LINE_JOINER.join(
"var x = {};",
"(function() {",
" /** @constructor @extends {PolymerElement} @implements {Polymerx_ZInterface}*/",
" x.Z = function() {};",
" x.Z = Polymer(/** @lends {x.Z.prototype} */ {",
" is: 'x-element',",
" /** @this {x.Z} */",
" sayHi: function() { alert('hi'); },",
" });",
"})()"));
}
/**
* The definition of XElement is placed in the global namespace,
* outside the IIFE so that the type system will understand that
* XElement is a type.
*/
public void testIIFEExtractionNoAssignmentTarget() {
test(
LINE_JOINER.join(
"(function() {",
" Polymer({",
" is: 'x',",
" sayHi: function() { alert('hi'); },",
" });",
"})()"),
LINE_JOINER.join(
"/**",
" * @constructor @extends {PolymerElement}",
" * @implements {PolymerXElementInterface}",
" */",
"var XElement = function() {};",
"(function() {",
" Polymer(/** @lends {XElement.prototype} */ {",
" is: 'x',",
" /** @this {XElement} */",
" sayHi: function() { alert('hi'); },",
" });",
"})()"));
}
/**
* The definition of FooThing is placed in the global namespace,
* outside the IIFE so that the type system will understand that
* FooThing is a type.
*/
public void testIIFEExtractionVarTarget() {
test(
LINE_JOINER.join(
"(function() {",
" var FooThing = Polymer({",
" is: 'x',",
" sayHi: function() { alert('hi'); },",
" });",
"})()"),
LINE_JOINER.join(
"/**",
" * @constructor @extends {PolymerElement}",
" * @implements {PolymerFooThingInterface}",
" */",
"var FooThing = function() {};",
"(function() {",
" FooThing = Polymer(/** @lends {FooThing.prototype} */ {",
" is: 'x',",
" /** @this {FooThing} */",
" sayHi: function() { alert('hi'); },",
" });",
"})()"));
}
public void testConstructorExtraction() {
test(
LINE_JOINER.join(
"var X = Polymer({",
" is: 'x-element',",
" /**",
" * @param {string} name",
" */",
" factoryImpl: function(name) { alert('hi, ' + name); },",
"});"),
LINE_JOINER.join(
"/**",
" * @param {string} name",
" * @constructor @extends {PolymerElement}",
" * @implements {PolymerXInterface}",
" */",
"var X = function(name) { alert('hi, ' + name); };",
"X = Polymer(/** @lends {X.prototype} */ {",
" is: 'x-element',",
" factoryImpl: function(name) { alert('hi, ' + name); },",
"});"));
}
public void testShorthandConstructorExtraction() {
testEs6(
LINE_JOINER.join(
"var X = Polymer({",
" is: 'x-element',",
" /**",
" * @param {string} name",
" */",
" factoryImpl(name) { alert('hi, ' + name); },",
"});"),
LINE_JOINER.join(
"/**",
" * @param {string} name",
" * @constructor @extends {PolymerElement}",
" * @implements {PolymerXInterface}",
" */",
"var X = function(name) { alert('hi, ' + name); };",
"",
"X = Polymer(/** @lends {X.prototype} */ {",
" is: 'x-element',",
" factoryImpl(name) { alert('hi, ' + name); },",
"});"));
}
public void testOtherKeysIgnored() {
test(
LINE_JOINER.join(
"var X = Polymer({",
" is: 'x-element',",
" listeners: {",
" 'click': 'handleClick',",
" },",
"",
" handleClick: function(e) {",
" alert('Thank you for clicking');",
" },",
"});"),
LINE_JOINER.join(
"/** @constructor @extends {PolymerElement} @implements {PolymerXInterface}*/",
"var X = function() {};",
"X = Polymer(/** @lends {X.prototype} */ {",
" is: 'x-element',",
" listeners: {",
" 'click': 'handleClick',",
" },",
"",
" /** @this {X} */",
" handleClick: function(e) {",
" alert('Thank you for clicking');",
" },",
"});"));
}
public void testListenersAndHostAttributeKeysQuoted() {
test(
LINE_JOINER.join(
"var X = Polymer({",
" is: 'x-element',",
" listeners: {",
" click: 'handleClick',",
" 'foo-bar': 'handleClick',",
" },",
" hostAttributes: {",
" role: 'button',",
" 'foo-bar': 'done',",
" blah: 1,",
" },",
"",
" handleClick: function(e) {",
" alert('Thank you for clicking');",
" },",
"});"),
LINE_JOINER.join(
"/** @constructor @extends {PolymerElement} @implements {PolymerXInterface}*/",
"var X = function() {};",
"X = Polymer(/** @lends {X.prototype} */ {",
" is: 'x-element',",
" listeners: {",
" 'click': 'handleClick',",
" 'foo-bar': 'handleClick',",
" },",
" hostAttributes: {",
" 'role': 'button',",
" 'foo-bar': 'done',",
" 'blah': 1,",
" },",
"",
" /** @this {X} */",
" handleClick: function(e) {",
" alert('Thank you for clicking');",
" },",
"});"));
}
public void testNativeElementExtension() {
String js = LINE_JOINER.join(
"Polymer({",
" is: 'x-input',",
" extends: 'input',",
"});");
test(
js,
LINE_JOINER.join(
"/**",
" * @constructor @extends {PolymerInputElement}",
" * @implements {PolymerXInputElementInterface}",
" */",
"var XInputElement = function() {};",
"Polymer(/** @lends {XInputElement.prototype} */ {",
" is: 'x-input',",
" extends: 'input',",
"});"));
testExternChanges(EXTERNS, js, INPUT_EXTERNS);
}
public void testExtendNonExistentElement() {
String js = LINE_JOINER.join(
"Polymer({",
" is: 'x-input',",
" extends: 'nonexist',",
"});");
testError(js, POLYMER_INVALID_EXTENDS);
}
public void testNativeElementExtensionExternsNotDuplicated() {
String js =
LINE_JOINER.join(
"Polymer({",
" is: 'x-input',",
" extends: 'input',",
"});",
"Polymer({",
" is: 'y-input',",
" extends: 'input',",
"});");
String newExterns = LINE_JOINER.join(
INPUT_EXTERNS,
"",
"/** @interface */",
"var PolymerYInputElementInterface = function() {};");
testExternChanges(EXTERNS, js, newExterns);
}
public void testPropertiesAddedToPrototype() {
test(
LINE_JOINER.join(
"/** @constructor */",
"var User = function() {};",
"var a = {};",
"a.B = Polymer({",
" is: 'x-element',",
" properties: {",
" /** @type {!User} @private */",
" user_: Object,",
" pets: {",
" type: Array,",
" notify: true,",
" },",
" name: String,",
" thingToDo: Function,",
" },",
"});"),
LINE_JOINER.join(
"/** @constructor */",
"var User = function() {};",
"var a = {};",
"/** @constructor @extends {PolymerElement} @implements {Polymera_BInterface}*/",
"a.B = function() {};",
"/** @type {!User} @private */",
"a.B.prototype.user_;",
"/** @type {!Array} */",
"a.B.prototype.pets;",
"/** @type {string} */",
"a.B.prototype.name;",
"/** @type {!Function} */",
"a.B.prototype.thingToDo;",
"a.B = Polymer(/** @lends {a.B.prototype} */ {",
" is: 'x-element',",
" properties: {",
" user_: Object,",
" pets: {",
" type: Array,",
" notify: true,",
" },",
" name: String,",
" thingToDo: Function,",
" },",
"});"));
}
public void testPropertiesObjLitShorthand() {
disableTypeCheck();
testErrorEs6(
LINE_JOINER.join(
"var XElem = Polymer({",
" is: 'x-element',",
" properties: {",
" name,",
" },",
"});"),
POLYMER_SHORTHAND_NOT_SUPPORTED);
}
public void testPropertiesDefaultValueFunctions() {
test(
LINE_JOINER.join(
"/** @constructor */",
"var User = function() {};",
"var a = {};",
"a.B = Polymer({",
" is: 'x-element',",
" properties: {",
" /** @type {!User} @private */",
" user_: {",
" type: Object,",
" value: function() { return new User(); },",
" },",
" pets: {",
" type: Array,",
" notify: true,",
" value: function() { return [this.name]; },",
" },",
" name: String,",
" },",
"});"),
LINE_JOINER.join(
"/** @constructor */",
"var User = function() {};",
"var a = {};",
"/** @constructor @extends {PolymerElement} @implements {Polymera_BInterface}*/",
"a.B = function() {};",
"/** @type {!User} @private */",
"a.B.prototype.user_;",
"/** @type {!Array} */",
"a.B.prototype.pets;",
"/** @type {string} */",
"a.B.prototype.name;",
"a.B = Polymer(/** @lends {a.B.prototype} */ {",
" is: 'x-element',",
" properties: {",
" user_: {",
" type: Object,",
" /** @this {a.B} @return {!User} */",
" value: function() { return new User(); },",
" },",
" pets: {",
" type: Array,",
" notify: true,",
" /** @this {a.B} @return {!Array} */",
" value: function() { return [this.name]; },",
" },",
" name: String,",
" },",
"});"));
}
public void testPropertiesDefaultValueShortHandFunction() {
testEs6(
LINE_JOINER.join(
"/** @constructor */",
"var User = function() {};",
"var ES6Test = Polymer({",
" is: 'x-element',",
" properties: {",
" user: {",
" type: Object,",
" value() { return new User();},",
" },",
" },",
"});"),
LINE_JOINER.join(
"/** @constructor */",
"var User = function() {};",
"",
"/** ",
" * @constructor @extends {PolymerElement} ",
" * @implements {PolymerES6TestInterface}",
" */",
"var ES6Test = function() {};",
"/** @type {!Object} */",
"ES6Test.prototype.user;",
"",
"ES6Test = Polymer(/** @lends {ES6Test.prototype} */ {",
" is: 'x-element',",
" properties: {",
" user: {",
" type: Object,",
" /** @this {ES6Test} @return {!Object} */",
" value() { return new User(); },",
" },",
" },",
"});"));
}
public void testReadOnlyPropertySetters() {
String js =
LINE_JOINER.join(
"var a = {};",
"a.B = Polymer({",
" is: 'x-element',",
" properties: {",
" /** @type {!Array<string>} */",
" pets: {",
" type: Array,",
" readOnly: true,",
" },",
" /** @private */",
" name_: String,",
" },",
"});");
test(
js,
LINE_JOINER.join(
"var a = {};",
"/** @constructor @extends {PolymerElement} @implements {Polymera_BInterface} */",
"a.B = function() {};",
"/** @type {!Array<string>} */",
"a.B.prototype.pets;",
"/** @private {string} */",
"a.B.prototype.name_;",
"/** @override */",
"a.B.prototype._setPets = function(pets) {};",
"a.B = Polymer(/** @lends {a.B.prototype} */ {",
" is: 'x-element',",
" properties: {",
" pets: {",
" type: Array,",
" readOnly: true,",
" },",
" name_: String,",
" },",
"});"));
testExternChanges(EXTERNS, js, READONLY_EXTERNS);
}
public void testShorthandFunctionDefinition() {
testEs6(
LINE_JOINER.join(
"var ES6Test = Polymer({",
" is: 'x-element',",
" sayHi() {",
" alert('hi');",
" },",
"});"),
LINE_JOINER.join(
"/** ",
" * @constructor @extends {PolymerElement} ",
" * @implements {PolymerES6TestInterface} ",
" */",
"var ES6Test = function() {};",
"",
"ES6Test = Polymer(/** @lends {ES6Test.prototype} */ {",
" is: 'x-element',",
" /** @this {ES6Test} */",
" sayHi() {",
" alert('hi');",
" },",
"});"));
}
public void testArrowFunctionDefinition() {
testEs6(
LINE_JOINER.join(
"var ES6Test = Polymer({",
" is: 'x-element',",
" sayHi: ()=>42,",
"});"),
LINE_JOINER.join(
"/** ",
" * @constructor @extends {PolymerElement} ",
" * @implements {PolymerES6TestInterface} ",
" */",
"var ES6Test = function() {};",
"",
"ES6Test = Polymer(/** @lends {ES6Test.prototype} */ {",
" is: 'x-element',",
" /** @this {ES6Test} */",
" sayHi: ()=>42,",
"});"));
}
public void testShorthandLifecycleCallbacks() {
testEs6(
LINE_JOINER.join(
"var ES6Test = Polymer({",
" is: 'x-element',",
" /** @override */",
" created() {",
" alert('Shorthand created');",
" },",
"});"),
LINE_JOINER.join(
"/** ",
" * @constructor @extends {PolymerElement} ",
" * @implements {PolymerES6TestInterface} ",
" */",
"var ES6Test = function() {};",
"",
"ES6Test = Polymer(/** @lends {ES6Test.prototype} */ {",
" is: 'x-element',",
" /** @override @this {ES6Test} */",
" created() {",
" alert('Shorthand created');",
" },",
"});"));
}
public void testShorthandFunctionDefinitionWithReturn() {
testEs6(
LINE_JOINER.join(
"var ESTest = Polymer({",
" is: 'x-element',",
" sayHi() {",
" return [1, 2];",
" },",
"});"),
LINE_JOINER.join(
"/** ",
" * @constructor @extends {PolymerElement} ",
" * @implements {PolymerESTestInterface} ",
" */",
"var ESTest = function() {};",
"",
"ESTest = Polymer(/** @lends {ESTest.prototype} */ {",
" is: 'x-element',",
" /** @this {ESTest} */",
" sayHi() {",
" return [1, 2];",
" },",
"});"));
}
public void testThisTypeAddedToFunctions() {
test(
LINE_JOINER.join(
"var X = Polymer({",
" is: 'x-element',",
" sayHi: function() {",
" alert('hi');",
" },",
" /** @override */",
" created: function() {",
" this.sayHi();",
" this.sayHelloTo_('Tester');",
" },",
" /**",
" * @param {string} name",
" * @private",
" */",
" sayHelloTo_: function(name) {",
" alert('Hello, ' + name);",
" },",
"});"),
LINE_JOINER.join(
"/** @constructor @extends {PolymerElement} @implements {PolymerXInterface} */",
"var X = function() {};",
"X = Polymer(/** @lends {X.prototype} */ {",
" is: 'x-element',",
" /** @this {X} */",
" sayHi: function() {",
" alert('hi');",
" },",
" /** @override @this {X} */",
" created: function() {",
" this.sayHi();",
" this.sayHelloTo_('Tester');",
" },",
" /**",
" * @param {string} name",
" * @private",
" * @this {X}",
" */",
" sayHelloTo_: function(name) {",
" alert('Hello, ' + name);",
" },",
"});"));
}
public void testDollarSignPropsConvertedToBrackets() {
test(
LINE_JOINER.join(
"/** @constructor */",
"var SomeType = function() {};",
"SomeType.prototype.toggle = function() {};",
"SomeType.prototype.switch = function() {};",
"SomeType.prototype.touch = function() {};",
"var X = Polymer({",
" is: 'x-element',",
" properties: {",
" /** @type {!HTMLElement} */",
" propName: {",
" type: Object,",
" value: function() { return this.$.id; },",
" },",
" },",
" sayHi: function() {",
" this.$.checkbox.toggle();",
" },",
" /** @override */",
" created: function() {",
" this.sayHi();",
" this.$.radioButton.switch();",
" },",
" /**",
" * @param {string} name",
" * @private",
" */",
" sayHelloTo_: function(name) {",
" this.$.otherThing.touch();",
" },",
"});"),
LINE_JOINER.join(
"/** @constructor */",
"var SomeType = function() {};",
"SomeType.prototype.toggle = function() {};",
"SomeType.prototype.switch = function() {};",
"SomeType.prototype.touch = function() {};",
"/** @constructor @extends {PolymerElement} @implements {PolymerXInterface} */",
"var X = function() {};",
"/** @type {!HTMLElement} */",
"X.prototype.propName;",
"X = Polymer(/** @lends {X.prototype} */ {",
" is: 'x-element',",
" properties: {",
" propName: {",
" type: Object,",
" /** @this {X} @return {!HTMLElement} */",
" value: function() { return this.$['id']; },",
" },",
" },",
" /** @this {X} */",
" sayHi: function() {",
" this.$['checkbox'].toggle();",
" },",
" /** @override @this {X} */",
" created: function() {",
" this.sayHi();",
" this.$['radioButton'].switch();",
" },",
" /**",
" * @param {string} name",
" * @private",
" * @this {X}",
" */",
" sayHelloTo_: function(name) {",
" this.$['otherThing'].touch();",
" },",
"});"));
}
public void testDollarSignPropsInShorthandFunctionConvertedToBrackets() {
testEs6(
LINE_JOINER.join(
"/** @constructor */",
"var SomeType = function() {};",
"SomeType.prototype.toggle = function() {};",
"var ES6Test = Polymer({",
" is: 'x-element',",
" sayHi() {",
" this.$.checkbox.toggle();",
" },",
"});"),
LINE_JOINER.join(
"/** @constructor */",
"var SomeType = function() {};",
"SomeType.prototype.toggle = function() {};",
"",
"/** ",
" * @constructor @extends {PolymerElement} ",
" * @implements {PolymerES6TestInterface} ",
" */",
"var ES6Test = function() {};",
"",
"ES6Test = Polymer(/** @lends {ES6Test.prototype} */ {",
" is: 'x-element',",
" /** @this {ES6Test} */",
" sayHi() {",
" this.$['checkbox'].toggle();",
" },",
"});"));
}
/**
* Test that behavior property types are copied correctly to multiple elements. See b/21929103.
*/
public void testBehaviorForMultipleElements() {
test(
LINE_JOINER.join(
"/** @constructor */",
"var FunObject = function() {};",
"/** @polymerBehavior */",
"var FunBehavior = {",
" properties: {",
" /** @type {!FunObject} */",
" funThing: {",
" type: Object,",
" }",
" },",
"};",
"var A = Polymer({",
" is: 'x-element',",
" properties: {",
" pets: {",
" type: Array,",
" notify: true,",
" },",
" name: String,",
" },",
" behaviors: [ FunBehavior ],",
"});",
"var B = Polymer({",
" is: 'y-element',",
" properties: {",
" foo: String,",
" },",
" behaviors: [ FunBehavior ],",
"});"),
LINE_JOINER.join(
"/** @constructor */",
"var FunObject = function() {};",
"/** @polymerBehavior @nocollapse */",
"var FunBehavior = {",
" properties: {",
" funThing: {",
" type: Object,",
" }",
" },",
"};",
"/** @constructor @extends {PolymerElement} @implements {PolymerAInterface}*/",
"var A = function() {};",
"/** @type {!FunObject} */",
"A.prototype.funThing;",
"/** @type {!Array} */",
"A.prototype.pets;",
"/** @type {string} */",
"A.prototype.name;",
"A = Polymer(/** @lends {A.prototype} */ {",
" is: 'x-element',",
" properties: {",
" pets: {",
" type: Array,",
" notify: true,",
" },",
" name: String,",
" },",
" behaviors: [ FunBehavior ],",
"});",
"/** @constructor @extends {PolymerElement} @implements {PolymerBInterface}*/",
"var B = function() {};",
"/** @type {!FunObject} */",
"B.prototype.funThing;",
"/** @type {string} */",
"B.prototype.foo;",
"B = Polymer(/** @lends {B.prototype} */ {",
" is: 'y-element',",
" properties: {",
" foo: String,",
" },",
" behaviors: [ FunBehavior ],",
"});"));
}
public void testSimpleBehavior() {
test(
LINE_JOINER.join(
"/** @polymerBehavior */",
"var FunBehavior = {",
" properties: {",
" /** @type {boolean} */",
" isFun: {",
" type: Boolean,",
" value: true,",
" }",
" },",
" listeners: {",
" click: 'doSomethingFun',",
" },",
" /** @type {string} */",
" foo: 'hooray',",
"",
" /** @return {number} */",
" get someNumber() {",
" return 5*7+2;",
" },",
" /** @param {string} funAmount */",
" doSomethingFun: function(funAmount) { alert('Something ' + funAmount + ' fun!'); },",
" /** @override */",
" created: function() {}",
"};",
"var A = Polymer({",
" is: 'x-element',",
" properties: {",
" pets: {",
" type: Array,",
" notify: true,",
" },",
" name: String,",
" },",
" behaviors: [ FunBehavior ],",
"});"),
LINE_JOINER.join(
"/** @polymerBehavior @nocollapse */",
"var FunBehavior = {",
" properties: {",
" isFun: {",
" type: Boolean,",
" value: true,",
" }",
" },",
" listeners: {",
" 'click': 'doSomethingFun',",
" },",
" /** @type {string} */",
" foo: 'hooray',",
"",
" /** @suppress {checkTypes|globalThis|visibility} */",
" get someNumber() {",
" return 5*7+2;",
" },",
" /** @suppress {checkTypes|globalThis|visibility} */",
" doSomethingFun: function(funAmount) { alert('Something ' + funAmount + ' fun!'); },",
" /** @suppress {checkTypes|globalThis|visibility} */",
" created: function() {}",
"};",
"/** @constructor @extends {PolymerElement} @implements {PolymerAInterface}*/",
"var A = function() {};",
"/** @type {boolean} */",
"A.prototype.isFun;",
"/** @type {!Array} */",
"A.prototype.pets;",
"/** @type {string} */",
"A.prototype.name;",
"/**",
" * @param {string} funAmount",
" * @suppress {unusedPrivateMembers}",
" */",
"A.prototype.doSomethingFun = function(funAmount) {",
" alert('Something ' + funAmount + ' fun!');",
"};",
"/** @type {string} */",
"A.prototype.foo;",
"/** @type {number} */",
"A.prototype.someNumber;",
"A = Polymer(/** @lends {A.prototype} */ {",
" is: 'x-element',",
" properties: {",
" pets: {",
" type: Array,",
" notify: true,",
" },",
" name: String,",
" },",
" behaviors: [ FunBehavior ],",
"});"));
// The original doSomethingFun definition in FunBehavior is on line 21, so make sure that
// line number is preserved when it's copied into the Polymer() call.
Node root = getLastCompiler().getRoot();
DoSomethingFunFinder visitor = new DoSomethingFunFinder();
NodeUtil.visitPreOrder(root, visitor);
assertThat(visitor.found).isTrue();
}
private static class DoSomethingFunFinder implements Visitor {
boolean found = false;
@Override
public void visit(Node n) {
if (n.matchesQualifiedName("A.prototype.doSomethingFun")) {
assertNode(n).hasLineno(21);
found = true;
}
}
}
/** If a behavior method is {@code @protected} there is no visibility warning. */
public void testBehaviorWithProtectedMethod() {
enableCheckAccessControls(true);
test(
new String[] {
LINE_JOINER.join(
"/** @polymerBehavior */",
"var FunBehavior = {",
" /** @protected */",
" doSomethingFun: function() {},",
"};"),
LINE_JOINER.join(
"var A = Polymer({",
" is: 'x-element',",
" callBehaviorMethod: function() {",
" this.doSomethingFun();",
" },",
" behaviors: [ FunBehavior ],",
"});"),
},
new String[] {
LINE_JOINER.join(
"/** @polymerBehavior @nocollapse */",
"var FunBehavior = {",
" /**",
" * @suppress {checkTypes|globalThis|visibility}",
" */",
" doSomethingFun: function() {},",
"};"),
LINE_JOINER.join(
"/**",
" * @constructor",
" * @extends {PolymerElement}",
" * @implements {PolymerAInterface}",
" */",
"var A = function() {};",
"",
"/**",
" * @public",
" * @suppress {unusedPrivateMembers}",
" */",
"A.prototype.doSomethingFun = function(){};",
"",
"A = Polymer(/** @lends {A.prototype} */ {",
" is: 'x-element',",
" /** @this {A} */",
" callBehaviorMethod: function(){ this.doSomethingFun(); },",
" behaviors: [FunBehavior],",
"})"),
});
}
/** If a behavior method is {@code @private} there is a visibility warning. */
public void testBehaviorWithPrivateMethod() {
enableCheckAccessControls(true);
testWarning(
new String[] {
LINE_JOINER.join(
"/** @polymerBehavior */",
"var FunBehavior = {",
" /** @private */",
" doSomethingFun: function() {},",
"};"),
LINE_JOINER.join(
"var A = Polymer({",
" is: 'x-element',",
" callBehaviorMethod: function() {",
" this.doSomethingFun();",
" },",
" behaviors: [ FunBehavior ],",
"});"),
},
CheckAccessControls.BAD_PRIVATE_PROPERTY_ACCESS);
}
/**
* Test that if a behavior function is implemented by the Element, the function from the behavior
* is not copied to the prototype of the Element.
*/
public void testBehaviorFunctionOverriddenByElement() {
test(
LINE_JOINER.join(
"/** @polymerBehavior */",
"var FunBehavior = {",
" /** @param {string} funAmount */",
" doSomethingFun: function(funAmount) { alert('Something ' + funAmount + ' fun!'); },",
"};",
"var A = Polymer({",
" is: 'x-element',",
" properties: {",
" pets: {",
" type: Array,",
" notify: true,",
" },",
" name: String,",
" },",
" /** @param {string} funAmount */",
" doSomethingFun: function(funAmount) {",
" alert('Element doing something' + funAmount + ' fun!');",
" },",
" behaviors: [ FunBehavior ],",
"});"),
LINE_JOINER.join(
"/** @polymerBehavior @nocollapse */",
"var FunBehavior = {",
" /** @suppress {checkTypes|globalThis|visibility} */",
" doSomethingFun: function(funAmount) { alert('Something ' + funAmount + ' fun!'); },",
"};",
"/** @constructor @extends {PolymerElement} @implements {PolymerAInterface}*/",
"var A = function() {};",
"/** @type {!Array} */",
"A.prototype.pets;",
"/** @type {string} */",
"A.prototype.name;",
"A = Polymer(/** @lends {A.prototype} */ {",
" is: 'x-element',",
" properties: {",
" pets: {",
" type: Array,",
" notify: true,",
" },",
" name: String,",
" },",
" /**",
" * @param {string} funAmount",
" * @this {A}",
" */",
" doSomethingFun: function(funAmount) {",
" alert('Element doing something' + funAmount + ' fun!');",
" },",
" behaviors: [ FunBehavior ],",
"});"));
}
public void testBehaviorShorthandFunctionOverriddenByElement() {
testEs6(
LINE_JOINER.join(
"/** @polymerBehavior */",
"var FunBehavior = {",
" /** @param {string} funAmount */",
" doSomethingFun(funAmount) { alert('Something ' + funAmount + ' fun!'); },",
"};",
"",
"var A = Polymer({",
" is: 'x-element',",
" properties: {",
" name: String,",
" },",
" /** @param {string} funAmount */",
" doSomethingFun(funAmount) {",
" alert('Element doing something' + funAmount + ' fun!');",
" },",
" behaviors: [ FunBehavior ],",
"});"),
LINE_JOINER.join(
"/** @polymerBehavior @nocollapse */",
"var FunBehavior = {",
" /** @suppress {checkTypes|globalThis|visibility} */",
" doSomethingFun(funAmount) { alert('Something ' + funAmount + ' fun!'); },",
"};",
"",
"/** @constructor @extends {PolymerElement} @implements {PolymerAInterface}*/",
"var A = function() {};",
"",
"/** @type {string} */",
"A.prototype.name;",
"",
"A = Polymer(/** @lends {A.prototype} */ {",
" is: 'x-element',",
" properties: {",
" name: String,",
" },",
" /**",
" * @param {string} funAmount",
" * @this {A}",
" */",
" doSomethingFun(funAmount) {",
" alert('Element doing something' + funAmount + ' fun!');",
" },",
" behaviors: [ FunBehavior ],",
"});"));
}
public void testBehaviorDefaultValueSuppression() {
test(
LINE_JOINER.join(
"/** @polymerBehavior */",
"var FunBehavior = {",
" properties: {",
" /** @type {boolean} */",
" isFun: {",
" type: Boolean,",
" value: true,",
" },",
" funObject: {",
" type: Object,",
" value: function() { return {fun: this.isFun }; },",
" },",
" funArray: {",
" type: Array,",
" value: function() { return [this.isFun]; },",
" },",
" },",
"};",
"var A = Polymer({",
" is: 'x-element',",
" properties: {",
" name: String,",
" },",
" behaviors: [ FunBehavior ],",
"});"),
LINE_JOINER.join(
"/** @polymerBehavior @nocollapse */",
"var FunBehavior = {",
" properties: {",
" isFun: {",
" type: Boolean,",
" value: true,",
" },",
" funObject: {",
" type: Object,",
" /** @suppress {checkTypes|globalThis|visibility} */",
" value: function() { return {fun: this.isFun }; },",
" },",
" funArray: {",
" type: Array,",
" /** @suppress {checkTypes|globalThis|visibility} */",
" value: function() { return [this.isFun]; },",
" },",
" },",
"};",
"/** @constructor @extends {PolymerElement} @implements {PolymerAInterface}*/",
"var A = function() {};",
"/** @type {boolean} */",
"A.prototype.isFun;",
"/** @type {!Object} */",
"A.prototype.funObject;",
"/** @type {!Array} */",
"A.prototype.funArray;",
"/** @type {string} */",
"A.prototype.name;",
"A = Polymer(/** @lends {A.prototype} */ {",
" is: 'x-element',",
" properties: {",
" name: String,",
" },",
" behaviors: [ FunBehavior ],",
"});"));
}
public void testArrayBehavior() {
test(
LINE_JOINER.join(
"/** @polymerBehavior */",
"var FunBehavior = {",
" properties: {",
" isFun: Boolean",
" },",
" /** @param {string} funAmount */",
" doSomethingFun: function(funAmount) { alert('Something ' + funAmount + ' fun!'); },",
" /** @override */",
" created: function() {}",
"};",
"/** @polymerBehavior */",
"var RadBehavior = {",
" properties: {",
" howRad: Number",
" },",
" /** @param {number} radAmount */",
" doSomethingRad: function(radAmount) { alert('Something ' + radAmount + ' rad!'); },",
" /** @override */",
" ready: function() {}",
"};",
"/** @polymerBehavior */",
"var SuperCoolBehaviors = [FunBehavior, RadBehavior];",
"/** @polymerBehavior */",
"var BoringBehavior = {",
" properties: {",
" boringString: String",
" },",
" /** @param {boolean} boredYet */",
" doSomething: function(boredYet) { alert(boredYet + ' ' + this.boringString); },",
"};",
"var A = Polymer({",
" is: 'x-element',",
" properties: {",
" pets: {",
" type: Array,",
" notify: true,",
" },",
" name: String,",
" },",
" behaviors: [ SuperCoolBehaviors, BoringBehavior ],",
"});"),
LINE_JOINER.join(
"/** @polymerBehavior @nocollapse */",
"var FunBehavior = {",
" properties: {",
" isFun: Boolean",
" },",
" /** @suppress {checkTypes|globalThis|visibility} */",
" doSomethingFun: function(funAmount) { alert('Something ' + funAmount + ' fun!'); },",
" /** @suppress {checkTypes|globalThis|visibility} */",
" created: function() {}",
"};",
"/** @polymerBehavior @nocollapse */",
"var RadBehavior = {",
" properties: {",
" howRad: Number",
" },",
" /** @suppress {checkTypes|globalThis|visibility} */",
" doSomethingRad: function(radAmount) { alert('Something ' + radAmount + ' rad!'); },",
" /** @suppress {checkTypes|globalThis|visibility} */",
" ready: function() {}",
"};",
"/** @polymerBehavior @nocollapse */",
"var SuperCoolBehaviors = [FunBehavior, RadBehavior];",
"/** @polymerBehavior @nocollapse */",
"var BoringBehavior = {",
" properties: {",
" boringString: String",
" },",
" /** @suppress {checkTypes|globalThis|visibility} */",
" doSomething: function(boredYet) { alert(boredYet + ' ' + this.boringString); },",
"};",
"/** @constructor @extends {PolymerElement} @implements {PolymerAInterface}*/",
"var A = function() {};",
"/** @type {boolean} */",
"A.prototype.isFun;",
"/** @type {number} */",
"A.prototype.howRad;",
"/** @type {string} */",
"A.prototype.boringString;",
"/** @type {!Array} */",
"A.prototype.pets;",
"/** @type {string} */",
"A.prototype.name;",
"/**",
" * @param {string} funAmount",
" * @suppress {unusedPrivateMembers}",
" */",
"A.prototype.doSomethingFun = function(funAmount) {",
" alert('Something ' + funAmount + ' fun!');",
"};",
"/**",
" * @param {number} radAmount",
" * @suppress {unusedPrivateMembers}",
" */",
"A.prototype.doSomethingRad = function(radAmount) {",
" alert('Something ' + radAmount + ' rad!');",
"};",
"/**",
" * @param {boolean} boredYet",
" * @suppress {unusedPrivateMembers}",
" */",
"A.prototype.doSomething = function(boredYet) {",
" alert(boredYet + ' ' + this.boringString);",
"};",
"A = Polymer(/** @lends {A.prototype} */ {",
" is: 'x-element',",
" properties: {",
" pets: {",
" type: Array,",
" notify: true,",
" },",
" name: String,",
" },",
" behaviors: [ SuperCoolBehaviors, BoringBehavior ],",
"});"));
}
public void testInlineLiteralBehavior() {
test(
LINE_JOINER.join(
"/** @polymerBehavior */",
"var FunBehavior = {",
" properties: {",
" isFun: Boolean",
" },",
" /** @param {string} funAmount */",
" doSomethingFun: function(funAmount) { alert('Something ' + funAmount + ' fun!'); },",
" /** @override */",
" created: function() {}",
"};",
"/** @polymerBehavior */",
"var SuperCoolBehaviors = [FunBehavior, {",
" properties: {",
" howRad: Number",
" },",
" /** @param {number} radAmount */",
" doSomethingRad: function(radAmount) { alert('Something ' + radAmount + ' rad!'); },",
" /** @override */",
" ready: function() {}",
"}];",
"var A = Polymer({",
" is: 'x-element',",
" properties: {",
" pets: {",
" type: Array,",
" notify: true,",
" },",
" name: String,",
" },",
" behaviors: [ SuperCoolBehaviors ],",
"});"),
LINE_JOINER.join(
"/** @polymerBehavior @nocollapse */",
"var FunBehavior = {",
" properties: {",
" isFun: Boolean",
" },",
" /** @suppress {checkTypes|globalThis|visibility} */",
" doSomethingFun: function(funAmount) { alert('Something ' + funAmount + ' fun!'); },",
" /** @suppress {checkTypes|globalThis|visibility} */",
" created: function() {}",
"};",
"/** @polymerBehavior @nocollapse */",
"var SuperCoolBehaviors = [FunBehavior, {",
" properties: {",
" howRad: Number",
" },",
" /** @suppress {checkTypes|globalThis|visibility} */",
" doSomethingRad: function(radAmount) { alert('Something ' + radAmount + ' rad!'); },",
" /** @suppress {checkTypes|globalThis|visibility} */",
" ready: function() {}",
"}];",
"/** @constructor @extends {PolymerElement} @implements {PolymerAInterface}*/",
"var A = function() {};",
"/** @type {boolean} */",
"A.prototype.isFun;",
"/** @type {number} */",
"A.prototype.howRad;",
"/** @type {!Array} */",
"A.prototype.pets;",
"/** @type {string} */",
"A.prototype.name;",
"/**",
" * @param {string} funAmount",
" * @suppress {unusedPrivateMembers}",
" */",
"A.prototype.doSomethingFun = function(funAmount) {",
" alert('Something ' + funAmount + ' fun!');",
"};",
"/**",
" * @param {number} radAmount",
" * @suppress {unusedPrivateMembers}",
" */",
"A.prototype.doSomethingRad = function(radAmount) {",
" alert('Something ' + radAmount + ' rad!');",
"};",
"A = Polymer(/** @lends {A.prototype} */ {",
" is: 'x-element',",
" properties: {",
" pets: {",
" type: Array,",
" notify: true,",
" },",
" name: String,",
" },",
" behaviors: [ SuperCoolBehaviors ],",
"});"));
}
/**
* If an element has two or more behaviors which define the same function, only the last
* behavior's function should be copied over to the element's prototype.
*/
public void testBehaviorFunctionOverriding() {
test(
LINE_JOINER.join(
"/** @polymerBehavior */",
"var FunBehavior = {",
" properties: {",
" isFun: Boolean",
" },",
" /** @param {boolean} boredYet */",
" doSomething: function(boredYet) { alert(boredYet + ' ' + this.isFun); },",
" /** @override */",
" created: function() {}",
"};",
"/** @polymerBehavior */",
"var RadBehavior = {",
" properties: {",
" howRad: Number",
" },",
" /** @param {boolean} boredYet */",
" doSomething: function(boredYet) { alert(boredYet + ' ' + this.howRad); },",
" /** @override */",
" ready: function() {}",
"};",
"/** @polymerBehavior */",
"var SuperCoolBehaviors = [FunBehavior, RadBehavior];",
"/** @polymerBehavior */",
"var BoringBehavior = {",
" properties: {",
" boringString: String",
" },",
" /** @param {boolean} boredYet */",
" doSomething: function(boredYet) { alert(boredYet + ' ' + this.boringString); },",
"};",
"var A = Polymer({",
" is: 'x-element',",
" properties: {",
" pets: {",
" type: Array,",
" notify: true,",
" },",
" name: String,",
" },",
" behaviors: [ SuperCoolBehaviors, BoringBehavior ],",
"});"),
LINE_JOINER.join(
"/** @polymerBehavior @nocollapse */",
"var FunBehavior = {",
" properties: {",
" isFun: Boolean",
" },",
" /** @suppress {checkTypes|globalThis|visibility} */",
" doSomething: function(boredYet) { alert(boredYet + ' ' + this.isFun); },",
" /** @suppress {checkTypes|globalThis|visibility} */",
" created: function() {}",
"};",
"/** @polymerBehavior @nocollapse */",
"var RadBehavior = {",
" properties: {",
" howRad: Number",
" },",
" /** @suppress {checkTypes|globalThis|visibility} */",
" doSomething: function(boredYet) { alert(boredYet + ' ' + this.howRad); },",
" /** @suppress {checkTypes|globalThis|visibility} */",
" ready: function() {}",
"};",
"/** @polymerBehavior @nocollapse */",
"var SuperCoolBehaviors = [FunBehavior, RadBehavior];",
"/** @polymerBehavior @nocollapse */",
"var BoringBehavior = {",
" properties: {",
" boringString: String",
" },",
" /** @suppress {checkTypes|globalThis|visibility} */",
" doSomething: function(boredYet) { alert(boredYet + ' ' + this.boringString); },",
"};",
"/** @constructor @extends {PolymerElement} @implements {PolymerAInterface}*/",
"var A = function() {};",
"/** @type {boolean} */",
"A.prototype.isFun;",
"/** @type {number} */",
"A.prototype.howRad;",
"/** @type {string} */",
"A.prototype.boringString;",
"/** @type {!Array} */",
"A.prototype.pets;",
"/** @type {string} */",
"A.prototype.name;",
"/**",
" * @param {boolean} boredYet",
" * @suppress {unusedPrivateMembers}",
" */",
"A.prototype.doSomething = function(boredYet) {",
" alert(boredYet + ' ' + this.boringString);",
"};",
"A = Polymer(/** @lends {A.prototype} */ {",
" is: 'x-element',",
" properties: {",
" pets: {",
" type: Array,",
" notify: true,",
" },",
" name: String,",
" },",
" behaviors: [ SuperCoolBehaviors, BoringBehavior ],",
"});"));
}
public void testBehaviorShorthandFunctionOverriding() {
testEs6(
LINE_JOINER.join(
"/** @polymerBehavior */",
"var FunBehavior = {",
" properties: {",
" isFun: Boolean",
" },",
" /** @param {boolean} boredYet */",
" doSomething(boredYet) { alert(boredYet + ' ' + this.isFun); },",
"};",
"",
"/** @polymerBehavior */",
"var RadBehavior = {",
" properties: {",
" howRad: Number",
" },",
" /** @param {boolean} boredYet */",
" doSomething(boredYet) { alert(boredYet + ' ' + this.howRad); },",
"};",
"",
"/** @polymerBehavior */",
"var SuperCoolBehaviors = [FunBehavior, RadBehavior];",
"",
"/** @polymerBehavior */",
"var BoringBehavior = {",
" properties: {",
" boringString: String",
" },",
" /** @param {boolean} boredYet */",
" doSomething(boredYet) { alert(boredYet + ' ' + this.boringString); },",
"};",
"",
"var A = Polymer({",
" is: 'x-element',",
" properties: {",
" pets: {",
" type: Array,",
" notify: true,",
" },",
" name: String,",
" },",
" behaviors: [ SuperCoolBehaviors, BoringBehavior ],",
"});"),
LINE_JOINER.join(
"/** @polymerBehavior @nocollapse */",
"var FunBehavior = {",
" properties: {",
" isFun: Boolean",
" },",
" /** @suppress {checkTypes|globalThis|visibility} */",
" doSomething(boredYet) { alert(boredYet + ' ' + this.isFun); },",
"};",
"",
"/** @polymerBehavior @nocollapse */",
"var RadBehavior = {",
" properties: {",
" howRad: Number",
" },",
" /** @suppress {checkTypes|globalThis|visibility} */",
" doSomething(boredYet) { alert(boredYet + ' ' + this.howRad); },",
"};",
"",
"/** @polymerBehavior @nocollapse */",
"var SuperCoolBehaviors = [FunBehavior, RadBehavior];",
"/** @polymerBehavior @nocollapse */",
"var BoringBehavior = {",
" properties: {",
" boringString: String",
" },",
" /** @suppress {checkTypes|globalThis|visibility} */",
" doSomething(boredYet) { alert(boredYet + ' ' + this.boringString); },",
"};",
"",
"/** @constructor @extends {PolymerElement} @implements {PolymerAInterface}*/",
"var A = function() {};",
"",
"/** @type {boolean} */",
"A.prototype.isFun;",
"/** @type {number} */",
"A.prototype.howRad;",
"/** @type {string} */",
"A.prototype.boringString;",
"/** @type {!Array} */",
"A.prototype.pets;",
"/** @type {string} */",
"A.prototype.name;",
"/**",
" * @param {boolean} boredYet",
" * @suppress {unusedPrivateMembers}",
" */",
"A.prototype.doSomething = function(boredYet) {",
" alert(boredYet + ' ' + this.boringString);",
"};",
"",
"A = Polymer(/** @lends {A.prototype} */ {",
" is: 'x-element',",
" properties: {",
" pets: {",
" type: Array,",
" notify: true,",
" },",
" name: String,",
" },",
" behaviors: [ SuperCoolBehaviors, BoringBehavior ],",
"});"));
}
public void testBehaviorReadOnlyProp() {
String js =
LINE_JOINER.join(
"/** @polymerBehavior */",
"var FunBehavior = {",
" properties: {",
" isFun: {",
" type: Boolean,",
" readOnly: true,",
" },",
" },",
" /** @param {string} funAmount */",
" doSomethingFun: function(funAmount) { alert('Something ' + funAmount + ' fun!'); },",
" /** @override */",
" created: function() {}",
"};",
"var A = Polymer({",
" is: 'x-element',",
" properties: {",
" pets: {",
" type: Array,",
" notify: true,",
" },",
" name: String,",
" },",
" behaviors: [ FunBehavior ],",
"});");
test(
js,
LINE_JOINER.join(
"/** @polymerBehavior @nocollapse */",
"var FunBehavior = {",
" properties: {",
" isFun: {",
" type: Boolean,",
" readOnly: true,",
" },",
" },",
" /** @suppress {checkTypes|globalThis|visibility} */",
" doSomethingFun: function(funAmount) { alert('Something ' + funAmount + ' fun!'); },",
" /** @suppress {checkTypes|globalThis|visibility} */",
" created: function() {}",
"};",
"/** @constructor @extends {PolymerElement} @implements {PolymerAInterface}*/",
"var A = function() {};",
"/** @type {boolean} */",
"A.prototype.isFun;",
"/** @type {!Array} */",
"A.prototype.pets;",
"/** @type {string} */",
"A.prototype.name;",
"/**",
" * @param {string} funAmount",
" * @suppress {unusedPrivateMembers}",
" */",
"A.prototype.doSomethingFun = function(funAmount) {",
" alert('Something ' + funAmount + ' fun!');",
"};",
"/** @override */",
"A.prototype._setIsFun = function(isFun) {};",
"A = Polymer(/** @lends {A.prototype} */ {",
" is: 'x-element',",
" properties: {",
" pets: {",
" type: Array,",
" notify: true,",
" },",
" name: String,",
" },",
" behaviors: [ FunBehavior ],",
"});"));
testExternChanges(EXTERNS, js, BEHAVIOR_READONLY_EXTERNS);
}
/**
* Behaviors whose declarations are not in the global scope may contain references to
* symbols which do not exist in the element's scope. Only copy a function stub.
*/
public void testBehaviorInIIFE() {
test(
LINE_JOINER.join(
"(function() {",
" /** @polymerBehavior */",
" Polymer.FunBehavior = {",
" properties: {",
" /** @type {boolean} */",
" isFun: {",
" type: Boolean,",
" value: true,",
" }",
" },",
" /** @type {string} */",
" foo: 'hooray',",
" /** @return {number} */",
" get someNumber() {",
" return 5*7+2;",
" },",
" /** @param {string} funAmount */",
" doSomethingFun: function(funAmount) {",
" alert('Something ' + funAmount + ' fun!');",
" },",
" /** @override */",
" created: function() {}",
" };",
"})();",
"var A = Polymer({",
" is: 'x-element',",
" properties: {",
" pets: {",
" type: Array,",
" notify: true,",
" },",
" name: String,",
" },",
" behaviors: [ Polymer.FunBehavior ],",
"});"),
LINE_JOINER.join(
"(function() {",
" /** @polymerBehavior @nocollapse */",
" Polymer.FunBehavior = {",
" properties: {",
" isFun: {",
" type: Boolean,",
" value: true,",
" }",
" },",
" /** @type {string} */",
" foo: 'hooray',",
" /** @suppress {checkTypes|globalThis|visibility} */",
" get someNumber() {",
" return 5*7+2;",
" },",
" /** @suppress {checkTypes|globalThis|visibility} */",
" doSomethingFun: function(funAmount) {",
" alert('Something ' + funAmount + ' fun!');",
" },",
" /** @suppress {checkTypes|globalThis|visibility} */",
" created: function() {}",
" };",
"})();",
"/** @constructor @extends {PolymerElement} @implements {PolymerAInterface}*/",
"var A = function() {};",
"/** @type {boolean} */",
"A.prototype.isFun;",
"/** @type {!Array} */",
"A.prototype.pets;",
"/** @type {string} */",
"A.prototype.name;",
"/**",
" * @param {string} funAmount",
" * @suppress {unusedPrivateMembers}",
" */",
"A.prototype.doSomethingFun = function(funAmount) {};",
"/** @type {string} */",
"A.prototype.foo;",
"/** @type {number} */",
"A.prototype.someNumber;",
"A = Polymer(/** @lends {A.prototype} */ {",
" is: 'x-element',",
" properties: {",
" pets: {",
" type: Array,",
" notify: true,",
" },",
" name: String,",
" },",
" behaviors: [ Polymer.FunBehavior ],",
"});"));
}
public void testDuplicatedBehaviorsAreCopiedOnce() {
test(
LINE_JOINER.join(
"/** @polymerBehavior */",
"var FunBehavior = {",
" properties: {",
" isFun: Boolean",
" },",
" /** @param {string} funAmount */",
" doSomethingFun: function(funAmount) { alert('Something ' + funAmount + ' fun!'); },",
" /** @override */",
" created: function() {}",
"};",
"",
"/** @polymerBehavior */",
"var RadBehavior = {",
" properties: {",
" howRad: Number",
" },",
" /** @param {number} radAmount */",
" doSomethingRad: function(radAmount) { alert('Something ' + radAmount + ' rad!'); },",
" /** @override */",
" ready: function() {}",
"};",
"",
"/** @polymerBehavior */",
"var SuperCoolBehaviors = [FunBehavior, RadBehavior];",
"var A = Polymer({",
" is: 'x-element',",
" properties: {",
" pets: {",
" type: Array,",
" notify: true,",
" },",
" name: String,",
" },",
" behaviors: [ SuperCoolBehaviors, FunBehavior ],",
"});"),
LINE_JOINER.join(
"/** @polymerBehavior @nocollapse */",
"var FunBehavior = {",
" properties: {",
" isFun: Boolean",
" },",
" /** @suppress {checkTypes|globalThis|visibility} */",
" doSomethingFun: function(funAmount) { alert('Something ' + funAmount + ' fun!'); },",
" /** @suppress {checkTypes|globalThis|visibility} */",
" created: function() {}",
"};",
"/** @polymerBehavior @nocollapse */",
"var RadBehavior = {",
" properties: {",
" howRad: Number",
" },",
" /** @suppress {checkTypes|globalThis|visibility} */",
" doSomethingRad: function(radAmount) { alert('Something ' + radAmount + ' rad!'); },",
" /** @suppress {checkTypes|globalThis|visibility} */",
" ready: function() {}",
"};",
"/** @polymerBehavior @nocollapse */",
"var SuperCoolBehaviors = [FunBehavior, RadBehavior];",
"/** @constructor @extends {PolymerElement} @implements {PolymerAInterface}*/",
"var A = function() {};",
"/** @type {number} */",
"A.prototype.howRad;",
"/** @type {boolean} */",
"A.prototype.isFun;",
"/** @type {!Array} */",
"A.prototype.pets;",
"/** @type {string} */",
"A.prototype.name;",
"/**",
" * @param {number} radAmount",
" * @suppress {unusedPrivateMembers}",
" */",
"A.prototype.doSomethingRad = function(radAmount) {",
" alert('Something ' + radAmount + ' rad!');",
"};",
"/**",
" * @param {string} funAmount",
" * @suppress {unusedPrivateMembers}",
" */",
"A.prototype.doSomethingFun = function(funAmount) {",
" alert('Something ' + funAmount + ' fun!');",
"};",
"A = Polymer(/** @lends {A.prototype} */ {",
" is: 'x-element',",
" properties: {",
" pets: {",
" type: Array,",
" notify: true,",
" },",
" name: String,",
" },",
" behaviors: [ SuperCoolBehaviors, FunBehavior ],",
"});"));
}
public void testInvalid1() {
disableTypeCheck();
testWarning("var x = Polymer('blah');", POLYMER_DESCRIPTOR_NOT_VALID);
testWarning("var x = Polymer('foo-bar', {});", POLYMER_DESCRIPTOR_NOT_VALID);
testError("var x = Polymer({},'blah');", POLYMER_UNEXPECTED_PARAMS);
testError("var x = Polymer({});", POLYMER_MISSING_IS);
testErrorEs6("var x = Polymer({is});", POLYMER_MISSING_IS);
testErrorEs6("var x = Polymer({is: 'x-element', shortHand,});",
POLYMER_SHORTHAND_NOT_SUPPORTED);
}
public void testInvalidProperties() {
testError(
LINE_JOINER.join(
"Polymer({",
" is: 'x-element',",
" properties: {",
" isHappy: true,",
" },",
"});"),
POLYMER_INVALID_PROPERTY);
testError(
LINE_JOINER.join(
"Polymer({",
" is: 'x-element',",
" properties: {",
" isHappy: {",
" value: true,",
" },",
" },",
"});"),
POLYMER_INVALID_PROPERTY);
testError(
LINE_JOINER.join(
"var foo = {};",
"foo.bar = {};",
"Polymer({",
" is: 'x-element',",
" properties: {",
" isHappy: {",
" type: foo.Bar,",
" value: true,",
" },",
" },",
"});"),
POLYMER_INVALID_PROPERTY);
}
public void testInvalidBehavior() {
testError(
LINE_JOINER.join(
"(function() {",
" var isNotGloabl = {};",
" Polymer({",
" is: 'x-element',",
" behaviors: [",
" isNotGlobal",
" ],",
" });",
"})();"),
POLYMER_UNQUALIFIED_BEHAVIOR);
testError(
LINE_JOINER.join(
"var foo = {};",
"(function() {",
" Polymer({",
" is: 'x-element',",
" behaviors: [",
" foo.IsNotDefined",
" ],",
" });",
"})();"),
POLYMER_UNQUALIFIED_BEHAVIOR);
testError(
LINE_JOINER.join(
"var foo = {};",
"foo.Bar;",
"(function() {",
" Polymer({",
" is: 'x-element',",
" behaviors: [",
" foo.Bar",
" ],",
" });",
"})();"),
POLYMER_UNQUALIFIED_BEHAVIOR);
testError(
LINE_JOINER.join(
"Polymer({",
" is: 'x-element',",
" behaviors: [",
" DoesNotExist",
" ],",
"});"),
POLYMER_UNQUALIFIED_BEHAVIOR);
}
public void testUnannotatedBehavior() {
testError(
LINE_JOINER.join(
"var FunBehavior = {",
" /** @override */",
" created: function() {}",
"};",
"var A = Polymer({",
" is: 'x-element',",
" behaviors: [ FunBehavior ],",
"});"),
POLYMER_UNANNOTATED_BEHAVIOR);
}
public void testInvalidTypeAssignment() {
test(
LINE_JOINER.join(
"var X = Polymer({",
" is: 'x-element',",
" properties: {",
" isHappy: Boolean,",
" },",
" /** @override */",
" created: function() {",
" this.isHappy = 7;",
" },",
"});"),
LINE_JOINER.join(
"/** @constructor @extends {PolymerElement} @implements {PolymerXInterface} */",
"var X = function() {};",
"/** @type {boolean} */",
"X.prototype.isHappy;",
"X = Polymer(/** @lends {X.prototype} */ {",
" is: 'x-element',",
" properties: {",
" isHappy: Boolean,",
" },",
" /** @override @this {X} */",
" created: function() {",
" this.isHappy = 7;",
" },",
"});"),
null,
TYPE_MISMATCH_WARNING);
}
public void testES6FeaturesInFunctionBody() {
disableTypeCheck();
testEs6(
LINE_JOINER.join(
"var X = Polymer({",
" is: 'x-element',",
" funcWithES6() {",
" var tag = 'tagged';",
" alert(`${tag}Template`);",
" var taggedTemp = `${tag}Template`;",
" var arrFunc = () => 42;",
" var num = arrFunc();",
" var obj = {one: 1, two: 2, three: 3};",
" var {one, two, three} = obj;",
" var arr = [1, 2, 3];",
" var [eins, zwei, drei] = arr;",
" },",
"});"),
LINE_JOINER.join(
"/** @constructor @extends {PolymerElement} @implements {PolymerXInterface} */",
"var X = function() {};",
"X = Polymer(/** @lends {X.prototype} */ {",
" is: 'x-element',",
" /** @this {X} */",
" funcWithES6() {",
" var tag = 'tagged';",
" alert(`${tag}Template`);",
" var taggedTemp = `${tag}Template`;",
" var arrFunc = () => 42;",
" var num = arrFunc();",
" var obj = {one: 1, two: 2, three: 3};",
" var {one, two, three} = obj;",
" var arr = [1, 2, 3];",
" var [eins, zwei, drei] = arr;",
" },",
"});"));
}
}