Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion lib/src/equatable.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion lib/src/equatable_mixin.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
29 changes: 20 additions & 9 deletions lib/src/equatable_utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,30 @@ bool equals(List<Object?>? a, List<Object?>? b) {
return iterableEquals(a, b);
}

/// Determines whether two props list are equal.
@pragma('vm:prefer-inline')
bool propsEquals(List<Object?> a, List<Object?> 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<Object?> a, Iterable<Object?> 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;
}
Expand Down Expand Up @@ -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')
Expand Down
80 changes: 64 additions & 16 deletions test/equatable_utils_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ class Person with EquatableMixin {
List<Object?> get props => [name];
}

extension IterableConverter<T> on Iterable<T> {
Iterable<T> toIterable() => map((e) => e);
}

void main() {
final bob = Person(name: 'Bob');
final alice = Person(name: 'Alice');
Expand All @@ -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);
Expand All @@ -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];
Expand All @@ -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);
});
});

Expand All @@ -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};
Expand Down