blob: 8fd9ddd56b1b145e17396de6958846453e49b26f [file] [log] [blame]
/*
* Copyright 2013 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.junit.client.GWTTestCase;
import java.util.Iterator;
import jsinterop.annotations.JsFunction;
import jsinterop.annotations.JsMethod;
import jsinterop.annotations.JsPackage;
import jsinterop.annotations.JsProperty;
import jsinterop.annotations.JsType;
/**
* Tests JsType functionality.
*/
@SuppressWarnings("cast")
public class JsTypeTest extends GWTTestCase {
@Override
public String getModuleName() {
return "com.google.gwt.core.Interop";
}
public void testConcreteJsTypeAccess() {
ConcreteJsType concreteJsType = new ConcreteJsType();
assertJsTypeHasFields(concreteJsType, "publicMethod", "publicField");
assertJsTypeDoesntHaveFields(concreteJsType, "publicStaticMethod", "privateMethod",
"protectedMethod", "packageMethod", "publicStaticField", "privateField", "protectedField",
"packageField");
assertEquals(10, callIntFunction(concreteJsType, "publicMethod"));
}
public void testAbstractJsTypeAccess() {
AbstractJsType jsType = new AbstractJsType() {
@Override
public int publicMethod() {
return 32;
}
};
assertJsTypeHasFields(jsType, "publicMethod");
assertEquals(32, callIntFunction(jsType, "publicMethod"));
assertEquals(32, jsType.publicMethod());
}
public void testConcreteJsTypeSubclassAccess() {
ConcreteJsTypeSubclass concreteJsTypeSubclass = new ConcreteJsTypeSubclass();
// A subclass of a JsType is not itself a JsType.
assertJsTypeDoesntHaveFields(concreteJsTypeSubclass, "publicSubclassMethod",
"publicSubclassField", "publicStaticSubclassMethod", "privateSubclassMethod",
"protectedSubclassMethod", "packageSubclassMethod", "publicStaticSubclassField",
"privateSubclassField", "protectedSubclassField", "packageSubclassField");
// But if it overrides an exported method then the overriding method will be exported.
assertJsTypeHasFields(concreteJsTypeSubclass, "publicMethod");
assertEquals(20, callIntFunction(concreteJsTypeSubclass, "publicMethod"));
assertEquals(10, concreteJsTypeSubclass.publicSubclassMethod());
}
@JsType
enum JsTypeEnum {
JSVALUE0,
JSVALUE1;
}
public void testJsTypeEnum() {
JsTypeEnum value = JsTypeEnum.JSVALUE1;
assertEquals(value.ordinal(), ((TestAccessor) this).callJsTypeEmumOrdinalMethod(value));
}
// Obscure the call with an alias so that the call is not detected by EnumOrdinalizer.
@JsType(isNative = true)
private interface TestAccessor {
@JsMethod
// Receive the JsType enum as its own type so that it is not seen as an upcast by
// EnumOrdinalizer, but actually dispach to a method that takes Object.
int callJsTypeEmumOrdinalMethod(JsTypeEnum value);
}
@JsMethod
private int callJsTypeEmumOrdinalMethod(Object value) {
return callIntFunction(value, "ordinal");
}
public void testConcreteJsTypeNoTypeTightenField() {
// If we type-tighten, java side will see no calls and think that field could only AImpl1.
ConcreteJsType concreteJsType = new ConcreteJsType();
setTheField(concreteJsType, new ConcreteJsType.AImpl2());
assertEquals(101, concreteJsType.notTypeTightenedField.x());
}
@JsType
interface A {
boolean m(Object o);
}
private static class AImpl implements A {
@Override
public boolean m(Object o) {
return o == null;
}
}
public void testNativeMethodOverrideNoTypeTightenParam() {
AImpl a = new AImpl();
assertTrue(a.m(null));
assertFalse((Boolean) callFunction(a, "m", new Object()));
}
private native void setTheField(ConcreteJsType obj, ConcreteJsType.A value)/*-{
obj.notTypeTightenedField = value;
}-*/;
public void testRevealedOverrideJsType() {
PlainParentType plainParentType = new PlainParentType();
RevealedOverrideSubType revealedOverrideSubType = new RevealedOverrideSubType();
// PlainParentType is neither @JsType or @JsType and so exports no functions.
assertFalse(hasField(plainParentType, "run"));
// RevealedOverrideSubType defines no functions itself, it only inherits them, but it still
// exports run() because it implements the @JsType interface JsTypeRunnable.
assertTrue(hasField(revealedOverrideSubType, "run"));
ConcreteJsTypeJsSubclass subclass = new ConcreteJsTypeJsSubclass();
assertEquals(100, subclass.publicMethodAlsoExposedAsNonJsMethod());
SubclassInterface subclassInterface = subclass;
assertEquals(100, subclassInterface.publicMethodAlsoExposedAsNonJsMethod());
}
@JsType(isNative = true)
interface MyNativeJsTypeInterface {
}
class MyNativeJsTypeInterfaceImpl implements MyNativeJsTypeInterface {
}
public void testCasts() {
Object myClass;
assertNotNull(myClass = (ElementLikeNativeInterface) createMyNativeJsType());
assertNotNull(myClass = (MyNativeJsTypeInterface) createMyNativeJsType());
assertNotNull(myClass = (HTMLElementConcreteNativeJsType) createNativeButton());
try {
assertNotNull(myClass = (HTMLElementConcreteNativeJsType) createMyNativeJsType());
fail();
} catch (ClassCastException cce) {
// Expected.
}
// Test cross cast for native types
Object nativeButton1 = (HTMLElementConcreteNativeJsType) createNativeButton();
Object nativeButton2 = (HTMLElementAnotherConcreteNativeJsType) nativeButton1;
/*
* If the optimizations are turned on, it is possible for the compiler to dead-strip the
* variables since they are not used. Therefore the casts could potentially be stripped.
*/
assertNotNull(myClass);
assertNotNull(nativeButton1);
assertNotNull(nativeButton2);
}
/**
* A test class marked with JsType but isn't referenced from any Java code except instanceof.
*/
@JsType(isNative = true)
public interface MyNativeJsTypeInterfaceAndOnlyInstanceofReference {
}
/**
* A test class marked with JsType but isn't referenced from any Java code except instanceof.
*/
@JsType(isNative = true, namespace = JsPackage.GLOBAL, name = "Error")
public static class AliasToMyNativeJsTypeWithOnlyInstanceofReference {
}
public void testInstanceOf_nativeJsType() {
Object object = createMyNativeJsType();
assertTrue(object instanceof Object);
assertFalse(object instanceof HTMLElementConcreteNativeJsType);
assertFalse(object instanceof HTMLElementAnotherConcreteNativeJsType);
assertFalse(object instanceof HTMLButtonElementConcreteNativeJsType);
assertFalse(object instanceof Iterator);
assertTrue(object instanceof MyNativeJsType);
assertFalse(object instanceof MyNativeJsTypeInterfaceImpl);
assertFalse(object instanceof ElementLikeNativeInterfaceImpl);
assertFalse(object instanceof MyJsInterfaceWithOnlyInstanceofReference);
assertTrue(object instanceof AliasToMyNativeJsTypeWithOnlyInstanceofReference);
assertFalse(object instanceof ConcreteJsType);
assertFalse(object instanceof MyNativeJsTypeInterface[]);
assertFalse(object instanceof MyNativeJsTypeInterfaceImpl[][]);
}
public void testInstanceOf_jsoWithoutProto() {
Object object = JavaScriptObject.createObject();
assertTrue(object instanceof Object);
assertFalse(object instanceof HTMLElementConcreteNativeJsType);
assertFalse(object instanceof HTMLElementAnotherConcreteNativeJsType);
assertFalse(object instanceof HTMLButtonElementConcreteNativeJsType);
assertFalse(object instanceof Iterator);
assertFalse(object instanceof MyNativeJsType);
assertFalse(object instanceof MyNativeJsTypeInterfaceImpl);
assertFalse(object instanceof ElementLikeNativeInterfaceImpl);
assertFalse(object instanceof MyJsInterfaceWithOnlyInstanceofReference);
assertFalse(object instanceof AliasToMyNativeJsTypeWithOnlyInstanceofReference);
assertFalse(object instanceof ConcreteJsType);
assertFalse(object instanceof MyNativeJsTypeInterface[]);
assertFalse(object instanceof MyNativeJsTypeInterfaceImpl[][]);
}
public void testInstanceOf_jsoWithNativeButtonProto() {
Object object = createNativeButton();
assertTrue(object instanceof Object);
assertTrue(object instanceof HTMLElementConcreteNativeJsType);
assertTrue(object instanceof HTMLElementAnotherConcreteNativeJsType);
assertTrue(object instanceof HTMLButtonElementConcreteNativeJsType);
assertFalse(object instanceof Iterator);
assertFalse(object instanceof MyNativeJsType);
assertFalse(object instanceof MyNativeJsTypeInterfaceImpl);
assertFalse(object instanceof ElementLikeNativeInterfaceImpl);
assertFalse(object instanceof MyJsInterfaceWithOnlyInstanceofReference);
assertFalse(object instanceof AliasToMyNativeJsTypeWithOnlyInstanceofReference);
assertFalse(object instanceof ConcreteJsType);
assertFalse(object instanceof MyNativeJsTypeInterface[]);
assertFalse(object instanceof MyNativeJsTypeInterfaceImpl[][]);
}
public void testInstanceOf_implementsJsType() {
// Foils type tightening.
Object object = new ElementLikeNativeInterfaceImpl();
assertTrue(object instanceof Object);
assertFalse(object instanceof HTMLElementConcreteNativeJsType);
assertFalse(object instanceof HTMLElementAnotherConcreteNativeJsType);
assertFalse(object instanceof HTMLButtonElementConcreteNativeJsType);
assertFalse(object instanceof Iterator);
assertFalse(object instanceof MyNativeJsType);
assertFalse(object instanceof MyNativeJsTypeInterfaceImpl);
assertTrue(object instanceof ElementLikeNativeInterfaceImpl);
assertFalse(object instanceof MyJsInterfaceWithOnlyInstanceofReference);
assertFalse(object instanceof AliasToMyNativeJsTypeWithOnlyInstanceofReference);
assertFalse(object instanceof ConcreteJsType);
assertFalse(object instanceof MyNativeJsTypeInterface[]);
assertFalse(object instanceof MyNativeJsTypeInterfaceImpl[][]);
}
public void testInstanceOf_implementsJsTypeWithPrototype() {
// Foils type tightening.
Object object = new MyNativeJsTypeInterfaceImpl();
assertTrue(object instanceof Object);
assertFalse(object instanceof HTMLElementConcreteNativeJsType);
assertFalse(object instanceof HTMLElementAnotherConcreteNativeJsType);
assertFalse(object instanceof HTMLButtonElementConcreteNativeJsType);
assertFalse(object instanceof Iterator);
assertFalse(object instanceof MyNativeJsType);
assertTrue(object instanceof MyNativeJsTypeInterfaceImpl);
assertFalse(object instanceof ElementLikeNativeInterfaceImpl);
assertFalse(object instanceof MyJsInterfaceWithOnlyInstanceofReference);
assertFalse(object instanceof AliasToMyNativeJsTypeWithOnlyInstanceofReference);
assertFalse(object instanceof ConcreteJsType);
assertFalse(object instanceof MyNativeJsTypeInterface[]);
assertFalse(object instanceof MyNativeJsTypeInterfaceImpl[][]);
}
public void testInstanceOf_concreteJsType() {
// Foils type tightening.
Object object = new ConcreteJsType();
assertTrue(object instanceof Object);
assertFalse(object instanceof HTMLElementConcreteNativeJsType);
assertFalse(object instanceof HTMLElementAnotherConcreteNativeJsType);
assertFalse(object instanceof HTMLButtonElementConcreteNativeJsType);
assertFalse(object instanceof Iterator);
assertFalse(object instanceof MyNativeJsType);
assertFalse(object instanceof MyNativeJsTypeInterfaceImpl);
assertFalse(object instanceof ElementLikeNativeInterfaceImpl);
assertFalse(object instanceof MyJsInterfaceWithOnlyInstanceofReference);
assertFalse(object instanceof AliasToMyNativeJsTypeWithOnlyInstanceofReference);
assertTrue(object instanceof ConcreteJsType);
assertFalse(object instanceof MyNativeJsTypeInterface[]);
assertFalse(object instanceof MyNativeJsTypeInterfaceImpl[][]);
}
@JsType(isNative = true, namespace = GLOBAL, name = "Error")
static class MyNativeJsType { }
static class MyNativeJsTypeSubclass extends MyNativeJsType { }
static class MyNativeJsTypeSubclassWithIterator extends MyNativeJsType implements Iterable {
@Override
public Iterator iterator() {
return null;
}
}
public void testInstanceOf_extendsNativeJsType() {
// Foils type tightening.
Object object = new MyNativeJsTypeSubclassWithIterator();
assertTrue(object instanceof Object);
// TODO(rluble): uncomment this when native JsType subclasses are setup correctly.
// assertTrue(object instanceof MyNativeJsType);
assertFalse(object instanceof MyNativeJsTypeSubclass);
assertTrue(object instanceof MyNativeJsTypeSubclassWithIterator);
assertFalse(object instanceof HTMLElementConcreteNativeJsType);
assertFalse(object instanceof HTMLElementAnotherConcreteNativeJsType);
assertFalse(object instanceof HTMLButtonElementConcreteNativeJsType);
assertTrue(object instanceof Iterable);
assertFalse(object instanceof MyNativeJsTypeInterfaceImpl);
assertFalse(object instanceof ElementLikeNativeInterfaceImpl);
assertFalse(object instanceof MyJsInterfaceWithOnlyInstanceofReference);
assertTrue(object instanceof AliasToMyNativeJsTypeWithOnlyInstanceofReference);
assertFalse(object instanceof ConcreteJsType);
assertFalse(object instanceof MyNativeJsTypeInterface[]);
assertFalse(object instanceof MyNativeJsTypeInterfaceImpl[][]);
}
@JsType(isNative = true, namespace = "testfoo.bar")
static class MyNamespacedNativeJsType {
}
public void testInstanceOf_withNameSpace() {
Object obj1 = createMyNamespacedJsInterface();
Object obj2 = createMyWrongNamespacedJsInterface();
assertTrue(obj1 instanceof MyNamespacedNativeJsType);
assertFalse(obj1 instanceof MyNativeJsType);
assertFalse(obj2 instanceof MyNamespacedNativeJsType);
}
public void testEnumeration() {
assertEquals(2, callPublicMethodFromEnumeration(MyEnumWithJsType.TEST1));
assertEquals(3, callPublicMethodFromEnumeration(MyEnumWithJsType.TEST2));
}
public void testEnumJsTypeAccess() {
assertJsTypeHasFields(MyEnumWithJsType.TEST2, "publicMethod", "publicField");
assertJsTypeDoesntHaveFields(MyEnumWithJsType.TEST2, "publicStaticMethod", "privateMethod",
"protectedMethod", "packageMethod", "publicStaticField", "privateField", "protectedField",
"packageField");
}
public void testEnumSubclassEnumeration() {
assertEquals(100, callPublicMethodFromEnumerationSubclass(MyEnumWithSubclassGen.A));
assertEquals(200, callPublicMethodFromEnumerationSubclass(MyEnumWithSubclassGen.B));
assertEquals(1, callPublicMethodFromEnumerationSubclass(MyEnumWithSubclassGen.C));
}
private static native int callIntFunction(Object object, String functionName) /*-{
return object[functionName]();
}-*/;
private static native Object callFunction(Object object, String functionName, Object param) /*-{
return object[functionName](param);
}-*/;
private static native Object createNativeButton() /*-{
return $doc.createElement("button");
}-*/;
private static native Object createMyNativeJsType() /*-{
return new $wnd.Error();
}-*/;
private static native Object createMyNamespacedJsInterface() /*-{
$wnd.testfoo = {};
$wnd.testfoo.bar = {};
$wnd.testfoo.bar.MyNamespacedNativeJsType = function(){};
return new $wnd.testfoo.bar.MyNamespacedNativeJsType();
}-*/;
private static native Object createMyWrongNamespacedJsInterface() /*-{
$wnd["testfoo.bar.MyNamespacedNativeJsType"] = function(){};
return new $wnd['testfoo.bar.MyNamespacedNativeJsType']();
}-*/;
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 callPublicMethodFromEnumeration(MyEnumWithJsType enumeration) /*-{
return enumeration.idxAddOne();
}-*/;
private static native int callPublicMethodFromEnumerationSubclass(
MyEnumWithSubclassGen enumeration) /*-{
return enumeration.foo();
}-*/;
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));
}
}
@JsType
interface SimpleJsTypeFieldInterface {
}
static class SimpleJsTypeFieldClass implements SimpleJsTypeFieldInterface {
}
static class SimpleJsTypeWithField {
@JsProperty
public SimpleJsTypeFieldInterface someField;
}
public void testJsTypeField() {
assertTrue(new SimpleJsTypeFieldClass() != new SimpleJsTypeFieldClass());
SimpleJsTypeWithField holder = new SimpleJsTypeWithField();
fillJsTypeField(holder);
SimpleJsTypeFieldInterface someField = holder.someField;
assertNotNull(someField);
}
private native void fillJsTypeField(SimpleJsTypeWithField jstype) /*-{
jstype.someField = {};
}-*/;
@JsType(isNative = true)
interface InterfaceWithSingleJavaConcrete {
int m();
}
static class JavaConcrete implements InterfaceWithSingleJavaConcrete {
public int m() {
return 5;
}
}
private native Object nativeObjectImplementingM() /*-{
return {m: function() { return 3;} }
}-*/;
public void testSingleJavaConcreteInterface() {
// Create a couple of instances and use the objects in some way to avoid complete pruning
// of JavaConcrete
assertTrue(new JavaConcrete() != new JavaConcrete());
assertSame(5, new JavaConcrete().m());
assertSame(3, ((InterfaceWithSingleJavaConcrete) nativeObjectImplementingM()).m());
}
@JsFunction
interface JsFunctionInterface {
int m();
}
static final class JavaConcreteJsFunction implements JsFunctionInterface {
public int m() {
return 5;
}
}
private native Object nativeJsFunction() /*-{
return function() { return 3;};
}-*/;
public void testSingleJavaConcreteJsFunction() {
// Create a couple of instances and use the objects in some way to avoid complete pruning
// of JavaConcrete
assertTrue(new JavaConcreteJsFunction() != new JavaConcreteJsFunction());
assertSame(5, new JavaConcreteJsFunction().m());
assertSame(3, ((JsFunctionInterface) nativeJsFunction()).m());
}
@JsType
static abstract class SomeAbstractClass {
public abstract SomeAbstractClass m();
}
// Do not rename this class.
@JsType
static abstract class SomeZAbstractSubclass extends SomeAbstractClass {
public abstract SomeZAbstractSubclass m();
}
@JsType
static class SomeConcreteSubclass extends SomeZAbstractSubclass {
public SomeConcreteSubclass m() {
return this;
}
}
public void testNamedBridge() {
// Bridges are sorted by signature in the JDT. Make sure that the bridge method appears second.
assertTrue(
SomeConcreteSubclass.class.getName().compareTo(SomeZAbstractSubclass.class.getName()) < 0);
SomeConcreteSubclass o = new SomeConcreteSubclass();
assertEquals(o, o.m());
}
static class NonPublicJsMethodClass {
@JsMethod private String foo() { return "foo"; }
@JsMethod String bar() { return "bar"; }
}
public void testJsMethodWithDifferentVisiblities() {
NonPublicJsMethodClass instance = new NonPublicJsMethodClass();
assertEquals("foo", instance.foo());
assertEquals("bar", instance.bar());
assertEquals("foo", callFunction(instance, "foo", null));
assertEquals("bar", callFunction(instance, "bar", null));
}
@JsType(isNative = true, namespace = JsPackage.GLOBAL, name = "*")
interface Star {
}
public void testStar() {
Object object = new Object();
assertNotNull((Star) object);
object = Double.valueOf(3.0);
assertNotNull((Star) object);
}
@JsType(isNative = true, namespace = JsPackage.GLOBAL, name = "?")
interface Wildcard {
}
public void testWildcard() {
Object object = new Object();
assertNotNull((Wildcard) object);
object = Double.valueOf(3.0);
assertNotNull((Wildcard) object);
}
static class ClassWithJsMethod {
@JsMethod(name = "name")
public String className() {
return ClassWithJsMethod.class.getName();
}
}
static class ClassWithJsMethodInheritingName extends ClassWithJsMethod {
@JsMethod
public String className() {
return ClassWithJsMethodInheritingName.class.getName();
}
}
private native String callName(Object o) /*-{
return o.name();
}-*/;
public void testInheritName() {
ClassWithJsMethod object = new ClassWithJsMethod();
assertEquals(ClassWithJsMethod.class.getName(), object.className());
assertEquals(ClassWithJsMethod.class.getName(), callName(object));
object = new ClassWithJsMethodInheritingName();
assertEquals(ClassWithJsMethodInheritingName.class.getName(), object.className());
assertEquals(ClassWithJsMethodInheritingName.class.getName(), callName(object));
}
}