diff --git a/CHANGELOG.md b/CHANGELOG.md index a4379988..c04cc493 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# 2.0.1 + +- fix: `hashCode` should be the same for equal objects (`Map` fix) + # 2.0.0 - **BREAKING**: opt into null safety diff --git a/lib/src/equatable_utils.dart b/lib/src/equatable_utils.dart index e1da7f4b..50dbb2ba 100644 --- a/lib/src/equatable_utils.dart +++ b/lib/src/equatable_utils.dart @@ -1,3 +1,5 @@ +import 'dart:collection'; + import 'package:collection/collection.dart'; import 'package:equatable/equatable.dart'; @@ -39,9 +41,11 @@ bool _isEquatable(dynamic object) { /// https://en.wikipedia.org/wiki/Jenkins_hash_function int _combine(int hash, dynamic object) { if (object is Map) { - object.forEach((dynamic key, dynamic value) { - hash = hash ^ _combine(hash, [key, value]); - }); + SplayTreeMap.of(object).forEach( + (dynamic key, dynamic value) { + hash = hash ^ _combine(hash, [key, value]); + }, + ); return hash; } if (object is Iterable) { diff --git a/pubspec.yaml b/pubspec.yaml index 700428f3..a9fe7e75 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: equatable description: A Dart package that helps to implement value based equality without needing to explicitly override == and hashCode. -version: 2.0.0 +version: 2.0.1 repository: https://github.com/felangel/equatable issue_tracker: https://github.com/felangel/equatable/issues homepage: https://github.com/felangel/equatable diff --git a/test/equatable_test.dart b/test/equatable_test.dart index 89dca563..3809e0dc 100644 --- a/test/equatable_test.dart +++ b/test/equatable_test.dart @@ -317,6 +317,52 @@ void main() { }); }); + group('Simple Equatable (map)', () { + test('should correct toString', () { + final instance = SimpleEquatable({}); + expect(instance.toString(), 'SimpleEquatable>({})'); + }); + + test('should return true when instance is the same', () { + final instance = SimpleEquatable({'a': 1, 'b': 2, 'c': 3}); + expect(instance == instance, true); + }); + + test('should return correct hashCode', () { + final instance = SimpleEquatable({'a': 1, 'b': 2, 'c': 3}); + expect( + instance.hashCode, + instance.runtimeType.hashCode ^ mapPropsToHashCode(instance.props), + ); + }); + + test('should have same hashCode when values are equal', () { + final instanceA = SimpleEquatable({'a': 1, 'b': 2, 'c': 3}); + final instanceB = SimpleEquatable({'b': 2, 'a': 1, 'c': 3}); + expect(instanceA == instanceB, true); + expect(instanceA.hashCode, instanceB.hashCode); + }); + + test('should return true when instances are different', () { + final instanceA = SimpleEquatable({'a': 1, 'b': 2, 'c': 3}); + final instanceB = SimpleEquatable({'a': 1, 'b': 2, 'c': 3}); + expect(instanceA == instanceB, true); + expect(instanceA.hashCode == instanceB.hashCode, true); + }); + + test('should return false when compared to non-equatable', () { + final instanceA = SimpleEquatable({'a': 1, 'b': 2, 'c': 3}); + final instanceB = NonEquatable(); + expect(instanceA == instanceB, false); + }); + + test('should return false when values are different', () { + final instanceA = SimpleEquatable({'a': 1, 'b': 2, 'c': 3}); + final instanceB = SimpleEquatable({'a': 1, 'b': 2, 'c': 4}); + expect(instanceA == instanceB, false); + }); + }); + group('Simple Equatable (Equatable)', () { test('should correct toString', () { final instance = SimpleEquatable(EquatableData(