| Index: test/codegen/lib/html/js_typed_interop_test.dart
|
| diff --git a/test/codegen/lib/html/js_typed_interop_test.dart b/test/codegen/lib/html/js_typed_interop_test.dart
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..431d533037e10d83a3493fd049c0da557b007927
|
| --- /dev/null
|
| +++ b/test/codegen/lib/html/js_typed_interop_test.dart
|
| @@ -0,0 +1,413 @@
|
| +// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
|
| +// 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.
|
| +
|
| +@JS()
|
| +library js_typed_interop_test;
|
| +
|
| +import 'dart:html';
|
| +
|
| +import 'package:js/js.dart';
|
| +import 'package:unittest/unittest.dart';
|
| +import 'package:unittest/html_config.dart';
|
| +import 'package:unittest/html_individual_config.dart';
|
| +
|
| +_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; },
|
| + multiplyDefault2Function: function(a, b) {
|
| + if (arguments.length >= 2) return a * b;
|
| + return a * 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: function() {
|
| + return bar;
|
| + }
|
| + };
|
| +
|
| + var foob = {
|
| + x: 8,
|
| + y: "why",
|
| + multiplyByX: function(arg) { return arg * this.x; }
|
| + };
|
| +
|
| + var bar = {
|
| + x: "foo",
|
| + multiplyByX: true,
|
| + getFoo: function() {
|
| + 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];
|
| +
|
| + function returnNumArgs() { return arguments.length; };
|
| + function returnLastArg() { return arguments[arguments.length-1]; };
|
| +
|
| + function confuse(obj) { return obj; }
|
| +
|
| + function StringWrapper(str) {
|
| + this.str = str;
|
| + }
|
| + StringWrapper.prototype = {
|
| + charCodeAt: function(index) {
|
| + return this.str.charCodeAt(index);
|
| + }
|
| + };
|
| + function getCanvasContext() {
|
| + return document.createElement('canvas').getContext('2d');
|
| + }
|
| + window.windowProperty = 42;
|
| + document.documentProperty = 45;
|
| +""");
|
| +}
|
| +
|
| +class RegularClass {
|
| + factory RegularClass(a) {
|
| + return new RegularClass.fooConstructor(a);
|
| + }
|
| + RegularClass.fooConstructor(this.a);
|
| + var a;
|
| +}
|
| +
|
| +@JS()
|
| +class ClassWithConstructor {
|
| + external ClassWithConstructor(aParam, bParam);
|
| + external getA();
|
| + external get a;
|
| + external get b;
|
| +}
|
| +
|
| +typedef num MultiplyWithDefault(num a, [num b]);
|
| +
|
| +@JS()
|
| +class Foo {
|
| + external int get x;
|
| + external set x(int v);
|
| + external num multiplyByX(num y);
|
| + external num multiplyBy2(num y);
|
| + external MultiplyWithDefault get multiplyDefault2Function;
|
| +
|
| + external callClosureWithArgAndThis(Function closure, arg);
|
| + external callClosureWithArg1(Function closure, arg1);
|
| + external callClosureWithArg2(Function closure, arg1, arg2);
|
| + external Bar getBar();
|
| +
|
| + external static num multiplyDefault2(num a, [num b]);
|
| +}
|
| +
|
| +@anonymous
|
| +@JS()
|
| +class ExampleLiteral {
|
| + external factory ExampleLiteral({int x, String y, num z});
|
| +
|
| + external int get x;
|
| + external String get y;
|
| + external num get z;
|
| +}
|
| +
|
| +@anonymous
|
| +@JS()
|
| +class EmptyLiteral {
|
| + external factory EmptyLiteral();
|
| +}
|
| +
|
| +@JS('Foob')
|
| +class Foob extends Foo {
|
| + external String get y;
|
| +}
|
| +
|
| +@JS('Bar')
|
| +class Bar {
|
| + external String get x;
|
| + external bool get multiplyByX;
|
| + external Foo getFoo();
|
| +}
|
| +
|
| +// No @JS is required for these external methods as the library is
|
| +// annotated with Js.
|
| +external Foo get foo;
|
| +external Foob get foob;
|
| +external Bar get bar;
|
| +external Selection get selection;
|
| +
|
| +addWithDefault(a, [b = 100]) => a + b;
|
| +
|
| +external Function get returnNumArgs;
|
| +external Function get returnLastArg;
|
| +
|
| +const STRINGIFY_LOCATION = "JSON.stringify";
|
| +@JS(STRINGIFY_LOCATION)
|
| +external String stringify(obj);
|
| +
|
| +@JS()
|
| +class StringWrapper {
|
| + external StringWrapper(String str);
|
| + external int charCodeAt(int i);
|
| +}
|
| +
|
| +// Defeat JS type inference by calling through JavaScript interop.
|
| +@JS()
|
| +external confuse(obj);
|
| +
|
| +@JS()
|
| +external CanvasRenderingContext2D getCanvasContext();
|
| +
|
| +@JS('window.window.document.documentProperty')
|
| +external num get propertyOnDocument;
|
| +
|
| +@JS('window.self.window.window.windowProperty')
|
| +external num get propertyOnWindow;
|
| +
|
| +main() {
|
| + _injectJs();
|
| +
|
| + useHtmlIndividualConfiguration();
|
| +
|
| + 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}'));
|
| + });
|
| +
|
| + test('empty', () {
|
| + var l = new EmptyLiteral();
|
| + expect(stringify(l), equals('{}'));
|
| + });
|
| + });
|
| +
|
| + 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));
|
| + expect(foob.x, equals(8));
|
| + expect(foob.y, equals("why"));
|
| +
|
| + // Exists in JS but not in API.
|
| + 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 as dynamic).y = "bla", throws);
|
| + expect(() => (foo as dynamic).unknownName = 42, throws);
|
| + });
|
| + });
|
| +
|
| + group('method', () {
|
| + test('call', () {
|
| + foo.x = 100;
|
| + expect(foo.multiplyByX(4), equals(400));
|
| + foob.x = 10;
|
| + expect(foob.multiplyByX(4), equals(40));
|
| + });
|
| +
|
| + test('tearoff', () {
|
| + foo.x = 10;
|
| + 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);
|
| +
|
| + MultiplyWithDefault multiplyWithDefault = foo.multiplyDefault2Function;
|
| + expect(multiplyWithDefault(6, 6), equals(36));
|
| + expect(multiplyWithDefault(6), equals(12));
|
| + Function untypedFunction = foo.multiplyDefault2Function;
|
| + // Calling with extra bogus arguments has no impact for JavaScript
|
| + // methods.
|
| + expect(untypedFunction(6, 6, "ignored", "ignored"), equals(36));
|
| + expect(untypedFunction(6, 6, "ignored", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
| + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), equals(36));
|
| + // Calling a JavaScript method with too few arguments is also fine and
|
| + // defaults to JavaScript behavior of setting all unspecified arguments
|
| + // to undefined resulting in multiplying undefined by 2 == NAN.
|
| + expect(untypedFunction(), isNaN);
|
| +
|
| + });
|
| + });
|
| +
|
| + group('static_method_call', () {
|
| + test('call directly from dart', () {
|
| + expect(Foo.multiplyDefault2(6, 7), equals(42));
|
| + expect(Foo.multiplyDefault2(6), equals(12));
|
| + });
|
| + });
|
| +
|
| + // Note: these extra groups are added to be able to mark each test
|
| + // individually in status files. This should be split as separate test files.
|
| + group('static_method_tearoff_1', () {
|
| + test('call tearoff from dart', () {
|
| + MultiplyWithDefault tearOffMethod = Foo.multiplyDefault2;
|
| + expect(tearOffMethod(6, 6), equals(36));
|
| + });
|
| + });
|
| +
|
| + group('static_method_tearoff_2', () {
|
| + test('call tearoff from dart', () {
|
| + MultiplyWithDefault tearOffMethod = Foo.multiplyDefault2;
|
| + 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);
|
| +
|
| + ExampleLiteral addXValues(that, ExampleLiteral arg) {
|
| + return new ExampleLiteral(x: that.x + arg.x);
|
| + }
|
| +
|
| + // Check to make sure returning a JavaScript value from a Dart closure
|
| + // works as expected.
|
| + expect(
|
| + foo
|
| + .callClosureWithArg2(allowInterop(addXValues),
|
| + new ExampleLiteral(x: 20), new ExampleLiteral(x: 10))
|
| + .x,
|
| + equals(30));
|
| +
|
| + foo.x = 50;
|
| + expect(
|
| + foo
|
| + .callClosureWithArgAndThis(allowInteropCaptureThis(addXValues),
|
| + new ExampleLiteral(x: 10))
|
| + .x,
|
| + equals(60));
|
| + });
|
| +
|
| + test('call from dart', () {
|
| + var returnNumArgsFn = returnNumArgs;
|
| + var returnLastArgFn = returnLastArg;
|
| + expect(returnNumArgsFn(), equals(0));
|
| + expect(returnNumArgsFn("a", "b", "c"), equals(3));
|
| + expect(returnNumArgsFn("a", "b", "c", null, null), equals(5));
|
| + expect(returnNumArgsFn(1, 2, 3, 4, 5, 6, null), equals(7));
|
| + expect(returnNumArgsFn(1, 2, 3, 4, 5, 6, 7, 8), equals(8));
|
| + expect(returnLastArgFn(1, 2, "foo"), equals("foo"));
|
| + expect(returnLastArgFn(1, 2, 3, 4, 5, 6, "foo"), equals("foo"));
|
| + });
|
| + });
|
| +
|
| + 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('avoid leaks on dart:core', () {
|
| + test('String', () {
|
| + var s = confuse('Hello');
|
| + var stringWrapper = confuse(new StringWrapper('Hello'));
|
| + // Make sure we don't allow calling JavaScript methods on String.
|
| + expect(() => s.charCodeAt(0), throws);
|
| + expect(stringWrapper.charCodeAt(0), equals(72));
|
| + });
|
| + });
|
| +
|
| + group('type check', () {
|
| + test('js interfaces', () {
|
| + // Is checks return true for all JavaScript interfaces.
|
| + expect(foo is Bar, 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 List, isFalse);
|
| + });
|
| +
|
| + test('dart interfaces', () {
|
| + expect(foo is Function, isFalse);
|
| + expect(selection is List, isTrue);
|
| + });
|
| + });
|
| + group('html', () {
|
| + test('return html type', () {
|
| + expect(getCanvasContext() is CanvasRenderingContext2D, isTrue);
|
| + });
|
| + test('js path contains html types', () {
|
| + expect(propertyOnWindow, equals(42));
|
| + expect(propertyOnDocument, equals(45));
|
| + });
|
| + });
|
| +}
|
|
|