// 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( () => context.callMethod('eval', ['self.foo()'])); var error = Expect.throws(() => context.callMethod('eval', ['self.foo()'])); Expect.notType(error); Expect.notEquals(interceptors.LegacyJavaScriptObject, error.runtimeType); Expect.throwsNoSuchMethodError( () => context.callMethod('eval', ['self.foo.bar()'])); Expect.throws( () => context.callMethod('eval', ['self.foo.bar()'])); error = Expect.throws(() => context.callMethod('eval', ['self.foo.bar()'])); Expect.notType(error); Expect.notEquals(interceptors.LegacyJavaScriptObject, error.runtimeType); }