Index: tests/html/js_typed_interop_test.dart |
diff --git a/tests/html/js_typed_interop_test.dart b/tests/html/js_typed_interop_test.dart |
index c8c02600743cdb4c345d992d4f709891ccfc16f7..2e566360a08de179ec68ca4d6ab7cf08bc469661 100644 |
--- a/tests/html/js_typed_interop_test.dart |
+++ b/tests/html/js_typed_interop_test.dart |
@@ -2,7 +2,8 @@ |
// for details. All rights reserved. Use of this source code is governed by a |
// BSD-style license that can be found in the LICENSE file. |
-library jsArrayTest; |
+@JsName() |
sra1
2015/09/17 19:52:57
It looks odd to me when there is no name in @JsNam
Siggi Cherem (dart-lang)
2015/09/18 20:34:10
I had a similar reaction - @JsName() seems like it
Jacob
2015/10/01 00:47:33
switched to @Js
there will probably be other anno
alexandre.ardhuin
2015/10/01 08:51:05
:( That means the dart libs that proxified Js lib
Jacob
2015/10/02 20:08:15
There are some significant tradeoffs at play that
alexandre.ardhuin
2015/10/02 20:29:56
Thanks for this explanation. It makes sense.
I'll
|
+library js_typed_interop_test; |
import 'dart:html'; |
import 'dart:js'; |
@@ -14,12 +15,31 @@ _injectJs() { |
document.body.append(new ScriptElement() |
..type = 'text/javascript' |
..innerHtml = r""" |
+ var Foo = { |
+ multiplyDefault2: function(a, b) { |
+ if (arguments.length >= 2) return a *b; |
+ return a * 2; |
+ } |
+ }; |
+ |
var foo = { |
x: 3, |
z: 40, // Not specified in typed Dart API so should fail in checked mode. |
multiplyByX: function(arg) { return arg * this.x; }, |
// This function can be torn off without having to bind this. |
- multiplyBy2: function(arg) { return arg * 2; } |
+ multiplyBy2: function(arg) { return arg * 2; }, |
+ callClosureWithArg1: function(closure, arg) { |
+ return closure(arg); |
+ }, |
+ callClosureWithArg2: function(closure, arg1, arg2) { |
+ return closure(arg1, arg2); |
+ }, |
+ callClosureWithArgAndThis: function(closure, arg) { |
+ return closure.apply(this, [arg]); |
+ }, |
+ getBar() { |
+ return bar; |
+ } |
}; |
var foob = { |
@@ -30,7 +50,19 @@ _injectJs() { |
var bar = { |
x: "foo", |
- multiplyByX: true |
+ multiplyByX: true, |
+ getFoo() { |
+ return foo; |
+ } |
+ }; |
+ |
+ function ClassWithConstructor(a, b) { |
+ this.a = a; |
+ this.b = b; |
+ }; |
+ |
+ ClassWithConstructor.prototype = { |
+ getA: function() { return this.a;} |
}; |
var selection = ["a", "b", "c", foo, bar]; |
@@ -38,36 +70,86 @@ _injectJs() { |
"""); |
} |
-abstract class Foo { |
- int get x; |
- set x(int v); |
- num multiplyByX(num y); |
- num multiplyBy2(num y); |
+// TODO(jacobr): depend on package:js instead of defining this annotation here. |
+class JsName { |
+ const JsName([this.name]); |
+ final String name; |
+} |
+ |
+class RegularClass { |
+ factory RegularClass(a) { |
+ return new RegularClass.fooConstructor(a); |
+ } |
+ RegularClass.fooConstructor(this.a); |
+ var a; |
} |
-abstract class Foob extends Foo { |
- final String y; |
+@JsName() |
+class ClassWithConstructor { |
+ external ClassWithConstructor(aParam, bParam); |
+ external getA(); |
+ external get a; |
+ external get b; |
} |
-abstract class Bar { |
- String get x; |
- bool get multiplyByX; |
+@JsName() |
+class Foo extends JavaScriptObject { |
+ external int get x; |
+ external set x(int v); |
+ external num multiplyByX(num y); |
+ external num multiplyBy2(num y); |
+ external callClosureWithArgAndThis(Function closure, arg); |
+ external callClosureWithArg1(Function closure, arg1); |
+ external callClosureWithArg2(Function closure, arg1, arg2); |
+ external Bar getBar(); |
+ external static int multiplyDefault2(int a, [b]); |
+} |
+ |
+@JsName() |
+class ExampleLiteral |
+{ |
+ external factory ExampleLiteral({int x, String y, num z}); |
+ |
+ external int get x; |
+ external String get y; |
+ external num get z; |
+} |
+ |
+@JsName('Foob') |
+class Foob extends Foo { |
+ external String get y; |
+} |
+ |
+@JsName('Bar') |
+class Bar extends JavaScriptObject { |
+ external String get x; |
+ external bool get multiplyByX; |
+ external Foo getFoo(); |
} |
class Baz {} |
-// This class shows the pattern used by APIs such as jQuery that add methods |
-// to Arrays. |
-abstract class Selection implements List { |
- num doubleLength(); |
+// This class shows the pattern used by APIs like jQuery that add methods |
+// to array like objects. |
+abstract class Selection implements JsArray { |
+ external num doubleLength(); |
} |
-Foo get foo => context['foo']; |
-Foob get foob => context['foob']; |
-Bar get bar => context['bar']; |
-Selection get selection => context['selection']; |
+// No @JsName is required for these external methods as the library is |
+// annotated with JsName. |
+external Foo get foo; |
+external Foob get foob; |
+external Bar get bar; |
+external Selection get selection; |
+ |
+addWithDefault(a, [b = 100]) => a + b; |
+ |
+@JsName("JSON.stringify") |
+external String stringify(obj); |
main() { |
+ var x = new RegularClass(42); |
+ print(x.toString()); |
// Call experimental API to register Dart interfaces implemented by |
// JavaScript classes. |
registerJsInterfaces([Foo, Foob, Bar, Selection]); |
@@ -76,6 +158,30 @@ main() { |
useHtmlConfiguration(); |
+ group('object literal', () { |
+ test('simple', () { |
+ var l = new ExampleLiteral(x: 3, y: "foo"); |
+ expect(l.x, equals(3)); |
+ expect(l.y, equals("foo")); |
+ expect(l.z, isNull); |
+ expect(stringify(l), equals('{"x":3,"y":"foo"}')); |
+ l = new ExampleLiteral(z: 100); |
+ expect(l.x, isNull); |
+ expect(l.y, isNull); |
+ expect(l.z, equals(100)); |
+ expect(stringify(l), equals('{"z":100}')); |
+ }); |
+ }); |
+ |
+ group('constructor', () { |
+ test('simple', () { |
+ var o = new ClassWithConstructor("foo", "bar"); |
+ expect(o.a, equals("foo")); |
+ expect(o.b, equals("bar")); |
+ expect(o.getA(), equals("foo")); |
+ }); |
+ }); |
+ |
group('property', () { |
test('get', () { |
expect(foo.x, equals(3)); |
@@ -83,15 +189,15 @@ main() { |
expect(foob.y, equals("why")); |
// Exists in JS but not in API. |
- expect(() => foo.z, throws); |
+ expect(() => (foo as dynamic).zSomeInvalidName, throws); |
expect(bar.multiplyByX, isTrue); |
}); |
test('set', () { |
foo.x = 42; |
expect(foo.x, equals(42)); |
// Property tagged as read only in typed API. |
- expect(() => foob.y = "bla", throws); |
- expect(() => foo.unknownName = 42, throws); |
+ expect(() => (foob as dynamic).y = "bla", throws); |
+ expect(() => (foo as dynamic).unknownName = 42, throws); |
}); |
}); |
@@ -105,22 +211,74 @@ main() { |
test('tearoff', () { |
foo.x = 10; |
- // TODO(jacobr): should we automatically bind "this" for tearoffs of JS |
- // objects? |
- JsFunction multiplyBy2 = foo.multiplyBy2; |
+ Function multiplyBy2 = foo.multiplyBy2; |
expect(multiplyBy2(5), equals(10)); |
+ Function multiplyByX = foo.multiplyByX; |
+ // Tearing off a JS closure doesn't bind this. |
+ // You will need to use the new method tearoff syntax to bind this. |
+ expect(multiplyByX(4), isNaN); |
+ }); |
+ }); |
+ |
+ group('static method', () { |
+ test('call from dart', () { |
+/* expect(Foo.multiplyDefault2(6, 7), equals(42)); |
+ expect(Foo.multiplyDefault2(6), equals(12));*/ |
+ Function tearOffMethod = Foo.multiplyDefault2; |
+ expect(tearOffMethod(6, 6), equals(36)); |
+ expect(tearOffMethod(6), equals(12)); |
+ }); |
+ }); |
+ |
+ group('closure', () { |
+ test('call from js', () { |
+ localClosure(x) => x * 10; |
+ var wrappedLocalClosure = allowInterop(localClosure); |
+ expect( |
+ identical(allowInterop(localClosure), wrappedLocalClosure), isTrue); |
+ expect(foo.callClosureWithArg1(wrappedLocalClosure, 10), equals(100)); |
+ expect(foo.callClosureWithArg1(wrappedLocalClosure, "a"), |
+ equals("aaaaaaaaaa")); |
+ expect(foo.callClosureWithArg1(allowInterop(addWithDefault), 10), |
+ equals(110)); |
+ expect(foo.callClosureWithArg2(allowInterop(addWithDefault), 10, 20), |
+ equals(30)); |
+ addThisXAndArg(Foo that, int arg) { |
+ return foo.x + arg; |
+ } |
+ var wrappedCaptureThisClosure = allowInteropCaptureThis(addThisXAndArg); |
+ foo.x = 20; |
+ expect(foo.callClosureWithArgAndThis(wrappedCaptureThisClosure, 10), |
+ equals(30)); |
+ foo.x = 50; |
+ expect(foo.callClosureWithArgAndThis(wrappedCaptureThisClosure, 10), |
+ equals(60)); |
+ expect( |
+ identical(allowInteropCaptureThis(addThisXAndArg), |
+ wrappedCaptureThisClosure), |
+ isTrue); |
+ }); |
+ }); |
+ |
+ group('chain calls', () { |
+ test("method calls", () { |
+ // In dart2js make sure we still use interceptors when making nested |
+ // calls to objects. |
+ var bar = foo.getBar().getFoo().getBar().getFoo().getBar(); |
+ expect(bar.x, equals("foo")); |
}); |
}); |
group('type check', () { |
test('js interfaces', () { |
- expect(foo is JsObject, isTrue); |
- // Cross-casts are allowed. |
+ // Is checks return true for all JavaScript interfaces. |
expect(foo is Bar, isTrue); |
- expect(selection is JsArray, isTrue); |
+ expect(foo is Foob, isTrue); |
+ |
+ expect(selection is List, isTrue); |
// We do know at runtime whether something is a JsArray or not. |
- expect(foo is JsArray, isFalse); |
+ expect(foo is List, isFalse); |
}); |
test('dart interfaces', () { |
@@ -128,12 +286,4 @@ main() { |
expect(selection is List, isTrue); |
}); |
}); |
- |
- group("registration", () { |
- test('repeated fails', () { |
- // The experimental registerJsInterfaces API has already been called so |
- // it cannot be called a second time. |
- expect(() => registerJsInterfaces([Baz]), throws); |
- }); |
- }); |
} |