[dart2js,ddc] Add package:js `.call()` tests Some expectations in these tests do not match the language specification or are undefined. These tests should help: - avoid regressions in the existing behavior - highlight incremental improvements towards implementing the desired behavior (with corresponding changes to the expectations) - identify the differences between the JavaScript compilers Change-Id: Icaa7371b3cf8c4221e4348176f712b3d03196720 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/359245 Commit-Queue: Nicholas Shahan <nshahan@google.com> Reviewed-by: Srujan Gaddam <srujzs@google.com> Reviewed-by: Bob Nystrom <rnystrom@google.com> Reviewed-by: Sigmund Cherem <sigmund@google.com>
diff --git a/tests/lib/js/call_field_test.dart b/tests/lib/js/call_field_test.dart new file mode 100644 index 0000000..b0cd0ce --- /dev/null +++ b/tests/lib/js/call_field_test.dart
@@ -0,0 +1,228 @@ +// Copyright (c) 2024, 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. + +/// WARNING +/// +/// Not all of the expectations in this test match the language specification. +/// +/// This is part of a set of tests covering "callable objects". Please consider +/// them all together when making changes: +/// +/// ``` +/// tests/lib/js/call_field_test.dart +/// tests/lib/js/call_getter_test.dart +/// tests/lib/js/call_method_test.dart +/// ``` +/// +/// This test was created with expectations that match the current behavior to +/// make it more clear when something changes and when the results in the web +/// compilers differ. +/// +/// If your change causes an expectation to fail you should decide if the +/// new result is desireable and update the expectation accordingly. + +import 'package:expect/expect.dart'; +import 'package:js/js.dart'; + +import 'call_utils.dart'; + +@JS() +class NamedClass { + external int Function(String s) call; +} + +@JS('jsFunction') +external NamedClass get jsFunctionAsNamedClass; + +@JS('jsObject') +external NamedClass get jsObjectAsNamedClass; + +@JS('jsClass') +external NamedClass get jsClassAsNamedClass; + +@JS() +class NamedClass2 { + external Function call; +} + +@JS('jsFunction') +external NamedClass2 get functionAsNamedClass2; + +@JS('jsObject') +external NamedClass2 get jsObjectAsNamedClass2; + +@JS('jsClass') +external NamedClass2 get jsClassAsNamedClass2; + +@JS() +@anonymous +class SomeClass { + external int Function(String s) call; +} + +@JS('jsFunction') +external SomeClass get functionAsSomeClass; + +@JS('jsObject') +external SomeClass get jsObjectAsSomeClass; + +@JS('jsClass') +external SomeClass get jsClassAsSomeClass; + +@JS() +@anonymous +class SomeClass2 { + external Function call; +} + +@JS('jsFunction') +external SomeClass2 get functionAsSomeClass2; + +@JS('jsObject') +external SomeClass2 get jsObjectAsSomeClass2; + +@JS('jsClass') +external SomeClass2 get jsClassAsSomeClass2; + +void main() { + injectJS(); + testNamedClass(); + testNamedClass2(); + testAnonymous(); + testAnonymous2(); + testDynamic(); +} + +void testNamedClass() { + var obj = jsFunctionAsNamedClass; + Expect.equals('C', obj.call('Cello')); + if (dart2js) { + Expect.throwsNoSuchMethodError(() => obj.call); + } else { + Expect.equals('C', (obj.call)('Cello')); + } + + obj = jsObjectAsNamedClass; + Expect.throwsNoSuchMethodError(() => obj.call('Cello')); + if (dart2js) { + Expect.throwsNoSuchMethodError(() => obj.call); + } else { + Expect.throwsNoSuchMethodError(() => (obj.call)('Cello')); + } + + obj = jsClassAsNamedClass; + Expect.throwsNoSuchMethodError(() => obj.call('Cello')); + if (dart2js) { + Expect.throwsNoSuchMethodError(() => obj.call); + } else { + Expect.throwsNoSuchMethodError(() => (obj.call)('Cello')); + } +} + +void testNamedClass2() { + var obj = functionAsNamedClass2; + Expect.equals('C', obj.call('Cello')); + if (dart2js) { + Expect.throwsNoSuchMethodError(() => obj.call); + } else { + Expect.equals('C', (obj.call)('Cello')); + } + + obj = jsObjectAsNamedClass2; + Expect.throwsNoSuchMethodError(() => obj.call('Cello')); + if (dart2js) { + Expect.throwsNoSuchMethodError(() => obj.call); + } else { + Expect.throwsNoSuchMethodError(() => (obj.call)('Cello')); + } + + obj = jsClassAsNamedClass2; + Expect.throwsNoSuchMethodError(() => obj.call('Cello')); + if (dart2js) { + Expect.throwsNoSuchMethodError(() => obj.call); + } else { + Expect.throwsNoSuchMethodError(() => (obj.call)('Cello')); + } +} + +void testAnonymous() { + var obj = functionAsSomeClass; + Expect.equals('C', obj.call('Cello')); + if (dart2js) { + Expect.throwsNoSuchMethodError(() => obj.call); + } else { + Expect.equals('C', (obj.call)('Cello')); + } + + obj = jsObjectAsSomeClass; + Expect.throwsNoSuchMethodError(() => obj.call('Cello')); + if (dart2js) { + Expect.throwsNoSuchMethodError(() => obj.call); + } else { + Expect.throwsNoSuchMethodError(() => (obj.call)('Cello')); + } + + obj = jsClassAsSomeClass; + Expect.throwsNoSuchMethodError(() => obj.call('Cello')); + if (dart2js) { + Expect.throwsNoSuchMethodError(() => obj.call); + } else { + Expect.throwsNoSuchMethodError(() => (obj.call)('Cello')); + } +} + +void testAnonymous2() { + var obj = functionAsSomeClass2; + Expect.equals('C', obj.call('Cello')); + if (dart2js) { + Expect.throwsNoSuchMethodError(() => obj.call); + } else { + Expect.equals('C', (obj.call)('Cello')); + } + + obj = jsObjectAsSomeClass2; + Expect.throwsNoSuchMethodError(() => obj.call('Cello')); + if (dart2js) { + Expect.throwsNoSuchMethodError(() => obj.call); + } else { + Expect.throwsNoSuchMethodError(() => (obj.call)('Cello')); + } + + obj = jsClassAsSomeClass2; + Expect.throwsNoSuchMethodError(() => obj.call('Cello')); + if (dart2js) { + Expect.throwsNoSuchMethodError(() => obj.call); + } else { + Expect.throwsNoSuchMethodError(() => (obj.call)('Cello')); + } +} + +void testDynamic() { + var d = confuse(jsFunctionAsNamedClass); + Expect.equals('F', d.call('Fosse')); + if (dart2js) { + Expect.throwsNoSuchMethodError(() => d.call); + } else { + Expect.equals('F', (d.call)('Fosse')); + } + Expect.equals('F', d('Fosse')); + + d = confuse(jsObjectAsNamedClass); + Expect.throwsNoSuchMethodError(() => d.call('Fosse')); + if (dart2js) { + Expect.throwsNoSuchMethodError(() => d.call); + } else { + Expect.equals('F', (d.call)('Fosse')); + } + Expect.throwsNoSuchMethodError(() => d('Fosse')); + + d = confuse(jsClassAsNamedClass); + Expect.throwsNoSuchMethodError(() => d.call('Fosse')); + if (dart2js) { + Expect.throwsNoSuchMethodError(() => d.call); + } else { + Expect.throws(() => (d.call)('Fosse'), jsThisIsNullCheck); + } + Expect.throwsNoSuchMethodError(() => d('Fosse')); +}
diff --git a/tests/lib/js/call_getter_test.dart b/tests/lib/js/call_getter_test.dart new file mode 100644 index 0000000..d52853a --- /dev/null +++ b/tests/lib/js/call_getter_test.dart
@@ -0,0 +1,228 @@ +// Copyright (c) 2024, 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. + +/// WARNING +/// +/// Not all of the expectations in this test match the language specification. +/// +/// This is part of a set of tests covering "callable objects". Please consider +/// them all together when making changes: +/// +/// ``` +/// tests/lib/js/call_field_test.dart +/// tests/lib/js/call_getter_test.dart +/// tests/lib/js/call_method_test.dart +/// ``` +/// +/// This test was created with expectations that match the current behavior to +/// make it more clear when something changes and when the results in the web +/// compilers differ. +/// +/// If your change causes an expectation to fail you should decide if the +/// new result is desireable and update the expectation accordingly. + +import 'package:expect/expect.dart'; +import 'package:js/js.dart'; + +import 'call_utils.dart'; + +@JS() +class NamedClass { + external int Function(String s) get call; +} + +@JS('jsFunction') +external NamedClass get jsFunctionAsNamedClass; + +@JS('jsObject') +external NamedClass get jsObjectAsNamedClass; + +@JS('jsClass') +external NamedClass get jsClassAsNamedClass; + +@JS() +class NamedClass2 { + external Function get call; +} + +@JS('jsFunction') +external NamedClass2 get functionAsNamedClass2; + +@JS('jsObject') +external NamedClass2 get jsObjectAsNamedClass2; + +@JS('jsClass') +external NamedClass2 get jsClassAsNamedClass2; + +@JS() +@anonymous +class SomeClass { + external int Function(String s) get call; +} + +@JS('jsFunction') +external SomeClass get functionAsSomeClass; + +@JS('jsObject') +external SomeClass get jsObjectAsSomeClass; + +@JS('jsClass') +external SomeClass get jsClassAsSomeClass; + +@JS() +@anonymous +class SomeClass2 { + external Function get call; +} + +@JS('jsFunction') +external SomeClass2 get functionAsSomeClass2; + +@JS('jsObject') +external SomeClass2 get jsObjectAsSomeClass2; + +@JS('jsClass') +external SomeClass2 get jsClassAsSomeClass2; + +void main() { + injectJS(); + testNamedClass(); + testNamedClass2(); + testAnonymous(); + testAnonymous2(); + testDynamic(); +} + +void testNamedClass() { + var obj = jsFunctionAsNamedClass; + Expect.equals('C', obj.call('Cello')); + if (dart2js) { + Expect.throwsNoSuchMethodError(() => obj.call); + } else { + Expect.equals('C', (obj.call)('Cello')); + } + + obj = jsObjectAsNamedClass; + Expect.throwsNoSuchMethodError(() => obj.call('Cello')); + if (dart2js) { + Expect.throwsNoSuchMethodError(() => obj.call); + } else { + Expect.throwsNoSuchMethodError(() => (obj.call)('Cello')); + } + + obj = jsClassAsNamedClass; + Expect.throwsNoSuchMethodError(() => obj.call('Cello')); + if (dart2js) { + Expect.throwsNoSuchMethodError(() => obj.call); + } else { + Expect.throwsNoSuchMethodError(() => (obj.call)('Cello')); + } +} + +void testNamedClass2() { + var obj = functionAsNamedClass2; + Expect.equals('C', obj.call('Cello')); + if (dart2js) { + Expect.throwsNoSuchMethodError(() => obj.call); + } else { + Expect.equals('C', (obj.call)('Cello')); + } + + obj = jsObjectAsNamedClass2; + Expect.throwsNoSuchMethodError(() => obj.call('Cello')); + if (dart2js) { + Expect.throwsNoSuchMethodError(() => obj.call); + } else { + Expect.throwsNoSuchMethodError(() => (obj.call)('Cello')); + } + + obj = jsClassAsNamedClass2; + Expect.throwsNoSuchMethodError(() => obj.call('Cello')); + if (dart2js) { + Expect.throwsNoSuchMethodError(() => obj.call); + } else { + Expect.throwsNoSuchMethodError(() => (obj.call)('Cello')); + } +} + +void testAnonymous() { + var obj = functionAsSomeClass; + Expect.equals('C', obj.call('Cello')); + if (dart2js) { + Expect.throwsNoSuchMethodError(() => obj.call); + } else { + Expect.equals('C', (obj.call)('Cello')); + } + + obj = jsObjectAsSomeClass; + Expect.throwsNoSuchMethodError(() => obj.call('Cello')); + if (dart2js) { + Expect.throwsNoSuchMethodError(() => obj.call); + } else { + Expect.throwsNoSuchMethodError(() => (obj.call)('Cello')); + } + + obj = jsClassAsSomeClass; + Expect.throwsNoSuchMethodError(() => obj.call('Cello')); + if (dart2js) { + Expect.throwsNoSuchMethodError(() => obj.call); + } else { + Expect.throwsNoSuchMethodError(() => (obj.call)('Cello')); + } +} + +void testAnonymous2() { + var obj = functionAsSomeClass2; + Expect.equals('C', obj.call('Cello')); + if (dart2js) { + Expect.throwsNoSuchMethodError(() => obj.call); + } else { + Expect.equals('C', (obj.call)('Cello')); + } + + obj = jsObjectAsSomeClass2; + Expect.throwsNoSuchMethodError(() => obj.call('Cello')); + if (dart2js) { + Expect.throwsNoSuchMethodError(() => obj.call); + } else { + Expect.throwsNoSuchMethodError(() => (obj.call)('Cello')); + } + + obj = jsClassAsSomeClass2; + Expect.throwsNoSuchMethodError(() => obj.call('Cello')); + if (dart2js) { + Expect.throwsNoSuchMethodError(() => obj.call); + } else { + Expect.throwsNoSuchMethodError(() => (obj.call)('Cello')); + } +} + +void testDynamic() { + var d = confuse(jsFunctionAsNamedClass); + Expect.equals('F', d.call('Fosse')); + if (dart2js) { + Expect.throwsNoSuchMethodError(() => d.call); + } else { + Expect.equals('F', (d.call)('Fosse')); + } + Expect.equals('F', d('Fosse')); + + d = confuse(jsObjectAsNamedClass); + Expect.throwsNoSuchMethodError(() => d.call('Fosse')); + if (dart2js) { + Expect.throwsNoSuchMethodError(() => d.call); + } else { + Expect.equals('F', (d.call)('Fosse')); + } + Expect.throwsNoSuchMethodError(() => d('Fosse')); + + d = confuse(jsClassAsNamedClass); + Expect.throwsNoSuchMethodError(() => d.call('Fosse')); + if (dart2js) { + Expect.throwsNoSuchMethodError(() => d.call); + } else { + Expect.throws(() => (d.call)('Fosse'), jsThisIsNullCheck); + } + Expect.throwsNoSuchMethodError(() => d('Fosse')); +}
diff --git a/tests/lib/js/call_method_test.dart b/tests/lib/js/call_method_test.dart new file mode 100644 index 0000000..cf31eaf --- /dev/null +++ b/tests/lib/js/call_method_test.dart
@@ -0,0 +1,229 @@ +// Copyright (c) 2024, 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. + +/// WARNING +/// +/// Not all of the expectations in this test match the language specification. +/// +/// This is part of a set of tests covering "callable objects". Please consider +/// them all together when making changes: +/// +/// ``` +/// tests/lib/js/call_field_test.dart +/// tests/lib/js/call_getter_test.dart +/// tests/lib/js/call_method_test.dart +/// ``` +/// +/// This test was created with expectations that match the current behavior to +/// make it more clear when something changes and when the results in the web +/// compilers differ. +/// +/// If your change causes an expectation to fail you should decide if the +/// new result is desireable and update the expectation accordingly. + +import 'package:expect/expect.dart'; +import 'package:js/js.dart'; + +import 'call_utils.dart'; + +@JS('jsFunction') +external int Function(String) jsFunctionAsFunction; + +@JS('jsObject') +external int Function(String) jsObjectAsFunction; + +@JS('jsClass') +external int Function(String) jsClassAsFunction; + +@JS('jsFunction') +external Function jsFunctionAsFunctionInterface; + +@JS('jsObject') +external Function jsObjectAsFunctionInterface; + +@JS('jsClass') +external Function jsClassAsFunctionInterface; + +@JS() +class NamedClass { + external int call(String s); +} + +@JS('jsFunction') +external NamedClass get functionAsNamedClass; + +@JS('jsObject') +external NamedClass get jsObjectAsNamedClass; + +@JS('jsClass') +external NamedClass get jsClassAsNamedClass; + +@JS() +@anonymous +class SomeClass { + external int call(String s); +} + +@JS('jsFunction') +external SomeClass get functionAsSomeClass; + +@JS('jsObject') +external SomeClass get jsObjectAsSomeClass; + +@JS('jsClass') +external SomeClass get jsClassAsSomeClass; + +void main() { + injectJS(); + testFunction(); + testFunctionInterface(); + testNamedClass(); + testAnonymous(); + testDynamic(); +} + +void testFunction() { + var obj = jsFunctionAsFunction; + Expect.equals('C', obj.call('Cello')); + if (dart2js) { + Expect.throwsNoSuchMethodError(() => obj.call); + } else { + Expect.equals('C', (obj.call)('Cello')); + } + Expect.equals('C', obj('Cello')); + + obj = jsObjectAsFunction; + Expect.throwsNoSuchMethodError(() => obj.call('Cello')); + if (dart2js) { + Expect.throwsNoSuchMethodError(() => obj.call); + } else { + Expect.throwsNoSuchMethodError(() => (obj.call)('Cello')); + } + Expect.throwsNoSuchMethodError(() => obj('Cello')); + + obj = jsClassAsFunction; + Expect.throwsNoSuchMethodError(() => obj.call('Cello')); + if (dart2js) { + Expect.throwsNoSuchMethodError(() => obj.call); + } else { + Expect.throwsNoSuchMethodError(() => (obj.call)('Cello')); + } + Expect.throwsNoSuchMethodError(() => obj('Cello')); +} + +void testFunctionInterface() { + var obj = jsFunctionAsFunctionInterface; + Expect.equals('C', obj.call('Cello')); + if (dart2js) { + Expect.throwsNoSuchMethodError(() => obj.call); + } else { + Expect.equals('C', (obj.call)('Cello')); + } + Expect.equals('C', obj('Cello')); + + obj = jsObjectAsFunctionInterface; + Expect.throwsNoSuchMethodError(() => obj.call('Cello')); + if (dart2js) { + Expect.throwsNoSuchMethodError(() => obj.call); + } else { + Expect.equals('C', (obj.call)('Cello')); + } + Expect.throwsNoSuchMethodError(() => obj('Cello')); + + obj = jsClassAsFunctionInterface; + Expect.throwsNoSuchMethodError(() => obj.call('Cello')); + if (dart2js) { + Expect.throwsNoSuchMethodError(() => obj.call); + } else { + Expect.throws(() => (obj.call)('Cello'), jsThisIsNullCheck); + } + Expect.throwsNoSuchMethodError(() => obj('Cello')); +} + +void testNamedClass() { + var obj = functionAsNamedClass; + Expect.equals('C', obj.call('Cello')); + if (dart2js) { + Expect.throwsNoSuchMethodError(() => obj.call); + } else { + Expect.equals('C', (obj.call)('Cello')); + } + Expect.equals('C', obj('Cello')); + + obj = jsObjectAsNamedClass; + Expect.throwsNoSuchMethodError(() => obj.call('Cello')); + if (dart2js) { + Expect.throwsNoSuchMethodError(() => obj.call); + } else { + Expect.throwsNoSuchMethodError(() => (obj.call)('Cello')); + } + Expect.throwsNoSuchMethodError(() => obj('Cello')); + + obj = jsClassAsNamedClass; + Expect.throwsNoSuchMethodError(() => obj.call('Cello')); + if (dart2js) { + Expect.throwsNoSuchMethodError(() => (obj.call)); + } else { + Expect.throwsNoSuchMethodError(() => (obj.call)('Cello')); + } + Expect.throwsNoSuchMethodError(() => obj('Cello')); +} + +void testAnonymous() { + var obj = functionAsSomeClass; + Expect.equals('C', obj.call('Cello')); + if (dart2js) { + Expect.throwsNoSuchMethodError(() => obj.call); + } else { + Expect.equals('C', (obj.call)('Cello')); + } + Expect.equals('C', obj('Cello')); + + obj = jsObjectAsSomeClass; + Expect.throwsNoSuchMethodError(() => obj.call('Cello')); + if (dart2js) { + Expect.throwsNoSuchMethodError(() => obj.call); + } else { + Expect.throwsNoSuchMethodError(() => (obj.call)('Cello')); + } + Expect.throwsNoSuchMethodError(() => obj('Cello')); + + obj = jsClassAsSomeClass; + Expect.throwsNoSuchMethodError(() => obj.call('Cello')); + if (dart2js) { + Expect.throwsNoSuchMethodError(() => obj.call); + } else { + Expect.throwsNoSuchMethodError(() => (obj.call)('Cello')); + } + Expect.throwsNoSuchMethodError(() => obj('Cello')); +} + +void testDynamic() { + var d = confuse(jsFunctionAsFunction); + Expect.equals('F', d.call('Fosse')); + if (dart2js) { + Expect.throwsNoSuchMethodError(() => d.call); + } else { + Expect.equals('F', (d.call)('Fosse')); + } + Expect.equals('F', d('Fosse')); + + d = confuse(jsClassAsFunction); + Expect.throwsNoSuchMethodError(() => d.call('Fosse')); + if (dart2js) { + Expect.throwsNoSuchMethodError(() => d.call); + } else { + Expect.throws(() => (d.call)('Fosse'), jsThisIsNullCheck); + } + Expect.throwsNoSuchMethodError(() => d('Fosse')); + + d = confuse(jsObjectAsFunction); + Expect.throwsNoSuchMethodError(() => d.call('Fosse')); + if (dart2js) { + Expect.throwsNoSuchMethodError(() => d.call); + } else { + Expect.equals('F', (d.call)('Fosse')); + } + Expect.throwsNoSuchMethodError(() => d('Fosse')); +}
diff --git a/tests/lib/js/call_utils.dart b/tests/lib/js/call_utils.dart new file mode 100644 index 0000000..97454af --- /dev/null +++ b/tests/lib/js/call_utils.dart
@@ -0,0 +1,56 @@ +// Copyright (c) 2024, 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. + +import 'package:js/js.dart'; + +const dart2js = const bool.fromEnvironment('dart.library._dart2js_only'); + +@pragma('dart2js:noInline') +@pragma('dart2js:assumeDynamic') +dynamic confuse(dynamic x) => x; + +@JS() +external dynamic eval(String script); + +void injectJS() { + eval( + ''' + self.jsFunction = function(s) { + if (this == null) { + throw "`this` is null or undefined"; + } + if (typeof s != 'string') { + throw "`s` is not a string"; + } + return s.at(0); + }; + self.jsObject = { call: function(s) { + if (this == null) { + throw "`this` is null or undefined"; + } + if (typeof s != 'string') { + throw "`s` is not a string"; + } + return s.at(0); + } }; + self.NamedClass = class NamedClass { + call(s) { + if (this == null) { + throw "`this` is null or undefined"; + } + if (typeof s != 'string') { + throw "`s` is not a string"; + } + return s.at(0); + } + } + self.jsClass = new NamedClass(); + ''', + ); +} + +bool jsThisIsNullCheck(e) => + e.toString().contains('`this` is null or undefined'); + +bool jsArgIsNotStringCheck(e) => e.toString().contains('`s` is not a string');
diff --git a/tests/lib/lib.status b/tests/lib/lib.status index fc812a2..b54fa9d 100644 --- a/tests/lib/lib.status +++ b/tests/lib/lib.status
@@ -61,6 +61,9 @@ [ $csp ] html/js_interop_constructor_name/*: SkipByDesign # Issue 42085. isolate/deferred_in_isolate2_test: Skip # Issue 16898. Deferred loading does not work from an isolate in CSP-mode +js/call_field_test: SkipByDesign # Issue 42085. CSP policy disallows injected JS code +js/call_getter_test: SkipByDesign # Issue 42085. CSP policy disallows injected JS code +js/call_method_test: SkipByDesign # Issue 42085. CSP policy disallows injected JS code js/extends_test/extends_test: SkipByDesign # Issue 42085. CSP policy disallows injected JS code js/extends_test/extends_with_es6_test: SkipByDesign # Issue 42085. CSP policy disallows injected JS code js/instanceof_test: SkipByDesign # Issue 42085. CSP policy disallows injected JS code