// Copyright (c) 2018, 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 "package:expect/expect.dart"; import 'dart:collection'; import 'dart:convert' show json; Map newJsonMap() => json.decode('{}'); Map newJsonMapCustomReviver() => json.decode('{}', reviver: (key, value) => value); void main() { test({}); test(new LinkedHashMap()); test(new HashMap()); test(new LinkedHashMap.identity()); test(new HashMap.identity()); test(new MapView(new HashMap())); test(new MapBaseMap()); test(new MapMixinMap()); testNonNull(newJsonMap()); testNonNull(newJsonMapCustomReviver()); testNonNull(new SplayTreeMap()); testNonNull(new SplayTreeMap((a, b) => Comparable.compare(a!, b!))); testNonNull(new MapView(new SplayTreeMap())); } void test(Map map) { testNonNull(map); // Also works with null keys and values (omitted for splay-tree based maps) map.clear(); map.update(null, unreachable, ifAbsent: () => null); Expect.mapEquals({null: null}, map); map.update(null, (v) => "$v", ifAbsent: unreachable); Expect.mapEquals({null: "null"}, map); map.update(null, (v) => null, ifAbsent: unreachable); Expect.mapEquals({null: null}, map); } void testNonNull(Map map) { // Only use literal String keys since JSON maps only accept strings, // and literals works with identity-maps, and it's comparable for SplayTreeMap // maps. Expect.mapEquals({}, map); map.update("key1", unreachable, ifAbsent: () => 42); Expect.mapEquals({"key1": 42}, map); map.clear(); map["key1"] = 42; map.update("key1", (v) => 1 + (v as int), ifAbsent: unreachable); Expect.mapEquals({"key1": 43}, map); map.clear(); // Operations on maps with multiple elements. var multi = { "k1": 1, "k2": 2, "k3": 3, "k4": 4, "k5": 5, "k6": 6, "k7": 7, "k8": 8, "k9": 9, "k10": 10, }; map.addAll(multi); Expect.mapEquals(multi, map); map.update("k3", (v) => 13); map.update("k6", (v) => 16); map.update("k11", unreachable, ifAbsent: () => 21); Expect.mapEquals({ "k1": 1, "k2": 2, "k3": 13, "k4": 4, "k5": 5, "k6": 16, "k7": 7, "k8": 8, "k9": 9, "k10": 10, "k11": 21, }, map); map.clear(); map.updateAll((k, v) => throw "unreachable"); Expect.mapEquals({}, map); map.addAll(multi); map.updateAll((k, v) => "$k:$v"); Expect.mapEquals({ "k1": "k1:1", "k2": "k2:2", "k3": "k3:3", "k4": "k4:4", "k5": "k5:5", "k6": "k6:6", "k7": "k7:7", "k8": "k8:8", "k9": "k9:9", "k10": "k10:10", }, map); map.clear(); Expect.throws( () => map.update("key1", unreachable, ifAbsent: () => throw "expected"), (t) => t == "expected"); map["key1"] = 42; Expect.throws(() => map.update("key1", (_) => throw "expected"), (t) => t == "expected"); // No ifAbsent means throw if key not there. Expect.throws(() => map.update("key-not", unreachable), (e) => e is Error); // Works with null values. map.clear(); map.update("key1", unreachable, ifAbsent: () => null); Expect.mapEquals({"key1": null}, map); map.update("key1", (v) => "$v", ifAbsent: unreachable); Expect.mapEquals({"key1": "null"}, map); map.update("key1", (v) => null, ifAbsent: unreachable); Expect.mapEquals({"key1": null}, map); } // Slow implementation of Map based on MapBase. abstract class MapBaseOperations { final List _keys = []; final List _values = []; int _modCount = 0; V? operator [](Object? key) { int index = _keys.indexOf(key); if (index < 0) return null; return _values[index]; } Iterable get keys => new TestKeyIterable(this); void operator []=(K key, V value) { int index = _keys.indexOf(key); if (index >= 0) { _values[index] = value; } else { _modCount++; _keys.add(key); _values.add(value); } } V? remove(Object? key) { int index = _keys.indexOf(key); if (index >= 0) { var result = _values[index]; key = _keys.removeLast(); var value = _values.removeLast(); if (index != _keys.length) { _keys[index] = key; _values[index] = value; } _modCount++; return result; } return null; } void clear() { // Clear cannot be based on remove, since remove won't remove keys that // are not equal to themselves. It will fail the testNaNKeys test. _keys.clear(); _values.clear(); _modCount++; } } class MapBaseMap = MapBase with MapBaseOperations; class MapMixinMap = MapBaseOperations with MapMixin; class TestKeyIterable extends IterableBase { final _map; TestKeyIterable(this._map); int get length => _map._keys.length; Iterator get iterator => new TestKeyIterator(_map); } class TestKeyIterator implements Iterator { final _map; final int _modCount; int _index = 0; var _current; TestKeyIterator(map) : _map = map, _modCount = map._modCount; bool moveNext() { if (_modCount != _map._modCount) { throw new ConcurrentModificationError(_map); } if (_index == _map._keys.length) { _current = null; return false; } _current = _map._keys[_index++]; return true; } K get current => _current; } Never unreachable([_, __]) => throw "unreachable";