From 0616ad93169a04b341fc202761b9e7b84f4aad1c Mon Sep 17 00:00:00 2001 From: Felix Angelov Date: Wed, 19 May 2021 11:39:46 -0500 Subject: [PATCH] fix: hashCode should be the same for equal objects (#118) --- CHANGELOG.md | 4 ++++ lib/src/equatable_utils.dart | 10 +++++--- pubspec.yaml | 2 +- test/equatable_test.dart | 46 ++++++++++++++++++++++++++++++++++++ 4 files changed, 58 insertions(+), 4 deletions(-) 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(