blob: b76911006cd15476aa6662eb7fcd224d87811863 [file] [log] [blame]
// Copyright (c) 2022, 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.
// TODO(srujzs): Break this test up into multiple tests, delete redundant tests,
// and move them to a shared library.
import 'dart:js_util';
import 'package:expect/expect.dart';
import 'package:js/js.dart';
import 'static_interop_library.dart';
@JS()
external void eval(String code);
@JS('JSClass')
@staticInterop
class StaticJSClass {
external factory StaticJSClass.factory(String foo);
external static String externalStaticMethod();
static String staticMethod() => 'bar';
}
extension StaticJSClassMethods on StaticJSClass {
external String foo;
external String sum(String a, String? b, String c);
external set nonNullableInt(int d);
external int get nonNullableInt;
external set nullableInt(int? d);
external int? get nullableInt;
external int nonNullableIntReturnMethod();
external int? nullableIntReturnMethod(bool returnNull);
external String doSum1Or2(String a, [String? b]);
external String doSumUpTo2([String? a, String? b]);
external String doSum1Or2NonNull(String a, [String b = 'bar']);
external String doSumUpTo2NonNull([String a = 'foo', String b = 'bar']);
external int doIntSum1Or2(int a, [int? b]);
external int doIntSumUpTo2([int? a, int? b]);
external int doIntSum1Or2NonNull(int a, [int b = 2]);
external int doIntSumUpTo2NonNull([int a = 1, int b = 2]);
@JS('nameInJSMethod')
external String nameInDartMethod(String a, String b);
@JS('nameInJSGetter')
external String get nameInDartGetter;
@JS('nameInJSSetter')
external set nameInDartSetter(String v);
external String get nameInJSSetter;
}
void createClassTest() {
eval(r'''
globalThis.JSClass = function(foo) {
this.foo = foo;
this.nonNullableInt = 6;
this.nameInJSGetter = 'foo';
this.nonNullableIntReturnMethod = function() {
return 7;
}
this.nullableIntReturnMethod = function(returnNull) {
if (returnNull) {
return null;
} else {
return 8;
}
}
this.sum = function(a, b, c) {
if (b == null) b = ' ';
return a + b + c;
}
this.doSum1Or2 = function(a, b) {
return a + (b ?? 'bar');
}
this.doSumUpTo2 = function(a, b) {
return (a ?? 'foo') + (b ?? 'bar');
}
this.doSum1Or2NonNull = function(a, b) {
return (a ?? 'foo') + (b ?? 'bar');
}
this.doSumUpTo2NonNull = function(a, b) {
return (a ?? 'foo') + (b ?? 'bar');
}
this.doIntSum1Or2 = function(a, b) {
return a + (b ?? 2);
}
this.doIntSumUpTo2 = function(a, b) {
return (a ?? 1) + (b ?? 2);
}
this.doIntSum1Or2NonNull = function(a, b) {
return a + (b ?? 2);
}
this.doIntSumUpTo2NonNull = function(a, b) {
return (a ?? 1) + (b ?? 2);
}
this.nameInJSMethod = function(a, b) {
return a + b;
}
}
globalThis.JSClass.externalStaticMethod = function() {
return 'foo';
}
globalThis.library = function() {}
globalThis.library.libraryTopLevelGetter = 'foo';
globalThis.library.jsedLibraryTopLevelGetter = 'foo';
globalThis.library.NamespacedClass = function() {
this.member = function() {
return 'foo';
}
}
''');
Expect.equals('foo', StaticJSClass.externalStaticMethod());
Expect.equals('bar', StaticJSClass.staticMethod());
final foo = StaticJSClass.factory('foo');
Expect.equals('foo', foo.foo);
foo.foo = 'bar';
Expect.equals('bar', foo.foo);
Expect.equals('hello world!!', foo.sum('hello', null, 'world!!'));
Expect.equals(null, foo.nullableInt);
foo.nullableInt = 5;
Expect.equals(5, foo.nullableInt);
Expect.equals(6, foo.nonNullableInt);
foo.nonNullableInt = 16;
Expect.equals(16, foo.nonNullableInt);
Expect.equals(7, foo.nonNullableIntReturnMethod());
Expect.equals(8, foo.nullableIntReturnMethod(false));
Expect.equals(null, foo.nullableIntReturnMethod(true));
Expect.equals('foobar', foo.doSum1Or2('foo'));
Expect.equals('foobar', foo.doSum1Or2('foo', 'bar'));
Expect.equals('foobar', foo.doSumUpTo2());
Expect.equals('foobar', foo.doSumUpTo2('foo'));
Expect.equals('foobar', foo.doSumUpTo2('foo', 'bar'));
Expect.equals('foobar', foo.doSum1Or2NonNull('foo'));
Expect.equals('foobar', foo.doSum1Or2NonNull('foo', 'bar'));
Expect.equals('foobar', foo.doSumUpTo2NonNull());
Expect.equals('foobar', foo.doSumUpTo2NonNull('foo'));
Expect.equals('foobar', foo.doSumUpTo2NonNull('foo', 'bar'));
Expect.equals(3, foo.doIntSum1Or2(1));
Expect.equals(3, foo.doIntSum1Or2(1, 2));
Expect.equals(3, foo.doIntSumUpTo2());
Expect.equals(3, foo.doIntSumUpTo2(1));
Expect.equals(3, foo.doIntSumUpTo2(1, 2));
Expect.equals(3, foo.doIntSum1Or2NonNull(1));
Expect.equals(3, foo.doIntSum1Or2NonNull(1, 2));
Expect.equals(3, foo.doIntSumUpTo2NonNull());
Expect.equals(3, foo.doIntSumUpTo2NonNull(1));
Expect.equals(3, foo.doIntSumUpTo2NonNull(1, 2));
Expect.equals('foobar', foo.nameInDartMethod('foo', 'bar'));
Expect.equals('foo', foo.nameInDartGetter);
foo.nameInDartSetter = 'boo';
Expect.equals('boo', foo.nameInJSSetter);
Expect.equals('foo', NamespacedClass().member());
Expect.equals('foo', libraryTopLevelGetter);
Expect.equals('foo', libraryOtherTopLevelGetter);
}
@JS('JSClass.NestedJSClass')
@staticInterop
class NestedJSClass {
external factory NestedJSClass.factory(String foo);
}
extension NestedJSClassMethods on NestedJSClass {
external String foo;
}
void createClassWithNestedJSNameTest() {
eval(r'''
globalThis.JSClass = {};
globalThis.JSClass.NestedJSClass = function(foo) {
this.foo = foo;
};
''');
final foo = NestedJSClass.factory('foo');
Expect.equals(foo.foo, 'foo');
}
@JS('JSParent')
@staticInterop
class StaticJSParent {
external factory StaticJSParent.factory();
}
extension StaticJSParentMethods on StaticJSParent {
external set child(StaticJSChild child);
external String childsFoo();
}
@JS('JSChild')
@staticInterop
class StaticJSChild {
external factory StaticJSChild.factory();
}
extension StaticJSChildMethods on StaticJSChild {
external set foo(String s);
}
void setInteropPropertyTest() {
eval(r'''
globalThis.JSParent = function() {
this.child = null;
this.childsFoo = () => {
return this.child.foo;
}
}
globalThis.JSChild = function() {
this.foo = null;
}
''');
final parent = StaticJSParent.factory();
final child = StaticJSChild.factory();
parent.child = child;
child.foo = 'boo';
Expect.equals('boo', parent.childsFoo());
}
class DartObject {
final int x;
DartObject(this.x);
}
@JS('JSHolder')
@staticInterop
class StaticJSHolder {
external factory StaticJSHolder.factory(DartObject object);
}
extension StaticJSHolderMethods on StaticJSHolder {
external DartObject foo;
}
void setDartObjectPropertyTest() {
eval(r'''
globalThis.JSHolder = function(foo) {
this.foo = foo;
}
''');
final traveler = DartObject(4);
final holder = StaticJSHolder.factory(traveler);
Expect.equals(traveler, holder.foo);
}
@JS()
external String get foo;
@JS()
external String? get blu;
@JS('')
external void set baz(String);
@JS('boo.bar')
external String get bam;
@JS('bar')
external String fooBar(String);
void topLevelMethodsTest() {
eval(r'''
globalThis.foo = 'bar';
globalThis.baz = null;
globalThis.boo = {
'bar': 'jam'
}
globalThis.bar = function(string) {
return string + ' ' + globalThis.baz;
}
''');
Expect.equals(foo, 'bar');
Expect.equals(blu, null);
Expect.equals(bam, 'jam');
baz = 'world!';
Expect.equals(fooBar('hello'), 'hello world!');
}
@JS()
@anonymous
@staticInterop
class AnonymousJSClass {
external factory AnonymousJSClass.factory(
{String? foo,
String bar = 'baz',
String? bleep,
int? goo,
int ooo = 1,
List<double>? saz,
List<double> zoo = const [1.0, 2.0]});
}
extension AnonymousJSClassExtension on AnonymousJSClass {
external String? get foo;
external String? get bar;
external String? get bleep;
external int? get goo;
external int? get ooo;
external List<Object?>? saz;
external List<double>? zoo;
}
void anonymousTest() {
final anonymousJSClass = AnonymousJSClass.factory(
foo: 'boo', bleep: 'bleep', saz: const [1.0, 2.0], goo: 0);
Expect.equals('boo', anonymousJSClass.foo);
Expect.equals(null, anonymousJSClass.bar);
Expect.equals('bleep', anonymousJSClass.bleep);
Expect.equals(0, anonymousJSClass.goo);
Expect.equals(null, anonymousJSClass.ooo);
Expect.listEquals(const [1.0, 2.0], anonymousJSClass.saz!);
Expect.equals(null, anonymousJSClass.zoo);
}
@JS()
@anonymous
@staticInterop
class AnonymousRedirectJSClass {
external factory AnonymousRedirectJSClass._({String Function(String)? foo});
factory AnonymousRedirectJSClass.concrete(String Function(String) foo) =>
AnonymousRedirectJSClass._(foo: allowInterop(foo));
}
extension AnonymousRedirectJSClassExtension on AnonymousRedirectJSClass {
external String foo(String bar);
}
void concreteFactoryConstructorTest() {
final anonymousRedirectJSClass =
AnonymousRedirectJSClass.concrete((String bar) => bar + bar);
Expect.equals('foofoo', anonymousRedirectJSClass.foo('foo'));
}
@JS()
@staticInterop
class JSArray {}
extension JSArrayExtension on JSArray {
external Object? operator [](int index);
external int get length;
external String get deoptKey;
}
@JS()
external JSArray get arrayObject;
void staticInteropBypassConversionTest() {
eval(r'''
globalThis.arrayObject = ['1', '2'];
globalThis.arrayObject.deoptKey = 'foo';
''');
JSArray a = arrayObject;
Expect.isTrue(instanceOfString(a, "Array"));
Expect.equals(2, a.length);
Expect.equals('foo', a.deoptKey);
}
void main() {
createClassTest();
createClassWithNestedJSNameTest();
setInteropPropertyTest();
setDartObjectPropertyTest();
topLevelMethodsTest();
anonymousTest();
concreteFactoryConstructorTest();
staticInteropBypassConversionTest();
}