[dart2wasm] Fix null rejection exception and add tests for conversions

Then callbacks should accept and return a JSAny?. Some JS types tests
were incomplete as well.

Change-Id: Id46e2a53f8f83ce17247fbd23d5be82f3f986f30
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/326688
Reviewed-by: Sigmund Cherem <sigmund@google.com>
Commit-Queue: Srujan Gaddam <srujzs@google.com>
This commit is contained in:
Srujan Gaddam 2023-09-21 21:41:39 +00:00 committed by Commit Queue
parent 94e4a4dfb5
commit d54f0acd02
2 changed files with 64 additions and 50 deletions

View file

@ -102,17 +102,19 @@ extension ObjectToJSBoxedDartObject on Object {
extension JSPromiseToFuture on JSPromise {
@patch
Future<JSAny?> get toDart {
final completer = Completer<JSAny>();
final success = (JSAny r) {
final completer = Completer<JSAny?>();
final success = (JSAny? r) {
return completer.complete(r);
}.toJS;
final error = (JSAny e) {
final error = (JSAny? e) {
// TODO(joshualitt): Investigate reifying `JSNull` and `JSUndefined` on
// all backends and if it is feasible, or feasible for some limited use
// cases, then we should pass [e] directly to `completeError`.
// TODO(joshualitt): Use helpers to avoid conflating `null` and `JSNull` /
// `JSUndefined`.
if (e == null) {
// Note that we pass false as a default. It's not currently possible to
// be able to differentiate between null and undefined.
return completer.completeError(js_util.NullRejectionException(false));
}
return completer.completeError(e);

View file

@ -316,86 +316,98 @@ void syncTests() {
expect(typeofEquals(definedNonNullAny, 'object'), true);
}
@JS()
external JSPromise get resolvedPromise;
@JS()
external JSPromise get rejectedPromise;
@JS()
external JSPromise getResolvedPromise();
@JS()
external JSPromise getRejectablePromise();
external JSPromise getRejectedPromise();
@JS()
external JSVoid rejectPromiseWithNull();
external JSPromise resolvePromiseWithNullOrUndefined(bool resolveWithNull);
@JS()
external JSVoid rejectPromiseWithUndefined();
external JSPromise rejectPromiseWithNullOrUndefined(bool resolveWithNull);
Future<void> asyncTests() async {
eval(r'''
globalThis.resolvedPromise = new Promise(resolve => resolve('resolved'));
globalThis.getResolvedPromise = function() {
return resolvedPromise;
return Promise.resolve('resolved');
}
globalThis.getRejectablePromise = function() {
return new Promise(function(_, reject) {
globalThis.rejectPromise = reject;
});
globalThis.getRejectedPromise = function() {
return Promise.reject(new Error('rejected'));
}
globalThis.rejectPromiseWithNull = function() {
globalThis.rejectPromise(null);
globalThis.resolvePromiseWithNullOrUndefined = function(resolveWithNull) {
return Promise.resolve(resolveWithNull ? null : undefined);
}
globalThis.rejectPromiseWithUndefined = function() {
globalThis.rejectPromise(undefined);
globalThis.rejectPromiseWithNullOrUndefined = function(rejectWithNull) {
return Promise.reject(rejectWithNull ? null : undefined);
}
''');
// [JSPromise] -> [Future].
// Test resolved
{
Future<JSAny?> f = resolvedPromise.toDart;
expect(((await f) as JSString).toDart, 'resolved');
}
// Test rejected
// TODO(joshualitt): Write a test for rejected promises that works on all
// backends.
// Test return resolved
// Test resolution.
{
Future<JSAny?> f = getResolvedPromise().toDart;
expect(((await f) as JSString).toDart, 'resolved');
}
// Test promise chaining
// Test rejection.
{
try {
await getRejectedPromise().toDart;
fail('Expected rejected promise to throw.');
} catch (e) {
final jsError = e as JSObject;
expect(jsError.toString(), 'Error: rejected');
}
}
// Test resolution Promise chaining.
{
bool didThen = false;
Future<JSAny?> f = getResolvedPromise().toDart;
f.then((resolved) {
Future<JSAny?> f = getResolvedPromise().toDart.then((resolved) {
expect((resolved as JSString).toDart, 'resolved');
didThen = true;
return null;
});
await f;
expect(didThen, true);
}
// Test rejecting promise with null should trigger an exception.
// TODO(joshualitt): `catchError` doesn't seem to clear the JS exception on
// Dart2Wasm.
//{
// bool threw = false;
// Future<JSAny?> f = getRejectablePromise().toDart;
// f.then((_) {}).catchError((e) {
// threw = true;
// expect(e is NullRejectionException, true);
// });
// rejectPromiseWithNull();
// await f;
// expect(threw, true);
//}
// Test rejection Promise chaining.
{
Future<JSAny?> f = getRejectedPromise().toDart.then((_) {
fail('Expected rejected promise to throw.');
return null;
}, onError: (e) {
final jsError = e as JSObject;
expect(jsError.toString(), 'Error: rejected');
});
await f;
}
// Test resolving promise with null and undefined.
Future<void> testResolveWithNullOrUndefined(bool resolveWithNull) async {
Future<JSAny?> f =
resolvePromiseWithNullOrUndefined(resolveWithNull).toDart;
expect(((await f) as JSAny?), null);
}
await testResolveWithNullOrUndefined(true);
await testResolveWithNullOrUndefined(false);
// Test rejecting promise with null and undefined should trigger an exception.
Future<void> testRejectionWithNullOrUndefined(bool rejectWithNull) async {
try {
await rejectPromiseWithNullOrUndefined(rejectWithNull).toDart;
fail('Expected rejected promise to throw.');
} catch (e) {
expect(e is NullRejectionException, true);
}
}
await testRejectionWithNullOrUndefined(true);
await testRejectionWithNullOrUndefined(false);
}
void main() async {