mirror of
https://github.com/dart-lang/sdk
synced 2024-10-14 17:40:04 +00:00
3f275d858f
Shorthand of hasProperty for the common case. Reworded some docs to be less strict about not using this library (since has and hasProperty have no good alternatives). CoreLibraryReviewExempt: Backend-specific library. Change-Id: I7e82385fd18ff4fbc83266e9b08c9d854ca62a78 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/343681 Reviewed-by: Sigmund Cherem <sigmund@google.com>
455 lines
15 KiB
Dart
455 lines
15 KiB
Dart
// 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 'dart:js_interop';
|
|
import 'dart:js_interop_unsafe';
|
|
import 'dart:typed_data';
|
|
|
|
import 'package:expect/expect.dart';
|
|
|
|
@JS()
|
|
external void eval(String code);
|
|
|
|
void createObjectTest() {
|
|
JSObject o = JSObject();
|
|
void testHasGetSet(String property, String? value) {
|
|
// has/[]/[]=
|
|
Expect.isFalse(o.has(property));
|
|
o[property] = value?.toJS;
|
|
Expect.isTrue(o.has(property));
|
|
Expect.equals(value, (o[property] as JSString?)?.toDart);
|
|
Expect.isTrue(o.delete(property.toJS).toDart);
|
|
|
|
// Weirdly enough, delete almost always returns true.
|
|
Expect.isTrue(o.delete(property.toJS).toDart);
|
|
|
|
// hasProperty/getProperty/setProperty
|
|
Expect.isFalse(o.hasProperty(property.toJS).toDart);
|
|
o.setProperty(property.toJS, value?.toJS);
|
|
Expect.isTrue(o.hasProperty(property.toJS).toDart);
|
|
Expect.equals(value, (o.getProperty(property.toJS) as JSString?)?.toDart);
|
|
Expect.isTrue(o.delete(property.toJS).toDart);
|
|
}
|
|
|
|
testHasGetSet('foo', 'bar');
|
|
testHasGetSet('baz', null);
|
|
}
|
|
|
|
void equalTest() {
|
|
// Different objects aren't equal.
|
|
{
|
|
JSObject o1 = JSObject();
|
|
JSObject o2 = JSObject();
|
|
Expect.notEquals(o1, o2);
|
|
}
|
|
|
|
{
|
|
eval(r'''
|
|
function JSClass() {}
|
|
|
|
globalThis.boolData = true;
|
|
globalThis.boolData2 = true;
|
|
globalThis.numData = 4;
|
|
globalThis.numData2 = 4;
|
|
globalThis.arrData = [1, 2, 3];
|
|
globalThis.strData = 'foo';
|
|
globalThis.strData2 = 'foo';
|
|
globalThis.funcData = function JSClass() {}
|
|
globalThis.JSClass = new globalThis.funcData();
|
|
''');
|
|
JSObject gc = globalContext;
|
|
void test(String propertyName, bool testCanonicalization) {
|
|
Expect.equals(gc[propertyName], gc[propertyName]);
|
|
if (testCanonicalization) {
|
|
Expect.equals(gc[propertyName], gc[propertyName + "2"]);
|
|
}
|
|
}
|
|
|
|
test("boolData", true);
|
|
test("numData", true);
|
|
test("arrData", false);
|
|
test("strData", true);
|
|
test("funcData", false);
|
|
test("JSClass", false);
|
|
}
|
|
}
|
|
|
|
// TODO(srujzs): This helper is no longer in dart:js_interop_unsafe. Move this
|
|
// test.
|
|
void typeofTest() {
|
|
eval(r'''
|
|
globalThis.b = true;
|
|
globalThis.n = 4;
|
|
globalThis.str = 'foo';
|
|
globalThis.f = function foo() {}
|
|
globalThis.o = {};
|
|
globalThis.nil = null;
|
|
globalThis.u = undefined;
|
|
globalThis.sym = Symbol('sym');
|
|
''');
|
|
|
|
final types = {
|
|
'boolean',
|
|
'number',
|
|
'string',
|
|
'function',
|
|
'object',
|
|
'undefined',
|
|
'symbol'
|
|
};
|
|
void test(String property, String expectedType) {
|
|
Expect.isTrue(globalContext[property].typeofEquals(expectedType));
|
|
for (final type in types) {
|
|
if (type != expectedType) {
|
|
Expect.isFalse(globalContext[property].typeofEquals(type));
|
|
}
|
|
}
|
|
}
|
|
|
|
test('b', 'boolean');
|
|
test('n', 'number');
|
|
test('str', 'string');
|
|
test('f', 'function');
|
|
test('o', 'object');
|
|
test('nil', 'object');
|
|
// TODO(joshualitt): Test for `undefined` when we it can flow into `JSAny?`.
|
|
// test('u', 'undefined');
|
|
test('sym', 'symbol');
|
|
}
|
|
|
|
// TODO(srujzs): This helper is no longer in dart:js_interop_unsafe. Move this
|
|
// test.
|
|
void instanceOfTest() {
|
|
eval(r'''
|
|
globalThis.JSClass1 = function() {}
|
|
globalThis.JSClass2 = function() {}
|
|
|
|
globalThis.obj = new JSClass1();
|
|
''');
|
|
JSObject gc = globalContext;
|
|
JSObject obj = gc['obj'] as JSObject;
|
|
JSFunction jsClass1Constructor = gc['JSClass1'] as JSFunction;
|
|
JSFunction jsClass2Constructor = gc['JSClass2'] as JSFunction;
|
|
Expect.isTrue(obj.instanceof(jsClass1Constructor));
|
|
Expect.isFalse(obj.instanceof(jsClass2Constructor));
|
|
Expect.isFalse(0.toJS.instanceof(jsClass2Constructor));
|
|
Expect.isTrue(obj.instanceOfString('JSClass1'));
|
|
Expect.isFalse(obj.instanceOfString('JSClass2'));
|
|
Expect.isFalse(0.toJS.instanceOfString('JSClass1'));
|
|
}
|
|
|
|
void _expectIterableEquals(Iterable<Object?> l, Iterable<Object?> r) {
|
|
final lIt = l.iterator;
|
|
final rIt = r.iterator;
|
|
while (lIt.moveNext()) {
|
|
Expect.isTrue(rIt.moveNext());
|
|
_expectRecEquals(lIt.current, rIt.current);
|
|
}
|
|
Expect.isFalse(rIt.moveNext());
|
|
}
|
|
|
|
void _expectRecEquals(Object? l, Object? r) {
|
|
if (l is Iterable && r is Iterable) {
|
|
_expectIterableEquals(l, r);
|
|
} else if (l is Map && r is Map) {
|
|
_expectIterableEquals(l.keys, r.keys);
|
|
for (final key in l.keys) {
|
|
_expectRecEquals(l[key], r[key]);
|
|
}
|
|
} else {
|
|
Expect.equals(l, r);
|
|
}
|
|
}
|
|
|
|
void methodsAndConstructorsTest() {
|
|
eval(r'''
|
|
function JSClass(c) {
|
|
this.c = c;
|
|
this.sum = (a, b) => {
|
|
console.log(a + ' ' + b);
|
|
return `${a}${b}${this.c}`;
|
|
}
|
|
}
|
|
globalThis.JSClass = JSClass;
|
|
''');
|
|
JSObject gc = globalContext;
|
|
JSFunction constructor = gc['JSClass'] as JSFunction;
|
|
|
|
// Var args one arg
|
|
JSObject jsObj1 =
|
|
constructor.callAsConstructorVarArgs<JSObject>(<JSAny?>['world!'.toJS]);
|
|
Expect.equals(
|
|
'hello world!',
|
|
jsObj1.callMethodVarArgs<JSString>(
|
|
'sum'.toJS, <JSAny?>['hello'.toJS, ' '.toJS]).toDart);
|
|
Expect.equals(
|
|
'helloundefinedworld!',
|
|
jsObj1.callMethodVarArgs<JSString>(
|
|
'sum'.toJS, <JSAny?>['hello'.toJS]).toDart);
|
|
Expect.equals('undefinedundefinedworld!',
|
|
jsObj1.callMethodVarArgs<JSString>('sum'.toJS, <JSAny?>[]).toDart);
|
|
Expect.equals('undefinedundefinedworld!',
|
|
jsObj1.callMethodVarArgs<JSString>('sum'.toJS).toDart);
|
|
Expect.equals(
|
|
'nullnullworld!',
|
|
jsObj1.callMethodVarArgs<JSString>(
|
|
'sum'.toJS, <JSAny?>[null, null]).toDart);
|
|
// Var args no args
|
|
jsObj1 = constructor.callAsConstructorVarArgs<JSObject>();
|
|
Expect.equals(jsObj1['c'], null);
|
|
|
|
// Fixed args one arg
|
|
jsObj1 = constructor.callAsConstructor<JSObject>('world!'.toJS);
|
|
Expect.equals('hello world!',
|
|
jsObj1.callMethod<JSString>('sum'.toJS, 'hello'.toJS, ' '.toJS).toDart);
|
|
Expect.equals('helloundefinedworld!',
|
|
jsObj1.callMethod<JSString>('sum'.toJS, 'hello'.toJS).toDart);
|
|
Expect.equals('undefinedundefinedworld!',
|
|
jsObj1.callMethod<JSString>('sum'.toJS, null).toDart);
|
|
Expect.equals('undefinedundefinedworld!',
|
|
jsObj1.callMethod<JSString>('sum'.toJS).toDart);
|
|
// Fixed args no args
|
|
jsObj1 = constructor.callAsConstructor<JSObject>();
|
|
Expect.equals(jsObj1['c'], null);
|
|
}
|
|
|
|
void deepConversionsTest() {
|
|
// Dart to JS.
|
|
Expect.isNull(null.jsify().dartify());
|
|
Expect.equals(true, true.jsify().dartify());
|
|
Expect.equals(2.0, 2.0.jsify().dartify());
|
|
Expect.equals('foo', 'foo'.jsify().dartify());
|
|
_expectRecEquals(
|
|
['a', 'b', 'c'], ['a', 'b', 'c'].jsify().dartify() as List<Object?>);
|
|
_expectRecEquals(
|
|
{
|
|
'null': 'foo',
|
|
'foo': null,
|
|
'a': 1,
|
|
'b': true,
|
|
'c': [1, 2, 3, null],
|
|
'd': 'foo',
|
|
'e': {
|
|
'f': 2,
|
|
'g': [2, 4, 6]
|
|
},
|
|
},
|
|
{
|
|
'null': 'foo',
|
|
'foo': null,
|
|
'a': 1,
|
|
'b': true,
|
|
'c': [1, 2, 3, null],
|
|
'd': 'foo',
|
|
'e': {
|
|
'f': 2,
|
|
'g': [2, 4, 6]
|
|
},
|
|
}.jsify().dartify());
|
|
List<Object?> l = Int8List.fromList(<int>[-128, 0, 127]);
|
|
_expectIterableEquals(l, l.jsify().dartify() as Int8List);
|
|
l = Uint8List.fromList([-1, 0, 255, 256]);
|
|
_expectIterableEquals(l, l.jsify().dartify() as Uint8List);
|
|
l = Uint8ClampedList.fromList([-1, 0, 255, 256]);
|
|
_expectIterableEquals(l, l.jsify().dartify() as Uint8ClampedList);
|
|
l = Int16List.fromList([-32769, -32768, 0, 32767, 32768]);
|
|
_expectIterableEquals(l, l.jsify().dartify() as Int16List);
|
|
l = Uint16List.fromList([-1, 0, 65535, 65536]);
|
|
_expectIterableEquals(l, l.jsify().dartify() as Uint16List);
|
|
l = Int32List.fromList([-2147483648, 0, 2147483647]);
|
|
_expectIterableEquals(l, l.jsify().dartify() as Int32List);
|
|
l = Uint32List.fromList([-1, 0, 4294967295, 4294967296]);
|
|
_expectIterableEquals(l, l.jsify().dartify() as Uint32List);
|
|
l = Float32List.fromList([-1000.488, -0.00001, 0.0001, 10004.888]);
|
|
_expectIterableEquals(l, l.jsify().dartify() as Float32List);
|
|
l = Float64List.fromList([-1000.488, -0.00001, 0.0001, 10004.888]);
|
|
_expectIterableEquals(l, l.jsify().dartify() as Float64List);
|
|
ByteBuffer buffer = Uint8List.fromList([0, 1, 2, 3]).buffer;
|
|
_expectIterableEquals(buffer.asUint8List(),
|
|
(buffer.jsify().dartify() as ByteBuffer).asUint8List());
|
|
ByteData byteData = ByteData.view(buffer);
|
|
_expectIterableEquals(byteData.buffer.asUint8List(),
|
|
(byteData.jsify().dartify() as ByteData).buffer.asUint8List());
|
|
|
|
// JS to Dart.
|
|
eval(r'''
|
|
globalThis.a = null;
|
|
globalThis.b = 'foo';
|
|
globalThis.c = ['a', 'b', 'c'];
|
|
globalThis.d = 2.5;
|
|
globalThis.e = true;
|
|
globalThis.f = function () { return 'hello world'; };
|
|
globalThis.g = {
|
|
null: 'foo',
|
|
'foo': null,
|
|
'a': 1,
|
|
'b': true,
|
|
'c': [1, 2, 3, null],
|
|
'd': 'foo',
|
|
'e': {'f': 2, 'g': [2, 4, 6]},
|
|
};
|
|
globalThis.invoke = function (f) { return f(); }
|
|
globalThis.rec = {};
|
|
globalThis.rec = {'a': rec};
|
|
globalThis.int8Array = new Int8Array([-128, 0, 127]);
|
|
globalThis.uint8Array = new Uint8Array([-1, 0, 255, 256]);
|
|
globalThis.uint8ClampedArray = new Uint8ClampedArray([-1, 0, 255, 256]);
|
|
globalThis.int16Array = new Int16Array([-32769, -32768, 0, 32767, 32768]);
|
|
globalThis.uint16Array = new Uint16Array([-1, 0, 65535, 65536]);
|
|
globalThis.int32Array = new Int32Array([-2147483648, 0, 2147483647]);
|
|
globalThis.uint32Array = new Uint32Array([-1, 0, 4294967295, 4294967296]);
|
|
globalThis.float32Array = new Float32Array([-1000.488, -0.00001, 0.0001,
|
|
10004.888]);
|
|
globalThis.float64Array = new Float64Array([-1000.488, -0.00001, 0.0001,
|
|
10004.888]);
|
|
globalThis.arrayBuffer = globalThis.uint8Array.buffer;
|
|
globalThis.dataView = new DataView(globalThis.arrayBuffer);
|
|
globalThis.implicitExplicit = [
|
|
{'foo': 'bar'},
|
|
[1, 2, 3, {'baz': 'boo'}],
|
|
];
|
|
let keyObject = function () {};
|
|
globalThis.keyObject1 = keyObject;
|
|
globalThis.keyObject2 = keyObject;
|
|
''');
|
|
JSObject gc = globalContext;
|
|
Expect.isNull(gc['a']);
|
|
Expect.equals('foo', gc.getProperty<JSString>('b'.toJS).toDart);
|
|
_expectRecEquals(
|
|
['a', 'b', 'c'],
|
|
gc
|
|
.getProperty<JSArray>('c'.toJS)
|
|
.toDart
|
|
.map((JSAny? o) => (o as JSString).toDart));
|
|
Expect.equals(2.5, gc.getProperty<JSNumber>('d'.toJS).toDartDouble);
|
|
Expect.equals(true, gc.getProperty<JSBoolean>('e'.toJS).toDart);
|
|
_expectRecEquals({
|
|
'null': 'foo',
|
|
'foo': null,
|
|
'a': 1,
|
|
'b': true,
|
|
'c': [1, 2, 3, null],
|
|
'd': 'foo',
|
|
'e': {
|
|
'f': 2,
|
|
'g': [2, 4, 6]
|
|
},
|
|
}, gc.getProperty('g'.toJS).dartify());
|
|
_expectRecEquals({
|
|
'a': {},
|
|
}, gc.getProperty('rec'.toJS).dartify());
|
|
|
|
_expectIterableEquals(Int8List.fromList(<int>[-128, 0, 127]),
|
|
gc.getProperty<JSInt8Array>('int8Array'.toJS).toDart);
|
|
_expectIterableEquals(Uint8List.fromList([-1, 0, 255, 256]),
|
|
gc.getProperty<JSUint8Array>('uint8Array'.toJS).toDart);
|
|
_expectIterableEquals(Uint8ClampedList.fromList([-1, 0, 255, 256]),
|
|
gc.getProperty<JSUint8ClampedArray>('uint8ClampedArray'.toJS).toDart);
|
|
_expectIterableEquals(Int16List.fromList([-32769, -32768, 0, 32767, 32768]),
|
|
gc.getProperty<JSInt16Array>('int16Array'.toJS).toDart);
|
|
_expectIterableEquals(Uint16List.fromList([-1, 0, 65535, 65536]),
|
|
gc.getProperty<JSUint16Array>('uint16Array'.toJS).toDart);
|
|
_expectIterableEquals(Int32List.fromList([-2147483648, 0, 2147483647]),
|
|
gc.getProperty<JSInt32Array>('int32Array'.toJS).toDart);
|
|
_expectIterableEquals(Uint32List.fromList([-1, 0, 4294967295, 4294967296]),
|
|
gc.getProperty<JSUint32Array>('uint32Array'.toJS).toDart);
|
|
_expectIterableEquals(
|
|
Float32List.fromList([-1000.488, -0.00001, 0.0001, 10004.888]),
|
|
gc.getProperty<JSFloat32Array>('float32Array'.toJS).toDart);
|
|
_expectIterableEquals(
|
|
Float64List.fromList([-1000.488, -0.00001, 0.0001, 10004.888]),
|
|
gc.getProperty<JSFloat64Array>('float64Array'.toJS).toDart);
|
|
_expectIterableEquals(Uint8List.fromList([-1, 0, 255, 256]),
|
|
gc.getProperty<JSArrayBuffer>('arrayBuffer'.toJS).toDart.asUint8List());
|
|
_expectIterableEquals(Uint8List.fromList([-1, 0, 255, 256]),
|
|
gc.getProperty<JSDataView>('dataView'.toJS).toDart.buffer.asUint8List());
|
|
|
|
// Confirm a function that takes a roundtrip remains a function.
|
|
JSFunction foo = gc['f'].dartify() as JSFunction;
|
|
Expect.equals(
|
|
'hello world', gc.callMethod<JSString>('invoke'.toJS, foo).toDart);
|
|
|
|
// Confirm arrays, which need to be converted implicitly, are still
|
|
// recursively converted by dartify() when desired.
|
|
_expectIterableEquals([
|
|
{'foo': 'bar'},
|
|
[
|
|
1,
|
|
2,
|
|
3,
|
|
{'baz': 'boo'}
|
|
],
|
|
], gc['implicitExplicit'].dartify() as Iterable);
|
|
|
|
// Test that JS objects behave as expected in Map / Set.
|
|
Set<Object?> set = {};
|
|
JSAny? key1 = gc['keyObject1'];
|
|
JSAny? key2 = gc['keyObject2'];
|
|
Expect.isTrue(set.add(key1));
|
|
Expect.isTrue(set.contains(key1));
|
|
Expect.isFalse(set.add(key2));
|
|
Expect.isTrue(set.contains(key2));
|
|
Expect.equals(1, set.length);
|
|
|
|
Map<Object?, Object?> map = {};
|
|
map[key1] = 'foo';
|
|
map[key2] = 'bar';
|
|
Expect.equals(1, map.length);
|
|
Expect.equals('bar', map[key1]);
|
|
}
|
|
|
|
@JS('Symbol')
|
|
@staticInterop
|
|
class _JSSymbol {
|
|
@JS('for')
|
|
external static JSAny _for(JSString s);
|
|
external static JSString keyFor(JSAny s);
|
|
}
|
|
|
|
@JS()
|
|
external JSAny get symbol;
|
|
|
|
@JS()
|
|
external JSAny get symbol2;
|
|
|
|
@JS()
|
|
external JSString methodWithSymbol(JSAny s);
|
|
|
|
void symbolTest() {
|
|
eval(r'''
|
|
var s1 = Symbol.for('symbol');
|
|
globalThis.symbol = s1;
|
|
globalThis[s1] = 'boo';
|
|
globalThis.methodWithSymbol = function(s) {
|
|
return Symbol.keyFor(s);
|
|
}
|
|
var symbol2 = Symbol.for('symbolMethod');
|
|
globalThis[symbol2] = function() {
|
|
return 'hello world';
|
|
}
|
|
globalThis.symbol2 = symbol2;
|
|
''');
|
|
JSObject gc = globalContext;
|
|
Expect.equals(
|
|
_JSSymbol.keyFor(_JSSymbol._for('symbol'.toJS)).toDart, 'symbol');
|
|
Expect.equals(
|
|
gc.getProperty<JSString>(gc.getProperty<JSAny>('symbol'.toJS)).toDart,
|
|
'boo');
|
|
Expect.equals(methodWithSymbol(symbol).toDart, 'symbol');
|
|
Expect.equals(_JSSymbol.keyFor(symbol).toDart, 'symbol');
|
|
Expect.equals(
|
|
_JSSymbol.keyFor(gc.getProperty<JSAny>('symbol'.toJS)).toDart, 'symbol');
|
|
Expect.equals(gc.callMethod<JSString>(symbol2).toDart, 'hello world');
|
|
}
|
|
|
|
void main() {
|
|
createObjectTest();
|
|
equalTest();
|
|
typeofTest();
|
|
instanceOfTest();
|
|
methodsAndConstructorsTest();
|
|
deepConversionsTest();
|
|
symbolTest();
|
|
}
|