/*
* Copyright 2014 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;
/** Tests {@link ChromePass}. */
public class ChromePassTest extends CompilerTestCase {
@Override
protected CompilerPass getProcessor(Compiler compiler) {
return new ChromePass(compiler);
}
@Override
protected int getNumRepetitions() {
// This pass isn't idempotent and only runs once.
return 1;
}
public void testCrDefineCreatesObjectsForQualifiedName() throws Exception {
test(
"cr.define('my.namespace.name', function() {\n" + " return {};\n" + "});",
"var my = my || {};\n"
+ "my.namespace = my.namespace || {};\n"
+ "my.namespace.name = my.namespace.name || {};\n"
+ "cr.define('my.namespace.name', function() {\n"
+ " return {};\n"
+ "});");
}
public void testCrDefineAssignsExportedFunctionByQualifiedName() throws Exception {
test(
"cr.define('namespace', function() {\n"
+ " function internalStaticMethod() {\n"
+ " alert(42);\n"
+ " }\n"
+ " return {\n"
+ " externalStaticMethod: internalStaticMethod\n"
+ " };\n"
+ "});",
"var namespace = namespace || {};\n"
+ "cr.define('namespace', function() {\n"
+ " namespace.externalStaticMethod = function internalStaticMethod() {\n"
+ " alert(42);\n"
+ " }\n"
+ " return {\n"
+ " externalStaticMethod: namespace.externalStaticMethod\n"
+ " };\n"
+ "});");
}
public void testCrDefineCopiesJSDocForExportedFunction() throws Exception {
test(
"cr.define('namespace', function() {\n"
+ " /** I'm function's JSDoc */\n"
+ " function internalStaticMethod() {\n"
+ " alert(42);\n"
+ " }\n"
+ " return {\n"
+ " externalStaticMethod: internalStaticMethod\n"
+ " };\n"
+ "});",
"var namespace = namespace || {};\n"
+ "cr.define('namespace', function() {\n"
+ " /** I'm function's JSDoc */\n"
+ " namespace.externalStaticMethod = function internalStaticMethod() {\n"
+ " alert(42);\n"
+ " }\n"
+ " return {\n"
+ " externalStaticMethod: namespace.externalStaticMethod\n"
+ " };\n"
+ "});");
}
public void testCrDefineReassignsExportedVarByQualifiedName() throws Exception {
test(
"cr.define('namespace', function() {\n"
+ " var internalStaticMethod = function() {\n"
+ " alert(42);\n"
+ " }\n"
+ " return {\n"
+ " externalStaticMethod: internalStaticMethod\n"
+ " };\n"
+ "});",
"var namespace = namespace || {};\n"
+ "cr.define('namespace', function() {\n"
+ " namespace.externalStaticMethod = function() {\n"
+ " alert(42);\n"
+ " }\n"
+ " return {\n"
+ " externalStaticMethod: namespace.externalStaticMethod\n"
+ " };\n"
+ "});");
}
public void testCrDefineExportsVarsWithoutAssignment() throws Exception {
test(
"cr.define('namespace', function() {\n"
+ " var a;\n"
+ " return {\n"
+ " a: a\n"
+ " };\n"
+ "});\n",
"var namespace = namespace || {};\n"
+ "cr.define('namespace', function() {\n"
+ " namespace.a;\n"
+ " return {\n"
+ " a: namespace.a\n"
+ " };\n"
+ "});\n");
}
public void testCrDefineExportsVarsWithoutAssignmentWithJSDoc() throws Exception {
test(
"cr.define('namespace', function() {\n"
+ " /** @type {number} */\n"
+ " var a;\n"
+ " return {\n"
+ " a: a\n"
+ " };\n"
+ "});\n",
"var namespace = namespace || {};\n"
+ "cr.define('namespace', function() {\n"
+ " /** @type {number} */\n"
+ " namespace.a;\n"
+ " return {\n"
+ " a: namespace.a\n"
+ " };\n"
+ "});\n");
}
public void testCrDefineCopiesJSDocForExportedVariable() throws Exception {
test(
"cr.define('namespace', function() {\n"
+ " /** I'm function's JSDoc */\n"
+ " var internalStaticMethod = function() {\n"
+ " alert(42);\n"
+ " }\n"
+ " return {\n"
+ " externalStaticMethod: internalStaticMethod\n"
+ " };\n"
+ "});",
"var namespace = namespace || {};\n"
+ "cr.define('namespace', function() {\n"
+ " /** I'm function's JSDoc */\n"
+ " namespace.externalStaticMethod = function() {\n"
+ " alert(42);\n"
+ " }\n"
+ " return {\n"
+ " externalStaticMethod: namespace.externalStaticMethod\n"
+ " };\n"
+ "});");
}
public void testCrDefineDoesNothingWithNonExportedFunction() throws Exception {
test(
"cr.define('namespace', function() {\n"
+ " function internalStaticMethod() {\n"
+ " alert(42);\n"
+ " }\n"
+ " return {};\n"
+ "});",
"var namespace = namespace || {};\n"
+ "cr.define('namespace', function() {\n"
+ " function internalStaticMethod() {\n"
+ " alert(42);\n"
+ " }\n"
+ " return {};\n"
+ "});");
}
public void testCrDefineDoesNothingWithNonExportedVar() throws Exception {
test(
"cr.define('namespace', function() {\n"
+ " var a;\n"
+ " var b;\n"
+ " return {\n"
+ " a: a\n"
+ " };\n"
+ "});\n",
"var namespace = namespace || {};\n"
+ "cr.define('namespace', function() {\n"
+ " namespace.a;\n"
+ " var b;\n"
+ " return {\n"
+ " a: namespace.a\n"
+ " };\n"
+ "});\n");
}
public void testCrDefineDoesNothingWithExportedNotAName() throws Exception {
test(
"cr.define('namespace', function() {\n"
+ " return {\n"
+ " a: 42\n"
+ " };\n"
+ "});\n",
"var namespace = namespace || {};\n"
+ "cr.define('namespace', function() {\n"
+ " return {\n"
+ " a: 42\n"
+ " };\n"
+ "});\n");
}
public void testCrDefineChangesReferenceToExportedFunction() throws Exception {
test(
"cr.define('namespace', function() {\n"
+ " function internalStaticMethod() {\n"
+ " alert(42);\n"
+ " }\n"
+ " function letsUseIt() {\n"
+ " internalStaticMethod();\n"
+ " }\n"
+ " return {\n"
+ " externalStaticMethod: internalStaticMethod\n"
+ " };\n"
+ "});",
"var namespace = namespace || {};\n"
+ "cr.define('namespace', function() {\n"
+ " namespace.externalStaticMethod = function internalStaticMethod() {\n"
+ " alert(42);\n"
+ " }\n"
+ " function letsUseIt() {\n"
+ " namespace.externalStaticMethod();\n"
+ " }\n"
+ " return {\n"
+ " externalStaticMethod: namespace.externalStaticMethod\n"
+ " };\n"
+ "});");
}
public void testCrDefineWrongNumberOfArguments() throws Exception {
testError(
"cr.define('namespace', function() { return {}; }, 'invalid argument')\n",
ChromePass.CR_DEFINE_WRONG_NUMBER_OF_ARGUMENTS);
}
public void testCrDefineInvalidFirstArgument() throws Exception {
testError(
"cr.define(42, function() { return {}; })\n", ChromePass.CR_DEFINE_INVALID_FIRST_ARGUMENT);
}
public void testCrDefineInvalidSecondArgument() throws Exception {
testError("cr.define('namespace', 42)\n", ChromePass.CR_DEFINE_INVALID_SECOND_ARGUMENT);
}
public void testCrDefineInvalidReturnInFunction() throws Exception {
testError(
"cr.define('namespace', function() {})\n", ChromePass.CR_DEFINE_INVALID_RETURN_IN_FUNCTION);
}
public void testObjectDefinePropertyDefinesUnquotedProperty() throws Exception {
test(
"Object.defineProperty(a.b, 'c', {});",
"Object.defineProperty(a.b, 'c', {});\n" + "/** @type {?} */\n" + "a.b.c;");
}
public void testCrDefinePropertyDefinesUnquotedPropertyWithStringTypeForPropertyKindAttr()
throws Exception {
test(
"cr.defineProperty(a.prototype, 'c', cr.PropertyKind.ATTR);",
"cr.defineProperty(a.prototype, 'c', cr.PropertyKind.ATTR);\n"
+ "/** @type {string} */\n"
+ "a.prototype.c;");
}
public void testCrDefinePropertyDefinesUnquotedPropertyWithBooleanTypeForPropertyKindBoolAttr()
throws Exception {
test(
"cr.defineProperty(a.prototype, 'c', cr.PropertyKind.BOOL_ATTR);",
"cr.defineProperty(a.prototype, 'c', cr.PropertyKind.BOOL_ATTR);\n"
+ "/** @type {boolean} */\n"
+ "a.prototype.c;");
}
public void testCrDefinePropertyDefinesUnquotedPropertyWithAnyTypeForPropertyKindJs()
throws Exception {
test(
"cr.defineProperty(a.prototype, 'c', cr.PropertyKind.JS);",
"cr.defineProperty(a.prototype, 'c', cr.PropertyKind.JS);\n"
+ "/** @type {?} */\n"
+ "a.prototype.c;");
}
public void testCrDefinePropertyCalledWithouthThirdArgumentMeansCrPropertyKindJs()
throws Exception {
test(
"cr.defineProperty(a.prototype, 'c');",
"cr.defineProperty(a.prototype, 'c');\n" + "/** @type {?} */\n" + "a.prototype.c;");
}
public void testCrDefinePropertyDefinesUnquotedPropertyOnPrototypeWhenFunctionIsPassed()
throws Exception {
test(
"cr.defineProperty(a, 'c', cr.PropertyKind.JS);",
"cr.defineProperty(a, 'c', cr.PropertyKind.JS);\n"
+ "/** @type {?} */\n"
+ "a.prototype.c;");
}
public void testCrDefinePropertyInvalidPropertyKind() throws Exception {
testError(
"cr.defineProperty(a.b, 'c', cr.PropertyKind.INEXISTENT_KIND);",
ChromePass.CR_DEFINE_PROPERTY_INVALID_PROPERTY_KIND);
}
public void testCrExportPath() throws Exception {
test(
"cr.exportPath('a.b.c');",
"var a = a || {};\n"
+ "a.b = a.b || {};\n"
+ "a.b.c = a.b.c || {};\n"
+ "cr.exportPath('a.b.c');");
}
public void testCrDefineCreatesEveryObjectOnlyOnce() throws Exception {
test(
"cr.define('a.b.c.d', function() {\n"
+ " return {};\n"
+ "});\n"
+ "cr.define('a.b.e.f', function() {\n"
+ " return {};\n"
+ "});",
"var a = a || {};\n"
+ "a.b = a.b || {};\n"
+ "a.b.c = a.b.c || {};\n"
+ "a.b.c.d = a.b.c.d || {};\n"
+ "cr.define('a.b.c.d', function() {\n"
+ " return {};\n"
+ "});\n"
+ "a.b.e = a.b.e || {};\n"
+ "a.b.e.f = a.b.e.f || {};\n"
+ "cr.define('a.b.e.f', function() {\n"
+ " return {};\n"
+ "});");
}
public void testCrDefineAndCrExportPathCreateEveryObjectOnlyOnce() throws Exception {
test(
"cr.exportPath('a.b.c.d');\n"
+ "cr.define('a.b.e.f', function() {\n"
+ " return {};\n"
+ "});",
"var a = a || {};\n"
+ "a.b = a.b || {};\n"
+ "a.b.c = a.b.c || {};\n"
+ "a.b.c.d = a.b.c.d || {};\n"
+ "cr.exportPath('a.b.c.d');\n"
+ "a.b.e = a.b.e || {};\n"
+ "a.b.e.f = a.b.e.f || {};\n"
+ "cr.define('a.b.e.f', function() {\n"
+ " return {};\n"
+ "});");
}
public void testCrDefineDoesntRedefineCrVar() throws Exception {
test(
"cr.define('cr.ui', function() {\n" + " return {};\n" + "});",
"cr.ui = cr.ui || {};\n" + "cr.define('cr.ui', function() {\n" + " return {};\n" + "});");
}
public void testCrExportPathInvalidNumberOfArguments() throws Exception {
testError("cr.exportPath();", ChromePass.CR_EXPORT_PATH_TOO_FEW_ARGUMENTS);
}
public void testCrMakePublicWorksOnOneMethodDefinedInPrototypeObject() throws Exception {
test(
"/** @constructor */\n"
+ "function Class() {};\n"
+ "\n"
+ "Class.prototype = {\n"
+ " /** @return {number} */\n"
+ " method_: function() { return 42; }\n"
+ "};\n"
+ "\n"
+ "cr.makePublic(Class, ['method']);",
"/** @constructor */\n"
+ "function Class() {};\n"
+ "\n"
+ "Class.prototype = {\n"
+ " /** @return {number} */\n"
+ " method_: function() { return 42; }\n"
+ "};\n"
+ "\n"
+ "/** @return {number} */\n"
+ "Class.method;\n"
+ "\n"
+ "cr.makePublic(Class, ['method']);");
}
public void testCrMakePublicWorksOnTwoMethods() throws Exception {
test(
"/** @constructor */\n"
+ "function Class() {}\n"
+ "\n"
+ "Class.prototype = {\n"
+ " /** @return {number} */\n"
+ " m1_: function() { return 42; },\n"
+ "\n"
+ " /** @return {string} */\n"
+ " m2_: function() { return ''; }\n"
+ "};\n"
+ "\n"
+ "cr.makePublic(Class, ['m1', 'm2']);",
"/** @constructor */\n"
+ "function Class() {}\n"
+ "\n"
+ "Class.prototype = {\n"
+ " /** @return {number} */\n"
+ " m1_: function() { return 42; },\n"
+ "\n"
+ " /** @return {string} */\n"
+ " m2_: function() { return ''; }\n"
+ "}\n"
+ "\n"
+ "/** @return {number} */\n"
+ "Class.m1;\n"
+ "\n"
+ "/** @return {string} */\n"
+ "Class.m2;\n"
+ "\n"
+ "cr.makePublic(Class, ['m1', 'm2']);");
}
public void testCrMakePublicRequiresMethodsToHaveJSDoc() throws Exception {
testError(
"/** @constructor */\n"
+ "function Class() {}\n"
+ "\n"
+ "Class.prototype = {\n"
+ " method_: function() {}\n"
+ "}\n"
+ "\n"
+ "cr.makePublic(Class, ['method']);",
ChromePass.CR_MAKE_PUBLIC_HAS_NO_JSDOC);
}
public void testCrMakePublicDoesNothingWithMethodsNotInAPI() throws Exception {
test(
"/** @constructor */\n"
+ "function Class() {}\n"
+ "\n"
+ "Class.prototype = {\n"
+ " method_: function() {}\n"
+ "}\n"
+ "\n"
+ "cr.makePublic(Class, []);",
"/** @constructor */\n"
+ "function Class() {}\n"
+ "\n"
+ "Class.prototype = {\n"
+ " method_: function() {}\n"
+ "}\n"
+ "\n"
+ "cr.makePublic(Class, []);");
}
public void testCrMakePublicRequiresExportedMethodToBeDeclared() throws Exception {
testError(
"/** @constructor */\n"
+ "function Class() {}\n"
+ "\n"
+ "Class.prototype = {\n"
+ "}\n"
+ "\n"
+ "cr.makePublic(Class, ['method']);",
ChromePass.CR_MAKE_PUBLIC_MISSED_DECLARATION);
}
public void testCrMakePublicWorksOnOneMethodDefinedDirectlyOnPrototype() throws Exception {
test(
"/** @constructor */\n"
+ "function Class() {}\n"
+ "\n"
+ "/** @return {number} */\n"
+ "Class.prototype.method_ = function() {};\n"
+ "\n"
+ "cr.makePublic(Class, ['method']);",
"/** @constructor */\n"
+ "function Class() {}\n"
+ "\n"
+ "/** @return {number} */\n"
+ "Class.prototype.method_ = function() {};\n"
+ "\n"
+ "/** @return {number} */\n"
+ "Class.method;\n"
+ "\n"
+ "cr.makePublic(Class, ['method']);");
}
public void testCrMakePublicWorksOnDummyDeclaration() throws Exception {
test(
"/** @constructor */\n"
+ "function Class() {}\n"
+ "\n"
+ "/** @return {number} */\n"
+ "Class.prototype.method_;\n"
+ "\n"
+ "cr.makePublic(Class, ['method']);",
"/** @constructor */\n"
+ "function Class() {}\n"
+ "\n"
+ "/** @return {number} */\n"
+ "Class.prototype.method_;\n"
+ "\n"
+ "/** @return {number} */\n"
+ "Class.method;\n"
+ "\n"
+ "cr.makePublic(Class, ['method']);");
}
public void testCrMakePublicReportsInvalidSecondArgumentMissing() throws Exception {
testError("cr.makePublic(Class);", ChromePass.CR_MAKE_PUBLIC_INVALID_SECOND_ARGUMENT);
}
public void testCrMakePublicReportsInvalidSecondArgumentNotAnArray() throws Exception {
testError("cr.makePublic(Class, 42);", ChromePass.CR_MAKE_PUBLIC_INVALID_SECOND_ARGUMENT);
}
public void testCrMakePublicReportsInvalidSecondArgumentArrayWithNotAString() throws Exception {
testError("cr.makePublic(Class, [42]);", ChromePass.CR_MAKE_PUBLIC_INVALID_SECOND_ARGUMENT);
}
}