| // 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. | |
| // dart2wasmOptions=--extra-compiler-option=--enable-experimental-wasm-interop | |
| import 'dart:js_interop'; | |
| import 'dart:js_interop_unsafe'; | |
| import 'package:expect/expect.dart'; | |
| @JS() | |
| external void eval(String code); | |
| typedef SumTwoPositionalFun = String Function(String a, String b); | |
| typedef SumOnePositionalAndOneOptionalFun = String Function( | |
| String a, [ | |
| String? b, | |
| ]); | |
| typedef SumTwoOptionalFun = String Function([String? a, String? b]); | |
| typedef SumOnePositionalAndOneOptionalNonNullFun = String Function( | |
| String a, [ | |
| String b, | |
| ]); | |
| typedef SumTwoOptionalNonNullFun = String Function([String a, String b]); | |
| @JS() | |
| @staticInterop | |
| class DartFromJSCallbackHelper { | |
| external factory DartFromJSCallbackHelper.factory( | |
| JSFunction sumTwoPositional, | |
| JSFunction sumOnePositionalOneOptional, | |
| JSFunction sumTwoOptional, | |
| JSFunction sumOnePositionalAndOneOptionalNonNull, | |
| JSFunction sumTwoOptionalNonNull, | |
| ); | |
| } | |
| extension DartFromJSCallbackHelperMethods on DartFromJSCallbackHelper { | |
| external String doSum1(); | |
| external String doSum2(String a, String b); | |
| external String doSum3(JSFunction summer); | |
| external String doSumOnePositionalAndOneOptionalA(String a); | |
| external String doSumOnePositionalAndOneOptionalB(String a, String b); | |
| external String doSumTwoOptionalA(); | |
| external String doSumTwoOptionalB(String a); | |
| external String doSumTwoOptionalC(String a, String b); | |
| external String doSumOnePositionalAndOneOptionalANonNull(String a); | |
| external String doSumOnePositionalAndOneOptionalBNonNull(String a, String b); | |
| external String doSumTwoOptionalANonNull(); | |
| external String doSumTwoOptionalBNonNull(String a); | |
| external String doSumTwoOptionalCNonNull(String a, String b); | |
| } | |
| String sumTwoPositional(String a, String b) { | |
| return a + b; | |
| } | |
| String sumOnePositionalAndOneOptional(String a, [String? b]) { | |
| return a + (b ?? 'bar'); | |
| } | |
| String sumTwoOptional([String? a, String? b]) { | |
| return (a ?? 'foo') + (b ?? 'bar'); | |
| } | |
| String sumOnePositionalAndOneOptionalNonNull(String a, [String b = 'bar']) { | |
| return a + b; | |
| } | |
| String sumTwoOptionalNonNull([String a = 'foo', String b = 'bar']) { | |
| return a + b; | |
| } | |
| void staticInteropCallbackTest() { | |
| eval(r''' | |
| globalThis.DartFromJSCallbackHelper = function( | |
| sumTwoPositional, sumOnePositionalOneOptional, sumTwoOptional, | |
| sumOnePositionalAndOneOptionalNonNull, sumTwoOptionalNonNull) { | |
| this.a = 'hello '; | |
| this.b = 'world!'; | |
| this.sum = null; | |
| this.sumTwoPositional = sumTwoPositional; | |
| this.sumOnePositionalOneOptional = sumOnePositionalOneOptional; | |
| this.sumTwoOptional = sumTwoOptional; | |
| this.sumOnePositionalAndOneOptionalNonNull = sumOnePositionalAndOneOptionalNonNull; | |
| this.sumTwoOptionalNonNull = sumTwoOptionalNonNull; | |
| this.doSum1 = () => { | |
| return this.sumTwoPositional(this.a, this.b); | |
| } | |
| this.doSum2 = (a, b) => { | |
| return this.sumTwoPositional(a, b); | |
| } | |
| this.doSum3 = (summer) => { | |
| return summer(this.a, this.b); | |
| } | |
| this.doSumOnePositionalAndOneOptionalA = (a) => { | |
| return sumOnePositionalOneOptional(a); | |
| } | |
| this.doSumOnePositionalAndOneOptionalB = (a, b) => { | |
| return sumOnePositionalOneOptional(a, b); | |
| } | |
| this.doSumTwoOptionalA = () => { | |
| return sumTwoOptional(); | |
| } | |
| this.doSumTwoOptionalB = (a) => { | |
| return sumTwoOptional(a); | |
| } | |
| this.doSumTwoOptionalC = (a, b) => { | |
| return sumTwoOptional(a, b); | |
| } | |
| this.doSumOnePositionalAndOneOptionalANonNull = (a) => { | |
| return sumOnePositionalAndOneOptionalNonNull(a); | |
| } | |
| this.doSumOnePositionalAndOneOptionalBNonNull = (a, b) => { | |
| return sumOnePositionalAndOneOptionalNonNull(a, b); | |
| } | |
| this.doSumTwoOptionalANonNull = () => { | |
| return sumTwoOptionalNonNull(); | |
| } | |
| this.doSumTwoOptionalBNonNull = (a) => { | |
| return sumTwoOptionalNonNull(a); | |
| } | |
| this.doSumTwoOptionalCNonNull = (a, b) => { | |
| return sumTwoOptionalNonNull(a, b); | |
| } | |
| } | |
| '''); | |
| final helper = DartFromJSCallbackHelper.factory( | |
| sumTwoPositional.toJS, | |
| sumOnePositionalAndOneOptional.toJS, | |
| sumTwoOptional.toJS, | |
| sumOnePositionalAndOneOptionalNonNull.toJS, | |
| sumTwoOptionalNonNull.toJS, | |
| ); | |
| Expect.equals('hello world!', helper.doSum1()); | |
| Expect.equals('foobar', helper.doSum2('foo', 'bar')); | |
| Expect.equals( | |
| 'hello world!', | |
| helper.doSum3(((String a, String b) => a + b).toJS), | |
| ); | |
| Expect.equals('foobar', helper.doSumOnePositionalAndOneOptionalA('foo')); | |
| Expect.equals( | |
| 'foobar', | |
| helper.doSumOnePositionalAndOneOptionalB('foo', 'bar'), | |
| ); | |
| Expect.equals('foobar', helper.doSumTwoOptionalA()); | |
| Expect.equals('foobar', helper.doSumTwoOptionalB('foo')); | |
| Expect.equals('foobar', helper.doSumTwoOptionalC('foo', 'bar')); | |
| Expect.equals( | |
| 'foobar', | |
| helper.doSumOnePositionalAndOneOptionalANonNull('foo'), | |
| ); | |
| Expect.equals( | |
| 'foobar', | |
| helper.doSumOnePositionalAndOneOptionalBNonNull('foo', 'bar'), | |
| ); | |
| Expect.equals('foobar', helper.doSumTwoOptionalANonNull()); | |
| Expect.equals('foobar', helper.doSumTwoOptionalBNonNull('foo')); | |
| Expect.equals('foobar', helper.doSumTwoOptionalCNonNull('foo', 'bar')); | |
| } | |
| typedef NoArgsFun = String Function(); | |
| typedef OneArgFun = String Function(String arg); | |
| typedef OnePositionalAndOneOptionalArgsFun = String Function( | |
| String arg, [ | |
| String arg2, | |
| ]); | |
| typedef TwoOptionalArgsFun = String Function([String arg, String arg2]); | |
| class TornOffClass { | |
| String noArgs() { | |
| return 'foo'; | |
| } | |
| String oneArg(String arg) { | |
| return arg; | |
| } | |
| String onePositionalAndOneOptionalArgs(String arg, [String arg2 = 'bar']) { | |
| return arg + arg2; | |
| } | |
| String twoOptionalArgs([String arg = 'foo', String? arg2]) { | |
| return arg + (arg2 ?? ''); | |
| } | |
| } | |
| typedef OneArgFunB = String Function(double arg); | |
| typedef OnePositionalAndOneOptionalArgsFunB = String Function( | |
| double arg, [ | |
| String arg2, | |
| ]); | |
| typedef TwoOptionalArgsFunB = String Function([double arg, String arg2]); | |
| class GenericTornOffClass<T, V> { | |
| String noArgs() { | |
| return 'foo'; | |
| } | |
| String oneArg(T arg) { | |
| return '$arg'; | |
| } | |
| String onePositionalAndOneOptionalArgs(T arg, [V? arg2]) { | |
| return '$arg $arg2'; | |
| } | |
| String twoOptionalArgs([T? arg, V? arg2]) { | |
| return '$arg $arg2'; | |
| } | |
| } | |
| void unsafeInteropCallbackTest() { | |
| eval(r''' | |
| globalThis.doSum1 = function(summer) { | |
| return summer('foo', 'bar'); | |
| } | |
| globalThis.doSum2 = function(a, b) { | |
| return globalThis.summer(a, b); | |
| } | |
| globalThis.doSumOnePositionalAndOneOptionalA = function(a) { | |
| return summer(a); | |
| } | |
| globalThis.doSumOnePositionalAndOneOptionalB = function(a, b) { | |
| return summer(a, b); | |
| } | |
| globalThis.doSumTwoOptionalA = function() { | |
| return summer(); | |
| } | |
| globalThis.doSumTwoOptionalB = function(a) { | |
| return summer(a); | |
| } | |
| globalThis.doSumTwoOptionalC = function(a, b) { | |
| return summer(a, b); | |
| } | |
| globalThis.doSumOnePositionalAndOneOptionalANonNull = function(a) { | |
| return summer(a); | |
| } | |
| globalThis.doSumOnePositionalAndOneOptionalBNonNull = function(a, b) { | |
| return summer(a, b); | |
| } | |
| globalThis.doSumTwoOptionalANonNull = function() { | |
| return summer(); | |
| } | |
| globalThis.doSumTwoOptionalBNonNull = function(a) { | |
| return summer(a); | |
| } | |
| globalThis.doSumTwoOptionalCNonNull = function(a, b) { | |
| return summer(a, b); | |
| } | |
| // tear off cases | |
| globalThis.tearOffNoArgs = function (f) { | |
| return f(); | |
| } | |
| globalThis.tearOffOneArg = function (f) { | |
| return f('foo'); | |
| } | |
| globalThis.tearOffOnePositionalAndOneOptionalArgsA = function (f) { | |
| return f('foo'); | |
| } | |
| globalThis.tearOffOnePositionalAndOneOptionalArgsB = function (f) { | |
| return f('foo', 'baz'); | |
| } | |
| globalThis.tearOffTwoOptionalArgsA = function (f) { | |
| return f('foo'); | |
| } | |
| globalThis.tearOffTwoOptionalArgsB = function (f) { | |
| return f('foo', 'baz'); | |
| } | |
| // tear off generic class cases | |
| globalThis.tearOffGenericNoArgs = function (f) { | |
| return f(); | |
| } | |
| globalThis.tearOffGenericOneArg = function (f) { | |
| return f(1.0); | |
| } | |
| globalThis.tearOffGenericOnePositionalAndOneOptionalArgsA = function (f) { | |
| return f(1.0); | |
| } | |
| globalThis.tearOffGenericOnePositionalAndOneOptionalArgsB = function (f) { | |
| return f(1.0, 'baz'); | |
| } | |
| globalThis.tearOffGenericTwoOptionalArgsA = function (f) { | |
| return f(1.0); | |
| } | |
| globalThis.tearOffGenericTwoOptionalArgsB = function (f) { | |
| return f(1.0, 'baz'); | |
| } | |
| '''); | |
| // General | |
| { | |
| final interopCallback = ((String a, String b) => a + b).toJS; | |
| Expect.equals( | |
| 'foobar', | |
| globalContext.callMethod('doSum1'.toJS, interopCallback).toString(), | |
| ); | |
| globalContext['summer'] = interopCallback; | |
| Expect.equals( | |
| 'foobar', | |
| globalContext | |
| .callMethod('doSum2'.toJS, 'foo'.toJS, 'bar'.toJS) | |
| .toString(), | |
| ); | |
| final roundTripCallback = globalContext['summer']; | |
| Expect.equals( | |
| 'foobar', | |
| (roundTripCallback.dartify() as SumTwoPositionalFun)('foo', 'bar'), | |
| ); | |
| } | |
| // 1 nullable optional argument | |
| { | |
| final interopCallback = ((String a, [String? b]) => a + (b ?? 'bar')).toJS; | |
| globalContext['summer'] = interopCallback; | |
| Expect.equals( | |
| 'foobar', | |
| globalContext | |
| .callMethod('doSumOnePositionalAndOneOptionalA'.toJS, 'foo'.toJS) | |
| .toString(), | |
| ); | |
| Expect.equals( | |
| 'foobar', | |
| globalContext | |
| .callMethod( | |
| 'doSumOnePositionalAndOneOptionalB'.toJS, | |
| 'foo'.toJS, | |
| 'bar'.toJS, | |
| ) | |
| .toString(), | |
| ); | |
| } | |
| // All nullable optional arguments | |
| { | |
| final interopCallback = (([ | |
| String? a, | |
| String? b, | |
| ]) => (a ?? 'foo') + (b ?? 'bar')).toJS; | |
| globalContext['summer'] = interopCallback; | |
| Expect.equals( | |
| 'foobar', | |
| globalContext.callMethod('doSumTwoOptionalA'.toJS).toString(), | |
| ); | |
| Expect.equals( | |
| 'foobar', | |
| globalContext.callMethod('doSumTwoOptionalB'.toJS, 'foo'.toJS).toString(), | |
| ); | |
| Expect.equals( | |
| 'foobar', | |
| globalContext | |
| .callMethod('doSumTwoOptionalC'.toJS, 'foo'.toJS, 'bar'.toJS) | |
| .toString(), | |
| ); | |
| } | |
| // 1 non-nullable optional argument | |
| { | |
| final interopCallback = ((String a, [String b = 'bar']) => a + b).toJS; | |
| globalContext['summer'] = interopCallback; | |
| Expect.equals( | |
| 'foobar', | |
| globalContext | |
| .callMethod('doSumOnePositionalAndOneOptionalA'.toJS, 'foo'.toJS) | |
| .toString(), | |
| ); | |
| Expect.equals( | |
| 'foobar', | |
| globalContext | |
| .callMethod( | |
| 'doSumOnePositionalAndOneOptionalB'.toJS, | |
| 'foo'.toJS, | |
| 'bar'.toJS, | |
| ) | |
| .toString(), | |
| ); | |
| } | |
| // All non-nullable optional arguments | |
| { | |
| final interopCallback = (([ | |
| String a = 'foo', | |
| String b = 'bar', | |
| ]) => a + b).toJS; | |
| globalContext['summer'] = interopCallback; | |
| Expect.equals( | |
| 'foobar', | |
| globalContext.callMethod('doSumTwoOptionalA'.toJS).toString(), | |
| ); | |
| Expect.equals( | |
| 'foobar', | |
| globalContext.callMethod('doSumTwoOptionalB'.toJS, 'foo'.toJS).toString(), | |
| ); | |
| Expect.equals( | |
| 'foobar', | |
| globalContext | |
| .callMethod('doSumTwoOptionalC'.toJS, 'foo'.toJS, 'bar'.toJS) | |
| .toString(), | |
| ); | |
| } | |
| // Tear off cases | |
| // No args. | |
| { | |
| final t = TornOffClass(); | |
| final interopCallback = t.noArgs.toJS; | |
| Expect.equals( | |
| 'foo'.toJS, | |
| globalContext.callMethod('tearOffNoArgs'.toJS, interopCallback), | |
| ); | |
| } | |
| // One arg. | |
| { | |
| final t = TornOffClass(); | |
| final interopCallback = t.oneArg.toJS; | |
| Expect.equals( | |
| 'foo'.toJS, | |
| globalContext.callMethod('tearOffOneArg'.toJS, interopCallback), | |
| ); | |
| } | |
| // One positional and one optional case A. | |
| { | |
| final t = TornOffClass(); | |
| final interopCallback = t.onePositionalAndOneOptionalArgs.toJS; | |
| Expect.equals( | |
| 'foobar'.toJS, | |
| globalContext.callMethod( | |
| 'tearOffOnePositionalAndOneOptionalArgsA'.toJS, | |
| interopCallback, | |
| ), | |
| ); | |
| } | |
| // One positional and one optional case B. | |
| { | |
| final t = TornOffClass(); | |
| final interopCallback = t.onePositionalAndOneOptionalArgs.toJS; | |
| Expect.equals( | |
| 'foobaz'.toJS, | |
| globalContext.callMethod( | |
| 'tearOffOnePositionalAndOneOptionalArgsB'.toJS, | |
| interopCallback, | |
| ), | |
| ); | |
| } | |
| // Two optional case A. | |
| { | |
| final t = TornOffClass(); | |
| final interopCallback = t.twoOptionalArgs.toJS; | |
| Expect.equals( | |
| 'foo'.toJS, | |
| globalContext.callMethod('tearOffTwoOptionalArgsA'.toJS, interopCallback), | |
| ); | |
| } | |
| // Two optional case B. | |
| { | |
| final t = TornOffClass(); | |
| final interopCallback = t.twoOptionalArgs.toJS; | |
| Expect.equals( | |
| 'foobaz'.toJS, | |
| globalContext.callMethod('tearOffTwoOptionalArgsB'.toJS, interopCallback), | |
| ); | |
| } | |
| // Tearoffs of generic classes. | |
| // No args. | |
| { | |
| final t = GenericTornOffClass<double, String>(); | |
| final interopCallback = t.noArgs.toJS; | |
| Expect.equals( | |
| 'foo'.toJS, | |
| globalContext.callMethod('tearOffGenericNoArgs'.toJS, interopCallback), | |
| ); | |
| } | |
| // One arg. | |
| { | |
| final t = GenericTornOffClass<double, String>(); | |
| final interopCallback = t.oneArg.toJS; | |
| Expect.equals( | |
| '1.0'.toJS, | |
| globalContext.callMethod('tearOffGenericOneArg'.toJS, interopCallback), | |
| ); | |
| } | |
| // One positional and one optional case A. | |
| { | |
| final t = GenericTornOffClass<double, String>(); | |
| final interopCallback = t.onePositionalAndOneOptionalArgs.toJS; | |
| Expect.equals( | |
| '1.0 null'.toJS, | |
| globalContext.callMethod( | |
| 'tearOffGenericOnePositionalAndOneOptionalArgsA'.toJS, | |
| interopCallback, | |
| ), | |
| ); | |
| } | |
| // One positional and one optional case B. | |
| { | |
| final t = GenericTornOffClass<double, String>(); | |
| final interopCallback = t.onePositionalAndOneOptionalArgs.toJS; | |
| Expect.equals( | |
| '1.0 baz'.toJS, | |
| globalContext.callMethod( | |
| 'tearOffGenericOnePositionalAndOneOptionalArgsB'.toJS, | |
| interopCallback, | |
| ), | |
| ); | |
| } | |
| // Two optional case A. | |
| { | |
| final t = GenericTornOffClass<double, String>(); | |
| final interopCallback = t.twoOptionalArgs.toJS; | |
| Expect.equals( | |
| '1.0 null'.toJS, | |
| globalContext.callMethod( | |
| 'tearOffGenericTwoOptionalArgsA'.toJS, | |
| interopCallback, | |
| ), | |
| ); | |
| } | |
| // Two optional case B. | |
| { | |
| final t = GenericTornOffClass<double, String>(); | |
| final interopCallback = t.twoOptionalArgs.toJS; | |
| Expect.equals( | |
| '1.0 baz'.toJS, | |
| globalContext.callMethod( | |
| 'tearOffGenericTwoOptionalArgsB'.toJS, | |
| interopCallback, | |
| ), | |
| ); | |
| } | |
| } | |
| void main() { | |
| staticInteropCallbackTest(); | |
| unsafeInteropCallbackTest(); | |
| } |