feat: deprecate `EquatableMixin` (#214)
diff --git a/README.md b/README.md
index d93764d..4eb5f05 100644
--- a/README.md
+++ b/README.md
@@ -271,17 +271,17 @@
}
```
-## EquatableMixin
+## Mixin Usage
Sometimes it isn't possible to extend `Equatable` because your class already has a superclass.
-In this case, you can still get the benefits of `Equatable` by using the `EquatableMixin`.
+In this case, you can still get the benefits of `Equatable` by using `Equatable` as a `mixin`.
### Usage
-Let's say we want to make an `EquatableDateTime` class, we can use `EquatableMixin` like so:
+Let's say we want to make an `EquatableDateTime` class, we can use `Equatable` like so:
```dart
-class EquatableDateTime extends DateTime with EquatableMixin {
+class EquatableDateTime extends DateTime with Equatable {
EquatableDateTime(
int year, [
int month = 1,
diff --git a/analysis_options.yaml b/analysis_options.yaml
index aded668..01683d4 100644
--- a/analysis_options.yaml
+++ b/analysis_options.yaml
@@ -16,6 +16,7 @@
- always_put_required_named_parameters_first
- always_use_package_imports
- annotate_overrides
+ - annotate_redeclares
- avoid_bool_literals_in_conditional_expressions
- avoid_catching_errors
- avoid_double_and_int_checks
@@ -29,7 +30,6 @@
- avoid_init_to_null
- avoid_js_rounded_ints
- avoid_multiple_declarations_per_line
- - avoid_null_checks_in_equality_operators
- avoid_positional_boolean_parameters
- avoid_print
- avoid_private_typedef_functions
@@ -65,7 +65,9 @@
- dangling_library_doc_comments
- depend_on_referenced_packages
- deprecated_consistency
+ - deprecated_member_use_from_same_package
- directives_ordering
+ - do_not_use_environment
- empty_catches
- empty_constructor_bodies
- empty_statements
@@ -86,23 +88,28 @@
- library_private_types_in_public_api
- lines_longer_than_80_chars
- literal_only_boolean_expressions
+ - matching_super_parameters
+ - missing_code_block_language_in_doc_comment
- missing_whitespace_between_adjacent_strings
- no_adjacent_strings_in_list
- no_default_cases
- no_duplicate_case_values
- no_leading_underscores_for_library_prefixes
- no_leading_underscores_for_local_identifiers
+ - no_literal_bool_comparisons
- no_logic_in_create_state
- no_runtimeType_toString
+ - no_self_assignments
+ - no_wildcard_variable_uses
- non_constant_identifier_names
- noop_primitive_operations
- null_check_on_nullable_type_parameter
- null_closures
- omit_local_variable_types
+ - omit_obvious_local_variable_types
- one_member_abstracts
- only_throw_errors
- overridden_fields
- - package_api_docs
- package_names
- package_prefixed_library_names
- prefer_adjacent_string_concatenation
@@ -120,6 +127,7 @@
- prefer_final_in_for_each
- prefer_final_locals
- prefer_for_elements_to_map_fromIterable
+ - prefer_foreach
- prefer_function_declarations_over_variables
- prefer_generic_function_type_aliases
- prefer_if_elements_to_conditional_expressions
@@ -132,6 +140,7 @@
- prefer_is_not_empty
- prefer_is_not_operator
- prefer_iterable_whereType
+ - prefer_mixin
- prefer_null_aware_method_calls
- prefer_null_aware_operators
- prefer_single_quotes
@@ -141,8 +150,10 @@
- provide_deprecation_message
- public_member_api_docs
- recursive_getters
+ - remove_deprecations_in_breaking_versions
- require_trailing_commas
- secure_pubspec_urls
+ - simple_directive_paths
- sized_box_for_whitespace
- sized_box_shrink_expand
- slash_for_doc_comments
@@ -150,11 +161,14 @@
- sort_constructors_first
- sort_pub_dependencies
- sort_unnamed_constructors_first
+ - strict_top_level_inference
+ - switch_on_type
- test_types_in_equals
- throw_in_finally
- tighten_type_of_initializing_formals
- type_annotate_public_apis
- type_init_formals
+ - type_literal_in_constant_pattern
- unawaited_futures
- unnecessary_await_in_return
- unnecessary_brace_in_string_interps
@@ -162,11 +176,13 @@
- unnecessary_const
- unnecessary_constructor_name
- unnecessary_getters_setters
+ - unnecessary_ignore
- unnecessary_lambdas
- unnecessary_late
- unnecessary_library_directive
- unnecessary_new
- unnecessary_null_aware_assignments
+ - unnecessary_null_aware_operator_on_extension_on_nullable
- unnecessary_null_checks
- unnecessary_null_in_if_null_operators
- unnecessary_nullable_for_final_variable_declarations
@@ -178,17 +194,21 @@
- unnecessary_string_interpolations
- unnecessary_this
- unnecessary_to_list_in_spreads
+ - unnecessary_unawaited
+ - unnecessary_underscores
+ - unreachable_from_main
- unrelated_type_equality_checks
- use_build_context_synchronously
- use_colored_box
+ - use_decorated_box
- use_enums
- use_full_hex_values_for_flutter_colors
- use_function_type_syntax_for_parameters
- - use_if_null_to_convert_nulls_to_bools
- use_is_even_rather_than_modulo
- use_key_in_widget_constructors
- use_late_for_private_fields_and_variables
- use_named_constants
+ - use_null_aware_elements
- use_raw_strings
- use_rethrow_when_possible
- use_setters_to_change_properties
@@ -197,5 +217,7 @@
- use_super_parameters
- use_test_throws_matchers
- use_to_and_as_if_applicable
+ - use_truncating_division
- valid_regexps
+ - var_with_no_type_annotation
- void_checks
diff --git a/example/main.dart b/example/main.dart
index 0b06a7a..ff0f7b0 100644
--- a/example/main.dart
+++ b/example/main.dart
@@ -1,8 +1,7 @@
// ignore_for_file: avoid_print
-
import 'package:equatable/equatable.dart';
-class Credentials extends Equatable {
+class Credentials with Equatable {
const Credentials({required this.username, required this.password});
final String username;
@@ -15,7 +14,7 @@
bool get stringify => false;
}
-class EquatableDateTime extends DateTime with EquatableMixin {
+class EquatableDateTime extends DateTime with Equatable {
EquatableDateTime(
super.year, [
super.month,
@@ -49,7 +48,7 @@
print(credentialsB == credentialsC); // true
print(credentialsA); // Credentials
- // Equatable Mixin
+ // Mixin Equatable
final dateTimeA = EquatableDateTime(2019);
final dateTimeB = EquatableDateTime(2019, 2, 20, 19, 46);
final dateTimeC = EquatableDateTime(2019, 2, 20, 19, 46);
diff --git a/example/pubspec.yaml b/example/pubspec.yaml
index 667d7c6..f0f1e8e 100644
--- a/example/pubspec.yaml
+++ b/example/pubspec.yaml
@@ -1,7 +1,7 @@
name: example
environment:
- sdk: ">=3.5.0 <4.0.0"
+ sdk: ^3.12.0
dependencies:
equatable:
diff --git a/lib/equatable.dart b/lib/equatable.dart
index 4f6b353..5dd8786 100644
--- a/lib/equatable.dart
+++ b/lib/equatable.dart
@@ -2,6 +2,6 @@
/// `==` operators or `hashCode`s.
library equatable;
-export './src/equatable.dart';
-export './src/equatable_config.dart';
-export './src/equatable_mixin.dart';
+export 'src/equatable.dart';
+export 'src/equatable_config.dart';
+export 'src/equatable_mixin.dart';
diff --git a/lib/src/equatable.dart b/lib/src/equatable.dart
index 6b4445b..5b450b3 100644
--- a/lib/src/equatable.dart
+++ b/lib/src/equatable.dart
@@ -15,9 +15,15 @@
/// List<Object> get props => [name];
/// }
/// ```
+///
+/// Equatable can also be used as a mixin
+///
+/// ```dart
+/// class Person with Equatable {...}
+/// ```
/// {@endtemplate}
@immutable
-abstract class Equatable {
+abstract mixin class Equatable {
/// {@macro equatable}
const Equatable();
@@ -38,7 +44,6 @@
/// `EquatableConfig.stringify` will be used instead. This defaults to
/// `false`.
/// {@endtemplate}
- // ignore: avoid_returning_null
bool? get stringify => null;
@override
diff --git a/lib/src/equatable_config.dart b/lib/src/equatable_config.dart
index e40a384..4cea186 100644
--- a/lib/src/equatable_config.dart
+++ b/lib/src/equatable_config.dart
@@ -1,4 +1,3 @@
-// ignore_for_file: avoid_classes_with_only_static_members
import 'package:equatable/src/equatable.dart';
/// The default configuration for all [Equatable] instances.
diff --git a/lib/src/equatable_mixin.dart b/lib/src/equatable_mixin.dart
index b69db15..f2fc1f2 100644
--- a/lib/src/equatable_mixin.dart
+++ b/lib/src/equatable_mixin.dart
@@ -8,13 +8,13 @@
///
/// Like with extending [Equatable], the [EquatableMixin] overrides the
/// [operator ==] as well as the [hashCode] based on the provided [props].
+@Deprecated('use Equatable as a mixin instead')
@immutable
mixin EquatableMixin {
/// {@macro equatable_props}
List<Object?> get props;
/// {@macro equatable_stringify}
- // ignore: avoid_returning_null
bool? get stringify => null;
@override
diff --git a/lib/src/equatable_utils.dart b/lib/src/equatable_utils.dart
index c56ce62..21cffbc 100644
--- a/lib/src/equatable_utils.dart
+++ b/lib/src/equatable_utils.dart
@@ -78,6 +78,7 @@
@pragma('vm:prefer-inline')
bool _isEquatable(Object? object) {
+ // ignore: deprecated_member_use_from_same_package
return object is Equatable || object is EquatableMixin;
}
diff --git a/pubspec.yaml b/pubspec.yaml
index 8184022..3bd7668 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -9,14 +9,14 @@
funding: [https://github.com/sponsors/felangel]
environment:
- sdk: ">=2.12.0 <4.0.0"
+ sdk: ">=3.0.0 <4.0.0"
dependencies:
- collection: ^1.15.0
- meta: ^1.3.0
+ collection: ^1.0.0
+ meta: ^1.0.0
dev_dependencies:
- test: ^1.16.0
+ test: ^1.0.0
screenshots:
- description: The equatable package logo.
diff --git a/test/equatable_config_test.dart b/test/equatable_config_test.dart
index 08483ba..beb65c9 100644
--- a/test/equatable_config_test.dart
+++ b/test/equatable_config_test.dart
@@ -20,6 +20,7 @@
bool? get stringify => shouldStringify;
}
+// ignore: deprecated_member_use_from_same_package
abstract class EquatableBase with EquatableMixin {}
class CredentialsMixin extends EquatableBase {
diff --git a/test/equatable_mixin_legacy_test.dart b/test/equatable_mixin_legacy_test.dart
new file mode 100644
index 0000000..66f2cd6
--- /dev/null
+++ b/test/equatable_mixin_legacy_test.dart
@@ -0,0 +1,708 @@
+// ignore_for_file: lines_longer_than_80_chars, deprecated_member_use_from_same_package, unrelated_type_equality_checks
+import 'dart:convert';
+
+import 'package:equatable/equatable.dart';
+import 'package:equatable/src/equatable_utils.dart';
+import 'package:test/test.dart';
+
+class NonEquatable {}
+
+abstract class EquatableBase with EquatableMixin {}
+
+class EmptyEquatable extends EquatableBase {
+ @override
+ List<Object> get props => const [];
+}
+
+class SimpleEquatable<T extends Object> extends EquatableBase {
+ SimpleEquatable(this.data);
+
+ final T data;
+
+ @override
+ List<Object> get props => [data];
+}
+
+class MultipartEquatable<T extends Object> extends EquatableBase {
+ MultipartEquatable(this.d1, this.d2);
+
+ final T d1;
+ final T d2;
+
+ @override
+ List<Object> get props => [d1, d2];
+}
+
+class OtherEquatable extends EquatableBase {
+ OtherEquatable(this.data);
+
+ final String data;
+
+ @override
+ List<Object> get props => [data];
+}
+
+enum Color { blonde, black, brown }
+
+class ComplexEquatable extends EquatableBase {
+ ComplexEquatable({this.name, this.age, this.hairColor, this.children});
+
+ final String? name;
+ final int? age;
+ final Color? hairColor;
+ final List<String>? children;
+
+ @override
+ List<Object?> get props => [name, age, hairColor, children];
+}
+
+class EquatableData extends EquatableBase {
+ EquatableData({this.key, this.value});
+
+ final String? key;
+ final dynamic value;
+
+ @override
+ List<Object?> get props => [key, value];
+}
+
+class Credentials extends EquatableBase {
+ Credentials({required this.username, required this.password});
+
+ factory Credentials.fromJson(Map<String, dynamic> json) {
+ return Credentials(
+ username: json['username'] as String,
+ password: json['password'] as String,
+ );
+ }
+
+ final String username;
+ final String password;
+
+ @override
+ List<Object> get props => [username, password];
+
+ Map<String, dynamic> toJson() {
+ return <String, dynamic>{
+ 'username': username,
+ 'password': password,
+ };
+ }
+}
+
+class ComplexStringify extends ComplexEquatable {
+ ComplexStringify({super.name, super.age, super.hairColor});
+
+ @override
+ bool get stringify => true;
+}
+
+class ExplicitStringifyFalse extends ComplexEquatable {
+ ExplicitStringifyFalse({super.name, super.age, super.hairColor});
+
+ @override
+ List<Object?> get props => [name, age, hairColor];
+
+ @override
+ bool get stringify => false;
+}
+
+class IterableWithFlag<T> extends Iterable<T> with EquatableMixin {
+ IterableWithFlag({required this.list, required this.flag});
+
+ final bool flag;
+ final List<T> list;
+
+ @override
+ List<Object> get props => [flag, list];
+
+ @override
+ Iterator<T> get iterator => list.iterator;
+}
+
+void main() {
+ late bool globalStringify;
+
+ setUp(() {
+ globalStringify = EquatableConfig.stringify;
+ });
+
+ tearDown(() {
+ EquatableConfig.stringify = globalStringify;
+ });
+
+ group('Empty Equatable', () {
+ test('should correct toString', () {
+ final instance = EmptyEquatable();
+ expect(instance.toString(), 'EmptyEquatable()');
+ });
+
+ test('should return true when instance is the same', () {
+ final instance = EmptyEquatable();
+ expect(instance == instance, true);
+ });
+
+ test('should return correct hashCode', () {
+ final instance = EmptyEquatable();
+ expect(
+ instance.hashCode,
+ instance.runtimeType.hashCode ^ mapPropsToHashCode(instance.props),
+ );
+ });
+
+ test('should return true when instances are different', () {
+ final instanceA = EmptyEquatable();
+ final instanceB = EmptyEquatable();
+ expect(instanceA == instanceB, true);
+ expect(instanceA.hashCode == instanceB.hashCode, true);
+ });
+
+ test('should return false when compared to non-equatable', () {
+ final instanceA = EmptyEquatable();
+ final instanceB = NonEquatable();
+ expect(instanceA == instanceB, false);
+ });
+ });
+
+ group('Simple Equatable (string)', () {
+ test('should correct toString', () {
+ final instance = SimpleEquatable('simple');
+ expect(instance.toString(), 'SimpleEquatable<String>(simple)');
+ });
+
+ test('should correct toString when EquatableConfig.stringify is false', () {
+ EquatableConfig.stringify = false;
+ final instance = SimpleEquatable('simple');
+ expect(instance.toString(), 'SimpleEquatable<String>');
+ });
+
+ test('should return true when instance is the same', () {
+ final instance = SimpleEquatable('simple');
+ expect(instance == instance, true);
+ });
+
+ test('should return correct hashCode', () {
+ final instance = SimpleEquatable('simple');
+ expect(
+ instance.hashCode,
+ instance.runtimeType.hashCode ^ mapPropsToHashCode(instance.props),
+ );
+ });
+
+ test('should return true when instances are different', () {
+ final instanceA = SimpleEquatable('simple');
+ final instanceB = SimpleEquatable('simple');
+ expect(instanceA == instanceB, true);
+ expect(instanceA.hashCode == instanceB.hashCode, true);
+ });
+
+ test('should return false when compared to non-equatable', () {
+ final instanceA = SimpleEquatable('simple');
+ final instanceB = NonEquatable();
+ expect(instanceA == instanceB, false);
+ });
+
+ test('should return false when compared to different equatable', () {
+ final instanceA = SimpleEquatable('simple');
+ final instanceB = OtherEquatable('simple');
+ expect(instanceA == instanceB, false);
+ });
+
+ test('should return false when values are different', () {
+ final instanceA = SimpleEquatable('simple');
+ final instanceB = SimpleEquatable('Simple');
+ expect(instanceA == instanceB, false);
+ });
+ });
+
+ group('Simple Equatable (number)', () {
+ test('should correct toString', () {
+ final instance = SimpleEquatable(0);
+ expect(instance.toString(), 'SimpleEquatable<int>(0)');
+ });
+
+ test('should return true when instance is the same', () {
+ final instance = SimpleEquatable(0);
+ expect(instance == instance, true);
+ });
+
+ test('should return correct hashCode', () {
+ final instance = SimpleEquatable(0);
+ expect(
+ instance.hashCode,
+ instance.runtimeType.hashCode ^ mapPropsToHashCode(instance.props),
+ );
+ });
+
+ test('should return true when instances are different', () {
+ final instanceA = SimpleEquatable(0);
+ final instanceB = SimpleEquatable(0);
+ expect(instanceA == instanceB, true);
+ expect(instanceA.hashCode == instanceB.hashCode, true);
+ });
+
+ test('should return false when compared to non-equatable', () {
+ final instanceA = SimpleEquatable(0);
+ final instanceB = NonEquatable();
+ expect(instanceA == instanceB, false);
+ });
+
+ test('should return false when values are different', () {
+ final instanceA = SimpleEquatable(0);
+ final instanceB = SimpleEquatable(1);
+ expect(instanceA == instanceB, false);
+ });
+ });
+
+ group('Simple Equatable (bool)', () {
+ test('should correct toString', () {
+ final instance = SimpleEquatable(true);
+ expect(instance.toString(), 'SimpleEquatable<bool>(true)');
+ });
+
+ test('should return true when instance is the same', () {
+ final instance = SimpleEquatable(true);
+ expect(instance == instance, true);
+ });
+
+ test('should return correct hashCode', () {
+ final instance = SimpleEquatable(true);
+ expect(
+ instance.hashCode,
+ instance.runtimeType.hashCode ^ mapPropsToHashCode(instance.props),
+ );
+ });
+
+ test('should return true when instances are different', () {
+ final instanceA = SimpleEquatable(true);
+ final instanceB = SimpleEquatable(true);
+ expect(instanceA == instanceB, true);
+ expect(instanceA.hashCode == instanceB.hashCode, true);
+ });
+
+ test('should return false when compared to non-equatable', () {
+ final instanceA = SimpleEquatable(true);
+ final instanceB = NonEquatable();
+ expect(instanceA == instanceB, false);
+ });
+
+ test('should return false when values are different', () {
+ final instanceA = SimpleEquatable(true);
+ final instanceB = SimpleEquatable(false);
+ expect(instanceA == instanceB, false);
+ });
+ });
+
+ group('Simple Equatable (Equatable)', () {
+ test('should correct toString', () {
+ final instance = SimpleEquatable(
+ EquatableData(
+ key: 'foo',
+ value: 'bar',
+ ),
+ );
+ expect(
+ instance.toString(),
+ 'SimpleEquatable<EquatableData>(EquatableData(foo, bar))',
+ );
+ });
+ test('should return true when instance is the same', () {
+ final instance = SimpleEquatable(
+ EquatableData(
+ key: 'foo',
+ value: 'bar',
+ ),
+ );
+ expect(instance == instance, true);
+ });
+
+ test('should return correct hashCode', () {
+ final instance = SimpleEquatable(
+ EquatableData(
+ key: 'foo',
+ value: 'bar',
+ ),
+ );
+ expect(
+ instance.hashCode,
+ instance.runtimeType.hashCode ^ mapPropsToHashCode(instance.props),
+ );
+ });
+
+ test('should return true when instances are different', () {
+ final instanceA = SimpleEquatable(
+ EquatableData(
+ key: 'foo',
+ value: 'bar',
+ ),
+ );
+ final instanceB = SimpleEquatable(
+ EquatableData(
+ key: 'foo',
+ value: 'bar',
+ ),
+ );
+ expect(instanceA == instanceB, true);
+ expect(instanceA.hashCode == instanceB.hashCode, true);
+ });
+
+ test('should return false when compared to non-equatable', () {
+ final instanceA = SimpleEquatable(
+ EquatableData(
+ key: 'foo',
+ value: 'bar',
+ ),
+ );
+ final instanceB = NonEquatable();
+ expect(instanceA == instanceB, false);
+ });
+
+ test('should return false when values are different', () {
+ final instanceA = SimpleEquatable(
+ EquatableData(
+ key: 'foo',
+ value: 'bar',
+ ),
+ );
+ final instanceB = SimpleEquatable(
+ EquatableData(
+ key: 'foo',
+ value: 'barz',
+ ),
+ );
+ expect(instanceA == instanceB, false);
+ });
+ });
+
+ group('Multipart Equatable', () {
+ test('should correct toString', () {
+ final instance = MultipartEquatable('s1', 's2');
+ expect(instance.toString(), 'MultipartEquatable<String>(s1, s2)');
+ });
+
+ test('should return true when instance is the same', () {
+ final instance = MultipartEquatable('s1', 's2');
+ expect(instance == instance, true);
+ });
+
+ test('should return correct hashCode', () {
+ final instance = MultipartEquatable('s1', 's2');
+ expect(
+ instance.hashCode,
+ instance.runtimeType.hashCode ^ mapPropsToHashCode(instance.props),
+ );
+ });
+
+ test('should return different hashCodes when property order has changed',
+ () {
+ final instance1 = MultipartEquatable('s1', 's2');
+ final instance2 = MultipartEquatable('s2', 's1');
+ expect(instance1.hashCode == instance2.hashCode, isFalse);
+ });
+
+ test('should return true when instances are different', () {
+ final instanceA = MultipartEquatable('s1', 's2');
+ final instanceB = MultipartEquatable('s1', 's2');
+ expect(instanceA == instanceB, true);
+ expect(instanceA.hashCode == instanceB.hashCode, true);
+ });
+
+ test('should return false when compared to non-equatable', () {
+ final instanceA = MultipartEquatable('s1', 's2');
+ final instanceB = NonEquatable();
+ expect(instanceA == instanceB, false);
+ });
+
+ test('should return false when values are different', () {
+ final instanceA = MultipartEquatable('s1', 's2');
+ final instanceB = MultipartEquatable('s2', 's1');
+ expect(instanceA == instanceB, false);
+
+ final instanceC = MultipartEquatable('s1', 's1');
+ final instanceD = MultipartEquatable('s2', 's1');
+ expect(instanceC == instanceD, false);
+ });
+ });
+
+ group('Complex Equatable', () {
+ test('should correct toString', () {
+ final instance = ComplexEquatable(
+ name: 'Joe',
+ age: 40,
+ hairColor: Color.black,
+ children: ['Bob'],
+ );
+ expect(
+ instance.toString(),
+ 'ComplexEquatable(Joe, 40, Color.black, [Bob])',
+ );
+ });
+ test('should return true when instance is the same', () {
+ final instance = ComplexEquatable(
+ name: 'Joe',
+ age: 40,
+ hairColor: Color.black,
+ children: ['Bob'],
+ );
+ expect(instance == instance, true);
+ });
+
+ test('should return correct hashCode', () {
+ final instance = ComplexEquatable(
+ name: 'Joe',
+ age: 40,
+ hairColor: Color.black,
+ children: ['Bob'],
+ );
+ expect(
+ instance.hashCode,
+ instance.runtimeType.hashCode ^ mapPropsToHashCode(instance.props),
+ );
+ });
+
+ test('should return true when instances are different', () {
+ final instanceA = ComplexEquatable(
+ name: 'Joe',
+ age: 40,
+ hairColor: Color.black,
+ children: ['Bob'],
+ );
+ final instanceB = ComplexEquatable(
+ name: 'Joe',
+ age: 40,
+ hairColor: Color.black,
+ children: ['Bob'],
+ );
+ expect(instanceA == instanceB, true);
+ expect(instanceA.hashCode == instanceB.hashCode, true);
+ });
+
+ test('should return false when compared to non-equatable', () {
+ final instanceA = ComplexEquatable(
+ name: 'Joe',
+ age: 40,
+ hairColor: Color.black,
+ children: ['Bob'],
+ );
+ final instanceB = NonEquatable();
+ expect(instanceA == instanceB, false);
+ });
+
+ test('should return false when values are different', () {
+ final instanceA = ComplexEquatable(
+ name: 'Joe',
+ age: 40,
+ hairColor: Color.black,
+ children: ['Bob'],
+ );
+ final instanceB = ComplexEquatable(
+ name: 'John',
+ age: 40,
+ hairColor: Color.brown,
+ children: ['Bobby'],
+ );
+ expect(instanceA == instanceB, false);
+ });
+
+ test('should return different hashCode even for empty list', () {
+ final instance = ComplexEquatable(
+ name: 'Joe',
+ age: 40,
+ hairColor: Color.black,
+ children: [],
+ );
+ final instance2 = ComplexEquatable(
+ name: 'John',
+ age: 40,
+ hairColor: Color.black,
+ children: [],
+ );
+ expect(instance.hashCode != instance2.hashCode, true);
+ });
+ });
+
+ group('Json Equatable', () {
+ test('should correct toString', () {
+ final instance = Credentials.fromJson(
+ json.decode(
+ '''
+ {
+ "username":"Admin",
+ "password":"admin"
+ }
+ ''',
+ ) as Map<String, dynamic>,
+ );
+ expect(instance.toString(), 'Credentials(Admin, admin)');
+ });
+
+ test('should return true when instance is the same', () {
+ final instance = Credentials.fromJson(
+ json.decode(
+ '''
+ {
+ "username":"Admin",
+ "password":"admin"
+ }
+ ''',
+ ) as Map<String, dynamic>,
+ );
+ expect(instance == instance, true);
+ });
+
+ test('should return correct hashCode', () {
+ final instance = Credentials.fromJson(
+ json.decode(
+ '''
+ {
+ "username":"Admin",
+ "password":"admin"
+ }
+ ''',
+ ) as Map<String, dynamic>,
+ );
+ expect(
+ instance.hashCode,
+ instance.runtimeType.hashCode ^ mapPropsToHashCode(instance.props),
+ );
+ });
+
+ test('should return true when instances are different', () {
+ final instanceA = Credentials.fromJson(
+ json.decode(
+ '''
+ {
+ "username":"Admin",
+ "password":"admin"
+ }
+ ''',
+ ) as Map<String, dynamic>,
+ );
+ final instanceB = Credentials.fromJson(
+ json.decode(
+ '''
+ {
+ "username":"Admin",
+ "password":"admin"
+ }
+ ''',
+ ) as Map<String, dynamic>,
+ );
+ expect(instanceA == instanceB, true);
+ expect(instanceA.hashCode == instanceB.hashCode, true);
+ });
+
+ test('should return false when compared to non-equatable', () {
+ final instanceA = Credentials.fromJson(
+ json.decode(
+ '''
+ {
+ "username":"Admin",
+ "password":"admin"
+ }
+ ''',
+ ) as Map<String, dynamic>,
+ );
+ final instanceB = NonEquatable();
+ expect(instanceA == instanceB, false);
+ });
+
+ test('should return false when values are different', () {
+ final instanceA = Credentials.fromJson(
+ json.decode(
+ '''
+ {
+ "username":"Admin",
+ "password":"admin"
+ }
+ ''',
+ ) as Map<String, dynamic>,
+ );
+ final instanceB = Credentials.fromJson(
+ json.decode(
+ '''
+ {
+ "username":"Admin",
+ "password":"password"
+ }
+ ''',
+ ) as Map<String, dynamic>,
+ );
+ expect(instanceA == instanceB, false);
+ });
+ });
+
+ group('To String Equatable', () {
+ test('Complex stringify', () {
+ final instanceA = ComplexStringify();
+ final instanceB = ComplexStringify(name: 'Bob', hairColor: Color.black);
+ final instanceC =
+ ComplexStringify(name: 'Joe', age: 50, hairColor: Color.blonde);
+ expect(
+ instanceA.toString(),
+ 'ComplexStringify(null, null, null, null)',
+ );
+ expect(
+ instanceB.toString(),
+ 'ComplexStringify(Bob, null, Color.black, null)',
+ );
+ expect(
+ instanceC.toString(),
+ 'ComplexStringify(Joe, 50, Color.blonde, null)',
+ );
+ });
+
+ test('with ExplicitStringifyFalse stringify', () {
+ final instanceA = ExplicitStringifyFalse();
+ final instanceB =
+ ExplicitStringifyFalse(name: 'Bob', hairColor: Color.black);
+ final instanceC =
+ ExplicitStringifyFalse(name: 'Joe', age: 50, hairColor: Color.blonde);
+ expect(instanceA.toString(), 'ExplicitStringifyFalse');
+ expect(instanceB.toString(), 'ExplicitStringifyFalse');
+ expect(instanceC.toString(), 'ExplicitStringifyFalse');
+ });
+ });
+
+ group('Iterable Equatable', () {
+ test('should be equal when different instances have same values', () {
+ final instanceA = IterableWithFlag(flag: true, list: [1, 2]);
+ final instanceB = IterableWithFlag(flag: true, list: [1, 2]);
+
+ expect(instanceA == instanceB, isTrue);
+ });
+
+ test('should not be equal when different instances have different values',
+ () {
+ final instanceA = IterableWithFlag(flag: false, list: [1, 2]);
+ final instanceB = IterableWithFlag(flag: true, list: [1, 2]);
+
+ expect(instanceA == instanceB, isFalse);
+ });
+
+ test('wrapper should be equal when different instances have same values',
+ () {
+ final instanceA = SimpleEquatable(
+ IterableWithFlag(flag: true, list: [1, 2]),
+ );
+ final instanceB = SimpleEquatable(
+ IterableWithFlag(flag: true, list: [1, 2]),
+ );
+
+ expect(instanceA == instanceB, isTrue);
+ });
+
+ test(
+ 'wrapper should not be equal '
+ 'when different instances have different values', () {
+ final instanceA = SimpleEquatable(
+ IterableWithFlag(flag: true, list: [1, 2]),
+ );
+ final instanceB = SimpleEquatable(
+ IterableWithFlag(flag: false, list: [1, 2]),
+ );
+
+ expect(instanceA == instanceB, isFalse);
+ });
+ });
+}
diff --git a/test/equatable_mixin_test.dart b/test/equatable_mixin_test.dart
index 41b5bad..0724e5a 100644
--- a/test/equatable_mixin_test.dart
+++ b/test/equatable_mixin_test.dart
@@ -7,7 +7,7 @@
class NonEquatable {}
-abstract class EquatableBase with EquatableMixin {}
+abstract class EquatableBase with Equatable {}
class EmptyEquatable extends EquatableBase {
@override
@@ -91,16 +91,14 @@
}
class ComplexStringify extends ComplexEquatable {
- ComplexStringify({String? name, int? age, Color? hairColor})
- : super(name: name, age: age, hairColor: hairColor);
+ ComplexStringify({super.name, super.age, super.hairColor});
@override
bool get stringify => true;
}
class ExplicitStringifyFalse extends ComplexEquatable {
- ExplicitStringifyFalse({String? name, int? age, Color? hairColor})
- : super(name: name, age: age, hairColor: hairColor);
+ ExplicitStringifyFalse({super.name, super.age, super.hairColor});
@override
List<Object?> get props => [name, age, hairColor];
@@ -109,7 +107,7 @@
bool get stringify => false;
}
-class IterableWithFlag<T> extends Iterable<T> with EquatableMixin {
+class IterableWithFlag<T> extends Iterable<T> with Equatable {
IterableWithFlag({required this.list, required this.flag});
final bool flag;
diff --git a/test/equatable_test.dart b/test/equatable_test.dart
index 9814049..9aac54d 100644
--- a/test/equatable_test.dart
+++ b/test/equatable_test.dart
@@ -1063,13 +1063,3 @@
});
});
}
-
-// test that subclasses of `Equatable` can have const constructors
-class ConstTest extends Equatable {
- const ConstTest(this.a);
-
- final int a;
-
- @override
- List<Object> get props => [a];
-}
diff --git a/test/equatable_utils_test.dart b/test/equatable_utils_test.dart
index 9b779d6..e0385a4 100644
--- a/test/equatable_utils_test.dart
+++ b/test/equatable_utils_test.dart
@@ -2,6 +2,7 @@
import 'package:equatable/src/equatable_utils.dart';
import 'package:test/test.dart' hide equals;
+// ignore: deprecated_member_use_from_same_package
class Person with EquatableMixin {
Person({required this.name});