blob: b10119985b3fdd3fc4cf96aa9f4b9d6b00e79728 [file] [log] [blame]
// Copyright (c) 2019, 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.
// Requirements=nnbd
@JS()
library js_interop_test;
import 'dart:_foreign_helper' as helper show JS;
import 'dart:_interceptors' as interceptors;
import 'dart:_runtime' as dart;
import 'dart:js' show context;
import 'package:expect/expect.dart';
import 'package:js/js.dart';
@JS()
class Console {
@JS()
external void log(arg);
}
@JS('console')
external Console get console;
@JS('console.log')
external Function get _log;
@JS('console.log')
external void log(String s);
String dartStaticMethod() => 'hello';
@JS('jsStaticVariable')
external Function? get _jsStaticVariable;
@JS('jsStaticVariable')
external set _jsStaticVariable(Function? f);
@JS('jsStaticVariable')
external void jsStaticVariable(String s);
@JS('jsStaticFunction')
external Function get _jsStaticFunction;
@JS('jsStaticFunction')
external set _jsStaticFunction(Function f);
@JS('jsStaticFunction')
external void jsStaticFunction(String Function() f);
@JS()
class SomeClass {
external factory SomeClass(String Function() f);
external set jsFunctionFieldSetter(String Function() f);
external void Function(String Function() f) get jsFunctionFieldGetter;
external String jsInstanceMethod(String Function() f);
external NestedJs get jsNonFunctionField;
}
@JS()
@anonymous
class NestedJs {
external factory NestedJs({required String Function() constructorArg});
external String get stringField;
}
@JS('someClass')
external set _someClass(dynamic s);
@JS('someClass')
external SomeClass get someClass;
void main() {
Function(String) jsFunc = helper.JS('', '(x) => {}');
Expect.equals(dart.assertInterop(jsFunc), jsFunc);
Expect.equals(dart.assertInterop(_log), _log);
Expect.equals(dart.assertInterop(console.log), console.log);
Expect.throws(() => dart.assertInterop(dartStaticMethod));
Expect.isNull(_jsStaticVariable);
_jsStaticVariable = jsFunc;
Expect.isNotNull(_jsStaticVariable);
Expect.equals(dart.assertInterop(_jsStaticVariable), _jsStaticVariable);
final dynamic wrappedDartStaticMethod = allowInterop(dartStaticMethod);
final Function localNonNullLegacy = () => 'hello';
final String Function() localNonNull = () => 'hello';
final Function? localNullableLegacy = () => 'hello';
final String Function()? localNullable = () => 'hello';
// Assignment to JS static field.
Expect.throws(() {
_jsStaticVariable = () => 'hello';
});
Expect.throws(() {
_jsStaticVariable = dartStaticMethod;
});
Expect.throws(() {
_jsStaticVariable = localNonNullLegacy;
});
Expect.throws(() {
_jsStaticVariable = localNonNull;
});
Expect.throws(() {
_jsStaticVariable = localNullableLegacy!;
});
Expect.throws(() {
_jsStaticVariable = localNullable!;
});
_jsStaticVariable = allowInterop(dartStaticMethod);
_jsStaticVariable = wrappedDartStaticMethod;
// Argument to static JS function.
Function(Function(String), String) jsFunc2 = helper.JS('', '(f) => f()');
_jsStaticFunction = jsFunc2;
Expect.throws(() {
jsStaticFunction(() => 'hello');
});
Expect.throws(() {
jsStaticFunction(dartStaticMethod);
});
Expect.throws(() {
jsStaticFunction(localNonNullLegacy as String Function());
});
Expect.throws(() {
jsStaticFunction(localNonNull);
});
Expect.throws(() {
jsStaticFunction(localNullableLegacy as String Function());
});
Expect.throws(() {
jsStaticFunction(localNullable!);
});
jsStaticFunction(allowInterop(() => 'hello'));
jsStaticFunction(wrappedDartStaticMethod);
// Argument to torn off static JS function
dynamic method = jsStaticFunction;
Expect.throws(() {
method(() => 'hello');
});
Expect.throws(() {
method(dartStaticMethod);
});
Expect.throws(() {
method(localNonNullLegacy);
});
Expect.throws(() {
method(localNonNull);
});
Expect.throws(() {
method(localNullableLegacy!);
});
Expect.throws(() {
method(localNullable!);
});
method(allowInterop(() => 'hello'));
method(wrappedDartStaticMethod);
// Assignment to instance field.
_someClass = helper.JS(
'',
'{"jsInstanceMethod": function(f) {return f();}, '
'"jsNonFunctionField": {"stringField":"hello js"}, '
'"jsFunctionFieldGetter": function(f) {return f();}}');
Expect.throws((() {
someClass.jsFunctionFieldSetter = () => 'hello';
}));
Expect.throws((() {
someClass.jsFunctionFieldSetter = dartStaticMethod;
}));
Expect.throws((() {
someClass.jsFunctionFieldSetter = localNonNullLegacy as String Function();
}));
Expect.throws((() {
someClass.jsFunctionFieldSetter = localNonNull;
}));
Expect.throws((() {
someClass.jsFunctionFieldSetter = localNullableLegacy as String Function();
}));
Expect.throws((() {
someClass.jsFunctionFieldSetter = localNullable!;
}));
someClass.jsFunctionFieldSetter = allowInterop(() => 'hello');
someClass.jsFunctionFieldSetter = wrappedDartStaticMethod;
// Argument to instance method.
Expect.throws(() {
someClass.jsInstanceMethod(() => 'hello');
});
Expect.throws(() {
someClass.jsInstanceMethod(dartStaticMethod);
});
Expect.throws(() {
someClass.jsInstanceMethod(localNonNullLegacy as String Function());
});
Expect.throws(() {
someClass.jsInstanceMethod(localNonNull);
});
Expect.throws(() {
someClass.jsInstanceMethod(localNullableLegacy as String Function());
});
Expect.throws(() {
someClass.jsInstanceMethod(localNullable!);
});
someClass.jsInstanceMethod(allowInterop(() => 'hello'));
someClass.jsInstanceMethod(wrappedDartStaticMethod);
// Argument to constructor.
context.callMethod('eval', ['function SomeClass(a) { a(); }']);
Expect.throws(() {
SomeClass(() => 'hello');
});
Expect.throws(() {
SomeClass(dartStaticMethod);
});
Expect.throws(() {
SomeClass(localNonNullLegacy as String Function());
});
Expect.throws(() {
SomeClass(localNonNull);
});
Expect.throws(() {
SomeClass(localNullableLegacy as String Function());
});
Expect.throws(() {
SomeClass(localNullable!);
});
SomeClass(allowInterop(() => 'hello'));
SomeClass(wrappedDartStaticMethod);
// Argument to anonymous constructor.
Expect.throws(() {
NestedJs(constructorArg: () => 'hello');
});
Expect.throws(() {
NestedJs(constructorArg: dartStaticMethod);
});
Expect.throws(() {
NestedJs(constructorArg: localNonNullLegacy as String Function());
});
Expect.throws(() {
NestedJs(constructorArg: localNonNull);
});
Expect.throws(() {
NestedJs(constructorArg: localNullableLegacy as String Function());
});
Expect.throws(() {
NestedJs(constructorArg: localNullable!);
});
NestedJs(constructorArg: allowInterop(() => 'hello'));
NestedJs(constructorArg: wrappedDartStaticMethod);
// Argument to torn off instance method.
method = someClass.jsInstanceMethod;
Expect.throws(() {
method(() => 'hello');
});
Expect.throws(() {
method(dartStaticMethod);
});
Expect.throws(() {
method(localNonNullLegacy);
});
Expect.throws(() {
method(localNonNull);
});
Expect.throws(() {
method(localNullableLegacy!);
});
Expect.throws(() {
method(localNullable!);
});
method(allowInterop(() => 'hello'));
method(wrappedDartStaticMethod);
// Function typed getter
Expect.throws(() {
someClass.jsFunctionFieldGetter(() => 'hello');
});
Expect.throws(() {
someClass.jsFunctionFieldGetter(dartStaticMethod);
});
Expect.throws(() {
someClass.jsFunctionFieldGetter(localNonNullLegacy as String Function());
});
Expect.throws(() {
someClass.jsFunctionFieldGetter(localNonNull);
});
Expect.throws(() {
someClass.jsFunctionFieldGetter(localNullableLegacy as String Function());
});
Expect.throws(() {
someClass.jsFunctionFieldGetter(localNullable!);
});
someClass.jsFunctionFieldGetter(allowInterop(() => 'hello'));
someClass.jsFunctionFieldGetter(wrappedDartStaticMethod);
// Stored Function typed getter
method = someClass.jsFunctionFieldGetter;
// We lose safety after calling a getter that returns a function, which takes
// a function as an argument. Since this can be modeled with a method, instead
// of a getter returning a function, we don't expect this is a pattern likely
// to show up in real code.
//Expect.throws(() {
// method(() => 'hello');
//});
//Expect.throws(() {
// method(dartStaticMethod);
//});
//Expect.throws(() {
// method(localNonNullLegacy);
//});
//Expect.throws(() {
// method(localNonNull);
//});
//Expect.throws(() {
// method(localNullableLegacy as String Function());
//});
//Expect.throws(() {
// method(localNullable!);
//});
method(allowInterop(() => 'hello'));
method(wrappedDartStaticMethod);
// Non-function fields
Expect.equals('hello js', someClass.jsNonFunctionField.stringField,
'Does not wrap access to a field');
// No such method errors from interop calls.
// The current behavior is that DDC does not treat these errors from the
// JavaScript side as a LegacyJavaScriptObject.
Expect.throwsNoSuchMethodError(
() => context.callMethod('eval', ['self.foo()']));
Expect.throws<interceptors.JSNoSuchMethodError>(
() => context.callMethod('eval', ['self.foo()']));
var error = Expect.throws(() => context.callMethod('eval', ['self.foo()']));
Expect.notType<interceptors.LegacyJavaScriptObject>(error);
Expect.notEquals(interceptors.LegacyJavaScriptObject, error.runtimeType);
Expect.throwsNoSuchMethodError(
() => context.callMethod('eval', ['self.foo.bar()']));
Expect.throws<interceptors.JSNoSuchMethodError>(
() => context.callMethod('eval', ['self.foo.bar()']));
error = Expect.throws(() => context.callMethod('eval', ['self.foo.bar()']));
Expect.notType<interceptors.LegacyJavaScriptObject>(error);
Expect.notEquals(interceptors.LegacyJavaScriptObject, error.runtimeType);
}