| /* |
| * 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); |
| } |
| } |