Skip to content

Commit

Permalink
Fix mocks on js types
Browse files Browse the repository at this point in the history
Change-Id: I4b07fd0fc5d3771d93f250b5ba51261ea5bef001
Reviewed-on: https://dart-review.googlesource.com/35480
Commit-Queue: Vijay Menon <vsm@google.com>
Reviewed-by: Jacob Richman <jacobr@google.com>
Reviewed-by: Jenny Messerly <jmesserly@google.com>
  • Loading branch information
vsmenon authored and commit-bot@chromium.org committed Jan 20, 2018
1 parent 08cac02 commit bbcb34c
Show file tree
Hide file tree
Showing 3 changed files with 130 additions and 28 deletions.
59 changes: 32 additions & 27 deletions pkg/dev_compiler/tool/input_sdk/private/ddc_runtime/types.dart
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ class Dynamic extends TypeRep {
check_T(object) => object;
}

bool _isJsObject(obj) => JS('bool', '# === #', getReifiedType(obj), jsobject);

class LazyJSType extends TypeRep {
final Function() _rawJSType;
final String _dartName;
Expand All @@ -90,29 +92,36 @@ class LazyJSType extends TypeRep {

toString() => typeName(_rawJSType());

rawJSTypeForCheck() {
_raw() {
var raw = _rawJSType();
if (raw == null) {
_warn('Cannot find native JavaScript type ($_dartName) for type check');
}
return raw;
}

rawJSTypeForCheck() {
var raw = _raw();
if (raw != null) return raw;
_warn('Cannot find native JavaScript type ($_dartName) for type check');
return JS('', '#.Object', global_);
// Treat as anonymous: return true for any JS object.
return jsobject;
}

@JSExportName('is')
bool is_T(obj) {
return JS('bool', '# instanceof #', obj, rawJSTypeForCheck());
bool isRawType(obj) {
var raw = _raw();
if (raw != null) return JS('bool', '# instanceof #', obj, raw);
// Treat as anonymous: return true for any JS object.
return _isJsObject(obj);
}

@JSExportName('is')
bool is_T(obj) => isRawType(obj) || instanceOf(obj, this);

@JSExportName('as')
as_T(obj) =>
JS('bool', '# instanceof #', obj, rawJSTypeForCheck()) || obj == null
? obj
: castError(obj, this, false);
as_T(obj) => obj == null || is_T(obj) ? obj : castError(obj, this, false);

@JSExportName('_check')
check_T(obj) =>
JS('bool', '# instanceof #', obj, rawJSTypeForCheck()) || obj == null
? obj
: castError(obj, this, true);
check_T(obj) => obj == null || is_T(obj) ? obj : castError(obj, this, true);
}

/// An anonymous JS type
Expand All @@ -124,20 +133,13 @@ class AnonymousJSType extends TypeRep {
toString() => _dartName;

@JSExportName('is')
bool is_T(obj) => JS('bool', '# === # || #', getReifiedType(obj), jsobject,
instanceOf(obj, this));
bool is_T(obj) => _isJsObject(obj) || instanceOf(obj, this);

@JSExportName('as')
as_T(obj) =>
JS('bool', '# == null || # === #', obj, getReifiedType(obj), jsobject)
? obj
: cast(obj, this, false);
as_T(obj) => obj == null || _isJsObject(obj) ? obj : cast(obj, this, false);

@JSExportName('_check')
check_T(obj) =>
JS('bool', '# == null || # === #', obj, getReifiedType(obj), jsobject)
? obj
: cast(obj, this, true);
check_T(obj) => obj == null || _isJsObject(obj) ? obj : cast(obj, this, true);
}

void _warn(arg) {
Expand Down Expand Up @@ -963,9 +965,6 @@ bool _isSubtype(t1, t2, isCovariant) => JS('', '''(() => {
// All JS types are subtypes of anonymous JS types.
return $t1 === $jsobject;
}
if ($t2 instanceof $LazyJSType) {
return $_isSubtype($t1, $t2.rawJSTypeForCheck(), isCovariant);
}
// Function subtyping.
Expand Down Expand Up @@ -1036,6 +1035,12 @@ isClassSubType(t1, t2, isCovariant) => JS('', '''(() => {
// I.e., given T1, ..., Tn where at least one Ti != dynamic we disallow:
// - S !<: S<T1, ..., Tn>
// - S<dynamic, ..., dynamic> !<: S<T1, ..., Tn>
// If we have lazy JS types, unwrap them. This will effectively
// reduce to a prototype check below.
if ($t1 instanceof $LazyJSType) $t1 = $t1.rawJSTypeForCheck();
if ($t2 instanceof $LazyJSType) $t2 = $t2.rawJSTypeForCheck();
if ($t1 == $t2) return true;
if ($t1 == $Object) return false;
Expand Down
66 changes: 66 additions & 0 deletions tests/lib_2/html/js_mock_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// Copyright (c) 2018, 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 mock;

import 'package:js/js.dart';
import 'package:expect/minitest.dart';

@JS('Node')
class Node {}

@JS('HTMLDocument')
class Document extends Node {
external Element get body;
}

@JS()
external get foo;

@JS()
external Document get document;

@JS('Element')
class Element extends Node {
external String get tagName;
}

class MockDocument implements Document {
final Element body = new MockElement();
}

class MockElement implements Element {
final tagName = 'MockBody';
}

void main() {
test('js', () {
var f = foo;
expect(f, isNull);

var doc = document;
expect(doc is Document, isTrue);
// Fails in dart2js
// expect(doc is! Element, isTrue);
expect(doc is Node, isTrue);

expect(doc is! MockDocument, isTrue);
expect(doc is! MockElement, isTrue);
});

test('mock', () {
var doc = new MockDocument();
expect(doc is Document, isTrue);
// Fails in dart2js
// expect(doc is! Element, isTrue);
expect(doc is Node, isTrue);

var body = doc.body;
// Fails in dart2js
// expect(body is! Document, isTrue);
expect(body is Element, isTrue);
expect(body is Node, isTrue);
});
}
33 changes: 32 additions & 1 deletion tests/lib_2/html/js_typed_interop_lazy_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,18 @@ abstract class Foo<T> {
T get obj;
}

class Mock1LazyClass implements LazyClass {
noSuchMethod(Invocation i) => i.memberName == #a ? 42 : null;
}

class Mock2LazyClass implements LazyClass {
get a => 42;
}

class Other {
noSuchMethod(Invocation i) {}
}

// This class would cause compile time issues if JS classes are not properly lazy.
class FooImpl extends Foo<LazyClass> {
LazyClass get obj => new LazyClass(100);
Expand Down Expand Up @@ -83,6 +95,11 @@ main() {
});

test('create instance', () {
var anon = new AnonClass(a: 42);
// Until LazyClass is defined, fall back to Anon behavior.
expect(anon is LazyClass, isTrue); //# 01: ok
expect(new Object() is! LazyClass, isTrue);

document.body.append(new ScriptElement()
..type = 'text/javascript'
..innerHtml = r"""
Expand All @@ -98,7 +115,6 @@ baz.LazyClass = function LazyClass(a) {
expect(l is AnonClass, isTrue);
expect((l as AnonClass) == l, isTrue);
expect((l as AnonClass2) == l, isTrue);
var anon = new AnonClass(a: 42);
expect(anon is! LazyClass, isTrue); //# 01: ok
expect(anon is AnonClass, isTrue);
expect(anon is AnonClass2, isTrue);
Expand Down Expand Up @@ -139,5 +155,20 @@ baz.LazyClass = function LazyClass(a) {
expect(() => genericClassDynamic.add(42), throws); //# 01: ok
genericClassDynamic.add(null);
});

test('mocks', () {
var mock1 = new Mock1LazyClass();
expect(mock1 is LazyClass, isTrue);
expect(mock1 as LazyClass, equals(mock1));
expect(mock1.a, equals(42));

var mock2 = new Mock2LazyClass();
expect(mock2 is LazyClass, isTrue);
expect(mock2 as LazyClass, equals(mock2));
expect(mock2.a, equals(42));

Object other = new Other();
expect(other is LazyClass, isFalse);
});
});
}

0 comments on commit bbcb34c

Please sign in to comment.