blob: e3fb9e71b40129dcf8482d02321292859f30ed8f [file] [log] [blame]
/*
* Copyright 2015 Google Inc.
*
* 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.gwt.core.interop;
import static jsinterop.annotations.JsPackage.GLOBAL;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.core.client.ScriptInjector;
import com.google.gwt.junit.client.GWTTestCase;
import jsinterop.annotations.JsProperty;
import jsinterop.annotations.JsType;
/**
* Tests JsProperty functionality.
*/
public class JsPropertyTest extends GWTTestCase {
private static final int SET_PARENT_X = 500;
private static final int GET_PARENT_X = 1000;
private static final int GET_X = 100;
private static final int SET_X = 50;
@Override
public String getModuleName() {
return "com.google.gwt.core.Interop";
}
@Override
protected void gwtSetUp() throws Exception {
ScriptInjector.fromString(
"function JsPropertyTest_MyNativeJsType(x) { this.x = x; this.ctorExecuted = true; }\n"
+ "JsPropertyTest_MyNativeJsType.staticX = 33;"
+ "JsPropertyTest_MyNativeJsType.answerToLife = function() { return 42;};"
+ "JsPropertyTest_MyNativeJsType.prototype.sum = "
+ " function sum(bias) { return this.x + bias; };"
+ "function JsPropertyTest_MyNativeJsTypeInterface() {}\n"
+ "JsPropertyTest_MyNativeJsTypeInterface.prototype.sum = "
+ " function sum(bias) { return this.x + bias; };")
.setWindow(ScriptInjector.TOP_WINDOW).inject();
}
@JsType
interface MyJsTypeInterfaceWithProperty {
@JsProperty
int getX();
@JsProperty
void setX(int x);
}
static class MyJavaTypeImplementingMyJsTypeInterfaceWithProperty
implements MyJsTypeInterfaceWithProperty {
private int x;
public int getX() {
return x + GET_X;
}
public void setX(int x) {
this.x = x + SET_X;
}
}
public void testJavaClassImplementingMyJsTypeInterfaceWithProperty() {
MyJavaTypeImplementingMyJsTypeInterfaceWithProperty obj =
new MyJavaTypeImplementingMyJsTypeInterfaceWithProperty();
assertEquals(0 + GET_X, getProperty(obj, "x"));
assertEquals(0 + GET_X, obj.getX());
assertEquals(0, obj.x);
setProperty(obj, "x", 10);
assertEquals(10 + GET_X + SET_X, getProperty(obj, "x"));
assertEquals(10 + GET_X + SET_X, obj.getX());
assertEquals(10 + SET_X, obj.x);
obj.setX(12);
assertEquals(12 + GET_X + SET_X, getProperty(obj, "x"));
assertEquals(12 + GET_X + SET_X, obj.getX());
assertEquals(12 + SET_X, obj.x);
MyJsTypeInterfaceWithProperty intf = new MyJavaTypeImplementingMyJsTypeInterfaceWithProperty();
assertEquals(0 + GET_X, getProperty(intf, "x"));
assertEquals(0 + GET_X, intf.getX());
assertEquals(0, ((MyJavaTypeImplementingMyJsTypeInterfaceWithProperty) intf).x);
setProperty(intf, "x", 10);
assertEquals(10 + GET_X + SET_X, getProperty(intf, "x"));
assertEquals(10 + GET_X + SET_X, intf.getX());
assertEquals(10 + SET_X, ((MyJavaTypeImplementingMyJsTypeInterfaceWithProperty) intf).x);
intf.setX(12);
assertEquals(12 + GET_X + SET_X, getProperty(intf, "x"));
assertEquals(12 + GET_X + SET_X, intf.getX());
assertEquals(12 + SET_X, ((MyJavaTypeImplementingMyJsTypeInterfaceWithProperty) intf).x);
}
@JsType
static class MyConcreteJsType {
private int x;
@JsProperty
public int getY() {
return x + GET_X;
}
@JsProperty
public void setY(int x) {
this.x = x + SET_X;
}
}
public void testConcreteJsType() {
MyConcreteJsType obj = new MyConcreteJsType();
assertEquals(0 + GET_X, getProperty(obj, "y"));
assertEquals(0 + GET_X,obj.getY());
assertEquals(0, obj.x);
setProperty(obj, "y", 10);
assertEquals(10 + GET_X + SET_X, getProperty(obj, "y"));
assertEquals(10 + GET_X + SET_X, obj.getY());
assertEquals(10 + SET_X, obj.x);
obj.setY(12);
assertEquals(12 + GET_X + SET_X, getProperty(obj, "y"));
assertEquals(12 + GET_X + SET_X, obj.getY());
assertEquals(12 + SET_X, obj.x);
}
@JsType(isNative = true, namespace = GLOBAL, name = "JsPropertyTest_MyNativeJsType")
static class MyNativeJsType {
public static int staticX;
public static native int answerToLife();
public boolean ctorExecuted;
public int x;
@JsProperty
public native int getY();
@JsProperty
public native void setY(int x);
public native int sum(int bias);
}
public void testNativeJsType() {
assertEquals(33, MyNativeJsType.staticX);
MyNativeJsType.staticX = 34;
assertEquals(34, MyNativeJsType.staticX);
assertEquals(42, MyNativeJsType.answerToLife());
MyNativeJsType obj = new MyNativeJsType();
assertTrue(obj.ctorExecuted);
assertTrue(isUndefined(obj.x));
obj.x = 72;
assertEquals(72, obj.x);
assertEquals(74, obj.sum(2));
assertTrue(isUndefined(obj.getY()));
obj.setY(91);
assertEquals(91, obj.getY());
}
static class MyNativeJsTypeSubclass extends MyNativeJsType {
MyNativeJsTypeSubclass() {
this.x = 42;
setY(52);
}
@Override
public int sum(int bias) {
return super.sum(bias) + GET_X;
}
}
public void testNativeJsTypeSubclass() {
MyNativeJsTypeSubclass mc = new MyNativeJsTypeSubclass();
assertTrue(mc.ctorExecuted);
assertEquals(143, mc.sum(1));
mc.x = -mc.x;
assertEquals(58, mc.sum(0));
assertEquals(52, mc.getY());
}
static class MyNativeJsTypeSubclassNoOverride extends MyNativeJsType { }
// TODO(rluble): enable when the subclass is setup correctly.
public void _disabled_testNativeJsTypeSubclassNoOverride() {
MyNativeJsTypeSubclassNoOverride myNativeJsType = new MyNativeJsTypeSubclassNoOverride();
myNativeJsType.x = 12;
assertEquals(42, myNativeJsType.sum(30));
}
@JsType(isNative = true, namespace = GLOBAL, name = "JsPropertyTest_MyNativeJsType")
static class MyNativeJsTypeWithConstructor {
public MyNativeJsTypeWithConstructor(int x) { }
public boolean ctorExecuted;
public int x;
}
public void testNativeJsTypeWithConstructor() {
MyNativeJsTypeWithConstructor obj = new MyNativeJsTypeWithConstructor(12);
assertTrue(obj.ctorExecuted);
assertEquals(12, obj.x);
}
static class MyNativeJsTypeWithConstructorSubclass extends MyNativeJsTypeWithConstructor {
public MyNativeJsTypeWithConstructorSubclass(int x) {
super(x);
}
}
public void testNativeJsTypeWithConstructorSubclass() {
MyNativeJsTypeWithConstructorSubclass obj = new MyNativeJsTypeWithConstructorSubclass(12);
assertTrue(obj.ctorExecuted);
assertEquals(12, obj.x);
}
@JsType(isNative = true, namespace = GLOBAL, name = "JsPropertyTest_MyNativeJsTypeInterface")
interface MyNativeJsTypeInterface {
@JsProperty
int getX();
@JsProperty
void setX(int x);
}
static class MyNativeJsTypeInterfaceImplementorNeedingBridge
extends AccidentalImplementer implements MyNativeJsTypeInterface {
}
static abstract class AccidentalImplementer {
private int x;
public int getX() {
return x + GET_X;
}
public void setX(int x) {
this.x = x + SET_X;
}
public int sum(int bias) {
return bias + x;
}
}
public void testJsPropertyBridges() {
MyNativeJsTypeInterface object = new MyNativeJsTypeInterfaceImplementorNeedingBridge();
object.setX(3);
assertEquals(3 + 150, object.getX());
assertEquals(3 + SET_X, ((AccidentalImplementer) object).x);
AccidentalImplementer accidentalImplementer = (AccidentalImplementer) object;
accidentalImplementer.setX(3);
assertEquals(3 + 150, accidentalImplementer.getX());
assertEquals(3 + 150, getProperty(object, "x"));
assertEquals(3 + SET_X, accidentalImplementer.x);
setProperty(object, "x", 4);
assertEquals(4 + 150, accidentalImplementer.getX());
assertEquals(4 + 150, getProperty(object, "x"));
assertEquals(4 + SET_X, accidentalImplementer.x);
assertEquals(3 + 4 + SET_X, accidentalImplementer.sum(3));
}
static class MyNativeJsTypeInterfaceImplNeedingBridgeSubclassed
extends OtherAccidentalImplementer implements MyNativeJsTypeInterface {
}
static abstract class OtherAccidentalImplementer {
private int x;
public int getX() {
return x + GET_PARENT_X;
}
public void setX(int x) {
this.x = x + SET_PARENT_X;
}
public int sum(int bias) {
return bias + x;
}
}
static class MyNativeJsTypeInterfaceImplNeedingBridgeSubclass
extends MyNativeJsTypeInterfaceImplNeedingBridgeSubclassed {
private int y;
public int getX() {
return y + GET_X;
}
public void setX(int y) {
this.y = y + SET_X;
}
public void setParentX(int value) {
super.setX(value);
}
public int getXPlusY() {
return super.getX() + y;
}
}
public void testJsPropertyBridgesSubclass() {
MyNativeJsTypeInterface object = new MyNativeJsTypeInterfaceImplNeedingBridgeSubclass();
object.setX(3);
assertEquals(3 + 150, object.getX());
OtherAccidentalImplementer simple = (OtherAccidentalImplementer) object;
simple.setX(3);
assertEquals(3 + GET_X + SET_X, simple.getX());
assertEquals(3 + GET_X + SET_X, getProperty(object, "x"));
assertEquals(3 + SET_X, ((MyNativeJsTypeInterfaceImplNeedingBridgeSubclass) object).y);
assertEquals(0, ((OtherAccidentalImplementer) object).x);
setProperty(object, "x", 4);
assertEquals(4 + GET_X + SET_X, simple.getX());
assertEquals(4 + GET_X + SET_X, getProperty(object, "x"));
assertEquals(4 + SET_X, ((MyNativeJsTypeInterfaceImplNeedingBridgeSubclass) object).y);
assertEquals(0, ((OtherAccidentalImplementer) object).x);
MyNativeJsTypeInterfaceImplNeedingBridgeSubclass subclass =
(MyNativeJsTypeInterfaceImplNeedingBridgeSubclass) object;
subclass.setParentX(5);
assertEquals(8 + SET_PARENT_X, simple.sum(3));
assertEquals(9 + SET_PARENT_X + GET_PARENT_X + SET_X, subclass.getXPlusY());
assertEquals(4 + SET_X, ((MyNativeJsTypeInterfaceImplNeedingBridgeSubclass) object).y);
assertEquals(5 + SET_PARENT_X, ((OtherAccidentalImplementer) object).x);
}
@JsType(isNative = true)
interface MyJsTypeInterfaceWithProtectedNames {
String var();
@JsProperty
String getNullField(); // Defined in object scope but shouldn't obfuscate
@JsProperty
String getImport();
@JsProperty
void setImport(String str);
}
public void testProtectedNames() {
MyJsTypeInterfaceWithProtectedNames obj = createMyJsInterfaceWithProtectedNames();
assertEquals("var", obj.var());
assertEquals("nullField", obj.getNullField());
assertEquals("import", obj.getImport());
obj.setImport("import2");
assertEquals("import2", obj.getImport());
}
@JsType(isNative = true)
interface JsTypeIsProperty {
@JsProperty
boolean isX();
@JsProperty
void setX(boolean x);
}
public void testJsPropertyIsX() {
JsTypeIsProperty object = (JsTypeIsProperty) JavaScriptObject.createObject();
assertFalse(object.isX());
object.setX(true);
assertTrue(object.isX());
object.setX(false);
assertFalse(object.isX());
}
@JsType(isNative = true)
interface AccidentalOverridePropertyJsTypeInterface {
@JsProperty
int getX();
}
static class AccidentalOverridePropertyBase {
public int getX() {
return 50;
}
}
static class AccidentalOverrideProperty extends AccidentalOverridePropertyBase
implements AccidentalOverridePropertyJsTypeInterface {
}
public void testJsPropertyAccidentalOverrideSuperCall() {
AccidentalOverrideProperty object = new AccidentalOverrideProperty();
assertEquals(50, object.getX());
assertEquals(50, getProperty(object, "x"));
}
@JsType
static class RemovedAccidentalOverridePropertyBase {
@JsProperty
public int getX() {
return 55;
}
}
static class RemovedAccidentalOverrideProperty extends RemovedAccidentalOverridePropertyBase
implements AccidentalOverridePropertyJsTypeInterface {
}
public void testJsPropertyRemovedAccidentalOverrideSuperCall() {
RemovedAccidentalOverrideProperty object = new RemovedAccidentalOverrideProperty();
// If the accidental override here were not removed the access to property x would result in
// an infinite loop
assertEquals(55, object.getX());
assertEquals(55, getProperty(object, "x"));
}
@JsType(isNative = true)
interface JsTypeGetProperty {
@JsProperty
int getX();
@JsProperty
void setX(int x);
}
public void testJsPropertyGetX() {
JsTypeGetProperty object = (JsTypeGetProperty) JavaScriptObject.createObject();
assertTrue(isUndefined(object.getX()));
object.setX(10);
assertEquals(10, object.getX());
object.setX(0);
assertEquals(0, object.getX());
}
private static native MyJsTypeInterfaceWithProtectedNames createMyJsInterfaceWithProtectedNames() /*-{
var a = {};
a["nullField"] = "nullField";
a["import"] = "import";
a["var"] = function() { return "var"; };
return a;
}-*/;
private static native boolean isUndefined(int value) /*-{
return value === undefined;
}-*/;
private static native boolean hasField(Object object, String fieldName) /*-{
return object[fieldName] != undefined;
}-*/;
private static native int getProperty(Object object, String name) /*-{
return object[name];
}-*/;
private static native void setProperty(Object object, String name, int value) /*-{
object[name] = value;
}-*/;
public static void assertJsTypeHasFields(Object obj, String... fields) {
for (String field : fields) {
assertTrue("Field '" + field + "' should be exported", hasField(obj, field));
}
}
public static void assertJsTypeDoesntHaveFields(Object obj, String... fields) {
for (String field : fields) {
assertFalse("Field '" + field + "' should not be exported", hasField(obj, field));
}
}
static class B {
@JsProperty
public String field;
}
private native String getB(B b) /*-{
return b.field;
}-*/;
public void testNotReadExportedFieldNotPruned() {
B b = new B();
b.field = "secret";
assertEquals("secret", getB(b));
}
@JsType
static class ClassWithFieldNotWrittenInJava {
public int fieldNotWrittenInJava = 0;
}
private native String setFieldNotWrittenInJava(Object obj, int value) /*-{
obj.fieldNotWrittenInJava = value;
}-*/;
public void testFieldNotWrittenInJava() {
ClassWithFieldNotWrittenInJava obj = new ClassWithFieldNotWrittenInJava();
assertEquals(0, obj.fieldNotWrittenInJava);
setFieldNotWrittenInJava(obj, 2);
assertEquals(2, obj.fieldNotWrittenInJava);
}
}