| // 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. | |
| // Check that JS backed [List] classes behaves correctly. | |
| import 'dart:math'; | |
| import 'dart:js_interop'; | |
| import 'package:expect/expect.dart'; | |
| // We run many tests in three configurations: | |
| // 1) Test should ensure receivers for all [List] operations will be | |
| // `JSArrayImpl`. | |
| // 2) Test should ensure arguments to all [List] operations will be | |
| // `JSArrayImpl`. | |
| // 3) Test should ensure both receivers and arguments for all [List] operations | |
| // will be `JSArrayImpl`. | |
| enum TestMode { jsReceiver, jsArgument, jsReceiverAndArguments } | |
| enum Position { jsReceiver, jsArgument } | |
| bool useJSType(Position pos, TestMode mode) => | |
| (pos == Position.jsReceiver && | |
| (mode == TestMode.jsReceiver || | |
| mode == TestMode.jsReceiverAndArguments)) || | |
| (pos == Position.jsArgument && | |
| (mode == TestMode.jsArgument || | |
| mode == TestMode.jsReceiverAndArguments)); | |
| // We test two types of round-trips from Dart to JS to Dart: | |
| // - A copy that `toJS` creates that then gets wrapped by JSArrayImpl | |
| // - A proxy that `toJSProxyOrRef` creates that then gets wrapped by JSArrayImpl | |
| List<T> jsList<T extends JSAny?>(List<T> l, bool testProxy) { | |
| final arr = testProxy ? l.toJS : l.toJSProxyOrRef; | |
| return arr.toDart; | |
| } | |
| String jsString(String s) => s.toJS.toDart; | |
| extension ListJSAnyExtension on List<JSAny?> { | |
| List<double?> get toListDouble => | |
| this.map((a) => (a as JSNumber?)?.toDartDouble).toList(); | |
| } | |
| extension ListNumExtension on List<num?> { | |
| List<T> toListT<T extends JSAny?>() => | |
| this.map<T>((n) => n?.toJS as T).toList(); | |
| List<T> toJSListT<T extends JSAny?>(bool testProxy) => | |
| jsList<T>(this.toListT<T>(), testProxy); | |
| } | |
| extension NullableJSAnyExtension on JSAny? { | |
| double? get toDouble => (this as JSNumber?)?.toDartDouble; | |
| } | |
| extension JSAnyExtension on JSAny { | |
| double get toDouble => (this as JSNumber).toDartDouble; | |
| } | |
| // Test the list methods that need to take in a list using the `mode` to | |
| // indicate whether the receiver or the argument or both are `JSArrayImpl` on | |
| // dart2wasm. `T` here is the type of the list to use (either `JSAny?` or | |
| // `JSArray?`) and `testProxy` determines whether we do a round trip conversion | |
| // using a potentially proxied list or the default `toJS` conversion. | |
| void modedTests<T extends JSAny?>(TestMode mode, {required bool testProxy}) { | |
| List<T> rListDouble(List<double> l) => useJSType(Position.jsReceiver, mode) | |
| ? l.toJSListT<T>(testProxy) | |
| : l.toListT<T>(); | |
| List<T> aListDouble(List<double> l) => useJSType(Position.jsArgument, mode) | |
| ? l.toJSListT<T>(testProxy) | |
| : l.toListT<T>(); | |
| var rlist = rListDouble([1, 2, 3, 4]); | |
| // setRange | |
| rlist.setRange(0, 4, aListDouble([3, 2, 1, 0])); | |
| Expect.listEquals([3, 2, 1, 0], rlist.toListDouble); | |
| var alist = aListDouble([3, 2, 1, 0]); | |
| rlist.setRange(1, 4, alist); | |
| Expect.listEquals([3, 3, 2, 1], rlist.toListDouble); | |
| alist = aListDouble([3, 3, 2, 1]); | |
| rlist.setRange(0, 3, alist, 1); | |
| Expect.listEquals([3, 2, 1, 1], rlist.toListDouble); | |
| alist = aListDouble([3, 2, 1, 1]); | |
| rlist.setRange(0, 3, alist, 1); | |
| Expect.listEquals([2, 1, 1, 1], rlist.toListDouble); | |
| alist = aListDouble([2, 1, 1, 1]); | |
| rlist.setRange(2, 4, alist, 0); | |
| Expect.listEquals([2, 1, 2, 1], rlist.toListDouble); | |
| // setAll | |
| rlist.setAll(0, aListDouble([3, 2, 0, 1])); | |
| Expect.listEquals([3, 2, 0, 1], rlist.toListDouble); | |
| rlist.setAll(1, aListDouble([0, 1])); | |
| Expect.listEquals([3, 0, 1, 1], rlist.toListDouble); | |
| // insertAll | |
| rlist.clear(); | |
| rlist.insertAll(0, aListDouble(<double>[])); | |
| Expect.isTrue(rlist.isEmpty); | |
| rlist.insertAll(0, aListDouble([1, 2, 3])); | |
| Expect.listEquals([1, 2, 3], rlist.toListDouble); | |
| rlist.insertAll(3, aListDouble([4, 5, 6])); | |
| Expect.listEquals([1, 2, 3, 4, 5, 6], rlist.toListDouble); | |
| // addAll | |
| rlist.clear(); | |
| rlist.addAll(aListDouble(<double>[])); | |
| Expect.isTrue(rlist.isEmpty); | |
| rlist.addAll(aListDouble([1, 2, 3])); | |
| Expect.listEquals([1, 2, 3], rlist.toListDouble); | |
| rlist.addAll(aListDouble([4, 5, 6])); | |
| Expect.listEquals([1, 2, 3, 4, 5, 6], rlist.toListDouble); | |
| // replaceRange | |
| rlist.replaceRange(0, 2, aListDouble([3, 4])); | |
| Expect.listEquals([3, 4, 3, 4, 5, 6], rlist.toListDouble); | |
| rlist.replaceRange(0, 0, aListDouble([1, 1, 1])); | |
| Expect.listEquals([1, 1, 1, 3, 4, 3, 4, 5, 6], rlist.toListDouble); | |
| rlist.clear(); | |
| rlist.replaceRange(0, 0, aListDouble([1])); | |
| Expect.listEquals([1], rlist.toListDouble); | |
| // operator+ | |
| rlist = rListDouble([1, 2]); | |
| alist = aListDouble([3, 4]); | |
| Expect.listEquals([1, 2, 3, 4], (rlist + alist).toListDouble); | |
| rlist = rListDouble([]); | |
| alist = aListDouble([3, 4]); | |
| Expect.listEquals([3, 4], (rlist + alist).toListDouble); | |
| rlist = rListDouble([1, 2]); | |
| alist = aListDouble([]); | |
| Expect.listEquals([1, 2], (rlist + alist).toListDouble); | |
| } | |
| // Test the list methods that don't need to take in a list, and therefore the | |
| // mode does not matter. `T` here is the type of the list to use (either | |
| // `JSAny?` or `JSArray?`) and `testProxy` determines whether we do a | |
| // round trip conversion using a potentially proxied list or the default | |
| // `toJS` conversion. | |
| void nonModedTests<T extends JSAny?>({required bool testProxy}) { | |
| List<T> toJSList(List<num?> l) => l.toJSListT<T>(testProxy); | |
| var list = toJSList([1, 2, 3, 4]); | |
| // iteration | |
| var count = 0; | |
| for (var _ in toJSList(<num>[])) { | |
| Expect.equals(true, false); | |
| count++; | |
| } | |
| Expect.equals(0, count); | |
| for (var i in toJSList([1])) { | |
| Expect.equals(1, i?.toDouble); | |
| count++; | |
| } | |
| Expect.equals(1, count); | |
| var index = 1; | |
| for (var i in list) { | |
| Expect.equals(index++, i?.toDouble); | |
| count++; | |
| } | |
| Expect.equals(5, count); | |
| // length | |
| Expect.equals(4, list.length); | |
| list.length = 3; | |
| Expect.equals(3, list.length); | |
| Expect.listEquals([1, 2, 3], list.toListDouble); | |
| list.length = 4; | |
| Expect.listEquals([1, 2, 3, null], list.toListDouble); | |
| // operators [], []= | |
| Expect.equals(1, list[0]?.toDouble); | |
| list[0] = 5.toJS as T; | |
| Expect.equals(5, list[0]?.toDouble); | |
| list[0] = null as T; | |
| Expect.equals(null, list[0]); | |
| // indexOf, lastIndexOf | |
| list = toJSList([0, 1, 2, 3]); | |
| for (var i = 0; i < 4; i++) { | |
| Expect.equals(i, list[i]?.toDouble); | |
| Expect.equals(i, list.indexOf(i.toJS as T)); | |
| Expect.equals(i, list.lastIndexOf(i.toJS as T)); | |
| } | |
| // fillRange | |
| list = toJSList([3, 3, 3, 1]); | |
| list.fillRange(1, 3); | |
| Expect.listEquals([3, null, null, 1], list.toListDouble); | |
| list.fillRange(1, 3, 7.toJS as T); | |
| Expect.listEquals([3, 7, 7, 1], list.toListDouble); | |
| list.fillRange(0, 0, 9.toJS as T); | |
| Expect.listEquals([3, 7, 7, 1], list.toListDouble); | |
| list.fillRange(4, 4, 9.toJS as T); | |
| Expect.listEquals([3, 7, 7, 1], list.toListDouble); | |
| list.fillRange(0, 4, 9.toJS as T); | |
| Expect.listEquals([9, 9, 9, 9], list.toListDouble); | |
| // sort | |
| list.setRange(0, 4, toJSList([3, 2, 1, 0])); | |
| list.sort(); | |
| Expect.listEquals([0, 1, 2, 3], list.toListDouble); | |
| // Iterable methods | |
| list = toJSList([0, 1, 2, 3]); | |
| Expect.listEquals([0, 2, 4, 6], list.map((v) => v!.toDouble * 2).toList()); | |
| bool matchAll(T _) => true; | |
| bool matchNone(T _) => false; | |
| bool matchSome(T v) { | |
| double d = v!.toDouble; | |
| return d == 1 || d == 2; | |
| } | |
| bool matchFirst(T v) => v!.toDouble == 0; | |
| bool matchLast(T v) => v!.toDouble == 3; | |
| // where | |
| Expect.listEquals([1, 2], list.where(matchSome).toList().toListDouble); | |
| // every | |
| Expect.isTrue(list.every(matchAll)); | |
| Expect.isFalse(list.every(matchSome)); | |
| Expect.isFalse(list.every(matchNone)); | |
| // any | |
| Expect.isTrue(list.any(matchAll)); | |
| Expect.isTrue(list.any(matchSome)); | |
| Expect.isTrue(list.any(matchFirst)); | |
| Expect.isTrue(list.any(matchLast)); | |
| Expect.isFalse(list.any(matchNone)); | |
| // add, removeLast | |
| list.clear(); | |
| Expect.equals(0, list.length); | |
| Expect.isTrue(list.isEmpty); | |
| list.add(4.toJS as T); | |
| Expect.isTrue(list.isNotEmpty); | |
| Expect.equals(1, list.length); | |
| Expect.equals(4, list.removeLast()?.toDouble); | |
| Expect.equals(0, list.length); | |
| list.add(null as T); | |
| Expect.equals(null, list.removeLast()); | |
| // remove | |
| list = toJSList([1, 2, 3, 4, 4]); | |
| Expect.isTrue(list.remove(4.toJS)); | |
| Expect.listEquals([1, 2, 3, 4], list.toListDouble); | |
| // removeWhere | |
| list = toJSList([1, 2, 3, 4]); | |
| list.removeWhere((T v) => v!.toDouble % 2 == 0); | |
| Expect.listEquals([1, 3], list.toListDouble); | |
| // retainWhere | |
| list = toJSList([1, 2, 3, 4]); | |
| list.retainWhere((T v) => v!.toDouble % 2 == 0); | |
| Expect.listEquals([2, 4], list.toListDouble); | |
| // insert | |
| list.clear(); | |
| list.insert(0, 0.toJS as T); | |
| Expect.listEquals([0], list.toListDouble); | |
| list.insert(0, 1.toJS as T); | |
| Expect.listEquals([1, 0], list.toListDouble); | |
| list.insert(2, 2.toJS as T); | |
| Expect.listEquals([1, 0, 2], list.toListDouble); | |
| list.insert(0, null as T); | |
| Expect.listEquals([null, 1, 0, 2], list.toListDouble); | |
| // removeAt | |
| list.removeAt(0); | |
| Expect.listEquals([1, 0, 2], list.toListDouble); | |
| list.removeAt(1); | |
| Expect.listEquals([1, 2], list.toListDouble); | |
| // reversed | |
| list = toJSList([1, 2, 3, 4, 5, 6]); | |
| Expect.listEquals([6, 5, 4, 3, 2, 1], list.reversed.toList().toListDouble); | |
| // forEach | |
| list = toJSList([1, 2, 3]); | |
| index = 0; | |
| list.forEach((v) { | |
| index++; | |
| Expect.equals(index, v!.toDouble); | |
| }); | |
| Expect.equals(index, list.length); | |
| // join | |
| list = toJSList([0, 1, 2]); | |
| Expect.equals('0,1,2', list.join(',')); | |
| Expect.equals('0,1,2', list.join(jsString(','))); | |
| Expect.equals('012', list.join()); | |
| list = toJSList(<int>[]); | |
| Expect.equals('', list.join(',')); | |
| Expect.equals('', list.join(jsString(','))); | |
| // take | |
| list = toJSList([1, 2, 3]); | |
| Expect.listEquals([], list.take(0).toList().toListDouble); | |
| Expect.listEquals([1], list.take(1).toList().toListDouble); | |
| // takeWhile | |
| Expect.listEquals( | |
| [], | |
| list.takeWhile((n) => (n as JSNumber).toDouble > 3).toList().toListDouble, | |
| ); | |
| Expect.listEquals([ | |
| 1, | |
| 2, | |
| ], list.takeWhile((n) => (n as JSNumber).toDouble < 3).toList().toListDouble); | |
| // skip | |
| Expect.listEquals([1, 2, 3], list.skip(0).toList().toListDouble); | |
| Expect.listEquals([2, 3], list.skip(1).toList().toListDouble); | |
| // skipWhile | |
| Expect.listEquals([ | |
| 1, | |
| 2, | |
| 3, | |
| ], list.skipWhile((n) => (n as JSNumber).toDouble > 3).toList().toListDouble); | |
| Expect.listEquals( | |
| [], | |
| list.skipWhile((n) => (n as JSNumber).toDouble < 4).toList().toListDouble, | |
| ); | |
| // reduce | |
| Expect.equals( | |
| 6, | |
| (list.reduce( | |
| (a, b) => ((a as JSNumber).toDouble + (b as JSNumber).toDouble).toJS as T, | |
| ) as JSNumber).toDartDouble, | |
| ); | |
| // fold | |
| Expect.equals( | |
| 6, | |
| list.fold<double>(0, (a, b) => a + (b as JSNumber).toDouble), | |
| ); | |
| // firstWhere | |
| Expect.equals( | |
| 1, | |
| list.firstWhere((a) => (a as JSNumber).toDartDouble == 1).toDouble, | |
| ); | |
| Expect.equals( | |
| 45, | |
| list | |
| .firstWhere( | |
| (a) => (a as JSNumber).toDartDouble == 4, | |
| orElse: () => 45.toJS as T, | |
| ) | |
| .toDouble, | |
| ); | |
| // lastWhere | |
| list = toJSList([1, 2, 3, 4]); | |
| Expect.equals( | |
| 4, | |
| list.lastWhere((a) => (a as JSNumber).toDartDouble % 2 == 0).toDouble, | |
| ); | |
| Expect.equals( | |
| 45, | |
| list | |
| .lastWhere( | |
| (a) => (a as JSNumber).toDartDouble == 5, | |
| orElse: () => 45.toJS as T, | |
| ) | |
| .toDouble, | |
| ); | |
| // singleWhere | |
| Expect.equals( | |
| 1, | |
| list.singleWhere((a) => (a as JSNumber).toDartDouble == 1).toDouble, | |
| ); | |
| Expect.throwsStateError( | |
| () => list.singleWhere((a) => (a as JSNumber).toDartDouble % 2 == 0), | |
| ); | |
| Expect.equals( | |
| 45, | |
| list | |
| .singleWhere( | |
| (a) => (a as JSNumber).toDartDouble == 5, | |
| orElse: () => 45.toJS as T, | |
| ) | |
| .toDouble, | |
| ); | |
| // sublist | |
| Expect.listEquals([1, 2, 3, 4], list.sublist(0).toListDouble); | |
| Expect.listEquals([2, 3, 4], list.sublist(1).toListDouble); | |
| Expect.listEquals([], list.sublist(0, 0).toListDouble); | |
| Expect.listEquals([1], list.sublist(0, 1).toListDouble); | |
| Expect.listEquals([2], list.sublist(1, 2).toListDouble); | |
| // getRange | |
| Expect.listEquals([1, 2, 3, 4], list.getRange(0, 4).toList().toListDouble); | |
| Expect.listEquals([2, 3, 4], list.getRange(1, 4).toList().toListDouble); | |
| Expect.listEquals([2, 3], list.getRange(1, 3).toList().toListDouble); | |
| Expect.listEquals([1, 2, 3], list.getRange(0, 3).toList().toListDouble); | |
| // removeRange | |
| list.removeRange(0, 4); | |
| Expect.listEquals([], list.toListDouble); | |
| list = toJSList([1, 2, 3, 4]); | |
| list.removeRange(1, 4); | |
| Expect.listEquals([1], list.toListDouble); | |
| list = toJSList([1, 2, 3, 4]); | |
| list.removeRange(1, 3); | |
| Expect.listEquals([1, 4], list.toListDouble); | |
| list = toJSList([1, 2, 3, 4]); | |
| list.removeRange(0, 3); | |
| Expect.listEquals([4], list.toListDouble); | |
| // shuffle | |
| list = toJSList([1, 2, 3, 4]); | |
| list.shuffle(MockRandom(4)); | |
| Expect.listEquals([4, 2, 3, 1], list.toListDouble); | |
| list = toJSList([1, 2, 3, 4]); | |
| list.shuffle(); | |
| Expect.equals(4, list.length); | |
| Expect.isTrue(list.contains(1.toJS)); | |
| Expect.isTrue(list.contains(2.toJS)); | |
| Expect.isTrue(list.contains(3.toJS)); | |
| Expect.isTrue(list.contains(4.toJS)); | |
| } | |
| void runAllTests() { | |
| for (final mode in [ | |
| TestMode.jsReceiver, | |
| TestMode.jsArgument, | |
| TestMode.jsReceiverAndArguments, | |
| ]) { | |
| modedTests<JSAny?>(mode, testProxy: true); | |
| modedTests<JSAny?>(mode, testProxy: false); | |
| modedTests<JSNumber?>(mode, testProxy: true); | |
| modedTests<JSNumber?>(mode, testProxy: false); | |
| } | |
| nonModedTests<JSAny?>(testProxy: true); | |
| nonModedTests<JSAny?>(testProxy: false); | |
| nonModedTests<JSNumber?>(testProxy: true); | |
| nonModedTests<JSNumber?>(testProxy: false); | |
| } | |
| void main() { | |
| runAllTests(); | |
| } | |
| // Will swap the first and last items in a list, while leaving the middle | |
| // elements in place because they will be swapped and then swapped back as | |
| // shuffle iterates through the list. | |
| class MockRandom implements Random { | |
| int index = 0; | |
| final int max; | |
| MockRandom(this.max); | |
| int nextInt(int limit) { | |
| return index++; | |
| } | |
| double nextDouble() => throw 'Not supported'; | |
| bool nextBool() => throw 'Not supported'; | |
| } |