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});