Skip to content

Commit

Permalink
Implement toString feature (#45)
Browse files Browse the repository at this point in the history
  • Loading branch information
Noé Fernández committed Feb 9, 2020
1 parent 1922ed8 commit 64b8545
Show file tree
Hide file tree
Showing 7 changed files with 170 additions and 6 deletions.
33 changes: 33 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,39 @@ class Person extends Equatable {
}
```

### `toString` Implementation
Equatable can implement `toString` method including all the given props. If you want that behaviour, just include the following:
```
@override
bool get stringify => true;
```

For instance:

```dart
import 'package:equatable/equatable.dart';
class Person extends Equatable {
final String name;
const Person(this.name);
@override
List<Object> get props => [name];
@override
bool get stringify => true;
}
```
For the name `Bob`, the outuput will be:
```
Person(Bob)
```
This flag by default is false and `toString` will return just the type:
```
Person
```

## Recap

### Without Equatable
Expand Down
8 changes: 8 additions & 0 deletions example/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ class Credentials extends Equatable {

@override
List<Object> get props => [username, password];

@override
bool get stringify => false;
}

class EquatableDateTime extends DateTime with EquatableMixin {
Expand All @@ -26,6 +29,9 @@ class EquatableDateTime extends DateTime with EquatableMixin {
List get props {
return [year, month, day, hour, minute, second, millisecond, microsecond];
}

@override
bool get stringify => true;
}

void main() {
Expand All @@ -39,6 +45,7 @@ void main() {
print(credentialsC == credentialsC); // true
print(credentialsA == credentialsB); // false
print(credentialsB == credentialsC); // true
print(credentialsA.toString()); // Credentials

// Equatable Mixin
final dateTimeA = EquatableDateTime(2019);
Expand All @@ -50,4 +57,5 @@ void main() {
print(dateTimeC == dateTimeC); // true
print(dateTimeA == dateTimeB); // false
print(dateTimeB == dateTimeC); // true
print(dateTimeA.toString()); // EquatableDateTime(2019, 1, 1, 0, 0, 0, 0, 0)
}
8 changes: 7 additions & 1 deletion lib/src/equatable.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'package:meta/meta.dart';

import './equatable_utils.dart';

/// A base class to facilitate [operator==] and [hashCode] overrides.
Expand All @@ -19,6 +20,10 @@ abstract class Equatable {
/// two [Equatables] are equal.
List<Object> get props;

/// If the value is [true], the `toString` method will be overrided to print
/// the equatable `props`.
bool get stringify => false;

/// A class that helps implement equality
/// without needing to explicitly override == and [hashCode].
/// Equatables override their own `==` operator and [hashCode] based on their `props`.
Expand All @@ -35,5 +40,6 @@ abstract class Equatable {
int get hashCode => runtimeType.hashCode ^ mapPropsToHashCode(props);

@override
String toString() => '$runtimeType';
String toString() =>
stringify ? mapPropsToString(runtimeType, props) : '$runtimeType';
}
7 changes: 6 additions & 1 deletion lib/src/equatable_mixin.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ mixin EquatableMixin {
/// two [Equatables] are equal.
List<Object> get props;

/// If the value is [true], the `toString` method will be overrided to print
/// the equatable `props`.
bool get stringify => false;

@override
bool operator ==(Object other) {
return identical(this, other) ||
Expand All @@ -21,5 +25,6 @@ mixin EquatableMixin {
int get hashCode => runtimeType.hashCode ^ mapPropsToHashCode(props);

@override
String toString() => '$runtimeType';
String toString() =>
stringify ? mapPropsToString(runtimeType, props) : '$runtimeType';
}
5 changes: 4 additions & 1 deletion lib/src/equatable_utils.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import 'package:collection/collection.dart';

int mapPropsToHashCode(Iterable props) =>
_finish(props.fold(0, (hash, object) => _combine(hash, object)));
_finish(props?.fold(0, (hash, object) => _combine(hash, object)) ?? 0);

const DeepCollectionEquality _equality = DeepCollectionEquality();

Expand Down Expand Up @@ -47,3 +47,6 @@ int _finish(int hash) {
hash = hash ^ (hash >> 11);
return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
}

String mapPropsToString(Type runtimeType, List<Object> props) =>
'$runtimeType${props?.map((prop) => prop?.toString() ?? '') ?? '()'}';
57 changes: 56 additions & 1 deletion test/equatable_mixin_test.dart
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import 'dart:convert';

import 'package:equatable/equatable.dart';
import 'package:equatable/src/equatable_utils.dart';
import 'package:test/test.dart';
import 'package:equatable/equatable.dart';

class NonEquatable {}

Expand Down Expand Up @@ -89,6 +89,30 @@ class Credentials extends EquatableBase {
}
}

class ComplexStringify extends ComplexEquatable {
final String name;
final int age;
final Color hairColor;

ComplexStringify({this.name, this.age, this.hairColor});

@override
List get props => [name, age, hairColor];

@override
bool get stringify => true;
}

class NullProps extends Equatable {
NullProps();

@override
List get props => null;

@override
bool get stringify => true;
}

void main() {
group('Empty Equatable', () {
test('should correct toString', () {
Expand Down Expand Up @@ -546,4 +570,35 @@ void main() {
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(, , )');
expect(instanceB.toString(), 'ComplexStringify(Bob, , Color.black)');
expect(instanceC.toString(), 'ComplexStringify(Joe, 50, Color.blonde)');
});
});

group('Null props Equatable', () {
test('should not crash invoking equals method', () {
final instanceA = NullProps();
final instanceB = NullProps();
expect(instanceA == instanceB, true);
});

test('should not crash invoking hascode method', () {
final instanceA = NullProps();
final instanceB = NullProps();
expect(instanceA.hashCode == instanceB.hashCode, true);
});

test('should not crash invoking toString method', () {
final instance = NullProps();
expect(instance.toString(), 'NullProps()');
});
});
}
58 changes: 56 additions & 2 deletions test/equatable_test.dart
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import 'dart:convert';

import 'package:equatable/equatable.dart';
import 'package:equatable/src/equatable_utils.dart';
import 'package:test/test.dart';

import 'package:equatable/equatable.dart';

import 'custom_list.dart';

class NonEquatable {}
Expand Down Expand Up @@ -92,6 +91,30 @@ class Credentials extends Equatable {
List<Object> get props => [username, password];
}

class ComplexStringify extends Equatable {
final String name;
final int age;
final Color hairColor;

ComplexStringify({this.name, this.age, this.hairColor});

@override
List get props => [name, age, hairColor];

@override
bool get stringify => true;
}

class NullProps extends Equatable {
NullProps();

@override
List get props => null;

@override
bool get stringify => true;
}

void main() {
group('Empty Equatable', () {
test('should correct toString', () {
Expand Down Expand Up @@ -721,6 +744,37 @@ void main() {
});
});
});

group('To String Equatable', () {
test('with 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(, , )');
expect(instanceB.toString(), 'ComplexStringify(Bob, , Color.black)');
expect(instanceC.toString(), 'ComplexStringify(Joe, 50, Color.blonde)');
});
});

group('Null props Equatable', () {
test('should not crash invoking equals method', () {
final instanceA = NullProps();
final instanceB = NullProps();
expect(instanceA == instanceB, true);
});

test('should not crash invoking hascode method', () {
final instanceA = NullProps();
final instanceB = NullProps();
expect(instanceA.hashCode == instanceB.hashCode, true);
});

test('should not crash invoking toString method', () {
final instance = NullProps();
expect(instance.toString(), 'NullProps()');
});
});
}

// test that subclasses of `Equatable` can have const constructors
Expand Down

0 comments on commit 64b8545

Please sign in to comment.