diff --git a/lib/src/equatable.dart b/lib/src/equatable.dart index 6b4445b9..d559fd21 100644 --- a/lib/src/equatable.dart +++ b/lib/src/equatable.dart @@ -46,7 +46,7 @@ abstract class Equatable { return identical(this, other) || other is Equatable && runtimeType == other.runtimeType && - iterableEquals(props, other.props); + propsEquals(props, other.props); } @override diff --git a/lib/src/equatable_mixin.dart b/lib/src/equatable_mixin.dart index b69db159..09f0794b 100644 --- a/lib/src/equatable_mixin.dart +++ b/lib/src/equatable_mixin.dart @@ -22,7 +22,7 @@ mixin EquatableMixin { return identical(this, other) || other is EquatableMixin && runtimeType == other.runtimeType && - iterableEquals(props, other.props); + propsEquals(props, other.props); } @override diff --git a/lib/src/equatable_utils.dart b/lib/src/equatable_utils.dart index 022aa790..3a27b55d 100644 --- a/lib/src/equatable_utils.dart +++ b/lib/src/equatable_utils.dart @@ -15,15 +15,30 @@ bool equals(List? a, List? b) { return iterableEquals(a, b); } +/// Determines whether two props list are equal. +@pragma('vm:prefer-inline') +bool propsEquals(List a, List b) { + if (identical(a, b)) return true; + if (a.length != b.length) return false; + for (var i = 0; i < a.length; i++) { + if (!objectsEquals(a.elementAt(i), b.elementAt(i))) return false; + } + return true; +} + /// Determines whether two iterables are equal. @pragma('vm:prefer-inline') bool iterableEquals(Iterable a, Iterable b) { - assert( - a is! Set && b is! Set, - "iterableEquals doesn't support Sets. Use setEquals instead.", - ); if (identical(a, b)) return true; if (a.length != b.length) return false; + if (a is Set && b is Set) { + for (final element in a) { + if (!b.any((e) => objectsEquals(element, e))) return false; + } + return true; + } + if (a is Set != b is Set) return false; + if (a is List != b is List) return false; for (var i = 0; i < a.length; i++) { if (!objectsEquals(a.elementAt(i), b.elementAt(i))) return false; } @@ -62,18 +77,14 @@ bool objectsEquals(Object? a, Object? b) { return numEquals(a, b); } else if (_isEquatable(a) && _isEquatable(b)) { return a == b; - } else if (a is Set && b is Set) { - return setEquals(a, b); } else if (a is Iterable && b is Iterable) { return iterableEquals(a, b); } else if (a is Map && b is Map) { return mapEquals(a, b); } else if (a?.runtimeType != b?.runtimeType) { return false; - } else if (a != b) { - return false; } - return true; + return a == b; } @pragma('vm:prefer-inline') diff --git a/test/equatable_utils_test.dart b/test/equatable_utils_test.dart index 1445dd70..61b5105a 100644 --- a/test/equatable_utils_test.dart +++ b/test/equatable_utils_test.dart @@ -11,6 +11,10 @@ class Person with EquatableMixin { List get props => [name]; } +extension IterableConverter on Iterable { + Iterable toIterable() => map((e) => e); +} + void main() { final bob = Person(name: 'Bob'); final alice = Person(name: 'Alice'); @@ -31,20 +35,54 @@ void main() { test('returns true for identical props', () { final value = [Object()]; expect(iterableEquals(value, value), isTrue); + expect(iterableEquals(value.toSet(), value.toSet()), isTrue); + expect(iterableEquals(value.toIterable(), value.toIterable()), isTrue); expect(equals(value, value), isTrue); }); test('returns true for empty iterables', () { expect(iterableEquals([], []), isTrue); + expect(iterableEquals({}, {}), isTrue); + // ignore: prefer_const_constructors + expect(iterableEquals(Iterable.empty(), Iterable.empty()), isTrue); expect(equals([], []), isTrue); }); test('returns false when props differ in length', () { final object = Object(); expect(iterableEquals([object], [object, object]), isFalse); + expect(iterableEquals({object}, {object, null}), isFalse); + expect( + iterableEquals([object].toIterable(), [object, object].toIterable()), + isFalse, + ); expect(equals([object], [object, object]), isFalse); }); + test('returns false when comparing a List to a non-List Iterable', () { + final list = [Object()]; + expect(iterableEquals(list, list.toSet()), isFalse); + expect(iterableEquals(list, list.toIterable()), isFalse); + }); + + test('returns false when comparing a Set to a non-Set Iterable', () { + final set = {Object()}; + expect(iterableEquals(set, set.toList()), isFalse); + expect(iterableEquals(set, set.toIterable()), isFalse); + }); + + test('returns false when comparing a Iterable to a Set or a List', () { + final iterable = Iterable.generate(1, (_) => Object()); + expect(iterableEquals(iterable, iterable.toList()), isFalse); + expect(iterableEquals(iterable, iterable.toSet()), isFalse); + }); + + test('returns false when comparing a List to a non-List Iterable', () { + final object = Object(); + expect(iterableEquals([object], {object}), isFalse); + expect(iterableEquals([object], [object].toIterable()), isFalse); + }); + test('uses == when props are equatable', () { expect(iterableEquals([alice], [aliceCopy]), isTrue); expect(iterableEquals([bob], [bob]), isTrue); @@ -63,11 +101,15 @@ void main() { final iterable1 = [1, 2, 3]; final iterable2 = [1, 2, 4]; expect(iterableEquals(iterable1, iterable2), isFalse); + expect(iterableEquals(iterable1.toSet(), iterable2.toSet()), isFalse); + expect( + iterableEquals(iterable1.toIterable(), iterable2.toIterable()), + isFalse, + ); expect(equals(iterable1, iterable2), isFalse); }); - test( - 'returns false for iterable with same elements ' + test('returns false for iterable with same elements ' 'but different order', () { final iterable1 = [1, 2, 3]; final iterable2 = [1, 3, 2]; @@ -76,29 +118,33 @@ void main() { }); test('returns true for nested identical iterables', () { - final iterable1 = [ + final list1 = [ [bob, alice], [alice, bob], ]; - final iterable2 = [ + final list2 = [ [bob, alice], [alice, bob], ]; - expect(iterableEquals(iterable1, iterable2), isTrue); - expect(equals(iterable1, iterable2), isTrue); + expect(iterableEquals(list1, list2), isTrue); + expect(iterableEquals(list1.toSet(), list2.toSet()), isTrue); + expect(iterableEquals(list1.toIterable(), list2.toIterable()), isTrue); + expect(equals(list1, list2), isTrue); }); test('returns false for nested iterables with different elements', () { - final iterable1 = [ + final list1 = [ [bob, 2], [3, 4], ]; - final iterable2 = [ + final list2 = [ [bob, 2], [3, 5], ]; - expect(iterableEquals(iterable1, iterable2), isFalse); - expect(equals(iterable1, iterable2), isFalse); + expect(iterableEquals(list1, list2), isFalse); + expect(iterableEquals(list1.toSet(), list2.toSet()), isFalse); + expect(iterableEquals(list1.toIterable(), list2.toIterable()), isFalse); + expect(equals(list1, list2), isFalse); }); }); @@ -109,12 +155,14 @@ void main() { expect(setEquals(set1, set2), isTrue); }); - test('returns true for identical sets with elements in different order', - () { - final set1 = {1, 3, 2}; - final set2 = {1, 2, 3}; - expect(setEquals(set1, set2), isTrue); - }); + test( + 'returns true for identical sets with elements in different order', + () { + final set1 = {1, 3, 2}; + final set2 = {1, 2, 3}; + expect(setEquals(set1, set2), isTrue); + }, + ); test('returns false for sets of different lengths', () { final set1 = {1, 2, 3};