| // 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<String, dynamic> 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<String, dynamic>' 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<String, dynamic>, type); | |
| Expect.isFalse(map is Map<int, dynamic>, 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)); | |
| } |