| 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..fb69069b9fce98c052f8d76824fee54602224420 100644
 | 
| --- a/tests/html/js_typed_interop_test.dart
 | 
| +++ b/tests/html/js_typed_interop_test.dart
 | 
| @@ -2,24 +2,45 @@
 | 
|  // 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;
 | 
| +@Js()
 | 
| +library js_typed_interop_test;
 | 
|  
 | 
|  import 'dart:html';
 | 
| -import 'dart:js';
 | 
|  
 | 
| +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; }
 | 
| +    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: function() {
 | 
| +      return bar;
 | 
| +    }
 | 
|    };
 | 
|  
 | 
|    var foob = {
 | 
| @@ -30,51 +51,145 @@ _injectJs() {
 | 
|  
 | 
|    var bar = {
 | 
|      x: "foo",
 | 
| -    multiplyByX: true
 | 
| +    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];  
 | 
| -  selection.doubleLength = function() { return this.length * 2; };
 | 
| +
 | 
| +  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);
 | 
| +    }
 | 
| +  };
 | 
|  """);
 | 
|  }
 | 
|  
 | 
| -abstract class Foo {
 | 
| -  int get x;
 | 
| -  set x(int v);
 | 
| -  num multiplyByX(num y);
 | 
| -  num multiplyBy2(num y);
 | 
| +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;
 | 
|  }
 | 
|  
 | 
| -abstract class Foob extends Foo {
 | 
| -  final String y;
 | 
| +@Js()
 | 
| +class Foo {
 | 
| +  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, [int b]);
 | 
| +
 | 
|  }
 | 
|  
 | 
| -abstract class Bar {
 | 
| -  String get x;
 | 
| -  bool get multiplyByX;
 | 
| +@Js()
 | 
| +class ExampleLiteral {
 | 
| +  external factory ExampleLiteral({int x, String y, num z});
 | 
| +
 | 
| +  external int get x;
 | 
| +  external String get y;
 | 
| +  external num get z;
 | 
|  }
 | 
|  
 | 
| -class Baz {}
 | 
| +@Js('Foob')
 | 
| +class Foob extends Foo {
 | 
| +  external String get y;
 | 
| +}
 | 
|  
 | 
| -// This class shows the pattern used by APIs such as jQuery that add methods
 | 
| -// to Arrays.
 | 
| -abstract class Selection implements List {
 | 
| -  num doubleLength();
 | 
| +@Js('Bar')
 | 
| +class Bar
 | 
| + {
 | 
| +  external String get x;
 | 
| +  external bool get multiplyByX;
 | 
| +  external Foo getFoo();
 | 
|  }
 | 
|  
 | 
| -Foo get foo => context['foo'];
 | 
| -Foob get foob => context['foob'];
 | 
| -Bar get bar => context['bar'];
 | 
| -Selection get selection => context['selection'];
 | 
| +// 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;
 | 
|  
 | 
| -main() {
 | 
| -  // Call experimental API to register Dart interfaces implemented by
 | 
| -  // JavaScript classes.
 | 
| -  registerJsInterfaces([Foo, Foob, Bar, 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);
 | 
| +
 | 
| +main() {
 | 
|    _injectJs();
 | 
|  
 | 
| -  useHtmlConfiguration();
 | 
| +  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}'));
 | 
| +    });
 | 
| +  });
 | 
| +
 | 
| +  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', () {
 | 
| @@ -83,15 +198,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 +220,96 @@ 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);
 | 
| +    });
 | 
| +
 | 
| +    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', () {
 | 
| -      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 +317,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);
 | 
| -    });
 | 
| -  });
 | 
|  }
 | 
| 
 |