blob: 69af5ff5ca95be63b3c941d559e1d2fdda448bea [file] [log] [blame]
// Copyright (c) 2022, 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.
// Test basic functionality of `createStaticInteropMock`. Basic methods, fields
// (final and not), getters, and setters are tested along with potential
// renames.
@JS()
library functional_test_lib;
import 'dart:js_interop';
import 'package:expect/minitest.dart'; // ignore: deprecated_member_use_from_same_package
import 'package:js/js_util.dart';
@JS()
@staticInterop
class Methods {}
extension on Methods {
external String concat(String foo, String bar);
// Sanity-check that non-externals are unaffected.
String nonExternal() => 'nonExternal';
@JS('_rename')
external String rename();
external String optionalConcat(String foo, String bar,
[String boo, String? baz]);
}
@JSExport()
class MethodsDart {
String concat(String foo, String bar) => foo + bar;
String nonExternal() => 'initialized';
String _rename() => 'initialized';
String optionalConcat(String foo, String bar, [String? boo, String? baz]) =>
foo + bar + (boo ?? '') + (baz ?? '');
}
@JS()
@staticInterop
class Fields {}
extension on Fields {
external String field;
external final String finalField;
@JS('_renamedField')
external String renamedField;
@JS('_renamedFinalField')
external final String renamedFinalField;
}
@JSExport()
class FieldsDart {
String field = 'initialized';
String finalField = 'initialized';
String _renamedField = 'initialized';
final String _renamedFinalField = 'initialized';
}
@JS()
@staticInterop
class GetSet {}
extension on GetSet {
external String get getSet;
external set getSet(String val);
@JS('_renamedGetSet')
external String get renamedGetSet;
@JS('_renamedGetSet')
external set renamedGetSet(String val);
@JS('_sameNameDifferentRenameGet')
external String get sameNameDifferentRename;
@JS('_sameNameDifferentRenameSet')
external set sameNameDifferentRename(String val);
@JS('_differentNameSameRename')
external String get differentNameSameRenameGet;
@JS('_differentNameSameRename')
external set differentNameSameRenameSet(String val);
}
@JSExport()
class GetSetDart {
String getSet = 'initialized';
String _renamedGetSet = 'initialized';
String _sameNameDifferentRenameGet = 'initialized';
String _sameNameDifferentRenameSet = 'initialized';
String _differentNameSameRename = 'initialized';
}
@JS()
@staticInterop
class Supersupertype {}
@JS()
@staticInterop
class Supertype<T extends JSObject> implements Supersupertype {}
extension SupertypeExtension<T extends JSObject> on Supertype<T> {
external T superMethod(T param);
}
// Note that even though `Supertype` is instantiated with `JSArray`, we still
// require users to implement `superMethod` using the `JSObject` bound.
// Functionally, this means `superMethod` needs to accept a `JSObject` (or a
// supertype), even though an object typed `Params` can never actually call
// `superMethod` with a type less specific than `JSArray`. This is probably
// rare, but can be a bit frustrating. A similar analysis can be made for the
// type parameters on `Params`. A user may want to mock only a
// `Params<JSArray, Params>`, but we require them to mock a
// `Params<JSObject, Supertype>` as those are the bounds. Unlike the
// `superMethod` case however, we have an error check when trying to pass in
// such a type to `createStaticInteropMock` to reduce confusion.
@JS()
@staticInterop
class Params<T extends JSObject, U extends Supertype>
extends Supertype<JSArray> {
external factory Params();
}
extension ParamsExtension<T extends JSObject, U extends Supertype>
on Params<T, U> {
external T get getSet;
external set getSet(T val);
external T method(T param);
external U interopTypeMethod(U param);
external V genericMethod<V extends JSObject>(V param);
}
late JSObject _jsObject;
@JSExport()
class ParamsImpl<S extends JSObject, T extends JSObject, U extends Supertype> {
S superMethod(S param) => param;
T get getSet => _jsObject as T;
set getSet(T val) => _jsObject = val;
T method(T param) => param;
U interopTypeMethod(U param) => param;
JSObject genericMethod(JSObject param) => param;
}
void test([Object? proto]) {
var jsMethods =
createStaticInteropMock<Methods, MethodsDart>(MethodsDart(), proto);
expect(jsMethods.concat('a', 'b'), 'ab');
expect(jsMethods.nonExternal(), 'nonExternal');
expect(jsMethods.rename(), 'initialized');
expect(jsMethods.optionalConcat('a', 'b'), 'ab');
expect(jsMethods.optionalConcat('a', 'b', 'c'), 'abc');
expect(jsMethods.optionalConcat('a', 'b', 'c', 'd'), 'abcd');
var dartFields = FieldsDart();
var jsFields = createStaticInteropMock<Fields, FieldsDart>(dartFields, proto);
expect(jsFields.field, 'initialized');
expect(jsFields.finalField, 'initialized');
expect(jsFields.renamedField, 'initialized');
expect(jsFields.renamedFinalField, 'initialized');
// Modify the JS mock and check for updates in the Dart mock.
jsFields.field = 'jsModified';
jsFields.renamedField = 'jsModified';
expect(dartFields.field, 'jsModified');
expect(dartFields._renamedField, 'jsModified');
// Modify the Dart mock and check for updates in the JS mock.
dartFields.field = 'dartModified';
dartFields.finalField = 'dartModified';
dartFields._renamedField = 'dartModified';
expect(jsFields.field, 'dartModified');
expect(jsFields.finalField, 'dartModified');
expect(jsFields.renamedField, 'dartModified');
var dartGetSet = GetSetDart();
var jsGetSet = createStaticInteropMock<GetSet, GetSetDart>(dartGetSet, proto);
expect(jsGetSet.getSet, 'initialized');
expect(jsGetSet.renamedGetSet, 'initialized');
expect(jsGetSet.sameNameDifferentRename, 'initialized');
expect(jsGetSet.differentNameSameRenameGet, 'initialized');
// Modify the JS mock and check for updates in the Dart mock.
jsGetSet.getSet = 'jsModified';
jsGetSet.renamedGetSet = 'jsModified';
jsGetSet.sameNameDifferentRename = 'jsModified';
jsGetSet.differentNameSameRenameSet = 'jsModified';
expect(dartGetSet.getSet, 'jsModified');
expect(dartGetSet._renamedGetSet, 'jsModified');
expect(dartGetSet._sameNameDifferentRenameGet, 'initialized');
expect(dartGetSet._sameNameDifferentRenameSet, 'jsModified');
expect(dartGetSet._differentNameSameRename, 'jsModified');
// Modify the Dart mock and check for updates in the JS mock.
dartGetSet.getSet = 'dartModified';
dartGetSet._renamedGetSet = 'dartModified';
// Use different values to disambiguate.
dartGetSet._sameNameDifferentRenameGet = 'dartModifiedGet';
dartGetSet._sameNameDifferentRenameSet = 'dartModifiedSet';
dartGetSet._differentNameSameRename = 'dartModified';
expect(jsGetSet.getSet, 'dartModified');
expect(jsGetSet.renamedGetSet, 'dartModified');
expect(jsGetSet.sameNameDifferentRename, 'dartModifiedGet');
expect(jsGetSet.differentNameSameRenameGet, 'dartModified');
// Calling members with type parameters.
final jsParams = createStaticInteropMock<Params, ParamsImpl>(ParamsImpl());
final jsArray = JSArray();
expect(jsParams.superMethod(jsArray), jsArray);
_jsObject = newObject<JSObject>();
expect(jsParams.getSet, _jsObject);
final newJsObject = newObject<JSObject>();
jsParams.getSet = newJsObject;
expect(jsParams.getSet, newJsObject);
expect(jsParams.method(_jsObject), _jsObject);
expect(jsParams.interopTypeMethod(_jsObject as Supertype), _jsObject);
expect(jsParams.genericMethod(_jsObject), _jsObject);
expect(jsParams.genericMethod<JSObject>(_jsObject), _jsObject);
expect(jsParams.genericMethod<JSArray>(jsArray), jsArray);
}