[js] Add Dart Object API tests for interop objects

Change-Id: I806ef3e5ed2468eecd91427a3e28cc0859aa81fa
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/317844
Reviewed-by: Srujan Gaddam <srujzs@google.com>
Reviewed-by: Sigmund Cherem <sigmund@google.com>
Commit-Queue: Nicholas Shahan <nshahan@google.com>
diff --git a/tests/lib/js/object_apis_interop_values_test.dart b/tests/lib/js/object_apis_interop_values_test.dart
new file mode 100644
index 0000000..7e6ed9c
--- /dev/null
+++ b/tests/lib/js/object_apis_interop_values_test.dart
@@ -0,0 +1,58 @@
+// Copyright (c) 2023, 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.
+
+@JS()
+library js_interop_test;
+
+import 'package:expect/expect.dart';
+import 'package:js/js.dart';
+
+import 'object_apis_utils.dart';
+
+@JS()
+external dynamic eval(String code);
+
+@JS()
+class JavaScriptClass {
+  external factory JavaScriptClass();
+  external int get instanceGetter;
+}
+
+void main() {
+  // Install a class that is accessible through interop.
+  eval('''
+self.JavaScriptClass = class JavaScriptClass {
+  constructor() {}
+  get instanceGetter() { return 42; }
+}
+''');
+  var instanceOfClass = JavaScriptClass();
+  var other = JavaScriptClass();
+  Expect.equals(42, instanceOfClass.instanceGetter);
+  testHashCode(instanceOfClass);
+  testRuntimeType(instanceOfClass);
+  testNoSuchMethod(instanceOfClass);
+  testNoSuchMethodTearoff(instanceOfClass);
+  testToString(instanceOfClass);
+  testToStringTearoff(instanceOfClass);
+  testEquals(instanceOfClass, other);
+
+  var emptyObject = eval('Object.create({})');
+  testHashCode(emptyObject);
+  testRuntimeType(emptyObject);
+  testNoSuchMethod(emptyObject);
+  testNoSuchMethodTearoff(emptyObject);
+  testToString(emptyObject);
+  testToStringTearoff(emptyObject);
+  testEquals(emptyObject, other);
+
+  var jsNull = eval('null');
+  testHashCode(jsNull);
+  testRuntimeType(jsNull);
+  testNoSuchMethod(jsNull);
+  testNoSuchMethodTearoff(jsNull);
+  testToString(jsNull);
+  testToStringTearoff(jsNull);
+  testEquals(jsNull, other);
+}
diff --git a/tests/lib/js/object_apis_utils.dart b/tests/lib/js/object_apis_utils.dart
new file mode 100644
index 0000000..b5041af
--- /dev/null
+++ b/tests/lib/js/object_apis_utils.dart
@@ -0,0 +1,65 @@
+// Copyright (c) 2023, 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:expect/expect.dart';
+
+// Avoid static type optimization by running all tests using this.
+@pragma('dart2js:noInline')
+@pragma('dart2js:assumeDynamic')
+confuse(x) => x;
+
+void testHashCode(x) {
+  var hashCodeResult = confuse(x.hashCode);
+  Expect.type<int>(hashCodeResult);
+  Expect.equals(x.hashCode, hashCodeResult);
+}
+
+void testRuntimeType(x) {
+  var runtimeTypeResult = confuse(x.runtimeType);
+  Expect.type<Type>(runtimeTypeResult);
+  Expect.equals(x.runtimeType, runtimeTypeResult);
+}
+
+void testNoSuchMethod(x) {
+  var noSuchMethodResult = Expect.throws(
+      () => x.noSuchMethod(Invocation.method(Symbol('testMethod'), null)));
+  Expect.type<NoSuchMethodError>(noSuchMethodResult);
+  Expect.contains('testMethod', noSuchMethodResult.toString());
+}
+
+void testNoSuchMethodTearoff(x) {
+  var noSuchMethodTearoff = confuse(x.noSuchMethod);
+  Expect.type<dynamic Function(Invocation)>(noSuchMethodTearoff);
+  Expect.equals(x.noSuchMethod, noSuchMethodTearoff);
+  var noSuchMethodResult = Expect.throws(
+      () => noSuchMethodTearoff(Invocation.method(Symbol('testMethod'), null)));
+  Expect.type<NoSuchMethodError>(noSuchMethodResult);
+  Expect.contains('testMethod', noSuchMethodResult.toString());
+}
+
+void testToString(x) {
+  var toStringResult = confuse(x.toString());
+  Expect.type<String>(toStringResult);
+  Expect.isTrue(toStringResult.isNotEmpty);
+  Expect.equals(x.toString(), toStringResult);
+}
+
+void testToStringTearoff(x) {
+  var toStringTearoff = confuse(x.toString);
+  Expect.type<String Function()>(toStringTearoff);
+  Expect.equals(x.toString, toStringTearoff);
+  var toStringResult = toStringTearoff();
+  Expect.type<String>(toStringResult);
+  Expect.isTrue(toStringResult.isNotEmpty);
+  Expect.equals(x.toString(), toStringResult);
+}
+
+void testEquals(x, other) {
+  var y = confuse(other);
+  var equalityResult = x == y;
+  Expect.type<bool>(equalityResult);
+  Expect.isFalse(equalityResult);
+  Expect.equals(equalityResult, x == y);
+  Expect.isTrue(confuse(x) == x);
+}
diff --git a/tests/lib/js/static_interop_test/object_apis_static_interop_values_test.dart b/tests/lib/js/static_interop_test/object_apis_static_interop_values_test.dart
new file mode 100644
index 0000000..d439fa8
--- /dev/null
+++ b/tests/lib/js/static_interop_test/object_apis_static_interop_values_test.dart
@@ -0,0 +1,62 @@
+// Copyright (c) 2023, 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.
+
+@JS()
+library object_apis_test;
+
+import 'package:expect/expect.dart';
+import 'package:js/js.dart';
+
+import '../object_apis_utils.dart';
+
+@JS()
+external dynamic eval(String code);
+
+@JS()
+@staticInterop
+class JavaScriptClass {
+  external factory JavaScriptClass();
+}
+
+extension on JavaScriptClass {
+  external int get instanceGetter;
+}
+
+void main() {
+  // Install a class that is accessible through interop.
+  eval('''
+self.JavaScriptClass = class JavaScriptClass {
+  constructor() {}
+  get instanceGetter() { return 42; }
+}
+''');
+  var instanceOfClass = JavaScriptClass();
+  var other = JavaScriptClass();
+  Expect.equals(42, instanceOfClass.instanceGetter);
+  testHashCode(instanceOfClass);
+  testRuntimeType(instanceOfClass);
+  testNoSuchMethod(instanceOfClass);
+  testNoSuchMethodTearoff(instanceOfClass);
+  testToString(instanceOfClass);
+  testToStringTearoff(instanceOfClass);
+  testEquals(instanceOfClass, other);
+
+  var emptyObject = eval('Object.create({})');
+  testHashCode(emptyObject);
+  testRuntimeType(emptyObject);
+  testNoSuchMethod(emptyObject);
+  testNoSuchMethodTearoff(emptyObject);
+  testToString(emptyObject);
+  testToStringTearoff(emptyObject);
+  testEquals(emptyObject, other);
+
+  var jsNull = eval('null');
+  testHashCode(jsNull);
+  testRuntimeType(jsNull);
+  testNoSuchMethod(jsNull);
+  testNoSuchMethodTearoff(jsNull);
+  testToString(jsNull);
+  testToStringTearoff(jsNull);
+  testEquals(jsNull, other);
+}