| Index: packages/quiver/test/collection/multimap_test.dart
|
| diff --git a/packages/quiver/test/collection/multimap_test.dart b/packages/quiver/test/collection/multimap_test.dart
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..19e5ed6cd949d9ec43c435bfa4d22df17ce8c268
|
| --- /dev/null
|
| +++ b/packages/quiver/test/collection/multimap_test.dart
|
| @@ -0,0 +1,906 @@
|
| +// Copyright 2013 Google Inc. All Rights Reserved.
|
| +//
|
| +// Licensed under the Apache License, Version 2.0 (the "License");
|
| +// you may not use this file except in compliance with the License.
|
| +// You may obtain a copy of the License at
|
| +//
|
| +// http://www.apache.org/licenses/LICENSE-2.0
|
| +//
|
| +// Unless required by applicable law or agreed to in writing, software
|
| +// distributed under the License is distributed on an "AS IS" BASIS,
|
| +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| +// See the License for the specific language governing permissions and
|
| +// limitations under the License.
|
| +
|
| +library quiver.collection.multimap_test;
|
| +
|
| +import 'package:quiver/collection.dart';
|
| +import 'package:test/test.dart';
|
| +
|
| +void main() {
|
| + group('Multimap', () {
|
| + test('should be a list-backed multimap', () {
|
| + var map = new Multimap();
|
| + expect(map is ListMultimap, true);
|
| + });
|
| + });
|
| +
|
| + group('Multimap asMap() view', () {
|
| + var mmap;
|
| + var map;
|
| + setUp(() {
|
| + mmap = new Multimap()
|
| + ..add('k1', 'v1')
|
| + ..add('k1', 'v2')
|
| + ..add('k2', 'v3');
|
| + map = mmap.asMap();
|
| + });
|
| +
|
| + test('operator[]= should throw UnsupportedError', () {
|
| + expect(() => map['k1'] = [1, 2, 3], throwsUnsupportedError);
|
| + });
|
| +
|
| + test('containsKey() should return false for missing key', () {
|
| + expect(map.containsKey('k3'), isFalse);
|
| + });
|
| +
|
| + test('containsKey() should return true for key in map', () {
|
| + expect(map.containsKey('k1'), isTrue);
|
| + });
|
| +
|
| + test('containsValue() should return false for missing value', () {
|
| + expect(map.containsValue('k3'), isFalse);
|
| + });
|
| +
|
| + test('containsValue() should return true for value in map', () {
|
| + expect(map.containsValue('v1'), isTrue);
|
| + });
|
| +
|
| + test('forEach should iterate over all key-value pairs', () {
|
| + var results = [];
|
| + map.forEach((k, v) => results.add(new Pair(k, v)));
|
| + expect(results, unorderedEquals([
|
| + new Pair('k1', ['v1', 'v2']),
|
| + new Pair('k2', ['v3'])
|
| + ]));
|
| + });
|
| +
|
| + test('isEmpty should return whether the map contains key-value pairs', () {
|
| + expect(map.isEmpty, isFalse);
|
| + expect(map.isNotEmpty, isTrue);
|
| + expect(new Multimap().asMap().isEmpty, isTrue);
|
| + expect(new Multimap().asMap().isNotEmpty, isFalse);
|
| + });
|
| +
|
| + test('length should return the number of key-value pairs', () {
|
| + expect(new Multimap().asMap().length, equals(0));
|
| + expect(map.length, equals(2));
|
| + });
|
| +
|
| + test('addAll(Map m) should throw UnsupportedError', () {
|
| + expect(() => map.addAll({'k1': [1, 2, 3]}), throwsUnsupportedError);
|
| + });
|
| +
|
| + test('putIfAbsent() should throw UnsupportedError', () {
|
| + var map = new Multimap().asMap();
|
| + expect(() => map.putIfAbsent('k1', () => [1]), throwsUnsupportedError);
|
| + });
|
| + });
|
| +
|
| + group('ListMultimap', () {
|
| + test('should initialize empty', () {
|
| + var map = new ListMultimap();
|
| + expect(map.isEmpty, true);
|
| + expect(map.isNotEmpty, false);
|
| + });
|
| +
|
| + test('should not be empty after adding', () {
|
| + var map = new ListMultimap<String, String>()..add('k', 'v');
|
| + expect(map.isEmpty, false);
|
| + expect(map.isNotEmpty, true);
|
| + });
|
| +
|
| + test('should return the number of keys as length', () {
|
| + var map = new ListMultimap<String, String>();
|
| + expect(map.length, 0);
|
| + map
|
| + ..add('k1', 'v1')
|
| + ..add('k1', 'v2')
|
| + ..add('k2', 'v3');
|
| + expect(map.length, 2);
|
| + });
|
| +
|
| + test('should return an empty iterable for unmapped keys', () {
|
| + var map = new ListMultimap<String, String>();
|
| + expect(map['k1'], []);
|
| + });
|
| +
|
| + test('should support adding values for unmapped keys', () {
|
| + var map = new ListMultimap<String, String>()..['k1'].add('v1');
|
| + expect(map['k1'], ['v1']);
|
| + });
|
| +
|
| + test('should support adding multiple values for unmapped keys', () {
|
| + var map = new ListMultimap<String, String>()..['k1'].addAll(['v1', 'v2']);
|
| + expect(map['k1'], ['v1', 'v2']);
|
| + });
|
| +
|
| + test('should support inserting values for unmapped keys', () {
|
| + var map = new ListMultimap<String, String>()..['k1'].insert(0, 'v1');
|
| + expect(map['k1'], ['v1']);
|
| + });
|
| +
|
| + test('should support inserting multiple values for unmapped keys', () {
|
| + var map = new ListMultimap<String, String>()
|
| + ..['k1'].insertAll(0, ['v1', 'v2']);
|
| + expect(map['k1'], ['v1', 'v2']);
|
| + });
|
| +
|
| + test('should support inserting multiple values for unmapped keys', () {
|
| + var map = new ListMultimap<String, String>()..['k1'].length = 2;
|
| + expect(map['k1'], [null, null]);
|
| + });
|
| +
|
| + test('should return unmapped iterables that stay in sync on add', () {
|
| + var map = new ListMultimap<String, String>();
|
| + List values1 = map['k1'];
|
| + List values2 = map['k1'];
|
| + values1.add('v1');
|
| + expect(map['k1'], ['v1']);
|
| + expect(values2, ['v1']);
|
| + });
|
| +
|
| + test('should return unmapped iterables that stay in sync on addAll', () {
|
| + var map = new ListMultimap<String, String>();
|
| + List values1 = map['k1'];
|
| + List values2 = map['k1'];
|
| + values1.addAll(['v1', 'v2']);
|
| + expect(map['k1'], ['v1', 'v2']);
|
| + expect(values2, ['v1', 'v2']);
|
| + });
|
| +
|
| + test('should support adding duplicate values for a key', () {
|
| + var map = new ListMultimap<String, String>()
|
| + ..add('k', 'v1')
|
| + ..add('k', 'v1');
|
| + expect(map['k'], ['v1', 'v1']);
|
| + });
|
| +
|
| + test('should support adding multiple keys', () {
|
| + var map = new ListMultimap<String, String>()
|
| + ..add('k1', 'v1')
|
| + ..add('k1', 'v2')
|
| + ..add('k2', 'v3');
|
| + expect(map['k1'], ['v1', 'v2']);
|
| + expect(map['k2'], ['v3']);
|
| + });
|
| +
|
| + test('should support adding multiple values at once', () {
|
| + var map = new ListMultimap<String, String>()
|
| + ..addValues('k1', ['v1', 'v2']);
|
| + expect(map['k1'], ['v1', 'v2']);
|
| + });
|
| +
|
| + test('should support adding multiple values at once for existing keys', () {
|
| + var map = new ListMultimap<String, String>()
|
| + ..add('k1', 'v1')
|
| + ..addValues('k1', ['v1', 'v2']);
|
| + expect(map['k1'], ['v1', 'v1', 'v2']);
|
| + });
|
| +
|
| + test('should support adding from another multimap', () {
|
| + var from = new ListMultimap<String, String>()
|
| + ..addValues('k1', ['v1', 'v2'])
|
| + ..add('k2', 'v3');
|
| + var map = new ListMultimap<String, String>()..addAll(from);
|
| + expect(map['k1'], ['v1', 'v2']);
|
| + expect(map['k2'], ['v3']);
|
| + });
|
| +
|
| + test('should support adding from another multimap with existing keys', () {
|
| + var from = new ListMultimap<String, String>()
|
| + ..addValues('k1', ['v1', 'v2'])
|
| + ..add('k2', 'v3');
|
| + var map = new ListMultimap<String, String>()
|
| + ..add('k1', 'v0')
|
| + ..add('k2', 'v3')
|
| + ..addAll(from);
|
| + expect(map['k1'], ['v0', 'v1', 'v2']);
|
| + expect(map['k2'], ['v3', 'v3']);
|
| + });
|
| +
|
| + test('should return its keys', () {
|
| + var map = new ListMultimap<String, String>()
|
| + ..add('k1', 'v1')
|
| + ..add('k1', 'v2')
|
| + ..add('k2', 'v3');
|
| + expect(map.keys, unorderedEquals(['k1', 'k2']));
|
| + });
|
| +
|
| + test('should return its values', () {
|
| + var map = new ListMultimap<String, String>()
|
| + ..add('k1', 'v1')
|
| + ..add('k1', 'v2')
|
| + ..add('k2', 'v3');
|
| + expect(map.values, unorderedEquals(['v1', 'v2', 'v3']));
|
| + });
|
| +
|
| + test('should support duplicate values', () {
|
| + var map = new ListMultimap<String, String>()
|
| + ..add('k1', 'v1')
|
| + ..add('k1', 'v2')
|
| + ..add('k2', 'v1');
|
| + expect(map.values, unorderedEquals(['v1', 'v2', 'v1']));
|
| + });
|
| +
|
| + test('should return an ordered list of values', () {
|
| + var map = new ListMultimap<String, String>()
|
| + ..add('k', 'v1')
|
| + ..add('k', 'v2');
|
| + expect(map['k'], ['v1', 'v2']);
|
| + });
|
| +
|
| + test('should reflect changes to underlying list', () {
|
| + var map = new ListMultimap<String, String>()
|
| + ..add('k', 'v1')
|
| + ..add('k', 'v2');
|
| + map['k'].add('v3');
|
| + map['k'].remove('v2');
|
| + expect(map['k'], ['v1', 'v3']);
|
| + });
|
| +
|
| + test('should return whether it contains a key', () {
|
| + var map = new ListMultimap<String, String>()
|
| + ..add('k', 'v1')
|
| + ..add('k', 'v2');
|
| + expect(map.containsKey('j'), false);
|
| + expect(map.containsKey('k'), true);
|
| + });
|
| +
|
| + test('should return whether it contains a value', () {
|
| + var map = new ListMultimap<String, String>()
|
| + ..add('k', 'v1')
|
| + ..add('k', 'v2');
|
| + expect(map.containsValue('v0'), false);
|
| + expect(map.containsValue('v1'), true);
|
| + });
|
| +
|
| + test('should remove specified key-value associations', () {
|
| + var map = new ListMultimap<String, String>()
|
| + ..add('k1', 'v1')
|
| + ..add('k1', 'v2')
|
| + ..add('k2', 'v3');
|
| + expect(map.remove('k1', 'v0'), false);
|
| + expect(map.remove('k1', 'v1'), true);
|
| + expect(map['k1'], ['v2']);
|
| + expect(map.containsKey('k2'), true);
|
| + });
|
| +
|
| + test('should remove a key when all associated values are removed', () {
|
| + var map = new ListMultimap<String, String>()
|
| + ..add('k1', 'v1')
|
| + ..remove('k1', 'v1');
|
| + expect(map.containsKey('k1'), false);
|
| + });
|
| +
|
| + test('should remove a key when all associated values are removed '
|
| + 'via the underlying iterable.remove', () {
|
| + var map = new ListMultimap<String, String>()..add('k1', 'v1');
|
| + map['k1'].remove('v1');
|
| + expect(map.containsKey('k1'), false);
|
| + });
|
| +
|
| + test('should remove a key when all associated values are removed '
|
| + 'via the underlying iterable.removeAt', () {
|
| + var map = new ListMultimap<String, String>()..add('k1', 'v1');
|
| + map['k1'].removeAt(0);
|
| + expect(map.containsKey('k1'), false);
|
| + });
|
| +
|
| + test('should remove a key when all associated values are removed '
|
| + 'via the underlying iterable.removeAt', () {
|
| + var map = new ListMultimap<String, String>()..add('k1', 'v1');
|
| + map['k1'].removeLast();
|
| + expect(map.containsKey('k1'), false);
|
| + });
|
| +
|
| + test('should remove a key when all associated values are removed '
|
| + 'via the underlying iterable.removeRange', () {
|
| + var map = new ListMultimap<String, String>()..add('k1', 'v1');
|
| + map['k1'].removeRange(0, 1);
|
| + expect(map.containsKey('k1'), false);
|
| + });
|
| +
|
| + test('should remove a key when all associated values are removed '
|
| + 'via the underlying iterable.removeWhere', () {
|
| + var map = new ListMultimap<String, String>()..add('k1', 'v1');
|
| + map['k1'].removeWhere((_) => true);
|
| + expect(map.containsKey('k1'), false);
|
| + });
|
| +
|
| + test('should remove a key when all associated values are removed '
|
| + 'via the underlying iterable.replaceRange', () {
|
| + var map = new ListMultimap<String, String>()..add('k1', 'v1');
|
| + map['k1'].replaceRange(0, 1, []);
|
| + expect(map.containsKey('k1'), false);
|
| + });
|
| +
|
| + test('should remove a key when all associated values are removed '
|
| + 'via the underlying iterable.retainWhere', () {
|
| + var map = new ListMultimap<String, String>()..add('k1', 'v1');
|
| + map['k1'].retainWhere((_) => false);
|
| + expect(map.containsKey('k1'), false);
|
| + });
|
| +
|
| + test('should remove a key when all associated values are removed '
|
| + 'via the underlying iterable.clear', () {
|
| + var map = new ListMultimap<String, String>()
|
| + ..add('k1', 'v1')
|
| + ..add('k1', 'v2');
|
| + map['k1'].clear();
|
| + expect(map.containsKey('k1'), false);
|
| + });
|
| +
|
| + test('should remove all values for a key', () {
|
| + var map = new ListMultimap<String, String>()
|
| + ..add('k1', 'v1')
|
| + ..add('k1', 'v2')
|
| + ..add('k2', 'v3');
|
| + expect(map.removeAll('k1'), ['v1', 'v2']);
|
| + expect(map.containsKey('k1'), false);
|
| + expect(map.containsKey('k2'), true);
|
| + });
|
| +
|
| + test('should clear underlying iterable on remove', () {
|
| + var map = new ListMultimap<String, String>()..add('k1', 'v1');
|
| + List values = map['k1'];
|
| + expect(map.removeAll('k1'), ['v1']);
|
| + expect(values, []);
|
| + });
|
| +
|
| + test('should return an empty iterable on removeAll of unmapped key', () {
|
| + var map = new ListMultimap<String, String>();
|
| + var removed = map.removeAll('k1');
|
| + expect(removed, []);
|
| + });
|
| +
|
| + test('should be uncoupled from the iterable returned by removeAll', () {
|
| + var map = new ListMultimap<String, String>()..add('k1', 'v1');
|
| + var removed = map.removeAll('k1');
|
| + removed.add('v2');
|
| + map.add('k1', 'v3');
|
| + expect(removed, ['v1', 'v2']);
|
| + expect(map['k1'], ['v3']);
|
| + });
|
| +
|
| + test('should clear the map', () {
|
| + var map = new ListMultimap<String, String>()
|
| + ..add('k1', 'v1')
|
| + ..add('k1', 'v2')
|
| + ..add('k2', 'v3')
|
| + ..clear();
|
| + expect(map.isEmpty, true);
|
| + expect(map.containsKey('k1'), false);
|
| + expect(map.containsKey('k2'), false);
|
| + });
|
| +
|
| + test('should clear underlying iterables on clear', () {
|
| + var map = new ListMultimap<String, String>()..add('k1', 'v1');
|
| + List values = map['k1'];
|
| + map.clear();
|
| + expect(values, []);
|
| + });
|
| +
|
| + test('should not add mappings on lookup of unmapped keys', () {
|
| + var map = new ListMultimap<String, String>()..['k1'];
|
| + expect(map.containsKey('k1'), false);
|
| + });
|
| +
|
| + test('should not remove mappings on clearing mapped values', () {
|
| + var map = new ListMultimap<String, String>()
|
| + ..add('k1', 'v1')
|
| + ..['v1'].clear();
|
| + expect(map.containsKey('k1'), true);
|
| + });
|
| +
|
| + test('should return a map view', () {
|
| + var mmap = new ListMultimap<String, String>()
|
| + ..add('k1', 'v1')
|
| + ..add('k1', 'v2')
|
| + ..add('k2', 'v3');
|
| + Map map = mmap.asMap();
|
| + expect(map.keys, unorderedEquals(['k1', 'k2']));
|
| + expect(map.values, hasLength(2));
|
| + expect(map.values, anyElement(unorderedEquals(['v1', 'v2'])));
|
| + expect(map.values, anyElement(unorderedEquals(['v3'])));
|
| + expect(map['k1'], ['v1', 'v2']);
|
| + expect(map['k2'], ['v3']);
|
| + });
|
| +
|
| + test('should return an empty iterable on map view unmapped key', () {
|
| + Map map = new ListMultimap<String, String>().asMap();
|
| + expect(map['k1'], []);
|
| + });
|
| +
|
| + test('should allow addition via unmapped key lookup on map view', () {
|
| + var mmap = new ListMultimap<String, String>();
|
| + Map map = mmap.asMap();
|
| + map['k1'].add('v1');
|
| + map['k2'].addAll(['v1', 'v2']);
|
| + expect(mmap['k1'], ['v1']);
|
| + expect(mmap['k2'], ['v1', 'v2']);
|
| + });
|
| +
|
| + test('should reflect additions to iterables returned by map view', () {
|
| + var mmap = new ListMultimap<String, String>()
|
| + ..add('k1', 'v1')
|
| + ..add('k1', 'v2');
|
| + Map map = mmap.asMap();
|
| + map['k1'].add('v3');
|
| + expect(mmap['k1'], ['v1', 'v2', 'v3']);
|
| + });
|
| +
|
| + test('should reflect removals of keys in returned map view', () {
|
| + var mmap = new ListMultimap<String, String>()
|
| + ..add('k1', 'v1')
|
| + ..add('k1', 'v2');
|
| + Map map = mmap.asMap();
|
| + map.remove('k1');
|
| + expect(mmap.containsKey('k1'), false);
|
| + });
|
| +
|
| + test('should reflect clearing of returned map view', () {
|
| + var mmap = new ListMultimap<String, String>()
|
| + ..add('k1', 'v1')
|
| + ..add('k1', 'v2')
|
| + ..add('k2', 'v3');
|
| + Map map = mmap.asMap();
|
| + map.clear();
|
| + expect(mmap.isEmpty, true);
|
| + });
|
| +
|
| + test('should support iteration over all {key, value} pairs', () {
|
| + Set s = new Set();
|
| + new ListMultimap<String, String>()
|
| + ..add('k1', 'v1')
|
| + ..add('k1', 'v2')
|
| + ..add('k2', 'v3')
|
| + ..forEach((k, v) => s.add(new Pair(k, v)));
|
| + expect(s, unorderedEquals(
|
| + [new Pair('k1', 'v1'), new Pair('k1', 'v2'), new Pair('k2', 'v3')]));
|
| + });
|
| +
|
| + test('should support iteration over all {key, Iterable<value>} pairs', () {
|
| + Map map = new Map();
|
| + var mmap = new ListMultimap<String, String>()
|
| + ..add('k1', 'v1')
|
| + ..add('k1', 'v2')
|
| + ..add('k2', 'v3')
|
| + ..forEachKey((k, v) => map[k] = v);
|
| + expect(map.length, mmap.length);
|
| + expect(map['k1'], ['v1', 'v2']);
|
| + expect(map['k2'], ['v3']);
|
| + });
|
| +
|
| + test(
|
| + 'should support operations on empty map views without breaking delegate synchronization',
|
| + () {
|
| + var mmap = new ListMultimap<String, String>();
|
| + List x = mmap['k1'];
|
| + List y = mmap['k1'];
|
| + List z = mmap['k1'];
|
| + List w = mmap['k1'];
|
| + mmap['k1'].add('v1');
|
| + expect(mmap['k1'], ['v1']);
|
| + x.add('v2');
|
| + expect(mmap['k1'], ['v1', 'v2']);
|
| + y.addAll(['v3', 'v4']);
|
| + expect(mmap['k1'], ['v1', 'v2', 'v3', 'v4']);
|
| + z.insert(0, 'v0');
|
| + expect(mmap['k1'], ['v0', 'v1', 'v2', 'v3', 'v4']);
|
| + w.insertAll(5, ['v5', 'v6']);
|
| + expect(mmap['k1'], ['v0', 'v1', 'v2', 'v3', 'v4', 'v5', 'v6']);
|
| + });
|
| + });
|
| +
|
| + group('SetMultimap', () {
|
| + test('should initialize empty', () {
|
| + var map = new SetMultimap<String, String>();
|
| + expect(map.isEmpty, true);
|
| + expect(map.isNotEmpty, false);
|
| + });
|
| +
|
| + test('should not be empty after adding', () {
|
| + var map = new SetMultimap<String, String>()..add('k', 'v');
|
| + expect(map.isEmpty, false);
|
| + expect(map.isNotEmpty, true);
|
| + });
|
| +
|
| + test('should return the number of keys as length', () {
|
| + var map = new SetMultimap<String, String>();
|
| + expect(map.length, 0);
|
| + map
|
| + ..add('k1', 'v1')
|
| + ..add('k1', 'v2')
|
| + ..add('k2', 'v3');
|
| + expect(map.length, 2);
|
| + });
|
| +
|
| + test('should return an empty iterable for unmapped keys', () {
|
| + var map = new SetMultimap<String, String>();
|
| + expect(map['k1'], []);
|
| + });
|
| +
|
| + test('should support adding values for unmapped keys', () {
|
| + var map = new SetMultimap<String, String>()..['k1'].add('v1');
|
| + expect(map['k1'], ['v1']);
|
| + });
|
| +
|
| + test('should support adding multiple values for unmapped keys', () {
|
| + var map = new SetMultimap<String, String>()..['k1'].addAll(['v1', 'v2']);
|
| + expect(map['k1'], unorderedEquals(['v1', 'v2']));
|
| + });
|
| +
|
| + test('should return unmapped iterables that stay in sync on add', () {
|
| + var map = new SetMultimap<String, String>();
|
| + Set values1 = map['k1'];
|
| + Set values2 = map['k1'];
|
| + values1.add('v1');
|
| + expect(map['k1'], ['v1']);
|
| + expect(values2, ['v1']);
|
| + });
|
| +
|
| + test('should return unmapped iterables that stay in sync on addAll', () {
|
| + var map = new SetMultimap<String, String>();
|
| + Set values1 = map['k1'];
|
| + Set values2 = map['k1'];
|
| + values1.addAll(['v1', 'v2']);
|
| + expect(map['k1'], unorderedEquals(['v1', 'v2']));
|
| + expect(values2, unorderedEquals(['v1', 'v2']));
|
| + });
|
| +
|
| + test('should not support adding duplicate values for a key', () {
|
| + var map = new SetMultimap<String, String>()
|
| + ..add('k', 'v1')
|
| + ..add('k', 'v1');
|
| + expect(map['k'], ['v1']);
|
| + });
|
| +
|
| + test('should support adding multiple keys', () {
|
| + var map = new SetMultimap<String, String>()
|
| + ..add('k1', 'v1')
|
| + ..add('k1', 'v2')
|
| + ..add('k2', 'v3');
|
| + expect(map['k1'], unorderedEquals(['v1', 'v2']));
|
| + expect(map['k2'], ['v3']);
|
| + });
|
| +
|
| + test('should support adding multiple values at once', () {
|
| + var map = new SetMultimap<String, String>()
|
| + ..addValues('k1', ['v1', 'v2']);
|
| + expect(map['k1'], ['v1', 'v2']);
|
| + });
|
| +
|
| + test('should support adding multiple values at once for existing keys', () {
|
| + var map = new SetMultimap<String, String>()
|
| + ..add('k1', 'v0')
|
| + ..addValues('k1', ['v1', 'v2']);
|
| + expect(map['k1'], unorderedEquals(['v0', 'v1', 'v2']));
|
| + });
|
| +
|
| + test('should support adding multiple values for existing (key,value)', () {
|
| + var map = new SetMultimap<String, String>()
|
| + ..add('k1', 'v1')
|
| + ..addValues('k1', ['v1', 'v2']);
|
| + expect(map['k1'], unorderedEquals(['v1', 'v2']));
|
| + });
|
| +
|
| + test('should support adding from another multimap', () {
|
| + var from = new SetMultimap<String, String>()
|
| + ..addValues('k1', ['v1', 'v2'])
|
| + ..add('k2', 'v3');
|
| + var map = new SetMultimap<String, String>()..addAll(from);
|
| + expect(map['k1'], unorderedEquals(['v1', 'v2']));
|
| + expect(map['k2'], ['v3']);
|
| + });
|
| +
|
| + test('should support adding from another multimap with existing keys', () {
|
| + var from = new SetMultimap<String, String>()
|
| + ..addValues('k1', ['v1', 'v2'])
|
| + ..add('k2', 'v3');
|
| + var map = new SetMultimap<String, String>()
|
| + ..add('k1', 'v0')
|
| + ..add('k2', 'v3')
|
| + ..addAll(from);
|
| + expect(map['k1'], unorderedEquals(['v0', 'v1', 'v2']));
|
| + expect(map['k2'], ['v3']);
|
| + });
|
| +
|
| + test('should return its keys', () {
|
| + var map = new SetMultimap<String, String>()
|
| + ..add('k1', 'v1')
|
| + ..add('k1', 'v2')
|
| + ..add('k2', 'v3');
|
| + expect(map.keys, unorderedEquals(['k1', 'k2']));
|
| + });
|
| +
|
| + test('should return its values', () {
|
| + var map = new SetMultimap<String, String>()
|
| + ..add('k1', 'v1')
|
| + ..add('k1', 'v2')
|
| + ..add('k2', 'v3');
|
| + expect(map.values, unorderedEquals(['v1', 'v2', 'v3']));
|
| + });
|
| +
|
| + test('should support duplicate values', () {
|
| + var map = new SetMultimap<String, String>()
|
| + ..add('k1', 'v1')
|
| + ..add('k1', 'v2')
|
| + ..add('k2', 'v1');
|
| + expect(map.values, unorderedEquals(['v1', 'v2', 'v1']));
|
| + });
|
| +
|
| + test('should return an ordered list of values', () {
|
| + var map = new SetMultimap<String, String>()
|
| + ..add('k', 'v1')
|
| + ..add('k', 'v2');
|
| + expect(map['k'], unorderedEquals(['v1', 'v2']));
|
| + });
|
| +
|
| + test('should reflect changes to underlying set', () {
|
| + var map = new SetMultimap<String, String>()
|
| + ..add('k', 'v1')
|
| + ..add('k', 'v2');
|
| + map['k'].add('v3');
|
| + map['k'].remove('v2');
|
| + expect(map['k'], unorderedEquals(['v1', 'v3']));
|
| + });
|
| +
|
| + test('should return whether it contains a key', () {
|
| + var map = new SetMultimap<String, String>()
|
| + ..add('k', 'v1')
|
| + ..add('k', 'v2');
|
| + expect(map.containsKey('j'), false);
|
| + expect(map.containsKey('k'), true);
|
| + });
|
| +
|
| + test('should return whether it contains a value', () {
|
| + var map = new SetMultimap<String, String>()
|
| + ..add('k', 'v1')
|
| + ..add('k', 'v2');
|
| + expect(map.containsValue('v0'), false);
|
| + expect(map.containsValue('v1'), true);
|
| + });
|
| +
|
| + test('should remove specified key-value associations', () {
|
| + var map = new SetMultimap<String, String>()
|
| + ..add('k1', 'v1')
|
| + ..add('k1', 'v2')
|
| + ..add('k2', 'v3');
|
| + expect(map.remove('k1', 'v0'), false);
|
| + expect(map.remove('k1', 'v1'), true);
|
| + expect(map['k1'], ['v2']);
|
| + expect(map.containsKey('k2'), true);
|
| + });
|
| +
|
| + test('should remove a key when all associated values are removed', () {
|
| + var map = new SetMultimap<String, String>()
|
| + ..add('k1', 'v1')
|
| + ..remove('k1', 'v1');
|
| + expect(map.containsKey('k1'), false);
|
| + });
|
| +
|
| + test('should remove a key when all associated values are removed '
|
| + 'via the underlying iterable.remove', () {
|
| + var map = new SetMultimap<String, String>()..add('k1', 'v1');
|
| + map['k1'].remove('v1');
|
| + expect(map.containsKey('k1'), false);
|
| + });
|
| +
|
| + test('should remove a key when all associated values are removed '
|
| + 'via the underlying iterable.removeAll', () {
|
| + var map = new SetMultimap<String, String>()
|
| + ..add('k1', 'v1')
|
| + ..add('k1', 'v2');
|
| + map['k1'].removeAll(['v1', 'v2']);
|
| + expect(map.containsKey('k1'), false);
|
| + });
|
| +
|
| + test('should remove a key when all associated values are removed '
|
| + 'via the underlying iterable.removeWhere', () {
|
| + var map = new SetMultimap<String, String>()..add('k1', 'v1');
|
| + map['k1'].removeWhere((_) => true);
|
| + expect(map.containsKey('k1'), false);
|
| + });
|
| +
|
| + test('should remove a key when all associated values are removed '
|
| + 'via the underlying iterable.retainAll', () {
|
| + var map = new SetMultimap<String, String>()..add('k1', 'v1');
|
| + map['k1'].retainAll([]);
|
| + expect(map.containsKey('k1'), false);
|
| + });
|
| +
|
| + test('should remove a key when all associated values are removed '
|
| + 'via the underlying iterable.retainWhere', () {
|
| + var map = new SetMultimap<String, String>()..add('k1', 'v1');
|
| + map['k1'].retainWhere((_) => false);
|
| + expect(map.containsKey('k1'), false);
|
| + });
|
| +
|
| + test('should remove a key when all associated values are removed '
|
| + 'via the underlying iterable.clear', () {
|
| + var map = new SetMultimap<String, String>()..add('k1', 'v1');
|
| + map['k1'].clear();
|
| + expect(map.containsKey('k1'), false);
|
| + });
|
| +
|
| + test('should remove all values for a key', () {
|
| + var map = new SetMultimap<String, String>()
|
| + ..add('k1', 'v1')
|
| + ..add('k1', 'v2')
|
| + ..add('k2', 'v3');
|
| + expect(map.removeAll('k1'), unorderedEquals(['v1', 'v2']));
|
| + expect(map.containsKey('k1'), false);
|
| + expect(map.containsKey('k2'), true);
|
| + });
|
| +
|
| + test('should clear underlying iterable on remove', () {
|
| + var map = new SetMultimap<String, String>()..add('k1', 'v1');
|
| + Set values = map['k1'];
|
| + expect(map.removeAll('k1'), ['v1']);
|
| + expect(values, []);
|
| + });
|
| +
|
| + test('should return an empty iterable on removeAll of unmapped key', () {
|
| + var map = new SetMultimap<String, String>();
|
| + var removed = map.removeAll('k1');
|
| + expect(removed, []);
|
| + });
|
| +
|
| + test('should be uncoupled from the iterable returned by removeAll', () {
|
| + var map = new SetMultimap<String, String>()..add('k1', 'v1');
|
| + var removed = map.removeAll('k1');
|
| + removed.add('v2');
|
| + map.add('k1', 'v3');
|
| + expect(removed, unorderedEquals(['v1', 'v2']));
|
| + expect(map['k1'], ['v3']);
|
| + });
|
| +
|
| + test('should clear the map', () {
|
| + var map = new SetMultimap<String, String>()
|
| + ..add('k1', 'v1')
|
| + ..add('k1', 'v2')
|
| + ..add('k2', 'v3')
|
| + ..clear();
|
| + expect(map.isEmpty, true);
|
| + expect(map.containsKey('k1'), false);
|
| + expect(map.containsKey('k2'), false);
|
| + });
|
| +
|
| + test('should clear underlying iterables on clear', () {
|
| + var map = new SetMultimap<String, String>()..add('k1', 'v1');
|
| + Set values = map['k1'];
|
| + map.clear();
|
| + expect(values, []);
|
| + });
|
| +
|
| + test('should not add mappings on lookup of unmapped keys', () {
|
| + var map = new SetMultimap<String, String>()..['k1'];
|
| + expect(map.containsKey('k1'), false);
|
| + });
|
| +
|
| + test('should not remove mappings on clearing mapped values', () {
|
| + var map = new SetMultimap<String, String>()
|
| + ..add('k1', 'v1')
|
| + ..['v1'].clear();
|
| + expect(map.containsKey('k1'), true);
|
| + });
|
| +
|
| + test('should return a map view', () {
|
| + var mmap = new SetMultimap<String, String>()
|
| + ..add('k1', 'v1')
|
| + ..add('k1', 'v2')
|
| + ..add('k2', 'v3');
|
| + Map map = mmap.asMap();
|
| + expect(map.keys, unorderedEquals(['k1', 'k2']));
|
| + expect(map['k1'], ['v1', 'v2']);
|
| + expect(map['k2'], ['v3']);
|
| + });
|
| +
|
| + test('should return an empty iterable on map view unmapped key', () {
|
| + Map map = new SetMultimap<String, String>().asMap();
|
| + expect(map['k1'], []);
|
| + });
|
| +
|
| + test('should allow addition via unmapped key lookup on map view', () {
|
| + var mmap = new SetMultimap<String, String>();
|
| + Map map = mmap.asMap();
|
| + map['k1'].add('v1');
|
| + map['k2'].addAll(['v1', 'v2']);
|
| + expect(mmap['k1'], ['v1']);
|
| + expect(mmap['k2'], unorderedEquals(['v1', 'v2']));
|
| + });
|
| +
|
| + test('should reflect additions to iterables returned by map view', () {
|
| + var mmap = new SetMultimap<String, String>()
|
| + ..add('k1', 'v1')
|
| + ..add('k1', 'v2');
|
| + Map map = mmap.asMap();
|
| + map['k1'].add('v3');
|
| + expect(mmap['k1'], unorderedEquals(['v1', 'v2', 'v3']));
|
| + });
|
| +
|
| + test('should reflect additions to iterables returned by map view', () {
|
| + var mmap = new SetMultimap<String, String>()
|
| + ..add('k1', 'v1')
|
| + ..add('k1', 'v2');
|
| + Map map = mmap.asMap();
|
| + map['k1'].add('v3');
|
| + expect(mmap['k1'], unorderedEquals(['v1', 'v2', 'v3']));
|
| + });
|
| +
|
| + test('should reflect removals of keys in returned map view', () {
|
| + var mmap = new SetMultimap<String, String>()
|
| + ..add('k1', 'v1')
|
| + ..add('k1', 'v2');
|
| + Map map = mmap.asMap();
|
| + map.remove('k1');
|
| + expect(mmap.containsKey('k1'), false);
|
| + });
|
| +
|
| + test('should reflect clearing of returned map view', () {
|
| + var mmap = new SetMultimap<String, String>()
|
| + ..add('k1', 'v1')
|
| + ..add('k1', 'v2')
|
| + ..add('k2', 'v3');
|
| + Map map = mmap.asMap();
|
| + map.clear();
|
| + expect(mmap.isEmpty, true);
|
| + });
|
| +
|
| + test('should support iteration over all {key, value} pairs', () {
|
| + Set s = new Set();
|
| + new SetMultimap<String, String>()
|
| + ..add('k1', 'v1')
|
| + ..add('k1', 'v2')
|
| + ..add('k2', 'v3')
|
| + ..forEach((k, v) => s.add(new Pair(k, v)));
|
| + expect(s, unorderedEquals(
|
| + [new Pair('k1', 'v1'), new Pair('k1', 'v2'), new Pair('k2', 'v3')]));
|
| + });
|
| +
|
| + test('should support iteration over all {key, Iterable<value>} pairs', () {
|
| + Map map = new Map();
|
| + var mmap = new SetMultimap<String, String>()
|
| + ..add('k1', 'v1')
|
| + ..add('k1', 'v2')
|
| + ..add('k2', 'v3')
|
| + ..forEachKey((k, v) => map[k] = v);
|
| + expect(map.length, mmap.length);
|
| + expect(map['k1'], unorderedEquals(['v1', 'v2']));
|
| + expect(map['k2'], unorderedEquals(['v3']));
|
| + });
|
| +
|
| + test('should support operations on empty map views without breaking '
|
| + 'delegate synchronization', () {
|
| + var mmap = new SetMultimap<String, String>();
|
| + Set x = mmap['k1'];
|
| + Set y = mmap['k1'];
|
| + mmap['k1'].add('v0');
|
| + x.add('v1');
|
| + y.addAll(['v2', 'v3']);
|
| + expect(mmap['k1'], unorderedEquals(['v0', 'v1', 'v2', 'v3']));
|
| + });
|
| + });
|
| +}
|
| +
|
| +class Pair {
|
| + final x;
|
| + final y;
|
| + Pair(this.x, this.y);
|
| + bool operator ==(Pair other) {
|
| + if (x != other.x) return false;
|
| + return equals(y).matches(other.y, {});
|
| + }
|
| + String toString() => "($x, $y)";
|
| +}
|
|
|