blob: ce8bc163c42a66b12dc4df91b484a7da6c443f5a [file] [log] [blame]
// 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 'dart:js_interop';
import 'dart:js_interop_unsafe';
import 'dart:typed_data';
import 'package:expect/expect.dart';
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);
}
}
@JS('SharedArrayBuffer')
external JSAny? get _sharedArrayBufferConstructor;
bool supportsSharedArrayBuffer = _sharedArrayBufferConstructor != null;
@JS('SharedArrayBuffer')
extension type JSSharedArrayBuffer._(JSObject _) implements JSObject {
external JSSharedArrayBuffer(int length);
}
@JS('Uint8Array')
extension type JSUint8ArrayShared._(JSUint8Array _) implements JSUint8Array {
external JSUint8ArrayShared(JSSharedArrayBuffer buf);
}
extension on JSUint8Array {
external int operator [](int index);
external operator []=(int index, int value);
external JSObject get buffer;
}
@JS()
external void eval(String code);
void main() {
// Dart to JS.
Expect.isNull(null.jsify().dartify());
Expect.isTrue(true.jsify().isA<JSBoolean>());
Expect.equals(true, true.jsify().dartify() as bool);
Expect.isTrue(2.0.jsify().isA<JSNumber>());
Expect.equals(2.0, 2.0.jsify().dartify() as double);
Expect.isTrue(0.jsify().isA<JSNumber>());
Expect.equals(0.0, 0.jsify().dartify() as double);
Expect.isTrue('foo'.jsify().isA<JSString>());
Expect.equals('foo', 'foo'.jsify().dartify() as String);
List<Object?> l = ['a', 'b', 'c'];
Expect.isTrue(l.jsify().isA<JSArray>());
_expectRecEquals(l, l.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(),
);
l = Int8List.fromList(<int>[-128, 0, 127]);
Expect.isTrue(l.jsify().isA<JSInt8Array>());
_expectIterableEquals(l, l.jsify().dartify() as Int8List);
l = Uint8List.fromList([-1, 0, 255, 256]);
Expect.isTrue(l.jsify().isA<JSUint8Array>());
_expectIterableEquals(l, l.jsify().dartify() as Uint8List);
l = Uint8ClampedList.fromList([-1, 0, 255, 256]);
Expect.isTrue(l.jsify().isA<JSUint8ClampedArray>());
_expectIterableEquals(l, l.jsify().dartify() as Uint8ClampedList);
l = Int16List.fromList([-32769, -32768, 0, 32767, 32768]);
Expect.isTrue(l.jsify().isA<JSInt16Array>());
_expectIterableEquals(l, l.jsify().dartify() as Int16List);
l = Uint16List.fromList([-1, 0, 65535, 65536]);
Expect.isTrue(l.jsify().isA<JSUint16Array>());
_expectIterableEquals(l, l.jsify().dartify() as Uint16List);
l = Int32List.fromList([-2147483648, 0, 2147483647]);
Expect.isTrue(l.jsify().isA<JSInt32Array>());
_expectIterableEquals(l, l.jsify().dartify() as Int32List);
l = Uint32List.fromList([-1, 0, 4294967295, 4294967296]);
Expect.isTrue(l.jsify().isA<JSUint32Array>());
_expectIterableEquals(l, l.jsify().dartify() as Uint32List);
l = Float32List.fromList([-1000.488, -0.00001, 0.0001, 10004.888]);
Expect.isTrue(l.jsify().isA<JSFloat32Array>());
_expectIterableEquals(l, l.jsify().dartify() as Float32List);
l = Float64List.fromList([-1000.488, -0.00001, 0.0001, 10004.888]);
Expect.isTrue(l.jsify().isA<JSFloat64Array>());
_expectIterableEquals(l, l.jsify().dartify() as Float64List);
ByteBuffer buffer = Uint8List.fromList([0, 1, 2, 3]).buffer;
Expect.isTrue(buffer.jsify().isA<JSArrayBuffer>());
final uint8List = (buffer.jsify().dartify() as ByteBuffer).asUint8List();
_expectIterableEquals(buffer.asUint8List(), uint8List);
Expect.isTrue(uint8List.toJS.buffer.isA<JSArrayBuffer>());
// TODO(https://github.com/dart-lang/sdk/issues/61043): Support this in the
// test runner.
if (supportsSharedArrayBuffer) {
// Test that `SharedArrayBuffer`s are dartified to `TypedData` correctly.
final sharedArrayBuffer = JSSharedArrayBuffer(1);
final uint8ArrayShared = JSUint8ArrayShared(sharedArrayBuffer);
uint8ArrayShared[0] = 42;
final uint8ListShared = uint8ArrayShared.dartify() as Uint8List;
Expect.equals(uint8ArrayShared[0], uint8ListShared[0]);
Expect.isTrue(uint8ListShared.toJS.buffer.isA<JSSharedArrayBuffer>());
}
ByteData byteData = ByteData.view(buffer);
Expect.isTrue(byteData.jsify().isA<JSDataView>());
_expectIterableEquals(
byteData.buffer.asUint8List(),
(byteData.jsify().dartify() as ByteData).buffer.asUint8List(),
);
// JS to Dart.
eval(r'''
globalThis.a = null;
globalThis.b = undefined;
globalThis.c = 'foo';
globalThis.d = ['a', 'b', 'c'];
globalThis.e = 2.5;
globalThis.f = true;
globalThis.g = function () { return 'hello world'; };
globalThis.h = {
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;
globalThis.symbol = Symbol('symbol');
''');
JSObject gc = globalContext;
Expect.isNull(gc['a']);
Expect.isNull(gc['b']);
Expect.equals('foo', gc['c'].dartify() as String);
_expectRecEquals(['a', 'b', 'c'], gc['d'].dartify() as List<Object?>);
Expect.equals(2.5, gc['e'].dartify() as double);
Expect.equals(true, gc['f'].dartify() as bool);
_expectRecEquals({
'null': 'foo',
'foo': null,
'a': 1,
'b': true,
'c': [1, 2, 3, null],
'd': 'foo',
'e': {
'f': 2,
'g': [2, 4, 6],
},
}, gc['h'].dartify() as Map<Object?, Object?>);
_expectRecEquals({'a': {}}, gc['rec'].dartify() as Map<Object?, Object?>);
_expectIterableEquals(
Int8List.fromList(<int>[-128, 0, 127]),
gc['int8Array'].dartify() as Int8List,
);
_expectIterableEquals(
Uint8List.fromList([-1, 0, 255, 256]),
gc['uint8Array'].dartify() as Uint8List,
);
_expectIterableEquals(
Uint8ClampedList.fromList([-1, 0, 255, 256]),
gc['uint8ClampedArray'].dartify() as Uint8ClampedList,
);
_expectIterableEquals(
Int16List.fromList([-32769, -32768, 0, 32767, 32768]),
gc['int16Array'].dartify() as Int16List,
);
_expectIterableEquals(
Uint16List.fromList([-1, 0, 65535, 65536]),
gc['uint16Array'].dartify() as Uint16List,
);
_expectIterableEquals(
Int32List.fromList([-2147483648, 0, 2147483647]),
gc['int32Array'].dartify() as Int32List,
);
_expectIterableEquals(
Uint32List.fromList([-1, 0, 4294967295, 4294967296]),
gc['uint32Array'].dartify() as Uint32List,
);
_expectIterableEquals(
Float32List.fromList([-1000.488, -0.00001, 0.0001, 10004.888]),
gc['float32Array'].dartify() as Float32List,
);
_expectIterableEquals(
Float64List.fromList([-1000.488, -0.00001, 0.0001, 10004.888]),
gc['float64Array'].dartify() as Float64List,
);
_expectIterableEquals(
Uint8List.fromList([-1, 0, 255, 256]),
(gc['arrayBuffer'].dartify() as ByteBuffer).asUint8List(),
);
_expectIterableEquals(
Uint8List.fromList([-1, 0, 255, 256]),
(gc['dataView'].dartify() as ByteData).buffer.asUint8List(),
);
// Confirm a function that takes a roundtrip remains a function.
// TODO(srujzs): Delete this test after we remove this conversion.
JSFunction foo = gc['g'].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 List<Object?>);
// 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]);
// Test that unrelated values are left alone/simply boxed.
Expect.isTrue((gc['symbol'].dartify() as JSAny).isA<JSSymbol>());
Expect.equals(gc['symbol'], gc['symbol'].dartify());
}