// Copyright (c) 2011, 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. library json_map_test; import "package:expect/expect.dart"; import 'dart:convert' show json; import 'dart:collection' show LinkedHashMap, HashMap; bool useReviver = false; Map jsonify(Map map) { String encoded = json.encode(map); return useReviver ? json.decode(encoded, reviver: (key, value) => value) : json.decode(encoded); } List listEach(Map map) { var result = []; map.forEach((key, value) { result.add(key); result.add(value); }); return result; } void main() { test(false); test(true); } void test(bool revive) { useReviver = revive; testEmpty(jsonify({})); testAtoB(jsonify({'a': 'b'})); // You can write 'Map' here (or 'var' which infers the // same), but if you write just 'Map' as the type, then the type of the // constant argument in the addAll below is not inferred correctly. var map = jsonify({}); map['a'] = 'b'; testAtoB(map); map = jsonify({}); Expect.equals('b', map.putIfAbsent('a', () => 'b')); testAtoB(map); map = jsonify({}); map.addAll({'a': 'b'}); testAtoB(map); testOrder(['a', 'b', 'c', 'd', 'e', 'f']); testProto(); testToString(); testConcurrentModifications(); testType(); testClear(); testListEntry(); testMutation(); } void testEmpty(Map map) { for (int i = 0; i < 2; i++) { Expect.equals(0, map.length); Expect.isTrue(map.isEmpty); Expect.isFalse(map.isNotEmpty); Expect.listEquals([], map.keys.toList()); Expect.listEquals([], map.values.toList()); Expect.isNull(map['a']); Expect.listEquals([], listEach(map)); Expect.isFalse(map.containsKey('a')); Expect.isFalse(map.containsValue('a')); Expect.isNull(map.remove('a')); testLookupNonExistingKeys(map); testLookupNonExistingValues(map); map.clear(); } } void testAtoB(Map map) { Expect.equals(1, map.length); Expect.isFalse(map.isEmpty); Expect.isTrue(map.isNotEmpty); Expect.listEquals(['a'], map.keys.toList()); Expect.listEquals(['b'], map.values.toList()); Expect.equals('b', map['a']); Expect.listEquals(['a', 'b'], listEach(map)); Expect.isTrue(map.containsKey('a')); Expect.isFalse(map.containsKey('b')); Expect.isTrue(map.containsValue('b')); Expect.isFalse(map.containsValue('a')); testLookupNonExistingKeys(map); testLookupNonExistingValues(map); Expect.equals('b', map.remove('a')); Expect.isNull(map.remove('b')); testLookupNonExistingKeys(map); testLookupNonExistingValues(map); map.clear(); testEmpty(map); } void testLookupNonExistingKeys(Map map) { for (var key in ['__proto__', 'null', null]) { Expect.isNull(map[key]); Expect.isFalse(map.containsKey(key)); } } void testLookupNonExistingValues(Map map) { for (var value in ['__proto__', 'null', null]) { Expect.isFalse(map.containsValue(value)); } } void testOrder(List list) { if (list.isEmpty) return; else testOrder(list.skip(1).toList()); Map original = {}; for (int i = 0; i < list.length; i++) { original[list[i]] = i; } Map map = jsonify(original); Expect.equals(list.length, map.length); Expect.listEquals(list, map.keys.toList()); for (int i = 0; i < 10; i++) { map["$i"] = i; Expect.equals(list.length + i + 1, map.length); Expect.listEquals(list, map.keys.take(list.length).toList()); } } void testProto() { Map map = jsonify({'__proto__': 0}); Expect.equals(1, map.length); Expect.isTrue(map.containsKey('__proto__')); Expect.listEquals(['__proto__'], map.keys.toList()); Expect.equals(0, map['__proto__']); Expect.equals(0, map.remove('__proto__')); testEmpty(map); map = jsonify({'__proto__': null}); Expect.equals(1, map.length); Expect.isTrue(map.containsKey('__proto__')); Expect.listEquals(['__proto__'], map.keys.toList()); Expect.isNull(map['__proto__']); Expect.isNull(map.remove('__proto__')); testEmpty(map); } void testToString() { Expect.equals("{}", jsonify({}).toString()); Expect.equals("{a: 0}", jsonify({'a': 0}).toString()); } void testConcurrentModifications() { void testIterate(Map map, Iterable iterable, Function f) { Iterator iterator = iterable.iterator; f(map); iterator.moveNext(); } void testKeys(Map map, Function f) => testIterate(map, map.keys, f); void testValues(Map map, Function f) => testIterate(map, map.values, f); void testForEach(Map map, Function f) { map.forEach((key, value) { f(map); }); } bool throwsCME(Function f) { try { f(); } on ConcurrentModificationError catch (e) { return true; } catch (e) { return false; } return false; } Map map = {}; Expect.isTrue(throwsCME(() => testKeys(jsonify(map), (map) => map['a'] = 0))); Expect.isTrue( throwsCME(() => testValues(jsonify(map), (map) => map['a'] = 0)), ); Expect.isFalse( throwsCME(() => testForEach(jsonify(map), (map) => map['a'] = 0)), ); Expect.isFalse(throwsCME(() => testKeys(jsonify(map), (map) => map.clear()))); Expect.isFalse( throwsCME(() => testValues(jsonify(map), (map) => map.clear())), ); Expect.isFalse( throwsCME(() => testForEach(jsonify(map), (map) => map.clear())), ); Expect.isFalse( throwsCME(() => testKeys(jsonify(map), (map) => map.remove('a'))), ); Expect.isFalse( throwsCME(() => testValues(jsonify(map), (map) => map.remove('a'))), ); Expect.isFalse( throwsCME(() => testForEach(jsonify(map), (map) => map.remove('a'))), ); Expect.isTrue( throwsCME( () => testKeys(jsonify(map), (map) => map.putIfAbsent('a', () => 0)), ), ); Expect.isTrue( throwsCME( () => testValues(jsonify(map), (map) => map.putIfAbsent('a', () => 0)), ), ); Expect.isFalse( throwsCME( () => testForEach(jsonify(map), (map) => map.putIfAbsent('a', () => 0)), ), ); Expect.isFalse( throwsCME(() => testKeys(jsonify(map), (map) => map.addAll({}))), ); Expect.isFalse( throwsCME(() => testValues(jsonify(map), (map) => map.addAll({}))), ); Expect.isFalse( throwsCME(() => testForEach(jsonify(map), (map) => map.addAll({}))), ); Expect.isTrue( throwsCME(() => testKeys(jsonify(map), (map) => map.addAll({'a': 0}))), ); Expect.isTrue( throwsCME(() => testValues(jsonify(map), (map) => map.addAll({'a': 0}))), ); Expect.isFalse( throwsCME(() => testForEach(jsonify(map), (map) => map.addAll({'a': 0}))), ); map = {'a': 1}; Expect.isFalse( throwsCME(() => testKeys(jsonify(map), (map) => map['a'] = 0)), ); Expect.isFalse( throwsCME(() => testValues(jsonify(map), (map) => map['a'] = 0)), ); Expect.isFalse( throwsCME(() => testForEach(jsonify(map), (map) => map['a'] = 0)), ); Expect.isTrue(throwsCME(() => testKeys(jsonify(map), (map) => map['b'] = 0))); Expect.isTrue( throwsCME(() => testValues(jsonify(map), (map) => map['b'] = 0)), ); Expect.isTrue( throwsCME(() => testForEach(jsonify(map), (map) => map['b'] = 0)), ); Expect.isTrue(throwsCME(() => testKeys(jsonify(map), (map) => map.clear()))); Expect.isTrue( throwsCME(() => testValues(jsonify(map), (map) => map.clear())), ); Expect.isTrue( throwsCME(() => testForEach(jsonify(map), (map) => map.clear())), ); Expect.isTrue( throwsCME(() => testKeys(jsonify(map), (map) => map.remove('a'))), ); Expect.isTrue( throwsCME(() => testValues(jsonify(map), (map) => map.remove('a'))), ); Expect.isTrue( throwsCME(() => testForEach(jsonify(map), (map) => map.remove('a'))), ); Expect.isFalse( throwsCME(() => testKeys(jsonify(map), (map) => map.remove('b'))), ); Expect.isFalse( throwsCME(() => testValues(jsonify(map), (map) => map.remove('b'))), ); Expect.isFalse( throwsCME(() => testForEach(jsonify(map), (map) => map.remove('b'))), ); Expect.isFalse( throwsCME( () => testKeys(jsonify(map), (map) => map.putIfAbsent('a', () => 0)), ), ); Expect.isFalse( throwsCME( () => testValues(jsonify(map), (map) => map.putIfAbsent('a', () => 0)), ), ); Expect.isFalse( throwsCME( () => testForEach(jsonify(map), (map) => map.putIfAbsent('a', () => 0)), ), ); Expect.isTrue( throwsCME( () => testKeys(jsonify(map), (map) => map.putIfAbsent('b', () => 0)), ), ); Expect.isTrue( throwsCME( () => testValues(jsonify(map), (map) => map.putIfAbsent('b', () => 0)), ), ); Expect.isTrue( throwsCME( () => testForEach(jsonify(map), (map) => map.putIfAbsent('b', () => 0)), ), ); Expect.isFalse( throwsCME(() => testKeys(jsonify(map), (map) => map.addAll({}))), ); Expect.isFalse( throwsCME(() => testValues(jsonify(map), (map) => map.addAll({}))), ); Expect.isFalse( throwsCME(() => testForEach(jsonify(map), (map) => map.addAll({}))), ); Expect.isFalse( throwsCME(() => testKeys(jsonify(map), (map) => map.addAll({'a': 0}))), ); Expect.isFalse( throwsCME(() => testValues(jsonify(map), (map) => map.addAll({'a': 0}))), ); Expect.isFalse( throwsCME(() => testForEach(jsonify(map), (map) => map.addAll({'a': 0}))), ); Expect.isTrue( throwsCME(() => testKeys(jsonify(map), (map) => map.addAll({'b': 0}))), ); Expect.isTrue( throwsCME(() => testValues(jsonify(map), (map) => map.addAll({'b': 0}))), ); Expect.isTrue( throwsCME(() => testForEach(jsonify(map), (map) => map.addAll({'b': 0}))), ); } void testType() { var map = jsonify({}); var type = "${map.runtimeType}"; // The documentation of json.decode doesn't actually specify that it returns // a map (it's marked dynamic), but it's a reasonable expectation if you // don't provide a reviver function. Expect.isTrue(map is Map, type); Expect.isTrue(map is Map, type); Expect.isFalse(map is Map, type); } void testClear() { Map map = jsonify({'a': 0}); map.clear(); Expect.equals(0, map.length); } void testListEntry() { Map map = jsonify({ 'a': [ 7, 8, {'b': 9}, ], }); List list = map['a']; Expect.equals(3, list.length); Expect.equals(7, list[0]); Expect.equals(8, list[1]); Expect.equals(9, list[2]['b']); } void testMutation() { Map map = jsonify({'a': 0}); Expect.listEquals(['a', 0], listEach(map)); map['a'] = 1; Expect.listEquals(['a', 1], listEach(map)); map['a']++; Expect.listEquals(['a', 2], listEach(map)); }