blob: 996525c8352d04823d7a9d35d7b7a08edb3b2b66 [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.client.interop;
import com.google.gwt.core.client.ScriptInjector;
import com.google.gwt.junit.DoNotRunWith;
import com.google.gwt.junit.Platform;
import com.google.gwt.junit.client.GWTTestCase;
import java.util.Iterator;
/**
* Tests JsType and JsExport.
*/
@DoNotRunWith({Platform.Devel, Platform.HtmlUnitBug})
public class JsTypeTest extends GWTTestCase {
@Override
protected void gwtSetUp() throws Exception {
ScriptInjector.fromString("function MyJsInterface() {}\n" +
"MyJsInterface.prototype.sum = function sum(bias) { return this.x + this.y + bias; }\n" +
"MyJsInterface.prototype.go = function(cb) { cb('Hello'); }")
.setWindow(ScriptInjector.TOP_WINDOW).inject();
ScriptInjector.fromString("function MyJsInterface() {}\n" +
"MyJsInterface.prototype.sum = function sum(bias) { return this.x + this.y + bias; }\n")
.inject();
patchPrototype(MyClassImpl.class);
}
/**
* Workaround for the fact that the script is injected after defineClass() has been called.
*/
private native void patchPrototype(Class<MyClassImpl> myClass) /*-{
@java.lang.Class::getPrototypeForClass(Ljava/lang/Class;)(myClass).prototype = $wnd.MyClass;
}-*/;
@Override
public String getModuleName() {
return "com.google.gwt.core.Core";
}
private native int callShouldBeAvailable(Object ref) /*-{
return ref.shouldBeAvailable();
}-*/;
private native String getEnumNameViaJs(NestedTest.NestedEnum ref) /*-{
return ref.name2();
}-*/;
private native int getFooBAR() /*-{
return $wnd && $wnd.foo && $wnd.foo.NamespaceTester && $wnd.foo.NamespaceTester.BAR || 0;
}-*/;
private native int getWOO() /*-{
return $wnd && $wnd.woo && $wnd.woo.PackageNamespaceTester
&& $wnd.woo.PackageNamespaceTester.WOO || 0;
}-*/;
private native Object getStaticInitializerStaticField() /*-{
return $wnd && $wnd.woo && $wnd.woo.StaticInitializerStaticField
&& $wnd.woo.StaticInitializerStaticField.STATIC;
}-*/;
private native Object getStaticInitializerStaticMethod() /*-{
return $wnd && $wnd.woo && $wnd.woo.StaticInitializerStaticMethod
&& $wnd.woo.StaticInitializerStaticMethod.getInstance();
}-*/;
private native Object getStaticInitializerVirtualMethod() /*-{
if($wnd && $wnd.woo && $wnd.woo.StaticInitializerVirtualMethod) {
var obj = new $wnd.woo.StaticInitializerVirtualMethod();
return obj.getInstance();
}
return null;
}-*/;
public void testClassNamespace() {
assertEquals(NamespaceTester.BAR, getFooBAR());
}
public void testPackageNamespace() {
assertEquals(PackageNamespaceTester.WOO, getWOO());
}
public void testStaticInitializerStaticField() {
assertNotNull(getStaticInitializerStaticField());
}
public void testStaticInitializerStaticMethod() {
assertNotNull(getStaticInitializerStaticMethod());
}
public void testStaticInitializerVirtualMethod() {
assertNotNull(getStaticInitializerVirtualMethod());
}
public void testVirtualUpRefs() {
ListImpl l2 = new ListImpl();
FooImpl f2 = new FooImpl(); // both inherit .add(), but this one shouldn't be exported
// prevent type tightening, force c to be Collection holding l2
Collection c = localMyClass() != null ? l2 : f2;
// should invoke obfuscated method
c.add("Hello");
assertEquals("HelloListImpl", l2.x);
// force ListImpl to be assigned to collection without tightening
Collection c2 = localMyClass() != null ? f2 : l2;
c2.add("World");
assertEquals("WorldCollectionBaseFooImpl", f2.x);
// should call not through bridge
f2.add("One");
assertEquals("OneCollectionBaseFooImpl", f2.x);
// call through bridge
l2.add("Two");
assertEquals("TwoListImpl", l2.x);
}
public void testJsTypeNestedEnum() {
assertEquals(NestedTest.NestedEnum.FOO.name(),
getEnumNameViaJs(NestedTest.NestedEnum.FOO));
}
public void testJsTypeCallableFromJs() {
MyJsTypeClass jsType = new MyJsTypeClass();
assertEquals(1138, callShouldBeAvailable(jsType));
}
public void testSubClassWithSuperCalls() {
MyClassImpl mc = new MyClassImpl();
assertEquals(150, mc.sum(1));
}
public void testJsProperties() {
MyClassImpl mc = new MyClassImpl();
// test both fluent and non-fluent accessors
mc.x(-mc.x()).setY(0);
assertEquals(58, mc.sum(0));
// TODO(cromwellian): Add test cases for property overriding of @JsProperty methods in java object
}
public void testJsExports() {
// Test exported method can be called from JS in host page
ScriptInjector.fromString("exportedFromJava();").setWindow(ScriptInjector.TOP_WINDOW).inject();
assertTrue(MyClassImpl2.calledFromJsHostPageWindow);
// Test exported method can be called from JS in module window
ScriptInjector.fromString("exportedFromJava2();").inject();
assertTrue(MyClassImpl2.calledFromJsModuleWindow);
// Test exported constructor called from JS in module window
ScriptInjector.fromString("new $wnd.MyClassImpl3();").inject();
assertTrue(MyClassImpl3.calledFromJsModuleWindow);
}
public void testCasts() {
MyJsInterface doc1 = null;
MyJsInterface.LocalMyClass doc2 = null;
MyJsInterface.ButtonLikeJso doc3 = null;
try {
assertNotNull(doc1 = (MyJsInterface) mainMyClass());
assertNotNull(doc2 = (MyJsInterface.LocalMyClass) localMyClass());
assertNotNull(doc2 = (MyJsInterface.LocalMyClass) mainMyClass());
} catch (ClassCastException cce) {
fail();
}
try {
assertNotNull(doc1 = (MyJsInterface) localMyClass());
fail();
} catch (ClassCastException cce) {
}
try {
assertNotNull(doc3 = (MyJsInterface.ButtonLikeJso) mainMyClass());
assertNotNull(doc3 = (MyJsInterface.ButtonLikeJso) localMyClass());
} catch (ClassCastException cce) {
fail();
}
/*
* If full optimizations are turned on, it is possible for the compiler to dead-strip the
* doc1/doc2/doc3 variables since they are not used, therefore the casts could potentially
* be stripped
*/
assertNotNull(doc1);
assertNotNull(doc2);
assertNotNull(doc3);
}
public void testInstanceOf() {
// check that instanceof works between frames
assertTrue(mainMyClass() instanceof MyJsInterface);
assertTrue(localMyClass() instanceof MyJsInterface.LocalMyClass);
assertTrue(mainMyClass() instanceof MyJsInterface.LocalMyClass);
// check that JsTypes without prototypes can cross-cast like JSOs
assertTrue(mainMyClass() instanceof MyJsInterface.ButtonLikeJso);
assertTrue(localMyClass() instanceof MyJsInterface.ButtonLikeJso);
// check that it doesn't work if $wnd is forced
assertFalse(localMyClass() instanceof MyJsInterface);
}
static native boolean isIE8() /*-{
return $wnd.navigator.userAgent.toLowerCase().indexOf('msie') != -1 && $doc.documentMode == 8;
}-*/;
static native boolean isFirefox40OrEarlier() /*-{
return @com.google.gwt.dom.client.DOMImplMozilla::isGecko2OrBefore()();
}-*/;
public void testInstanceOfNative() {
if (isIE8() || isFirefox40OrEarlier()) {
return;
}
Object obj = makeNativeButton();
assertTrue(obj instanceof Object);
assertTrue(obj instanceof HTMLButtonElement);
assertTrue(obj instanceof HTMLElement);
assertFalse(obj instanceof Iterator);
assertTrue(obj instanceof HTMLAnotherElement);
assertFalse(obj instanceof MyJsInterface.LocalMyClass);
// to foil type tightening
obj = alwaysTrue() ? new MyButtonWithIterator() : null;
assertTrue(obj instanceof Object);
assertTrue(obj instanceof HTMLButtonElement);
assertTrue(obj instanceof HTMLElement);
assertTrue(obj instanceof Iterable);
/*
* TODO: this works, but only because Object can't be type-tightened to HTMLElement. But it will
* evaluate statically to false for HTMLElement instanceof HTMLAnotherElement. Depending on
* what the spec decides, fix JTypeOracle so that canTheoreticallyCast returns the appropriate
* result, as well as add a test here that can be type-tightened.
*/
assertTrue(obj instanceof HTMLAnotherElement);
assertFalse(obj instanceof MyJsInterface.LocalMyClass);
}
private native boolean alwaysTrue() /*-{
return !!$wnd;
}-*/;
private native Object makeNativeButton() /*-{
return $doc.createElement("button");
}-*/;
private native Object localMyClass() /*-{
return new MyJsInterface();
}-*/;
private native Object mainMyClass() /*-{
return new $wnd.MyJsInterface();
}-*/;
/*
* TODO (cromwellian): Add test case for following:
* interface ANonJsType {
* void methodA()
* }
* interface AJsType {
* void methodA()
* }
* class MyJsInterface implements ANonJsType, AJsType {
* void methodA() { ... }
* }
* verify methodA() is dispatched properly from both interfaces.
* Add similar test case with methodA implemented in parent JS class.
*/
}